• Win32病毒入门(三)


    【pker / CVC.GB】 
    8、API函数地址的获得 
    -------------------- 
    回忆一下刚才我们是如何调用API的:首先,引入表是由一系列的IMAGE_IMPORT_DESCRIPTOR 
    结构组成的,这个结构中有一个FirstThunk字段,它指向一个数组,这个数组中的值在文件 
    被pe ldr加载到内存后被改写成函数的真正入口。一些编译器在调用API时把后面的地址指向 
    一个跳转表,这个跳转表中的jmp后面的地址就是FirstThunk中函数的真正入口。对于FASM 
    编译器,由于PE文件的引入表是由我们自己建立的,所以我们可以直接使用FirstThunk数组 
    中的值。 
    无论是哪种情况,总之,call的地址在编译时就被确定了。而我们的病毒代码是要插入到宿 
    主的代码中去的,所以我们的call指令后面的地址必须是在运行时计算的。那么怎么找到API 
    函数的地址呢?我们可以到宿主的引入表中去搜索那个对应函数的FirstThunk,但是这样做 
    有一个问题,我们需要函数并不一定是宿主程序需要的。换句话说,就是可能我们需要的函 
    数在宿主的引入表中不存在。这使我们不得不考虑别的实现。我们可以直接从模块的导出表 
    中搜索API的地址。 

    8.1、暴力搜索kernel32.dll 
    ------------------------- 
    在kernel32.dll中有两个API -- LoadLibraryA和GetProcAddress。前者用来加载一个动态 
    链接库,后者用来从一个已加载的动态链接库中找到API的地址。我们只要得到这两个函数 
    就可以调用任何库中的任意函数了。 
    在上一节中我们说过,程序被加载后[esp]的值是kernel32.dll中的ExitThread的地址,所以 
    我们可以肯定kernel32.dll是一定被加载的模块。所以我们第一步就是要找到kernel32.dll 
    在内存中的基地址。 
    那么我们从哪里入手呢?我们可以使用硬编码,比如Win2k下一般是77e60000h,WinXP SP1 
    是77e40000h,SP2是7c800000h等。但是这么做不具有通用性,所以这里我们介绍一个通用 
    也是现在最流行的方法:暴力搜索kernel32.dll。 
    大概的思想是这样的:我们只要找到得到任意一个位于kernel32.dll地址空间的地址,从这 
    个地址向下搜索就一定能得到kernel32.dll的基址。还记得刚才说的那个[esp]么,那个 
    ExitThread的地址就是位于kernel32.dll中的,我们可以从这里入手。考虑如下代码: 
                mov     edi,[esp]               ; get address of kernel32!ExitThread 
                and     edi,0ffff0000h          ; base address must be aligned by 1000h 
            krnl_search: 
                cmp     word [edi],'MZ'         ; 'MZ' signature? 
                jnz     not_pe                  ; it's not a PE, continue searching 
                lea     esi,[edi+3ch]           ; point to e_lfanew 
                lodsd                           ; get e_lfanew 
                test    eax,0fffff000h          ; DOS header+DOS stub mustn't > 4k 
                jnz     not_pe                  ; it's not a PE, continue searching 
                add     eax,edi                 ; point to IMAGE_NT_HEADER 
                cmp     word [eax],'PE'         ; 'PE' signature? 
                jnz     not_pe                  ; it's not a PE, continue searching 
                jmp     krnl_found 
            not_pe: 
                dec     edi 
                xor     di,di                   ; decrease 4k bytes 
                cmp     edi,70000000h           ; the base cannot below 70000000h 
                jnb     krnl_search 
                xor     edi,edi                 ; base not found 
            krnl_found: 
                ...                             ; now EDI contains the kernel base 
                                                ; zero if not found 
    程序首先把ExitThread的地址和0ffff0000h相与,因为kernel32.dll在内存中一定是1000h字 
    节对齐的(什么?为什么?还记得IMAGE_OPTIONAL_HEADER中的SectionAlignment么 :P)。 
    然后我们比较EDI指向的字单元是不是MZ标识,如果不是那么一定不是一个PE文件的起始位 
    置;如果是,那么我们就得到e_lfanew。我们先检查这个偏移是不是小于4k,因为这个值一 
    般是不会大于4k的。如果仍然符合条件,我们把这个值与EDI相加,如果EDI就是kernel32的 
    基址那么这时相加的结果应该指向IMAGE_NT_HEADER,所以我们检查这个字单元,如果是PE 
    标识,那么我们可以肯定这就是我们要找的kernel32了;如果不是把EDI的值减少4k并继续 
    查找。一般kernel32.dll的基址不会低于70000000h的,所以我们可以把这个地址作为下界, 
    如果低于这个地址我们还没有找到kernel32那么我们可以认为我们找不到kernel32了 :P 
    但是上面的作为有一些缺陷,因为我们的代码是要插入到宿主体内的,所以我们不能保证在 
    我们的代码执行前堆栈没有被破坏。假如宿主在我们的代码执行前进行了堆栈操作那么我们 
    很可能就得不到kernel32.dll了。 
    还有一个方法,就是遍历SEH链。在SEH链中prev字段为0ffffffffh的ER结构的异常处理例程 
    是在kernel32.dll中的。所以我们可以找到这个ER结构,然后... 
    下面我给出一个完整的程序,演示了如何搜索kernel32.dll并显示: 
    format  PE GUI 4.0 
    entry   __start 


    ; code section... 

    section '.text' code    readable writeable executable 
        szText: times 20h   db  0 

        ; 
        ; _get_krnl_base: get kernel32.dll's base address 
        ; 
        ; input: 
        ;       nothing 
        ; 
        ; output: 
        ;       edi:    base address of kernel32.dll, 0 if not found 
        ; 
        _get_krnl_base: 
                mov     esi,[fs:0] 
            visit_seh: 
                lodsd 
                inc     eax 
                jz      in_krnl 
                dec     eax 
                xchg    esi,eax 
                jmp     visit_seh 
            in_krnl: 
                lodsd 
                xchg    eax,edi 
                and     edi,0ffff0000h          ; base address must be aligned by 1000h 
            krnl_search: 
                cmp     word [edi],'MZ'         ; 'MZ' signature? 
                jnz     not_pe                  ; it's not a PE, continue searching 
                lea     esi,[edi+3ch]           ; point to e_lfanew 
                lodsd                           ; get e_lfanew 
                test    eax,0fffff000h          ; DOS header+DOS stub mustn't > 4k 
                jnz     not_pe                  ; it's not a PE, continue searching 
                add     eax,edi                 ; point to IMAGE_NT_HEADER 
                cmp     word [eax],'PE'         ; 'PE' signature? 
                jnz     not_pe                  ; it's not a PE, continue searching 
                jmp     krnl_found 
            not_pe: 
                dec     edi 
                xor     di,di                   ; decrease 4k bytes 
                cmp     edi,70000000h           ; the base cannot below 70000000h 
                jnb     krnl_search 
                xor     edi,edi                 ; base not found 
            krnl_found: 
                ret 

        ; 
        ; main entrance... 
        ; 
        __start: 
                call    _get_krnl_base 
                push    edi                     ; now EDI contains the kernel base 
                call    push_format             ; zero if not found 
                db      'kernel32 base = 0x%X',0 
            push_format: 
                push    szText 
                call    [wsprintf] 
                add     esp,0ch 
                xor     eax,eax 
                push    eax 
                call    push_caption 
                db      'kernel',0 
            push_caption: 
                push    szText 
                push    eax 
                call    [MessageBox] 
                ret 


    ; import section... 

    section '.idata' import data    readable 
        ; image import descriptor 
        dd      0,0,0,RVA usr_dll,RVA usr_thunk 
        dd      0,0,0,0,0 
        ; dll name 
        usr_dll     db      'user32.dll',0 
        ; image thunk data 
        usr_thunk: 
            MessageBox      dd      RVA __imp_MessageBox 
            wsprintf        dd      RVA __imp_wsprintf 
                            dd      0 
        ; image import by name 
        __imp_MessageBox    dw      0 
                            db      'MessageBoxA',0 
        __imp_wsprintf      dw      0 
                            db      'wsprintfA',0 

    8.2、搜索导出表,获取API地址 
    ---------------------------- 
    在开始之前,如果大家对前面导出表的知识还不熟悉,那么请务必再复习一遍,否则后边的 
    内容会显得很晦涩... 
    好了,我们继续吧 :P 
    整个搜索的过程说起来很简单,但做起来很麻烦,让我们一点一点来。首先我们要先导出函 
    数名称表中找到我们要得到的函数,并记下它在这个数组中的索引值。然后通过这个索引值 
    在序号数组中找到它对应的序号。最后通过这个序号在导出函数入口表中找到其入口。 
    下面我们慢慢来。先要匹配函数名。假设edx中存放着kernel32.dll的基址,esi中存放着API 
    的名称。考虑如下代码: 
                mov     ebx,edx                 ; save module image base for 
                                                ; later use 
                push    esi                     ; save API name 
                xchg    esi,edi 
                xor     ecx,ecx 
                xor     al,al 
                dec     ecx 
                repnz   scasb 
                neg     ecx 
                dec     ecx 
                push    ecx                     ; save length of the API name 
                lea     edi,[edx+3ch] 
                add     edx,dword [edi]         ; edx points to IMAGE_NT_HEADER 
                push    edx                     ; save IMAGE_NT_HEADER 
                mov     edi,dword [edx+78h]     ; edi has the RVA of export table 
                add     edi,ebx                 ; edi points to export table 
                lea     esi,[edi+18h] 
                lodsd                           ; eax get NumberOfNames 
                push    eax                     ; save NumberOfNames 
                mov     esi,[edi+20h] 
                add     esi,ebx                 ; now points to name RVA table 
                xor     edx,edx 
            match_api_name: 
                lodsd 
                add     eax,ebx 
                xchg    eax,edi                 ; get a API name 
                xchg    esi,ebp 
                mov     ecx,dword [esp+08h]     ; length of API name 
                mov     esi,dword [esp+0ch]     ; API name buffer 
                repz    cmpsb 
                jz      api_name_found 
                xchg    esi,ebp 
                inc     edx 
                cmp     edx,dword [esp] 
                jz      api_not_found 
                jmp     match_api_name 
    上面的代码首先把kernel32.dll的基址复制到ebx中保存,然后计算了API名称的长度(包括 
    零)并进行匹配,如果匹配成功则edx包含了这个函数在函数名数组中的索引值。下面在序号 
    数组中通过这个索引值得到这个函数的序号。考虑如下代码: 
                shl     edx,1 
                mov     esi,[esp+04h]           ; export table address 
                mov     eax,[esi+24h] 
                add     eax,ebx                 ; ordinal table 
                movzx   edx,word [eax+edx] 
                shl     edx,2 
                mov     eax,[esi+1ch] 
                add     eax,ebx                 ; function address table 
                mov     eax,[eax+edx] 
                add     eax,ebx                 ; found!!! 
    首先我们可以得到序号数组的RVA,然后把这个值与模块(这里是kernel32.dll)的基地址 
    相加,这样就得到了数组的内存地址。由于序号数组是WORD型的,所以我们的索引值必须要 
    乘以2。然后通过这个值在数组中索引到函数在导出函数入口表中的索引。由于这个数组是 
    DWORD型的,所以我们这个索引要乘以4。我们很容易得到导出函数入口表的内存地址。最后 
    我们通过刚才的索引得到函数的入口地址。 
    下面我们看一个完整的代码: 
    format  PE GUI 4.0 
    entry   __start 


    ; code section... 

    section '.text' code    readable writeable executable 
        ; 
        ; _get_krnl_base: get kernel32.dll's base address 
        ; 
        ; input: 
        ;       nothing 
        ; 
        ; output: 
        ;       edi:    base address of kernel32.dll, zero if not found 
        ; 
        _get_krnl_base: 
                mov     esi,[fs:0] 
            visit_seh: 
                lodsd 
                inc     eax 
                jz      in_krnl 
                dec     eax 
                xchg    esi,eax 
                jmp     visit_seh 
            in_krnl: 
                lodsd 
                xchg    eax,edi 
                and     edi,0ffff0000h          ; base address must be aligned by 1000h 
            krnl_search: 
                cmp     word [edi],'MZ'         ; 'MZ' signature? 
                jnz     not_pe                  ; it's not a PE, continue searching 
                lea     esi,[edi+3ch]           ; point to e_lfanew 
                lodsd                           ; get e_lfanew 
                test    eax,0fffff000h          ; DOS header+DOS stub mustn't > 4k 
                jnz     not_pe                  ; it's not a PE, continue searching 
                add     eax,edi                 ; point to IMAGE_NT_HEADER 
                cmp     word [eax],'PE'         ; 'PE' signature? 
                jnz     not_pe                  ; it's not a PE, continue searching 
                jmp     krnl_found 
            not_pe: 
                dec     edi 
                xor     di,di                   ; decrease 4k bytes 
                cmp     edi,70000000h           ; the base cannot below 70000000h 
                jnb     krnl_search 
                xor     edi,edi                 ; base not found 
            krnl_found: 
                ret 

        ; 
        ; _get_apiz: get apiz from a loaded module, something like GetProcAddress 
        ; 
        ; input: 
        ;       edx:    module handle (module base address) 
        ;       esi:    API name 
        ; 
        ; output: 
        ;       eax:    API address, zero if fail 
        ; 
        _get_apiz: 
                push    ebp 
                mov     ebp,esp 
                push    ebx 
                push    ecx 
                push    edx 
                push    esi 
                push    edi 
                or      edx,edx                 ; module image base valid? 
                jz      return 
                mov     ebx,edx                 ; save module image base for 
                                                ; later use 
                push    esi                     ; save API name 
                xchg    esi,edi 
                xor     ecx,ecx 
                xor     al,al 
                dec     ecx 
                repnz   scasb 
                neg     ecx 
                dec     ecx 
                push    ecx                     ; save length of the API name 
                lea     edi,[edx+3ch] 
                add     edx,dword [edi]         ; edx points to IMAGE_NT_HEADER 
                push    edx                     ; save IMAGE_NT_HEADER 
                mov     edi,dword [edx+78h]     ; edi has the RVA of export table 
                add     edi,ebx                 ; edi points to export table 
                push    edi                     ; save address of export table 
                lea     esi,[edi+18h] 
                lodsd                           ; eax get NumberOfNames 
                push    eax                     ; save NumberOfNames 
                mov     esi,[edi+20h] 
                add     esi,ebx                 ; now points to name RVA table 
                xor     edx,edx 
            match_api_name: 
                lodsd 
                add     eax,ebx 
                xchg    eax,edi                 ; get a API name 
                xchg    esi,eax 
                mov     ecx,dword [esp+0ch]     ; length of API name 
                mov     esi,dword [esp+10h]     ; API name buffer 
                repz    cmpsb 
                jz      api_name_found 
                xchg    esi,eax 
                inc     edx 
                cmp     edx,dword [esp] 
                jz      api_not_found 
                jmp     match_api_name 
            api_not_found: 
                xor     eax,eax 
                xor     edi,edi 
                jmp     return 
            api_name_found: 
                shl     edx,1 
                mov     esi,[esp+04h]           ; export table address 
                mov     eax,[esi+24h] 
                add     eax,ebx                 ; ordinal table 
                movzx   edx,word [eax+edx] 
                shl     edx,2 
                mov     eax,[esi+1ch] 
                add     eax,ebx                 ; function address table 
                mov     eax,[eax+edx] 
                add     eax,ebx                 ; found!!! 
            return: 
                add     esp,14h 
                pop     edi 
                pop     esi 
                pop     edx 
                pop     ecx 
                pop     ebx 
                mov     esp,ebp 
                pop     ebp 
                ret 

        ; 
        ; main entrance... 
        ; 
        __start: 
                call    _get_krnl_base          ; get kernel32.dll base address 
                or      edi,edi 
                jz      exit 
                xchg    edi,edx                 ; edx <-- kernel32.dll's image base 
                call    @f 
                db      'LoadLibraryA',0 
            @@: 
                pop     esi                     ; esi <-- api name 
                call    _get_apiz 
                or      eax,eax 
                jz      exit 
                mov     [__addr_LoadLibrary],eax 
                call    @f 
                db      'GetProcAddress',0 
            @@: 
                pop     esi 
                call    _get_apiz 
                or      eax,eax 
                jz      exit 
                mov     [__addr_GetProcAddress],eax 
                call    @f 
                db      'user32.dll',0 
            @@: 
                mov     eax,12345678h 
            __addr_LoadLibrary  =   $-4 
                call    eax 
                call    @f 
                db      'MessageBoxA',0 
            @@: 
                push    eax 
                mov     eax,12345678h 
            __addr_GetProcAddress   =   $-4 
                call    eax 
                xor     ecx,ecx 
                push    ecx 
                call    @f 
                db      'get_apiz',0 
            @@: 
                call    @f 
                db      'Can you find the import section from this app ^_^',0 
            @@: 
                push    ecx 
                call    eax 
            exit: 
                ret 
  • 相关阅读:
    java图片压缩处理
    RocketMQ启动broker提示 错误:找不到或无法加载主类
    Docker
    openresty (nginx+lua)实现请求过滤
    mybatis-3.4.0 Date类型非空字符串判断bug
    ClickHouse 官方文档
    Flume 搭建遇到的问题
    Hadoop与HDFS
    关于Linux
    mybatis insert 返回主键
  • 原文地址:https://www.cnblogs.com/qq78292959/p/2077118.html
Copyright © 2020-2023  润新知