Win32 利用远程线程注入dll
Posted 不会写代码的丝丽
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Win32 利用远程线程注入dll相关的知识,希望对你有一定的参考价值。
前言
一些程序会利用CreateRemoteThread
这个函数为其他进程创建一个线程,而传入的线程回调函数地址为kernel32.dll
的LoadLibraryA
函数。
这个函数最原始的功能是用来加载动态库的,而且这个函数可以和最原始的线程回调
函数声明是相同的。因此一些Hack利用这个特性来完成一些跨进程dll注入。
我们首先观察下这个函数
//kernel32.dll
HMODULE
WINAPI
LoadLibraryA(_In_ LPCSTR lpLibFileName);
在对比下最原始的线程回调函数
typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(
LPVOID lpThreadParameter
);
可以发现两个函数传入的参数是一样的(单个参数且都可以视为char*),所以我们利用这个特性完成一些奇淫技巧(传入的参数为dll地址那不就可以加载一个库了吗)。
我们首先定下一个目标程序给其注入一个dll,并打印一个弹出窗口。
目标程序这里选取的计算器
我们的dll代码如下:
//dll被加载的时候会回调这个函数
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
OutputDebugString("Your was injected by test ");
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBox(NULL, "成功注入dll", "注入提示", MB_OK);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
预期效果如下:
我们首先看CreateRemoteThread
文档
CreateRemoteThread 文档
HANDLE CreateRemoteThread(
HANDLE hProcess,//指定为哪一个进程创建线程
LPSECURITY_ATTRIBUTES lpThreadAttributes,//安全属性
SIZE_T dwStackSize, //指定栈大小
LPTHREAD_START_ROUTINE lpStartAddress,//回调函数
LPVOID lpParameter,//回调的参数
DWORD dwCreationFlags,//创建一些标识符 我们传0即可
LPDWORD lpThreadId //线程id 可以传NULL
);
我们的大致流程:
- 寻找计算器的句柄
- 获取
kernel32
模块中的LoadLibraryA
函数地址。 - 将dll字符串的内容拷贝到
计算器
进程 - 调用
CreateRemoteThread
为计算器进程创建线程,回调函数为LoadLibraryA
,回调函数的传入参数为dll目录字符串地址 - 资源释放
int main()
{
/**
第一步:寻找计算器的句柄
**/
//返回窗口句柄
HWND hWndCalc = FindWindow(NULL, "计算器");
DWORD wdproId = 0;
//获取窗口句柄对应的线程ID https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowthreadprocessid
GetWindowThreadProcessId(hWndCalc, &wdproId);
//返回进程的句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, wdproId);
/**
第二步: 获取`kernel32`模块中的`LoadLibraryA`函数地址
**/
//得到加载kernel32模块的句柄
HMODULE hModker = GetModuleHandle("kernel32");
//从指定的动态链接库 (DLL) 中检索导出函数或变量的地址。
LPVOID pLoadLibrary = GetProcAddress(hModker, "LoadLibraryA");
/**
第三步: 将dll字符串的内容拷贝到`计算器`进程
**/
char szDllPath[] = { "C:\\\\Users\\\\fmy\\\\source\\\\repos\\\\MyDllProject\\\\x64\\\\Debug\\\\MyDllProject.dll" };
//https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex
LPVOID pPathBuf = VirtualAllocEx(hProcess, NULL, sizeof(szDllPath), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pPathBuf == NULL)
{
return 0;
}
SIZE_T writeWord;
WriteProcessMemory(hProcess, pPathBuf, szDllPath, sizeof(szDllPath), &writeWord);
/**
第四步: 调用`CreateRemoteThread`为计算器进程创建线程,回调函数为`LoadLibraryA`,回调函数的传入参数为dll目录字符串地址
**/
HANDLE hTread = CreateRemoteThread(
hProcess,
NULL,
0,
(LPTHREAD_START_ROUTINE)pLoadLibrary,
(LPVOID)pPathBuf,
0,
NULL
);
/**
第五步:资源释放
**/
WaitForSingleObject(hTread, INFINITE);
BOOL result = VirtualFreeEx(hProcess, pPathBuf, 0, MEM_RELEASE);
CloseHandle(hTread);
return EXIT_SUCCESS;
}
需要注意一点:你要特别留意目标进程和dll库以及注入进程的位数必须要相同。比如全部都是64位等
为什么需要第三步: 将dll字符串的内容拷贝到计算器
进程 ?
因为这个字符串内容现在在你的进程内存中,而对于计算器进程来说是不可以访问到的
实操
一个小实验项目:我们给notepad++.exe
注入一个DLL
.这个DLL
库会在notepad++.exe
注入一个菜单按钮。
如下图所示:
上面有两个二级菜单:
- 弹出消息框:弹出一个信息框
- 卸载:卸载被注入的dll
上面我们要响应点击事件必然要监听消息分发。(替换窗口过程函数)
我们看下被注入dll
源代码:
#include "pch.h"
#include <windows.h>
#include <strsafe.h>
//最原始窗口notepad++.过程函数
WNDPROC g_pfnOldProc = NULL;
//当前被注入的dll
HMODULE hModule;
//将要替换最原始的g_pfnOldProc 窗口函数的声明
LRESULT CALLBACK NewWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam);
//执行注入代码
void InjectCalc() {
//寻找窗口句柄
HWND hWnd = ::FindWindow(NULL, "new 1 - Notepad++");
if (hWnd == NULL)
{
MessageBox(NULL, "没有寻找到句柄", "注入提示3", MB_OK);
return;
}
//获取菜单
HMENU hmenu = ::GetMenu(hWnd);
//创建一个新的菜单
HMENU hNewMenu = ::CreatePopupMenu();//创建个菜单
//插入菜单
::InsertMenu(hmenu, 0, MF_BYPOSITION| MF_STRING| MF_POPUP, (UINT_PTR)hNewMenu, "注入菜单");
::AppendMenu(hNewMenu, MF_STRING, 65534, "弹出消息框");//子菜单
::AppendMenu(hNewMenu, MF_STRING, 65535, "卸载");//子菜单
//替换过程函数,因为你需要监听菜单点击事件
if (::IsWindowUnicode(hWnd))
{
//SetWindowLongW用户替换旧的窗口属性
//GWLP_WNDPROC用于指定为替换旧的过程函数,然后返回旧的的窗口过程函数
//返回旧的窗口函数地址
g_pfnOldProc = (WNDPROC)::SetWindowLongPtrW(hWnd, GWLP_WNDPROC, (LONG_PTR)NewWndProc);
}else
{
g_pfnOldProc = (WNDPROC)::SetWindowLongPtrA(hWnd, GWLP_WNDPROC, (LONG_PTR)NewWndProc);
}
MessageBox(NULL, "成功注入dll", "注入提示3", MB_OK);
}
//dll被加载的时候会回调
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
::hModule = hModule;
MessageBox(NULL, "成功注入dll", "注入提示232", MB_OK);
InjectCalc();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
LRESULT CALLBACK NewWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam) {
if (nMsg == WM_COMMAND)
{
//卸载按钮点击
if (LOWORD(wParam) == 65535)
{
MessageBox(NULL, "卸载模块成功", "注入提示", MB_OK);
//替换回过程函数
if (::IsWindowUnicode(hWnd))
{
//SetWindowLongW用户替换旧的窗口属性
//GWLP_WNDPROC用于指定为替换旧的过程函数,然后返回旧的的窗口过程函数
(WNDPROC)::SetWindowLongPtrW(hWnd, GWLP_WNDPROC, (LONG_PTR)g_pfnOldProc);
}
else
{
(WNDPROC)::SetWindowLongPtrA(hWnd, GWLP_WNDPROC, (LONG_PTR)g_pfnOldProc);
}
//重画状态栏
HMENU hmenu = ::GetMenu(hWnd);
DeleteMenu(hmenu,0, MF_BYPOSITION);
DrawMenuBar(hWnd);
//释放module库,这里直接调用FreeLibrary(hModule)回导致后面的代码异常,因为你已经移除了module内存
::CreateThread(NULL,0, (LPTHREAD_START_ROUTINE)FreeLibrary, hModule,0,NULL);
//释放module
//::FreeLibrary(hModule);
return 0;
}
else if (LOWORD(wParam) == 65534)
{
MessageBox(NULL, "你好", "注入提示", MB_OK);
}
}
return g_pfnOldProc(hWnd, nMsg, wParam, lParam);
}
以上是关于Win32 利用远程线程注入dll的主要内容,如果未能解决你的问题,请参考以下文章
安全之路 —— 利用远程线程注入的方法(使用DLL)实现穿墙与隐藏进程