滴水三期:day41.1-导入表注入

本文详细介绍了DLL注入的概念,包括前后知识串联、导入表注入原理、移动导入表的原因和步骤,以及如何设计和使用DLL。通过实例展示了如何将DLL手动或通过代码注入到目标程序中,强调了在注入过程中对导入表结构和数据目录的修改。此外,还提及了特洛伊注入作为另一种DLL注入方式。

一、前后知识串联

  • C语言的学习是为了熟练使用指针

  • 写一个游戏辅助(.exe):有自动打怪、加血、自动任务等功能。那么当点击自动打怪按钮时,怎么让游戏(.exe)去执行这个功能呢?游戏的打怪功能就是一个函数,传入相关的参数、调用打怪函数就可以实现打怪,只不过我们用鼠标或者键盘是一种快捷键的方式操作,而不是直接操控代码。

  • 如果我们把①定位游戏打怪函数的call、②传入相关参数、③执行这部分代码等功能封装到一个DLL中,把这个DLL注入到游戏运行时的虚拟空间中,接着去调用执行这个DLL,不就实现了自动打怪的功能。(上述过程就是我们提过的DLL注入

  • 那么如何指挥这个DLL去做事呢?就需要知道进程通信,即游戏辅助(.exe)与游戏(.exe)之间进行通信。(所以需要学习进程通信相关API,即Win32API,以及线程相关概念)

    image-20230420182032963
  • 而游戏里打怪相关数据有很多,不可能胡乱存,一般需要一种合适的数据结构来存储这些繁杂的数据,比如数组、链表、二叉树等(所以需要学习C++以及数据结构,可以更有开发效率的编程)

二、注入

1.注入的种类

  • 在操作系统3环层面有8种常见注入方式:①注册表注入;②导入表注入;③特洛伊注入;④远程线程注入;⑤无DLL注入;⑥Apc注入;⑦Windows挂钩注入DLL;⑧输入法注入

    今天主要讲导入表注入;后面等基础知识学的差不多了再学习其他注入方式

2.导入表注入原理

  • 当exe被加载时,系统会根据exe的导入表信息来加载此程序需要用到的DLL

    • 比如通过导入表的所有Name成员,知道要装载哪些DLL,接着操作系统会去程序所在当前文件夹下查找DLL去装载,找不到再去系统盘中找;或者如果导入表的某个结构中OriginalFirstThunk和FirstThunk都为0,系统就不会加载这个导入表结构对应的DLL,因为操作系统知道你没有使用这个DLL中的任何函数,加载没有意义。

    • 所以在下面导入表注入实操时,一定要创建一个完整的“链”:新增一个导入表结构的同时,还需要新增对应的INT表以及IAT表、以及函数名称表,不能只创建一个导入表结构

  • 导入表注入的原理就是:修改exe导入表,将自己写的DLL相关信息添加到exe的导入表中,这样exe运行时就可以将自己的DLL加载到exe的进程虚拟内存中

三、移动导入表

1.为什么要移动导入表

  • 结合导入表注入的原理思考:由于一个导入表结构对应一个DLL,所以你把DLL添加到.exe进程空间中,就需要在.exe的导入表中新增一个导入表结构(还要新增对应INT表、IAT表和函数名称表),但是原来的导入表结构是一个紧挨着一个存储的,最后有sizeof(导入表结构体)个0结尾,再后面的数据肯定都是有用的。所以我们没办法直接在导入表个结构中插入一个新的导入表结构,也没办法在原有的导入表结尾处新增导入表结构,那就只能把导入表移走!再在它结尾处新增导入表结构和全0结尾即可
  • 但是注意:只用把原来的导入表结构移走即可,不需要移走原来各个导入表结构对应的INT表和IAT表,也就不需要修改原来导入表结构中的OriginalFirstThunk和FirstThunk等字段
    • 而且IAT表是一定不能移动的
    • 因为前面学过,当程序运行时,系统会把程序中调用DLL函数的call语句后面的地址也改了!改成什么?------程序的call后面跟的是间接寻址,即call [0x....],实际上这里的地址改成了IAT表中对应函数绝对地址值的地址!
      • 即运行前:call [0x....]后面间接寻址的地址0x....INT表中某个元素的地址;

      • 而运行后:call [0x....]后面间接寻址的地址0x....,改成了IAT表中某个元素的地址;

    • 所以,要是移动IAT表,IAT表地址变了,那么你就要把程序中所有调用DLL函数的call语句后面的间接寻址的地址也改掉!但是这个工作量是非常大的!

2.导入表注入步骤

  1. 设计要注入的DLL

  2. 先找到可选PE头中最后一个成员数据目录,第二个结构就是导入表数据目录,通过VirtualAddress定位到导入表地址

  3. 依次移动原来导入表所有结构到新增节或者节与节之间的空白区或者扩大节中(哪种方法都行)

  4. 在移动后的导入表后面,新增一个导入表结构(别忘了还要在结尾留下sizeof(导入表结构体)个0)

  5. 新增一个至少8字节的INT表和IAT表,并且修正新增导入表结构中的OriginalFirstThunk和FirstThunk

    因为上面说过,导入表结构中的OriginalFirstThunk和FirstThunk字段不能为0,即必须要使用对应DLL中的至少一个函数才行,不然系统不会加载此DLL到程序虚拟内存中的。所以INT表和IAT表中至少要有一个函数信息还有4字节0表示结束(具体大小为多少,还是要看你的DLL有多少函数给程序使用)

  6. 还要新增至少一个IMAGE_IMPORT_BY_NAME结构,即函数名称表:前两个字节为0即可,后面是函数名称字符串

  7. 修改INT表和IAT表(运行前)中的元素值,指向IMAGE_IMPORT_BY_NAME结构中元素。

  8. 再在后面分配空间存储此DLL名称字符串,并将该字符串的起始RVA赋值给导入表结构中的Name

  9. 最后修正导入表数据目录中的VirtualAddress和Size

四、设计要注入的DLL

1.创建DLL

  • 我们先自己编写一个DLL,用于后面导入表注入实验

  • 先用VC6++创建一个DLL:名为InjectDll,接着在InjectDll.h头文件中声明三个函数,前两个DLL内部使用,最后一个为导出函数供别的程序使用

    创建DLL和使用DLL都在day35.1-静态、动态链接库中详细讲过

    // InjectDll.h: interface for the InjectDll class.
    //
    //////////////////////////////////////////////////////////////////////
    #if !defined(AFX_INJECTDLL_H__0713C052_B98E_4101_AB64_77B2AE5596C8__INCLUDED_)
    #define AFX_INJECTDLL_H__0713C052_B98E_4101_AB64_77B2AE5596C8__INCLUDED_
    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000
    
    void Init();  //这两个函数不用导出
    void Destroy();
    extern "C" _declspec(dllexport) void ExportFunction();  //这个函数要导出
    
    #endif // !defined(AFX_INJECTDLL_H__0713C052_B98E_4101_AB64_77B2AE5596C8__INCLUDED_)
    
    image-20230420191726611
  • 再在InjectDll.cpp中定义三个函数

    // InjectDll.cpp: implementation of the InjectDll class.
    #include "InjectDll.h"
    #include <windows.h>
    
    void Init(){
         
         
    	MessageBox(0,"Init","Init",MB_OK); //简单的一个弹窗功能
    }
    void Destroy(){
         
         
    	MessageBox(0,"Destroy","Destroy",MB_OK);
    }
    void ExportFunction(){
         
         
    	MessageBox(0,"ExportFunction","ExportFunction",MB_OK);
    }
    
  • 最后定义DLL入口函数:即当程序运行、装载此DLL时以及程序退出、卸载此DLL时会调用DllMain()函数,调用DllMain方法后,如果传入的ul_reason_for_call参数为DLL_PROCESS_ATTACH(这是一个宏),表示正在装载DLL,则会调用Init()方法;如果传入的ul_reason_for_call参数为DLL_PROCESS_DETACH,表示正在卸载DLL,则会调用Destroy()方法。

    BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved){
         
           //定义DllMain方法
    	switch(ul_reason_for_call){
         
         
    		case DLL_PROCESS_ATTACH:  //当程序运行,装载DLL时会调用一次DllMain方法,执行Init()
    			Init();
    			break;
    		case DLL_PROCESS_DETACH:  //当程序结束,卸载DLL时会调用一次DllMain方法,执行Destroy()
    			Destroy();
    			break;
        }
        return TRUE;
    }
    
    image-20230420192512005

2.使用DLL

  • 创建好DLL后,我们来使用一下DLL中的功能,看看究竟这个DLL干了什么。所以再创建一个控制台项目:名为useDll(就是我们平时写C创建的工程),把InjectDll工程中的Inject.dllInject.lib复制到useDll工程目录下面

    192820
  • 最后在UseDll.cpp中使用隐式链接,调用一下InjectDll导出的函数

    // UseDll.cpp : Defines the entry point for the console application.
    #include "stdafx.h"
    #include <windows.h>
    
    #pragma comment(lib,"InjectDll.lib")
    extern "C" _declspec(dllexport) void ExportFunction();
    int main(int argc, char* argv[]){
         
         
    	ExportFunction();  //调用DLL中的导出函数
    	return 0;
    }
    
    image-20230420193417197
  • 查看一下功能:F7编译、F5运行UseDll.exe、再依次点确定、最后按F5:发现会先弹Init窗口、接着弹ExportFunction窗口、最后弹Destroy窗口

    image-20230420193606262 image-20230420193719746 image-20230420193920470

    由于此DLL中定义了DllMain函数,所以当UseDll.exe程序最终执行时由于使用了此DLL中的函数,那么必然会装载DLL和卸载DLL,所以就会自动执行DllMain函数。所以会先因为DLL装载而执行Init()、再因为调用了导出函数而执行ExportFunction()、最后因为程序退出而执行Destroy()

  • 也可以使用显式链接、使用DLL中的函数

    // UseDll.cpp : Defines the entry point for the console application.
    #include "stdafx.h"
    #include <windows.h>
    
    typedef void (*Funcp)();  //声明函数指针
    int main(int argc, char* argv[]){
         
         
    	HMODULE hModule = LoadLibrary("InjectDll.dll");  //加载DLL
    	Funcp myFunc = (Funcp)GetProcAddress(hModule,"ExportFunction");  //获取导出函数地址并赋给函数指针
    	myFunc();  //调用导出函数
    	FreeLibrary(hModule);  //释放DLL
    	return 0;
    }
    

    同样,在LoadLibrary行设断点:加载DLL时弹Init框、调用导出函数时弹ExportFunction框、释放DLL时弹Destroy框。显式链接使用DLL更加灵活,可以主动加载DLL和释放DLL

五、作业

在了解上述DLL的功能之后,我们就需要以导入表注入的方式,将此DLL注入到一个程序中(这里以ipmsg.exe举例)

1.手动用工具实现注入

  • 我们将InjectDll.dll注入到ipmsg_inject.exe中:先创建一个ipmsg.exe程序的副本,改名为ipmsg_inject.exe,接着把写好的InjectDll.dll复制到ipmsg文件所在目录;再使用LordPE工具打开ipmsg_inject.exe导入表;在导入表的空白处右键–Add Import;接着输入要注入的DLL名称:InjectDll.dll,而且必须要输入至少一个API函数(前面解释过),由于我们只设计了一个ExportFunction()导出函数,所以再API栏中输入再点击+键。此时就完成了导入表DLL注入

    170056
  • 我们双击运行ipmsg_inject.exe,查看注入后的效果:双击运行时由于加载了DLL,就会调用当中的DllMain()函数,且由于是加载,所以又会调用Init()函数;最后在任务栏中右键关闭ipmsg_inject.exe,由于卸载了DLL,所以会调用当中的DllMain()函数,且由于是退出,所以又会调用Destroy()函数。但由于程序没有主动或人为的使用到DLL的ExportFunction()函数,所以并不会有ExportFunction的弹窗

    image-20230421170746758 image-20230421170823335

  • 最后我们可以再用LordPE查看一下注入后ipmsg_inject.exe的PE文件结构有什么不同:会发现原来导入表的RVA是0x253C0,现在导入表RVA为0x3D027,且发现多了一个InjectDll.dll的导入表结构以及INT表和IAT表还有函数名称等,且RVA都是在0x3D000左右,所以可以发现这个工具是将原来的导入表移到了新节当中,并在新节中新增了所需要的结构

    171355

2.代码实现导入表注入

#include "stdafx.h"
#include <stdlib.h>
#include <string.h>

typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef unsigned char BYTE;

#define MZ 0x5A4D
#define PE 0x4550
#define IMAGE_SIZEOF_SHORT_NAME 8

//DOS头
struct _IMAGE_DOS_HEADER {
   
   
	WORD e_magic;  //MZ标记
	WORD e_cblp;
	WORD e_cp;
	WORD e_crlc;
	WORD e_cparhdr;
	WORD e_minalloc;
	WORD e_maxalloc;
	WORD e_ss;
	WORD e_sp;
	WORD e_csum;
	WORD e_ip;
	WORD e_cs;
	WORD e_lfarlc;
	WORD e_ovno;
	WORD e_res[4];
	WORD e_oemid;
	WORD e_oeminfo;
	WORD e_res2[10];
	DWORD e_lfanew;  //PE文件真正开始的偏移地址
};

//标准PE头
struct _IMAGE_FILE_HEADER {
   
   
	WORD Machine;  
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值