hxpCTF 2021 revvm Writeup
题目描述
题目给出了一个虚拟机程序,其中 revvm
是解释器程序,chall.bin
是包含虚拟机指令的文件。
执行下面的命令运行虚拟机:
./revvm chall.rbin
结构分析
虚拟机程序结构如下:
main(){
code, global = load_program()
threads = [new thread(0,new stack(0))]
for thread in threads{
instructions = get_instruction_list(code, thread.pc)
for instruction in instructions{
new_pc, new_stack = dispatch(instruction, thread.stack, global)
threads.append(new thread(new_pc, new_stack))
}
}
}
该虚拟机使用的是一个不定 Bit
长指令集,CPU
对同一个 PC
会调用 get_instruction_list
尝试用不同长度来解析指令,解析成功的全部添加到指令队列 instructions
中等待执行,执行完成后将下一条指令的地址添加到线程队列 threads
中等待调度,最后大概类似于 BFS
的效果。
此外程序中的所有数据也是不定 Bit
长。
虚拟机一共有 16
条指令:
ADD
SUB
MUL
DIV
PUSH
POP
DUP
READ_STACK
WRITE_STACK
READ_GLOBAL
WRITE_GLOBAL
JEQ
JMP
SYSCALL
SET_DELAY
SET_LIMIT
指令插桩
这里使用 IDAPython
对虚拟机进行插桩,可以打印每条指令的地址、类型、操作数、运算结果以及栈指针位置:
from __future__ import print_function
import ida_dbg
import ida_ida
import ida_lines
from idc import *
code_type_str=["add","sub","mul","div","push","pop","dup","readstk","writestk","readglobal","writeglobal","jeq","jmp","syscall","setreg1","setreg2"]
code_arg_cnt=[2, 2, 2, 2, 1, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 1]
class MyDbgHook(ida_dbg.DBG_Hooks):
""" Own debug hook class that implementd the callback functions """
def __init__(self):
ida_dbg.DBG_Hooks.__init__(self) # important
def get_bitvec_len(self,stack):
next = read_dbg_qword(stack + 24)
size = read_dbg_qword(stack + 32)
if next:
return size + self.get_bitvec_len(next)
else:
return size
def get_arg_str(self,idx):
return "%s@%d:0x%x"%(self.arg_type[idx],self.arg_len[idx],self.arg_value[idx])
def dbg_bpt(self, tid, ea):
bpt_cnt = get_bpt_qty()
bpt_lst = [get_bpt_ea(i) for i in range(bpt_cnt)]
bpt_idx = bpt_lst.index(ea)
i = iter(range(bpt_cnt))
#print(bpt_idx)
if next(i) == bpt_idx: # ArgImm Getter
self.arg_type.append("ArgImm")
self.arg_len.append(read_dbg_qword(get_reg_value("rdi") + 8))
if next(i) == bpt_idx: # ArgStk Getter
self.arg_type.append("ArgStk")
self.arg_len.append(read_dbg_qword(get_reg_value("rdi") + 8))
if next(i) == bpt_idx: # Dispatch Begin
self.arg_type = []
self.arg_len = []
self.arg_value = []
self.code_from = get_reg_value("rdx")
self.stack = get_reg_value("r8")
self.stack_len = self.get_bitvec_len(self.stack)
if self.stack_len == None:
self.stack_len = -1
if next(i) == bpt_idx: # 1st Arg Value
self.arg_value.append(get_reg_value("rax"))
if next(i) == bpt_idx: # Next PC
self.code_to = get_reg_value("r15")
if next(i) == bpt_idx: # 2nd Arg Value
self.arg_value.append(get_reg_value("rax"))
if next(i) == bpt_idx: # Instruction Type
self.code_type = get_reg_value("rax")
if next(i) == bpt_idx: # ADD Result
self.result = get_reg_value("rsi")
if next(i) == bpt_idx: # SUB Result
self.result = get_reg_value("rsi")
if next(i) == bpt_idx: # MUL Result
self.result = get_reg_value("rsi")
if next(i) == bpt_idx: # DIV Result1
self.result = get_reg_value("rsi")
if next(i) == bpt_idx: # DIV Result2
self.result2 = get_reg_value("rsi")
if next(i) == bpt_idx: # Dispatch End
self.code_len = self.code_to-self.code_from
self.code_addr="%x_%x"%(self.code_from,self.code_len)
prefix = "%x_%x\t\t%d\t%s"%(self.code_from,self.code_len,self.stack_len,code_type_str[self.code_type])
if self.code_type < 3:
print(prefix,self.get_arg_str(0),self.get_arg_str(1),"Ret:0x%x"%self.result)
if self.code_type == 3:
print(prefix,self.get_arg_str(0),self.get_arg_str(1),"Div:0x%x"%self.result,"Mod:0x%x"%self.result2)
if self.code_type > 3:
if code_arg_cnt[self.code_type] == 2:
print(prefix,self.get_arg_str(0),self.get_arg_str(1))
else:
print(prefix,self.get_arg_str(0))
ida_dbg.continue_process()
return 0
try:
if debughook:
print("Removing previous hook ...")
debughook.unhook()
except:
pass
debughook = MyDbgHook()
debughook.hook()
这里输入 abcdefghijklmnopqrstuvwxy
打一个 log
,方便后续分析。
控制流分析
这里可以简单跟一下 JMP
和 JEQ
指令来解析指令,但是所有可执行的指令过多,直接都打印出来不太可行。
容易猜到其实大部分指令都是没有副作用的无意义指令,所以直接从最后输出结果的 0x190d
地址沿着调用链反向剪枝即可:
#include <cstdio>
#include <queue>
#include <iostream>
#include <cstring>
using namespace std;
typedef unsigned int dword;
typedef unsigned long long qword;
typedef unsigned short word;
typedef unsigned char byte;
const int N=0x340*8;
queue<int> nodes;
byte rbin[N],bbin[N],tmp[N],reg[N];
byte ins_arg_count[] = {2, 2, 2, 2, 1, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 1};
const char *ins_type_str[] = {
"add",
"sub",
"mul",
"div",
"push",
"pop",
"dup",
"readstk",
"writestk",
"readglobal",
"writeglobal",
"jeq",
"jmp",
"syscall",
"setreg1",
"setreg2",
};
byte trans[] =
{
0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90,
0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, 0x08, 0x88, 0x48, 0xC8,
0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8,
0x78, 0xF8, 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4,
0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, 0x0C, 0x8C,
0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC,
0x3C, 0xBC, 0x7C, 0xFC, 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2,
0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A,
0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, 0x06, 0x86, 0x46, 0xC6,
0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6,
0x76, 0xF6, 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE,
0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, 0x01, 0x81,
0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1,
0x31, 0xB1, 0x71, 0xF1, 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9,
0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95,
0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, 0x0D, 0x8D, 0x4D, 0xCD,
0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD,
0x7D, 0xFD, 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3,
0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, 0x0B, 0x8B,
0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB,
0x3B, 0xBB, 0x7B, 0xFB, 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7,
0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F,
0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
};
void bwrite(byte *buf,int index,int width,qword data){
for (int i=0;i<width;i++){
buf[index+i]=(data>>i)&1;
}
}
qword bread(byte *buf,int index,int width){
qword ret=0;
for (int i=0;i<width;i++){
ret|=((1LL*buf[index+i])<<i);
}
return ret;
}
int head[N],vis[N],sz=0;
struct E{
int next,to;
}e[N*10];
void insert(int a,int b){
sz++;
e[sz].next=head[a];
head[a]=sz;
e[sz].to=b;
}
void dfs(int x){
vis[x]=1;
for (int i=head[x];i;i=e[i].next){
int v=e[i].to;
if (!vis[v]) dfs(v);
}
}
int xx[]={0x0,0x3a,0x4c,0x59,0x6a,0x7e,0x90,0xa1,0xb2,0xc6,0xd2,0xe3,0xf1,0x102,0x111,0x122,0x133,0x146,0x166,0x177,0x18a,0x1a6,0x1e1,0x1f4,0x207,0x21a,0x230,0x23c,0x24c,0x25f,0x272,0x286,0x299,0x2ad,0x2be,0x2cd,0x2eb,0x2fb,0x307,0x31a,0x32d,0x344,0x355,0x364,0x382,0x391,0x3a4,0x3b0,0x3c3,0x3d6,0x3f4,0x403,0x416,0x429,0x43c,0x44f,0x462,0x478,0x48b,0x49e,0x4af,0x4cd,0x4dd,0x4f0,0x503,0x512,0x528,0x538,0x54e,0x561,0x574,0x580,0x593,0x59f,0x5b2,0x5cc,0x5f4,0x61c,0x62c,0x643,0x654,0x664,0x678,0x68d,0x6a0,0x6b3,0x6bf,0x6d6,0x6e9,0x6fc,0x70d,0x719,0x72d,0x73c,0x74c,0x761,0x771,0x785,0x791,0x7a4,0x7b3,0x7c2,0x7d2,0x7e9,0x805,0x821,0x837,0x84b,0x85b,0x867,0x87b,0x88f,0x89b,0x8ae,0x8ba,0x8d4,0x8e4,0x8f7,0x907,0x917,0x927,0x93b,0x94f,0x95f,0x9ad,0x9bf,0x9cf,0x9df,0x9f2,0xa02,0xa12,0xa29,0xd70,0xd7f,0xd99,0xda9,0xdc6,0xde0,0xdf4,0xe04,0xe21,0xe33,0xe3f,0xe6e,0xe80,0xe8c,0xebb,0xec7,0xed3,0xee6,0xefe,0xf16,0xf25,0xf35,0xf42,0xf52,0xf63,0xf74,0xf8c,0xf9b,0xfac,0xfbe,0xfd5,0xfee,0x1005,0x1018,0x102b,0x103b,0x104e,0x105a,0x106d,0x1080,0x108c,0x10a6,0x10b6,0x10c9,0x10d8,0x10e7,0x10f7,0x110a,0x111d,0x1136,0x1147,0x115a,0x1166,0x1177,0x1188,0x1197,0x11a6,0x11b5,0x11c4,0x11fd,0x120f,0x129c,0x12ac,0x12bd,0x12d2,0x12ef,0x12fb,0x130b,0x1321,0x1332,0x1347,0x1364,0x137b,0x1387,0x139b,0x13ab,0x13b8,0x13cb,0x13de,0x13ea,0x1404,0x1414,0x1427,0x143b,0x1447,0x145b,0x146b,0x147b,0x1493,0x14a2,0x14b3,0x14c1,0x14cf,0x14e0,0x14f1,0x150a,0x1519,0x152c,0x153d,0x154d,0x1562,0x1571,0x157d,0x158e,0x15a4,0x15b4,0x15c0,0x15cf,0x15e2,0x15f8,0x1604,0x1615,0x1626,0x163c,0x1658,0x1674,0x168a,0x169e,0x16b4,0x16d9,0x16ef,0x16fb,0x170c,0x1722,0x172e,0x1748,0x175e,0x1774,0x1785,0x1797,0x17ad,0x17b9,0x17c5,0x17f4,0x1804,0x1814,0x1823,0x1832,0x1841,0x1852,0x186c,0x187c,0x1890,0x18af,0x18cb,0x18ea,0x18fa,0x190d};
int res[N];
int main(){
for (int i=0;i<sizeof(xx)/sizeof(int);i++) res[xx[i]]=1;
FILE *f = fopen("revvm/chall.rbin","rb");
fread(rbin,1,0x400,f);
int code=*(unsigned int *)rbin+8;
for (int i=0;i<N;i++) bbin[i]=(rbin[i/8+code]>>(i%8))&1;
int ptr=0;
nodes.push(0);
reg[0]=1;
while (!nodes.empty()){
ptr=nodes.front();
nodes.pop();
for (int i=12;i<=76;i++){
word ins_raw=bread(bbin,ptr+i-12,12);
byte ins_type=ins_raw>>8;
qword imm_len=(ins_raw&0x3f)+1;
qword op1_type,op1_len,op1_data;
qword op2_type,op2_len,op2_data;
if (ptr==0x11c4&&i==0x14) continue; // hack
if ((ins_raw&0x80)==0){
if (imm_len+12==i){
if (ins_raw&0x40){
op1_type=0;
op1_len=imm_len;
op1_data=bread(bbin,ptr,imm_len);
}else{
for (int i=0;i<imm_len;i+=8){
bwrite(tmp,(imm_len+7)/8*8-i-8,8,trans[bread(bbin,ptr+i,(imm_len-i<8)?(imm_len-i):8)]);
}
op1_type=0;
op1_len=imm_len;
op1_data=bread(tmp,(64-imm_len)%8,imm_len);
}
}else{
continue;
}
}else{
if (ins_type!=4&&ins_type!=15&&i==12){
op1_type=1;
op1_len=imm_len;
}else{
continue;
}
}
if (ins_arg_count[ins_type]==2){
op2_type=1;
op2_len=imm_len;
}
int flag=0;
if (ins_type!=12){ // Not JMP
if (ptr+i<N){
insert(ptr+i,ptr);
if (res[ptr]&&res[ptr+i]) {
printf("+ %x_%x %x\n",ptr,i,ptr+i);
flag=1;
}
}
if (ptr+i<N&&!reg[ptr+i]) {
reg[ptr+i]=1;
nodes.push(ptr+i);
}
}
if (ins_type==11&&op1_type==0){ // JEQ ArgImm
if (ptr+i+op1_data<N){
insert(ptr+i+op1_data,ptr);
if (res[ptr]&&res[ptr+i+op1_data]){
printf("+ %x_%x %x\n",ptr,i,ptr+i+op1_data);
flag=1;
}
}
if (ptr+i+op1_data<N&&!reg[ptr+i+op1_data]){
reg[ptr+i+op1_data]=1;
nodes.push(ptr+i+op1_data);
}
}
if (ins_type==12&&op1_type==0){ // JMP ArgImm
if (op1_data<N){
insert(op1_data,ptr);
if (res[ptr]&&res[op1_data]){
printf("+ %x_%x %x\n",ptr,i,op1_data);
flag=1;
}
}
if (op1_data<N&&!reg[op1_data]){
reg[op1_data]=1;
nodes.push(op1_data);
}
}
if ((ins_type==11||ins_type==12)&&op1_type==1){ // JMP ArgStk
//hack
if (ptr==0x115a){
insert(0x1674,0x115a);
printf("+ %x_%x %x\n",0x115a,0xc,0x1674);
flag=1;
if (!reg[0x1674]){
reg[0x1674]=1;
nodes.push(0x1674);
}
}
if (ptr==0x70d){
insert(0x821,0x70d);
printf("+ %x_%x %x\n",0x70d,0xc,0x821);
flag=1;
if (!reg[0x821]){
reg[0x821]=1;
nodes.push(0x821);
}
}
if (ptr==0x230){
insert(0x821,0x230);
printf("+ %x_%x %x\n",0x230,0xc,0x821);
flag=1;
if (!reg[0x821]){
reg[0x821]=1;
nodes.push(0x821);
}
}
}
if (flag) {
printf("+ %x %x_%x\n",ptr,ptr,i);
printf("- %x_%x %s ",ptr,i,ins_type_str[ins_type]);
if (op1_type==0)
printf("Arg1Imm@%llx:%llx, ",op1_len,op1_data);
else
printf("Arg1Stk@%llx, ",op1_len);
if (ins_arg_count[ins_type]==2){
if (op2_type==0)
printf("Arg2Imm@%llx:%llx, ",op2_len,op2_data);
else
printf("Arg2Stk@%llx, ",op2_len);
}
printf("\n");
}
}
}
//dfs(0x190d);
//for (int i=0;i<N;i++) if (reg[i]) printf("%x,",i);
//for (int i=0;i<N;i++) if (vis[i]) printf("%x,",i);
}
同理可以将 log
中的无效指令也过滤掉:
addr=[0x0,0x3a,0x4c,0x59,0x6a,0x7e,0x90,0xa1,0xb2,0xc6,0xd2,0xe3,0xf1,0x102,0x111,0x122,0x133,0x146,0x166,0x177,0x18a,0x1a6,0x1e1,0x1f4,0x207,0x21a,0x230,0x23c,0x24c,0x25f,0x272,0x286,0x299,0x2ad,0x2be,0x2cd,0x2eb,0x2fb,0x307,0x31a,0x32d,0x344,0x355,0x364,0x382,0x391,0x3a4,0x3b0,0x3c3,0x3d6,0x3f4,0x403,0x416,0x429,0x43c,0x44f,0x462,0x478,0x48b,0x49e,0x4af,0x4cd,0x4dd,0x4f0,0x503,0x512,0x528,0x538,0x54e,0x561,0x574,0x580,0x593,0x59f,0x5b2,0x5cc,0x5f4,0x61c,0x62c,0x643,0x654,0x664,0x678,0x68d,0x6a0,0x6b3,0x6bf,0x6d6,0x6e9,0x6fc,0x70d,0x719,0x72d,0x73c,0x74c,0x761,0x771,0x785,0x791,0x7a4,0x7b3,0x7c2,0x7d2,0x7e9,0x805,0x821,0x837,0x84b,0x85b,0x867,0x87b,0x88f,0x89b,0x8ae,0x8ba,0x8d4,0x8e4,0x8f7,0x907,0x917,0x927,0x93b,0x94f,0x95f,0x9ad,0x9bf,0x9cf,0x9df,0x9f2,0xa02,0xa12,0xa29,0xd70,0xd7f,0xd99,0xda9,0xdc6,0xde0,0xdf4,0xe04,0xe21,0xe33,0xe3f,0xe6e,0xe80,0xe8c,0xebb,0xec7,0xed3,0xee6,0xefe,0xf16,0xf25,0xf35,0xf42,0xf52,0xf63,0xf74,0xf8c,0xf9b,0xfac,0xfbe,0xfd5,0xfee,0x1005,0x1018,0x102b,0x103b,0x104e,0x105a,0x106d,0x1080,0x108c,0x10a6,0x10b6,0x10c9,0x10d8,0x10e7,0x10f7,0x110a,0x111d,0x1136,0x1147,0x115a,0x1166,0x1177,0x1188,0x1197,0x11a6,0x11b5,0x11c4,0x11fd,0x120f,0x129c,0x12ac,0x12bd,0x12d2,0x12ef,0x12fb,0x130b,0x1321,0x1332,0x1347,0x1364,0x137b,0x1387,0x139b,0x13ab,0x13b8,0x13cb,0x13de,0x13ea,0x1404,0x1414,0x1427,0x143b,0x1447,0x145b,0x146b,0x147b,0x1493,0x14a2,0x14b3,0x14c1,0x14cf,0x14e0,0x14f1,0x150a,0x1519,0x152c,0x153d,0x154d,0x1562,0x1571,0x157d,0x158e,0x15a4,0x15b4,0x15c0,0x15cf,0x15e2,0x15f8,0x1604,0x1615,0x1626,0x163c,0x1658,0x1674,0x168a,0x169e,0x16b4,0x16d9,0x16ef,0x16fb,0x170c,0x1722,0x172e,0x1748,0x175e,0x1774,0x1785,0x1797,0x17ad,0x17b9,0x17c5,0x17f4,0x1804,0x1814,0x1823,0x1832,0x1841,0x1852,0x186c,0x187c,0x1890,0x18af,0x18cb,0x18ea,0x18fa,0x190d]
with open("newlog.txt","r") as f:
for i in f.readlines():
l=i.strip().split('_')
if int(l[0],16) in addr:
print(i,end="")
然后可以使用 Graphviz
把整个图给打印出来:
from graphviz import Digraph
g=Digraph(name="revvm",graph_attr={"bgcolor":"bisque"})
with open("dump.txt","r",encoding="utf-16") as f:
for i in f.readlines():
l=i.strip().split(maxsplit=2)
print(l)
if l[0]=="+":
print(l[1],l[2])
g.edge(l[1],l[2])
else:
g.node(l[1],l[1]+": "+l[2])
pass
g.view()
打印结果如下图所示,这里标注了各个代码段的地址范围:
这样就把整个程序分成了四个部分,接下来只需要各个击破就可以得到整体的逻辑了。
第一部分 0x0 ~ 0x1a6
注意这里有一个 WRITE_GLOBAL
操作,重点关注一下:
用 fzf -tac --no_sort
筛选地址 0xc6
:
一共执行了 25
次,并且栈指针递减。
可以看出来第一部分的作用就是将输入的 25
个字符中每个字符低位的 7
个 Bit
拷贝到 global[0:0+175]
。
第二部分 0x1166 ~ 0x1658
注意这里有一个 MUL
与 DIV
的组合,重点关注一下:
筛选地址 0x13de
:
一共执行了 125
次。
可以看出这里是两个 5*5
矩阵的乘法运算,其中一个是由输入内容组成的矩阵 \(F\),另外一个是程序中固定的矩阵 \(M\)。
随后 0x15f8
的地方有一个 WRITE_GLOBAL
操作:
一共执行了 25
次。
可以看出这里是将运算的结果 \(F*M\) 拷贝到 global[184:184+175]
第三部分 0x1e1 ~ 0x115a
这里是最复杂的部分,我们可以先看一下进入和退出的地方。
在进入的地方首先将第四部分的起始地址 0x1674
压入栈中,然后执行一个 JMP
指令,类似于 CALL
的结构:
在退出的地方从栈中取出地址,然后执行 JMP
跳转到这个地址,类似于 RET
的结构:
随后有个 WRITE_GLOBAL
操作,将栈顶的值写入到 global[564:564+10]
。
结合地址的连续性可以恢复大概的程序结构:
main(){
... // 0x0 ~ 0x1a6
A()
}
B(){
... // 0x1e1 ~ 0x115a
}
A(){
... // 0x1166 ~ 0x1658
global[564:564+10] = B()
... // 0x1674 ~ 0x190d
}
第四部分 0x1674 ~ 0x190d
注意这里有两个 READ_GLOBAL
,以及一个 SUB
与 JEQ
的组合,重点关注一下:
筛选一下这几个地址:
结合之前打的 log
看一下:
第一个 READ_GLOBAL
写入到 stack[41:76]
。
第二个 READ_GLOBAL
写入到 stack[76:111]
。
SUB
将 stack[41:76]
和 stack[76:111]
的内容相减。
随后 JEQ
根据 SUB
的结果进行跳转。
接下来分析两次 READ_GLOBAL
对应的地址即可。
这里结合流图和 log
可以写出对应的伪代码:
is_flag = 0
for i in range(5):
a = global[(4-i)*7+536:(4-i)*7+536+35]
b = global[i*35+184:i*35+184+35]
if a != b:
is_flag = 0xc
if is_flag == 0:
print(":)")
else:
print(":(")
这里的 global + 184
正好对应于第二部分矩阵乘法的结果 \(F*M\)。
再回去看第三部分最后的 WRITE_GLOBAL
操作,假设这里的数据以 7
个 Bit
为一组,那么global + 564
正好是 global + 536
开始的第 5
项,设这个数值为 det
,则比较的过程如下图所示:
可以看出这里在检查矩阵 \(F*M\) 是否为主对角线元素均为 det
的对角矩阵。
现在重点就是推测第三部分计算出来的 det
的含义。
之前的输入内容只有一个 5*5
的矩阵 \(F\),合理推测 det
是通过这个矩阵计算出来的一个数值。
这里随便填入一个满秩矩阵,可以发现 det
的数值不为 0
。
继续尝试可以判断出第三部分计算的 det
是输入矩阵 \(F\) 的行列式 \(det\space F\)。
求解过程
通过求解下面的方程可以得到包含 flag
的矩阵 \(F\):
\(F*M=det\space F*I\space(mod\space 127)\)
hxp{Wh4t_4_dum6_D3s1gn!1}
后记
如果用可视化工具将流图中的节点和 log
结合起来看,分析过程应该会更快一点。
用油猴糊了个原型出来:
脚本代码:
// ==UserScript==
// @name Trace Navigator
// @namespace Violentmonkey Scripts
// @match http://viz-js.com/
// @grant none
// @version 1.0
// @author Byaidu
// @description 2021/12/23 上午2:32:17
// ==/UserScript==
function query(addr){
inscount = 0
document.getElementById("editor").innerHTML = ""
window.trace_log.split("\n").forEach((i)=>{if (i.startsWith(addr)) {inscount += 1;document.getElementById("editor").innerHTML+=i+"<br>"}})
document.getElementById("editor").innerHTML += "Count: " + inscount
}
function trace_run(){
nodes = Array.from(document.getElementsByClassName("node"))
nodes.forEach((i)=>{i.addEventListener("click",()=>{query(i.getElementsByTagName("title")[0].innerHTML.split("\t")[0])})})
query(nodes[0].getElementsByTagName("title")[0].innerHTML.split("\t")[0])
document.getElementById("editor").style="padding: 10px;overflow-y: scroll;"
document.getElementById("editor").classList.remove("ace-tm")
document.getElementById("header").style="display: none;"
document.getElementById("options").style="display: none;"
document.getElementById("app").style="background-color: bisque;"
}
svgPanZoom_ = window.svgPanZoom
svgcount = 0
window.svgPanZoom = (a,b)=>{
svgcount += 1
if (svgcount > 1) trace_run()
b.minZoom = 0.01
b.maxZoom = 100
console.log(a,b)
return svgPanZoom_(a,b)
}
trace_data()
params = {
src: window.trace_graph,
options: {
engine: "dot",
format: "svg"
}
}
worker.postMessage(params);
function trace_data(){
window.trace_graph = `
digraph revvm {
graph [bgcolor=bisque]
"0_3a" -> "3a"
0 -> "0_3a"
"0_3a" [label="0_3a: push Arg1Imm@2e:80e9e5952e8,"]
"3a_12" -> "4c"
"3a" -> "3a_12"
"3a_12" [label="3a_12: syscall Arg1Imm@6:0,"]
"4c_d" -> 59
"4c" -> "4c_d"
"4c_d" [label="4c_d: syscall Arg1Imm@1:1,"]
}
`
window.trace_log = `
0_3a 0 push ArgImm@46:0x80e9e5952e8
3a_12 46 syscall ArgImm@6:0x0
3a_1b 46 add ArgImm@15:0x141 ArgStk@15:0x52e8 Ret:0x5429
3a_2d 46 add ArgImm@33:0x505c02c ArgStk@33:0x9e5952e8 Ret:0xa35f1314
4c_d 0 syscall ArgImm@1:0x1
`
}