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:加载立即数0
  • MOVE 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 关键知识点总结

  1. mruby字节码特点

    • 基于寄存器的虚拟机
    • 函数调用使用连续寄存器传参
    • 类和方法定义有特定指令模式
  2. 逆向技巧

    • 对照mruby源码中的ops.h理解指令
    • 注意函数调用时的寄存器使用规则
    • 类和方法定义会跳转到独立代码块
  3. 加密算法分析

    • 识别TEA算法的特征结构
    • 注意魔改点:delta值、密钥调度方式
    • 预处理和后处理的组合加密方式
  4. 解密流程

    • 逆向加密步骤的顺序
    • 处理边界条件(如第一个字符)
    • 使用多种语言协作解密(Python+C++)

0x06 参考资源

  1. mruby官方源码:https://github.com/mruby/mruby
  2. mruby字节码指令定义:include/mruby/ops.h
  3. TEA算法原理:https://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm
  4. Ruby字节码逆向工具:https://github.com/mruby/mruby/blob/master/doc/limitations.md
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 字节码分析基础命令 0x02 字节码结构解析 2.1 基本结构 2.2 关键指令类型 变量操作 LOADNIL Rx :寄存器置空 LOADI_0 Rx :加载立即数0 MOVE Rx Ry :寄存器间传值 函数调用 SSEND :直接调用 SEND :调用内置函数 格式: SSEND Rx :func n=y ,使用Rx到Rx+y的寄存器 控制流 JMP :无条件跳转 JMPNOT :条件跳转 类与方法定义 0x03 题目逆向分析 3.1 主程序逻辑 3.2 check函数分析 3.3 加密算法详解 3.3.1 预处理异或 3.3.2 TEA加密实现 0x04 解密过程 4.1 TEA解密算法 4.2 完整解密脚本 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