Dofoil病毒分析
Dofoil-Like Virus
这个样本粗略地看了一下,里面的符号信息保留得不少,以为是个比较容易分析的东西,哪知道炸了。
行为
扔到虚拟机里跑,删除自身,没有弹窗,也没有生成什么文件,文件未被加密说明不是勒索软件。这个情况可能是:
- 反虚拟机机制,病毒行为与实际不同
- 木马,注入某进程在后台隐蔽地监听
- 挖矿
单看肯定没用,扔到沙盒里试试,投放了与自身相同的文件,创建了一个进程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准备的。原本的字符串分别是VtttvvvAlloc和ternel32,通过下面函数替换后可以产生函数名,规避分析者对于字符串的检查。
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…)。

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

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

根据函数调用图可以指定一个分析策略:先分析没有依赖的函数,自底向上直到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;
}
整个的代码逻辑包含:
- 一个循环
- 通过终止条件和循环次数控制返回值,return 1 or 0 (或许是true和false)
- 包含递增(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 阶段
毛来了微软的报告。