吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 20908|回复: 62
收起左侧

[原创] 【又见AES】第十届全国大学生信息安全大赛之逆向--欢迎来到加基森

  [复制链接]
skywilling 发表于 2018-1-10 18:27
本帖最后由 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命令即可,效果如下图:
0.png
我简单地解释一下这些文字的含义,首先看到 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调试分析
调试之前观察程序运行结果是必须的,观察结果将为我们指明调试分析的方向。我们首先尝试几个输入:
1.png
可以看到在随机尝试了几次后,我们可以得出几条关键的信息,Key length error! 说明程序对输入进行了长度的检测,Wrong key! 说明了长度通过了程序的检测,Welcome to Gadgetzan!~和Show me your key: 说明输入点就在这两个字符串输出之后。我们掌握了这些信息就可以开始动静结合的分析调试了。
使用IDA是必须的,直接用IDA打开ELF文件,根据我们得到的信息,在Strings window 窗口查看:
2.png
对我们有用的字符串我都用红框标出来了,我们直接跟踪
Show me your key: 找到程序输入点
3.png
然后F5查看伪代码
[C] 纯文本查看 复制代码
int __cdecl sub_80484B0(int a1)
{
  int v1; // ecx
  int result; // eax
  char v3; // [esp-12h] [ebp-30h]
  int v4; // [esp+1h] [ebp-1Dh]
  int v5; // [esp+5h] [ebp-19h]
  int v6; // [esp+9h] [ebp-15h]
  int v7; // [esp+Dh] [ebp-11h]
  char v8; // [esp+11h] [ebp-Dh]
  unsigned int v9; // [esp+12h] [ebp-Ch]
  int *v10; // [esp+16h] [ebp-8h]

  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;
}

4.png
往下看,就会发现v4就是我们的input,也就是说sub_8051000调用用了输入的方法,紧跟着是一个if条件判断语句,可以判断sub_805D820(&v4)就是计算输入字符串的长度,进而,程序接收的字符串长度必须是16。这里我们为了方便分析可以将v4更名为input:
5.png
接下来我们主要分析上面的两个函数。首先是sub_804A310(&input),为了方便分析我同样修改了一些变量的名称
[C] 纯文本查看 复制代码
int __cdecl sub_804A310(int *input)
{
  int input_1; // eax
  int v2; // eax
  int v3; // esi
  bool v4; // zf
  int v5; // ecx
  unsigned int v6; // et1
  int input_2; // [esp+18h] [ebp-50h]
  int v9; // [esp+1Ch] [ebp-4Ch]
  int v10; // [esp+20h] [ebp-48h]
  int v11; // [esp+24h] [ebp-44h]
  int v12; // [esp+28h] [ebp-40h]
  int v13; // [esp+2Ch] [ebp-3Ch]
  int v14; // [esp+30h] [ebp-38h]
  int v15; // [esp+34h] [ebp-34h]
  char v16; // [esp+38h] [ebp-30h]
  char v17; // [esp+39h] [ebp-2Fh]
  char v18; // [esp+3Ah] [ebp-2Eh]
  char v19; // [esp+3Bh] [ebp-2Dh]
  char v20; // [esp+3Ch] [ebp-2Ch]
  char v21; // [esp+3Dh] [ebp-2Bh]
  char v22; // [esp+3Eh] [ebp-2Ah]
  char v23; // [esp+3Fh] [ebp-29h]
  char v24; // [esp+40h] [ebp-28h]
  char v25; // [esp+41h] [ebp-27h]
  char v26; // [esp+42h] [ebp-26h]
  char v27; // [esp+43h] [ebp-25h]
  char v28; // [esp+44h] [ebp-24h]
  char v29; // [esp+45h] [ebp-23h]
  char v30; // [esp+46h] [ebp-22h]
  char v31; // [esp+47h] [ebp-21h]
  char v32; // [esp+48h] [ebp-20h]
  char v33; // [esp+49h] [ebp-1Fh]
  char v34; // [esp+4Ah] [ebp-1Eh]
  char v35; // [esp+4Bh] [ebp-1Dh]
  char v36; // [esp+4Ch] [ebp-1Ch]
  char v37; // [esp+4Dh] [ebp-1Bh]
  char v38; // [esp+4Eh] [ebp-1Ah]
  char v39; // [esp+4Fh] [ebp-19h]
  char v40; // [esp+50h] [ebp-18h]
  char v41; // [esp+51h] [ebp-17h]
  char v42; // [esp+52h] [ebp-16h]
  char v43; // [esp+53h] [ebp-15h]
  char v44; // [esp+54h] [ebp-14h]
  char v45; // [esp+55h] [ebp-13h]
  char v46; // [esp+56h] [ebp-12h]
  char v47; // [esp+57h] [ebp-11h]
  unsigned int v48; // [esp+58h] [ebp-10h]

  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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
int __cdecl sub_8049F00(int input, int output)
{
  int input_1; // ecx
  int v3; // eax
  int v4; // esi
  int v5; // edx
  int v6; // ebp
  int v7; // esi
  unsigned __int8 v8; // bl
  char v9; // dl
  char v10; // al
  int v11; // edx
  int *v12; // edx
  unsigned __int8 v13; // al
  int v14; // ecx
  char v15; // bl
  int result; // eax
  unsigned int v17; // et1
  int *i; // edx
  signed int v19; // [esp+4h] [ebp-38h]
  int v20; // [esp+8h] [ebp-34h]
  unsigned __int8 v21; // [esp+Ch] [ebp-30h]
  __int16 v22; // [esp+Dh] [ebp-2Fh]
  unsigned __int8 v23; // [esp+Fh] [ebp-2Dh]
  unsigned __int8 v24; // [esp+18h] [ebp-24h]
  char v25[3]; // [esp+19h] [ebp-23h]
  unsigned int v26; // [esp+1Ch] [ebp-20h]

  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] 纯文本查看 复制代码
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
            #print("=======%X======" % i)
            if i%8==0:
                  shiftup_4(temp)
                  substitution(temp, s_box)
                  #printf(temp,4)
                  xor(temp, [2**(int(i/8)-1), 0, 0, 0])
            if i%8==4:
                  substitution(temp,s_box)
            #printf(init[32:],len(init[32:]))
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
 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] 纯文本查看 复制代码
unsigned int __cdecl sub_80492B0(int *a1)
{
  int v1; // edx
  unsigned int result; // eax
  int v3; // eax
  int v4; // eax

  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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
int __cdecl sub_804A5A0(int input)
{
  char v1; // bl
  int v2; // eax
  int v3; // esi
  signed int v4; // eax
  int v5; // ecx
  int v6; // eax
  int v7; // esi
  __int16 *v8; // ebp
  int v9; // edi
  unsigned int v10; // edx
  int v11; // ecx
  int v12; // ebx
  int v13; // eax
  __int16 v14; // ax
  unsigned __int8 v16; // [esp+16h] [ebp-36h]
  unsigned __int16 i; // [esp+16h] [ebp-36h]
  int v18; // [esp+18h] [ebp-34h]
  int v19; // [esp+1Ch] [ebp-30h]
  int v20; // [esp+20h] [ebp-2Ch]
  unsigned __int8 *v21; // [esp+24h] [ebp-28h]
  int v22; // [esp+2Ch] [ebp-20h]

  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] 纯文本查看 复制代码
#转为二进制
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] 纯文本查看 复制代码
#检测结果
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] 纯文本查看 复制代码
#猜测得到ans
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] 纯文本查看 复制代码
v = s_box[16*i+j]
i+16*j == inv_s_box[16*(v&0xf)+v/0x10]

(3)求列混淆的逆参数
由于在列混淆方面,这道题目也是用了非标准的参数,所以就需要我们来求解。我想到方法还是暴力破解。
[Python] 纯文本查看 复制代码
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

其实这里还是有一个问题的,比如如何确认参数的范围
[C] 纯文本查看 复制代码
s = [0xD, 0xE, 0xF, 0xD]

这是正向列混淆的参数,可以看出这几个参数都是小于16的,猜想逆向参数也是如此。其实在不确定的情况下,我们完全可以认为都是小于0x100的,这是因为这些参数是在GF(2^8)这个有限域中的,所以小于0x100是完全可以确定的,然而问题来了,如果我要穷举所有可能,我需要进行超过1600万次的猜测,这是一个庞大的数据。
6.png
大概运行完,需要很长一段时间,这是难以接受的,期待大神的解答。
0x05成功运行
成功运行后的效果,如下图
7.png
后面还打印了一个二维码
20180109221822.png
我是没有扫出来,可能是我的屏幕不够大,官方给出了二维码的结果
8.png
附件:https://pan.baidu.com/s/1jKek3Ng 密码:9rlr

版权声明:允许转载,但是一定要注明出处

点评

感谢原创  发表于 2018-1-22 20:36

免费评分

参与人数 30吾爱币 +34 热心值 +29 收起 理由
pooler + 1 + 1 谢谢@Thanks!
camila + 1 我很赞同!
HaoChen + 1 + 1 谢谢@Thanks!
siuhoapdou + 1 + 1 谢谢@Thanks!
土川鼠 + 1 + 1 用心讨论,共获提升!
zjls9933 + 1 + 1 我很赞同!
潇潇麦蒂 + 1 + 1 谢谢@Thanks!
sunnylds7 + 1 + 1 谢谢@Thanks!
gxkyrftx + 1 + 1 用心讨论,共获提升!
chuang2015 + 1 + 1 我很赞同!
Hackerpro + 1 + 1 谢谢@Thanks!
其实什么都没有 + 1 + 1 谢谢@Thanks!
Liamemp + 1 谢谢@Thanks!
HW606 + 1 + 1 热心回复!
吾要开始学习ing + 1 + 1 我很赞同!
懒癌萌妹子 + 1 + 1 感谢您的宝贵建议,我们会努力争取做得更好!
Tomatoman + 1 + 1 用心讨论,共获提升!
鸡儿在学习 + 1 + 1 谢谢@Thanks!
no_one + 1 + 1 谢谢@Thanks!
小俊 + 2 + 1 谢谢@Thanks!
Allyn0303 + 1 + 1 我很赞同!
象相合 + 1 + 1 用心讨论,共获提升!
Dachun + 1 + 1 我很赞同!
Dfeng + 1 + 1 用心讨论,共获提升!
执子之手丶 + 1 + 1 二维码我也扫不出来,很难受
尾叶 + 1 + 1 我很赞同!
610100 + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
cr4ck + 1 + 1 用心讨论,共获提升!
zbnysjwsnd8 + 3 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
haoqwenie + 2 + 1 热心回复!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| skywilling 发表于 2018-1-25 20:32
veritas501 发表于 2018-1-25 18:58
MixColumn里的s不是爆破解的,我当时写过博客,你可以参考一下.

http://veritas501.space/2017/07/11/2017 ...

看了一下,博客写的不错。重点看了一下你写的列混淆和字节码解释器,发现你使用了matlab,因为我不懂matlab所以只能用笨办法了。在我分析过程中没有看明白这个switch条件分支语句,看了你的分析豁然开朗,十分感谢
veritas501 发表于 2018-1-25 18:58
MixColumn里的s不是爆破解的,我当时写过博客,你可以参考一下.

http://veritas501.space/2017/07/11/2017%E5%85%A8%E5%9B%BD%E5%A4%A7%E5%AD%A6%E7%94%9F%E4%BF%A1%E6%81%AF%E5%AE%89%E5%85%A8%E7%AB%9E%E8%B5%9B_re450_Gadgetzan/
梦想成为大牛 发表于 2018-1-10 19:06
MXWXZ 发表于 2018-1-10 19:12
1600W的数据还是不要用Py了,翻译成C的话顶多一个小时就能跑出来。
或者我上次爆破一条AES就临时开了台最高配的融合计算花了50G内存和半小时强行跑出来了23333(标准解法就是翻译成C,我的低压U上大概4G内存和1小时搞定)

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
610100 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

 楼主| skywilling 发表于 2018-1-10 19:16
MXWXZ 发表于 2018-1-10 19:12
1600W的数据还是不要用Py了,翻译成C的话顶多一个小时就能跑出来。
或者我上次爆破一条AES就临时开了台最 ...

我是感觉跑这是没有必要的
清风园丁 发表于 2018-1-10 19:39
真的牛啊!!!!!!!!
qqju 发表于 2018-1-10 19:46
真的牛啊!
sakura1997 发表于 2018-1-10 19:52
要么加入污手党,要么滚出加基森
zbnysjwsnd8 发表于 2018-1-10 21:10
支持一下,很厉害,学习了!
Aug.LuKai 发表于 2018-1-10 21:19
楼主,这个大赛还在进行吗?如果结束了,能说下这些大学生大赛的在哪里参赛吗?还有开赛时间!
 楼主| skywilling 发表于 2018-1-10 22:16
Aug.LuKai 发表于 2018-1-10 21:19
楼主,这个大赛还在进行吗?如果结束了,能说下这些大学生大赛的在哪里参赛吗?还有开赛时间!

大概是每年的六七月份
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-22 16:10

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表