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":"[email protected]","height":1.77},
{"lastName":"Jerry","email":"[email protected]","age":8,"height":1.55},
{"email":"[email protected]","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測試的效果。