[RoarCTF2019]polyre

知识点

  • 控制流平坦化
  • CRC加密
  • 在IDA中运行python

main

用IDA打开查看主函数,发现是多重While需要对代码进行控制流平坦化,可以参考下面的文章

利用符号执行去除控制流平坦化

OLLVM混淆,使用工具defalt.py去混淆

python deflat.py -f /home/phantomor/Desktop/polyre –addr 0x400620(去混淆函数地址)

查看运行出的新程序,

__int64 __fastcall main(int a1, char **a2, char **a3)
{
signed __int64 v4; // [rsp+1E0h] [rbp-110h]
int i; // [rsp+1E8h] [rbp-108h]
int v6; // [rsp+1ECh] [rbp-104h]
int v7; // [rsp+1ECh] [rbp-104h]
char s1[48]; // [rsp+1F0h] [rbp-100h] BYREF
char s[60]; // [rsp+220h] [rbp-D0h] BYREF
unsigned int v10; // [rsp+25Ch] [rbp-94h]
char *v11; // [rsp+260h] [rbp-90h]
int v12; // [rsp+26Ch] [rbp-84h]
bool v13; // [rsp+272h] [rbp-7Eh]
unsigned __int8 v14; // [rsp+273h] [rbp-7Dh]
int v15; // [rsp+274h] [rbp-7Ch]
char *v16; // [rsp+278h] [rbp-78h]
int v17; // [rsp+284h] [rbp-6Ch]
int v18; // [rsp+288h] [rbp-68h]
bool v19; // [rsp+28Fh] [rbp-61h]
char *v20; // [rsp+290h] [rbp-60h]
int v21; // [rsp+298h] [rbp-58h]
bool v22; // [rsp+29Fh] [rbp-51h]
__int64 v23; // [rsp+2A0h] [rbp-50h]
bool v24; // [rsp+2AFh] [rbp-41h]
__int64 v25; // [rsp+2B0h] [rbp-40h]
__int64 v26; // [rsp+2B8h] [rbp-38h]
__int64 v27; // [rsp+2C0h] [rbp-30h]
__int64 v28; // [rsp+2C8h] [rbp-28h]
int v29; // [rsp+2D0h] [rbp-20h]
int v30; // [rsp+2D4h] [rbp-1Ch]
char *v31; // [rsp+2D8h] [rbp-18h]
int v32; // [rsp+2E0h] [rbp-10h]
int v33; // [rsp+2E4h] [rbp-Ch]
bool v34; // [rsp+2EBh] [rbp-5h]

v10 = 0;
memset(s, 0, 0x30uLL);
memset(s1, 0, sizeof(s1));
printf("Input:");
v11 = s;
if ( dword_603058 >= 10 && ((((_BYTE)dword_603054 - 1) * (_BYTE)dword_603054) & 1) != 0 )
goto LABEL_43;
while ( 1 )
{
__isoc99_scanf("%s", v11);
v6 = 0;
if ( dword_603058 < 10 || ((((_BYTE)dword_603054 - 1) * (_BYTE)dword_603054) & 1) == 0 )
break;
LABEL_43:
__isoc99_scanf("%s", v11);
}
while ( 1 )
{
do
v12 = v6;
while ( dword_603058 >= 10 && ((((_BYTE)dword_603054 - 1) * (_BYTE)dword_603054) & 1) != 0 );
v13 = v12 < 64;
while ( dword_603058 >= 10 && ((((_BYTE)dword_603054 - 1) * (_BYTE)dword_603054) & 1) != 0 )
;
if ( !v13 )
break;
v14 = s[v6];
do
v15 = v14;
while ( dword_603058 >= 10 && ((((_BYTE)dword_603054 - 1) * (_BYTE)dword_603054) & 1) != 0 );
if ( v15 == 10 )
{
v16 = &s[v6];
*v16 = 0;
break;
}
v17 = v6 + 1;
do
v6 = v17;
while ( dword_603058 >= 10 && ((((_BYTE)dword_603054 - 1) * (_BYTE)dword_603054) & 1) != 0 );
}
for ( i = 0; ; ++i )
{
do
v18 = i;
while ( dword_603058 >= 10 && ((((_BYTE)dword_603054 - 1) * (_BYTE)dword_603054) & 1) != 0 );
do
v19 = v18 < 6;
while ( dword_603058 >= 10 && ((((_BYTE)dword_603054 - 1) * (_BYTE)dword_603054) & 1) != 0 );
if ( !v19 )
break;
do
v20 = s;
while ( dword_603058 >= 10 && ((((_BYTE)dword_603054 - 1) * (_BYTE)dword_603054) & 1) != 0 );
v4 = *(_QWORD *)&v20[8 * i];
v7 = 0;
while ( 1 )
{
v21 = v7;
do
v22 = v21 < 64;
while ( dword_603058 >= 10 && ((((_BYTE)dword_603054 - 1) * (_BYTE)dword_603054) & 1) != 0 );
if ( !v22 )
break;
v23 = v4;
v24 = v4 < 0;
if ( v4 >= 0 )
{
v27 = v4;
do
v28 = 2 * v27;
while ( dword_603058 >= 10 && ((((_BYTE)dword_603054 - 1) * (_BYTE)dword_603054) & 1) != 0 );
v4 = v28;
}
else
{
v25 = 2 * v4;
do
v26 = v25;
while ( dword_603058 >= 10 && ((((_BYTE)dword_603054 - 1) * (_BYTE)dword_603054) & 1) != 0 );
v4 = v26 ^ 0xB0004B7679FA26B3LL;
}
v29 = v7;
do
v7 = v29 + 1;
while ( dword_603058 >= 10 && ((((_BYTE)dword_603054 - 1) * (_BYTE)dword_603054) & 1) != 0 );
}
v30 = 8 * i;
v31 = &s1[8 * i];
if ( dword_603058 >= 10 && ((((_BYTE)dword_603054 - 1) * (_BYTE)dword_603054) & 1) != 0 )
LABEL_55:
*(_QWORD *)v31 = v4;
*(_QWORD *)v31 = v4;
if ( dword_603058 >= 10 && ((((_BYTE)dword_603054 - 1) * (_BYTE)dword_603054) & 1) != 0 )
goto LABEL_55;
v32 = i + 1;
}
do
v33 = memcmp(s1, &unk_402170, 0x30uLL);
while ( dword_603058 >= 10 && ((((_BYTE)dword_603054 - 1) * (_BYTE)dword_603054) & 1) != 0 );
v34 = v33 != 0;
while ( dword_603058 >= 10 && ((((_BYTE)dword_603054 - 1) * (_BYTE)dword_603054) & 1) != 0 )
;
if ( v34 )
puts("Wrong!");
else
puts("Correct!");
return v10;
}

发现还是有混淆

dword_603058 >= 10 && ((((_BYTE)dword_603054 - 1) * (_BYTE)dword_603054) & 1) != 0

基本都是while或if,根据逻辑判断,可推出该条件永真,是OLLVM的虚假控制流

接下来写个脚本将程序运行一下,将其去除。运行脚本在File栏里

st = 0x401121
end = 0x402144
def patch_nop(start, end):
for i in range(start, end):
PatchByte(i, 0x90) # 修改指令地址处 0x90 是最简单的1字节nop
def next_instr(addr):
return addr+ItemSize(addr) # ItemSize获取指令或数据长度,这个函数的作用就是去往下一条指令
addr = st
while(addr < end):
next = next_instr(addr)
if "dword_603058" in GetDisasm(addr): # GetDisasm(addr)得到addr的反汇编语句
while(True):
addr = next
next = next_instr(addr)
if "jnz" in GetDisasm(addr):
dest = GetOperandValue(addr, 0) # 得到操作数,就是指令后的数
PatchByte(addr, 0xe9)
PatchByte(addr+5, 0x90)
offset = dest - (addr + 5)
PatchDword(addr + 1, offset)
print("patch bcf: 0x%x" % addr)
addr = next
break
else:
addr = next

脚本好了,但跑不了,可能是某插件有问题,之后看看

逻辑清楚了,也可以手动去混淆

大概加密算法如下,是网上找的去混淆后的代码

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
signed __int64 v4; // [rsp+1E0h] [rbp-110h]
signed int j; // [rsp+1E8h] [rbp-108h]
signed int i; // [rsp+1ECh] [rbp-104h]
signed int k; // [rsp+1ECh] [rbp-104h]
char s1[48]; // [rsp+1F0h] [rbp-100h]
char s[60]; // [rsp+220h] [rbp-D0h]
unsigned int v10; // [rsp+25Ch] [rbp-94h]
char *v11; // [rsp+260h] [rbp-90h]
int v12; // [rsp+26Ch] [rbp-84h]
bool v13; // [rsp+272h] [rbp-7Eh]
unsigned __int8 v14; // [rsp+273h] [rbp-7Dh]
int v15; // [rsp+274h] [rbp-7Ch]
char *v16; // [rsp+278h] [rbp-78h]
int v17; // [rsp+284h] [rbp-6Ch]
int v18; // [rsp+288h] [rbp-68h]
bool v19; // [rsp+28Fh] [rbp-61h]
char *v20; // [rsp+290h] [rbp-60h]
int v21; // [rsp+298h] [rbp-58h]
bool v22; // [rsp+29Fh] [rbp-51h]
__int64 v23; // [rsp+2A0h] [rbp-50h]
bool v24; // [rsp+2AFh] [rbp-41h]
__int64 v25; // [rsp+2B0h] [rbp-40h]
__int64 v26; // [rsp+2B8h] [rbp-38h]
__int64 v27; // [rsp+2C0h] [rbp-30h]
__int64 v28; // [rsp+2C8h] [rbp-28h]
int v29; // [rsp+2D0h] [rbp-20h]
int v30; // [rsp+2D4h] [rbp-1Ch]
char *v31; // [rsp+2D8h] [rbp-18h]
int v32; // [rsp+2E0h] [rbp-10h]
int v33; // [rsp+2E4h] [rbp-Ch]
bool v34; // [rsp+2EBh] [rbp-5h]

v10 = 0;
memset(s, 0, 0x30uLL);
memset(s1, 0, 0x30uLL);
printf("Input:", 0LL);
v11 = s;
__isoc99_scanf("%s", s, (dword_603054 - 1), 3788079310LL);
for ( i = 0; ; ++i )
{
v12 = i;
v13 = i < 64;
if ( i >= 64 )
break;
v14 = s[i];
v15 = v14;
if ( v14 == 10 )
{
v16 = &s[i];
*v16 = 0;
break;
}
v17 = i + 1;
}
for ( j = 0; ; ++j )
{
v18 = j;
v19 = j < 6;
if ( j >= 6 )
break;
v20 = s;
v4 = *&s[8 * j];
for ( k = 0; ; ++k )
{
v21 = k;
v22 = k < 64;
if ( k >= 64 )
break;
v23 = v4;
v24 = v4 < 0;
if ( v4 >= 0 )
{
v27 = v4;
v28 = 2 * v4;
v4 *= 2LL;
}
else
{
v25 = 2 * v4;
v26 = 2 * v4;
v4 = 2 * v4 ^ 0xB0004B7679FA26B3LL;
}
v29 = k;
}
v30 = 8 * j;
v31 = &s1[8 * j];
*v31 = v4;
v32 = j + 1;
}
v33 = memcmp(s1, &unk_402170, 0x30uLL);
v34 = v33 != 0;
if ( v33 != 0 )
puts("Wrong!");
else
puts("Correct!");
return v10;
}

如果足够了解密码,实际上这部分代码使用的是CRC32的查表法,对数据进行加密

加密原理实际上就是CRC32算法—输入一组长度48的字符串,每8个字节分为1组,共6组。对每一组取首位,判断正负。正值,左移一位;负值,左移一位,再异或0xB0004B7679FA26B3。重复判断操作64次,得到查表法所用的表。

secret = [0xBC8FF26D43536296, 0x520100780530EE16, 0x4DC0B5EA935F08EC,
0x342B90AFD853F450, 0x8B250EBCAA2C3681, 0x55759F81A2C68AE4]
key = 0xB0004B7679FA26B3

flag = ""

# 产生CRC32查表法所用的表
for s in secret:
for i in range(64):
sign = s & 1
# 判断是否为负
if sign == 1:
s ^= key
s //= 2
# 防止负值除2,溢出为正值
if sign == 1:
s |= 0x8000000000000000
# 输出表
print(hex(s))
# 计算CRC64
j = 0
while j < 8:
flag += chr(s&0xFF)
s >>= 8
j += 1
print(flag)
# flag{6ff29390-6c20-4c56-ba70-a95758e3d1f8}

别的解法(不使用IDA_Python)

IDA_Python用不了,自己不会写,上面的脚本是别人写好的。

试试不用脚本跑自己手动分析

将加密的逻辑记录下

v12 = 0
if(v12<0x40):
v4 = v28
if(v4 >= 0):
v28 = v28 * 2
else:
v28 = v28 * 2
v28 = v28 ^ 0xB0004B7679FA26B3
v12 += 1

参考链接

利用符号执行去除控制流平坦化