导入表的加密应该是壳对应用程序加密时最重要的部分了。首先我们先了解一下怎样寻找引入表,并列出其中的导入的函数与DLL命。其次,进行讲解如何加密引入表。最后,讲解在我们自定义的节表中恢复引入表并重定位函数地址。

1.寻找引入表
在IMAGE_NT_HEADERS结构中的OptionalHeader字段中有一组数据目录,数据目录数组的第二个元素就是引入表的目录索引。
数据目录的结构是这样的
IMAGE_DATA_DIRECTORY STRUCT 
  VirtualAddress dd ? 
  isize dd ? 
IMAGE_DATA_DIRECTORY ENDS 
VirtualAddress 是此表的RVA,也就是相对于模块加载的偏移量,此地址指向一个由IMAGE_IMPORT_DESCRIPTOR结构组成的数组。
isize 是此表的大小
我们利用以下算法寻找引入表
1.从 DOS header 找到 PE header 
2.从 数据目录中 读取 data directory 的地址。 第二个索引就为引入表的地址。
3.IMAGE_DATA_DIRECTORY的虚拟地址偏移转化为文件偏移
4.文件偏移加上文件映射基址就为引入表的地址
寻找到引入表后,接下来的工作就是展开引入表,此时的指针指向一个IMAGE_IMPORT_DESCRIPTOR结构的数组,我们可以认为一个IMAGE_IMPORT_DESCRIPTOR结构就是一个DLL,如果你的程序引用了5个DLL那么你的程序用就有5个IMAGE_IMPORT_DESCRIPTOR结构,并且这个数组以一个全0的IMPORT_IMPORT_DESCRIPTOR为结束。为了让读者好理解在以下称IMAGE_IMPORT_DESCRIPTOR为DLL结构。
对于加密导入表最重要的有三个属性,Name1,OriginalFirstThunk,FirstThunk。
Name1也是一个偏移量,指向这个DLL的DLL字符串的内存偏移。
OriginalFirstThunk与FirstThunk两个字段也同属于指针类型的。并且在文件中都指向一个位置。当加载到内存时,OriginalFirstThunk还指向原来指向的地方,FirstThunk指向一个API函数的地址的数据。(由PE加载器帮助定位并修改的)
在文件中,OriginalFirstThunk指向一组地址的偏移,这个地址偏移被称为IMAGE_THUNK_DATA
这个偏移值指向一组IMAGE_IMPORT_BY_NAME结构的数组。这个结构读者可以认为是这个DLL文件中的API。有几个API代表有几个这样的结构。
IMAGE_IMPORT_BY_NAME STRUCT 
  Hint dw ? 
  Name1 db ? 
IMAGE_IMPORT_BY_NAME ENDS
Hint:代表此API是DLL中的第几个函数
Name1:为此API的名字,最后以NULL结尾。我们加密API的名字就是加密Name1的字段。
另一个FirstThunk如同复制一样也一模一样的指向了与OrigFirstThunk一样的地方。但是当文件加载到内存中后,FirstThunk会指向函数的地址。这个转换由PE加载器加载。
列出引入表代码如下

代码:
ListIID proc pFilename : LPSTR
    ;; map file to memory
    LOCAL hFile : HANDLE
    LOCAL hMap : HANDLE
    LOCAL pMem : LPVOID
    LOCAL dwNTHeaderAddr : DWORD
    LOCAL szTmpBuf[MAX_PATH] : BYTE
    
    ;; open file
    invoke CreateFile, pFilename,\
                      GENERIC_WRITE + GENERIC_READ,\
                      FILE_SHARE_WRITE + FILE_SHARE_READ,\
                      NULL,\
                      OPEN_EXISTING,\
                      FILE_ATTRIBUTE_NORMAL,\
                      0
    .IF eax == INVALID_HANDLE_VALUE
        jmp OpenFileFailed                
    .ENDIF
    mov hFile, eax 
    invoke GetFileSize, hFile, NULL
    .IF eax == 0
        invoke CloseHandle, hFile  
        jmp GetFileSizeFailed
    .ENDIF  
    
    ;; create memory map
    xor ebx, ebx     
    invoke CreateFileMapping, hFile, ebx, PAGE_READWRITE, ebx, eax, ebx
    .IF eax == 0
        invoke CloseHandle, hFile
        jmp CreateMapFailed                
    .ENDIF
    mov hMap, eax
    ;; map file to memory
    invoke MapViewOfFile, hMap,
                       FILE_MAP_WRITE+FILE_MAP_READ+FILE_MAP_COPY, 
                       ebx, ebx, ebx
    .IF eax == 0
        invoke CloseHandle, hMap
        invoke CloseHandle, hFile
        jmp MapFileFailed
    .ENDIF
    mov pMem, eax                               
    ;; check it's PE file or not ?
    xchg eax, esi
    assume esi : ptr IMAGE_DOS_HEADER
    .IF [esi].e_magic != 'ZM'
        invoke UnmapViewOfFile, pMem
        invoke CloseHandle, hMap
        invoke CloseHandle, hFile
        jmp InvalidPE        
    .ENDIF       
    add esi, [esi].e_lfanew
    assume esi : ptr IMAGE_NT_HEADERS   
    .IF word ptr [esi].Signature != 'EP'
        invoke UnmapViewOfFile, pMem
        invoke CloseHandle, hMap
        invoke CloseHandle, hFile
        jmp InvalidPE        
    .ENDIF
    mov dwNTHeaderAddr, esi
    
    ;; 寻找引入表
    assume esi : ptr IMAGE_NT_HEADERS
    mov eax, dword ptr [esi].OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT * sizeof IMAGE_DATA_DIRECTORY].VirtualAddress
    ;; 将内存偏移转换为文件偏移,再加上文件头的内存映射就等于引入表在文件的地址了
    invoke RVA2Offset, pMem, eax
    xchg ebx, eax
    add ebx, pMem
    assume ebx : ptr IMAGE_IMPORT_DESCRIPTOR
    ;; 这里直到一个全0的IMAGE_IMPORT_DESCRIPTOR结构为结束
    ;; 这里判断Name1是否为NULL
    ListIIDLoop:
        mov eax, dword ptr [ebx].Name1
        test eax, eax
        jz EndListIIDLoop
        ;; 此时eax指向一个DLL名字的RVA,我们将RVA转换为文件的偏移
        invoke RVA2Offset, pMem, eax
        ;; 文件的偏移加上文件的起始指针就为文件中的地址
        add eax, pMem
        ;; 打印DLL名
        invoke PrintLine, offset g_szOutFormat, offset g_szOutLine        
        invoke PrintLine, offset g_szOutFormat, eax
        invoke PrintLine, offset g_szOutFormat, offset g_szOutLine
        ;; 检查OriginalFirstThunk是否为0。如果为0则使用FirstThunk        
        mov edx, dword ptr [ebx].OriginalFirstThunk
        test edx, edx
        jnz UseOrignalFirstThunk
        mov edx, dword ptr [ebx].FirstThunk
    UseOrignalFirstThunk:        
        ;; 转换RVA转换文件偏移
        invoke RVA2Offset, pMem, edx
        add eax, pMem
        mov edx, eax
    DisplayApiName:
        ;; 查看edx的最高位是否是1确定是否以序数引出
        test dword ptr [edx], IMAGE_ORDINAL_FLAG32
        jnz DisPlayOrd
        ;; 这里edx指向IMAGE_IMPORT_BY_NAME结构的RVA,继续将它转换
        mov eax, dword ptr [edx]
        invoke RVA2Offset, pMem, eax
        add eax, pMem
        assume eax : ptr IMAGE_IMPORT_BY_NAME
        ;; 打印API字符串,将Name1的地址设置给eax寄存器
        lea eax, [eax].Name1
        invoke PrintLine, offset g_szOutFormat, eax
        jmp NextAPI
        DisPlayOrd:
        ;; 取出序数,低2个字节为序数
        mov eax, dword ptr [edx]
        and eax, 0FFFFh        
        invoke PrintLine, offset g_szOutOrdFormat, eax
    NextAPI:
        ;; 取下一个IMAGE_THUNK_DATA的值
        add edx, 04h
        ;; 直到edx指向一个0
        mov eax, dword ptr [edx]
        test eax, eax
        jnz DisplayApiName
        ;; 指向下一个DLL
        add ebx, sizeof IMAGE_IMPORT_DESCRIPTOR
        jmp ListIIDLoop
    EndListIIDLoop:   
LogicShellExit:
    ;; close handle & write it
    invoke UnmapViewOfFile, pMem
    invoke CloseHandle, hMap
    invoke CloseHandle, hFile
    assume ebx : nothing
    assume esi : nothing
    ret
;; ----- Show error message ----- 
OpenFileFailed:
     lea eax, g_szOpenFileFailed
     jmp ShowErr
GetFileSizeFailed:
     lea eax, g_szGetFileSizeFailed
     jmp ShowErr    
CreateMapFailed:
     lea eax, g_szCreateMapFailed
     jmp ShowErr
MapFileFailed:
     lea eax, g_szMapFileFailed
     jmp ShowErr        
InvalidPE:          
     lea eax, g_szInvalidPE
     jmp ShowErr   
ShowErr:
     invoke MessageBox, NULL, eax, offset g_szErr, MB_ICONERROR
     jmp LogicShellExit
ListIID endp
列出引入表不知道我讲的是否清楚,总之当从PE头结构中获取到引入表地址将它的RVA转换为FVA再加上文件映射基址就可以按照微软定义的引入表结构进行加密与销毁。接下来我们要做的就是将引入表中每个DLL名字与API的名字都进行加密,后记录每个IMAGE_IMPORT_DESCRIPTOR中的FirstThunk值。并记录到我们自己的定义的结构中。RVA2Offset函数是转换RVA到FVA。此函数在附件代码中定义。

前面的只是给加密引入表做个铺垫。加密引入表和列出引入表算法都一样。只不过将显示函数加密而已。这里直接将代码列出。加密字符串的算法也很简单只是异或上99h而已。其次和列出不一样的地方是,加密引入表记录了引入表每个IMAGE_IMPORT_DESCRIPTOR的Name1,OrigFirstThunk,FirstThunk三个字段。然后在解密段中按此三个字段在解密引入表。并重定位API的地址表。
这里是我们自己的引入表结构。这个结构安置在解密体内
代码:
IID_PRIVATE_DATA struct
    Name1                   dd  0
    OriginalFirstThunk      dd  0
    FirstThunk              dd  0
IID_PRIVATE_DATA ends
下面列出其加密引入表的代码
代码:
EnCryptIID proc uses ebx ecx edx esi edi, pMem : LPVOID, pCurrentIID : LPVOID
;; encrypt image import directory
    LOCAL pIID[IMAGE_IMPORT_TABLE_SIZE] : BYTE
    LOCAL pImportFVA : LPVOID
    LOCAL pImportRVA : DWORD
    LOCAL dwLoadLibraryThunkData : DWORD
    LOCAL dwGetProcAddressThunkData : DWORD
    
    ;; 将IID_PRIVATE_DATA结构清0
    mov edi, pCurrentIID
    mov eax, sizeof IID_PRVATE_DATA
    mov ecx, MAX_IID_NUM
    imul ecx
    xchg eax, ecx
    xor eax, eax
    cld
    rep stosb

    ;; 定位引入表的位置
    mov esi, pMem
    add esi, dword ptr [esi+03ch]
    assume esi : ptr IMAGE_NT_HEADERS
    mov ecx, dword ptr [esi].OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT * sizeof IMAGE_DATA_DIRECTORY].isize    
    test ecx, ecx
    jz ExitEnCryptIID
    mov esi, dword ptr [esi].OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT * sizeof IMAGE_DATA_DIRECTORY].VirtualAddress
    test esi, esi
    jz ExitEnCryptIID
    ;; change RVA to offset
    invoke RVA2Offset, pMem, esi
    add eax, pMem
    xchg eax, edi           ; esi = Import Symbol Table on file
    ;; 清除原始的引入表
    assume edi : ptr IMAGE_IMPORT_DESCRIPTOR
    mov esi, pCurrentIID
    assume esi : ptr IID_PRIVATE_DATA
    xor ebx, ebx            ; ebx = max of IID
ClearOrigIIDLoop:
    mov eax, dword ptr [edi].Name1
    test eax, eax           ; eax = dll name string RVA
    jz ExitClearOrigIIDLoop
    inc ebx
    test ebx, MAX_IID_NUM
    jnz ExitClearOrigIIDLoop
    
    ;; 储存原始IID的信息,保存到我们自己的IID结构,供解密用
    push dword ptr [edi].Name1
    pop dword ptr [esi].Name1
    push dword ptr [edi].OriginalFirstThunk
    pop dword ptr [esi].OriginalFirstThunk
    push dword ptr [edi].FirstThunk
    pop dword ptr [esi].FirstThunk
    
    ;; change Name1 RVA to offset
    invoke RVA2Offset, pMem, eax
    add eax, pMem

    ;; 加密DLL库文件的文件名, 这个函数将字符串的每个字节异或上99h
    invoke EnCryptString, eax
    
    ;; 加密引入表和显示引入表的情况相同,把显示换成加密即可
    mov eax, dword ptr [edi].OriginalFirstThunk
    test eax, eax
    jnz UseOriginalFirstThunk
    mov eax, dword ptr [edi].FirstThunk
UseOriginalFirstThunk:
    ;; change Api Name string RVA to offset
    invoke RVA2Offset, pMem, eax
    add eax, pMem
    xchg edx, eax       ; edx = Api offset in file
    push esi
EnCryptApiNameLoop:
    mov esi, dword ptr [edx]
    test esi, esi
    jz EndEnCryptApiNameLoop    
    ;; judege IMAGE_ORDINAL_FLAG32 flags
    test esi, IMAGE_ORDINAL_FLAG32
    jnz SkipEncrypt
    invoke RVA2Offset, pMem, esi
    test eax, eax
    jz SkipEncrypt 
    add eax, pMem
    add eax, 02h        ; skip HINT
    invoke EnCryptString, eax
SkipEncrypt:
    add edx, 04h   
    jmp EnCryptApiNameLoop
EndEnCryptApiNameLoop:
    pop esi             ; esi = the point of current own IID
    ;; 这里是毁坏原始IID
    push 0
    pop dword ptr [edi].Name1
    push 0
    pop dword ptr [edi].OriginalFirstThunk
    push 0
    pop dword ptr [edi].FirstThunk
    push 0
    pop dword ptr [edi].TimeDateStamp
    push 0
    pop dword ptr [edi].ForwarderChain
    
    ;; mov to next IID
    add edi, sizeof IMAGE_IMPORT_DESCRIPTOR
    add esi, sizeof IID_PRIVATE_DATA
    jmp ClearOrigIIDLoop
ExitClearOrigIIDLoop:

    ;; clear the new IID
    lea edi, pIID
    mov ecx, IMAGE_IMPORT_TABLE_SIZE
    cld
    xor al, al
    rep stosb
    sub edi, IMAGE_IMPORT_TABLE_SIZE            ; set edi back to start point
    
    ;; 增加一个我们自己的IID节,随后会将构建的IID写入到此节中
    invoke AddSection, pMem, NULL, IMAGE_IMPORT_TABLE_SIZE
    mov pImportFVA, eax    
    assume eax : ptr IMAGE_SECTION_HEADER
    ;; 这里创建一个新的IID
    ;; 我们的IID表只导入一个DLL那就是基本的kernel32.dll引入的函数也只有两个函数
    ;; LoadLibraryA,GetProcAddress两个函数。在解密节中利用这两个最基本的函数获取
    ;; 其他的API地址与DLL句柄
    ;; 结构如下
    ;; 构建新的IID按照以下结构填写即可
    ;; make new IID
    ;; ----- Image Import Descriptor -----
    ;; IMAGE_IMPORT_DESCRIPTOR
    ;; IMAGE_IMPORT_DESCRIPTOR(0)
    ;; IMAGE_THUNK_DATA
    ;; IMAGE_THUNK_DATA
    ;; IMAGE_THUNK_DATA(0)
    ;; kernel32.dll,0
    ;; IMAGE_IMPORT_BY_NAME(LoadLibraryA)
    ;; IMAGE_IMPORT_BY_NAME(GetProcAddress)
    lea edx, pIID
    assume edx : ptr IMAGE_IMPORT_DESCRIPTOR
    mov ecx, sizeof IMAGE_IMPORT_DESCRIPTOR
    add ecx, sizeof IMAGE_IMPORT_DESCRIPTOR
    add ecx, sizeof IMAGE_THUNK_DATA
    add ecx, sizeof IMAGE_THUNK_DATA
    add ecx, sizeof IMAGE_THUNK_DATA
    mov eax, dword ptr [eax].VirtualAddress
    mov pImportRVA, eax
    add eax, ecx                                ; ecx = kernel32.dll string offset
    mov dword ptr [edx].Name1, eax
    
    ;; 拷贝kernel32.dll 字符串到 文件
    mov esi, offset g_szKernelDll
    add edi, ecx
CopyKernel32StrLoop:
    mov al, byte ptr [esi]
    test al, al
    jz EndCopyKernel32StrLoop
    mov byte ptr [edi], al
    inc esi
    inc edi
    jmp CopyKernel32StrLoop
EndCopyKernel32StrLoop:
    mov byte ptr [edi], al
    inc edi
    
    ;; 设置LoadLibraryA的IMAGE_IMPORT_BY_NAME
    mov eax, edi
    sub eax, edx
    mov dwLoadLibraryThunkData, eax
    xor eax, eax
    mov word ptr [edi], ax
    add edi, 02h
    mov esi, offset g_szLoadLibrary
CopyLoadLibraryLoop:
    mov al, byte ptr [esi]
    test al, al
    jz EndCopyLoadLibraryLoop
    mov byte ptr [edi], al
    inc esi
    inc edi
    jmp CopyLoadLibraryLoop
EndCopyLoadLibraryLoop:
    mov byte ptr [edi], al
    inc edi
    
    ;; 设置GetProcAddress的IMAGE_IMPORT_BY_NAME结构
    mov eax, edi
    sub eax, edx
    mov dwGetProcAddressThunkData, eax  
    xor eax, eax
    mov word ptr [edi], ax
    add edi, 02h
    mov esi, offset g_szGetProcAddress
CopyGetProcAddressLoop:
    mov al, byte ptr [esi]
    test al, al
    jz EndCopyGetProcAddressLoop
    mov byte ptr [edi], al
    inc esi
    inc edi
    jmp CopyGetProcAddressLoop
EndCopyGetProcAddressLoop:   
    mov byte ptr [edi], al
    inc edi
     
    ;; 设置IMAGE_IMPORT_DESCRIPTOR
    ;; 我们这里设置自己的IMAGE_IMPORT_DESCRIPTOR结构,单使用FirstThunk字段
    ;; 将OriginalFirstThunk设置为0
    xor eax, eax
    mov dword ptr [edx].Characteristics, eax
    mov dword ptr [edx].TimeDateStamp, eax
    mov dword ptr [edx].ForwarderChain, eax
    push 0
    pop dword ptr [edx].OriginalFirstThunk
    mov eax, pImportRVA
    add eax, sizeof IMAGE_IMPORT_DESCRIPTOR
    add eax, sizeof IMAGE_IMPORT_DESCRIPTOR
    mov dword ptr [edx].FirstThunk, eax
    sub eax, pImportRVA                         ;  eax = 2 of IMAGE_IMPORT_DESCRIPTOR SIZE
    add edx, eax
    mov eax, dwLoadLibraryThunkData
    add eax, pImportRVA
    mov dword ptr [edx], eax
    add edx, 04h                                ; move to next point
    mov eax, dwGetProcAddressThunkData
    add eax, pImportRVA
    mov dword ptr [edx], eax
    
    ;; 拷贝新的IID到文件
    lea esi, pIID
    mov edi, pImportFVA
    assume edi : ptr IMAGE_SECTION_HEADER
    mov edi, dword ptr [edi].PointerToRawData    
    add edi, pMem
    mov ecx, IMAGE_IMPORT_TABLE_SIZE
    cld
    rep movsb
    
    ;; 修改IT表的虚拟地址和尺寸
    ;; 设置这个数据目录以便PE加载器可以寻找到IID表
    mov esi, pMem
    add esi, dword ptr [esi+03ch]
    assume esi : ptr IMAGE_NT_HEADERS
    mov eax, pImportFVA
    assume eax : ptr IMAGE_SECTION_HEADER
    push dword ptr [eax].VirtualAddress
    pop dword ptr [esi].OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT * sizeof IMAGE_DATA_DIRECTORY].VirtualAddress
    push dword ptr [eax].Misc.VirtualSize
    pop dword ptr [esi].OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT * sizeof IMAGE_DATA_DIRECTORY].isize
    
    assume edx : nothing
    assume eax : nothing
    assume esi : nothing
    assume edi : nothing
    xor eax, eax
ExitEnCryptIID:
    ret
EnCryptIID endp
最后要讲解的就是解密引入表了。解密引入表比起加密来要复杂一些。我们的具体思路是这样的。建立两个节,一个为新的引入表,一个为解密节,这个节也设置为起始的节。
解密引入表,两种解密的方式,一是直接解密后将获取的API地址填写入FirstThunk字段指向的区域,另一种方式是重定位引入地址表。
例如,在我们的程序中调用CreateFileA这个函数。call dword ptr [XXXX], 这个XXXX其实就是FirstThunk指向的那片区域中的一个地址而已,XXXX指向的地址,如果没有经过我们重定位那么直接就是跟的这个API的地址,如果经过我们重定位将这个XXXX指向的地址指到我们分配的内存中例如YYYY,那么最终将会转到YYYY指向,我们再在YYYY这处地址写入例如JMP ZZZZ样的跳转指令,其中ZZZZ代表的是系统API地址到YYYY这个地址的偏移量。那么当程序调用API时最终通过我们设置的一篇代理代码将跳转到API内执行。
这里主要的是,我们只重定位系统自身的DLL,如果是第三方的DLL,我们直接将它的地址设置到其FirstThunk指向的区域就好了。在NT系统下,加载第三方DLL是在小于070000000h,大于077FFFFFFh,9x下为小于080000000h。
解密节的代码如下
代码:
;; ----- 解密IID -----
DecryptIID:
DecryptIIDStackSize         equ 20h
DecryptIIDEip               equ -04h
DecryptIIDImageBase         equ -08h
DecryptIIDKernel32Dll       equ -0ch
DecryptIIDLoadLibraryA      equ -10h
DecryptIIDGetProcAddress    equ -14h
DecryptIIDGlobalAlloc       equ -18h
DecryptIIDGetVersion        equ -1ch
DecryptIIDIsNT              equ -20h
        
    push ebp
    mov esp, ebp
    sub esp, DecryptIIDStackSize
    ;; 获取新的EIP
    call GetEip
    GetEip:
    pop eax
    sub eax, offset GetEip - offset DecryptIID
    mov dword ptr [ebp+DecryptIIDEip], eax
    ;; 获取ImageBase
    add eax, offset DecryptIID_ImageBase - offset DecryptIID
    mov eax, dword ptr [eax]
    mov dword ptr [ebp+DecryptIIDImageBase], eax
    ;; 获取引入表地址地址
    add eax, dword ptr [eax+3ch]
    assume eax : ptr IMAGE_NT_HEADERS
    mov eax, dword ptr [eax].OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT * sizeof IMAGE_DATA_DIRECTORY].VirtualAddress
    ;; 这里就获得了引入表在内存中的位置
    add eax, dword ptr [ebp+DecryptIIDImageBase]
    ;; 获取最基本的API地址
    assume eax : ptr IMAGE_IMPORT_DESCRIPTOR
    mov eax, dword ptr [eax].FirstThunk
    add eax, dword ptr [ebp+DecryptIIDImageBase]
    ;; 从FirstThunk中取出API的地址
    push dword ptr [eax]
    pop dword ptr [ebp+DecryptIIDLoadLibraryA]
    push dword ptr [eax+04h]
    pop dword ptr [ebp+DecryptIIDGetProcAddress]
    
    ;; get kernel32.dll的句柄
    call strKernel32Dll
        db 'kernel32.dll',0
    strKernel32Dll:
    call dword ptr [ebp+DecryptIIDLoadLibraryA]
    mov dword ptr [ebp+DecryptIIDKernel32Dll], eax
    call strGlobalAlloc
        db 'GlobalAlloc',0
    strGlobalAlloc:
    push eax
    call dword ptr [ebp+DecryptIIDGetProcAddress]
    mov dword ptr [ebp+DecryptIIDGlobalAlloc], eax
    call strGetVersion
        db 'GetVersion',0
    strGetVersion:
    push dword ptr [ebp+DecryptIIDKernel32Dll]
    call dword ptr [ebp+DecryptIIDGetProcAddress]
    mov dword ptr [ebp+DecryptIIDGetVersion], eax
    ;; 获取操作系统
    push eax
    call GetSystemVersion
    mov dword ptr [ebp+DecryptIIDIsNT], eax
            
    ;; 初始化 引入表
    mov esi, dword ptr [ebp+DecryptIIDEip]
    add esi, offset IID_Private_Data - offset DecryptIID
    assume esi : ptr IID_PRIVATE_DATA
    ;; 重定位API地址
    push esi
    mov edi, dword ptr [ebp+DecryptIIDEip]
    add edi, offset NEW_Thunk_Data - offset DecryptIID
    assume edi : ptr NEW_THUNK_DATA
    xor ecx, ecx
    ;; 计算每个FirstThunk列表的值
    CountEachOrigFirstThunkNum:
    mov eax, dword ptr [esi].FirstThunk
    test eax, eax
    jz EndCountEachOrigFirstThunkNum
    mov edx, eax
    add edx, dword ptr [ebp+DecryptIIDImageBase]
        CountFirstThunkListNum:
        mov eax, dword ptr [edx]
        test eax, eax
        jz EndCountFirstThunkListNum
        inc ecx
        add edx, 04h
        jmp CountFirstThunkListNum
        EndCountFirstThunkListNum:
    add esi, sizeof IID_PRIVATE_DATA
    jmp CountEachOrigFirstThunkNum
    EndCountEachOrigFirstThunkNum:
    ;; 分配内存为新的API地址表
    xor edx, edx
    mov eax, sizeof IMPORT_API_INSTRUCTION
    imul ecx
    push eax
    push GMEM_FIXED
    call dword ptr [ebp+DecryptIIDGlobalAlloc]
    mov dword ptr [edi].NewFirstThunk, eax
    mov dword ptr [edi].NextFirstThunk, eax
    assume edi : nothing
    pop esi
    
    ;; 开始处理地址表
HandleFirstThunk:
    mov eax, dword ptr [esi].FirstThunk
    test eax, eax
    jz EndHandleFirstThunk
    ;; 加载库
    mov ebx, dword ptr [esi].Name1
    add ebx, dword ptr [ebp+DecryptIIDImageBase]
    ;; 解密库文件名
    push ebx
    call DeCryptString
    ;; 加载库文件
    push ebx
    call dword ptr [ebp+DecryptIIDLoadLibraryA]
    ;; 销毁DLL文件名
    push ebx
    call ZeroString
    mov ebx, eax    ; ebx = dll handle
    
    ;; 处理OriginalFirstThunk与FirstThunk
    mov ecx, dword ptr [esi].OriginalFirstThunk
    test ecx, ecx
    jnz UseOriginalFirstThunk
    mov ecx, dword ptr [esi].FirstThunk
    UseOriginalFirstThunk:
    ;; ecx指向正确的ThunkData
    add ecx, dword ptr [ebp+DecryptIIDImageBase]
    mov edx, dword ptr [esi].FirstThunk
    add edx, dword ptr [ebp+DecryptIIDImageBase]
CreateNewApiAddrTbl:
    mov eax, dword ptr [ecx]
    test eax, eax
    jz EndCreateNewApiAddrTbl
    ;; 判断是否按序数引出
    test eax, IMAGE_ORDINAL_FLAG32
    jnz OnOrdinalImport
    ;; 处理API名字表
    add eax, dword ptr [ebp+DecryptIIDImageBase]
    add eax, 02h    
    ;; 解密API名字
    push eax
    call DeCryptString
    ;; 保存API字符串的指针
    push eax
    ;; 获取API的地址
    push edx
    push ecx
    push eax
    push ebx            ;  ebx = dll handle
    call dword ptr [ebp+DecryptIIDGetProcAddress]
    pop ecx
    pop edx
    ;; 销毁API名字
    call ZeroString
    ;; 设置API地址
    mov dword ptr [edx], eax
    ;; 转到处理下一个IMAGE_THUNK_DATA
    jmp HandleNextThunkData
OnOrdinalImport:
    push edx
    push ecx
    sub eax, IMAGE_ORDINAL_FLAG32
    push eax
    push ebx
    call dword ptr [ebp+DecryptIIDGetProcAddress]
    pop ecx
    pop edx
    ;; 设置API地址
    mov dword ptr [edx], eax
HandleNextThunkData:
    ;; 以DLL基址判断是否是系统的DLL,如非系统DLL则不用重定位
    test dword ptr [ebp+DecryptIIDIsNT], 1
    jz NowIs9x
    cmp ebx, 070000000h
    jb SkipMakeNewThunkTbl
    cmp ebx, 077FFFFFFh
    ja SkipMakeNewThunkTbl
    jmp StartHandleNextThunkData
    NowIs9x:
    cmp ebx, 080000000h
    jb SkipMakeNewThunkTbl
    
    StartHandleNextThunkData:
    push edi
    push esi
    mov edi, dword ptr [ebp+DecryptIIDEip]
    add edi, offset NEW_Thunk_Data - offset DecryptIID
    assume edi : ptr NEW_THUNK_DATA
    mov esi, dword ptr [edi].NextFirstThunk
    ;; 指向新的API地址表
    mov dword ptr [edx], esi
    ;; 相减获取偏移量
    sub eax, esi
    sub eax, sizeof IMPORT_API_INSTRUCTION
    assume esi : ptr IMPORT_API_INSTRUCTION
    mov byte ptr [esi].JmpOpcode, LONG_JMP_OPCODE
    mov dword ptr [esi].JmpAddr, eax
    ;; 移动到下一个FirstThunk
    add dword ptr [edi].NextFirstThunk, sizeof IMPORT_API_INSTRUCTION
    assume esi : nothing
    assume edi : nothing
    pop esi
    pop edi
    ;; 移动到下一个
    SkipMakeNewThunkTbl:
    add ecx, 04h
    add edx, 04h
    jmp CreateNewApiAddrTbl    
    EndCreateNewApiAddrTbl:
    add esi, sizeof IID_PRIVATE_DATA
    jmp HandleFirstThunk
    EndHandleFirstThunk:
    
    ;; 设置返回地址并跳入原入口节
    mov eax, dword ptr [ebp+DecryptIIDEip]
    add eax, offset DecryptIID_OrigEntryPoint - offset DecryptIID
    mov eax, dword ptr [eax]
    mov esp, ebp
    pop ebp
    jmp eax
    ;; ----- 结束解密IID -----
    
    ;; 原入口点
    DecryptIID_OrigEntryPoint       dd  0
    DecryptIID_ImageBase            dd  0
    ;; 自己的IID,最多MAX_IID_NUM个DLL
    IID_Private_Data    db (MAX_IID_NUM * sizeof IID_PRIVATE_DATA) dup (0)
    ;; 重定位的API地址表结构
    NEW_Thunk_Data      NEW_THUNK_DATA <0>
            
        ;; ----- ZeroString -----
        ZeroString:
        ZeroStringArg_String        equ 08h
            push ebp
            mov ebp, esp
            push esi
            push edi
            push eax
            
            ;; 将字符串清0
            mov edi, dword ptr [ebp+ZeroStringArg_String]
            mov esi, edi
            cld
            ZeroStringLoop:
            lodsb
            test al, al
            jz EndZeroStringLoop
            xor al, al
            stosb
            jmp ZeroStringLoop
            EndZeroStringLoop:
            
            pop eax
            pop edi
            pop esi
            mov esp, ebp
            pop ebp
            retn 4
        
        ;; ----- 解密字符串 -----
        DeCryptString:
        DeCryptStringArg_String     equ 08h
            push ebp
            mov ebp, esp
            push esi
            push edi
            push eax
            
            ;; 解密字符串
            mov edi, dword ptr [ebp+DeCryptStringArg_String]
            mov esi, edi
            cld
            DeCryptStringLoop:            
            lodsb
            test al, al
            jz EndDeCryptStringLoop
            xor al, ENCRYPT_STRING_KEY
            stosb
            jmp DeCryptStringLoop
            EndDeCryptStringLoop:
            
            pop eax
            pop edi
            pop esi
            mov esp, ebp
            pop ebp
            retn 04h
        ;; ----- 判断操作系统 -----
        GetSystemVersion:
        GetSystemVersionArg_GetVersion      equ 08h
        ;; nt: eax = 1
        ;; 9x: eax = 0

            push ebp
            mov ebp, esp
            push ecx
            push edx
            
            call dword ptr [ebp+GetSystemVersionArg_GetVersion]
            test eax, 080000000h
            jz GetSystemVersion_IsNT
            xor eax, eax
            jmp ExitGetSystemVersion
            GetSystemVersion_IsNT:
            xor eax, eax
            inc eax
            ExitGetSystemVersion:
            
            pop edx
            pop ecx
            mov esp, ebp
            pop ebp
            retn 04h            
EndDecryptIID: 
最后要注意的就是有时候FirstThunk指向的区域是不能写的,所以当我们做完全部工作后要将所有节的属性赋予它可写的权限。
代码:
    ;; 修改所有节为可写属性
    mov esi, dwNTHeaderAddr
    assume esi : ptr IMAGE_NT_HEADERS
    mov cx, word ptr [esi].FileHeader.NumberOfSections
    movzx ecx, cx
    add esi, sizeof IMAGE_NT_HEADERS
    assume esi : ptr IMAGE_SECTION_HEADER
    .WHILE ecx != 0
        mov eax, dword ptr [esi].Characteristics
        or eax, IMAGE_SCN_MEM_WRITE
        mov dword ptr [esi].Characteristics, eax
        dec ecx
        add esi, sizeof IMAGE_SECTION_HEADER
    .ENDW
IMAGE_SCN_MEM_WRITE 是写属性的常量值。

终于写完了。累。。。
这节的原理也可以用在R3下的HOOK方面,大家应该很轻松的想到如何做R3下的HOOK了,如果有刚刚接触研究病毒的朋友也可以联想到一种模糊入口点的方式。呵呵!
...才知道看雪论坛一贴只能传两个附件,我将列出引入表的附件穿到自己的沙发了 呵呵。。
上传的附件 CryptIID_bin.rar
CryptIID_src.rar

  • 标 题:列出引入表附件
  • 作 者:玩命
  • 时 间:2008-06-23 05:19:28

列出引入表的附件。。。

上传的附件 ListIID_bin.rar
ListIID_src.rar