好友
阅读权限 10
听众
最后登录 1970-1-1
迷人宝贝
发表于 2008-6-25 15:36
导入表的加密应该是壳对应用程序加密时最重要的部分了。首先我们先了解一下怎样寻找引入表,并列出其中的导入的函数与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了,如果有刚刚接触研究病毒的朋友也可以联想到一种模糊入口点的方式。呵呵!
引入朋友的话:‘要是用C写就好了。asm看着真累’哎。混倒
下面是引入列表的附件