例如:
有一个游戏修改器;其中有一个按钮“自动打怪”;点击时游戏会实现相应的功能;
对于游戏程序来说,自动打怪操作本质上就是call调用一个函数;
但是修改器和游戏是两个独立的程序,游戏无法直接调用修改器中的函数;
可以考虑将修改器中的函数封装成一个dll,然后想办法将dll放到游戏的4gb空间,这样就可以了;
怎么想办法将dll放进目标程序的4gb空间中去,这就是dll注入的本质;
又有一个问题:修改器如何指挥注入到游戏中的dll做哪些操作;
这涉及到了进程通信;
需要了解进程相关的api,还需要了解线程的控制;也就是要了解win32的知识;
比如想实现“自动打怪”,一般怪物相关的数据会存放在数组、链表、二叉树等数据结构中;需要遍历相关的数据结构;因此需要了解数据结构相关知识;
1.dll的入口函数
可以在dll中添加一个入口函数;
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: Init(); //自定义的函数,在dll被加载前执行 break; case DLL_PROCESS_DETACH: Destroy(); //自定义的函数,在dll被移出时执行 break; } return TRUE; }
当dll在被加载时或被移除时会执行入口函数,并根据switch语句的case对应的阶段来决定执行相关语句;
利用入口函数,可以检测是否将dll注入成功;
创建一个用来注入的dll
头文件:
MyDll.h
#if !defined(AFX_MYDLL_H__A5631B62_A8CF_4C84_B164_75BDD6108880__INCLUDED_) #define AFX_MYDLL_H__A5631B62_A8CF_4C84_B164_75BDD6108880__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 void Init(); void Destroy(); extern "C" _declspec(dllexport) void ExportFunction(); #endif // !defined(AFX_MYDLL_H__A5631B62_A8CF_4C84_B164_75BDD6108880__INCLUDED_)
Stdafx.h
#if !defined(AFX_STDAFX_H__77F350E0_A16A_4DEF_ACEE_3955B97E200C__INCLUDED_) #define AFX_STDAFX_H__77F350E0_A16A_4DEF_ACEE_3955B97E200C__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 // Insert your headers here #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers #include <windows.h> // TODO: reference additional headers your program requires here //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately before the previous line. #endif // !defined(AFX_STDAFX_H__77F350E0_A16A_4DEF_ACEE_3955B97E200C__INCLUDED_)
实现:
MyDll.cpp
#include "stdafx.h" #include "MyDll.h" ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// void Init() { MessageBox(0,"Dll加载前:我从未见过有如此厚颜无耻之人","Init",MB_OK); } void Destroy() { MessageBox(0,"Dll销毁时:诸葛村夫,你敢","Destroy",MB_OK); } void ExportFunction() { MessageBox(0,"不能打架,不能打架金坷垃好处都有啥,谁说对了就给他","ExportFunction",MB_OK); }
入口主函数:
#include "stdafx.h" #include "MyDll.h" BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: Init(); //DLL被加载的时候会调用;HINSTANCE hModule = LoadLibrary("InjectDll.dll"); break; case DLL_PROCESS_DETACH: Destroy(); // DLL被销毁的时候;FreeLibrary(hModule); break; } return TRUE; }
该dll只有一个导出函数,作用是弹一个标题为ExportFunction的框;
有两个不导出的函数,Init()和Destory();在dll入口函数的加载和销毁阶段调用,用来提示dll是否成功注入;
如果dll注入成功,则在启动目标程序前会弹init框;
2.关于dll注入
1)常见的3环dll注入种类
注册表注入
导入表注入
特洛伊注入
远程线程注入
无DLL注入
Apc 注入
Windows挂钩注入DLL
输入法注入
特洛伊注入的原理:
exe根据导入表来加载dll,一个exe加载dll时首先要找到这个dll;
例如:如果一个程序要加载kernel32.dll,首先会在exe所在目录去找该dll,如果找不到就去系统目录找;
根据这个特点,可以自己发布一个dll,也叫kernel32,并且该dll的导出表导出的函数和kernel32一样,并放到exe所在目录;
为了防止程序崩溃,在自己的dll的导出函数中执行自己的操作后转发kernel32函数;相当于在执行真正的kernel32函数前先
3.导入表注入
1)原理:
当Exe被加载时,系统会根据Exe导入表信息来加载需要用到的DLL,
导入表注入的原理就是修改exe导入表,将自己的DLL添加到exe的导入表中,
这样exe运行时可以将自己的DLL加载到exe的进程空间.
2)主要步骤
1】根据目录项(第二个就是导入表)得到导入表信息
数据目录结构:
typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
VirtualAddress :指向导入表结构
Size:导入表的总大小
2】计算插入新导入表所需要的空间
导入表结构:
typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; }; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; } IMAGE_IMPORT_DESCRIPTOR; typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
INT表和IAT表中以名字导出的函数rva指向的结构:
typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; BYTE Name[1]; } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
新增一个导入表所需的空间如图:
A:20字节 ->导入表占20个字节
B:16字节 ->INT表每一项是一个4字节的结构,再加上结尾的全0的一项共需8字节,IAT表和IAT表在文件镜像中一样也要8字节;
C:取决于DLL名串的长度+1 ->Dll的名字符串,加1字节的表示字符串结尾的0
D:取决于函数名的长度+1+2 ->函数名字符串 + 1字节的字符串结尾符0 + 2字节的Hint
判断哪一个节的空白区 > Size(原导入表的大小) + 20 + A + B + C + D
如果空间不够:可以将C/D 存储在其他的空白区
也就是,只要空白区 > Size + 0x20就可以了
如果仍然不够,就需要扩大最后一个节,或者新增节来解决.
3】将原导入表全部Copy到空白区
4】在新的导入表后面,追加一个导入表.
5】追加8个字节的INT表 8个字节的IAT表
6】追加一个IMAGE_IMPORT_BY_NAME 结构,前2个字节是0 后面是函数名称字符串
7】将IMAGE_IMPORT_BY_NAME结构的RVA赋值给INT和IAT表中的第一项
8】分配空间存储DLL名称字符串 并将该字符串的RVA赋值给Name属性
9】修正IMAGE_DATA_DIRECTORY结构的VirtualAddress和Size