本文仅用于技术研究学习,请遵守相关法律,禁止使用本文所提及的相关技术开展非法攻击行为,由于传播、利用本文所提供的信息而造成任何不良后果及损失,与本账号及作者无关。
关于无问社区
无问社区致力于打造一个面向于网络安全从业人员的技术综合服务社区,可免费获取安全技术资料,社区内技术资料知识面覆盖全面,功能丰富。
特色功能:划词解析、调取同类技术资料、基于推荐算法,为每一位用户量身定制专属技术资料。
无问社区-官网:http://wwlib.cn
无问社区站内阅读链接:
http://www.wwlib.cn/index.php/artread/artid/14312.html
这个小项目演示了我发现的另一种将内存写入远程进程的技术 - 这可用于替换 WriteProcessMemory API。
此方法使用针对新创建的线程计划的一系列 APC 调用,并利用 Windows 中的各种预先存在的函数。
用于计划 APC 调用的标准 Windows API (QueueUserAPC) 仅允许我们使用单个参数调用函数。如果不注入额外的代码,就很难利用它来将特定数据写入特定地址。在内部,QueueUserAPC 调用一个名为 NtQueueApcThread 的未记录的 ntdll.dll 函数 - 此函数允许我们为回调函数指定 3 个参数。这是因为 QueueUserAPC 在调用目标函数之前,最初将所有 APC 调用路由到名为 RtlDispatchAPC 的内部重定向程序函数。现在我们能够指定一个具有 3 个自定义参数的回调函数,这为覆盖内存提供了更多机会。
我最初的想法是使用类似于 memset 的函数。例如,安排对 memset(0x11223344, 'a', 1) 的调用会写入一个 'a' (0x61) 字符来0x11223344。不幸的是,memset 函数使用 cdecl 调用约定,而 NtQueueApcThread 函数需要使用 stdcall 调用约定的回调。这意味着 memset 会导致 32 位进程上的堆栈损坏。我知道ntdll.dll导出了一些与常见的 C 运行时函数匹配的 Rtl* 函数 - 我看了一下,发现了一个名为 RtlFillMemory 的函数。此函数执行与 memset 相同的功能,但关键是使用 stdcall 调用约定,使其适合与 NtQueueApcThread 一起使用。
此概念验证需要以下步骤:
1. 找到 NtCreateThreadEx、NtQueueApcThread、RtlFillMemory 在ntdll.dll中的地址。
2. 调用 NtCreateThreadEx 以在目标进程中创建入口点为 ExitThread 的挂起线程。
3. 对于要写入的每个字节,调用 NtQueueApcThread 以在新创建的线程中计划对 RtlFillMemory 的 APC 调用。
4. 调用 ResumeThread 开始执行目标线程。这将在调用 ExitThread(0) 之前执行所有计划的 RtlFillMemory 调用。
完整代码如下:
DWORD WriteProcessMemoryAPC(HANDLE hProcess, BYTE *pAddress, BYTE *pData, DWORD dwLength) {
HANDLE hThread = NULL;
DWORD (WINAPI *pNtQueueApcThread)(HANDLE ThreadHandle, PVOID pApcRoutine, PVOID pParam1, PVOID pParam2, PVOID pParam3) = NULL;
DWORD (WINAPI *pNtCreateThreadEx)(HANDLE *phThreadHandle, DWORD DesiredAccess, PVOID ObjectAttributes, HANDLE hProcessHandle, PVOID StartRoutine, PVOID Argument, ULONG CreateFlags, DWORD *pZeroBits, SIZE_T StackSize, SIZE_T MaximumStackSize, PVOID AttributeList) = NULL;
void (*pRtlFillMemory)(void *, SIZE_T, BYTE) = NULL;
pNtQueueApcThread = (DWORD (WINAPI *)(HANDLE, PVOID, PVOID, PVOID, PVOID))GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQueueApcThread");
if (pNtQueueApcThread == NULL) {
return 1;
}
pNtCreateThreadEx = (DWORD (WINAPI *)(HANDLE *, DWORD, PVOID, HANDLE, PVOID, PVOID, ULONG, DWORD *, SIZE_T, SIZE_T, PVOID))GetProcAddress(GetModuleHandle("ntdll.dll"), "NtCreateThreadEx");
if (pNtCreateThreadEx == NULL) {
return 1;
}
pRtlFillMemory = (void (*)(void *, SIZE_T, BYTE))GetProcAddress(GetModuleHandle("kernel32.dll"), "RtlFillMemory");
if (pRtlFillMemory == NULL) {
return 1;
}
if (pNtCreateThreadEx(&hThread, NT_CREATE_THREAD_EX_ALL_ACCESS, NULL, hProcess, (LPVOID)ExitThread, (LPVOID)0, NT_CREATE_THREAD_EX_SUSPENDED, NULL, 0, 0, NULL) != 0) {
return 1;
}
for (DWORD i = 0; i < dwLength; i++) {
if (pNtQueueApcThread(hThread, pRtlFillMemory, (void *)((BYTE *)pAddress + i), (void *)1, (void *)*(BYTE *)(pData + i)) != 0) {
TerminateThread(hThread, 0);
CloseHandle(hThread);
return 1;
}
}
ResumeThread(hThread);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
return 0;
}
int main() {
char szTestString[1024];
char szOverwriteValue[64];
printf("WriteProcessMemoryAPC - www.x86matthew.comn");
memset(szTestString, 0, sizeof(szTestString));
_snprintf(szTestString, sizeof(szTestString) - 1, "原始值");
printf("值:'%s'n", szTestString);
printf("覆盖数据...n");
memset(szOverwriteValue, 0, sizeof(szOverwriteValue));
_snprintf(szOverwriteValue, sizeof(szOverwriteValue) - 1, "** 覆盖值 **");
if (WriteProcessMemoryAPC(GetCurrentProcess(), (BYTE *)szTestString, (BYTE *)szOverwriteValue, strlen(szOverwriteValue) + 1) != 0) {
return 1;
}
printf("值:'%s'n", szTestString);
return 0;
}
加入交流群
点“阅读原文”,访问无问社区
原文始发于微信公众号(白帽子社区团队):WriteProcessMemoryAPC - 使用 APC 调用将内存写入远程进程
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论