基于qemu和unicorn的Fuzz手艺剖析 | 申博官网
登录
  • 欢迎进入申博官网!
  • 如果您觉得申博官网对你有帮助,那么赶紧使用Ctrl+D 收藏申博官网并分享出去吧
  • 这里是申博官方网!
  • 申博官网是菲律宾sunbet官网品牌平台!
  • 申博开户专业品牌平台!

基于qemu和unicorn的Fuzz手艺剖析

申博_安全防护 申博 50次浏览 已收录 0个评论

windows样本高级静态分析之识别汇编中C代码结构

目标 通过分析代码结构来理解一个恶意样本的总体功能 分析流程 1.基础静态分析 2.基础动态分析 3.高级静态分析 实践过程 实例1 Lab06-01.exe 基础静态分析 导入表:wininet.dll、kernel32.net
导入函数:InternetGetConnectedState
字符串值:Error 1.1: No Internet、Success: Internet Connection 从导入库、导入函数、以及字符串可以看出该样本存在检测网络状态的功能 基础动态分析 运行样本后,通过联网和断网两种情景样本打印出不同输出,基本可以确定存在网络状态检测功能 高级静

媒介

本文重要引见假如运用 qemuunicorn 来汇集递次实行的覆盖率信息以及怎样把汇集到的覆盖率信息反馈到 fuzzer 中辅佐 fuzz 的举行。

AFL Fork Server

为了背面引见 aflqemu 形式和 unicorn 形式, 起首也许讲一下 aflfork server 的完成机制。aflfork server 的通讯流程如图所示

基于qemu和unicorn的Fuzz手艺剖析

  1. 起首 afl-fuzz 挪用 init_forkserver 函数 fork 出一个新历程作为 fork server , 然后守候 fork server 发送 4 个字节的数据, 假如能够一般吸收到数据则示意 fork server 启动一般。
  2. fork server 起来后会运用 read 壅塞住, 守候 afl-fuzz 发送敕令来启动一个测试历程。
  3. 当须要举行一次测试时,afl-fuzz 会挪用 run_target , 起首往管道发送 4 个字节关照 fork serverfork 一个历程来测试。
  4. fork server 新建历程后,会经由过程管道发送方才 fork 出的历程的 pidfork server.
  5. afl-fuzz 依据吸收到的 pid 守候测试历程完毕,然后依据测试天生的覆盖率信息来指导后续的测试。

AFL qemu 形式

AFLqemu 形式的完成和 winafl 运用 dynamorio 来插桩的完成体式格局比较相似,winafl 的完成细节以下

https://xz.aliyun.com/t/5108

原始版本

源码地点

https://github.com/google/AFL/tree/master/qemu_mode/patches

qemu 在实行一个递次时,从被实行递次的进口点最先对基本块翻译并实行,为了提拔效力,qemu会把翻译出来的基本块存放到 cache 中,当 qemu 要实行一个基本块时起首推断基本块是不是在 cache 中,假如在 cache 中则直接实行基本块,否则会翻译基本块并实行。

AFLqemu 形式就是经由过程在预备实行基本块的和预备翻译基本块的前面增添一些代码来完成的。起首会在每次实行一个基本块前挪用 AFL_QEMU_CPU_SNIPPET2 来和 afl 通讯。

#define AFL_QEMU_CPU_SNIPPET2 do { \
    if(itb->pc == afl_entry_point) { \
      afl_setup(); \
      afl_forkserver(cpu); \
    } \
    afl_maybe_log(itb->pc); \
  } while (0)

假如当前实行的基本块是 afl_entry_point (即目的递次的进口点),就设置好与 afl 通讯的定名管道和同享内存并初始化 fork server ,然后经由过程 afl_maybe_log 往同享内存中设置覆盖率信息。统计覆盖率的体式格局和 afl 的体式格局一样。

cur_loc  = (cur_loc >> 4) ^ (cur_loc << 8);
  cur_loc &= MAP_SIZE - 1;
  afl_area_ptr[cur_loc ^ prev_loc]++;  // 和 afl 一样 统计 edge 覆盖率

fork server 的代码以下

static void afl_forkserver(CPUState *cpu) {

  // 关照 afl-fuzz fork server 启动一般
  if (write(FORKSRV_FD + 1, tmp, 4) != 4) return;

  // fork server 的主轮回,不停地 fork 新历程
  while (1) {
    // 壅塞地守候 afl-fuzz 发送敕令,fork 新历程
    if (read(FORKSRV_FD, tmp, 4) != 4) exit(2);

    child_pid = fork(); // fork 新历程
    if (!child_pid) {
      // 子历程会进入这,封闭通讯管道描述符,然后从 afl_forkserver 返回继续往下实行被测试递次
      afl_fork_child = 1;
      close(FORKSRV_FD);
      close(FORKSRV_FD + 1);
      close(t_fd[0]);
      return;

    }

    // fork server 历程,发送 fork 出来的测试历程的 pid 给 afl-fuzz
    if (write(FORKSRV_FD + 1, &child_pid, 4) != 4) exit(5);

    // 不停守候处置惩罚 测试历程的 翻译基本块的要求
    afl_wait_tsl(cpu, t_fd[0]);

    // 守候子历程完毕
    if (waitpid(child_pid, &status, 0) < 0) exit(6);
    if (write(FORKSRV_FD + 1, &status, 4) != 4) exit(7);

  }
}

forkserver 的代码流程以下

  1. 起首发送数据给 afl-fuzz, 示意 fork server 启动一般,关照完以后会进入轮回壅塞在 read ,直到 afl-fuzz 端发送音讯。
  2. 吸收到数据后,fork serverfork 出新历程,此时子历程会封闭一切与 afl-fuzz 通讯的文件描述符并从 afl_forkserver 返回继续往下实行被测试递次。而父历程则把方才 fork出的测试历程的 pid 经由过程管道发送给 afl-fuzz
  3. 以后 fork server 历程进入 afl_wait_tsl ,不停轮回处置惩罚子历程翻译基本块的要求。

下面剖析 afl_wait_tsl 的道理, 起首 afl 会在 翻译基本块后插进去一段代码

tb = tb_gen_code(cpu, pc, cs_base, flags, 0); // 翻译基本块
                 AFL_QEMU_CPU_SNIPPET1;  // 关照父历程 (fork server历程) 方才翻译了一个基本块

#define AFL_QEMU_CPU_SNIPPET1 do { \
    afl_request_tsl(pc, cs_base, flags); \
  } while (0)

afl_request_tsl 就是把测试历程方才翻译的基本块的信息发送给父历程(fork server 历程)

static void afl_request_tsl(target_ulong pc, target_ulong cb, uint64_t flags) {
  struct afl_tsl t;
  if (!afl_fork_child) return;
  t.pc      = pc;
  t.cs_base = cb;
  t.flags   = flags;
  // 经由过程管道发送信息给 父历程 (fork server 历程)
  if (write(TSL_FD, &t, sizeof(struct afl_tsl)) != sizeof(struct afl_tsl))
    return;
}

下面看看 afl_wait_tsl 的代码

static void afl_wait_tsl(CPUState *cpu, int fd) {

  while (1) {

    // 死轮回不停吸收子历程的翻译基本块要求
    if (read(fd, &t, sizeof(struct afl_tsl)) != sizeof(struct afl_tsl))
      break;
    // 去fork server历程的 tb cache 中搜刮
    tb = tb_htable_lookup(cpu, t.pc, t.cs_base, t.flags);
    // 假如该基本块不在在 cache 中就运用 tb_gen_code 翻译基本块并放到 cache 中 
    if(!tb) {
      mmap_lock();
      tb_lock();
      tb_gen_code(cpu, t.pc, t.cs_base, t.flags, 0);
      mmap_unlock();
      tb_unlock();
    }
  }
  close(fd);
}

代码流程以下

  1. 这个函数内里就是一个死轮回,不停地吸收测试历程翻译基本块的要求。
  2. 吸收到要求后会运用 tb_htable_lookupfork server 历程的 cache 中搜刮,假如基本块不在 cache 中的话就运用 tb_gen_code 翻译基本块并安排到 fork server 历程的 cache 中。

这个函数有两个 tips

  1. 起首函数内里是死轮回,只要当 read 失利了才会退出轮回,read 又是壅塞的,所以只要 fd 管道的另一端封闭了才会 read 失利退出函数,所以当子历程实行完毕或许由于历程超时被 afl-fuzz 杀身后, afl_wait_tsl 就会由于 read 失利而退出该函数,守候接下来的 fork 要求。
  2. 子历程向父历程( fork server 历程)发送基本块翻译要求的缘由是让 fork server 历程把子历程方才翻译的基本块在 fork server 历程也翻译一遍并放入 cache,如许在后续测试中 fork 出的新历程就会由于 fork 的特征继续 fork servertb cache,从而防止反复翻译之前子历程翻译过的基本块。

革新版本

源码地点

https://github.com/vanhauser-thc/AFLplusplus

在原始的 AFL qemu 版本中猎取覆盖率的体式格局是在每次翻译基本块前挪用 afl_maybe_logafl-fuzz 同步覆盖率信息,这类体式格局有一个题目就是由于 qemu 会把递次实行的基本块 chain 一同,如许能够提拔实行速率。但是在这类体式格局下有的基本块就会由于 chain 的缘由致使追踪不到基本块的实行, afl 的处置惩罚体式格局是禁用 qemuchain 功用,如许则会减少 qemu 的机能。

为此有人提出了一些革新的体式格局

windows中常见后门持久化方法总结

前言 当我们通过各种方法拿到一个服务器的权限的时候,我们下一步要做的就是后渗透了,而后门持久化也是我们后渗透很重要的一部分,下面我来总结一下windows下常见的后门持久化的方法 后门持久化 我的操作环境是: 无AV、管理员权限(提权、免杀等是后门持久化的铺垫,当然有的方法也并不是全部需要这些铺垫) 操作系统:win7,windows server 2008R2,xp shift后门 这个是比较老的方式了,这里简单讲一下,在windows中有一些辅助功能,能在用户未登录系统之前可以通过组合键来启动它,

https://abiondo.me/2018/09/21/improving-afl-qemu-mode/

为了能够启用 chain 功用,能够直接把统计覆盖率的代码插进去到每一个翻译的基本块的前面

TranslationBlock *tb_gen_code(CPUState *cpu,
     ............................
     ............................
     tcg_ctx->cpu = ENV_GET_CPU(env);
     afl_gen_trace(pc);  // 天生统计覆盖率的代码
     gen_intermediate_code(cpu, tb);
     tcg_ctx->cpu = NULL;
     ............................

afl_gen_trace 的作用是插进去一个函数挪用在翻译的基本块前面,以后在每次实行基本块前会实行 afl_maybe_log 统计递次实行的覆盖率信息。

同时为了能够进一步提拔速率能够把子历程天生的 基本块chain 也同步到 fork server 历程。

bool was_translated = false, was_chained = false;
     tb = tb_lookup__cpu_state(cpu, &pc, &cs_base, &flags, cf_mask);
     if (tb == NULL) {
         mmap_lock();
         tb = tb_gen_code(cpu, pc, cs_base, flags, cf_mask);
         was_translated = true; // 示意当前基本块被翻译了
         mmap_unlock();

     /* See if we can patch the calling TB. */
     if (last_tb) {
         tb_add_jump(last_tb, tb_exit, tb);
         was_chained = true; // 示意当前基本块实行了 chain 操纵
     }
     if (was_translated || was_chained) {
         // 假如有新翻译的基本块或许新构建的 chain 就关照 fork server 更新 cache
         afl_request_tsl(pc, cs_base, flags, cf_mask, was_chained ? last_tb : NULL, tb_exit);
     }

重要流程就是当有新的基本块和新的 chain 构建时就关照父历程 (fork server历程)更新父历程的 cache.

基于qemu还能够完成 aflpersistent 形式,详细的完成细节就是在被测函数的最先和末端插进去指令

#define AFL_QEMU_TARGET_i386_SNIPPET                                          \
  if (is_persistent) {                                                        \
                                                                              \
    if (s->pc == afl_persistent_addr) {                                       \
                                                                              \
      I386_RESTORE_STATE_FOR_PERSISTENT;                                      \
                                                                              \
      if (afl_persistent_ret_addr == 0) {                                     \
                                                                              \
        TCGv_ptr paddr = tcg_const_ptr(afl_persistent_addr);                  \
        tcg_gen_st_tl(paddr, cpu_regs[R_ESP], persisent_retaddr_offset);      \
                                                                              \
      }                                                                       \
      tcg_gen_afl_call0(&afl_persistent_loop);                                \
                                                                              \
    } else if (afl_persistent_ret_addr && s->pc == afl_persistent_ret_addr) { \
                                                                              \
      gen_jmp_im(s, afl_persistent_addr);                                     \
      gen_eob(s);                                                             \
                                                                              \
    }                                                                         \
                                                                              \
  }
  1. 在被测函数的开首(afl_persistent_addr)插进去指令挪用 afl_persistent_loop 函数, 该函数的作用是在每次进入被测函数前初始化一些信息,比方存储递次实行的覆盖率信息的同享内存。
  2. 然后在 被测函数的末端 afl_persistent_ret_addr 增添一条跳转指令直接跳转到函数的进口(afl_persistent_addr)
  3. 经由过程如许能够完成不停对函数举行轮回测试

AFL unicorn 形式

源码地点

https://github.com/vanhauser-thc/AFLplusplus

afl 能够运用 unicorn 来汇集覆盖率,其完成体式格局和 qemu 形式相似(由于 unicorn 自身也就是基于 qemu 搞的).它经由过程在 cpu_exec 实行基本块前插进去设置forkserver和统计覆盖率的代码,如许在每次实行基本块时 afl 就可以猎取到覆盖率信息

static tcg_target_ulong cpu_tb_exec(CPUState *cpu, uint8_t *tb_ptr);
@@ -228,6 +231,8 @@
                             next_tb & TB_EXIT_MASK, tb);
                 }

                 AFL_UNICORN_CPU_SNIPPET2; // unicorn 插进去的代码
                 /* cpu_interrupt might be called while translating the
                    TB, but before it is linked into a potentially
                    infinite loop and becomes env->current_tb. Avoid

插进去的代码以下

#define AFL_UNICORN_CPU_SNIPPET2 do { \
    if(afl_first_instr == 0) { \  // 假如是第一次实行就设置 forkserver
      afl_setup(); \  // 初始化管道
      afl_forkserver(env); \  // 设置 fork server
      afl_first_instr = 1; \
    } \
    afl_maybe_log(tb->pc); \  // 统计覆盖率
  } while (0)

qemu 相似在实行第一个基本块时初始化 afl 的定名管道而且设置好 forkserver,然后经由过程 afl_maybe_logafl-fuzz 端同步覆盖率。

forkserver 的作用和 qemu 形式中的相似,重要就是吸收敕令 fork 新历程而且处置惩罚子历程的基本块翻译要求来提拔实行速率。

libFuzzer unicorn 形式

源码地点

https://github.com/PAGalaxyLab/uniFuzzer

libfuzzer 支撑从外部猎取覆盖率信息

__attribute__((section("__libfuzzer_extra_counters")))
uint8_t Counters[PCS_N];

上面的定义示意 libfuzzerCounters 内里掏出覆盖率信息来指导变异。

那末下面就简朴了,起首经由过程 unicorn 的基本块 hook 事宜来汇集实行的基本块信息,然后在回调函数内里更新Counters, 就可以够把被 unicorn 模仿实行的递次的覆盖率信息反馈给 libfuzzer

// hook basic block to get code coverage
    uc_hook hookHandle;
    uc_hook_add(uc, &hookHandle, UC_HOOK_BLOCK, hookBlock, NULL, 1, 0);

下面看看 hookBlock 的完成

// update code coverage counters by hooking basic block
void hookBlock(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) {
    uint16_t pr = crc16(address);
    uint16_t idx = pr ^ prevPR;
    Counters[idx]++;
    prevPR = (pr >> 1);
}

实在就是模仿 libfuzzer 统计覆盖率的体式格局在 Counters 更新覆盖率信息并反馈给 libfuzzer.

总结

经由过程剖析 aflforkserver 机制、 afl qemu的完成机制以及 afl unicorn 的完成机制能够得出afl 的变异战略调理模块和被测递次实行和覆盖率信息汇集模块是相对自力的,二者经由过程定名管道举行通讯。假定我们须要完成一种新的覆盖率汇集体式格局并把覆盖率反馈给 afl 来运用 aflfuzz 战略,我们重要就须要模仿 fork serverafl-fuzz 举行通讯,然后把覆盖率反馈给 afl-fuzz 即可。

关于 libfuzzer 而言,它自身就支撑从外部猎取递次实行的覆盖率信息(经由过程全局变量来通报),所以假如要完成新的覆盖率汇集体式格局,根据 libfuzzer 的范例来完成即可。

 


申博|网络安全巴士站声明:该文看法仅代表作者自己,与本平台无关。版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明基于qemu和unicorn的Fuzz手艺剖析
喜欢 (0)
[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址