使用Windows API繞過進程保護
前言
最近在研究某數字殺軟的時候看到有個配置選項:
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 e0 mov 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
#include
#include"tchar.h"
#include
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:%d",GetLastError());
return FALSE;
}
else
{
printf("[*] Open CurrentProcessToken Successfully!,TokenHandle is:%d", hToken);
}
// 獲取本地系統的 pszPrivilegesName 特權的LUID值
bRet = ::LookupPrivilegeValue(NULL, pszPrivilegesName, &luidValue);
if(FALSE == bRet)
{
printf("[!] LookupPrivilegeValue Error,Error is:%d", GetLastError());
return FALSE;
}
else
{
printf("[*] LookupPrivilegeValue Successfully!");
}
// 設置提升權限信息
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:%d", GetLastError());
return FALSE;
}
else
{
// 根據錯誤碼判斷是否特權都設置成功
dwRet = ::GetLastError();
if(ERROR_SUCCESS == dwRet)
{
printf("[√] ALL_ASSIGNED!");
return TRUE;
}
elseif(ERROR_NOT_ALL_ASSIGNED == dwRet)
{
printf("[!] ERROR:NOT_ALL_ASSIGNED,Error is %d", 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 %d",GetLastError());
}
me32.dwSize = sizeof(MODULEENTRY32);
if(!Module32First(hModuleSnap, &me32))
{
printf("[!] Enum Error!");
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 foundPlease check that your path is correct or absolute");
return FALSE;
}
fclose(fp);
printf("****************************************************************************");
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", GetLastError());
return FALSE;
}
else
{
printf("[*] OpenProcess Successfully!");
}
_SIZE = strlen(psDllPath)+1;
pAlloc = ::VirtualAllocEx(hprocess, NULL, _SIZE, MEM_COMMIT, PAGE_READWRITE);
if(pAlloc == NULL)
{
printf("[!] VirtualAllocEx Error,Error is:%d", GetLastError());
return FALSE;
}
else
{
printf("[*] VirtualAllocEx Successfully!");
}
BOOL x = ::WriteProcessMemory(hprocess, pAlloc, psDllPath, _SIZE, NULL);
if(FALSE == x)
{
printf("[!] WriteMemory Error,Error is:%d", GetLastError());
return FALSE;
}
else
{
printf("[*] WriteMemory Successfully!");
}
HMODULE hNtdll = LoadLibrary(L"ntdll.dll");
if(hNtdll == NULL)
{
printf("[!] LoadNTdll Error,Error is:%d", GetLastError());
return FALSE;
}
else
{
printf("[*] Load ntdll.dll Successfully!");
}
pThreadFunction = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");
if(pThreadFunction == NULL)
{
printf("[!] Get LoadLibraryA Address Error,Error is:%d", GetLastError());
return FALSE;
}
else
{
printf("[*] Get LoadLibraryA Address Successfully! Address is %x", pThreadFunction);
}
#ifdef _WIN64
typedef 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
);
#else
typedef 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
);
#endif
typedef_ZwCreateThreadEx ZwCreateThreadEx= NULL;
ZwCreateThreadEx= (typedef_ZwCreateThreadEx)::GetProcAddress(hNtdll, "ZwCreateThreadEx");
if(ZwCreateThreadEx== NULL)
{
printf("[!] Get ZwCreateThreadEx Address Error,Error is:%d", GetLastError());
return FALSE;
}
else
{
printf("[*] Get ZwCreateThreadEx Address Successfully! Address is %x", 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", GetLastError());
CloseHandle(hprocess);
return FALSE;
}
printf("[*] Please wait for a moment in the process of injection:");
for(int m = 0;m<5;m++)
{
if(EnumModules(_Pid, psDllPath))
{
printf("[√] Creat RemoteThread Successfully! RemoteThread Id is %x", hRemoteThread);
VirtualFreeEx(hprocess, pAlloc, 0, MEM_RELEASE);
CloseHandle(hRemoteThread);
CloseHandle(hprocess);
FreeLibrary(hNtdll);
return TRUE;
}
Sleep(2000);
}
printf("[!] DLL injection failed!Notice:Please check that your path is absolute or correct");
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.");
printf("[!] Notice:[!] The first parameter is the PID of the target process[!] 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換成DetourDetach
DetourDetach((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;
}
}
