本文为看雪论坛优秀文章
看雪论坛作者ID:kardpan
一
前言
针对r3层的最强防御,应该就是自己实现调用r0的内核函数。不使用系统提供的API接口。这样能防御别人在导入函数的下断点,或者IAT HOOK。当然如果有些函数是不用进入内核,在r3层就 实现了所有功能的,不在这个讨论范围之内。
二
分析
通过具体的两个API,来看r3是如何进入r0,看如何实现权限切换的。分析的环境为win10x64,版本为1903,样本程序也是64位程序。
调用OpenProcess
00007FF790489EEF | 44:8B85 20010000 | mov r8d,dword ptr ss:[rbp+120] | TestDomeDlg.cpp:632 //pid
00007FF790489EF6 | 33D2 | xor edx,edx | //false
00007FF790489EF8 | B9 FFFF1F00 | mov ecx,1FFFFF | //PROCESS_ALL_ACCESS
00007FF790489EFD | 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+E1
00007FFC7207C131 | 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下个断点,才能调试内核。
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
三
自实现代码
通过分析后得到,r3层最底层调用的是ntdll!NtOpenProcess。下面开始实现ntdll!NtOpenProcess。
● 由于vs对64位程序,不支持内联汇编。所以这里使用汇编和c/c++的联合编译。
● 由于根据IDA下载的微软pdb,能够解析NtOpenProcess调用的时候传入的参数的类型。但是没有具体的结构体的定义
● 通过wrk查询到OBJECT_ATTRIBUTES和CLIENT_ID结构体的定义。
门调用的关键代码
MyNtOpenProcess2 proc
mov eax,26h
jmp r3syscallProxy
MyNtOpenProcess2 endp
;只写了门 没有写中断 进入内核
;因为会系统会检测当前是否支持 门 并且通过内核共享一个出一段内存地址提供给 r3的程序读取,来判断是走门 还是中断进入内核
;由于这个地址不同的系统版本可能会不同,还没测试所以暂时只写了门进入内核的方法
r3syscallProxy proc
mov r10,rcx
syscall
ret
r3syscallProxy 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!NtOpenProcess
HANDLE 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;
}
}
MyZwAllocateVirtualMemory proc
mov eax,18h
jmp r3syscallProxy
MyZwAllocateVirtualMemory 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
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!