首页
论坛
课程
招聘
雪    币: 1864
活跃值: 活跃值 (10)
能力值: ( LV13,RANK:283 )
在线值:
发帖
回帖
粉丝

[原创]CTF2018第二题半加器 writeup

2018-12-3 22:26 873

[原创]CTF2018第二题半加器 writeup

2018-12-3 22:26
873
        题目似乎并没有特别的保护方式,除了使用vs2017的C++开发,静态链接ucrt运行时库,使用的调试版本,导致大量未优化的代码影响IDA全自动出来的结果太难看..因为经验不足,IDA全自动出来的代码确实在很大程度上影响了分析思路。
拿到题目后,首先扔进IDA7, 提示

The input file was linked with debug information

and the symbol filename is:

'C:\Users\83966\Desktop\Exam\Debug\Exam.pdb'

而且文件体积较大,基本可以确定是使用的Debug版本。Ctrl + E 找到start, 一路跟随,并没有发现什么有用的信息,遂放弃直接看静态代码的方式。运行程序,有提示输入的信息,IDA中 Shift+ F12搜索 Please Input,找到字符串,顺便把 aPleaseInput 下面的 unk_5B25CC A了一下,转换成字符串,直接识别出来 输入错误; 的字符。任意输入一串字符,果真提示有 输入错误。查找 输入提示和错误信息的交叉引用,都提示出在 sub_4A19B0中使用,如下 图1 :


进到 sub_4A19B0,习惯性地F5,发现堆栈 居然 不平衡,无法自动转换为伪代码,再结合IDA自动出来的一万多个函数,想当然地认为程序应该是被处理过(当时并没有想到是因为未优化过导致。),老实地回到汇编状态,阅读aPleaseInput附近的代码,大至判断输入的长为10到30个字符之间,如下图2所示:

得出第一个满足条件:输入的长度10到30个字符。
长度验证通过后,进入到标签 loc_4A1A30, 在 loc_4A1A30下,有对疑似输入的缓存byte_5F3068和dword_5F3088作为sub_48E5BF的入参,进行一某种运算, dword_5F3088作为函数的返回,第七个字节的值必须为 0x41(字符 A)。如下图3所示:

这步验证通过后,来到把 dword_5f3088作为结果,来到sub_48D3A4, 该函数里面直接jmp到sub_49DBD0, 对该函数直接F5, 得到如下代码
int __usercall sub_49DBD0@<eax>(int xmm0_4_0@<xmm0>, char *a1)
{
  unsigned int i; // [esp+D0h] [ebp-8h]

  sub_48D7B4(&unk_5F6007);
  a1[7] = '#';
  for ( i = 0; i < sub_48A9A6((int)a1); ++i )
    a1[i] ^= 0x1Fu;
  return sub_48D935(1, (int)a1, xmm0_4_0);
}
从该函数看出,把输入的第七个字符设置成 '#',然后对内存中的每一个字符与 0x1F异或(跟进sub_48a9a6 可以大概得出该函数是对输入的计算长度,类似strlen)。
再点进 sub_49dbd0调用的 sub_48D935,一路跟下去,发现快跟丢了,静态分析就暂且停下,开始动态调试。

在上面图3的预处理输入后设置一个断点,以 0123456789 作为测试输入,调试发现第一步预处理应该只是一个字符串的拷贝。
因此,得到第二个满足条件:第七个字符为 'A', 
继续动态调试,发现调试时跟丢了..多次尝试F7,结果并没有进展。因程序中多次出现jmp,还有读取输入的代码自动分析出来堆栈不平衡,不能直接F5看伪代码,一度怀疑程序被手工处理,尝试对jmp的函数做直接修改后依然没有解决任何问题。清空idb文件,重新打开,再次搜 Input, 瞄到下面一行出现ucrt字等串,如图4所示

似乎恍然大悟了,用 VS 自带的 dumpbin /HEADERS Exam.exe 查看程序的LINK信息,发现使用 VS2017编译。仔细看了下IDA的输出,发现使用的是vc6win 的 library和  Using FLIRT signature: SEH for vc7-14。手动选择 ucrt的签名文件重新分析,如图5所示

完成后,查找交叉引用 Input,来到 sub_4A19B0, 直接F5,伪代码清晰地出来了。前面部分和先前的静态分析结果差不多,如果直接再次调试,必然会继续跟丢。
对 sub_4A19B0的查看得到,输入结果拷到一个全局的指针中,然后对全局指针进行了一点变换。对该全局变量查找引用,进入到一个很有可能是结果对比的 sub_49DC80 函数:
int __userpurge sub_49DC80@<eax>(int a1@<xmm0>, char *a2)
{
  size_t i; // [esp+E8h] [ebp-14h]

  sub_48D7B4(&unk_5F6007);
  if ( a2 )
  {
    for ( i = 0; i < j__strlen(a2); ++i )
      a2[i] ^= 0x1Cu;
    if ( !j__strcmp(a2, Dst) )
    {
      sub_48B4AA(&unk_5F31E0, 111);
      sub_48B4AA(&unk_5F31E0, 107);
    }
  }
  return sub_48D935(1, 0, a1);
}
直接在该函数设断点,重新调试,输入 0123456A89,直接运行后断点触发,函数输入的字符串为 "invalid argument", 然后逐个与 0x1C异或,再与输入变换过的值对比,在调用完strcmp后,手动更改 eax 为0,出现成功提示。至此,所有的工作已经完成,写一小段程序验证:
#include <iostream>
int main()
{
	const char *pret = "invalid argument";
	int clen = strlen(pret);
	char szRet01[30];
	memset(szRet01, 0, 30);
	for (int i = 0; i < clen; i++){
		szRet01[i] = pret[i] ^ 0x1c ^ 0x1f;
	}
	szRet01[7] = 'A';
	std::cout << szRet01 << std::endl;
}
执行程序,得到结果 jmubojgAbqdvnfmw

虽然结果出来了,但这个CM实现的方式还是没有弄明白,从IDA自动出现的代码里面大至猜测,应该用了诸如Lambda函数之类的C++新标准,又是未经优化过的代码,出现了非常多影响分析的程序,要是只跟着代码走太容易迷路了。



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

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