2019 湖湘杯 Reverse WP

0x01 arguement

下载链接:https://www.lanzous.com/i7atyhc

1.准备

获取到信息:

  1. 32位的文件
  2. upx加密文件

 

在控制台打开文件

 

使用"upx -d reverse.exe"解密

 

2.IDA打开

直接在String中,找到引用"Input Your Flag:"的位置

int __usercall sub_415280@<eax>(int xmm0_4_0@<xmm0>, int a1, int a2)
{
  int v3; // edx
  FILE *v4; // eax
  int v5; // edx
  int v6; // ecx
  char *v7; // eax
  int v8; // edx
  int v9; // ecx
  int v10; // ST04_4
  int v11; // ST08_4
  char v13; // [esp+0h] [ebp-134h]
  char Buf; // [esp+D0h] [ebp-64h]
  size_t i; // [esp+100h] [ebp-34h]
  FILE *File; // [esp+10Ch] [ebp-28h]
  char v17; // [esp+118h] [ebp-1Ch]
  int v18; // [esp+130h] [ebp-4h]
  int savedregs; // [esp+134h] [ebp+0h]


  sub_411208((int)&unk_41C008);
  printf("Input Your Flag:\n", v13);
  sub_41137F("%19s", (unsigned int)&v17);       // v12大小为24
  if ( a1 != 2 )
  {
    printf("Input error!\n", v13);
    exit(1);
  }
  printf("%s\n", *(_DWORD *)(a2 + 4));          // 在执行文件时,执行的第一个参数
  for ( i = 0; i < j_strlen(*(const char **)(a2 + 4)); ++i )// 遍历执行文件时输入的字符串
    *(_BYTE *)(*(_DWORD *)(a2 + 4) + i) += i;   // 每一位 str[i] += i
  if ( !j_strcmp(Str1, *(const char **)(a2 + 4)) )// 和“fmcj2y~{”比较
  {
    v4 = fopen(*(const char **)(a2 + 4), "r");  // 下面两句文件操作,应该是获取输入的
    File = (FILE *)sub_411212(v6, v5, &v13 == &v13, (int)v4, xmm0_4_0);
    if ( File )
    {
      v7 = fgets(&Buf, 40, File);
      sub_411212(v9, v8, &v13 == &v13, (int)v7, xmm0_4_0);
      if ( j_strlen(&Buf) != 32 || j_strlen(&Buf) % 2 == 1 )// 输入长度应该是32
        exit(1);
      sub_4113B1(xmm0_4_0, &Buf, (int)&unk_41A4E0);
      if ( sub_4113B6(xmm0_4_0, (int)&unk_41A4E0) )
        printf("flag{%s}", (unsigned int)&Buf);
      else
        printf("Input Error!\n", v13);
    }
    else
    {
      printf("Input Error!\n", v13);
    }
  }
  else
  {
    printf("Input Error!\n", v13);
  }
  sub_411235(&savedregs, dword_4154C4, 0, v3);
  return sub_411212((unsigned int)&savedregs ^ v18, v11, 1, v10, xmm0_4_0);
}

因此在运行时文件我们要在后面带一个参数,这个参数通过分析代码,我们可以写个脚本解出

str1 = 'fmcj2y~{'

str2 = ''

for i in range(len(str1)):
    str2 += chr(ord(str1[i]) - i)

print (str2

 

3.代码分析

通过分析,可以知道关键的函数是sub_4113B1sub_4113B6

3.1 sub_4113B1函数

int __usercall sub_414E50@<eax>(int a1@<xmm0>, char *Str, int a3)
{
  unsigned int v3; // eax
  int v4; // edx
  int v5; // ecx
  char v7; // [esp+0h] [ebp-E4h]
  int v8; // [esp+D0h] [ebp-14h]
  signed int i; // [esp+DCh] [ebp-8h]


  sub_411208((int)&unk_41C008);
  dword_41A078[8] = 0xA7;
  dword_41A078[9] = 0xDE;
  dword_41A078[10] = 0xDA;
  dword_41A078[11] = 0x46;
  dword_41A078[12] = 0xAB;
  dword_41A078[13] = 0x2E;
  dword_41A078[14] = 0xFF;
  dword_41A078[15] = 0xDB;
  for ( i = 0; ; i += 2 )                       // 0开始,2跳
  {
    v3 = j_strlen(Str);                         // v3为Buf长度32
    if ( i >= v3 )
      break;
    if ( Str[i] >= 48 && Str[i] <= 57 )         // '0'~'9'
    {
      *(_DWORD *)(a3 + 4 * (i / 2)) = Str[i] - 48;// 字符转换为整型
    }
    else
    {
      if ( Str[i] < 97 || Str[i] > 102 )        // 非'a'~'f',说明组成的字符是'0'~'9'||'a'~'f',十六进制啊
      {
        printf("Input Error!\n", v7);
        exit(0);
      }
      *(_DWORD *)(a3 + 4 * (i / 2)) = Str[i] - 87;// 十六进制啊!'a'~'f'就是10~15
    }
    *(_DWORD *)(a3 + 4 * (i / 2)) *= 16;        // 最后还要乘16
    if ( Str[i + 1] >= 48 && Str[i + 1] <= 57 ) // i=1,3,5...如果为数字
    {
      v8 = Str[i + 1] - 48;                     // v8=字符转数字
    }
    else
    {
      if ( Str[i + 1] < 97 || Str[i + 1] > 102 )
      {
        printf("Input Error!\n", v7);
        exit(0);
      }
      v8 = Str[i + 1] - 87;                     // i=1,3,5...如果是'a'~'f'字符转数字,10~15
    }
    *(_DWORD *)(a3 + 4 * (i / 2)) += v8;        // a3[i]+=v8
  }
  return sub_411212(v5, v4, 1, v3, a1);
}

这个函数有两个作用:

  1. 对dword_41A078后8双字节赋值
  2. 将输入的字符串,两两字符转换为数字,存入v4,例如:输入"a1b2c3"将会转换为0xa1b2c3

 

3.2 sub_4113B6函数

int __usercall sub_411D90@<eax>(int a1@<xmm0>, int a2)
{
  int v2; // edx
  int v3; // ecx
  int v4; // eax
  signed int i; // [esp+D0h] [ebp-8h]


  sub_411208((int)&unk_41C008);
  for ( i = 0; i < 16; ++i )
  {
    v3 = a2;                                    // v3赋值为之前得到的整数
    v2 = *(_DWORD *)(a2 + 4 * i) + 1;           // v2=v2[i]+1
    if ( v2 != dword_41A078[i] )
    {
      v4 = 0;
      return sub_411212(v3, v2, 1, v4, a1);
    }
  }
  v4 = 1;
  return sub_411212(v3, v2, 1, v4, a1);
}

这个函数将我们转换输入字符串得到的v4。每一位加1之后与已知的dword_41A078数组比较。

dword_41A078

50h 0C6h 0F1h 0E4h 0E3h 0E2h 9Ah 0A1h 0A7h 0DEh 0DAh 46h 0ABh 2Eh 0FFh 0DBh

因此我们只要将dword_41A078每一位减1,就能得到v4,从而输入的Buf也得到了了

v4

4fc5f0e3e2e199a0a6ddd945aa2dfeda

Buf

"4fc5f0e3e2e199a0a6ddd945aa2dfeda"

而回到主函数

if ( sub_4113B6(xmm0_4_0, (int)&unk_41A4E0) )
    printf("flag{%s}", (unsigned int)&Buf);

flag就是flag{Buf}

4.get flag!

flag{4fc5f0e3e2e199a0a6ddd945aa2dfeda}

 

0x02 EzRE

下载链接:https://www.lanzous.com/i7atzwd

 原题:

https://www.jianshu.com/p/7fdfe2333123

http://www.pianshen.com/article/8985544588/

flag{#FFRFFF####ZZRZZZ##FF#FFFF}

 

0x03 icekey

下载链接:https://www.lanzous.com/i7atzwd

 

使用dnSpy打开之后,找到main函数打开

这通过观察反编译的C#代码,能够知道这段代码就是将我们输入的字符串经过加密后,与b="3ACF8D62AAA0B630C4AF43AF327CE129D46F0FEB98D9040F713BE65502A5107A"比较,相同则返回true否则返回false。

并且这段代码还很贴心的在下面写出了解密函数,因此只需要在我们调试中,输入字符串并加密后,将调试窗口处的array数组和bytes数组改为与b数组相等即可(因为在加密和比较中用到了这两个数组)

最终,调试至程序结束,我们能够在内存窗口追踪到bytes解密后的字符数组,即为我们正确的输入:

 

5acb06231724c8c369bae711166dbe85

 

0x04 android

下载链接:https://www.lanzous.com/i7au7pe

 2019中国杭州网络安全技能大赛 预选赛原题,不过没解完。

https://xhyeax.github.io/2019/04/07/2019-hzwas-wp/

 

0x05 总结

湖湘杯又被吐槽了...Reverse基本都是签到题,菜鸡做着还是舒服。

posted @ 2019-11-11 20:20  Hk_Mayfly  阅读(730)  评论(0编辑  收藏  举报