vm题目记录(2)

花指令,除0异常,反调试再加上vm,比较复杂的题目,做了很长时间才最终做出来

周三 4月 09 2025
771 字 · 6 分钟

开幕就是花指令。

image-20250409230742793

去掉花指令之后发现是一个除0异常。

PLAINTEXT
.text:004016A2 E8 69 FF FF FF                                call    sub_401610
.text:004016A7 E8 0C 04 00 00                                call    __ftol
.text:004016AC 8B C8                                         mov     ecx, eax
.text:004016AE 83 E9 02                                      sub     ecx, 2
.text:004016B1 8B 45 E4                                      mov     eax, [ebp+var_1C]
.text:004016B4 99                                            cdq
.text:004016B5 F7 F9                                         idiv    ecx
.text:004016B7 89 45 E4                                      mov     [ebp+var_1C], eax

sub_401610返回值是2.0(未调试情况下,这里有一个反调试),然后后续-2,后续004016B5触发了除0异常。

故进入函数sub_4012F0。

函数

C
 sub_401872("%s", v4);
  if ( strlen(v4) == 42 )
  {
    v2 = sub_4011E0(v4);
    if ( (**v1)(v1, &unk_40B030, &unk_40B050, 0, v2) )
      sub_4016F9(aCongratulation);
    else
      sub_4016F9(aUnfortunatelyI);
  }

输入v4后经过sub_4011E0(变种base64编码)。后传入虚拟机操作

PLAINTEXT
.rdata:0040A0D4 dd offset sub_4013B0 0xC0
.rdata:0040A0D8 dd offset sub_4013C0 0XC1
.rdata:0040A0DC dd offset sub_4013D0
.rdata:0040A0E0 dd offset sub_4013E0
.rdata:0040A0E4 dd offset sub_4013F0
.rdata:0040A0E8 dd offset sub_401400
.rdata:0040A0EC dd offset sub_401410
.rdata:0040A0F0 dd offset sub_401420
.rdata:0040A0F4 dd offset sub_401430
.rdata:0040A0F8 dd offset sub_401440
.rdata:0040A0FC dd offset sub_401450
.rdata:0040A100 dd offset sub_401460
.rdata:0040A104 dd offset sub_401470
.rdata:0040A108 dd offset sub_401490
.rdata:0040A10C dd offset sub_4014B0
.rdata:0040A110 dd offset sub_4014D0
.rdata:0040A114 dd offset sub_4014F0
.rdata:0040A118 dd offset sub_401540
.rdata:0040A11C dd offset sub_401590
.rdata:0040A120 dd offset sub_4015D0
.rdata:0040A124 dd offset sub_4015F0

有一堆函数,opcode是按顺序排的,下面进行分析。

先执行sub_401000

PLAINTEXT
int __thiscall sub_401000(_DWORD *vm, int &opcodes, int &enc, int 0, int &origin_data)
{
  vm[1] =  &opcodes; a2是opcode地址
  注:2345均为寄存器,初始为0
  vm[6] = &enc;
  vm[7] = 0;
  vm[8] = &origin_data;
  while ( 2 )
  {
    switch ( *(_BYTE *)vm[1] )
    {
      case 0xC0:
        (*(void (__thiscall **)(_DWORD *))(*vm + 4))(vm);
        continue;

(表格是函数后三位地址简写)

opcode函数功能
0xCA450指针后移5位,r3=opcode[1]=0
0xCB460指针后移5位,r4=opcode[1]=0
0xCC470指针后移1位,r2=origin_data[r4]
0xCF4D0指针后移1位,r3=r3^r2
0xC9440指针后移5位,r2=opcode[1]=0xEE
0xCF4D0指针后移1位,r3=r3^r2
0xD1540指针后移1位,判断r3跟enc[r4]是否相等,相等:r5=1,不相等:r3>=enc[r4]:r5=2,否则r5=0
0xD35D0如果r5=1,指针后移2位+指针下一位的值。否则指针后移两位
0xC23D0指针后移1位,r4++
0xD2590如果opcode[1]等于r4:r5=1,指针后移5位;不相等,指针后移5位,r4>=opcode[1]:r5=2,否则为0(r5=0)
0xD45F0指针后移2位,r5如果是0,指针继续后移+指针下一位的值。后移0xEF
0xCC470指针后移1位,r2=origin_data[r4]
0xCF4D0指针后移1位,r3=r3^r2
0xC9

这里发现了规律

经过复杂的分析,还原大致逻辑如下:

PYTHON
r3=0
for r4 in range(0,0x39):
	r3 ^= (origin[r4]^0xee)
	if r3==enc[r4]:
    	pass
    else:
        #错误退出

解密脚本如下:

PYTHON
enc = [ 190,  54, 172,  39, 153,  79, 222,  68, 238,  95,
  218,  11, 181,  23, 184, 104, 194,  78, 156,  74,
  225,  67, 240,  34, 138,  59, 136,  91, 229,  84,
  255, 104, 213, 103, 212,   6, 173,  11, 216,  80,
  249,  88, 224, 111, 197,  74, 253,  47, 132,  54,
  133,  82, 251, 115, 215,  13, 227]
def recover_origin(enc):
    origin = []
    r3 = 0
    for r4 in range(len(enc)):
        origin1 = enc[r4] ^ r3 ^ 0xee
        origin.append(origin1)
        r3 ^= (origin1 ^ 0xee)
    return origin
print(recover_origin(enc))

上面的代码逆向了vm中实现的逻辑,

PYTHON
import base64
def reverse_custom_base64_from_bytes(byte_data):
    if byte_data[-1] == 0:
        byte_data = byte_data[:-1]
    encoded = ''.join(chr(b) for b in byte_data)
    encoded_bytes = bytearray(encoded, 'ascii')
    for i in range(len(encoded_bytes)):
        pos = i % 4
        if pos == 0:
            encoded_bytes[i] ^= 0xA
        elif pos == 1:
            encoded_bytes[i] ^= 0xB
        elif pos == 2:
            encoded_bytes[i] ^= 0xC
        elif pos == 3:
            encoded_bytes[i] ^= 0xD
    modified = encoded_bytes.decode('ascii')
    padding_needed = (4 - (len(modified) % 4)) % 4
    modified += '=' * padding_needed
    decoded = base64.b64decode(modified)
    return decoded.decode('utf-8')
data = [80, 102, 116, 101, 80, 56, 127, 116, 68, 95, 107, 63, 80, 76, 65, 62,
        68, 98, 60, 56, 69, 76, 93, 60, 70, 95, 93, 61, 80, 95, 69, 121, 83, 92,
        93, 60, 69, 72, 61, 102, 71, 79, 86, 97, 68, 97, 89, 60, 69, 92, 93, 57,
        71, 102, 74, 52, 0]
result = reverse_custom_base64_from_bytes(data)
print(f"Decoded result: {result}")
#flag{2586dc76-98d5-44e2-ad58-d06e6559d82a}

相较于标准base64解码这里多了异或这一步。

最终获得flag。


Thanks for reading!

vm题目记录(2)

周三 4月 09 2025
771 字 · 6 分钟