首页
论坛
课程
招聘
雪    币: 9413
活跃值: 活跃值 (28)
能力值: ( LV12,RANK:790 )
在线值:
发帖
回帖
粉丝

[看雪CTF.TSRC 2018 团队赛][分析] 第二题 半加器

2018-12-3 23:30 883

[看雪CTF.TSRC 2018 团队赛][分析] 第二题 半加器

2018-12-3 23:30
883

第二题 半加器

观察

题目给出了一个 32 位控制台 Windows 应用程序 Exam.exe:

$ file Exam.exe
Exam.exe: PE32 executable (console) Intel 80386, for MS Windows
$ strings Exam.exe
<...略...>
c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\xmemory0
<...略...>

看起来没有什么保护,它是一个 MSVC 2017 编译的 C/C++ 程序。

 

运行一下:

N:\pediy\2>Exam.exe
Please Input:hasjkhdasjkdsda
输入错误;

看起来就是一个经典的输入 flag,输出对错的挑战。

分析

简单用 IDA Pro 分析一下,顺着 MSVCRT 的初始化逻辑脑补一下,可以找到 main 在 0x4A19B0 处。
逻辑大致如下:

int main(int argc, char* argv[])
{
  signed int len;
  fprintf(stdout, "Please Input:");
  scanf_s("%s", g_input, 30);
  len = strlen(g_input);
  if ( len > 30 || len < 10 )
  {
    puts("输入错误;");
    exit(0);
  }
  tcscpy_s(g_p_buf, 30, g_input);
  if ( g_p_buf[7] != 'A' )
  {
    puts("输入错误;");
    exit(0);
  }
  j_MangleInput(g_p_buf);
  return 0;
}

void MangleInput(char *buf)
{
  unsigned int i;

  buf[7] = 35;
  for ( i = 0; i < strlen(buf); ++i )
    buf[i] ^= 0x1Fu;
}

仅仅检查了长度是否大于等于10,小于30,以及 0-based index 下 [7] 处是否是 'A'。
若不是的话则报错,这很奇怪,因为按照规则,若答案正确的话应该输出明确的正确提示。
尝试输入满足以上条件的串 0123456A89,程序确实没有任何输出。

 

显然,程序中存在一些隐藏的检查 flag 的逻辑。观察可以发现程序没有写文件和外部环境的行为,因此可以排除规则中所允许的“重启验证”,flag 应该就在本次执行中被验证了。考虑到输入的来源就这么两处,只要死咬着输入(和被变换过的输入)不放,肯定能找到处理 flag 的地方。

 

在下一个教科书般的内存读取断点之前,让我们先试试查找到 g_inputg_p_buf 的交叉引用,结果发现在 mainMangleInput 以外另有两个函数 0x495810 (命名为 InitBuffer) 和 0x49DC80 (命名为ValidateFlag)引用到了 g_input

 

这两个函数的逻辑也很简单:

void InitBuffer()
{
  g_p_buf = (char *)malloc(30);
}

void ValidateFlag(char *expected)
{
  unsigned int i;

  if ( expected )
  {
    for ( i = 0; i < strlen(expected); ++i )
      expected[i] ^= 0x1Cu;
    if ( !strcmp(expected, g_p_buf) )
    {
      fputc(stdout, 'o');
      fputc(stdout, 'k');
    }
  }
}

看起来,这里才是真正的检查 flag 的地方,注意到与之比较的标准答案来自于函数参数,寻找交叉引用得到该参数:

ValidateFlag("invalid argument");

解决

至此,已经可以计算出输入惹:

from pwn import *

ans = list(xor('invalid argument', 0x1c, 0x1f))
ans[7] = 'A'
print ''.join(ans)

# jmubojgAbqdvnfmw

提交即可。可以看出来多出来的那个判断 [7] 的地方是为了对付规则中的 “flag只能包含字母和数字”。

分析 2

然而,题目里的谜团还没有彻底解开,这个检查 flag 的函数到底是在哪里调用的呢?
继续追踪交叉引用,可以找到是在 0x4957B0 这个函数里调用 atexit() 注册成退出时执行的函数的。

Trivia

其实这里的 [7] 的 7 都是用 1 * 7 算出来的,不过直接被 Hex-Rays 的常量折叠折成最终结果辣。

吐槽

简单的题目的 writeup 写起来好累呀。



HWS计划·2020安全精英夏令营来了!我们在华为松山湖欧洲小镇等你

最新回复 (0)
游客
登录 | 注册 方可回帖
返回