FIRM-AFL源码分析

Posted by 许大仙 on September 5, 2020

本章目的

  • 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 
    
  • 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_pcori_threadstack_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_forkafl_wants_cpu_to_stopafl_start_codeafl_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