最心有余而力不足的一集,做完 vm 颈椎病犯了,第二天根本打。最后,加上学弟学妹打的,最后剩一个 Android 逆向没 AK,要是没有颈椎病这一说肯定 AK 了。感觉快退役了...
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 ctypesclass Operand(object):def __init__(self, size):self.size = sizeclass Imm(Operand):def __init__(self, val, size):super().__init__(size)self.val = valdef __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_arrclass 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.nameclass 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 = opself.operand1 = operand1def __str__(self):return f'{self.op}({self.operand1})'class BExp(Operand):def __init__(self, op, operand1, operand2, size):super().__init__(size)self.op = opself.operand1 = operand1self.operand2 = operand2def __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 = 0self.base = expreturnassert exp.op == 'add' and isinstance(exp.operand1, Imm);self.disp = exp.operand1.valif isinstance(exp.operand2, BExp):self.complex = 2exp_inner = exp.operand2assert exp_inner.op == 'add' and isinstance(exp_inner.operand2, Reg);self.base = exp_inner.operand2exp_offset = exp_inner.operand1assert isinstance(exp_offset, BExp) and exp_offset.op == 'imul';self.offset = exp_offset.operand2self.scale = exp_offset.operand1else:self.complex = 1self.base = exp.operand2def __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 = bufferself.cs = Cs(CS_ARCH_X86, CS_MODE_64)self.stack = []self.name_idx = 0def next_temp_name(self):name = f't_{self.name_idx}'self.name_idx += 1return namedef disassemble(self, offset):"""注意:push 和 pop 至少是 2bytes, 所有 u8 都必须复用 u16 的代码:param offset::return:"""opcode = self.buffer[offset]opcode_b = self.buffer[offset + 1]# 校验 opcode 和 opcode_bassert opcode <= 29 and opcode_b <= 8opcode_size = 2is_end = Falsematch opcode:case 0:# pushoperand = 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_bcase 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_u128raise "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_u128raise "not impl"case 7:# addA = self.stack.pop()B = self.stack.pop()self.stack.append(BExp('add', A, B, opcode_b))case 8:# subA = 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) == 0print(f'{offset:08x}: cmp {B}, {A}')case 19 | 20:assert opcode_b == 2raise "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).valueprint(f'{offset:08x}: j{cond} label_{offset - operand:08x}')opcode_size += 8 + 1case 22:# push_vm VM_ptrself.stack.append(VMPtr())case 23:# addA = 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:# mulA = self.stack.pop()B = self.stack.pop()self.stack.append(BExp('imul', A, B, opcode_b))case 25:# subA = self.stack.pop()B = self.stack.pop()self.stack.append(BExp('sub', A, B, opcode_b))case 26:# do nothingoperand = int.from_bytes(self.buffer[offset + 2:offset + 2 + opcode_b], "little")opcode_size += opcode_bcase 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 + 3case 28:print(f'{offset:08x}: return')is_end = Truecase _:print(f'{offset:08x}: unk_opcode {opcode}')is_end = Truereturn opcode_size, is_endvm = VM(bytes.fromhex(code))off = 0while True:size_of_op, is_end = vm.disassemble(off)if is_end:breakoff += size_of_op
反编译完 jmp 指令的目标地址对不上了,要修一下:
import rea = 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')continueif 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 += 1f.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 * 9content = 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 * 10content = 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 * 10content = 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次方=16for (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 Attackif (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 phoenixAESdata = """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_bytesx1 = 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