本帖最后由 ANY.LNK 于 2025-9-29 00:00 编辑
受前不久的某篇报告和某些媒体的影响,难以继续原定的计划,干扰到了我的正常生活历程,遂对其进行了一个几乎完整的逆向。
分析用的文件本体和数据库放在文章末尾。
样本版本:4.4.0.0
样本哈希(SHA256):D0AD4BA2DB697DDAFCD44F98A4DE2ACB9C0749586F8792B440732896A3F7E81B
分析工具:Binary Ninja Free 4.2.6455 Stable,部分参与:Microsoft Copilot, DeepSeek R1, 纳米AI, 密塔AI等大语言模型
(由于是免费版本,不能像https://mp.weixin.qq.com/s/suFU765TtC7pknVszD_XeQ那样直接接入LLM扩展,因此大部分内容仍依赖于我自己的分析,仅在涉及复杂计算没有明显标识的地方引入LLM辅助分析,可能存在错误,若发现恳请诸位帮忙纠正)
下面是具体的分析内容:
Ⅰ-驱动初始化
从DriverEntry开始,首先进行安全检测防止栈溢出,随后进入主流程
驱动基于Wdf构建,若驱动对象为空则进入驱动加载流程,否则进入卸载流程
获取一系列系统信息
其中InitRegChangeNotifyRoutineObject创建一个内核线程,用于监测注册表UCPD服务相关项,可能用于动态更新配置
InitFeatureConfig从注册表的FeatureV2下读取配置,存入全局变量FeatureV2ConfigFlags
这里虽然通过两种方式进行了设备是否位于中国的的判断,但最后的结果在计算时会被清除。在当前的情况下,无论如何,FeatureV2ConfigFlags对应于0x109的位都会被清除。这可能是为了检测正在开发中的函数是否可用或留作将来备用。
最后生成一份有关于驱动配置加载情况的ETW汇报(微软使用了自定义结构体,目前看来与特定地域开启强制数据收集无关)
RegistryChangeKeyNotifyRoutineThread是实际上的监测注册表更改并处理的函数
若更新的内容为REG_DWORD,则将更新后的配置指针存入FeatureV2ConfigFlags,并生成有关配置情况的ETW日志
若更新内容为REG_SZ且配置开关0x1a被设置,则进入DecodePEAndConfigsInDev
此函数首先将读取到的Base64 Unicode转为ANSI,并转为二进制文件,
接着解析PE结构,
分段加载到内存,验证有效性
验证完毕的各个PE段存储在arg3中返回,然后计算哈希SHA256
返回的SHA256继续进行校验确认是否为可信发布者(话说这里是我的问题还是微软写错了?如果我没看错的话这里将计算出的哈希作为唯一的参数传入了CiCheckSignedFile?这函数确认能这么用?)
(从ci.dll导出的CiCheckSignedFile一共有8个参数,用法如下)
- extern "C" __declspec(dllimport) NTSTATUS _stdcall CiCheckSignedFile(
- const PVOID digestBuffer,
- int digestSize,
- int digestIdentifier,
- const LPWIN_CERTIFICATE winCert,
- int sizeOfSecurityDirectory,
- PolicyInfo* policyInfoForSigner,
- LARGE_INTEGER* signingTime,
- PolicyInfo* policyInfoForTimestampingAuthority);
复制代码
更新,这里实际上为转为C伪代码时的BUG,汇编后实际调用类似于
- if (r9 < rcx) {
- goto failure;
- }
- eax = *(DWORD*)r9; // Read 4 bytes from r9
- if (eax <= 0x0C) { // Check if value is ≤ 12
- goto failure;
- }
- if (*(WORD*)(r9 + 4) != 0x200) { // Check if next 2 bytes equal 512
- goto failure;
- }
- if (*(WORD*)(r9 + 6) != r11w) { // Compare next 2 bytes with r11w
- goto failure;
- }
- // Prepare arguments for CiCheckSignedFile
- arg1 = &var_58;
- arg2 = &var_60;
- arg3 = &var_90;
- arg4 = r10;
- arg5 = eax; // Structure size
- CiCheckSignedFile(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
复制代码
对PE的处理在此函数内就告一段落,下面的是一个我这里命名为ObfuseMD5XORCryptConfigs的函数,此函数的主要通过MD5XOR等算法对潜在的配置数据进行加密/解密,可能用于生成抗破解的备份/校验用数据,也可能用于解密已加密的配置数据,或许是对此前出现的对默认浏览器配置的加密数据逆向破解并修改的情况的回应
ObfuseMD5XORCryptConfigs的一阶段是调用ConvertMD5XOR,对输入参数arg[1](即ObfuseMD5XORCryptConfigs的arg3,DecodePEAndConfigsInDev传入的变量var_28[2])进行四轮运算处理
每轮对arg[1]的不同64位段进行XOR操作,调用XORWithMD5对新生成的数据进行MD5计算,用自定义算法、硬编码的种子和MD5值进行两轮处理并将两次的结果进行异或处理并返回掩码(其中运算结果填充掩码的前四字节,后四字节由未知局部变量var_44和var_3c决定)
算法1:
算法2:
完成后,再次循环对ConvertMD5XOR输出的结果进行XORWithMD5运算:
接着对每个掩码和缓冲区中的对应字节进行异或运算
每次处理8字节的一块,直至处理完成。
但最后只返回了用于判断函数是否执行成功了的result,最终的计算结果rdi被忽略没有返回,同时,作为局部传入变量的var_28(就目前来看,如此,不排除以后重新填充的可能)也没有找到赋值的地方
此变量最终传递给了全局变量pMD5XORedConfig,在部分的ETW生成事件时作为被记录的一部分使用,推测可能与云配置有关
DispatcherConfig函数在一系列的验证后在分配的全局表内存中依据ID查找对应函数指针,并以如下伪代码形式调用-处理传入的参数:
- int64_t Dispatcher(int32_t context, int32_t* buffer, int32_t bufferSize) {
- int32_t* entryPtr = &buffer[1]; // Skip the entry count
- uint32_t entryCount = buffer[0];
- // Validate input
- if (!buffer || bufferSize < 5 || entryCount < 1 || entryCount > 999)
- return ERROR_INVALID_PARAMETER;
- // Check buffer is large enough for all entries
- if (bufferSize < entryCount * 13)
- return ERROR_INVALID_PARAMETER;
- for (int i = 0; i < entryCount; i++) {
- // Bounds check for entry header
- if (entryPtr + 3 > buffer + bufferSize)
- break;
- uint32_t handlerId = entryPtr[0];
- uint32_t payloadSize = entryPtr[2];
- // Validate handler ID and payload size
- if (handlerId < 1 || handlerId > 999 || payloadSize == 0)
- break;
- // Bounds check for payload
- if (entryPtr + 3 + payloadSize > buffer + bufferSize)
- break;
- // Lookup handler function
- FunctionPointer handler = HandlerTable[handlerId];
- if (handler)
- handler(context, &entryPtr[3]); // Call handler with payload
- // Move to next entry
- entryPtr += payloadSize + 3;
- }
- return SUCCESS;
- }
复制代码
若上述任意环节出错,则转入异常处理流程ETWCloudConfigCcVerFailedPhase,记录下出错的环节并生成ETW日志
此函数目前尚存在诸多问题,包括未赋值的变量,不正确的函数参数传递调用以及下发的注册表配置不正确(解码后非有效PE格式),该分支判断目前不具有除了报错以外的功能,应该为尚处于开发测试中,且为尚不确定未来是否会实际加入的功能。
但可以大致猜测其功能。未使用的变量var_28可能为FeatureV2ConfigFlags标识出的扩展配置,驱动本身不具有解析实现其对应功能的函数,此时加载扩展,并从这个扩展库中读取解析对应的函数进行操作处理。
当前此驱动还不具备实际的从外部加载其他PE的实际代码,但在DispatcherConfig中有从外部库引入函数处理的代码表明,如若加载,应该是加载到pAllocatePoolForExtension(data_14001e738)指针指向的内存池(分配这个池的历程只有驱动完全加载完成后才会启动,后续会提到)
完成后,获取系统API,注册并启动minifilter
此后进入核心历程的初始化,首先初始化进程相关结构体
获取硬编码的注册表路径,键值,特定的黑名单进程名称
配置
插入泛型表
解析
接着创建在进程创建和加载模块时用于更新进程列表的结构
将新增加的进程插入AVL表
在进程加载新的模块的时候将模块加入AVL表
Ⅱ-注册表更改回调
这也是整个驱动最核心的部分
对如下操作进行检测
-删除键
-修改键值
-删除键值
-重命名键值
-设置键的安全属性
对每种操作的处理逻辑大同小异,监控的具体键值略有不同。但大体都遵循如下逻辑:
1.判断进程是否受信任,及特定配置是否开启
若不满足条件,返回ACCESS_DENIED
两种条件进程的行为会被放行:
(1)进程为系统内核进程(System,PID=4)
(2)进程满足不在黑名单内、在白名单内、签名证书受信任、OriginalFileName被允许
其中对原始文件名检测的函数逻辑尚处于开发阶段,目前只做到了解析OriginalFileName字段的逻辑,并未对实际内容进行任何的提取或判断,此函数总是会返回True,因此实际进行检测的只有前三个判断
2.生成修改的ETW日志(只要出现了对应的操作就会生成)
对一系列中国软件证书的额外检测逻辑包含在ProcNativeCallbackModify函数中,它的上游函数几乎都是对默认浏览器的操作(以及一个任务栏操作)
判断函数支持动态列表和静态列表,其中静态列表是22个硬编码的证书名称
(注:由于对中文的证书名称解析存在问题,部分列表条目显示为空。未能显示的条目包括“珠海市君天电子科技有限公司”等,对应的软件包括360、腾讯电脑管家、鲁大师、WPS Office、迅读PDF等,可自行查阅这些软件在网络上的风评)
动态列表规则也同样由栈回溯规则提供,由ParseRuleStackTrace的arg2传入
若匹配上了证书规则,继续获取目标对象名称
获取到之后继续进行下一层判断(这里的var_188值定义为1,因此不会直接进入这个ETW生成分支,唯一的进入此逻辑的方式就是通过label跳转)
进入这个label的方式是地区为中国(由RCodeCache指示)且生成的随机数能够整除100
这也是该驱动的地区判断唯一一次实际采用的地方,就此判断而言,不能复现此驱动的“依据地区强制开启数据上报”
label的内容为ETW额外记录一次特定模块修改默认浏览器等配置被拒绝的事件
Ⅲ-进程权限回调
核心部分之后,是第二层逻辑,用于动态更新规则列表,以及对一些特定的软件采取额外操作
CreateRegUpdateRoutine - 内容类似于InitRegChangeNotifyRoutineObject
InitRuleList - 加载一般的AllowList和DenyList
SetSpecificBrowserRules - 要采取特殊操作的浏览器列表
目前共两款4个条目
QQ浏览器、Opera浏览器,这些浏览器都实现了自己解析默认浏览器配置并修改的功能,绕过用户在“设置”应用中的配置
SetAntiRogueInjectRules - 对于一些会注入白名单进程(如explorer.exe或msedge.exe)的软件额外设立的规则
此规则列表较为复杂,包含
-发起注入的进程路径
-发起注入的进程名称
-被注入的进程名称
-发起注入的进程签名
(末尾的数字是每组此类别规则列表的条目数量)
InitStackTraceRule - 加载栈回溯规则
AddScheduleCheckDefaultToRoutineList - 设立一个计时器,在操作系统启动后有进程创建的时候定期检查指定的配置是否与通过OpenWith.exe设置的是否一致,同时更新进程列表
若发生变更,则重置回默认值,并生成ETW日志
第二部分的核心
如果发现*匹配规则的进程*或*explorer.exe、msedge.exe正在被注入*
则
(1)移除名单中进程和发起注入的进程的THREAD_SET_CONTEXT、PROCESS_VM_WRITE的权限
(2)将UI Automation相关的0x20位(DESKTOP_JOURNALPLAYBACK)设置为0xffffffdf,AIJDesktop(这是什么?)的0x20位设置为0xfffffff7,相当于阻止利用了
(3)依据情况生成完整的日志
Ⅳ-回调/驱动的卸载和清理
在出现异常/收到指令/需要卸载驱动的情况,将所有的列表清空,资源释放,卸载回调、过滤器和驱动本身
Ⅴ-总结
基于以上分析,目前暂未发现此驱动有超过其描述“User Choice Protection”的界限。
当前版本涉及PE解析部分的代码存在许多问题,尚不具有实际可执行性(不具有加载执行外部未知PE的能力),但已经实现的部分代码的确包含验证外部库、从外部库引入调用额外的函数处理变量(目前还未有充足的定义和赋值)的部分。
此部分代码质量让我有些怀疑微软是否真的有明确的开发计划加入此功能,这些只能等到未来版本才能给出答案。
目前AI说这种动态加载在各软件(比如游戏反作弊、安全软件、硬件驱动开发等)还算常见,此说法尚未得到我的验证
相较于前人的分析,此分析
·否认了https://mp.weixin.qq.com/s/63wAttTVfeMY-y11MTKigA中提到的“驱动自动开启监控功能”“拦截中国安全软件的保护”“阻止中国软件执行”的理论
·纠正了https://mp.weixin.qq.com/s/suFU765TtC7pknVszD_XeQ对于检查是否为可信进程的描述(作者把黑名单和白名单的判断函数写反了,那个if分支下的rbx=0其实是可以NOP掉的,因为默认就是0,此处进行的判断主要是为了决断能否进入嵌套的下一个分支)
·更新了https://binary.ninja/2025/03/25/default-browser-upcd.html中对于特定软件的操作现已升级为“或”,即满足任意其一条件就会触发移除权限的操作,降低了对象的区分度,无需等到特定软件开始注入或调用功能时再移除权限(个人认为确实应该如此,没有暂停目标进程或暂时阻止目标操作的操作,代码执行需要时间,可能移除权限时对方已经完成注入和其他操作了)
其余部分,涉及到云配置,这便是UCPDMgr.exe的内容了,在此不进行过多赘述。
Ⅵ-分析样本及相关数据库
https://wwxl.lanzoul.com/izisH373n5qd,访问密码:bdaz,压缩包密码:infected
PS:折腾了一周,总算能回去写二创了,不知道还能不能接的上。
|