这是一个CTF的逆向题,考点为:1.64位upx手工脱壳。2.魔改XTEA。

首先用EXEINFO查看一下程序的基本信息:

Untitled

Win64程序,加了upx壳。

尝试运行一下:

Untitled

运行可以看到提示输入flag的字符串。

基本信息收集完,下面开始解题:

1.UPX脱壳:

使用命令upx -d对程序进行脱壳,发现脱壳失败了。

Untitled

这里有两种解决方案。

  1. 方案一:(使用文件查看工具打开看段)

    Untitled

    这里是将UPX修改成了uPX,将其修改回来,重新upx -d就可以看到脱壳成功了。

    Untitled

  2. 方案二:(手动脱壳)

    用x64dbg打开文件,然后停到EP处

    Untitled

    我们可以利用rsp定律,对程序下断点找到pop处的地址,这里我没有使用下断点的方式来定位,直接往下翻就可以找到了。

    Untitled

    这里的经过一系列的弹栈操作之后jmp跳转到了一个较远的代码处,jmp的地址就是OEP所在的位置,下个断点直接跟过去。下面就是程序的OEP了。

    Untitled

    使用x64dbg自带的scylla插件,对解压好的程序进行dump。

    Untitled

    将程序保存完后:

    Untitled

    点击第一步插件会自动搜索IAT,如果显示下图就代表找到了程序的IAT表

    Untitled

    然后直接点击获取导入就会在上面显示找到的导入表

    Untitled

    这里可以看到有一个表没有导出成功,一开始我是直接点击修复转储将这四个表都导入到了dump下来的程序里面,程序虽然不能运行但是不影响IDA调试,但是后面听别的师傅直接删除这个导出失败的表,就可以正常运行了

    Untitled

    修复完后可以看到文件夹下多了两个程序,第二个是导出但没有修复IAT的程序,第三个是基于导出的源程序修复好了IAT。双击是可以直接运行的。

2.IDA静态分析:

根据提示可知这个程序使用的是TEA系列的加密算法,用IDA打开可以直接查看,先对主函数进行分析。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  uint32_t key[4]; // [rsp+20h] [rbp-90h] BYREF
  uint32_t flag[8]; // [rsp+30h] [rbp-80h] BYREF
  char tmp[32]; // [rsp+50h] [rbp-60h] BYREF
  char str[38]; // [rsp+70h] [rbp-40h] BYREF
  int i_1; // [rsp+A4h] [rbp-Ch]
  int i_0; // [rsp+A8h] [rbp-8h]
  int i; // [rsp+ACh] [rbp-4h]

  _main(argc, argv, envp);
  if ( IsDebuggerPresent() )
  {
    puts("Do not debug me!!!!");
    exit(-1);
  }
  printf("input the flag:");
  scanf("%38s", str);                           // 接收用户输入的flag
  if ( str[0] != 'f' && str[1] != 'l' && str[2] != 'a' && str[3] != 'g' && str[4] != '{' && str[37] != '}' )// 对输入的flag格式进行判断
    exit(-1);
  memset(tmp, 0, sizeof(tmp));
  strncpy(tmp, &str[5], 0x20ui64);              // 截取flag的有效数据
  for ( i = 0; i <= 31; i += 4 )                // 将flag的有效数据以XTA加密每次加密长度进行规整
    flag[i / 4] = (tmp[i + 2] << 8) + (tmp[i + 1] << 16) + (tmp[i] << 24) + tmp[i + 3];
  qmemcpy(key, "I love dreak tea", sizeof(key));// xtea的密钥为字符串“I love dreak tea”
  for ( i_0 = 0; i_0 <= 7; i_0 += 2 )
    xtea_encipher(&flag[i_0], key);             // 调用加密函数对数据进行加密
  for ( i_1 = 0; i_1 <= 7; ++i_1 )              // 循环比对,判断输入的flag是否正确
  {
    if ( enc[i_1] != flag[i_1] )
      exit(-1);
  }
  printf("You got the flag!!!");
  return 0;
}

接着是加密函数的分析,下面主要列出魔改的地方。

**void __cdecl encipher(uint32_t *v, uint32_t *key)
{
  uint32_t sum; // [rsp+20h] [rbp-10h]
  uint32_t v1; // [rsp+24h] [rbp-Ch]
  uint32_t v0; // [rsp+28h] [rbp-8h]
  unsigned int i; // [rsp+2Ch] [rbp-4h]

  v0 = *v;
  v1 = v[1];
  sum = 0;
  if ( IsDebuggerPresent() )
  {
    puts("Do not debug me!!!!");
    exit(-1);
  }
  for ( i = 0; i <= 0xF; ++i )
  {
    v0 += (v1 + ((v1 >> 5) ^ (16 * v1))) ^ (key[sum & 3] + sum);
    sum -= 559038737;                           // 魔改处1:delta的值有改变
    v1 += (v0 + ((v0 >> 5) ^ (16 * v0))) ^ (key[(sum >> 11) & 3] + sum);
    *key ^= salt[0];                            // 魔改处2:多加了一个对密钥的异或修改
    key[1] ^= salt[1];
    key[3] ^= salt[2];
    key[4] ^= salt[3];
  }
  *v = v0;
  v[1] = v1;
}**