Windows所提供給R3環(huán)的API抡爹,實(shí)質(zhì)就是對(duì)操作系統(tǒng)接口的封裝,其實(shí)現(xiàn)部分都是在R0實(shí)現(xiàn)的芒划。很多惡意程序會(huì)利用鉤子來(lái)鉤取這些API冬竟,從而達(dá)到截取內(nèi)容,修改數(shù)據(jù)的意圖∶癖疲現(xiàn)在我們使用ollydbg對(duì)ReadProcessMemory進(jìn)行跟蹤分析泵殴,查看其在R3的實(shí)現(xiàn)。
測(cè)試
od
我們首先在od里面跟一下在ring3層ReadProcessMemory的調(diào)用過(guò)程
首先在 exe 中 調(diào)用?kernel32.ReadProcessMemory函數(shù)拼苍,我們可以看到這一部分主要是call dword ptr ds:[<&KERNEL32.ReadProcessMemory>]; kernel32.ReadProcessMemory這一行代碼比較關(guān)鍵笑诅,調(diào)用了kernel32.ReadProcessMemory,繼續(xù)往里面跟
01314E3E? 8BF4? ? mov esi,esp
01314E40? 6A 00? ? push 0x0
01314E42? 6A 04? ? push 0x4
01314E44? 8D45 DC? lea eax,dword ptr ss:[ebp-0x24]
01314E47? 50? ? ? push eax
01314E48? 8B4D C4? mov ecx,dword ptr ss:[ebp-0x3C]
01314E4B? 8D548D E8? lea edx,dword ptr ss:[ebp+ecx*4-0x18]
01314E4F? 52? ? ? push edx
01314E50? 6A FF? ? push -0x1
01314E52? FF15 64B0310 call dword ptr ds:[<&KERNEL32.ReadProcessMemory>]; kernel32.ReadProcessMemory
01314E58? 3BF4? ? cmp esi,esp
在?ReadProcessMemory函數(shù) 中調(diào)用?jmp.&API-MS-Win-Core-Memory-L1-1-0.ReadProcessMemory>?函數(shù)吆你,在kenel32.dll中,mov edi,edi?是用于熱補(bǔ)丁技術(shù)所保留的俊犯,這段代碼仔細(xì)看其實(shí)除了jmp什么也沒(méi)干,繼續(xù)跟jmp
7622C1CE? 8BFF? ? ? ? mov edi,edi
7622C1D0? 55? ? ? ? ? push ebp
7622C1D1? 8BEC? ? ? ? mov ebp,esp
7622C1D3? 5D? ? ? ? ? pop ebp? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
7622C1D4? E9 F45EFCFF? ? ? jmp <jmp.&API-MS-Win-Core-Memory-L1-1-0.ReadProcessMemory>
在?API-MS-Win-Core-Memory-L1-1-0.ReadProcessMemo?中調(diào)用?KernelBase.ReadProcessMemory?函數(shù)咸包,這里的調(diào)用鏈就是從kernel32.dll到了kernelBase.dll
761F20CD? FF25 0C191F7
? ? jmp dword ptr ds:[<&API-MS-Win-Core-Memory-L1-1-0.ReadProcessMemory>; KernelBase.ReadProcessMemory
在KernelBase.ReadProcessMemory中 調(diào)用?<&ntdll.NtReadVirtualMemory>?函數(shù)桃序,將ReadProcessMemory中傳入的參數(shù)再次入棧,調(diào)用ntdll.ZwReadVirtualMemory函數(shù)烂瘫,再往里面走
75DA9A0A? 8BFF? ? mov edi,edi
75DA9A0C? 55? ? ? push ebp
75DA9A0D? 8BEC? ? mov ebp,esp
75DA9A0F? 8D45 14? lea eax,dword ptr ss:[ebp+0x14]
75DA9A12? 50? ? ? push eax
75DA9A13? FF75 14? push dword ptr ss:[ebp+0x14]
75DA9A16? FF75 10? push dword ptr ss:[ebp+0x10]
75DA9A19? FF75 0C? push dword ptr ss:[ebp+0xC]
75DA9A1C? FF75 08? push dword ptr ss:[ebp+0x8]
75DA9A1F? FF15 C411DA7
? ? ? call dword ptr ds:[<&ntdll.NtReadVirtualMemory>] ; ntdll.ZwReadVirtualMemory
在?<&ntdll.NtReadVirtualMemory>?中調(diào)用?ntdll.KiFastSystemCall?函數(shù)媒熊,這里往eax里存放了一個(gè)編號(hào),對(duì)應(yīng)在內(nèi)核中ReadProcessMemory的實(shí)現(xiàn)坟比,在?0x7FFE0300處存放了一個(gè)函數(shù)指針芦鳍,該函數(shù)指針決定了以什么方式進(jìn)入0環(huán)(中斷/快速調(diào)用)
77A162F8? B8 15010000 mov eax,0x115 // 對(duì)應(yīng)操作系統(tǒng)內(nèi)核中某一函數(shù)的編號(hào)
77A162FD? BA 0003FE7F mov edx,0x7FFE0300 // 該地方是一個(gè)函數(shù),該函數(shù)決定了什么方式進(jìn)零環(huán)
77A16302? FF12? ? call dword ptr ds:[edx] ; ntdll.KiFastSystemCall
在?ntdll.KiFastSystemCall?中 調(diào)用?sysenter
77A170B0? 8BD4? ? mov edx,esp
77A170B2? 0F34? ? sysenter
77A170B4? C3? ? ? retn
其實(shí)在ida里面整個(gè)調(diào)用鏈會(huì)更加清晰葛账,首先定位到ReadProcessMemory可以發(fā)現(xiàn)柠衅,在調(diào)用NtReadVirtualMemory之前會(huì)往參數(shù)里面壓入5個(gè)值
再到Imports模塊繼續(xù)跟NtProtectVirtualMemory可以發(fā)現(xiàn)是調(diào)用了ntdll.dll
那么我們?cè)俚絥tdll.dll里面定位,因?yàn)檫@里我直接拿的win10的ntdll.dll籍琳,在win10里面NtProtectVirtualMemory和ZwProtectVirtualMemory是同一個(gè)函數(shù)菲宴,可以看到這個(gè)地方首先也是將內(nèi)核函數(shù)的編號(hào)給了eax,然后將函數(shù)指針存入edx趋急,該函數(shù)指針決定了是以中斷方式還是快速調(diào)用方式進(jìn)入0環(huán)喝峦,然后再調(diào)用Wow64SystemServiceCall()
思路
雖然這里因?yàn)橄到y(tǒng)的原因最后調(diào)用的函數(shù)不同,但是實(shí)現(xiàn)的方法都是相同的呜达。因?yàn)槭窃趚p里面進(jìn)行實(shí)驗(yàn)谣蠢,這里就用od里面的調(diào)用進(jìn)行分析實(shí)現(xiàn)
我們希望可以在自己的代碼中直接使用?sysenter,但經(jīng)過(guò)編寫發(fā)現(xiàn)其并沒(méi)有提供這種指令查近。因此在sysenter無(wú)法直接使用的情況下眉踱,只能去調(diào)用ntdll.KiFastSystemCall函數(shù)
ntdll.KiFastSystemCall函數(shù)需要借助ntdll.NtReadVirtualMemory傳遞過(guò)來(lái)的參數(shù),然后執(zhí)行call指令霜威。我們并不希望執(zhí)行call指令執(zhí)行勋锤,因?yàn)閳?zhí)行call指令意味著又上了一層。我們希望自己的代碼中直接傳遞參數(shù)侥祭,并且直接調(diào)用調(diào)用ntdll.KiFastSystemCall函數(shù)叁执。因此我們需要模擬call指令,call指令的本質(zhì)就是將返回地址入棧矮冬,并跳轉(zhuǎn)谈宛。所以我們不需要跳轉(zhuǎn),只需要將返回地址入棧(四個(gè)字節(jié) 使用?sub esp,4?模擬)
我們內(nèi)嵌匯編代碼后胎署,需要手動(dòng)平衡棧吆录,我們只需要分析esp改變了多少(push、pop以及直接對(duì)esp的計(jì)算)琼牧。經(jīng)過(guò)分析共減少了24字節(jié)恢筝,所以代碼最后應(yīng)該有?add esp,0x18?來(lái)平衡棧
實(shí)現(xiàn)
代碼如下
// MyReadMemory.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <Windows.h>
void? MyReadMemory(HANDLE hProcess, PVOID pAddr, PVOID pBuffer, DWORD dwSize, DWORD? *dwSizeRet)
{
? ? _asm
? ? {
? ? ? ? lea eax, [ebp + 0x14]
push eax //dwSizeRet
push [ebp + 0x14] //dwSize
push [ebp + 0x10] //pBuffer
push [ebp + 0xC] //pAddr
push [ebp + 0x8] //hProcess
sub esp,4 //平衡 call NtReadProcessMemory 堆棧
? ? ? ? mov eax, 0x115
? ? ? ? mov edx, 0X7FFE0300?
? ? ? ? call dword ptr [edx]
? ? ? ? add esp, 0x18
? ? }
}
int main()
{
? ? HANDLE hProcess = 0;
? ? int t = 123;
? ? DWORD pBuffer;
? ? MyReadMemory((HANDLE)-1, (PVOID)&t, &pBuffer, sizeof(int), 0);
? ? printf("MyReadMemory : %x\n", pBuffer);
? ? ReadProcessMemory((HANDLE)-1, &t, &pBuffer, sizeof(int), 0);
? ? printf("ReadProcessMemory : %x\n", pBuffer);
? ? getchar();
? ? return 0;
}
實(shí)現(xiàn)效果如下哀卫,可以看到我們自己實(shí)現(xiàn)的函數(shù)跟調(diào)用ReadProcessMemory輸出的結(jié)果是相同的
拓展
再看下WriteProcessMemory,還是調(diào)用了ntdll.dll的NtProtectVirtualMemory
跟到NtProtectVirtualMemory后發(fā)現(xiàn)跟ReadProcessMemory的結(jié)構(gòu)相同
那么也可以進(jìn)行WriteProcessMemory的重寫
// MyWriteProcessMemory.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
void MyWriteProcessMemory(HANDLE hProcess,LPVOID lpBaseAddress,LPVOID lpBuffer,DWORD nSize,LPDWORD lpNumberOfBytesWritten)
{
_asm
{
lea eax,[ebp + 0x18]
push eax //lpNumberOfBytesWritten
push [ebp + 0x14] //nSize
push [ebp + 0x10] //lpBuffer
push [ebp + 0xC] //lpBaseAddress
push [ebp + 0x8] //hProcess
sub esp,4 //平衡 call NtWriteProcessMemory 堆棧
mov eax, 0x115
mov edx,0x7FFE0300
call dword ptr [edx]
add esp,0x18
}
}
int main(int argc, char* argv[])
{
char szBuffer[10] = "Drunkmars";
char InBuffer[10] = {0};
SIZE_T size = 0;
WriteProcessMemory((HANDLE)-1,InBuffer,szBuffer,sizeof(szBuffer)9,&size);
printf("WriteProcessMemory : %s\n",InBuffer);
MyWriteProcessMemory((HANDLE)-1,InBuffer,szBuffer,sizeof(szBuffer),&size);
printf("MyWriteProcessMemory : %s\n",InBuffer);
return 0;
}
也跟WriteProcessMemory所打印出的效果相同
進(jìn)階
在前面我們是直接通過(guò)間接call?0x7FFE0300這個(gè)地址撬槽,來(lái)實(shí)現(xiàn)進(jìn)入ring0的效果此改,我們繼續(xù)探究
_KUSER_SHARED_DATA
在 User 層和 Kernel 層分別定義了一個(gè)?_KUSER_SHARED_DATA結(jié)構(gòu)區(qū)域,用于 User 層和 Kernel 層共享某些數(shù)據(jù)侄柔,它們使用固定的地址值映射共啃,_KUSER_SHARED_DATA?結(jié)構(gòu)區(qū)域在 User 和 Kernel 層地址分別為:
User 層地址為:0x7ffe0000
Kernnel 層地址為:0xffdf0000
雖然指向的是同一個(gè)物理頁(yè),但在ring3層是只讀的暂题,在ring0層是可寫的
在0x30偏移處SystemCall存放的地址就是真正進(jìn)入ring0的實(shí)現(xiàn)方法
我們跟進(jìn)去看看移剪,這里有兩個(gè)函數(shù),一個(gè)是KiFastSystemCall即快速調(diào)用薪者,一個(gè)是KiIntSystemCall纵苛。因?yàn)樵谙到y(tǒng)版本的原因,一些操作系統(tǒng)并不支持快速調(diào)用進(jìn)ring0的指令言津,這時(shí)候就會(huì)使用到KiIntSystemCall攻人,即中斷門的形式進(jìn)入ring0
kd> u 0x7c92e4f0
ntdll!KiFastSystemCall:
7c92e4f0 8bd4? ? ? mov? edx,esp
7c92e4f2 0f34? ? ? sysenter
ntdll!KiFastSystemCallRet:
7c92e4f4 c3? ? ? ret
7c92e4f5 8da42400000000 lea? esp,[esp]
7c92e4fc 8d642400? ? lea? esp,[esp]
ntdll!KiIntSystemCall:
7c92e500 8d542408? ? lea? edx,[esp+8]
7c92e504 cd2e? ? ? int? 2Eh
7c92e506 c3? ? ? ret
那么我們?cè)撊绾闻袛喈?dāng)前系統(tǒng)是否支持快速調(diào)用呢?
當(dāng)通過(guò)eax=1來(lái)執(zhí)行cpuid指令時(shí)纺念,處理器的特征信息被放在ecx和edx寄存器中,其中edx包含了一個(gè)SEP位(11位)想括,該位指明了當(dāng)前處理器是否支持sysenter/sysexit指令陷谱,進(jìn)入od使用cpuid指令,這里為了方便查看寄存器的變化把eax置1瑟蜈,ecx和edx置0
執(zhí)行命令后烟逊,這里的edx為BFEBFBFF,拆完edx后铺根,SEP位為1宪躯,證明支持sysenter/sysexit,即調(diào)用ntdll.dll!KiFastSystemCall()這個(gè)函數(shù)進(jìn)入ring0
也可以在ida里面查看這兩個(gè)函數(shù)
進(jìn)0環(huán)需要更改CS位迂、SS访雪、ESP、EIP四個(gè)寄存器
CS的權(quán)限由3變?yōu)? 意味著需要新的CS
SS與CS的權(quán)限永遠(yuǎn)一致 需要新的SS
權(quán)限發(fā)生切換的時(shí)候掂林,堆棧也一定會(huì)切換臣缀,需要新的ESP
進(jìn)0環(huán)后代碼的位置,需要EIP
首先看一下中斷門泻帮,通過(guò)0x2E的中斷號(hào)最終進(jìn)入了KiSystemService這個(gè)內(nèi)核模塊
如果通過(guò)sysenter精置,即快速調(diào)用進(jìn)入內(nèi)核。中斷門進(jìn)0環(huán)锣杂,需要的CS脂倦、EIP在IDT表中番宁,需要查內(nèi)存(SS與ESP由TSS提供)
而CPU如果支持sysenter指令時(shí),操作系統(tǒng)會(huì)提前將CS/SS/ESP/EIP的值存儲(chǔ)在MSR寄存器中赖阻,sysenter指令執(zhí)行時(shí)蝶押,CPU會(huì)將MSR寄存器中的值直接寫入相關(guān)寄存器,沒(méi)有讀內(nèi)存的過(guò)程政供,所以叫快速調(diào)用播聪,本質(zhì)是一樣的
我們?cè)谌h(huán)執(zhí)行的api無(wú)非是一個(gè)接口,真正執(zhí)行的功能在內(nèi)核實(shí)現(xiàn)布隔,我們便可以直接重寫三環(huán)api离陶,直接sysenter進(jìn)內(nèi)核,這樣可以規(guī)避所有三環(huán)hook衅檀。
API通過(guò)中斷門進(jìn)0環(huán):
固定中斷號(hào)為0x2E招刨,CS/EIP由門描述符提供 ESP/SS由TSS提供,進(jìn)入0環(huán)后執(zhí)行的內(nèi)核函數(shù):NT!KiSystemService
API通過(guò)sysenter指令進(jìn)0環(huán):
CS/ESP/EIP由MSR寄存器提供(SS是算出來(lái)的)哀军,進(jìn)入0環(huán)后執(zhí)行的內(nèi)核函數(shù):NT!KiFastCallEntry
因?yàn)檫@里_asm不支持?sysenter指令沉眶,可以用?_emit?代替,在模擬調(diào)用CALL [0x7FFE0300]這條指令的時(shí)候需要填入調(diào)用函數(shù)的真實(shí)地址杉适,否則會(huì)報(bào)錯(cuò)0xC0000005
// sysenter.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
BOOL __stdcall MyReadProcessMemory_IntGate(HANDLE hProcess, PVOID pAddr, PVOID pBuffer, DWORD dwSize, DWORD? *dwSizeRet)
{
LONG NtStatus;
__asm
{
// 直接模擬 KiIntSystemCall
lea edx,hProcess; // 要求 edx 存儲(chǔ)最后入棧的參數(shù)
mov eax, 0xBA;
int 0x2E;
mov NtStatus, eax;
}
if (dwSizeRet != NULL)
{
*dwSizeRet = dwSize;
}
if (NtStatus < 0)
{
return FALSE;
}
return TRUE;
}
BOOL __stdcall MyReadProcessMemory_sysenter(HANDLE hProcess, PVOID pAddr, PVOID pBuffer, DWORD dwSize, DWORD? *dwSizeRet)
{
LONG NtStatus;
__asm
{
// 模擬 ReadProcessMemory
lea eax,[ebp + 0x18]
push eax //dwSizeRet
push [ebp + 0x14] //dwSize
push [ebp + 0x10] //pBuffer
push [ebp + 0xC] //pAddr
push [ebp + 0x8] //hProcess
sub esp, 4; // 模擬 ReadProcessMemory 里的 CALL NtReadVirtualMemory
// 模擬 NtReadVirtualMemory
mov eax, 0xBA;
push 0x004010EC; // 模擬 NtReadVirtualMemory 函數(shù)里的 CALL [0x7FFE0300]
// 模擬 KiFastSystemCall
mov edx, esp;
_emit 0x0F; // sysenter
_emit 0x34;
NtReadVirtualMemoryReturn:
add esp, 0xBA; // 模擬 NtReadVirtualMemory 返回到 ReadProcessMemory 時(shí)的 RETN 0x14
mov NtStatus, eax;
}
if (dwSizeRet != NULL)
{
*dwSizeRet = dwSize;
}
// 錯(cuò)誤檢查
if (NtStatus < 0)
{
return FALSE;
}
return TRUE;
}
BOOL __stdcall MyWriteProcessMemory_IntGate(HANDLE hProcess,LPVOID lpBaseAddress,LPVOID lpBuffer,DWORD nSize,LPDWORD lpNumberOfBytesWritten)
{
LONG NtStatus;
_asm
{
lea edx,hProcess;
mov eax, 0x115;
int 0x2E;
mov NtStatus, eax;
}
if (lpNumberOfBytesWritten != NULL)
{
*lpNumberOfBytesWritten = nSize;
}
if (NtStatus < 0)
{
return FALSE;
}
return TRUE;
}
BOOL __stdcall MyWriteProcessMemory_sysenter(HANDLE hProcess,LPVOID lpBaseAddress,LPVOID lpBuffer,DWORD nSize,LPDWORD lpNumberOfBytesWritten)
{
LONG NtStatus;
_asm
{
lea eax,[ebp + 0x18]
push eax //lpNumberOfBytesWritten
push [ebp + 0x14] //nSize
push [ebp + 0x10] //lpBuffer
push [ebp + 0xC] //lpBaseAddress
push [ebp + 0x8] //hProcess
sub esp,4 //平衡 call NtWriteProcessMemory 堆棧
mov eax, 0x115
push 0x004011F9; // 模擬 NtWriteVirtualMemory 函數(shù)里的 CALL [0x7FFE0300]
// 模擬 KiFastSystemCall
mov edx, esp;
_emit 0x0F; // sysenter
_emit 0x34;
NtWriteVirtualMemoryReturn:
add esp, 0x18; // 模擬 NtWriteVirtualMemory 返回到 WriteProcessMemory 時(shí)的 RETN 0x14
mov NtStatus, eax;
}
if (lpNumberOfBytesWritten != NULL)
{
*lpNumberOfBytesWritten = nSize;
}
if (NtStatus < 0)
{
return FALSE;
}
return TRUE;
}
int main(int argc, char* argv[])
{
char szBuffer[10] = "Drunkmars";
char InBuffer[10] = {0};
SIZE_T size = 0;
HANDLE hProcess = 0;
? ? int t = 123;
? ? DWORD pBuffer, dwRead;
? ? ReadProcessMemory((HANDLE)-1, &t, &pBuffer, sizeof(int), &dwRead);
? ? printf("ReadProcessMemory : %x\n", pBuffer);
MyReadProcessMemory_IntGate((HANDLE)-1, &t, &pBuffer, sizeof(int), &dwRead);
printf("MyReadProcessMemory_IntGate : %x\n", pBuffer);
MyReadProcessMemory_sysenter((HANDLE)-1, &t, &pBuffer, sizeof(int), &dwRead);
printf("MyReadProcessMemory_sysenter : %x\n", pBuffer);
WriteProcessMemory((HANDLE)-1,InBuffer,szBuffer,sizeof(szBuffer),&size);
printf("WriteProcessMemory : %s\n",InBuffer);
MyWriteProcessMemory_IntGate((HANDLE)-1,InBuffer,szBuffer,sizeof(szBuffer),&size);
printf("MyWriteProcessMemory_IntGate : %s\n",InBuffer);
MyWriteProcessMemory_sysenter((HANDLE)-1,InBuffer,szBuffer,sizeof(szBuffer),&size);
printf("MyWriteProcessMemory_sysenter : %s\n",InBuffer);
getchar();
return 0;
}
實(shí)現(xiàn)效果如下