用戶(hù)層下API的逆向分析及重構(gòu)

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ù)的意圖?,F(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

ida

其實(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ò)編寫(xiě)發(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的重寫(xiě)

// 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層是可寫(xiě)的

在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,即中斷門(mén)的形式進(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

首先看一下中斷門(mén),通過(guò)0x2E的中斷號(hào)最終進(jìn)入了KiSystemService這個(gè)內(nèi)核模塊

如果通過(guò)sysenter,即快速調(diào)用進(jìn)入內(nèi)核。中斷門(mén)進(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寄存器中的值直接寫(xiě)入相關(guān)寄存器,沒(méi)有讀內(nèi)存的過(guò)程,所以叫快速調(diào)用,本質(zhì)是一樣的

我們?cè)谌h(huán)執(zhí)行的api無(wú)非是一個(gè)接口,真正執(zhí)行的功能在內(nèi)核實(shí)現(xiàn),我們便可以直接重寫(xiě)三環(huán)api,直接sysenter進(jìn)內(nèi)核,這樣可以規(guī)避所有三環(huán)hook。

API通過(guò)中斷門(mén)進(jìn)0環(huán):

固定中斷號(hào)為0x2E,CS/EIP由門(mén)描述符提供 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

代碼實(shí)現(xiàn)

因?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)效果如下

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容