周末做的一个很有趣的逆向,但是没在比赛结束之前做出来。第一次做VM的逆向,也算是打消了对未知的恐惧。感觉还是挺有意思的。
简单逆向 题目是shiftycode,VM的源码也十分简单。主要逻辑全部在main函数中。
文件首先接受一个bytecode,这个字节码题目也给了,其中就包含了检验flag的逻辑。
输出 我们直接运行
程序会给我们输出很多字节码。逻辑如下。这也就方便我们观察字节码是什么。
接着就到了循环。其实很好逆向,就是一个循环,类似switch-case语句,不断读取字节码,识别字节码的指令,然后根据指令做相应的事情。
这里可以看到shiftycode的含义了。可以看到在每一次判断oprand之后,都会对当前的oprand自增。这样相当于下一次调用相同指令的code不一样。(如上面的++add)。
同时,在add中,如果第二个操作数是1和2,对应的行为也有差别
其余的逻辑很简单。这里举一个例子说明。其他的结构大同小异。
例如字节码
04 02 21 05
就可以被翻译为 all[0x21] += all[5]。注意第二个字节这里如果是01,就表示第四个字节是一个立即数,如果是02就表示操作数是我们输入的下表为5的位置。其余的还有一些跳转指令、比较指令、加减乘除都有。
整体逻辑 经过对bytecode的分析,可以大致总结出本题的逻辑。字节码和对应的汇编指令就写在下面了。注意这道题有一些跳转,一开始并不知道是否需要跳转,但是可以看到这些跳转大都是连续的(也就是说一个跳转的下一跳,还是一个跳转。这样最终将越过所有的检验逻辑)因此我最后尝试所有的跳转都无法进行,可以得到结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 // 接受用户输入[0,0x25]个字符 06 00 07 01 08 02 09 03 0a 04 0b 05 0c 06 0d 07 0e 08 0f 09 10 0a 11 0b 12 0c 13 0d 14 0e 15 0f 16 10 17 11 18 12 19 13 1a 14 1b 15 1c 16 1d 17 1e 18 1f 19 20 1a 21 ff 22 ff 23 ff 24 ff 25 ff 26 ff 27 ff 28 ff 29 ff 2a ff 2b ff 2c ff 2d ff // all即为用户输入所储存的地方 // cmd是当前指令指针位置 07 01 00 66 ;if all[0] !=’f' 05 01 04 ; cmd+4 08 03 01 6d ;if all[1] >='k' 06 01 04 ; cmd+4 09 04 01 6b ;if all[1] <='m' 07 01 18 ; cmd+0x18 // x 00 02 1e 02 ; all[0x1e] +=all[2] 01 02 1f 03 ; all[0x1f] +=all[3] 02 02 20 03 ; all[0x20] +=all[3] 03 02 1f 1e ; all[0x1f] +=all[0x1e] 01 02 20 1e ; all[0x20] -=all[0x1e] 0a 01 1f c8 ; if all[0x1f] !=0xc8 08 01 08 ; cmd+=8 02 02 1f 20 ; all[0x1f] -=all[0x20] 0b 01 1f c2 ; if all[0x1f]!=0xc2 09 01 04 ; cmd +=4 0c 01 04 7b ; if all[0x4] !='{' 0a 01 04 ; cmd+=4 0d 01 1a 7d ; if all[0x1a] !='}' 0b 01 0c ; cmd+=0xc # 04 02 21 05 ; all[0x21] += all[5] 05 02 21 07 ; all[0x21] += all[7] 0e 01 21 cc ; if all[0x21] !=0xcc 0c 01 10 ; cmd +=0x10 06 02 21 06 ; all[0x21] += all[6] 07 02 22 21 ; all[0x22] += all[0x21] 03 01 22 64 ; all[0x22] -=0x64 0f 01 22 e1 ; if all[0x22] !=0xe1 0d 01 08 ; cmd +=0x8 04 02 21 05 ; all[0x21] -=all[0x5] 10 01 21 d8 ; if all[0x21] !=0xd8 0e 01 0c ;cmd +=0xc 08 02 23 09 ; all[0x23] +=all[0x9] 09 01 23 0b ; all[0x23] += 0xb 11 02 08 23 ; if all[0x8] != all[0x23] 0f 01 0c ; cmd +=0xc 0a 02 24 0a ; all[0x24] +=all[0xa] 0b 01 24 42 ; all[0x24] +=0x42 12 02 08 24 ; if all[0x8] != all[0x24] 10 01 10 ; cmd +=0x10 0c 02 25 0c ; all[0x25] +=all[0xc] 0d 02 25 0b ; all[0x25] +=all[0xb] 05 02 25 08 ; all[0x25] -=all[0x8] 13 01 25 67 ; if all[0x25] !=0x67 11 01 18 ;cmd +=0x18 0e 02 26 0c ; all[0x26] +=all[0xc] 0f 02 26 0a ; all[0x26] +=all[0xa] 06 02 26 09 ; all[0x26] -=all[0x9] 10 02 27 0b ; all[0x27] +=all[0xb] 07 01 27 29 ; all[0x27] -=0x29 14 02 26 27 ; if all[0x26] !=all[0x27] 12 01 f3 ; cmd +=0xf3 11 01 27 29 ; all[0x27] +=0x29 08 02 27 0a ; all[0x27] -=all[0xa] 15 01 27 35 ; if all[0x27] !=0x35 13 01 e4 ; cmd +=0xe4 16 02 0e 13 ; if all[0xe] != all[0x13] 14 01 dd ; cmd +=0xdd 17 02 0f 12 ; if all[0xf] != all[0x12] 15 01 d6 ; cmd +=0xd6 18 02 10 07 ; if all[0x10] != all[0x7] 16 01 cf ; cmd +=0xcf 12 02 28 0d ; all[0x28] +=all[0xd] 09 02 28 12 ; all[0x28] -=all[0x12] 0a 02 28 11 ; all[0x28] -=all[0x11] 19 01 28 00 ; if all[0x28] != 0 17 01 bc ; cmd +=0xbc 1a 05 11 12 ; if all[0x11] >= all[0x12] 18 01 b5 ; cmd +=0xb5 1b 06 11 13 ; if all[0x11] <=all[0x13] 19 01 ae ; cmd +=0xae 13 02 28 12 ; all[0x28] +=all[0x12] 0b 02 28 13 ; all[0x28] -=all[0x13] 1c 01 28 02 ; if all[0x28] !=0x2 1a 01 9f ; cmd+=0x9f 14 02 29 0d ; all[0x29] +=all[0xd] 0c 01 29 36 ; all[0x29] -=0x36 1d 02 29 0e ; if all[0x29] !=all[0x0e] 1b 01 90 ; cmd +=0x90 15 02 2a 13 ; all[0x2a] += all[0x13] 02 01 2a 02 ; all[0x2a] *= 0x2 1e 02 2a 17 ; if all[0x2a] != all[0x17] 1c 01 81 ; cmd +=0x81 1f 02 14 10 ; if all[0x14] != all[0x10] 1d 01 7a ; cmd +=0x7a 20 02 16 0a ; if all[0x16] != all[0x0a] 1e 01 73 ; cmd +=0x73 16 02 2b 15 ; all[0x2b] +=all[0x15] 03 01 2a 02 ; all[0x2a] *=0x2 ==> no use! 03 01 2b 02 ; all[0x2b] /=0x2 0d 01 2b 06 ; all[0x2b] -=0x6 21 02 2b 16 ; if all[0x2b] != all[0x16] 1f 01 5c ; cmd +=0x5c 17 02 2c 19 ; all[0x2c] +=all[0x19] 0e 02 2c 18 ; all[0x2c] -=all[0x18] 22 01 2c 05 ; if all[0x2c] !=0x5 20 01 4d ; cmd +=0x4d 18 02 2d 19 ; all[0x2d] +=all[0x19] 0f 02 2d 15 ; all[0x2d] -=all[0x15] 23 01 2d 0b ; if all[0x2d] !=0xb 21 01 3e ; cmd +=0x3e 04 01 2a 02 ; all[0x2a] *=0x2 04 01 2a 02 ; all[0x2a] /=0x2 // 下面是输出部分,从0到0x1a输出内容,如果执行正确,应该会执行到这里 04 00 04 01 04 02 04 03 04 04 04 05 04 06 04 07 04 08 04 09 04 0a 04 0b 04 0c 04 0d 04 0e 04 0f 04 10 04 11 04 12 04 13 04 14 04 15 04 16 04 17 04 18 04 19 04 1a
解题脚本 根据上面的约束,直接写到z3里面即可。我这里写的太啰嗦了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 from z3 import *def to_int (x ): return ArithRef(Z3_mk_bv2int(x.ctx_ref(), x.as_ast(), 0 ), x.ctx) def solve (): f5 = BitVec("f5" ,8 ) f6 = BitVec("f6" ,8 ) f7 = BitVec("f7" ,8 ) f8 = BitVec("f8" ,8 ) f9 = BitVec("f9" ,8 ) fa = BitVec("fa" ,8 ) fb = BitVec("fb" ,8 ) fc = BitVec("fc" ,8 ) fd = BitVec("fd" ,8 ) fe = BitVec("fe" ,8 ) ff = BitVec("ff" ,8 ) f10 = BitVec("f10" ,8 ) f11 = BitVec("f11" ,8 ) f12 = BitVec("f12" ,8 ) f13 = BitVec("f13" ,8 ) f14 = BitVec("f14" ,8 ) f15 = BitVec("f15" ,8 ) f16 = BitVec("f16" ,8 ) f17 = BitVec("f17" ,8 ) f18 = BitVec("f18" ,8 ) f19 = BitVec("f19" ,8 ) solver = Solver() solver.add(f5+f7 == 0xcc ) solver.add(f5+f6+f7-0x64 ==0xe1 ) solver.add(f6+f7 == 0xd8 ) solver.add(f8==f9+0xb ) solver.add(f8==fa+0x42 ) solver.add(fc + fb - f8 == 0x67 ) solver.add(fc+fa-f9 == fb-0x29 ) solver.add(fb-fa == 0x35 ) solver.add(fe == f13) solver.add(ff == f12) solver.add(f10 == f7) solver.add(fd-f12==f11) solver.add(f11<f12) solver.add(f11>f13) solver.add(fd-0x36 == fe) solver.add(f12-f13==2 ) solver.add(2 *f13==f17) solver.add(f14==f10) solver.add((f15/2 ) == f16 + 0x6 ) solver.add((f15/2 )*2 == f15) solver.add(f16==fa) solver.add(f19-f18 == 0x5 ) solver.add(f19-f15 == 0xb ) if solver.check() == z3.sat: print ("can solve" ) m = solver.model() a5 = m[f5].as_long() a6 = m[f6].as_long() a7 = m[f7].as_long() a8 = m[f8].as_long() a9 = m[f9].as_long() aa = m[fa].as_long() ab = m[fb].as_long() ac = m[fc].as_long() ad = m[fd].as_long() ae = m[fe].as_long() af = m[ff].as_long() a10 = m[f10].as_long() a11 = m[f11].as_long() a12 = m[f12].as_long() a13 = m[f13].as_long() a14 = m[f14].as_long() a15 = m[f15].as_long() a16 = m[f16].as_long() a17 = m[f17].as_long() a18 = m[f18].as_long() a19 = m[f19].as_long() ans = "" ans +=chr (a5) ans +=chr (a6) ans +=chr (a7) ans +=chr (a8) ans +=chr (a9) ans +=chr (aa) ans +=chr (ab) ans +=chr (ac) ans +=chr (ad) ans +=chr (ae) ans +=chr (af) ans +=chr (a10) ans +=chr (a11) ans +=chr (a12) ans +=chr (a13) ans +=chr (a14) ans +=chr (a15) ans +=chr (a16) ans +=chr (a17) ans +=chr (a18) ans +=chr (a19) print (ans) else : print ("can't solve" ) if __name__ == "__main__" : solve()
多解问题 注意到上面有一个除法操作。这里会产生多解问题。
这样会产生两种可能的解。
flag{my_sh1fti35_453_n1fty}
flag{my_sh1fti35_453_o1fuz}
后面询问了出题人,两种都算是可以了。