## I. Introduction - Writeup đánh dấu cho lần đầu tiên viết về rev của mình, nếu có sai sót mong được thông cảm hihi. Trong tuần này mình sẽ cố gắng wu thêm về CTFLearn nữa, xử lí series `*R challenge` của tác giả `kcbowhunter`. Giờ thì bắt tay vào giải thui, mình cũng chẳng nhớ thứ tự bài ra sao nữa, nên mình sẽ giải theo sắp xếp của anh [Tùng Dvan](https://github.com/DecemberRecruitment/SSB3YW50IHRvIGJlY29tZSBhIG1lbWJlciBvZiB0aGUgS0NTQyBjbHViLg-/tree/master/KCSC_RECRUITMENT_2023/chall). Một điều nữa, mình không có desc của chall nên đây chỉ là lời giải thui, các bạn có thể lấy chall từ repo trên của anh Tùng, shoutout :fire: ## II. Writeup ### 1. Awg_Mah_Back - Vào bài, chúng ta được cho hai file `output.txt` và `src.py` như sau: - Challenge output: ``` A9,tE4+6&:G7 9.-c]6H5uo2=gi 6.t 02=:%`<*E15| 8>  wU9wWo3Uc0O3UozEaTTvaEjLTEzcTDTmQ3mzcFXT`nXV ``` - Challenge source: ```python= from pwn import * with open('flag.txt', 'rb') as (f): flag = f.read() a = flag[0:len(flag) // 3] b = flag[len(flag) // 3:2 * len(flag) // 3] c = flag[2 * len(flag) // 3:] a = xor(a, int(str(len(flag))[0]) + int(str(len(flag))[1])) b = xor(a, b) c = xor(b, c) a = xor(c, a) b = xor(a, b) c = xor(b, c) c = xor(c, int(str(len(flag))[0]) * int(str(len(flag))[1])) enc = a + b + c with open('output.txt', 'wb') as (f): f.write(enc) ``` - Mình nghĩ trong CNTT, chưa ai chưa từng làm việc với phép `xor` cả, đây là phép logic có tính giao hoán, hay `a ^ b = b ^ a` và `a ^ b = c <=> a = b ^ c`. - Nếu trích lấy `enc` từ file `output.txt`, có thể thấy `len(enc) = 120` là bội của 3, từ `src.py` trên ta biết được `len(a) = len(b) = len(c) = 120 // 3` và `len(flag) = len(enc) = 120` - Do biết được `len(flag)`, chúng ta tìm được hai ẩn quan trọng là `int(str(len(flag))[0]) + int(str(len(flag))[1])` và `int(str(len(flag))[0]) * int(str(len(flag))[1])`, đặt chúng lần lượt là `x` và `y`, ta có thể dễ dàng đảo ngược quá trình mã hóa để tìm lại flag: ```python= from pwn import * from base64 import b64decode as d with open("output.txt", "rb") as (f): enc = f.read() f.close() LEN = len(enc) a = enc[0:LEN // 3] b = enc[LEN // 3:2 * LEN // 3] c = enc[2 * LEN // 3:] x = int(str(LEN)[0]) + int(str(LEN)[1]) y = int(str(LEN)[0]) * int(str(LEN)[1]) c = xor(c, y) c = xor(b, c) b = xor(a, b) a = xor(c, a) c = xor(b, c) b = xor(a, b) a = xor(a, x) flag = a + b + c while b"KCSC" not in flag: flag = d(flag) print(flag) ``` > ~~``KCSC{84cK_t0_BaCK_To_B4ck_X0r`_4nD_864_oM3g4LuL}``~~ - Note một chút, lúc đầu mình làm ra `flag` là chuỗi này cơ: ![decrypt](https://hackmd.io/_uploads/BJu4YbKW1x.png) nhìn định dạng thì chuẩn base64 cũng đã giải thích cho mình tại sao `len(flag)` lại dài 120 lận nên mình quyết định `b64decode` một loạt luôn và nhận được `flag` thực sự. ### 2. Images - Giải nén file, chúng ta nhận được 12 file ảnh liên tiếp là các dòng code Assembly. Đây là ngôn ngữ mẹ đẻ của Reverse mà bất kì Reverser nào cũng cần phải đọc thông viết thạo. Do không có chương trình cụ thể nên chúng ta chỉ có thể phân tích tĩnh luồng chương trình qua các ảnh. - Quan sát ảnh 1: ![1](https://hackmd.io/_uploads/S1vc9WK-Jx.jpg) có thể thấy chương trình in ra lời `Welcome` và yêu cầu `Nhap vao flag: `. Ở ảnh 2 chỉ cần quan tâm `fgets` được gọi để ghi `input` vào `Buffer` rồi gán vào `rcx`. - Bắt đầu từ ảnh 3, chúng ta có những instruction tương tác trực tiếp lên địa chỉ của `Buffer`: ![3](https://hackmd.io/_uploads/HJ3KoWFb1l.jpg) ``` mov eax, 1 imul rax, 0 movsx eax, [rbp+rax+230h+Buffer] cmp eax, 75 jnz loc_140012834 ``` - Mình thấy phép so sánh thanh ghi `eax` với một giá trị ASCII được lặp lại rất nhiều trong 12 ảnh, kế đó là `jnz loc_140012834` giúp mình dự đoán từng phép `cmp` chính là so sánh `input` chúng ta nhập vào với `flag` chuẩn của bài. Các instruction trên có thể viết dưới dạng mã giả là: ```C= idx = 1 * 0 if(Buffer[idx] != 75) printf("Wrong") ``` - Dựa vào idea này và tinh mắt nhìn từng ảnh, mình tìm lại được flag: ```python= flag = [None] * 100 def i(b, c): eax = b flag[eax] = chr(c) i(0, 75) i(0x1A, 110) i(0x14, 101) i(0x18, 104) i(34, 101) i(23, 110) i(18, 107) i(21, 110) i(22, 95) i(9, 111) i(5, 67) i(8, 95) i(10, 110) i(14, 95) i(12, 118) i(16, 97) i(3, 67) i(30, 105) i(48, 121) i(41, 95) i(44, 104) i(39, 110) i(7, 109) i(28, 110) i(29, 104) i(15, 100) i(38, 111) i(42, 97) i(46, 110) i(37, 100) i(33, 104) i(32, 95) i(36, 95) i(31, 110) i(25, 97) i(27, 95) i(35, 116) i(19, 105) i(40, 103) i(43, 110) i(47, 97) i(17, 95) i(49, 96) i(50, 125) i(4, 123) i(13, 105) i(11, 95) i(6, 97) i(2, 95) i(6, 97) i(2, 83) i(1, 67) flag = [i for i in flag if i is not None] print(''.join(flag)) ``` > ~~``KCSC{Cam_on_vi_da_kien_nhan_nhin_het_dong_anhnay`}``~~ - That's it, idea không khó nên chỉ cần kĩ kĩ một chút là lụm được điểm rùi. ### 3. asm - The Ultimate Xor - Thành thực mà nói, mình thấy bài này không khó, về idea thì cũng na ná với chall trên, chỉ có điều thay vì 12 ảnh thì chúng ta có đến hơn 9k dòng code Asm (vì file quá to nên mình không attach ở đây). - Mình sẽ phân tích khoảng 17 dòng đầu tiên: ``` 0x401514: push rbp 0x401515: mov ebp, esp 0x401517: sub esp, 0x28 ``` đoạn này bỏ qua nhé vì chỉ là calling convention ``` 0x40151a: mov dword ptr [rbp - 0xc], 0 0x401521: mov dword ptr [rbp - 0x10], 0x9e 0x401528: mov edx, dword ptr [rbp - 0xc] 0x40152b: mov eax, dword ptr [rbp + 8] 0x40152e: add eax, edx 0x401530: movzx eax, byte ptr [rax] 0x401533: movsx eax, al 0x401536: xor al, 0xd6 0x401538: cmp eax, dword ptr [rbp - 0x10] 0x40153b: je 0x401548 0x40153d: mov eax, dword ptr [rbp - 0xc] 0x401540: mov dword ptr [rsp], eax 0x401543: call 0x401340 ``` tới đây, giá trị 0 được đẩy vào trong stack ở `0xc`; trong các câu lệnh sau, mỗi lần nó lại được `add 1` nên mình dự đoán đây chính là `index` (của cái gì thì có lẽ là `flag` và `input` mà mình sẽ nhập vào). ![Ảnh chụp màn hình 2024-11-06 224850](https://hackmd.io/_uploads/Sy1qeMKWkl.png) - Kế đó là flow như sau: ```python= [rbp - 0x10] = 0x9e edx = [rbp - 0xc] = 0 eax = [rbp + 8] eax += edx eax = [rax] eax = al al ^= 0xd6 if eax == [rbp - 0x10]: continue else: eax = [rbp - 0xc] [rsp] = eax call 0x401340 ``` - Tại đây, mình có thể đưa ra nhận định cơ bản về các room trong stack như sau: - `[rbp - 0x10]` đang lưu kí tự mã hóa (e) - `[rbp - 0xc]` đang lưu chỉ số (idx) - `[rbp + 8]` đang lưu **địa chỉ của input** - Rút gọn code: ```python= al = [[rbp + 8] + [rbp - 0xc]] <=> char = [*input + idx] = input[idx] if char ^ 0xd6 == e: continue else: """Wrong path""" ``` - Vậy, chương trình sẽ nhận vào `input` của chúng ta, thực hiện phép `xor` với một giá trị nào đó rồi `cmp` để kiểm tra. Tuy nhiên, mình lại tìm thấy 715 phép `cmp`, nhưng chỉ có 711 phép `xor`: ![cmp](https://hackmd.io/_uploads/HJmmJ7FWkg.png) ![xor](https://hackmd.io/_uploads/S1hrkmt-yx.png) - Vậy 4 phép `xor` nữa đã bị thay thành phép gì? Dựa vào idea trên, mình trích xuất các số và thực hiện `xor` ngược lại để tìm ra `flag`, kết quả như sau: ![err](https://hackmd.io/_uploads/rkPGgmFWye.png) - Mình quyết định truy ngược lại nơi `xor` bị lỗi, in từng chỉ số của `line`, mình tìm được lỗi nằm ở `idx == 99`, phép `cmp` thứ 100: ![err2](https://hackmd.io/_uploads/SkNpeXY-kx.png) ![err3](https://hackmd.io/_uploads/SJAE-mF-Jx.png) - Tại đây không hề có phép `xor` nào mà chỉ là gán, kiểm tra giá trị bình thường, vì vậy mình sẽ sửa code một chút: ```python= f = open("asm.txt", "r").readlines() flag = [] flag_idx = 0 for line in f: if "mov\tdword ptr [rbp - 0x10], " in line: c = line.split(", ")[1].strip() flag.append([int(c, 16), 0]) elif "xor" in line: c = line.split(", ")[1].strip() flag[flag_idx][1] = int(c, 16) elif "add\tdword ptr [rbp - 0xc], 1" in line: flag_idx += 1 flag = ''.join([chr(x[0] ^ x[1]) for x in flag]) print(flag) ``` nhận được: ![text](https://hackmd.io/_uploads/r1wMfQYZke.png) - That's it, flag của chúng ta đã hiện ra > ~~`KCSC{I_reverse_all_this_and_all_I_got_is_this_flag}`~~ - Mình khá bất ngờ khi thấy bài không có quá nhiều sol, mặc dù bài khá hay và idea rõ ràng ngay từ đầu, có lẽ là do làm sau khi giải end nên mình chưa cảm nhận được áp lực làm bài :face_with_hand_over_mouth: ### 4. chal - Dont Call Me Kamui - Đây là chall tiếp theo mình sẽ xiên, mình cũng quên tác giả là ai rồi nhưng có nhớ desc của bài là `hash...` gì đó, gợi ý cho mình rằng bài sẽ liên quan đến hàm băm, có thể là MD5, SHA256, who knows. - Bước đầu chall cho chúng ta nhập vào `key`, sau khi nhập, chưa biết đúng hay sai đã exit luôn :face_with_one_eyebrow_raised: ![image](https://hackmd.io/_uploads/ryDPRAF-Jl.png) - Load vào IDA thử đọc xem sao, `Shift + F12` để tìm lại string `[+] Input Key: ` sau đó `xref`: ![image](https://hackmd.io/_uploads/S1jlk15Zkx.png) - Đi sâu vào hai hàm `sub_401C50` và `sub_401C80` ![sub1](https://hackmd.io/_uploads/SJU8119ZJe.png) ![sub2](https://hackmd.io/_uploads/ryNFkk9Zyl.png) - Có thể nhận định đây chính là hai hàm `printf` và `scanf`, vì vậy mình sẽ đổi tên hàm. Khám phá tiếp ở phía dưới thấy được hai phần code thông báo: ![noti](https://hackmd.io/_uploads/SyYJZ1cbJx.png) - Mình thấy việc đổi màu graph trong ida khá hay, giúp mình nhìn được tổng quan chương trình theo hướng đúng, vậy nên màu xanh là flow chuẩn của ta. Tuy nhiên để vào được graph xanh thì chương trình đã có một loạt các bước so sánh ở trên. Do màn graph khá bé, nếu attach nhiều ảnh ở đây sẽ rối nên mình sẽ screenshot rồi chú thích thẳng vào ảnh luôn. ![Ảnh chụp màn hình 2024-11-07 134843](https://hackmd.io/_uploads/r1ZEUkcWJe.png) - Tại `loc_401B95`, mình đoán `[esi]` đang giữ `true_key` vì giá trị đó không thay đổi mỗi lần mình debug, còn `[eax]` giữ `encrypt(key)` của mình vì nó thay đổi liên tục. Một điều nữa, vì `key` của mình bị sú sau khi chạy qua `sub_4011E0` nên đây có khả năng là hàm mã hóa. ![image](https://hackmd.io/_uploads/SJC-vycZyx.png) - Giá trị của `[esi]` là `1cf18a243c25a56a993c8207d1161a9c2de5f34b952d382704b94dc5e888b108`. Các code dưới chỉ đơn giản là check từng kí tự của `encrypt(key)` với `true_key` nên mình sẽ không chú thích nữa. ![checking](https://hackmd.io/_uploads/HkX_wkqbyx.png) - Flow chuẩn là phần xanh như mình đã đổi. Chúng ta sẽ phân tích hàm `sub_4011E0` một chút (mình cũng không hiểu sao nhưng một số hàm của mình bị lệch sau khi debug rồi :v, các bạn theo dõi bằng cách nhìn 3 kí tự cuối được giữ nguyên nhé). Bước vào hàm là một tổ hợp các hỗn độn luôn, cả khi nhìn bằng Assembly hay mã giả. Tuy nhiên, có một vài hằng số được khai báo như gợi ý ![hint](https://hackmd.io/_uploads/Sy8OK15Wye.png) và mình osint được đây chính là các hằng số của hàm băm SHA256: ``` ['0x6a09e667', '0xbb67ae85', '0x3c6ef372', '0xa54ff53a', '0x510e527f', '0x9b05688c', '0x1f83d9ab', '0x5be0cd19'] ``` ![sha256](https://hackmd.io/_uploads/BylI5JqWkx.png) - Để kiểm tra lại, mình sẽ nhập vào chuỗi `KCSC` và cắm breakpoint ngay sau hàm này: ![encrypt](https://hackmd.io/_uploads/ryQLsJ5ZJx.png) - Nhận được `[eax] = f60e9120d0a179b32a338b7eb6b5f966dd17150600e1376a48d1e1a39605559c`, check: ![image](https://hackmd.io/_uploads/ByTUnkc-Je.png) - Vậy là sure, hàm mã hóa ở trên chính là `SHA256`, nhưng đây là hàm băm, các chuỗi là duy nhất và rất khó để reverse lại nó. Chuỗi `key` nhập vào phải thỏa `sha256(key) = [esi] = 1cf18a243c25a56a993c8207d1161a9c2de5f34b952d382704b94dc5e888b108`. - Quăng bừa chuỗi trên lên osint, chúng ta cũng có thể tìm lại được `key`: ![key](https://hackmd.io/_uploads/HkBUpyqZkg.png) - Nhưng nếu mình là author thì mình sẽ không prefer cách giải này lắm mà sẽ tìm hướng khác. Để ý ở phía dưới, sau khi chương trình in ra `Your Flag: ` còn thực thi ![Ảnh chụp màn hình 2024-11-07 143839](https://hackmd.io/_uploads/HJkn8l5byg.png) - Mình nhận định `another const string in eax` vì chuỗi đó được khởi tạo giống hệt nhau mỗi lần mình debug, giá trị của nó là (do thấy có `cmp esi, 35h` nên mình sẽ lấy 53 giá trị): ```= eax = [0x2C, 0x2C, 0x3F, 0x27, 0x1D, 0x01, 0x16, 0x1C, 0x38, 0x16, 0x33, 0x10, 0x13, 0x06, 0x1D, 0x0F, 0x38, 0x1D, 0x03, 0x0D, 0x39, 0x07, 0x16, 0x06, 0x38, 0x0B, 0x05, 0x3B, 0x07, 0x07, 0x2C, 0x1C, 0x15, 0x00, 0x01, 0x3B, 0x0F, 0x0D, 0x16, 0x09, 0x38, 0x24, 0x21, 0x25, 0x25, 0x3D, 0x35, 0x37, 0x0F, 0x05, 0x04, 0x0E, 0x1B] ``` ![const](https://hackmd.io/_uploads/SkXrGl9-1g.png) - `Shift + E` để lấy giá trị thành mảng nhé. Do sau đó `key` của mình còn được `xor` với `eax` nữa, suy nghĩ một chút, chương trình yêu cầu nhập vào `key` để nhả ra `flag = key ^ eax`, mà `sha256(key) = 1cf18a243c25a56a993c8207d1161a9c2de5f34b952d382704b94dc5e888b108`, bên cạnh đó chúng ta cũng được biết `flag` bắt đầu với `KCSC{`. Phép `xor` có tính giao hoán: ``` flag = key ^ eax key = flag ^ eax ``` - Khi đó nếu nhập vào `KCSC{`, chương trình sẽ trả ra đúng 5 kí tự đầu tiên của `key`: ![image](https://hackmd.io/_uploads/r1bIVlcZJe.png) - Vậy `key` bắt đầu với `goldf`, back lại một chút về chú thích `eax = esi (idx), edx = eax % ecx` của mình, điều này giúp chuỗi `key` lặp lại nhiều lần (trong trường hợp `len(key) < len(eax)`) vì phép `xor` yêu cầu hai chuỗi đầu vào phải cùng độ dài. Cũng chính vì điều này, chúng ta sẽ đọc được vài chunk 5 kí tự chính xác trong flag: ![image](https://hackmd.io/_uploads/H1Ye1B5Zke.png) - Và chúng ta thấy cụm `_KMAC` ở vị trí `i = 40`, có thể suy đoán nó chính là `_KMACTF`, mình sẽ lấy cụm này để `xor` với `eax[40:47]`: ![image](https://hackmd.io/_uploads/ByXnhB9Zkg.png) - Nhận được `goldfis` là một chunk 7, mình đoán nó là `goldfish`, đem đi `xor` lại: ![image](https://hackmd.io/_uploads/B1ngAr5bke.png) và đây là `flag` của chúng ta > ~~`KCSC{het_y_tuong_roi_nen_di_an_trom_idea_KMACTF_hjhj}`~~ ### 5. chall - Real Warmup - Ờm, với bài này thì mình thấy khá ba chấm, tại nó clear quá. ![image](https://hackmd.io/_uploads/HJlNozjWyg.png) - Chúng ta được tiếp cận với chương trình check flag, load vào ida và tìm câu chào kia, có được: ![image](https://hackmd.io/_uploads/HJfk3GjZyg.png) - Vậy chương trình sẽ nhận string của mình vào và check với `Str2 = S0NTQ3tDaDQwYF9NfF98bjlgX0QzTidfVjAxJ183N1wvX0tDU0N9`, nếu đúng thì in ra `"Great!!!""`, không thì in ra `"Sai roi. Hay thu lai"`. Đem `Str2` đi giải, đây là chuỗi base64 nên thu được: ![image](https://hackmd.io/_uploads/BJJ9nMsZ1g.png) - Đề phòng mã hóa bằng b64 nhiều lần thì mình cho giải mã nhiều lần luôn. > ~~``KCSC{Ch40`_M|_|n9`_D3N'_V01'_77\\/_KCSC}``~~ ### 6. mission impossible - Vào bài, chạy `GamePlay` sẽ thấy ![image](https://hackmd.io/_uploads/rkH2m2i-Jg.png) - Tiếp tục bấm `Start`, chương trình cho mình nhập vai vào một con xe tăng giữa nhiều con xe tăng khác và bị bắn nát người luôn :v. Cuối cùng là thua, vậy giờ mình phải tìm cách thắng. Trong lúc đang chạy game thì mình thấy các file ẩn: ![image](https://hackmd.io/_uploads/rJT4NniW1g.png) - Ở đây, folder `config` là đáng ngờ nhất, vì file `config.json` bên trong chứa trong đó thông tin về những con NPC xung quanh: ![image](https://hackmd.io/_uploads/H1-64ns-ke.png) - Tuy nhiên sau khi thoát game thì những file ẩn này bị xóa đi. Để ý thấy bên cạnh file game còn có `GamePlay` và `resources`: ![image](https://hackmd.io/_uploads/HyJQS3i-kg.png) - Vậy thì chắc chắn file game sẽ thực hiện trích xuất thông tin từ hai file này, mình sẽ load vào ida để phân tích. Do không có hàm `main`, mình `Shift + F12` tìm chuỗi khả nghi thì thấy: ![image](https://hackmd.io/_uploads/By97InjbJe.png) - `xref` ngược lại `./config`, mình thấy 3 file sau khi declare path thì được call vào cùng một hàm: ![image](https://hackmd.io/_uploads/H16FUhoWke.png) - Cắm bp tại lệnh trước các hàm này, mình thấy game vẫn được thực thi và mình vẫn thua như bình thường, vậy hướng làm này của chúng ta chưa chính xác. Dạo xuống dưới một chút, mình nghi ngờ một hàm sử dụng để xóa 3 file này đi: ![image](https://hackmd.io/_uploads/rkNYyRh-Jg.png) - Đặt bp và kiểm tra: - Trước:![image](https://hackmd.io/_uploads/ryrpk0hWkl.png) - Sau:![image](https://hackmd.io/_uploads/ry8kx02bke.png) - Vậy thì chúng ta đã đi quá nửa luồng của chương trình rồi, vì đây là bước xóa sạch các file resources. Quay lại với `Shift + F12`, mình thấy 3 chuỗi rất khả nghi: ![image](https://hackmd.io/_uploads/rySUgRhbyg.png) #### Extracting stage - `xref` và cắm bp tại chuỗi đầu tiên, sau vài lần `F8` (và chẳng hiểu gì), mình dừng lại ở đây: ![image](https://hackmd.io/_uploads/SyjwZCn-Je.png) - Đây chính là tên tác giả, có thể thấy trước đó chương trình đã gọi hàm `read` để đọc file, cụ thể ở mã giả: ![image](https://hackmd.io/_uploads/HygIG0n-yx.png) - Chính xác là chương trình đọc 4 bytes đầu tiên của file `./resources` và check với `sonx`: ![image](https://hackmd.io/_uploads/BJb9zAn-kl.png) - Nếu đúng đó là `sonx`, chương trình sẽ thực hiện thay đổi nó thành `Rar!`: - Trước:![image](https://hackmd.io/_uploads/S1Hc7C2bye.png) - Sau:![image](https://hackmd.io/_uploads/BkivVChb1x.png) - Vậy là signature của file đã được chuyển thành `Rar!`, thử đổi định dạng của file sang `.rar` và extract, có được: ![image](https://hackmd.io/_uploads/Sk3-B0nWyl.png) - Chính xác rồi, vậy là các file ẩn được giấu sâu trong `./resources`, tiếp theo đó chương trình sẽ giải nén cho bung các file game này ra, nên chúng ta sẽ exploit ở quanh đây. #### Patching stage - Trong lúc làm bài thì mình có hai ý tưởng, một là thay đổi file `config` ngay sau khi nó vừa được sinh ra, và chơi game từ lúc đó, hai là thay đổi cả `resources`. Tuy nhiên cách 2 thì mình bị lỗi winRar archives nên sẽ theo cách 1 nhé. - Trở lại với bp khi mà các file bị bung ra, mình sẽ thay đổi các thông số của file `config` thành như này: ![image](https://hackmd.io/_uploads/ry6GARhW1g.png) - Cho `F9` chạy game đến bp kia: ![image](https://hackmd.io/_uploads/S1Xv0Rh-1e.png) rồi thay đổi thông số và chơi tiếp: ![image](https://hackmd.io/_uploads/rJl900h-ke.png) - Đi diệt NPC là xong: ![image](https://hackmd.io/_uploads/Skj0CRhZyl.png) > ~~`KCSC{y0u_w0n_4nd_g3t_th3_fl4g}`~~ ### 7. dynamic function - Đây là một bài mình khá tiếc và cũng để rút kinh nghiệm cho những lần reverse sau. Thực thi file, chúng ta thấy một màn hình console yêu cầu nhập flag: ![image](https://hackmd.io/_uploads/SkLb0zsbkg.png) - Tuy nhiên, sau khi mình nhập sau thì chương trình lập tức exit. Load vào ida phân tích thôi. Thường khi phân tích mình sẽ cố tìm hàm `main` hoặc `start` để bắt đầu hoặc `Shift + F12`, trong trường hợp lỏ quá thì chọn debug dừng ngay tại `EP` cho dễ: ![image](https://hackmd.io/_uploads/Syb-J7jbJl.png) - Dễ dàng `xref` lại chuỗi ban đầu: ![image](https://hackmd.io/_uploads/By591mi-ye.png) - Dạo qua một chút ở dưới, mình thấy hàm `fgets` để nhập chuỗi từ user (tạm gọi là `mK`). Khi đó, `mK` sẽ được lưu vào `Buffer`, max là 32 bytes. Dưới đó, `mK` được chỉnh bytes cuối cùng từ `0Ah (\n)` thành `0 (NULL)` để kết thúc chuỗi (do `fgets` nhận cả phím `Enter (\n)`). Mình chọn chuỗi là `KCSC{Dua_het_flag_day}`: ![image](https://hackmd.io/_uploads/rkUaGmiZJl.png) - Lúc này, `len(mK) = 22`, do nhỏ hơn `1Eh` nên lập tức chương trình nhảy tới in sai luôn. Vậy thì viết thêm vài kí tự nữa cho đủ `1Eh = 30` là được, `mK = KCSC{Dua_flag_cho_em_huhuhuhu}` - Sau đó, chương trình tách `KCSC{}` ra khỏi `mK`, lưu vào `Str`, tiếp tục kiểm tra xem `len(Str) = 30 - len("KCSC{}) = 24 = 18h"` hay không: ![image](https://hackmd.io/_uploads/ryTlSmoZJg.png) - Tiếp tục tung tăng phía dưới, mình thấy hàm rất lạ là `VirtualAlloc` được gọi: ![image](https://hackmd.io/_uploads/H1yoSQsbJg.png) #### VirutalAlloc - Theo như [docs của Microsoft](https://learn.microsoft.com/en-us/windows/win32/memory/reserving-and-committing-memory), `VirtualAlloc` là một hàm dùng để xin cấp phát một vùng nhớ NULL, tức một kiểu cấp phát động (điều này cũng giải thích về tên bài là `dynamicFunction`). Để giải phóng vùng nhớ này, chúng ta sử dụng `VirtualFree`. - Đây là syntax của `VirtualAlloc`: ![VirutalAlloc](https://hackmd.io/_uploads/ByVhUmiWJl.png) các arg được đẩy vào hàm trong bài là: ![VA](https://hackmd.io/_uploads/HkJEwmj-kx.png) - Tham số đầu là NULL, tức chương trình tùy chọn địa chỉ để cấp phát. Tham số sau là `0A4h`, là kích thước của vùng nhớ cấp phát động, tương ứng với 164 bytes. Tham số thứ ba mình không hiểu lắm nhưng tham số thứ tự là cấp quyền cho vùng nhớ (page) đó. Với `40h`, page được phép thực thi (Execute), đọc và viết (Read write). - Thông thường, người ta sẽ sử dụng `VirtualAlloc` để `Dynamic code generation`, tức sinh code "động" và thực thi nó; hoặc sử dụng trong kĩ thuật shellcode injection (cái này mình chưa tìm hiểu kĩ). - Tóm lại, sau khi chạy hàm này, chall của chúng ta đã có thêm một code mới nằm tại địa chỉ của `lpAddress`. #### lpAddress - Sau khi xin cấp phát 164 bytes tại `lpAddress`, chương trình bắt đầu viết code tại đó: ![image](https://hackmd.io/_uploads/rkhocmobkl.png) chính là phần xanh dương mình đã tô màu. Việc viết code này mình không cần thiết phải can thiệp vào, vì code là do author mặc định rồi, chúng ta chỉ nên dịch ngược code tại `lpAddress` thôi. - Cắm bp tại `loc_1F58A6` để phân tích tiếp: ![image](https://hackmd.io/_uploads/SJlYdoXo-Jg.png) - `F8` liên tọi đến dòng này rồi thì đừng `F8` tiếp, vì sẽ bị lỡ mất code tại `lpAddress`. Đây cũng chính là sai lầm của mình khiến mình không giải được bài vì quên :cry:. Nhảy được vào đây là nắm chắc phần thắng rồi: ![image](https://hackmd.io/_uploads/rk0Ts7jZyg.png) - Tại đây mình sẽ `Create Function` để phân tích tiếp: ![image](https://hackmd.io/_uploads/rJEM3miWkx.png) - Vì là hàm động, nên mình không thể phân tích khi không debug được. Thấy hàm tính toán lằng nhằng quá mình quyết định `F5` luôn cho nhanh: ![image](https://hackmd.io/_uploads/SkXOnQoW1l.png) - That shit looks shit, mã giả này xấu là do kiểu tham số truyền vào toàn là `int a1, a2, a3`. Trong khi đó, ở code Assembly trước khi gọi hàm, mình có: ![image](https://hackmd.io/_uploads/BkBan7iWJl.png) - Các tham số trong `lpAddress` là `(*Src, *Dest, len(Src))` với `*Src` là địa chỉ của `Str` của chúng ta, `*Dest` là địa chỉ mà `lpAddress` sẽ viết đè vào. ![image](https://hackmd.io/_uploads/Hk34RQsWkg.png) - Chỉnh sửa lại bằng `Set Ivar type [Y]`, mình được code rất đẹp như sau: ![image](https://hackmd.io/_uploads/H1jzy4j-Jl.png) - Vậy, hàm `lpAddress` sẽ mã hóa chuỗi của chúng ta bằng thuật toán trên, không khó để reverse algo này. Tạm bỏ qua bước này vì còn bước check ở phía dưới nữa. - Sau khi xin cấp phát và chạy `lpAddress`, chương trình lập tức `VirtualFree` luôn, ném đá giấu tay :face_with_raised_eyebrow:. Next, chương trình lại tiếp tục mang chuỗi `Str` đi tính độ dài rồi thực hiện so sánh hai chuỗi ở `var_40` và `byte_1F7CF4` ![image](https://hackmd.io/_uploads/SkFLlNsZyl.png) - Quay lại ảnh số 4 từ đây trở lên, chúng ta thấy `*Dest` của hàm `lpAdress` chính là `[ebp+var_40]`. Chứng tỏ, chuỗi `Str` của chúng ta được mã hóa và lưu vào trong `var_40`, cần check với `byte_1F7CF4`. Do `len(Str) = 24 = len(var_40)` nên mình lấy 24 giá trị của mảng `byte_1F7CF4` (ở đây thì `[ebp+var70]` lưu chỉ số của nó): ``` byte_1F7CF4 = [0x44, 0x93, 0x51, 0x42, 0x24, 0x45, 0x2E, 0x9B, 0x01, 0x99, 0x7F, 0x05, 0x4D, 0x47, 0x25, 0x43, 0xA2, 0xE2, 0x3E, 0xAA, 0x85, 0x99, 0x18, 0x7E] ``` - Reverse lại thuật toán trong `lpAddress`, chúng ta tìm được `Str` chuẩn: ```python= byte_1F7CF4 = [0x44, 0x93, 0x51, 0x42, 0x24, 0x45, 0x2E, 0x9B, 0x01, 0x99, 0x7F, 0x05, 0x4D, 0x47, 0x25, 0x43, 0xA2, 0xE2, 0x3E, 0xAA, 0x85, 0x99, 0x18, 0x7E] v4 = "reversing_is_pretty_cool" LEN = 24 Src = "" for i in range(LEN): v5 = ord(v4[i]) ^ byte_1F7CF4[i] v5 = 16 * (v5 % 16) + v5 // 16 Src += chr(v5) print(Src) ``` >~~`KCSC{correct_flag!submit_now!}`~~ - That's it, một bài khá hay và cung cấp cho mình quá nhiều kiến thức. Cảm ơn tác giả so much:fire:. ### 8. hide and seek - Một bài nữa mình cũng thấy khá là ba chấm :v, lạ và vui. Thực thi file, trên màn hình console thấy: ![image](https://hackmd.io/_uploads/r1-QHEibkl.png) - Dòng chữ bôi xanh chính là địa chỉ trong máy tính của mình. Mình đọc vài wu thì thấy không có màu xanh này, bruh. Copy dòng xanh đó thì được `C:\Users\ADMIN\AppData\Local\Temp\.temp_html_file_66845750.html` - Kiếm đúng path này trong máy: ![image](https://hackmd.io/_uploads/BJt-LVoWJl.png) nhận được: ![image](https://hackmd.io/_uploads/HywNUEsZkg.png) - Và đây chính là flag luôn. Nhưng mình sẽ thử dịch ngược lại chương trình này (just for studying). Load vào ida, bài này phân tích động khá rén vì chương trình đi hơi sâu vào máy mình. Để có cái nhìn chi tiết hơn, các bạn có thể tìm đọc ở [Writeup của anh Sonx - author của bài](https://github.com/scrymastic/CTFs/blob/master/KCSC-Recruitment-2023/hide%20and%20seek/wu/hide_and_seek.md). Về cơ bản, chương trình được chia làm hai `Stage`, `Stage1` đóng vai trò xác định path sẽ viết file, `Stage2` dùng để tạo file (ở đây là html). Hai hàm này được viết rất rõ trong hàm `main_0` (đọc mã giả chứ đọc code Assembly là tèo vì bội thực thông tin đó): ![image](https://hackmd.io/_uploads/HJ3eIHoWJl.png) - Mình đã đổi tên hàm chút cho dễ nhìn, giờ thì phân tích thôi. #### Stage 1: - Đi sâu vào stage này, dựa vào các thông tin nhỏ lẻ mà mình đổi tên các hàm con: ![image](https://hackmd.io/_uploads/S1JNLdjWkx.png) - Tại đây mình chú ý tới hàm `wsprintfw` khá lạ. Cách nhanh nhất để hiểu hàm này là đặt bp ngay sau khi nó thực thi, khi đó nhận được: ![image](https://hackmd.io/_uploads/r1LI8usZkg.png) - Mình đã highlight `word_8C73E0` lại để phân tích. Sau khi thực thi `wsprintfw`, tại offset `word_8C73E0` giờ đã trở thành đường dẫn (file path): ![image](https://hackmd.io/_uploads/SkBld_i-Jg.png) - Alright, lúc đầu khi thấy path này mình đã chạy đi kiếm nó, nhưng không, file html này không tồn tại. Có vẻ chương trình chỉ hình thành file path trỏ tới đây mà chưa viết file. #### Stage 2: - `F8` liên tọi tới bp này: ![image](https://hackmd.io/_uploads/S1D0tdsZJe.png) thì chương trình của mình in ra dòng xanh như ban đầu: ![image](https://hackmd.io/_uploads/BJWbqOobkx.png) - Nó chính xác là file path tại offset `word_8C73E0` kia. Sau này mình mới nhận ra lại quên `F7` vào lúc `call Stage_2` kia :face_palm:. Trở lại dòng đó và phân tích thôi. Make code từ hàm này, mình được một code toàn hàm lạ: ![image](https://hackmd.io/_uploads/BJkbsuo-kl.png) - Cắm bp sau `FindResourceW`, mình phân tích được hàm đã tìm lại resource nằm trong file `.exe` của chall rồi ném resource (gọi là res cho ngắn) vào hàm kế là `LoadResource`. Lại cắm bp ngay sau `LoadResource`, mình nhận được code html tại `eax -> [ebp+hResData]`: ![image](https://hackmd.io/_uploads/S1VyTusWJx.png) ![image](https://hackmd.io/_uploads/BJGWpdsbyg.png) - Well, đọc code này thì thấy mảng flagArray luôn (chắc chắn đây là flag nhưng đã được `xor 0x55` để mã hóa) nhưng mình sẽ tạm thời bỏ qua để phân tích tiếp. Hàm tiếp theo là `LockResource`, mình không thấy điều gì đặc biệt lắm, nhưng code html của chương trình đã được load vào `[ebp+lpBuffer]`. - `SizeofResource` chỉ đơn giản là hàm check kích thước của code rồi lưu vào `eax` mà thôi, ở đây là `0x412 byte`: ![image](https://hackmd.io/_uploads/HJivCOi-1x.png) - Biết được số byte cần viết, chương trình gọi tiếp `CreateFileW`, đây chính là hàm tạo file, giờ thì file có tại path kia rồi: ![image](https://hackmd.io/_uploads/rJ9-1YjbJe.png) - Ở đây có 3 file là số lần mình debug chương trình này, file được bôi đen có 0 bytes kia chính là file vừa được tạo. Cuối cùng là `WriteFile` và `CloseHandle` để kết thúc quá trình này. Và chỉ sau khi `CloseHandle`, file mới có code html: ![image](https://hackmd.io/_uploads/H1Gi1YjWkx.png) - Size `1.01kB` kia chính là `0x412 bytes`: ![image](https://hackmd.io/_uploads/BkW-xFiZJx.png) - That's all, đó là toàn bộ quá trình dịch ngược chương trình của mình. Việc tìm flag có vẻ đơn giản nhưng để hiểu chương trình thì đòi hỏi nhiều hơn thế. Mình lúc làm ra flag cũng khá vui, nhưng sau khi hiểu được toàn bộ thì bantumlum thực sự :fire: > ~~`KCSC{~(^._.)=^._.^=(._.^)~}`~~ ### 9. password checker - Đây là chall tiếp theo mình sẽ phân tích (trước khi đọc lời giải thì mình chỉ giải được 7/10 thôi, còn sót lại password checker, mission impossible và two faces). Về cơ bản, chall này không khó nhưng mình làm bài lúc đã thấm mệt nên kết quả là hụt mất bài này. - Một lời khuyên khi làm những bài như thế này là hãy duplicate file chall, vì bài chỉ cho mình đúng 3 lần nhập `pass`. `pass` chuẩn nằm trong file `wordlist` giữa 10k text khác nên phương án brute force dường như bất khả thi. Sau khi nhập sai đủ 3 lần, chương trình tự xóa đi file `hash.so` và chính nó (mình sử dụng dual boot ubuntu của mình để giải bài do WSL thiếu mất thư viện): ![image](https://hackmd.io/_uploads/Sy_dIqsbkg.png) - Ngay khi tiếp cận bài, mình đã tự hỏi tại sao chương trình lại có thể nhớ được số lần mà mình đã thử nhập `pass`, vì chỉ khi patch được phần này mới có thể vũ phu nó được. Chắc chắn là chương trình không thể tự lưu số lần nhập trong chính nó, vì vậy mình nghi ngờ file `hash.so` kia. - Mình sẽ thử load `passwordChecker` vào ida xem có hành động nào khả nghi liên quan đến `hash.so` không. File `hash.so` là một `shared object`, đóng vai trò gần như một `dll`, giúp chúng ta thực thi subprocess, cứ coi nó như thư viện là được. Mình cắm bp ngay tại `main` để phân tích từng hành vi của chương trình: ![image](https://hackmd.io/_uploads/SyfLc9jb1e.png) - Tại đây đã có dấu hiệu đầu tiên của file `hash.so`. `F8` tiếp, ta thấy nó gọi tới `_dlopen` để mở file, giá trị trả về nằm trong `rax` được lưu vào trong `[rbp+handle]`. `[rbp+handle]` này được sử dụng cho hàm `_dlsym` kế, giúp load symbol của file `.so` phục vụ cho `_dlerror` ở tít sau: ![image](https://hackmd.io/_uploads/ByGYjqoZ1e.png) - Tung tăng tiếp theo luồng chương trình, mình thấy sú sú ở đoạn này, tại hàm: ![image](https://hackmd.io/_uploads/Hyfb3qs-yx.png) - Ban đầu giá trị của `eax` sau khi gọi hàm là 3. Để test, mình `Ctrl + F2` thoát khỏi debugger rồi `F9` lại, lúc này `eax` đã về 2: ![image](https://hackmd.io/_uploads/ryJInqi-1x.png) - Vậy số lần thử của chúng ta đã bị giảm sau khi chạy qua hàm `_Z25numberOfAttemptsRemainingv`. Để có thể vũ phu wordlist, mình sẽ đi sâu vào hàm này tìm chỗ patch. `Ctrl + F2` lần nữa rồi `F7` vào hàm: ![_Z25numberOfAttemptsRemainingv](https://hackmd.io/_uploads/Hku1T5o-ye.png) - Với những ai đã từng code C++, có thể các hàm dưới đều rất quen thuộc, nhưng mình mới học code C nên chỉ có thể đoán sương sương tác dụng của chúng. Mình sẽ bỏ qua giải thích dòng 12 vì chưa hiểu kĩ nó. Tới dòng 14 và 15, chương trình chỉ đơn giản thực hiện `or(16, 8, 4)` để gán vào `v1`, giá trị đó là `v1 = 0x1C = 28`. Tiếp theo, chương trình mở file tại `libPath` kia, đó chính là đường dẫn của `hash.so`: ![image](https://hackmd.io/_uploads/Hy3yejsZ1l.png) - Kế đó, `seekg(v5, v8, 0)` giúp di chuyển vị trí con trỏ đọc tới `v8` và đọc một byte tại đó bằng `read`, lưu byte đó vào `v4`. Đây sẽ chính là số lần mình đã mở chạy chương trình, ở đây `v4 = 2`: ![image](https://hackmd.io/_uploads/ryDNbooZ1l.png) - Lấy `v7 = 3 - v4`, vậy `v7 = 3 - 2 = 1`, chúng ta chỉ còn duy nhất một lần chạy chương trình mà thôi: ![image](https://hackmd.io/_uploads/rJJ5mjib1e.png) - Vậy chúng ta chỉ cần patch lại chương trình, hoặc là cho `v4` không tăng, hoặc patch cho chương trình nhảy thẳng đến luồng đúng là được: ![image](https://hackmd.io/_uploads/BkoVroo-1e.png) cách này trong lúc wu mình mới nghĩ ra :v, lúc này thì Attemps của chúng ta âm luôn. Viết code vũ phu wordlist, chúng ta tìm được chuỗi đúng: ```python= import subprocess password_checker_path = './passwordChecker' wordlist_path = 'wordlist.txt' with open(wordlist_path, 'r') as f: for line in f: password = line.strip() process = subprocess.Popen(password_checker_path, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) output, error = process.communicate(input=password) if 'Wrong password' not in output: print(f'Password is: {password}') break ``` - Code từ anh Sonx, thankiu anh :heart:, và đây là kết quả: ![image](https://hackmd.io/_uploads/Bk07woj-Jl.png) #### Standard case: - Trong trường hợp đã hết 3 lần thử, chúng ta sẽ được nhảy tới đây: ![image](https://hackmd.io/_uploads/ryv2dijZkg.png) - Cắm bp trước hàm check số lần thử, mình set `eax = 0` để nó nhảy ngay tới luồng trên: ![image](https://hackmd.io/_uploads/SJdmKojbyx.png) - Chương trình in ra `No more attempts remaining! Bye!` trước: ![image](https://hackmd.io/_uploads/rJIqFsj-kg.png) - Gán lại đường dẫn của `hash.so` vào `rdi`, chương trình búng tay bay màu file đó luôn: - Trước biến cố![image](https://hackmd.io/_uploads/BykWcoi-1e.png) - Sau biến cố![image](https://hackmd.io/_uploads/SJHG5oibyg.png) - Sau đó gán tiếp đường dẫn của `passwordChecker` vào `rdi`: ![image](https://hackmd.io/_uploads/HyzOqsjbyg.png) - Và bay màu: ![image](https://hackmd.io/_uploads/rkS5cojZ1x.png) - Chỉ vậy thôi, mình sẽ không đi dịch ngược hàm `remove` và thuật toán trong `hash.so` đâu (có lúa thì làm :smile:) vì nó khá khó và dịch ngược nó thì mình không nghĩ mình sẽ học được gì đó :v. > ~~`KCSC{tuyet_voi!!!qua_dep_trai_roi<3<3} `~~ - Note chút, thực ra vẫn còn một cách nữa, đó là kiếm hàm checker trong `passwordChecker` rồi tìm chuỗi chuẩn, cũng rất dễ tìm: `b99aff88d8e71fba4bce610f4d3cbc8d` nhưng mình không sao chạy được code ctypes python như anh Sonx nên tạm thời bỏ qua nhé. ### 10. two faces - Bài cuối cùng là `two faces`, đúng như tên gọi của nó, hai mặt thật sự. Nói vậy thôi nhưng mình học được khá nhiều bài học từ chall này. Bên cạnh việc giúp nhập môn `anti debug`, nó còn perform kĩ thuật `function hooking`. Các bạn có thể đọc thêm tại [đây](https://github.com/khalladay/hooking-by-example?tab=readme-ov-file), mình không chắc lắm nhưng có vẻ bài của chúng ta thuộc dạng `Hook Virtual Function`, trong series `*R challenge` của `kcbowhunter` cũng có một bài `function hooking` nhưng là `Hook Free Function` (có lẽ vậy). - Thực thi file, chương trình yêu cầu chúng ta nhập vào flag: ![image](https://hackmd.io/_uploads/rkdVBMCbkx.png) - Load vào trong ida, rất nhanh chóng mình có thể hiểu được flow trong `main`, nhớ tìm hàm `main_0` mà đấm. Tóm lại, chuỗi chúng ta nhập vào không được dài quá 32 bytes, cụ thể độ dài 22 là chính xác, vậy thì mình sẽ lấy chuỗi `KCSC{Dua_het_flag_day}`: ![image](https://hackmd.io/_uploads/H1YBLG0-1e.png) - Mình đã đổi tên biến cho dễ nhìn rùi, kế đó sẽ tách `KCSC{}` ra khỏi `user_flag`, mình tạm gọi nó là `kernel`, vì `len(user_flag) == 22` nên `len(kernel) = 22 - 6 = 16`: ![image](https://hackmd.io/_uploads/rJ6j8MCWkx.png) - Trước đó, chương trình đã tạo ra một biến là `Block` và xin cấp phát một vùng nhớ `16 * 4 bytes` cho nó. Trên thực tế, `Block` ở đây là một con trỏ cấp 2, nó trỏ tới 1 địa chỉ tạm gọi là `a`. Khi ấy, nếu chúng ta gọi `a[0]` sẽ đồng nghĩa với việc truy cập tới con trỏ cấp 1 tại `a[0]`, và con trỏ này trỏ tới một vùng nhớ có `4 bytes`. Theo hướng này, `Block` sẽ tương ứng với một mảng 2 chiều `4x4`: ![image](https://hackmd.io/_uploads/ryQBKzRZJg.png) ![image](https://hackmd.io/_uploads/r12H5zAbkx.png) - Các offset mà mình highlight cam ở kia chính là các `Block[i]`. Tiếp theo, các bytes của `kernel` sẽ được điền lần lượt vào `Block`: ![image](https://hackmd.io/_uploads/S1bGsGRZyl.png) - Sau cuối, `Block` trở thành: ```python= Block = [ ["D", "u", "a", "_"], ["h", "e", "t", "_"], ["f", "l", "a", "g"], ["_", "d", "a", "y"] ] ``` - Ở bước tiếp theo, chương trình sẽ mã hóa `Block` này, với lần lượt các bước: ![image](https://hackmd.io/_uploads/rkeqoGAZJl.png) - Để tiết kiệm thời gian, mình đã đổi tên hàm luôn và viết một script python mô phỏng toàn bộ quá trình mã hóa này: ```python= def xorElement(Block, a4): for i in range(0, 4): for j in range(0, 4): Block[i][j] = chr(ord(Block[i][j]) ^ a4) result = i+1 return result def shiftLeft(Block): for i in range(0, 4): b = Block[i][i:4] for j in range(0, i): b.append(Block[i][j]) Block[i] = b return Block def transpose(Block): new_Block = [] for i in range(0, 4): new_b = [] for j in range(0, 4): new_b.append(Block[j][i]) new_Block.append(new_b) return new_Block def shiftUp(Block): Block = transpose(Block) Block = shiftLeft(Block) Block = transpose(Block) return Block def reverseElement(Block): for i in range(0, 4): for j in range(0, 4): b = ord(Block[i][j]) b = 16 * (b & 0xF) + (b >> 4) Block[i][j] = chr(b) return Block def encrypt(Block): for m in range(100): Block = shiftLeft(Block) Block = shiftUp(Block) Block = reverseElement(Block) sub_E711A4(Block, m+85) return Block ``` - Kiểm tra kết quả sau loop của chương trình và script của mình với `user_flag = "KCSC{Dua_het_flag_day}"`: ![image](https://hackmd.io/_uploads/HJSvCf0bye.png) ![image](https://hackmd.io/_uploads/BybHpGRbyl.png) ![image](https://hackmd.io/_uploads/B1NLTMAZyl.png) ![image](https://hackmd.io/_uploads/rkGwpzCZke.png) ![image](https://hackmd.io/_uploads/ryD_azRbke.png) - Và để viết lại quá trình giải mã cũng không có gì khó, script: ```python= def rev_shiftLeft(Block): for i in range(3): Block = shiftLeft(Block) return Block def rev_shiftUp(Block): Block = transpose(Block) for i in range(3): Block = shiftLeft(Block) Block = transpose(Block) return Block def rev_reverseElement(Block): for i in range(0, 4): for j in range(0, 4): b = ord(Block[i][j]) b = (b & 0xF) * 16 + (b >> 4) Block[i][j] = chr(b) return Block def decrypt(Block): for m in range(99, -1, -1): xorElement(Block, m+85) Block = rev_reverseElement(Block) Block = rev_shiftUp(Block) Block = rev_shiftLeft(Block) return Block ``` - Các hàm mã hóa các bạn tự phân tích nha, mình viết ví dụ ra thì dài lắm :v, biết rằng kết quả mà chương trình trả ra khớp với script của mình là được. Check hàm giải mã: ![image](https://hackmd.io/_uploads/Hy7ke70Zyg.png) - Nếu đã có hàm mã hóa thì chắc chắn chúng ta sẽ có một checker ở dưới, không đó để tìm ra phần đó: ![image](https://hackmd.io/_uploads/r1s7xQA-1e.png) - Vậy chương trình check chuỗi mã hóa của chúng ta với `Str2 = "FDA6FF91ADA0FDB7ABA9FB91EFAFFAA2"`, kết quả giải mã chuỗi này được: ![image](https://hackmd.io/_uploads/rk7qeQRZkg.png) - Nhưng nếu đem chuỗi này đi submit thì sai lè: ![image](https://hackmd.io/_uploads/S16uZXAZ1e.png) - That's it, câu trả lời cho điều này là chúng ta đã đi vào luồng sai của chương trình. Ban đầu mình check đúng tất cả mọi thứ, đặt bp tại dòng thông báo, nhập flag vào chương trình vẫn báo đúng, tuy nhiên lại sai nếu chạy trên console. Có nghĩa là chúng ta vẫn làm đúng, nhưng không phải trong cùng dòng luồng chuẩn của chương trình :v (giống việc mình sẽ là idol Odin siêu cấp pro nhưng không phải trên vũ trụ này vậy). Vì vậy cần tìm lí do tại sao bị vào luồng sai. - Chall này đã dạy cho mình một điều rằng: trước khi đi xiên, cần phải check kĩ xem có `anti-debug` không. Về `anti-debug` mình sẽ viết sau nhưng các bạn có thể tham khảo trước tại [đây](https://legend.octopuslabs.io/archives/2100/2100.htm) - Một kĩ thuật rất cũ nhưng vẫn hiệu quả là sử dụng hàm `TLS Callback`. Hàm này thực thi trước cả EP (bá vcl), và nó che mắt được cả debugger. Tuy nhiên mình có thể tìm được hàm này: ![image](https://hackmd.io/_uploads/BJVc7QAb1x.png) - Chạy vào xem thử mã giả: ![image](https://hackmd.io/_uploads/r1jhQ7CW1g.png) - Ở đây mình sẽ đặt bp tại `result`, mục đích là để chạy vào `if` kia: ![image](https://hackmd.io/_uploads/HkH7V7Abyg.png) - Sửa `ZF` hoặc `Set IP` và vào đọc thôi: ![image](https://hackmd.io/_uploads/ryoIVXCZyl.png) - Tại đây là một vùng trời mới kiến thức :v. Trước hết, địa chỉ của hàm `sub_78133E` (có thể khác so với các bạn nhưng sẽ cùng mấy bytes cuối tên hàm) được gán vào `Src`. Kế đó, `v4` được gán bằng 104, tức `0x68` - là lệnh `push`, `v5` được gán bằng -61, tức `0xC3` - là lệnh `ret`: ![image](https://hackmd.io/_uploads/ry2MUQCb1e.png) - Tiếp theo, hàm `VirutalProtect` được gọi với các tham số lần lượt như trên, syntax của hàm là: ![image](https://hackmd.io/_uploads/HyQYUm0-kx.png) - Chi tiết các tham số có thể đọc thêm ở [đây](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect). Về cơ bản, chức năng của hàm sẽ là thay đổi quyền trong vùng nhớ đã được cấp phát tĩnh, ở đây là `j_strcmp`. Có thể thấy, tác giả không trực tiếp sử dụng `strcmp` vì `j_strcmp` sẽ thực thi một hàm khác. - Trước khi call `VirutalProtect` và `j_memcpy`:![image](https://hackmd.io/_uploads/Hy97YmCZkl.png) - Sau khi call và chạy `j_memcpy`:![image](https://hackmd.io/_uploads/BythKXC-1e.png) - Hàm được hook trong `j_strcmp` chính là `sub_78133E`: ![image](https://hackmd.io/_uploads/H1bH-4CZke.png) - Về cách mã hóa ở đây khá đơn giản rồi, từ chuỗi `Str2` ở trên, chương trình đã biến nó thành một chuỗi khác. `F4` cho chương trình nhảy thẳng đến dòng 21, ta thu được chuỗi `St2` mới có giá trị `"A8A191A0A7FEFFADFEA5A0BAA9BBA0A6"`. Vác chuỗi này đi giải, ta được: ![image](https://hackmd.io/_uploads/r1ba7VAbye.png) - Và: ![image](https://hackmd.io/_uploads/rJceVEAWJe.png) - Đây chính là flag chuẩn của chúng ta. Note một chút về mảng `v7` ở kia, khi extract lấy data để `xor` thì cần phải lấy chuẩn vì sắp xếp của ida và python là khác nhau, dẫn đến chuỗi ở ida là chuỗi ngược với python. Chính vì vậy mình extract lấy `Str2` mới chứ không lấy `v7` để `xor` vì đảo little/big endian khá mất thời gian. > ~~`KCSC{function_h00k1ng}`~~ ## III. End - That's it, đây là wu cho toàn bộ các bài trong TTV 2023 của KCSC, bài viết của mình có tham khảo từ nhiều nguồn như [wu của anh TungDvan](https://github.com/DecemberRecruitment/SSB3YW50IHRvIGJlY29tZSBhIG1lbWJlciBvZiB0aGUgS0NTQyBjbHViLg-/tree/master/KCSC_RECRUITMENT_2023), [wu của author anh sonx](https://github.com/scrymastic/CTFs/tree/master/KCSC-Recruitment-2023),... Mình đánh giá cao các wu trên vì độ chính xác và hoàn thiện, các bạn có thể tìm đọc thêm. Hi vọng bài viết này hữu ích với các bạn. Dear!!!