这是个比较正规的逆向题
主程序先读入flag然后打开一个文档并统计字频然后加密输出到out文件
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
void *v3; // ST00_8
const char *v4; // ST08_8
v3 = malloc(0xBCuLL);
v4 = (const char *)malloc(0x50uLL);
init_0();
read_flag((__int64)v4);
sub_400C02((__int64)v4); // 去皮
sub_400AAA("that_girl", (__int64)v3); // v3统计字频
sub_400E54(v4, (__int64)v3); // 加密输出
return 0LL;
}
加密输入这块先转码再重排序然后整串循环向左移3位
unsigned __int64 __fastcall sub_400E54(const char *a1, __int64 a2)
{
int i; // [rsp+18h] [rbp-48h]
int v4; // [rsp+1Ch] [rbp-44h]
char v5[56]; // [rsp+20h] [rbp-40h]
unsigned __int64 v6; // [rsp+58h] [rbp-8h]
v6 = __readfsqword(0x28u);
v4 = strlen(a1);
for ( i = 0; i < v4; ++i )
v5[i] = *(_DWORD *)(4LL * (signed int)trancode(a1[i]) + a2);
sub_400D33((unsigned __int8 *)v5); // 交换顺序
sub_400DB4(v5, v4); // 前3后5交错
write_file((__int64)v5, "out", v4);
return __readfsqword(0x28u) ^ v6;
}
转码部分,这块由于不知道flag用的字符是哪些,这个转码并不是所有转码都正确(仅有用的字符正确)但逆向时就麻烦了,所以在这里需要改一下再逆。
__int64 __fastcall trancode(char a1)
{
__int64 result; // rax
result = (unsigned int)(a1 - 10);
switch ( a1 )
{
case 10:
result = (unsigned int)(a1 + 35);
break;
case 32:
case 33:
case 34:
result = (unsigned int)(a1 + 10);
break;
case 39:
result = (unsigned int)(a1 + 2);
break;
case 44:
result = (unsigned int)(a1 - 4);
break;
case 46:
result = (unsigned int)(a1 - 7);
break;
case 58:
case 59:
result = (unsigned int)(a1 - 21);
break;
case 63:
result = (unsigned int)(a1 - 27);
break;
case 95:
result = (unsigned int)(a1 - 49);
break;
default:
if ( a1 <= 47 || a1 > 48 )
{
if ( a1 <= 64 || a1 > 90 )
{
if ( a1 > 96 && a1 <= 122 )
result = (unsigned int)(a1 - 87);
}
else
{
result = (unsigned int)(a1 - 55);
}
}
else
{
result = (unsigned int)(a1 - 48);
}
break;
}
return result;
}
交换位置这块看起来先绕,不过想想快排啥的,大意就是按一个顺序表重排
__int64 __fastcall sub_400D33(unsigned __int8 *a1)
{
__int64 result; // rax
_BYTE v2[5]; // [rsp+13h] [rbp-5h]
v2[4] = 0;
*(_DWORD *)v2 = *a1;
while ( dword_6020A0[*(signed int *)&v2[1]] )
{
a1[*(signed int *)&v2[1]] = a1[dword_6020A0[*(signed int *)&v2[1]]];
*(_DWORD *)&v2[1] = dword_6020A0[*(signed int *)&v2[1]];
}
result = v2[0];
a1[*(signed int *)&v2[1]] = v2[0];
return result;
}
整体循环错3位这块,理解起来还不难
_BYTE *__fastcall sub_400DB4(_BYTE *a1, int a2)
{
_BYTE *result; // rax
char v3; // [rsp+17h] [rbp-5h]
int i; // [rsp+18h] [rbp-4h]
v3 = *a1 >> 5;
for ( i = 0; a2 - 1 > i; ++i )
a1[i] = 8 * a1[i] | (a1[i + 1] >> 5);
result = &a1[i];
*result = 8 * *result | v3;
return result;
}
这里换顺序这块可以后作,都是单个字节处理所以后两步不影响顺序,当flag的字符都出来后换顺序可以很容易验证
第1步先循环右移3位
第2步恢复原来的字符
第3步再恢复原来的顺序,表里的序号就是字符在原串中的位置
v3的词频转码表可以gdb中得到,dword_6020a0在程序里有
转换表不区分大小写,大概可以接数字0-9,字母1-35,然后是一堆符号,不能直接用原程序转。
#v3 词频转码表
v3 = [0,0,0,0,0,0,0,0,0,0,104,30,15,29,169,19,38,67,60,0,20,39,28,118,165,26,0,61,51,133,45,7,34,0,62,0,0,0,0,0,0,40,71,0,0,66,245,0,0,0,97,0]
#dword_6020a0 顺序表
dword_6020a0 = [22,0,6,2,30,24,9,1,21,7,18,10,8,12,17,23,13,4,3,14,19,11,20,16,15,5,25,36,27,28,29,37,31,32,33,26,34,35]
#1 循环向右移3位(尾3位移到头)
a = open('out', 'rb').read()
b = ''
for i in a:
b += bin(i)[2:].rjust(8, '0')
b = b[-3:]+b[:-3]
c = b''
for i in range(0, len(b), 8):
c += bytes([int(b[i:i+8], 2)])
b = c
print(b)
def trancode(a):
if a==10:
return a+35
elif (a==32) or (a==33) or (a==34):
return a+10
elif a==39:
return a+2
elif a==44:
return a-4
elif a==46:
return a-7
elif (a==58) or (a==59):
return a-21
elif a==63:
return a-27
elif a==95:
return a-49
else:
if a <48 or a>57: #数字
if (a<=64) or (a>90):
if (a>96) and (a<=122):
return a-87 #大写字母 不区分
else:
return -1
else:
return a-55 #小写字母
else:
return a-48 #0-9
return a-10
code = [0]*256
for i in range(256):
code[i] = (256+trancode(i))%256
#print(code)
c = ''
for i in b:
t1 = v3.index(i)
t2 = code.index(t1)
c += chr(t2)
#print(i, t1, t2)
print(c)
a = list(c)
for i,v in enumerate(dword_6020a0):
a[v]=c[i]
print(''.join(a))
#THAT_GIRL_SAYING_NO_FOR_YOUR_VINDICATE
#QCTF{that_girl_saying_no_for_your_vindicate}
最后提交的时候加上包裹,由于转码程序不分大小写,大写提交不成改小写成功。