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,方便后續分析。

控制流分析

這里可以簡單跟一下 JMPJEQ 指令來解析指令,但是所有可執行的指令過多,直接都打印出來不太可行。

容易猜到其實大部分指令都是沒有副作用的無意義指令,所以直接從最后輸出結果的 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 個字符中每個字符低位的 7Bit 拷貝到 global[0:0+175]

第二部分 0x1166 ~ 0x1658

注意這里有一個 MULDIV 的組合,重點關注一下:

篩選地址 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,以及一個 SUBJEQ 的組合,重點關注一下:

篩選一下這幾個地址:

結合之前打的 log 看一下:

第一個 READ_GLOBAL 寫入到 stack[41:76]

第二個 READ_GLOBAL 寫入到 stack[76:111]

SUBstack[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 操作,假設這里的數據以 7Bit 為一組,那么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
`
}

參考鏈接

https://github.com/leetonidas/revvm


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM