hxpCTF 2021 revvm Writeup
2021/12/20 23:22:36
本文主要是介绍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
会尝试用不同长度来解析指令,解析成功的全部添加到队列等待执行,最后大概类似于 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); //for (int i=0;i<N;i++) printf("%02x,",rbin[i]); 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); //printf("%x %x\n",i,ins_raw); 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; //printf("%x\n",imm_len); 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); //printf("Arg1Imm %llx@%llx,",op1_data,op1_len); }else{ for (int i=0;i<imm_len;i+=8){ //printf("(%02x),",trans[bread(bbin,ptr+i,(imm_len-i<8)?(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); //printf("Arg1Imm %llx@%llx,",op1_data,op1_len); } }else{ //printf("IGNORE,\n"); continue; } }else{ if (ins_type!=4&&ins_type!=15&&i==12){ op1_type=1; op1_len=imm_len; //printf("Arg1Stk @%llx,",op1_len); }else{ //printf("IGNORE,"); continue; } } if (ins_arg_count[ins_type]==2){ op2_type=1; op2_len=imm_len; //printf("Arg2Stk @%llx,",op2_len); } int flag=0; if (ins_type!=12){ // None 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 insert(N-1,ptr); if (ptr==0x115a){ insert(0x1674,0x115a); if (!reg[0x1674]){ reg[0x1674]=1; nodes.push(0x1674); printf("+ %x_%x %x\n",0x115a,0xc,0x1674); flag=1; } } if (ptr==0x70d){ insert(0x821,0x70d); if (!reg[0x821]){ reg[0x821]=1; nodes.push(0x821); printf("+ %x_%x %x\n",0x70d,0xc,0x821); flag=1; } } if (ptr==0x230){ insert(0x821,0x230); if (!reg[0x821]){ reg[0x821]=1; nodes.push(0x821); printf("+ %x_%x %x\n",0x230,0xc,0x821); flag=1; } } } 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"); } } //getchar(); } //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("revvm") 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()
打印结果如下图所示,这里标注了各个代码段的地址范围:
根据地址的连续性可以恢复大概的程序结构:
main(){ ... // 0x0 ~ 0x1a6 A() } A(){ ... // 0x1166 ~ 0x1658 B() ... // 0x1674 ~ 0x190d } B(){ ... // 0x719 ~ 0x115a }
这样就把整个程序分成了四个部分,接下来只需要各个击破就可以得到整体的逻辑了。
第一部分 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
矩阵的乘法运算,其中一个是由输入内容组成的矩阵,另外一个是程序中固定的矩阵。
随后 0x15f8
的地方有一个 WRITE_GLOBAL
操作:
一共执行了 25
次。
可以看出这里是将运算的结果拷贝到 global[184:184+175]
第三部分 0x719 ~ 0x115a
这里是最复杂的部分,我们可以先看一下进入和退出的地方。
在进入的地方首先将第四部分的起始地址压入栈中,然后执行一个 JMP
指令,类似于 CALL
的结构。
在退出的地方从栈中取出地址,然后执行 JMP
跳转到这个地址,类似于 RET
的结构。
随后有个 WRITE_GLOBAL
操作,将栈顶的值写入到 global[564:564+10]
。
第四部分 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
对应的地址即可。
这里根据指令可以写出对应的伪代码:
for i in range(5): a=global[(4-i)*7+536:(4-i)*7+536+35] b=global[(4-i)*35+184:(4-i)*35+184+35] if a == b: pass # do something
这里的 global + 184
正好对应于第二部分矩阵乘法的结果。
再回去看第三部分最后的 WRITE_GLOBAL
操作,假设这里的数据以 7
个 Bit
为一组,那么global + 564
正好是 global + 536
开始的第 5
项,设这个数值为 det
,则比较的过程如下图所示。
可以看出这里在检查矩阵是否为主对角线元素均为 det
的对角矩阵。
现在重点就是推测第三部分计算出来的 det
的含义。
之前的输入内容只有一个 5*5
的矩阵,合理推测这个 det
是通过输入矩阵计算出来的一个数值。
这里随便填入一个满秩矩阵,可以发现 det
的数值不为 0
。
继续尝试可以判断出第三部分计算的 det
是输入矩阵的行列式。
求解过程
通过求解下面的方程可以得到包含 flag
的矩阵 \(F\)
\(F*M=det\space F*I\space(mod\space 127)\)
hxp{Wh4t_4_dum6_D3s1gn!1}
参考链接
https://github.com/leetonidas/revvm
这篇关于hxpCTF 2021 revvm Writeup的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-26MATLAB 中 A(7)=[];什么意思?-icode9专业技术文章分享
- 2024-11-26UniApp 中如何实现使用输入法时保持页面列表不动的效果?-icode9专业技术文章分享
- 2024-11-26在 UniApp 中怎么实现输入法弹出时禁止页面向上滚动?-icode9专业技术文章分享
- 2024-11-26WebSocket是什么,怎么使用?-icode9专业技术文章分享
- 2024-11-26页面有多个ref 要动态传入怎么实现?-icode9专业技术文章分享
- 2024-11-26在 UniApp 中实现一个底部输入框的常见方法有哪些?-icode9专业技术文章分享
- 2024-11-26RocketMQ入门指南:搭建与使用全流程详解
- 2024-11-26RocketMQ入门教程:轻松搭建与使用指南
- 2024-11-26手写RocketMQ:从入门到实践的简单教程
- 2024-11-25【机器学习(二)】分类和回归任务-决策树(Decision Tree,DT)算法-Sentosa_DSML社区版