Cris.Q

Back

记一杯花茶的逆向工程Blur image

(本文是新手 re 同学的学习笔记,所以可能比较啰唆,见谅~)

审题#

审题可知,flower 代表花指令,tea 代表加密算法是 TEA 或者某种 TEA 的衍生算法。

分析加密算法#

首先导航到 Strings 发现一个很明显的 base 64 编码数据 bTRwbGUxc3hubg==,解密后发现 主席是男娘 得到了 m4ple1sxnn,猜想应该是密钥。

查看字符串

再使用 FindCrypt 插件查看是否有明显的常见加密算法迹象:

查看可能的加密

没有看到有用信息,重新根据 Strings 中保留的字符串,定位到跳转点。整理后代码如下(其中包含对我的一些疑问点的注释,感谢 GPT 5 的解答):

__int64 sub_140001A79()
{
  _QWORD v1[2]; // 实际扮演的是 结构体 而不是单纯数组。
                // v1[0] 保存了地址值(这里是 &unk_1400C82E0)。
                // v1[1] 保存了长度值(15)。
                // sub_1400AB3F0 接收到的第二个参数本质上就是“指向一块数据的指针 + 这块数据的长度”。
  _BYTE input[32]; // [rsp+30h] [rbp-70h] BYREF
  _BYTE key[32]; // [rsp+50h] [rbp-50h] BYREF
  _BYTE cipher[30]; // [rsp+70h] [rbp-30h] BYREF
  char v5; // [rsp+8Eh] [rbp-12h] BYREF
  char v6; // [rsp+8Fh] [rbp-11h] BYREF
  char *v7; // [rsp+90h] [rbp-10h]
  char *v8; // [rsp+98h] [rbp-8h]
  sub_14000DC40();
  v8 = &v5;
  v1[0] = &unk_1400C82E0;
  v1[1] = 15;
  sub_1400AB3F0(cipher, v1, &v5);
  sub_1400A1160(&v5);
  v7 = &v6;
  base64_decode(key, "bTRwbGUxc3hubg==", &v6); // 这里明显是base64处理函数,外部做解码即可。
  sub_1400A10A0(&v6);
  printf(&unk_1400C6BC0, "plz input your flag: "); // 这里应该是单纯打印字符串的函数
  // 但是读取输入为何要顺带读一个"全局变量?"
  // 绝大多数 C 程序在调用 `printf` 的时候,编译器最终会把它翻译成 `fprintf(stdout, "msg")`。
  // `stdout` 在 Windows CRT 里就是一个全局变量,编译时常常会被放在 `.data` 里,反编译看上去就是 `&unk_1400C6BC0`。
  get_input(input);// 显然,获取输入
  trim_input(&unk_1400C6860, input); // 这里读取一个全局变量并对input做处理
  // 可能的处理方式:1. 标准化字符串,删除空格和特殊字符之类。 2. 对字符串和读入的全局变量做变换,使之以某种方式"混合"或者"发生作用关系"。这里我们就认为是普通的处理输入。
  if ( (unsigned __int8)xxtea_encrypt(input, cipher, key) )// 同时出现密文,密钥和输入,显然是加密比较函数了
    printf(&unk_1400C6BC0, "Right!let_us_have_a_cup_of_tea!!!\n");
  else
    printf(&unk_1400C6BC0, "oh_no_no_no!!!\n");
  sub_1400AFEB0(input);
  sub_1400AFEB0(key);
  sub_1400AB510(cipher);
  // 对那几个栈上对象做收尾工作:释放可能的堆内存、把长度归零等。
  return 0;
  // 标准返回函数
}
c

于是我们清理 sub_1400018CA (在上述清理过的代码中为 xxtea_encrypt )代码:

__int64 __fastcall xxtea_encrypt(__int64 input, __int64 cipher, __int64 key)
{
  size_t n16; // rbx
  const void *v4; // rax
  size_t Size; // rsi
  const void *v6; // rbx
  void *v7; // rax
  __int64 v8; // rbx
  __int64 v9; // rax
  _BYTE v11[32]; // [rsp+20h] [rbp-70h] BYREF
  _QWORD v12[2]; // [rsp+40h] [rbp-50h] BYREF
  _BYTE v13[43]; // [rsp+50h] [rbp-40h] BYREF
  char v14; // [rsp+7Bh] [rbp-15h] BYREF
  int v15; // [rsp+7Ch] [rbp-14h] BYREF
  char *v16; // [rsp+80h] [rbp-10h]
  unsigned __int64 v17; // [rsp+88h] [rbp-8h]
  sub_140001450(v13, key);
  
  // 下面是处理 key 的函数
  v12[0] = 0;
  v12[1] = 0;
  if ( (unsigned __int64)sub_14002F0A0(v13) > 0x10 )
    n16 = 16;
  else
    n16 = sub_14002F0A0(v13);
  v4 = (const void *)sub_1400AD570(v13);
  memcpy(v12, v4, n16);
  // 将10位的小暖男(确信)密钥扩展成16位,空缺处补0
  // eg: maple1sxnn -> maple1sxnn000000
   
  v17 = (unsigned __int64)(sub_14002F0A0(input) + 3) >> 2; // XXTEA 所需的"按4字节对齐后的词数"
  
  v16 = &v14;
  v15 = 0;
  sub_1400AB490(v11, v17, &v15, &v14);
  sub_1400A1160(&v14);
  Size = sub_14002F0A0(input);
  v6 = (const void *)sub_14002EEB0(input);
  v7 = (void *)sub_1400AB3C0(v11);
  memcpy(v7, v6, Size);
  // 准备工作,实现一些拷贝,缓冲之类的操作
  
  v8 = sub_14002D4C0(v11);          // 当前数据长度(字节数)
  v9 = sub_1400AB3C0(v11);          // 数据指针
  xxtea_encrypt(v9, v8, v12);       // XXTEA 加密的核心函数
  // 这里把 buf[0..len-1] 按小端 32-bit 词视图、长度 n = (len+3)>>2,用 key(四个 32-bit 子键)做循环加密。
  LODWORD(v8) = sub_1400BF530(v11, cipher);
  sub_1400AB510(v11);
  sub_1400AFEB0(v13);
  return (unsigned int)v8;
}
c

可知:

  1. 使用 m4ple1sxnn 作为密钥材料(16 字节,因此填 0)
  2. 对用户输入的flag进行加密(通过sub_1400016DC函数)
  3. 将加密结果与预存的数据进行比较

花指令处理#

于是定位到 sub_1400016DC 函数,发现是一个跳转。显然这里 JUMPOUT 出的地址才是真正的实现函数。但是由于它没有被 IDA 识别为函数,我们只能强行看汇编了。

void __fastcall encrypt_impl(__int64 a1, unsigned __int64 a2)
{
  if ( a2 > 1 && 0x34 / (unsigned int)a2 != -6 )
    JUMPOUT(0x140001744LL);
}
c

(AI 注释: 你不一定要把 JUMPOUT 目标当“新函数”看。 JUMPOUT 往往只是跳到一个代码块(可能在同一函数里,或落在别的函数的中间/尾块),反编译器因此不把它认作函数入口。先把它当作“落点基本块”去读即可;若你确认那儿才是一个独立入口,再手动建函数。 )

; 噪音 / 误解码(建议当作 db)
; xor     eax, [rcx+4514F845h]
; adc     [rax], eax
; xor eax, [rcx+4514F845h] 带巨大的位移,对未定义的 RCX 取内存;adc [rax], eax 会对 EAX 指向的地址写内存,若 EAX 未设定几乎必崩——它们既不保存现场也不建立栈框,和后续“规整的逻辑”断层。
; loc_14000174B  ← 以这里作为代码起点更合理
mov     eax, [rbp+var_8]      ; eax = sum !!!注意,这里是读取sum的地方!!初始的Sum就是我们需要的Delta
shr     eax, 2
and     eax, 3                ; eax = (sum >> 2) & 3  → e ∈ {0,1,2,3}
mov     [rbp+var_18], eax     ; e
mov     [rbp+var_C], 0        ; i = 0
jmp     loc_140001806         ; 进入主循环	
asm

如果我们将上面两条花指令 NOP 掉,其实可以看见部分 XXTEA 的实现(Delta 累加部分被更多花指令隐藏掉了):

void __fastcall encrypt_impl(_DWORD *a1, unsigned __int64 a2, __int64 a3)
{
  unsigned int *v3; // rax
  unsigned int *v4; // rax
  unsigned int v6; // [rsp+4h] [rbp-1Ch]
  unsigned int v7; // [rsp+10h] [rbp-10h]
  unsigned int i; // [rsp+14h] [rbp-Ch]
  unsigned int v9; // [rsp+1Ch] [rbp-4h]
  if ( a2 > 1 )
  {
    v9 = a1[(unsigned int)(a2 - 1)];
    v7 = 0x34 / (unsigned int)a2 + 6;
    while ( v7-- )
    {
      for ( i = 0; i < (int)a2 - 1; ++i )
      {
        v6 = a1[i + 1];
        v3 = &a1[i];
        *v3 += (v6 + (v9 ^ *(_DWORD *)(4LL * (i & 3) + a3))) ^ (((4 * v6) ^ (v9 >> 5)) + ((v6 >> 3) ^ (16 * v9)));
        v9 = *v3;
      }
      v4 = &a1[(unsigned int)(a2 - 1)];
      *v4 += (*a1 + (v9 ^ *(_DWORD *)(4LL * (i & 3) + a3))) ^ (((4 * *a1) ^ (v9 >> 5)) + ((*a1 >> 3) ^ (16 * v9)));
      v9 = *v4;
    }
  }
}
c

进入主循环(太长了不贴),可以发现是一个标准的”判断是主循环还是尾循环,并执行符合 XXTEA 规则的轮加密”的函数。自此我们确定了该函数是 XXTEA 加密。(这一步需要有密码学基础,至少得熟悉常见的对称加密的代码实现,可以选择问AI)下一步就是找 Delta 了。

然而,上面 FindCrypt 的结果已经指出:TEA 类算法中经典的 Delta 值(0x9E3779B9)并没有出现在我们的程序中(表现为检测不出 TEA 类算法),于是我们猜测该程序有自定义的 Delta 值。结合上述内容可知 Delta 被花指令隐藏,该部分处理比较困难,于是考虑通过动态调试获取之(由于笔者使用 Linux 环境,这里使用 winedbg + gdb,选用x64dbg 等亦可):

动态调试#

首先,我们需要解决一个问题:去哪找?

根据上述内容,注意到:

mov     eax, [rbp+var_8]      ; eax = sum !!!注意,这里是读取sum的地方!!初始的Sum就是我们需要的Delta
asm

这里就是我们需要的函数,它的地址在 IDA 中可以发现是 0x14000174b,于是启动 gdb 并下断:

pwndbg>  winedbg --gdb another_flower_tea.exe
(Some Output)
pwndbg>  b *0x14000174b # 下断点
pwndbg>  c # continue,表示执行程序到断点
# 等待程序停在断点处,中间可能需要按照源程序要求执行输入操作,随便输入些东西即可
pwndbg>  set $sum_addr = $rbp - 8 # 源程序显示采用的是负偏移,所以我们先提前算出负偏移的 offset
pwndbg> x/wx $sum_addr
0x21fd68:	0x00114514
bash

好了!我们拿到了初始的 Delta——0x00114514,一个非常符合网安学长精神状态的数。于是根据标准的 XXTEA 加密过程,可以开始写解密脚本了。

写出解密脚本如下(可以求助 AI 或翻阅 Github 现成实现并修改 Delta 值):

#include <stdint.h>
#include <stdio.h>
#include <string.h>
#define DELTA 0x00114514
void xxtea_decrypt(uint32_t *v, uint32_t len, uint32_t *k) {
  uint32_t n = len - 1;
  uint32_t z = v[n], y = v[0], sum = 0, e;
  uint32_t p, q;
  q = 6 + 52 / (n + 1);
  sum = q * DELTA; // 初始sum值
  while (sum != 0) {
    e = (sum >> 2) & 3;
    for (p = n; p > 0; p--) {
      z = v[p - 1];
      y = v[p] -= (((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^
                  ((sum ^ y) + (k[(p & 3) ^ e] ^ z));
    }
    z = v[n];
    y = v[0] -= (((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^
                ((sum ^ y) + (k[(p & 3) ^ e] ^ z));
    sum -= DELTA;
  }
}
// 将密钥扩展为4个uint32_t,不足部分填0
void key_setup(const char *key_str, uint32_t *key) {
  memset(key, 0, 16); // 先清零
  memcpy(key, key_str, strlen(key_str) < 16 ? strlen(key_str) : 16);
}
int main() {
  // 加密数据(60字节,15个uint32_t)
  uint8_t enc_data[] = {
      0x92, 0x37, 0x53, 0x0B, 0x48, 0x37, 0x0A, 0x7D, 0x08, 0xF6, 0x91, 0x5F,
      0xA2, 0x4B, 0x3C, 0xAD, 0x06, 0xFB, 0x6C, 0x8B, 0xF3, 0x51, 0x74, 0x6D,
      0xF3, 0x8F, 0x6F, 0x58, 0x20, 0x75, 0xFE, 0x81, 0xE0, 0x46, 0x0A, 0x88,
      0x0E, 0x80, 0x04, 0xBD, 0xBE, 0xCB, 0x4B, 0x74, 0xC4, 0x58, 0x12, 0x30,
      0x91, 0x29, 0x4D, 0x12, 0x1E, 0xCE, 0x38, 0x01, 0xD5, 0xF3, 0x0D, 0x23};
  uint32_t data[15];
  memcpy(data, enc_data, sizeof(data));
  // 密钥
  const char *key_str = "m4ple1sxnn";
  uint32_t key[4];
  key_setup(key_str, key);
  // 解密
  xxtea_decrypt(data, 15, key);
  // 以字符串形式输出(可读明文)
  printf("\n解密结果 (string):\n");
  char *str = (char *)data;
  for (int i = 0; i < 60; i++) {
    if (str[i] >= 32 && str[i] <= 126) {
      putchar(str[i]);
    } else if (str[i] == 0) {
      putchar('\\');
      putchar('0');
    } else {
      printf("\\x%02X", (unsigned char)str[i]);
    }
  }
  putchar('\n');
  return 0;
}
c
记一杯花茶的逆向工程
https://crisq.top/blog/another_flower_tea_wp/another-flower-tea%E7%9A%84-wp
Author Cris.Q
Published at 2025年8月25日
Comment seems to stuck. Try to refresh?✨