前言:
病毒没有什么可怕的,也并不象想像中的复杂,玩汇编的人如果没有看过病毒?简直是白活一遭...病毒就象是双刃剑,恶意使用就会带来恶果,我本人对于此类行为深恶痛绝!我们研究不是为了破坏而是为了知己知彼,另外病毒中确实也有很多高超的技巧值得我们学习,这才是我们的目的所在,我绝没有教唆人犯罪的意图而且就我的水平来讲也远达不到.
在研究病毒之前有几项基础知识要了解:
1)ring0的获取,可参见我翻译的一篇ring0的文章,另外这些资料internet上也很多.
2)Seh的知识,可参见我写的<<seh in asm研究>>
3)PE结构的知识,ZouDan大侠的论文,IczeLion的PE教学和载 LUEVELSMEYER的《PE文件格式》,最为重要!
4)文件读写的基本知识,主要是CreateFileA,ReadFile,WriteFile,CreateFileMapping...等文件读写知识,如果你还不了解,最好先学学win32ASM的基础知识.
5)PolyEngine...MetaMorphism等基本概念
6)anti-debug的一些概念
7)有关MBR,FAT等知识,对了解一些老Virus有用.
下面我有的一些tips,可能会对理解病毒有所帮助.另外这些技术在加壳类软件中也很常见.
--------------------------------------------------------------------------------
tip1: api函数地址的获取
这是一个老题目了,如果我们不用任何引入库,能否在程序中调用api函数?当然可以!方法有很多,你可能早就知道了,如果你已经了解了,就此打住,这是为还不了解这一技术而写.另外这也是病毒必用的技巧之一,如果你对病毒技术感兴趣,接着看下去.
这里假设你了解PE的基本结构,如果还不懂,找点资料来看看,到处都是呦.
在几乎每个病毒的开头都用下面的语句:
call delta
delta:
pop ebp
sub ebp,offset delta
mov dword ptr [ebp+offset
appBase],ebp
让我们考虑一下程序的执行情况,如果下面的代码由编译器自动编译连接,那么程序执行的基址一般是400000h,如果是在Nt下执行,那么基址可能不同,比如从100000h开始,不用担心,操作系统的Loader会自动为你重定位.但是这里停下来让我们看一下,如果你想要把这段代码附加到其他程序的后面并想让其正确执行的话,就不是那么简单了,因为你的代码可能要从555588h处开始执行,而在没有得到宿主程序许可的情况下期望操作系统自动为你修正偏移错误是不可能的,既然有非常的目的,就得费点力气,自己搞定重定位.而上面的代码就是首先得到eip指针,也是delta在程序执行时的实际偏移,然后减掉代码头到delta的偏移从而得到你的代码的真正基址,后面对于偏移的操作都应以这个真正的偏移为准.这就是你上面看到的.如果不明白,就仔细想一下,nothing
difficult!下面的例子演示了这一点,并没有全部重定位,因为这只是技术演示.
现在回到本文的正式内容,要想获得api的地址,得首先获得诸如kernel32.dll,user32.dll的基址,然后再找到真正的函数地址.如何获得基址和函数地址呢呢?有几种方法
1)搜寻宿主的引入表获得GetModuleHandleA函数和GetProcAddress的地址,然后通过他返回系统dll的基址.因为很多程序都要使用这两个函数,因此在某些情况下是可行的,如果宿主没有使用GetProcAddress,那你就不得不搜寻Export表了.
2)直接获得kernel32.dll的基址,然后再搜寻Export表获得GetProcAddress和LoadLibraryA的地址,然后我们就能得到任何想调用的函数地址.
3)硬编码调用函数,比如在9X下GetModuleHandleA的地址一般是BFF7****.
第一种和第三种方法存在兼容性的问题,假如宿主没有调用GetModuleHandleA,那么你就不能获得基址,别的就更别想了...硬编码问题更大,操作系统不同则不能运行了,比如9X下可能在有些计算机上正常,但肯定不能在Nt/2K下运行...
第二种方法兼容性比较好,因此作以介绍.
一点背景:在PE Loader装入我们的程序启动后堆栈顶的地址是是程序的返回地址,肯定在Kernel中!
因此我们可以得到这个地址,然后向低地址缩减验证一直到找到模块的起始地址,验证条件为PE头不能大于4096bytes,PE header的ImageBase值应该和当前指针相等,嘿嘿,简单吧,而且兼容性还不错.
要获得Api的地址首先要获得GetModuleHandle,LoadLibraryA,GetProcAddress的地址,这是通过搜索Export表来实现的,具体原理就是PE Export表的结构,如果了解了PE结构就很简单了.下面我加了点注释,没有优化代码,是为了便于理解.
好,这一部分结束了!
这是一个例子,没有用任何预引入函数,加了一条invoke InitCommonControls是为了在2K下也能正常运行,否则不能在2K下不加载!
程序得到MessageBoxA的地址然后显示一个消息框,目的在于演示,重要部分加了注释,很好明白.
注意连接时加入/section:.text,RWE选项。
.586
.model
flat, stdcall
option casemap :none ; case sensitive
include c:\hd\hd.h
include
c:\hd\mac.h
;;--------------
GetApiA proto :DWORD,:DWORD
;;--------------
.CODE
appBase
dd ?
k32Base dd ?
lpApiAddrs
label near
dd offset sGetModuleHandle
dd offset sGetProcAddress
dd offset
sExitProcess
dd
offset sLoadLibrary
dd 0
sGetModuleHandle
db "GetModuleHandleA",0
sGetProcAddress
db "GetProcAddress",0
sExitProcess
db "ExitProcess",0
sLoadLibrary
db "LoadLibraryA",0
sMessageBoxA db "MessageBoxA",0
aGetModuleHandle
dd 0
aGetProcAddress
dd 0
aExitProcess
dd 0
aLoadLibrary
dd 0
aMessageBoxA
dd 0
u32
db "User32.dll",0
k32
db "Kernel32.dll",0
sztit
db "By Hume,2002",0
szMsg0
db "Hey,Hope
U enjoy it!",0
;;-----------------------------------------
__Start:
invokeInitCommonControls
call delta
delta:
pop ebp
;得到delta地址
sub ebp,offset delta
;因为在其他程序中基址可能不是默认的所以需要重定位
mov dword ptr [ebp+offset appBase],ebp ;呵呵仔细想想
mov ecx,[esp]
;返回地址
xor edx,edx
getK32Base:
dec ecx
;逐字节比较验证
mov dx,word ptr [ecx+IMAGE_DOS_HEADER.e_lfanew]
;就是ecx+3ch
test dx,0f000h
;Dos
Header+stub不可能太大,超过4096byte
jnz getK32Base
;加速检验
cmp ecx,dword ptr [ecx+edx+IMAGE_NT_HEADERS.OptionalHeader.ImageBase]
jnz getK32Base
;看Image_Base值是否等于ecx即模块起始值,
mov [ebp+offset k32Base],ecx
;如果是,就认为找到kernel32的Base值
lea edi,[ebp+offset aGetModuleHandle]
lea esi,[ebp+offset lpApiAddrs]
lop_get:
lodsd
cmp
eax,0
jz End_Get
push eax
push
dword ptr [ebp+offset k32Base]
callGetApiA
;获取API地址
stosd
jmp lop_get
End_Get:
push offset u32
call
dword ptr [ebp+offset aLoadLibrary] ;在程序空间加载User32.dll
lea EDX,[EBP+OFFSET
sMessageBoxA]
push edx
push eax
mov
eax,dword ptr [ebp+aGetProcAddress] ;用GetProcAddress获得MessageBoxA的地址
call eax
;调用GetProcAddress
push 40h+1000h
;style
push offset sztit
;title
push offset szMsg0
;消息内容
push 0
call
eax
;一个消息框产生了...嘿嘿
;有理由为此高兴吧,因为我们没有预先引入
@@:
;这些函数
push 0
call
[ebp+aExitProcess]
;-----------------------------------------
K32_api_retrieve
proc Base:DWORD ,sApi:DWORD
push
edx
;保存edx
xor eax,eax
;此时esi=sApi
Next_Api:
;edi=AddressOfNames
mov
esi,sApi
xor edx,edx
dec edx
Match_Api_name:
mov bl,byte ptr [esi]
inc esi
cmp
bl,0
jz foundit
inc edx
push eax
mov
eax,[edi+eax*4] ;AddressOfNames的指针,递增
add eax,Base
;注意是RVA,一定要加Base值
cmp
bl,byte ptr [eax+edx] ;逐字符比较
pop eax
jz Match_Api_name
;继续搜寻
inc
eax
;不匹配,下一个api
loop Next_Api
jmp no_exist
;若全部搜完,即未存在
foundit:
pop edx
;edx=AddressOfNameOrdinals
shl
eax,1
;*2得到AddressOfNameOrdinals的指针
movzx
eax,word ptr [edx+eax] ;eax返回指向AddressOfFunctions的指针
ret
no_exist:
pop
edx
xor eax,eax
ret
K32_api_retrieve endp
;-----------------------------------------
GetApiA
proc Base:DWORD,sApi:DWORD
local ADDRofFun:DWORD
pushad
mov edi,Base
add edi,IMAGE_DOS_HEADER.e_lfanew
mov edi,[edi]
;现在edi=off PE_HEADER
add edi,Base
;得到IMAGE_NT_HEADERS的偏移
mov
ebx,edi
mov edi,[edi+IMAGE_NT_HEADERS.OptionalHeader.DataDirectory.VirtualAddress]
add edi,Base
;得到edi=IMAGE_EXPORT_DIRECTORY入口
mov eax,[edi+1ch]
;AddressOfFunctions的地址
add eax,Base
mov ADDRofFun,eax
;ecx=NumberOfNames
mov ecx,[edi+18h]
mov
edx,[edi+24h]
add edx,Base
;edx=AddressOfNameOrdinals
mov
edi,[edi+20h]
add edi,Base
;edi=AddressOfNames
invokeK32_api_retrieve,Base,sApi
mov ebx,ADDRofFun
shl
eax,2
;要*4才得到偏移
add
eax,ebx
mov eax,[eax]
add eax,Base
;加上Base!
mov [esp+7*4],eax
;eax返回api地址
popad
ret
GetApiA endp
;-----------------------------------------
END__Start
;------------------------------------------End
all
看看我的代码
看看我的代码
;适用系统Win9x/me/2k/xp/nt
.586p
.model flat, stdcall
option casemap :none ; case sensitive
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
GetApiAddress PROTO :DWORD,:BYTE
.data
Kernel32Addrdd ?
ExportKernel dd ?
GetProcAddr dd ?
GetModuleHandleAddr dd ?
LoadLibraryAddr dd ?
ExitProcessAddr dd ?
aGetProcAddr db "GetProcAddress",0
GetProcAddLen equ $-aGetProcAddr-1
aGetModuleHandle db "GetModuleHandle",0
GetModuleHandleLen equ $-aGetModuleHandle-1
aLoadLibrary db "LoadLibrary",0
LoadLibraryLen equ $-aLoadLibrary-1
aExitProcess db "ExitProcess",0
ExitProcessLen equ $-aExitProcess-1
szTitle db "Test",0
temp1 db " Kernel32.dll Address is:%8x:",0dh,0ah
db " GetProcAddress Address is:%8x:",0dh,0ah
db "GetModuleHandle Address is:%8x",0dh,0ah
db " LoadLibrary Address is:%8x",0dh,0ah
db " ExitProcess Address is:%8x",0
temp2 db 256 dup(?)
.code
Start:
mov eax,[esp] ;//取Kernel32返回地址
and ax,0f000h
mov esi,eax ;//得到Kernel.PELoader代码位置(不精确)
LoopFindKernel32:
sub esi,1000h
cmp word ptr[esi],'ZM' ;//搜索EXE文件头
jnz short LoopFindKernel32
GetPeHeader:
movzx edi,word ptr[esi+3ch]
add edi,esi
cmp word ptr[edi],'EP' ;//确认是否PE文件头
jnz short LoopFindKernel32 ;esi->kernel32,edi->kernel32
PE HEADER
;//////////////////////////////////////////////////任务:查找GetProcAddress函数地址
mov Kernel32Addr,esi
GetPeExportTable:
mov ebp,[edi+78h];4+14h+60h
add ebp,Kernel32Addr ;//得到输出函数表
mov ExportKernel,ebp
push GetProcAddLen
push offset aGetProcAddr
call GetApiAddress
mov GetProcAddr,eax
push GetModuleHandleLen
push offset aGetModuleHandle
call GetApiAddress
mov GetModuleHandleAddr,eax
push LoadLibraryLen
push offset aLoadLibrary
call GetApiAddress
mov LoadLibraryAddr,eax
push ExitProcessLen
push offset aExitProcess
call GetApiAddress
mov ExitProcessAddr,eax
invoke wsprintf,addr temp2,addr temp1,Kernel32Addr,GetProcAddr,GetModuleHandleAddr,LoadLibraryAddr,ExitProcessAddr
invoke MessageBoxA,0,addr temp2,addr szTitle,0
push 0
call dword ptr[ExitProcessAddr]
GetApiAddress proc AddressOfName:dword,ApiLength:byte
push ebx
push esi
push edi
mov edi,ExportKernel
assume edi:ptr IMAGE_EXPORT_DIRECTORY
GetExportNameList:
mov ebx,[edi].AddressOfNames ;//得到输出函数名表
add ebx,Kernel32Addr ;ebx->AddressOfNames(函数名字的指针地址).
xor eax,eax ;//函数序号计数
mov edx,Kernel32Addr ;//暂存Kernel32模块句柄;edx->kernel32
push edi ;保存EDI
LoopFindApiStr:
add ebx,04
inc eax ;//增加函数计数
mov edi,[ebx]
add edi,edx ;//得到一个Api函数名字符串.edi->函数名
StrGetProcAddress:
mov esi,AddressOfName ;//得到Api名字字符串
cmpsd
;比较前4个字符是否相等
jnz short LoopFindApiStr ;eax=函数名的INDEX
xor ecx,ecx
mov cl, ApiLength
sub cl,4 ;//比较剩余的GetProcAddress串
cld
Goon:
cmpsb
jnz short LoopFindApiStr ;eax=函数名的INDEX
loop Goon
pop edi ;恢复EDI
mov esi,edx
movebx,[edi].AddressOfNameOrdinals
addebx,esi ;//取函数序号地址列表,ebx->AddresssOfNameOrdinals
movzx ecx,word ptr [ebx+eax*2]
mov ebx,[edi].AddressOfFunctions
add ebx,esi ;//得到Kernel32函数地址列表
mov ebx,[ebx+ecx*4]
add ebx,esi ;//计算GetProcAddress函数地址
mov eax,ebx ;eax=API函数地址,esi=Kernel32.dll
hModule
pop edi
pop esi
pop ebx
ret
GetApiAddress endp
end Start