标 题: [翻译]向导入表中注入代码
翻 译: arhat
时 间: 2006-08-06 17:41
链 接: http://bbs.pediy.com/showthread.php?threadid=30166
详细信息:

向导入表中注入代码

 

                

文档编号:

NULL

原作者:

Ashkbiz Danehkar

译者:

Arhat[ptg]

 

审校:

NULL

 

发布时间:

2007-01-07

原文:

http://www.codeproject.com/useritems/inject2it.asp

关键词:

导入表 注入 木马 PE

本文主要介绍向PE文件格式的导入表中注入代码,这种技术也被称为API重定向技术。

 

 

 


 

 

0     深入导入表...................................... 4

1     导入描述符一瞥...................................... 7

2     API重定向技术....................................... 12

2.1      它怎么工作?...................................... 15

2.2      What you call this?............................... 16

3     防止逆向工程......................................... 20

4     Runtime导入表注入.................................... 22

5     特洛伊木马........................................... 29

5.1      How works a Yahoo Messenger hooker?................ 29

6     结论................................................. 31

 


 

Note: This is an unedited contribution. If this article is inappropriate, needs attention or copies someone else's work without reference then please Report this article.

Download the code for this article:

·                      itview.zip (87.1 KB) Import Table viewer

·                      pemaker6.zip (96.6 KB) PE Maker to redirect API by JMP

·                      pemaker7.zip (193 KB) PE Maker to redirect ShellAbout()

·                      zimport.zip (130 KB) Import Table runtime redirector

让我们设想一下,如果可以通过操纵导入表thunk把导入函数的入口重定向到我们指定的特殊例程,那么将有可能通过我们的例程过滤导入的需求。此外,我们也可以通过它安置适当的例程,这一般由专业的PE保护器完成,另外,有些rootkits用这个方法把它的恶意代码嵌入受害程序。

在逆向工程领域,我们把它称做API重定向技术,尽管如此,我不准备用源码的形式补充说明这个领域内的所有观点,这篇文章只准备用一些简单的代码说明这个技术的概貌;我不会发布与商业项目有关或有可能被用作恶意用途的代码,不过,我认为这篇文章可以做为了解此类主题的入门介绍。


 

0    深入导入表

注:这一部分的内容在看雪出版的《软件加密技术内幕》里已有详细说明,这里把它翻译出来主要是为了保持文章的完整性。

PE文件结构包括了MS-DOS头,NT头,区段(section)头和区段映像,大致如Figure 1如示。从DOSWindows时代,MS-DOS头一直存在于所有的Microsoft可执行文件中。NT头的概念是从UNIX系统的ELFExecutable and Linkable Format)中借用的,所以,PE格式实际上是ELF的姊妹。PE格式头部包含了“PE”特征,COFFCommon Object File Format)头部,Portable Executable Optimal头和区段头。

Figure 1 - PE可执行文件格式结构

Visual C++included目录里的<winnt.h>头文件里可以发现NT头的定义。通过使用DbgHelp.dll里的ImageNtHeader()可以轻易找到这个信息。为了找到NT头,你也可以用DOS头,因为DOS头的末尾部分e_lfanew提供了NT头的偏移量。

 

typedef struct _IMAGE_NT_HEADERS { 

    DWORD Signature; 

    IMAGE_FILE_HEADER FileHeader; 

    IMAGE_OPTIONAL_HEADER OptionalHeader;

} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;

PE Optional头里有一些数据目录,它们描绘了当前进程的虚拟内存里的主信息表的大小和相对位置。这些表可以保存资源,导入表,输出表,重定位,调试,TLSthread local storage)和COM runtime的信息。想找到没有导入表的PE可执行文件是不可能的;这个表包含DLL名和函数名,当程序想通过它们的虚拟地址请求它们时,这些信息是必需的。控制台可执行文件不包含资源表;不过,资源表却是Windows GUIGraphic User Interface)可执行文件的重要组成部分。当DLL倾向于向外输出它的函数时,以及在OLE Active-X容器中,输出表是必须的。当没有COM+ runtime头部时.NET虚拟机不能被执行。像你看到的那样,在PE格式里每个表都有特殊的用途,见Figure 2

Figure 2 - 数据目录

Data
Directories

0 Export Table

1 Import Table

2 Resource Table

3 Exception Table

4 Certificate File

5 Relocation Table

6 Debug Data

7 Architecture Data

8 Global Ptr

9 Thread Local Storage Table

10 Load Config Table

11 Bound Import Table

12 Import Address Table

13 Delay Import Descriptor

14 COM+ Runtime Header

15 Reserved

 

// <winnt.h>

 

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16

 

// Optional header format.

 

typedef struct _IMAGE_OPTIONAL_HEADER {

 

    ...

   

    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];

} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

 

 

// Directory Entries

#define IMAGE_DIRECTORY_ENTRY_EXPORT     0 // Export Directory

#define IMAGE_DIRECTORY_ENTRY_IMPORT     1 // Import Directory

#define IMAGE_DIRECTORY_ENTRY_RESOURCE   2 // Resource Directory

#define IMAGE_DIRECTORY_ENTRY_BASERELOC  5 // Base Relocation Table

#define IMAGE_DIRECTORY_ENTRY_DEBUG      6 // Debug Directory

#define IMAGE_DIRECTORY_ENTRY_TLS        9 // TLS Directory

仅用两三行代码,我们就可以获得导入表的大小和位置。知道导入表的位置后,我们可以进行下一步――找回DLL名和函数名,我们随后将讨论这个内容。

PIMAGE_NT_HEADERS pimage_nt_headers = ImageNtHeader(pImageBase);

DWORD it_voffset = pimage_nt_headers->OptionalHeader.

    DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;

PIMAGE_DOS_HEADER pimage_dos_header = PIMAGE_DOS_HEADER(pImageBase);

PIMAGE_NT_HEADERS pimage_nt_headers = (PIMAGE_NT_HEADERS)

    (pImageBase + pimage_dos_header->e_lfanew);

DWORD it_voffset = pimage_nt_headers->OptionalHeader.

    DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;


1    导入描述符一瞥

导入表的导入目录条目把我们引向文件映像中的导入表的位置。每个导入的DLL都有一个容器――导入描述符,它包含第一个thunk的地址,最初的第一个thunk的地址,指向DLL名的指针。第一个thunk引用第一个thunk的位置,在程序运行期间,WindowsPE loader将把这些thunk初始化,如Figure 5。最初的第一个thunk指向thunk的第一个存放处,那里提供了Hint数据的地址,以及每个函数的函数名,如Figure 4。在这个例子里,第一个最初的thunk不存在;因此,第一个thunk引用了Hint数据和函数名所在的地方,如Figure 3

如下列如示,用IMAGE_IMPORT_DESCRIPTOR结构说明导入描述符:

typedef struct _IMAGE_IMPORT_DESCRIPTOR {

    DWORD   OriginalFirstThunk;

    DWORD   TimeDateStamp;

    DWORD   ForwarderChain;

    DWORD   Name;

    DWORD   FirstThunk;

} IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;

成员

OriginalFirstThunk
它指向第一个thunk――IMAGE_THUNK_DATA,这个thunk保存着Hint的地址和函数名。

TimeDateStamp
如果存在绑定(binding)它将包含time/data戳。如果它是0,在导入的DLL里没有绑定发生。在近来,把它设为0xFFFFFFFF以描述发生了绑定。

ForwarderChain
在绑定的老版本中,它引用API链的第一个传送器。把它设为0xFFFFFFFF意思是没有传送器。

Name
它显示DLL名的RVArelative virtual address)。

FirstThunk
它包含由IMAGE_THUNK_DATA定义的第一个thunk数组的虚拟地址,loader用函数虚拟地址初始化这个thunk,它指向第一个thunkHintthunk和函数名。

typedef struct _IMAGE_IMPORT_BY_NAME {

    WORD    Hint;

    BYTE    Name[1];

} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

 

typedef struct _IMAGE_THUNK_DATA {

    union {

        PDWORD                 Function;

        PIMAGE_IMPORT_BY_NAME  AddressOfData;

    } u1;

} IMAGE_THUNK_DATA, *PIMAGE_THUNK_DATA;

Figure 3 - Import Table View

Figure 4 - Import Table View with Orignal First Thunk

这两个导入表(figure 3 and figure 4)说明了有/没有最初的第一个thunk的导入表之间的异同。

Figure 5 - Import Table after overwritten by PE loader

我们可以用Dependency WalkerFigure 6查看导入表的全部信息。顺便说一下,我提供了另一个工具――Import Table viewer,如Figure 7所示,它的界面比较简单,操作和Dependency Walker差不多。我相信它的源码将对你更好地理解这类工具所实现的主要功能有所帮助。

Figure 6 - Dependency Walker, Steve P. Miller

在这里,我们看到控制台程序中能显示导入DLL和导入函数的一段简单的源码。不过,我想我的Import Table viewerFigure 7)将因为它的图形化界面,而对我们的主题有更多推动。

PCHAR       pThunk;

PCHAR       pHintName;

DWORD       dwAPIaddress;

PCHAR       pDllName;

PCHAR       pAPIName;

//----------------------------------------

DWORD dwImportDirectory= RVA2Offset(pImageBase, pimage_nt_headers->OptionalHeader.

    DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

//----------------------------------------

PIMAGE_IMPORT_DESCRIPTOR pimage_import_descriptor= (PIMAGE_IMPORT_DESCRIPTOR)

                                                   (pImageBase+dwImportDirectory);

//----------------------------------------

while(pimage_import_descriptor->Name!=0)

{

    pThunk= pImageBase+pimage_import_descriptor->FirstThunk;

    pHintName= pImageBase;

    if(pimage_import_descriptor->OriginalFirstThunk!=0)

    {

        pHintName+= RVA2Offset(pImageBase, pimage_import_descriptor->OriginalFirstThunk);

    }

    else

    {

        pHintName+= RVA2Offset(pImageBase, pimage_import_descriptor->FirstThunk);

    }

    pDllName= pImageBase + RVA2Offset(pImageBase, pimage_import_descriptor->Name);

    printf(" DLL Name: %s First Thunk: 0x%x", pDllName,

           pimage_import_descriptor->FirstThunk);

    PIMAGE_THUNK_DATA pimage_thunk_data= (PIMAGE_THUNK_DATA) pHintName;

    while(pimage_thunk_data->u1.AddressOfData!=0)

    {

        dwAPIaddress= pimage_thunk_data->u1.AddressOfData;

        if((dwAPIaddress&0x80000000)==0x80000000)

        {

            dwAPIaddress&= 0x7FFFFFFF;

            printf("Proccess: 0x%x", dwAPIaddress);

        }

        else

        {

            pAPIName= pImageBase+RVA2Offset(pImageBase, dwAPIaddress)+2;

            printf("Proccess: %s", pAPIName);

        }

        pThunk+= 4;

        pHintName+= 4;

        pimage_thunk_data++;

    }

    pimage_import_descriptor++;

}

Figure 7 - Import Table viewer


 

2    API重定向技术

我们已经学习了与导入表有关的基础知识,是介绍重定向方法的时候了。重定向算法非常简单,在当前进程的虚拟内存里生成一个额外的虚拟空间,生成用JMP重定向到最初函数位置的指令。我们可以用绝对jump或相对jump实现它。在绝对jump的例子里,你要倍加小心,你不能像Figure 8那样简单的实现,你首先应当把虚拟地址移到EAX,然后用JMP EAX转移。在pemaker6.zip里,我用相对jump完成重定向。

Figure 8 - Overview of a simple API redirection by the absolute jump instruction

我在以前的文章[