[安全] ADCTF 2024 rkvm 题解

ADCTF2024 rkvm WP

这道题做了很久,由于使用的是比较暴力的方法,时间跨度大概有五六天,许多细节我都不能以刚刚看到题目的意识来讲解,十分抱歉
也因此,我会使用已经部分重命名(尽管不太准确)的代码进行讲解,题目作者十分友善,没有去除 vm 相关函数的符号,所以问题应该不大

整体观察

main 函数

int32_t main(int32_t argc, char** argv, char** envp)
 
    puts(str: "tell me what you learnt from the demo: ")
    char input[0x50]
    memset(&input, 0, 0x48)
    fgets(buf: &input, n: 0x48, fp: __TMC_END__)
    input[strcspn(&input, "\r\n")] = 0
    // the len_condition make sure that 
    // the len is in [57, 58, 59, 60, 61, 62, 63, 64]
    int64_t len_condition = (((strlen(&input) + 3) u>> 2) + 1) & 0xfffffffffffffffe
    
    if (len_condition == 0x10)
        int32_t KEY0_
        __builtin_strncpy(dest: &KEY0_, src: "oR0yuki5g3n0ww1@", count: 0x10)
        int64_t KEY0 = KEY0_.q
        int32_t KEY1_
        int64_t KEY1 = KEY1_.q
        int64_t KEY2 = (zx.q(reg4_get1(&input)) << 32) + 1999843683
        int64_t KEY3
        __builtin_strncpy(dest: &KEY3, src: "!DA0t3m0", count: 8)
        int64_t block_id = 0
        
        while (true)
            if (block_id u>= len_condition)
                puts(str: "congratulations.")
                break
            
            int64_t var_a0 = reg2_get1(KEY0.d, KEY0:4.d)
            reg3_get1(KEY2, KEY3, &var_a0)
            reg1_get2(var_a0, &KEY0)
            var_a0 = reg2_get1(KEY1.d, KEY1:4.d)
            reg3_get1(KEY3, KEY2, &var_a0)
            reg1_get2(var_a0, &KEY1)
            int32_t rax_20 = reg4_get1(&input[block_id << 2])
            int32_t var_bc_1 = reg4_get1(&input[(block_id + 1) << 2])
            int64_t ENC_input = rax_20.q
            complex_run(&ENC_input, &KEY0)
            
            if (ENC_input != *((block_id << 2) + &encrypted))
                puts(str: "nope.")
                break
            
            block_id += 2
    else
        puts(str: "nope.")
    
    return 0

整体的结构是利用输入构造密钥的一部分,然后分成 8 轮对输入进行加密,最后将加密的输入和密文比对,得出是否正确的结果
其中每一个被我重命名的函数 regx_getx 以及 complex_run,都是虚拟机的包装函数,每个函数的结构大概如下

uint64_t reg4_get1(char* arg1)
 
    void REG_ADDR
    rkvm_regset(3, &REG_ADDR, sx.q(*arg1))
    rkvm_regset(7, &REG_ADDR, sx.q(arg1[1]))
    rkvm_regset(0xb, &REG_ADDR, sx.q(arg1[2]))
    rkvm_regset(0xf, &REG_ADDR, sx.q(arg1[3]))
    run(SC_ADDR: 0x4035b0, SC_LEN: 0x63, &REG_ADDR)
    return rkvm_regval(0, &REG_ADDR) u>> 0x20

创建一个寄存器数组,然后设置寄存器的值,运行地址处的字节码之后返回某个寄存器的值

那么分析字节码的重点部分就在 run 函数里

int64_t run(int64_t SC_ADDR, uint64_t SC_LEN, void* REG_ADDR)
 
    FILE* SC_fp = fmemopen(buf: SC_ADDR, len: SC_LEN, mode: "r")
    void SC
    rkvme_load(SC_fp, &SC)
    rkvm_interpret(&SC, REG_ADDR)
    rkvme_free(&SC)
    return fclose(fp: SC_fp)

可以看到是读取了内存中的一段二进制数据,并通过 rkvm_interpret 进行解释

重点分析

这部分我用 IDA 分析的

char *__fastcall rkvm_interpret(__int64 *SHELLCODE, __int64 *REG_MEM)
{
  unsigned __int8 *v2; // rax
  __int64 v3_1; // rax
  unsigned __int8 *v4; // rax
  unsigned __int8 *p_index_div; // rax
  unsigned __int8 *OP_reg_situ_val...; // rax
  unsigned __int8 *v7; // rax
  size_t v8; // rax
  __int64 v9; // rax
  __int64 v10; // rax
  __int64 v11; // rax
  unsigned __int64 v12; // rax
  __int64 v13; // rax
  __int64 v14; // rax
  __int64 v15; // rax
  unsigned __int64 v16; // rax
  __int64 v17; // rax
  char *ret_val_is_not_important; // rax
  char **output; // [rsp+18h] [rbp-58h] BYREF
  unsigned __int64 val_1; // [rsp+20h] [rbp-50h]
  unsigned __int64 val_2; // [rsp+28h] [rbp-48h]
  unsigned __int8 size_of_reg; // [rsp+33h] [rbp-3Dh]
  unsigned int OP; // [rsp+34h] [rbp-3Ch]
  unsigned __int64 END_IP; // [rsp+38h] [rbp-38h]
  int index_div; // [rsp+44h] [rbp-2Ch]
  unsigned __int64 val; // [rsp+48h] [rbp-28h]
  unsigned __int8 i; // [rsp+57h] [rbp-19h]
  char *target_string; // [rsp+58h] [rbp-18h]
  char FLAG; // [rsp+67h] [rbp-9h]
  unsigned __int8 *IP; // [rsp+68h] [rbp-8h]
 
  IP = (unsigned __int8 *)SHELLCODE[2];
  END_IP = SHELLCODE[2] + SHELLCODE[3];
  while ( 1 )
  {
    ret_val_is_not_important = (char *)IP;
    if ( (unsigned __int64)IP >= END_IP )
      return ret_val_is_not_important;
    FLAG = 0;
    v2 = IP++;
    OP = *v2;
    if ( OP > 0x44 )
      goto REG_VAL_CALC;                        // 实际上这里会直接跳过,因为 OP 最大只定义到 0x44
    if ( OP >= 8 )
    {
      v3_1 = 1LL << ((unsigned __int8)OP - 8);  // 它这个OP都是两位一组的,分别对应着
                                                // 从寄存器中取出 val 来对目标寄存器操作
                                                // 从OPCODE中取出 val (立即数)来对目标寄存器操作
      if ( (v3_1 & 0x220242002222LL) != 0 )     // 
        goto Immediate_VAL;
      if ( (v3_1 & 0x110121001111LL) != 0 )     // 
        goto Register_VAL;
      if ( (v3_1 & 0x1F00000000000000LL) != 0 ) // FLAG set
      {
        target_string = (char *)IP;             // 当前存储的数据是一个字符串
                                                // 读完这个字符串之后直接跳过
        v8 = strlen((const char *)IP) + 1;
        IP += v8;
      }
    }
    else
    {
      if ( OP == 5 )                            // OP == 5 := set 一个立即数
        goto Immediate_VAL;
      if ( OP > 5 )                             // 实际上这个情况只有 6 7,但是这两个op没有操作,不管
        goto REG_VAL_CALC;
      if ( OP == 4 || !OP )                     // OP == 4 := reg += other reg
                                                // OP == 0 := reg := other reg
      {
Register_VAL:                                   // OP reg_situ val_situ
                                                // val is from reg and
        v4 = IP++;
        index_div = *v4;
        p_index_div = IP++;
        val = rkvm_regval(*p_index_div, REG_MEM);
        goto REG_VAL_CALC;
      }
      if ( OP == 1 )
      {
Immediate_VAL:
        OP_reg_situ_val... = IP++;              // OP reg_situ val...
        index_div = *OP_reg_situ_val...;
        size_of_reg = rkil_regsize(index_div);
        val = 0;
        for ( i = 0; i < size_of_reg; ++i )     // 对 OPCODE 上连续的 u8 进行位压缩
        {
          v7 = IP++;
          val += *v7 << (8 * i);                // 假设原来是 u8_0, u8_1, u8_2 这样排列的,
                                                // 那么压缩之后
                                                // val := (u8_2, u8_1, u8_0)
                                                // 所以是小端序?
        }
      }
    }
REG_VAL_CALC:
    switch ( OP )
    {
      case 0u:
      case 1u:                                  // reg := val
        rkvm_regset(index_div, REG_MEM, val);
        break;
      case 4u:
      case 5u:                                  // reg += val
        v9 = rkvm_regval(index_div, REG_MEM);
        val_2 = val + v9;
        rkvm_regset(index_div, REG_MEM, val + v9);
        break;
      case 8u:
      case 9u:                                  // reg -= val
        v10 = rkvm_regval(index_div, REG_MEM);
        val_2 = v10 - val;
        rkvm_regset(index_div, REG_MEM, v10 - val);
        break;
      case 0xCu:
      case 0xDu:                                // reg *= val
        v11 = rkvm_regval(index_div, REG_MEM);
        val_2 = val * v11;
        rkvm_regset(index_div, REG_MEM, val * v11);
        break;
      case 0x10u:
      case 0x11u:                               // reg /= val
        v12 = rkvm_regval(index_div, REG_MEM);
        val_2 = v12 / val;
        rkvm_regset(index_div, REG_MEM, v12 / val);
        break;
      case 0x14u:
      case 0x15u:                               // some CMP
        val_1 = rkvm_regval(index_div, REG_MEM);
        val_2 = val_1 - val;
        *((_BYTE *)REG_MEM + 64) = val_1 == val;// reg64 := (reg == val)
        *((_BYTE *)REG_MEM + 65) = val_1 < val_2;// reg65 := (val < 0)
        break;
      case 0x20u:
      case 0x21u:                               // reg |= val
        v13 = rkvm_regval(index_div, REG_MEM);
        val_2 = val | v13;
        rkvm_regset(index_div, REG_MEM, val | v13);
        break;
      case 0x25u:
      case 0x26u:                               // reg &= val
        v14 = rkvm_regval(index_div, REG_MEM);
        val_2 = val & v14;
        rkvm_regset(index_div, REG_MEM, val & v14);
        break;
      case 0x28u:
      case 0x29u:                               // reg ^= val
        v15 = rkvm_regval(index_div, REG_MEM);
        val_2 = val ^ v15;
        rkvm_regset(index_div, REG_MEM, val ^ v15);
        break;
      case 0x30u:
      case 0x31u:                               // reg >>= val
        v16 = rkvm_regval(index_div, REG_MEM);
        val_2 = v16 >> val;
        rkvm_regset(index_div, REG_MEM, v16 >> val);
        break;
      case 0x34u:
      case 0x35u:                               // reg <<= val
        v17 = rkvm_regval(index_div, REG_MEM);
        val_2 = v17 << val;
        rkvm_regset(index_div, REG_MEM, v17 << val);
        break;
      case 0x40u:
        FLAG = 1;                               // FLAG := true
        break;
      case 0x41u:
        FLAG = *((_BYTE *)REG_MEM + 64);        // FLAG := reg64
                                                // FLAG := (reg == val)
        break;
      case 0x42u:
        FLAG = *((_BYTE *)REG_MEM + 64) == 0;   // FLAG := !reg64
                                                // FLAG := (reg != val)
        FLAG &= 1u;
        break;
      case 0x43u:
        FLAG = *((_BYTE *)REG_MEM + 65);        // FLAG := reg65
                                                // FLAG := val < 0
        break;
      case 0x44u:
        FLAG = *((_BYTE *)REG_MEM + 65) == 0;   // FLAG := !reg65
                                                // FLAG := val >= 0
        FLAG &= 1u;
        break;
      default:
        break;
    }
    if ( FLAG )                                 // 条件跳转
    {
      rkvm_find_symbol((__int64 *)*SHELLCODE, SHELLCODE[1], target_string, (const char ***)&output);
      IP = (unsigned __int8 *)&output[1][SHELLCODE[2]];// output[0] := string
                                                // output[1] := offset
                                                // 
                                                // &output[1][SHELLCODE[2]] :=
                                                // SHELLCODE[2] + offset ==
                                                // BEGIN_IP + offset
    }
  }
}

当时的分析在注释处,可以看看,可能也不太准,感性理解一下就好了

同时分析出来从内存中读出来 字节码 数据如下

SHELLCODE[0]  : __int64 *    符号表基址 (name, offset) 对的数组
SHELLCODE[1]  : __int64      符号表条目数
SHELLCODE[2]  : __int64 *    字节码段起始地址 (IP 初始值)
SHELLCODE[3]  : __int64      字节码段长度 (END_IP = [2] + [3])

嗯这块我也忘了很多,因为是非常前期的工作了,中期大部分时间都记住了分析结果在改和写解释器,这些是当时的分析结果,看不懂的可以多看看(?

然后差不多就可以写解释器了,我的想法是这样的

  1. 由于寄存器的值大部分由输入或者一些每次调用的时候会改变的值确定,因此我希望我的解释器可以接受字符串作为未知的符号进行运算,也能接受运行时确定的数字进行运算,运算的结果由包含未知量(寄存器)的表达式表示
  2. 有两种方式,可以先打印每个函数来确定函数的功能(因为读取同一处地址所以功能一定相同),也可以模拟 main 函数打印循环的第一轮来确定是什么样的加密算法

那么接下来就是实现了,写了很久,虽然用了 AI 但是没什么用,最有用的大概是为我的第 1 点想法提供了一个“写一个类”来解决的思路,后续 AI 基本都是帮倒忙,错误看不出还给一些错误的建议,可能这也是为什么逆了这么久吧

解释器实现

使用了 Binary Ninja 脚本来实现(能够比较方便地读取内存),运行需要 Binary Ninja 的起码 Personal License

import binaryninja as bn
import struct
import base64
import sys
from typing import Union, Optional
 
class VMValue:
    """
    Type-safe wrapper for virtual machine values that can be either
    an integer (for actual computation) or a string (for symbolic representation).
    """
    def __init__(self, value: Union[int, str, 'VMValue']):
        if isinstance(value, VMValue):
            self.value = value.value
        elif isinstance(value, (int, str)):
            self.value = value
        else:
            raise TypeError(f"VMValue must be int, str or VMValue, got {type(value)}")
 
    def __add__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        other_val = other.value if isinstance(other, VMValue) else other
        if isinstance(self.value, int) and isinstance(other_val, int):
            return VMValue(self.value + other_val)
        return VMValue(f"({self.value} + {other_val})")
 
    def __radd__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        other_val = other.value if isinstance(other, VMValue) else other
        if isinstance(self.value, int) and isinstance(other_val, int):
            return VMValue(other_val + self.value)
        return VMValue(f"({other_val} + {self.value})")
 
    def __sub__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        other_val = other.value if isinstance(other, VMValue) else other
        if isinstance(self.value, int) and isinstance(other_val, int):
            return VMValue(self.value - other_val)
        return VMValue(f"({self.value} - {other_val})")
 
    def __rsub__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        other_val = other.value if isinstance(other, VMValue) else other
        if isinstance(self.value, int) and isinstance(other_val, int):
            return VMValue(other_val - self.value)
        return VMValue(f"({other_val} - {self.value})")
 
    def __mul__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        other_val = other.value if isinstance(other, VMValue) else other
        if isinstance(self.value, int) and isinstance(other_val, int):
            return VMValue(self.value * other_val)
        return VMValue(f"({self.value} * {other_val})")
 
    def __rmul__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        other_val = other.value if isinstance(other, VMValue) else other
        if isinstance(self.value, int) and isinstance(other_val, int):
            return VMValue(other_val * self.value)
        return VMValue(f"({other_val} * {self.value})")
 
    def __floordiv__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        other_val = other.value if isinstance(other, VMValue) else other
        if isinstance(self.value, int) and isinstance(other_val, int):
            return VMValue(self.value // other_val)
        return VMValue(f"({self.value} // {other_val})")
 
    def __rfloordiv__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        other_val = other.value if isinstance(other, VMValue) else other
        if isinstance(self.value, int) and isinstance(other_val, int):
            return VMValue(other_val // self.value)
        return VMValue(f"({other_val} // {self.value})")
 
    def __truediv__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        return self.__floordiv__(other)
 
    def __rtruediv__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        return self.__rfloordiv__(other)
 
    def __or__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        other_val = other.value if isinstance(other, VMValue) else other
        if isinstance(self.value, int) and isinstance(other_val, int):
            return VMValue(self.value | other_val)
        return VMValue(f"({self.value} | {other_val})")
 
    def __ror__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        other_val = other.value if isinstance(other, VMValue) else other
        if isinstance(self.value, int) and isinstance(other_val, int):
            return VMValue(other_val | self.value)
        return VMValue(f"({other_val} | {self.value})")
 
    def __and__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        other_val = other.value if isinstance(other, VMValue) else other
        if isinstance(self.value, int) and isinstance(other_val, int):
            return VMValue(self.value & other_val)
        return VMValue(f"({self.value} & {other_val})")
 
    def __rand__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        other_val = other.value if isinstance(other, VMValue) else other
        if isinstance(self.value, int) and isinstance(other_val, int):
            return VMValue(other_val & self.value)
        return VMValue(f"({other_val} & {self.value})")
 
    def __xor__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        other_val = other.value if isinstance(other, VMValue) else other
        if isinstance(self.value, int) and isinstance(other_val, int):
            return VMValue(self.value ^ other_val)
        return VMValue(f"({self.value} ^ {other_val})")
 
    def __rxor__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        other_val = other.value if isinstance(other, VMValue) else other
        if isinstance(self.value, int) and isinstance(other_val, int):
            return VMValue(other_val ^ self.value)
        return VMValue(f"({other_val} ^ {self.value})")
 
    def __lshift__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        other_val = other.value if isinstance(other, VMValue) else other
        if isinstance(self.value, int) and isinstance(other_val, int):
            return VMValue(self.value << other_val)
        return VMValue(f"({self.value} << {other_val})")
 
    def __rlshift__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        other_val = other.value if isinstance(other, VMValue) else other
        if isinstance(self.value, int) and isinstance(other_val, int):
            return VMValue(other_val << self.value)
        return VMValue(f"({other_val} << {self.value})")
 
    def __rshift__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        other_val = other.value if isinstance(other, VMValue) else other
        if isinstance(self.value, int) and isinstance(other_val, int):
            return VMValue(self.value >> other_val)
        return VMValue(f"({self.value} >> {other_val})")
 
    def __rrshift__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        other_val = other.value if isinstance(other, VMValue) else other
        if isinstance(self.value, int) and isinstance(other_val, int):
            return VMValue(other_val >> self.value)
        return VMValue(f"({other_val} >> {self.value})")
 
    def __eq__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        other_val = other.value if isinstance(other, VMValue) else other
        if isinstance(self.value, int) and isinstance(other_val, int):
            return VMValue(int(self.value == other_val))
        return VMValue(f"({self.value} == {other_val})")
 
    def __ne__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        other_val = other.value if isinstance(other, VMValue) else other
        if isinstance(self.value, int) and isinstance(other_val, int):
            return VMValue(int(self.value != other_val))
        return VMValue(f"({self.value} != {other_val})")
 
    def __lt__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        other_val = other.value if isinstance(other, VMValue) else other
        if isinstance(self.value, int) and isinstance(other_val, int):
            return VMValue(int(self.value < other_val))
        return VMValue(f"({self.value} < {other_val})")
 
    def __ge__(self, other: Union['VMValue', int, str]) -> 'VMValue':
        other_val = other.value if isinstance(other, VMValue) else other
        if isinstance(self.value, int) and isinstance(other_val, int):
            return VMValue(int(self.value >= other_val))
        return VMValue(f"({self.value} >= {other_val})")
 
    def __str__(self) -> str:
        if isinstance(self.value, int):
            return hex(self.value)
        return str(self.value)
 
def rkvm_regptr(situ: int):
    return situ // 4
 
def rkil_regsize(situ: int):
    return 1 << (3 - situ % 4)
 
def rkvm_regset(situ: int, val: Union[VMValue, int, str], ip: Optional[int] = None):
    val = VMValue(val)
    reg_i = rkvm_regptr(situ)
    sz    = rkil_regsize(situ)
 
    # mask  = {
    #     8: 0xFFFFFFFFFFFFFFFF,
    #     4: 0x00000000FFFFFFFF,
    #     2: 0x000000000000FFFF,
    #     1: 0x00000000000000FF,
    # }
 
    if ip is not None:
        print(f"[0x{ip:06x}]   (REG[{reg_i}] as u{sz * 8 :<2}) := ({val} as u{sz * 8 :<2})")
    else:
        print(f"(REG[{reg_i}] as u{sz * 8 :<2}) := ({val} as u{sz * 8 :<2})")
 
def rkvm_regval(situ: int) -> str:
    reg_i = rkvm_regptr(situ)
    sz    = rkil_regsize(situ)
 
    # mask  = {
    #     8: 0xFFFFFFFFFFFFFFFF,
    #     4: 0x00000000FFFFFFFF,
    #     2: 0x000000000000FFFF,
    #     1: 0x00000000000000FF,
    # }
 
    return f"(REG[{reg_i}] as u{sz * 8 :<2})"
 
 
def rkvm_interpret(bv: bn.BinaryView, SC_ADDR: int, SC_LEN: int):
    SC = bv.read(SC_ADDR, SC_LEN)
    if SC is None or len(SC) < 8:
        raise ValueError("Failed to read valid SC or SC too short")
    
    offset = 0
    S_LEN = struct.unpack("<Q", SC[offset:offset+8])[0]
    offset += 8
    
    symbols = []
    for _ in range(S_LEN):
        end_idx = SC.find(b'\x00', offset)
        if end_idx == -1:
            raise ValueError("Failed to parse symbol name from stream")
        sym_name = SC[offset:end_idx]
        offset = end_idx + 1
        sym_val = struct.unpack("<Q", SC[offset:offset+8])[0]
        offset += 8
        symbols.append((sym_name, sym_val))
 
    IP_ADDR = SC_ADDR + offset
    IP_LEN = SC_LEN - offset
    
    print(f"Parsed {S_LEN} symbols. Instructions start at {hex(IP_ADDR)} with length {IP_LEN}")
 
    IP = IP_ADDR
    IV = set((5, 1, 9, 13, 17, 21, 33, 38, 41, 49, 53))
    RV = set((4, 0, 8, 12, 16, 20, 32, 37, 40, 48, 52))
 
    while True:
        if IP >= IP_ADDR + IP_LEN:
            break
 
        current_ip = IP
        OP_B = bv.read(IP, 1)
        if OP_B is None or len(OP_B) == 0:
            raise ValueError(f"Failed to read OP byte at {hex(IP)}")
        OP = struct.unpack("<B", OP_B)[0]
        IP += 1
 
        if OP >= 0x40 and OP <= 0x44:
            target_string = b""
            while True:
                cur_char = bv.read(IP, 1)
                IP += 1
                if cur_char is None or struct.unpack("<B", cur_char)[0] == 0:
                    break
                target_string += cur_char
        elif OP < 0x40 and OP >= 0x0:
            if OP in IV:
                val = VMValue(0)
 
                index_div_bytes = bv.read(IP, 1)
                if index_div_bytes is None or len(index_div_bytes) == 0:
                    raise ValueError(f"Failed to read index_div at {hex(IP)}")
                index_div: int = struct.unpack("<B", index_div_bytes)[0]
                IP += 1
                
                size_of_reg = rkil_regsize(index_div)
                val_bytes = bv.read(IP, size_of_reg)
                if val_bytes is None or len(val_bytes) < size_of_reg:
                    raise ValueError(f"Failed to read val_bytes at {hex(IP)}")
                
                # Unpack integer properly based on size
                if size_of_reg == 1:
                    raw_val = struct.unpack("<B", val_bytes)[0]
                elif size_of_reg == 2:
                    raw_val = struct.unpack("<H", val_bytes)[0]
                elif size_of_reg == 4:
                    raw_val = struct.unpack("<I", val_bytes)[0]
                elif size_of_reg == 8:
                    raw_val = struct.unpack("<Q", val_bytes)[0]
                else:
                    raw_val = struct.unpack(f"<{size_of_reg}B", val_bytes)[0]
                    
                val = VMValue(raw_val)
                IP += size_of_reg
            elif OP in RV:
                val = VMValue(0)
                
                index_div_bytes = bv.read(IP, 1)
                if index_div_bytes is None or len(index_div_bytes) == 0:
                    raise ValueError(f"Failed to read index_div at {hex(IP)}")
                index_div: int = struct.unpack("<B", index_div_bytes)[0]
                IP += 1
 
                val_div_bytes = bv.read(IP, 1)
                if val_div_bytes is None or len(val_div_bytes) == 0:
                    raise ValueError(f"Failed to read val_div at {hex(IP)}")
                val_div: int = struct.unpack("<B", val_div_bytes)[0]
                IP += 1
 
                val = VMValue(rkvm_regval(val_div))
        else:
            pass
 
        if OP == 0x00 or OP == 0x01:
            rkvm_regset(index_div, val, current_ip)
        elif OP == 0x04 or OP == 0x05:
            v9 = VMValue(rkvm_regval(index_div))
            v9 = val + v9
            rkvm_regset(index_div, v9, current_ip)
        elif OP == 0x08 or OP == 0x09:
            v10 = VMValue(rkvm_regval(index_div))
            v10 = v10 - val
            rkvm_regset(index_div, v10, current_ip)
        elif OP == 0x10 or OP == 0x11:
            v12 = VMValue(rkvm_regval(index_div))
            v12 = v12 / val
            rkvm_regset(index_div, v12, current_ip)
        elif OP == 0xC or OP == 0xD:
            v11 = VMValue(rkvm_regval(index_div))
            v11 = v11 * val
            rkvm_regset(index_div, v11, current_ip)
        elif OP == 0x20 or OP == 0x21:
            v13 = VMValue(rkvm_regval(index_div))
            v13 = v13 | val
            rkvm_regset(index_div, v13, current_ip)
        elif OP == 0x25 or OP == 0x26:
            v14 = VMValue(rkvm_regval(index_div))
            v14 = v14 & val
            rkvm_regset(index_div, v14, current_ip)
        elif OP == 0x28 or OP == 0x29:
            v15 = VMValue(rkvm_regval(index_div))
            v15 = v15 ^ val
            rkvm_regset(index_div, v15, current_ip)
        elif OP == 0x30 or OP == 0x31:
            v16 = VMValue(rkvm_regval(index_div))
            v16 = v16 >> val
            rkvm_regset(index_div, v16, current_ip)
        elif OP == 0x34 or OP == 0x35:
            v17 = VMValue(rkvm_regval(index_div))
            v17 = v17 << val
            rkvm_regset(index_div, v17, current_ip)
        
        elif OP == 0x14 or OP == 0x15:
            rg_val = VMValue(rkvm_regval(index_div))
            print(f"[0x{current_ip:06x}]   REG[64] = ({rg_val == val})")
            print(f"[0x{current_ip:06x}]   REG[65] = ({val < 0})")
        elif OP == 0x40:
            print(f"[0x{current_ip:06x}]   FLAG := True")
        elif OP == 0x41:
            print(f"[0x{current_ip:06x}]   FLAG := REG[64]")
        elif OP == 0x42:
            print(f"[0x{current_ip:06x}]   FLAG := not REG[64]")
        elif OP == 0x43:
            print(f"[0x{current_ip:06x}]   FLAG := REG[65]")
        elif OP == 0x44:
            print(f"[0x{current_ip:06x}]   FLAG := not REG[65]")
 
        if OP >= 0x40 and OP <= 0x44:
            target_offset = None
            for sym_name, sym_offset in symbols:
                if sym_name == target_string:
                    target_offset = sym_offset
                    break
 
            print(f"[0x{current_ip:06x}]   IF FLAG:")
            if target_offset is not None:
                target_ip = IP_ADDR + target_offset
                print(f"\t                 goto {hex(target_ip)}  // {target_string.decode('utf-8', 'ignore')}")
            else:
                print(f"\t                 goto UNKNOWN_SYMBOL ({target_string.decode('utf-8', 'ignore')})")
 
        # else:
        #     print(f"CURRENT OPCODE: 0x{OP:02x} - {index_div} - {val}")
        #     print(f"                index_div: REG[{rkvm_regptr(index_div)}] as u{rkil_regsize(index_div) * 8}")
 
 
def reg4_get1(bv: bn.BinaryView, in0: VMValue, in1: VMValue, in2: VMValue, in3: VMValue) -> VMValue:
    print("Entered Func, Setting REGs...")
    rkvm_regset(0x3, in0)
    rkvm_regset(0x7, in1)
    rkvm_regset(0xB, in2)
    rkvm_regset(0xF, in3)
    print("Start to Interpret...")
    rkvm_interpret(bv, 0x4035b0, 0x63)
    print("Return")
    return VMValue(f"({rkvm_regval(0)} >> 0x20)")
 
 
def reg2_get1(bv: bn.BinaryView, arg1: VMValue, arg2: VMValue) -> VMValue:
    print("Entered Func, Setting REGs...")
    rkvm_regset(1, arg1)
    rkvm_regset(5, arg2)
    print("Start to Interpret...")
    rkvm_interpret(bv, 0x403630, 0x15)
    print("Return")
    return VMValue(rkvm_regval(0))
 
 
def reg3_get1(bv: bn.BinaryView, arg1: VMValue, arg2: VMValue, arg3: VMValue) -> VMValue:
    print("Entered Func, Setting REGs...")
    rkvm_regset(0, arg1)
    rkvm_regset(4, arg2)
    rkvm_regset(8, arg3)
    print("Start to Interpret...")
    rkvm_interpret(bv, 0x403530, 0x68)
    print("Return")
    return VMValue(rkvm_regval(0x10))
 
 
def reg1_get2(bv: bn.BinaryView, arg1: VMValue) -> tuple[VMValue, VMValue]:
    print("Entered Func, Setting REGs...")
    rkvm_regset(0, arg1)
    print("Start to Interpret...")
    rkvm_interpret(bv, 0x403660, 0x15)
    print("Return")
    return VMValue(rkvm_regval(1)), VMValue(rkvm_regval(5))
 
 
def complex_run(
    bv: bn.BinaryView,
    input_lo: VMValue,
    input_hi: VMValue,
    key0_lo: VMValue,
    key0_hi: VMValue,
    key1_lo: VMValue,
    key1_hi: VMValue
) -> tuple[VMValue, VMValue]:
    print("Entered Func")
    print(f"\nleft_key = reg2_get1({key0_lo}, {key0_hi})")
    left_key = reg2_get1(bv, key0_lo, key0_hi)
    print(f"\nright_key = reg2_get1({key1_lo}, {key1_hi})")
    right_key = reg2_get1(bv, key1_lo, key1_hi)
    print(f"\ninput_mix = reg2_get1({input_lo}, {input_hi})")
    input_mix = reg2_get1(bv, input_lo, input_hi)
    print("\nSetting REGs...")
    rkvm_regset(0, left_key)
    rkvm_regset(4, right_key)
    rkvm_regset(8, input_mix)
    print("Start to Interpret...")
    rkvm_interpret(bv, 0x403450, 0xCB)
    print("Return")
    return reg1_get2(bv, VMValue(rkvm_regval(8)))
 
def main(bv: bn.BinaryView, input_bytes: list[VMValue], encrypted: list[VMValue]) -> VMValue:
    key01_bytes = b"oR0yuki5g3n0ww1@"
    k0_lo, k0_hi, k1_lo, k1_hi = struct.unpack("<IIII", key01_bytes)
    key0_lo = VMValue(k0_lo)
    key0_hi = VMValue(k0_hi)
    key1_lo = VMValue(k1_lo)
    key1_hi = VMValue(k1_hi)
 
    key3_bytes = b"!DA0t3m0"
    k3_val = struct.unpack("<Q", key3_bytes)[0]
    key3 = VMValue(k3_val)
 
    key2_add = struct.unpack(">I", b"w31c")[0]
    print(f"key2 = reg4_get1({input_bytes[0]}, {input_bytes[1]}, {input_bytes[2]}, {input_bytes[3]}) << 0x20 + {key2_add}")
    key2 = (reg4_get1(bv, input_bytes[0], input_bytes[1], input_bytes[2], input_bytes[3]) << 0x20)
    print(f"key2 = {key2} (retval from reg4_get1) + {key2_add}")
    key2 = key2 + VMValue(key2_add)
 
    print(f"key0 = {key0_lo} {key0_hi}")
    print(f"key1 = {key1_lo} {key1_hi}")
    print(f"key2 = {key2}")
    print(f"key3 = {key3}")
 
    block_id = 0
    print(f"block_id = {block_id}")
    
    print("=======================================")
    print("==============Start Loop===============")
    print("=======================================")
    while True:
        if block_id >= 0x2:
            print(" 此处省略了另外 7 个循环 ")
            print("If all Check is true: \n\tcongratulations.")
            return VMValue(1)
 
        print(f"var_a0 = reg2_get1({key0_lo}, {key0_hi})")
        var_a0 = reg2_get1(bv, key0_lo, key0_hi)
        print(f"var_a0 = {var_a0} (retval from reg2_get1)")
        
        print("=======================================")
        
        print(f"var_a0 = reg3_get1({key2}, {key3}, {var_a0})")
        var_a0 = reg3_get1(bv, key2, key3, var_a0)
        print(f"var_a0 = {var_a0} (retval from reg3_get1)")
        
        print("=======================================")
 
        print(f"key0_lo, key0_hi = reg1_get2({var_a0})")
        key0_lo, key0_hi = reg1_get2(bv, var_a0)
        print(f"key0_lo = {key0_lo} (retval from reg1_get2)")
        print(f"key0_hi = {key0_hi} (retval from reg1_get2)")
        
        print("=======================================")
        
        print(f"var_a0 = reg2_get1({key1_lo}, {key1_hi})")
        var_a0 = reg2_get1(bv, key1_lo, key1_hi)
        print(f"var_a0 = {var_a0} (retval from reg2_get1)")
        
        print("=======================================")
        
        print(f"var_a0 = reg3_get1({key3}, {key2}, {var_a0})")
        var_a0 = reg3_get1(bv, key3, key2, var_a0)
        print(f"var_a0 = {var_a0} (retval from reg3_get1)")
        
        print("=======================================")
        
        print(f"key1_lo, key1_hi = reg1_get2({var_a0})")
        key1_lo, key1_hi = reg1_get2(bv, var_a0)
        print(f"key1_lo = {key1_lo} (retval from reg1_get2)")
        print(f"key1_hi = {key1_hi} (retval from reg1_get2)")
 
        byte_base0 = block_id * 4
        byte_base1 = (block_id + 1) * 4
        
        print("=======================================")
        
        print(f"input_lo = reg4_get1({input_bytes[byte_base0]}, {input_bytes[byte_base0 + 1]}, {input_bytes[byte_base0 + 2]}, {input_bytes[byte_base0 + 3]})")
        input_lo = reg4_get1(
            bv,
            input_bytes[byte_base0],
            input_bytes[byte_base0 + 1],
            input_bytes[byte_base0 + 2],
            input_bytes[byte_base0 + 3]
        )
        print(f"input_lo = {input_lo} (retval from reg4_get1)")
        
        print("=======================================")
        
        print(f"input_hi = reg4_get1({input_bytes[byte_base1]}, {input_bytes[byte_base1 + 1]}, {input_bytes[byte_base1 + 2]}, {input_bytes[byte_base1 + 3]})")
        input_hi = reg4_get1(
            bv,
            input_bytes[byte_base1],
            input_bytes[byte_base1 + 1],
            input_bytes[byte_base1 + 2],
            input_bytes[byte_base1 + 3]
        )
        print(f"input_hi = {input_hi} (retval from reg4_get1)")
        
        print("=======================================")
        
        print(f"enc_lo, enc_hi = complex_run({input_lo}, {input_hi}, {key0_lo}, {key0_hi}, {key1_lo}, {key1_hi})")
        enc_lo, enc_hi = complex_run(bv, input_lo, input_hi, key0_lo, key0_hi, key1_lo, key1_hi)
        print(f"enc_lo = {enc_lo} (retval from complex_run)")
        print(f"enc_hi = {enc_hi} (retval from complex_run)")
        
        enc_qword = (enc_hi << 0x20) + enc_lo
        target_qword = encrypted[block_id // 2]
        print(f"CHECK[{block_id // 2}] : {enc_qword} == {target_qword}")
 
        block_id += 2
        print(f"block_id += 2")
 
        print("=======================================")
 
if __name__ == "__main__":
    sys.stdout = open("rkvm.log", "w", encoding="utf-8")
 
    input_b = []
    for i in range(64):
        input_b.append(f"input[0x{i:02x}]")
    # enc_b64 = "9ZIAZkgASBs0Lo1UlwlJv8+zy+LOvztsjOfzWB2SccV+dqzjFP/baFQtqr+lVn3Xejh/oCzbpPIrgAPFheqCpHIAAAAAAAAA"
    # enc = base64.b64decode(enc_b64)
    enc = []
    for i in range(8):
        enc.append(f"ENCRYPTED[{i}]")
    main(bv, input_b, enc)
 
    sys.stdout.close()
    sys.stdout = sys.__stdout__

log 分析

打印出来的 log 如下

key2 = reg4_get1(input[0x00], input[0x01], input[0x02], input[0x03]) << 0x20 + 1999843683
Entered Func, Setting REGs...
(REG[0] as u8 ) := (input[0x00] as u8 )
(REG[1] as u8 ) := (input[0x01] as u8 )
(REG[2] as u8 ) := (input[0x02] as u8 )
(REG[3] as u8 ) := (input[0x03] as u8 )
Start to Interpret...
Parsed 0 symbols. Instructions start at 0x4035b8 with length 91
[0x4035b8]   (REG[0] as u64) := (((REG[0] as u64) << 8) as u64)
[0x4035c2]   (REG[0] as u8 ) := ((REG[1] as u8 ) as u8 )
[0x4035c5]   (REG[0] as u64) := (((REG[0] as u64) << 8) as u64)
[0x4035cf]   (REG[0] as u8 ) := ((REG[2] as u8 ) as u8 )
[0x4035d2]   (REG[0] as u64) := (((REG[0] as u64) << 8) as u64)
[0x4035dc]   (REG[0] as u8 ) := ((REG[3] as u8 ) as u8 )
[0x4035df]   (REG[0] as u64) := (((REG[0] as u64) << 8) as u64)
[0x4035e9]   (REG[0] as u8 ) := ((REG[4] as u8 ) as u8 )
[0x4035ec]   (REG[0] as u64) := (((REG[0] as u64) << 8) as u64)
[0x4035f6]   (REG[0] as u8 ) := ((REG[5] as u8 ) as u8 )
[0x4035f9]   (REG[0] as u64) := (((REG[0] as u64) << 8) as u64)
[0x403603]   (REG[0] as u8 ) := ((REG[6] as u8 ) as u8 )
[0x403606]   (REG[0] as u64) := (((REG[0] as u64) << 8) as u64)
[0x403610]   (REG[0] as u8 ) := ((REG[7] as u8 ) as u8 )
Return
key2 = (((REG[0] as u64) >> 0x20) << 32) (retval from reg4_get1) + 1999843683
key0 = 0x7930526f 0x35696b75
key1 = 0x306e3367 0x40317777
key2 = ((((REG[0] as u64) >> 0x20) << 32) + 1999843683)
key3 = 0x306d337430414421
block_id = 0
=======================================
==============Start Loop===============
=======================================
var_a0 = reg2_get1(0x7930526f, 0x35696b75)
Entered Func, Setting REGs...
(REG[0] as u32) := (0x7930526f as u32)
(REG[1] as u32) := (0x35696b75 as u32)
Start to Interpret...
Parsed 0 symbols. Instructions start at 0x403638 with length 13
[0x403638]   (REG[0] as u64) := (((REG[0] as u64) << 32) as u64)
[0x403642]   (REG[0] as u32) := ((REG[1] as u32) as u32)
Return
var_a0 = (REG[0] as u64) (retval from reg2_get1)
=======================================
var_a0 = reg3_get1(((((REG[0] as u64) >> 0x20) << 32) + 1999843683), 0x306d337430414421, (REG[0] as u64))
Entered Func, Setting REGs...
(REG[0] as u64) := (((((REG[0] as u64) >> 0x20) << 32) + 1999843683) as u64)
(REG[1] as u64) := (0x306d337430414421 as u64)
(REG[2] as u64) := ((REG[0] as u64) as u64)
Start to Interpret...
Parsed 1 symbols. Instructions start at 0x403545 with length 83
[0x403545]   (REG[3] as u8 ) := (0x0 as u8 )
[0x403548]   (REG[4] as u64) := (0x0 as u64)
[0x403552]   (REG[5] as u8 ) := (0x7 as u8 )
[0x403555]   (REG[5] as u8 ) := ((33 + (REG[5] as u8 )) as u8 )
[0x403558]   (REG[4] as u64) := (((REG[4] as u64) << 8) as u64)
[0x403562]   (REG[0] as u8 ) := (((REG[0] as u8 ) * (REG[1] as u8 )) as u8 )
[0x403565]   (REG[0] as u8 ) := (((REG[0] as u8 ) ^ (REG[1] as u8 )) as u8 )
[0x403568]   (REG[2] as u8 ) := (((REG[2] as u8 ) ^ (REG[0] as u8 )) as u8 )
[0x40356b]   (REG[4] as u8 ) := ((REG[2] as u8 ) as u8 )
[0x40356e]   (REG[0] as u64) := (((REG[0] as u64) >> 8) as u64)
[0x403578]   (REG[1] as u64) := (((REG[1] as u64) >> 8) as u64)
[0x403582]   (REG[2] as u64) := (((REG[2] as u64) >> 8) as u64)
[0x40358c]   (REG[3] as u8 ) := ((1 + (REG[3] as u8 )) as u8 )
[0x40358f]   REG[64] = (((REG[3] as u8 ) == 8))
[0x40358f]   REG[65] = (0x0)
[0x403592]   FLAG := REG[65]
[0x403592]   IF FLAG:
	                 goto 0x403555  // loop
Return
var_a0 = (REG[4] as u64) (retval from reg3_get1)
=======================================
key0_lo, key0_hi = reg1_get2((REG[4] as u64))
Entered Func, Setting REGs...
(REG[0] as u64) := ((REG[4] as u64) as u64)
Start to Interpret...
Parsed 0 symbols. Instructions start at 0x403668 with length 13
[0x403668]   (REG[1] as u32) := ((REG[0] as u32) as u32)
[0x40366b]   (REG[0] as u64) := (((REG[0] as u64) >> 32) as u64)
Return
key0_lo = (REG[0] as u32) (retval from reg1_get2)
key0_hi = (REG[1] as u32) (retval from reg1_get2)
=======================================
var_a0 = reg2_get1(0x306e3367, 0x40317777)
Entered Func, Setting REGs...
(REG[0] as u32) := (0x306e3367 as u32)
(REG[1] as u32) := (0x40317777 as u32)
Start to Interpret...
Parsed 0 symbols. Instructions start at 0x403638 with length 13
[0x403638]   (REG[0] as u64) := (((REG[0] as u64) << 32) as u64)
[0x403642]   (REG[0] as u32) := ((REG[1] as u32) as u32)
Return
var_a0 = (REG[0] as u64) (retval from reg2_get1)
=======================================
var_a0 = reg3_get1(0x306d337430414421, ((((REG[0] as u64) >> 0x20) << 32) + 1999843683), (REG[0] as u64))
Entered Func, Setting REGs...
(REG[0] as u64) := (0x306d337430414421 as u64)
(REG[1] as u64) := (((((REG[0] as u64) >> 0x20) << 32) + 1999843683) as u64)
(REG[2] as u64) := ((REG[0] as u64) as u64)
Start to Interpret...
Parsed 1 symbols. Instructions start at 0x403545 with length 83
[0x403545]   (REG[3] as u8 ) := (0x0 as u8 )
[0x403548]   (REG[4] as u64) := (0x0 as u64)
[0x403552]   (REG[5] as u8 ) := (0x7 as u8 )
[0x403555]   (REG[5] as u8 ) := ((33 + (REG[5] as u8 )) as u8 )
[0x403558]   (REG[4] as u64) := (((REG[4] as u64) << 8) as u64)
[0x403562]   (REG[0] as u8 ) := (((REG[0] as u8 ) * (REG[1] as u8 )) as u8 )
[0x403565]   (REG[0] as u8 ) := (((REG[0] as u8 ) ^ (REG[1] as u8 )) as u8 )
[0x403568]   (REG[2] as u8 ) := (((REG[2] as u8 ) ^ (REG[0] as u8 )) as u8 )
[0x40356b]   (REG[4] as u8 ) := ((REG[2] as u8 ) as u8 )
[0x40356e]   (REG[0] as u64) := (((REG[0] as u64) >> 8) as u64)
[0x403578]   (REG[1] as u64) := (((REG[1] as u64) >> 8) as u64)
[0x403582]   (REG[2] as u64) := (((REG[2] as u64) >> 8) as u64)
[0x40358c]   (REG[3] as u8 ) := ((1 + (REG[3] as u8 )) as u8 )
[0x40358f]   REG[64] = (((REG[3] as u8 ) == 8))
[0x40358f]   REG[65] = (0x0)
[0x403592]   FLAG := REG[65]
[0x403592]   IF FLAG:
	                 goto 0x403555  // loop
Return
var_a0 = (REG[4] as u64) (retval from reg3_get1)
=======================================
key1_lo, key1_hi = reg1_get2((REG[4] as u64))
Entered Func, Setting REGs...
(REG[0] as u64) := ((REG[4] as u64) as u64)
Start to Interpret...
Parsed 0 symbols. Instructions start at 0x403668 with length 13
[0x403668]   (REG[1] as u32) := ((REG[0] as u32) as u32)
[0x40366b]   (REG[0] as u64) := (((REG[0] as u64) >> 32) as u64)
Return
key1_lo = (REG[0] as u32) (retval from reg1_get2)
key1_hi = (REG[1] as u32) (retval from reg1_get2)
=======================================
input_lo = reg4_get1(input[0x00], input[0x01], input[0x02], input[0x03])
Entered Func, Setting REGs...
(REG[0] as u8 ) := (input[0x00] as u8 )
(REG[1] as u8 ) := (input[0x01] as u8 )
(REG[2] as u8 ) := (input[0x02] as u8 )
(REG[3] as u8 ) := (input[0x03] as u8 )
Start to Interpret...
Parsed 0 symbols. Instructions start at 0x4035b8 with length 91
[0x4035b8]   (REG[0] as u64) := (((REG[0] as u64) << 8) as u64)
[0x4035c2]   (REG[0] as u8 ) := ((REG[1] as u8 ) as u8 )
[0x4035c5]   (REG[0] as u64) := (((REG[0] as u64) << 8) as u64)
[0x4035cf]   (REG[0] as u8 ) := ((REG[2] as u8 ) as u8 )
[0x4035d2]   (REG[0] as u64) := (((REG[0] as u64) << 8) as u64)
[0x4035dc]   (REG[0] as u8 ) := ((REG[3] as u8 ) as u8 )
[0x4035df]   (REG[0] as u64) := (((REG[0] as u64) << 8) as u64)
[0x4035e9]   (REG[0] as u8 ) := ((REG[4] as u8 ) as u8 )
[0x4035ec]   (REG[0] as u64) := (((REG[0] as u64) << 8) as u64)
[0x4035f6]   (REG[0] as u8 ) := ((REG[5] as u8 ) as u8 )
[0x4035f9]   (REG[0] as u64) := (((REG[0] as u64) << 8) as u64)
[0x403603]   (REG[0] as u8 ) := ((REG[6] as u8 ) as u8 )
[0x403606]   (REG[0] as u64) := (((REG[0] as u64) << 8) as u64)
[0x403610]   (REG[0] as u8 ) := ((REG[7] as u8 ) as u8 )
Return
input_lo = ((REG[0] as u64) >> 0x20) (retval from reg4_get1)
=======================================
input_hi = reg4_get1(input[0x04], input[0x05], input[0x06], input[0x07])
Entered Func, Setting REGs...
(REG[0] as u8 ) := (input[0x04] as u8 )
(REG[1] as u8 ) := (input[0x05] as u8 )
(REG[2] as u8 ) := (input[0x06] as u8 )
(REG[3] as u8 ) := (input[0x07] as u8 )
Start to Interpret...
Parsed 0 symbols. Instructions start at 0x4035b8 with length 91
[0x4035b8]   (REG[0] as u64) := (((REG[0] as u64) << 8) as u64)
[0x4035c2]   (REG[0] as u8 ) := ((REG[1] as u8 ) as u8 )
[0x4035c5]   (REG[0] as u64) := (((REG[0] as u64) << 8) as u64)
[0x4035cf]   (REG[0] as u8 ) := ((REG[2] as u8 ) as u8 )
[0x4035d2]   (REG[0] as u64) := (((REG[0] as u64) << 8) as u64)
[0x4035dc]   (REG[0] as u8 ) := ((REG[3] as u8 ) as u8 )
[0x4035df]   (REG[0] as u64) := (((REG[0] as u64) << 8) as u64)
[0x4035e9]   (REG[0] as u8 ) := ((REG[4] as u8 ) as u8 )
[0x4035ec]   (REG[0] as u64) := (((REG[0] as u64) << 8) as u64)
[0x4035f6]   (REG[0] as u8 ) := ((REG[5] as u8 ) as u8 )
[0x4035f9]   (REG[0] as u64) := (((REG[0] as u64) << 8) as u64)
[0x403603]   (REG[0] as u8 ) := ((REG[6] as u8 ) as u8 )
[0x403606]   (REG[0] as u64) := (((REG[0] as u64) << 8) as u64)
[0x403610]   (REG[0] as u8 ) := ((REG[7] as u8 ) as u8 )
Return
input_hi = ((REG[0] as u64) >> 0x20) (retval from reg4_get1)
=======================================
enc_lo, enc_hi = complex_run(((REG[0] as u64) >> 0x20), ((REG[0] as u64) >> 0x20), (REG[0] as u32), (REG[1] as u32), (REG[0] as u32), (REG[1] as u32))
Entered Func
 
left_key = reg2_get1((REG[0] as u32), (REG[1] as u32))
Entered Func, Setting REGs...
(REG[0] as u32) := ((REG[0] as u32) as u32)
(REG[1] as u32) := ((REG[1] as u32) as u32)
Start to Interpret...
Parsed 0 symbols. Instructions start at 0x403638 with length 13
[0x403638]   (REG[0] as u64) := (((REG[0] as u64) << 32) as u64)
[0x403642]   (REG[0] as u32) := ((REG[1] as u32) as u32)
Return
 
right_key = reg2_get1((REG[0] as u32), (REG[1] as u32))
Entered Func, Setting REGs...
(REG[0] as u32) := ((REG[0] as u32) as u32)
(REG[1] as u32) := ((REG[1] as u32) as u32)
Start to Interpret...
Parsed 0 symbols. Instructions start at 0x403638 with length 13
[0x403638]   (REG[0] as u64) := (((REG[0] as u64) << 32) as u64)
[0x403642]   (REG[0] as u32) := ((REG[1] as u32) as u32)
Return
 
input_mix = reg2_get1(((REG[0] as u64) >> 0x20), ((REG[0] as u64) >> 0x20))
Entered Func, Setting REGs...
(REG[0] as u32) := (((REG[0] as u64) >> 0x20) as u32)
(REG[1] as u32) := (((REG[0] as u64) >> 0x20) as u32)
Start to Interpret...
Parsed 0 symbols. Instructions start at 0x403638 with length 13
[0x403638]   (REG[0] as u64) := (((REG[0] as u64) << 32) as u64)
[0x403642]   (REG[0] as u32) := ((REG[1] as u32) as u32)
Return
 
Setting REGs...
(REG[0] as u64) := ((REG[0] as u64) as u64)
(REG[1] as u64) := ((REG[0] as u64) as u64)
(REG[2] as u64) := ((REG[0] as u64) as u64)
Start to Interpret...
Parsed 1 symbols. Instructions start at 0x403465 with length 182
[0x403465]   (REG[3] as u32) := (0x19198100 as u32)
[0x40346b]   (REG[4] as u8 ) := (0x0 as u8 )
[0x40346e]   (REG[3] as u32) := ((2233333945 + (REG[3] as u32)) as u32)
[0x403474]   (REG[7] as u64) := ((REG[2] as u64) as u64)
[0x403477]   (REG[6] as u64) := ((REG[0] as u64) as u64)
[0x40347a]   (REG[6] as u64) := (((REG[6] as u64) >> 32) as u64)
[0x403484]   (REG[5] as u32) := ((REG[7] as u32) as u32)
[0x403487]   (REG[5] as u32) := (((REG[5] as u32) << 4) as u32)
[0x40348d]   (REG[5] as u32) := (((REG[6] as u32) + (REG[5] as u32)) as u32)
[0x403490]   (REG[6] as u32) := ((REG[7] as u32) as u32)
[0x403493]   (REG[6] as u32) := (((REG[3] as u32) + (REG[6] as u32)) as u32)
[0x403496]   (REG[5] as u32) := (((REG[5] as u32) ^ (REG[6] as u32)) as u32)
[0x403499]   (REG[6] as u64) := ((REG[0] as u64) as u64)
[0x40349c]   (REG[7] as u32) := (((REG[7] as u32) >> 5) as u32)
[0x4034a2]   (REG[7] as u32) := (((REG[6] as u32) + (REG[7] as u32)) as u32)
[0x4034a5]   (REG[5] as u32) := (((REG[5] as u32) ^ (REG[7] as u32)) as u32)
[0x4034a8]   (REG[6] as u64) := ((REG[2] as u64) as u64)
[0x4034ab]   (REG[6] as u64) := (((REG[6] as u64) >> 32) as u64)
[0x4034b5]   (REG[6] as u32) := (((REG[5] as u32) + (REG[6] as u32)) as u32)
[0x4034b8]   (REG[6] as u64) := (((REG[6] as u64) << 32) as u64)
[0x4034c2]   (REG[6] as u32) := ((REG[2] as u32) as u32)
[0x4034c5]   (REG[2] as u64) := ((REG[6] as u64) as u64)
[0x4034c8]   (REG[7] as u64) := ((REG[2] as u64) as u64)
[0x4034cb]   (REG[7] as u64) := (((REG[7] as u64) >> 32) as u64)
[0x4034d5]   (REG[6] as u64) := ((REG[1] as u64) as u64)
[0x4034d8]   (REG[6] as u64) := (((REG[6] as u64) >> 32) as u64)
[0x4034e2]   (REG[5] as u32) := ((REG[7] as u32) as u32)
[0x4034e5]   (REG[5] as u32) := (((REG[5] as u32) << 4) as u32)
[0x4034eb]   (REG[5] as u32) := (((REG[6] as u32) + (REG[5] as u32)) as u32)
[0x4034ee]   (REG[6] as u32) := ((REG[7] as u32) as u32)
[0x4034f1]   (REG[6] as u32) := (((REG[3] as u32) + (REG[6] as u32)) as u32)
[0x4034f4]   (REG[5] as u32) := (((REG[5] as u32) ^ (REG[6] as u32)) as u32)
[0x4034f7]   (REG[6] as u64) := ((REG[1] as u64) as u64)
[0x4034fa]   (REG[7] as u32) := (((REG[7] as u32) >> 5) as u32)
[0x403500]   (REG[7] as u32) := (((REG[6] as u32) + (REG[7] as u32)) as u32)
[0x403503]   (REG[5] as u32) := (((REG[5] as u32) ^ (REG[7] as u32)) as u32)
[0x403506]   (REG[2] as u32) := (((REG[5] as u32) + (REG[2] as u32)) as u32)
[0x403509]   (REG[3] as u32) := ((421101824 + (REG[3] as u32)) as u32)
[0x40350f]   (REG[4] as u8 ) := ((1 + (REG[4] as u8 )) as u8 )
[0x403512]   REG[64] = (((REG[4] as u8 ) == 32))
[0x403512]   REG[65] = (0x0)
[0x403515]   FLAG := REG[65]
[0x403515]   IF FLAG:
	                 goto 0x40346e  // loop
Return
Entered Func, Setting REGs...
(REG[0] as u64) := ((REG[2] as u64) as u64)
Start to Interpret...
Parsed 0 symbols. Instructions start at 0x403668 with length 13
[0x403668]   (REG[1] as u32) := ((REG[0] as u32) as u32)
[0x40366b]   (REG[0] as u64) := (((REG[0] as u64) >> 32) as u64)
Return
enc_lo = (REG[0] as u32) (retval from complex_run)
enc_hi = (REG[1] as u32) (retval from complex_run)
CHECK[0] : (((REG[1] as u32) << 32) + (REG[0] as u32)) == ENCRYPTED[0]
block_id += 2
=======================================
 此处省略了另外 7 个循环 
If all Check is true: 
	congratulations.
 

我们可以简单地看出例如 reg4_get1 是一个把四字节压缩成一个 u32 的函数
其余分析类似

结合 main 函数,我们能很清楚地分辨出来这是一个 TEA-CBC 的加密

据此可以写出解密脚本

import struct
import base64
 
def pressed(a, b, c):
    r4 = 0
    for _ in range(8):
        r4 = (r4 << 8) & 0xFFFFFFFFFFFFFFFF
        b0 = a & 0xFF
        b1 = b & 0xFF
        b2 = c & 0xFF
        
        b0 = (b0 * b1) & 0xFF
        b0 = (b0 ^ b1) & 0xFF
        b2 = (b2 ^ b0) & 0xFF
        
        r4 = (r4 & 0xFFFFFFFFFFFFFF00) | b2
        
        a >>= 8
        b >>= 8
        c >>= 8
    return r4
 
def tea(v0, v1, k):
    delta = 0x9e3779b9
    sum_val = (delta * 32) & 0xFFFFFFFF
    
    for _ in range(32):
        v1 = (v1 - (((v0 << 4) + k[2]) ^ (v0 + sum_val) ^ ((v0 >> 5) + k[3]))) & 0xFFFFFFFF
        v0 = (v0 - (((v1 << 4) + k[0]) ^ (v1 + sum_val) ^ ((v1 >> 5) + k[1]))) & 0xFFFFFFFF
        sum_val = (sum_val - delta) & 0xFFFFFFFF
        
    return v0, v1
 
def solve():
    enc_b64  = "9ZIAZkgASBs0Lo1UlwlJv8+zy+LOvztsjOfzWB2SccV+dqzjFP/baFQtqr+lVn3Xejh/oCzbpPIrgAPFheqCpHIAAAAAAAAA"
    enc_data = base64.b64decode(enc_b64)
    
    enc_blocks = struct.unpack("<8Q", enc_data[:64])
    
    key0 = 0x7930526f35696b75
    key1 = 0x306e336740317777
    key3 = 0x306d337430414421
    
    I_0 = struct.unpack(">I", b"flag")[0]
    
    key2 = ((I_0 << 32) + 1999843683) & 0xFFFFFFFFFFFFFFFF
    
    flag = b""
    for block in enc_blocks:
        key0 = pressed(key2, key3, key0)
        key1 = pressed(key3, key2, key1)
        
        k = [
            (key0 >> 32) & 0xFFFFFFFF,
            key0 & 0xFFFFFFFF,
            (key1 >> 32) & 0xFFFFFFFF,
            key1 & 0xFFFFFFFF
        ]
        
        v0 = block & 0xFFFFFFFF
        v1 = (block >> 32) & 0xFFFFFFFF
        
        dec_v0, dec_v1 = tea(v0, v1, k)
        
        flag += struct.pack(">I", dec_v0) + struct.pack(">I", dec_v1)
        
    print(f"{flag.decode()}")
 
if __name__ == "__main__":
    solve()

flag{d0_U_iMp13meN7_A_d1s@s3mb13r_4_th3_i7t3rmEd1@t3_1@ngUag3?}

脚本补充

做题中使用了某些脚本,不知道该在哪说,一股脑堆后面吧

def test_condition():
    res = []
    for length in range(100):
        if (((length + 3) >> 2) + 1) & 0xfffffffffffffffe == 0x10:
            res.append(length)
    print(res)
 
def test_reg_or_val():
    IV = [5, 1]
    RV = [4, 0]
    SF = []
    for OP in range(8, 0x45):
        v3 = 1 << (OP - 8)
        if v3 & 0x220242002222:
            IV.append(OP)
            print(f"0x{OP:02x} : Immediate_VAL")
        elif v3 & 0x110121001111:
            RV.append(OP)
            print(f"0x{OP:02x} : Register_VAL")
        elif v3 & 0x1F00000000000000:
            SF.append(OP)
            print(f"0x{OP:02x} : Set_FLAG")
        else:
            print(f"0x{OP:02x} : UNKNOWN")
    print(f"Immediate_VAL: {IV}")
    print(f"Register_VAL : {RV}")
    print(f"Set_FLAG     : {SF}")
 
 
if __name__ == "__main__":
    # test_condition()
    test_reg_or_val()

分别是确定长度范围和确定使用立即数或者寄存器的指令