pwnable.kr-unexploitable
1.常规checksec,只开启了NX。IDA打开找漏洞,程序很简单,读入数据栈溢出,可以读取1295个字节,但是buf只有0x10h大小,所以可以溢出足够长的数据。
2.程序没有后门,没有导入system函数,没有binsh字符串,也没有write、put、printf之类的可以泄露libc的函数,没办法ROP,然后ROPgadget也搜不到syscall。这里换用另一种工具ropper来搜索:ropper –file=unexploitable –search “syscall”,可以搜到一个。有了syscall,可以尝试用rop来给寄存器赋值然后开shell,但是这里还是搜不到给rsi,rdi等寄存器赋值的gadget,这就意味着我们也没办法直接通过ROP实现getshell。
3.如果没有开NX,直接栈劫持然后shellcode就完事了,但是开启了NX,没办法shellcode。
4.啥也不管用的时候,可以用SROP,也就是通过syscall再利用SigreturnFrame来设置寄存器rsi和rid的值,加上字符串binsh可以直接getshell,不用非得设置rsi,rdi寄存器值的rop。但是这里使用SigreturnFrame有限制,需要溢出的长度较长一些,32位下需要依顺序布置栈,64位条件下需要往栈中布置一个结构体,所以需要输入足够长的payload来修改。
5.这里使用的方案是SigreturnFrame,先考虑一段足够长的可修改的内存地址来给我们存放栈劫持的内容。但是在IDA中按ctrl+s查看内存段,发现所有的可改可读的内存段都特别小,没办法存放足够长的溢出内容。这里忽略了一个知识点,临时创建的缓存:也就是我们使用read(0, &buf, 0x50FuLL);时,会临时创建一个长度为0x50F的缓存区间,这个区间足够满足我们的需求,但是在IDA中找不到,那就没办法栈劫持到这个位置。这里可以先动态调试一下,由于没有开启PIE,程序加载后的基地址是固定的,所以无论程序加载多少次,地址仍然不会发生改变。那么转向动态调试,可以看到程序冒出来一个可读可写的内存段:unexploitable,这个就是临时创建的一个缓存区间,长度为F88,足够用来执行操作。
6.在这个区间上任意选取一个地址来栈劫持,这里选择0x60116c,然后编写payload,尝试能否成功栈劫持并且读入binsh:
1 | #注释头 |
这样接下来如果再输入binsh字符串,就可以读取到[rbp+buf]处。需要注意的是,这里的set_read_addr是从下图最开始跳转,如果直接跳转call read,那么就会由于read取参是edx,rsi,edi,从而导致数据会存入rsi指向的地址,没办法存到我们劫持的栈中。观察read函数汇编代码可以知道,虽然read存入的地址是rsi,但是rsi是通过rbp+buf来赋值的,所以我们可以通过修改rbp为fake_stack,使得rbp+buf的地址变为fake_stack上的某个地址,再执行下图中的代码,就可以使得read读取的内容直接进入到劫持栈rbp+buf上,也就是fake_stack上。
7.栈劫持完成之后,考虑第二段的payload,也就是输入binsh字符串和后续内容,来执行SigreturnFrame,使用:
1 | #注释头 |
输入字符串binsh,存放在fake_stack_addr-0x10处
1 | #注释头 |
读取完之后,执行leave指令之前的栈底为0x60116c,而leave指令相当于:mov rsp rbp;和pop rbp:
(1)第一条mov rsp rbp之后,0x60116c就被赋值给rsp,也就是rsp指向0x60116c。
(2)第二条pop rbp之后,把0x60116c处的内容赋值给rbp,这里设置0x60116c处的内容为fake_stack_addr+0x10,也就是0x60117c,那么rbp指向0x60117c。rsp下挪一个单位,指向0x60116c+0x08=0x601174。
故leave指令执行完后rsp = 0x601174,rbp = 0x60117c。
▲这里这么设置是有原因的,为了挪动rsp来指向0x601174。
1 | #注释头 |
接着执行retn指令,相当于pop eip,此时的rsp指向 0x601174,所以我们需要将0x601174处的值变为read_addr的地址,也就是这条语句,这里设置read_addr为0x400571,也就是带有call指令的read。
1 | 注释头 |
retn指令之后就是call指令,各种寄存器的值还是没变,所以照常用就行,回来之后rsp仍旧指向0x60117c。此时栈状态为:
rsp = 0x60117c,rbp = 0x60117c。
1 | #注释头 |
读取15个字符到0x60115c,目的是利用read返回值为读取的字节数的特性设置rax=0xf,注意不要使/bin/sh\x00字符串发生改变。
最后io.interactive()即可getshell。
▲总的程序流应该是:首次read->set_read->call_read->syscall
结构体的设置,固定模式:
1 | #注释头 |
开头设置:
1 | #注释头 |
参考资料:
https://bbs.ichunqiu.com/forum.php?mod=collection&action=view&ctid=157