• 标 题:病毒基础系列
  • 作 者:Hume/冷雨飘心
  • 时 间:2002/03/10 08:10pm
  • 链 接:http://bbs.pediy.com

前言:
   病毒没有什么可怕的,也并不象想像中的复杂,玩汇编的人如果没有看过病毒?简直是白活一遭...病毒就象是双刃剑,恶意使用就会带来恶果,我本人对于此类行为深恶痛绝!我们研究不是为了破坏而是为了知己知彼,另外病毒中确实也有很多高超的技巧值得我们学习,这才是我们的目的所在,我绝没有教唆人犯罪的意图而且就我的水平来讲也远达不到.
   在研究病毒之前有几项基础知识要了解:
       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