//上传附件的时候,太大了,没法上传
//新手上路,老鸟勿喷
//技术点
//自定位、重定位、模拟PEloader装载过程
感染型病毒分析
MD5:18e56133699cd7372e8c129976bb4b40
类型:推广软件
一:病毒功能概要
该病毒属于感染型病毒,感染酷盘的安装程序。该病毒的感染方式为:将酷盘安装程序的入口地址(位于文件偏移:00263C处)开始处的大小为:36E个字节的数据修改为病毒的代码,并且将酷盘安装程序位于此处的代码备份到感染病毒的酷盘安装程序文件偏移0x003C3347地址开始的36E个字节中。从3AC5E8开始直到文件的结尾都是病毒附加的数据。
该病毒运行之后首先执行入口处的病毒代码,这部分代码完成的功能比较简单,首先,获得病毒当前执行代码所需要的API函数,当前病毒的主要功能是将部分病毒代码从感染病毒的酷盘安装程序文件中读取到内存中的指定位置:009B0000H。在这个内存位置,第一次形成一个完整的EXE文件(带有附加数据,并且是以文件的形式)。
程序的执行流程转向内存中的这个EXE文件的某个地址处,这个地址处的代码的主要功能就是模拟PEloader的工作原理将一个dll文件装载到内存中。
这部分代码在模拟加载该dll的过程中首先从被感染病毒的这个安装程序获取出该dll的完整数据,存储在内存:009D0000H。然后检查该dll文件的有效性,读取该dll载入内存中的首选RVA,并且在这个地址处申请该dll大小的内存空间10000000H(如果申请到的是其它的内存地址,则存在重定位的问题)接下来将文件头与节表的数据读入到申请到的这个内存空间。接下来根据文件头以及节表中的一些信息开始装载该dll,主要有装载各个节区、填充IAT、重定位、修改内存页的属性四大操作。完成这些操作之后,该dll被顺利的加载到内存空间中,接下来将程序的执行流程转向这个dll中。
在该dll中主要完成两件事情:第一件事情就是释放原来的安装程序到系统的临时目录下,并且启动它;第二件事就是释放一系列的安装程序到临时目录下,并且启动它们。
该dll释放原来的安装程序的过程,也就是针对该病毒的专杀过程。该dll首先将感染原先入口处的代码从文件的备份区copy到入口代码处,使得原先的程序能够正常的运行,然后截掉病毒代码,使得释放出来的程序是无毒的,然后将病毒代码释放到临时目录下,并且执行原来的文件(在所给的第一个样本中是无法执行这个纯净的快盘安装程序的,因为在文件的5BFBA4这个地方记录的值是31,读到内存中去的时候,赋给哨兵的值为1,因此在生成这个纯净版的快盘安装程序得不到执行的机会)
病毒程序为什么也得不到执行的机会?这应该也算是一种反调试的技术,病毒作者是这么做的,首先在感染病毒的安装程序的某个部分存储一个表示时间的数据(这个数据的的值是程序从被双击到执行到这一步的时间的一个上限****实际上这个值被设置的非常小,足以在测试阶段使得程序无法释放病毒)。在程序执行的过程中会获得程序从开始执行到现在所经历的时间,如果发现这个时间大于文件中记录的这个上限值,则说明该文件不是正常的执行,很可能是处于调试状态才执行到这一步的,因此拒绝执行生成病毒体的这一步操作。
从以上两点来看,该病毒应该是在测试阶段。
如果病毒程序部分能够正常执行,则会从染毒的快盘安装程序的中释放8个文件,并且顺次执行其中的可执行文件。在这8个文件中又有一个类似于下载者的“超级助手安装程序”,这个程序从网络上下载了四个程序,并且执行。
从病毒的执行效果来看,该病毒应该是一个推广软件
二、详细分析
1:当该染毒酷盘安装程序开始执行的时候,首先获得在这个执行阶段需要的API函数,该病毒程序通过以下几个步骤来获得所需要的API函数
a):先通过查找PEB的方式来获得kernel32.dll这个动态链接库的地址
xor ecx,ecx
mov eax,dword ptr fs:[30] ;eax = PEB地址
mov eax,dword ptr ds:[eax+c] ;eax = PEB_LDR_DATA
@@:
mov esi,[eax+1c] ;esi指向list_entry结构,从属于LDR_MODULE结构
mov ebp,[esi+8] ;ebp = 某个dll的基地址
mov edi,[esi+20] ;edi = 存储该dll的名称字符串的地址
mov esi,[esi] ;esi = 下一个list_entry的地址
cmp [edi+18],cl ;cl = 0
jnz @B
mov eax,ebp ;eax=返回查找到的地址
说明1:cmp指令为什么与0比较。在系统中一些系统必要的dll被装入内存空间的地址相对来说是比较固定的,在PEB中记录的该应用程序用到的dll链一般来说为ntdll.dll、kernel32.dl.而在记录kernel32.dll的结构LDR_MODULE中,偏移20处是存储该dll名称字符串的地址,在这个地址的偏移18处得值是固定的0,而存储ntdll.dll的信息的结构在这个地方的值非0。因此,为了更快捷、高效的查找kernel32.dll,应用了比较这个特殊区域的值的方式
说明2:如果将cmp指令改为cmp edi,offset szXXdll,则该程序段就能完成查找任意的dll基址的功能。
b):通过找到的kernel32.dll,来获得从kernel32.dll中导出的GetProcAddress
mov ebx,eax ;ebx = kernel32.dll的基地址 为DOS头部MZ
add eax,[ebx+3c] ;eax = PE头的位置,应该为:Signature = PE
mov ecx,ebx ;ebx = DOS头的位置
add ecx,[eax+78] ;ecx = 数据目录表的第一项,导出表
mov esi,ebx ;esi = DOS头得位置
add esi,[ecx+20] ;esi = AddressOfNames,即指向存储这个DLL导出的函数名地址数据
;esi现在已经指向了存储函数名字符串的一个内存区域
Mov edi,ebx ;edi = DOS头的位置
add edi,[ecx+24] ;edi = AddressOfNameOrdinals;名字索引数组;
mov edx,ebx ;edx = DOS头的位置
add edx,[ecx+1c] ;edx = AddressOfFunctions;函数地址数组
add ecx,[ecx+18] ;ecx = Base,是一个基数,加上序数就是函数地址数组的索引值;作者没有使用,不过确实没有用,值为0;
address_FindApi:
cld
loads dword ptr [esi] ;eax = 存储函数名字符串的相对地址RVA。
add eax,ebx ;eax = VA,是一个绝对地址
cmp dword ptr [eax],50746547
cmp dword ptr [eax+8],65726464
cmp dword ptr [eax+c],7373 ;只是检查了G e t P d d r e s s 这几个字符串,查 找字符串的时候无法完全解析出来
cmp byte ptr [eax+E],0
jnz @F ;如果不相等,则继续下一个元素的匹配
xor eax,eax
mov ax,[edi] ;将指向名字索引数组的地址空间的值取出来,实际上是函数地址数组的索引值(当然得加上一个Base)
mov eax,[edx+eax*4]
;每一项占用四个字节,eax = 这个函数在函数地址数组中的位置,这是一个RVA
add eax,ebx ;这是一个VA
ret ;返回 eax = 所寻找函数地址
@@:
Add edi,2 ;指向名字索引数组的指针同步移动。当字符串匹配之后,可以直接从名字数组中取出索引
loopd address_findApi
3):获取这段程序运行中用到的API函数,因为存储这些API函数名得字符串保存在全局变量中,因此在根据函数名获得这些API函数地址的时候,需要重定位,在本病毒的解决方法为
call @F
@@:
pop eax
add eax,5
ret
ascII "GetModuleFileNam"
ascII "eA",0
一般自定位技术的应用
szText db 'GetFileNameA',0
call @F
@@:
pop ebx
sub ebx,offset @B
mov eax,[ebx+offset szText]
eax返回后就是在任意内存地址处表示API函数名称的字符串的地址,最后利用GetProcAddress函数获得程序当前执行所需要的API函数
GetModuleFileNameA
CreateFileA
GetFileSize
SetFilePointer
ReadFile
VirtualAlloc
CloseHandle
2:在内存空间9B000000处生成第一个EXE文件,并将程序流程转到这个EXE文件的入口代码处开始执行。
首先将要生成的程序数据在感染病毒的酷盘安装程序中定位。并且将病毒体数据读取出,并且定位到该exe文件的代码入口处
调用GetModuleFileNameA,获得本进程的可执行文件路径名
调用CreateFileA,打开文件
调用GetFileSize,获得文件大小 5BFC34
调用SetFilePointer,设置文件指针到00B5FC24
调用ReadFile,读取文件中的数据:003C36E1
将这个数据 003C36E1 - 2C = 003C36B5,作为下一次进行读取数据的地址
调用SetFilePointer,设置文件指针到003C36B5
调用ReadFile读取这个地址开始的2C部分数据
调用一个子程序将上一步读到的数据解密出来
00 00 40 00 3C 32 00 00 3C 32 00 00 6E 03 00 00
E8 C5 3A 00 00 64 01 00 E8 29 3C 00 5F 09 00 00
47 33 3C 00 53 45 C9 00 B0 33 34 49 43
00400000 建议载入地址
0000323C程序入口地址
003AC5E8病毒体在文件中的起始位置
调用VirtualAlloc申请000170F9大小空间, 返回009B0000
(3C36E1 – 3AC5E8):这个值便是病毒体的大小
调用SetFilePointer 、ReadFile在染毒的快盘安装文件的偏移003AC5E8将病毒体读取出
调用子程序解密读到的这部分数据得到一个EXE文件
然后定位到生成的这个PE文件的偏移16400处开始执行。
说明一:在这一步生成的PE文件包含了接下来生成的PE文件,如果将第一步生成的PE文件的偏移16400之后的内容截取,便是第二次生成的PE文件。
说明二:在执行这部分代码时,为什么不需要重定位。因为在接下来的执行过程中,凡是涉及到需要重定位的地方(访问全局变量、包含绝对地址的跳转等)都进行了自定位。
3:从16400开始的这部分执行代码的作用,就是将一个从染毒的快盘安装程序中得到的dll装载到内存中,并且执行。即模拟Peloader装载可执行文件的过程
a):首先获得本exe运行过程中所需要的API函数,过程类似于上述染毒快盘程序获得自己所需的API函数的过程。
b):以类似于获得第一个exe程序在染毒文件中的位置的方式定位dll文件在染毒程序中的位置,这次生成的PE文件的大小是生成的第一个PE文件的大小截去偏移16400之后的数据。本dll的数据被存储在009D0000
c):验证该PE结构的有效性,主要是验证MZ标志以及PE标志两个关键点。
d):装载程序首先根据该dll的PE结构中IMAGE_OPTIONAL32_HEADER结构中的成员变量ImageBase申请内存空间,申请的内存空间是否是这个记录值,决定了这段程序需不需要重定位操作。
e):该装载程序首先将该PE文件的PE头+节表数据部分读到新申请到的内存空间中。因为在后续的操作中,将会根据PE文件头+节表中记录的一些重要数据信息进行装载,确保装载过程的顺利进行。
f):根据读取的节表信息以及PE文件头信息,开始往申请的内存空间中填充各个节区的数据。
mov eax,offset PE_address ;指向PE文件头
cmp word ptr [eax],5A4D
mov esi,[eax+3c]
add esi,eax ;esi执行IMAGE_FILE_HEADER
cmp dword ptr [esi],4550
mov eax,[esi+50] ;程序调入内存后占用空间的大小
mov ecx,[esi+34] ;载入程序的首选RVA
//验证PE文件的有效性
invoke VirtualAlloc,ecx,eax,MEM_RESERVER,PAGE_ECECUTE_READ_WRITE
mov hMmeory,eax
push esi
mov esi,offset file_address ;esi = 文件在磁盘上的地址
mov ecx,sizeo_header_section ;edi = 文件被映射进内存的地址
rep movs dword ptr es:[edi],dword ptr [esi]
;到这一步已经将文件头以及节表信息读入到了另外的一个内存中
mov eax,edi
add eax,[eax+3C]
mov Nume_Sections,[eax+06h]
@@:
lea ebx,[edi+sizeof IMAGE_OPION_HEADER32];指向节表的节表项
add ebx,10 ;ebx 指向成员变量 SizeOfRawData,本节经过对齐后的大小
mov edx,[ebx-4] ;本节的RVA
add edx,edi
mov ecx,[ebx] ;本节经过文件对齐后的大小
invoke VirtualAlloc,edx,ecx,1000,0004
mov ecx,[ebx+4] ;本节原始数据在文件中的位置RVA
add ecx,esi ;应该在文件中读取的数据的位置VA
mov edx,[ebx] ;本节经过文件对齐后的大小
invoke memcpy,eax,ecx,edx
xor eax,eax
inc eax
add Num_counter,eax
add edi,28H ;每个节表项大小为28H个字节,指向下一个节表
cmp Num_Sections,Num_counter
jl @B
;以上过程就完成了各个节区数据的装载过程
g):根据申请到的内存空间地址是否为ImageBase中记录的值来判断要不要进行重定位
其重定位过程可以描述为:
push ebx mov ebx, addr_loadin ;ebx中存储的是实际装入到内存中的值
mov eax,addr_PE ;eax指向PE文件头的地址
add eax, 0A0 ;eax = 指向数据目录表中的第六项,重定位表
mov ecx, [eax+4] ;判断一下重定位地址表的大小是否为0 test ecx, ecx
jbe addr_end
mov ecx, [eax] ;ecx现在取得了重定位基址表的RVA
mov eax, [ebx+ecx] ;eax,现在成为了VA,eax现在指向重定位表第一个表项
;这个数据是一个RVA,这块需要重定位的数据的开始 的RVA
Add ecx, ebx ;ecx,现在也是指向重定位表的第一个表项
Test eax, eax
Jbe addr_end
Push ebp
Push esi
Push edi
addr_relocation_big:
lea edi, [ebx+eax] ;edi现在是一个VA,是这个区块中需要重定位的数据的 基地址VA,注意这个块中需要重定位
;的数据表述也是以RVA表示的,因此找到需要重定位 的数据之后要加上这个VA
mov eax, [ecx+4] ;重定位块中该表项的长度
sub eax, 8
xor esi, esi
test eax, FFFFFFFE 如果该表项长度无效,则跳转
lea edx, [ecx+8] ;edx现在指向重定向位数组的第一项
jbe addr_fuck_unless_2
addr_relocation_small:
xor eax, eax
mov ax, [edx] ;eax,是需要重定位的数据
mov ebp, eax
and ebp, FFFFF000 ;屏蔽掉低三位
cmp ebp, 3000 ;判断这个重定位数据项是否有效
jnz addr_fuck_unless ;无效则退出
mov ebp, sub_counter ;实际装入的地址与预装入的地址差值
and eax, 0FFF ;这个内存页中需要重定位的数据一共有4KB add eax, edi ;eax,现在是一个VA
add [eax], ebp ;完成重定位操作。
addr_fuck_unless_1:
mov eax, [ecx+4] //该表项的大小
inc esi //用作一个计数器
sub eax, 8 //这个结构中页地址以及表项的大小(为什么要减去8的答案)
add edx, 2 //指向重定位项数组的下一项
shr eax, 1 //每一个表项是占用了两个字节,因此记数的时候应该/2 cmp esi, eax //判断这一个页面上的数据是否重定位完
jb addr_relocation_small
addr_fuck_unless_2:
add ecx, [ecx+4] //ecx通过这种方式指向下一个表项
mov eax, [ecx]
test eax, eax //最后一个表项全部为0,因此可以这种方式判断
ja addr_relocation_big
pop edi
pop esi
pop ebp
addr_end:
pop ebx
retn 0C
至此完成重定位的操作
h):接下来要填充IAT
mov eax,offset PE_address ;存储这个文件的'PE'标志的地址
mov edx,offset Image_Base ;存储这个文件的基地地址
add eax,80H ;eax = 现在指向记录导入表的数据结构 IMAGE_DATA_DIRECTORY
mov edx,[eax+4] ;edx = 导入表这个结构的大小
test edx,edx
jbe addr_end
mov esi,[eax] ;将导入表的基址传递给esi,这是一个RVA
add esi,Image_Base ;将RVA转化为VA
invoke IsBadReadPtr,esi,14H ;检查这块内存区
test eax,eax
jnz addr_end
search_dll:
mov e ax,[esi+0c] ;eax = 存储DLL名称的RVA
add eax,Image_Base ;找到存储dll名字字符串的VA
invoke LoadLibrary,eax ;将此dll加载到内存
mov ebx,eax
cmp ebx,-1
je addr_end
mov m_dll_Base,eax
mov eax,[esi] ;现在eax 是OriginalFirstThunk这个结构的RVA
mov edx,Image_Base
mov esi,[esi+10] ;指向FirstThunk这个成员变量的值即IAT的RVA
lea edi,[edx+eax] ;现在edi 是OriginalFirstThunk这个结构的VA
mov eax,edx ;eax = Image_Base
add esi,eax ;IAT这个结构的VA被记录
mov eax,[edi] ;eax 指向一个IMAGE_THUNK_DATA的结构
test eax,eax
jz addr_end
continue_getproc:
test eax,80000000 ;判断导入函数的的导入方式
je import_process ;函数是以序号的方式导入的
and eax,0FFFF ;表示函数是以字符串的方式导入
mov edx,Image_Base
lea eax,[edx+eax+2] ;得到的这个值是存储函数名称字符串的地址
;+2是因为跳过IMAGE_IMPORT_BY_NAME的Hint字段
import_process:
invoke GetProcAddress,ebx,eax
mov [esi],eax ;将得到的函数地址存储在IAT中
mov eax,[edi+4] ;判断需要从这个dll中导入的函数是否结束, 因为OriginalFirstThunk这个结构最后一项以0结尾
add edi,4
add esi,4
test eax,eax
mov eax,[edi]
jnz continue_getproc
mov edi,offset PE_addr
add esi,14H
mov ebx, Image_Base
jmp search_dll
addr_end:
ret
至此,IAT的填充过程已经结束
i):利用函数VirtualProtect函数修改节的属性
j):获得装载好的这个dll的入口代码,开始执行
4:这个dll在执行的过程中完成了两件事情,第一件事情是将原来没有被感染的酷盘安 装程序给还原出来,并且启动;第二件事情,就是释放8个安文件,并且执行其中的七个,另一个是个pdf文档(无效文件)。以达到推广软件的目的。
a):启动无毒的酷盘安装程序的过程
创建路径:c:\document~1\admini~1\loacals~1\temp\9b55\kanbox_10039.exe
这个病毒创建原无毒酷盘安装程序的思路
1:恢复入口代码处得原先被病毒覆盖了的数据
2:获得文件总的大小,减去病毒的大小,得到原文件没有被感染之前的大小
3:WriteFile的方式写出来
4:以CreateProcess的方式执行原来的文件。
入口处的数据被病毒保存在了感染后的文件的偏移
起始地址为:0x003c3347
终止地址为:0x003c36b4
被篡改的字节数为:36E
原文件被增加的数据地址3AC5E8,从这个地址以后的所有数据都是染毒文件数据, 因此应该清除
入口处被改动的数据参数为:起始地址为:0000263C
终止地址为:000029A9
被篡改的字节数为:36E
具体实现过程,在针对该病毒的专杀工具中有说明。
b):该病毒释放资源,并且执行之;
该病毒在释放资源的时候,为每一个资源都组建了一个数据结构,这个数据结构 存储在被感染的快盘安装程序的某个部分。程序运行时读取这个数据到内存中。
Resource struct
Resource_Name dd ? ;存储生成的文件名字符串
Resource_Addr dd ? ;存储资源的地址
Resource ends
然后多个以它为类型的数据元素组织成数组。该病毒循环遍历这个数组,找到记录某一个资源的数据结构,根据数据结构组建出要释放的资源,并执行。
在资源中释放的这些文件中,有一个需要特别指出——setup3027.exe
这个安装程序又下载了四个程序,下载之后安装执行。
从资源中释放的文件
由setup3027.exe下载的文件
注:在调试过程中,该程序设置了很多反调试的陷阱,但是手法上都是一样的。就是检测当前程序的运行状态,是否处于调试状态,如果是的话就退出程序的执行。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)