前言
这几天在弄进程注入的事情,一直做不出来直接的进程注入,也就是不要dll的注入。因为dll注入据说容易触发杀软,但是弄了两天没弄出来。代码明明不报错,目标进程一旦注入就崩溃,完全没有提示信息,windows日志也没有什么有价值的线索。所以今天就尝试了dll注入,结果一次成功了。。。
前提
- 进程间内存独立
运行在安全模式下的Windows,每个进程的内存空间都是独立的,互相不能访问彼此,所以代码注入才是一件比较麻烦的事情
- 所有进程中
kernel32.dll
地址相同
每个进程都会加载
kernel32.dll
,并且在那些进程中,这个库的地址都是一样的
原理
dll注入主要分为如下几个步骤
- 提权:如果不提权的话,进程列表中很多进程是打不开的,并且可能获取不到
PROCESS_ALL_ACCESS
权限 - 获取PID:这部分不是必须的,但是PID每次运行都不一样,所以最好能获取,这里通过
CreateToolhelp32Snapshot()
函数获取进程,遍历结果之后得到对应的PID - 打开进程:通过
OpenProcess()
函数打开进程,同时指定权限是PROCESS_ALL_ACCESS
权限,否则后面操作会失败,如果要获取这个权限就得先提权 - 通过
VirtualAllocEx()
函数在目标进程中申请内存,然后用WriteProcessMemory()
函数将LoadLibraey()
函数的参数准备好写入到内存中 - 通过
LoadLibrary()
函数加载kernel32.dll
,实际上进程都会加载这个,第二次加载似乎直接获取模块句柄了。 - 通过
GetProcAddress()
函数获取到LoadLibraryW()
函数的地址,因为kernel32.dll
在所有进程中的地址都相同,所以,在本进程中获取的函数地址在目标进程中也是那个函数 - 通过
CreateRemoteThread()
函数在目标进程中创建一个线程,执行LoadLibraryW()
函数载入自己的dll
代码
- 注入代码
#include <windows.h>
#include <iostream>
#include <tlhelp32.h>
using namespace std;
DWORD GetPidByName(LPCWSTR lpName)
{
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
if (!hSnap)
{
cout << "创建进程快照失败" << endl;
return 0;
}
PROCESSENTRY32 pe;
pe.dwSize = sizeof(PROCESSENTRY32);
Process32First(hSnap, &pe);
do
{
if (!_wcsicmp(lpName, pe.szExeFile))
{
return pe.th32ProcessID;
}
} while (Process32Next(hSnap,&pe));
return 0;
}
// 提权
int EnableDebugPrivilege()
{
HANDLE token;
TOKEN_PRIVILEGES tp;
// 打开进程令牌环
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))
{
cout << "打开进程令牌失败" << endl;
return 0;
}
// 获取进程本地唯一ID
LUID luid;
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid))
{
cout << "获取LUID失败" << endl;
return 0;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
tp.Privileges[0].Luid = luid;
// 调整进程权限
if (!AdjustTokenPrivileges(token, 0, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL))
{
cout << "提权失败" << endl;
return 0;
}
return 1;
}
int main()
{
if (!EnableDebugPrivilege())
{
cout << "提权失败"<<endl;
return 0;
}
DWORD dwTargetPid = GetPidByName(L"notepad.exe");
if (!dwTargetPid)
{
cout << "获取PID失败" << endl;
return 0;
}
cout << "PID: " << dwTargetPid << endl;
// 打开进程
HANDLE hTarget = OpenProcess(PROCESS_ALL_ACCESS,false,dwTargetPid);
if (!hTarget)
{
cout << "打开进程失败"<<GetLastError() << endl;
return 0;
}
void* pLoadLibFuncParam = nullptr;
pLoadLibFuncParam = VirtualAllocEx(hTarget,0,4096,MEM_COMMIT|MEM_RESERVE,PAGE_READWRITE);
if (pLoadLibFuncParam == nullptr)
{
cout << "申请内存失败" << endl;
CloseHandle(hTarget);
return 0;
}
LPCTSTR lpParam = L"D:\1.dll";
if (!WriteProcessMemory(hTarget, pLoadLibFuncParam, (LPCVOID)lpParam, (wcslen(lpParam)+1)*sizeof(TCHAR), NULL))
{
cout << "写入内存失败" << endl;
CloseHandle(hTarget);
return 0;
}
HMODULE hNtdll = LoadLibrary(L"kernel32.dll");
if (!hNtdll)
{
cout << "加载模块错误" <<GetLastError()<< endl;
CloseHandle(hTarget);
return 0;
}
cout << "模块句柄: " << hNtdll << endl;
void* pLoadLibrary = nullptr;
pLoadLibrary = GetProcAddress(hNtdll, "LoadLibraryW");
if (pLoadLibrary == nullptr)
{
cout << "找不到函数" << endl;
CloseHandle(hTarget);
return 0;
}
cout << "函数地址: " << pLoadLibrary << endl;
DWORD dwThreadId = 0;
HANDLE hRemoteThread = CreateRemoteThread(hTarget, NULL, 0, (LPTHREAD_START_ROUTINE)pLoadLibrary, (LPVOID)pLoadLibFuncParam, 0,&dwThreadId);
if (!hRemoteThread)
{
cout << "创建进程失败" << endl;
CloseHandle(hTarget);
return 0;
}
cout<<"运行结束"<< hRemoteThread <<endl;
getchar();
getchar();
CloseHandle(hTarget);
return 0;
}
- DLL中的代码
代码编译之后,放到D:1.dll的位置
DWORD WINAPI ThreadProc()
{
MessageBox(NULL,L"我已成功打入敌人内部 By Startu",L"报告首长",0);
return 0;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
CreateThread(NULL,0, (LPTHREAD_START_ROUTINE)ThreadProc,NULL,0,NULL);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
一些问题
- 为什么是
LoadLibraryW()
我最开始获取的函数是
LoadLibrary()
函数,但是实际上这个函数并不存在,这个只是一个宏,在windows中,有两个函数LoadLibraryW()
和LoadLibraryA()
分别用于,不同的编码
- 为什么需要在目标进程中申请地址存放参数
当代码被注入到目标进程之后,实际上这段代码算是目标进程的,如果直接传入参数的本质是传入地址,直接在本进程中传入地址,
- 调试问题
dll中的代码,所有的printf() 或者 cout的输出都看不到,曾经我诧异了很久,直到突然想起来,这里的输出都应该是由宿主程序输出的,但是宿主程序没有控制台,所以就看不到了。这种时候 MessageBox()就要用到了。另外,通过 命令行
rundll32 1.dll func
的方式可以加载dll,执行里面的函数