吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 8135|回复: 11
收起左侧

软件保护壳技术专题 - 加密引入表

[复制链接]
迷人宝贝 发表于 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看着真累’哎。混倒
下面是引入列表的附件

CryptIID_bin.rar

3 KB, 下载次数: 5, 下载积分: 吾爱币 -1 CB

CryptIID_src.rar

8 KB, 下载次数: 8, 下载积分: 吾爱币 -1 CB

ListIID_bin.rar

2 KB, 下载次数: 3, 下载积分: 吾爱币 -1 CB

ListIID_src.rar

3 KB, 下载次数: 2, 下载积分: 吾爱币 -1 CB

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Squn 发表于 2008-6-25 16:05
标 题: 【成果6.2】软件保护壳技术专题 - 加密引入表
作 者: 玩命
时 间: 2008-06-23,05:18
链 接: http://bbs.pediy.com/showthread.php?t=67098

[s:45]
sizhaoming 发表于 2008-6-25 16:10
telive 发表于 2008-6-25 16:12
没能看懂这么高深的东西呢,先收藏,,过些日子再来看看
askyer 发表于 2008-6-25 16:36
写的很详细 呵呵
偶的32位汇编需要加强了 呵呵
微笑嘻嘻 发表于 2009-3-6 21:16
太好了,完全看不懂
hj_18 发表于 2009-4-17 19:35
为了看这个教程有所收获,顶一下。
hj_18 发表于 2009-4-17 19:36
为了看这个教程有所收获,顶一下。
ieaoe 发表于 2012-4-22 22:37
这么老的帖子还是被我挖出来了
ieaoe 发表于 2012-4-22 22:39
为什么没给钱啊
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-23 08:30

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表