自己写导入引入表

我们平时做好程序后准备发布时,经常会对软件做一些相关的保护措施,对执行文件进行加密处理(即所谓的加壳)是我们常用的方法之一。 

我们在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。例如:我们的程序中所调用的函数来自于3DLL文件,那么就有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字符串

说了这么多结构,可能大家感觉有点晕,不要紧,下面给出自建引入表的代码,看着代码再对照着上面的相关结构,你会更明白一些。 假设我们要建的壳程序只会显示一个对话框,那么他将会调用5API函数,这5API函数都是最基本的,其中4个用于导入原引入表,余下的一个用于显示对框框,因此缺一不可。这5个函数来源于KERNEL32.DLLUSER32.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、结束符。由于我们总共就引用2DLL,因此将

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_AKAPI_DUAPI_AIMAGE_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、将得到的模块句柄进行保存

4FunTable_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、付费专栏及课程。

余额充值