自建引入表

本文出在  水色银光「  http://www.vbin.org/ 」


上一篇文章只是大体介绍了一下有关引入表的内容,收到部份网友的来信说不太详细,将以前整理的一篇也拿了出来,希望这一篇能给大家更大的帮助。
-------------------------------------------------------------------------------
  我们平时做好程序后准备发布时,经常会对软件做一些相关的保护措施,对执行文件进行加密处理(即所谓的加壳)是我们常用的方法之一。 

  我们在Win32系统生成的EXE文件一般是PE文件,看过“PE文件格式“内容的朋友应该知道,在我们的程序中调用的API函数,程序执行时通过PE引入表(Import Table)可以得到API函数的地址,从而使程序能够正常运行。那么当我们对程序加壳后,壳程序中所调用的API函数在执行时又是如何取得其在系统中的地址的呢?

  通常加壳程序都是通过自建引入表、壳程序本身再将原引入表导入的方法使壳程序能够正常的运行。本节将详细的讲解壳程序是如何自建引入表、导入原引入表的方法,使你能够对引入表有更深入的了解。
一、自建引入表
  首先我们先回顾一下关于引入表的部分内容,详细介绍参见有关PE资料。
引入表的RVA和长度,都是存放在PE文件头结构中最后一项的目录表项部份中的第二项,其结构如下:
--------------------------------------------------
 IMAGE_DATA_DIRECTORY STRUCT
   VirtualAddress dword ? ;1
   isize      dword ? ;2
 IMAGE_DATA_DIRECTORY ENDS
 
 1、指向IMAGE_IMPORT_DESCRIPTOR数组的RVA
 2、长度
-------------------------------------------------- 
  引入表其实就是一个IMAGE_IMPORT_DESCRIPTOR 结构数组,每个被程序引用的DLL都有一个IMAGE_IMPORT_DESCRIPTOR。例如:我们的程序中所调用的函数来自于3个DLL文件,那么就有3个这样的数组。该数组的每一项如果全为0表示结尾。
IMAGE_IMPORT_DESCRIPTOR的结构如下:
--------------------------------------------------
 IMAGE_IMPORT_DESCRIPTOR STRUCT
   OriginalFirstThunk  dd ? ;1
   TimeDateStamp     dd ? ;2
   ForwarderChain    dd ? ;3
   Name1         dd ? ;4
   FirstThunk      dd ? ;5
 IMAGE_IMPORT_DESCRIPTOR ENDS

 1、指向IMAGE_THUNK_DATA数组的RVA
 2、时间日期标志
 3、正向链接索引
 4、指向DLL文件名的RVA
 5、指向引入函数真实地址单元处的RVA
--------------------------------------------------
IMAGE_THUNK_DATA其实就是一个指向IMAGE_IMPORT_BY_NAME结构的指针,来看一下
IMAGE_IMPORT_BY_NAME 的结构:
--------------------------------------------------
 IMAGE_IMPORT_BY_NAME STRUCT
   Hint   dw ? ;1
   Name1   db ? ;2
 IMAGE_IMPORT_BY_NAME ENDS

 1、索引号
 2、引入函数的函数名的ASCII字符串
--------------------------------------------------
  说了这么多结构,可能大家感觉有点晕,不要紧,下面给出自建引入表的代码,看着代码再对照着上面的相关结构,你会更明白一些。 假设我们要建的壳程序只会显示一个对话框,那么他将会调用5个API函数,这5个API函数都是最基本的,其中4个用于导入原引入表,余下的一个用于显示对框框,因此缺一不可。这5个函数来源于KERNEL32.DLL和USER32.DLL两个DLL文件,根据上面讲的,构建他们的IMAGE_IMPORT_DESCRIPTOR结构:
--------------------------------------------------
 align 4
 v_ImportA   dd Ker_API-v_ImportA     ;1
 v_TimeDateA  dd 0             ;2
 v_ForChainA  dd 0             ;3
 v_DllNameA   dd KerName-v_ImportA     ;4
 v_FThunkA   dd vGetProcAddress-v_ImportA ;5

 v_ImportB   dd Use_API-v_ImportA     ;1
 v_TimeDateB  dd 0             ;2
 v_ForChainB  dd 0             ;3
 v_DllNameB   dd UserName-v_ImportA     ;4
 v_FThunkB   dd vMessageBoxA-v_ImportA  ;5
        dd 20 dup (0)        ;6

 1、指向IMAGE_THUNK_DATA数组的地址。具体信息见下方
 2、时间日期标志。此项可以忽略
 3、正向链接索引。此项可以忽略
 4、指向我们所要调用的DLL文件名的地址
 5、指向引入函数真实地址单元处的地址。具体信息见下方
 6、结束符。由于我们总共就引用2个DLL,因此将
 IMAGE_IMPORT_DESCRIPTOR中的各项全部设为0,表示结尾
--------------------------------------------------
余下的引入表部份见下方:
--------------------------------------------------
 KerName db 'KERNEL32.DLL',0 
 Ker_API dd KAPI_A-v_ImportA
     dd KAPI_B-v_ImportA
     dd KAPI_C-v_ImportA
     dd KAPI_D-v_ImportA
     dd 0

 UserName  db 'USER32.DLL',0
 Use_API   dd UAPI_A-v_ImportA
       dd 0

 vGetProcAddress  dd 0
 vGetModuleHandleA dd 0
 vLoadLibraryA   dd 0
 vExitProcess    dd 0

 vMessageBoxA  dd 0
         dd 0

 KAPI_A db 0,0,'GetProcAddress',0
 KAPI_B db 0,0,'GetModuleHandleA',0
 KAPI_C db 0,0,'LoadLibraryA',0
 KAPI_D db 0,0,'ExitProcess',0

 UAPI_A db 0,0,'MessageBoxA',0
 vImport_End:

可以看出,以上部份也是按照引入表结构中的各项构造的。
像KAPI_A到KAPI_D和UAPI_A即IMAGE_IMPORT_BY_NAME结构。
--------------------------------------------------
  至此引入表部份已经完成一大半啦。从上面的代码可以看出,我们在某些项中所填写的地址,是相对于v_ImportA来说的偏移地址,因此还需要再得到我们自建的引入表所在位置的RVA地址,并将此值加上上面的各地址项,再将引入表所在位置的RVA地址,保存到PE文件头结构中最后一项的目录表项部份中的第二项的VirtualAddress中,再将我们所建的引入表的大小(即vImport_End-v_ImportA)保存到isize中,至此大功告成。

二、导入原引入表

为了更好的理解这部份的代码,首先讲解一下导入引入表的步骤:

1、首先获取第一个DLL模块的模块句柄;
2、利用第一步得到的DLL模块句柄通过调用GetProcAddress函数,循环得到引用此DLL 文件中的所有函数的地址并将得到的地址保存到地址表中;
3、再获取下一个DLL模块句柄,执行第2步,一直到操作完最后一个模块为止。

按照代码输入的顺序看一下代码,更容易理解:
--------------------------------------------------
 ; 前提
 ; edx保存此PE文件的基址
 ; esi保存当前引入表的RVA地址
 Next_DLL:
 mov eax,[esi+0ch]      ;1
 or eax,eax         ;
 jz Dll_END          ;2
 add eax,edx         ;
 mov ebx,eax         ;3

 push eax          ;
 call [vGetModuleHandleA]   ;4

 or eax,eax          ;
 jnz Dll_LOADED       ;

 push ebx          ;
 call [vLoadLibraryA]    ;5

 or eax,eax         ;
 jnz Dll_LOADED        ;6

 到1、获得当前IMAGE_IMPORT_DESCRIPTOR结构所指的DLL名称;
 到2、如果存在继续往下执行,如果不存在(已到结尾)跳到最后,结束导入工作;
 到3、将DLL名称所在的RVA地址加上基址,并保存在ebx中;
 到4、调用GetModuleHandleA函数,得到此DLL模块句柄;
 到5、如果此DLL未载入到内存中,通过调用LoadLibraryA函数载入此DLL;
 到6、得到DLL句柄跳到Dll_LOADED,否则往下运行到Exit_LOADER处
--------------------------------------------------
 Exit_LOADER:
 lea eax,[MI_ERR_TITLE] ;
 push 64         ;
 push eax        ;
 lea eax,[MI_ERR_TEXTS] ;
 push eax        ;
 push 0         ;
 call [vMessageBoxA]   ;1
 push 0         ;
 call [vExitProcess]   ;2

 Dll_LOADED       :
 mov [hDll],eax     ;3
 mov [FunTable_Count],0 ;4

 当我们在载入引入表某个步骤失败时,都会跳到此处。
 到1、显示出错信息;
 到2、退出程序
 到3、将得到的模块句柄进行保存
 到4、FunTable_Count表示当前我们正在操作函数的索引值
--------------------------------------------------
 Next_FUNCTION:
 mov edx,[BASE_RVA]  ;1
 mov eax,[esi]    ;
 or eax,eax      ;
 jnz Hint_OK      ;
 mov eax,[esi+10h]   ;2

 到1、得到基址
 到2、得到IMAGE_THUNK_DATA结构数组的RVA
--------------------------------------------------
 Hint_OK:
 add eax,edx        ;1
 add eax,[FunTable_Count] ;2
 
 mov ebx,[eax]       ;3

 mov edi,[esi+10h]     ;
 add edi,edx        ;
 add edi,[FunTable_Count] ;4

 test ebx,ebx       ;
 jz Function_END      ;5

 test ebx,80000000h    ;
 jnz Function_ORDINAL   ;6
 add ebx,edx        ;
 add ebx,2         ;
 jmp Function_GOON     ;7

 到1、加上基址
 到2、加上所操作函数的索引值
 到3、保存当前所操作函数信息的所在地址
 到4、得到当前所操作函数的地址表的地址
 到5、当前函数不存在跳到Function_END
 到6、当前函数信息如果是以序数表示的,跳到Function_ORDINAL
 到7、得到函数的名称,并跳到Function_GOON
--------------------------------------------------
 Function_ORDINAL:
 and ebx,0FFFFFFFh    ;1
 Function_GOON:      ;
 push ebx         ;
 push dword ptr [hDll]  ;
 call [vGetProcAddress] ;2

 or eax,eax        ;
 jz Exit_LOADER      ;3
 mov [edi],eax      ;4 
 add [FunTable_Count],4 ;
 jmp Next_FUNCTION    ;5

 Function_END:
 add esi,14h       ;
 mov edx,[BASE_RVA]    ;
 jmp Next_DLL       ;6
 Dll_END:

 到1、屏序数的高4位
 到2、调用GetProcAddress得到所操作函数的地址
 到3、不成功,退出;
 到4、将函数的地址保存到地址表中
 到5、将所操作函数的索引值加上4,继续操作下一个函数
 到6、当当前DLL的所有函数全部操作完毕,继续操作下一个DLL
--------------------------------------------------
  至此利用我们自己的程序导入引入表讲解完啦。大家可以下载代码再研究一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值