Actual combat | use Windows API to bypass process protection

Keywords: github network security

Launched in Qianxin attack and Defense Community

Article address:


Recently, when studying a digital kill software, I saw a configuration option:


This self-protection actually loads the 360SelfProtection.sys driver (see the name should also have the 360SelfProtection_win10.sys file), Fenggao network In the 0 ring, the registry key and important processes are protected by hook and other means.


For example, if you want to end a core process, it will show that it cannot be ended and access is denied.



This is not to say that the permission is not enough, even the system permission is not enough. At the bottom, the API that kills the process has been hook. This should be the kernel hook. When the process you want to end is the core process, it will directly return a pop-up window that cannot terminate the process. This paper explores how to realize a process protection function. The driver will not write, but write a user layer.

Implementation principle

windows provides an API that can kill other processes: TerminateProcess. If it is the end itself, the process is ExitProcess.

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

Ending a process with the command taskkill or through GUI tools such as task manager is essentially a called TerminateProcess. Some may call a lower level ZwTerminateProcess, but the essence is the same. It just depends on the API used by the process, Pindu Yachuang   For convenience, we will use TerminateProcess for demonstration.

Use hook TerminateProcess to directly return no permission when executing this function.

Get the API address and create a new hook function

static BOOL(WINAPI* OldTerminateProcess)(    HANDLE hProcess,    UINT   uExitCode) = TerminateProcess;BOOL WINAPI New_TerminateProcess(_In_ HANDLE hProcess,_In_ UINT uExitCode) {    unhookTerminateProcess();MessageBox(NULL,L"The process is protected!", L"Access Denied",NULL);    hookTerminateProcess();returnfalse;}

Here is a 64 bit process. The hard coding to be changed is 12 bytes, and there is no need to calculate the offset.

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

Change the transfer address of the original function to the jump address of our own function

void hookTerminateProcess() {    DWORD dwTerminateProcessOldProtect;    BYTE pData[12] = { 0x48,0xb8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xFF,0xE0};    ULONGLONG ullNewFuncAddr = (ULONGLONG)New_TerminateProcess;//Save the original bytes:: RtlCopyMemory(g_OldTerminateProcess, OldTerminateProcess, sizeof(pData))// Change permission virtualprotect (oldterminateprocess, 12, page_execute_readwrite, & dwterminateprocess oldprotect)// Change the original machine code to the address of our own function:: rtlcopymemory (& pdata [2], & ullnewfuncaddr, sizeof (ullnewfuncaddr));:: RtlCopyMemory(OldTerminateProcess, pData, sizeof(pData));// Restore attribute virtualprotect (oldterminateprocess, 12, dwterminateprocess, oldprotect, & dwterminateprocess, oldprotect);}

Restore the hard coding and restore the transfer address.

void unhookTerminateProcess() {    DWORD dwOldProtect = NULL;//Change to writeable attribute virtualprotect (oldterminateprocess, 12, page_execute_readwrite, & dwoldprotect)// Restore the original hard coded RtlCopyMemory(OldTerminateProcess, g_OldTerminateProcess, sizeof(g_OldTerminateProcess))// Restore attribute virtualprotect (oldterminateprocess, 12, dwoldprotect, & dwoldprotect);}

The most convenient is to write a dll, which can be injected into the task manager and run as a thread.

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;}

Injection here is also for hands-on training. I wrote an injection program slightly. For future methods, I wrote an injection that breaks through 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;// Open the process token and obtain the process token handle BRET =:: openprocesstoken (hProcess, token_adjust_privileges, & htoken); If (false = = BRET) {printf ("[!] open currentprocesstoken error, error is:% d \ n", getlasterror()); return false;}else{printf ("[*] open currentprocesstoken successfully!, tokenhandle is:% d \ n", htoken);} / / get the LUID value of pszPrivilegesName privilege of the local system BRET =:: lookupprivilegevalue (null, pszPrivilegesName, & luidvalue); If (false = = BRET) {printf ("[!] lookupprivilegevalue error, error is:% d \ n", getlasterror()); return false;}else{printf ("[*] lookupprivilegevalue successfully! \ n");} / / set the promotion permission information TokenPrivileges. Privilegecount = 1; tokenPrivileges.Privileges[0].LUID= luidValue;     tokenPrivileges.Privileges[0].Attributes= SE_ PRIVILEGE_ ENABLED;//  Promote process token access authority BRET =:: AdjustTokenPrivileges (htoken, false, & TokenPrivileges, 0, null, null); If (false = = BRET) {printf ("[!] AdjustTokenPrivileges error, error is:% d \ n", getlasterror()); return false;} else {/ / judge whether the privileges are set successfully according to the error code dwret =:: getlasterror(); if (error_success = = dwret) {printf ("[√] all_assigned! \ n"); return true;} else if (error_not_all_assigned = = dwret) {printf ("[!]  ERROR:NOT_ALL_ASSIGNED,Error is %d\n", dwRet);return FALSE;}}return FALSE;} DWORD enummodules (DWORD Hpid, lpcstr hmoudlepath) {wchar szbuffer [max_path] = {0}; mbstowcs (szbuffer, hmoudlepath, max_path); / / list all modules through pid. Hmodulesnap = invalid_handle_value; moduleentry32 me32; / / set a snapshot for the module information referenced by the process. 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 %d\n",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 found\nPlease check that your path is correct or absolute\n");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:%d\n", 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:%d\n", GetLastError());return FALSE;}else{printf("[*]  VirtualAllocEx Successfully!\n");}BOOL x = ::WriteProcessMemory(hprocess, pAlloc, psDllPath, _SIZE, NULL);if(FALSE == x){printf("[!] WriteMemory Error,Error is:%d\n", GetLastError());return FALSE;}else{printf("[*] WriteMemory Successfully!\n");}HMODULE hNtdll = LoadLibrary(L"ntdll.dll");if(hNtdll == NULL){printf("[!] LoadNTdll Error,Error is:%d\n" , 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:%d\n", GetLastError());return FALSE;}else{printf("[*]  Get LoadLibraryA Address Successfully! Address is %x\n", 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:%d\n", GetLastError());return FALSE;}else{printf("[*] Get ZwCreateThreadEx Address Successfully! Address is %x\n", 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:%d\n", 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 %x\n", 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 correct\n");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 process\n[!] The second parameter is the location of the injected DLL,Please enter the absolute path!");return0;}}

Direct injection. If you see that the thread is created successfully, it means that it has been successfully created.


You can also take a look with procexp.


For example, now we want to end the QQ process.


Will find it impossible to end.


Since no judgment rule has been written here, the task manager cannot end any process, that is, all processes cannot end. Obviously, the user experience is not good.

So what should we do if we want to selectively protect the process. Note that the first parameter of TerminateProcess passes in a handle, which needs to be obtained from the return value of openprocess, so we also need to know the handle of the open process.

The handles obtained by the same process using openprocess multiple times are different.

The stability of inline hook is still poor, which is easy to crash the process. According to the experiment: the task manager will constantly call the api openprocess, whether there is an operation or not. Here we use Microsoft's more stable detours library.

voidHook(){DetourTransactionBegin();DetourUpdateThread(GetCurrentThread());// Parameter 1 is the address of the original function, and parameter 2 is the address of the new function detourattach ((pvoid *) & oldopenprocess, new_openprocess); DetourAttach((PVOID*)&OldTerminateProcess, New_TerminateProcess); DetourTransactionCommit();} Void unhook() {detourtransactionbegin(); detourupdatethread (getcurrentthread()); / / it's exactly the same as Hook, except that detouratach is replaced by detourdetachdetourdetach ((pvoid *) & oldterminateprocess, new_terminateprocess); detourdetach ((pvoid *) & oldopenprocess, new_openprocess); detourtransactioncommit();}

Our own functions are written as follows to make some simple judgments. For example, the process id to be protected here is 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"The process is protected!", L"Access Denied", NULL);        g_handle = NULL;returnfalse;}else{OldTerminateProcess(hProcess, uExitCode);returntrue;}}


Posted by KPH71 on Tue, 16 Nov 2021 20:12:39 -0800