世上最完整的病毒分及实例文档格式.docx
- 文档编号:17704394
- 上传时间:2022-12-08
- 格式:DOCX
- 页数:148
- 大小:703.96KB
世上最完整的病毒分及实例文档格式.docx
《世上最完整的病毒分及实例文档格式.docx》由会员分享,可在线阅读,更多相关《世上最完整的病毒分及实例文档格式.docx(148页珍藏版)》请在冰豆网上搜索。
它的一些特性继承自Unix的Coff(commonobjectfileformat)文件格式,同时为了保证与旧版本MS-DOS及Windows操作系统的兼容,PE文件格式也保留了MS-DOS中那熟悉的MZ头部。
病毒能够感染PE文件,因为病毒设计者深知其结构。
1.2.1PE病毒常用技术
病毒也和正常的应用程序一样,涉及到函数的调用和变量的使用。
1、调用API函数的方法
API是“ApplicationProgrammingInterface”的英文缩写,很象DOS下的中断。
中断是系统提供的功能,在DOS运行后就被装载在内存中,而API函数是当应用程序运行时,通过将函数所在的动态连接库装载到内存后调用函数的。
请大家先在MSDN的“索引”中输入函数“MessageBox”然后回车,就可以查到该函数的使用方法。
MSDN是微软提供的开发帮助,是在Windows下编程必备的资料文件。
在Windows下设计应用程序不直接或间接使用API是不可能的,有些高级语言看似没有使用API,只不过它们提供的模块对API进了封装。
API的使用分为静态和动态使用两种方式。
在源程序中调用API两种方式都可以使用,但对未公开API因为无相应的头文件,只能使用动态方式。
下面以VC++中调用MessageBox说明两种方式的区别。
(1)静态方式
charnote_inf[]=”谢谢使用”;
charnote_head[]=”提示信息”;
:
MessageBox(0,note_inf,note_head,MB_OK);
//:
表示全局函数
反汇编结果如图3-1。
“PUSH00000000”对应的是MB_OK常量入栈,“PUSH0040302C”对应的是一个字符串的偏移地址入栈,“PUSH00403020”对应的是另一个字符串偏移地址的入栈,第2行“PUSH00000000”对应窗口句柄入栈。
当程序执行时,装载器会将user32.dll装载到应用程序虚拟空间,同时将MessageBoxA(对应ANSI格式,另一种为UNICODE格式,用MessageBoxW表示。
这是因为函数的参数有字符串,而字符串有两种格式所致)的入口地址填充到虚拟地址004021B8h。
虚拟地址004021B8h是由PE头中IMAGE_DATA_DIRECTORY数组来定位的,当编译器生成PE文件时就计算好了。
图3-1API的汇编调用
(2)动态方式
动态方式先定义函数指针,使用函数LoadLibrary装载要调用的函数所在的dll文件,获取模块句柄。
然后调用GetProcAddress获取要调用的函数的地址。
voidCTestDlg:
OnButton
{
//定义MessageBox函数指针
typedefint(WINAPI*_MessageBox)(
HWNDhWnd,
LPCTSTRlpText,
LPCTSTRlpCaption,
UINTuType
);
//定义MessageBox指针变量
_MessageBoxnew_MessageBox;
//装载MessageBox函数所在dll文件
HINSTANCEhb=LoadLibrary("
user32.dll"
//获取ANSI格式的MessageBox函数地址
new_MessageBox=(_MessageBox)GetProcAddress(hb,"
MessageBoxA"
//动态调用函数MessageBox
new_MessageBox(0,"
欢迎使用!
"
"
提示信息"
MB_OK);
//释放MessageBox函数所在模块
CloseHandle(hb);
}
动态方式是在需要调用函数时才将函数所在模块调入到内存的,同时也不需要编译器为该函数在导入表中建立相应的项。
2、病毒调用API函数
病毒要完成相应的功能,不可能不调用API函数。
病毒感染PE文件可能是在源程序中加入病毒代码,但多数是在生成PE文件后通过修改PE文件感染的。
对后种情况,病毒难以去为使用的API建立导入表项,只有使用第动态方式调用API。
动态使用API的前提是预先知道LoadLibrary和GetProcAddress的地址,可以预先设定或搜索API的地址实现。
一个正常的Windows程序,它至少需要调用模块kernel32.dll,因为应用程序正常退出时需要调用函数ExitProcess,而该函数位于模块kernel32.dll内。
然而函数LoadLibrary和GetProcAddress也位于模块kernel32.dll内。
既然模块kernel32.dll总在内存,如果我们知道这两个函数地址,直接调用就可以了。
(1)检测函数地址
先用间接方式检测函数的地址,代码如下,结果见图3-2。
OnButton1()
//定义函数LoadLibrary和GetProcAddress的原型
typedefHINSTANCE(WINAPI*_LoadLibrary)(
LPCTSTRlpLibFileName
typedefFARPROC(WINAPI*_GetProcAddress)(
HMODULEhModule,
LPCSTRlpProcName
//定义指针
_LoadLibrarynew_LoadLibrary;
_GetProcAddressnew_GetProcAddress;
//装载函数所在模块kernel32.dll
kernel32.dll"
//获取函数首地址
new_LoadLibrary=(_LoadLibrary)GetProcAddress(hb,"
LoadLibraryA"
new_GetProcAddress=(_GetProcAddress)GetProcAddress(hb,"
GetProcAddress"
//显示结果
CStringinf;
inf.Format("
LoadLibrary=%Xh\r\nGetProcAddress=%Xh"
new_LoadLibrary,new_GetProcAddress);
MessageBox(0,inf,"
地址信息"
}
图3-2取函数地址
(2)在程序中直接使用函数地址
如下的代码直接使用函数LoadLibrary和GetProcAddress地址,然后用它们动态调用函数MessageBox,执行的结果是显示信息框。
OnButton2()
//定义MessageBox原型
charfunName[]="
;
//定义地址
DWORDLoadLibraryAddr=0x77E80221;
DWORDGetProcAddressAddr=0x77E80CAB;
//定义函数所在模块名
HINSTANCEhb;
chardllName[]="
__asm{;
VC++中嵌入汇编代码
leaeax,dllName
pusheax
movebx,LoadLibraryAddr
callebx;
调用LoadLibrary获取shell32.dll模块句柄
movhb,eax
//直接使用地址
leaeax,funName
pusheax
pushhb
movebx,GetProcAddressAddr;
callebx
movnew_MessageBox,eax
//动态调用MessageBox,显示信息框
预先设定API地址使代码比较短,但只能局限在某个操作系统版本下运行,也必须先保证它所在模块在内存中。
同一个API函数,在不同的系统下的地址可能不相同。
该方法,也叫“预编码”技术。
(3)搜索API地址
只需要从虚拟内存搜索到LoadLibrary和GetProcAddress的地址,用这两个函数就可以获取其它函数的地址。
前面已经介绍过,一般程序都会加载LoadLibrary和GetProcAddress所在的库文件kernel32.dll,那么在内存中搜索kernel32.dll所在基地址,然后再分析kernel32.dll的PE结构,就可以找到LoadLibrary和GetProcAddress的地址。
分析多个Windows系统,可以知道kernel32.dll加载的大致地址,比如根据在9X下其加载地址是0xBFF70000,在Windows2000下加载基址是0x77E80000,然后可由该地址向高地址搜索可以找到其基址。
也可以由高地址到低地址开始搜索,搜索开始的地址由程序入口处的ESP获得。
程序装载器调用一个程序后将程序的返回地址入栈,然后转去执行该程序。
经反汇编证明,返回地址是属于Kernel32.dll模块中。
由于内存属性决定,有些内存可能因未分配而不能读,如果读它,将导致出错。
为避免因错误而程序不能继续,必须使用SEH处理。
SEH(“StructuredExceptionHandling”)即结构化异常处理,是Windows操作系统提供给程序设计者的强有力的处理程序错误或异常的武器,有些类似于VISUALC++中使用的_try{}_finally{}和_try{}_except{}。
后面的例子使用了从这些地址向高地址搜索的方法。
如果搜索到Kernel32.DLL的加载地址,其头部一定是“MZ”标志,由模块起始偏移0x3C的双字确定e_lfanew,再由e_lfanew找到的PE头部标志必然是“PE”,因此可根据这两个标志判断是否找到了模块加载地址。
经实验证明,该判断方法非常可靠,基本不会出现错误。
因为所有版本的Windows系统下Kernel32.DLL的加载基址都是按照0x1000对齐的,根据这一特点可以不必逐字节搜索,按照0x1000对齐的边界地址搜索即可。
以由程序入口处的ESP为例,方法如下。
下面的代码搜索LoadLibrary和GetProcAddress的地址。
.586p
.modelflat,stdcall
optioncasemap:
none;
casesensitive
include\masm32\include\windows.inc
include\masm32\include\kernel32.inc
includelib\masm32\lib\kernel32.lib
include\masm32\include\user32.inc
includelib\masm32\lib\user32.lib
GetApiAddressPROTO:
DWORD,:
DWORD
.data
Kernel32Addrdd?
ExportKerneldd?
GetProcAddrdd?
LoadLibraryAddrdd?
aGetProcAddrdb"
0
GetProcAddLenequ$-aGetProcAddr-1
aLoadLibrarydb"
LoadLibraryLenequ$-aLoadLibrary-1
szTitledb"
检测结果"
temp1db"
Kernel32.dll基本地址:
%8x"
0dh,0ah
db"
LoadLibrary地址:
GetProcAddress地址:
0dh,0ah,0
temp2db256dup(?
)
.code
main:
Start:
movesi,[esp];
esi为返回地址所在的页,例若[esp]=77e78f94h,esi=77e78000h
andesi,0fffff000h;
转换为1000h字节的倍数
LoopFindKernel32:
subesi,1000h
cmpwordptr[esi],'
ZM'
;
搜索EXE文件头
jnzshortLoopFindKernel32
GetPeHeader:
movedi,dwordptr[esi+3ch];
偏移3ch处为"
PE"
addedi,esi
cmpwordptr[edi],4550h;
确认是否PE文件头
jnzshortLoopFindKernel32;
esi->
kernel32,edi->
kernel32PEHEADER
movKernel32Addr,esi
获得Kernel32.dll中的所需的Api的线性地址:
invokeGetApiAddress,Kernel32Addr,addraLoadLibrary
movLoadLibraryAddr,eax
invokeGetApiAddress,Kernel32Addr,addraGetProcAddr
movGetProcAddr,eax
invokewsprintf,addrtemp2,addrtemp1,Kernel32Addr,
LoadLibraryAddr,GetProcAddr
invokeMessageBoxA,0,addrtemp2,addrszTitle,0
invokeExitProcess,0
******************************************************************
函数功能:
从内存中Kernel32.dll的导出表中获取某个API的入口地址
GetApiAddressprocusesecxebxedxesiedihModule:
DWORD,szApiName:
DWORD
LOCALdwReturn:
DWORD
LOCALdwApiLength:
movdwReturn,0
计算API字符串的长度(带尾部的0)
movesi,szApiName
movedx,esi
Continue_Searching_Null:
cmpbyteptr[esi],0;
是否为Null-terminatedchar?
jzWe_Got_The_Length;
Yeah,wegotit.:
incesi;
No,continuesearching.
jmpContinue_Searching_Null;
searching.......
We_Got_The_Length:
呵呵,别忘了还有最后一个“0”的长度。
subesi,edx;
esi=APINamesize
movdwApiLength,esi;
dwApiLength=APINamesize
从PE文件头的数据目录获取输出表的地址
movesi,hModule
addesi,[esi+3ch]
assumeesi:
ptrIMAGE_NT_HEADERS
movesi,[esi].OptionalHeader.DataDirectory.VirtualAddress
addesi,hModule
ptrIMAGE_EXPORT_DIRECTORY;
esi指向Kernel32.dll的输出表
遍历AddressOfNames指向的数组的RVA对应的函数名字符串
AddressOfNames为RVA,指向一个RVA数组
数组为DWORD类型,是RVA值,指向函数名字符串
用字符串名描述的函数的个数在NumberOfNames,包括序号引
出的总数在AddressOfFunctions
movebx,[esi].AddressOfNames
addebx,hModule;
AddressOfNames是RVA,还要加上基地址
xoredx,edx;
edx=函数计数值,初始化为0,每查一个函数的RVA,加1
.repeat
pushesi;
保存esi,后面会用到
movedi,[ebx];
edi=导出表中函数字符串的RVA
addedi,hModule;
别忘了加上基地址
movesi,szApiName;
函数名字的首地址
movecx,dwApiLength;
函数名字的长度
cld;
设置方向标志DF=0,地址递增
repzcmpsb;
比较字符串,直到CX=0
.ifZERO?
;
ZF=1,找到了
popesi;
恢复esi
jmp_Find_Index;
查找该函数的地址索引
.endif
popesi;
addebx,4;
下一个函数名的RVA(每个函数占用4个字节)
incedx;
增加函数计数
.untiledx>
=[esi].NumberOfNames;
函数个数已经大于记数的总数NumberOfNames
jmp_Exit;
没找到,退出
得到ebx为RVA值,[ebx]+hModule指向函数字符串
函数名称索引->
序号索引->
地址索引
公式:
API'
s地址=(API的序号*4)+AddressOfFunctions的VA+Kernel32基地址
_Find_Index:
subebx,[esi].AddressOfNames;
esi就指向了下一个函数的首地址,所以要先减掉它
subebx,hModule;
减掉基地址,得到RVA
shrebx,1;
要除以2,还是因为repzcmpsb那行
addebx,[esi].AddressOfNameOrdinals;
AddressOfNameOrdinals是RVA,指向
包含16位函数序号的数组
要加基地址
函数序号*2+AddressOfFunctions+hModule为函数地址值的地址
movzxeax,wordptr[ebx];
eax=API的序号
shleax,2;
要乘以4才得到偏移
addeax,[esi].AddressOfFunctions;
加AddressOfFunctions的VA
addeax,hModule;
别忘了基地址
从地址表得到导出函数地址
moveax,[eax];
得到函数的RVA
addeax,hModule;
movdwReturn,eax;
最终得到的函数的线性地址
_Exit:
moveax,dwReturn;
函数地址
ret
GetApiAddressendp
endmain
显示结果如图3-3。
图3-3搜索到的API地址
3、病毒使用变量
学汇编时我们知道,当寄存器不够用,所以要使用变量。
病毒代码侵入到可执行文件中的位置对于不同的可执行文件是不同的。
在下面的代码中
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 世上 完整 病毒 实例