首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
逆向工程
发新帖
5
2
[原创]《逆向工程核心原理》书中代码注入实验在64位Windows11上的复现与思考
发表于: 2026-3-18 16:48
2728
[原创]《逆向工程核心原理》书中代码注入实验在64位Windows11上的复现与思考
mb_nnqgphwf
2026-3-18 16:48
2728
# 《逆向工程核心原理》书中代码注入实验在64位Windows11上的复现与思考 书中的实验是在32位Windows7上演示的,我在64位Windows11中复现了书中的实验。在复现的过程中遇到了不少问题,故写一篇博客记录下来。由于笔者水平有限,文中难免有错误,请广大读者斧正。 ## 代码注入原理 和使用远程线程进行DLL注入很像,但是代码注入更有隐蔽性和灵活性。主要的思路如下: 1. 编写注入代码 2. 构造注入代码的参数 3. 在目标进程中分配两块内存空间(VirtualAllocEx) 4. 拷贝注入代码与参数至目标进行中(WriteProcessMemory) 5. 调用CreateRemoteThread执行注入的代码(注入程序需要具有特定权限,可以使用OpenProcessToken等函数进行提权) ## 原作者示例代码与分析 下面的代码是原作者的代码: ```cpp // CodeInjection.cpp // reversecore@gmail.com // http://www.reversecore.com #include "stdio.h" #include "windows.h" typedef struct _THREAD_PARAM { FARPROC pFunc[2]; // LoadLibraryA(), GetProcAddress() char szBuf[4][128]; // "user32.dll", "MessageBoxA", "www.reversecore.com", "ReverseCore" } THREAD_PARAM, *PTHREAD_PARAM; typedef HMODULE(WINAPI *PFLOADLIBRARYA)(LPCSTR lpLibFileName); typedef FARPROC(WINAPI *PFGETPROCADDRESS)(HMODULE hModule, LPCSTR lpProcName); typedef int(WINAPI *PFMESSAGEBOXA)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType); DWORD WINAPI ThreadProc(LPVOID lParam) { PTHREAD_PARAM pParam = (PTHREAD_PARAM)lParam; HMODULE hMod = NULL; FARPROC pFunc = NULL; // LoadLibrary() hMod = ((PFLOADLIBRARYA)pParam->pFunc[0])(pParam->szBuf[0]); // "user32.dll" if (!hMod) return 1; // GetProcAddress() pFunc = (FARPROC)((PFGETPROCADDRESS)pParam->pFunc[1])(hMod, pParam->szBuf[1]); // "MessageBoxA" if (!pFunc) return 1; // MessageBoxA() ((PFMESSAGEBOXA)pFunc)(NULL, pParam->szBuf[2], pParam->szBuf[3], MB_OK); return 0; } BOOL InjectCode(DWORD dwPID) { HMODULE hMod = NULL; THREAD_PARAM param = { 0, }; HANDLE hProcess = NULL; HANDLE hThread = NULL; LPVOID pRemoteBuf[2] = { 0, }; DWORD dwSize = 0; hMod = GetModuleHandleA("kernel32.dll"); // set THREAD_PARAM param.pFunc[0] = GetProcAddress(hMod, "LoadLibraryA"); param.pFunc[1] = GetProcAddress(hMod, "GetProcAddress"); strcpy_s(param.szBuf[0], "user32.dll"); strcpy_s(param.szBuf[1], "MessageBoxA"); strcpy_s(param.szBuf[2], "www.reversecore.com"); strcpy_s(param.szBuf[3], "ReverseCore"); // Open Process if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, // dwDesiredAccess FALSE, // bInheritHandle dwPID))) // dwProcessId { printf("OpenProcess() fail : err_code = %d\n", GetLastError()); return FALSE; } // Allocation for THREAD_PARAM dwSize = (DWORD)InjectCode - (DWORD)ThreadProc; if (!(pRemoteBuf[0] = VirtualAllocEx(hProcess, // hProcess NULL, // lpAddress dwSize, // dwSize MEM_COMMIT, // flAllocationType PAGE_READWRITE))) // flProtect { printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError()); return FALSE; } if (!WriteProcessMemory(hProcess, // hProcess pRemoteBuf[0], // lpBaseAddress (LPVOID)¶m, // lpBuffer dwSize, // nSize NULL)) // [out] lpNumberOfBytesWritten { printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError()); return FALSE; } // Allocation for ThreadProc() dwSize = (DWORD)InjectCode - (DWORD)ThreadProc; if (!(pRemoteBuf[1] = VirtualAllocEx(hProcess, // hProcess NULL, // lpAddress dwSize, // dwSize MEM_COMMIT, // flAllocationType PAGE_EXECUTE_READWRITE))) // flProtect { printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError()); return FALSE; } if (!WriteProcessMemory(hProcess, // hProcess pRemoteBuf[1], // lpBaseAddress (LPVOID)ThreadProc, // lpBuffer dwSize, // nSize NULL)) // [out] lpNumberOfBytesWritten { printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError()); return FALSE; } if (!(hThread = CreateRemoteThread(hProcess, // hProcess NULL, // lpThreadAttributes 0, // dwStackSize (LPTHREAD_START_ROUTINE)pRemoteBuf[1], // dwStackSize pRemoteBuf[0], // lpParameter 0, // dwCreationFlags NULL))) // lpThreadId { printf("CreateRemoteThread() fail : err_code = %d\n", GetLastError()); return FALSE; } WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); CloseHandle(hProcess); return TRUE; } BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) { TOKEN_PRIVILEGES tp; HANDLE hToken; LUID luid; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) { printf("OpenProcessToken error: %u\n", GetLastError()); return FALSE; } if (!LookupPrivilegeValue(NULL, // lookup privilege on local system lpszPrivilege, // privilege to lookup &luid)) // receives LUID of privilege { printf("LookupPrivilegeValue error: %u\n", GetLastError()); return FALSE; } tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; if (bEnablePrivilege) tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; else tp.Privileges[0].Attributes = 0; // Enable the privilege or disable all privileges. if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) { printf("AdjustTokenPrivileges error: %u\n", GetLastError()); return FALSE; } if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) { printf("The token does not have the specified privilege. \n"); return FALSE; } return TRUE; } int main(int argc, char *argv[]) { DWORD dwPID = 0; if (argc != 2) { printf("\n USAGE : %s <pid>\n", argv[0]); return 1; } // change privilege if (!SetPrivilege(SE_DEBUG_NAME, TRUE)) return 1; // code injection dwPID = (DWORD)atol(argv[1]); InjectCode(dwPID); return 0; } ``` 代码不多,先是定义了注入代码需要用到的参数结构体与一些函数指针。然后对注入程序进行提权,执行注入函数。这里需要分析一下如何在远程进程中调用DLL中的函数。 **原作者的代码基于一个重要的事实:Windows上的程序一般都会载入kernel32.dll并且加载基址相同**。这使得我们可以在自己的进程中使用GetProcAddress函数获取kernel32.dll中导出函数的地址(绝对地址),把这个地址放在远程进程中,也是正确的函数起始地址。这样我们就可以在自己的进程中编写使用kernel32.dll中的函数的代码,并将这些代码拷贝到目标进程中执行。 **注意,在原作者的代码中,对于ThreadProc函数大小的计算是dwSize = InjectCode - ThreadProc,这很重要,原作者假定InjectCode函数的起始地址在ThreadProc函数之后,事实上在原作者的环境中也确实如此(32位Windows7)。但是在64位的Windows11中也这样吗?请接着往下看笔者的代码。** ## 笔者的代码与分析 下面是我的代码,基于原作者的代码修改而来,主要的修改为: 1. 硬编码调用UNICODE版本的函数 2. **在计算ThreadProc大小的语句中,笔者的计算方式和原作者是相反的,即:dwSize = ThreadProc - InjectCode** ThreadProc大小的计算方式之所以与原作者相反,是因为笔者的调试注入程序时发现:**ThreadProc的起始地址是大于InjectCode的。**也许读者以为函数的起始地址排列方式是与它们在源文件中的定义顺序一致的,但是在64位Windows11中并非如此,后面会详细分析这一问题。先看笔者的源代码: ```cpp #include "Windows.h" #include "iostream" // 定义线程过程ThreadProc的参数结构体 struct THREAD_PARAM { FARPROC pFunc[2]; // LoadLibrary, GetProcAddress TCHAR szDllName[32]; // L"user32" char szProcName[32]; // "MessageBoxW" TCHAR szMsgArgs[2][32]; }; typedef HMODULE(WINAPI *PFLOADLIBRARYW)(LPCWSTR lpLibFileName); // LoadLibraryW typedef FARPROC(WINAPI *PFGETPROCADDRESS)(HMODULE hModule, LPCSTR lpProcName); // GetProcAddress typedef int(WINAPI *PFMESSAGEBOXW)(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType); // 需要注入的代码 DWORD WINAPI ThreadProc(LPVOID lParam) { THREAD_PARAM *pParam = (THREAD_PARAM *)lParam; HMODULE hMod = NULL; FARPROC pFunc = NULL; // LoadLibrary() hMod = ((PFLOADLIBRARYW)pParam->pFunc[0])(pParam->szDllName); // "user32.dll" if (!hMod) return 1; // GetProcAddress() pFunc = (FARPROC)((PFGETPROCADDRESS)pParam->pFunc[1])(hMod, pParam->szProcName); // "MessageBoxW" if (!pFunc) return 1; // MessageBox() ((PFMESSAGEBOXW)pFunc)(NULL, pParam->szMsgArgs[0], pParam->szMsgArgs[1], MB_OK); return 0; } DWORD WINAPI ThreadProcEnd() { return 0; } BOOL InjectCode(DWORD dwPID) { //DbgFunc(); HMODULE hMod = NULL; THREAD_PARAM param = {0}; HANDLE hProcess = NULL; HANDLE hThread = NULL; LPVOID pRemoteBuf[2] = {0}; DWORD dwSize = 0; hMod = GetModuleHandleW(L"kernel32.dll"); // set THREAD_PARAM param.pFunc[0] = GetProcAddress(hMod, "LoadLibraryW"); param.pFunc[1] = GetProcAddress(hMod, "GetProcAddress"); wcscpy_s(param.szDllName, L"user32.dll"); strcpy_s(param.szProcName, "MessageBoxW"); wcscpy_s(param.szMsgArgs[0], L"www.reversecore.com"); wcscpy_s(param.szMsgArgs[1], L"ReverseCore"); if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID))) { printf("OpenProcess() fail : err_code = %d\n", GetLastError()); return FALSE; } // 在需要注入的进程中分配内存,这块内存就是ThreadFunc的参数所在之处 dwSize = sizeof(THREAD_PARAM); if (!(pRemoteBuf[0] = VirtualAllocEx(hProcess, // hProcess NULL, // lpAddress dwSize, // dwSize MEM_COMMIT, // flAllocationType PAGE_READWRITE))) // flProtect { printf("VirtualAllocEx() param fail : err_code = %d\n", GetLastError()); return FALSE; } // 将param写入远程进程的地址空间中 if (!WriteProcessMemory(hProcess, // hProcess pRemoteBuf[0], // lpBaseAddress (LPVOID)¶m, // lpBuffer dwSize, // nSize NULL)) // [out] lpNumberOfBytesWritten { printf("WriteProcessMemory() param fail : err_code = %d\n", GetLastError()); return FALSE; } // 分配ThreadProc的空间 ======== 这里有点坑 dwSize = (DWORD)ThreadProc - (DWORD)InjectCode; // dwSize = (DWORD)ThreadProcEnd - (DWORD)ThreadProc; std::cout << dwSize << std::endl; if (!(pRemoteBuf[1] = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE))) { printf("VirtualAllocEx() ThreadProc fail : err_code = %d\n", GetLastError()); return FALSE; } if (!WriteProcessMemory(hProcess, // hProcess pRemoteBuf[1], // lpBaseAddress (LPVOID)ThreadProc, // lpBuffer dwSize, // nSize NULL)) // [out] lpNumberOfBytesWritten { printf("WriteProcessMemory() ThreadProc fail : err_code = %d\n", GetLastError()); return FALSE; } std::cout << "lParam: " << pRemoteBuf[0] << std::endl; std::cout << "ThreadProc: " << pRemoteBuf[1] << std::endl; // 开始注入代码 if (!(hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pRemoteBuf[1], pRemoteBuf[0], 0, NULL))) { printf("CreateRemoteThread() fail : err_code = %d\n", GetLastError()); return FALSE; } std::cout << "Injected !!" << std::endl; WaitForSingleObject(hThread, INFINITE); std::cout << "Done!!" << std::endl; CloseHandle(hThread); CloseHandle(hProcess); return TRUE; } // 打开或者关闭当前进程的特定权限 // 1. 字符串形式的特权名称 // 2. 开启或者关闭 BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) { TOKEN_PRIVILEGES tp; HANDLE hToken; LUID luid; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) { std::cout << "OpenProcessToken error: " << GetLastError() << std::endl; return FALSE; } // 查找特权LUID:将lpszPrivilege转换位luid if (!LookupPrivilegeValue(NULL, lpszPrivilege, &luid)) { std::cout << "LookupPrivilegeValue error: " << GetLastError() << std::endl; return FALSE; } tp.PrivilegeCount = 1; // 只修改一个特权 tp.Privileges[0].Luid = luid; // 设置需要修改的特权LUID if (bEnablePrivilege) // 判断是开启还是关闭特权 { tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; } else { tp.Privileges[0].Attributes = 0; } // 调整令牌的特权 if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) { std::cout << "AjustTokenPrivileges error: " << GetLastError() << std::endl; return FALSE; } if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) { std::cout << "The token does not have the specified privilege. \n"; return FALSE; } return TRUE; } int main(int argc, char const *argv[]) { DWORD dwPID = 0; if (argc != 2) { std::cout << "\n USAGE : " << argv[0] << "<pid>" << std::endl; return 1; } // change privilege if (!SetPrivilege(SE_DEBUG_NAME, TRUE)) return 1; // code injection dwPID = (DWORD)atol(argv[1]); InjectCode(dwPID); return 0; } ``` **注意,在编译的时候,请使用Release编译选项,如果使用Debug编译选项,得到的注入代码在目标进程中执行的时候会报内存违规访问错误,这个问题后面也会详细分析。** ## 在64位Windows11中的复现 **注入程序需要以管理员身份运行** 这里展示注入成功的截图: 首先查看notepad的PID:  运行注入程序(注意此处使用的是Release版本的):  注入成功:  ## 复现中遇到的问题 上面提到两个问题: 1. **ThreadProc大小计算方式问题:笔者的计算方式和原作者的计算方式相反** 2. **编译选项问题:只有使用Release编译选项编译的注入程序才可以注入成功,使用Debug版本的则不行** ### 第一个问题 这个问题的发现源于代码中WriteProcessMemory返回的错误代码:299,这表示只写入了一部分数据,为什么只写入一部分呢?我怀疑是写入的东西太多了,于是在代码中添加了输出dwSize的代码,发现是一个FFFF开头的数,由于DWORD是unsigned long的类型别名(FFFF开头的dwSize被解释为无符号数的时候是一个很大的数字),说明dwSize是一个负数,所以我调整了计算方式,使计算方式和原作者的相反。 这样确实得到了正确的大小,为什么呢?难道函数的起始地址和函数在源文件中的声明顺序不同?下图是计算dwSize的反汇编代码:  **对应源代码,我们发现,rax是ThreadProc的首地址,rcx是InjecCode的首地址,明显的,rax > rcx。在源文件中ThreadProc的定义是在InjectCode之上的。** 这是在64位Windows11上的分析结果,如果是原作者的程序呢?  经过验证,原作者的及算方式也是正确的。 **在不同的平台上(包括使用不同的编译选项),函数的起始地址并没有绝对的顺序。但具体的规则是什么,我也不清楚。希望大佬解答一下。** 在我的平台上,我怀疑编译器对ThreadProc函数进行了内联?貌似只有这样解释得通。本人很菜,如果有知道的希望在评论区留言,谢谢~ ### 第二个问题 对于这个问题,我首先想到的是在Debug编译选项下编译器向可执行程序中添加了一些调试信息。下面的代码是我用于生成注入程序的CMakeLists.txt: ```txt cmake_minimum_required(VERSION 3.15) project(CodeInjection LANGUAGES CXX) # 设置C++标准 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) file(GLOB SOURCES "src/*.cpp") # 可执行文件 add_executable(${PROJECT_NAME} ${SOURCES}) # 目标链接库 target_link_libraries(${PROJECT_NAME} PRIVATE kernel32.lib user32.lib advapi32.lib ) # Windows通用定义 target_compile_definitions(${PROJECT_NAME} PRIVATE UNICODE _UNICODE _WIN32_WINNT=0x0A00 ) # 通用编译选项 target_compile_options(${PROJECT_NAME} PRIVATE /W4 /EHsc ) # Debug配置特定的编译选项 target_compile_options(${PROJECT_NAME} PRIVATE "$<$<CONFIG:Debug>:/MDd>" "$<$<CONFIG:Debug>:/Od>" "$<$<CONFIG:Debug>:/Zi>" "$<$<CONFIG:Debug>:/D_DEBUG>" ) # Release配置特定的编译选项 target_compile_options(${PROJECT_NAME} PRIVATE "$<$<CONFIG:Release>:/MD>" "$<$<CONFIG:Release>:/O2>" "$<$<CONFIG:Release>:/DNDEBUG>" ) # 链接选项 target_link_options(${PROJECT_NAME} PRIVATE /SUBSYSTEM:CONSOLE "$<$<CONFIG:Debug>:/DEBUG>" "$<$<CONFIG:Release>:/OPT:REF>" "$<$<CONFIG:Release>:/OPT:ICF>" ) # 设置输出目录(直接放在build下) set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/Debug" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/Release" WIN32_EXECUTABLE TRUE ) ``` 使用Debug版的注入程序,CreateRemoteThread是可以正确返回的,但是注入的代码在执行的时候会报一个**内存违规访问的错误。**我使用windbg调试,忘记截图了,错误代码是:Access Violation - Code c0000005。发生错误的地址位于注入代码的那段地址空间中。 ## 总结 纸上得来终觉浅,绝知此事要躬行。
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
#调试逆向
#问题讨论
收藏
・
5
点赞
・
2
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
wx_晨梦
感谢你的贡献,论坛因你而更加精彩!
2026-7-16 05:40
我的小拇指啊
感谢你的贡献,论坛因你而更加精彩!
2026-3-22 16:57
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
4
)
xyzliao
雪 币:
650
活跃值:
(5605)
能力值:
( LV3,RANK:20 )
在线值:
发帖
4
回帖
127
粉丝
4
关注
私信
xyzliao
2
楼
妙哇
2026-4-3 10:52
0
mb_gmajzygm
雪 币:
20
能力值:
( LV1,RANK:0 )
在线值:
发帖
4
回帖
37
粉丝
1
关注
私信
mb_gmajzygm
3
楼
可以搞搞64位单机游戏拓展。
2026-4-11 16:24
0
mb_cjlxyeig
雪 币:
66
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
3
粉丝
0
关注
私信
mb_cjlxyeig
4
楼
找到跟我一样再看这本书的人了,大哭
2026-4-11 17:20
0
yuanyouran
雪 币:
305
活跃值:
(3075)
能力值:
( LV2,RANK:10 )
在线值:
发帖
5
回帖
184
粉丝
0
关注
私信
yuanyouran
5
楼
感谢分享
2026-4-11 17:53
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
mb_nnqgphwf
7
发帖
7
回帖
30
RANK
关注
私信
他的文章
[原创]详细分析MSVC中std::string对象内存布局
213
[原创]分析Windows Section Object机制
1000
[原创]I Pack You加密壳:实现页粒度的动态解密和惰性加密
6212
[原创]I Pack You:实现基本的软件壳框架
2683
[原创]调试Windows11启动过程
12241
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部