好友
阅读权限30
听众
最后登录1970-1-1
|
本帖最后由 skywilling 于 2018-2-25 11:07 编辑
0x00前言
从难度上来看,这道题目是这次比赛逆向题目中最难的一个了(当然,这也是相对的,对于入门级的人来说,是一个比较大的挑战了,而对于赛场老手来说,经验丰富,姿势风骚,这种题目也不足为虑。在这里,本人作为一个入门级的菜鸟,当然是从入门级的角度来分析这道题目了)。为什么说这道题目是最难的呢?首先,这道题目涉及到AES(高级加密标准),而且还是经过修改的;其次,在分析题目过程中,有几个坑(难点)。所以说,与前几个题目相比,难度有所上升,毕竟是最后一道逆向题目,理所当然的就作为压轴题了。这道题目应该是我写的第三篇关于AES加解密的文章了,再次分析AES,对AES又有了新的不同的理解,这是一个循序渐进的过程。下面就开始具体的讲解。
0x01工具
俗话说,工欲善其事,必先利其器。找到趁手的工具很是重要,使用合适的工具,可以明显提高工作学习的效率。就这道题目来讲,这是一个ELF文件(我在之前无知的把它称作Linux PE,这显然是错误的,我在这里纠正一下),既然是ELF文件就需要在Linux系统中运行了,这时我们就需要使用虚拟机创建一个Linux系统了。首先我想到的是Kali系统,这是一个专门用来做网络渗透的Linux系统,里面集成了大量的工具,其中就有逆向工程的工具集。其中就有一个类似于OD的工具可以直接调试ELF文件,然后我就开始一段艰辛调试,然而使用起来不尽人如意,然后就想没有其他更好用的工具呢。最后我想到了IDA的调试功能,可谓十分强大,几乎涵盖了所有平台的调试功能,使用起来可谓畅快。最后我使用的工具主要是虚拟机创建的Ubuntu系统和IDA,我也大力推荐使用这两个工具。
0x02分析文件
ELF调试流程和调试PE差不多,首先要看一下ELF文件的一些属性,使用file命令即可,效果如下图:
我简单地解释一下这些文字的含义,首先看到 ELF 32-bit LSB executable 说明这是一个32位可执行ELF文件,接着是 Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32 是说编译ELF文件的平台、版本、链接方式(静态链接)、内核,最后是 BuildID[sha1]=9ae4ce241d1fbada4c2eff6134b74ed2444d305f, stripped 由BuildID[sha1],可见后面跟着的是ELF文件SHA1校验值,最后有个stripped,这个单词翻译过来就是抽取的意思,即抽取过。我简单说一下抽取过的含义,首先就是要清楚ELF文件的结构,和PE文件的结构也是差不多的,大概可分为三部分。第一部分就是文件头,主要存储一些文件的属性和校验值;第二部分是区块表,简单的说就是存放区块的数量、类型、名称、地址等信息;第三部分就是区块,就是各个区块存储的具体内容,主要是程序运行需要的代码和数据。有了这些基础知识,我们再来谈stripped,前面我也说了区块中主要是代码和数据,这其实是针对程序本身来说的,因为程序是代码和数据的集合。其实在编译完成后,程序中的区块中是含有debug信息和符号信息(这些信息是不会对程序的运行造成影响的),这是为了方便调试而保留的信息,那么抽取的含义就是将debug信息和符号信息从程序中剔除,这样做的目的主要是减少程序的体积(对用户来说)和增加逆向难度(对我们来说)。简单地说,单纯静态分析抽取过的ELF文件将是一场灾难。
0x03调试分析
调试之前观察程序运行结果是必须的,观察结果将为我们指明调试分析的方向。我们首先尝试几个输入:
可以看到在随机尝试了几次后,我们可以得出几条关键的信息,Key length error! 说明程序对输入进行了长度的检测,Wrong key! 说明了长度通过了程序的检测,Welcome to Gadgetzan!~和Show me your key: 说明输入点就在这两个字符串输出之后。我们掌握了这些信息就可以开始动静结合的分析调试了。
使用IDA是必须的,直接用IDA打开ELF文件,根据我们得到的信息,在Strings window 窗口查看:
对我们有用的字符串我都用红框标出来了,我们直接跟踪 Show me your key: 找到程序输入点
然后F5查看伪代码
[C] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | int __cdecl sub_80484B0( int a1)
{
int v1;
int result;
char v3;
int v4;
int v5;
int v6;
int v7;
char v8;
unsigned int v9;
int *v10;
v10 = &a1;
v9 = __readgsdword(0x14u);
sub_80515D0(( int ) "Welcome to Gadgetzan!~" );
sub_8071770(1, ( int ) "Show me your key:" , v3);
v4 = 0;
v5 = 0;
v6 = 0;
v7 = 0;
v8 = 0;
sub_8051000(( int ) "%16s" , &v4);
if ( sub_805D820(&v4) == 16 )
{
sub_804A310(&v4);
sub_804A5A0(( int )&v4);
}
else
{
sub_80515D0(( int ) "Key length error!" );
}
result = 0;
if ( __readgsdword(0x14u) != v9 )
sub_8071890(v1);
return result;
}
|
往下看,就会发现v4就是我们的input,也就是说sub_8051000调用用了输入的方法,紧跟着是一个if条件判断语句,可以判断sub_805D820(&v4)就是计算输入字符串的长度,进而,程序接收的字符串长度必须是16。这里我们为了方便分析可以将v4更名为input:
接下来我们主要分析上面的两个函数。首先是sub_804A310(&input),为了方便分析我同样修改了一些变量的名称
[C] 纯文本查看 复制代码 001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 | int __cdecl sub_804A310( int *input)
{
int input_1;
int v2;
int v3;
bool v4;
int v5;
unsigned int v6;
int input_2;
int v9;
int v10;
int v11;
int v12;
int v13;
int v14;
int v15;
char v16;
char v17;
char v18;
char v19;
char v20;
char v21;
char v22;
char v23;
char v24;
char v25;
char v26;
char v27;
char v28;
char v29;
char v30;
char v31;
char v32;
char v33;
char v34;
char v35;
char v36;
char v37;
char v38;
char v39;
char v40;
char v41;
char v42;
char v43;
char v44;
char v45;
char v46;
char v47;
unsigned int v48;
v16 = 0xE6u;
v48 = __readgsdword(0x14u);
v17 = 0xB8u;
v18 = 0xA1u;
v19 = 0xE3u;
v20 = 0x80u;
v21 = 0x82u;
v22 = 0xE6u;
v23 = 0xB8u;
v24 = 0xA1u;
v25 = 0xE3u;
v26 = 0x80u;
v27 = 0x82u;
v28 = 0xE6u;
v29 = 0xB8u;
v30 = 0xA1u;
v31 = 0xE3u;
v32 = 0x80u;
v33 = 0x82u;
v34 = 0xE6u;
v35 = 0xB8u;
v36 = 0xA1u;
v37 = 0xE3u;
v38 = 0x80u;
v39 = 0x82u;
v40 = 0xE6u;
v41 = 0xB8u;
v42 = 0xA1u;
v43 = 0xE3u;
input_1 = *input;
v44 = 0x80u;
v45 = 0x82u;
v46 = 0xE6u;
input_2 = input_1;
v2 = input[1];
v47 = 0xB8u;
dword_80EFB24 = 8;
dword_80EFB28 = 14;
v9 = v2;
v10 = input[2];
v11 = input[3];
v3 = sub_805B3F0(60 * dword_80EE084);
sub_8049F00(( int )&v16, v3);
sub_804A1A0(&input_2, &v12, v3);
*input = v12;
input[1] = v13;
input[2] = v14;
v6 = __readgsdword(0x14u);
v5 = v6 ^ v48;
v4 = v6 == v48;
input[3] = v15;
if ( !v4 )
sub_8071890(v5);
return 0;
}
|
首先我们看到一个一系列的变量被赋值,从v16到v47,一共是32个,这也就是一个长度为32数组:
[C] 纯文本查看 复制代码 1 2 | init=[0xE6,0xB8,0xA1,0xE3,0x80,0x82,0xE6,0xB8,0xA1,0xE3,0x80,0x82,0xE6,0xB8,0xA1,
0xE3,0x80,0x82,0xE6,0xB8,0xA1,0xE3,0x80,0x82,0xE6,0xB8,0xA1,0xE3,0x80,0x82,0xE6,0xB8]
|
再往下,v3 = sub_805B3F0(60 * dword_80EE084),赋值v3是长度为(60*4)240的数组
再往下是,sub_8049F00((int)&v16, v3):
[C] 纯文本查看 复制代码 001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | int __cdecl sub_8049F00( int input, int output)
{
int input_1;
int v3;
int v4;
int v5;
int v6;
int v7;
unsigned __int8 v8;
char v9;
char v10;
int v11;
int *v12;
unsigned __int8 v13;
int v14;
char v15;
int result;
unsigned int v17;
int *i;
signed int v19;
int v20;
unsigned __int8 v21;
__int16 v22;
unsigned __int8 v23;
unsigned __int8 v24;
char v25[3];
unsigned int v26;
v26 = __readgsdword(0x14u);
input_1 = input;
v23 = dword_80EE084 * (dword_80EFB28 + 1);
v19 = dword_80EFB24;
if ( dword_80EFB24 > 0 )
{
v3 = 0;
v4 = 0;
do
{
v5 = 4 * v3;
++v4;
*(_BYTE *)(output + 4 * v3) = *(_BYTE *)(input + 4 * v3);
*(_BYTE *)(output + v5 + 1) = *(_BYTE *)(input + 4 * v3 + 1);
*(_BYTE *)(output + v5 + 2) = *(_BYTE *)(input + 4 * v3 + 2);
*(_BYTE *)(output + v5 + 3) = *(_BYTE *)(input + 4 * v3 + 3);
v3 = (unsigned __int8 )v4;
}
while ( (unsigned __int8 )v4 < dword_80EFB24 );
v19 = dword_80EFB24;
}
v21 = v19;
if ( v23 > (unsigned __int8 )v19 )
{
while ( 1 )
{
v6 = v21;
v7 = 4 * (v21 - 1);
v8 = *(_BYTE *)(output + v7);
v9 = *(_BYTE *)(output + v7 + 3);
v10 = *(_BYTE *)(output + v7 + 2);
input_1 = *(unsigned __int8 *)(output + v7 + 1);
v24 = *(_BYTE *)(output + v7);
HIBYTE(v22) = v9;
v25[2] = v9;
LOBYTE(v22) = v10;
v25[1] = v10;
v25[0] = input_1;
v20 = v19;
v11 = v21 % v19;
if ( v11 )
{
if ( v19 > 6 && v11 == 4 )
{
for ( i = ( int *)&v24; ; v8 = *(_BYTE *)i )
{
i = ( int *)(( char *)i + 1);
*((_BYTE *)i - 1) = byte_80BDE60[16 * (v8 >> 4) + (v8 & 0xF)];
if ( i == ( int *)&v26 )
break ;
}
v8 = v24;
input_1 = (unsigned __int8 )v25[0];
v22 = *(_WORD *)&v25[1];
}
}
else
{
v24 = input_1;
v25[2] = v8;
v12 = ( int *)&v24;
*(_WORD *)v25 = v22;
while ( 1 )
{
v13 = input_1;
v14 = input_1 & 0xF;
v12 = ( int *)(( char *)v12 + 1);
*((_BYTE *)v12 - 1) = byte_80BDE60[16 * (v13 >> 4) + v14];
if ( &v26 == (unsigned int *)v12 )
break ;
LOBYTE(input_1) = *(_BYTE *)v12;
}
if ( (unsigned __int8 )(v21 / v19) == 1 )
{
byte_80EE080 = 1;
v15 = 1;
}
else
{
v15 = byte_80EE080;
if ( (unsigned __int8 )(v21 / v19) > 1u )
{
sub_80489A0(v14, v21 % v19);
v15 = byte_80EE080;
v20 = dword_80EFB24;
}
}
input_1 = (unsigned __int8 )v25[0];
v8 = v24 ^ v15;
LOBYTE(input_1) = byte_80EE081 ^ v25[0];
v22 = word_80EE082 ^ *(_WORD *)&v25[1];
*(_WORD *)&v25[1] ^= word_80EE082;
v24 = v8;
v25[0] ^= byte_80EE081;
}
++v21;
*(_BYTE *)(output + v7 + 4) = *(_BYTE *)(output + 4 * (v6 - v20)) ^ v8;
LOBYTE(input_1) = *(_BYTE *)(output + 4 * (v6 - dword_80EFB24) + 1) ^ input_1;
*(_BYTE *)(output + v7 + 5) = input_1;
*(_WORD *)(output + v7 + 6) = *(_WORD *)(output + 4 * (v6 - dword_80EFB24) + 2) ^ v22;
if ( v23 <= v21 )
break ;
v19 = dword_80EFB24;
}
}
v17 = __readgsdword(0x14u);
result = v17 ^ v26;
if ( v17 != v26 )
sub_8071890(input_1);
return result;
}
|
到这里就看的比较蒙逼了,所以就需要我们进行动态调试了,但是在这里我不讲如何进行动态调试,这里只讲解题的思路和大体步骤,动态调试技能需要你们自己学习。其实这块就是密钥的扩展,下面给出实现的代码:
[Python] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | def shiftup_4(temp):
i = temp[ 0 ]
temp[ 0 ] = temp[ 1 ]
temp[ 1 ] = temp[ 2 ]
temp[ 2 ] = temp[ 3 ]
temp[ 3 ] = i
def substitution(temp,s_box):
i = 0
while i< len (temp):
temp[i] = s_box[temp[i]]
i + = 1
def xor(temp,xor_temp):
i = 0
while i< len (temp):
temp[i]^ = xor_temp[i]
i + = 1
def func1(temp,init,s_box):
i = 8
while i< 60 :
for j in range ( 4 ):
temp[j]^ = init[ 32 * ( int (i / 8 ) - 1 ) + 4 * (i % 8 ) + j]
init.append(temp[j])
i + = 1
if i % 8 = = 0 :
shiftup_4(temp)
substitution(temp, s_box)
xor(temp, [ 2 * * ( int (i / 8 ) - 1 ), 0 , 0 , 0 ])
if i % 8 = = 4 :
substitution(temp,s_box)
init = [ 0xE6 , 0xB8 , 0xA1 , 0xE3 , 0x80 , 0x82 , 0xE6 , 0xB8 , 0xA1 , 0xE3 , 0x80 , 0x82 , 0xE6 , 0xB8 , 0xA1 ,
0xE3 , 0x80 , 0x82 , 0xE6 , 0xB8 , 0xA1 , 0xE3 , 0x80 , 0x82 , 0xE6 , 0xB8 , 0xA1 , 0xE3 , 0x80 , 0x82 , 0xE6 , 0xB8 ]
temp = init[ 28 : 32 ]
shiftup_4(temp)
substitution(temp, s_box)
xor(temp, [ 1 , 0 , 0 , 0 ])
func1(temp,init,s_box)
|
由于代码是我在调试的同时还原出来的,所以函数的命名规范和代码简洁程度都不堪入目,所以就将就的看一下,毕竟密钥扩展并不是我们的重点,而且步骤和方式一般都是固定的
让我们接着往下分析,sub_804A1A0(&input_2, &v12, v3),直接F5
[C] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | 0EE084;
v5 = alloca(4 * dword_80EE084);
v6 = &v18;
do
{
if ( v23 > 0 )
{
v7 = 0;
v8 = 0;
v22 = v6;
do
{
++v8;
*((_BYTE *)v22 + v7 + v3) = *(_BYTE *)(v21 + 4 * v7 + v4);
v7 = (unsigned __int8 )v8;
}
while ( v23 > (unsigned __int8 )v8 );
v6 = v22;
}
++v4;
v3 += v23;
}
while ( v4 != 4 );
v9 = 1;
v10 = 1;
sub_8049C60(v6, v20, 0);
if ( dword_80EFB28 > 1 )
{
do
{
++v10;
sub_8049E50(v6);
sub_8049DC0(v6);
sub_8049D00(v6);
sub_8049C60(v6, v20, v9);
sub_80495C0(v6);
v9 = (unsigned __int8 )v10;
}
while ( (unsigned __int8 )v10 < dword_80EFB28 );
}
v11 = 0;
sub_8049E50(v6);
sub_8049DC0(v6);
sub_8049C60(v6, v20, (unsigned __int8 )dword_80EFB28);
v13 = dword_80EE084;
v14 = v19;
v23 = ( signed int )v6;
do
{
if ( v13 > 0 )
{
v15 = 0;
v12 = 0;
do
{
++v12;
*(_BYTE *)(v14 + 4 * v15 + v11) = *(_BYTE *)(v23 + v15 + v11 * v13);
v13 = dword_80EE084;
v15 = (unsigned __int8 )v12;
}
while ( (unsigned __int8 )v12 < dword_80EE084 );
}
++v11;
}
while ( v11 != 4 );
v17 = __readgsdword(0x14u);
result = v17 ^ v24;
if ( v17 != v24 )
sub_8071890(v12);
return result;
}
|
这里就是AES加密的主要过程了,我同样给出实现的代码:
[Python] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 | temp = to_ord( input )
trans(temp)
xor_col(temp,key_groups, 1 )
i = 2
while i< = 0xE :
sub_byte(temp,s_box)
shift_row(temp)
mix_col(temp,s)
xor_col(temp,key_groups,i)
xor_key(temp,key)
i + = 1
sub_byte(temp,s_box)
shift_row(temp)
xor_col(temp,key_groups,i)
trans(temp)
|
说到这里就要说一下,这里的一个难点了,就是函数sub_80495C0中的sub_80492B0(v4)对应的是xor_key(),为什么说是难点呢?对比一下伪代码和还原出来的代码就知道了。
[C] 纯文本查看 复制代码 001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | unsigned int __cdecl sub_80492B0( int *a1)
{
int v1;
unsigned int result;
int v3;
int v4;
v1 = *a1;
result = *(unsigned __int8 *)*a1 - 12;
while ( 1 )
{
switch ( result )
{
case 0u:
sub_8048F40(a1, *( char *)(v1 + 1));
v3 = *a1;
goto LABEL_5;
case 8u:
v3 = v1 + 2;
if ( *(_DWORD *)(a1[1] + 28) == 1 )
goto LABEL_4;
goto LABEL_5;
case 9u:
sub_8048C90(( int )a1, *( char *)(v1 + 1), *( char *)(v1 + 2));
v3 = *a1;
goto LABEL_5;
case 0x1Eu:
v3 = v1 + 2;
if ( *(_DWORD *)(a1[1] + 28) <= 1u )
goto LABEL_4;
goto LABEL_5;
case 0x21u:
sub_8049060(( int )a1, *( char *)(v1 + 1), *( char *)(v1 + 2));
v3 = *a1;
goto LABEL_5;
case 0x25u:
sub_8048D60(a1, *( char *)(v1 + 1), *( char *)(v1 + 2));
v3 = *a1;
goto LABEL_5;
case 0x2Au:
v3 = v1 + 2;
if ( *(_DWORD *)(a1[1] + 28) == 2 )
goto LABEL_4;
goto LABEL_5;
case 0x5Bu:
sub_8048FF0(a1, *( char *)(v1 + 1));
v3 = *a1;
goto LABEL_5;
case 0x64u:
sub_8048E40(( int )a1, *( char *)(v1 + 1), *( char *)(v1 + 2));
v3 = *a1;
goto LABEL_5;
case 0x65u:
sub_8049260(a1, *( char *)(v1 + 1), 4);
goto LABEL_14;
case 0x67u:
v3 = v1 + 2;
if ( *(_DWORD *)(a1[1] + 28) )
goto LABEL_4;
goto LABEL_5;
case 0x77u:
sub_80490E0(a1, *( char *)(v1 + 1), *(_DWORD *)(v1 + 2), 1);
goto LABEL_10;
case 0x79u:
sub_8048C10(a1, *( char *)(v1 + 1), *( char *)(v1 + 2));
v3 = *a1;
goto LABEL_5;
case 0x7Au:
sub_8049260(a1, *( char *)(v1 + 1), 1);
goto LABEL_14;
case 0x7Eu:
sub_80490A0(a1, *( char *)(v1 + 1), *(_DWORD *)(v1 + 2), 1);
goto LABEL_12;
case 0x8Du:
sub_80490E0(a1, *( char *)(v1 + 1), *(_DWORD *)(v1 + 2), 4);
goto LABEL_10;
case 0x94u:
sub_8048F90(a1, *( char *)(v1 + 1));
v3 = *a1;
goto LABEL_5;
case 0x98u:
v3 = v1 + 2;
if ( *(_DWORD *)(a1[1] + 28) != 1 )
goto LABEL_4;
goto LABEL_5;
case 0x9Du:
sub_8048DC0(a1, *( char *)(v1 + 1), *( char *)(v1 + 2));
v3 = *a1;
goto LABEL_5;
case 0xA0u:
goto LABEL_4;
case 0xABu:
sub_8048CF0(a1, *( char *)(v1 + 1), *( char *)(v1 + 2));
v3 = *a1;
goto LABEL_5;
case 0xB5u:
sub_8048B90(a1, *( char *)(v1 + 1), *( char *)(v1 + 2));
v3 = *a1;
goto LABEL_5;
case 0xB7u:
sub_8048EC0(( int )a1, *( char *)(v1 + 1), *( char *)(v1 + 2));
v3 = *a1;
goto LABEL_5;
case 0xBFu:
sub_8049030(a1, *( char *)(v1 + 1));
v3 = *a1;
goto LABEL_5;
case 0xD9u:
v3 = v1 + 2;
a1[2] = *(_DWORD *)(v1 + 1);
goto LABEL_5;
case 0xDDu:
sub_80490E0(a1, *( char *)(v1 + 1), *(_DWORD *)(v1 + 2), 2);
LABEL_10:
v3 = *a1;
goto LABEL_5;
case 0xE6u:
sub_80490A0(a1, *( char *)(v1 + 1), *(_DWORD *)(v1 + 2), 2);
goto LABEL_12;
case 0xEDu:
sub_8049260(a1, *( char *)(v1 + 1), 2);
LABEL_14:
v3 = *a1;
goto LABEL_5;
case 0xEFu:
v3 = v1 + 2;
if ( !*(_DWORD *)(a1[1] + 28) )
LABEL_4:
v3 = v1 + *(_DWORD *)(v1 + 1) - 3;
goto LABEL_5;
case 0xF2u:
sub_80490A0(a1, *( char *)(v1 + 1), *(_DWORD *)(v1 + 2), 4);
LABEL_12:
v3 = *a1;
goto LABEL_5;
case 0xF3u:
return result;
default :
do
{
v3 = v1 - 2;
LABEL_5:
v1 = v3 + 3;
v4 = *(unsigned __int8 *)(v3 + 3);
*a1 = v1;
result = v4 - 12;
}
while ( result > 0xF3 );
break ;
}
}
}
|
恐怖的就是switch分支语句了,即使动态调试也是一脸懵逼,我就是被卡在这里的,所以参考了官方的writeup,毕竟是官方的人,一句话带过,就给出了代码:
[Python] 纯文本查看 复制代码 1 2 3 4 | def xor_key(temp,key):
for i in range ( 15 ):
temp[i] ^ = key[i]
temp[ 14 - i]^ = temp[ 15 - i]
|
很简单吧,三四行代码就解决了,其实标准的AES加密,在这个地方就仅仅是temp[index] ^= key[index],是没有后面的代码的,所以才说这是一个修改过的AES加密,而且S盒都换了新的,这也奠定了解密满是坑的基础。到这里AES就结束了,接下里就是判断结果了。
我们分析最后一个主要的函数sub_804A5A0((int)&input)
[C] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | int __cdecl sub_804A5A0( int input)
{
char v1;
int v2;
int v3;
signed int v4;
int v5;
int v6;
int v7;
__int16 *v8;
int v9;
unsigned int v10;
int v11;
int v12;
int v13;
__int16 v14;
unsigned __int8 v16;
unsigned __int16 i;
int v18;
int v19;
int v20;
unsigned __int8 *v21;
int v22;
v1 = 88;
v2 = sub_805BC30(256, 1);
v22 = v2;
v21 = (unsigned __int8 *)&unk_80BDE68;
v3 = v2;
v4 = 54;
v20 = 0;
while ( 1 )
{
v16 = v1;
v18 = *(unsigned __int8 *)(input + v20);
v5 = 0;
v19 = *(unsigned __int8 *)(input - v20 + 15);
while ( 1 )
{
*(_BYTE *)(v3 + v4) = (v18 >> v5) & 1;
v6 = v19 >> v5++;
*(_BYTE *)(v3 + v16) = v6 & 1;
if ( v5 == 8 )
break ;
v4 = (unsigned __int8 )byte_80BDE60[8 * v20 + v5];
v16 = v21[v5 + 120];
}
if ( ++v20 == 16 )
break ;
v4 = *v21;
v1 = v21[128];
v21 += 8;
}
v7 = v22;
v8 = ( __int16 *)&unk_80BDC42;
v9 = 0;
v10 = 0xFFFF8EBC;
for ( i = 0x86C1u; ; i = v14 )
{
v11 = 0;
v12 = 0;
while ( 1 )
{
v13 = *(unsigned __int8 *)(v7 + 4 * v11);
++v11;
v12 ^= v13 * v10;
if ( v11 == 16 )
break ;
v10 = (unsigned __int16 )word_80BDC60[v9 + v11];
}
if ( (_WORD)v12 != i )
break ;
++v7;
v9 += 16;
if ( v8 == word_80BDC60 )
{
sub_80515D0(( int ) "Good job! The flag is your input..." );
sub_804A470(v22);
sub_805B900(v22);
return 0;
}
v14 = *v8;
v10 = (unsigned __int16 )word_80BDC60[v9];
++v8;
}
sub_80515D0(( int ) "Wrong key!" );
return 0;
}
|
在这里我们终于看到了期待已久的 Good job! The flag is your input...,它就我们要的输出结果。给出还原代码:
[Python] 纯文本查看 复制代码 1 2 3 4 5 6 | def to_bin(temp,s_box,ans):
for i in range ( 16 ):
for j in range ( 8 ):
ans[s_box[i * 8 + j]] = (temp[i]>>j)& 1
ans[s_box[(i + 16 ) * 8 + j]] = (temp[ 15 - i]>>j)& 1
|
[Python] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 | def check(v8,data,ans):
for i in range ( 16 ):
final = v8[i * 2 ] + (v8[i * 2 + 1 ] << 8 )
k = 0
for j in range ( 16 ):
d = data[(i * 16 + j) * 2 ] + (data[(i * 16 + j) * 2 + 1 ]<< 8 )
k = k^(ans[j * 16 + i] * d)
if k! = final:
return 0
return 1
|
这里首先是将长度为16的数据转为长度为256(16*16)的二进制数据,然后再进行判断。
那么到这里加密的分析就结束了,下面说一下解密过程中的几个坑。
0x04解密
解密说白了就是加密步骤反过来执行。
(1)检测部分的解密
检测部分也就是check()函数了,通过分析这里只能使用暴力破解了(穷举),官方也是这么说的。通过对check()函数的理解,给出inv_check()爆破函数:
[Python] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 | def inv_check(v8,data,ans,guess):
for i in range ( 16 ):
final = v8[i * 2 ] + (v8[i * 2 + 1 ] << 8 )
for x in range ( 0x10000 ):
k = 0
for j in range ( 16 ):
d = data[(i * 16 + j) * 2 ] + (data[(i * 16 + j) * 2 + 1 ]<< 8 )
k^ = (d * guess[x * 16 + j])
if k = = final:
for j in range ( 16 ):
ans[j * 16 + i] = guess[x * 16 + j]
|
这里进行了65536中可能的猜测,有人会问怎么知道是65536种可能呢?其实不难看出,ans是16*16的二维二进制数组,每次循环都会使用一列,也就是16个,所以所有可能就是2^16(65536)种了。
(2)求逆S盒
其实只要知道了S盒和逆S盒的关系也就不难求出逆S盒了。给出官方给出的关系式子:
[C] 纯文本查看 复制代码 1 2 | v = s_box[16*i+j]
i+16*j == inv_s_box[16*(v&0xf)+v/0x10]
|
(3)求列混淆的逆参数
由于在列混淆方面,这道题目也是用了非标准的参数,所以就需要我们来求解。我想到方法还是暴力破解。
[Python] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 | def get_inv_s(s):
u = [ 0 for x in range ( 16 )]
x = [ 1 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 1 ]
for i in range ( 4 ):
for j in range ( 4 ):
u[( 3 - j) * 4 + i] = s[(( 3 - j) - i + 4 ) % 4 ]
for a in range ( 0x10 ):
for b in range ( 0x10 ):
for c in range ( 0x10 ):
for d in range ( 0x10 ):
k = u[:]
mix_col(k,[a,b,c,d])
n = 0
for i in range ( 16 ):
if (k[i] = = x[i]):
n + = 1
if n = = 16 :
return [a,b,c,d]
return None
|
其实这里还是有一个问题的,比如如何确认参数的范围
这是正向列混淆的参数,可以看出这几个参数都是小于16的,猜想逆向参数也是如此。其实在不确定的情况下,我们完全可以认为都是小于0x100的,这是因为这些参数是在GF(2^8)这个有限域中的,所以小于0x100是完全可以确定的,然而问题来了,如果我要穷举所有可能,我需要进行超过1600万次的猜测,这是一个庞大的数据。
大概运行完,需要很长一段时间,这是难以接受的,期待大神的解答。
0x05成功运行
成功运行后的效果,如下图
后面还打印了一个二维码
我是没有扫出来,可能是我的屏幕不够大,官方给出了二维码的结果
附件:https://pan.baidu.com/s/1jKek3Ng 密码:9rlr
版权声明:允许转载,但是一定要注明出处。 |
免费评分
-
查看全部评分
|