BabyVM
进来跟着main一顿跳, 跳到412CC0, 先把花指令去掉
from ida_bytes import *
base = 0x412DBC
to = 0x413937
for i in range(base, to) :
if get_bytes(i, 5) == b'\x74\x03\x75\x01\xe8' :
patch_bytes(i, b'\x90\x90\x90\x90\x90')
每条指令用了三个64bit, 按这种格式把数据转换出来方便分析
int main() {
unsigned long long * p = (unsigned long long *) ida_chars;
for(int i = 0; i < 888; i += 24) {
printf("0x%llx 0x%llx, 0x%llx\n", *p, *(p+1), *(p+2) != -1ll ? *(p+2) : 0);
p += 3;
}
}
0x12,0x0, 0x0,
略
0x19
写了部分脚本之后没找到对比, 那就是还有OPCode, 我太天真了, 以为只用这些, 实际应该是我去花指令之后声明函数没太声明好.下面是提出来的内存, 太长了没必要都挂上, 0x19是每段的结束.
反调太多, 没能拿动调找到, 是通过找交叉引用找到的,其实这些内存都在一起,看到一个就能看到后面的
sub_6E2CC0是vm函数
0x12,0x2, 0x2,
略
0x19, 0, 0,
0x12,0x2, 0x2,
略
0x19, 0, 0,
0x6,0x0, 0x0,
略
0x19,0xffffffffffffffff, 0x0,
略
0x19,0xffffffffffffffff, 0x0
三个64bit我分别定义成了opcode, rs, rt, 差不多就是mips那种
这个分析的时候没把寄存器看成寄存器, 实际的寄存器就是这个mem1和mem2, 下面会解释 ,核心vm的操作都写在下面的脚本里了,我主要解释一下命名,可以结合你的IDA对照着看, 因为这边IDA数据库改掉之后没有原始哑名了
Mem_table是传入参数,也是Code_Segment,
mem1和mem2共同构成了脚本中的mem,你可能看到mem1和mem2使用的偏移都在基础寻址上乘了2, 这是一种类似储存器位扩展的形式,它是这样的
-----------------------------------
-------------------------------
^ ^
| |
m1 m2
m1永远奇数, m2永远偶数, 由此两个int就可以交叉成一个64位, 我觉得它更像一个寄存器的结构, 我们可以分别读取高低32位
这里的mem3是一个类似栈的结构,在下面的脚本中被我称为了mem64, 原因是原vm代码中它都是以64位而非上面的交叉结构存在的
脚本中还有一个mem2, 它不同于IDA里被我改名的mem2, 是case 0x5, 0x6中 出现的另一段内存, 实际上,它的性质才更像内存. 但是改命名太麻烦了,能看懂就好
另外还有两个寄存器, 分别被我命名成了 cnt 和 ZF/ZF1, 前者是栈寄存器,后者是判断寄存器.
非常传统的vm题, 我没有其他可以说的了
CS = [
略
]
cnt = 0
for IP in range(0, len(CS), 3) :
RS = CS[IP+1]
RT = CS[IP+2]
print(cnt, end = " : ")
if CS[IP] == 1 :
print("mem[", RS, "] = ", RT, "")
elif CS[IP] == 5 :
print("mem64[++cnt] = mem[", RS, "]")
elif CS[IP] == 7 :
print("mem[", RS, "] += ", RT, "")
elif CS[IP] == 0x12 :
print("mem[", RS, "] ^= mem[", RT, "]")
elif CS[IP] == 0x17 :
print("mem[", RS, "] = Input()")
elif CS[IP] == 0x18 :
print("Print(lo32(mem[", RS, "]))")
elif CS[IP] == 0x19 :
print("Exit\nRestart 0x0 :")
cnt = 0
continue
elif CS[IP] == 0x1a :
print("ZF = mem[", RS, "] ==", RT)
print("ZF1 = mem[", RS, "] <", RT)
elif CS[IP] == 0x1e :
print("JZ1", RS)
elif CS[IP] == 0 :
print("mem2[mem[", RS, "]] = ", RT )
elif CS[IP] == 3 :
print("mem[", RS, "] = mem2[mem1[", RT, "]]")
elif CS[IP] == 9 :
print("mem[", RS, "] -= ", RT)
elif CS[IP] == 4 :
print("mem2[mem[", RS, "]] = mem[", RT, "]")
elif CS[IP] == 6 :
print("mem[", RS, "] = mem64[cnt--]")
elif CS[IP] == 0x1C :
print("JZ", RS)
elif CS[IP] == 0x1d :
print("JMP", RS)
elif CS[IP] == 0x1f :
print("JNZ", RS)
elif CS[IP] == 0x11 :
print("mem[", RS, "] ^=", RT)
elif CS[IP] == 0xD :
print("mem[", RS, "] <<=", RT)
elif CS[IP] == 0x1B :
print("ZF = mem[", RS, "] == mem[", RT, "]")
print("ZF1 = mem[", RS, "] < mem[", RT, "]")
else :
print(hex(CS[IP]))
break
cnt += 1
0 : mem[ 0 ] ^= mem[ 0 ]
1 : mem[ 1 ] ^= mem[ 1 ]
2 : mem[ 2 ] ^= mem[ 2 ]
3 : mem[ 3 ] ^= mem[ 3 ]
4 : mem[ 6 ] ^= mem[ 6 ]
5 : mem[ 7 ] ^= mem[ 7 ]
6 : mem[ 0 ] = 105
7 : mem[ 1 ] = 110
8 : mem[ 2 ] = 112
9 : mem[ 3 ] = 117
10 : mem[ 6 ] = 116
11 : mem[ 7 ] = 32
12 : Print(lo32(mem[ 0 ]))
13 : Print(lo32(mem[ 1 ]))
14 : Print(lo32(mem[ 2 ]))
15 : Print(lo32(mem[ 3 ]))
16 : Print(lo32(mem[ 6 ]))
17 : Print(lo32(mem[ 7 ]))
18 : mem[ 0 ] = 102
19 : mem[ 1 ] = 108
20 : mem[ 2 ] = 97
21 : mem[ 3 ] = 103
22 : mem[ 6 ] = 58
23 : mem[ 7 ] = 32
24 : Print(lo32(mem[ 0 ]))
25 : Print(lo32(mem[ 1 ]))
26 : Print(lo32(mem[ 2 ]))
27 : Print(lo32(mem[ 3 ]))
28 : Print(lo32(mem[ 6 ]))
29 : Print(lo32(mem[ 7 ]))
30 : mem[ 1 ] ^= mem[ 1 ]
31 : mem[ 0 ] = Input()
32 : mem64[++cnt] = mem[ 0 ]
33 : mem[ 1 ] += 1
34 : ZF = mem[ 1 ] == 38
ZF1 = mem[ 1 ] < 38
35 : JZ1 31
36 : Exit
Restart 0x0 :
0 : mem[ 2 ] ^= mem[ 2 ]
1 : mem2[mem[ 2 ]] = 255
2 : mem[ 2 ] += 1
3 : mem2[mem[ 2 ]] = 547
4 : mem[ 2 ] += 1
5 : mem2[mem[ 2 ]] = 571
6 : mem[ 2 ] += 1
7 : mem2[mem[ 2 ]] = 567
8 : mem[ 2 ] += 1
9 : mem2[mem[ 2 ]] = 567
10 : mem[ 2 ] += 1
11 : mem2[mem[ 2 ]] = 587
12 : mem[ 2 ] += 1
13 : mem2[mem[ 2 ]] = 555
14 : mem[ 2 ] += 1
15 : mem2[mem[ 2 ]] = 251
16 : mem[ 2 ] += 1
17 : mem2[mem[ 2 ]] = 555
18 : mem[ 2 ] += 1
19 : mem2[mem[ 2 ]] = 547
20 : mem[ 2 ] += 1
21 : mem2[mem[ 2 ]] = 591
22 : mem[ 2 ] += 1
23 : mem2[mem[ 2 ]] = 239
24 : mem[ 2 ] += 1
25 : mem2[mem[ 2 ]] = 567
26 : mem[ 2 ] += 1
27 : mem2[mem[ 2 ]] = 239
28 : mem[ 2 ] += 1
29 : mem2[mem[ 2 ]] = 591
30 : mem[ 2 ] += 1
31 : mem2[mem[ 2 ]] = 591
32 : mem[ 2 ] += 1
33 : mem2[mem[ 2 ]] = 547
34 : mem[ 2 ] += 1
35 : mem2[mem[ 2 ]] = 547
36 : mem[ 2 ] += 1
37 : mem2[mem[ 2 ]] = 571
38 : mem[ 2 ] += 1
39 : mem2[mem[ 2 ]] = 567
40 : mem[ 2 ] += 1
41 : mem2[mem[ 2 ]] = 255
42 : mem[ 2 ] += 1
43 : mem2[mem[ 2 ]] = 563
44 : mem[ 2 ] += 1
45 : mem2[mem[ 2 ]] = 563
46 : mem[ 2 ] += 1
47 : mem2[mem[ 2 ]] = 563
48 : mem[ 2 ] += 1
49 : mem2[mem[ 2 ]] = 567
50 : mem[ 2 ] += 1
51 : mem2[mem[ 2 ]] = 587
52 : mem[ 2 ] += 1
53 : mem2[mem[ 2 ]] = 563
54 : mem[ 2 ] += 1
55 : mem2[mem[ 2 ]] = 591
56 : mem[ 2 ] += 1
57 : mem2[mem[ 2 ]] = 555
58 : mem[ 2 ] += 1
59 : mem2[mem[ 2 ]] = 555
60 : mem[ 2 ] += 1
61 : mem2[mem[ 2 ]] = 587
62 : mem[ 2 ] += 1
63 : mem2[mem[ 2 ]] = 239
64 : mem[ 2 ] += 1
65 : Exit
Restart 0x0 :
0 : mem[ 2 ] ^= mem[ 2 ]
1 : mem[ 0 ] = mem2[mem1[ 2 ]]
2 : mem[ 0 ] -= 99
3 : mem2[mem[ 2 ]] = mem[ 0 ]
4 : mem[ 2 ] += 1
5 : ZF = mem[ 2 ] == 32
ZF1 = mem[ 2 ] < 32
6 : JZ1 1
7 : Exit
Restart 0x0 :
0 : mem[ 0 ] = mem64[cnt--]
1 : ZF = mem[ 0 ] == 125
ZF1 = mem[ 0 ] < 125
2 : JZ 18
3 : mem[ 0 ] = 119
4 : mem[ 1 ] = 114
5 : mem[ 2 ] = 111
6 : mem[ 3 ] = 110
7 : mem[ 6 ] = 103
8 : mem[ 7 ] = 33
9 : Print(lo32(mem[ 0 ]))
10 : Print(lo32(mem[ 1 ]))
11 : Print(lo32(mem[ 2 ]))
12 : Print(lo32(mem[ 3 ]))
13 : Print(lo32(mem[ 6 ]))
14 : Print(lo32(mem[ 7 ]))
15 : mem[ 0 ] = 10
16 : Print(lo32(mem[ 0 ]))
17 : Exit
Restart 0x0 :
0 : mem[ 8 ] = 256
1 : ZF = mem[ 8 ] == 225
ZF1 = mem[ 8 ] < 225
2 : JZ1 25
3 : mem[ 0 ] = mem64[cnt--]
4 : mem2[mem[ 8 ]] = mem[ 0 ]
5 : mem[ 8 ] -= 1
6 : JMP 19
7 : mem[ 0 ] = mem64[cnt--]
8 : ZF = mem[ 0 ] == 123
ZF1 = mem[ 0 ] < 123
9 : JNZ 3
10 : mem[ 0 ] = mem64[cnt--]
11 : ZF = mem[ 0 ] == 103
ZF1 = mem[ 0 ] < 103
12 : JNZ 3
13 : mem[ 0 ] = mem64[cnt--]
14 : ZF = mem[ 0 ] == 97
ZF1 = mem[ 0 ] < 97
15 : JNZ 3
16 : mem[ 0 ] = mem64[cnt--]
17 : ZF = mem[ 0 ] == 108
ZF1 = mem[ 0 ] < 108
18 : JNZ 3
19 : mem[ 0 ] = mem64[cnt--]
20 : ZF = mem[ 0 ] == 102
ZF1 = mem[ 0 ] < 102
21 : JNZ 3
22 : mem[ 9 ] ^= mem[ 9 ]
23 : mem[ 10 ] = 225
24 : mem[ 7 ] = mem2[mem1[ 9 ]]
25 : mem[ 6 ] = mem2[mem1[ 10 ]]
26 : mem[ 6 ] ^= 66
27 : mem[ 6 ] <<= 2
28 : ZF = mem[ 6 ] == mem[ 7 ]
ZF1 = mem[ 6 ] < mem[ 7 ]
29 : JNZ 3
30 : mem[ 9 ] += 1
31 : mem[ 10 ] += 1
32 : ZF = mem[ 9 ] == 32
ZF1 = mem[ 9 ] < 32
33 : JZ1 42
34 : mem[ 0 ] = 99
35 : mem[ 1 ] = 111
36 : mem[ 2 ] = 114
37 : mem[ 3 ] = 114
38 : mem[ 6 ] = 101
39 : mem[ 7 ] = 99
40 : Print(lo32(mem[ 0 ]))
41 : Print(lo32(mem[ 1 ]))
42 : Print(lo32(mem[ 2 ]))
43 : Print(lo32(mem[ 3 ]))
44 : Print(lo32(mem[ 6 ]))
45 : Print(lo32(mem[ 7 ]))
46 : mem[ 0 ] = 116
47 : mem[ 1 ] = 108
48 : mem[ 2 ] = 121
49 : mem[ 3 ] = 33
50 : mem[ 6 ] = 10
51 : Print(lo32(mem[ 0 ]))
52 : Print(lo32(mem[ 1 ]))
53 : Print(lo32(mem[ 2 ]))
54 : Print(lo32(mem[ 3 ]))
55 : Print(lo32(mem[ 6 ]))
56 : Exit
Restart 0x0 :
先把输入读到mem64, 再在mem2预置一串数, 并全减掉99, flag ^ 66 再 左移2 之后和预置对比
Mem = [
255, 547, 571, 567, 567,
587, 555, 251, 555, 547,
591, 239, 567, 239, 591,
591, 547, 547, 571, 567,
255, 563, 563, 563, 567,
587, 563, 591, 555, 555,
587, 239
]
for i in range(len(Mem)) :
Mem[i] -= 99;
Mem[i] >>= 2;
Mem[i] ^= 66
print(chr(Mem[i]), end = "")
EasyVM
进来没有main, 有两条花指令, nop掉
401610看上去像反调, 把jnz nop掉, 强行返回2.0
4012F0初始化, 把输入扔4011E0里, 返回是分发器的最后一个参数
输入函数一会再看
申请了一段地址, 作为函数表, 接下来一个一个函数分析, 函数分析结果写在下面的C脚本里了
其中this[2]~[4]是AX-CX寄存器
int __thiscall distributer(_DWORD *this, char *src, char *dst, int a4, int a5)
{
this[1] = src;
this[6] = dst;
this[7] = a4;
this[8] = a5;
while ( 2 )
{
switch ( *(_BYTE *)this[1] )
{
case 0xC0:
AX ++;
case 0xC1:
BX ++;
case 0xC2:
CX ++;
case 0xC3:
AX = BX;
case 0xC4:
AX = CX;
case 0xC5:
BX = AX;
case 0xC6:
BX = CX;
case 0xC7:
CX = AX;
case 0xC8:
CX = BX;
case 0xC9:
LOAD([IP+1](uint32), AX), IP += 4;
case 0xCA:
LOAD([IP+1](uint32), BX), IP += 4;
case 0xCB:
LOAD([IP+1](uint32), CX), IP += 4;
case 0xCC:
LOAD(Pre(uint8)[CX], AX)
case 0xCD:
LOAD(Pre(uint8)[CX], BX)
case 0xCE:
AX ^= BX
case 0xCF:
BX ^= AX
case 0xD0:
if AX == DST[CX] :
DX = 1;
else :
AX >= DST ? DX = 2 : DX = 0;
case 0xD1:
if BX == DST[CX] :
DX = 1;
else :
BX >= DST[CX] ? DX = 2 : DX = 0;
case 0xD2:
if CX == [IP+1] :
DX = 1;
else :
CX >= [IP+1] ? DX = 2 : DX = 0;
IP += 4
case 0xD3:
if DX == 1 :
IP += [IP+1](uint8);
IP ++;
case 0xD4:
if DX != 1 :
IP += [IP+1](uint8);
IP ++;
case 0xFE:
return 0;
case 0xFF:
return 1;
default:
print((int)aCmdError);
return 0;
IP ++;
}
}
}
输入大概就是B64解码, 返回一个首地址, 目的是通过JMP跳到 0xFF
内存拽出来分析成汇编, 汇编逻辑可以直接看下面的脚本
0x0 : LD32 BX, 0
IP += 5
0x5 : LD32 CX, 0
IP += 5
0xa : LD8 AX, Inp[CX]
IP += 1
0xb : XOR BX AX
IP += 1
0xc : LD32 AX, 0xEE
IP += 5
0x11 : XOR BX AX
IP += 1
0x12 : CMP BX, TAB[CX]
IP += 1
0x13 : JPD 0x1
IP += 2
0x15 : FAILED
IP += 1
0x16 : INC CX
IP += 1
0x17 : CMP CX, 0x39
IP += 5
0x1c : JND 0xEC // 0x1C + 0xEC + 2 & 0xFF = 0xA
IP += 2
0x1e : SUCCESS
IP += 1
逻辑就是0x38位每位异或前一位结果, 再异或0xEE要等于明文
脚本
Target = [
0, 0xBE, 0x36, 0xAC, 0x27, 0x99, 0x4F, 0xDE, 0x44, 0xEE, 0x5F,
0xDA, 0x0B, 0xB5, 0x17, 0xB8, 0x68, 0xC2, 0x4E, 0x9C, 0x4A,
0xE1, 0x43, 0xF0, 0x22, 0x8A, 0x3B, 0x88, 0x5B, 0xE5, 0x54,
0xFF, 0x68, 0xD5, 0x67, 0xD4, 0x06, 0xAD, 0x0B, 0xD8, 0x50,
0xF9, 0x58, 0xE0, 0x6F, 0xC5, 0x4A, 0xFD, 0x2F, 0x84, 0x36,
0x85, 0x52, 0xFB, 0x73, 0xD7, 0x0D, 0xE3
]
salt = [0xD, 0xC, 0xB, 0xA]
for i in range(1, len(Target), 1) :
print(chr(Target[i] ^ 0xEE ^ Target[i-1] ^ salt[3-(i-1)%4]), end = "")
print()
#ZmxhZ3syNTg2ZGM3Ni05OGQ1LTQ0ZTItYWQ1OC1kMDZlNjU1OWQ4MmF9
注意到input里给连续四位分别异或了ABCD, 解码要异或回来, 解base64即可