职业病毒分析师的职责是为安全运营团队提供有力的后台支持,并能处置突发的大型感染病毒和勒索病毒。感染性病毒反反复复难以根除,往往是安全运营团队棘手的问题,给企业带来了巨大的损失。
下面是自己写的Virut感染型病毒一种变种的修复工具。该工具没有遍历文件,只是修复了C盘下被病毒感染后的一个文件:hypertrm.exe.V。有兴趣的可以研究交流。
先普及一个重要的知识点:RVA转FOA
FOA是实际的文件HEX的物理文件偏移,RVA是相对于基地址ImageBase的偏移。
它俩的关系:
转换的代码如下:
首先对于感染型病毒,我们要弄清楚的几点:
1.感染哪种类型的文件(exe?dll?sys?com?)
2.感染GUI还是CUI?(这个问题和1一样,只是容易被忽略)
3.入口点有没有被篡改?假如被篡改如何找到入口点?
4.附加恶意代码数据有几种方式?
带着这几种问题进行动态调试可以事半功倍。
Virut本变种感染方式:
1.感染.rsrc区段并在尾部追加病毒代码
2.修改入口点到资源段的病毒代码处
感染型病毒修复流程:
1.定位被感染区段,找到入口点地址。
2.入口点修复(真实的入口点可能需要执行一段病毒代码才出现,具体入口点地址何时暴露,需要动态调试分析)
3.删除附加的病毒代码数据
4.PE信息修复(包括被感染区段大小,区段数量(一般减1),SizeOfImage)
首先解析PE结构,拿到各个信息点的指针,定位到区段信息指针PIMAGE_SECTION_HEADER。具体PE结构的知识这里不再说。
遍历区段信息表,找到被感染的.rsrc资源段,为后续抹除掉.rsrc段尾部数据做好准备。但在抹除掉附加数据之前,必须先在病毒代码里找到被修改的入口点地址。
上面代码主要目的是修复入口点信息。因为Virut感染后的文件起初加载的入口点被修改到了资源节,通过动态调试发现真正的入口点RealEntry是第一个call的相对偏移再加上一个有特征的随机数。最后把RealEntry写入pOptionalHeader->AddressOfEntryPoint信息处修复原来的PE入口。
删除.rsrc资源段并修复信息。原始资源段如果不足0x200字节对齐的话,都会在区段末尾有"PADDINGX"的字符,搜索到这种字符直接剪切。或者搜索到固定的6个零字节,那么也认为是原始资源段的结尾(这是不严密的,但是此变种感染附加的代码是纯汇编而写,不会存在函数间隙,的确没有发现过多的零字节,这样才能作为资源段结尾特征,其他感染性病毒要看情况而定)。
最后根据对齐方式校正文件大小(一般都是文件大小0x200对齐,镜像内存大小0x1000对齐),修复SizeOfImage大小,整个修复过程基本完成。
可见感染型病毒的修复过程是具有一定挑战性的,因为很难保证自己的一些判断依据(如入口点寻找的特征码、附加病毒数据的定位)是严格正确又严密的。如果这些判断标准比较严格,那么很有可能会减小用户文件的大小甚至造成原文件损坏,相反标准太低可能会造成部分被感染文件未被修复,从而造成反弹重复感染(客户现场见太多了)。彻底查杀并且完美查杀一种感染型病毒需要对病毒有非常深入的了解,才能设计出完美独特的修复方案,做到百无一失。
最后附上源代码,供大家学习交流。共享看雪论坛,共建网络安全家园。
DWORD Convert(wchar_t
*
lpBase, DWORD dwAddr,
BOOL
bFile2RVA)
{
DWORD dwRet
=
-
1
;
/
/
读取该文件的信息(文件内存对齐方式以及区块数量,并将区块表指针指向区块表第一个区块头)
PIMAGE_DOS_HEADER pDosHeader
=
(PIMAGE_DOS_HEADER)lpBase;
/
/
pDosHeader
-
>e_lfanew,
LONG
AddressOfNewExeHeader定位到
4550
的
50
PIMAGE_NT_HEADERS pNtHeader
=
(PIMAGE_NT_HEADERS)((unsigned
long
)lpBase
+
pDosHeader
-
>e_lfanew);
/
/
转换为
long
是操作实际物理地址值,再从unsigned
long
转换为指针
/
/
printf(
"%x\n"
,
*
pNtHeader);
/
/
显示此时是不是
4550
/
/
printf(
"NumberOfSection:%x\n"
,
*
(pNtHeader
+
4
));
DWORD dwMemAlign
=
pNtHeader
-
>OptionalHeader.SectionAlignment;
DWORD dwFileAlign
=
pNtHeader
-
>OptionalHeader.FileAlignment;
int
dwSecNum
=
pNtHeader
-
>FileHeader.NumberOfSections;
PIMAGE_SECTION_HEADER pSecHeader
=
(PIMAGE_SECTION_HEADER)((char
*
)lpBase
+
pDosHeader
-
>e_lfanew
+
sizeof(IMAGE_NT_HEADERS));
/
/
用同样的方法定义到了段头部
DWORD dwHeaderSize
=
0
;
if
(!bFile2RVA)
/
/
内存偏移转换为文件偏移
{
/
/
看需要转移的偏移是否在PE头内,如果在则两个偏移相同
dwHeaderSize
=
pNtHeader
-
>OptionalHeader.SizeOfHeaders;
/
/
0x400
if
(dwAddr <
=
dwHeaderSize)
/
/
0x400
{
delete lpBase;
lpBase
=
NULL;
return
dwAddr;
}
else
/
/
不在PE头里,查看该地址在哪个区块中
{
for
(
int
i
=
0
; i < dwSecNum; i
+
+
)
/
/
0x7200
十进制
29184
{
DWORD dwSecSize
=
pSecHeader[i].Misc.VirtualSize;
if
((dwAddr >
=
pSecHeader[i].VirtualAddress) && (dwAddr <
=
pSecHeader[i].VirtualAddress
+
dwSecSize))
/
/
文件偏移
=
该区块的文件偏移
+
(该偏移
-
该区块的内存偏移)
dwRet
=
pSecHeader[i].PointerToRawData
+
dwAddr
-
pSecHeader[i].VirtualAddress;
}
}
}
else
/
/
文件偏移转换为内存偏移
{
dwHeaderSize
=
pNtHeader
-
>OptionalHeader.SizeOfHeaders;
/
/
看需要转移的偏移是否在PE头内,如果在则两个偏移相同
if
(dwAddr <
=
dwHeaderSize)
{
delete lpBase;
lpBase
=
NULL;
return
dwAddr;
}
else
/
/
不再PE头里,查看该地址在哪个区块中
{
for
(
int
i
=
0
; i < dwSecNum; i
+
+
)
{
DWORD dwSecSize
=
pSecHeader[i].Misc.VirtualSize;
if
((dwAddr >
=
pSecHeader[i].PointerToRawData) && (dwAddr <
=
pSecHeader[i].PointerToRawData
+
dwSecSize))
/
/
内存偏移
=
该区块的内存偏移
+
(该偏移
-
该区块的文件偏移)
dwRet
=
pSecHeader[i].VirtualAddress
+
dwAddr
-
pSecHeader[i].PointerToRawData;
}
}
}
/
/
delete lpBase;
/
/
lpBase
=
NULL;
return
dwRet;
}
DWORD Convert(wchar_t
*
lpBase, DWORD dwAddr,
BOOL
bFile2RVA)
{
DWORD dwRet
=
-
1
;
/
/
读取该文件的信息(文件内存对齐方式以及区块数量,并将区块表指针指向区块表第一个区块头)
PIMAGE_DOS_HEADER pDosHeader
=
(PIMAGE_DOS_HEADER)lpBase;
/
/
pDosHeader
-
>e_lfanew,
LONG
AddressOfNewExeHeader定位到
4550
的
50
PIMAGE_NT_HEADERS pNtHeader
=
(PIMAGE_NT_HEADERS)((unsigned
long
)lpBase
+
pDosHeader
-
>e_lfanew);
/
/
转换为
long
是操作实际物理地址值,再从unsigned
long
转换为指针
/
/
printf(
"%x\n"
,
*
pNtHeader);
/
/
显示此时是不是
4550
/
/
printf(
"NumberOfSection:%x\n"
,
*
(pNtHeader
+
4
));
DWORD dwMemAlign
=
pNtHeader
-
>OptionalHeader.SectionAlignment;
DWORD dwFileAlign
=
pNtHeader
-
>OptionalHeader.FileAlignment;
int
dwSecNum
=
pNtHeader
-
>FileHeader.NumberOfSections;
PIMAGE_SECTION_HEADER pSecHeader
=
(PIMAGE_SECTION_HEADER)((char
*
)lpBase
+
pDosHeader
-
>e_lfanew
+
sizeof(IMAGE_NT_HEADERS));
/
/
用同样的方法定义到了段头部
DWORD dwHeaderSize
=
0
;
if
(!bFile2RVA)
/
/
内存偏移转换为文件偏移
{
/
/
看需要转移的偏移是否在PE头内,如果在则两个偏移相同
dwHeaderSize
=
pNtHeader
-
>OptionalHeader.SizeOfHeaders;
/
/
0x400
if
(dwAddr <
=
dwHeaderSize)
/
/
0x400
{
delete lpBase;
lpBase
=
NULL;
return
dwAddr;
}
else
/
/
不在PE头里,查看该地址在哪个区块中
{
for
(
int
i
=
0
; i < dwSecNum; i
+
+
)
/
/
0x7200
十进制
29184
{
DWORD dwSecSize
=
pSecHeader[i].Misc.VirtualSize;
if
((dwAddr >
=
pSecHeader[i].VirtualAddress) && (dwAddr <
=
pSecHeader[i].VirtualAddress
+
dwSecSize))
/
/
文件偏移
=
该区块的文件偏移
+
(该偏移
-
该区块的内存偏移)
dwRet
=
pSecHeader[i].PointerToRawData
+
dwAddr
-
pSecHeader[i].VirtualAddress;
}
}
}
else
/
/
文件偏移转换为内存偏移
{
dwHeaderSize
=
pNtHeader
-
>OptionalHeader.SizeOfHeaders;
/
/
看需要转移的偏移是否在PE头内,如果在则两个偏移相同
if
(dwAddr <
=
dwHeaderSize)
{
delete lpBase;
lpBase
=
NULL;
return
dwAddr;
}
else
/
/
不再PE头里,查看该地址在哪个区块中
{
for
(
int
i
=
0
; i < dwSecNum; i
+
+
)
{
DWORD dwSecSize
=
pSecHeader[i].Misc.VirtualSize;
if
((dwAddr >
=
pSecHeader[i].PointerToRawData) && (dwAddr <
=
pSecHeader[i].PointerToRawData
+
dwSecSize))
/
/
内存偏移
=
该区块的内存偏移
+
(该偏移
-
该区块的文件偏移)
dwRet
=
pSecHeader[i].VirtualAddress
+
dwAddr
-
pSecHeader[i].PointerToRawData;
}
}
}
/
/
delete lpBase;
/
/
lpBase
=
NULL;
return
dwRet;
}
if
(((PIMAGE_DOS_HEADER)pFile)
-
>e_magic !
=
IMAGE_DOS_SIGNATURE)
return
-
1
;
/
/
不是DOS头,返回
DWORD dwNewPos
=
(DWORD)pFile
+
((PIMAGE_DOS_HEADER)pFile)
-
>e_lfanew;
PIMAGE_NT_HEADERS32 pNTHeader
=
(PIMAGE_NT_HEADERS32)(dwNewPos);
if
(pNTHeader
-
>Signature !
=
IMAGE_NT_SIGNATURE)
return
-
1
;
/
/
不是NT头,说明不是PE文件,返回
PIMAGE_FILE_HEADER pFileHeader
=
&(pNTHeader
-
>FileHeader);
PIMAGE_OPTIONAL_HEADER32 pOptionalHeader
=
&(pNTHeader
-
>OptionalHeader);
PIMAGE_SECTION_HEADER pSectionHeader
=
IMAGE_FIRST_SECTION(pNTHeader);
if
(((PIMAGE_DOS_HEADER)pFile)
-
>e_magic !
=
IMAGE_DOS_SIGNATURE)
return
-
1
;
/
/
不是DOS头,返回
DWORD dwNewPos
=
(DWORD)pFile
+
((PIMAGE_DOS_HEADER)pFile)
-
>e_lfanew;
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2020-9-22 11:14
被绝望的皮卡丘编辑
,原因: