2022CISCN-babycode
字数 1102 2025-08-06 23:10:35
mruby字节码逆向分析教程:从CISCN 2022 babycode题目解析
0x01 mruby字节码基础
1.1 mruby简介
mruby是Ruby语言的轻量级实现,可以将Ruby源码编译为中间字节码,然后通过基于寄存器的虚拟机解释执行。特点包括:
- 编译生成
.mrb后缀的字节码文件 - 文件头标识版本:
- RITE0006:mruby v2.1.0
- RITE0200:mruby v3.0.0
- RITE0300:mruby v3.1.0(本题使用版本)
1.2 开发工具链
编译mruby源码后得到的关键工具:
mruby:字节码解释器,可打印字节码信息mirb:Ruby代码评估器mrbc:将Ruby源码编译为.mrb字节码文件
1.3 字节码分析基础命令
mruby -b babycode.mrb # 打印字节码信息
mruby -v babycode.mrb # 详细输出
0x02 字节码结构解析
2.1 基本结构
irep 0x56268619b4b0
nregs=5 // 使用5个寄存器
nlocals=2 // 2个局部变量
pools=1 // 1个常量池
syms=5 // 5个符号
reps=2 // 2个子IREP
ilen=55 // 55条指令
2.2 关键指令类型
变量操作
LOADNIL Rx:寄存器置空LOADI_0 Rx:加载立即数0MOVE Rx Ry:寄存器间传值
函数调用
SSEND:直接调用SEND:调用内置函数- 格式:
SSEND Rx :func n=y,使用Rx到Rx+y的寄存器
控制流
JMP:无条件跳转JMPNOT:条件跳转
类与方法定义
CLASS R2 :Crypt // 定义Crypt类
EXEC R2 I(0:0x...) // 类主体代码地址
METHOD R3 I(1:0x...) // 方法定义
DEF R2 :check // 定义check方法
0x03 题目逆向分析
3.1 主程序逻辑
def main():
p = gets().chomp() # 获取输入
if check(p): # 检查输入
puts("yes") # 正确输出yes
else:
return nil # 错误返回nil
3.2 check函数分析
def check(p):
lst_ch = 0
# 第一步:异或处理
for i in range(len(p)):
c = ord(p[i])
p[i] = chr(((ord(p[i]) ^ lst_ch) ^ (i+1)) & 0xff)
lst_ch = c
# 第二步:TEA加密
cipher = Crypt::CIPHER.encrypt(p, "aaaassssddddffff")
# 第三步:比对密文
target = "f469358b7f165145116e127ad6105917bce5225d6d62a714c390c5ed93b22d8b6b102a8813488fdb"
return cipher == target
3.3 加密算法详解
3.3.1 预处理异或
# 对每个字符进行:
# 1. 与前一个字符异或
# 2. 与位置索引+1异或
c = ord(p[i])
p[i] = chr(((c ^ lst_ch) ^ (i+1)) & 0xff)
lst_ch = c # 保存原始字符
3.3.2 TEA加密实现
#define ut32 unsigned int
#define delta 305419896
void encrypt(ut32* src, ut32* k) {
ut32 sum = 0;
ut32 y = src[0];
ut32 z = src[1];
for(int i=0; i<16; i++) {
y += (((z<<3)^(z>>5)) + z) ^ (sum + k[((sum>>11)+1)&3]);
sum += delta;
z += (((y<<3)^(y>>5)) + y) ^ (sum + k[(sum&3)]);
}
src[0] = y;
src[1] = z;
}
0x04 解密过程
4.1 TEA解密算法
void decrypt(ut32* enc, ut32* k) {
ut32 sum = delta * 16;
ut32 y = enc[0];
ut32 z = enc[1];
for(int i=0; i<16; i++) {
z -= (((y<<3)^(y>>5)) + y) ^ (sum + k[(sum+1)&3]);
sum -= delta;
y -= (((z<<3)^(z>>5)) + z) ^ (sum + k[((sum>>11)+1)&3]);
}
enc[0] = y;
enc[1] = z;
}
4.2 完整解密脚本
from Crypto.Util.number import *
# 1. 分割密文
s = "f469358b7f165145116e127ad6105917bce5225d6d62a714c390c5ed93b22d8b6b102a8813488fdb"
c = []
for i in range(0, len(s), 8):
c.append(int(s[i:i+8], 16))
# 2. 准备密钥
k = "aaaassssddddffff"
key = []
for i in range(0, len(k), 4):
key.append(bytes_to_long(k[i:i+4].encode()))
# 3. C++解密TEA(需编译执行)
"""
#include <iostream>
#define ut32 unsigned int
#define delta 305419896
void decrypt(ut32* enc, ut32* k) {
ut32 sum = delta * 16;
ut32 y = enc[0];
ut32 z = enc[1];
for(int i=0; i<16; i++) {
z -= (((y<<3)^(y>>5)) + y) ^ (sum + k[(sum+1)&3]);
sum -= delta;
y -= (((z<<3)^(z>>5)) + z) ^ (sum + k[((sum>>11)+1)&3]);
}
enc[0] = y;
enc[1] = z;
}
int main() {
ut32 enc[10] = {4100535691,2132169029,292426362,3591395607,
3169133149,1835181844,3281044973,2477927819,
1796221576,323522523};
ut32 k[4] = {1633771873,1936946035,1684300900,1717986918};
for(int i=0; i<10; i+=2) decrypt(enc+i, k);
for(int i=0; i<10; i++) printf("%08x", enc[i]);
return 0;
}
// 输出:67080e02194b500d5c585f0b5e40461511470a08154211560d47491e04031d262771217626242765
"""
# 4. 解密异或
c = bytes.fromhex('67080e02194b500d5c585f0b5e40461511470a08154211560d47491e04031d262771217626242765')
c = list(c)
c[0] ^= 1 # 第一个字符的特殊处理
for i in range(1, len(c)):
c[i] ^= (i+1)
c[i] ^= c[i-1]
print(bytes(c)) # flag{6ad1c70c-daa4-11ec-9d64-0242ac1200}
0x05 关键知识点总结
-
mruby字节码特点:
- 基于寄存器的虚拟机
- 函数调用使用连续寄存器传参
- 类和方法定义有特定指令模式
-
逆向技巧:
- 对照mruby源码中的ops.h理解指令
- 注意函数调用时的寄存器使用规则
- 类和方法定义会跳转到独立代码块
-
加密算法分析:
- 识别TEA算法的特征结构
- 注意魔改点:delta值、密钥调度方式
- 预处理和后处理的组合加密方式
-
解密流程:
- 逆向加密步骤的顺序
- 处理边界条件(如第一个字符)
- 使用多种语言协作解密(Python+C++)
0x06 参考资源
- mruby官方源码:https://github.com/mruby/mruby
- mruby字节码指令定义:include/mruby/ops.h
- TEA算法原理:https://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm
- Ruby字节码逆向工具:https://github.com/mruby/mruby/blob/master/doc/limitations.md