恶意代码 实验三1,3 PE文件病毒感染与恢复
1.分析感染后的可执行程序
(1)确定感染后程序包含哪几个节,每个节的访问属性如何;
访问属性参考文档
来源于PE Explorer
的帮助文档
The last member of a section header is the 32 bits 'Characteristics', which is a bunch of flags describing how the section's memory should be treated:
If bit 5 (IMAGE_SCN_CNT_CODE) is set, the section contains executable code.
If bit 6 (IMAGE_SCN_CNT_INITIALIZED_DATA) is set, the section contains data that gets a defined value before execution starts. In other words: the section's data in the file is meaningful.
If bit 7 (IMAGE_SCN_CNT_UNINITIALIZED_DATA) is set, this section's data is uninitialized data and will be initialized to all-0-bytes before execution starts. This is normally the BSS.
If bit 9 (IMAGE_SCN_LNK_INFO) is set, the section doesn't contain image data but comments, description or other documentation.
If bit 11 (IMAGE_SCN_LNK_REMOVE) is set, the data is part of an object file's section that is supposed to be left out when the executable file is linked.
If bit 12 (IMAGE_SCN_LNK_COMDAT) is set, the section contains "common block data", which are packaged functions of some sort.
Bit 15 (IMAGE_SCN_MEM_FARDATA) is reserved for future use.
Bit 17 (IMAGE_SCN_MEM_PURGEABLE) is reserved for future use.
The same is IMAGE_SCN_MEM_16BIT.
Bit 18 (IMAGE_SCN_MEM_LOCKED) is reserved for future use.
Bit 19 (IMAGE_SCN_MEM_PRELOAD) is reserved for future use.
Bits 20 to 23 specify an alignment of data on a 1/8192-byte boundary. This is valid for object files only.
Bit 24 (IMAGE_SCN_LNK_NRELOC_OVFL) indicates that the count of relocations for the section exceeds the 16 bits reserved for it in section header. If the bit is set and the NumberOfRelocations field in the section header is 0xffff, the actual relocation count is stored in the 32-bit VirtualAddress field of the first relocation.
If bit 25 (IMAGE_SCN_MEM_DISCARDABLE) is set, the section's data is not needed after the process has started. This is the case, for example, with the relocation information. I've seen it also for startup routines of drivers and services that are only executed once.
If bit 26 (IMAGE_SCN_MEM_NOT_CACHED) is set, the section's data should not be cached. Don't ask my why not. Does this mean to switch off the 2nd-level-cache?
If bit 27 (IMAGE_SCN_MEM_NOT_PAGED) is set, the section's data should not be paged out. This may be interesting for drivers.
If bit 28 (IMAGE_SCN_MEM_SHARED) is set, the section's data is shared among all running instances of the image. If it is e.g. the initialized data of a DLL, all running instances of the DLL will at any time have the same variable contents.
Note that only the first instance's section is initialized. Sections containing code are always shared.If bit 29 (IMAGE_SCN_MEM_EXECUTE) is set, the process gets 'execute'-access to the section's memory.
If bit 30 (IMAGE_SCN_MEM_READ) is set, the process gets 'read'-access to the section's memory.
If bit 31 (IMAGE_SCN_MEM_WRITE) is set, the process gets 'write'-access to the section's memory.
--------------
After the section headers we find the sections themselves. They are, in the file, aligned to 'FileAlignment' bytes (that is, after the optional header and after each section's data there will be padding bytes). When loaded (in RAM), the sections are aligned to 'SectionAlignment' bytes.
As an example, if the optional header ends at file offset 981 and 'FileAlignment' is 512, the first section will start at byte 1024. Note that you can find the sections via the 'PointerToRawData' or the 'VirtualAddress', so there is hardly any need to actually fuss around with the alignments.
The remainder of an image file contains blocks of data that are not necessarily at any specific file offset. Instead the locations are defined by pointers in the Optional Header or a section header. An exception is for images with a Section Alignment value (see the Optional Header description) of less than the page size of the architecture (4K for Intel x86 and for MIPS; 8K for Alpha). In this case there are constraints on the file offset of the section data. Another exception is that attribute certificate and debug information must be placed at the very end of an image file (with the attribute certificate table immediately preceding the debug section), because the loader does not map these into memory. The rule on attribute certificate and debug information does not apply to object files, however.
原本的弹窗程序test.exe
被感染后
原本的记事本程序重命名为test.exe
并被感染后
原本的计算器程序重命名为test.exe
并被感染后
(2)这两方面与感染前程序相比,有什么变化
原本的弹窗程序test.exe
被感染前
原本的记事本程序notepad.exe
被感染前
原本的计算器程序calc.exe
被感染前
分析发现,不管是哪个文件,文件被感染后比被感染前多出了一个.hum
节,其访问属性是IMAGE_SCN_CNT_CODE
(节中包含可执行的代码)、IMAGE_SCN_MEM_EXECUTE
(进程对节的内存有执行权限)、IMAGE_SCN_MEM_READ
(进程对节的内存有读权限)、IMAGE_SCN_MEM_WRITE
(进程对节的内存有写内存)。而感染前本来就有的节没有发生变化。
(3)判断“病毒”程序是否会反复感染同一文件;如果可以,多次感染和一次感染的不同点是什么;如果不可以,那么“病毒” 程序如何做到不重复感染
“病毒”程序不可以反复感染同一文件。
test.exe
已经被感染后尝试再次运行病毒程序,提示没有再次感染。
GetApiA部分代码的调试
用ollydbg调试,F7单步执行,步入GetApiA
在GetApiA
中调用另外一个过程
发现程序死循环在这个过程里面
查资料后发现这并不是死循环,而是当每次循环使计数器减一,当计数器值为0时跳出循环。此处loop使用的计数器是寄存器ECX,而通过寄存器EAX查找目标函数,根据堆栈和ESI,猜测要寻找的是GetModuleHandleA
函数。根据cmp bl,byte ptr ds:[edx+eax]
和 je short 感染程序.00401016
完成判断跳出循环。
用depends打开一个exe,查看user32.dll
中的函数,发现GetModuleHandleA
的编号是177。
此时找到GetModuleHandleA
一个字符一个字符地进行比较,把相同的位数存到EDX中
发现之后又使用这个循环去寻找其他函数,那么直接用F8跳过找函数的call 40100A
这部分代码即可。注意到其在查找GetModuleHandleA
之后又寻找了GetProcAddress
、LoadLibraryA
、CreateFileA
、CreatingFileMappingA
、MapViewOfFile
、UnmapViewOfFile
、CloseHandle
、GetFileSize
、SetFilePointer
、SetEndOfFile
、ExitProcess
这些函数。
my_infect部分代码的调试
跳转到40144D
处,F2下断点,F9运行至断点处,步入my_infect
部分的代码
经过call [ebp+aCreateFile]
和inc eax
后,EAX的值不为0,ZF值为0,所以je _Err
不会进行跳转,说明打开目标文件test.exe
成功。
同理,后面的得到文件大小、关闭文件、创建文件映射、映射文件也都成功,my_include
部分代码顺利执行完毕。
modipe.asm
部分代码的调试
my_include
部分执行结束后,紧接着的是modipe.asm
执行cmp word ptr [esi],'ZM'
后,ZF为1,所以jne CouldNotInfect
不跳转。
执行cmp word ptr [esi],'EP'
后,ZF为1,所以jne CouldNotInfect
不跳转
执行cmp dword ptr [esi+8],'dark'
后,ZF为1,所以je CouldNotInfect
跳转。也就是说,因为文件中已经存在了dark
这个感染标记,所以不再次感染此文件。
CouldNotInfect
部分代码的调试
执行test eax,eax
后,ZF为0,所以jnz @g12
不跳转
然后会依次call No caption
和call _tips
_tips
部分代码的调试
这里调用MessageBoxA函数,弹出弹窗“本程序仅感染本目录下未被感染过的test.exe
程序。”
_where
部分代码的调试
执行cmp dword ptr [esi+8],'dark'
后,ZF为0,判断出是启动程序而不是HOST程序,所以je jmp_oep
不跳转
然后jmp _xit
跳转到_xit
_xit
部分的代码调试
退出启动程序。
2.分析源程序main.asm
(1)定位并分析其中重定位相关的代码,说明存储相应偏差位移量的寄存器是哪个;
(2)定位并分析其中获取kernel32.dll在内存中装载位置的代码,说明记录装载位置的存储单元(包含寄存器和变量)
3.手工清除被感染程序中的“病毒”部分,将感染程序尽可能还原为原正常的程序,同时达到免疫效果
用winhex和PEview恢复文件并实现免疫
用winhex打开一个被感染后和一个被感染前的test.exe
,进行对比,同时借助PEview理解PE文件格式。
B6
处的节数修改为原来的3
B8
处的感染标记"dark"不要删除,这是使test.exe
免疫此实验中的病毒所要利用的。
D8
处的入口点地址需要修改为原来的1000
100
处的Image大小修改为原来的4000
220
到248
的.hum的IMAGE_SECTION_HEADER清0
A00
到最后的.hum节清零
A00
到最后的.hum节删掉,把文件恢复至原来大小
使用winhex的选块选择这部分
鼠标右键→编辑→移除
发现剩下了近一行,继续移除
至此文件恢复完毕
保存恢复后的文件为test_Rev.exe
,发现其大小已经变为被感染前的2560字节,而不再是被感染后的6656字节。
比较恢复后和被感染前的文件
在linux中使用xxd
指令将二进制文件转为16进制文本文件
xxd test_Rev.exe > test_Rev.txt
xxd test.exe > test.txt
然后用vscode比较两个文本文件的不同
比较发现,两个文件唯一不同的地方就在于test_Rev.exe
具有"dark"这个感染标记,则确定已经尽可能将文件恢复为感染前的样子
运行测试结果
运行test_Rev.exe
,可以实现与被感染前的test.exe
一样的效果
将test_Rev.exe
重命名为test.exe
,放到与病毒文件同一个文件夹中,尝试用病毒文件感染它
提示无法感染
再次运行test.exe
,发现确实没有被感染