最心有餘而力不足的一集,做完 vm 頸椎病犯了,第二天根本打。最後,加上學弟學妹打的,最後剩一個 Android 逆向沒 AK,要是沒有頸椎病這一說肯定 AK 了。感覺快退役了...
原文使用 hackmd 編寫,所以圖片均託管在外網,圖沒掛載入不出來圖片自己想辦法。
mips
編譯一個 qemu-6.2.0 mips-linux-user
bindiff 一下恢復符號,懷疑修改了 ELF loader 或者 syscall,最後發現是後者改了 do_syscall1 函式 read 和 write 系統呼叫實現。交叉引用找到加密演算法
//
// Created by gaoyucan on 2024/11/2.
//
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
uint8_t *decrypt_func(const uint8_t *buf) {
uint8_t sbox[256] = {0};
unsigned char key_arr[4] = {
0xDE, 0xAD, 0xBE, 0xEF
};
for (int i = 0; i < 256; ++i) {
sbox[i] = i;
}
const char *key = "6105t3";
int v5 = 0;
for (int i = 0; i < 256; ++i) {
uint8_t a = sbox[i];
uint8_t b = key[i % 6];
v5 += a + b;
sbox[i] = sbox[v5 & 0xff];
sbox[v5 & 0xff] = a;
}
uint8_t *ret = malloc(256);
memset(ret, 0, 256);
int v7 = 0;
int v8 = 0;
for (int i = 0; i < 22; ++i) {
v7 += 1;
uint8_t a = sbox[v7 & 0xff];
v8 += a;
sbox[v7 & 0xff] = sbox[v8 & 0xff];
sbox[v8 & 0xff] = a;
uint8_t t = sbox[0xff & (sbox[v7 & 0xff] + a)] ^ key_arr[i & 0x3] ^ buf[i];
t = ((t << 5 | t >> 3) & 0xff) ^ 0xde;
t = ((t << 4 | t >> 4) & 0xff) ^ 0xad;
t = ((t << 3 | t >> 5) & 0xff) ^ 0xbe;
t ^= (0x3b | 0xc0);
t = ((t << 2 | t >> 6) & 0xff);
t = ((t << 1 | t >> 7) & 0xff);
ret[i] = t;
}
return ret;
}
int swap(uint8_t *buf, int a, int b) {
uint8_t tmp = buf[a];
buf[a] = buf[b];
buf[b] = tmp;
return 0;
}
int main() {
uint8_t dest_value[24] = {
0x000000C4, 0x000000EE, 0x0000003C, 0x000000BB, 0x000000E7, 0x000000FD, 0x00000067, 0x0000001D,
0x000000F8, 0x00000097, 0x00000068, 0x0000009D, 0x0000000B, 0x0000007F, 0x000000C7, 0x00000080,
0x000000DF, 0x000000F9, 0x0000004B, 0x000000A0, 0x00000046, 0x00000091, 0x00000000, 0x00000000
};
swap(dest_value, 7, 11);
swap(dest_value, 12, 16);
for (int i = 0; i < 22; ++i) {
dest_value[i] ^= 0xa;
}
uint8_t *ret = decrypt_func(dest_value);
printf("%s\n", ret);
for (int i = 0; i < 22; ++i) {
printf("%02X ", ret[i]);
}
return 0;
}
ez_vm
虛擬機器 + 白盒 AES
虛擬機器是個棧虛擬機器,指令看起來就是從 x64 彙編翻譯過來的,下面是反編譯指令碼,前面手開頭的要手動處理一下,也沒幾個 replace 替換一下即可
import math
from capstone import *
from capstone.x86_const import *
from vm_const import *
import ctypes
class Operand(object):
def __init__(self, size):
self.size = size
class Imm(Operand):
def __init__(self, val, size):
super().__init__(size)
self.val = val
def __str__(self):
return hex(self.val)
properties_map = {
0x00000010: ["rax", "eax", "ax", "al"],
0x00000018: ["rcx", "ecx", "cx", "cl"],
0x00000020: ["rdx", "edx", "dx", "dl"],
0x00000028: ["rbx", "ebx", "bx", "bl"],
0x00000030: ["rsp", "esp", "sp", "spl"],
0x00000038: ["rbp", "ebp", "bp", "bpl"],
0x00000040: ["rsi", "esi", "si", "sil"],
0x00000048: ["rdi", "edi", "di", "dil"],
0x00000050: ["r8", "r8d", "r8w", "r8b"],
0x00000058: ["r9", "r9d", "r9w", "r9b"],
0x00000060: ["r10", "r10d", "r10w", "r10b"],
0x00000068: ["r11", "r11d", "r11w", "r11b"],
0x00000070: ["r12", "r12d", "r12w", "r12b"],
0x00000078: ["r13", "r13d", "r13w", "r13b"],
0x00000080: ["r14", "r14d", "r14w", "r14b"],
0x00000088: ["r15", "r15d", "r15w", "r15b"]
}
size_map = {
1: "byte",
2: "word",
4: "dword",
8: "qword"
}
class RegPtr(Operand):
def __init__(self, name_arr, size=8):
super().__init__(size)
self.name_arr = name_arr
class Reg(Operand):
def __init__(self, reg_ptr: RegPtr, size):
super().__init__(size)
self.name = reg_ptr.name_arr[3 - int(math.log2(size))]
def __str__(self):
return self.name
class VMPtr(Operand):
def __init__(self):
super().__init__(8)
def get_property(self, offset):
return RegPtr(properties_map[offset])
class SExp(Operand):
def __init__(self, op, operand1, size):
super().__init__(size)
self.op = op
self.operand1 = operand1
def __str__(self):
return f'{self.op}({self.operand1})'
class BExp(Operand):
def __init__(self, op, operand1, operand2, size):
super().__init__(size)
self.op = op
self.operand1 = operand1
self.operand2 = operand2
def __str__(self):
return f'({self.operand2}) {self.op} ({self.operand1})'
class LoadExp(Operand):
def __init__(self, exp: BExp | Reg, size):
super().__init__(size)
if isinstance(exp, Reg):
self.complex = 0
self.base = exp
return
assert exp.op == 'add' and isinstance(exp.operand1, Imm);
self.disp = exp.operand1.val
if isinstance(exp.operand2, BExp):
self.complex = 2
exp_inner = exp.operand2
assert exp_inner.op == 'add' and isinstance(exp_inner.operand2, Reg);
self.base = exp_inner.operand2
exp_offset = exp_inner.operand1
assert isinstance(exp_offset, BExp) and exp_offset.op == 'imul';
self.offset = exp_offset.operand2
self.scale = exp_offset.operand1
else:
self.complex = 1
self.base = exp.operand2
def __str__(self):
if self.complex == 2:
return f'{size_map[self.size]} ptr [{self.base} + {self.offset} * {self.scale} + 0x{self.disp:x}]'
elif self.complex == 1:
return f'{size_map[self.size]} ptr [{self.base} + 0x{self.disp:x}]'
else:
return f'{size_map[self.size]} ptr [{self.base}]'
class VM(object):
def __init__(self, buffer: bytes):
self.buffer = buffer
self.cs = Cs(CS_ARCH_X86, CS_MODE_64)
self.stack = []
self.name_idx = 0
def next_temp_name(self):
name = f't_{self.name_idx}'
self.name_idx += 1
return name
def disassemble(self, offset):
"""
注意:push 和 pop 至少是 2bytes, 所有 u8 都必須複用 u16 的程式碼
:param offset:
:return:
"""
opcode = self.buffer[offset]
opcode_b = self.buffer[offset + 1]
# 校驗 opcode 和 opcode_b
assert opcode <= 29 and opcode_b <= 8
opcode_size = 2
is_end = False
match opcode:
case 0:
# push
operand = int.from_bytes(self.buffer[offset + 2:offset + 2 + opcode_b], "little")
# print(f'{offset:08x}: push_u{opcode_b * 8} {operand:08x}')
self.stack.append(Imm(operand, opcode_b))
opcode_size += opcode_b
case 1:
# load => push(*pop())
A = self.stack.pop()
if isinstance(A, RegPtr):
self.stack.append(Reg(A, opcode_b))
else:
assert isinstance(A, BExp) or isinstance(A, Reg)
self.stack.append(LoadExp(A, opcode_b))
case 2:
# load_u128
raise "not impl"
case 3 | 5 | 6: # 看起來 3 和 5 完全一樣
# store => A = pop(); *A = pop()
A = self.stack.pop()
B = self.stack.pop()
if isinstance(A, RegPtr):
A = Reg(A, opcode_b)
if isinstance(B, BExp):
if not isinstance(B.operand2, Reg):
print(f'{offset:08x}: 手 {A} = {B}')
elif A.name == B.operand2.name:
print(f'{offset:08x}: {B.op} {A}, {B.operand1}')
else:
print(f'{offset:08x}: 手 {A} = {B}')
else:
print(f'{offset:08x}: mov {A}, {B}')
else:
assert isinstance(A, BExp) or isinstance(A, Reg)
A = LoadExp(A, opcode_b)
print(f'{offset:08x}: mov {A}, {B}')
case 4:
# store_u128
raise "not impl"
case 7:
# add
A = self.stack.pop()
B = self.stack.pop()
self.stack.append(BExp('add', A, B, opcode_b))
case 8:
# sub
A = self.stack.pop()
B = self.stack.pop()
self.stack.append(BExp('sub', A, B, opcode_b))
case 9:
raise "not impl"
case 10:
raise "not impl"
case 11:
A = self.stack.pop()
B = self.stack.pop()
if isinstance(A, Imm) and A.val == 2:
self.stack.append(BExp('shr', Imm(1, 8), B, opcode_b))
else:
raise "not impl"
case 12:
raise "not impl"
case 13:
A = self.stack.pop()
B = self.stack.pop()
self.stack.append(BExp('imul', A, B, opcode_b))
case 14:
A = self.stack.pop()
B = self.stack.pop()
self.stack.append(BExp('and', A, B, opcode_b))
case 15:
A = self.stack.pop()
B = self.stack.pop()
self.stack.append(BExp('or', A, B, opcode_b))
case 16:
A = self.stack.pop()
B = self.stack.pop()
self.stack.append(BExp('xor', A, B, opcode_b))
case 17:
A = self.stack.pop()
self.stack.append(SExp('not', A, opcode_b))
case 18:
A = self.stack.pop()
B = self.stack.pop()
assert len(self.stack) == 0
print(f'{offset:08x}: cmp {B}, {A}')
case 19 | 20:
assert opcode_b == 2
raise "not impl"
case 21:
cond = self.buffer[offset + 2]
if cond == 0:
cond = 'mp'
if cond == 5:
cond = 'ge'
operand = int.from_bytes(self.buffer[offset + 3:offset + 3 + 8], "little")
operand = ctypes.c_int64(operand).value
print(f'{offset:08x}: j{cond} label_{offset - operand:08x}')
opcode_size += 8 + 1
case 22:
# push_vm VM_ptr
self.stack.append(VMPtr())
case 23:
# add
A = self.stack.pop()
B = self.stack.pop()
if isinstance(B, VMPtr) and isinstance(A, Imm):
self.stack.append(B.get_property(A.val))
else:
self.stack.append(BExp('add', A, B, opcode_b))
case 24:
# mul
A = self.stack.pop()
B = self.stack.pop()
self.stack.append(BExp('imul', A, B, opcode_b))
case 25:
# sub
A = self.stack.pop()
B = self.stack.pop()
self.stack.append(BExp('sub', A, B, opcode_b))
case 26:
# do nothing
operand = int.from_bytes(self.buffer[offset + 2:offset + 2 + opcode_b], "little")
opcode_size += opcode_b
case 27:
operand1 = self.buffer[offset + 2]
shellcode = self.buffer[offset + 3: offset + 3 + operand1]
shellcode = '\r\n' + '\r\n'.join(str(ins) for ins in self.cs.disasm(shellcode, 0))
print(f'{offset:08x}: run_shellcode {shellcode}')
opcode_size = operand1 + 3
case 28:
print(f'{offset:08x}: return')
is_end = True
case _:
print(f'{offset:08x}: unk_opcode {opcode}')
is_end = True
return opcode_size, is_end
vm = VM(bytes.fromhex(code))
off = 0
while True:
size_of_op, is_end = vm.disassemble(off)
if is_end:
break
off += size_of_op
反編譯完 jmp 指令的目標地址對不上了,要修一下:
import re
a = re.compile('jmp label_([a-f0-9]+)')
b = re.compile('jge label_([a-f0-9]+)')
with open('./x86.txt', 'rb') as f:
content = f.read().decode('utf8')
label_arr = a.findall(content)
label_arr += b.findall(content)
label_arr = [int(_, 16) for _ in label_arr]
label_arr.sort()
idx = 0
lines = content.split('\r\n')
c = re.compile('([a-f0-9]+): (.+)')
with open('./x86_fix.txt', 'wb') as f:
for line in lines:
m = c.match(line)
if m is None:
f.write(b'\t' + line.encode() + b'\r\n')
continue
if idx < len(label_arr):
addr = int(m.group(1), 16)
if addr >= label_arr[idx]:
f.write(f'label_{label_arr[idx]:08x}:'.encode() + b'\r\n')
idx += 1
f.write(b'\t' + m.group(2).encode() + b'\r\n')
修復完之後,手動調整一下格式,nasm 編譯一下(要去掉 ptr,寫反編譯的時候按 masm 寫的,但是發現 masm 一堆逼事,轉而使用 nasm),編譯成功之後得到原始碼般的享受,發現是白盒AES
dump 出來常量表
from idaapi import *
def get_TyiBoxes(base_addr):
with open(f'TyiBoxes_{base_addr:x}', 'wb') as f:
size = 256 * 4 * 16 * 9
content = get_bytes(base_addr, size)
f.write(content)
def get_TBoxs(base_addr):
with open(f'TBoxs_{base_addr:x}', 'wb') as f:
size = 256 * 16 * 10
content = get_bytes(base_addr, size)
f.write(content)
def get_mixBijOut(base_addr):
with open(f'mixBijOut_{base_addr:x}', 'wb') as f:
size = 256 * 4 * 16 * 10
content = get_bytes(base_addr, size)
f.write(content)
get_TyiBoxes(0x140013000)
get_TBoxs(0x140011000 - 9 * 16 * 256)
get_mixBijOut(0x140037000)
DFA 攻擊
//
// Created by gaoyucan on 2024/11/2.
//
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#define ROTL8(x, shift) ((u8) ((x) << (shift)) | ((x) >> (8 - (shift))))
typedef unsigned char u8;
typedef unsigned int u32;
u8 DFA = 0;
u8 xorTable[9][96][16][16];
u32 TyiBoxes[9][16][256];
unsigned char TBoxes[10][16][256];
unsigned int mixBijOut[10][16][256];
void GetxorTable() {
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 96; j++) {
for (int x = 0; x < 16; x++) { //2的4次方=16
for (int y = 0; y < 16; y++) {
xorTable[i][j][x][y] = x ^ y;
}
}
}
}
}
void shiftRows(u8 state[16]) {
u8 out[16];
int shiftTab[16] = {0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, 1, 6, 11};
for (int i = 0; i < 16; i++) {
out[i] = state[shiftTab[i]];
}
memcpy(state, out, sizeof(out));
}
void AES_128_encrypt(unsigned char ciphertext[16], unsigned char plaintext[16]) {
u8 input[16] = {0};
u8 output[16] = {0};
memcpy(input, plaintext, 16);
u32 a, b, c, d, aa, bb, cc, dd;
for (int i = 0; i < 9; i++) {
// DFA Attack
if (DFA && i == 8) {
input[rand() % 16] = rand() % 256;
}
shiftRows(input);
for (int j = 0; j < 4; j++) {
a = TyiBoxes[i][4 * j + 0][input[4 * j + 0]];
b = TyiBoxes[i][4 * j + 1][input[4 * j + 1]];
c = TyiBoxes[i][4 * j + 2][input[4 * j + 2]];
d = TyiBoxes[i][4 * j + 3][input[4 * j + 3]];
aa = xorTable[i][24 * j + 0][(a >> 28) & 0xf][(b >> 28) & 0xf];
bb = xorTable[i][24 * j + 1][(c >> 28) & 0xf][(d >> 28) & 0xf];
cc = xorTable[i][24 * j + 2][(a >> 24) & 0xf][(b >> 24) & 0xf];
dd = xorTable[i][24 * j + 3][(c >> 24) & 0xf][(d >> 24) & 0xf];
input[4 * j + 0] = (xorTable[i][24 * j + 4][aa][bb] << 4) | xorTable[i][24 * j + 5][cc][dd];
aa = xorTable[i][24 * j + 6][(a >> 20) & 0xf][(b >> 20) & 0xf];
bb = xorTable[i][24 * j + 7][(c >> 20) & 0xf][(d >> 20) & 0xf];
cc = xorTable[i][24 * j + 8][(a >> 16) & 0xf][(b >> 16) & 0xf];
dd = xorTable[i][24 * j + 9][(c >> 16) & 0xf][(d >> 16) & 0xf];
input[4 * j + 1] = (xorTable[i][24 * j + 10][aa][bb] << 4) | xorTable[i][24 * j + 11][cc][dd];
aa = xorTable[i][24 * j + 12][(a >> 12) & 0xf][(b >> 12) & 0xf];
bb = xorTable[i][24 * j + 13][(c >> 12) & 0xf][(d >> 12) & 0xf];
cc = xorTable[i][24 * j + 14][(a >> 8) & 0xf][(b >> 8) & 0xf];
dd = xorTable[i][24 * j + 15][(c >> 8) & 0xf][(d >> 8) & 0xf];
input[4 * j + 2] = (xorTable[i][24 * j + 16][aa][bb] << 4) | xorTable[i][24 * j + 17][cc][dd];
aa = xorTable[i][24 * j + 18][(a >> 4) & 0xf][(b >> 4) & 0xf];
bb = xorTable[i][24 * j + 19][(c >> 4) & 0xf][(d >> 4) & 0xf];
cc = xorTable[i][24 * j + 20][(a >> 0) & 0xf][(b >> 0) & 0xf];
dd = xorTable[i][24 * j + 21][(c >> 0) & 0xf][(d >> 0) & 0xf];
input[4 * j + 3] = (xorTable[i][24 * j + 22][aa][bb] << 4) | xorTable[i][24 * j + 23][cc][dd];
a = mixBijOut[i][4 * j + 0][input[4 * j + 0]];
b = mixBijOut[i][4 * j + 1][input[4 * j + 1]];
c = mixBijOut[i][4 * j + 2][input[4 * j + 2]];
d = mixBijOut[i][4 * j + 3][input[4 * j + 3]];
aa = xorTable[i][24 * j + 0][(a >> 28) & 0xf][(b >> 28) & 0xf];
bb = xorTable[i][24 * j + 1][(c >> 28) & 0xf][(d >> 28) & 0xf];
cc = xorTable[i][24 * j + 2][(a >> 24) & 0xf][(b >> 24) & 0xf];
dd = xorTable[i][24 * j + 3][(c >> 24) & 0xf][(d >> 24) & 0xf];
input[4 * j + 0] = (xorTable[i][24 * j + 4][aa][bb] << 4) | xorTable[i][24 * j + 5][cc][dd];
aa = xorTable[i][24 * j + 6][(a >> 20) & 0xf][(b >> 20) & 0xf];
bb = xorTable[i][24 * j + 7][(c >> 20) & 0xf][(d >> 20) & 0xf];
cc = xorTable[i][24 * j + 8][(a >> 16) & 0xf][(b >> 16) & 0xf];
dd = xorTable[i][24 * j + 9][(c >> 16) & 0xf][(d >> 16) & 0xf];
input[4 * j + 1] = (xorTable[i][24 * j + 10][aa][bb] << 4) | xorTable[i][24 * j + 11][cc][dd];
aa = xorTable[i][24 * j + 12][(a >> 12) & 0xf][(b >> 12) & 0xf];
bb = xorTable[i][24 * j + 13][(c >> 12) & 0xf][(d >> 12) & 0xf];
cc = xorTable[i][24 * j + 14][(a >> 8) & 0xf][(b >> 8) & 0xf];
dd = xorTable[i][24 * j + 15][(c >> 8) & 0xf][(d >> 8) & 0xf];
input[4 * j + 2] = (xorTable[i][24 * j + 16][aa][bb] << 4) | xorTable[i][24 * j + 17][cc][dd];
aa = xorTable[i][24 * j + 18][(a >> 4) & 0xf][(b >> 4) & 0xf];
bb = xorTable[i][24 * j + 19][(c >> 4) & 0xf][(d >> 4) & 0xf];
cc = xorTable[i][24 * j + 20][(a >> 0) & 0xf][(b >> 0) & 0xf];
dd = xorTable[i][24 * j + 21][(c >> 0) & 0xf][(d >> 0) & 0xf];
input[4 * j + 3] = (xorTable[i][24 * j + 22][aa][bb] << 4) | xorTable[i][24 * j + 23][cc][dd];
}
}
shiftRows(input);
for (int j = 0; j < 16; j++) {
input[j] = TBoxes[9][j][input[j]];
}
for (int i = 0; i < 16; i++)
output[i] = input[i];
memcpy(ciphertext, output, 16);
}
void readBoxes() {
const char *PATH1 = "C:\\Users\\gaoyucan\\Desktop\\temp\\qwb\\ez_vm_a1774a181b73b74e6428c2c80ecf6b63\\TyiBoxes_140013000";
FILE *fp = fopen(PATH1, "rb");
fread(TyiBoxes, sizeof(TyiBoxes), 1, fp);
fclose(fp);
const char *PATH2 = "C:\\Users\\gaoyucan\\Desktop\\temp\\qwb\\ez_vm_a1774a181b73b74e6428c2c80ecf6b63\\TBoxs_140008000";
fp = fopen(PATH2, "rb");
fread(TBoxes, sizeof(TBoxes), 1, fp);
fclose(fp);
const char *PATH3 = "C:\\Users\\gaoyucan\\Desktop\\temp\\qwb\\ez_vm_a1774a181b73b74e6428c2c80ecf6b63\\mixBijOut_140037000";
fp = fopen(PATH3, "rb");
fread(mixBijOut, sizeof(mixBijOut), 1, fp);
fclose(fp);
}
int main() {
srand(time(0));
GetxorTable();
readBoxes();
for (int i = 0; i < 32; ++i) {
DFA = i != 0;
char flag[16] = {0};
memset(flag, 0x11, 16);
unsigned char ciphertext[16] = {0};
AES_128_encrypt(ciphertext, flag);
for (int i = 0; i < 16; ++i) {
printf("%02X", ciphertext[i]);
}
puts("");
}
return 0;
}
phoenixAES 恢復出來最後一輪金鑰
import phoenixAES
data = """0A4A2B9BF7BF167B758998590B7B415F
0A4A2B98F7BF177B75129859137B415F
0A4A029BF718167B848998590B7B4142
624A2B9BF7BF16CF7589B1590B4C415F
934A2B9BF7BF16F5758912590B3E415F
044A2B9BF7BF1642758908590B7D415F
A94A2B9BF7BF16ED75895C590B45415F
564A2B9BF7BF161B7589BB590BE5415F
0A4AED9BF722167BD18998590B7B4152
0A982B9B39BF167B758998250B7B4E5F
0A4A2BEEF7BFDB7B75259859BA7B415F
0A4A2BD8F7BFC77B759098596F7B415F
0A4A2BE0F7BF097B75A59859A77B415F
0A732B9B94BF167B758998450B7BDE5F
0AA22B9B77BF167B758998FB0B7BE85F
0A4A2BB3F7BF607B75799859D57B415F
0AE72B9B44BF167B758998B40B7B465F
0A4A2B50F7BFCB7B75469859BF7B415F
0A4ACA9BF794167BD28998590B7B41C7
0A4A229BF75C167B598998590B7B415C
0A4A2B17F7BFD27B75C198598B7B415F
0A932B9B67BF167B758998B00B7BAA5F
0A4AC19BF702167BC68998590B7B4181
E34A2B9BF7BF164E758929590B70415F
0A4AAB9BF710167B798998590B7B4158
0A572B9BFEBF167B758998E70B7BFC5F
0A4A2B80F7BFC07B750098595F7B415F
0A4AD09BF7BC167B2D8998590B7B41BE
0A4A2B63F7BF727B75D698598B7B415F
0A4A2B21F7BFE47B75BD9859117B415F
0A4A2B80F7BFC07B750098595F7B415F
504A2B9BF7BF16C7758900590BC5415F
"""
with open('crackfile', 'w') as fp:
fp.write(data)
phoenixAES.crack_file('crackfile', [], True, False, verbose=2)
Stark 恢復金鑰
Cyberchef 解一下
remem
VM 很簡單解出來 5 個方程,使用 SageMath 求解一下
from Crypto.Util.number import long_to_bytes
x1 = 862152290
x2 = 53932501 + 0x5E2F4391
x3 = 962670904
x4 = 859320929
x5 = 50524140 + 0x5E2F4391
print(long_to_bytes(x1) + long_to_bytes(x2) + long_to_bytes(x3) + long_to_bytes(x4) + long_to_bytes(x5))
# 2*x1*x1 + 13*x2 + 17*x1
# 5*x3*x3 + 88*x3 + 4294967291*x1*x3
# 5*x3*x3 + 232*x4 -4*x3*x4
# 8*x5 + 16*x5*x5 -0x23*x4*x4