前言:
病毒沒有什麼可怕的,也並不象想像中的複雜,玩彙編的人如果沒有看過病毒?簡直是白活一遭...病毒就象是雙刃劍,惡意使用就會帶來惡果,我本人對於此類行為深惡痛絕!我們研究不是為了破壞而是為了知己知彼,另外病毒中確實也有很多高超的技巧值得我們學習,這才是我們的目的所在,我絕沒有教唆人犯罪的意圖而且就我的水平來講也遠達不到.
在研究病毒之前有幾項基礎知識要了解:
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
鏈 接:http://bbs.pediy.com
看看我的程式碼
作 者:wowocock
時 間:2002/08/22 08:55pm
鏈 接:http://bbs.pediy.com
看看我的程式碼
;適用系統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