到了后边就都不会了,只有这个VM还可以磨磨
主程序非常短,输入32个字节然后就去比较了
signed __int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
unsigned int (__fastcall ***v3)(_QWORD, void *, void *, char *); // rbx
char s; // [rsp+10h] [rbp-80h]
int v6; // [rsp+70h] [rbp-20h]
unsigned __int64 v7; // [rsp+78h] [rbp-18h]
v7 = __readfsqword(0x28u);
memset(&s, 0, 0x60uLL);
v6 = 0;
v3 = (unsigned int (__fastcall ***)(_QWORD, void *, void *, char *))operator new(0x28uLL);
sub_400C1E((__int64)v3);
puts("please input your flag:");
scanf("%s", &s);
if ( strlen(&s) != 32 )
{
puts("The length of flag is wrong!");
puts("Please try it again!");
}
if ( (**v3)(v3, &unk_602080, &unk_6020A0, &s) != 0 )
{
puts("Congratulations!");
printf("The flag is UNCTF{%s}", &s);
}
return 1LL;
}
v3在sub_400C1E 给了一个地址,然后其它的运算都在这个位置上偏移
__int64 __fastcall sub_400C1E(__int64 a1)
{
__int64 result; // rax
*(_QWORD *)a1 = off_4010A8;
*(_QWORD *)(a1 + 8) = 0LL;
*(_BYTE *)(a1 + 16) = 0;
*(_BYTE *)(a1 + 17) = 0;
*(_BYTE *)(a1 + 18) = 0;
*(_DWORD *)(a1 + 20) = 0;
*(_QWORD *)(a1 + 24) = 0LL;
result = a1;
*(_QWORD *)(a1 + 32) = 0LL;
return result;
}
偏移多少是哪个函数在数据off_4010A8这有,为方便专门都改了个名字,对应switch里的编号。刚一开始跳转表看串行了,困惑好久,像九阴真经练反了的感觉。
.rodata:00000000004010A8 off_4010A8 dq offset A__main_switch
.rodata:00000000004010A8 ; DATA XREF: sub_400C1E+8↑o
.rodata:00000000004010B0 dq offset A0_16add1
.rodata:00000000004010B8 dq offset A1_17add1
.rodata:00000000004010C0 dq offset A2_18add1
.rodata:00000000004010C8 dq offset A3_16sub18
.rodata:00000000004010D0 dq offset A4_16xor17
.rodata:00000000004010D8 dq offset A5_17xor16
.rodata:00000000004010E0 dq offset A6_16sub51
.rodata:00000000004010E8 dq offset A7_17in16
.rodata:00000000004010F0 dq offset A8_18_cd
.rodata:00000000004010F8 dq offset A9_16e32add18
.rodata:0000000000401100 dq offset AA_17e32add18
.rodata:0000000000401108 dq offset AB_16cmp24add18
.rodata:0000000000401110 dq offset AC_17cmp24add18
.rodata:0000000000401118 dq offset AD_18gt0x1f
偏移0的时候是主程序。开始的时候从a2[9]开始也就是A9。然后变换*(a1+8)实现了一个循环。+8这可以看成rip,a3指向校验数据在*(a1+24),a4是输入的数据在*(a1+32),另外有模拟3个寄存器+16,+17,+18和一个标志位+20如果处理后的输入字符校验未通过会置标志然后退出。
signed __int64 __fastcall s01_main_switch(__int64 a1, __int64 a2, __int64 a3, __int64 a4)
{
*(_QWORD *)(a1 + 8) = a2 + 9; // A0-AF
*(_QWORD *)(a1 + 24) = a3; // code
*(_QWORD *)(a1 + 32) = a4; // input
while ( 2 )
{
switch ( **(unsigned __int8 **)(a1 + 8) )
{
case 0xA0u:
(*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 8LL))(a1);
continue;
case 0xA1u:
(*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x10LL))(a1);
continue;
case 0xA2u:
(*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x18LL))(a1);// 9
*(_QWORD *)(a1 + 8) += 11LL;
continue;
case 0xA3u:
(*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x20LL))(a1);// 2
*(_QWORD *)(a1 + 8) += 2LL;
continue;
case 0xA4u:
(*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x28LL))(a1);// 5
*(_QWORD *)(a1 + 8) += 7LL;
continue;
case 0xA5u:
(*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x30LL))(a1);// 3
++*(_QWORD *)(a1 + 8);
continue;
case 0xA6u:
(*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x38LL))(a1);// 4
*(_QWORD *)(a1 + 8) -= 2LL;
continue;
case 0xA7u:
(*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x40LL))(a1);// 7
*(_QWORD *)(a1 + 8) += 7LL;
continue;
case 0xA8u:
(*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x48LL))(a1);
continue;
case 0xA9u:
(*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x50LL))(a1);// 1
*(_QWORD *)(a1 + 8) -= 6LL;
continue;
case 0xAAu:
(*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x58LL))(a1);
continue;
case 0xABu:
(*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x60LL))(a1);// 6
*(_QWORD *)(a1 + 8) -= 4LL;
continue;
case 0xACu:
(*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x68LL))(a1);
continue;
case 0xADu:
(*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x70LL))(a1);// 10
*(_QWORD *)(a1 + 8) += 2LL;
continue;
case 0xAEu: // 8
if ( *(_DWORD *)(a1 + 20) )
return 0LL;
*(_QWORD *)(a1 + 8) -= 12LL;
continue;
case 0xAFu: // out 11
if ( *(_DWORD *)(a1 + 20) != 1 )
{
*(_QWORD *)(a1 + 8) -= 6LL;
continue;
}
return 1LL;
default:
puts("cmd execute error");
return 0LL;
}
}
}
这里面有向个是没有rip变动的,比如A0,A1,A8,AA,AC这如果运行到这就会是死循环,所以这些运行不到。还有几个没有运行函数,是判断跳转语句AE,AF。其它在a1后边的偏移就是前面跳转表上偏移,表示运行哪个函数。
每个函数都很简单,明确以后就可以捋捋加密过程了。
+18这个位置放的是个计数器,相当于for(i=0;i<=31;i++)里的i
先读入一个输入的字符减去序号然后和r2异或(这个异或的r2后边有)。然后再与0xcd异或。然后和校验数据比较不同则退出
成功后会把这个数据(与检验同,逆向时使用检验数据)存入r2,循环下次时使用。
然后就是i++,判断是否结束。
'''
rip: *(a1+8) A0-AF
r1: *(a1+16)
r2: *(a1+17)
i: *(a1+18) *(ptr+i) = flag[i]
code:*(a1+24) code
ptr: *(a1+32) input flag
1,A9: r1= *[ptr + i]
2,A3: r1-=i
3,A5: r2^=r1
4,A6: r1 =0xcd
5,A4: r1^=r2
6,AB: cmp(r1,code[i]) #(flag[i]-i)^code[i-1]^0xcd = code[i]
7,A7: r2 =r1
8,AE: jnz A2 # *(a1+20) == 0
9,A2: i++
10,AD: cmp(i,0x1f) #结束标志,总行0x1f
11,AF: jmp A9
'''
所以解码程序就有了(由于用到r2的结果,所以解码要从后向前)
code = bytes.fromhex('00'+'F40AF76499789E7DEA7B9E7B9F7EEB71E800E8079819F425F321A42FF42FA67C')
flag = bytes([(code[i+1]^code[i]^0xcd) + i for i in range(0x1f, -1, -1)])[::-1]
print(flag)
#942a4115be2359ffd675fa6338ba23b6
#UNCTF{942a4115be2359ffd675fa6338ba23b6}