平常用的最多的dll注入技术就是远程线程,刚刚逛看雪,看到有人写的面试的时候被问到的问题,其中就有dll注入的方法,我突然想到我开始面试的时候也被问了dll注入的方法,当时也是就只知道一个远程线程,答的也不好,然后就想把一些注入技术写个总结。接下来讲的注入技术,有ring3层的lld的远程线程和apc注入,还有ring0的apc注入,此外还有更为隐蔽的代码注入。
先写最广泛的,也是相对简单的注入方式,远程线程。
一 ring3 dll的远程线程
我写的涉及到x86和x64的注入,因为x64的系统本身增加了较多权限的校验,需要进行提权处理。所以先进行系统版本的校验:
typedef enum _WIN_VERSION { WindowsNT, Windows2000, WindowsXP, Windows2003, WindowsVista, Windows7, Windows8, WinUnknown }WIN_VERSION; WIN_VERSION GetWindowsVersion() { OSVERSIONINFOEX OsVerInfoEx; OsVerInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); GetVersionEx((OSVERSIONINFO *)&OsVerInfoEx); // 注意转换类型 switch (OsVerInfoEx.dwPlatformId) { case VER_PLATFORM_WIN32_NT: { if (OsVerInfoEx.dwMajorVersion <= 4 ) { return WindowsNT; } if (OsVerInfoEx.dwMajorVersion == 5 && OsVerInfoEx.dwMinorVersion == 0) { return Windows2000; } if (OsVerInfoEx.dwMajorVersion == 5 && OsVerInfoEx.dwMinorVersion == 1) { return WindowsXP; } if (OsVerInfoEx.dwMajorVersion == 5 && OsVerInfoEx.dwMinorVersion == 2) { return Windows2003; } if (OsVerInfoEx.dwMajorVersion == 6 && OsVerInfoEx.dwMinorVersion == 0) { return WindowsVista; } if (OsVerInfoEx.dwMajorVersion == 6 && OsVerInfoEx.dwMinorVersion == 1) { return Windows7; } if (OsVerInfoEx.dwMajorVersion == 6 && OsVerInfoEx.dwMinorVersion == 2 ) { return Windows8; } break; } default: { return WinUnknown; } } }
x86和x64的注入最主要的不同点就是对于x64的提权处理,接下来先讲x64的提权处理。
x64的提权主要就是用到了ntdll.dll中未导出的一个函数,RtlAdjustPrivilege(),这个函数的作用是很大的,之前的瞬间关机的代码就是用了这个函数进行提权。
/* .常量 SE_BACKUP_PRIVILEGE, "17", 公开 .常量 SE_RESTORE_PRIVILEGE, "18", 公开 .常量 SE_SHUTDOWN_PRIVILEGE, "19", 公开 .常量 SE_DEBUG_PRIVILEGE, "20", 公开 */
我们提升的权限就是SE_DEBUG_PRIVILEGE 20
//程序编译成64位可以注入64位 编译成32位可以注入32位 CWinApp theApp; typedef enum _WIN_VERSION { WindowsNT, Windows2000, WindowsXP, Windows2003, WindowsVista, Windows7, Windows8, WinUnknown }WIN_VERSION; VOID InjectDll(ULONG_PTR ProcessID); WIN_VERSION GetWindowsVersion(); BOOL InjectDllByRemoteThread32(const TCHAR* wzDllFile, ULONG_PTR ProcessId); WIN_VERSION WinVersion = WinUnknown; BOOL InjectDllByRemoteThread64(const TCHAR* wzDllFile, ULONG_PTR ProcessId); typedef long (__fastcall *pfnRtlAdjustPrivilege64)(ULONG,ULONG,ULONG,PVOID); pfnRtlAdjustPrivilege64 RtlAdjustPrivilege; int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) { cout<<"查看要注入进程的ID"<<endl; ULONG_PTR ProcessID = 0; WinVersion = GetWindowsVersion(); printf("Input ProcessID\r\n"); cin>>ProcessID; InjectDll(ProcessID); return 0; } VOID InjectDll(ULONG_PTR ProcessID) { CString strPath32 = L"MessageBox32.dll"; //32位dll注入32位系统 CString strPath64 = L"MessageBox64.dll"; if (ProcessID == 0) { return; } if (PathFileExists(strPath32)&&PathFileExists(strPath64)) { switch(WinVersion) { case Windows7: //这里用的是Win7 x64 sp1 { WCHAR wzPath[MAX_PATH] = {0}; GetCurrentDirectory(260,wzPath); wcsncat_s(wzPath, L"\\", 2); wcsncat_s(wzPath, strPath64.GetBuffer(), strPath64.GetLength());//dll完整路径 strPath32.ReleaseBuffer(); if (!InjectDllByRemoteThread64(wzPath,ProcessID)) printf("Inject Fail\r\n"); else printf ("Inject Success\r\n"); break; } case WindowsXP: //WinXp x86 sp3 { WCHAR wzPath[MAX_PATH] = {0}; GetCurrentDirectory(260,wzPath); wcsncat_s(wzPath, L"\\", 2); wcsncat_s(wzPath, strPath32.GetBuffer(), strPath32.GetLength()); strPath32.ReleaseBuffer(); if (!InjectDllByRemoteThread32(wzPath,ProcessID)) printf("Inject Fail\r\n"); else printf("Inject Success\r\n"); break; } } } } BOOL InjectDllByRemoteThread64(const TCHAR* wzDllFile, ULONG_PTR ProcessId) { if (NULL == wzDllFile || 0 == ::_tcslen(wzDllFile) || ProcessId == 0 || -1 == _taccess(wzDllFile, 0)) { return FALSE; } HANDLE hProcess = NULL; HANDLE hThread = NULL; DWORD dwRetVal = 0; LPTHREAD_START_ROUTINE FuncAddress = NULL; DWORD dwSize = 0; TCHAR* VirtualAddress = NULL; //预编译,支持Unicode #ifdef _UNICODE FuncAddress = (PTHREAD_START_ROUTINE)::GetProcAddress(::GetModuleHandle(_T("Kernel32")), "LoadLibraryW"); #else FuncAddress = (PTHREAD_START_ROUTINE)::GetProcAddress(::GetModuleHandle(_T("Kernel32")), "LoadLibraryA"); #endif if (FuncAddress==NULL) { return FALSE; } RtlAdjustPrivilege=(pfnRtlAdjustPrivilege64)GetProcAddress((HMODULE)(FuncAddress(L"ntdll.dll")),"RtlAdjustPrivilege"); if (RtlAdjustPrivilege==NULL) { return FALSE; } /* .常量 SE_BACKUP_PRIVILEGE, "17", 公开 .常量 SE_RESTORE_PRIVILEGE, "18", 公开 .常量 SE_SHUTDOWN_PRIVILEGE, "19", 公开 .常量 SE_DEBUG_PRIVILEGE, "20", 公开 */ RtlAdjustPrivilege(20,1,0,&dwRetVal); //19 hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE, ProcessId); if (NULL == hProcess) { printf("Open Process Fail\r\n"); return FALSE; } // 在目标进程中分配内存空间 dwSize = (DWORD)::_tcslen(wzDllFile) + 1; VirtualAddress = (TCHAR*)::VirtualAllocEx(hProcess, NULL, dwSize * sizeof(TCHAR), MEM_COMMIT, PAGE_READWRITE); if (NULL == VirtualAddress) { printf("Virtual Process Memory Fail\r\n"); CloseHandle(hProcess); return FALSE; } // 在目标进程的内存空间中写入所需参数(模块名) if (FALSE == ::WriteProcessMemory(hProcess, VirtualAddress, (LPVOID)wzDllFile, dwSize * sizeof(TCHAR), NULL)) { printf("Write Data Fail\r\n"); VirtualFreeEx(hProcess, VirtualAddress, dwSize, MEM_DECOMMIT); CloseHandle(hProcess); return FALSE; } hThread = ::CreateRemoteThread(hProcess, NULL, 0, FuncAddress, VirtualAddress, 0, NULL); if (NULL == hThread) { printf("CreateRemoteThread Fail\r\n"); VirtualFreeEx(hProcess, VirtualAddress, dwSize, MEM_DECOMMIT); CloseHandle(hProcess); return FALSE; } // 等待远程线程结束 WaitForSingleObject(hThread, INFINITE); // 清理资源 VirtualFreeEx(hProcess, VirtualAddress, dwSize, MEM_DECOMMIT); CloseHandle(hThread); CloseHandle(hProcess); return TRUE; } BOOL InjectDllByRemoteThread32(const TCHAR* wzDllFile, ULONG_PTR ProcessId) { // 参数无效 if (NULL == wzDllFile || 0 == ::_tcslen(wzDllFile) || ProcessId == 0 || -1 == _taccess(wzDllFile, 0)) { return FALSE; } HANDLE hProcess = NULL; HANDLE hThread = NULL; DWORD dwSize = 0; TCHAR* VirtualAddress = NULL; LPTHREAD_START_ROUTINE FuncAddress = NULL; // 获取目标进程句柄 hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, ProcessId); if (NULL == hProcess) { printf("Open Process Fail\r\n"); return FALSE; } // 在目标进程中分配内存空间 dwSize = (DWORD)::_tcslen(wzDllFile) + 1; VirtualAddress = (TCHAR*)::VirtualAllocEx(hProcess, NULL, dwSize * sizeof(TCHAR), MEM_COMMIT, PAGE_READWRITE); if (NULL == VirtualAddress) { printf("Virtual Process Memory Fail\r\n"); CloseHandle(hProcess); return FALSE; } // 在目标进程的内存空间中写入所需参数(模块名) if (FALSE == ::WriteProcessMemory(hProcess, VirtualAddress, (LPVOID)wzDllFile, dwSize * sizeof(TCHAR), NULL)) { printf("Write Data Fail\r\n"); VirtualFreeEx(hProcess, VirtualAddress, dwSize, MEM_DECOMMIT); CloseHandle(hProcess); return FALSE; } // 从 Kernel32.dll 中获取 LoadLibrary 函数地址 #ifdef _UNICODE FuncAddress = (PTHREAD_START_ROUTINE)::GetProcAddress(::GetModuleHandle(_T("Kernel32")), "LoadLibraryW"); #else FuncAddress = (PTHREAD_START_ROUTINE)::GetProcAddress(::GetModuleHandle(_T("Kernel32")), "LoadLibraryA"); #endif if (NULL == FuncAddress) { printf("Get LoadLibrary Fail\r\n"); VirtualFreeEx(hProcess, VirtualAddress, dwSize, MEM_DECOMMIT); CloseHandle(hProcess); return false; } // 创建远程线程调用 LoadLibrary hThread = ::CreateRemoteThread(hProcess, NULL, 0, FuncAddress, VirtualAddress, 0, NULL); if (NULL == hThread) { printf("CreateRemoteThread Fail\r\n"); VirtualFreeEx(hProcess, VirtualAddress, dwSize, MEM_DECOMMIT); CloseHandle(hProcess); return FALSE; } // 等待远程线程结束 WaitForSingleObject(hThread, INFINITE); // 清理 VirtualFreeEx(hProcess, VirtualAddress, dwSize, MEM_DECOMMIT); CloseHandle(hThread); CloseHandle(hProcess); return TRUE; } WIN_VERSION GetWindowsVersion() { OSVERSIONINFOEX OsVerInfoEx; OsVerInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); GetVersionEx((OSVERSIONINFO *)&OsVerInfoEx); // 注意转换类型 switch (OsVerInfoEx.dwPlatformId) { case VER_PLATFORM_WIN32_NT: { if (OsVerInfoEx.dwMajorVersion <= 4 ) { return WindowsNT; } if (OsVerInfoEx.dwMajorVersion == 5 && OsVerInfoEx.dwMinorVersion == 0) { return Windows2000; } if (OsVerInfoEx.dwMajorVersion == 5 && OsVerInfoEx.dwMinorVersion == 1) { return WindowsXP; } if (OsVerInfoEx.dwMajorVersion == 5 && OsVerInfoEx.dwMinorVersion == 2) { return Windows2003; } if (OsVerInfoEx.dwMajorVersion == 6 && OsVerInfoEx.dwMinorVersion == 0) { return WindowsVista; } if (OsVerInfoEx.dwMajorVersion == 6 && OsVerInfoEx.dwMinorVersion == 1) { return Windows7; } if (OsVerInfoEx.dwMajorVersion == 6 && OsVerInfoEx.dwMinorVersion == 2 ) { return Windows8; } break; } default: { return WinUnknown; } } }
二 ring3 apc注入
APC注入的原理是利用当线程被唤醒时APC中的注册函数会被执行的机制,并以此去执行我们的DLL加载代码,进而完成DLL注入的目的,其具体流程如下:
1)当EXE里某个线程执行到SleepEx()或者WaitForSingleObjectEx()时,系统就会产生一个软中断。
2)当线程再次被唤醒时,此线程会首先执行APC队列中的被注册的函数。
3)利用QueueUserAPC()这个API可以在软中断时向线程的APC队列插入一个函数指针,如果我们插入的是Loadlibrary()执行函数的话,就能达到注入DLL的目的。
#define _WIN32_WINNT 0x0400 #include <windows.h> #include <TlHelp32.h> #include <iostream> #include <string> using namespace std; #define DEF_BUF_SIZE 1024 typedef long (__fastcall *pfnRtlAdjustPrivilege64)(ULONG,ULONG,ULONG,PVOID); pfnRtlAdjustPrivilege64 RtlAdjustPrivilege; // 用于存储注入模块DLL的路径全名 char szDllPath[DEF_BUF_SIZE] = {0} ; // 使用APC机制向指定ID的进程注入模块 BOOL InjectModuleToProcessById ( DWORD dwProcessId ) { DWORD dwRet = 0 ; BOOL bStatus = FALSE ; LPVOID lpData = NULL ; UINT uLen = strlen(szDllPath) + 1; #ifdef _WIN64 // x64 OpenProcess提权操作 RtlAdjustPrivilege=(pfnRtlAdjustPrivilege64)GetProcAddress((HMODULE)(FuncAddress(L"ntdll.dll")),"RtlAdjustPrivilege"); if (RtlAdjustPrivilege==NULL) { return FALSE; } /* .常量 SE_BACKUP_PRIVILEGE, "17", 公开 .常量 SE_RESTORE_PRIVILEGE, "18", 公开 .常量 SE_SHUTDOWN_PRIVILEGE, "19", 公开 .常量 SE_DEBUG_PRIVILEGE, "20", 公开 */ RtlAdjustPrivilege(20,1,0,&dwRetVal); //19 #endif // 打开目标进程 HANDLE hProcess = OpenProcess ( PROCESS_ALL_ACCESS, FALSE, dwProcessId ) ; if ( hProcess ) { // 分配空间 lpData = VirtualAllocEx ( hProcess, NULL, uLen, MEM_COMMIT, PAGE_EXECUTE_READWRITE ) ; if ( lpData ) { // 写入需要注入的模块路径全名 bStatus = WriteProcessMemory ( hProcess, lpData, szDllPath, uLen, &dwRet ) ; } CloseHandle ( hProcess ) ; } if ( bStatus == FALSE ) return FALSE ; // 创建线程快照 THREADENTRY32 te32 = { sizeof(THREADENTRY32) } ; HANDLE hThreadSnap = CreateToolhelp32Snapshot ( TH32CS_SNAPTHREAD, 0 ) ; if ( hThreadSnap == INVALID_HANDLE_VALUE ) return FALSE ; bStatus = FALSE ; // 枚举所有线程 if ( Thread32First ( hThreadSnap, &te32 ) ) { do{ // 判断是否目标进程中的线程 if ( te32.th32OwnerProcessID == dwProcessId ) { // 打开线程 HANDLE hThread = OpenThread ( THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID ) ; if ( hThread ) { // 向指定线程添加APC DWORD dwRet = QueueUserAPC ( (PAPCFUNC)LoadLibraryA, hThread, (ULONG_PTR)lpData ) ; if ( dwRet > 0 ) bStatus = TRUE ; CloseHandle ( hThread ) ; } } }while ( Thread32Next ( hThreadSnap, &te32 ) ) ; } CloseHandle ( hThreadSnap ) ; return bStatus; } int _tmain(int argc, _TCHAR* argv[]) { // 取得当前工作目录路径 GetCurrentDirectoryA ( DEF_BUF_SIZE, szDllPath ) ; // 生成注入模块DLL的路径全名 strcat ( szDllPath, "\\DLLSample.dll" ) ; DWORD dwProcessId = 0 ; // 接收用户输入的目标进程ID while ( cout << "请输入目标进程ID:" && cin >> dwProcessId && dwProcessId > 0 ) { BOOL bRet = InjectModuleToProcessById ( dwProcessId ) ; cout << (bRet ? "注入成功!":"注入失败!") << endl ; } return 0; }
三ring3层的远程线程的代码注入
代码是成功,但是每次运行就explorer直接崩溃。我开始还以为是我程序写的有问题,后来用WinDbg Attach到explorer进程调试,才发现问题,就是函数调用的问题,在汇编中call调用函数时不是绝对地址,机器码是相对偏移,这就涉及到函数的便宜重新重定位的问题,得不偿失,代价太大。仅仅以学习为目的的话,可以考虑用0day中非常经典的类似于GetProcAddress()函数实现的191个字节的shellcode来获取函数地址。先放上代码吧,在Windbg中对explorer中的RemoteCodeAddr下断点,是可以执行到那的,然后执行到call MessageBox就崩溃,就是函数的偏移的问题。
#include "stdafx.h" #include <iostream> #include <Windows.h> using namespace std; typedef long (__fastcall *pfnRtlAdjustPrivilege64)(ULONG,ULONG,ULONG,PVOID); pfnRtlAdjustPrivilege64 RtlAdjustPrivilege; static DWORD WINAPI MyFunc (LPVOID pData) { //do something //... //pData输入项可以是任何类型值 // ::MessageBoxA(NULL,(char*)pData,"INJECT",0); //不能直接调用 //调用函数时要考虑call RVA 都是相对的偏移, return *(DWORD*)pData; } static void AfterMyFunc (void) { } int _tmain(int argc, _TCHAR* argv[]) { DWORD cbCodeSize = (ULONG_PTR)AfterMyFunc-(ULONG_PTR)MyFunc +0x100; DWORD ProcessId = 0; cin>>ProcessId; #ifdef _WIN64 // x64 OpenProcess提权操作 RtlAdjustPrivilege=(pfnRtlAdjustPrivilege64)GetProcAddress((HMODULE)(GetModuleHandle(L"ntdll.dll")),"RtlAdjustPrivilege"); if (RtlAdjustPrivilege==NULL) { return FALSE; } /* .常量 SE_BACKUP_PRIVILEGE, "17", 公开 .常量 SE_RESTORE_PRIVILEGE, "18", 公开 .常量 SE_SHUTDOWN_PRIVILEGE, "19", 公开 .常量 SE_DEBUG_PRIVILEGE, "20", 公开 */ DWORD dwReturnVal; RtlAdjustPrivilege(20,1,0,&dwReturnVal); //19 #endif HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE, ProcessId); //申请放置代码的内存 PVOID RemoteCodeAddr = (PVOID)VirtualAllocEx( hProcess, 0, cbCodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); WriteProcessMemory( hProcess, RemoteCodeAddr, &MyFunc, cbCodeSize, NULL); char szBuffer[] = "HelloWorld"; PVOID RemoteDataAddr = (PVOID)VirtualAllocEx( hProcess, 0, sizeof(szBuffer), MEM_COMMIT, PAGE_READWRITE ); WriteProcessMemory( hProcess, RemoteDataAddr, szBuffer, sizeof(szBuffer), NULL); HANDLE hThread = ::CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE) RemoteCodeAddr, RemoteDataAddr, 0 , NULL); DWORD h; if (hThread) { ::WaitForSingleObject( hThread, INFINITE ); ::CloseHandle( hThread ); } //cout<<dwSizeOfMyFunc<<endl; return 0; }
明天再把ring0的apc注入写上。