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

posted @ 2021-12-20 22:19  Byaidu  阅读(423)  评论(0编辑  收藏  举报