导入表的加密应该是壳对应用程序加密时最重要的部分了。首先我们先了解一下怎样寻找引入表,并列出其中的导入的函数与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
前面的只是给加密引入表做个铺垫。加密引入表和列出引入表算法都一样。只不过将显示函数加密而已。这里直接将代码列出。加密字符串的算法也很简单只是异或上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:
;; 修改所有节为可写属性 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
终于写完了。累。。。

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