Nook in the Lunar Mare

Dofoil病毒分析

Jan 16, 2021

Dofoil-Like Virus

这个样本粗略地看了一下,里面的符号信息保留得不少,以为是个比较容易分析的东西,哪知道炸了。

行为

扔到虚拟机里跑,删除自身,没有弹窗,也没有生成什么文件,文件未被加密说明不是勒索软件。这个情况可能是:

  1. 反虚拟机机制,病毒行为与实际不同
  2. 木马,注入某进程在后台隐蔽地监听
  3. 挖矿

单看肯定没用,扔到沙盒里试试,投放了与自身相同的文件,创建了一个进程explorer。因此整个病毒的行为猜测是投放payload然后注入explorer之类。

检查GetVolumeInformation的调用结果,本体和explorer里都有调用。显示的serial是0x944c1ba9 ,搜索的结果与该沙盒平台高度相关。这个应该是沙盒自己对硬盘序列修改,避开恶意软件利用硬盘序列对环境进行检测。

剩下的行为记录都比较少,可能是利用其他机制躲开了沙盒的后续检测。

补:尝试另一个沙盒后,所得的分析结果更少了,但是显示其病毒特征为(Dofoil),这个沙盒与前面的区别在于对于免费用户有时间限制(60s),而我在虚拟机调试过程中也发现该病毒后续会调用2~3次的tickCount这类函数,停顿一段时间,通过这样做来规避沙盒的检测。

Init 阶段

入口很好找,前段都是一些迷惑性的语句,应该是利用这些正常操作的API使自己特征趋向于正常软件,避开杀软。Init也就是样本本体的编程风格是尽可能地使用全局变量,然后在各个函数中修改和调用。(下面的函数已被重命名过)

undefined * __cdecl entry(undefined4 param_1)
{
  int local_2790;
  uint local_2720;
  
  // anti-analyze
  jmp_to_anti_decompile();
    
  // 干扰语句,无用,
  GetEnhMetaFileA(s_gipOoZaUyi_0040d2ac);
  local_2720 = 0x16;
  while (local_2720 < 0x8542) {
    GetEnhMetaFileA(s_gipOoZaUyi_0040d2b8);
                    /* 0x4a85 */
    local_2720 = local_2720 + 0x16;
  }
  
  // 通过大量正常API混淆特征,略去
  DeleteMetaFile(hmenu);
  CreateMenu();
  GetPixelFormat((HDC)hmenu);
  // ...
  
  // 设置了entry ebp,可能是为之后重新跳回代码段做准备
  DAT_0040d3dc = param_1;
  entry_ebp = &stack0xfffffffc;
    
  // 无用的junk code, 干扰分析者的
  junk_code();
    
  // 将指向RegOpenKey的指针存在一个全局变量里,参数不太重要
  // PS.(这个函数也可以当作取min(a,b)来用,不过这里没有用到返回值故不重要)
  set_regopenkey_func(0x7b,4);
  local_2720 = 0;
   
  // 修改了一个全局的字符串,最终的效果是存入了一个Key
  // interface\{3050F557-98B5-11CF-BB82-00AA00BDCE0B}
  interface_name_in_mem();
    
  
  DAT_0040d1e8 = DAT_0040d1e8 + -2;
  
  // 遍历两个键下的子键:interface\{3050F557-98B5-11CF-BB82-00AA00BDCE0B}
  // 不存在则直接退出
  while( true ) {
    if (0x12 < local_2720) {
      return (undefined *)0x0;
    }
    local_2790 = (*DAT_A_Check)(DAT_0040d1e8,interface_key,&key_handle);
    
    // 找到会返回error success(即0),跳出循环
    if (local_2790 == 0) break;
    local_2720 = local_2720 + 6;
  }
    
  // 一个比较关键的函数,里面会检查前面key的值是否为“DispHTMLCurrentStyle”
  DAT_0040d3e4 = FUN_004027c0();
    
  // 通过替换法还原virtualAlloc这个字符串(以及kernel32)
  // 分配一块堆空间,后续的loader将在堆空间上执行。
  mem_size = virtualAllocString();
  mem_buf = call_virtual_alloc();
  g_mem_size = mem_size;
                    /* 0x253ce0 */
  
  // 这个神秘的地址,通过debug发现是堆空间上代码的entry point
  // 3ce0即为偏移地址
  _magic_mem_addr = mem_buf + 0x3ce0;
  g_mems_0 = 0;
  DAT_0040d3cc = 0;
  DAT_0040d3c4 = 10;
  
  // 下面这部分代码主要干的就是内存拷贝,把loader代码拷到堆上
  do {
    local_2790 = 0;
    local_2720 = set_regopenkey_func(another_size,g_mem_size);
    if (mem_size <= g_mems_0) break;
    global_memcpy(local_2720);
    _minsize_of_another_and_mem = local_2720;
    DAT_0040d3cc = ((DAT_0040d3c4 + another_size) - local_2790) + DAT_0040d3cc;
    g_mems_0 = another_size + local_2790 + g_mems_0;
    g_mem_size = g_mem_size - _minsize_of_another_and_mem;
  } while (mem_buf != 0);
  
  // 解密堆上数据
  wrapper_for_401200(0x7fb67,0xcfdb);
  
  // 跳到key jmp函数
  return unk_prepare_and_jmp;
}

首先是干扰decompiler的一个函数,IDA可能会显示positive sp,而ghidra则是显示UNRECOVERED_JUMPTABLE。可以总结如下

  • 原本应该是stdcall函数负责栈的清理工作,该函数没直接做。此外entry函数本身没有分配栈空间
  • 调用时未分配栈空间,此时esp、ebp指向同一个位置,esp指向的栈顶即返回地址。JMP EDX可以跳回下一条指令
  • 一些MOV ECX,ECX的花指令,可忽略

这个函数实际上就是为entry分配栈空间(0x278C),不过我看手头上的3个反编译工具受到的干扰似乎不大。

# ===========================
# entry0:
# PUSH EBP
# MOV EBP, ESP
# MOV EAX, 0x278c
# CALL jmp_anti_decompiler
# ===========================

# jmp_anti_decompiler
thunk undefined __stdcall jmp_to_anti_decompile(void)
             undefined         AL:1           <RETURN>

        00402660 eb 00           JMP        anti_decompiler

# anti_decompiler
undefined __stdcall anti_decompiler(void)
        undefined         AL:1           <RETURN>
        undefined4        Stack[0x0]:4   local_res0                             
        
        00402662 8b c9           MOV        ECX,ECX
        00402664 33 d2           XOR        EDX,EDX
        00402666 8b c9           MOV        ECX,ECX
        00402668 87 14 24        XCHG       dword ptr [ESP]=>local_res0,EDX
        0040266b 8b c9           MOV        ECX,ECX
        0040266d 83 c4 04        ADD        ESP,0x4
        00402670 2b e0           SUB        ESP,EAX
        00402672 ff e2           JMP        EDX
        00402674 cc              ??         CCh

接下来检查了一些莫名奇妙的文件,都是不存在的。junk code是一个空函数,不知道为什么出现于此。或许以后会跳回代码段修改该函数?

interface是通过修改字符数组写入的,规避静态分析时对字符串的检查。这个注册表key指向的内容很明确,是一篇微软关于Dofoil的文档,看来病毒基本上就是Dofoil系列病毒了。

void interface_name_in_mem(void)
{
  // decode => interface\{3050F557-98B5-11CF-BB82-00AA00BDCE0B}
  *(undefined2 *)interface_key = 0x69;
  *(undefined2 *)(interface_key + 2) = 0x6e;
  *(undefined2 *)(interface_key + 4) = 0x74;
  *(undefined2 *)(interface_key + 6) = 0x65;
  *(undefined2 *)(interface_key + 8) = 0x72;
  *(undefined2 *)(interface_key + 10) = 0x66;
  *(undefined2 *)(interface_key + 0xc) = 0x61;
  *(undefined2 *)(interface_key + 0xe) = 99;
  // ... More ...
  return;
}

这个作者是真的很爱用这种写入的技术,确实这个方法也造成我查看二进制中string时没找到什么有用的信息。另一个例子就是这个virtualAllocString()函数里,单看字符以为是ualkir,原以为是作者名字,后面对照原本字符串进行替换才发现是为了LoadLibirary和getProcAddr准备的。原本的字符串分别是VtttvvvAllocternel32,通过下面函数替换后可以产生函数名,规避分析者对于字符串的检查。

undefined4 virtualAllocString(void)
{
  HMODULE hModule;
  char *lpProcName;
  
  virtualAlloc_string[4] = 'u';
  virtualAlloc_string[5] = 'a';
  virtualAlloc_string[6] = 'l';
  kernel_32_string[0] = 'k';
  virtualAlloc_string[1] = 'i';
  virtualAlloc_string[2] = 'r';
  lpProcName = virtualAlloc_string;
  hModule = LoadLibraryA(kernel_32_string);
  virtual_alloc_addr = GetProcAddress(hModule,lpProcName);
  
  // global var -- memsize
  return *(undefined4 *)(DAT_0040d3e4 + -4);
}

此外这个思路还体现在对于interface\{3050F557-98B5-11CF-BB82-00AA00BDCE0B}键值的检查上。FCN_004027c0中会检查该字符串是否为DispHTMLCurrentStyle,其实现不通过strcmp这类直接对字符串进行比较(这样会造成二进制文件里包含目标字符串),而是跳跃式的检查几个位置上的字符(str[1]是否为i,str[7]是否为L…)。

disp

接下来的部分就是获取api地址的代码,分配堆内存,释放loader代码到堆上,让我们略过此处。关注wrapper_for_401200(0x7fb67,0xcfdb);这个函数。这是一个嵌套比较多的函数,其在写完堆数据之后进行调用,作用是解密堆上的数据(为了规避反病毒引擎通常都会对恶意代码进行加密)。借助调试获取了DAT_0040d1fc的值为0x2。

fcn_call
void unk_modify_global_accordingto_memsize(void)
{
  while (global_cnt_item < mem_size) {
    cur_cnt = global_cnt_item;
    mem_buf_eleaddr = (int *)(mem_buf + global_cnt_item);
    *mem_buf_eleaddr = *mem_buf_eleaddr + global_cnt_item;
                    /* DAT_0040d1fc is 0x02 */
    DAT_40d1fc_cnt_37 = global_cnt_item + 0x37 + DAT_0040d1fc;
    cur_ele_addr = mem_buf_eleaddr;
    unk_modify_global_up();
    global_cnt_item = global_cnt_item + 4;
  }
  return;
}

void unk_modify_global_up(void)
{
  DAT_40d1fc_cnt_37_cpy = DAT_40d1fc_cnt_37;
  cur_ele_addr_cpy = cur_ele_addr;
  cur_ele = *cur_ele_addr;
  decode_func();
  return;
}

void decode_func(void)
{
  cur_ele = cur_ele ^ DAT_40d1fc_cnt_37_cpy;
  *cur_ele_addr_cpy = cur_ele;
  return;
}

解密数组元素的方法就是异或(0x39 + element_index)。执行该函数后,堆上数据才布置完成。最后通过一个跳转调用(堆地址 + 0x3ce0处的函数)

堆代码执行阶段

简要来说,这个病毒比较谨慎,每次往内存中调用完相应代码之后都会擦除数据,如此反复。然而od似乎有些bug,导致堆代码执行阶段之后的部分无法dump进程,无奈只好dump内存中的数据。

不过堆上的代码还是好好地dump下来了,先进行分析,鉴于整个堆上代码比较小巧且缺失文件格式,选择radare2进行分析。(比较痛苦的一点是radare2目前project的支持基本不能用了,只能通过写r2脚本的方式保存和还原)

前面动态调试的时候dump了多次数据,此时正好检查一下它们之间的差别。看来拿后面这份内存分析就可。

$ radiff2 -x _002D0000.mem _002D0000_api.mem
  offset     0 1 2 3 4 5 6 7 01234567    0 1 2 3 4 5 6 7 01234567
0x00000000  6d6b6e6a68743334 mknjht34   6d6b6e6a68743334 mknjht34
0x00000008  7466736572646766 tfserdgf   7466736572646766 tfserdgf
0x00000010! 7731313131313263 w111112c   7747657450726f63 wGetProc
0x00000018! 6c64647231313100 lddr111.   4164647265737300 Address.
0x00000020! 00006d6e31313232 ..mn1122   0000566972747561 ..Virtua
0x00000028! 767634346f630000 vv44oc..   6c416c6c6f630000 lAlloc..
0x00000030! 0000006e6d6a6b31 ...nmjk1   0000004c6f61644c ...LoadL
0x00000038! 6962316166664578 ib1affEx   6962726172794578 ibraryEx
0x00000040! 4100000066666767 A...ffgg   4100000053657446 A...SetF
0x00000048! 336c65506f696e74 3lePoint   696c65506f696e74 ilePoint
0x00000050  65720000006c7374 er...lst   65720000006c7374 er...lst
0x00000058  726c656e41000000 rlenA...   726c656e41000000 rlenA...
...

由于从堆上dump下来的数据一般只包含代码,不带文件头,因此r2加载时默认映射到0x0,直接从0x00开始分析会导致分析结果偏离实际情况。因此首先需要手动操作一下(没找到设置entry的方法),跳转到0x3ce0的位置,从这里开始使用aaa进行分析。此外注意设置一下架构和位数,默认是x86架构64位,使用-b 32将其作为32位程序分析。

r2 -b 32 _002D000_api.mem
s 0x3ce0
aaa

分析完成,首先看看function call graph,整份函数调用很工整。

# Alternatives
# 1. agCv                        browser in r2
# 2. agCd | xdot -               require xdot installed
[0x00003ce0]> e graph.gv.format = png
[0x00003ce0]> agCw ./fcn_call.png
fcn_call_graph

根据函数调用图可以指定一个分析策略:先分析没有依赖的函数,自底向上直到entry。

分析Utilites Part1

最下面那两个函数很显眼,而且没有任何依赖,是独立的函数。先从它们进行分析。

移到fcn.00003130,看看代码。call相当于push $(next_addr) ; jmp target_addr,在堆上eax的内容应该为heap_baseaddr + 0x3135,加之后续的sub指令减去了0x404135,因此这个函数就是返回heap_base与主代码基址0x400000之间的差值。

[0x00003135]> pdf
            ; CALL XREF from fcn.00003230 @ 0x32ae
            ; CALL XREFS from fcn.00003ce0 @ 0x3d1b, 0x3d51, 0x3d66
fcn.00003130 ();
           0x00003130      e800000000     call 0x3135
           ; CALL XREF from fcn.00003130 @ 0x3130
           0x00003135      58             pop eax
           0x00003136      2d35414000     sub eax, 0x404135
           0x0000313b      c3             ret

rename这个函数。

[0x00003135]> afn return_delta_to_heapaddr
[0x00003135]> f
0x00003130 12 return_delta_to_heapaddr
0x00003140 232 fcn.00003140
......

接下来是fcn_00003610,可以选择用pdd生成伪代码观察其大体逻辑:

[0x00003135]> s fcn.00003610
[0x00003610]> pdd
/* r2dec pseudo code output */
/* _002D0000_api.mem @ 0x3610 */
#include <stdint.h>
 
int32_t fcn.00003610 (int32_t arg_8h, int32_t arg_ch, uint32_t arg_10h) {
    int32_t var_4h;
    var_4h = 0;
    while (1) {
        eax = var_4h;
        eax++;
        var_4h = eax;
        ecx = eax;
        if (ecx >= arg_10h) {
            goto label_0;
        }
        edx = arg_8h;
        edx += var_4h;
        eax = *(edx);
        ecx = arg_ch;
        ecx += var_4h;
        edx = *(ecx);
        if (eax != edx) {
            eax = 1;
            goto label_1;
        }
    }
label_0:
    eax = 0;
label_1:
    return eax;
}

整个的代码逻辑包含:

  1. 一个循环
  2. 通过终止条件和循环次数控制返回值,return 1 or 0 (或许是true和false)
  3. 包含递增(var_4h)、解引用这些操作

看起来很像字符串比较类的函数,var_4h 看起来很像迭代计数变量,arg_10用于控制最大迭代次数,arg_8h和arg_ch分别是两个指针(string 或者 array 之类),对这些本地变量和参数重命名一下。

[0x00003610]> afvn i var_4h
[0x00003610]> afvn addr1 arg_8h
[0x00003610]> afvn addr2 arg_ch
[0x00003610]> afvn cnt arg_10h

此时再检查一下汇编代码,验证我们对该函数的猜想。

[0x00003610]> pdf
            ; CALL XREF from fcn.00003140 @ 0x31f0
 67: mem_cmp_n (int32_t addr1, int32_t addr2, uint32_t cnt);
           ; var int32_t i @ ebp-0x4
           ; arg int32_t addr1 @ ebp+0x8
           ; arg int32_t addr2 @ ebp+0xc
           ; arg uint32_t cnt @ ebp+0x10
           0x00003610      55             push ebp
           0x00003611      8bec           mov ebp, esp
           0x00003613      51             push ecx
           0x00003614      c745fc000000.  mov dword [i], 0
       ┌─< 0x0000361b      eb09           jmp 0x3626
          ; CODE XREF from mem_cmp_n @ 0x364b
      ┌──> 0x0000361d      8b45fc         mov eax, dword [i]
      ╎│   0x00003620      83c001         add eax, 1
      ╎│   0x00003623      8945fc         mov dword [i], eax
      ╎│   ; CODE XREF from mem_cmp_n @ 0x361b
      ╎└─> 0x00003626      8b4dfc         mov ecx, dword [i]
          0x00003629      3b4d10         cmp ecx, dword [cnt]
      ╎┌─< 0x0000362c      731f           jae 0x364d
      ╎│   0x0000362e      8b5508         mov edx, dword [addr1]
      ╎│   0x00003631      0355fc         add edx, dword [i]
      ╎│   0x00003634      0fb602         movzx eax, byte [edx]
      ╎│   0x00003637      8b4d0c         mov ecx, dword [addr2]
      ╎│   0x0000363a      034dfc         add ecx, dword [i]
      ╎│   0x0000363d      0fb611         movzx edx, byte [ecx]
      ╎│   0x00003640      3bc2           cmp eax, edx
     ┌───< 0x00003642      7407           je 0x364b
     │╎│   0x00003644      b801000000     mov eax, 1
    ┌────< 0x00003649      eb04           jmp 0x364f
    ││││   ; CODE XREF from mem_cmp_n @ 0x3642
    │└└──< 0x0000364b      ebd0           jmp 0x361d
         ; CODE XREF from mem_cmp_n @ 0x362c
      └─> 0x0000364d      33c0           xor eax, eax
          ; CODE XREF from mem_cmp_n @ 0x3649
    └────> 0x0000364f      8be5           mov esp, ebp
           0x00003651      5d             pop ebp
           0x00003652      c3             ret

一个经典的for loop结构,内嵌一个if语句,用于break。是个memncmp功能,照前面将其重命名为mem_cmp_n

for (i = 0; i < cnt; ++i)
    if (addr1[i] != addr2[i])
        return 1;
return 0;

Utilities Part2

接下来,我们该分析哪个函数?我喜欢先从小函数进行分析。因此需要先对样本中的函数大小有一个overview

[0x00003660]> afi @@f ~name;~size
name: fcn.00003ce0
size: 249
name: fcn.00003580
size: 55
name: fcn.00003230
size: 497
name: return_delta_to_heapaddr
size: 12
name: fcn.00003430
size: 115
name: fcn.00003810
size: 183
name: fcn.00003140
size: 232
name: fcn.000035c0
size: 66
name: fcn.00003c90
size: 80
name: fcn.00003980
size: 626
name: mem_cmp_n
size: 67
name: fcn.000034b0
size: 109
name: fcn.00003c00
size: 113
name: fcn.00003700
size: 219
name: fcn.00003660
size: 159
name: fcn.000038d0
size: 130
name: fcn.00003560
size: 22
name: fcn.00003520
size: 64
name: fcn.000037e0
size: 45
name: fcn.00003960
size: 23

0x3560

[0x00003660]> s 0x3560
[0x00003560]> pdf
            ; CALL XREF from fcn.00003980 @ 0x3bdf
/ 22: fcn.00003560 (int32_t arg_8h);
|           ; arg int32_t arg_8h @ ebp+0x8
|           0x00003560      55             push ebp
|           0x00003561      8bec           mov ebp, esp
|           0x00003563      64a118000000   mov eax, dword fs:[0x18]
|           0x00003569      3e8b4030       mov eax, dword ds:[eax + 0x30]
|           0x0000356d      8b4d08         mov ecx, dword [arg_8h]
|           0x00003570      3e894808       mov dword ds:[eax + 8], ecx
|           0x00003574      5d             pop ebp
\           0x00003575      c3             ret

这里都是Windows特有的属性,fs:[0x18]是一个很特殊的指针,指向TEB的线性地址(TEB的开头),接下来的mov eax, dword ds:[eax + 0x30]获取了PEB的地址。

mov ecx, dword [arg_8h];mov dword ds:[eax + 8], ecx将参数(一个指针)存入PEB + 0x8的位置,也就是Ldr。这一步是在存储kernel32和ntdll的基址。重命名为set_ldr

0x3c00

[0x00003c00]> pdf
            ; CALL XREF from fcn.00003980 @ 0x3a5c
/ 113: fcn.00003c00 (int32_t arg_8h);
|           ; var int32_t var_10h @ ebp-0x10
|           ; var int32_t var_ch @ ebp-0xc
|           ; var int32_t var_8h @ ebp-0x8
|           ; var int32_t var_4h @ ebp-0x4
|           ; arg int32_t arg_8h @ ebp+0x8
|           0x00003c00      55             push ebp
|           0x00003c01      8bec           mov ebp, esp
|           0x00003c03      83ec10         sub esp, 0x10
|           0x00003c06      8b4508         mov eax, dword [arg_8h]
|           0x00003c09      2500000020     and eax, 0x20000000
|           0x00003c0e      f7d8           neg eax
|           0x00003c10      1bc0           sbb eax, eax
|           0x00003c12      f7d8           neg eax
|           0x00003c14      8945f8         mov dword [var_8h], eax
|           0x00003c17      8b4d08         mov ecx, dword [arg_8h]
|           0x00003c1a      81e100000040   and ecx, 0x40000000
|           0x00003c20      f7d9           neg ecx
|           0x00003c22      1bc9           sbb ecx, ecx
|           0x00003c24      f7d9           neg ecx
|           0x00003c26      894df4         mov dword [var_ch], ecx
|           0x00003c29      8b5508         mov edx, dword [arg_8h]
|           0x00003c2c      81e200000080   and edx, 0x80000000
|           0x00003c32      f7da           neg edx
|           0x00003c34      1bd2           sbb edx, edx
|           0x00003c36      f7da           neg edx
|           0x00003c38      8955fc         mov dword [var_4h], edx
|           0x00003c3b      8b45f8         mov eax, dword [var_8h]
|           0x00003c3e      c1e004         shl eax, 4
|           0x00003c41      8b4df4         mov ecx, dword [var_ch]
|           0x00003c44      8d94c8001140.  lea edx, [eax + ecx*8 + 0x401100]
|           0x00003c4b      8b45fc         mov eax, dword [var_4h]
|           0x00003c4e      8b0c82         mov ecx, dword [edx + eax*4]
|           0x00003c51      894df0         mov dword [var_10h], ecx
|           0x00003c54      8b5508         mov edx, dword [arg_8h]
|           0x00003c57      81e200000004   and edx, 0x4000000
|       ,=< 0x00003c5d      740b           je 0x3c6a
|       |   0x00003c5f      8b45f0         mov eax, dword [var_10h]
|       |   0x00003c62      0d00020000     or eax, 0x200
|       |   0x00003c67      8945f0         mov dword [var_10h], eax
|       |   ; CODE XREF from fcn.00003c00 @ 0x3c5d
|       `-> 0x00003c6a      8b45f0         mov eax, dword [var_10h]
|           0x00003c6d      8be5           mov esp, ebp
|           0x00003c6f      5d             pop ebp
\           0x00003c70      c3             ret

记录一下一个常见的gadget

neg eax
sbb eax, eax
neg eax

若最初eax为0时则最终eax的值为1,反之为0,可以用esil模拟验证这点,如下初始化后单步执行可观察eax结果。

[0x00003c00]> aei
[0x00003c00]> aeim
[0x00003c00]> aeip
[0x00003c00]> aer eax=0x666

而结合函数中的0x20000000, 0x40000000, 0x80000000应是进行flag的比较,检查参数(32位)

0x20000000 -> 0b 0010 0000 0000 0000 0000 0000 0000 0000
0x40000000 -> 0b 0100 0000 0000 0000 0000 0000 0000 0000
0x80000000 -> 0b 1000 0000 0000 0000 0000 0000 0000 0000

比较的结果存在三个变量(var_8h, var_ch, var_4h)中,重命名这些变量使得汇编可读性更好。

[0x00003c00]> afvn flag_A var_8h
[0x00003c00]> afvn flag_B var_ch
[0x00003c00]> afvn flag_C var_4h

接着其进入主要逻辑

          0x00003c3b      8b45f8         mov eax, dword [flag_A]
          0x00003c3e      c1e004         shl eax, 4
          0x00003c41      8b4df4         mov ecx, dword [flag_B]
          0x00003c44      8d94c8001140.  lea edx, [eax + ecx*8 + 0x401100]
          0x00003c4b      8b45fc         mov eax, dword [flag_C]
          0x00003c4e      8b0c82         mov ecx, dword [edx + eax*4]
          0x00003c51      894df0         mov dword [var_10h], ecx
          0x00003c54      8b5508         mov edx, dword [arg_8h]
          0x00003c57      81e200000004   and edx, 0x4000000
      ,=< 0x00003c5d      740b           je 0x3c6a
      |   0x00003c5f      8b45f0         mov eax, dword [var_10h]
      |   0x00003c62      0d00020000     or eax, 0x200
      |   0x00003c67      8945f0         mov dword [var_10h], eax
      |   ; CODE XREF from fcn.00003c00 @ 0x3c5d
      `-> 0x00003c6a      8b45f0         mov eax, dword [var_10h]
          0x00003c6d      8be5           mov esp, ebp
          0x00003c6f      5d             pop ebp
          0x00003c70      c3             ret

翻译

var_10h = *(flag_A << 4 + flag_B << 3 + flag_C << 2 + 0x401100);

if (arg & 0x4000000)
    return var_10h;
else
    return var_10h | 0x200;

0x3580

[0x00003580]> s 0x3580
[0x00003580]> pdf
            ; CALL XREF from fcn.00003ce0 @ 0x3cf8
/ 55: fcn.00003580 (int32_t arg_8h, int32_t arg_ch, uint32_t arg_10h);
|           ; var int32_t var_8h @ ebp-0x8
|           ; var int32_t var_4h @ ebp-0x4
|           ; arg int32_t arg_8h @ ebp+0x8
|           ; arg int32_t arg_ch @ ebp+0xc
|           ; arg uint32_t arg_10h @ ebp+0x10
|           0x00003580      55             push ebp
|           0x00003581      8bec           mov ebp, esp
|           0x00003583      83ec08         sub esp, 8
|           0x00003586      8b4508         mov eax, dword [arg_8h]
|           0x00003589      8945f8         mov dword [var_8h], eax
|           0x0000358c      c745fc000000.  mov dword [var_4h], 0
|       ,=< 0x00003593      eb09           jmp 0x359e
|       |   ; CODE XREF from fcn.00003580 @ 0x35b1
|      .--> 0x00003595      8b4dfc         mov ecx, dword [var_4h]
|      :|   0x00003598      83c101         add ecx, 1
|      :|   0x0000359b      894dfc         mov dword [var_4h], ecx
|      :|   ; CODE XREF from fcn.00003580 @ 0x3593
|      :`-> 0x0000359e      8b55fc         mov edx, dword [var_4h]
|      :    0x000035a1      3b5510         cmp edx, dword [arg_10h]
|      :,=< 0x000035a4      730d           jae 0x35b3
|      :|   0x000035a6      8b45f8         mov eax, dword [var_8h]
|      :|   0x000035a9      0345fc         add eax, dword [var_4h]
|      :|   0x000035ac      8a4d0c         mov cl, byte [arg_ch]
|      :|   0x000035af      8808           mov byte [eax], cl
|      `==< 0x000035b1      ebe2           jmp 0x3595
|       |   ; CODE XREF from fcn.00003580 @ 0x35a4
|       `-> 0x000035b3      8be5           mov esp, ebp
|           0x000035b5      5d             pop ebp
\           0x000035b6      c3             ret

又是一个loop,将传入地址上的数据替换n个字节。

0x3c90

不出意外的话应该是个解密函数

for (i = 0; i < arg2; ++i)
{
    // element in arg1 array is 4-bytes object
    arg1[i] = (arg1[i] + i) ^ (0x3e9 + i);
}

0x3660

看起来像是一个以NUL或NULL作结尾的循环结构。

0x3700

看到了值得注意的字符,0x3C、0x80相关,猜测与PE有关,加载了一个lib

afvn offset_to_pehdr var_18h
afvn import_table var_10h
afvn base_addr arg_ch
afvn entry var_20h
afvn img_thunk var_ch

0x38d0

获取PEB里的Ldr_data,然后遍历之

afvn ldr_data var_ch
afvn inLoadOrderModuleList var_8h

获取对应DLL的base address

0x3810

无论寄存器为何,其值都是从var_10h这个变量中获得,且整个过程中var_10h未被修改。在本函数中分别对

  • [var_10h]
  • [var_10h + 2]
  • [var_10h + 4]

这几个位置有一个比较,这些比较的数值都介于字符的十六进制范围内,为kK eE rR,即进行了一个大小写的判断,猜测在此处其正在寻找kernel32

afn search_kernel32

二级函数

0x35c0

类似mem_cpy_nbytes的函数

for (i = 0; i < cnt; ++i)
    arg1[i] = arg2[i];

0x3140

首先暴露了’GetProcAddress’。分析可得pe结构,重建IAT

afvn file_hdr arg_8h
afvn pe_hdr var_2ch
afvn export_table var_8h
afvn addr_of_ordname var_18h
afvn addr_of_name var_10h
afvn addr_of_func var_4h
afvn tmp var_ch

afn recoverIAT

0x34b0 & 0x3430

s 0x3430
afvn virtual_alloc
s 0x34b0
afvn virtual_free

0x3520

创建新页面然后写入数据

afn new_mem

0x3230

还记得前面的比较结果么,从那里可以看出端倪,恢复作用

afn prepare_right_string

孤悬函数

0x3960

[0x00003960]> pdf
/ 21: fcn.00003960 (int32_t arg_8h, uint32_t arg_ch);
|           ; arg int32_t arg_8h @ ebp+0x8
|           ; arg uint32_t arg_ch @ ebp+0xc
|           0x00003960      55             push ebp
|           0x00003961      8bec           mov ebp, esp
|           0x00003963      8b4508         mov eax, dword [arg_8h]
|           0x00003966      3b450c         cmp eax, dword [arg_ch]
|       ,=< 0x00003969      7307           jae 0x3972
|       |   0x0000396b      8b4508         mov eax, dword [arg_8h]
|      ,==< 0x0000396e      eb05           jmp 0x3975
..
|     |||   ; CODE XREF from fcn.00003960 @ 0x3969
|     ||`-> 0x00003972      8b450c         mov eax, dword [arg_ch]
|     ||    ; CODE XREF from fcn.00003960 @ 0x396e
|     ||    ; CODE XREF from fcn.00003960 @ +0x10
|     ``--> 0x00003975      5d             pop ebp
\           0x00003976      c3             ret

比较函数,返回较小的

if (arg1 >= arg2)
    return arg2
else
    return arg1

脚本记录

e scr.utf8 = false
e graph.gv.format = png
s 0x3ce0
aaa
s 0x3130
afn return_delta_to_heapaddr
s 0x3610
afn mem_cmp_n
s 0x3560
afn set_ldr
s 0x3580
afn replace_addr_nbytes
s 0x3c90
afn decode_n_bytesA
s 0x3960
afn get_min
s 0x35c0
afn mem_cpy_nbytes
s 0x3c00
afn magic_addr
s 0x3660
afn loop_to_modify
s 0x3700
afn unk_load_lib
s 0x38d0
afn fetch_mod_baseaddr
s fcn.00003140 
afn recover_IAT
s fcn.000034b0
afn virtualFree
s fcn.00003430
afn virtualAlloc
s 0x3520
afn new_memalloc
s 0x3230
afn prepare_getproc_and_loaddll
s 0x3810
afn search_kernel32
s 0x3ce0
afn mainfunc
s 0x3980
afn sub_mainfunc

Loader 阶段

毛来了微软的报告。