WindowsAPI查缺补漏-动态链接库

静态链接库

静态链接库头文件:

1
2
3
#pragma once
int funAdd(int a, int b);
int funMul(int a, int b);

静态链接源文件:

1
2
3
4
5
6
7
#include "StaticLinkLibrary.h"
int funAdd(int a, int b) {
return a + b;
};
int funMul(int a, int b) {
return a * b;
};

调用方:

1
2
3
4
5
6
7
8
#include <Windows.h>
#include "StaticLinkLibrary.h" // StaticLinkLibrary.h头文件
#pragma comment(lib, "StaticLinkLibrary.lib") // StaticLinkLibrary.lib对象库
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
TCHAR szBuf[256] = { 0 };
wsprintf(szBuf, TEXT("funAdd(5, 6) = %d\nfunMul(5, 6) = %d"), funAdd(5, 6), funMul(5, 6));
MessageBox(NULL, szBuf, TEXT("提示"), MB_OK);
};

动态链接库

DLL头文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#pragma once
// 声明导出的变量、类和函数
#ifdef DLL_EXPORT
#define DLL_VARABLE extern "C" __declspec(dllexport)
#define DLL_CLASS __declspec(dllexport)
#define DLL_API extern "C" __declspec(dllexport)
#else
#define DLL_VARABLE extern "C" __declspec(dllimport)
#define DLL_CLASS __declspec(dllimport)
#define DLL_API extern "C" __declspec(dllimport)
#endif
/****************************************************************/
typedef struct _POSITION {
int x;
int y;
}POSITION, * PPOSITION;
// 导出变量
DLL_VARABLE int nValue; // 导出普通变量
DLL_VARABLE POSITION ps; // 导出结构体变量
// 导出类
class DLL_CLASS CStudent {
public:
CStudent(LPTSTR lpName, int nAge);
~CStudent();
public:
LPTSTR GetName();
int GetAge();
private:
TCHAR m_szName[64];
int m_nAge;
};
// 导出函数
DLL_API int WINAPI funAdd(int a, int b);
DLL_API int WINAPI funMul(int a, int b);

DLL源文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 定义DLL的导出变量、类和函数
#include <Windows.h>
#include <strsafe.h>
#include <tchar.h>
#define DLL_EXPORT
#include "DllSample.h"
// 变量
int nValue; // 普通变量
POSITION ps; // 结构体变量
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {
nValue = 5;
ps.x = 6;
ps.y = 7;
break;
};
case DLL_THREAD_ATTACH: {};
case DLL_THREAD_DETACH: {};
case DLL_PROCESS_DETACH: {
break;
};
};
return TRUE;
};
// 类
CStudent::CStudent(LPTSTR lpName, int nAge) {
if (m_szName)
StringCchCopy(m_szName, _countof(m_szName), lpName);
m_nAge = nAge;
};
CStudent::~CStudent() {};
LPTSTR CStudent::GetName() {
return m_szName;
};
int CStudent::GetAge() {
return m_nAge;
};
// 函数
int WINAPI funAdd(int a, int b) {
return a + b;
};
int WINAPI funMul(int a, int b) {
return a * b;
};

但不建议从DLL中导出变量和类,好孩子不要这么干。调用时需要.h、.lib和.dll。静态链接库.lib为对象库,包含函数实现代码,而DLL的.lib为导入库,里面只有连接到.dll的符号列表。

C++编译器对函数导出名有命名规则,函数名以“?”开头,后面是函数名,在“@@YA”后面表示参数列表,用代号表示如X为void、D为char、E为unsigned char、F为short、H为int、I为unsigned int、N为double等,最后以“@Z”结尾。如上述funAdd的导出名为?funAdd@@YAHHH@Z。这样命名规则导致C++编译的DLL没法被其他语言调用,解决方法是用关键字extern "C"强制使用标准C函数名称,调用约定默认为__cdecl,当强制改为__stdcall调用约定时,函数导出名前加下划线,结尾“@”后加参数传递给函数的字节数,如_funAdd@8。如果仍然要导出funAdd函数时,新建模块定义.def文件:

1
2
3
EXPORTS
funAdd
funMul

然后对DLL的隐式链接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <Windows.h>
#include <strsafe.h>
#include <tchar.h>
#include "DllSample.h" // DllSample.h头文件
#pragma comment(lib, "DllSample.lib") // DllSample.lib导入库文件
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
TCHAR szBuf[256] = { 0 };
TCHAR szBuf2[512] = { 0 };
// 测试导出变量
wsprintf(szBuf, TEXT("nValue = %d\nps.x = %d, ps.y = %d\n"), nValue, ps.x, ps.y);
StringCchCopy(szBuf2, _countof(szBuf2), szBuf);
// 测试导出类
CStudent student((LPTSTR)TEXT("老王"), 40);
wsprintf(szBuf, TEXT("姓名:%s, 年龄:%d\n"), student.GetName(), student.GetAge());
StringCchCat(szBuf2, _countof(szBuf2), szBuf);
// 测试导出函数
wsprintf(szBuf, TEXT("funAdd(5, 6) = %d\nfunMul(5, 6) = %d"), funAdd(5, 6), funMul(5, 6));
StringCchCat(szBuf2, _countof(szBuf2), szBuf);
MessageBox(NULL, szBuf2, TEXT("提示"), MB_OK);
};

发行时应带上.dll文件,定位DLL文件的顺序为:可执行文件目录、Windows系统目录、Windows目录、进程当前目录、PATH环境变量列出的所有目录。

VC++编译使用/MT或/MTd选项时多线程静态链接C运行时库,程序体积增大但可在其他机器上正常运行;使用/MD或/MDd选项时多线程动态链接C运行时库,程序体积减小但可能出现缺少相关动态链接库的错误。

入口点函数DllMain

代码如上,处理一些调用DLL入口点函数的原因码,如果不需要空着就行。常见的有:

枚举值 含义
DLL_PROCESS_ATTACH DLL第一次被映射到进程地址空间中时引发,后续如果再有线程用LoadLibrary(Ex)映射时不会引发,返回TRUE初始化成功,返回FALSE则整个进程直接退出。隐式链接时lpvReserved非NULL,显式链接为NULL。
DLL_PROCESS_DETACH DLL被从进程内存空间撤销映射时引发,用TerminateProcess终止进程时不会引发,DLL加载失败、进程终止或FreeLibrary卸载DLL时引发,忽略返回值。用FreeLibrary或DLL加载失败而卸载DLL时lpvReserved为NULL,进程终止导致DLL卸载时lpvReserved为非NULL。
DLL_THREAD_ATTACH 进程中创建一个新线程时引发,一般用于新线程初始化。地址空间所有DLL都会被通知,所有DLL该通知都执行结束后才创建,主线程的创建不引发该通知,忽略返回值。
DLL_THREAD_DETACH 进程中有一个线程正在退出时引发,一般用于相关善后。所有DLL都处理完该通知时该线程才退出,用TerminateProcess终止进程时不会引发,DLL加载失败、进程终止或FreeLibrary卸载DLL时不引发。

延迟加载DLL

即显式加载DLL。编译器在可执行模块中嵌入__delayLoadHelper2函数,延迟加载DLL函数时跳转该函数,该函数解析延迟加载导入表,并用LoadLibrary(Ex)GetProcAddress等函数完成加载和函数地址解析。当延迟加载的DLL不存在时,或当DLL中一个函数不存在时,该函数抛出一个异常,可用结构化异常处理SEH捕捉异常来继续运行,否则进程终止。

__FUnloadDelayLoadedDLL2

当希望一个延迟加载的DLL在进程结束或DLL卸载时不会被立即释放内存,下次加载时速度更快时,可对链接器开启/DELAY:UNLOAD选项。当确实要把某个延迟加载的DLL从内存中释放时使用该函数:

1
2
3
BOOL WINAPI __FUnloadDelayLoadedDLL2(
LPCSTR szDll //ANSI字符串 区分大小写
);

DragQueryFile

查询所拖放文件的名称:

1
2
3
4
5
6
UINT DragQueryFile(
_In_ HDROP hDrop,
_In_ UINT iFile, //要查询的文件索引
_Out_ LPTSTR lpszFile, //返回指定索引的文件名的缓冲区
UINT cch //lpszFile缓冲区大小 单位字符
); //成功返回复制到lpszFile缓冲区字符数 不含终止空字符

对于iFile,文件索引从0开始,设置为0xFFFFFFFF时函数返回所拖放文件总数。该值介于0和所拖放文件总数之间时将指定索引文件名复制到lpszFile缓冲区。 当lpszFile为NULL则函数返回所需缓冲区大小,单位字符,不含终止空字符。

要创建一个支持拖放的窗口时,CreateWindowEx的dwExStyle指定WS_EX_ACCEPTFILES,程序接收WM_DROPFILES消息,wParam为所拖放文件信息的HDROP类型结构句柄,lParam缺省,处理完该消息应返回0。

例如获取所有拖动文件的文件名:

1
2
3
4
5
6
7
8
9
10
11
HDROP hDrop;
UINT uDragCount;
TCHAR szFileName[MAX_PATH] = { 0 };
case WM_DROPFILES: {
hDrop = (HDROP)wParam;
uDragCount = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
for (UINT i = 0; i < uDragCount; i++) {
DragQueryFile(hDrop, i, szFileName, _countof(szFileName));
//...
};
};

DragQueryPoint

查询拖动操作期间鼠标指针位置:

1
2
3
4
BOOL DragQueryPoint(
_In_ HDROP hDrop,
_Out_ PPOINT lppt
);

DragFinish

释放系统为拖动操作分配的内存:

1
2
3
BOOL DragFinish(
HDROP hDrop
);

例子

GetMd5.dll源文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// 定义DLL的导出函数
#include <Windows.h>
#define DLL_EXPORT
#include "GetMd5.h"
// 函数
BOOL GetMd5(LPCTSTR lpFileName, LPTSTR lpMd5) {
HANDLE hFile, hFileMap;
HCRYPTPROV hProv, hHash;
TCHAR szContainer[] = TEXT("MyKeyContainer");
LPVOID lpMemory;
// 打开文件
hFile = CreateFile(lpFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
return FALSE;
// 获取指定加密服务提供程序(CSP)中指定密钥容器的句柄
if (!CryptAcquireContext(&hProv, szContainer, NULL, PROV_RSA_FULL, 0))
if (!CryptAcquireContext(&hProv, szContainer, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET))
return FALSE;
// 创建加密服务提供程序(CSP)哈希对象的句柄,第2个参数指定为不同的值可以获取不同的哈希例如SHA
if (!CryptCreateHash(hProv, CALG_MD5, NULL, 0, &hHash))
return FALSE;
// 为了能够处理大型文件,所以使用内存映射文件。为hFile文件对象创建一个文件映射内核对象
hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (!hFileMap)
return FALSE;
// 获取文件大小,内存分配粒度
__int64 qwFileSize;
DWORD dwFileSizeHigh;
SYSTEM_INFO si;
qwFileSize = GetFileSize(hFile, &dwFileSizeHigh);
qwFileSize += (((__int64)dwFileSizeHigh) << 32);
GetSystemInfo(&si);
// 把文件映射对象hFileMap不断映射到进程的虚拟地址空间中
__int64 qwFileOffset = 0; // 文件映射对象偏移量
DWORD dwBytesInBlock; // 本次映射大小
while (qwFileSize > 0) {
dwBytesInBlock = si.dwAllocationGranularity;
if (qwFileSize < dwBytesInBlock)
dwBytesInBlock = (DWORD)qwFileSize;
lpMemory = MapViewOfFile(hFileMap, FILE_MAP_READ, (DWORD)(qwFileOffset >> 32), (DWORD)(qwFileOffset & 0xFFFFFFFF), dwBytesInBlock);
if (!lpMemory)
return FALSE;
// 对已映射部分进行操作,将数据添加到哈希对象
if (!CryptHashData(hHash, (LPBYTE)lpMemory, dwBytesInBlock, 0))
return FALSE;
// 取消本次映射,进行下一轮映射
UnmapViewOfFile(lpMemory);
qwFileOffset += dwBytesInBlock;
qwFileSize -= dwBytesInBlock;
};
// 获取哈希值中的字节数
DWORD dwHashLen = 0;
if (!CryptGetHashParam(hHash, HP_HASHVAL, NULL, &dwHashLen, 0))
return FALSE;
// 获取哈希值
LPBYTE lpHash = new BYTE[dwHashLen];
if (!CryptGetHashParam(hHash, HP_HASHVAL, lpHash, &dwHashLen, 0))
return FALSE;
for (DWORD i = 0; i < dwHashLen; i++)
wsprintf(lpMd5 + i * 2, TEXT("%02X"), lpHash[i]);
delete[]lpHash;
// 关闭文件映射内核对象句柄
CloseHandle(hFileMap);
// 关闭文件句柄
CloseHandle(hFile);
// 释放哈希句柄
CryptDestroyHash(hHash);
// 释放CSP句柄
CryptReleaseContext(hProv, 0);
return TRUE;
};
/*
CryptAcquireContext函数用于获取指定加密服务提供程序(CSP)中指定密钥容器的句柄,返回的句柄用于调用所选CSP的CryptoAPI函数;
CryptCreateHash函数启动数据流的哈希处理, 该函数创建加密服务提供程序(CSP)哈希对象的句柄,该句柄可以用于后续的CryptHashData函数调用;
CryptHashData函数用于将数据添加到指定的哈希对象,可以多次调用该函数来计算长数据流或不连续数据流的哈希;
CryptGetHashParam函数用于获取具体的哈希值。
*/

GetMd5.dll头文件:

1
2
3
4
5
6
7
8
9
#pragma once
// 声明导出的函数
#ifdef DLL_EXPORT
#define DLL_API extern "C" __declspec(dllexport)
#else
#define DLL_API extern "C" __declspec(dllimport)
#endif
// 导出函数
DLL_API BOOL GetMd5(LPCTSTR lpFileName, LPTSTR lpMd5);

调用者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <windows.h>
#include <Shlwapi.h>
#include <delayimp.h>
#include "resource.h"
#include "GetMd5.h"
#pragma comment(lib, "Shlwapi.lib")
#pragma comment(lib, "GetMd5.lib")
// 全局变量
HINSTANCE g_hInstance;
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
g_hInstance = hInstance;
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
HRSRC hResBlock;
HANDLE hRes;
LPVOID lpDll;
DWORD dwDllSize;
HANDLE hFile;
TCHAR szFileName[MAX_PATH] = { 0 };
TCHAR szMd5[64] = { 0 };
HDROP hDrop;
switch (uMsg) {
case WM_INITDIALOG: {
// 关于ChangeWindowMessageFilterEx函数,请参见用户界面特权隔离一节
ChangeWindowMessageFilterEx(hwndDlg, WM_DROPFILES, MSGFLT_ALLOW, NULL);
ChangeWindowMessageFilterEx(hwndDlg, 0x49, MSGFLT_ALLOW, NULL);// 0x49 == WM_COPYGLOBALDATA
//ChangeWindowMessageFilter(WM_DROPFILES, MSGFLT_ADD);
//ChangeWindowMessageFilter(0x49, MSGFLT_ADD);
// 如果当前目录下不存在GetMd5.dll
if (!PathFileExists(TEXT("GetMd5.dll"))) {
hResBlock = FindResource(g_hInstance, MAKEINTRESOURCE(IDR_MYDLL), TEXT("MyDll"));
if (!hResBlock)
return FALSE;
hRes = LoadResource(g_hInstance, hResBlock);
if (!hRes)
return FALSE;
lpDll = LockResource(hRes);
dwDllSize = SizeofResource(g_hInstance, hResBlock);
hFile = CreateFile(TEXT("GetMd5.dll"), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
return FALSE;
WriteFile(hFile, lpDll, dwDllSize, NULL, NULL);
CloseHandle(hFile);
};
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_GETMD5: {
// 获取指定文件的MD5
if (GetDlgItemText(hwndDlg, IDC_EDIT_FILENAME, szFileName, _countof(szFileName)))
if (GetMd5(szFileName, szMd5)) {
SetDlgItemText(hwndDlg, IDC_EDIT_MD5, szMd5);
// 卸载延迟加载的dll
__FUnloadDelayLoadedDLL2("GetMd5.dll");
};
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
case WM_DROPFILES: {
hDrop = (HDROP)wParam;
DragQueryFile(hDrop, 0, szFileName, _countof(szFileName));
SetDlgItemText(hwndDlg, IDC_EDIT_FILENAME, szFileName);
DragFinish(hDrop);
return FALSE;
};
};
return FALSE;
};

线程局部存储

原理

线程局部存储TLS结构包含索引数组(或进程位数组)和存储槽。索引数组为4字,一共64位,每一位对应存储槽数组中的一个元素。存储槽为一个64个LPVOID类型元素的数组,每个LPVOID元素为一个指向具体内容的指针。每一个线程都有一个存储槽,但线程之间共用同一个索引数组。索引数组的每一位为1则代表所有线程的存储槽该数组索引位置均有地址指向数据,为0则没有。索引数组位数最少为TLS_MINIMUM_AVAILABLE即64个,需要时系统分配更多,最高记录1088个。用NtCurrentTeb获取当前线程的线程环境块TEB结构指针。

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct _TEB {
PVOID Reserved1[12];
PPEB ProcessEnvironmentBlock; //进程环境块PEB结构指针
PVOID Reserved2[399];
BYTE Reserved3[1952];
PVOID TlsSlots[64]; //线程存储槽
BYTE Reserved4[8];
PVOID Reserved5[26];
PVOID ReservedForOle;
PVOID Reserved6[4];
PVOID TlsExpansionSlots;
} TEB, * PTEB;

TlsAlloc

从进程中分配一个TLS索引,把索引数组某位设为1,且每个线程存储槽中该索引位置初始化为NULL,进程中每个线程可使用该索引操作自己的数据。分配到的TLS索引为了每个线程都要访问,一般要设为全局变量。

1
DWORD WINAPI TlsAlloc(VOID); //成功返回TLS索引 失败TLS_OUT_OF_INDEXES

TlsSetValue

在存储槽指定索引处写入数据:

1
2
3
4
BOOL WINAPI TlsSetValue(
_In_ DWORD dwTlsIndex, //TLS索引
_In_opt_ LPVOID lpTlsValue //要存储在调用线程TLS插槽中指定索引处的值
);

TlsGetValue

获取存储槽中指定索引处的数据:

1
2
3
LPVOID WINAPI TlsGetValue(
_In_ DWORD dwTlsIndex //TLS索引
); //成功返回调用线程存储槽中指定索引处值 失败返回0且GetLastError为ERROR_SUCCESS

TlsFree

释放TLS索引,把索引数组中对应位置0,并把每个线程存储槽中该索引位置数据化NULL。注意它不释放内容指针指向内存,自己搞的自己手动释放。

1
2
3
BOOL WINAPI TlsFree(
_In_ DWORD dwTlsIndex //TLS索引
);

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <windows.h>
#include "resource.h"
// 宏定义
#define THREADCOUNT 5
// 全局变量
DWORD g_dwTlsIndex;
HWND g_hwndDlg;
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 线程函数
DWORD WINAPI ThreadProc(LPVOID lpParameter);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
HANDLE hThread[THREADCOUNT] = { 0 };
switch (uMsg) {
case WM_INITDIALOG: {
g_hwndDlg = hwndDlg;
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_OK: {
// 从进程中分配一个TLS索引
g_dwTlsIndex = TlsAlloc();
if (g_dwTlsIndex == TLS_OUT_OF_INDEXES) {
MessageBox(hwndDlg, TEXT("TlsAlloc函数调用失败!"), TEXT("错误提示"), MB_OK);
return FALSE;
};
// 创建THREADCOUNT个线程
SetDlgItemText(g_hwndDlg, IDC_EDIT_TLSSLOTS, TEXT(""));
for (int i = 0; i < THREADCOUNT; i++)
if ((hThread[i] = CreateThread(NULL, 0, ThreadProc, (LPVOID)i, 0, NULL)) != NULL)
CloseHandle(hThread[i]);
// 等待所有线程结束,释放TLS索引
WaitForMultipleObjects(THREADCOUNT, hThread, TRUE, INFINITE);
TlsFree(g_dwTlsIndex);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
LPVOID lpData = NULL;
TCHAR szBuf[64] = { 0 };
lpData = new BYTE[256];
ZeroMemory(lpData, 256);
// 在自己的存储槽中指定索引处写入数据
if (!TlsSetValue(g_dwTlsIndex, lpData)) {
wsprintf(szBuf, TEXT("线程%d调用TlsSetValue失败"), (INT)lpParameter);
MessageBox(g_hwndDlg, szBuf, TEXT("错误提示"), MB_OK);
delete[]lpData;
return 0;
};
// 获取自己的存储槽中指定索引处的数据
lpData = TlsGetValue(g_dwTlsIndex);
if (!lpData && GetLastError() != ERROR_SUCCESS) {
wsprintf(szBuf, TEXT("线程%d调用TlsGetValue失败"), (INT)lpParameter);
MessageBox(g_hwndDlg, szBuf, TEXT("错误提示"), MB_OK);
};
// 每个线程存储槽中指定索引处的数据显示到编辑控件中
wsprintf(szBuf, TEXT("线程%d的索引%d处的值:0x%p\r\n"), (INT)lpParameter, g_dwTlsIndex, lpData);
SendMessage(GetDlgItem(g_hwndDlg, IDC_EDIT_TLSSLOTS), EM_SETSEL, -1, -1);
SendMessage(GetDlgItem(g_hwndDlg, IDC_EDIT_TLSSLOTS), EM_REPLACESEL, TRUE, (LPARAM)szBuf);
delete[]lpData;
return 0;
};

静态TLS

使用这种方式声明为全局变量或静态变量:

1
__declspec(thread) LPVOID gt_lpData;

该前缀在编译链接时把变量放到.tls区段,如果是Release版本可能会被优化到别的区段。一般用gt_前缀表示全局TLS变量,用st_表示静态TLS变量。当然每个线程的该变量都是独立的,上面那个例子可以改写成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <windows.h>
#include "resource.h"
// 宏定义
#define THREADCOUNT 5
// 全局变量
__declspec(thread) LPVOID gt_lpData = NULL;
HWND g_hwndDlg;
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 线程函数
DWORD WINAPI ThreadProc(LPVOID lpParameter);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
HANDLE hThread[THREADCOUNT];
switch (uMsg) {
case WM_INITDIALOG: {
g_hwndDlg = hwndDlg;
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_OK: {
// 创建THREADCOUNT个线程
SetDlgItemText(g_hwndDlg, IDC_EDIT_TLSSLOTS, TEXT(""));
for (int i = 0; i < THREADCOUNT; i++)
if ((hThread[i] = CreateThread(NULL, 0, ThreadProc, (LPVOID)i, 0, NULL)) != NULL)
CloseHandle(hThread[i]);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
TCHAR szBuf[64] = { 0 };
gt_lpData = new BYTE[256];
ZeroMemory(gt_lpData, 256);
// 每个线程的静态TLS数据显示到编辑控件中
wsprintf(szBuf, TEXT("线程%d的gt_lpData值:0x%p\r\n"), (INT)lpParameter, gt_lpData);
SendMessage(GetDlgItem(g_hwndDlg, IDC_EDIT_TLSSLOTS), EM_SETSEL, -1, -1);
SendMessage(GetDlgItem(g_hwndDlg, IDC_EDIT_TLSSLOTS), EM_REPLACESEL, TRUE, (LPARAM)szBuf);
delete[]gt_lpData;
return 0;
};

Windows钩子

SetWindowsHookEx

将应用程序定义的钩子函数安装到钩子链中,钩子函数用于监视系统某些类型事件:

1
2
3
4
5
6
HHOOK WINAPI SetWindowsHookEx(
_In_ INT idHook, //要安装的钩子函数类型
_In_ HOOKPROC lpfn, //指向钩子函数指针
_In_ HINSTANCE hMod, //包含钩子函数的DLL模块句柄
_In_ DWORD dwThreadId //与钩子函数关联的线程ID 即被监视进程
); //成功返回钩子函数句柄 失败NULL

idHook钩子函数类型常用的有:

枚举值 含义
WH_GETMESSAGE GetMessagePeekMessage获取消息时调用钩子函数。
WH_KEYBOARD GetMessagePeekMessage获取键盘消息WM_KEYUP或WM_KEYDOWN时调用钩子函数。
WH_MOUSE GetMessagePeekMessage获取鼠标消息时调用钩子函数。
WH_CALLWNDPROC 系统消息发送到目标从窗口过程前调用钩子函数。
WH_CALLWNDPROCRET 目标窗口过程处理完消息后调用钩子函数。
WH_DEBUG /调用其他钩子函数前调用本钩子函数。
WH_CBT 激活/创建/销毁/最小化/最大化/移动/调整窗口前、完成系统命令前、从系统消息队列中删除鼠标/键盘事件前、设置键盘焦点前、与系统消息队列同步前调用钩子函数。
WH_FOREGROUNDIDLE 前台线程即将变为空闲时调用钩子函数。
WH_JOURNALRECORD 记录发送给系统消息队列所有信息,钩子函数不需要在DLL中。
WH_JOURNALPLAYBACK 回放日志记录钩子记录的系统事件,钩子函数不需要在DLL中。
WH_MSGFILTER 用户对对话框/消息框/菜单/滚动条操作后,系统发送对应消息前调用钩子函数,局部钩子。
WH_SYSMSGFILTER 同WH_MSGFILTER,系统钩子。
WH_SHELL Windows Shell接收通知事件前调用钩子函数,如Shell被激活/重绘。

当监视当前进程时,lpfn指向的钩子函数位于当前进程程序代码中,hMod为NULL,dwThreadId设置为当前进程的线程ID。当监视其他进程时,lpfn指向的钩子函数必须位于DLL中,hMod为包含钩子函数的DLL模块句柄,dwThreadId设置为其他进程创建的线程ID。当设置全局系统钩子时,lpfn指向的钩子函数必须位于DLL中,hMod为包含钩子函数的DLL模块句柄,dwThreadId为0。

钩子回调函数定义如下:

1
2
3
4
5
LRESULT CALLBACK HookProc(
INT nCode,
WPARAM wParam,
LPARAM lParam
);

CallNextHookEx

把消息事件传递给钩子链中下一个钩子函数。系统中可安装多个不同类型或相同类型的钩子,每种钩子有维护一个钩子链,是同种类型钩子的钩子函数指针列表,最近加入的钩子放在链表头部,每个钩子函数有义务把消息事件传递下去。

1
2
3
4
5
6
LRESULT WINAPI CallNextHookEx(
_In_opt_ HHOOK hhk, //忽略
_In_ INT nCode,
_In_ WPARAM wParam,
_In_ LPARAM lParam
);

nCode、wParam、lParam用钩子函数同名参数即可。

UnhookWindowsHookEx

卸载安装在钩子链中的钩子函数:

1
2
3
BOOL WINAPI UnhookWindowsHookEx(
_In_ HHOOK hhk //钩子句柄
);

ToUnicode

把虚拟键码转为Unicode字符:

1
2
3
4
5
6
7
8
INT WINAPI ToUnicode(
_In_ UINT wVirtKey, //要转换的虚拟键码
_In_ UINT wScanCode, //按键扫描码
_In_opt_ CONST PBYTE lpKeyState, //指向包含当前键盘状态的256字节数组指针
_Out_ LPWSTR pwszBuff, //接收转换后的Unicode字符缓冲区
_In_ INT cchBuff, //pwszBuff缓冲区大小 单位字符
_In_ UINT wFlags //为0或1则菜单处于活动状态
);

就钩子函数应用场景来说,wVirtKey用wParam,wScanCode用lParam高16位即可,lpKeyState不讲了看下面例子用法。

例子

定义一个WH_KEYBOARD键盘钩子的钩子函数。对于回调函数的nCode,当小于0时钩子函数必须将消息传递给CallNextHookEx并返回其返回值,当为HC_ACTION时表示wParam和lParam中含有有关击键消息,处理完用CallNextHookEx传递下去,也可以直接返回TRUE丢弃信息并组织信息向下传播。wParam包含虚拟键码,lParam包含消息重复计数、扫描码、扩展键标志、状态描述吗、先前键状态标志、转换状态标志等。

DLL头文件为:

1
2
3
4
5
6
7
8
9
10
11
12
#pragma once
// 声明导出的函数
#ifdef DLL_EXPORT
#define DLL_API extern "C" __declspec(dllexport)
#else
#define DLL_API extern "C" __declspec(dllimport)
#endif
// 导出函数
DLL_API BOOL InstallHook(int idHook, DWORD dwThreadId, HWND hwnd);
DLL_API BOOL UninstallHook();
// 内部函数
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);

DLL源文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 定义DLL的导出函数
#include <Windows.h>
#include <tchar.h>
#define DLL_EXPORT
#include "HookDll.h"
// 全局变量
HINSTANCE g_hMod;
HHOOK g_hHookKeyboard;
TCHAR g_szBuf[256] = { 0 };
#pragma data_seg("Shared")
HWND g_hwnd = NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:Shared,RWS")
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {
g_hMod = hModule;
break;
};
case DLL_THREAD_ATTACH: {};
case DLL_THREAD_DETACH: {};
case DLL_PROCESS_DETACH: {
break;
};
};
return TRUE;
};
// 导出函数
BOOL InstallHook(int idHook, DWORD dwThreadId, HWND hwnd) {
if (!g_hHookKeyboard) {
g_hwnd = hwnd;
g_hHookKeyboard = SetWindowsHookEx(idHook, KeyboardProc, g_hMod, dwThreadId);
if (!g_hHookKeyboard)
return FALSE;
};
return TRUE;
};
BOOL UninstallHook() {
if (g_hHookKeyboard)
if (!UnhookWindowsHookEx(g_hHookKeyboard))
return FALSE;
g_hHookKeyboard = NULL;
return TRUE;
};
// 内部函数
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
BYTE bKeyState[256];
COPYDATASTRUCT copyDataStruct = { 0 };
if (nCode < 0)
return CallNextHookEx(NULL, nCode, wParam, lParam);
if (nCode == HC_ACTION) {
GetKeyboardState(bKeyState);
bKeyState[VK_SHIFT] = HIBYTE(GetKeyState(VK_SHIFT));
ZeroMemory(g_szBuf, sizeof(g_szBuf));
ToUnicode(wParam, lParam >> 16, bKeyState, g_szBuf, _countof(g_szBuf), 0);
copyDataStruct.cbData = sizeof(g_szBuf);
copyDataStruct.lpData = g_szBuf;
SendMessage(g_hwnd, WM_COPYDATA, (WPARAM)g_hwnd, (LPARAM)&copyDataStruct);
};
return CallNextHookEx(NULL, nCode, wParam, lParam);
};

监视程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <windows.h>
#include "HookDll.h"
#include "resource.h"
#pragma comment(lib, "HookDll.lib")
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_INSTALLHOOK: {
InstallHook(WH_KEYBOARD, 0, hwndDlg);
break;
};
case IDC_BTN_UNINSTALLHOOK: {
UninstallHook();
break;
};
case IDCANCEL: {
UninstallHook();
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
case WM_COPYDATA: {
SendMessage(GetDlgItem(hwndDlg, IDC_EDIT_KEYBOARD), EM_SETSEL, -1, -1);
SendMessage(GetDlgItem(hwndDlg, IDC_EDIT_KEYBOARD), EM_REPLACESEL, TRUE, (LPARAM)(LPTSTR)(((PCOPYDATASTRUCT)lParam)->lpData));
return TRUE;
};
};
return FALSE;
};

获取其他窗口信息时最好用PostMessage而不是SendMessage,要不处理事件可能过长。

变量共享

创建一个叫“Shared”的区段,里面放个变量。注意只能将已初始化的变量保存到自定义段中,没初始化就跑别的段去了。结尾表示自定义段结束,回到默认段。

1
2
3
#pragma data_seg("Shared")
HWND g_hwnd = NULL;
#pragma data_seg()

为“Shared”段指定相关属性,R为READ,W为WRITE,E为EXECUTE,S为SHARED。

1
#pragma comment(linker,"/SECTION:Shared,RWS")

DLL注入

前提芝士

下面小节有杂七杂八的新芝士,这里一口气介绍完。

对于桌面上图标,每个图标都为一个列表项。发送LVM_GETITEMTEXT获取列表项文本,wParam为列表项索引,lParam为指向LVITEM结构的指针。 发送LVM_GETITEMPOSITION获取列表项位置,wParam指定为列表项索引,lParam为指向POINT结构指针,返回列表项左上角坐标。发送LVM_FINDITEM查找桌面上具有指定列表项文本的列表项,wParam指定开始搜索的列表项索引,不包括指定项,-1从头开始,lParam是指向LVFINDINFO结构指针,包含有关要搜索内容信息。发送LVM_SETITEMPOSITION设置该列表项位置,wParam为列表项索引,LOWORD(lParam)和HIWORD(lParam)为左上角的X、Y坐标。

系统中程序管理器Program Manager窗口类名为Progman(Win10为WorkerW?),其下有一个窗口类名SHELLDLL_DefView的子窗口,旗下有一个窗口类名为SysListView32的子窗口,即桌面列表视图控件。

GetTopWindow

查找指定父窗口关联的第一个子窗口句柄:

1
2
3
HWND WINAPI GetTopWindow(
_In_opt_ HWND hWnd //父窗口句柄 为NULL则返回桌面窗口顶层窗口句柄
);

GetNativeSystemInfo

之前GetSystemInfo返回的SYSTEM_INFO结构的wProcessorArchitecture如果为PROCESSOR_ARCHITECTURE_INTEL则32位,为PROCESSOR_ARCHITECTURE_AMD64或PROCESSOR_ARCHITECTURE_IA64则64位,但这返回的是系统架构。如果想要获取当前进程为64位还是在WoW64下运行的32位应用程序,如:

1
2
3
4
5
6
7
BOOL Is64bitSystem(VOID) {
SYSTEM_INFO si = { 0 };
GetNativeSystemInfo(&si);
if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 || si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64)
return TRUE;
return FALSE;
};

IsWow64Process

判断一个进程是否运行在WoW64环境中:

1
2
3
4
BOOL WINAPI IsWow64Process(
_In_ HANDLE hProcess, //进程句柄
_Out_ PBOOL Wow64Process //返回TRUE或FALSE
);

hProcess需要PROCESS_QUERY_INFORMATION或PROCESS_QUERY_LIMITED_INFORMATION。

CreateRemoteThread

在其他进程中创建一个远程线程:

1
2
3
4
5
6
7
8
9
HANDLE WINAPI CreateRemoteThread(
_In_ HANDLE hProcess, //目标远程进程
_In_ LPSECURITY_ATTRIBUTES lpThreadAttributes, //线程安全属性结构
_In_ SIZE_T dwStackSize, //线程栈空间大小 单位字节
_In_ LPTHREAD_START_ROUTINE lpStartAddress, //线程函数指针
_In_ LPVOID lpParameter, //传递给线程函数的参数
_In_ DWORD dwCreationFlags, //线程创建标志
_Out_ LPDWORD lpThreadId //返回线程ID
);

SystemParameterInfo

用于获取或设置系统参数,包括桌面、图标、输入(键盘、鼠标、输入语言等)、菜单、电源、屏保、超时、UI效果、窗口和辅助功能等。

1
2
3
4
5
6
BOOL WINAPI SystemParameterInfo(
_In_ UINT uiAction, //要获取或设置的系统参数
_In_ UINT uiParam, //参数
_Inout_ PVOID pvParam, //参数
_In_ UINT fWinIni //选项
);

uiAction系统参数很多自己去查MSDN,其决定uiParam和pvParam的具体含义。当fWinIni为SPIF_UPDATEINIFILE表示将新的系统参数写入用户配置文件,为SPIF_SENDCHANGE表示既写入用户配置文件也广播WM_SETTINGCHANGE消息到所有顶级窗口,为0则不需要。

Wow64DisableWow64FsRedirection/Wow64RevertWow64FsRedirection

禁用/恢复调用线程的文件系统重定向:

1
2
3
4
5
6
BOOL WINAPI Wow64DisableWow64FsRedirection(
_Out_ PVOID* ppOldValue //返回一些WoW64文件系统重定向信息
);
BOOL WINAPI Wow64RevertWow64FsRedirection(
_In_ PVOID pOldValue //Wow64DisableWow64FsRedirection返回的文件重定向信息
);

这俩玩意儿一般这么配合着用:

1
2
3
LPVOID pOldValue = NULL;
Wow64DisableWow64FsRedirection(&pOldValue);
Wow64RevertWow64FsRedirection(&pOldValue);

Windows钩子注入

被注入的DLL头文件:

1
2
3
4
5
6
7
8
9
10
#pragma once
// 声明导出的函数
#ifdef DLL_EXPORT
#define DLL_API extern "C" __declspec(dllexport)
#else
#define DLL_API extern "C" __declspec(dllimport)
#endif
// 导出函数
DLL_API BOOL InstallHook(int idHook, DWORD dwThreadId);// 两参数分别是钩子类型和资源管理器线程ID
DLL_API BOOL UninstallHook();

被注入的DLL源文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// 定义DLL的导出函数
#include <Windows.h>
#include <Commctrl.h>
#include "resource.h"
#define DLL_EXPORT
#include "DIPSHookDll.h"
// 全局变量
HINSTANCE g_hMod;
HHOOK g_hHook;
TCHAR g_szRegSubKey[] = TEXT("Software\\Desktop Item Position Saver");
// 内部函数
LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam);
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
VOID SaveListViewItemPositions(HWND hwndLV);
VOID RestoreListViewItemPositions(HWND hwndLV);
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {
g_hMod = hModule;
break;
};
case DLL_THREAD_ATTACH: {};
case DLL_THREAD_DETACH: {};
case DLL_PROCESS_DETACH: {
break;
};
};
return TRUE;
};
// 导出函数
BOOL InstallHook(int idHook, DWORD dwThreadId) {
if (!g_hHook) {
g_hHook = SetWindowsHookEx(idHook, GetMsgProc, g_hMod, dwThreadId);
if (!g_hHook)
return FALSE;
};
// 消息钩子已经安装,通知资源管理器线程调用GetMsgProc钩子函数(为了及时响应所以主动通知)
PostThreadMessage(dwThreadId, WM_NULL, 0, 0);
return TRUE;
};
BOOL UninstallHook() {
if (g_hHook)
if (!UnhookWindowsHookEx(g_hHook))
return FALSE;
g_hHook = NULL;
return TRUE;
};
// 内部函数
LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam) {
// dll是否是刚被注入
static BOOL bFirst = TRUE;
if (nCode < 0)
return CallNextHookEx(NULL, nCode, wParam, lParam);
if (nCode == HC_ACTION)
if (bFirst) {
bFirst = FALSE;
// 在资源管理器进程中创建一个服务器窗口来处理控制程序的请求(保存、恢复桌面图标等)
CreateDialogParam(g_hMod, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
};
return CallNextHookEx(NULL, nCode, wParam, lParam);
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_APP: {
if (lParam)
SaveListViewItemPositions((HWND)wParam);
else
RestoreListViewItemPositions((HWND)wParam);
return TRUE;
};
case WM_CLOSE: {
DestroyWindow(hwndDlg);
return TRUE;
};
};
return FALSE;
};
VOID SaveListViewItemPositions(HWND hwndLV) {
int nCount;
HKEY hKey;
LVITEM lvi = { 0 };
TCHAR szName[MAX_PATH] = { 0 };
POINT pt;
// 先删除旧注册表
RegDeleteKey(HKEY_CURRENT_USER, g_szRegSubKey);
// 获取桌面列表项总数
nCount = SendMessage(hwndLV, LVM_GETITEMCOUNT, 0, 0);
// 创建子键HKEY_CURRENT_USER\Software\Desktop Item Position Saver
RegCreateKeyEx(HKEY_CURRENT_USER, g_szRegSubKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hKey, NULL);
lvi.mask = LVIF_TEXT;
lvi.pszText = szName;
lvi.cchTextMax = _countof(szName);
// 为每个列表项创建一个键值项,以列表项的文本为键名,以列表项的位置为键值
for (int i = 0; i < nCount; i++) {
ZeroMemory(szName, _countof(szName) * sizeof(TCHAR));
SendMessage(hwndLV, LVM_GETITEMTEXT, i, (LPARAM)&lvi);
SendMessage(hwndLV, LVM_GETITEMPOSITION, i, (LPARAM)&pt);
RegSetValueEx(hKey, szName, 0, REG_BINARY, (LPBYTE)&pt, sizeof(pt));
};
RegCloseKey(hKey);
};
VOID RestoreListViewItemPositions(HWND hwndLV) {
HKEY hKey;
TCHAR szName[MAX_PATH] = { 0 };
POINT pt;
DWORD dwType;
LONG_PTR lStyle;
LONG lResult;
LVFINDINFO lvfi = { 0 };
int nItem;
// 打开子键HKEY_CURRENT_USER\Software\Desktop Item Position Saver
RegOpenKeyEx(HKEY_CURRENT_USER, g_szRegSubKey, 0, KEY_QUERY_VALUE, &hKey);
// 关闭桌面图标自动排列
lStyle = GetWindowLongPtr(hwndLV, GWL_STYLE);
if (lStyle & LVS_AUTOARRANGE)
SetWindowLongPtr(hwndLV, GWL_STYLE, lStyle & ~LVS_AUTOARRANGE);
// 枚举子键HKEY_CURRENT_USER\Software\Desktop Item Position Saver下的所有键值项
lResult = ERROR_SUCCESS;
for (int i = 0; lResult != ERROR_NO_MORE_ITEMS; i++) {
DWORD dwchName = _countof(szName);
DWORD dwcbDaata = sizeof(pt);
lResult = RegEnumValue(hKey, i, szName, &dwchName, NULL, &dwType, (LPBYTE)&pt, &dwcbDaata);
if (lResult == ERROR_NO_MORE_ITEMS)
continue;
// 查找桌面上具有指定列表项文本的列表项,重新设置该列表项的位置
lvfi.flags = LVFI_STRING;
lvfi.psz = szName;
if ((dwType == REG_BINARY) && (dwcbDaata == sizeof(pt))) {
nItem = SendMessage(hwndLV, LVM_FINDITEM, -1, (LPARAM)&lvfi);
if (nItem != -1)
SendMessage(hwndLV, LVM_SETITEMPOSITION, nItem, MAKELPARAM(pt.x, pt.y));
};
};
SetWindowLongPtr(hwndLV, GWL_STYLE, lStyle);
RegCloseKey(hKey);
};

控制程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <windows.h>
#include "resource.h"
#include "DIPSHookDll.h"
#pragma comment(lib, "DIPSHookDll.lib")
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static HWND hwndLV;
HWND hwndDIPSServer;
switch (uMsg) {
case WM_INITDIALOG: {
hwndLV = GetTopWindow(GetTopWindow(FindWindow(TEXT("ProgMan"), NULL)));
// 禁用保存桌面图标、恢复桌面图标和卸载消息钩子按钮
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_SAVE), FALSE);
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_RESTORE), FALSE);
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_UNINSTALLHOOK), FALSE);
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_INSTALLHOOK: {
InstallHook(WH_GETMESSAGE, GetWindowThreadProcessId(hwndLV, NULL));
// 启用保存桌面图标、恢复桌面图标和卸载消息钩子按钮
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_SAVE), TRUE);
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_RESTORE), TRUE);
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_UNINSTALLHOOK), TRUE);
MessageBox(hwndDlg, TEXT("安装消息钩子成功"), TEXT("成功"), MB_OK);
break;
};
case IDC_BTN_UNINSTALLHOOK: {
// 获取服务器窗口句柄
hwndDIPSServer = FindWindow(NULL, TEXT("DIPSServer"));
// 使用SendMessage而不是PostMessage,确保钩子卸载以前,服务器对话框已经销毁
SendMessage(hwndDIPSServer, WM_CLOSE, 0, 0);
UninstallHook();
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_SAVE), FALSE);
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_RESTORE), FALSE);
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_UNINSTALLHOOK), FALSE);
MessageBox(hwndDlg, TEXT("卸载消息钩子成功"), TEXT("成功"), MB_OK);
break;
};
case IDC_BTN_SAVE: {
// 获取服务器窗口句柄
hwndDIPSServer = FindWindow(NULL, TEXT("DIPSServer"));
SendMessage(hwndDIPSServer, WM_APP, (WPARAM)hwndLV, TRUE);
MessageBox(hwndDlg, TEXT("保存桌面图标成功"), TEXT("成功"), MB_OK);
break;
};
case IDC_BTN_RESTORE: {
// 获取服务器窗口句柄
hwndDIPSServer = FindWindow(NULL, TEXT("DIPSServer"));
SendMessage(hwndDIPSServer, WM_APP, (WPARAM)hwndLV, FALSE);
MessageBox(hwndDlg, TEXT("恢复桌面图标成功"), TEXT("成功"), MB_OK);
break;
};
case IDCANCEL: {
if (FindWindow(NULL, TEXT("DIPSServer")))
SendMessage(hwndDlg, WM_COMMAND, IDC_BTN_UNINSTALLHOOK, 0);
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};

远程线程注入

CreateRemoteThread在目标进程中创建远程线程,但麻烦的是lpStartAddress线程函数也必须在目标进程地址空间中。我们无法将可执行代码等直接写入目标进程地址空间,获取API函数因ASLR也异常麻烦。但Kernel32.dll、User32.dll和Gdi32.dll等常用DLL在不同进程中被载入相同内存地址,但每次开机加载地址会变一次。

方法就是直接拿CreateRemoteThread启动LoadLibraryA/LoadLibraryW,字符串参数用VirtualAllocEx在目标进程分配内存地址,用WriteProcessMemory写入DLL文件名称。

DLL源文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <Windows.h>
#include <tchar.h>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
TCHAR szBuf[MAX_PATH] = { 0 }; // 模块名称
LPBYTE lpAddress = NULL; // 页面区域的起始地址
MEMORY_BASIC_INFORMATION mbi = { 0 }; // 返回页面信息
int nLen;
TCHAR szModName[MAX_PATH] = { 0 };
HWND hwndRemoteApp;
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {
// RemoteApp的窗口句柄
hwndRemoteApp = FindWindow(TEXT("#32770"), TEXT("RemoteApp"));
while (VirtualQuery(lpAddress, &mbi, sizeof(mbi)) == sizeof(mbi)) {
// 页面区域中页面的状态为MEM_FREE空闲
if (mbi.State == MEM_FREE)
mbi.AllocationBase = mbi.BaseAddress;
if ((mbi.AllocationBase == NULL) || (mbi.AllocationBase == hModule) || (mbi.BaseAddress != mbi.AllocationBase))
// 如果空间区域的基地址为NULL,或者空间区域的基地址是本模块基地址,或者页面区域的基地址并不是空间区域的基地址(每一个模块就是一块空间区域),则不要将模块名称添加到列表中
nLen = 0;
else
// 获取加载到空间区域基地址处的模块文件名
nLen = GetModuleFileName(HMODULE(mbi.AllocationBase), szModName, _countof(szModName));
if (nLen > 0) {
wsprintf(szBuf, TEXT("%p\t%s\r\n"), mbi.AllocationBase, szModName);
// 模块名称显示到RemoteApp的编辑控件中
SendDlgItemMessage(hwndRemoteApp, 1005, EM_SETSEL, -1, -1);
SendDlgItemMessage(hwndRemoteApp, 1005, EM_REPLACESEL, TRUE, (LPARAM)szBuf);
};
lpAddress += mbi.RegionSize;
};
break;
};
case DLL_THREAD_ATTACH: {};
case DLL_THREAD_DETACH: {};
case DLL_PROCESS_DETACH: {
break;
};
};
return TRUE;
};

控制程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#include <windows.h>
#include <tchar.h>
#include <TlHelp32.h>
#include "resource.h"
// 全局变量
HWND g_hwndDlg;
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
DWORD WINAPI ThreadProc(LPVOID lpParameter);
BOOL InjectDll(DWORD dwProcessId, LPTSTR lpDllPath);
BOOL EjectDll(DWORD dwProcessId, LPTSTR lpDllPath);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
HANDLE hThread = NULL;
DWORD dwProcessId;
TCHAR szDllPath[MAX_PATH] = { 0 };
switch (uMsg) {
case WM_INITDIALOG: {
g_hwndDlg = hwndDlg;
SetDlgItemText(hwndDlg, IDC_EDIT_PROCESSID, TEXT("请输入进程ID"));
SetDlgItemText(hwndDlg, IDC_EDIT_DLLPATH, TEXT("F:\\Source\\Windows\\Chapter16\\RemoteDll\\Debug\\RemoteDll.dll"));
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_INJECT: {
// 创建新线程完成对目标进程中dll的注入
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
if (hThread)
CloseHandle(hThread);
break;
};
case IDC_BTN_EJECT: {
dwProcessId = GetDlgItemInt(hwndDlg, IDC_EDIT_PROCESSID, NULL, FALSE);
GetDlgItemText(hwndDlg, IDC_EDIT_DLLPATH, szDllPath, _countof(szDllPath));
EjectDll(dwProcessId, szDllPath);
break;
};
case IDCANCEL: {
SendMessage(hwndDlg, WM_COMMAND, IDC_BTN_EJECT, 0);
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
DWORD dwProcessId;
TCHAR szDllPath[MAX_PATH] = { 0 };
dwProcessId = GetDlgItemInt(g_hwndDlg, IDC_EDIT_PROCESSID, NULL, FALSE);
GetDlgItemText(g_hwndDlg, IDC_EDIT_DLLPATH, szDllPath, _countof(szDllPath));
return InjectDll(dwProcessId, szDllPath);
};
BOOL InjectDll(DWORD dwProcessId, LPTSTR lpDllPath) {
HANDLE hProcess = NULL;
LPTSTR lpDllPathRemote = NULL;
HANDLE hThread = NULL;
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, dwProcessId);
if (!hProcess)
return FALSE;
// (1) 调用VirtualAllocEx函数在远程进程的地址空间中分配一块内存;
int cbDllPath = (_tcslen(lpDllPath) + 1) * sizeof(TCHAR);
lpDllPathRemote = (LPTSTR)VirtualAllocEx(hProcess, NULL, cbDllPath, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (!lpDllPathRemote)
return FALSE;
// (2) 调用WriteProcessMemory函数把要注入的dll的路径复制到第1步分配的内存中;
if (!WriteProcessMemory(hProcess, lpDllPathRemote, lpDllPath, cbDllPath, NULL))
return FALSE;
// (3) 调用GetProcAddress函数得到LoadLibraryA / LoadLibraryW函数(Kernel32.dll)的实际地址;
PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
if (!pfnThreadRtn)
return FALSE;
// (4) 调用CreateRemoteThread函数在远程进程中创建一个线程
hThread = CreateRemoteThread(hProcess, NULL, 0, pfnThreadRtn, lpDllPathRemote, 0, NULL);
if (!hThread)
return FALSE;
WaitForSingleObject(hThread, INFINITE);
// (5) 调用VirtualFreeEx函数释放第1步分配的内存;
if (!lpDllPathRemote)
VirtualFreeEx(hProcess, lpDllPathRemote, 0, MEM_RELEASE);
if (hThread)
CloseHandle(hThread);
if (hProcess)
CloseHandle(hProcess);
return TRUE;
};
BOOL EjectDll(DWORD dwProcessId, LPTSTR lpDllPath) {
HANDLE hSnapshot;
MODULEENTRY32 me = { sizeof(MODULEENTRY32) };
BOOL bRet;
BOOL bFound = FALSE;
HANDLE hProcess = NULL;
HANDLE hThread = NULL;
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId);
if (hSnapshot == INVALID_HANDLE_VALUE)
return FALSE;
bRet = Module32First(hSnapshot, &me);
while (bRet) {
if (_tcsicmp(TEXT("RemoteDll.dll"), me.szModule) == 0 || _tcsicmp(lpDllPath, me.szExePath) == 0) {
bFound = TRUE;
break;
};
bRet = Module32Next(hSnapshot, &me);
};
if (!bFound)
return FALSE;
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION, FALSE, dwProcessId);
if (!hProcess)
return FALSE;
// (6) 调用GetProcAddress得到FreeLibrary函数(Kernel32.dll)的实际地址;
PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "FreeLibrary");
if (!pfnThreadRtn)
return FALSE;
// (7) 调用CreateRemoteThread函数在远程进程中创建一个新线程,
// 让该线程调用FreeLibrary函数并在参数中传入已注入dll的模块地址以卸载该dll
hThread = CreateRemoteThread(hProcess, NULL, 0, pfnThreadRtn, me.modBaseAddr, 0, NULL);
if (!hThread)
return FALSE;
WaitForSingleObject(hThread, INFINITE);
if (hSnapshot != INVALID_HANDLE_VALUE)
CloseHandle(hSnapshot);
if (hThread)
CloseHandle(hThread);
if (hProcess)
CloseHandle(hProcess);
return TRUE;
};

函数转发机制注入

如kernel32.dll有许多转发函数,用VS Developer Command Prompt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
dumpbin -exports kernel32.dll
Microsoft (R) COFF/PE Dumper Version 14.39.33523.0
Copyright (C) Microsoft Corporation. All rights reserved.


Dump of file kernel32.dll

File Type: DLL

Section contains the following exports for KERNEL32.dll

00000000 characteristics
2EE8ABD0 time date stamp
0.00 version
1 ordinal base
1671 number of functions
1671 number of names

ordinal hint RVA name

1 0 AcquireSRWLockExclusive (forwarded to NTDLL.RtlAcquireSRWLockExclusive)
2 1 AcquireSRWLockShared (forwarded to NTDLL.RtlAcquireSRWLockShared)
3 2 000187B0 ActivateActCtx
4 3 00014620 ActivateActCtxWorker
5 4 00020FA0 ActivatePackageVirtualizationContext

如前2个函数没有RVA,因为kernel32.dll中根本没有转发函数的实现,调用时直接转发到NTDLL.DLL对应函数中。实现具有函数转发器功能的DLL可以:

1
#pragma comment(linker, "/export:被转发函数=某模块.目标函数")

函数转发代码用AheadLib批量生成。例如一个单纯的函数转发器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <Windows.h>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {
break;
};
case DLL_THREAD_ATTACH: {};
case DLL_THREAD_DETACH: {};
case DLL_PROCESS_DETACH: {
break;
};
};
return TRUE;
};
#pragma comment(linker, "/export:MyMessageBox=User32.MessageBoxW")

调用程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <windows.h>
#include "resource.h"
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
HMODULE hFunctionForwarder = NULL;
typedef BOOL(WINAPI* pfnMyMessageBox)(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);
pfnMyMessageBox pMyMessageBox = NULL;
switch (uMsg) {
case WM_INITDIALOG: {
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_MYMESSAGEBOX: {
hFunctionForwarder = LoadLibrary(TEXT("FunctionForwarderDll.dll"));
if (hFunctionForwarder) {
pMyMessageBox = (pfnMyMessageBox)GetProcAddress(hFunctionForwarder, "MyMessageBox");
if (pMyMessageBox)
pMyMessageBox(hwndDlg, TEXT("MyMessageBox"), TEXT("提示"), MB_OK);
FreeLibrary(hFunctionForwarder);
};
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};

假如某个程序调用SomeDLL.dll,那么我们利用函数转发搞一个新的SomeDLL.dll,旧的更名为SomeDLLReplace.dll,然后不需要拦截的直接从SomeDLL.dll转发到SomeDLLReplace.dll中。

实例:DrawDll.dll中导出一个绘制矩形和绘制椭圆的函数,然后DrawDll2.dll更名为DrawDll.dll并替换,实际链接新的DrawDllReplace.dll,实施DLL劫持,可执行程序DrawApp对DrawDll.dll调用时矩形函数画椭圆,椭圆函数画矩形。

DrawDll.h:

1
2
3
4
5
6
7
8
9
10
#pragma once
// 声明导出的函数
#ifdef DLL_EXPORT
#define DLL_API extern "C" __declspec(dllexport)
#else
#define DLL_API extern "C" __declspec(dllimport)
#endif
// 导出函数
DLL_API VOID DrawRectangle(HWND hwnd);
DLL_API VOID DrawEllipse(HWND hwnd);

DrawDll.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 定义DLL的导出函数
#include <Windows.h>
#include <tchar.h>
#define DLL_EXPORT
#include "DrawDll.h"
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {};
case DLL_THREAD_ATTACH: {};
case DLL_THREAD_DETACH: {};
case DLL_PROCESS_DETACH: {
break;
};
};
return TRUE;
};
// 导出函数
VOID DrawRectangle(HWND hwnd) {
HDC hdc;
hdc = GetDC(hwnd);
Rectangle(hdc, 10, 10, 110, 110);
ReleaseDC(hwnd, hdc);
};
VOID DrawEllipse(HWND hwnd) {
HDC hdc;
hdc = GetDC(hwnd);
Ellipse(hdc, 10, 10, 110, 110);
ReleaseDC(hwnd, hdc);
};

DrawDll2.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <Windows.h>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {
break;
};
case DLL_THREAD_ATTACH: {};
case DLL_THREAD_DETACH: {};
case DLL_PROCESS_DETACH: {
break;
};
};
return TRUE;
};
#pragma comment(linker, "/export:DrawRectangle=DrawDllReplace.DrawRectangle")
#pragma comment(linker, "/export:DrawEllipse=DrawDllReplace.DrawEllipse")

DrawDllReplace.h:

1
2
3
4
5
6
7
8
9
10
#pragma once
// 声明导出的函数
#ifdef DLL_EXPORT
#define DLL_API extern "C" __declspec(dllexport)
#else
#define DLL_API extern "C" __declspec(dllimport)
#endif
// 导出函数
DLL_API VOID DrawRectangle(HWND hwnd);
DLL_API VOID DrawEllipse(HWND hwnd);

DrawDllReplace.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 定义DLL的导出函数
#include <Windows.h>
#include <tchar.h>
#define DLL_EXPORT
#include "DrawDllReplace.h"
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {};
case DLL_THREAD_ATTACH: {};
case DLL_THREAD_DETACH: {};
case DLL_PROCESS_DETACH: {
break;
};
};
return TRUE;
};
// 导出函数
VOID DrawRectangle(HWND hwnd) {
HDC hdc;
hdc = GetDC(hwnd);
Ellipse(hdc, 10, 10, 110, 110);
ReleaseDC(hwnd, hdc);
};
VOID DrawEllipse(HWND hwnd) {
HDC hdc;
hdc = GetDC(hwnd);
Rectangle(hdc, 10, 10, 110, 110);
ReleaseDC(hwnd, hdc);
};

DrawApp.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <windows.h>
#include "resource.h"
#include "DrawDll.h"
#pragma comment(lib, "DrawDll.lib")
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static BOOL bFirst = TRUE;
static BOOL bRect;
HDC hdc;
PAINTSTRUCT ps;
switch (uMsg) {
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_DRAWRECT: {
bFirst = FALSE;
bRect = TRUE;
InvalidateRect(hwndDlg, NULL, TRUE);
break;
};
case IDC_BTN_DRAWELLIPSE: {
bFirst = FALSE;
bRect = FALSE;
InvalidateRect(hwndDlg, NULL, TRUE);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
case WM_PAINT: {
hdc = BeginPaint(hwndDlg, &ps);
if (!bFirst) {
if (bRect)
DrawRectangle(hwndDlg);
else
DrawEllipse(hwndDlg);
};
EndPaint(hwndDlg, &ps);
return TRUE;
};
};
return FALSE;
};

CreateProcess写ShellCode注入

CreateProcess以挂起模式创建一个子进程,在子进程主线程运行前可项子进程地址空间注入ShellCode并率先获得执行权,ShellCode执行完成再转去执行主线程原代码。

以下代码ShellCode中最后部分需要根据实际更改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include <windows.h>
#include "resource.h"
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
BOOL CreateProcessAndInjectDll();
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_CREATE: {
CreateProcessAndInjectDll();
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};
BOOL CreateProcessAndInjectDll() {
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi = { 0 };
TCHAR szExePath[MAX_PATH] = TEXT("ThreeThousandYears.exe");
TCHAR szDllPath[MAX_PATH] = TEXT("MessageBoxDll.dll");
BOOL bRet;
// 29字节的机器指令和MAX_PATH * sizeof(TCHAR)字节的要注入的dll的名称
BYTE ShellCode[29 + MAX_PATH * sizeof(TCHAR)] = {
0x60, // pushad
0x9C, // pushfd
0x68,0xAA,0xBB,0xCC,0xDD, // push [0xDDCCBBAA](0xDDCCBBAA是目标进程中要注入的dll的名称)
0xFF,0x15,0xDD,0xCC,0xBB,0xAA, // call [0xDDCCBBAA](0xDDCCBBAA是LoadLibraryW函数的地址)
0x9D, // popfd
0x61, // popad
0xFF,0x25,0xAA,0xBB,0xCC,0xDD, // jmp [0xDDCCBBAA](0xDDCCBBAA为目标进程原入口点)
0xAA,0xAA,0xAA,0xAA, // 保存loadlibraryW函数地址的4字节数据区域
0xAA,0xAA,0xAA,0xAA, // 保存目标进程原入口点地址的4字节数据区域
0, // 往后开始就是存放要注入的dll名称的数据区域
};
// 以挂起模式创建一个进程
bRet = CreateProcess(szExePath, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
if (!bRet)
return FALSE;
// 获取目标进程主线程环境(EIP)
CONTEXT context;
context.ContextFlags = CONTEXT_FULL;
if (!GetThreadContext(pi.hThread, &context))
return FALSE;
// 获得LoadLibraryW函数的地址
DWORD dwLoadLibraryWAddr = (DWORD)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "LoadLibraryW");
if (!dwLoadLibraryWAddr)
return FALSE;
// 在目标进程中分配内存,存放ShellCode
LPVOID lpMemoryRemote = VirtualAllocEx(pi.hProcess, NULL, 29 + MAX_PATH * sizeof(TCHAR), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!lpMemoryRemote)
return FALSE;
// push [0xDDCCBBAA](0xDDCCBBAA是目标进程中要注入的dll的名称) 偏移ShellCode + 3
*(PDWORD)(ShellCode + 3) = (DWORD)lpMemoryRemote + 29;
// call [0xDDCCBBAA](0xDDCCBBAA是LoadLibraryW函数的地址) 偏移ShellCode + 9
*(PDWORD)(ShellCode + 9) = (DWORD)lpMemoryRemote + 21;
// jmp [0xDDCCBBAA](0xDDCCBBAA为目标进程原入口点) 偏移ShellCode + 17
*(PDWORD)(ShellCode + 17) = (DWORD)lpMemoryRemote + 25;
// 保存loadlibraryW函数地址的4字节数据区域 偏移ShellCode + 21
*(PDWORD)(ShellCode + 21) = dwLoadLibraryWAddr;
// 保存目标进程原入口点地址的4字节数据区域 偏移ShellCode + 25
*(PDWORD)(ShellCode + 25) = context.Eip;
// 往后开始就是存放要注入的dll名称的数据区域 偏移ShellCode + 29
memcpy_s(ShellCode + 29, MAX_PATH * sizeof(TCHAR), szDllPath, sizeof(szDllPath));
// 把shellcode写入目标进程
if (!WriteProcessMemory(pi.hProcess, lpMemoryRemote, ShellCode, 29 + MAX_PATH * sizeof(TCHAR), NULL))
return FALSE;
// 修改目标进程的EIP,执行被注入的代码
context.Eip = (DWORD)lpMemoryRemote;
if (!SetThreadContext(pi.hThread, &context))
return FALSE;
// 恢复目标进程的执行
ResumeThread(pi.hThread);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return TRUE;
};

APC机制注入

Windows为每个线程维护一个APC队列,类似于远程线程注入,用QueueUserAPC把APC回调函数设为LoadLibraryA/LoadLibraryW地址。因为不知道目标进程中哪些线程处于可通知等待状态,为确保成功需要向目标进程每个线程都注入APC。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#include <windows.h>
#include <tchar.h>
#include <TlHelp32.h>
#include "resource.h"
// 全局变量
HWND g_hwndDlg;
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
DWORD WINAPI ThreadProc(LPVOID lpParameter);
BOOL InjectDll(DWORD dwProcessId, LPTSTR lpDllPath);
BOOL EjectDll(DWORD dwProcessId, LPTSTR lpDllPath);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
HANDLE hThread = NULL;
DWORD dwProcessId;
TCHAR szDllPath[MAX_PATH] = { 0 };
switch (uMsg) {
case WM_INITDIALOG: {
g_hwndDlg = hwndDlg;
SetDlgItemText(hwndDlg, IDC_EDIT_PROCESSID, TEXT("请输入进程ID"));
SetDlgItemText(hwndDlg, IDC_EDIT_DLLPATH, TEXT("F:\\Source\\Windows\\Chapter16\\RemoteDll2\\Debug\\RemoteDll.dll"));
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_INJECT: {
// 创建新线程完成对目标进程中dll的注入
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
if (hThread)
CloseHandle(hThread);
break;
};
case IDC_BTN_EJECT: {
dwProcessId = GetDlgItemInt(hwndDlg, IDC_EDIT_PROCESSID, NULL, FALSE);
GetDlgItemText(hwndDlg, IDC_EDIT_DLLPATH, szDllPath, _countof(szDllPath));
EjectDll(dwProcessId, szDllPath);
break;
};
case IDCANCEL: {
SendMessage(hwndDlg, WM_COMMAND, IDC_BTN_EJECT, 0);
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
DWORD dwProcessId;
TCHAR szDllPath[MAX_PATH] = { 0 };
dwProcessId = GetDlgItemInt(g_hwndDlg, IDC_EDIT_PROCESSID, NULL, FALSE);
GetDlgItemText(g_hwndDlg, IDC_EDIT_DLLPATH, szDllPath, _countof(szDllPath));
return InjectDll(dwProcessId, szDllPath);
};
BOOL InjectDll(DWORD dwProcessId, LPTSTR lpDllPath) {
HANDLE hProcess = NULL;
LPTSTR lpDllPathRemote = NULL;
hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, dwProcessId);
if (!hProcess)
return FALSE;
// (1) 调用VirtualAllocEx函数在远程进程的地址空间中分配一块内存;
SIZE_T cbDllPath = (_tcslen(lpDllPath) + 1) * sizeof(TCHAR);
lpDllPathRemote = (LPTSTR)VirtualAllocEx(hProcess, NULL, cbDllPath, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (!lpDllPathRemote)
return FALSE;
// (2) 调用WriteProcessMemory函数把要注入的dll的路径复制到第1步分配的内存中;
if (!WriteProcessMemory(hProcess, lpDllPathRemote, lpDllPath, cbDllPath, NULL))
return FALSE;
// (3) 调用GetProcAddress函数得到LoadLibraryA / LoadLibraryW函数(Kernel32.dll)的实际地址;
HMODULE hModule = GetModuleHandle(TEXT("Kernel32"));
PAPCFUNC pfnAPC = NULL;
if (hModule != NULL) {
pfnAPC = (PAPCFUNC)GetProcAddress(hModule, "LoadLibraryW");
if (!pfnAPC)
return FALSE;
};
// (4) 遍历目标进程中的线程
HANDLE hSnapshot = INVALID_HANDLE_VALUE;
THREADENTRY32 te = { sizeof(THREADENTRY32) };
BOOL bRet = FALSE;
HANDLE hThread = NULL;
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
return FALSE;
bRet = Thread32First(hSnapshot, &te);
while (bRet) {
if (te.th32OwnerProcessID == dwProcessId) {
hThread = OpenThread(THREAD_SET_CONTEXT, FALSE, te.th32ThreadID);
if (hThread) {
QueueUserAPC(pfnAPC, hThread, (ULONG_PTR)lpDllPathRemote);
// 关闭线程句柄
CloseHandle(hThread);
hThread = NULL;
};
};
bRet = Thread32Next(hSnapshot, &te);
};
CloseHandle(hSnapshot);
//// (5) 调用VirtualFreeEx函数释放第1步分配的内存;
//if (!lpDllPathRemote)
// VirtualFreeEx(hProcess, lpDllPathRemote, 0, MEM_RELEASE);
if (hProcess)
CloseHandle(hProcess);
return TRUE;
};
// 自定义函数EjectDll不保证卸载成功
BOOL EjectDll(DWORD dwProcessId, LPTSTR lpDllPath) {
HANDLE hSnapshot = INVALID_HANDLE_VALUE;
MODULEENTRY32 me = { sizeof(MODULEENTRY32) };
THREADENTRY32 te = { sizeof(THREADENTRY32) };
BOOL bRet = FALSE;
BOOL bFound = FALSE;
// 调用GetProcAddress函数得到FreeLibrary函数(Kernel32.dll)的实际地址
HMODULE hModule = GetModuleHandle(TEXT("Kernel32"));
PAPCFUNC pfnAPC = NULL;
if (hModule != NULL) {
pfnAPC = (PAPCFUNC)GetProcAddress(hModule, "FreeLibrary");
if (!pfnAPC)
return FALSE;
};
// 获取目标进程中RemoteDll.dll的模块句柄
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId);
if (hSnapshot == INVALID_HANDLE_VALUE)
return FALSE;
bRet = Module32First(hSnapshot, &me);
while (bRet) {
if (_tcsicmp(TEXT("RemoteDll.dll"), me.szModule) == 0 || _tcsicmp(lpDllPath, me.szExePath) == 0) {
bFound = TRUE;
break;
};
bRet = Module32Next(hSnapshot, &me);
};
CloseHandle(hSnapshot);
if (!bFound)
return FALSE;
// 遍历目标进程中的线程
hSnapshot = INVALID_HANDLE_VALUE;
bRet = FALSE;
HANDLE hThread = NULL;
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
return FALSE;
bRet = Thread32First(hSnapshot, &te);
while (bRet) {
if (te.th32OwnerProcessID == dwProcessId) {
hThread = OpenThread(THREAD_SET_CONTEXT, FALSE, te.th32ThreadID);
if (hThread) {
QueueUserAPC(pfnAPC, hThread, (ULONG_PTR)(me.modBaseAddr));
// 关闭线程句柄
CloseHandle(hThread);
hThread = NULL;
};
};
bRet = Thread32Next(hSnapshot, &te);
};
CloseHandle(hSnapshot);
return TRUE;
};

输入法机制注入

用户切换输入法时,输入法管理器加载所选择输入法对应的.ime文件到当前活动进程中,这实际上是个DLL。只需在该.ime文件DllMain函数的DLL_PROCESS_ATTACH中调用LoadLibrary加载被注入的DLL即可。

输入法.ime工程必须添加一个版本信息资源Version,其中FILETYPE为VFT_DRV,FILESUBTYPE为VFT2_DRV_INPUTMETHOD,其他信息无关紧要。还要在项目属性的目标文件扩展名改为.ime。

如果编译成32位在64位机器下跑时则要注意文件系统重定向问题,这时需要更改代码中系统目录。

输入法.ime文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <Windows.h>
#include <tchar.h>
#include <strsafe.h>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
static HANDLE hFileMap;
static LPVOID lpMemory;
static TCHAR szInjectDllName[MAX_PATH] = { 0 }; // 注入dll完整路径
static HMODULE hModuleInject; // 注入dll模块句柄
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {
// 打开命名文件映射内核对象,从内存映射文件中获取注入dll的完整路径
hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 4096, TEXT("DEAE59A6-F81B-4DC4-B375-68437206A1A4"));
if (!hFileMap)
return FALSE;
// 把文件映射对象hFileMap的全部映射到进程的虚拟地址空间中
lpMemory = MapViewOfFile(hFileMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
if (!lpMemory)
return FALSE;
// 获取注入dll的完整路径
StringCchCopy(szInjectDllName, _countof(szInjectDllName), (LPTSTR)lpMemory);
// 加载注入dll
hModuleInject = LoadLibrary(szInjectDllName);
break;
};
case DLL_THREAD_ATTACH: {
break;
};
case DLL_THREAD_DETACH: {
break;
};
case DLL_PROCESS_DETACH: {
UnmapViewOfFile(lpMemory);
CloseHandle(hFileMap);
if (hModuleInject)
FreeLibrary(hModuleInject);
break;
};
};
return TRUE;
};

要被注入的.dll:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <Windows.h>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {
MessageBox(NULL, TEXT("我是通过输入法注入的dll"), TEXT("提示"), MB_OK);
break;
};
case DLL_THREAD_ATTACH: {};
case DLL_THREAD_DETACH: {};
case DLL_PROCESS_DETACH: {
break;
};
};
return TRUE;
};

安装、卸载和清理输入法文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#include <windows.h>
#include <tchar.h>
#include <strsafe.h>
#include "resource.h"
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#pragma comment(lib, "Imm32.lib")
// 全局变量
HWND g_hwndDlg;
HKL g_hklDefault; // 原来的默认输入法的键盘布局句柄
HKL g_hklMy; // 自己的输入法的键盘布局句柄
TCHAR g_szKLID[16]; // 自己的输入法的键盘布局句柄的字符串形式
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
VOID InstallMyIME(); // 安装输入法
VOID UninstallMyIME(); // 卸载输入法
VOID ClearMyIME(); // 清理输入法
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static HANDLE hFileMap;
static LPVOID lpMemory;
TCHAR szInjectDllName[MAX_PATH] = { 0 }; // 注入dll完整路径
LPTSTR lpStr = NULL;
switch (uMsg) {
case WM_INITDIALOG: {
g_hwndDlg = hwndDlg;
// 同目录下注入dll的完整路径
GetModuleFileName(NULL, szInjectDllName, _countof(szInjectDllName));
if (lpStr = _tcsrchr(szInjectDllName, TEXT('\\')))
StringCchCopy(lpStr + 1, _tcslen(TEXT("MyIMETestDll.dll")) + 1, TEXT("MyIMETestDll.dll"));
// 创建一个命名文件映射内核对象,4096字节,用于存放注入dll的完整路径
hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 4096, TEXT("DEAE59A6-F81B-4DC4-B375-68437206A1A4"));
if (!hFileMap) {
MessageBox(hwndDlg, TEXT("CreateFileMapping调用失败"), TEXT("提示"), MB_OK);
return TRUE;
};
// 把文件映射对象hFileMap的全部映射到进程的虚拟地址空间中
lpMemory = MapViewOfFile(hFileMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
if (!lpMemory) {
MessageBox(hwndDlg, TEXT("MapViewOfFile调用失败"), TEXT("提示"), MB_OK);
return TRUE;
};
// 复制注入dll的完整路径到内存映射文件
StringCchCopy((LPTSTR)lpMemory, MAX_PATH, szInjectDllName);
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_INJECT: {
InstallMyIME();
break;
};
case IDC_BTN_EJECT: {
UninstallMyIME();
break;
};
case IDC_BTN_CLEAR: {
ClearMyIME();
break;
};
};
return TRUE;
};
case WM_CLOSE: {
UnmapViewOfFile(lpMemory);
CloseHandle(hFileMap);
UninstallMyIME();
ClearMyIME();
EndDialog(hwndDlg, 0);
return TRUE;
};
};
return FALSE;
};
VOID InstallMyIME() {
// 复制.ime文件到系统目录中
CopyFile(TEXT("MyIME.ime"), TEXT("C:\\WINDOWS\\system32\\MyIME.ime"), FALSE);
// 获取当前默认输入法的键盘布局句柄(句柄包括语言ID和物理布局ID),通过全局变量g_hklDefault返回
SystemParametersInfo(SPI_GETDEFAULTINPUTLANG, 0, &g_hklDefault, FALSE);
// 安装自己的输入法(我这里返回值为0xE0200804)
g_hklMy = ImmInstallIME(TEXT("MyIME.ime"), TEXT("我的输入法"));
StringCchPrintf(g_szKLID, _countof(g_szKLID), TEXT("%08X"), (DWORD)g_hklMy);
// 如果自己的输入法安装成功
if (ImmIsIME(g_hklMy)) {
// 加载自己的输入法到系统中
LoadKeyboardLayout(g_szKLID, KLF_ACTIVATE);
// LoadKeyboardLayout在调用进程没有具有键盘焦点的窗口时调用失败 保险起见投递一条WM_INPUTLANGCHANGEREQUEST消息到前台窗口(模拟用户选择新的输入法)
PostMessage(GetForegroundWindow(), WM_INPUTLANGCHANGEREQUEST, INPUTLANGCHANGE_SYSCHARSET, (LPARAM)g_hklMy);
// 设置为默认输入法
SystemParametersInfo(SPI_SETDEFAULTINPUTLANG, 0, &g_hklMy, SPIF_SENDCHANGE);
MessageBox(g_hwndDlg, TEXT("我的输入法已经设置为默认输入法"), TEXT("提示"), MB_OK);
};
return;
};
VOID UninstallMyIME() {
// 先设置回原来的默认输入法
SystemParametersInfo(SPI_SETDEFAULTINPUTLANG, 0, &g_hklDefault, SPIF_SENDCHANGE);
// 卸载自己的输入法
if (UnloadKeyboardLayout(g_hklMy))
MessageBox(g_hwndDlg, TEXT("我的输入法已经卸载成功"), TEXT("提示"), MB_OK);
return;
};
VOID ClearMyIME() {
// 删除HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts\E0200804子键
TCHAR szSubKey[MAX_PATH] = TEXT("SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\");
StringCchCat(szSubKey, _countof(szSubKey), g_szKLID);
RegDeleteKey(HKEY_LOCAL_MACHINE, szSubKey);
// 删除HKEY_CURRENT_USER\Keyboard Layout\Preload下面键值为"E0200804"的键值项
HKEY hKey;
LPCTSTR lpSubKey = TEXT("Keyboard Layout\\Preload");
DWORD dwIndex = 0;
TCHAR szValueName[16] = { 0 };
DWORD dwchValueName;
TCHAR szValueData[MAX_PATH] = { 0 };
DWORD dwcbValueData;
LONG lRet;
RegOpenKeyEx(HKEY_CURRENT_USER, lpSubKey, 0, KEY_READ | KEY_WRITE, &hKey);
while (TRUE) {
dwchValueName = _countof(szValueName);
dwcbValueData = sizeof(szValueData);
lRet = RegEnumValue(hKey, dwIndex, szValueName, &dwchValueName, NULL, NULL, (LPBYTE)szValueData, &dwcbValueData);
if (lRet == ERROR_NO_MORE_ITEMS)
break;
if (_tcsicmp(g_szKLID, szValueData) == 0)
RegDeleteValue(hKey, szValueName);
dwIndex++;
};
// 删除输入法文件
if (!DeleteFile(TEXT("C:\\WINDOWS\\system32\\MyIME.ime"))) {
// 下次重新启动系统以后删除
MoveFileEx(TEXT("C:\\WINDOWS\\system32\\MyIME.ime"), NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
MessageBox(g_hwndDlg, TEXT("我的输入法已清理完毕,重启后删除.ime文件"), TEXT("提示"), MB_OK);
}
else
MessageBox(g_hwndDlg, TEXT("我的输入法已清理完毕"), TEXT("提示"), MB_OK);
return;
};

Shadow API

Shadow API能过调试器对API下的断点。例如要过MessageBox时,Win10中该API的实现在USER32.DLL中,内容为直接调用user32!MessageBoxTimeoutW。选择用VirtualAlloc申请一块内存空间,把MessageBoxW的代码复制到其中,然后调用时直接调用该内存空间。但是其中汇编call指令二进制码需要进行修改,二进制下4字节为MessageBoxTimeoutW函数地址减call MessageBoxTimeoutW下一条指令地址,这里要修改的偏移为22。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <windows.h>
#include "resource.h"
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
typedef int (WINAPI* pfnMessageBoxW)(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType);
pfnMessageBoxW pMessageBoxW = NULL;
static pfnMessageBoxW pMessageBoxWNew = NULL; // 分配内存空间存放MessageBoxW函数机器码
BYTE bArr[30] = { 0 }; // 存放MessageBoxW函数实现代码的缓冲区
LPBYTE pMessageBoxTimeoutW = NULL; // MessageBoxTimeoutW函数的地址
DWORD dwReplace; // MessageBoxTimeoutW函数的相对地址
switch (uMsg) {
case WM_INITDIALOG: {
// 获取MessageBoxW函数的地址,并读取函数数据
pMessageBoxW = (pfnMessageBoxW)GetProcAddress(GetModuleHandle(TEXT("User32.dll")), "MessageBoxW");
ReadProcessMemory(GetCurrentProcess(), pMessageBoxW, bArr, sizeof(bArr), NULL);
// 分配内存空间存放MessageBoxW函数,可读可写可执行
pMessageBoxWNew = (pfnMessageBoxW)VirtualAlloc(NULL, sizeof(bArr), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(GetCurrentProcess(), pMessageBoxWNew, bArr, sizeof(bArr), NULL);
// 获取MessageBoxTimeoutW函数的地址
pMessageBoxTimeoutW = (LPBYTE)GetProcAddress(GetModuleHandle(TEXT("User32.dll")), "MessageBoxTimeoutW");
// 计算并修改相对地址,就是pMessageBoxWNew函数偏移22的DWORD数据
dwReplace = pMessageBoxTimeoutW - (LPBYTE)pMessageBoxWNew - 26;
WriteProcessMemory(GetCurrentProcess(), (LPBYTE)pMessageBoxWNew + 22, &dwReplace, 4, NULL);
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_OK: {
// 如果在调试器中对MessageBoxW函数下了int3断点,有可能第一个字节被修改为0xCC
if (*(LPBYTE)pMessageBoxWNew == 0xCC)
*(LPBYTE)pMessageBoxWNew = 0x8B;
pMessageBoxWNew(hwndDlg, TEXT("内容"), TEXT("标题"), MB_OK);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};

综合实战:API HOOK

下面讲的只适用于32位下,

例如一个模仿视频移动水印的程序,有一个图像静态控件,每隔2秒用ExtTextOut把蓝色水印位置变一下,每隔5秒用DrawText把红色水印位置变一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <windows.h>
#include <tchar.h>
#include <time.h>
#include "resource.h"
#pragma comment(lib, "Winmm.lib")
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
HDC hdc;
HFONT hFont, hFontOld;
TCHAR szTextBlue[] = TEXT("用户名:老王"); // 蓝色水印
TCHAR szTextRed[] = TEXT("Powered By 老王"); // 红色水印
static SIZE sizeBlue, sizeRed; // 蓝色、红色水印字符串的宽度高度
static RECT rcBlue, rcRed; // 要绘制的蓝色、红色水印的开始位置范围
static RECT rcExtTextOut = { 0 }; // 保存ExtTextOut函数的上次绘制区域
static RECT rcDrawText = { 0 }; // 保存DrawText函数的上次绘制区域
RECT rcClient;
int x, y;
switch (uMsg) {
case WM_INITDIALOG: {
PlaySound(TEXT("三生石上刻相思.wav"), NULL, SND_FILENAME | SND_ASYNC | SND_LOOP);
// 蓝色、红色水印字符串的宽度高度
hdc = GetDC(hwndDlg);
hFont = CreateFont(12, 0, 0, 0, 0, 0, 0, 0, GB2312_CHARSET, 0, 0, 0, 0, TEXT("宋体"));
hFontOld = (HFONT)SelectObject(hdc, hFont);
GetTextExtentPoint32(hdc, szTextBlue, _tcslen(szTextBlue), &sizeBlue);
GetTextExtentPoint32(hdc, szTextRed, _tcslen(szTextRed), &sizeRed);
SelectObject(hdc, hFontOld);
ReleaseDC(hwndDlg, hdc);
// 要绘制的蓝色、红色水印的矩形范围
GetClientRect(hwndDlg, &rcClient);
SetRect(&rcBlue, 0, 0, rcClient.right - sizeBlue.cx, rcClient.bottom - sizeBlue.cy);
SetRect(&rcRed, 0, 0, rcClient.right - sizeRed.cx, rcClient.bottom - sizeRed.cy);
// 创建两个计时器,分别显示蓝色、红色水印
SetTimer(hwndDlg, 1, 2000, NULL); // 蓝色水印,2秒触发一次
SetTimer(hwndDlg, 2, 5000, NULL); // 红色水印,5秒触发一次
return TRUE;
};
case WM_TIMER: {
hdc = GetDC(hwndDlg);
SetBkMode(hdc, TRANSPARENT);
hFont = CreateFont(12, 0, 0, 0, 0, 0, 0, 0, GB2312_CHARSET, 0, 0, 0, 0, TEXT("宋体"));
hFontOld = (HFONT)SelectObject(hdc, hFont);
switch (wParam) {
case 1: {
// 处理蓝色水印,2秒触发一次,要绘制的字符串的开始位置随机
InvalidateRect(hwndDlg, &rcExtTextOut, TRUE); // 擦除上次的文字
SetTextColor(hdc, RGB(0, 0, 255));
srand((UINT)time(NULL));
x = rand() % (rcBlue.right + 1);
y = rand() % (rcBlue.bottom + 1);
SetRect(&rcExtTextOut, x, y, x + sizeBlue.cx, y + sizeBlue.cy);
ExtTextOut(hdc, x, y, 0, NULL, szTextBlue, _tcslen(szTextBlue), NULL);
break;
};
case 2: {
// 处理红色水印,5秒触发一次,要绘制的字符串的开始位置随机
InvalidateRect(hwndDlg, &rcDrawText, TRUE); // 擦除上次的文字
SetTextColor(hdc, RGB(255, 0, 0));
srand(GetTickCount());
x = rand() % (rcRed.right + 1);
y = rand() % (rcRed.bottom + 1);
SetRect(&rcDrawText, x, y, x + sizeRed.cx, y + sizeRed.cy);
DrawText(hdc, szTextRed, _tcslen(szTextRed), &rcDrawText, DT_SINGLELINE);
break;
};
};
SelectObject(hdc, hFontOld);
DeleteObject(hFont);
ReleaseDC(hwndDlg, hdc);
return TRUE;
};
case WM_SIZE: {
SetRect(&rcBlue, 0, 0, LOWORD(lParam) - sizeBlue.cx, HIWORD(lParam) - sizeBlue.cy);
SetRect(&rcRed, 0, 0, LOWORD(lParam) - sizeRed.cx, HIWORD(lParam) - sizeRed.cy);
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};

选择用远程线程注入方式注入一个DLL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <windows.h>
#include <tchar.h>
#include "resource.h"
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
BOOL InjectDll();
BOOL EjectDll();
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_INJECT: {
InjectDll();
break;
};
case IDC_BTN_EJECT: {
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
}
return TRUE;
};
};
return FALSE;
};
BOOL InjectDll() {
TCHAR szCommandLine[MAX_PATH] = TEXT("FloatingWaterMark.exe");
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi = { 0 };
//LPCTSTR lpDllPath = TEXT("..\\..\\FWMDll\\Release\\FWMDll.dll");
LPCTSTR lpDllPath = TEXT("..\\..\\FWMDll\\Release\\FWMDll_修改.dll");
//LPCTSTR lpDllPath = TEXT("..\\..\\FWMDll2\\Release\\FWMDll.dll");
//LPCTSTR lpDllPath = TEXT("..\\..\\..\\Detours\\InjectDll\\Release\\InjectDll.dll");
LPTSTR lpDllPathRemote = NULL;
HANDLE hThreadRemote = NULL;
GetStartupInfo(&si);
CreateProcess(NULL, szCommandLine, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
// (1) 调用VirtualAllocEx函数在远程进程的地址空间中分配一块内存;
int cbDllPath = (_tcslen(lpDllPath) + 1) * sizeof(TCHAR);
lpDllPathRemote = (LPTSTR)VirtualAllocEx(pi.hProcess, NULL, cbDllPath, MEM_COMMIT, PAGE_READWRITE);
if (!lpDllPathRemote)
return FALSE;
// (2) 调用WriteProcessMemory函数把要注入的dll的路径复制到第1步分配的内存中;
if (!WriteProcessMemory(pi.hProcess, lpDllPathRemote, lpDllPath, cbDllPath, NULL))
return FALSE;
// (3) 调用GetProcAddress函数得到LoadLibraryA / LoadLibraryW函数(Kernel32.dll)的实际地址;
PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
if (!pfnThreadRtn)
return FALSE;
// (4) 调用CreateRemoteThread函数在远程进程中创建一个线程
hThreadRemote = CreateRemoteThread(pi.hProcess, NULL, 0, pfnThreadRtn, lpDllPathRemote, 0, NULL);
if (!hThreadRemote)
return FALSE;
WaitForSingleObject(hThreadRemote, INFINITE);
ResumeThread(pi.hThread);
// (5) 调用VirtualFreeEx函数释放第1步分配的内存;
if (!lpDllPathRemote)
VirtualFreeEx(pi.hProcess, lpDllPathRemote, 0, MEM_RELEASE);
if (!pi.hThread)
CloseHandle(pi.hThread);
if (!pi.hProcess)
CloseHandle(pi.hProcess);
return TRUE;
};
BOOL EjectDll() {
return TRUE;
};

被注入的DLL,选择修改该API开头为call,跳转到自定义函数中,这里不需要导出函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <Windows.h>
#include <tchar.h>
// 全局变量
LPBYTE pExtTextOutW;
TCHAR szText1[] = TEXT("屏幕");
TCHAR szText2[] = TEXT("用户名");
TCHAR szText3[] = TEXT("购买者");
TCHAR szTextReplace[] = TEXT(" ");
LPTSTR lpStr;
VOID InterceptExtTextOutW(LPTSTR lpText);
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
BYTE bExtTextOutWCall[] = { 0xFF, 0x74, 0x24, 0x18, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x90, 0x90, 0x90, 0x90 };
DWORD dwOldProtect;
MEMORY_BASIC_INFORMATION mbi = { 0 };
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {
// 获取ExtTextOutW函数的地址
pExtTextOutW = (LPBYTE)GetProcAddress(GetModuleHandle(TEXT("Gdi32.dll")), "ExtTextOutW");
*(LPINT)(bExtTextOutWCall + 5) = (INT)InterceptExtTextOutW - (INT)pExtTextOutW - 0x9;
// 把ExtTextOutW函数起始处改为Call
VirtualQuery(pExtTextOutW, &mbi, sizeof(mbi));
VirtualProtect(pExtTextOutW, 512, PAGE_EXECUTE_READWRITE, &dwOldProtect);
WriteProcessMemory(GetCurrentProcess(), pExtTextOutW, bExtTextOutWCall, sizeof(bExtTextOutWCall), NULL);
VirtualProtect(pExtTextOutW, 512, mbi.Protect, &dwOldProtect);
break;
};
case DLL_THREAD_ATTACH: {};
case DLL_THREAD_DETACH: {};
case DLL_PROCESS_DETACH: {
break;
};
};
return TRUE;
};
//_declspec(naked)表示不对函数做栈处理 如函数开头初始化(ebp作指针、开辟局部变量控件、保存某些寄存器值)和函数尾部(平衡栈、恢复保存寄存器、ret)
_declspec(naked) VOID InterceptExtTextOutW(LPTSTR lpText) {
_asm {
// 大多数函数开头就是这个样子
push ebp
mov ebp, esp
sub esp, 0x10
push ebx
push esi
push edi
// 额外保存ecx和edx,eax和esp暂时不需要关心
push ecx
push edx
};
// 下面的C++代码中可能会有一些push指令,但是没关系,编译器会自动恢复esp的值,我们无需关心
if ((lpStr = _tcsstr(lpText, szText1)) || (lpStr = _tcsstr(lpText, szText2)) || (lpStr = _tcsstr(lpText, szText3)))
memcpy(lpStr, szTextReplace, _tcslen(lpStr) * sizeof(TCHAR));
_asm {
// 恢复edx和ecx
pop edx
pop ecx
// 大多数函数结尾都是这个样子
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
// 修改ExtTextOutW函数的时候,有一个push和call,导致esp减了8个字节
add esp, 8
// 已经恢复各通用寄存器和堆栈空间布局,开始执行原ExtTextOutW函数开头的一些指令行
mov edi, edi
push ebp
mov ebp, esp
push ecx
mov dword ptr[ebp - 0x4], 0x49414E
// 跳转到我们修改过的指令的下一条指令行继续执行,就是ExtTextOutW + 0xD地址处
mov eax, pExtTextOutW
add eax, 0xD
jmp eax
};
};