开幕就是花指令。
去掉花指令之后发现是一个除0异常。
.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。
函数
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编码)。后传入虚拟机操作
.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
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 | 函数 | 功能 |
---|---|---|
0xCA | 450 | 指针后移5位,r3=opcode[1]=0 |
0xCB | 460 | 指针后移5位,r4=opcode[1]=0 |
0xCC | 470 | 指针后移1位,r2=origin_data[r4] |
0xCF | 4D0 | 指针后移1位,r3=r3^r2 |
0xC9 | 440 | 指针后移5位,r2=opcode[1]=0xEE |
0xCF | 4D0 | 指针后移1位,r3=r3^r2 |
0xD1 | 540 | 指针后移1位,判断r3跟enc[r4]是否相等,相等:r5=1,不相等:r3>=enc[r4]:r5=2,否则r5=0 |
0xD3 | 5D0 | 如果r5=1,指针后移2位+指针下一位的值。否则指针后移两位 |
0xC2 | 3D0 | 指针后移1位,r4++ |
0xD2 | 590 | 如果opcode[1]等于r4:r5=1,指针后移5位;不相等,指针后移5位,r4>=opcode[1]:r5=2,否则为0(r5=0) |
0xD4 | 5F0 | 指针后移2位,r5如果是0,指针继续后移+指针下一位的值。后移0xEF |
0xCC | 470 | 指针后移1位,r2=origin_data[r4] |
0xCF | 4D0 | 指针后移1位,r3=r3^r2 |
0xC9 | ||
… |
这里发现了规律
经过复杂的分析,还原大致逻辑如下:
r3=0
for r4 in range(0,0x39):
r3 ^= (origin[r4]^0xee)
if r3==enc[r4]:
pass
else:
#错误退出
解密脚本如下:
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中实现的逻辑,
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!