本章目的
augmented process emulation
的实现方式user_mode
<->system mode
的转换方式- 如何代替fork server
- copy-on-write的实现
- 如何针对
network-related
的系统调用进行feed seed_input
- 如何检测到系统调用来自于被fuzz的程序,而不是其他的系统服务
Firm-AFL
的启动过程【分析启动脚本】- busybox->program
1. /usr_mode/linux-user/main.c
这是user mode端的操作
int main()
├─cross_process_mutex_first_init() //创建并初始化互斥量p_mutex_shared,其分配在所创建的共享内存中。
├─parse_mapping_table("mapping_table") //读取mapping_table中的信息【解析如1.1小节】,写入到多个全局变量【mapping_table如何生成?】
├─getconfig("program_analysis", program_analysis) //该函数用于读取FirmAFL_config中的配置,因此这里得到program_analysis为待分析程序的名称
├─FirmAFL_config() //间接调用getconfig,获取opti_str、feed_type、id_str等所有的配置
【此后就是正常QEMU中的/usr_mode/linux-user/main.c中的main()】
1.1 mapping_table(以9050为例)
7fe258e62000 #赋值到hva_start
4023e0 #赋值到start_fork_pc【和FirmAFL_config/9050/FirmAFL_config中的start_fork_pc一致】
0 #对于arm构架以下为16个寄存器的初始值,对于mips构架以下为36个寄存器的初始值,赋值到init_regs
fffffff8
77826bf0
0
1
7fcf3c24
7fcf3c2c
0
419920
fd6
b9
0
77875274
20
ffffff18
402280
7fcf3cf4
777e88c0
7fcf3b68
7fcf3c24
1
4021a8
4023e0
16
91
4023e0
8072d004
0
7782a4e0
7fcf3b50
533bc4
7780bf00
4023e0
a411
10800008
7784ce34 #这里以上都是寄存器的值
400000:41c000:8001875:cgibin #在初始化gva_start和gva_end为0之后,进入循环,继续读取,直到出现子串“####”,循环终止break
42c000:42d000:8101873:cgibin #每一行以":"分割的列,分别对应得到gva_start、gva_end、gva_type、gva_name【GVA相关的变量】
42d000:42e000:100077: #如果gva_name是空的,gva_seq[vir_spa_index] = 0;
777b4000:77812000:8000075:libuClibc-0.9.30.1.so
77812000:77821000:70:
77821000:77822000:8100071:libuClibc-0.9.30.1.so
77822000:77823000:8100073:libuClibc-0.9.30.1.so #如果不是空的,但gva_name和前一行的gva_name一样,同样gva_seq[vir_spa_index] = 0;
77823000:77828000:100073:
77828000:77851000:8000075:libgcc_s.so.1 #否则,gva_seq[vir_spa_index] = gva_seq[vir_spa_index -1] + 1;
77851000:77861000:70:
77861000:77862000:8100073:libgcc_s.so.1 ##以上变量为数组,以vir_spa_index作为index,从0起,每轮循环vir_spa_index ++;
77862000:77867000:8000875:ld-uClibc-0.9.30.1.so
77875000:77876000:100073:
77876000:77877000:8100871:ld-uClibc-0.9.30.1.so
77877000:77878000:8100873:ld-uClibc-0.9.30.1.so
7fcd3000:7fcf5000:100177:
7fff7000:7fff8000:4040075:
########### #当读取到这一行,循环结束,下面进入新的循环
400000:ef2f000:0 #每一行以":"分割的列,分别对应得到virt_addr、phys_addr、prot【感觉像是GVA->GPA页表】
401000:ef7d000:0 #以上变量为数组,以vir_phy_index作为index,从0起,每轮循环vir_phy_index ++;
402000:ef7b000:0
7780b000:134e000:0
419000:ef05000:0
41b000:eee9000:0
77822000:eedb000:1
77826000:ee09000:1
77828000:1314000:0
77829000:1315000:0
7782a000:1316000:0
42c000:eeda000:1
7782e000:1363000:0
7782f000:1364000:0
77833000:1368000:0
7784c000:1320000:0
7784f000:132c000:0
77861000:f18e000:1
77862000:1303000:0
77863000:1304000:0
77865000:1306000:0
77866000:1312000:0
77875000:ef67000:1
77877000:edc5000:1
777b4000:133e000:0
777b5000:133f000:0
777b8000:1376000:0
777b9000:1377000:0
777ba000:1378000:0
777bb000:1379000:0
777bc000:137a000:0
777bd000:137b000:0
777c0000:137e000:0
777cb000:1389000:0
777e3000:13a1000:0
777e8000:13a6000:0
7fcf3000:ef6a000:1
77862000:1303000:0
777cb000:1389000:0
7780b000:134e000:0
777e8000:13a6000:0
########### #当读取到这一行,循环结束;
1998860 #syn_shmem_id,感觉是用于结合user_mode和system_mode的共享内存id【和同步共享内存->copy on write相关】,在start_fork中创建
0 #accept_fd
0 #CP0_UserLocal
2. /qemu_mode/DECAF_qemu_2.10/accel/tcg/cpu-exec.c
这是system mode 端的操作
-
int add_physical_page(int phys_addr)
: 根据phys_addr,增加/更新phys_addr_stored_bitmap -
int if_physical_exist(int phys_addr)
:根据phys_addr_stored_bitmap,判断phys_addr是否已经map过 -
static void convert_endian_4b(uint32_t *data)
:将大小端序相互切换 -
static void read_ptr(CPUState* cpu, int vaddr, uint32_t *pptr)
:读取从vaddr地址起4个字节大小的值到pptr中- 其中间接调用了
cpu_memory_rw_debug(cpu, vaddr, pptr, 4, 0)
,即physical memory access (slow version, mainly for debug)
- 其中间接调用了
-
static void write_ptr(CPUState* cpu, int vaddr, int pptr_addr)
:将4个字节大小的pptr值写到vaddr地址中- 其中间接调用了
cpu_memory_rw_debug(cpu, vaddr, &pptr_addr, 4, 1)
- 其中间接调用了
-
target_ulong getWork(char * ptr, target_ulong sz)
:从aflFile【该文件是本次输入的种子,fuzz的文件输入】中逐个读取字节,共读取sz个到ptr指针中
-
int write_package(CPUState *cpu, int vir_addr, char* cont, int len)
int write_package ├─ DECAF_write_mem(cpu, vir_addr, len, cont) //根据应用程序的vir_addr(GVA),将len长度的cont(buf)待写入数据逐步写到物理真实内存中 | └─ DECAF_memory_rw(env, vaddr, buf, len, 1) //1表示是写(is_write) | ├─ DECAF_get_phys_addr(env, page) //计算addr对应的虚拟页的首地址,然后获取该页对应的物理首地址phys_addr【应该是GVA->GPA】 | | └─ _DECAF_get_phys_addr(env, addr) | | └─ cpu_get_phys_page_debug(env, addr & TARGET_PAGE_MASK) //Obtains the physical page corresponding to a virtual one. | └─ cpu_physical_memory_rw(phys_addr + (addr & ~TARGET_PAGE_MASK), buf, l,is_write) //从物理页中读取数据,逐页逐页读取【循环获取物理页】,直到读满len长度 | └─ address_space_rw(&address_space_memory, addr, MEMTXATTRS_UNSPECIFIED, | buf, len, is_write) //访问AddressSpace中的MemoryRegion进行读操作【GPA->HVA】 └─ return vir_addr + len //返回指向下一个待写位置
-
void prepare_feed_input(CPUState * cpu)
//以下全局变量,涉及多个process的集合信息(from vmi.cpp) //map cr3 to process_info_t unordered_map < uint32_t, process * >process_map; //根据cr3/pgd找process进程【map<first(cr3),second(process)>】 //map pid to process_info_t unordered_map < uint32_t, process * >process_pid_map; //根据pid找process进程【map<first(pid),second(process)>】 // module list unordered_map < string, module * >module_name;
void prepare_feed_input //根据config文件中的fuzz类型配置不同,进行不同的种子准备。当完成种子准备,则设置pre_feed_finish为1 ├─ strcmp(feed_type, "FEED_ENV") ├─ DECAF_getPGD(cpu) // return the page table directory (e.g., CR3 in x86 and CP15 in ARM)【此后用于获取当前对应执行的进程】 ├─ print_mapping(modname, pgd, &base, fp2) └─ traverse_mmap_new(current_cpu, proc, fp) //遍历进程的vma_area_struct,将整个进程的虚拟内存空间的布局情况打印/写到“mapping”文件中【得到进程的虚拟空间布局,解析得到libc.so的加载基地址,从而用于之后获取_environ变量的真实加载地址,便于fuzz环境变量】 ├─ fp3 = fopen("mapping", "r") //重新读取mapping文件,获取name ├─ libuclibc_addr = gva_start //根据FirmAFL-config文件中设置的lib_name,确定libc的加载基地址 ├─ global_addr = libuclibc_addr + environ_offset //计算得到libuClibc.so加载后1的environ的虚拟地址 ├─ read_ptr(cpu, environ_addr, &content_addr) //读取environ指针指向的环境变量指针数组到content_addr中 └─ get_page_addr_code(env, content_addr) //???【参考:https://blog.csdn.net/alloc_young/article/details/7741779】,目的应该是把GPA->HVA,如果tlb命中就直接得到HVA,否则就进行softMMU地址翻译【如果不获取返回值,很可能只是触发TLB cache】 ├─ strcmp(feed_type, "FEED_HTTP") //不进行任何准备,且不设置pre_feed_finish为1 ├─ strcmp(feed_type, "FEED_CMD") // ??? ├─ strcmp(feed_type, "NONE") └─ DECAF_printf("feed type error\n");
void traverse_mmap_new(CPUState *env, void *opaque, FILE *fp)
Linux内核中虚存管理的最基本单元vm_area_struct 参考:http://abcdxyzk.github.io/blog/2015/09/09/kernel-mm-vm_area/
tb_page_addr_t get_page_addr_code(CPUArchState *env, target_ulong addr)
:感觉像是模拟访问一个页,使得TLB填充(??)
-
int check_input(char * input, int len)
:检测在‘=’之前,是否都是可读字符 -
int check_http_header(char * input)
:检测id为9925、10853、161161的程序http头部是否是指定的值。program_id input header 9925 GET /session_login.php HTTP/1.1 10853 POST /HNAP1/ HTTP/1.1 161161 POST /apply.cgi HTTP/1.1 -
int feed_input(CPUState * cpu)
:int feed_input(CPUState * cpu) ├─ strcmp(feed_type, "FEED_ENV") └─ getWork(input_buf, MAX_LEN) //aflFile【fuzz种子文件】共读取MAX_LEN个到input_buf指针中,eg:HTTP_COOKIE=zywzyw…… └─ DECAF_write_mem(cpu, content_addr, get_len, input_buf) //aflfile中的内容写入content_addr中【环境变量】 ├─ strcmp(feed_type, "FEED_HTTP") ├─ total_len = getWork(recv_buf, 4096) //从aflfile中读取了4096的字节到recv_buf【全局变量】,长度为total_len └─ check_http_header(recv_buf) //种子文件变异时,http请求包的头部不能有更改 ├─ strcmp(feed_type, "FEED_CMD") // ??? ├─ strcmp(feed_type, "NONE") └─ DECAF_printf("feed type error\n");
-
target_ulong program_pgd[100]
:程序页表管理栈void pgd_push(target_ulong pgd)
:push pgd入栈target_ulong pgd_pop()
:pop pgd出栈且返回target_ulong get_current_pgd()
:获取当前栈顶部的pgd
-
static void fuzz_mem_write(DECAF_Callback_Params *dcp)
static void fuzz_mem_write(DECAF_Callback_Params *dcp) ├─ afl_user_fork == 1 //?? ├─ 读取DECAF_Callback_Params *dcp.mw相关信息 //监视内存写DECAF_Mem_Write_Params信息,读取vaddr[GVA]、paddr[GPA]、haddr[HVA]、data_type、value等 ├─ if_physical_exist(phys_addr & 0xfffff000) //如果物理页已经被映射过,在phys_addr_stored_bitmap中 └─ return ├─ add_physical_page(phys_addr & 0xfffff000) //否则将这个物理页更新在phys_addr_stored_bitmap中【因为在写操作发生时,这个页已经被成功加载到物理内存中了】 └─ store_page(virt_addr & 0xfffff000, page_host_addr, in_httpd) //存储该页,用于未来fuzz fork的恢复 └─ return
// DECAF提供了一个回调函数联合体,根据不同的事件,提供不同的参数。 typedef struct _DECAF_Callback_Params { DECAF_Handle cbhandle; union{ DECAF_Block_Begin_Params bb; DECAF_Block_End_Params be; ////翻译块执行结束事件,这一类型中有四个变量:当前CPU状态、翻译块指针、当前程序PC值和下一个PC值 DECAF_Insn_Begin_Params ib; DECAF_Insn_End_Params ie; DECAF_Mem_Read_Params mr; DECAF_Mem_Write_Params mw; // …… DECAF_Read_Taint_Mem rt; DECAF_Write_Taint_Mem wt; #ifdef CONFIG_TCG_LLVM DECAF_Block_Trans_Params bt; #endif /* CONFIG_TCG_LLVM */ }; } DECAF_Callback_Params; typedef struct _DECAF_Mem_Write_Params { gva_t vaddr; gpa_t paddr; DATA_TYPE dt; unsigned long value; #ifdef STORE_PAGE_FUNC unsigned long haddr; int caller_pos; #endif }DECAF_Mem_Write_Params;
-
void store_page(uint32_t virt_addr, uintptr_t addr, int in_httpd)
:将当前即将被写入的页的原数据拷贝到DECAF进程的某个堆位置存储起来,并且将被写入页的信息存储在STORE_PAGE *pt[0x1000]中,用于之后的restore_page
函数。typedef struct STORE_PAGE{ uintptr_t prev_addr; uintptr_t curr_addr; struct STORE_PAGE * page; //hash碰撞时,用于构建二级hash } STORE_PAGE; //这个结构体应该时用于Copy-on-write中write时,暂存需要拷贝的页面 STORE_PAGE *pt[0x1000]; //virt_addr为GVA,addr为HVA void store_page(uint32_t virt_addr, uintptr_t addr, int in_httpd) { full_store_count += 1; int page_exist = 0; int index = (addr >> 12) & 0xfff; //hash-key STORE_PAGE * tmp_p = pt[index]; //hash STORE_PAGE * last_p; if(tmp_p == NULL) { STORE_PAGE * page = malloc(sizeof(STORE_PAGE)); void * dst = malloc(0x1000); //在DECAF进程的堆空间中分配了0x1000页大小的空间 memset(dst, 0, 0x1000); void * src = addr; memcpy(dst, src, 0x1000); //把HVA【应该也是DECAF进程】中的数据拷贝到DECAF进程的另外一个位置(堆空间)中。这样原来的位置就可以修改,堆HVA中的内容就是write之前的数据 //full_store_count += 1; uint32_t phys_addr = qemu_ram_addr_from_host(addr); //通过这个函数将HVA转换为GPA //show_store_phys(); //printf("index1:%x, copy finished from %lx(%lx) to %lx, int_httpd:%x\n", virt_addr, src, phys_addr, dst, in_httpd); page->prev_addr = src; //拷贝页的原位置【HVA】,未来将会被污染。 page->curr_addr = dst; //拷贝页(写之前页的数据内容)在curr_addr【HVA】中(用于fork时还原) page->page = NULL; pt[index] = page; //放入store_page hash中,用于未来fork时进行恢复 return; } while(tmp_p!= NULL) //hash碰撞 { last_p = tmp_p; //printf("store page:%lx,%lx\n", addr, tmp_p->prev_addr); if(addr == tmp_p->prev_addr){ //prev_addr的意义在于记录被写入页的原位置,那么二次写入某个页的时候会触发store_page,此时如果这个页已经在STORE_PAGE *pt[0x1000]中,则不要二次存储及恢复 page_exist = 1; return; } //printf("into list\n"); tmp_p = tmp_p->page; //进入二级hash,遍历 } if(page_exist == 0){ //如果这个页曾经没有被store过/被写入过,但是却触发了hash碰撞,那么就插入二级hash STORE_PAGE * page = malloc(sizeof(STORE_PAGE)); void * dst = malloc(0x1000); memset(dst, 0, 0x1000); void * src = addr; memcpy(dst, src, 0x1000); //full_store_count += 1; uint32_t phys_addr = qemu_ram_addr_from_host(addr); //printf("index2:%x, copy finished from %lx(%lx) to %lx, in_httpd:%x\n", virt_addr , src, phys_addr, dst, in_httpd); page->prev_addr = src; page->curr_addr = dst; page->page = NULL; last_p->page = page; //插入二级hash表 } }
2.1 Copy on write之store page
//在vl.c/main()中,有对callback函数回调的注册初始化
#ifdef DECAF
DECAF_blocks_init(); //zyw
DECAF_init(); //zyw
callbacktests_init();
#endif
//callbacktests_init()的定义在accel/tcg/cpu-exec.c中
int callbacktests_init(void)
{
DECAF_output_init(NULL);
DECAF_printf("Hello World\n");
processbegin_handle = VMI_register_callback(VMI_CREATEPROC_CB, &callbacktests_loadmainmodule_callback, NULL);
removeproc_handle = VMI_register_callback(VMI_REMOVEPROC_CB, &callbacktests_removeproc_callback, NULL);
block_begin_handle = DECAF_registerOptimizedBlockBeginCallback(&do_block_begin, NULL, INV_ADDR, OCB_ALL);
#ifdef STORE_PAGE_FUNC
//block_end_handle = DECAF_registerOptimizedBlockEndCallback(&do_block_end, NULL, INV_ADDR, INV_ADDR);
mem_write_cb_handle = DECAF_register_callback(DECAF_MEM_WRITE_CB,fuzz_mem_write,NULL); //这里注册了对内存写之前,调用fuzz_mem_write,从而触发store_page
#endif
return (0);
}
-
static void callbacktests_loadmainmodule_callback(VMI_Callback_Params* params)
:当有新的进程创建时(VMI监视),判断是否时被fuzz的程序,若是则将其虚拟布局写入mapping1111中。typedef union _VMI_Callback_Params //支持的callback类型 { VMI_CreateProc_Params cp; VMI_RemoveProc_Params rp; VMI_LoadModule_Params lm; VMI_RemoveModule_Params rm; //LoadMainModule_Params lmm; } VMI_Callback_Params;
-
根据DECAF的事件驱动机制,用户可选择所关注的事件进行注册,并根据这个事件的发生,获取系统信息,进行相应的处理与分析,eg:
- 注册新进程开始事件:handle_load_mainmodule=VMI_register_callback(VMI_CREATEPROC_CB,ProcessTrace_loadmainmodule_notify,&should_monitor);
- 对注册的时间关联函数进行注销:handle_remov_process=VMI_register_callback(VMI_REMOVEPROC_CB,ProcessTrace_procexit,&should_monitor);
-
当新的进程开始运行这一事件发生后,所需要进行的操作如下。首先需要判断新开始运行的这个进程是否为被测进程,这可以通过获取QEMU当前系统环境信息实现。具体数据结构设计如下:
typedef struct _VMI_CreateProc_Params { uint32_t pid; //新进程PID uint32_t cr3; //新进程CR3 char *name; //新进程名称。通过这个参数,判断当前程序的名称是否与用户输入要监控的进程名称一致。如果不一致,继续等待下一个新进程被运行; }VMI_CreateProc_Params;
-
被监控程序需要采用如下数据结构来保存相关信息。
struct monitored_proc { char name[512]; //被测进程名称 char configfile[512]; //编译文件 uint32_t cr3; //被测进程CR3 uint32_t pid; //被测进程PID FILE *tracefile; //记录文件地址 };
static void callbacktests_loadmainmodule_callback ├─ VMI_find_process_by_cr3_all(params->cp.cr3, procname, 64, &pid, &par_pid); //VMI监视所有进程时,获取新进程创建的VMI_CreateProc_Params cp信息,根据cr3,获取进程的名称、pid和父进程pid信息 └─ p = VMI_find_process_by_pgd(cr3) //通过页表指针(cr3)确定进程操作结构体process,从而得到p->name、p->pid、p->parent_pid ├─ strcmp(procname,program_analysis) //判断新创建的进程和program_analysis(要fuzz的程序)是否一致 ├─ VMI_find_process_by_pid_c(par_pid, par_proc_name, 100, &par_cr3) //根据父进程pid,获得httpd_pgd父进程的信息 ├─ pgd_push(params->cp.cr3) //将页表push到program_pgd栈中,且从栈顶读取出来,存到httpd_pgd变量中 ├─ FILE *fp3 = fopen("mapping1111", "w") //打开mapping11111文件 └─ print_mapping(modname2, httpd_pgd, &base2, fp3) //解析通过pgd搜索process_map,得到进程操作结构体process,从而获得fuzz进程的虚存布局,写到mapping文件中【感觉就是mapping_table那个文件中的内容,vma_vm_start、vma_vm_end、vma_flags、name】
-
-
static void callbacktests_removeproc_callback(VMI_Callback_Params* params)
:当有进程销毁时(VMI监视),判断是否时被fuzz的程序,若是则将其从program_pgd栈中pop掉,更新httpd_pgd。static void callbacktests_removeproc_callback ├─ VMI_find_process_by_cr3_c(params->rp.cr3, procname, 64, &pid); //VMI监视所有进程时,获取销毁进程的VMI_CreateProc_Params cp信息,根据cr3,获取进程的名称、pid和父进程pid信息 ├─ strcmp(procname,program_analysis) //判断当前销毁的进程和program_analysis(要fuzz的程序)是否一致 ├─ tmp_pgd = pgd_pop(); //将页表从program_pgd栈中pop掉 └─ httpd_pgd = get_current_pgd() //并且将program_pgd的栈顶,复制到httpd_pgd【也就是说httpd_pgd维护着最上级的进程】
注意:httpd_pgd维护了当前系统的正在执行/最顶级的进程PGA
-
int callbacktests_init(void)
:注册VMI需要监视的内容(可选的有进程创建、进程销毁、进程中模块加载和模块卸载。int callbacktests_init(void) { DECAF_output_init(NULL); DECAF_printf("Hello World\n"); //VMI监视[DECAF的VMI组件能够重建虚拟机的全新OS级视图,包括进程,线程,代码模块和符号以支持二进制分析] processbegin_handle = VMI_register_callback(VMI_CREATEPROC_CB, &callbacktests_loadmainmodule_callback, NULL); //注册新进程开始事件,这里注册了上述函数callbacktests_loadmainmodule_callback removeproc_handle = VMI_register_callback(VMI_REMOVEPROC_CB, &callbacktests_removeproc_callback, NULL); //在被测程序关闭后,监控工作也应该自动结束,且完成一些进程销毁的数据维护工作,这里注册了上述函数callbacktests_removeproc_callback //DECAF注册事件发生时要调用的相关函数 block_begin_handle = DECAF_registerOptimizedBlockBeginCallback(&do_block_begin, NULL, INV_ADDR, OCB_ALL); //在block执行前调用do_block_begin #ifdef STORE_PAGE_FUNC //block_end_handle = DECAF_registerOptimizedBlockEndCallback(&do_block_end, NULL, INV_ADDR, INV_ADDR); mem_write_cb_handle = DECAF_register_callback(DECAF_MEM_WRITE_CB,fuzz_mem_write,NULL); //是对上述两个函数的复杂封装,当存在对内存进行写操作前,回调fuzz_mem_write,触发store_page #endif return (0); } //DECAF_register_callback可以注册的时间类型 typedef enum { DECAF_BLOCK_BEGIN_CB = 0, DECAF_BLOCK_END_CB, DECAF_INSN_BEGIN_CB, DECAF_INSN_END_CB, DECAF_MEM_READ_CB, DECAF_MEM_WRITE_CB, DECAF_EIP_CHECK_CB, DECAF_KEYSTROKE_CB,//keystroke event DECAF_NIC_REC_CB, DECAF_NIC_SEND_CB, DECAF_OPCODE_RANGE_CB, DECAF_TLB_EXEC_CB, DECAF_READ_TAINTMEM_CB, DECAF_WRITE_TAINTMEM_CB, #ifdef CONFIG_TCG_LLVM DECAF_BLOCK_TRANS_CB, #endif /* CONFIG_TCG_LLVM */ DECAF_LAST_CB, //place holder for the last position, no other uses. } DECAF_callback_type_t;
2.2 覆盖率信息统计
-
static inline tcg_target_ulong cpu_tb_exec(CPUState **cpu*, TranslationBlock **itb*)
:该函数执行了一个翻译块,同时调用了AFL记录边覆盖率信息。#ifdef FUZZ if(afl_user_fork && !into_syscall && itb->pc < kernel_base) //important { target_ulong pgd = DECAF_getPGD(cpu); if(pgd == httpd_pgd) //如果当前执行的进程是要fuzz的进程,那么就记录覆盖率的信息 { //last_log_pc = itb->pc; CPUArchState *env = cpu->env_ptr; AFL_QEMU_CPU_SNIPPET2; //AFL QEMU MODE: qemu首先会在每次执行一个基本块前调用 AFL_QEMU_CPU_SNIPPET2 来和 afl 通信 //这段snippet调用了afl_maybe_log(pc),统计了在共享内存afl_area_ptr中统计了边覆盖率信息 } } #endif
-
void copy_tlb_helper(CPUTLBEntry *d, CPUTLBEntry *s,bool atomic_set)
:复制TLB条目 -
void print_tlb(CPUArchState *env, FILE * fp)
:将TLB表的数据【页基地址的GVA、GPA、proc】写入fp关联的文件【即最后写入了mapping_table中的#####
之间。- 需要在fork的时候,进行copy的就是addr_write不为-1的页表项对应的页,也就是mapping_table中
#####
之间的每行的最后一个选项不为0,则需要copy【初始设置了权限都为可读,如果更新为写权限,则说明被写过】
//tlb 的结构如下,addr_xxx 表示 GVA 地址,同时也表示了执行权限【如果有该权限则不为-1】; addrend = hva_base – gva_base; //轉換 GVA 到 HVA 的過程中,會先搜尋 TLB。如果命中,則將 GVA 加上該偏移量得到 HVA。若否,則需填充TLB typedef struct CPUTLBEntry { target_ulong addr_read; // 可读[GVA] target_ulong addr_write; // 可写 target_ulong addr_code; // 可执行 unsigned long addend; //存放 GVA 相對於 HVA 的偏移量。 } CPUTLBEntry; qemu_ram_addr_from_host(addr + addend); //HVA->GPA //因此可以实现GVA ---CPUTLBEntry.addend---> HVA ---qemu_ram_addr_from_host---> GPA //即GVA -> GPA
- 需要在fork的时候,进行copy的就是addr_write不为-1的页表项对应的页,也就是mapping_table中
void skip_syscall(CPUState *cpu, int ret_value, int error_value)
:跳过一个syscall调用,即使得pc指向syscall以后的下一条指令int determine_if_end(int *program_id*)
:进行了read、select、poll、epoll等IO多路复用系统调用的判断int determine_if_network_recv(int program_id, CPUState *cpu)
:判断是不是来自待fuzz程序(根据http_pgd判断)的read、recv、recvfrom系统调用,是的话返回1,并且设置handle_recv=1
表示当前需要处理一个recv相关的系统调用int specify_fork_pc(CPUState *cpu)
:确定start_fork_pc
、ori_thread
、stack_mask
等值void handle_accept_after(CPUState *cpu, target_ulong pc)
:
2.3 构建mapping_table文件
-
int start_fork(CPUState **cpu*, target_ulong *pc*)
:记录了mapping_table文件以及设置了afl_user_fork
、afl_wants_cpu_to_stop
、afl_start_code
、afl_end_code
的值。【感觉是fork之前的信息收集】int start_fork(CPUState *cpu, target_ulong pc) { CPUArchState * env = cpu->env_ptr; #if defined(FUZZ) || defined(MEM_MAPPING) #ifdef LMBENCH if(pc == 0x402950 && fork_times == 0) //test #else if(pc == start_fork_pc && fork_times == 0) //?////????????? #endif //LMBENCH { target_ulong pgd = DECAF_getPGD(cpu); #ifdef DECAF #ifdef LMBENCH if(pgd == httpd_pgd) //if(pgd != 0 && httpd_pgd !=0 ) { #else if(pgd == httpd_pgd) //确定是当前待fuzz的程序 { #endif //lmbench #endif //DECAF fork_times = 1; printf("start_fork\n"); char modname[512]; target_ulong base; #if defined(FUZZ) prepare_feed_input(cpu); //做input种子之前的准备工作 FILE * fp2 = fopen("mapping", "w"); print_mapping(modname, pgd, &base, fp2);// obtain mapping,将待fuzz进程当前的虚拟内存布局写道fp2——mapping文件中 fclose(fp2); FILE * fp3 = fopen("mapping", "r"); char strline[100]; while(fgets(strline, 100, fp3)!=NULL) //逐行读取mapping文件 { char *p1 = strtok(strline, ":"); char *p2 = strtok(NULL, ":"); char *p3 = strtok(NULL, ":"); char *p4 = strtok(NULL, ":"); p4[strlen(p4)-1]='\0'; if(strcmp(p4, "libuClibc-0.9.30.so") ==0) { int gva_start = strtol(p1,NULL, 16); libuclibc_addr = gva_start; //确定libc的加载基地址 DECAF_printf("libuclibc addr:%x\n", libuclibc_addr); break; } } fclose(fp3); #endif //FUZZ #ifdef MEM_MAPPING char mapping_filename[256]; getconfig("mapping_filename", mapping_filename); //mapping_filename = mapping_table文件 assert(strlen(mapping_filename)>0); FILE * fp = fopen(mapping_filename, "a+"); printf("map file:%s\n", mapping_filename); fprintf(fp,"%x\n", start_fork_pc); //在int cpu_exec(CPUState *cpu)中会在调用本函数之前调用specify_fork_pc(),得到start_fork_pc,从而写入mapping_table文件 #ifdef TARGET_MIPS for(int i=0;i<32;i++) { //记录mips的32个通用寄存器和四个特殊寄存器 fprintf(fp, "%x\n", env->active_tc.gpr[i]); } fprintf(fp, "%x\n", env->active_tc.PC); fprintf(fp ,"%x\n", env->CP0_Status); fprintf(fp ,"%x\n", env->CP0_Cause); fprintf(fp ,"%x\n", env->CP0_EPC); #elif defined(TARGET_ARM) for(int i=0;i<16;i++) { //记录arm的32个寄存器 fprintf(fp, "%x\n", env->regs[i]); } #endif //TARGET_MIPS DECAF_printf("print_mapping for %x\n", pgd); print_mapping(modname, pgd, &base, fp);// obtain mapping,获取该fuzz进程的当前虚存布局【mapping_table中的虚存布局时程序执行到fork点的情况,而mapping1111是程序刚刚创建时的虚存布局,存在一些不一致的地方】 //memory for snapshot consistency fprintf(fp, "###########\n"); print_tlb(env, fp); //将guest OS的完整页表(GVA->GPA)写入mapping_table文件 fprintf(fp, "###########\n"); #ifdef SNAPSHOT_SYNC /* IPC_CREAT //如果不存在就创建 IPC_EXCL //如果存在则返回失败 */ int shmem_id = shmget(0, 8192, IPC_CREAT|IPC_EXCL); //0xfffffff(7 bit) orig:131072 fprintf(fp, "%d\n", shmem_id); //将共享内存id写到·mapping_table中 void * shmem_start = shmat(shmem_id, NULL, 1); //将共享内存附加到DECAF进程中【sys mode】 memset(shmem_start, 0, 8192); phys_addr_stored_bitmap = (char *)shmem_start; //在共享内存的起始地址作为phys_addr_stored_bitmap指向的位置 printf("share mem id:%d, shmem_start: %x\n", shmem_id, shmem_start); #endif printf("accept fd:%d\n", accept_fd); fprintf(fp, "%d\n", accept_fd); //记录accept()返回的socket fprintf(fp, "%x\n", CP0_UserLocal); //??在cpu_exec()中有 fclose(fp); #endif //MEM_MAPPING #if defined(FUZZ) && !defined(MEM_MAPPING) #ifdef TARGET_MIPS startTrace(cpu, 0, 0x7fffffff); //设置afl_start_code~afl_end_code = 0 ~ 0x7fffffff #elif defined(TARGET_ARM) startTrace(cpu, 0, 0xbfffffff); #endif afl_user_fork = 1; //fork标志? #endif //exit_status = 0; afl_wants_cpu_to_stop = 1; //fork之后要求cpu stop? return 1; //goto end; #ifdef DECAF } #endif } #endif //defined(FUZZ) || defined(MEM_MAPPING) return 0; }
-
记录accept_fd和recv的意义?
3. /qemu_mode/DECAF_qemu_2.10/cpus.c
static void gotPipeNotification(void *ctx)
:lightweight snapshot
qemu_tcg_cpu_thread_fn
及其附近的函数还未阅读
4. AFL
- 在
cpus.c/gotPipeNotification(void *ctx)
中启动forkserver() - 在
afl-fuzz.c/run_target
中执行
参考资料
- 基于虚拟机的程序运行时监控方法:http://html.rhhz.net/HEBGCDXXB/html/201607055.htm
- linux进程间通信——匿名管道:https://www.cnblogs.com/52php/p/5840229.html
- AFL qemu mode:https://www.cnblogs.com/hac425/p/11614235.html
- Linux System Call Table for many archs:https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md
- FirmAFL\qemu_mode\DECAF_qemu_2.10\plugins\unpacker\system.xml
- QEMU最全总结:https://blog.csdn.net/pkufergus/article/details/18401915
- https://blog.csdn.net/robertsong2004/article/details/38820537