实战|使用Windows API绕过进程保护

admin 2021年11月17日09:00:00评论93 views字数 9446阅读31分29秒阅读模式

首发于奇安信攻防社区

文章地址:https://forum.butian.net/share/817

前言

最近在研究某数字杀软的时候看到有个配置选项:

img

这个自我保护实际上是加载360SelfProtection.sys驱动(看这名字应该还有360SelfProtection_win10.sys文件),在0环通过hook等手段保护注册表项,重要进程进程等。

img

比如这里要结束某核心进程,会显示无法结束,拒绝访问。

img
img

这个并不是说权限不够的问题,即便是system权限也不行。而是由于在底层,杀死进程的API已经被hook了,这里应该是内核hook,当想要结束的进程为核心进程时,就直接返回一个无法终止进程的弹窗。本文就如何实现一个进程保护功能进行探究,驱动就不写了,就写一个用户层的。

实现原理

windows提供了一个可以杀死其他进程的API:TerminateProcess。如果是结束本身进程为:ExitProcess。

BOOL TerminateProcess([in] HANDLE hProcess,[in] UINT   uExitCode);

通过命令taskkill或者通过任务管理器等GUI工具去结束进程,本质上都是调用的TerminateProcess,有些可能是调用更为底层的ZwTerminateProcess,但是本质都差不多,只是看该进程用的什么API,这里为了方便就使用TerminateProcess进行演示。

通过hook TerminateProcess让执行该函数时直接返回没有权限。

获取API地址并创建新的hook函数

static BOOL(WINAPI* OldTerminateProcess)(    HANDLE hProcess,    UINT   uExitCode) = TerminateProcess;BOOL WINAPI New_TerminateProcess(_In_ HANDLE hProcess,_In_ UINT uExitCode) {    unhookTerminateProcess();MessageBox(NULL,L"该进程受保护!", L"Access Denied",NULL);    hookTerminateProcess();returnfalse;}

这里是64位的进程,要改变的硬编码是12个字节,而且不需要去算偏移。

48 b8 _dwNewAddress(0x1122334455667788)ff e0mov rax, _dwNewAddress(0x1122334455667788)jmp rax

将原来函数的调转地址改为我们自己函数的跳转地址

void hookTerminateProcess() {    DWORD dwTerminateProcessOldProtect;    BYTE pData[12] = { 0x48,0xb8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xFF,0xE0};    ULONGLONG ullNewFuncAddr = (ULONGLONG)New_TerminateProcess;//保存本来的字节::RtlCopyMemory(g_OldTerminateProcess, OldTerminateProcess, sizeof(pData));//更改权限VirtualProtect(OldTerminateProcess, 12, PAGE_EXECUTE_READWRITE, &dwTerminateProcessOldProtect);//更改原来的机器码,改为我们自己函数的地址::RtlCopyMemory(&pData[2], &ullNewFuncAddr, sizeof(ullNewFuncAddr));::RtlCopyMemory(OldTerminateProcess, pData, sizeof(pData));//恢复属性VirtualProtect(OldTerminateProcess, 12, dwTerminateProcessOldProtect, &dwTerminateProcessOldProtect);}

恢复硬编码,还原调转地址。

void unhookTerminateProcess() {    DWORD dwOldProtect = NULL;//改为可写属性VirtualProtect(OldTerminateProcess, 12, PAGE_EXECUTE_READWRITE, &dwOldProtect);//还原原来的硬编码RtlCopyMemory(OldTerminateProcess, g_OldTerminateProcess, sizeof(g_OldTerminateProcess));//还原属性VirtualProtect(OldTerminateProcess, 12, dwOldProtect, &dwOldProtect);}

最方便的就是写成一个dll,这样注入到任务管理器并起一个线程跑就行了。

BOOL APIENTRY DllMain( HMODULE hModule,                       DWORD  ul_reason_for_call,                       LPVOID lpReserved){switch(ul_reason_for_call){case DLL_PROCESS_ATTACH:        hookTerminateProcess();break;case DLL_THREAD_ATTACH:break;case DLL_THREAD_DETACH:break;case DLL_PROCESS_DETACH:        unhookTerminateProcess();break;}return TRUE;}

注入这里也是为了练手,稍微写了一个注入程序,为了以后的方法写成了突破session 0的注入。

#include<iostream>#include<windows.h>#include"tchar.h"#include<TlHelp32.h>usingnamespace std;BOOL EnbalePrivileges(HANDLE hProcess, LPCWSTR pszPrivilegesName){    HANDLE hToken = NULL;    LUID luidValue = { 0};    TOKEN_PRIVILEGES tokenPrivileges = { 0};    BOOL bRet = FALSE;    DWORD dwRet = 0;// 打开进程令牌并获取进程令牌句柄    bRet = ::OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hToken);if(FALSE == bRet){        printf("[!] Open CurrentProcessToken Error,Error is:%dn",GetLastError());return FALSE;}else{        printf("[*] Open CurrentProcessToken Successfully!,TokenHandle is:%dn", hToken);}// 获取本地系统的 pszPrivilegesName 特权的LUID值    bRet = ::LookupPrivilegeValue(NULL, pszPrivilegesName, &luidValue);if(FALSE == bRet){        printf("[!] LookupPrivilegeValue Error,Error is:%dn", GetLastError());return FALSE;}else{        printf("[*] LookupPrivilegeValue Successfully!n");}// 设置提升权限信息    tokenPrivileges.PrivilegeCount= 1;    tokenPrivileges.Privileges[0].Luid= luidValue;    tokenPrivileges.Privileges[0].Attributes= SE_PRIVILEGE_ENABLED;// 提升进程令牌访问权限    bRet = ::AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, 0, NULL, NULL);if(FALSE == bRet){        printf("[!] AdjustTokenPrivileges Error,Error is:%dn", GetLastError());return FALSE;}else{// 根据错误码判断是否特权都设置成功        dwRet = ::GetLastError();if(ERROR_SUCCESS == dwRet){            printf("[√] ALL_ASSIGNED!n");return TRUE;}elseif(ERROR_NOT_ALL_ASSIGNED == dwRet){            printf("[!] ERROR:NOT_ALL_ASSIGNED,Error is %dn", dwRet);return FALSE;}}return FALSE;}DWORD EnumModules(DWORD hPid, LPCSTR hMoudlePath){    WCHAR szBuffer[MAX_PATH] = { 0};    mbstowcs(szBuffer, hMoudlePath, MAX_PATH);//通过pid列出所有的Modules    HANDLE hModuleSnap = INVALID_HANDLE_VALUE;    MODULEENTRY32    me32;//给进程所引用的模块信息设定一个快照    hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, hPid);if(hModuleSnap == INVALID_HANDLE_VALUE){        printf("[!] Error:Enum modules failed to detect if there is an injected DLL module,error is %dn",GetLastError());}    me32.dwSize = sizeof(MODULEENTRY32);if(!Module32First(hModuleSnap, &me32)){        printf("[!] Enum Error!n");CloseHandle(hModuleSnap);}do{if(!memcmp(me32.szExePath, szBuffer,MAX_PATH))return1;           } while(Module32Next(hModuleSnap, &me32));CloseHandle(hModuleSnap);return0;}DWORD _InjectThread(DWORD _Pid, LPCSTR psDllPath){    FILE* fp;    fp = fopen(psDllPath, "r");if(!fp){        printf("[!] Error:DLL path not foundnPlease check that your path is correct or absoluten");return FALSE;}    fclose(fp);    printf("****************************************************************************n");    HANDLE hprocess = NULL;    HANDLE hThread = NULL;    DWORD _SIZE = 0;    LPVOID pAlloc = NULL;    FARPROC pThreadFunction = NULL;    DWORD ZwRet= 0;    hprocess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _Pid);if(hprocess == NULL){printf("[!] OpenProcess Error,Error is:%dn", GetLastError());return FALSE;}else{printf("[*] OpenProcess Successfully!n");}_SIZE = strlen(psDllPath)+1;pAlloc = ::VirtualAllocEx(hprocess, NULL, _SIZE, MEM_COMMIT, PAGE_READWRITE);if(pAlloc == NULL){printf("[!] VirtualAllocEx Error,Error is:%dn", GetLastError());return FALSE;}else{printf("[*] VirtualAllocEx Successfully!n");}BOOL x = ::WriteProcessMemory(hprocess, pAlloc, psDllPath, _SIZE, NULL);if(FALSE == x){printf("[!] WriteMemory Error,Error is:%dn", GetLastError());return FALSE;}else{printf("[*] WriteMemory Successfully!n");}HMODULE hNtdll = LoadLibrary(L"ntdll.dll");if(hNtdll == NULL){printf("[!] LoadNTdll Error,Error is:%dn", GetLastError());return FALSE;}else{printf("[*] Load ntdll.dll Successfully!n");}pThreadFunction = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");if(pThreadFunction == NULL){printf("[!] Get LoadLibraryA Address Error,Error is:%dn", GetLastError());return FALSE;}else{printf("[*] Get LoadLibraryA Address Successfully! Address is %xn", pThreadFunction);}#ifdef _WIN64typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(PHANDLE ThreadHandle,ACCESS_MASK DesiredAccess,LPVOID ObjectAttributes,HANDLE ProcessHandle,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,ULONG CreateThreadFlags,SIZE_T ZeroBits,SIZE_T StackSize,SIZE_T MaximumStackSize,LPVOID pUnkown);#elsetypedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(PHANDLE ThreadHandle,ACCESS_MASK DesiredAccess,LPVOID ObjectAttributes,HANDLE ProcessHandle,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,BOOL CreateSuspended,DWORD dwStackSize,DWORD dw1,DWORD dw2,LPVOID pUnkown);#endiftypedef_ZwCreateThreadEx ZwCreateThreadEx= NULL;ZwCreateThreadEx= (typedef_ZwCreateThreadEx)::GetProcAddress(hNtdll, "ZwCreateThreadEx");if(ZwCreateThreadEx== NULL){printf("[!] Get ZwCreateThreadEx Address Error,Error is:%dn", GetLastError());return FALSE;}else{printf("[*] Get ZwCreateThreadEx Address Successfully! Address is %xn", ZwCreateThreadEx);}HANDLE hRemoteThread;ZwRet= ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hprocess,(LPTHREAD_START_ROUTINE)pThreadFunction, pAlloc, 0, 0, 0, 0, NULL);if(hRemoteThread == NULL){printf("[!] Creat RemoteThread Error,Error is:%dn", GetLastError());CloseHandle(hprocess);return FALSE;}printf("[*] Please wait for a moment in the process of injection:n");for(int m = 0;m<5;m++){if(EnumModules(_Pid, psDllPath)){printf("[√] Creat RemoteThread Successfully! RemoteThread Id is %xn", hRemoteThread);VirtualFreeEx(hprocess, pAlloc, 0, MEM_RELEASE);CloseHandle(hRemoteThread);CloseHandle(hprocess);FreeLibrary(hNtdll);return TRUE;}Sleep(2000);}printf("[!] DLL injection failed!nNotice:Please check that your path is absolute or correctn");VirtualFreeEx(hprocess, pAlloc, 0, MEM_RELEASE);CloseHandle(hRemoteThread);CloseHandle(hprocess);FreeLibrary(hNtdll);return FALSE;}int main(int argc, char* argv[]){if(argc == 3){EnbalePrivileges(GetCurrentProcess(), SE_DEBUG_NAME);DWORD dwPid;sscanf(argv[1],"%d", &dwPid);_InjectThread(dwPid, argv[2]);return1;}else{printf("[!] You passed in the wrong number of parameters!Please pass in two parameters.n");printf("[!] Notice:n[!] The first parameter is the PID of the target processn[!] The second parameter is the location of the injected DLL,Please enter the absolute path!");return0;}}

直接注入,看到线程创建成功就表示已经成功了。

img

用procexp也可以看一眼。

img

比如现在我们想要结束QQ进程。

img

会发现无法结束。

img

这里由于还没有写判断规则,这个任务管理器目前无法结束任何进程,也就是所有进程都无法结束,这显然用户体验是不好的。

那么如果要选择性保护进程,又应该怎么做呢。注意TerminateProcess的第一个参数,传入的是一个句柄,这个句柄需要从openprocess的返回值获得,所以我们还需要知道打开进程的句柄。

同一进程多次使用openprocess获取的句柄是不一样的。

inline hook的稳定性还是差了点,很容易让进程崩溃。根据实验:任务管理器会不断地调用openprocess这个api,不管有没有操作都会一直调用。这里就用微软更加稳定的detours库。

voidHook(){DetourTransactionBegin();DetourUpdateThread(GetCurrentThread());// 参数一是原函数地址,参数二是新函数地址DetourAttach((PVOID*)&OldOpenProcess, New_OpenProcess);DetourAttach((PVOID*)&OldTerminateProcess, New_TerminateProcess);DetourTransactionCommit();}voidUnHook(){DetourTransactionBegin();DetourUpdateThread(GetCurrentThread());// 和Hook完全一样,不同的只是将DetourAttach换成DetourDetachDetourDetach((PVOID*)&OldTerminateProcess, New_TerminateProcess);DetourDetach((PVOID*)&OldOpenProcess, New_OpenProcess);DetourTransactionCommit();}

我们自己的函数如下编写,就是做一些简单的判断,比如这里要保护的进程id为5568 :

HANDLE g_handle = NULL;HANDLE WINAPI New_OpenProcess(DWORD dwDesiredAccess,BOOL bInheritHandle,DWORD dwProcessId) {if(dwProcessId == 5568) {        g_handle = OldOpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId);return g_handle;}else{        HANDLE hHandle = OldOpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId);return hHandle;}}BOOL WINAPI New_TerminateProcess(_In_ HANDLE hProcess,_In_ UINT uExitCode) {if(g_handle == hProcess){MessageBox(NULL, L"该进程受保护!", L"Access Denied", NULL);        g_handle = NULL;returnfalse;}else{OldTerminateProcess(hProcess, uExitCode);returntrue;}}

实战|使用Windows API绕过进程保护

1635146306969.gif

实战|使用Windows API绕过进程保护

推荐阅读


实战 | 记一次攻防演练


干货 | 绕过AMSI实现免杀的研究和思路

实战 | C++ Socket详解与研究

实战 | 进程启动技术的思路和研究

点赞 在看 转发

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年11月17日09:00:00
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   实战|使用Windows API绕过进程保护https://cn-sec.com/archives/634530.html

发表评论

匿名网友 填写信息