ctfshow pwn67
这个题加深了对栈结构的理解,具体题解如下:
下载附件,checksec一下,发现开启了canary保护,32位。其他保护没开。丢到IDA去看一下:
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
不难发现通过fgets((char *)seed, 4096, stdin); 写入shellcode,然后想办法让程序能跳转到shellcode地址那里去执行,就可以了,这里有个__isoc99_scanf("%p", &v5); 可以输入地址,我们可以输入shellcode写入的首地址,然后 v5();调用就可以getshell了。
所以难点就是怎么去确定shellcode的首地址,也就是seed的地址。刚好这里给了我们一个 position = query_position(); ,可以把此时的一个栈上面变量的地址去泄露出来,然后通过栈帧结构分布,去推算seed的地址。这里更正我之前一个错误观点,之前认为不同函数栈帧之间是割裂的,其实不是,在整体上面,它们是存在联系的(等下看个图就可以明白) 。 我们跟进query_position()函数去看一下:
1 | char *query_position() |
设置的干扰就是这里的v2,是个随机数,但是有范围。返回的是局部变量v1的地址加上v2的,可以由此推算出v1的变化范围,然后结合栈帧结构去推算seed的一个范围。大概思路就是这样。
可以去手算栈帧结构,但是过于麻烦,这里可以通过gdb去调试下:

可以看到进入query_position(); 函数前的ebp和进入之后ebp的一个对比值。0xffffcf28 是 seed 变量对应的ebp,0xffffbf08是v1变量对应的ebp。画个简单的草图看一下相对位置:

通过这个图可以很清晰的看出来v1与seed之间的偏移。接下来就是解决rand的问题了。这里有个知识点是空操作雪橇的知识点。nop sled ,nop是一个不执行任何操作的命令,效果是可以使IP寄存器+1。我们虽然不知道rand具体是多少,也就确认不了shellcode首地址具体是多少,但是可以往里面填充nop指令,让他不停地+1,+1,+1,直到shellcode 。具体效果图如下:

postion=v1+v2(-668<=v2<=668) position+668=v1~v1+1336
那么我们的payload就是\x90*1336+asm(shellcraft.sh()) ,v5输入的地址就是 position+668+padding(v1和seed之间的偏移)
最终的脚本:
1 | from pwn import * |