R3层最后的倔强--权限切换
2023-1-20 18:16:29 Author: 看雪学苑(查看原文) 阅读量:9 收藏


本文为看雪论坛优秀文章

看雪论坛作者ID:kardpan


前言

针对r3层的最强防御,应该就是自己实现调用r0的内核函数。不使用系统提供的API接口。这样能防御别人在导入函数的下断点,或者IAT HOOK。当然如果有些函数是不用进入内核,在r3层就 实现了所有功能的,不在这个讨论范围之内。


分析

通过具体的两个API,来看r3是如何进入r0,看如何实现权限切换的。分析的环境为win10x64,版本为1903,样本程序也是64位程序。

OpenProcess

调用流程

具体分析

调用OpenProcess

00007FF790489EEF | 44:8B85 20010000         | mov r8d,dword ptr ss:[rbp+120]          | TestDomeDlg.cpp:632    //pid00007FF790489EF6 | 33D2                     | xor edx,edx                             |                        //false00007FF790489EF8 | B9 FFFF1F00              | mov ecx,1FFFFF                          |                        //PROCESS_ALL_ACCESS00007FF790489EFD | FF15 4D21AB00            | call qword ptr ds:[<&OpenProcess>]      |

OpenProcess本质调用的是NtOpenProcess

00007FFC7207C0F0 | 4C:8BDC                  | mov r11,rsp                             |00007FFC7207C0F3 | 48:83EC 68               | sub rsp,68                              |00007FFC7207C0F7 | 49:8363 C0 00            | and qword ptr ds:[r11-40],0             |00007FFC7207C0FC | 4D:8D4B B8               | lea r9,qword ptr ds:[r11-48]            |00007FFC7207C100 | 49:63C0                  | movsxd rax,r8d                          |00007FFC7207C103 | 0F57C0                   | xorps xmm0,xmm0                         |00007FFC7207C106 | C74424 30 30000000       | mov dword ptr ss:[rsp+30],30            | 30:'0'00007FFC7207C10E | 4D:8D43 C8               | lea r8,qword ptr ds:[r11-38]            |00007FFC7207C112 | 49:8363 D0 00            | and qword ptr ds:[r11-30],0             |00007FFC7207C117 | F7DA                     | neg edx                                 |00007FFC7207C119 | 49:8943 B8               | mov qword ptr ds:[r11-48],rax           |00007FFC7207C11D | 8BD1                     | mov edx,ecx                             |00007FFC7207C11F | 49:8D4B 20               | lea rcx,qword ptr ds:[r11+20]           | [r11+20]:L"兽郴翷"00007FFC7207C123 | 1BC0                     | sbb eax,eax                             |00007FFC7207C125 | 83E0 02                  | and eax,2                               |00007FFC7207C128 | 894424 48                | mov dword ptr ss:[rsp+48],eax           |00007FFC7207C12C | 49:8363 D8 00            | and qword ptr ds:[r11-28],0             | [r11-28]:AfxWndProc+E100007FFC7207C131 | F3:0F7F4424 50           | movdqu xmmword ptr ss:[rsp+50],xmm0     |00007FFC7207C137 | 48:FF15 BA781900         | call qword ptr ds:[<&NtOpenProcess>]    |00007FFC7207C13E | 0F1F4400 00              | nop dword ptr ds:[rax+rax],eax          |00007FFC7207C143 | 85C0                     | test eax,eax                            |00007FFC7207C145 | 78 0E                    | js kernelbase.7FFC7207C155              |00007FFC7207C147 | 48:8B8424 88000000       | mov rax,qword ptr ss:[rsp+88]           |00007FFC7207C14F | 48:83C4 68               | add rsp,68                              |00007FFC7207C153 | C3                       | ret                     

NtOpenProcess的关键代码就是调用syscall

00007FFC7454D1F0 | 4C:8BD1                  | mov r10,rcx                             |00007FFC7454D1F3 | B8 26000000              | mov eax,26   //每个不同的API都有不同的编号来区分                          | 26:'&'00007FFC7454D1F8 | F60425 0803FE7F 01       | test byte ptr ds:[7FFE0308],1           |00007FFC7454D200 | 75 03                    | jne ntdll.7FFC7454D205                  |00007FFC7454D202 | 0F05                     | syscall     //通过门进入内核00007FFC7454D204 | C3                       | ret                                     |00007FFC7454D205 | CD 2E                    | int 2E      //通过中断进入内核                             |00007FFC7454D207 | C3                       | ret                                     |

● 本质上所有进入内核的API都是通过syscall(32系统通过sysenter来进入内核,xp通过int 0x2e中断进入内核)进入r0,通过sysret从r0返回r3.
● 通过寄存器eax来区分不同的API.实际这个编号就是SSDT的序号。

● 参数通过栈传递,拷贝
● 关于0x7FFE308是_KUSER_SHARED_DATA结构体。内核有读写权限,r3只有读取权限。通过 _KUSER_SHARED_DATA.SystemCall的高位来判断系统是走中断进入内核还是通过门进入内核。

_KUSER_SHARED_DATA结构体解析如下:

1: kd> dt nt!_KUSER_SHARED_DATA*** Unable to resolve unqualified symbol in Bp expression 'l'.   +0x000 TickCountLowDeprecated : 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] Wchar   +0x238 MaxStackTraceDepth : Uint4B   +0x23c CryptoExponent   : Uint4B   +0x240 TimeZoneId       : Uint4B   +0x244 LargePageMinimum : Uint4B   +0x248 AitSamplingValue : Uint4B   +0x24c AppCompatFlag    : Uint4B   +0x250 RNGSeedVersion   : Uint8B   +0x258 GlobalValidationRunlevel : Uint4B   +0x25c TimeZoneBiasStamp : Int4B   +0x260 NtBuildNumber    : Uint4B   +0x264 NtProductType    : _NT_PRODUCT_TYPE   +0x268 ProductTypeIsValid : UChar   +0x269 Reserved0        : [1] UChar   +0x26a NativeProcessorArchitecture : Uint2B   +0x26c NtMajorVersion   : Uint4B   +0x270 NtMinorVersion   : Uint4B   +0x274 ProcessorFeatures : [64] UChar   +0x2b4 Reserved1        : Uint4B   +0x2b8 Reserved3        : Uint4B   +0x2bc TimeSlip         : Uint4B   +0x2c0 AlternativeArchitecture : _ALTERNATIVE_ARCHITECTURE_TYPE   +0x2c4 BootId           : Uint4B   +0x2c8 SystemExpirationDate : _LARGE_INTEGER   +0x2d0 SuiteMask        : Uint4B   +0x2d4 KdDebuggerEnabled : UChar   +0x2d5 MitigationPolicies : UChar   +0x2d5 NXSupportPolicy  : Pos 0, 2 Bits   +0x2d5 SEHValidationPolicy : Pos 2, 2 Bits   +0x2d5 CurDirDevicesSkippedForDlls : Pos 4, 2 Bits   +0x2d5 Reserved         : Pos 6, 2 Bits   +0x2d6 CyclesPerYield   : Uint2B   +0x2d8 ActiveConsoleId  : Uint4B   +0x2dc DismountCount    : Uint4B   +0x2e0 ComPlusPackage   : Uint4B   +0x2e4 LastSystemRITEventTickCount : Uint4B   +0x2e8 NumberOfPhysicalPages : Uint4B   +0x2ec SafeBootMode     : UChar   +0x2ed VirtualizationFlags : UChar   +0x2ee Reserved12       : [2] UChar   +0x2f0 SharedDataFlags  : Uint4B   +0x2f0 DbgErrorPortPresent : Pos 0, 1 Bit   +0x2f0 DbgElevationEnabled : Pos 1, 1 Bit   +0x2f0 DbgVirtEnabled   : Pos 2, 1 Bit   +0x2f0 DbgInstallerDetectEnabled : Pos 3, 1 Bit   +0x2f0 DbgLkgEnabled    : Pos 4, 1 Bit   +0x2f0 DbgDynProcessorEnabled : Pos 5, 1 Bit   +0x2f0 DbgConsoleBrokerEnabled : Pos 6, 1 Bit   +0x2f0 DbgSecureBootEnabled : Pos 7, 1 Bit   +0x2f0 DbgMultiSessionSku : Pos 8, 1 Bit   +0x2f0 DbgMultiUsersInSessionSku : Pos 9, 1 Bit   +0x2f0 DbgStateSeparationEnabled : Pos 10, 1 Bit   +0x2f0 SpareBits        : Pos 11, 21 Bits   +0x2f4 DataFlagsPad     : [1] Uint4B   +0x2f8 TestRetInstruction : Uint8B   +0x300 QpcFrequency     : Int8B   +0x308 SystemCall       : Uint4B  //占4位,根据汇编代码却检测的1位   +0x30c SystemCallPad0   : Uint4B   +0x310 SystemCallPad    : [2] Uint8B   +0x320 TickCount        : _KSYSTEM_TIME   +0x320 TickCountQuad    : Uint8B   +0x320 ReservedTickCountOverlay : [3] Uint4B   +0x32c TickCountPad     : [1] Uint4B   +0x330 Cookie           : Uint4B   +0x334 CookiePad        : [1] Uint4B   +0x338 ConsoleSessionForegroundProcessId : Int8B   +0x340 TimeUpdateLock   : Uint8B   +0x348 BaselineSystemTimeQpc : Uint8B   +0x350 BaselineInterruptTimeQpc : Uint8B   +0x358 QpcSystemTimeIncrement : Uint8B   +0x360 QpcInterruptTimeIncrement : Uint8B   +0x368 QpcSystemTimeIncrementShift : UChar   +0x369 QpcInterruptTimeIncrementShift : UChar   +0x36a UnparkedProcessorCount : Uint2B   +0x36c EnclaveFeatureMask : [4] Uint4B   +0x37c TelemetryCoverageRound : Uint4B   +0x380 UserModeGlobalLogger : [16] Uint2B   +0x3a0 ImageFileExecutionOptions : Uint4B   +0x3a4 LangGenerationCount : Uint4B   +0x3a8 Reserved4        : Uint8B   +0x3b0 InterruptTimeBias : Uint8B   +0x3b8 QpcBias          : Uint8B   +0x3c0 ActiveProcessorCount : Uint4B   +0x3c4 ActiveGroupCount : UChar   +0x3c5 Reserved9        : UChar   +0x3c6 QpcData          : Uint2B   +0x3c6 QpcBypassEnabled : UChar   +0x3c7 QpcShift         : UChar   +0x3c8 TimeZoneBiasEffectiveStart : _LARGE_INTEGER   +0x3d0 TimeZoneBiasEffectiveEnd : _LARGE_INTEGER   +0x3d8 XState           : _XSTATE_CONFIGURATION

● mov r10,rcx为什么要添加这句汇编指令,没有分析出来求大佬补充。
● 注意这里用windbg单步步入syscall是会跑飞的,只能在nt!NtOpenProcess下个断点,才能调试内核。

VirtualProtectEx

调用流程

具体分析

VirtualAllocEx函数调用

00007FF65A280D5B | 8BC0                     | mov eax,eax                             |00007FF65A280D5D | C74424 20 40000000       | mov dword ptr ss:[rsp+20],40            | 40:'@'00007FF65A280D65 | 41:B9 00100000           | mov r9d,1000                            |00007FF65A280D6B | 44:8BC0                  | mov r8d,eax                             |00007FF65A280D6E | 48:8B55 08               | mov rdx,qword ptr ss:[rbp+8]            | [rbp+8]:"MZ?"00007FF65A280D72 | 48:8B85 00010000         | mov rax,qword ptr ss:[rbp+100]          |00007FF65A280D79 | 48:8B08                  | mov rcx,qword ptr ds:[rax]              |00007FF65A280D7C | FF15 B6D3AB00            | call qword ptr ds:[<&VirtualAllocEx>]   |

VirtualAllocEx内部调用kernelbase.VirtualAllocExNuma

00007FFF28984C00 | 48:83EC 38               | sub rsp,38                              |00007FFF28984C04 | 834C24 28 FF             | or dword ptr ss:[rsp+28],FFFFFFFF       |00007FFF28984C09 | 8B4424 60                | mov eax,dword ptr ss:[rsp+60]           |00007FFF28984C0D | 894424 20                | mov dword ptr ss:[rsp+20],eax           |00007FFF28984C11 | E8 1A000000              | call <kernelbase.VirtualAllocExNuma>    |00007FFF28984C16 | 48:83C4 38               | add rsp,38                              |00007FFF28984C1A | C3                       | ret                                     |

kernelbase.VirtualAllocExNuma 调用 ZwAllocateVirtualMemory

00007FFF28984C30 | 4C:894424 18             | mov qword ptr ss:[rsp+18],r8            |00007FFF28984C35 | 48:895424 10             | mov qword ptr ss:[rsp+10],rdx           |00007FFF28984C3A | 48:83EC 38               | sub rsp,38                              |00007FFF28984C3E | 48:85D2                  | test rdx,rdx                            | rdx:"MZ?"00007FFF28984C41 | 75 52                    | jne kernelbase.7FFF28984C95             |00007FFF28984C43 | 8B5424 68                | mov edx,dword ptr ss:[rsp+68]           |00007FFF28984C47 | 8D42 C0                  | lea eax,qword ptr ds:[rdx-40]           |00007FFF28984C4A | 83F8 BE                  | cmp eax,FFFFFFBE                        |00007FFF28984C4D | 0F86 F3480400            | jbe kernelbase.7FFF289C9546             |00007FFF28984C53 | 41:83E1 C0               | and r9d,FFFFFFC0                        |00007FFF28984C57 | 83FA FF                  | cmp edx,FFFFFFFF                        |00007FFF28984C5A | 75 49                    | jne kernelbase.7FFF28984CA5             |00007FFF28984C5C | 8B4424 60                | mov eax,dword ptr ss:[rsp+60]           |00007FFF28984C60 | 48:8D5424 48             | lea rdx,qword ptr ss:[rsp+48]           | [rsp+48]:"MZ?"00007FFF28984C65 | 894424 28                | mov dword ptr ss:[rsp+28],eax           |00007FFF28984C69 | 45:33C0                  | xor r8d,r8d                             |00007FFF28984C6C | 44:894C24 20             | mov dword ptr ss:[rsp+20],r9d           |00007FFF28984C71 | 4C:8D4C24 50             | lea r9,qword ptr ss:[rsp+50]            |00007FFF28984C76 | 48:FF15 0BEB1400         | call qword ptr ds:[<&ZwAllocateVirtualMemory |00007FFF28984C7D | 0F1F4400 00              | nop dword ptr ds:[rax+rax],eax          |00007FFF28984C82 | 85C0                     | test eax,eax                            |00007FFF28984C84 | 0F88 CF480400            | js kernelbase.7FFF289C9559              |00007FFF28984C8A | 48:8B4424 48             | mov rax,qword ptr ss:[rsp+48]           | [rsp+48]:"MZ?"00007FFF28984C8F | 48:83C4 38               | add rsp,38                              |00007FFF28984C93 | C3                       | ret                                     |

ntdll!ZwAllocateVirtualMemory 进入内核

00007FFF2B14D030 | 4C:8BD1                  | mov r10,rcx                             |00007FFF2B14D033 | B8 18000000              | mov eax,18                              |00007FFF2B14D038 | F60425 0803FE7F 01       | test byte ptr ds:[7FFE0308],1           |00007FFF2B14D040 | 75 03                    | jne ntdll.7FFF2B14D045                  |00007FFF2B14D042 | 0F05                     | syscall                                 |00007FFF2B14D044 | C3                       | ret                                     |00007FFF2B14D045 | CD 2E                    | int 2E                                  |00007FFF2B14D047 | C3                       | ret                                     |

● 内核对应函数名nt!NtAllocateVirtualMemory


自实现代码

OpenProcess

通过分析后得到,r3层最底层调用的是ntdll!NtOpenProcess。下面开始实现ntdll!NtOpenProcess。

● 由于vs对64位程序,不支持内联汇编。所以这里使用汇编和c/c++的联合编译。
● 由于根据IDA下载的微软pdb,能够解析NtOpenProcess调用的时候传入的参数的类型。但是没有具体的结构体的定义
● 通过wrk查询到OBJECT_ATTRIBUTES和CLIENT_ID结构体的定义。

门调用的关键代码

MyNtOpenProcess2 proc    mov eax,26h    jmp r3syscallProxyMyNtOpenProcess2 endp ;只写了门  没有写中断 进入内核    ;因为会系统会检测当前是否支持 门 并且通过内核共享一个出一段内存地址提供给 r3的程序读取,来判断是走门 还是中断进入内核;由于这个地址不同的系统版本可能会不同,还没测试所以暂时只写了门进入内核的方法r3syscallProxy proc       mov r10,rcx    syscall    retr3syscallProxy endp
//声明汇编的MyNtOpenProcess2函数extern "C" bool MyNtOpenProcess2(PVOID * pOutAddress, DWORD dwDesiredAccess, OBJECT_ATTRIBUTES * pObjectAttr, PCLIENT_ID  dwPid); typedef struct _CLIENT_ID {    HANDLE UniqueProcess;    HANDLE UniqueThread;} CLIENT_ID;typedef CLIENT_ID *PCLIENT_ID; typedef struct _OBJECT_ATTRIBUTES {    ULONG           Length;    HANDLE          RootDirectory;    PUNICODE_STRING ObjectName;    ULONG           Attributes;    PVOID           SecurityDescriptor;    PVOID           SecurityQualityOfService;} OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES; //自己实现ntdll!NtOpenProcessHANDLE r3NtOpenProcess(DWORD dwPid, DWORD dwDesiredAccess = PROCESS_ALL_ACCESS, BOOL bInheritHandle = false){    //__debugbreak();    ULONGLONG ullv5;    struct _OBJECT_ATTRIBUTES obj_attr = {};    obj_attr.Length = sizeof(_OBJECT_ATTRIBUTES);//48    CLIENT_ID pPid = {};    pPid.UniqueProcess = (HANDLE)dwPid;    obj_attr.Attributes = bInheritHandle ? 2 : 0;    HANDLE handle = 0;    LONG lret = MyNtOpenProcess2(&handle, dwDesiredAccess, &obj_attr, &pPid);    if (lret >= 0)    {        return handle;    }    else    {        return NULL;    }}

VirtualProtectEx

MyZwAllocateVirtualMemory proc    mov eax,18h    jmp r3syscallProxyMyZwAllocateVirtualMemory endp
LPVOID WINAPI r3VirtualAllocExNuma(    __in      HANDLE hProcess,    __in_opt  LPVOID lpAddress,    __in      SIZE_T dwSize,    __in      DWORD flAllocationType,    __in      DWORD flProtect,    __in      DWORD nndPreferred = 0xFFFFFFFF){    PVOID BaseAddress = lpAddress;    ULONG_PTR RegionSize = dwSize;    DWORD AllocationType  ;    AllocationType = flAllocationType & 0xFFFFFFC0;    if (nndPreferred != -1)        AllocationType |= nndPreferred + 1;    NTSTATUS stat = MyZwAllocateVirtualMemory(hProcess, &BaseAddress, 0, &RegionSize, AllocationType, flProtect);    if (stat >= 0)    {        return BaseAddress;    }    else    {        return NULL;    }}

r3VirtualAllocExNuma测试效果如下:

在ZwAllocateVirtualMemory下断点是不会被命中的:


结束语

● 本意是想在写壳的时候,初始化IAT的时候,将IAT填入自己实现的API调用,避免在关键API下断。

● 奈何windows版本太多,ssdt的序号每个版本可能变化。提供两个思路:
○ 从不同版本的ntdll文件中解析ssdt序号
○ 从不同版本的pdb来解析ssdt序号

● 思路来自张老师讲Android的api的调用。Android的API序号不变,没有版本兼容问题。

看雪ID:kardpan

https://bbs.kanxue.com/user-home-122914.htm

*本文由看雪论坛 kardpan 原创,转载请注明来自看雪社区

# 往期推荐

1.CVE-2022-21882提权漏洞学习笔记

2.wibu证书 - 初探

3.win10 1909逆向之APIC中断和实验

4.EMET下EAF机制分析以及模拟实现

5.sql注入学习分享

6.V8 Array.prototype.concat函数出现过的issues和他们的POC们

球分享

球点赞

球在看

点击“阅读原文”,了解更多!


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458493079&idx=1&sn=8912bbe6407469e9d22e875c455c29e6&chksm=b18e901d86f9190bc3f6f487b62b2a70628679d08a42ceff85847b2d1d9f7ebf00fe63260708#rd
如有侵权请联系:admin#unsafe.sh