首页
社区
课程
招聘
[原创]尝试分析某海外加固
发表于: 2026-6-21 22:57 13629

[原创]尝试分析某海外加固

2026-6-21 22:57
13629

样本是ClashRoyale,本文由AI生成,就当笔记了,请见谅(
有一说一,ai的算法敏感度和复杂逻辑抽取关键部分的能力真的吊

本文逐轮记录对 Shield 加固层的源码分析过程。每一轮按 thinking → 源码 → 分析 → 结论 结构组织,函数地址均为相对 SO 基址的 RVA。

经过方法论纠正后,第一件事是把几个看似关键的函数放在一起读,看逻辑连贯性,而不是孤立地"这个函数调了 tgkill 所以它是校验"。选的切入点是:一个注册回调的函数(sub_21A69C)、一个被大量引用的数据表首项(sub_45129C)、一个看起来像"空校验"的桩(nullsub_103)、一个走 BIO 风格回调的函数(sub_5E1AB8)。目的是先判断之前认定的"函数指针校验表 / CRC 校验"是不是真的存在。

sub_5E1AB8 (RVA 0x5E1AB8) —— 一个多分支回调,按操作码 n2 分发:

sub_21A69C (RVA 0x21A69C) —— 构造对象并装入回调表:

sub_45129C (RVA 0x45129C) —— 设置 vtable:

nullsub_103 (RVA 0x453834) —— 空桩:

sub_612A7C (RVA 0x612A7C) —— 字符串常量:

把这几个函数放一起看,逻辑高度自洽:

之前认定的"0x692Dxx 是函数指针校验表"完全错误。这片实际上是:

sub_453898(之前认定的"Shield kill 入口")只是某个 C++ 类的虚方法(析构类),跟检测无关。libsupercell_clashroyale.so 的绝大部分体积是静态链接的 LibreSSL,Shield 保护逻辑只占一小部分混在其中。抓到的 crc32 / tgkill 全是 LibreSSL 正常功能,不能作为 Shield 入口。

既然 0x692Dxx 是 vtable,那 Shield 真正的保护层得重新找。方法论给的线索是三条:调 ChaCha20 解密代码段、读写 rwxp 段、mprotect 改权限。先从 ChaCha20 入手 —— 之前 agent 报告说 ChaCha20 在 sub_249D94,但那是 agent 猜的,需要自己读代码确认,然后沿 caller 链往上追,看它解密的是 TLS 数据还是代码段。

sub_249D94 (RVA 0x249D94) —— 含 ChaCha20 常量:

sub_24A228 (RVA 0x24A228) —— caller,ChaCha20 封装:

sub_551630 (RVA 0x551630) —— 上一层 caller:

sub_551750 (RVA 0x551750) —— 再上一层,函数体充满页对齐常量:

这条链(sub_551750 → sub_551630 → sub_24A228 → sub_249D94)是按页(4096 字节)解密代码段的 ChaCha20 解密器,不是 TLS 加密。agent 之前给的 ChaCha20 地址(0x68192C)是错的,真正的 core 在 0x249D94

但要判断它是 Shield 解密还是 LibreSSL 内部用,还得继续往上追 caller —— 看它服务于什么。

sub_551750 的 caller 是 sub_5512A4,继续往上追。如果 caller 链通往 ssl_pkt / ssl_lib 范围,那是 TLS;如果通往一个独立线程循环,那是 Shield 解密服务。重点看 sub_5512A4 的栈分配(页大小缓冲?)和它的 caller 是不是一个 __noreturn 循环。

sub_5512A4 (RVA 0x5512A4) —— 单页解密入口:

sub_550F20 (RVA 0x550F20) —— sub_5512A4 的调用者,__noreturn

sub_5ECA08 (RVA 0x5ECA08) —— 等待原语:

sub_550E54 (RVA 0x550E54) —— 创建该线程:

Shield 用的是 生产者-消费者 RPC 线程模型做代码段按需解密(lazy decryption):

这彻底推翻了之前脑补的"周期性轮询 CRC" —— 实际是按需解密,事件驱动。

解密线程找到了,但要理解整体调度,得找到"业务代码 → 提交解密请求"这一环。sub_551110 是请求提交函数(写 dword_6BEE44),它的 caller 就是业务侧调用点。再往上 sub_551110 ← sub_5E244C,而 sub_5E244C 看起来是按 api_id 派发 —— 这可能就是用户洞察的"全局统一的调用派发函数"。重点读它的查表、解密触发、调用逻辑。

sub_5E244C (RVA 0x5E244C) —— 全局派发咽喉:

sub_551110 (RVA 0x551110) —— 解密请求提交:

完整可信执行调度模型串起来了,用户洞察全部命中:

关键 RVA:

模型建立后,下一个问题是 CRC/完整性校验在哪一层。sub_5E244Cif (*result != 1) 是状态检查不是 CRC。候选点:sub_62F0C8(状态检查函数内部)和 sub_5512A4(解密入口内部)。sub_5512A4 的 callees 里有 sub_18D6F8 / sub_18D7F0 / sub_18D930 —— 在 0x18Dxxx 区域,跟之前看到的 sub_18D17C(crc32)同区域,可能是 CRC helper。挨个读确认。

sub_62F0C8 (RVA 0x62F0C8) —— 状态检查函数:

sub_5512A4 后半段 —— 解密后的处理:

sub_18D6F8 (RVA 0x18D6F8) —— 被"上千"函数调用:

sub_62F0C8 是 TLS 缓冲区管理,sub_18D6F8 是 string 析构 —— 都不是 CRC。sub_5E244C 里的 *result != 1 检查的是 TLS 缓冲项首字节(解密状态标记),不是完整性比较。sub_5512A4 后半的 sub_550D6C 是候选比较点,下一轮确认。

继续排除。读 sub_18D930(0x18Dxxx 区域,可能是 hash)、sub_18D7F0(同区域)、sub_550D6C(比较候选)。这三个放一起看,判断 sub_5512A4 后半是不是解密后的完整性校验。

sub_18D930 (RVA 0x18D930) —— 被上千函数调用:

sub_18D7F0 (RVA 0x18D7F0) —— 构造+析构临时对象:

sub_550D6C (RVA 0x550D6C) —— "比较候选":

sub_5512A4 后半的 sub_550D6C(dword_6BF258, v14)把解密结果 write 到 fd,不是 CRC 比较。dword_6BF258sub_550E54 传入的 fd(解密线程关联的输出 fd)。

sub_18D930 / sub_18D7F0 / sub_18D6F8 全是 std::string 操作。到目前为止整条解密链里没找到 CRC 比较

还剩 sub_47981Csub_20D574(都在 sub_5512A4 解密后调用)。读这两个,如果还不是 CRC,就能下结论:libsupercell 侧根本没有独立的 CRC 校验代码,完整性靠别的机制。

sub_47981C (RVA 0x47981C) —— 经典编码特征:

sub_20D574 (RVA 0x20D574) —— 字符串操作:

libsupercell 侧整条解密链里没有 CRC 校验

所有候选都排除了:

初步推断:libsupercell 只是解密服务(ChaCha20 按需解密代码页),完整性校验 + tgkill + 重加密逻辑不在 libsupercell 的反编译里,可能在 libg.so 的 rwxp 段(Shield 解密出的保护代码本身)。(这个推断在第 10 轮被修正 —— 校验其实也在 libsupercell,只是藏在多层派发后面。)

回到第 1 点任务:dump off_6A6F98 表,对比 maps + libc decompile 回注函数名。先看表结构 —— sub_5E244C*(&off_6A6F98 + v7) 取的是指针(QWORD),指向 entry 结构(+0 flags, +8 函数指针)。先 dump 指针数组本身,再 dump 它指向的 entry 内容。

off_6A6F98 指针数组(内存 dump)

整理(off_6A6F98[v7],v7 = api_id - 4):

entry 内容(0x6CD0C8 - 0x6CD1FF,内存 dump)

entry 表运行时按需解密。静态 dump 抓的时机 entry 还未被解密(全 0xFF = 加密态/未初始化态)。

这印证了第 3-4 轮的模型:sub_5E244C 触发时,先经 sub_551110 解密对应 entry → 读 flags + 函数指针 → 调用 → (可能)重新加密回去。所以静态无法回注函数名 —— 抓到的 entry 全是 0xFF。

要拿真实函数指针,必须在运行时 hook sub_5E244C,在 v8 = off_6A6F98[v7] 之后、调用 v9 之前 dump entry。但 hook 本身可能触发检测 —— 鸡生蛋问题,需要先理解检测在哪。

第 4 轮看到 sub_5E244C 的 LABEL_11 有一行 sub_6826D0(1)(返回值被忽略),当时不知道它干啥。现在回头追。

第一步:读 sub_6826D0,发现是 thunk —— return qword_6C5288(),即读 GOT[0x6C5288] 跳转。这是 Shield 的第一层混淆:通过 GOT 间接调用,让静态 caller 关系断裂(sub_6826D0 的 callers 显示 none)。

第二步:读 GOT 0x6C5288 的值。这里我读错了 —— dump 字节是 B0 88 DE 97 6F 00 00 00,我丢了一位读成 0x6FDE88B0(32 位,不像合法 ARM64 指针,查 maps 也查不到,卡住了)。用户纠正为 0x6F97DE88B0(48 位,合法指针)。

第三步:重新算 RVA:0x6F97DE88B0 - 0x6F97C5A000 (libsupercell base) = 0x18E8B0,对照 maps 落在 0x6f97de7000-0x6f982b7000 r-xp 0x18d000 段内。所以 sub_6826D0 不是 libc 函数,是 Shield 自家代码,故意用 GOT 间接调用来抗分析。

第四步:读 sub_18E8B0(RVA 0x18E8B0),发现它又是派发:sub_5533DC(ctx, 4, off_6ACCE8[0])off_6ACCE8 这张新表浮现。

关键转折是用户纠正地址读错。 没这一步,会一直卡在"0x6FDE88B0 是什么",看不到 sub_18E8B0off_6ACCE8。教训:dump 字节要逐位核对,ARM64 指针是 48 位,丢了高位就会变成无效地址。

sub_6826D0 (RVA 0x6826D0) —— thunk:

GOT 0x6C5288 的值(内存 dump):

0x6F97DE88B0 - 0x6F97C5A000 = 0x18E8B0(RVA,在 libsupercell 内)

sub_18E8B0 (RVA 0x18E8B0) —— callers: none(通过 GOT 间接调用):

off_6ACCE8 表(内存 dump)

整理:

追踪链(从 LABEL_11 到校验核心):

三层间接,每层都用不同手段混淆:GOT 间接(断裂 caller 关系)+ thunk 中转 + 表驱动派发。

off_6ACCE8 的角色 —— 校验目标表

sub_5533DC 怎么用它(第三个参数 a3):

a3 = off_6ACCE8[0] = 0x642BF8 是一个代码段地址sub_5533DC 拿它对应的缓存项 v8,对 v8 处 0x98 字节算 XXH64。所以 off_6ACCE8 每一项 = 一段需要 XXH64 完整性校验的代码的起始地址

sub_18E8B0 不是孤例 —— 它是一群 thunk 中的一个

sub_5533DC 的 callers 有 22 个,全是 0x18Exxx

每个都是 sub_18E8B0 的同胞(同样的小 thunk 结构),区别只是用 off_6ACCxx 系列里不同的表 + 不同的 key。每个 thunk 对应一个受保护 API 的校验入口。

两层表的角色对比(容易混)

off_6A6F98 回答"调哪个函数",off_6ACCE8 回答"校验哪段代码"。两张表服务于派发的两个不同阶段。

多级派发的完整图景浮现

off_6ACCE8校验目标表(不是函数表),列出所有需要完整性校验的代码段地址。sub_5533DC 是真正的校验+派发核心。下一轮深读 sub_5533DC + sub_452500,定位校验算法和 kill 路径。

sub_5533DC 是多级派发的最后一层,参数 (ctx, key, code_ptr) 强烈暗示"校验 code_ptr 处的代码然后派发"。读它完整逻辑,重点找:查表、比较、不等时的处理。同时读它调用的 sub_452500(可能是校验算法)和 sub_2ADF90(不等时调用,可能是重加密)。

sub_5533DC (RVA 0x5533DC) —— 校验+派发核心:

sub_452500 (RVA 0x452500) —— 校验算法(常量铁证):

常量识别:

sub_2ADF90 (RVA 0x2ADF90) —— 不等时调用,被 37 处调用:

sub_5533DC 完整逻辑:

sub_452500 的 5 个常量是 XXH64 的素数定义(Yann Collet xxHash 库的 XXH_PRIME64_1..5)。算法是 XXH64,不是 CRC。输入 code_ptr 处 0x98(152)字节,输出 64 位 hash。这正是用户说的"直接比较" —— 算 hash 然后直接和存储值比。

sub_2ADF90 逐字段拆解(提交重加密任务到任务队列):

结构是典型的生产者-消费者任务队列提交

疑点已解开(汇编确认):sub_5533DCsub_2ADF90() 紧跟 SVC 0x80,而 SVC 解码结果是 exit_group(1)(见下 SVC 解码)—— 进程直接终止。所以 sub_2ADF90 提交的任务根本来不及被消费执行

sub_2ADF90 的真实意义是 取证/记录

修正之前的说法:之前笼统说"由后台线程处理重加密"完全错误。准确说 —— sub_2ADF90异常取证入队,进程随后 exit_group(1) 直接死,队列不会被消费。sub_2ADF9037 处调用(所有 XXH64 校验失败点 + 其他检测点),是统一的"异常记录口"。

关于"重新加密代码段"的观察:如果动态调试时确实看到代码段被重新加密,那不是 sub_5533DC 这条路径干的(它直接 exit)。可能是:

需要进一步动态确认 "重加密" 到底发生在哪条路径。

SVC 0x80 汇编解码 —— 确认是 exit_group(1)

从 IDA 反汇编(loc_553630,RVA 0x553630)解码:

修正之前的判断:之前推测"可能是 tgkill/exit_group/rt_tgsigqueueinfo 之一" —— 现在汇编确认是 exit_group(1)。这同时解开了 sub_2ADF90 的疑点(见下)。

完整检测 + kill 链最终定位

修正第 7 轮的初步推断:之前说"libsupercell 侧没有 CRC,校验在 libg rwxp 段" —— 不准确。校验就在 libsupercellsub_5533DC + sub_452500),只是藏在三层派发(sub_6826D0sub_18E8B0sub_5533DC)后面,且算法是 XXH64 不是 CRC。

第 10 轮确认 sub_5533DC 路径是 sub_2ADF90 + exit_group(1),没有重加密。但 sub_2ADF90 有 37 个 caller,且用户动态观察到过"重新加密代码段"。两条线查重加密到底在哪:

线索:pointers.txt 显示 qword_6C7C10/18/20 的引用全部集中在 0x2AD8F0-0x2AD9DCsub_2ADF90 之前),说明生产者和消费者都在 0x2ADxxx 区域。0x2AD948/974/9C4/9DC 这些地址读 qword_6C7C18(头指针) 和 qword_6C7C20(计数) —— 这是遍历/消费特征。这些地址都落在 sub_2AD8BC(0x2AD8BC-0x2ADAE4)内部。

sub_5D34BC (RVA 0x5D34BC) —— 代表性 caller(37 个之一):

sub_2AD8BC (RVA 0x2AD8BC) —— 链表消费者,__noreturn,caller = sub_550E54(创建解密线程那个函数!):

libsupercell 里根本没有"重加密代码段"的执行路径。

检测响应链统一是:

qword_6C7C10检测事件记录表(供 crash report / forensic),不是重加密队列。没有任何函数从 qword_6C7C10 取节点做 ChaCha20 重加密。

那用户观察到的"重新加密代码段"是什么? 三种可能:

最可能是第 1 种(误判)。按需解密模型下,代码段平时加密存储,首次执行时解密成明文并保持。不存在"解密→执行→重加密"的循环。观察到的"代码段变化"是首次解密,不是重加密。

修正第 10 轮的说法:之前推测"sub_2ADF90 提交重加密任务"完全错误。准确说 —— sub_2ADF90检测事件入队sub_2AD8BC事件聚合统计,两者都写 qword_6C7C10 记录表,无消费者做重加密。检测失败的最终响应就是 exit_group(1)

Shield kill 走 SVC 0x80(直接系统调用),不经过 libc PLT。汇编解码确认是 exit_group(1)(X8=0x5E=94,X0=1),且 syscall 号通过常量混淆(0xE5113923 ^ 0xE511397D = 0x5E)动态算出。所以 hook libc 的 tgkill / exit / exit_group 完全拦不到,连 syscall 层 hook 都要小心处理常量混淆。

qword_6C7C10检测事件记录表(不是重加密队列):sub_2ADF90(37 处检测点)和 sub_2AD8BC(聚合器)都向它写,供 crash report / forensic 用,无消费者做重加密。libsupercell 里没有"解密→执行→重加密"循环 —— 代码段首次解密后保持明文。

最干净:patch sub_5533DC 的检测分支条件,让 if (标记有效 && XXH64 != stored && 标记非0) 永远 false。需 IDA 定位 RVA 0x5533DC 处对应的条件跳转指令(B.NE / CBNZ),改成 B(跳过 kill 块)或 NOP 掉比较+跳转。

用户洞察 代码证据
"stub 持有含目标函数绝对地址的对象" off_6A6F98 函数地址表 ✅
"全局统一的调用派发函数" sub_5E244C
"在合适时机 hook 解密的函数" sub_551110 (请求) / sub_5512A4 (执行) ✅
"周期性轮询"(我脑补) 错误,实际是按需解密 ✅
候选 实际功能
sub_18D17C crc32(但只用于 BIO 文件读取流式校验,非代码段)
sub_18D6F8 std::string 析构
sub_18D7F0 临时 string 构造+析构
sub_18D930 std::string::append (memcpy)
sub_20D574 std::string::replace
sub_47981C Base64 编码
sub_62F0C8 TLS 缓冲区管理
sub_550D6C write() 封装
v7 api_id 值(指向 entry)
0 4 0x6CD128
1 5 0x6CD1A8
2 6 0x6CD0C8
3 7 0x6CD0E8
4 8 0x6CD108
7 11 0x6CD168
角色 内容
off_6A6F98 函数派发表(sub_5E244C 用) entry 槽 → 真实函数指针(运行时解密)
off_6ACCE8 校验目标表(sub_5533DC 用) 代码段 RVA → 被校验的 152 字节起点
组件 RVA 真身
校验算法 sub_452500 XXH64(PRIME1-5 常量铁证)
校验+派发 sub_5533DC 比较 XXH64 → 不等 kill
重加密提交 sub_2ADF90 挂链表 qword_6C7C10,37 处调用
kill SVC 0x80 直接 syscall(绕过 libc)
受保护代码表 off_6ACCE8 代码段 RVA 列表(0x642BF8 等),每段 0x98 字节被校验
校验点 位置 类型 作用
A sub_5E244C *result != 1 1 字节状态标记 判断代码页是否已解密,未解密则触发 ChaCha20 解密
B sub_5533DC XXH64 != stored 64 位 XXH64 完整性校验,每次派发都走,不等则 kill
线程 RVA 职责
sub_550F20 0x550F20 按需解密 —— cond_wait(dword_6BEE44) 等请求,ChaCha20 解密代码页
sub_2AD8BC 0x2AD8BC 事件聚合 —— cond_wait(unk_6C7BA0) 等事件,聚合统计到 qword_6C7C10
符号 RVA 角色
sub_5E244C 0x5E244C 全局派发咽喉
sub_551110 0x551110 解密请求提交(同步 RPC)
sub_550F20 0x550F20 解密线程主循环
sub_550E54 0x550E54 创建解密线程
sub_5512A4 0x5512A4 单页解密入口
sub_249D94 0x249D94 ChaCha20 core
sub_6826D0 0x6826D0 thunk → GOT → sub_18E8B0
sub_18E8B0 0x18E8B0 第二级派发
sub_5533DC 0x5533DC 校验+kill 核心
sub_452500 0x452500 XXH64 校验算法
sub_2ADF90 0x2ADF90 检测事件入队(37 处检测点写 qword_6C7C10)
sub_2AD8BC 0x2AD8BC 事件聚合线程(第二常驻线程,sub_550E54 启动)
off_6A6F98 0x6A6F98 受保护函数地址表(entry 加密存储)
off_6ACCE8 0x6ACCE8 受保护代码段地址表(每段 0x98 字节被 XXH64)
dword_6BEE44 0x6BEE44 解密请求标志
dword_6BEE40 0x6BEE40 请求结构起点(1048B)
__int64 sub_5E1AB8(_QWORD *a1, int *a2, __int64 a3, unsigned __int64 n7, signed int n2) {
    ...
    if (n2 <= 1) {
        if (!n2) { ... return; }
        if (n2 != 1) return sub_5614E8(...);
        v11 = sub_5215A8((__int64)a1, a3, n7);       // read
        if ((v11 & 0x8000000000000000LL) == 0) {
            ...
            do {
                ...
                LODWORD(v15) = crc32(v15, a3 + v14, (unsigned int)v16);  // 流式 CRC
                ...
            } while (v14 < v10);
            ...
        }
    }
    if (n2 == 7) return *((_QWORD *)a2 + 4);   // tell
    if (n2 == 8) { ... return v9 | ...; }      // size
    ...
}
_QWORD *sub_21A69C(__int64 a1, __int64 (__fastcall *a2)(...), __int64 a3, __int64 a4) {
    v11[0] = sub_59ECE0(a1);
    v8 = a2(a1, a3, v11, 8, 8);          // 用回调初始化
    ...
    result = (_QWORD *)sub_19492C(a4);
    if (result) {
        *result = a1;
        result[1] = a2;                   // 存回调表
        result[2] = a3;
        result[5] = v9;
    }
    return result;
}
__int64 sub_45129C(_QWORD *a1) {
    *a1 = off_692D10;                     // 把 0x692D10 装到对象头
    return sub_324B20((__int64)(a1 + 2));
}
void nullsub_103() { ; }
void __noreturn sub_612A7C() {
    sub_612DAC("Pure virtual function called!");
}
__int64 sub_249D94(_OWORD *a1, _OWORD *a2, unsigned __int64 n0x3F_1, int *a4, __int64 a5, unsigned int a6) {
    ...
    int n1797285236;  // 1797285236
    int n2036477234;  // 2036477234
    int n857760878;   // 857760878
    int n1634760805;  // 1634760805
    ...
}
__int64 sub_24A228(_OWORD *a1, _OWORD *a2, unsigned __int64 n0x3F, __int64 a4, __int64 a5) {
    if (*(_QWORD *)(a4 + 16) != 8 || *(_QWORD *)(a5 + 16) != 32)
        return 0;
    v5 = *(int **)a4;                    // nonce
    if (!v5) { v5 = &byte_6C6540; byte_6C6540 = 0; }
    v6 = *(char **)a5;                   // key
    if (!v6) { v6 = &byte_6C6540; byte_6C6540 = 0; }
    return sub_249D94(a1, a2, n0x3F, v5, (__int64)v6, 0x14u);  // 0x14 = 20 rounds
}
__int64 sub_551630(__int64 a1, __int64 x1_0) {
    ...
    sub_240BF8(*(_QWORD *)a1, a2);
    sub_19C758(25, v5);
    sub_5A5868(&n4, a2, v5);
    ...
    sub_24A228(*(_OWORD **)(a1 + 4136), *(_OWORD **)(a1 + 4136),
               a1 - *(_QWORD *)(a1 + 4136) + 4128, x1_0, (__int64)v8);
    return sub_19BF40((__int64)v8);
}
__int64 sub_551750(__int64 a1, __int64 a2) {
    ...
    char n20;                // w8
    unsigned __int64 n0xFFF; // x9
    __int64 n4096;           // x9
    ...
    __int64 n32;             // x10
    ...
    unsigned __int64 n0x1000; // x9
    unsigned __int64 n0xFFF_1; // x8
    __int64 n4096_1;         // x8
    ...
}
__int64 sub_5512A4(__int64 *a1) {
    ...
    char v25[4112];                      // ⭐ 4112 = 4096 + 16,一页 + 元数据
    ...
    sub_682420(v25, 0, 4112);            // memset 4112 字节
    v3 = sub_551750(v22, &v15);          // 调页解密器
    n1274593961 = *off_6AF320;
    ...
}
void __noreturn sub_550F20() {
    while (1) {
        sub_682420(v4, 0, 1040);
        for (i = 0; i != 1024; i += 16) {
            v1 = &v4[i];
            *((_DWORD *)v1 + 4) = 2;
            *((_QWORD *)v1 + 3) = 0;
        }
        v2 = &unk_6CBA88;
        v3 = 1;
        sub_5ECFEC(&unk_6CBA88);
        while (!dword_6BEE44)             // ⭐ 等待解密请求标志
            sub_5ECA08(&unk_6CBA58, &v2); // cond_wait
        sub_682330(v4, &dword_6BEE40, 1048);  // 取请求结构
        if (v3 == 1) sub_5ED040(v2);
        sub_2ADB1C();
        sub_5512A4(v4);                   // ⭐ ChaCha20 解密一页
        sub_5EC9E8(&unk_6CBAB0);
    }
}
__int64 sub_5ECA08(pthread_cond_t *cond, __int64 a2) {
    if (*(_BYTE *)(a2 + 8) != 1) {
        ... "condition_variable::wait: mutex not locked"
    }
    result = pthread_cond_wait(cond, *(pthread_mutex_t **)a2);
    ...
}
__int64 sub_550E54(int a1, __int64 a2, __int64 a3) {
    dword_6BF258 = a1;
    qword_6CBB08 = a2;
    sub_551EEC(&unk_6CBB10, a3);
    sub_551004(v4, sub_550F20);          // ⭐ pthread_create(start=sub_550F20)
    sub_5ED1DC(v4);
    sub_5ED170(v4);
    ...
}
_BYTE *sub_5E244C(unsigned int a1, __int64 a2, __int64 a3) {
    result = (_BYTE *)sub_62F0C8(&unk_6C3348);    // 查 TLS 缓冲
    if (*result != 1) {                            // 【校验点 A: 1字节解密状态标记】
        sub_551110(a1, a2);                        // 提交解密请求
        goto LABEL_11;
    }
    v7 = a1 - 4;
    if (a1 - 4 >= 0xD || ((0x129Fu >> v7) & 1) == 0)  // 位图校验合法 api_id
LABEL_11:
        sub_6826D0(1);
    v8 = (__int64)*(&off_6A6F98 + v7);            // ⭐ 查函数地址表
    v9 = *(__int64 (__fastcall **)(_QWORD))(v8 + 8); // 取真实函数指针
    if ((*(_BYTE *)v8 & 4) != 0)
        return (_BYTE *)v9(a1, a2, a3);            // 派发调用
    if (v9 != 1) {
        if (v9) return (_BYTE *)v9(a1);
        ...
    }
    return result;
}
__int64 sub_551110(int a1, __int64 a2) {
    v4 = sub_5ECFEC(&unk_6CBA88);
    v5 = sub_682410(v4);
    dword_6BEE40 = v5;                  // 请求序号
    dword_6BEE44 = a1;                  // ⭐ 设置 api_id
    qword_6BEE48 = a2;                  // 请求数据
    do {                                // 填充请求表 (6BEE58 起, 1024字节)
        n1024 -= 16;
        *((_DWORD *)v7 - 2) = 2;
        *v7 = 0;
        v7 += 2;
    } while (n1024);
    qword_6BF250 = 0;
    sub_439028((__int64)&unk_6BEE50);
    sub_5ED040(&unk_6CBA88);            // signal 解密线程
    sub_5EC9E8(&unk_6CBA58);            // unlock
    sub_5ECFEC(&unk_6CBAE0);
    result = sub_5ECA08(&unk_6CBAB0, &v9);  // ⭐ cond_wait 等结果 (同步 RPC)
    ...
}
__int64 sub_62F0C8(_QWORD *a1) {
    v2 = atomic_load(a1 + 2);
    if (!v2) {
        sub_1E9BE4(dword_6D2C08, sub_62F2F0);
        pthread_mutex_lock(&mutex__8);
        v2 = a1[2];
        if (!v2) {
            v2 = ++qword_6D2C10;
            atomic_store(qword_6D2C10, a1 + 2);
        }
        pthread_mutex_unlock(&mutex__8);
        pointer_2 = pthread_getspecific(key_3);   // ⭐ TLS 查询
        ...
        pointer[1] = v6;
        pthread_setspecific(key, pointer);        // 存回 TLS
    }
    ...
    *pointer = 1;                                  // 标记初始化
    return result;
}
    LODWORD(v19[0]) = v6 & 0xFF00FFFF;
    memset(v14, 0, sizeof(v14));
    sub_18D7F0();
    sub_18D930(v14, 0, (char *)v19, 0);          // 0x18Dxxx 区域调用
    ...
    sub_18D930(&v11, 0, v25, v7);
    sub_47981C((__int64)&v11, v19);
    sub_20D574(v14, 1u, _1, v19[2]);
    sub_18D6F8(v19);
    sub_18D6F8(&v11);
    if (dword_6BF258 != -1) {
        if ((char *)sub_550D6C(dword_6BF258, (__int64)v14) != &byte_88020) {  // ⭐ 比较
            v10 = (_QWORD *)sub_6826D0(1);
            sub_18D6F8(v14);
            sub_33BC80(v10);
        }
        sub_682560((unsigned int)dword_6BF258);
        sub_6824D0(10);
    }
    return sub_18D6F8(v14);
__int64 sub_18D6F8(_QWORD *a1) {
    v1 = *a1;
    result = sub_18D784(*a1, a1[2]);              // 释放
    if (v1) result = sub_62D93C(v1);              // free
    *a1 = 0; a1[1] = 0; a1[2] = 0;                // 清零三字段
    return result;
}
__int64 *sub_18D930(__int64 *result, unsigned __int64 a2, char *a3, unsigned __int64 a4) {
    v4 = *result;
    if (*result) {
        v5 = result[1];                  // 长度
        v7 = v5 - a2;                    // 偏移
        if (v5 >= a2) {
            v8 = (char *)(v4 + a2);      // 目标
            if (a4 < v7) v7 = a4;
            if (a3 >= v8 || &a3[v7] <= v8) {
                // 正向拷贝
                do { *v8++ = *a3++; --v11; } while (v11);
            } else {
                // 重叠,反向拷贝
                do { *(_BYTE *)(a2 + v4 - 1 + v9) = a3[v9 - 1]; --v9; } while (!v10);
            }
            if (result[2] < v7 + a2) return sub_18D9BC();  // 扩容
        }
    }
    return result;
}
__int64 sub_18D7F0() {
    _QWORD v1[4];
    v1[3] = *(_QWORD *)(_ReadStatusReg(TPIDR_EL0) + 40);
    sub_18D844(v1);                       // 构造临时 string
    return sub_18D6F8(v1);                // 立即析构
}
__int64 sub_550D6C(unsigned int a1, __int64 a2) {
    _1 = *(char **)a2;                    // 数据指针
    if (!*(_QWORD *)a2) { _1 = 1; ... }
    v4 = *(_QWORD *)(a2 + 16);            // 长度
    if (!v4) return n4;
    n4 = n4_0;
    do {
        v7 = sub_682490(a1, _1, v4);      // ⭐ syscall 封装
        if (v7 < 1 || v4 < v7) {
            if (!v7) return n4;
            n11 = *(_DWORD *)_errno(v7, v8);
            if (n11 != 11 && n11 != 4) return n4;   // 11=EAGAIN, 4=EINTR
        } else {
            _1 += v7; v4 -= v7;
        }
    } while (v4);
    return n4_1;
}
__int64 *sub_47981C(__int64 a1, __int64 *a2) {
    n3 = *(_QWORD *)(a1 + 16);
    v5 = n3 / 3;                         // ⭐ 3 字节分组
    n3_2 = 3 * (n3 / 3);
    v27 = n3 % 3;
    if (n3 % 3) v7 = v5 + 1;
    else v7 = n3 / 3;
    result = sub_220388(4 * v7, a2);     // ⭐ 输出 4 字节
    ...
    do {
        v15 = ((unsigned __int8)_1[3 * v11] << 16)
            | ((unsigned __int8)_1[3 * v11 + 1] << 8)
            | (unsigned __int8)_1[3 * v11 + 2];   // 24 位打包
        do {
            result = sub_479CF4((v15 >> (n24 - 6)) & 0x3F);  // ⭐ 取 6 位 + 查表
            n24 -= 6;
            *_1_3++ = (char)result;
        } while (n24);
        ++v11; _1_2 += 4;
    } while (v11 != v5);
    ...
    return sub_682420(&v16[n3_1], 61, 3 - v27);   // ⭐ 61 = '=' padding
}
__int64 *sub_20D574(__int64 *a1, unsigned __int64 a2, char *a3, unsigned __int64 a4) {
    v5 = a1[2];
    if (a2 <= v5) {
        ...
        sub_18DAD8(a1, n0x7F, v14);              // 临时
        v12 = a1[2];
        if (a1[1] - v12 < v8) v8 = a1[1] - v12;
        sub_18D930(a1, v8 + a2, (char *)(*a1 + a2), v12 - a2);  // 移动后半
        sub_18D930(a1, a2, a3, v8);              // 拷贝新数据
        sub_18D6F8(v14);                         // 析构临时
    }
    return a1;
}
0x6A6F90 | 68 2D 5E 00 00 00 00 00  28 D1 6C 00 00 00 00 00   (表前一项)
0x6A6FA0 | A8 D1 6C 00 00 00 00 00  C8 D0 6C 00 00 00 00 00
0x6A6FB0 | E8 D0 6C 00 00 00 00 00  08 D1 6C 00 00 00 00 00
0x6A6FC0 | 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
0x6A6FD0 | 68 D1 6C 00 00 00 00 00  ...
0x6CD0C8 | FF FF FF FF FF FF FF FF  FF FF FF FF FF FF FF FF
0x6CD0D8 | FF FF FF FF FF FF FF FF  FF FF FF FF FF FF FF FF
...      (全部 0xFF)
0x6CD1FF | FF FF FF FF FF FF FF FF  FF FF FF FF FF FF FF FF
__int64 sub_6826D0() {
    return qword_6C5288();               // GOT 间接调用
}
0x6C5288 | B0 88 DE 97 6F 00 00 000x6F97DE88B0
__int64 sub_18E8B0(unsigned int a1) {
    v2 = sub_553218();                                       // 获取上下文
    v3 = sub_5533DC(v2, 4, off_6ACCE8[0]);                   // ⭐ 按 key=4 查 off_6ACCE8 表
    return v3(a1);                                           // 调用返回的函数指针
}
0x6ACCE0 | 64 42 63 00 00 00 00 00  F8 2B 64 00 00 00 00 00
0x6ACCF0 | 78 AA 65 00 00 00 00 00  8C 41 63 00 00 00 00 00

传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!

收藏
免费 6
打赏
分享
最新回复 (3)
雪    币: 5
活跃值: (5365)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
好文章啊,顶你一下
2026-6-26 22:10
0
雪    币: 351
活跃值: (7799)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
3
记得这个玩意有一个超大的函数
2026-6-29 10:22
0
雪    币: 1791
活跃值: (546)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
4
New对象处 记得这个玩意有一个超大的函数
init_proc入口函数都挺大 最后rustfrida秒过,换个工具发现以前都是没苦硬吃
2026-7-23 22:43
0
游客
登录 | 注册 方可回帖
返回