二叉树
二叉树是计算机科学中最基础且重要的树形数据结构。简单来说,它是一个有序的树,其中每个节点最多只能有两个子节点,分别称为左子节点和右子节点(左右顺序不能颠倒)
基本术语:最顶层的节点叫根节点;没有子节点的节点叫叶子节点;每个节点到根节点有一条唯一的路径
核心特性:二叉树具有递归性——每个节点的左子树和右子树本身也是一棵二叉树。这就意味着处理二叉树的问题,天然适合用递归算法来解决
实战案例
tree(二叉树)

反编译定位 main 函数,将我们输入的值 v4 传入了 chkflag() 中

进入函数,分析核心逻辑

函数开头固定了 flag 格式
1 | strcpy(Str, "flag{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"); |
每个 x 位置要求输入一个十六进制字符
1 | if ( Str[i] == 120 ) // 120 = x |
每读到一个 x 位置,程序会把该字符转成 4 位二进制字符
1 | case 'a': |
完整映射如下
1 | 0 -> 0000 |
所以 chkflag() 的本质是把 flag 中的 32 个十六进制字符展开成 128 位二进制串
继续往后分析 parse() 函数

明显是二叉树,循环 128 次(v5 <= 127):
按位移动:
如果当前位是
'0'(ASCII 48):v3 = *(v3 + 12),即移动到左子节点如果当前位是
'1'(ASCII 49):v3 = *(v3 + 16),即移动到右子节点
检查当前节点(
*v3):读取节点首字节判断是否为小写字母(
> 96且<= 122):如果是:将
*v3这个字符存入Str2,索引v4加一。最关键的一步:将当前节点v3重置回根节点a1(表示一个字符合成完毕,准备解码下一个字符)如果不是:不输出字符,
v3保持当前节点,继续用下一个 bit 深入遍历
使用 strncmp 比较 Str2 和硬编码字符串 "zvzjyvosgnzkbjjjypjbjdvmsjjyvsjx"
parse(a1) 的参数 a1 是树根节点,所以这棵树一定在前面被构造,下一步就是分析 init() 函数

接下来是算法逻辑拆解,第一步:初始化 26 个叶子节点
代码首先从 &unk_404040 拷贝了 26 个 int 数值到局部数组 v1。这 26 个数就是 26 个小写字母 a~z 的权重
第二步:霍夫曼树构建循环
这是一个经典的 “寻找两个最小权重节点,合并为新节点” 的过程:
寻找最小两个:循环遍历当前所有节点(
v7表示当前节点总数),找出两个未被合并(标志位为 0)且权重最小的节点:v5指向最小权重节点v4指向次小权重节点
终止条件:如果找不到第二个节点(v4 == -1),说明只剩下根节点了,跳出循环
合并节点:
创建一个新节点(索引为当前的
v7):左子节点设为最小节点
v5(存储在偏移 +12)右子节点设为次小节点
v4(存储在偏移 +16)新节点权重 = 两者权重之和
将
v5和v4的标志位设为 1(标记它们已合并)节点总数
v7加 1
第三步:返回根节点
循环结束后,v5 存储的就是最后一次未被合并的节点(即整棵树的根节点)。函数将其地址赋值给全局变量 root 并返回
查看权重
1 | a: 3 |
简单来说,init() 的任务就是
1 | 每次找两个最轻的东西,把它们绑在一起,变成一个新的更重的东西 |
最后这个整体就是一棵树,假设现在只有 4 个字母,不是 26 个,一开始它们是分开的:
1 | a(3) b(5) c(7) d(10) |
先找两个最轻的,把它们绑成一个新东西
1 | ab(8) c(7) d(10) |
树的样子是:
1 | ab(8) |
然后继续找两个最轻的,把它们绑起来
1 | ab(8) c(7) d(10) |
树变成:
1 | cab(15) |
然后继续
1 | d(10) cab(15) |
绑起来
1 | root(25) |
树变成
1 | root(25) |
规则是往左走 = 0,往右走 = 1
从 root 到每个字母的路,就是这个字母的编码
例如 a 就是 root -> 右 -> 右 -> 左,所以就是 110
编写脚本解出 Huffman 表
1 | weights = [ |
运行结果
1 | a -> 111010111 |
将 zvzjyvosgnzkbjjjypjbjdvmsjjyvsjx 转为二进制
1 | 10101111101001000001111111001000010101110100111100010010010010000001101010000100100111010111111101110001001000001111100010011100 |
然后每 4 位转为 Hex 最后得到 flag
1 | flag{afa41fc8-574f-1248-1a84-9d7f7120f89c} |