返回
顶部

cfg如何工作

reference:

在阅读upx源码的时候发现默认不支持开启了cfg的pe文件,比如尝试给notepad.exe加壳就会出现报错

image-20250603152338445

看一下cfg是如何实现的

cfg可以在vs的编译选项中开启

image-20250603154248937

这里有一个使用了间接调用的示例代码

开启cfg

image-20250603154348352

关闭cfg

image-20250603154513697

开启了cfg的pe相比关闭cfg的pe中会多出来这些东西

开启cfg

image-20250603154937824

关闭cfg

image-20250603154859838

这里面有如下三个关键部分

  • cfg check function pointer,指向一个空函数image-20250603155241790
  • cfg function table,内核会用到这个数据
  • cfg flag

check function pointer你在ida或者pebear中看他是一个空函数,但是它实际上会在pe加载的时候由LdrpCfgProcessLoadConfig函数设置为下面这个函数

image-20250603155719107

不过我们实际测试的话,发现并没有直接调用check function pointer,而是调用的__guard_dispatch_icall_fptr

image-20250603162400901

0:000> dqs test+0020280 l100007ff6`4bd40280  00007ffb`79f1cd00 ntdll!LdrpDispatchUserCallTarget

其实check(LdrpValidateUserCallTarget)函数和dispatch(LdrpDispatchUserCallTarget)函数的实际代码是一样的

module tamper protection

references:

这个东西可以预防process hollowing技术

在eprocess中可以看到这个标志位

1: kd> dx @$curprocess.KernelObject.MitigationFlagsValues 
@$curprocess.KernelObject.MitigationFlagsValues                  [Type: <unnamed-tag>]
    [+0x000 ( 0: 0)] ControlFlowGuardEnabled : 0x1 [Type: unsigned long]
    [+0x000 ( 1: 1)] ControlFlowGuardExportSuppressionEnabled : 0x0 [Type: unsigned long]
    [+0x000 ( 2: 2)] ControlFlowGuardStrict : 0x0 [Type: unsigned long]
    [+0x000 ( 3: 3)] DisallowStrippedImages : 0x0 [Type: unsigned long]
    [+0x000 ( 4: 4)] ForceRelocateImages : 0x0 [Type: unsigned long]
    [+0x000 ( 5: 5)] HighEntropyASLREnabled : 0x1 [Type: unsigned long]
    [+0x000 ( 6: 6)] StackRandomizationDisabled : 0x0 [Type: unsigned long]
    [+0x000 ( 7: 7)] ExtensionPointDisable : 0x0 [Type: unsigned long]
    [+0x000 ( 8: 8)] DisableDynamicCode : 0x0 [Type: unsigned long]
    [+0x000 ( 9: 9)] DisableDynamicCodeAllowOptOut : 0x0 [Type: unsigned long]
    [+0x000 (10:10)] DisableDynamicCodeAllowRemoteDowngrade : 0x0 [Type: unsigned long]
    [+0x000 (11:11)] AuditDisableDynamicCode : 0x0 [Type: unsigned long]
    [+0x000 (12:12)] DisallowWin32kSystemCalls : 0x0 [Type: unsigned long]
    [+0x000 (13:13)] AuditDisallowWin32kSystemCalls : 0x0 [Type: unsigned long]
    [+0x000 (14:14)] EnableFilteredWin32kAPIs : 0x0 [Type: unsigned long]
    [+0x000 (15:15)] AuditFilteredWin32kAPIs : 0x0 [Type: unsigned long]
    [+0x000 (16:16)] DisableNonSystemFonts : 0x0 [Type: unsigned long]
    [+0x000 (17:17)] AuditNonSystemFontLoading : 0x0 [Type: unsigned long]
    [+0x000 (18:18)] PreferSystem32Images : 0x0 [Type: unsigned long]
    [+0x000 (19:19)] ProhibitRemoteImageMap : 0x0 [Type: unsigned long]
    [+0x000 (20:20)] AuditProhibitRemoteImageMap : 0x0 [Type: unsigned long]
    [+0x000 (21:21)] ProhibitLowILImageMap : 0x0 [Type: unsigned long]
    [+0x000 (22:22)] AuditProhibitLowILImageMap : 0x0 [Type: unsigned long]
    [+0x000 (23:23)] SignatureMitigationOptIn : 0x0 [Type: unsigned long]
    [+0x000 (24:24)] AuditBlockNonMicrosoftBinaries : 0x0 [Type: unsigned long]
    [+0x000 (25:25)] AuditBlockNonMicrosoftBinariesAllowStore : 0x0 [Type: unsigned long]
    [+0x000 (26:26)] LoaderIntegrityContinuityEnabled : 0x0 [Type: unsigned long]
    [+0x000 (27:27)] AuditLoaderIntegrityContinuity : 0x0 [Type: unsigned long]
    [+0x000 (28:28)] EnableModuleTamperingProtection : 0x0 [Type: unsigned long]
    [+0x000 (29:29)] EnableModuleTamperingProtectionNoInherit : 0x0 [Type: unsigned long]
    [+0x000 (30:30)] RestrictIndirectBranchPrediction : 0x1 [Type: unsigned long]
    [+0x000 (31:31)] IsolateSecurityDomain : 0x0 [Type: unsigned long]

28和29bit

在nt内核的PspApplyMitigationOptions函数中设置这两个标志位

image-20250604103610071

这两个bit的检查位置在ntdll的LdrpGetImportDescriptorForSnap函数中

就是在这里检查的

image-20250603174035010

需要设置一下这个内存位置的类型,它实际上是_PS_SYSTEM_DLL_INIT_BLOCK结构体,这个符号可以在combase.dll中找到

0:000> dt _PS_SYSTEM_DLL_INIT_BLOCK 
combase!_PS_SYSTEM_DLL_INIT_BLOCK
   +0x000 Size             : Uint4B
   +0x008 SystemDllWowRelocation : Uint8B
   +0x010 SystemDllNativeRelocation : Uint8B
   +0x018 Wow64SharedInformation : [16] Uint8B
   +0x098 RngData          : Uint4B
   +0x09c Flags            : Uint4B
   +0x09c CfgOverride      : Pos 0, 1 Bit
   +0x09c Reserved         : Pos 1, 31 Bits
   +0x0a0 MitigationOptionsMap : _PS_MITIGATION_OPTIONS_MAP
   +0x0b8 CfgBitMap        : Uint8B
   +0x0c0 CfgBitMapSize    : Uint8B
   +0x0c8 Wow64CfgBitMap   : Uint8B
   +0x0d0 Wow64CfgBitMapSize : Uint8B
   +0x0d8 MitigationAuditOptionsMap : _PS_MITIGATION_AUDIT_OPTIONS_MAP

我们把这个内容保存成头文件,然后加载到ntdll的ida项目中

struct _PS_MITIGATION_OPTIONS_MAP
{
  unsigned __int64 Map[3];
};
typedef _PS_MITIGATION_OPTIONS_MAP *PPS_MITIGATION_OPTIONS_MAP;
struct _PS_MITIGATION_AUDIT_OPTIONS_MAP
{
  unsigned __int64 Map[3];
};
typedef _PS_MITIGATION_AUDIT_OPTIONS_MAP *PPS_MITIGATION_AUDIT_OPTIONS_MAP;
struct _PS_SYSTEM_DLL_INIT_BLOCK
{
  unsigned int Size;
  unsigned __int64 SystemDllWowRelocation;
  unsigned __int64 SystemDllNativeRelocation;
  unsigned __int64 Wow64SharedInformation[16];
  unsigned int RngData;
   unsigned int ___u5;
  _PS_MITIGATION_OPTIONS_MAP MitigationOptionsMap;
  unsigned __int64 CfgBitMap;
  unsigned __int64 CfgBitMapSize;
  unsigned __int64 Wow64CfgBitMap;
  unsigned __int64 Wow64CfgBitMapSize;
  _PS_MITIGATION_AUDIT_OPTIONS_MAP MitigationAuditOptionsMap;
};
typedef const _PS_SYSTEM_DLL_INIT_BLOCK *PCPS_SYSTEM_DLL_INIT_BLOCK;

然后直接ctrl+G输入LdrSystemDllInitBlock跳转过去,设置类型为_PS_SYSTEM_DLL_INIT_BLOCK

然后在伪代码中选中qword_18019b3b8按y再回车即可

image-20250603175605738

MitigationOptionsMap就是一个包含3个8bytes的结构体而已,每个8bytes存储着不同的mitigation相关的一些flag

这里的>>44 & 3检查的就是PROCESS_CREATION_MITIGATION_POLICY2_MODULE_TAMPERING_PROTECTION_MASK标志位

WinBase.h中定义了这个flag

#define PROCESS_CREATION_MITIGATION_POLICY2_MODULE_TAMPERING_PROTECTION_MASK              (0x00000003ui64 << 12)

这个东西在bitmap[1]的高dword上,所以实际上是12+32=44bit,因此上面右移44bit很合理

原作者说在createprocess指定PROCESS_CREATION_MITIGATION_POLICY2_MODULE_TAMPERING_PROTECTION_MASK就可以,但是我实际测试发现并没有生效

我只能手动从nt内核给他开启

windwos自带的会开启这个选项的是C:\Windows\System32\SystemSettingsAdminFlows.exe,犹appinfo服务创建,他这个选项是在PspInheritMitigationOptions的下面这个位置开启的

1: kd> k
 # Child-SP          RetAddr               Call Site
00 ffffe08e`a5253a10 fffff803`71501b23     nt!PspInheritMitigationOptions+0xb3
01 ffffe08e`a5253a80 fffff803`71504348     nt!PspAllocateProcess+0x16d7
02 ffffe08e`a52542b0 fffff803`712274e5     nt!NtCreateUserProcess+0x778
03 ffffe08e`a5254a70 00007fff`e78d0dc4     nt!KiSystemServiceCopyEnd+0x25

image-20250604104644679

我让chatgpt分析了一下PspInheritMitigationOptions函数,它其实就是把前两个参数整合到第三个参数里面

前两个都是mitigationmap结构体,也就是长度为3的8bytes数组

经过回溯可以知道mitigation选项来自于ntcreateuserprocess的第11个参数

经过一番探索,最终知道这个mitigation是通过ntcreateuserprocess的a11 PS_ATTRIBUTE_LIST传进来的

image-20250604142016780

image-20250604142036830

通过在PspBuildCreateProcessContext函数par @$ra可以知道是在PS_ATTRIBUTE结构体的Attribute字段为0000000000020010 的时候终止循环的

image-20250604142214692

结合内存值我们就可以确定20010就是mitigation属性,长度为18,实际上就是_PS_MITIGATION_OPTIONS_MAP

那么现在我们就来看一下这个属性是如何构造出来的

referecen:

使用这个代码可以创建出开启了tamper保护的进程

image-20250604165457639

其实就是手动构造PPS_ATTRIBUTE_LIST

然后还有一点需要注意的就是,我在测试过程中发现这个注册表路径会在PspAllocateProcess函数的这个地方被检查

image-20250604165807669

image-20250604165831047

我们测试的时候,尽量不要使用出现这里面的二进制程序

下面我们要测试一下这个mitigation是怎么跑到用户模式的那个_PS_SYSTEM_DLL_INIT_BLOCK里面的

LdrSystemDllInitBlock的初始化

references :

这个东西是在内核的PspSetupUserProcessAddressSpace函数中通过调用PspPrepareSystemDllInitBlock来初始化的

在调用PspPrepareSystemDllInitBlock函数之前,会先切换到目标进程的空间中

在这个函数中,poi(PsNtdllExports)就是ntdll的base

然后我们的tamper在用户模式检查的时候出现在相对于LdrSystemDllInitBlock 0xa8的位置,这里是oword操作,直接包含了

image-20250604175908293

v15来自于

image-20250604175956129

这个就是前面创建进程时PspBuildCreateProcessContext函数的第四个参数,0x150偏移就是mitigation map

image-20250604180128435

这样就完成了对LdrSystemDllInitBlock的初始化

tamper protection的检查

现在我们再来看一下这个东西是怎么检查的

我测来测去,发现这个mitigation根本就没有用,process hollowing还是可以正常工作的