<h1 id="Hack-The-Box">Hack The Box - Reversing (Medium Part 1)</h1> ![image](https://hackmd.io/_uploads/SyreLqbeR.png) Ghi chú: <ul> <li>Medium: 1-6 challenge</li> <ul> <li><a href="#Headache">1. Headache</a></li> <li><a href="#The Vault">2. The Vault</a></li> <li><a href="#Malception">3. Malception</a></li> <li><a href="#Teleport">4. Teleport</a></li> <li><a href="#Indefinite">5. Indefinite</a></li> <li><a href="#Pseudo">6. Pseudo</a></li> </ul> </li> </ul> <h2>Difficulty: Medium </h2> <div id="Headache"></div> <h3>Challenge 1: Headache</h3> Make the flag. <a href="https://drive.google.com/file/d/1OZ-WQmth2QMBtaR9wp0Y2Mxe6XCddf0Q/view?usp=sharing">Download Challenge Here</a> <h3>Solution</h3> Chúng ta có thể sử dụng lệnh 'file' để cung cấp cái nhìn tổng quan về loại tệp. ![image](https://hackmd.io/_uploads/HkRGTfxYT.png) ><b>Một chút thông tin về ELF JMPREL Relocation Table</b> Bảng định vị JMPREL là một phần của bảng định vị, nó nằm trong phần .rel.plt của tập tin ELF. Bảng định vị này chứa thông tin về các mục tiêu của các lệnh JMPREL trong mã thực thi. Khi chương trình được nạp vào bộ nhớ và thực thi, các mục tiêu trong bảng định vị JMPREL sẽ được sửa đổi để tham chiếu đến địa chỉ thực tế của các hàm và thư viện trong bộ nhớ. ![image](https://hackmd.io/_uploads/Sky2VEeFT.png) Thông qua `ELF JMPREL Relocation Table`, ta có thể đổi tên tất cả các hàm với địa chỉ offset và tên hàm được cung cấp. ![image](https://hackmd.io/_uploads/SkCiExrsa.png) Mở file trong IDA64, ta biết được chương trình sẽ thực hiện lần lượt hai hàm là `sub_1275()` và `sub_1270()` ![image](https://hackmd.io/_uploads/ryJdYwdo6.png) ![image](https://hackmd.io/_uploads/Hkw_YlBjT.png) Ta sẽ phân tích nội dung của hàm `sub_1275()`. Ta cần tìm hiểu nội dung của các hàm: `sub_17E2()`, `sub_1ED2()`, `sub_13C1()`, `sub_1E33()`. ![image](https://hackmd.io/_uploads/HJ2VcgSsT.png) Tại hàm `sub_17E2()`, ta biết rằng các tham số được chương trình tự đưa vào và xử lý => giá trị này không thay đổi => ta không cần tìm hiểu nội dung của nó. Tại hàm `sub_13C1()`, ta biết nó thực hiện tính toán để kiểm tra. Ta tạm thời đổi tên là `checker()`. ![image](https://hackmd.io/_uploads/H19gRpzFa.png) Tại hàm `sub_1ED2()`, hàm thực hiện `sao chép chuỗi được truyền vào tham số` vào `con trỏ của một biến toàn cục`. Biến toàn cục này khá quan trọng. Hãy lưu ý đến nó!! ![image](https://hackmd.io/_uploads/rJ9kTl7Yp.png) `Hàm mprotect` được gọi để thay đổi quyền truy cập vào bộ nhớ. `Tham số đầu tiên` là địa chỉ bắt đầu của phạm vi cần thay đổi quyền truy cập. `Tham số thứ hai` là kích thước của vùng nhớ. `Tham số thứ ba` là các quyền truy cập mới được thiết lập cho vùng nhớ đó. Nếu `mprotect()` không thành công thì sẽ thông báo lỗi. :::danger `sysconf(0x1E)`: `_SC_PAGESIZE` được dùng để lấy thông tin kích thước trang trong bộ nhớ ảo của hệ thống, kết quả được trả về dạng byte. Mục đích để căn chỉnh địa chỉ hàm với `page boundary`. ::: ![image](https://hackmd.io/_uploads/Syof1-QYa.png) Tại hàm `sub_1D07()`, chương trình sẽ thực hiện tự chỉnh sửa nội dung các byte của nó `từ 0x1FAF đến 0x2684 - tổng 0x6D5 byte` với `key = a15abe90c112d09369d9f9da9a8c046e`. Nó là biến toàn cục đã được `hàm sub_1ED2()` sao chép chuỗi :::info ``` import ida_bytes start = 0x1FAF step = 0x6D5 key = "a15abe90c112d09369d9f9da9a8c046e" for i in range(0x6D5): tmp = ida_bytes.get_byte(start + i) ida_bytes.patch_byte(start + i,tmp^ord(key[i%len(key)])) ``` ::: Sau khi sửa đổi tệp, chúng tôi nhận được sub_1FAF(), dẫn đến sub_13C1(). Chúng ta có thể tìm thấy lá cờ giả ở đây. Từ nội dung giả mã, ta tìm được các `fake flag` sau. :::info ``` data = [0x2A, 0x31, 0x31, 0xF, 0x1C, 0x55, 0xE, 0x3A, 0x2, 0xd, 0x46, 0x12, 0x1C, 0x2D, 0x11, 0x55, 0x51, 0x5C, 0x14, 0x19] key = [0x62, 0x65, 0x73, 0x74, 0x6b, 0x65, 0x79, 0x65, 0x76, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x72, 0x65, 0x61, 0x6c, 0x78, 0x64] flag = ''.join(chr(d ^ k) for d, k in zip(data, key)) print(flag) ``` ::: :::success ``` HTB{w0w_th4ts_c000l} ``` ::: :::info ``` import base64 encoded_string = "SFRCe3RoMXNfMXNfdGgzX2ZsNGd9" flag = base64.b64decode(encoded_string).decode('utf-8') print(flag) ``` ::: :::success ``` HTB{th1s_1s_th3_fl4g} ``` ::: :::info ``` data = [0x29, 0x35, 0x23, 0x1a, 0x15, 0x09, 0x55, 0x15, 0x3e, 0x16, 0x55, 0x12, 0x3e, 0x09, 0x55, 0x13, 0x05, 0x1c] key = 0x61 flag = ''.join(chr(byte ^ key) for byte in data) print(flag) ``` ::: :::success ``` HTB{th4t_w4s_h4rd} ``` ::: Chức năng này quá phức tạp để tự mình phục hồi, chúng tôi sẽ sử dụng `Angr` để tự động giải quyết thử thách. ![image](https://hackmd.io/_uploads/ByZcVI7YT.png) :::info ``` import angr import claripy from Crypto.Util.number import long_to_bytes entry_address = 0x1faf flag_length = 0x14 flag = claripy.BVS('flag', flag_length * 8) proj = angr.Project("./headache", load_options={'main_opts': {'base_addr': 0x0}}) state = proj.factory.call_state(entry_address, stdin=flag) known_prefix = b"HTB{" for i, c in enumerate(flag.chop(8)): if i < len(known_prefix): state.solver.add(c == known_prefix[i]) else: state.solver.add(state.solver.And(c >= 0x20, c <= 0x7e)) simgr = proj.factory.simgr(state) simgr.use_technique(angr.exploration_techniques.DFS()) simgr.explore(find=0x2675, avoid=0x20f2) if simgr.found: state = simgr.found[0] flag_value = state.solver.eval(flag) print(long_to_bytes(flag_value).decode()) else: print("Unable to solve") ``` ::: :::success ``` Flag: HTB{l4yl3_w4s_h3r3!} ``` ::: <div id="The Vault"></div> <h3>Challenge 2: The Vault</h3> After following a series of tips, you have arrived at your destination; a giant vault door. Water drips and steam hisses from the locking mechanism, as you examine the small display - 'PLEASE SUPPLY PASSWORD'. Below, a typewriter for you to input. You must study the mechanism hard - you might only have one shot... <a href="https://drive.google.com/file/d/1PaB94dtfvm5M9PiXjnvgSWk-Cvl4-ouW/view?usp=sharing">Download Challenge Here</a> <h3>Solution</h3> Chúng ta có thể sử dụng lệnh 'file' để cung cấp cái nhìn tổng quan về loại tệp. ![image](https://hackmd.io/_uploads/B12eDomKp.png) Mở file trong I DA64 ![image](https://hackmd.io/_uploads/SJb4T3mtT.png) Đây là cách nó hoạt động: - Hàm bắt đầu bằng cách tạo một mảng 66 byte để chứa thông tin xác thực. - Nếu tệp mở thành công, hàm sẽ bắt đầu vòng lặp để kiểm tra từng ký tự trong mảng. - Bên trong vòng lặp, nó kiểm tra xem ký tự hiện tại có hợp lệ với định dạng thông tin xác thực hay không . - Nếu tất cả các ký tự vượt qua quá trình xác thực, hàm sẽ kết thúc vòng lặp thành công và in ra một thông báo xác Nó sử dụng `byte_xxxx` mảng làm bảng tra cứu để truy cập gián tiếp vào các vị trí bộ nhớ cụ thể được lưu trữ trong `off_xxxxx` -> `off_xxxxx` -> `byte_xxxx` và nó trả về một ký tự trong cờ. :::info ``` import ida_bytes BASE_ADDRESS = 0x17880 index = [0xE0, 0xD1, 0xBB, 0x27, 0xF6, 0x72, 0xDB, 0xA3, 0x83, 0xB9, 0x69, 0x23, 0xDB, 0x63, 0xB9, 0x23, 0x05, 0x2B, 0x2B, 0x83, 0x23, 0x39, 0x45, 0x39, 0x92] flag = "" for i in range(len(index)): flag += chr(ida_bytes.get_byte(ida_bytes.get_dword(ida_bytes.get_dword(ida_bytes.get_dword(BASE_ADDRESS + index[i] * 8)))+9)) print(flag) ``` ::: :::success ``` HTB{vt4bl3s_4r3_c00l_huh} ``` ::: <div id="Malception"></div> <h3>Challenge 3: Malception</h3> Attackers have infiltrated our domain and poisoned DNS records to infect users with a ransomware. We weren't able to retrieve any artifacts, but we have a packet capture you could use. <a href="https://drive.google.com/file/d/13FYqXBRMBmaO_0ZsNRh8ch32r5Z11pHL/view?usp=sharing">Download Challenge Here</a> <h3>Solution</h3> Thử thách là mang lại cho chúng ta một pcapng fileđiều mà chúng ta có thể làm được export object from HTTP. Chúng tôi có thể nhận được `xQWdrq.exe` - PE64 có thể thực thi được. Chúng ta có thể sử dụng lệnh 'file' để cung cấp cái nhìn tổng quan về loại tệp. ![image](https://hackmd.io/_uploads/B1an-TQYa.png) Mở tệp trong IDA, ta quan sát nội dung của hàm `main()`. >Trong lập trình C , các hàm `getaddrinfo()` và `getnameinfo()` chuyển đổi `domain names`, `hostnames`, and `IP addresses` từ các văn bản mà con người có thể đọc được thành các định dạng nhị phân có cấu trúc cho `OS's networking API`. <a href="https://en.wikipedia.org/wiki/Getaddrinfo">Đọc thêm tại đây.</a> ![image](https://hackmd.io/_uploads/BJ6MqEM9p.png) Chương trình cố gắng lấy thông tin địa chỉ cho máy chủ ("utube.online") và cổng ("31337"). Liên tục thiết lập kết nối mạng bằng `socket` và thử kết nối tới máy chủ từ xa bằng `connect`. ![image](https://hackmd.io/_uploads/Syc4aEGcT.png) Nếu kết nối thành công thì gửi chuỗi `z11gj1\n` - 7 kí tự đến `socket` đã thiết lập. Tạo mảng `buf` để lưu 1028 bytes nhận từ `socket`. Lấy tên miền DNS của máy tính cục bộ và lưu nó vào `Buffer`. Thực hiện `Buffer[i] ^ buf[i%len()]` rồi gửi chuỗi `533_11s4\n`. ![image](https://hackmd.io/_uploads/rJ0FWrzc6.png) Không có cách nào để thực hành kết nối đến `utube.online`, may mắn thay: ta đã có `.pcap` để biết tất cả hoạt động mạng. Ta biết nó đã thiết lập kết nối với cổng 31337, ta lọc với nội dung `tcp.dstport == 31337 or tcp.srcport == 31337 ` và `follow TCP stream`. ![image](https://hackmd.io/_uploads/rJ0mBBf56.png) Sau khi gửi `z11gj1\n`, ta nhận được 8 bytes - đây là `buf` để `xor` với `ComputerNameDnsDomain`, tiếp tục gửi `533_11s4\n` và ta nhận được phần còn lại - được lưu vào `v11`. Dữ liệu này và `Buffer` được truyền tham số vào hàm `sub_140001010()` - đây là mã hóa `RC4` với `Buffer_xored` là `key`. Ta cần tìm giá trị của `ComputerNameDnsDomain`. . Mở tệp `pcap` trong `NetworkMiner`, chúng ta có các giao thức: `DNS, HTTP, HTTPS, SMB,` đặc biệt là `Kerberos`. ![image](https://hackmd.io/_uploads/S1MKocz9a.png) ![image](https://hackmd.io/_uploads/B1H_rcm5p.png) Ta biết được máy `192.168.0.105` tải tệp `.exe` từ `192.168.0.104` - `host utube.online`. Ta biết `Domain name`: `MEGACORP.LOCAL` và `Username:` `rick.a` Ta thực hiện giải mã: tìm giá trị `key` của mã hóa RC4. Giá trị này được tính bởi: `Key = ComputerNameDnsDomain ^ recv_1`. Ta tính được `key` như sau: ![image](https://hackmd.io/_uploads/rJ9Xp5z96.png) Tiếp tực giải mã của mã hóa RC4 với `key = dc 46 73 b0 5e 39 16 b3` và `data = recv_2`. Ta chọn `show data as Raw` rồi tiến hành giải mã. ![image](https://hackmd.io/_uploads/H1AUysG9T.png) Ta tải giá trị vừa giải mã thành file, dùng DIE để kiểm tra tệp đó. Ta sẽ biết được đây là một tệp `DLL` viết bằng `.NET`. ![image](https://hackmd.io/_uploads/SyzBRIX5p.png) `ILSpy engine` đưa ra ngoại lệ được `dnSpy` phát hiện và xuất ra các `khối comment` khi `decompiler`. Ta tạm thời dừng ở đây và tiếp tục phân tích trong IDA. ![image](https://hackmd.io/_uploads/r1sKVFX5p.png) Lấy `handle` của `module` hiện tại (tập tin thực thi). Tìm `resource` của một `module` được chỉ định - ở đây là `resource` số 101 và `load` `resource` vào bộ nhớ. Nó sẽ được giải mã bằng `key` như ta đã dùng. Giá trị sau khi được giải mã sẽ được ghi lên vùng nhớ của tệp `DLL` từ byte `0xDB0`. :::info ``` from arc4 import ARC4 key = b"\xdc\x46\x73\xb0\x5e\x39\x16\xb3" with open("encrypted.bin", "rb") as file1, open("resource_101", "rb") as file2: enc_dat = file1.read() enc_dat2 = file2.read() dec_dat = bytearray(ARC4(key).decrypt(enc_dat)) dec_dat[0xdb0 : (0xdb0 + len(enc_dat2))] = ARC4(key).decrypt(enc_dat2) with open("decrypted.bin", "wb") as output_file: output_file.write(dec_dat) ``` ::: Ta mở tệp `Payload.dll` - tập tin thực thi .NET trong `dnSpy`, chương trình dùng `namespace CorpSpace` với ba lớp: CorpClass, Graphy, Stego. * Class Stego: cung cấp các phương thức để tạo cặp khóa RSA và mã hóa dữ liệu bằng khóa công khai RSA, sử dụng `RSACryptoServiceProvider` để thực hiện các thao tác liên quan đến RSA. * `CreateKeys` Method: Tạo cặp khóa công khai và khóa riêng tư sử dụng thuật toán RSA. * `Encrypt` Method: Mã hóa một mảng byte sử dụng khóa công khai RSA. * Class Graphy: cung cấp các phương thức để mã hóa dữ liệu sử dụng thuật toán Rijndael (AES) và cung cấp một số thông số cấu hình. * `Encrypt` Method: Mã hóa một mảng byte sử dụng thuật toán Rijndael (AES). Hỗ trợ nhiều loại thuật toán thông qua sử dụng generics. * Class CorpSpace: * `EntryPoint` Method: Kiểm tra xem ứng dụng có đang được debug không, và sau đó thực hiện việc mã hóa các tệp tin trong thư mục `Users\\username\\Documents`. * `EncryptFiles` Method: Mã hóa tất cả các tệp tin trong một thư mục và các thư mục con. * `EncryptFile` Method: Mã hóa một tệp tin và gửi nó đến một máy chủ qua mạng. ![image](https://hackmd.io/_uploads/HkCUptQ96.png) Hoạt động chính của chương trình như sau: > 1. Tạo cặp khóa `privateKey`, `publicKey`. `privateKey` được lưu vào `text` > 2. Đọc dữ liệu từ file. - `(bytes)`. > 3. Tạo GUID mới và tạo hash MD5. - `(array = md5hash = md5(guid()))`. > 4. Mã hóa dữ liệu (bytes) bằng thuật toán AES với `base64(md5hash)` - `array2`. > 5. Mã hóa `md5hash` bằng thuật toán RSA với `publicKey` - `array3`. > 6. Chuyển tham số `key` thành mảng byte - `array5`. > 7. XORing `privateKey` và `array5` - `array4`. > 8. Thực hiện để kết nối đến máy chủ có địa chỉ IP hoặc tên máy chủ là "utube.online" và cổng là 31338, qua giao thức TCP/IP và gửi dữ liệu đến máy chủ. > 9. Gửi `str(len(array3)+len(array4))` và `array6` - gộp từ `array3 và array4`. > 10. XORing `len(filename.enc)` và `array5` - `array7`. > 11. Gửi `str(len(array7))`, `array7`, `str(len(array2))`, `array2`. > 12. Nhận dữ liệu đến khi có `end`. > 13. Đóng kết nối , xóa file. Ta biết nó đã thiết lập kết nối với cổng 31338, ta lọc với nội dung `tcp.dstport == 31338` và `follow TCP stream`. Ta sẽ có được nội dung của: `array6`, `array7`, `array2`. Từ `array6` ta biết `array3`, `array4`. Ta lại quan sát chương trình trong IDA, nó gọi hàm `CLRCreateInstance` để tạo một trường đối tượng của `CorpClass`. Tham số `key`đã sử dụng ở RC4 tiếp tục được dùng để mã hóa `RSA key`, giải mã hóa ta được `AES key` và tệp được truyền đi. Ta sẽ dùng `key = dc 46 73 b0 5e 39 16 b3`. Ta có thể mô hình hóa nó như sau: > Luồng 1: `Stego.Encrypt((md5hash, pub) + XOR(key, priv)` với `md5hash = md5(guid())` > Luồng 2: `XOR(filename + '.enc', key)` > Luồng 3: `Graphy.Encrypt(filecontent, base64(md5hash))` Ta đã có giá trị của `key`, ta có thể tính được `filename` như sau ![image](https://hackmd.io/_uploads/HyQCL0796.png) Giải mã trong Cyberchef, ta biết được kết quả sẽ có chứa `RSAKeyValue`, từ đó ta tách được `array3` và `array4`. Ta viết đoạn giải mã để tính `RSA privateKey` và `Stego.Encrypt((md5hash, pub)`. Tính được `privateKey` sẽ giúp ta tìm được mã băm MD5. ![image](https://hackmd.io/_uploads/BJMtDAQqT.png) ![image](https://hackmd.io/_uploads/rJldIORm5a.png) ![image](https://hackmd.io/_uploads/ry1bt0m9p.png) Với giá trị `md5hash`, `filename` mà ta đã tính được, ta có thể tìm được nội dung của tệp thông qua thuật toán mã hóa AES với `salt`, `base64_encode(md5hash)` ![image](https://hackmd.io/_uploads/SkOVtRX56.png) Toàn thể đoạn code như sau: :::info ``` from Crypto.Cipher import AES, PKCS1_v1_5 from Crypto.PublicKey import RSA from Crypto.Protocol import KDF from Crypto.Hash import SHA from Crypto import Random from pwn import xor import subprocess import base64 from lxml import etree def run_tshark_filter(output_file, port_filter): command = f"tshark -r capture.pcap -Y \"{port_filter}\" -T fields -e data | sed -n '2,$p' | tr -d '\n\r' | xxd -r -p > {output_file}" subprocess.run(command, shell=True) def stego_decrypt(cipher, md5_guid, filename): salt, password = bytearray([21, 204, 127, 153, 3, 237, 10, 26, 19, 103, 23, 31, 55, 49, 32, 57]), base64.b64encode(bytearray.fromhex(md5_guid)) derived_key = KDF.PBKDF2(password, salt, dkLen=32, count=2) iv = KDF.PBKDF2(password, salt, dkLen=32 + 16, count=2)[32:] aes_cipher = AES.new(derived_key, AES.MODE_CBC, IV=iv) with open(filename, 'wb') as file: file.write(aes_cipher.decrypt(cipher)) print(f"{filename} has been recovered!") def graphy_decrypt(data, priv, index_priv): e, p, q, d = int(base64.b64decode(priv.find("Exponent").text).hex(), 16), int(base64.b64decode(priv.find("P").text).hex(), 16), int(base64.b64decode(priv.find("Q").text).hex(), 16), int(base64.b64decode(priv.find("D").text).hex(), 16) rsa_key = RSA.construct((p * q, e, d, p, q)) cipher = PKCS1_v1_5.new(rsa_key) md5_guid = cipher.decrypt(data[:index_priv], Random.new().read(15 + SHA.digest_size)).hex() print("MD5(GUID):", md5_guid) return md5_guid def recover_private_key(data, key): uncipher_data, index_priv = xor(data, key), xor(data, key).index(b"<RSAKeyValue>") priv = etree.fromstring(uncipher_data[index_priv:].decode(errors='ignore')) print("Private Key:\n", etree.tostring(priv, pretty_print=True).decode()) return priv, index_priv def recover_hash(data, key): priv, index_priv = recover_private_key(data, key) return graphy_decrypt(data, priv, index_priv) def recover_key(cipher, key): filename_enc = xor(cipher, key).decode(errors='ignore') print("\nFound filename:", filename_enc[filename_enc.rindex("\\")+1:filename_enc.index(".enc")]) return filename_enc def parse_stream(stream): newline, len_data = stream.index(b'\n'), int(stream[:stream.index(b'\n')].decode()) stream = stream[newline+1:] return len_data, stream[:len_data], stream[len_data:] if __name__ == '__main__': run_tshark_filter("stream1.bin", "tcp.srcport == 49829 && tcp.dstport == 31338") run_tshark_filter("stream2.bin", "tcp.srcport == 49830 && tcp.dstport == 31338") STREAM1, STREAM2 = open('stream1.bin', 'rb').read(), open('stream2.bin', 'rb').read() STREAM, key = STREAM1 + STREAM2, bytes.fromhex("dc 46 73 b0 5e 39 16 b3") while STREAM: len1, array6, STREAM = parse_stream(STREAM) len2, array7, STREAM = parse_stream(STREAM) len3, array2, STREAM = parse_stream(STREAM) filename, md5_guid = recover_key(array7, key), recover_hash(array6, key) stego_decrypt(array2, md5_guid, filename) ``` ::: Kết quả ta nhận được từ port 49829 ![secret](https://hackmd.io/_uploads/rJW19AXqa.jpg) Kết quả ta nhận được từ port 49830 ![image](https://hackmd.io/_uploads/r1_xqAX9T.png) :::success ``` Flag: HTB{m1nd_b3nd1ng_m@lwAr3!??} ``` ::: <div id="Teleport"></div> <h3>Challenge 4: Teleport</h3> You've been sent to a strange planet, inhabited by a species with the natural ability to teleport. If you're able to capture one, you may be able to synthesise lightweight teleportation technology. However, they don't want to be caught, and disappear out of your grasp - can you get the drop on them? <a href="https://drive.google.com/file/d/1viiOJKdVIHiXrQJuFmoozhDZgjKgMlFH/view?usp=sharing">Download Challenge Here</a> <h3>Solution</h3> Chúng ta có thể sử dụng lệnh 'file' để cung cấp cái nhìn tổng quan về loại tệp. ![image](https://hackmd.io/_uploads/BkHGYJEcp.png) Mở file trong IDA64, chương trình thực hiện 43 vòng lặp, mỗi lần thực hiện 1 hàm. ![image](https://hackmd.io/_uploads/B1P46D4cT.png) Ta biết rẳng bài này chỉ đơn giản kiểm tra từng kí tự ta nhập vào. ![image](https://hackmd.io/_uploads/BkuKTv45p.png) Ta viết một đoạn script IDAPython để tìm ra flag :::info ``` import ida_bytes start_address = 0xB2A flag_raw = [0 for _ in range(101)] for i in range(43): char = ida_bytes.get_byte(start_address + i * 0x44 + 0x1C) index = ida_bytes.get_byte(start_address + i * 0x44 + 0x20) flag_raw[index] = char flag = [chr(element) for element in flag_raw if element != 0] print("".join(flag)) ``` ::: :::success ``` HTB{jump1ng_thru_th3_sp4c3_t1m3_c0nt1nuum!} ``` ::: <div id="Indefinite"></div> <h3>Challenge 5: Indefinite</h3> You hold in one hand an encrypted datastream, and in the other the central core of a Golden Fang communications terminal. Countless spies have risked their lives to steal both the encrypted attack plans, and the technology used to conceal it, and bring them to you for expert analysis. To your horror, as you turn the screws on the core, its defense mechanisms spring to life, concealing and covering its workings. You have minutes to spare before the device destroys itself - can you crack the code? <a href="https://drive.google.com/file/d/1NZ07-BwF5KeAgfX8XHI0kRRCPdy7y1WS/view?usp=sharing">Download Challenge Here</a> <h3>Solution</h3> Chúng ta có thể sử dụng lệnh 'file' để cung cấp cái nhìn tổng quan về loại tệp. ![image](https://hackmd.io/_uploads/Syh5SfSjT.png) ><b>Một chút thông tin về fork() trong Linux:</b> >1. `Trước khi có sự kiện fork`, chương trình chính là tiến trình. Tại thời điểm này chỉ có một tiến trình. >2. `Sau khi có sự kiện fork`, chương trình sẽ phân thân thành hai tiếng trình giống hệt nhau, chỉ khác nhau PID. >3. Tiến trình ban đầu gọi là tiến trình cha, tiến trình được phân thân gọi là tiến trình con. Sau khi tạo ra tiến trình con, tiến trình cha có thể tiếp tục thực thi hoặc có thể đợi đến khi tiến trình con hoàn thành. >4. Tiến trình con thường sẽ thay thế hoàn toàn chính mình bằng một chương trình khác sau khi nhận ra rằng nó là một tiến trình con. Điều này làm mất mã và không gian địa chỉ của chương trình ban đầu. >5. Nếu tiến trình cha chọn đợi cho đến khi tiến trình con kết thúc, thì tiến trình cha sẽ nhận được mã thoát (exit code) của chương trình mà tiến trình con thực thi. >6. Để tránh tiến trình con đã kết thúc nhưng vẫn được liệt kê trong bảng tiến trình với một mã thoát chưa được lấy) tiến trình cha nên gọi hàm `wait()` cho các tiến trình con của mình, hoặc định kỳ hoặc khi nhận được tín hiệu `SIGCHLD`, tín hiệu này chỉ ra rằng một tiến trình con đã kết thúc. > >Các điểm cần lưu ý: >* Các tiến trình này có vùng nhớ riêng => thay đổi biến của tiến trình cha không làm thay đổi tiến trình con và ngược lại. >* Tiến trình cha có kết quả trả về của hàm fork là pid của con. >* Tiến trình con có thể được tạo nhiều phân <=> tiến trình cho có thể phân thân nhiều lần. >* Thực hiện sự kiên: `child_pid = fork()` `nếu child_pid == 0 => code chạy tiến trình con` `nếu child_pid > 0 => code chạy tiến trình cha` `nếu child_pid == -1 => tạo phân thân thất bại` Mở file trong IDA64, tôi đã `NOP` lệnh `int 3` để nội dung giải mã được chính xác. ![image](https://hackmd.io/_uploads/HJ04Imro6.png) Chương trình sẽ thay đổi quyền truy cập hàm `child` - cho phép ta `write-read-execute`. Sau đó thực hiện sự kiện `fork()`: * `Nếu là tiến trình con`, nó sẽ bị theo dõi bởi tiến trình cha. Sau đó gọi `child(argv[1])`. * `Nếu là tiến trình cha`, nó sẽ chờ tiến trình con kết thúc và theo dõi tiến trình con bằng cách gọi `tracer`. ![image](https://hackmd.io/_uploads/HyusAoBop.png) Hàm `child()` sẽ thực hiện đọc 8 byte từ `/dev/urandom` và dùng nó để mã hóa file. Tuy nhiên tại thời điểm này, chương trình bị lỗi, khả năng cao là do lệnh `UD2`. Ta sẽ quan sát tiến trình cha đang làm gì. ![image](https://hackmd.io/_uploads/HyS90iBoa.png) Tôi đã giải thích hàm `tracer()` trong comment của ảnh. Ta nên đổi kiểu dữ liệu của biến lưu kết quả trạng thái các thanh ghi thành `user_regs_struct` để hiểu rõ đoạn code hơn. ![image](https://hackmd.io/_uploads/By8Fgnrs6.png) Trong hàm `do_inflate()` sẽ khởi tạo hệ thống giải nén, dùng`zlib 1.2.11` để giải nén `112 byte`. Thực hiện giải nén với: * `compressed_buffer` là `địa chỉ buffer để lưu dữ liệu nén` * `deflate_buffer` là `địa chỉ buffer để lưu dữ liệu sau khi giải nén.` Khi tiến trình con thực thi lệnh `UD2`, tiến trình cha sẽ thực hiện `tracer`. Ta có thể mô tả chương trình hoạt động như sau: 1. `Đọc 8 byte` từ tiến trình con, tìm kiếm giá trị `0x0B0F` (`little edian` của `UD2` có opcode là `0x0F0B`) 1. `Đọc 2 byte` để làm `kích thước dữ liệu nén` 1. `Đọc 4 byte` để làm `kích thước dữ liệu giải nén` 1. Dùng `zlib` để giải nén dữ liệu theo các đoạn `8 byte` được đọc 1. `Ghi dữ liệu giải nén vào tiến trình con`, bắt đầu từ lệnh `UD2` bằng lệnh `process_vm_writev` - nó đã được `cấp quyền ghi` từ `mprotect()`. Ta có được công thức sau:`compress_size + NULL data + 8 = decompress_data` với `8 byte = sizeof(0x0F0B)+sizeof(compress_size)+sizeof(decompress_data)`. Ta viết chương trình để giải nén tệp. :::info ```python import zlib from elftools.elf.elffile import ELFFile def find_symbol_table(elf): for section in elf.iter_sections(): if section.name == '.symtab': return section raise ValueError("No symbol table found") def decompress_function_code(stream, start_address, size): compressed = bytes(stream[start_address + 8: start_address + 8 + size]) return list(zlib.decompress(compressed)) elf_file = 'indefinite' UD2_bytes = b'\x0f\x0b' with open(elf_file, 'rb') as file: stream = list(file.read()) elf = ELFFile(file) symtab = find_symbol_table(elf) print(f"Functions with first 2 bytes as 0x{UD2_bytes}:") for symbol in symtab.iter_symbols(): if symbol['st_info']['type'] == 'STT_FUNC': func_name = symbol.name address = symbol['st_value'] size = symbol['st_size'] file.seek(address) bytes_2 = file.read(2) if bytes_2 == UD2_bytes: print(f"Function: {func_name}, Address: 0x{address:08x}") function_code = decompress_function_code(stream, address, size) stream[address: address + size] = function_code with open('decompress', 'wb') as file: file.write(bytes(stream)) ``` ::: Ta mở tệp `decompress` trong IDA và tiếp tục phân tích theo luồng của tiến trình con. ![image](https://hackmd.io/_uploads/Hk3A3Q8j6.png) Tại `hàm do_encrypt_file()`, chương trình sẽ thêm vào tên tệp `.enc`, tăng kích thước tệp và thực hiện mã hóa tệp. Lưu ý: khi này `random_8_bytes` đã được đưa vào đầu của tệp mã hóa rồi đưa để thực hiện mã hóa nội dung sau. ![image](https://hackmd.io/_uploads/HyiV3QIo6.png) Tại `hàm do_encryption()`, chương trình sẽ tính toán lại `8 byte random` và lấy nó để `XOR file_data[i:i+8]` ![image](https://hackmd.io/_uploads/HJOsAmIsT.png) Hàm tính toán giá trị CRC32 từ dữ liệu đầu vào. Nó sẽ trả về giá trị `CRC32(data)+CRC32(data)[::-1]`. Ví dụ: `CRC32(data)="ABCD" => ret "ABCDDCBA"`. Ta đã biết `8 byte` đầu là `key` của thuật toán CRC32. Từ đây, ta có thể tìm lại nội dung gốc. :::info ```python import zlib from pwn import xor def crc_double(key): crc = zlib.crc32(key).to_bytes(4, byteorder='little') return crc + crc[::-1] with open("flag.txt.enc", "rb") as file: data = file.read() blocks = [data[i:i+8] for i in range(0, len(data), 8)] key, blocks = blocks[0], blocks[1:-1] output = b'' for chunk in blocks: key = crc_double(key) output += xor(chunk, key) print(output) # b'At 3730 Galactic Time, we will convene at our outpost the Phey forest, 4 miles from the Annara defense systems. Remember, the password for the bunker door is HTB{unp4ck1ng_th3_s3cr3t,unr4v3ll1ng_th3_c0d3}.\n!\x83' ``` ::: :::success ``` Flag: HTB{unp4ck1ng_th3_s3cr3t,unr4v3ll1ng_th3_c0d3} ``` ::: <div id="Pseudo"></div> <h3>Challenge 6: Pseudo</h3> Do you have enough permissions to get the flag? <a href="https://drive.google.com/file/d/1ld_-f1s56DCzKos-neB7YTZj4hdTEOjG/view?usp=sharing">Download Challenge Here</a> <h3>Solution</h3> Chúng ta có thể sử dụng lệnh 'file' để cung cấp cái nhìn tổng quan về loại tệp. ![image](https://hackmd.io/_uploads/B10BNL8op.png) Khác với mọi bài trước, tệp này có kiến trúc `ARM 64-bit (aarch64)` nên ta cần mô phỏng môi trường `aarch64 ` để thực thi thay vì `x86_64` trên máy tính. Hãy chạy câu lệnh sau để cài đặt QEMU software: :::info ```shell sudo apt install qemu-system qemu-user qemu-efi-aarch64 ``` ::: Khi debug chương trình ta sẽ dùng: :::info ```shell qemu-aarch64 -g 23946 pseudo_unpack ``` ::: Mở chương trình trong DIE, ta sẽ biết được chương trình đã bị đóng gói UPX. ![image](https://hackmd.io/_uploads/Hk1g0LUoa.png) Chúng ta có thể sử dụng lệnh 'file' để cung cấp cái nhìn tổng quan về loại tệp. ![image](https://hackmd.io/_uploads/B1FEQ7viT.png) Lệnh `readelf -h pseudo_unpack` được sử dụng để đọc và hiển thị thông tin tiêu đề (header) của một tệp thực thi. Ta biết được `OEP` của nó là `0x401198` ![image](https://hackmd.io/_uploads/rk_invvjp.png) Chạy chương trình, chương trình sẽ hiển thị tin nhắn `what is this, a terminal for ants?`. ![image](https://hackmd.io/_uploads/rkFnTuws6.png) Với lệnh `strace ./pseudo_unpack`, ta biết được chương trình sẽ lấy thông số cột, số hàng của terminal hiện tại và in ra thông báo. Thông qua đây, tôi dự đoán do terminal hiện tại đang bị nhỏ so với yêu cầu của chương trình. ![image](https://hackmd.io/_uploads/BJj-btwo6.png) Mở file trong IDA64 và Ghidra để có thể hiểu chương trình rõ hơn. Đây là lần đầu tôi phân tích tệp ARM nên chưa rành lắm. ![image](https://hackmd.io/_uploads/S1gq6_Dj6.png) Ta có thể dự đoán được hàm `entry() trong Ghidra` tương đương với `start() trong IDA`. Ta biết được hàm `_libc_start_main()` tham số đầu tiên là hàm `main()`. Hàm `sub_4CC240()` thực hiện lệnh lấy thông tin thông số của kích thước terminal bằng `__NR_ioctl(1,TIOCGWINSZ,array)`. <a href="https://android.googlesource.com/platform/external/kernel-headers/+/3c977d220ea6a9450a060861cd5a4317c37cb190%5E%5E1..3c977d220ea6a9450a060861cd5a4317c37cb190%5E/"> Truy cập tại đây để hiểu thêm về các thông số.</a> Sau đó kiểm tra số cột của terminal hiện tại với 158, nếu bé hơn hoặc bằng sẽ in ra chuỗi `what is this, a terminal for ants?`. Bạn có thể dùng lệnh sau để thay đổi kích thước: :::info ```shell stty cols 200 rows 40 ``` ::: Khi đó, thực hiện chương trình, nó sẽ yêu cầu ta nhập mật khẩu. Nếu ta nhập mật khẩu sai, nó thực hiện lặp liên tục cho đến khi ta can thiệp và kết thúc nó. ![image](https://hackmd.io/_uploads/rJWRfqvsp.png) Sau quá trình phân tích tĩnh, ta có thể ghi chú các hoạt độn của đoạn mã dưới đây. ![image](https://hackmd.io/_uploads/SJA-Iqwsa.png) Ta có thể thấy hoạt động của nó có nhiều nét tương đồng với `brainfuck`. Hơi bị bất ngờ khi gặp `brainfuck` trong một bài như thế này. Sau một thời gian phân tích động, ta kết luận một số hành động của chương trình như sau: * opcode = 0xEF - lấy username * opcode = 0x80 - đọc mật khẩu * opcode = 0xD2 - kiểm tra mật khẩu 1. Khi `opcode = 0xEF`, chương trình dùng `malloc(5)` và `getenv` để lưu `username` đang được đăng nhập. 2. Khi `opcode = 0x80`, chương trình đọc nội dung nhập từ `stdin` và với từng kí tự đọc được trừ đi 0x5E. <a href="https://stackoverflow.com/questions/2187474/i-am-not-able-to-flush-stdin-how-can-i-flush-stdin-in-c">Đọc thêm tại đây</a> 3. Khi `opcode = 0xD2`, tại `0x40091c ` chương trình thực hiện `cmp w2,w0,UXTB` với `w2 = ord(char)-0x5E` và `w0 = calculated_number`. Lưu ý: thứ tự so sánh từ kí tự cuối đến kí tự đầu. Bạn có thể debug chương trình và lưu từng kí tự tại thanh ghi w0 và ghép nó thành chuỗi. Ở đây ta sẽ viết chương trình để trích giá trị ấy. <a href="https://docs.qiling.io/en/latest/hijack/">Mình đã nghiên cứu rất nhiều cách để Hijacking với Qiling.</a> Phải dùng `timeout=100000` để chương trình có thời gian thực thi. :::info ``` from qiling import Qiling from qiling.extensions import pipe password = "" def getdata(ql: Qiling): global password value = ql.arch.regs.w0 + 0x5E password = chr(value) + password if __name__ == "__main__": ql = Qiling(["./pseudo_unpack"]) ql.os.stdin = pipe.SimpleInStream(0) ql.os.stdin.write(b"a" * 16 + b"\n") ql.hook_address(getdata, 0x40091c) ql.run(timeout=100000) print(password) ``` ::: Ta nhập mật khẩu đã tìm được ở trên và có được ![image](https://hackmd.io/_uploads/HkNGo8wsp.png) :::success ``` Flag: HTB{~vms_all_the_way} ``` :::