[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; int i; int v6; int v7; char s1[48 ]; char s[60 ]; unsigned int v10; char *v11; int v12; bool v13; unsigned __int8 v14; int v15; char *v16; int v17; int v18; bool v19; char *v20; int v21; bool v22; __int64 v23; bool v24; __int64 v25; __int64 v26; __int64 v27; __int64 v28; int v29; int v30; char *v31; int v32; int v33; bool v34; v10 = 0 ; memset (s, 0 , 0x30 uLL); 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 ^ 0xB0004B7679FA26B3 LL; } 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, 0x30 uLL); 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 ) def next_instr (addr ): return addr+ItemSize(addr) addr = st while (addr < end): next = next_instr(addr) if "dword_603058" in GetDisasm(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; signed int j; signed int i; signed int k; char s1[48 ]; char s[60 ]; unsigned int v10; char *v11; int v12; bool v13; unsigned __int8 v14; int v15; char *v16; int v17; int v18; bool v19; char *v20; int v21; bool v22; __int64 v23; bool v24; __int64 v25; __int64 v26; __int64 v27; __int64 v28; int v29; int v30; char *v31; int v32; int v33; bool v34; v10 = 0 ; memset (s, 0 , 0x30 uLL); memset (s1, 0 , 0x30 uLL); 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 ^ 0xB0004B7679FA26B3 LL; } v29 = k; } v30 = 8 * j; v31 = &s1[8 * j]; *v31 = v4; v32 = j + 1 ; } v33 = memcmp (s1, &unk_402170, 0x30 uLL); 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 = "" for s in secret: for i in range (64 ): sign = s & 1 if sign == 1 : s ^= key s //= 2 if sign == 1 : s |= 0x8000000000000000 print (hex (s)) j = 0 while j < 8 : flag += chr (s&0xFF ) s >>= 8 j += 1 print (flag)
别的解法(不使用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
参考链接 利用符号执行去除控制流平坦化