0x10 cJSON 介绍
0x11 项目简介
Json作为一种轻量级的文本数据交换格式,广为应用,虽然是JavaScript语言中,出现的一个子集,但是早已成为独立的语言格式。其语法格式,类似于Python中的字典。然而,C语言原生并不支持字典,更不用说对Json文件的解析了。
cJSON就这样应运而生,作为一个使用链表实现的库,cJSON的存在是为了尽可能多地消除繁琐的工作,这样一款优秀的开源解析库,大大降低了解析网络编程中频繁使用的json文件的难度。项目地址:https://github.com/DaveGamble/cJSON
cJSON的使用方式也较为简单,主要有以下两种方式:
- 源码编译生成 libcjson.so
- 直接将
cJSON.c
cJSON.h
复制到自己的项目里
0x12 典型源码说明
cJSON定义的数据结构,从下面的代码 cJSON.c
不难看出,cJSON 使用链表的方式存储
typedef struct cJSON {
struct cJSON *next,*prev; /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
struct cJSON *child; /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
int type; /* The type of the item, as above. cjson结构的类型上面宏定义的7中之一*/
char *valuestring; /* The item's string, if type==cJSON_String */
int valueint; /* The item's number, if type==cJSON_Number */
double valuedouble; /* The item's number, if type==cJSON_Number */
char *string; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
} cJSON;
解析json用到的函数(头文件 cjson.h 定义),这也是我们在项目中经常用到的一些函数
extern cJSON *cJSON_Parse(const char *value);//从 给定的json字符串中得到cjson对象
extern char *cJSON_Print(cJSON *item);//从cjson对象中获取有格式的json对象
extern char *cJSON_PrintUnformatted(cJSON *item);//从cjson对象中获取无格式的json对象
extern void cJSON_Delete(cJSON *c);//删除cjson对象,释放链表占用的内存空间
extern int cJSON_GetArraySize(cJSON *array);//获取cjson对象数组成员的个数
extern cJSON *cJSON_GetArrayItem(cJSON *array,int item);//根据下标获取cjosn对象数组中的对象
extern cJSON *cJSON_GetObjectItem(cJSON *object,const char *string);//根据键获取对应的值(cjson对象)
extern const char *cJSON_GetErrorPtr(void);//获取错误字符串
0x20 使用cJSON解析json文件
我们已实际的json格式的数据为例,讲解如何使用cJSON进行解析,所有源码已放在github上,供读者参考,项目地址:https://github.com/liyansong2018/cJsonDemo
将github中的 cJSON.c
cJSON.h
拷贝到自己的项目中,即可使用。在笔者实践过程中,绕了不少弯路,主要是由于,对json文本的不理解,对于解析单个json格式的字符串,直接使用cJSON_Parse(str)
这样的函数,即可转换成 cJSON 格式,但是,对于一个完整的cJSON文件,并不是简单的将文本读出来,转换成字符串,再进行调用。比如应当用下面的代码,即 获取json 对象
读取文件的完整内容
char * buf;
char * read_file(char * file_name)
{
FILE * fp = fopen(file_name, "r");
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
buf = (char *)malloc(size + 1);
rewind(fp);
fread(buf, sizeof(char), size, fp);
buf[size] = '\0';
return buf;
}
0x21 获取json对象
整体的思路就是开辟堆内存,读取文件,将文件的内容利用 cJSON_Parse
转换成指向 cJSON
的指针类型
cJSON * get_json_object(char * file_name)
{
FILE * fp;
long len;
char * content;
cJSON * result;
fp = fopen(file_name, "rb");
fseek(fp, 0, SEEK_END);
len = ftell(fp);
fseek(fp, 0, SEEK_SET);
content = (char *) malloc(len + 1);
fread(content, 1, len, fp);
fclose(fp);
result = cJSON_Parse(content);
free(content);
return result;
}
测试用例如下,注意json文件的格式,每组最后一个键值对是不需要加符号的!这很重要,不然我们的解析程序会报错。
{
"people":[
{"firstName":"Tom","lastName":"Jason","email":"test@aa.com","height":1.77},
{"lastName":"Jerry","email":"Jerry@bb.com","age":8,"height":1.55},
{"email":"cccc@126.com","firstName":"z","lastName":"Juliet","age":36,"height":1.33}
],
"animal":{"dog":"wangcai!"}
}
0x22 解析json文件核心代码
int main(int argc, char const *argv[])
{
cJSON * root = NULL;
cJSON * item = NULL;
root = get_json_object(argv[1]);
if (!root)
{
printf("Error before: [%s]\n",cJSON_GetErrorPtr());
}
else
{
printf("有格式打印: %s\n", cJSON_Print(root));
printf("原始文件: %s\n", cJSON_PrintUnformatted(root));
item = cJSON_GetObjectItem(root, "animal");
if(item)
{
printf("筛选一: %s\n", cJSON_Print(item));
}
item = cJSON_GetObjectItem(item, "dog");
if(item)
{
printf("筛选二: %s\n", cJSON_Print(item));
printf("%s:", item->string); //看一下cjson对象的结构体中这两个成员的意思
printf("%s\n", item->valuestring);
}
printf("打印json所有最内层键值对: %s\n");
print_json(root);
}
return 0;
}
0x23 编译并运行
编译源代码,并且删除中间文件。项目地址:https://github.com/liyansong2018/cJsonDemo
结果如下所示
0x30 使用afl对cJSON库进行fuzz测试
如果想使用afl-fuzz对cJSON组件进行安全测试,需要修改项目中的主文件 main
,44 行改为 45 行,这是因为,afl-fuzz需要从输入的文件直接读取字符串,而不是直接解析json文件,请注意甄别。
改完代码之后,需要重写 Makefile,这里,项目已经写好了备份文件,直接使用以下命令替换即可
这时,再运行命令
afl-fuzz -m none -i in -o out ./main @@
成功对cJSON组件进行fuzz测试
0x40 总结
本文介绍了如何使用cJSON库,对json数据格式的文件进行解析,cJSON这样一款优秀的开源轻量级json解析库还有很多功能,比如创建json文件,限于篇幅,只是介绍了如何使用它进行数据解析。并且最后,结合afl,详细讲述利用源码编译的方式进行插桩,达到fuzz测试的效果。