通过TSC观察CPU

自Pentium开始x86 CPU均引入TSC了,可提供指令级执行时间度量的64位时间戳计数寄存器,随着CPU时钟自动增加。

CPU指令

rdtsc: Read Time-Stamp Counter
rdtscp: Read Time-Stamp Counter and Processor ID

调用:

Microsoft Visual C++:

unsigned __int64 __rdtsc();
unsigned __int64 __rdtscp( unsigned int * AUX );

Linux & gcc :

extern __inline unsigned long long
__attribute__((__gnu_inline__, __always_inline__, __artificial__))
__rdtsc (void) {
  return __builtin_ia32_rdtsc ();
}
extern __inline unsigned long long
__attribute__((__gnu_inline__, __always_inline__, __artificial__))
__rdtscp (unsigned int *__A)
{
  return __builtin_ia32_rdtscp (__A);
}

示例:

1: L1 cache及内存的延迟测量:

代码:

{
    ......
    /* flush cache line */
    _mm_clflush(&data[0]);

    /* measure cache miss latency */
    ts = rdtscp(&ui);
    m |= data[0];
    te = rdtscp(&ui);
    CALC_MIN(csci[0], ts, te);

    /* measure cache hit latency */
    ts = rdtscp(&ui);
    m &= data[0];
    te = rdtscp(&ui);
      /* flush cache line */
    _mm_clflush(&data[0]);

    /* measure cache miss latency */
    ts = rdtscp(&ui);
    m |= data[0];
    te = rdtscp(&ui);
    CALC_MIN(csci[0], ts, te);

    /* measure cache hit latency */
    ts = rdtscp(&ui);
    m &= data[0];
    te = rdtscp(&ui);
    CALC_MIN(csci[1], ts, te);
    CALC_MIN(csci[1], ts, te);
}

结果:

rdtscp指令自身耗时:X86: 31 X64: 33

架构 X86 X64
长度 BYTE WORD DWORD QWORD BYTE WORD DWORD QWORD
冷:内存 244 241 246 250 254 254 260 261
热:L1 31 31 31 31 35 35 35 35

问题:读取1个字节与8个字节的所用的时间是一样的,为什么?

2: 常见整型运算及多条指令执行周期:

代码:

{
    ......
    /* measure mul latency */
    ts = rdtscp(&ui);
    m *= *((U32 *)&data[0]);
    te = rdtscp(&ui);
    CALC_MIN(csci[2], ts, te);

    /* measure div latnecy */
    ts = rdtscp(&ui);
    m /= *((U32 *)&data[0]);
    te = rdtscp(&ui);
    CALC_MIN(csci[3], ts, te);

    /* measure 2*mul latnecy */
    ts = rdtscp(&ui);
    m *= *((U32 *)&data[0]);
    m *= *((U32 *)&data[0]);
    te = rdtscp(&ui);
    CALC_MIN(csci2[0], ts, te);

    /* double div */
    ts = rdtscp(&ui);
    m /= *((U32 *)&data[0]);
    m /= *((U32 *)&data[0]);
    te = rdtscp(&ui);
    CALC_MIN(csci2[1], ts, te);

    /* mul + div */
    ts = rdtscp(&ui);
    m *= *((U32 *)&data[0]);
    m /= *((U32 *)&data[0]);
    te = rdtscp(&ui);
    CALC_MIN(csci2[2], ts, te);

    /* measure float mul latency */
    ts = rdtscp(&ui);
    f = f * m;
    te = rdtscp(&ui);
    CALC_MIN(csci[4], ts, te);

    /* measure float div latency */
    while (!m)
        m = rand();
    ts = rdtscp(&ui);
    f = f / m;
    te = rdtscp(&ui);
    CALC_MIN(csci[5], ts, te);
}

结果:

指令周期
数据类型 整型 浮点数
指令组合 m* m/ m*, m* m*, n* m/, m/ m/, n/ m*, m/ f* f/
指行时间 2 20 4 4 48 26 24 17 26

问题:m及n的除法运算的耗时只比m的除法多了一点,但却明显少于m的两次除法,为什么?

注意事项:

  1. 考虑到CPU乱序执行的问题,rdtsc需要配合cpuid或lfence指令,以保证计这一刻流水线已排空,即rdtsc要测量的指令已执行完。后来的CPU提供了rdtscp指令,相当于cpuid + rdtsc,但cpuid指令本身的执行周期有波动,而rdtscp指令的执行更稳定。不过rdtscp不是所有的CPU都支持,使用前要通过cpuid指令查询是不是支持: 即CPUID.80000001H:EDX.RDTSCP[bit 27]是不是为1
  2. 多核系统:新的CPU支持了Invariant TSC特性,可以保证在默认情况下各核心看到的TSC是一致的,否则测量代码执行时不能调度至其它核心上。另外TSC是可以通过MSR来修改的,这种情况下也要注意:
    Invariant TSC:
    Software can modify the value of the time-stamp counter (TSC) of a logical processor by using the WRMSR instruction to write to the IA32_TIME_STAMP_COUNTER MSR
  3. CPU降频问题:第一代TSC的实现是Varient TSC,没有考虑到降频的问题,故在低功耗TSC计数会变慢,甚至停止;后来又有了Constant TSC,解决了降频的问题,但在DEEP-C状态下依然会发生停止计数的情况,所以又有了最新的Invariant TSC的特性:
    The time stamp counter in newer processors may support an enhancement, referred to as invariant TSC.
    Processor’s support for invariant TSC is indicated by CPUID.80000007H:EDX[8].
    The invariant TSC will run at a constant rate in all ACPI P-, C-. and T-states. This is the architectural behavior moving forward. On processors with invariant TSC support, the OS may use the TSC for wall clock timer services (instead of ACPI or HPET timers). TSC reads are much more efficient and do not incur the overhead associated with a ring transition or access to a platform resource.
  4. 指令本身的时间开销
    Pentinum Gold G5500T: 31 cycles
    Core i7-7820HQ: 25 cycles
  5. 权限问题(此指令可用于时序攻击,如Meltdown及Spectre):
    CR4.TSD:
    Time Stamp Disable (bit 2 of CR4) — Restricts the execution of the RDTSC instruction to procedures
    running at privilege level 0 when set; allows RDTSC instruction to be executed at any privilege level when
    clear. This bit also applies to the RDTSCP instruction if supported (if CPUID.80000001H:EDX[27] = 1).
  6. 计数器溢出可能:计算器本身是64位的,即使是主频4G的CPU,也要100多年才会溢出,对于我们的测量来说可以不用考虑
  7. 时序测量容易被干扰(线程调度、抢占、系统中断、虚拟化等),要求测量的指令序列尽量短,并且需要进行多次测量
Continue reading » · Rating: · Written on: 08-17-20 · No Comments »

Windows系统通用回调

本文将针对Windows操作系统所提供系统回调操作做逐一分析,以了解每个回调所能达成什么样的功能及效果。比如部分回调只是用于通知,我们只能通过此回调来了解某一类型事件的发生,只能做审计,拦截动作需要通过其它途径实现;也有回调可以实现即时拦截,通过回调即可阻止恶意程序的攻击企图。

Windows系统在内核及应用层均有提供回调机制,下面分为两部分作介绍:

1 内核回调

内核回调机制 OS版本 功能描述 备注
PsSetCreateProcessNotifyRoutine WIN2K 只是进程创建及退出通知
PsSetCreateProcessNotifyRoutineEx VISTA SP1/SRV2008 可通过返回值阻止进程创建
PsSetCreateProcessNotifyRoutineEx2 WIN10 1703 可接收到WSL/PICO进程创建及退出通知;可阻止进程创建
PsSetCreateThreadNotifyRoutine (PsRemoveCreateThreadNotifyRoutine) WIN2K 线程创建及退出通知 创建时的context为创建者,可用以判断是不是远程线程
PsSetCreateThreadNotifyRoutineEx (PsRemoveCreateThreadNotifyRoutine) WIN10 线程创建及退出通知 创建时的context为新线程环境
PsSetLoadImageNotifyRoutine (PsRemoveLoadImageNotifyRoutine) WIN2K 模块加载通知:支持驱动及应用层DLL加载通知 无模块卸载通知
PsSetLoadImageNotifyRoutineEx (PsRemoveLoadImageNotifyRoutine) WIN10 1709 模块加载通知:支持驱动及应用层DLL加载通知 无模块卸载通知
IoRegisterBootDriverCallback (IoUnRegisterBootDriverCallback) WIN8 Boot-Start驱动加载通知,可拦截,但拦截的后果即BSOD 加载顺序问题;ELAM驱动最早加载,需要特殊签名
SeRegisterImageVerificationCallback (SeUnregisterImageVerificationCallback) WIN8.1 ? 驱动加载回调,可以验证驱动签名信息 函数未公开,目前Windows Defender会使用
ObRegisterCallbacks (ObUnRegisterCallbacks) VISTA SP1 SRV2008 针对进程及线程对象句柄的创建提供前置及后置回调,可修改句柄权限,如针对进程对象无VM映射权限。子进程继承句柄不会通知 WIN7系统可监控文件句柄的创建,需要修改未公开的系统结构(ObjectType->SupportsObjectCallbacks),WIN8后PG会监控。WIN10系统增加了桌面对象的监控支持
CmRegisterCallback (CmUnRegisterCallback) XP 注册表操作回调,可修改、转向或拦截原访问请求 XP/SRV2003/VISTA行为有差异
CmRegisterCallbackEx (CmUnRegisterCallback) VISTA 注册表操作回调,可修改、转向或拦截原访问请求
ExRegisterCallback
-- \Callback\SetSystemTime WIN2K 系统时钟修改回调
-- \Callback\PowerState WIN2K 电源状态改变回调(AC/DC,电源策略,待机或关机) 早于Power Manager的IRP_SET_POWER请求
-- \Callback\ProcessorAdd VISTA 动态增加新CPU事件通知
KeRegisterBoundCallback (KeDeregisterBoundCallback) WIN10 用以捕获应用层边界检测异常,可用于应用层HOOK
Standard Event Objects for VM : 所有 主要用于监控内存状态
-- \KernelObjects\HighMemoryCondition
-- \KernelObjects\LowMemoryCondition
-- \KernelObjects\HighPagedPoolCondition
-- \KernelObjects\LowPagedPoolCondition
-- \KernelObjects\HighNonPagedPoolCondition
-- \KernelObjects\LowNonPagedPoolCondition
-- \KernelObjects\LowCommitCondition
-- \KernelObjects\HighCommitCondition
-- \KernelObjects\MaximumCommitCondition
KeRegisterBugCheckCallback (KeDeregisterBugCheckCallback) WIN2K 系统出错准备蓝屏时的回调通知
KeRegisterBugCheckReasonCallback (KeDeregisterBugCheckReasonCallback) XP SP1 SRV2003 蓝屏后DUMP生成通知,可写入额外1-PAGE信息
FsRtlRegisterFileSystemFilterCallbacks XP 文件系统或过滤驱动回调,一般由I/o Manager,VM及CM发起
IoRegisterFsRegistrationChange (IoUnregisterFsRegistrationChange) 所有 文件系统注册事件通知回调 WIN2K: 已注册文件系统驱动不再通知;XP: 均会通知
IoRegisterFsRegistrationChangeEx (IoUnregisterFsRegistrationChange) WIN2K SP4 文件系统注册事件通知回调,忽略RAW设备 同IoRegisterFsRegistrationChange
IoRegisterFsRegistrationChangeMountAware (IoUnregisterFsRegistrationChange) WIN7 文件系统注册事件通知回调,忽略RAW设备 会额外处理与挂载/Mount的竞争问题
TmEnableCallbacks VISTA 文件事务操作回调
IoRegisterContainerNotification (IoUnregisterContainerNotification) WIN7 会话/Session改变通知,如新用户登录或登出,功能类似于用户层的WTSRegisterSessionNotification
KeRegisterProcessorChangeCallback (KeDeregisterProcessorChangeCallback) WIN2K 动态CPU添加事件回调 有Pre和Post处理
KeRegisterNmiCallback (KeDeregisterNmiCallback) SRV2003 发生不可屏蔽中断时的回调通知
IoRegisterShutdownNotification (IoUnregisterShutdownNotification) WIN2K IRP_MJ_SHUTDOWN请求回调
IoRegisterLastChanceShutdownNotification (IoUnregisterShutdownNotification) WIN2K IRP_MJ_SHUTDOWN请求回调 调用时机晚,在文件系统处理完毕之后
PoRegisterPowerSettingCallback (PoUnregisterPowerSettingCallback) VISTA 电源管理事件响应回调
IoRegisterPlugPlayNotification (IoUnregisterPlugPlayNotification[Ex]) WIN2K PNP事件回调(设备热插拔)
IoWMISetNotificationCallback XP WMI事件回调
DbgSetDebugPrintCallback 未公开 VISTA 捕获内核打印事件(DbgPrint)
IoRegisterPriorityCallback (IoUnregisterPriorityCallback) 未公开 WIN7 SP1 ? I/O 请求优先级调整
PoRegisterCoalescingCallback (PoUnregisterCoalescingCallback) 未公开 WIN8.1 CM和文件系统相关:在系统空闲的时候实施cache的聚合刷新
PsSetLegoNotifyRoutine 未公开 XP 类似TLS,可以实现线程终结时的回调通知

2 应用层回调

应用层回调 OS 功能 备注
LdrRegisterDllNotification (LdrUnregisterDllNotification) VISTA 可接收进程内DLL模块加载及卸载事件,不可拦截
ProcessInstrumentationCallback WIN7 可在程序每次Ring 0至3切换后收到通知,在系统服务调用结束时刻 需要进程注入后主动调用NtSetInformationProcess,需要SeDebugPrivilege等
RegisterServiceCtrlHandler(Ex) XP 在应用层服务中接收系统事件:设备热插拔、用户登录、电源管理等
RegisterDeviceNotification 所有 GUI程序获取硬件改变通知
RegNotifyChangeKeyValue WIN2K 监控注册表指定键的子键及值的改变
ReadDirectoryChange 所有 监控指定目录中的文件及子目录的变化
AddSecureMemoryCacheCallback (RemoveSecureMemoryCacheCallback) VISTA SP1 可监控Secure类型的共享内存的释放
LdrSetDllManifestProber 未公开 XP 可收到DLL模块加载事件,可用于拦截
LdrInitShimEngineDynamic 未公开 WIN7 可接收Shim Engine通知,可接管DLL加载等 调用时机有要求,需要R3 HOOK; 不同Windows版本如WIN7及8.1差别较大
RtlSetThreadPoolStartFunc 未公开 WIN2K 设置线程初始化及退出函数,kernel32内部使用

3 通用回调的限制

Windows系统虽然提供了回调接口,但因为性能等各种因素的原因还会限定回调的数量:

  1. CmRegisterCallback限制为最多100个,无高度号设置,后注册的回调可能收不到请求
  2. PsSetLoadImageNotifyRoutine限制为最多8个,64位系统共64个
  3. PsSetCreateProcessNotifyRoutine限制为最多8个
  4. PsSetCreateThreadNotifyRoutine限制为最多8个

不少驱动安装为了防止注册失败的情况选择了尽早启动并完成相关回调的注册,也有一些工具如PcHunter会通过DKOM方式进行动态扩展。

另外不同的OS版本的支持限制,在上表中也有说明,如Vista之后的Windows系统才开始支持注册表事务操作,这部分在使用中也要注意,Ob Callback也有类似的问题。

4 参考链接

  1. Magnesium Object Manager Sandbox, A More Effective Sandbox Method for Windows 7
  2. MSDN: Filtering Registry Calls
  3. Filter Manager Support for Minifilter Drivers
  4. MSDN: REG_NOTIFY_CLASS enumeration
  5. CodeMachine: Kernel Callback Functions
  6. OSR: Kernel Mode Extensions Driver
  7. WDK Samples: Registry Logging
  8. RegistryCallback routines
  9. MSDN:Handling Notifications
Continue reading » · Rating: · Written on: 05-04-19 · No Comments »

Windows进程注入方法

0 前言

安全软件为了达成进程安全及行为审计的目标经常会采用进程注入的方式,即将自己的DLL注入至用户进程中,以对恶意的注入模块进行对抗。就进程注入方法来说有多种方式,木马及攻击方可使用的手段更多,毕竟攻击者对稳定性及善后部分不用做过多考虑。

本文将对注入方式做一个简单的汇总对比,下面分别从应用层及内核层的不同实现方案进行拆解:

1 应用层注入手段

本章节将主要介绍完全应用层的注入实现。

1.1 远程线程及APC注入、SetThreadContext

这两种方式的实现机制是不同的,但思路是一样的,都需要上传负载(Payload)至要注入的进程空间,一般的操作过程如下:

通过OpenProcess获取进程句柄(HANDLE),然后在目标进程空间申请内存(VirtualAlloc或VirtualAllocEx]),然后调用WriteProcessMemory将shellcode负载写入目标进程空间,最后调用CreateRemoteThread、NtCreateThreadEx、RtlCreateUserThread等创建用户线程,或者添加APC(QueueUserApc)至用户线程以完成shellcode的执行目的。

SetThreadContxt机制是将原线程挂起(SuspendThread),通过修改线程Context中的eip/rip指针至上传的shellcode地址。

Shellcode的设计一般只是简单的LdrLoadDll的调用,复杂的有如DoublePulsar木马所采用的,直接将Payload DLL进行展开并手工加载。

用户层注入的问题是权限受限,另外很容易被检测到,现在的杀软普遍都会特殊关照上述的这些特征函数。

1.2 Win32消息钩子

通过Win32 API SetWindowsHookEx注册系统级消息钩子,以截获同一桌面上所有线程的消息通信,从而实现了DLL模块的注入。当然消息钩子的局限也很明显:

  1. 被注入进程必须接受用户输入,使用了消息队列,如带GUI界面程序;服务进程一般无需用户输入,所以此种注入方法对服务进程无效
  2. 64位系统中,64位进程只能设置针对64位程序的消息HOOK,32位进程只针对32位程序,不可交叉混用。和SetWindowsHookEx类似的SetWinEventHook 虽然同样可以取到所有进程的消息,但并不能导致DLL注入。

  3. Windows Automation API:Windows Automation API提供给程序访问其它程序窗口及组件(UI elements)的能力,一般用于自动化测试。利用Windows Automation API实现注入的过程,和消息钩子的方式没有本质区别,也有着同样的限制。

1.3 系统提供机制(注册表选项)

1) App Init DLLs

所有加载User32.dll的程序均会自动加载此键值下的DLL,主要针对Win7及之前的Windows版本,从Win8之后此机制不被推荐使用,在UEFI Secure Boot模式下此项被默认关闭。

所在注册表位置:

  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows

  • HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs

使用此方式的木马及病毒:GinwuiCherry PickerT9000

2) App Cert DLLs

所有调用下面Win32 API的程序均会自动加载上述注册表中所列的DLL文件:

  • CreateProcess
  • CreateProcessAsUser
  • CreateProcessWithLoginW
  • CreateProcessWithTokenW
  • WinExec

所在注册表位置:

  • HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager

使用此方式的木马及病毒:HoneybeeFIN8 PUNCHBUGGY

3) Image File Execution Options

这是Windows系统提供的一个调试辅助机制,用以在特定进程启动或退出时启动指定程序(如调试器等)。用户通过更改IFEO键值达成启动不同进程的目的,从而导致原进程加载请求失败。

所在注册表位置:

  • HKLM\SOFTWARE{\Wow6432Node}\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\

  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SilentProcessExit\

4) Shim Database (SDB)攻击

Windows系统通过Shim数据库(SDB文件,位于%windir%\AppPatch\sysmain.sdb)以提升应用程序的向后兼容(backward compatibility)。SDB数据库中包含针对上千个程序的上百种配置,只要有管理员权限就可操纵此数据库,就可以修改任意程序的各种属性,如以下多种属性:

  • InjectDll
  • LoadLibraryRedirectFlag
  • ForceAdminAccess
  • RelaunchElevated
  • WrpMitigation
  • DisableNX
  • ModifyShellLinkPath
  • VirtualRegistry
  • DisableAdvancedRPCClientHardening
  • CorrectFilePaths
  • DisableSeh
  • DisableWindowsDefender
  • ShellExecuteXP

涉及注册表项:

  • HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Custom
  • HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\InstalledSDB

使用此方式的木马及病毒:BlackEnergy, GooKit, Roaming Tiger,QianSet.exe,VzQqgi.dll木马

1.4 PE程序IAT表静态修改

早期的Win32病毒常采用的就是静态感染方式,会将自身的代码写入被被感染程序,然后修改PE入口函数至病毒的代码段;同样的方式,静态修改PE文件的导入表以增加新的DLL注入亦不是难事。

针对有签名的PE程序,类似的修改会导致签名校验失败。

1.5 DLL替换

创建一个假个但导出一模一样的DLL文件用以替换原系统的,替换办法一般有两种办法:

  1. 系统原DLL文件改名,并替换系统DLL
  2. 更改DLL搜索路径(SetDLLDirectory)

这种方式要处理的问题:

  1. 系统模块签名问题无法处理:PPL进程将无法运行
  2. 不同版本的系统DLL处理堆积

2 内核层注入手段

内核R0层注入要比应用层R3的手段少了很多,实现难度会大一些,但内核实现更加隐蔽,难以被屏蔽。通过创建用户层线程或插入APC以实现注入的方式被普遍使用,原理上同R3的远程线程及APC注入的思路基本一致,均需要向目标进程空间上载参数及shellcode代码,只是R0及R3各自使用的支持函数不同。

2.1 IAT表注入

在进程创建过程中,内核驱动通过PsSetCreateProcessNotifyRoutine或PsSetCreateProcessNotifyRoutineEx得到通知,此时用户进程的创建过程是被阻塞的,在处理通知的回调中,内核驱动可以修改进程的内存镜像中的导入表,将要注入的驱动加入其中。

以wermgr.exe进程(PID: 0x6bc)的创建为例,此进程由services.exe(PID: 0x2bc)创建:

后续会收到模块加载的通知回调,加载模块的顺序依次为wermgr.exe自身、ntdll.dll、kernel32.dll、KernelBase.dll、msvcrt.dll等,回调时的调用栈为:

导入表修改动作是在进程镜像本身的模块加载回调中执行的,即程序本身模块加载的时机。

IAT表注入的主要问题:

  1. .NET程序的兼容性问题
    • 托管代码与非托管DLL代码的混合
    • 64位.NET程序没有导入表项
  2. 受保护进程的注入问题,必须要通过NTDLL HOOK来解决,因为创建Section对象时内核会验证DLL签名等,在IAT已注入的情况下签名又无法签证通过时会导致程序加载失败

2.2 Shellcode注入

创建用户线程及插入APC的注入手段均需要上载shellcode代码至目标进程,shellcode可以只是简单的LdrLoadDll调用用以加载HOOK引擎及工作模块,复杂一些的话可以在内核层将DLL手工加载至目标进程空间,正如木马DoublePulsar所实现的加载器【D23】"Generic Relective DLL Loader"或者内核层Turla Driver Loader(TDL)驱动加载器【D18】。

常用shellcode/payload构造(BlackBone的实现):

    // shellcode for X64
    UCHAR code[] =
    {
        0x48, 0x83, 0xEC, 0x28,             // sub rsp, 0x28
        0x48, 0x31, 0xC9,                   // xor rcx, rcx
        0x48, 0x31, 0xD2,                   // xor rdx, rdx
        0x49, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0, // mov r8, ModuleFileName   offset+12
        0x49, 0xB9, 0, 0, 0, 0, 0, 0, 0, 0, // mov r9, ModuleHandle     offset+28
        0x48, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0, // mov rax, LdrLoadDll      offset+32
        0xFF, 0xD0,                         // call rax
        0x48, 0xBA, 0, 0, 0, 0, 0, 0, 0, 0, // mov rdx, COMPLETE_OFFSET offset+44
        0xC7, 0x02, 0x7E, 0x1E, 0x37, 0xC0, // mov [rdx], CALL_COMPLETE 
        0x48, 0x83, 0xC4, 0x28,             // add rsp, 0x28
        0xC3                                // ret
    };

    // shellcode for X86
    UCHAR code[] = 
    { 
        0x68, 0, 0, 0, 0,                   // push ModuleHandle        offset+01 
        0x68, 0, 0, 0, 0,                   // push ModuleFileName      offset+06
        0x6A, 0,                            // push Flags  
        0x6A, 0,                            // push PathToFile
        0xE8, 0, 0, 0, 0,                   // call LdrLoadDll          offset+15
        0xBA, 0, 0, 0, 0,                   // mov edx, COMPLETE_OFFSET offset+20
        0xC7, 0x02, 0x7E, 0x1E, 0x37, 0xC0, // mov [edx], CALL_COMPLETE     
        0xC2, 0x04, 0x00                    // ret 4
    };    
1) 创建用户线程

创建用户线程一般通过NtCreateThreadEx(Visata及以后OS)或NtCreateThread(XP),这两个函数在内核中均未导出且是未公开的,其地址获取可以通过SSDT或者ntoskrnl镜像解析完成。

NTKERNELAPI NTSTATUS NTAPI
NtCreateThread(
    __out PHANDLE ThreadHandle,
    __in ACCESS_MASK DesiredAccess,
    __in_opt POBJECT_ATTRIBUTES ObjectAttributes,
    __in HANDLE ProcessHandle,
    __out PCLIENT_ID ClientId,
    __in PCONTEXT ThreadContext,
    __in PINITIAL_TEB InitialTeb,
    __in BOOLEAN CreateSuspended
    );

NTKERNELAPI NTSTATUS NTAPI
NtCreateThreadEx(
    OUT PHANDLE hThread,
    IN ACCESS_MASK DesiredAccess,
    IN PVOID ObjectAttributes,
    IN HANDLE ProcessHandle,
    IN PVOID lpStartAddress,
    IN PVOID lpParameter,
    IN ULONG Flags,
    IN SIZE_T StackZeroBits,
    IN SIZE_T SizeOfStackCommit,
    IN SIZE_T SizeOfStackReserve,
    OUT PVOID lpBytesBuffer
    );
2) APC注入

在内核中构造一个APC结构并添加至用户线程的APC队列,等条件满足时系统会执行APC中设定的callback,从而达成进程DLL注入的目的。

相关函数原型如下:

NTKERNELAPI VOID NTAPI
KeInitializeApc (
    __out PRKAPC    Apc,
    __in PRKTHREAD  Thread,
    __in KAPC_ENVIRONMENT   Environment,
    __in PKKERNEL_ROUTINE   KernelRoutine,
    __in_opt PKRUNDOWN_ROUTINE  RundownRoutine,
    __in_opt PKNORMAL_ROUTINE   NormalRoutine,
    __in_opt KPROCESSOR_MODE    ProcessorMode,
    __in_opt PVOID  NormalContext 
);

NTKERNELAPI BOOLEAN NTAPI
KeInsertQueueApc    (
    __inout PRKAPC  Apc,
    __in_opt PVOID  SystemArgument1,
    __in_opt PVOID  SystemArgument2,
    __in KPRIORITY  Increment 
);

360安全卫士的驱动360fsflt.sys、木马DoublePulsar【D22】等所采用的方式就是Kernel APC注入,这种方式被普遍使用。但APC注入要注意以下问题:

  1. 时机问题:要求所注入进程已经创建了线程并且已经执行(主线程都是紧随进程创建的)
  2. 依赖线程状态:APC的执行依赖于所注入线程的状态:一般是从内核态切换回用户态时执行
  3. 必须与注入线程做同步,或者在所注入线程环境中插入APC,不然可能会注入至错误进程,另外与第三方驱动同时注入APC存在竞争及一致性问题,当然竞争窗口极小; 每个线程均有两个APC队列,当线程在内核态下调用KeAttachProcess时会做切换,切换过程中没有任何保护
3) HOOK NTDLL方式

内核驱动可以通过内核的回调机制截获用户进程创建及PE加载器加载并初始化DLL链的每个环节,可以监控到指定模块加载时同步进行inline hook,并将关注点的执行流程导向至已上载的shellcode负载。

回调本身是串行在程序的加载流程中的,即回调不返回,进程的创建及DLL的加载过程是被阻塞的,省却了同步与不一致性的问题的处理。

之所以要选择ntdll.dll的原因是,ntdll.dll是所有的Win32程序必须加载的,并且其执行过程比程序本身的入口执行要早;另外选择ntdll.dll而不是Kernel32.dll等模块的原因是,Kernel32.dll等模块并不是必须的,比如Native程序如csrss,exe、autochk.exe等,还有一些程序将Kernel32.dll等模块设置为Delay-Loading-DLL,并不会在程序启动之初就立即加载并执行。

为了尽早地获取控制权,常用的HOOK点一般放在ntdll!LdrInitializeThunk、ntdll!NtOpenDirectoryObject或者ntdll!LdrLoadDLL等关键函数点。Haiheiwang木马【D16】就是通过修改ntdll!NtTestAlert的流程将自己的工作模块加入被感染进程的,当然ntdll!NtTestAlert也存在时机较晚的问题,并不能满足咱们的需求。

这种方式在安全软件中也广泛使用,特别是对针对Win8系统年引入的受保护进程的注入问题,由内核注入Shellcode并通过HOOK ntdll!NtCreateSection来加载非系统模块是最实用且有效方式,目前360安全卫士的注入亦是采用此方式。受保护PPL进程只对用户层的Section对象创建有签名验证,我们通过将内核层创建Section对象映射至用户空间的方式达成向受保护PPL进程注入的目的。

其它的替代方案会导致受保护进行安全性的妥协等,比如短暂取消受保护进行的保护状态以达成注入目的,此种方式有可能会触发KPP/PG检测失效,另外也很容易被第三方恶意利用,比如MalwareFox AntiMalware的一个被曝光的漏洞【D24: MalwareFox AntiMalware 2.74.0.150 LPE】。

Shellcode内存映射可以在HOOK引擎模块加载之后进行销毁,存在窗口时间非常短,被其它安全软件查到或被其它恶意程序所利用的可能比较小。

3 参考资料

3.1 浏览器自身防护资讯

  1. B1: About Google Chrome's incompatible applications warning
  2. B2: Firefox will block DLL Injections
  3. B3: Protecting Microsoft Edge against binary injection

3.2 进程DLL注入资料

  1. D1: DLL Injection with SetThreadContext
  2. D2: R3 DLL Injection: Inject All The Things
  3. D3: COUNTERCEPT: Analyzing the DOUBLEPULSAR Kernel DLL Injection Technique
  4. D4: COUNTERCEPT: Dynamic Shellcode Execution
  5. D5: Ten Process Injection Techniques
  6. D6: DLL Injector via Windows Automation API
  7. D7: Microsoft: AppInit DLLs and Secure Boot
  8. D8: BlackHat: Malicious Application Compatibility Shims
  9. D9: To SDB, Or Not To SDB: FIN7 Leveraging Shim Databases for Persistence
  10. D10: FreeBuf: 走近微软安全技术Shim
  11. D11: ABICC: Windows API/ABI Changes Analysis
  12. D12: MITRE ATT&CK: Application Shimming
  13. D13: FireEye: The Real Shim Shady
  14. D14: iSIGHTPARTNERS: 固守: 微软Fix It补丁机制原理及攻击利用
  15. D15: 腾讯:木马牟利再出新招-恶意利用Windows shim技术锁主页
  16. D16: 腾讯:Haiheiwang木马分析
  17. D17: Windows Vista APC Internals
  18. D18: TDL: Turla Driver Loader
  19. D19: Scylla and API Set Map
  20. D20: Microsoft Docs: Windows API Sets
  21. D21: Windows_7_Kernel_Changes: api-ms-win-core DLLs
  22. D22: DOUBLEPULSAR Kernel DLL Injection Technique
  23. D23: DOUBLEPULSAR: Generic Reflective DLL Loader
  24. D24: MalwareFox AntiMalware 2.74.0.150 - LPE
  25. D25:DLL Injection via SetDLLDirectory
  26. D26: CodeProject: Create your Proxy DLLs automatically
  27. D27: Proxy WS2_32.DLL to create your own firewall
  28. D28: CodeProject: API hooking revealed

3.3 DLL注入商业方案

  1. D30: Shellter Pro: DLL Injection Kit
  2. D31: madCodeHook: DLL Injection Kit
Continue reading » · Rating: · Written on: 04-01-19 · No Comments »

脑残的PsSetCreateProcessNotifyRoutine

Vista之前的系统,XP及Server 2003只有8个坑,就是说前8个调用成功之后,后续的所有调用都会失败,看来通过注册callback的方式来监控进程非常不靠谱。

从内核角度来说,尽量少的调用不仅提高系统响应时间,也降低了不靠谱的驱动所可能带来问题,但限制为区区8个总不是解决办法吧,比如Symantec家的SEP动不动就占3个,腾讯家(Tencent)更牛B,安全管家要用几个,QQ也要用,就连浏览器也要搞个驱动并也要注册个进程创建的回调通知,这也太让人无语了吧。

好在从Vista开始,这个限制从8个变成了64个,翻了整整8倍,呵呵,这下够用了?!

下面是一个典型的XP系统实例,8个回调会被占满了,后起的驱动们只能想其它办法了:

0: kd> x nt!PspCreateProcessNotifyRoutine
80564a40 nt!PspCreateProcessNotifyRoutine = <no type information>
0: kd> dd 80564a40
80564a40  e101894f e15e4eb7 e2031ff6 e1e8b56f
80564a50  e20a8d9f e51c641f e52246b7 e5ad82cf
80564a60  00000008 00000000 86a85d58 8656fac0
80564a70  86b9b5a0 86be5208 86be45a0 00000000
80564a80  00000000 00000000 00000000 00000000
80564a90  00000000 00000000 00000000 00000000
80564aa0  00000000 6d6f7441 00000000 00000001
80564ab0  00000000 00000000 00000000 00000000
0: kd> .for (r $t0=0; $t0 < 8; r $t0=$t0+1) {r $t1 = poi($t0 * 4 + nt!PspCreateProcessNotifyRoutine); .if ($t1 == 0) {.continue;}; r $t1 = $t1 & 0xFFFFFFF8; r $t1 = poi($t1 + 4); r $t0; r $t1; u $t1; ln $t1;}
$t0=00000000
$t1=f7310790
TsFltMgr+0x10790:
f7310790 55              push    ebp
f7310791 8bec            mov     ebp,esp
f7310793 83e4f8          and     esp,0FFFFFFF8h
f7310796 83ec14          sub     esp,14h
f7310799 53              push    ebx
f731079a 56              push    esi
f731079b 8b35a07b31f7    mov     esi,dword ptr [TsFltMgr+0x17ba0 (f7317ba0)]
f73107a1 57              push    edi
$t0=00000001
$t1=f771a788
ghcore+0x3788:
f771a788 8bff            mov     edi,edi
f771a78a 55              push    ebp
f771a78b 8bec            mov     ebp,esp
f771a78d 33c0            xor     eax,eax
f771a78f 390564b471f7    cmp     dword ptr [ghcore+0x4464 (f771b464)],eax
f771a795 7508            jne     ghcore+0x379f (f771a79f)
f771a797 39056cb471f7    cmp     dword ptr [ghcore+0x446c (f771b46c)],eax
f771a79d 741a            je      ghcore+0x37b9 (f771a7b9)
$t0=00000002
$t1=ed1a3b00
SYMEVENT+0xcb00:
ed1a3b00 8bff            mov     edi,edi
ed1a3b02 55              push    ebp
ed1a3b03 8bec            mov     ebp,esp
ed1a3b05 51              push    ecx
ed1a3b06 53              push    ebx
ed1a3b07 8b5d08          mov     ebx,dword ptr [ebp+8]
ed1a3b0a 56              push    esi
ed1a3b0b 33f6            xor     esi,esi
$t0=00000003
$t1=ecfeb120
QQFrmMgr+0xe120:
ecfeb120 8bff            mov     edi,edi
ecfeb122 55              push    ebp
ecfeb123 8bec            mov     ebp,esp
ecfeb125 51              push    ecx
ecfeb126 51              push    ecx
ecfeb127 53              push    ebx
ecfeb128 8b5d0c          mov     ebx,dword ptr [ebp+0Ch]
ecfeb12b 56              push    esi
$t0=00000004
$t1=ecd098a0
SysPlant+0x78a0:
ecd098a0 55              push    ebp
ecd098a1 8bec            mov     ebp,esp
ecd098a3 83e4f8          and     esp,0FFFFFFF8h
ecd098a6 51              push    ecx
ecd098a7 53              push    ebx
ecd098a8 56              push    esi
ecd098a9 57              push    edi
ecd098aa a12480d1ec      mov     eax,dword ptr [SysPlant+0x16024 (ecd18024)]
$t0=00000005
$t1=866b41d0
866b41d0 90              nop
866b41d1 90              nop
866b41d2 e9e7c4f466      jmp     QMUdisk+0x26be (ed6006be)
866b41d7 90              nop
866b41d8 90              nop
866b41d9 90              nop
866b41da 90              nop
866b41db 90              nop
$t0=00000006
$t1=ecaf8070
BHDrvx86+0x6c070:
ecaf8070 8bff            mov     edi,edi
ecaf8072 55              push    ebp
ecaf8073 8bec            mov     ebp,esp
ecaf8075 807d1000        cmp     byte ptr [ebp+10h],0
ecaf8079 750f            jne     BHDrvx86+0x6c08a (ecaf808a)
ecaf807b 8b450c          mov     eax,dword ptr [ebp+0Ch]
ecaf807e 8b0d48e0b8ec    mov     ecx,dword ptr [BHDrvx86+0x102048 (ecb8e048)]
ecaf8084 50              push    eax
$t0=00000007
$t1=ee461eba
dekfs+0x4eba:
ee461eba 8bff            mov     edi,edi
ee461ebc 55              push    ebp
ee461ebd 8bec            mov     ebp,esp
ee461ebf 83ec10          sub     esp,10h
ee461ec2 807d1000        cmp     byte ptr [ebp+10h],0
ee461ec6 53              push    ebx
ee461ec7 56              push    esi
ee461ec8 57              push    edi

Server 2008 X64系统已经可以支持64个注册回调了,这个限制是硬编码在

函数PsSetCreateProcessNotifyRoutine中的:

0: kd> u nt!PspSetCreateProcessNotifyRoutine
nt!PspSetCreateProcessNotifyRoutine:
fffff800`01c3fab0 48895c2408      mov     qword ptr [rsp+8],rbx
fffff800`01c3fab5 48896c2410      mov     qword ptr [rsp+10h],rbp
fffff800`01c3faba 4889742418      mov     qword ptr [rsp+18h],rsi
……
fffff800`01c3fad4 bb01000000      mov     ebx,1
……
fffff800`01c3fb3e e89d3cf1ff      call    nt!ExDereferenceCallBackBlock (fffff800`01b537e0)
fffff800`01c3fb43 4403e3          add     r12d,ebx
fffff800`01c3fb46 4183fc40        cmp     r12d,40h
fffff800`01c3fb4a 72ae            jb      nt!PspSetCreateProcessNotifyRoutine+0x4a (fffff800`01c3fafa)
fffff800`01c3fb4c 66019fb4010000  add     word ptr [rdi+1B4h],bx
fffff800`01c3fb53 7518            jne     nt!PspSetCreateProcessNotifyRoutine+0xbd (fffff800`01c3fb6d)

此系统上并未安装腾讯管家等类似的防毒程序,但是64个注册回调已有6个被使用,差不多全是M$自家给占用的:

0: kd> x nt!PspCreateProcessNotifyRoutine
fffff800`019f8ee0 nt!PspCreateProcessNotifyRoutine = <no type information>
0: kd> dq fffff800`019f8ee0
fffff800`019f8ee0  fffff880`00008c4f fffff880`006d7acf
fffff800`019f8ef0  fffff880`006f694f fffff880`007ff5cf
fffff800`019f8f00  fffff880`098168af fffff880`0a30216f
fffff800`019f8f10  00000000`00000000 00000000`00000000
fffff800`019f8f20  00000000`00000000 00000000`00000000
fffff800`019f8f30  00000000`00000000 00000000`00000000
fffff800`019f8f40  00000000`00000000 00000000`00000000
fffff800`019f8f50  00000000`00000000 00000000`00000000
0: kd> .for (r $t0=0; $t0 < 0x40; r $t0=$t0+1) {r $t1=poi($t0 * 8 + nt!PspCreateProcessNotifyRoutine); .if ($t1 == 0) {.continue}; r $t0; r $t1 = $t1 & 0xFFFFFFFFFFFFFFF0; r $t1 = poi($t1 + 8); r $t1; u $t1; ln $t1;}
$t0=0000000000000000
$t1=fffff8000188dbd0
nt!ViCreateProcessCallback:
fffff800`0188dbd0 fff3            push    rbx
fffff800`0188dbd2 4883ec40        sub     rsp,40h
fffff800`0188dbd6 833dc3a2160000  cmp     dword ptr [nt!ViVerifierEnabled (fffff800`019f7ea0)],0
fffff800`0188dbdd 0f8583b6fcff    jne     nt! ?? ::FNODOBFM::`string'+0x21140 (fffff800`01859266)
fffff800`0188dbe3 4883c440        add     rsp,40h
fffff800`0188dbe7 5b              pop     rbx
fffff800`0188dbe8 c3              ret
fffff800`0188dbe9 90              nop
(fffff800`0188dbd0)   nt!ViCreateProcessCallback   |  (fffff800`0188dbf0)   nt!IopAllocateFileObjectExtension
Exact matches:
nt!ViCreateProcessCallback = <no type information>
$t0=0000000000000001
$t1=fffffa6000e52ffc
ksecdd!CredMarshalTargetInfo+0x8cc:
fffffa60`00e52ffc 4883ec28        sub     rsp,28h
fffffa60`00e53000 488364244800    and     qword ptr [rsp+48h],0
fffffa60`00e53006 4584c0          test    r8b,r8b
fffffa60`00e53009 488bc2          mov     rax,rdx
fffffa60`00e5300c 7546            jne     ksecdd!CredMarshalTargetInfo+0x924 (fffffa60`00e53054)
fffffa60`00e5300e 488d542448      lea     rdx,[rsp+48h]
fffffa60`00e53013 488bc8          mov     rcx,rax
fffffa60`00e53016 ff15dc00feff    call    qword ptr [ksecdd!BCryptDestroySecret+0x191c8 (fffffa60`00e330f8)]
(fffffa60`00e52730)   ksecdd!CredMarshalTargetInfo+0x8cc   |  (fffffa60`00e535f4)   ksecdd!AcquireCredentialsHandleW
$t0=0000000000000002
$t1=fffffa600106f830
tcpip+0x69830:
fffffa60`0106f830 4d85c0          test    r8,r8
fffffa60`0106f833 7405            je      tcpip+0x6983a (fffffa60`0106f83a)
fffffa60`0106f835 e9860b0000      jmp     tcpip+0x6a3c0 (fffffa60`010703c0)
fffffa60`0106f83a e911130000      jmp     tcpip+0x6ab50 (fffffa60`01070b50)
fffffa60`0106f83f 90              nop
fffffa60`0106f840 90              nop
fffffa60`0106f841 90              nop
fffffa60`0106f842 90              nop
$t0=0000000000000003
$t1=fffffa600074e06c
CI!I_PEProcessNotify:
fffffa60`0074e06c 4584c0          test    r8b,r8b
fffffa60`0074e06f 7528            jne     CI!I_PEProcessNotify+0x2d (fffffa60`0074e099)
fffffa60`0074e071 53              push    rbx
fffffa60`0074e072 4883ec20        sub     rsp,20h
fffffa60`0074e076 488bda          mov     rbx,rdx
fffffa60`0074e079 ff15f1d1f5ff    call    qword ptr [CI!_imp_IoGetCurrentProcess (fffffa60`006ab270)]
fffffa60`0074e07f 488bc8          mov     rcx,rax
fffffa60`0074e082 ff1530d0f5ff    call    qword ptr [CI!_imp_PsIsProtectedProcess (fffffa60`006ab0b8)]
(fffffa60`0074e06c)   CI!I_PEProcessNotify   |  (fffffa60`0074e0a0)   CI!RSA32Alloc
Exact matches:
CI!I_PEProcessNotify = <no type information>
$t0=0000000000000004
$t1=fffffa6002fac964
dekfs+0x6964:
fffffa60`02fac964 48895c2408      mov     qword ptr [rsp+8],rbx
fffffa60`02fac969 48896c2410      mov     qword ptr [rsp+10h],rbp
fffffa60`02fac96e 56              push    rsi
fffffa60`02fac96f 57              push    rdi
fffffa60`02fac970 4154            push    r12
fffffa60`02fac972 4155            push    r13
fffffa60`02fac974 4156            push    r14
fffffa60`02fac976 4883ec50        sub     rsp,50h
$t0=0000000000000005
$t1=fffffa600c545b2d
peauth!I_PEProcessNotify:
fffffa60`0c545b2d 48895c2408      mov     qword ptr [rsp+8],rbx
fffffa60`0c545b32 57              push    rdi
fffffa60`0c545b33 4883ec20        sub     rsp,20h
fffffa60`0c545b37 e8cc6bfdff      call    peauth!WARBIRD::Stub_VerifyVerifierCheckSum (fffffa60`0c51c708)
fffffa60`0c545b3c 90              nop
fffffa60`0c545b3d 360000          add     byte ptr ss:[rax],al
fffffa60`0c545b40 00fd            add     ch,bh
fffffa60`0c545b42 140a            adc     al,0Ah
(fffffa60`0c545b2d)   peauth!I_PEProcessNotify   |  (fffffa60`0c545fe1)   peauth!PEReturnCertChain
Exact matches:
peauth!I_PEProcessNotify = <no type information>

Continue reading » · Rating: · Written on: 06-22-15 · No Comments »