静态注入PE导入表

阅读量286117

|

发布时间 : 2021-09-16 16:30:45

 

0.说明

编译器:vc++6.0

编写语言:c

全代码及测试案例的链接在最后面

 

1.导入表注入的前提

(1)注入

注入可以理解为将自己的代码添加到注入对象exe的内存(运行前后都可以),目的就是让注入对象exe在运行时能调用我们注入的代码。

(2)系统读取导入表

系统显示读取导入表结构,读取PE导入表中Name对应的DLL名,然后判断OriginalFirstAddress和FirstAddress对应的INT表和IAT表是否都有一个及以上的值,如果是,就加载对应DLL进内存,然后获取导入函数的地址。

(3)导入表注入假想

我们可以利用系统读取导入表的过程,我们自己写一个DLL,只需写一个导出函数,然后给exe构建一个导入表结构,同时构造对应的INT表和IAT表。就成功让exe加载我们的DLL到内存中。

 

2.导入表注入对应的DLL

(1)dll

要向实现导入表注入,必须先了解DLL的入口函数,类似c的main函数,dll也会有DllMain函数。

可以看下这篇文章: 9.DLL的入口函数DllMain函数)

大概就是,DLL在被调用或关闭的时候,都会返回一个有对应且固定的值,且运行DllMain函数,同时这个值也是DllMain函数的一个参数。

我们利用这个参数可以通过switch-case语句,实现子函数的调用。

而我们要实现注入的代码,就写到这个子函数里。

dll大概这样写:(dll名我写的inject.dll)

然后在对应.h头文件中声明导出函数

然后就会生成导入表注入所需的dll。

(2)inject.dll的C源码

//注入函数的内容我们可以自己写

void initialization()//注入函数,用于 进程开始时 我们主动调用
{
    MessageBox(0 , "initialization" , "1" , MB_OK);
}

void destroy()//注入函数,用于 进程结束时 我们主动调用
{
    MessageBox(0 , "destroy" , "2" , MB_OK);
}

void ImportFunction()//要导出的函数
{
    MessageBox(0 , "ImportFunction" , "3" , MB_OK);
}

BOOL APIENTRY DllMain( HMODULE hModule , DWORD  ul_reason_for_call , LPVOID lpReserved )
{
    switch (ul_reason_for_call)
    {
        case DLL_PROCESS_ATTACH://进程创建的时候调用
            initialization();
            break;
        case DLL_THREAD_ATTACH://线程创建的时候调用
            break;
        case DLL_THREAD_DETACH://线程结束的时候调用
            break;
        case DLL_PROCESS_DETACH://进程结束的时候调用
            destroy();
            break;
    }
    return TRUE;
}
//头文件中声明导出函数。
extern "C" _declspec(dllexport) void ImportFunction();

 

3.构建新的导入表的过程

(1)过程

由于我们不确定导入表末尾是否有足够的空间让我们再添加一个20字节的导入表(且保留末尾20字节0的空导入表作为结尾),我们可以先将导入表给移动到新建节区(保证构建导入表足够的空间)。

ps

  1. 可以不用移动INT表。所以导入表的内容都不用改。
  2. 新增节区必须同时包含可读可写两个属性,才能正常解析导入表和修正IAT表。

下面是我写的导入表注入的步骤

image-20200715033829302

构建完成后,我们就可以直接将dll放入注入对象exe同一文件夹,运行exe,即可成功调用。

说明:也可以不用新建节区,就像前面说的,不过是为了防止导入表后续没有足够的空间让我们添加20字节数据,如果确定他又足够的空间,那就不需要新建节区再移动懂导入表了,直接在原始位置后面添加导入表即可。

(2)C源代码

//功能:导入表注入
//参数:指向文件内存的指针
//返回值:指向导入表注入后的PE内存的指针
LPVOID ImportDescriptorInjection( LPVOID pFileBuffer )
{
    DWORD Offset;//复制导入表时的偏移等于新增节区前的文件大小
    DWORD SIZEOF_INT = 0;

    PIMAGE_DOS_HEADER pDosHeader = NULL;
    PIMAGE_NT_HEADERS32 pNtHeader = NULL;
    PIMAGE_FILE_HEADER pFileHeader = NULL;
    PIMAGE_OPTIONAL_HEADER pOptionHeader = NULL;

    PIMAGE_DATA_DIRECTORY pDataDirectory = NULL;
    PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor = NULL;

    PDWORD pThunkINT = NULL;//INT表
    PDWORD pThunkIAT = NULL;//IAT表

    PIMAGE_IMPORT_BY_NAME pImportByName = NULL;

    //第一步:    新增一个节区(注意新增节区的大小是不装的下导入表)(注意新增节区属性:可读可写(0xC0000000))                
    pFileBuffer = IncreaseNewSection( pFileBuffer , 0x3000 );//第二个参数是新增节的大小

    //初始化PE头
    pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
    pNtHeader = (PIMAGE_NT_HEADERS32)( (DWORD)pDosHeader + pDosHeader->e_lfanew );
    pFileHeader = (PIMAGE_FILE_HEADER)( (DWORD)pNtHeader + 4);
    pOptionHeader = (PIMAGE_OPTIONAL_HEADER)( (DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER);

    pDataDirectory = (PIMAGE_DATA_DIRECTORY)pOptionHeader->DataDirectory;
    pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)( (DWORD)pDosHeader + ConvertRvaToFoa(pDataDirectory[1].VirtualAddress , pFileBuffer ) );

    //初始化偏移Offset
    Offset = FileSize;

    //第二步:    按照目录项里的VirtualAddress将导入表移动到新增节区                        
    //同时修改VirtualAddress,并且Size增加一个导入表结构体大小(20字节)                                                                
    memcpy((PDWORD)( (DWORD)pDosHeader + Offset) , (PDWORD)pImportDescriptor , pDataDirectory[1].Size );
    pDataDirectory[1].VirtualAddress = ConvertFoaToRva(Offset  , pFileBuffer );
    pDataDirectory[1].Size += sizeof(IMAGE_IMPORT_DESCRIPTOR);
    //修改偏移
    Offset += pDataDirectory[1].Size ;

    //初始化指向导入表的指针
    pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)( (DWORD)pDosHeader + ConvertRvaToFoa( pDataDirectory[1].VirtualAddress , pFileBuffer ) );

    //第三步:    挨着最后一个导入表构建一个新的导入表(先遍历导入表,使指针指向最后一个空导入表)                                
    //(至少要有一个真实存在的导入函数,系统才会解析                
    //并且导入函数末尾要留四字节0做为结尾符)                
    while( pImportDescriptor->Name )
        pImportDescriptor++;

    //第四步:    挨着导入表构建INT表(结尾留4字节0),修改对应的OriginalFirstThunk    
    pImportDescriptor->OriginalFirstThunk = ConvertFoaToRva( Offset , pFileBuffer );
    //修改INT表内容    
    pThunkINT = (PDWORD)( (DWORD)pDosHeader + Offset);
    Offset += 8;//8个自己等于4个字节的OriginFirstThunk+4个末尾字节。
    *pThunkINT = ConvertFoaToRva( Offset , pFileBuffer );
    //粘贴对应的函数名结构体
    pImportByName = (PIMAGE_IMPORT_BY_NAME)( (DWORD)pDosHeader + Offset);
    strcpy( (PBYTE)&(pImportByName->Name)  , "ImportFunction");


    //修改偏移
    Offset += 2 + strlen( (PBYTE)&(pImportByName->Name)  ) + 1;//名字前还有个序号占两个字节。

    //第五步:    在其后复制INT表给IAT表(结尾留4字节0)                                    
    pThunkIAT = (PDWORD)( (DWORD)pDosHeader + Offset);
    *pThunkIAT = *pThunkINT;
    //修改对应的FirstThunk        
    pImportDescriptor->FirstThunk = ConvertFoaToRva( Offset , pFileBuffer);

    //修改偏移
    Offset += 8;

    //第六步:    在其后填写dll的名称,对应修改Name(RVA)                
    strcpy(  (PBYTE)( (DWORD)pDosHeader + Offset ) , "inject.dll");
    pImportDescriptor->Name = ConvertFoaToRva( Offset , pFileBuffer);


    return pFileBuffer ;
}

 

4.全代码演示

(1)演示

这里有个helloworld小程序

然后运行注入程序(保证要注入的dll与注入程序在同一文件夹)、输入要被注入程序地址

执行之后,在hello.exe小程序的目录里多出了注入的dll以及一个新的被注入的exe。

运行第二个hello_New.exe,即可演示被注入之后的样子

打开时第一时间:

结束时第一时间

成功!

(2)全代码及测试案例

链接:https://pan.baidu.com/s/1lGCDAxDLBfgsckoGzG0qpQ
提取码:vo4u

本文由1nt3原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/253112

安全客 - 有思想的安全新媒体

分享到:微信
+19赞
收藏
1nt3
分享到:微信

发表评论

内容需知
  • 投稿须知
  • 转载须知
  • 官网QQ群8:819797106
  • 官网QQ群3:830462644(已满)
  • 官网QQ群2:814450983(已满)
  • 官网QQ群1:702511263(已满)
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 360网络攻防实验室 安全客 All Rights Reserved 京ICP备08010314号-66