导入表的加密应该是壳对应用程序加密时最重要的部分了。首先我们先了解一下怎样寻找引入表,并列出其中的导入的函数与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
[COLOR=Red];; 寻找引入表[/COLOR]
assume esi : ptr IMAGE_NT_HEADERS
mov eax, dword ptr [esi].OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT * sizeof IMAGE_DATA_DIRECTORY].VirtualAddress
[COLOR=Red];; 将内存偏移转换为文件偏移,再加上文件头的内存映射就等于引入表在文件的地址了[/COLOR]
invoke RVA2Offset, pMem, eax
xchg ebx, eax
add ebx, pMem
assume ebx : ptr IMAGE_IMPORT_DESCRIPTOR
[COLOR=Red];; 这里直到一个全0的IMAGE_IMPORT_DESCRIPTOR结构为结束
;; 这里判断Name1是否为NULL[/COLOR]
ListIIDLoop:
mov eax, dword ptr [ebx].Name1
test eax, eax
jz EndListIIDLoop
[COLOR=Red];; 此时eax指向一个DLL名字的RVA,我们将RVA转换为文件的偏移[/COLOR]
invoke RVA2Offset, pMem, eax
[COLOR=Red];; 文件的偏移加上文件的起始指针就为文件中的地址[/COLOR]
add eax, pMem
[COLOR=Red] ;; 打印DLL名[/COLOR]
invoke PrintLine, offset g_szOutFormat, offset g_szOutLine
invoke PrintLine, offset g_szOutFormat, eax
invoke PrintLine, offset g_szOutFormat, offset g_szOutLine
[COLOR=Red];; 检查OriginalFirstThunk是否为0。如果为0则使用FirstThunk [/COLOR]
mov edx, dword ptr [ebx].OriginalFirstThunk
test edx, edx
jnz UseOrignalFirstThunk
mov edx, dword ptr [ebx].FirstThunk
UseOrignalFirstThunk:
[COLOR=Red] ;; 转换RVA转换文件偏移[/COLOR]
invoke RVA2Offset, pMem, edx
add eax, pMem
mov edx, eax
DisplayApiName:
[COLOR=Red];; 查看edx的最高位是否是1确定是否以序数引出[/COLOR]
test dword ptr [edx], IMAGE_ORDINAL_FLAG32
jnz DisPlayOrd
[COLOR=Red];; 这里edx指向IMAGE_IMPORT_BY_NAME结构的RVA,继续将它转换[/COLOR]
mov eax, dword ptr [edx]
invoke RVA2Offset, pMem, eax
add eax, pMem
assume eax : ptr IMAGE_IMPORT_BY_NAME
[COLOR=Red];; 打印API字符串,将Name1的地址设置给eax寄存器[/COLOR]
lea eax, [eax].Name1
invoke PrintLine, offset g_szOutFormat, eax
jmp NextAPI
DisPlayOrd:
[COLOR=Red] ;; 取出序数,低2个字节为序数[/COLOR]
mov eax, dword ptr [edx]
and eax, 0FFFFh
invoke PrintLine, offset g_szOutOrdFormat, eax
NextAPI:
[COLOR=Red];; 取下一个IMAGE_THUNK_DATA的值[/COLOR]
add edx, 04h
[COLOR=Red];; 直到edx指向一个0[/COLOR]
mov eax, dword ptr [edx]
test eax, eax
jnz DisplayApiName
[COLOR=Red];; 指向下一个DLL[/COLOR]
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
IID_PRIVATE_DATA struct
Name1 dd 0
OriginalFirstThunk dd 0
FirstThunk dd 0
IID_PRIVATE_DATA ends
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)