CSAW Quals CTF 2017-pilot

1.常规check,发现这破程序啥保护也没开,而且还存在RWX段:

img

这不瞎搞嘛。之后IDA找漏洞,发现栈溢出:

1
2
3
4
#注释头

char buf; // [rsp+0h] [rbp-20h]
if ( read(0, &buf, 0x40uLL) > 4 ):

2.这里就可以思考下攻击思路,存在栈溢出,还有RWX段,考虑用shellcode。虽然这个RWX段是随机生成的栈,地址没办法确定。再看看程序,发现程序自己给我们泄露了buf的栈地址:

img

也就是说紧跟再location后面的打印出来的就是buf的真实栈地址,这样我们就可以接受该栈地址,然后用栈溢出使得我们的main函数返回时可以跳转到对应的buf地址上,而buf地址上的内容就是我们的输入,也就是输入的shellcode,这样就可以执行我们的shellcode了。

3.但是写完shellcode会发现程序崩溃,这里进入IDA调试一下shellcode。可以发现程序运行过程中,Main函数return执行之后,跳转到shellcode的地方,然后运行shellcode。但是这一过程中,栈顶指向了main函数return的地址。所以在运行shellcode过程中,由于shellcode中有一个push rbx命令,导致rsp向上移动8个字节会覆盖掉shellcode的首地址。本来这没啥事,毕竟已经进入到shellcode当中去了,但是后面还有push rax和push rdi这两个改变rsp的命令,这就导致了rsp再次向低地址覆盖了16个字节,总共覆盖了24个字节。但是我们输入的shellcode有48个字节,顺序为shellcode+nop*10+addr_shellcode,也就是扣掉最后18个字节,还多出来6个字节覆盖掉了我们的执行代码shellcode的最后6个字节代码,导致我们的shellcode没办法完全执行,最终导致程序出错。

4.由于read函数允许我们输入0x40,也就是64个字节,也就是在覆盖掉返回地址之后,我们还能再输入64-48=16个字节。由于push rdi之后的片段为8个字节(包括了push rdi),小于16个字节,能够容纳下我们被覆盖掉的shellcode的代码,所以这里我们可以考虑用拼接的方式来把shellcode完美执行。

5.现在考虑如何把两段shellcode汇编代码连在一起。有call,return和jmp,但是前面两条指令中,call会push进函数地址,而return也会修改栈和寄存器的状态,ret指令的本质是pop eip,即把当前栈顶的内容作为内存地址进行跳转。所以只能选择jmp跳转。

6.可以查阅Intel开发者手册或其他资料找到jmp对应的字节码,或者这个程序中带了一条Jmp可以加以利用。为EB,jmp x总共2个字节:EB x.

7.将两段隔开,从push rdi开始,将push rdi和之后的代码都挪到下一个地方。这时第一段shellcode应该是22+2(jmp x)=24个字节,距离下段shellcode的距离应该是48-24=24,也就对应0x18h,所以总的shellcode应该是shellcode1+EB 18h+shellcode2,这样可以顺利执行需要的shellcode。

▲jmp的跳转计算距离是从jmp指令下一条开始计算的。

▲shellcode的两段执行:

1.需要泄露地址,读取泄露地址:

A.print io.recvuntil(“Location:”)#读取到即将泄露地址的地方。

B.shellcode_address_at_stack = int(io.recv()[0:14], 16)#将泄露出来的地址转换为数字流

C.log.info(“Leak stack address = %x”, shellcode_address_at_stack)#将泄露地址尝试输出,观察是否泄露成功。

2.需要跳转jmp命令或者是return/call,但是return会pop eip,call会push eip,都会修改掉栈中的内容。如果shellcode的两段执行计算偏移地址的话,可能需要将这两个内容也计算进入。但是jmp就不会需要,是直接无条件跳转,所以大多时候选择jmp比较好。

参考资料:

https://bbs.ichunqiu.com/forum.php?mod=collection&action=view&ctid=157