360ichunqiu 2017-smallest
1.常规checksec,开了一个NX,没办法shellcode。IDA打开查看程序,找漏洞,有个屁的漏洞,只有一个syscall的系统调用,各种栈操作也没有。
2.观察这个系统调用,系统调用参数通过edx,rsi,rdi赋值,edx直接被赋值为400h,buf对应的rsi被rsp赋值,系统调用号fd对应的rdi被rax赋值。再查看汇编代码,有xor rax,rax,所以rax一定是0,那么这个syscall系统调用的就是read函数,读取的数取直接存入栈顶。由于buf大小为400h,且只有一个syscall,之后直接retn,没有leave指令,这就代表了rsp指向的地址就是我们执行完syscall后start函数retn的返回地址(pop eip)。也就是如果输入一个地址,读取完之后,通过retn就会跳转到该地址中。另外程序中除了retn之外没有其它对栈帧进行操作的指令,如果输入多个syscall地址,就可以反复执行syscall。并且最开始输入400h字节,程序流完全可控。
3.首先想到rop,但是题目没给Libc,并且通过调试发现,这个程序压根就没导入外部的Libc库,IDA中打开没有extern,完全没办法常规rop,那么想用SROP。远程调试一下查看堆栈数据,发现临时创建的smallest段数据没有可写权限,能够利用的只有[stack]栈数据。所以这里需要先泄露一个栈地址来让我们能够往栈中写入数据binsh从而调用execve(‘/bin/sh\x00’,0,0)来直接getshell。
4.之后观察栈上的数据,发现当运行到syscall时,rsp下方的内容全是栈上的地址。
rbp一直都是0x000……这是因为程序只有一个start函数,根本就没有为函数再次创建栈,所用的只是最初生成的栈空间。根据这个原理,我们可以通过系统调用sys_write函数,来打印rsp指向的内容,也就是某个栈地址,这样就成功泄露栈地址。
5.但是sys_write的调用号是1,而通过调试发现rax的初始值被默认设置为0,并且程序中没有任何修改rax的代码。唯一一个也只有xor rax, rax,但是任何数和本身异或的结果都是0,所以如果程序每次都从这行代码执行,那么执行的系统调用号永远都是0,也就是会无限循环read。这里想到由于栈完全可控,并且输入一个地址,程序执行完这个地址对应的函数后retn会直接跳转到rsp的下一行。这里选择让程序再执行一次sys_read函数,之后我们为其中一次输入一个字节,并且这次返回不再从xor这行代码开始执行,从mov rsi, rsp开始。由于sys_read的返回值自动写回给rax(一般函数的返回值都会写给rax),所以读取几个字节read就向rax写入多少,这样就会使得rax也可以得到控制,不再被xor为0,调用我们想调用的系统函数。
6.所以编写payload:先尝试一下看能否泄露栈地址,test1.py
1 | #注释头 |
#利用sys_read随便读取一个字符,设置rax = 1,由于retn关系,rsp下拉了一个单位,所以这里会读入到原先的rsp+0x8处,也就是从原先的Payload中第8个字符开始,抽取一个字符,就是set_rsi_rdi_addr的最后一个字节,为了不改变返回地址。如果写成:io.send(‘\xb8’)效果一样,都是为了不改变返回地址。之后再执行set_rsi_rdi_addr从而执行write函数,
1 | #注释头 |
7.这里可以看到成功泄露了一个栈地址,但是不能再用简单读入binsh字符串之后设置SigreturnFrame结构体来getshell,因为这里设置读入地址是通过rsp设置的。如果将rsp设置为我们想读入binsh的栈地址,那么肯定是可以读入binsh字符串的,但是当程序运行到retn时,跳转的是binsh这个地址,这是不合法的,没办法跳转,程序会崩溃。
这里就考虑使用SigreturnFrame()来进行栈劫持,将整个栈挪移到目的地。
(1)首先布置SigreturnFrame()的栈空间,进行栈劫持:
1 | #注释头 |
(2)发送payload。
1 | #注释头 |
程序运行SROP过程中,会执行read函数,将payload2读取到stack_addr处,所以当程序运行完SROP后,栈顶rsp被劫持到stack_addr处,同时stack_addr上保存的内容是payload2,首地址是start,所以retn执行后仍旧从start开始。
(3)设置第二次的SigreturnFrame攻击:
1 | #注释头 |
这里的0x108是计算出来的,需要计算从stack_addr到rdi,也就是binsh字符串的距离。由于传进去的是结构体,大小为0xf8。前一个例子中binsh字符串是放在str(frameExecve)之前,所以没有那么大。这里却是放在str(frame_execve)之后,所以从stack_addr为起始,start_addr,syscall_addr,frame_execve),总共为0xf8+0x08*2=0x108,这里不太懂可以调试一下看看。也就是再一次start_addr读取字符串binsh的位置。
8.发送payload,读取binsh字符串,getshell:
1 | #注释头 |
9.尝试使用mprotect为栈内存添加可执行权限x,从而shellcode来getshell。
(1)第一段的劫持栈和读取payload2进入劫持栈处都是一样的
1 | #注释头 |
(2)第二段需要调用mprotect来修改权限:
1 | #注释头 |
(3)最后的shellcode:
1 | #注释头 |
参考资料:
https://bbs.ichunqiu.com/forum.php?mod=collection&action=view&ctid=157