1.API函数的调用过程(3环进0环)
一、Windows API
-
Application Programming Interface,简称 API 函数。
-
Windows有多少个API?
主要是存放在 C:\WINDOWS\system32 下面所有的dll -
几个重要的DLL
Kernel32.dll:最核心的功能模块,比如管理内存、进程和线程相关的函数等.
User32.dll:是Windows用户界面相关应用程序接口,如创建窗口和发送消息等.
GDI32.dll:全称是Graphical Device Interface(图形设备接口),包含用于画图和显示文本的函数.比如要显示一个程序窗口,就调用了其中的函数来画这个窗口.
Ntdll.dll:大多数API都会通过这个DLL进入内核(0环).
二、分析WriteProcessMemory(Ring3 To Ring0)
先用IDA找到WriteProcessMemory
调用了一个函数
在导入表里找到是ntdll的函数
再打开ntdll找NtProtectVirtualMemory函数,就这么几行代码
eax保存的是编号,用来找到函数
它到底是怎么进ring0的呢?那就不得不介绍_KUSER_SHARED_DATA结构了
三、_KUSER_SHARED_DATA结构
- 在 User 层和 Kernel 层分别定义了一个 _KUSER_SHARED_DATA 结构区域,用于 User 层和 Kernel 层共享某些数据
- 它们使用固定的地址值映射,_KUSER_SHARED_DATA 结构区域在 User 和 Kernel 层地址分别为:
User 层地址为:0x7ffe0000
Kernnel 层地址为:0xffdf0000
特别说明:
虽然指向的是同一个物理页,但在User 层是只读的,在Kernnel层是可写的.
并且该表由操作系统来填充
相信大家都注意到了0x7FFE0300正好是_KUSER_SHARED_DATA偏移0x300的位置
四、0x7FFE0300(SsytemCall)到底存储的是什么?
它存储的是一个函数地址,功能就是用来进Ring0
五、实验:查看CPU是否支持快速调用(大部分都支持)
可以看到EDX的第11位为1(B为1100)
当通过eax=1来执行cpuid指令时,处理器的特征信息被放在ecx和edx寄存器中,其中edx包含了一个SEP位(11位),该位指明了当前处理器知否支持sysenter/sysexit指令
支持:
KiFastSystemCall
不支持:
KiIntSystemCall
六、sysenter进0环
无论是KiFastSystemCall
还是KiIntSystemCall
都是为了提权进Ring0,进Ring0就必须换栈,换栈就必须保存原来的SS,ESP,CS,EIP寄存器,还得提供新的SS,ESP,CS,EIP,其中CS和EIP由中断门描述符来提供,ESP和SS由TSS来提供。
接下来介绍一下sysenter指令
在执行sysenter指令之前,操作系统必须指定0环的CS段、SS段、EIP以及ESP.
MSR | 索引 |
---|---|
IA32_SYSENTER_CS | 174H |
IA32_SYSENTER_ESP | 175H |
IA32_SYSENTER_EIP | 176H |
可以通过RDMSR/WRMST来进行读写(操作系统使用WRMST写该寄存器):
kd> rdmsr 174 //查看CS //SS = CS + 8
kd> rdmsr 175 //查看ESP
kd> rdmsr 176 //查看EIP
参考:Intel白皮书第二卷(搜索sysenter)
七、总结
API通过中断门进0环(KiIntSystemCall):
1) 固定中断号为0x2E
2) CS/EIP由门描述符提供 ESP/SS由TSS提供
3) 进入0环后执行的内核函数:NT!KiSystemService
API通过sysenter指令进0环(KiFastSystemCall):
1) CS/ESP/EIP由MSR寄存器提供(SS是算出来的) SS=CS+8
2) 进入0环后执行的内核函数:NT!KiFastCallEntry
内核模块:ntoskrnl.exe/ntkrnlpa.exe
二者区别:
- KiFastSystemCall(快速调用):操作系统会提前将CS/SS/ESP/EIP的值存储在MSR寄存器中,sysenter指令执行时,CPU会将MSR寄存器中的值直接写入相关寄存器,没有读内存的过程,所以叫快速调用
- KiIntSystemCall:通过中断门进0环,需要的CS、EIP在IDT表中,需要查内存(SS与ESP由TSS提供)
八、实现ReadProcessMemory和WriteProcessMemory的3环部分
BOOL ReadProcessMemory(
HANDLE hProcess, // 被读取进程的句柄
LPCVOID lpBaseAddress, // 基址 从具体何处读取
LPVOID lpBuffer, // Out类型 接收数据的Buffer地址 将读取的内容写入此处
DWORD nSize, // 读取的字节数
LPDWORD lpNumberOfBytesRead // Out类型 读取实际大小
);
BOOL WriteProcessMemory(
HANDLE hProcess, // 被读取进程的句柄
LPVOID lpBaseAddress, // 要写的内存首地址
LPVOID lpBuffer, // 指向缓冲区的指针,该缓冲区提供数据。
DWORD nSize, // 要写入的字节数
LPDWORD lpNumberOfBytesWritten // Out 实际写入的字节数
);
运行前:
运行后:
#include "stdafx.h"
#include <windows.h>
BOOL EnableDebugPrivilege()
{
HANDLE hToken;
BOOL fOk=FALSE;
if(OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken))
{
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount=1;
LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&tp.Privileges[0].Luid);
tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken,FALSE,&tp,sizeof(tp),NULL,NULL);
fOk=(GetLastError()==ERROR_SUCCESS);
CloseHandle(hToken);
}
return fOk;
}
//写进程内存(中断门调用)
BOOL WINAPI WriteProcessMemory_KiInt(HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesWritten)
{
LONG NtStatus;
__asm
{
mov eax, 0x115; //编号
lea edx,hProcess; //取第一个参数地址
int 0x2E; //中断门
mov NtStatus, eax; //返回值
}
if (lpNumberOfBytesWritten != NULL)
{
*lpNumberOfBytesWritten = nSize;
}
// 错误检查
if (NtStatus < 0)
{
return FALSE;
}
return TRUE;
}
//读进程内存(中断门调用)
BOOL WINAPI ReadProcessMemory_KiInt(HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesWritten)
{
LONG NtStatus;
__asm
{
mov eax,0xBA //KIntSystemCall需要eax存储编号
lea edx,hProcess //edx存储[esp+8]的地址
int 0x2E //中断门
mov NtStatus, eax //返回值
}
if (lpNumberOfBytesWritten != NULL)
{
*lpNumberOfBytesWritten = nSize;
}
// 错误检查
if (NtStatus < 0)
{
return FALSE;
}
return TRUE;
}
// 读进程内存(快速调用)
BOOL WINAPI ReadProcessMemory_FAST(HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesWritten)
{
LONG NtStatus;
__asm
{
lea eax, nSize
push eax
push nSize
push lpBuffer
push lpBaseAddress
push hProcess
sub esp,0x4
mov eax, 0xBA
push FromFastcallRet
mov edx,esp
_emit 0x0F
_emit 0x34
FromFastcallRet:
add esp, 0x18
mov NtStatus, eax
}
if (lpNumberOfBytesWritten != NULL)
{
*lpNumberOfBytesWritten = nSize;
}
// 错误检查
if (NtStatus < 0)
{
return FALSE;
}
return TRUE;
}
// 写进程内存(快速调用)
BOOL WINAPI WriteProcessMemory_FAST(HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesWritten)
{
LONG NtStatus;
__asm
{
lea eax, nSize
push eax
push nSize
push lpBuffer
push lpBaseAddress
push hProcess
sub esp,0x4
mov eax, 0x115
push FromFastcallRet
mov edx,esp
_emit 0x0F
_emit 0x34
FromFastcallRet:
add esp, 0x18
mov NtStatus, eax
}
if (lpNumberOfBytesWritten != NULL)
{
*lpNumberOfBytesWritten = nSize;
}
// 错误检查
if (NtStatus < 0)
{
return FALSE;
}
return TRUE;
}
int main(int argc, char* argv[])
{
EnableDebugPrivilege();
DWORD pid;
DWORD lpBaseAddress;
printf("请输入PID和地址(十六进制)\n");
scanf("%X %X",&pid,&lpBaseAddress);
wchar_t wtBufferWrite1[100] = L"WriteProcessMemory_FAST";
wchar_t wtBufferWrite2[100] = L"WriteProcessMemory_KiInt";
char lpBufferRead[100] = {0};
DWORD nSize = 10;
DWORD lpNumberOfBytesWritten;
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid);
ReadProcessMemory_FAST(hProcess, (PVOID)lpBaseAddress, lpBufferRead, nSize,&lpNumberOfBytesWritten);
printf("函数:ReadProcessMemory_FAST读取的字符串:%ls\n",lpBufferRead);
memset(lpBufferRead,0,100);
ReadProcessMemory_KiInt(hProcess, (PVOID)lpBaseAddress, lpBufferRead, nSize,&lpNumberOfBytesWritten);
printf("函数:ReadProcessMemory_KiInt读取的字符串:%ls\n",lpBufferRead);
//wcslen返回值是字符个数,不是字节数
WriteProcessMemory_FAST(hProcess,(LPCVOID)lpBaseAddress,wtBufferWrite1,2*wcslen(wtBufferWrite1),&lpNumberOfBytesWritten);
printf("函数:WriteProcessMemory_FAST写的字符串:%ls\n",wtBufferWrite1);
WriteProcessMemory_KiInt(hProcess,(LPCVOID)(lpBaseAddress),wtBufferWrite2,2*wcslen(wtBufferWrite2),&lpNumberOfBytesWritten);
printf("函数:WriteProcessMemory_KiInt写的字符串:%ls\n",wtBufferWrite2);
return 0;
}
下一节具体分析一下到底是如何进0环的