【技术分享】x86系统调用(上)

admin 2025年7月3日03:09:04评论5 views字数 6191阅读20分38秒阅读模式

【技术分享】x86系统调用(上)

01
windows API

Application Programming Interface,简称 API 函数。

Windows有多少个API?

主要是存放在 C:WINDOWSsystem32下面所有的dll

几个重要的DLL:

  • Kernel32.dll:最核心的功能模块,比如管理内存、进程和线程相关的函数等.

  • User32.dll:是Windows用户界面相关应用程序接口,如创建窗口和发送消息等.

  • GDI32.dll:全称是Graphical Device Interface(图形设备接口),包含用于画图和显示文本的函数.比如要显示一个程序窗口,就调用了其中的函数来画这个窗口.

  • Ntdll.dll:大多数API都会通过这个DLL进入内核(0环).

02
分析ReadProcessMemory

该API在Kernel32.dll中导出。

【技术分享】x86系统调用(上)

通过IDA分析可以看到,在函数中又调用了另一个导入的函数。

【技术分享】x86系统调用(上)

kernel32.dll的导入函数中,准确的说在IAT表中,可以找到是用的哪个dll提供的函数。

【技术分享】x86系统调用(上)

可以看到是ntdll.dll,那么我们继续跟踪ntdll.dll。

【技术分享】x86系统调用(上)

在ntdll中,可以看到先传入给eax一个值,这个值实际上是个索引号。然后再call了0x7FFE0300这个值。

这个地址可以看到他是写死的,那么就意味着任何一个exe加载这个dll,都会去call这个位置,这个位置是所有进程共享的。实际上这里是一个结构体(_KUSER_SHARED_DATA),call的是其中一个成员。

kd> dt _KUSER_SHARED_DATA 0x7ffe0000

【技术分享】x86系统调用(上)

并且这个KUSER_SHARED_DATA结构体是共享的:在 User 层和 Kernel 层分别定义了一个 _KUSER_SHARED_DATA 结构区域,用于 User 层和 Kernel 层共享某些数据。

_它们使用固定的地址值映射,_KUSER_SHARED_DATA 结构区域在 User 和 Kernel 层地址分别为:

  • User 层地址为:0x7ffe0000

  • Kernel 层地址为:0xffdf0000

虽然指向的是同一个物理页,但在User 层是只读的,在Kernel层是可写的。

可以再windbg通过指令查看:

kd> !vtop 0a5c03c0 7ffe0000X86VtoP: Virt 7ffe0000, pagedir a5c03c0X86VtoP: PAE PDPE a5c03c8 - 0000000010dca001X86VtoP: PAE PDE 10dcaff8 - 0000000010e3e067X86VtoP: PAE PTE 10e3ef00 - 8000000000041025X86VtoP: PAE Mapped phys 41000Virtual address 7ffe0000 translates to physical address 41000.
物理地址为41000

PTE的属性最后是5,即为0101,R/W位为0,则属性为可写。

同样的查看kernel层:

kd> !vtop 0a5c03c0 ffdf0000X86VtoP: Virt ffdf0000, pagedir a5c03c0X86VtoP: PAE PDPE a5c03d8 - 0000000011448001X86VtoP: PAE PDE 11448ff0 - 000000000038f163X86VtoP: PAE PTE 38ff80 - 0000000000041163X86VtoP: PAE Mapped phys 41000Virtual address ffdf0000 translates to physical address 41000.
指向的是同一个物理页。

PTE的属性最后是3,即为0011,R/W位为1,可读可写。

这就意味着在三环,我们无法通过hook这个地址来hook所有进程的执行流,但当然是拦不住我们的。

在我们了解了KUSER_SHARED_DATA结构体后,就可以知道call的实际上是Systemcall的地址,通过反汇编查看

kd>  u 0x7c92e4f0ntdll!KiFastSystemCall:7c92e4f0 8bd4            mov     edx,esp7c92e4f2 0f34            sysenterntdll!KiFastSystemCallRet:7c92e4f4 c3              ret7c92e4f5 8da42400000000  lea     esp,[esp]7c92e4fc 8d642400        lea     esp,[esp]ntdll!KiIntSystemCall:7c92e500 8d542408        lea     edx,[esp+8]7c92e504 cd2e            int     2Eh7c92e506 c3              ret

通过sysenter指令(快速调用)进入0环。操作系统会在系统启动的时候在KUSER_SHARED_DATA结构体的+300的位置,写入一个函数,这个函数就是KiFastSystemCall或者KiIntSystemCall。

当通过eax=1来执行cpuid指令时,处理器的特征信息被放在ecx和edx寄存器中,其中edx包含了一个SEP位(11位),该位指明了当前处理器知否支持sysenter/sysexit指令。

支持:ntdll.dll!KiFastSystemCall()

不支持:ntdll.dll!KiIntSystemCall()。该方式通过中断门进入0环。

kd> u KiIntSystemCallntdll!KiIntSystemCall:7c92e500 8d542408        lea     edx,[esp+8]7c92e504 cd2e            int     2Eh7c92e506 c3              ret7c92e507 90              nopntdll!RtlRaiseException:7c92e508 55              push    ebp7c92e509 8bec            mov     ebp,esp7c92e50b 9c              pushfd7c92e50c 81ecd0020000    sub     esp,2D0h

因为我们比较了解中断门,我们先看看中断门是怎么进入0环的。

kd> dq 8003f400 + 0x2e*88003f570  8053ee00`0008e481 80548e00`000817808003f580  80538e00`0008db40 80538e00`0008db4a8003f590  80538e00`0008db54 80538e00`0008db5e8003f5a0  80538e00`0008db68 80538e00`0008db728003f5b0  80538e00`0008db7c 806d8e00`000827288003f5c0  80538e00`0008db90 80538e00`0008db9a8003f5d0  80538e00`0008dba4 80538e00`0008dbae8003f5e0  80538e00`0008dbb8 806d8e00`00083b70

该描述符对应的eip是8053e481,反汇编查看。

【技术分享】x86系统调用(上)

这里就已经进入到内核模块,函数为KiSystemService。

从r3到r0必然是需要提权的,替换的寄存器有:CS,EIP,SS,ESP(SS与ESP由TSS提供)。这是通过中断门的方式。

如果通过sysenter,即快速调用进入内核。

操作系统会提前将CS/SS/ESP/EIP的值存储在MSR寄存器中,sysenter指令执行时,CPU会将MSR寄存器中的值直接写入相关寄存器,没有读内存的过程,所以叫快速调用,本质是一样的!

【技术分享】x86系统调用(上)

MSR寄存器存储了很多值,微软只公布了一小部分。

我们可以通过RDMSR/WRMST来进行读写(操作系统使用WRMST写该寄存器):

kd> rdmsr 174   //查看CSkd> rdmsr 175   //查看ESPkd> rdmsr 176   //查看EIPSS是被写死的,算出来的。cs=8 --》ss=0x10

【技术分享】x86系统调用(上)

所以一个是通过内存获取(IDT TSS),一个是通过寄存器获取。本质上没有区别,只是效率上的区别。

03
总 结

我们在三环执行的api无非是一个接口,真正执行的功能在内核实现,我们便可以直接重写三环api,直接sysenter进内核,这样可以规避所有三环hook。

API通过中断门进0环:

  1. 固定中断号为0x2E

  2. CS/EIP由门描述符提供 ESP/SS由TSS提供

  3. 进入0环后执行的内核函数:NT!KiSystemService

API通过sysenter指令进0环:

1) CS/ESP/EIP由MSR寄存器提供(SS是算出来的)
2) 进入0环后执行的内核函数:NT!KiFastCallEntry

04
实 验

自己实现直接通过sysenter 和 int 2e直接进入0环

这种方式可以绕过所有的三环钩子

  • sysenter

#include "StdAfx.h"#include <windows.h>#include <stdio.h>#include <iostream>#include <stdlib.h>using namespace std;BOOL __stdcall My_ReadProcessMemory(HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead);#define EXIT_ERROR(x)                                     do                                                    {                                                         cout << "error in line " << __LINE__ << endl;         printf("errcode = %dn", GetLastError());             cout << x;                                            system("pause");                                      exit(EXIT_FAILURE);                               } while (0)int main(){    int pid = 0;    cout << "请输入要读取的进程的PID:";    cin >> pid;    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);    if (hProcess == NULL)        EXIT_ERROR("hProcess == NULL!");    WORD t;    DWORD dwSizeRead;    ReadProcessMemory(hProcess, (LPCVOID)0x00400000,                      &t, sizeof WORD, &dwSizeRead);    cout << hex << t << " " << dwSizeRead << endl;    getchar();    system("pause");    My_ReadProcessMemory(hProcess, (LPCVOID)0x00400000,                         &t, sizeof WORD, &dwSizeRead);    cout << hex << t << " " << dwSizeRead;    getchar();    system("pause");    return 0;}BOOL __stdcall My_ReadProcessMemory(HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead){    DWORD NtStatus;    __asm {        lea eax, [nSize]        push eax        push nSize // nsize值入栈        push lpBuffer // buffer入栈        push lpBaseAddress // lpBaseAddress入栈        push hProcess // hProcess入栈        sub esp, 0x04; // 模拟 ReadProcessMemory 里的 CALL NtReadVirtualMemory        mov  eax, 0BAh  // NtReadVirtualMemory               push 0x00401EBC //NtReadRet的地址        // kifastcall        mov    edx,esp        _emit 0x0f        _emit 0x34 // 没有sysenter,要硬编码0x0f34NtReadRet:        add esp, 0x18;                     // 模拟 NtReadVirtualMemory 返回到 ReadProcessMemory 时的 RETN 0x14        mov NtStatus, eax;    }    printf("nsize = %d, status = %dn", nSize, NtStatus);    return 0;}
【技术分享】x86系统调用(上)
  • int 2E

#include "StdAfx.h"#include <windows.h>#include <stdio.h>#include <iostream>#include <stdlib.h>using namespace std;BOOL __stdcall My_ReadProcessMemory_INT(HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead);#define EXIT_ERROR(x)                                     do                                                {                                                     cout << "error in line " << __LINE__ << endl;     printf("errcode = %dn", GetLastError());         cout << x;                                        system("pause");                                  exit(EXIT_FAILURE);                               } while (0)int main(){    int pid = 0;    cout << "请输入要读取的进程的PID:";    cin >> pid;    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);    if (hProcess == NULL)        EXIT_ERROR("hProcess == NULL!");    WORD t;    DWORD dwSizeRead;    ReadProcessMemory(hProcess, (LPCVOID)0x00400000,        &t, sizeof WORD, &dwSizeRead);    cout << hex << t << " " << dwSizeRead << endl;    getchar();    system("pause");    My_ReadProcessMemory_INT(hProcess, (LPCVOID)0x00400000,        &t, sizeof WORD, &dwSizeRead);    cout << hex << t << " " << dwSizeRead;    getchar();    system("pause");    return 0;}BOOL WINAPI My_ReadProcessMemory_INT(HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead){    DWORD NtStatus;    __asm    {        mov eax, 0xBA;        lea     edx, hProcess // edx里面存储最后入栈的参数            int     2Eh            mov NtStatus, eax    }    *lpNumberOfBytesRead = nSize;    if (NtStatus) return NtStatus;    else return 0;}

【技术分享】x86系统调用(上)

【技术分享】x86系统调用(上)
- 结尾 -
精彩推荐
【技术分享】针对Office宏病毒的高级检测
【技术分享】Tomcat 内存马技术分析— Filter型
【技术分享】ZN600电信光猫分析 — 研精钩深
【技术分享】x86系统调用(上)
戳“阅读原文”查看更多内容

原文始发于微信公众号(安全客):【技术分享】x86系统调用(上)

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年7月3日03:09:04
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【技术分享】x86系统调用(上)https://cn-sec.com/archives/801516.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息