Windows7 x64系統調用分析


目錄

  • 0x01 Syscall & Sysret
  • 0x02 KiSystemCall64
    •  構造TrapFrame
    •  _kthread->header->DebugActive.ActiveDR7|Instrumented
    •  user-mode scheduling(???這部分暫時還不明白)
    •  SSDT & ShadowSSDT API地址計算
    •  KiInitiateUserApc Apc分發
    •  InstrumentationCallback用戶層回調

0x01 Syscall & Sysret

  • Syscall

 

Syscall

IF (CS.L ≠ 1 ) or (IA32_EFER.LMA ≠ 1) or (IA32_EFER.SCE ≠ 1) (* Not in 64-Bit Mode or SYSCALL/SYSRET not enabled in IA32_EFER *) THEN #UD; FI; RCX ← RIP; (* Will contain address of next instruction *) RIP ← IA32_LSTAR; R11 ← RFLAGS; RFLAGS ← RFLAGS AND NOT(IA32_FMASK); CS.Selector ← IA32_STAR[47:32] AND FFFCH (* Operating system provides CS; RPL forced to 0 *) (* Set rest of CS to a fixed value *) CS.Base ← 0; (* Flat segment *) CS.Limit ← FFFFFH; (* With 4-KByte granularity, implies a 4-GByte limit *) CS.Type ← 11; (* Execute/read code, accessed *) CS.S ← 1; CS.DPL ← 0; CS.P ← 1; CS.L ← 1; (* Entry is to 64-bit mode *) CS.D ← 0; (* Required if CS.L = 1 *) CS.G ← 1; (* 4-KByte granularity *) CPL ← 0;

  從上面的Syscall的偽代碼可以看到,rcx儲存的是下一條指令的地址,所以windows在Syscall之前會將rcx儲存到r10中。r11中儲存的是rflags。rip是從IA32_LSTAR中獲取的,cs的選擇子是從IA32_STAR[47:32] 讀取的,但是cs的其他段屬性並沒有根據選擇子在內存中進行讀取,而是設置的固定值,因此OS有義務讓段選擇對應的段描述符與設置的固定值相對應。可以發現上面偽代碼並沒有進行堆棧切換,因此需要OS在系統調用例程中自己進行堆棧切換。

  • Sysret

IF (CS.L ≠ 1 ) or (IA32_EFER.LMA ≠ 1) or (IA32_EFER.SCE ≠ 1)
(* Not in 64-Bit Mode or SYSCALL/SYSRET not enabled in IA32_EFER *)
THEN #UD; FI;
IF (CPL ≠ 0) THEN #GP(0); FI;
IF (operand size is 64-bit)
THEN (* Return to 64-Bit Mode *)
IF (RCX is not canonical) THEN #GP(0);
RIP ← RCX;
ELSE (* Return to Compatibility Mode *)
RIP ← ECX;
FI;
RFLAGS ← (R11 & 3C7FD7H) | 2; (* Clear RF, VM, reserved bits; set bit 2 *)
IF (operand size is 64-bit)
THEN CS.Selector ← IA32_STAR[63:48]+16;
ELSE CS.Selector ← IA32_STAR[63:48];
FI;
CS.Selector ← CS.Selector OR 3; (* RPL forced to 3 *)
(* Set rest of CS to a fixed value *)
CS.Base ← 0; (* Flat segment *)
CS.Limit ← FFFFFH; (* With 4-KByte granularity, implies a 4-GByte limit *)
CS.Type ← 11; (* Execute/read code, accessed *)
CS.S ← 1;
CS.DPL ← 3;
CS.P ← 1;
IF (operand size is 64-bit)
THEN (* Return to 64-Bit Mode *)
CS.L ← 1; (* 64-bit code segment *)
CS.D ← 0; (* Required if CS.L = 1 *)
ELSE (* Return to Compatibility Mode *)
CS.L ← 0; (* Compatibility mode *)
CS.D ← 1; (* 32-bit code segment *)
FI;
CS.G ← 1; (* 4-KByte granularity *)
CPL ← 3;
SS.Selector ← (IA32_STAR[63:48]+8) OR 3; (* RPL forced to 3 *)
(* Set rest of SS to a fixed value *)
SS.Base ← 0; (* Flat segment *)
SS.Limit ← FFFFFH; (* With 4-KByte granularity, implies a 4-GByte limit *)
SS.Type ← 3; (* Read/write data, accessed *)
SS.S ← 1;
SS.DPL ← 3;
SS.P ← 1;
SS.B ← 1; (* 32-bit stack segment*)
SS.G ← 1; (* 4-KByte granularity *)

  SYSRET loads the CS and SS selectors with values derived from bits 63:48 of the IA32_STAR MSR. However, the CS and SS descriptor caches are not loaded from the descriptors (in GDT or LDT) referenced by those selectors. Instead, the descriptor caches are loaded with fixed values.

  • rip<-rcx
  • rflags<-(R11 & 3C7FD7H) | 2

0x02 KiSystemCall64

    • 構造TrapFrame
.text:000000014007F640                 swapgs                  ; switch to _kpcr
.text:000000014007F643                 mov     gs:10h, rsp     ; UserRsp
.text:000000014007F64C                 mov     rsp, gs:1A8h    ; RspBase
.text:000000014007F655                 push    2Bh ; '+'       ; ------------------
.text:000000014007F655                                         ;            ss
.text:000000014007F655                                         ;            rsp
.text:000000014007F655                                         ;            rflags
.text:000000014007F655                                         ;            cs
.text:000000014007F655                                         ; rsp-->     rip
.text:000000014007F657                 push    qword ptr gs:10h
.text:000000014007F65F                 push    r11
.text:000000014007F661                 push    33h ; '3'
.text:000000014007F663                 push    rcx             ; ------------------
.text:000000014007F664                 mov     rcx, r10        ; move first param from r10 to rcx
.text:000000014007F667                 sub     rsp, 8          ; reserve 8byte for errorCode
.text:000000014007F66B                 push    rbp             ; 以上是構造異常發生時的堆棧環境
.text:000000014007F66C                 sub     rsp, 158h
.text:000000014007F673                 lea     rbp, [rsp+80h]
.text:000000014007F67B                 mov     [rbp+0C0h], rbx
.text:000000014007F682                 mov     [rbp+0C8h], rdi
.text:000000014007F689                 mov     [rbp+0D0h], rsi

  首先在函數開頭調用swapgs將指向_teb的gs指向_kpcr,然后切換0環堆棧。在堆棧上構造出類似於中斷/異常的環境。然后sub rsp, 158h給_TrapFrame申請空間,后面一段代碼都是在填充_TrapFrame。這里給出ReactOS中的amd64下的_TrapFrame結構。

typedef struct _KTRAP_FRAME
{
    UINT64 P1Home;
    UINT64 P2Home;
    UINT64 P3Home;
    UINT64 P4Home;
    UINT64 P5;
    CHAR PreviousMode;
    UCHAR PreviousIrql;
    UCHAR FaultIndicator;
    UCHAR ExceptionActive;
    ULONG MxCsr;
    UINT64 Rax;
    UINT64 Rcx;
    UINT64 Rdx;
    UINT64 R8;
    UINT64 R9;
    UINT64 R10;
    UINT64 R11;
    union
    {
        UINT64 GsBase;
        UINT64 GsSwap;
    };
    M128A Xmm0;
    M128A Xmm1;
    M128A Xmm2;
    M128A Xmm3;
    M128A Xmm4;
    M128A Xmm5;
    union
    {
        UINT64 FaultAddress;
        UINT64 ContextRecord;
        UINT64 TimeStampCKCL;
    };
    UINT64 Dr0;
    UINT64 Dr1;
    UINT64 Dr2;
    UINT64 Dr3;
    UINT64 Dr6;
    UINT64 Dr7;
    union
    {
        struct
        {
            UINT64 DebugControl;
            UINT64 LastBranchToRip;
            UINT64 LastBranchFromRip;
            UINT64 LastExceptionToRip;
            UINT64 LastExceptionFromRip;
        };
        struct
        {
            UINT64 LastBranchControl;
            ULONG LastBranchMSR;
        };
    };
    USHORT SegDs;
    USHORT SegEs;
    USHORT SegFs;
    USHORT SegGs;
    UINT64 TrapFrame;
    UINT64 Rbx;
    UINT64 Rdi;
    UINT64 Rsi;
    UINT64 Rbp;
    union
    {
        UINT64 ErrorCode;
        UINT64 ExceptionFrame;
        UINT64 TimeStampKlog;
    };
    UINT64 Rip;
    UINT64 SegCs;
    UINT64 EFlags;
    UINT64 Rsp;
    UINT64 SegSs;
} KTRAP_FRAME, *PKTRAP_FRAME;
    • _kthread->header->DebugActive.ActiveDR7|Instrumented
.text:000000014007F6B1                 cmp     byte ptr [rbx+3], 0 ; Debug_active
.text:000000014007F6B5                 mov     word ptr [rbp+80h], 0 ; dr7
.text:000000014007F6BE                 jz      no_debug
.text:000000014007F6C4                 mov     [rbp-50h], rax
.text:000000014007F6C8                 mov     [rbp-48h], rcx
.text:000000014007F6CC                 mov     [rbp-40h], rdx
.text:000000014007F6D0                 test    byte ptr [rbx+3], 3 ; ActiveDR7|Instrumented
.text:000000014007F6D0                                         ; _kprocess.InstrumentationCallback回調被設置時,_kthread.header.DebugActive.Instrumented
                                      會被置位
.text:000000014007F6D4 mov [rbp-38h], r8 .text:000000014007F6D8 mov [rbp-30h], r9 .text:000000014007F6DC jz short loc_14007F6E3 ; 如果沒有設置InstrumentationCallback或者dr7沒有設置, .text:000000014007F6DC ; 則不會保存調試寄存器 .text:000000014007F6DE call KiSaveDebugRegisterState ; 保存Last Branch Recording(LBR)的五個msr寄存器和dr調試寄存器 .text:000000014007F6DE ; struct .text:000000014007F6DE ; { .text:000000014007F6DE ; UINT64 DebugControl; .text:000000014007F6DE ; UINT64 LastBranchToRip; .text:000000014007F6DE ; UINT64 LastBranchFromRip; .text:000000014007F6DE ; UINT64 LastExceptionToRip; .text:000000014007F6DE ; UINT64 LastExceptionFromRip; .text:000000014007F6DE ; };

  這段代碼可以看到它檢測了_kthread.DebugActive是否為0,如果為0,則會直接跳轉到查SSDT表的代碼,如果不為0,則會進行下一步判斷其是否等於3,通過在Windbg中查看,可以發現這個DebugActive其實是一個位域結構體,3代表ActiveDR7 | Instrumented,也就是說如果dr7沒有設置或者沒有設置InstrumentationCallback,那么在系統調用中將不會保存dr寄存器。

  分析到這里,我終於明白了上次寫的那個逆向題中在第一個seh中設置了dr0~dr3,但是在第二個seh中檢查卻發現dr0~dr3都為0,原來是因為沒有設置dr7,在系統調用時系統認為沒有設置dr7,也就是沒有啟用硬件斷點,自然dr寄存器就不用保存。

    [+0x003] DebugActive      : 0x3 [Type: unsigned char]
    [+0x003 ( 0: 0)] ActiveDR7        : 0x1 [Type: unsigned char]
    [+0x003 ( 1: 1)] Instrumented     : 0x1 [Type: unsigned char]
    [+0x003 ( 5: 2)] Reserved2        : 0x0 [Type: unsigned char]
    [+0x003 ( 6: 6)] UmsScheduled     : 0x0 [Type: unsigned char]
    [+0x003 ( 7: 7)] UmsPrimary       : 0x0 [Type: unsigned char]
    • user-mode scheduling(???這部分暫時還不明白)
.text:000000014007F6B1                 cmp     byte ptr [rbx+3], 0 ; Debug_active
.text:000000014007F6B5                 mov     word ptr [rbp+80h], 0 ; dr7
.text:000000014007F6BE                 jz      no_debug
.text:000000014007F6C4                 mov     [rbp-50h], rax
.text:000000014007F6C8                 mov     [rbp-48h], rcx
.text:000000014007F6CC                 mov     [rbp-40h], rdx
.text:000000014007F6D0                 test    byte ptr [rbx+3], 3 ; ActiveDR7|Instrumented
.text:000000014007F6D0                                         ; _kprocess.InstrumentationCallback回調被設置時,_kthread.header.DebugActive.Instrumented
                                      會被置位
.text:000000014007F6D4 mov [rbp-38h], r8 .text:000000014007F6D8 mov [rbp-30h], r9 .text:000000014007F6DC jz short loc_14007F6E3 ; 如果沒有設置InstrumentationCallback或者dr7沒有設置, .text:000000014007F6DC ; 則不會保存調試寄存器 .text:000000014007F6DE call KiSaveDebugRegisterState ; 保存Last Branch Recording(LBR)的五個msr寄存器和dr調試寄存器 .text:000000014007F6DE ; struct .text:000000014007F6DE ; { .text:000000014007F6DE ; UINT64 DebugControl; .text:000000014007F6DE ; UINT64 LastBranchToRip; .text:000000014007F6DE ; UINT64 LastBranchFromRip; .text:000000014007F6DE ; UINT64 LastExceptionToRip; .text:000000014007F6DE ; UINT64 LastExceptionFromRip; .text:000000014007F6DE ; }; .text:000000014007F6E3 .text:000000014007F6E3 loc_14007F6E3: ; CODE XREF: KiSystemCall64+9C↑j .text:000000014007F6E3 test byte ptr [rbx+3], 80h ; kthread._DISPATCHER_HEADER.UmsPrimary .text:000000014007F6E7 jz short loc_14007F72B ; kthread._DISPATCHER_HEADER.UmsScheduled .text:000000014007F6E9 mov ecx, 0C0000102h ; IA32_KERNEL_GS_BASE .text:000000014007F6EE rdmsr .text:000000014007F6F0 shl rdx, 20h .text:000000014007F6F4 or rax, rdx .text:000000014007F6F7 cmp [rbx+0B8h], rax ; _kthread.teb .text:000000014007F6FE jz short loc_14007F72B ; kthread._DISPATCHER_HEADER.UmsScheduled .text:000000014007F700 cmp [rbx+1B0h], rax  ; kthread.TebMappedLowVa .text:000000014007F707 jz short loc_14007F72B ; kthread._DISPATCHER_HEADER.UmsScheduled .text:000000014007F709 mov rdx, [rbx+1B8h] .text:000000014007F710 bts dword ptr [rbx+4Ch], 0Bh .text:000000014007F715 dec word ptr [rbx+1C4h] .text:000000014007F71C mov [rdx+80h], rax .text:000000014007F723 sti .text:000000014007F724 call KiUmsCallEntry ; user-mode scheduling

  如果_kthread.DebugActive不為0並且其第7位UmsPrimary置位,如果IA32_KERNEL_GS_BASEkthread.TebMappedLowVa  &  _kthread.teb相等則會調用KiUmsCallEntry,這個函數似乎於user-mode scheduling有關,這一機制允許用戶在用戶層進行任務調度。(具體的東西我也不懂......)

    • SSDT & ShadowSSDT API地址計算
text:000000014007F750                 sti
.text:000000014007F751                 mov     [rbx+1E0h], rcx ; _kthread.FirstArgument
.text:000000014007F758                 mov     [rbx+1F8h], eax ; _kthread.SystemCallNumber
.text:000000014007F75E
.text:000000014007F75E KiSystemServiceStart:                   ; DATA XREF: KiServiceInternal+5A↑o
.text:000000014007F75E                                         ; .data:00000001401EE648↓o
.text:000000014007F75E                 mov     [rbx+1D8h], rsp ; _kthread._KTRAP_FRAME
.text:000000014007F765                 mov     edi, eax
.text:000000014007F767                 shr     edi, 7
.text:000000014007F76A                 and     edi, 20h        ; 判斷傳入的序號是不是0x1xxx類型的,是否是ShdowSSDT的api
.text:000000014007F76D                 and     eax, 0FFFh
.text:000000014007F772
.text:000000014007F772 KiSystemServiceRepeat:                  ; CODE XREF: KiSystemCall64+47B↓j
.text:000000014007F772                 lea     r10, KeServiceDescriptorTable
.text:000000014007F779                 lea     r11, KeServiceDescriptorTableShadow
.text:000000014007F780                 test    dword ptr [rbx+100h], 80h ; _kthread.GuiThread
.text:000000014007F78A                 cmovnz  r10, r11
.text:000000014007F78E                 cmp     eax, [rdi+r10+10h]
.text:000000014007F793                 jnb     loc_14007FA82   ; 函數序號超過ssdt中存在的:
.text:000000014007F793                                         ; 1.可能是傳入的序號出錯
.text:000000014007F793                                         ; 2.或者是還沒有轉換成gui線程調用了shadowSSDT的函數
.text:000000014007F799                 mov     r10, [rdi+r10]  ; rdi為0x20或0x00很巧妙,剛好可以作為ssdt和Shadow ssdt的偏移
.text:000000014007F79D                 movsxd  r11, dword ptr [r10+rax*4]
.text:000000014007F7A1                 mov     rax, r11
.text:000000014007F7A4                 sar     r11, 4
.text:000000014007F7A8                 add     r10, r11        ; ssdt_func_addtr = &ssdt + ssdt.func_addr>>4
.text:000000014007F7AB                 cmp     edi, 20h ; ' '
.text:000000014007F7AE                 jnz     short loc_14007F800 ; 不是ShadowSSDT調用則跳轉

  這段代碼首先會判斷eax的第12位是否為1設置edi的值,然后判斷當前線程是否是GUI線程,如果當前線程不是GUI線程,則cmovnz r10, r11則不會將r11傳給r10,r10中也就是存放的KeServiceDescriptorTable,因此如果這個線程是第一次調用ShadowSSDT中的api,那么還不是GUI線程,cmp eax, [rdi+r10+10h]這里的eax肯定就會大於后面的[rdi+r10+10h],因為還不是GUI線程,那么r10這里是指向的KeServiceDescriptorTable,而rdi等於0x20,又因為在非GUI線程中的第二張SSDT表是空的,所以下面的跳轉就會生效。這個跳轉函數主要是判斷是否是win32k的調用還是序號傳錯了,如果是win32k,則將線程轉為GUI線程,否則退出系統調用。

  可以從上面發現64位下的ssdt表中的函數地址計算方法為:ssdt_func_addtr = &ssdt + ssdt.func_addr>>4,也就是說ssdt.func_addr的低4位在計算地址上並沒有用上,但是在后面其實是用上了的,詳情請看后面分析。

0: kd> dq KeServiceDescriptorTableShadow
fffff800`04117880  fffff800`03ee7300 00000000`00000000
fffff800`04117890  00000000`00000191 fffff800`03ee7f8c
fffff800`041178a0  fffff960`00181f00 00000000`00000000
fffff800`041178b0  00000000`0000033b fffff960`00183c1c
fffff800`041178c0  00000000`77b31206 00000000`00000000
fffff800`041178d0  fffff800`00a014a0 fffff800`00a01450
fffff800`041178e0  00000000`00000002 00000000`00007010
fffff800`041178f0  00000000`00078ed0 00000000`00000000
0: kd> dq KeServiceDescriptorTable                                  //可以看到第二張SSDT表是空的,全為0
fffff800`04117840  fffff800`03ee7300 00000000`00000000
fffff800`04117850  00000000`00000191 fffff800`03ee7f8c
fffff800`04117860  00000000`00000000 00000000`00000000
fffff800`04117870  00000000`00000000 00000000`00000000
fffff800`04117880  fffff800`03ee7300 00000000`00000000
fffff800`04117890  00000000`00000191 fffff800`03ee7f8c
fffff800`041178a0  fffff960`00181f00 00000000`00000000
fffff800`041178b0  00000000`0000033b fffff960`00183c1c
text:000000014007FA82                 cmp     edi, 20h ; ' '
.text:000000014007FA85                 jnz     short loc_14007FAE2
.text:000000014007FA87                 mov     [rbp-80h], eax
.text:000000014007FA8A                 mov     [rbp-78h], rcx
.text:000000014007FA8E                 mov     [rbp-70h], rdx
.text:000000014007FA92                 mov     [rbp-68h], r8
.text:000000014007FA96                 mov     [rbp-60h], r9
.text:000000014007FA9A                 call    KiConvertToGuiThread
.text:000000014007FA9F                 or      eax, eax
.text:000000014007FAA1                 mov     eax, [rbp-80h]
.text:000000014007FAA4                 mov     rcx, [rbp-78h]
.text:000000014007FAA8                 mov     rdx, [rbp-70h]
.text:000000014007FAAC                 mov     r8, [rbp-68h]
.text:000000014007FAB0                 mov     r9, [rbp-60h]
.text:000000014007FAB4                 mov     [rbx+1D8h], rsp
.text:000000014007FABB                 jz      KiSystemServiceRepeat
.text:000000014007FAC1                 lea     rdi, unk_1402B18A0
.text:000000014007FAC8                 mov     esi, [rdi+10h]
.text:000000014007FACB                 mov     rdi, [rdi]
.text:000000014007FACE                 cmp     eax, esi
.text:000000014007FAD0                 jnb     short loc_14007FAE2
.text:000000014007FAD2                 lea     rdi, [rdi+rsi*4]
.text:000000014007FAD6                 movsx   eax, byte ptr [rax+rdi]
.text:000000014007FADA                 or      eax, eax
.text:000000014007FADC                 jle     KiSystemServiceExit
.text:000000014007FAE2
.text:000000014007FAE2 loc_14007FAE2:                          ; CODE XREF: KiSystemCall64+445↑j
.text:000000014007FAE2                                         ; KiSystemCall64+490↑j
.text:000000014007FAE2                 mov     eax, 0C000001Ch
.text:000000014007FAE7                 jmp     KiSystemServiceExit

.text:000000014007F800 and eax, 0Fh ; 上面提到了ssdt_func_addtr = &ssdt + ssdt.func_addr>>4 .text:000000014007F800 ; 所以ssdt.func_addr的低4字節用來表示除了rcx,rdx,r8,r9這四個參數外 .text:000000014007F800 ; api剩余的需要復制到棧中的參數個數 .text:000000014007F803 jz KiSystemServiceCopyEnd ; ???似乎和ums有關,暫時還不懂 .text:000000014007F809 shl eax, 3 ; 參數個數*8byte .text:000000014007F80C lea rsp, [rsp-70h] .text:000000014007F811 lea rdi, [rsp+18h] .text:000000014007F816 mov rsi, [rbp+100h] ; syscall之前的user.esp .text:000000014007F81D lea rsi, [rsi+20h] ; 因為64的葉函數要給自己調用的函數保留0x20大小的空間保存rcx rdx r8 r9 .text:000000014007F821 test byte ptr [rbp+0F0h], 1 ; cs如果第0位為1,代表這個調用是從3環過來的, .text:000000014007F821 ; 因此要檢查一下esp是否超過的用戶空間的最大值 .text:000000014007F828 jz short loc_14007F840 .text:000000014007F82A cmp rsi, cs:MmUserProbeAddress .text:000000014007F831 cmovnb rsi, cs:MmUserProbeAddress .text:000000014007F839 nop dword ptr [rax+00000000h] .text:000000014007F840 .text:000000014007F840 loc_14007F840: ; CODE XREF: KiSystemCall64+1E8↑j .text:000000014007F840 lea r11, KiSystemServiceCopyEnd ; ???似乎和ums有關,暫時還不懂 .text:000000014007F847 sub r11, rax ; 這里設計的比較巧妙吧,KiSystemServiceCopyEnd上面就是復制參數, .text:000000014007F847 ; 根據減去rax的值來定位具體要復制多少參數 .text:000000014007F84A jmp r11 .text:000000014007F84A ; --------------------------------------------------------------------------- .text:000000014007F84D align 10h .text:000000014007F850 .text:000000014007F850 KiSystemServiceCopyStart: ; DATA XREF: KiSystemServiceHandler+1A↑o .text:000000014007F850 mov rax, [rsi+70h] .text:000000014007F854 mov [rdi+70h], rax .text:000000014007F858 mov rax, [rsi+68h] .text:000000014007F85C mov [rdi+68h], rax .text:000000014007F860 mov rax, [rsi+60h] .text:000000014007F864 mov [rdi+60h], rax .text:000000014007F868 mov rax, [rsi+58h] .text:000000014007F86C mov [rdi+58h], rax .text:000000014007F870 mov rax, [rsi+50h] .text:000000014007F874 mov [rdi+50h], rax .text:000000014007F878 mov rax, [rsi+48h] .text:000000014007F87C mov [rdi+48h], rax .text:000000014007F880 mov rax, [rsi+40h] .text:000000014007F884 mov [rdi+40h], rax .text:000000014007F888 mov rax, [rsi+38h] .text:000000014007F88C mov [rdi+38h], rax .text:000000014007F890 mov rax, [rsi+30h] .text:000000014007F894 mov [rdi+30h], rax .text:000000014007F898 mov rax, [rsi+28h] .text:000000014007F89C mov [rdi+28h], rax .text:000000014007F8A0 mov rax, [rsi+20h] .text:000000014007F8A4 mov [rdi+20h], rax .text:000000014007F8A8 mov rax, [rsi+18h] .text:000000014007F8AC mov [rdi+18h], rax .text:000000014007F8B0 mov rax, [rsi+10h] .text:000000014007F8B4 mov [rdi+10h], rax .text:000000014007F8B8 mov rax, [rsi+8] .text:000000014007F8BC mov [rdi+8], rax
.text:000000014007F8C0 KiSystemServiceCopyEnd:                 ; CODE XREF: KiSystemCall64+1C3↑j
.text:000000014007F8C0                                         ; DATA XREF: KiSystemServiceHandler+27↑o ...
.text:000000014007F8C0                 test    cs:dword_140207688, 40h ; ???似乎和ums有關,暫時還不懂
.text:000000014007F8CA                 jnz     loc_14007FB20
.text:000000014007F8D0                 call    r10             ; 正式調用api

  這塊代碼可以分析出來ssdt.func_addr的低四位其實是用來表示這個api除了rcx rdx r8 r9四個參數外剩余的參數,通過乘8得到的偏移和KiSystemServiceCopyStart地址相減來從用戶的棧空間復制參數,因為從硬編碼來看KiSystemServiceCopyStart每一個參數的復制語句正好占8字節。當然這里還涉及到了cs選擇子的權限檢查,如果是從3環發起的調用,則會檢查棧地址是否超過用戶空間最大地址。在復制完參數后就通過call r10正式調用api了。

    • KiInitiateUserApc Apc分發
.text:000000014007F8DB                 mov     rbx, [rbp+0C0h]
.text:000000014007F8E2                 mov     rdi, [rbp+0C8h]
.text:000000014007F8E9                 mov     rsi, [rbp+0D0h]
.text:000000014007F8F0                 mov     r11, gs:188h
.text:000000014007F8F9                 test    byte ptr [rbp+0F0h], 1
.text:000000014007F900                 jz      loc_14007FA55   ; 如果是從0環來的調用,ret返回
.text:000000014007F906                 mov     rcx, cr8
.text:000000014007F90A                 or      cl, [r11+1F0h]
.text:000000014007F911                 or      ecx, [r11+1C4h]
.text:000000014007F918                 jnz     loc_14007FAEC
.text:000000014007F91E                 cli
.text:000000014007F91F                 mov     rcx, gs:188h
.text:000000014007F928                 cmp     byte ptr [rcx+7Ah], 0 ; _kthread.SavedApcState.UserApcPending
.text:000000014007F92C                 jz      short no_user_apc ; 如果該標志位置位,則准備進行用戶層apc分發
.text:000000014007F92E                 mov     [rbp-50h], rax
.text:000000014007F932                 xor     eax, eax
.text:000000014007F934                 mov     [rbp-48h], rax
.text:000000014007F938                 mov     [rbp-40h], rax
.text:000000014007F93C                 mov     [rbp-38h], rax
.text:000000014007F940                 mov     [rbp-30h], rax
.text:000000014007F944                 mov     [rbp-28h], rax
.text:000000014007F948                 mov     [rbp-20h], rax
.text:000000014007F94C                 pxor    xmm0, xmm0
.text:000000014007F950                 movaps  xmmword ptr [rbp-10h], xmm0
.text:000000014007F954                 movaps  xmmword ptr [rbp+0], xmm0
.text:000000014007F958                 movaps  xmmword ptr [rbp+10h], xmm0
.text:000000014007F95C                 movaps  xmmword ptr [rbp+20h], xmm0
.text:000000014007F960                 movaps  xmmword ptr [rbp+30h], xmm0
.text:000000014007F964                 movaps  xmmword ptr [rbp+40h], xmm0
.text:000000014007F968                 mov     ecx, 1
.text:000000014007F96D                 mov     cr8, rcx        ; 將irql轉為apc級別
.text:000000014007F971                 sti
.text:000000014007F972                 call    KiInitiateUserApc
.text:000000014007F977                 cli
.text:000000014007F978                 mov     ecx, 0
.text:000000014007F97D                 mov     cr8, rcx
.text:000000014007F981                 mov     rax, [rbp-50h]

  在上面的api調用返回后進入KiSystemServiceExit流程,可以看到如果當前線程是一個從3環來的調用並且_kthread.SavedApcState.UserApcPending被置位,就會嘗試進行apc分發。

    • InstrumentationCallback用戶層回調
text:000000014007F9C4 loc_14007F9C4:                          ; CODE XREF: KiSystemCall64+354↑j
.text:000000014007F9C4                 ldmxcsr dword ptr [rbp-54h]
.text:000000014007F9C8                 xor     r10, r10
.text:000000014007F9CB                 cmp     word ptr [rbp+80h], 0 ; 如果DebugAcitve為0,則不恢復調試寄存器
.text:000000014007F9D3                 jz      short loc_14007FA13
.text:000000014007F9D5                 mov     [rbp-50h], rax
.text:000000014007F9D9                 call    KiRestoreDebugRegisterState
.text:000000014007F9DE                 mov     rax, gs:188h
.text:000000014007F9E7                 mov     rax, [rax+70h]  ; _kthread.apcState._kprocess
.text:000000014007F9EB                 mov     rax, [rax+100h] ; _kprocess.InstrumentationCallback
.text:000000014007F9F2                 or      rax, rax
.text:000000014007F9F5                 jz      short loc_14007FA0F
.text:000000014007F9F7                 cmp     word ptr [rbp+0F0h], 33h ; '3'
.text:000000014007F9FF                 jnz     short loc_14007FA0F
.text:000000014007FA01                 mov     r10, [rbp+0E8h] ; user.rip
.text:000000014007FA08                 mov     [rbp+0E8h], rax
.text:000000014007FA0F
.text:000000014007FA0F loc_14007FA0F:                          ; CODE XREF: KiSystemCall64+3B5↑j
.text:000000014007FA0F                                         ; KiSystemCall64+3BF↑j
.text:000000014007FA0F                 mov     rax, [rbp-50h]
.text:000000014007FA13
.text:000000014007FA13 loc_14007FA13:                          ; CODE XREF: KiSystemCall64+393↑j
.text:000000014007FA13                 mov     r8, [rbp+100h]
.text:000000014007FA1A                 mov     r9, [rbp+0D8h]
.text:000000014007FA21                 xor     edx, edx
.text:000000014007FA23                 pxor    xmm0, xmm0
.text:000000014007FA27                 pxor    xmm1, xmm1
.text:000000014007FA2B                 pxor    xmm2, xmm2
.text:000000014007FA2F                 pxor    xmm3, xmm3
.text:000000014007FA33                 pxor    xmm4, xmm4
.text:000000014007FA37                 pxor    xmm5, xmm5
.text:000000014007FA3B                 mov     rcx, [rbp+0E8h]
.text:000000014007FA42                 mov     r11, [rbp+0F8h]
.text:000000014007FA49                 mov     rbp, r9
.text:000000014007FA4C                 mov     rsp, r8
.text:000000014007FA4F                 swapgs
.text:000000014007FA52                 sysret

  text:000000014007FA01 mov r10, [rbp+0E8h] ; user.rip

  text:000000014007FA08 mov [rbp+0E8h], rax

  這里會根據DebugActive的值來決定是否恢復dr寄存器,隨后判斷了_kprocess.InstrumentationCallback是否有值,如果有值,前面說的DebugActive.Instrumented也會置位,同時最重要的是可以看到這兩句代碼,他把3環要返回的rip保存到了r10,把_kprocess.InstrumentationCallback的值賦給了3環的rip(當然是操作的_TrapFrame),這也就說明了我們可以通過設置這個回調在api返回時劫持程序流程。后面代碼基本上就說從_TrapFrame中還原寄存器,然后swapgs還原gs,sysret返回3環。

  通過查找資料發現可以在3環通過ZwSetInformationProcess的40功能號設置該回調。通過編寫代碼測試發現,如下代碼在win10上可以正常的在api返回時調用回調。但是在win7中測試的時候發現一直返回下面的錯誤碼。

//
// MessageId: STATUS_PRIVILEGE_NOT_HELD
//
// MessageText:
//
// A required privilege is not held by the client.
//
#define STATUS_PRIVILEGE_NOT_HELD        ((NTSTATUS)0xC0000061L)

  然后在ntoskrl中看了一下ZwSetInformationProcess的40功能號的實現發現有個SE_DEBUG_NAME的權限檢查。在win7下非管理員啟動的程序是不持有SE_DEBUG_NAME權限的,所以我們需要管理員權限啟動並且在代碼中手動開啟SE_DEBUG_NAME權限。下面的代碼在win7/win10中測試成功在api返回時調用回調。但是程序肯定會崩潰,我沒有保存原來的寄存器環境,這只是這個回調可用性的demo,具體詳細的寫法請參照https://secrary.com/Random/InstrumentationCallback/

  v24 = ProcessInformationClass - ProcessInstrumentationCallback;
  if ( !v24 )
  {
    if ( ProcessInformationLength != 8 )
      return 0xC0000004i64;
    if ( SeSinglePrivilegeCheck(SeDebugPrivilege, v8) )
      JUMPOUT(0x1403D483Ei64);
    return 0xC0000061i64;
#include<stdio.h>
#include <windows.h>


typedef enum _PROCESSINFOCLASS {
    ProcessBasicInformation = 0,
    ProcessQuotaLimits = 1,
    ProcessIoCounters = 2,
    ProcessVmCounters = 3,
    ProcessTimes = 4,
    ProcessBasePriority = 5,
    ProcessRaisePriority = 6,
    ProcessDebugPort = 7,
    ProcessExceptionPort = 8,
    ProcessAccessToken = 9,
    ProcessLdrInformation = 10,
    ProcessLdtSize = 11,
    ProcessDefaultHardErrorMode = 12,
    ProcessIoPortHandlers = 13,
    ProcessPooledUsageAndLimits = 14,
    ProcessWorkingSetWatch = 15,
    ProcessUserModeIOPL = 16,
    ProcessEnableAlignmentFaultFixup = 17,
    ProcessPriorityClass = 18,
    ProcessWx86Information = 19,
    ProcessHandleCount = 20,
    ProcessAffinityMask = 21,
    ProcessPriorityBoost = 22,
    ProcessDeviceMap = 23,
    ProcessSessionInformation = 24,
    ProcessForegroundInformation = 25,
    ProcessWow64Information = 26,
    ProcessImageFileName = 27,
    ProcessLUIDDeviceMapsEnabled = 28,
    ProcessBreakOnTermination = 29,
    ProcessDebugObjectHandle = 30,
    ProcessDebugFlags = 31,
    ProcessHandleTracing = 32,
    ProcessIoPriority = 33,
    ProcessExecuteFlags = 34,
    ProcessTlsInformation = 35,
    ProcessCookie = 36,
    ProcessImageInformation = 37,
    ProcessCycleTime = 38,
    ProcessPagePriority = 39,
    ProcessInstrumentationCallback = 40, // that's what we need
    ProcessThreadStackAllocation = 41,
    ProcessWorkingSetWatchEx = 42,
    ProcessImageFileNameWin32 = 43,
    ProcessImageFileMapping = 44,
    ProcessAffinityUpdateMode = 45,
    ProcessMemoryAllocationMode = 46,
    ProcessGroupInformation = 47,
    ProcessTokenVirtualizationEnabled = 48,
    ProcessConsoleHostProcess = 49,
    ProcessWindowInformation = 50,
    MaxProcessInfoClass
} PROCESSINFOCLASS;

typedef NTSTATUS(NTAPI* pNtSetInformationProcess)(
    HANDLE ProcessHandle,
    PROCESS_INFORMATION_CLASS ProcessInformationClass,
    PVOID ProcessInformation,
    ULONG ProcessInformationLength
    );

typedef NTSTATUS (NTAPI* pRtlAdjustPrivilege)(
    ULONG Privilege,
    BOOLEAN Enable,
    BOOLEAN CurrentThread,
    PBOOLEAN Enabled);

typedef struct _PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION
{
    PVOID Callback;
} PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION, * PPROCESS_INSTRUMENTATION_CALLBACK_INFORMATION;


void medium()
{
    while (1)
    {
    }
}


void AdjustPrivileges()
{
    HMODULE hModule = GetModuleHandle("ntdll.dll");
    pRtlAdjustPrivilege RtlAdjustPrivilege = (pNtSetInformationProcess)GetProcAddress(hModule, "RtlAdjustPrivilege");

    RtlAdjustPrivilege(0x14, 1, 0, NULL);

    return;
}



int main()
{

    pNtSetInformationProcess NtSetInformationProcess = NULL;

    HMODULE hModule = GetModuleHandle("ntdll.dll");
    NtSetInformationProcess = (pNtSetInformationProcess)GetProcAddress(hModule, "NtSetInformationProcess");


    PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION nirvana;
    nirvana.Callback = (PVOID)(ULONG_PTR)medium;

    AdjustPrivileges();

    NTSTATUS n = NtSetInformationProcess(
        GetCurrentProcess(),
        (PROCESS_INFORMATION_CLASS)ProcessInstrumentationCallback,
        &nirvana,
        0x8);

    printf("%x\n", n);
    
    
    
    OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
    OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
    OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
    OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
    OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
    OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
    OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());


    return 0;
}
    • 總結

  這次win7 64位的系統調用算是粗略的分析完了,同時也解決了之前一直困惑的東西,也學到了新東西。博客園排版好像有問題,這里附上pdf附件https://files.cnblogs.com/files/DreamoneOnly/syscall.7z

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM