第一次做 CTF 逆向题,太难了!

我之前从来没打过 CTF。拿到这道题的时候毫无头绪,所以我只好到网上疯狂 Google 相关知识。可惜逆向这个东西是速成不了的,经过几周几次高并发尝试之后,我放弃了。

打这篇blog的时候再次失败了。打到一半电脑没电,几百字的 blog(近期最长啊!)就没了啊orz我恨

2022 Update: 现在这篇已经快 1k 字了!反咕咕大成功!

题源

司虎老师的《网络安全基础与应用》选做题 happyreverse:happyreverse

废话

剪掉了!(其实是因为压力太大,没力气再码一遍了)

最远尝试

损坏 rar 修复

在老师提供的课程主页上拿到 rar 的时候解压不了。刚开始的时候我是在 iPad 上解压的,换了 n 个软件也不行,回去用电脑仍然未果。所以我考虑这个压缩文件已经损坏。

用了两个小时,配置好 Windows 官方 VMware 虚拟机之后把它放入找的 rar 修复软件里,顺利修复(实际上用 WinHex 看了下前面有缺失的 HEX 字符,对应填补后就可以了)。解压后是 MFC 写的应用程序。

image-20211213145050980

查壳

拿到 MFC 应用程序之后开始准备逆向,第一步应该是看下有没有加壳,所以找了 peid 看一下。

刚开始下的 peid 有毛病,告诉我是穿山甲壳,傻傻的信了:

image-20211213144838059

所以找了 n 小时的破壳脚本和教程,没有任何进展。开始思考是不是根本就没壳,换了看起来正常一点的 peid 结果是没壳的(最后找老师确认也是这样)。

image-20211213144634957

白跑一场,不过没壳是好事。

开始逆向

动态调试 ollydbg + 静态 IDA Pro。

IDA Pro 静态调试

逆向过后查 string 找到 flag 位置:

image-20211213145345875

找到 data 段位置:

image-20211213150732728

查交叉引用 XREF:

image-20211213150819932

找到对应汇编代码段,反汇编试一下:

image-20211213150854559

在标黄的地方。同时这个函数也是加/解密的主函数。

我已经将一些伪代码(未命名的)通过具体行为分析猜测它对应 C 的哪些函数了。重要代码段如下:

// 省略了前面的变量声明部分
__alloca_probe();
  v48 = a1;
  your_flag = (const WCHAR *)a3;
  v46 = (wchar_t *)a2;
  v45 = (unsigned int)&savedregs ^ __security_cookie;
  v3 = sub_407915();
  if ( v3 == 0 )
    return_error(-2147467259);	// 应该是初始化部分,如果没初始化成功就返回错误信息
  v4 = ((int (__thiscall *)(int (__stdcall ***)(int, int), unsigned int, wchar_t *, const WCHAR *))(*v3)[3])(
         v3,
         v45,
         v46,
         your_flag);
  *(_DWORD *)ArgList = v4 + 16;
  v49 = 0;
  v5 = *(_DWORD *)v4;
  if ( *(_DWORD *)(v4 + 4) )
  {
    if ( *(_DWORD *)(v4 + 12) >= 0 )
    {
      if ( _InterlockedDecrement((volatile signed __int32 *)(v4 + 12)) <= 0 )
        (*(void (__stdcall **)(int))(**(_DWORD **)v4 + 4))(v4);
      *(_DWORD *)ArgList = (*(int (__thiscall **)(int, int))(*(_DWORD *)v5 + 12))(v5, v48) + 16;
    }
    else
    {
      if ( *(_DWORD *)(v4 + 8) < 0 )
        return_error(-2147024809);
      *(_DWORD *)(v4 + 4) = 0;
      **(_WORD **)ArgList = 0;
    }
  }
  your_flag = (const WCHAR *)ArgList;	// flag
  v6 = mfc_stuff(1001);					// MFC 相关函数,和题目本身没啥关系
  sub_40F9D4(v6, (int)your_flag);		
  if ( *(_DWORD *)(*(_DWORD *)ArgList - 12) > 0 )
  {
    v7 = sub_407915();
    if ( v7 == 0 )
      return_error(-2147467259);
    string_input = (LPCWSTR)(((int (__thiscall *)(int (__stdcall ***)(int, int), int))(*v7)[3])(v7, v48) + 16);			
    LOBYTE(v49) = 1;
    v8 = *(const WCHAR **)ArgList;
    if ( ((1 - *(_DWORD *)(*(_DWORD *)ArgList - 4)) | *(_DWORD *)(*(_DWORD *)ArgList - 8)) < 0 )
    {
      modify_v15((int *)ArgList, 0);
      v8 = *(const WCHAR **)ArgList;
    }
    your_flag = v8;						// flag
    scanf((int)&string_input, &The_key_you_typed_is, v8);	// 接收输入字符串
    your_flag = string_input;			// 将输入字符串赋值给 your_flag 变量
    v9 = (HWND *)mfc_stuff(1002);		// MFC 相关函数
    sub_4119E3(v9, your_flag);			//
    printf(L"\r\n", 2u);				// 打印字符串:“尝试利用该密钥进行解密:”
    printf(&Try_to_use_this_key, '\r');	// 打印字符串:“尝试利用该密钥进行解密:”
    your_flag = string_input;			// 将输入字符串赋值给 your_flag 变量
    v10 = (HWND *)mfc_stuff(1002);		// MFC 相关函数
    sub_4119E3(v10, your_flag);
    v24 = 0;
    memset(&v25, 0, 0x7FEu);
    if ( ((1 - *(_DWORD *)(*(_DWORD *)ArgList - 4)) | *(_DWORD *)(*(_DWORD *)ArgList - 8)) < 0 )								// 检查输入长度
      modify_v15((int *)ArgList, 0);
    v22 = 0;
    memset(&v23, 0, 0x7FEu);
    sub_401140();
    sub_401000(2 * wcslen((const unsigned __int16 *)&v22));
    memset(&v26, 0, 0x100u);
    sub_4011B0(wcslen((const unsigned __int16 *)&v24));
     // 隐藏 flag (加密过的)
    *(_DWORD *)v27 = 316603392;
    v28 = 521699724;
    v29 = -1896982701;
    v30 = 23873730;
    v31 = -1217616604;
    v32 = -653821873;
    v33 = 147644523;
    v34 = 1788871112;
    v35 = -401501575;
    v36 = -670459317;
    v37 = -247200577;
    v38 = 691461817;
    v39 = 800007765;
    v40 = -1582862633;
    v41 = 779312504;
    v42 = -1253129845;
    v43 = -1463759142;
    v44 = -434474754;
    v45 = -729680901;
    v46 = (wchar_t *)254367706;
    your_flag = (const WCHAR *)-1086890754;
    sub_401260(v27);                            //加密函数
    
    // 错误处理 
    v11 = sub_407915();
    if ( !v11 )
      return_error(-2147467259);
    v19 = (wchar_t *)(((int (__thiscall *)(int (__stdcall ***)(int, int)))(*v11)[3])(v11) + 16);
    your_flag = (const WCHAR *)v27;
    LOBYTE(v49) = 2;
    scanf((int)&v19, L"%s", v27);
    v12 = v19;
     // 处理输入长度
    if ( ((1 - *((_DWORD *)v19 - 1)) | *((_DWORD *)v19 - 2)) < 0 )
    {
      modify_v15((int *)&v19, 0);
      v12 = v19;
    }
      // 如果输入正确
    if ( _wcsnicmp(v12, L"flag{", '\x05') )
    {
      your_flag = (const WCHAR *)5;
      v46 = &STRING_the_key_you_typed_is_WRONG;
    }
      // 输入错误了
    else
    {
      printf(L"Flag : ", '\a');
      your_flag = (const WCHAR *)*((_DWORD *)v12 - 3);
      v46 = v12;
    }
      // 打印你输入的 flag
    printf(v46, (size_t)your_flag);
    v13 = string_input;
    your_flag = string_input;
      // MFC 相关函数
    v14 = (HWND *)mfc_stuff(1002);
    sub_4119E3(v14, your_flag);
    LOBYTE(v49) = 1;
    if ( _InterlockedDecrement((volatile signed __int32 *)v12 - 1) <= 0 )
    {
      v15 = **((_DWORD **)v12 - 4);
      your_flag = v12 - 8;
      (*(void (__stdcall **)(wchar_t *))(v15 + 4))(v12 - 8);
    }
    LOBYTE(v49) = 0;
    if ( _InterlockedDecrement((volatile signed __int32 *)v13 - 1) <= 0 )
    {
      v16 = **((_DWORD **)v13 - 4);
      your_flag = v13 - 8;
      (*(void (__stdcall **)(LPCWSTR))(v16 + 4))(v13 - 8);
    }
  }
  else
  {
    sub_406D72((int)&STRING_Please_Input_Your_key, 0, 0);
  }
  v49 = -1;
  result = (const WCHAR *)(*(_DWORD *)ArgList - 16);
  if ( _InterlockedDecrement((volatile signed __int32 *)(*(_DWORD *)ArgList - 16 + 12)) <= 0 )
  {
    v18 = **(_DWORD **)result;
    your_flag = result;
    result = (const WCHAR *)(*(int (__stdcall **)(const WCHAR *))(v18 + 4))(result);
  }
  return result;
}

看到 flag 已经被加密了。主函数里面没有加密函数。找到其他函数:

int __usercall sub_401260@<eax>(int result@<eax>, int a2@<ecx>)
{
  int v2; // esi
  int v3; // edi
  _BYTE *v4; // ecx
  int v5; // esi
  unsigned __int8 v6; // dl
  int v7; // edi
  int v8; // esi
  unsigned __int8 v9; // dl
  int v10; // edi
  int v11; // esi
  unsigned __int8 v12; // dl
  int v13; // edi
  int v14; // esi
  unsigned __int8 v15; // dl
  int v16; // edi
  int v17; // esi
  unsigned __int8 v18; // dl
  int v19; // edi
  unsigned __int8 v20; // dl
  signed int v21; // [esp+Ch] [ebp-4h]

  v2 = 0;
  v3 = 0;
  v4 = (_BYTE *)(a2 + 1);
  v21 = 14;
  do
  {
    v5 = (v2 + 1) % 256;
    v6 = *(_BYTE *)(v5 + result);
    v7 = (v6 + v3) % 256;
    *(_BYTE *)(v5 + result) = *(_BYTE *)(v7 + result);
    *(_BYTE *)(v7 + result) = v6;
    *(v4 - 1) ^= *(_BYTE *)((v6 + *(unsigned __int8 *)(v5 + result)) % 256 + result);
    v8 = (v5 + 1) % 256;
    v9 = *(_BYTE *)(v8 + result);
    v10 = (v9 + v7) % 256;
    *(_BYTE *)(v8 + result) = *(_BYTE *)(v10 + result);
    *(_BYTE *)(v10 + result) = v9;
    *v4 ^= *(_BYTE *)((v9 + *(unsigned __int8 *)(v8 + result)) % 256 + result);
    v11 = (v8 + 1) % 256;
    v12 = *(_BYTE *)(v11 + result);
    v13 = (v12 + v10) % 256;
    *(_BYTE *)(v11 + result) = *(_BYTE *)(v13 + result);
    *(_BYTE *)(v13 + result) = v12;
    v4[1] ^= *(_BYTE *)((v12 + *(unsigned __int8 *)(v11 + result)) % 256 + result);
    v14 = (v11 + 1) % 256;
    v15 = *(_BYTE *)(v14 + result);
    v16 = (v15 + v13) % 256;
    *(_BYTE *)(v14 + result) = *(_BYTE *)(v16 + result);
    *(_BYTE *)(v16 + result) = v15;
    v4[2] ^= *(_BYTE *)((v15 + *(unsigned __int8 *)(v14 + result)) % 256 + result);
    v17 = (v14 + 1) % 256;
    v18 = *(_BYTE *)(v17 + result);
    v19 = (v18 + v16) % 256;
    *(_BYTE *)(v17 + result) = *(_BYTE *)(v19 + result);
    *(_BYTE *)(v19 + result) = v18;
    v4[3] ^= *(_BYTE *)((v18 + *(unsigned __int8 *)(v17 + result)) % 256 + result);
    v2 = (v17 + 1) % 256;
    v20 = *(_BYTE *)(v2 + result);
    v3 = (v20 + v19) % 256;
    *(_BYTE *)(v2 + result) = *(_BYTE *)(v3 + result);
    *(_BYTE *)(v3 + result) = v20;
    v4[4] ^= *(_BYTE *)((v20 + *(unsigned __int8 *)(v2 + result)) % 256 + result);
    v4 += 6;
    --v21;
  }
  while ( v21 );
  return result;
}

这个就是加密算法了啊,实际上是类 RC 4 算法,这之后应该跑一下脚本对之前的密文进行解码,但能力实在不够了 TAT 就到此为止了。

Ollydbg 动态调试

把文件拖入到 Ollydbg 打断点看了下跑起来时候的具体行为,具体是:

  • 接收输入input
  • 把 input base64 编码一遍
  • 把编码后的 input 继续 rc4 加密一遍
  • 对 hard-code 的 flag (放在上面了)进行同样操作
  • 比较,得出结果

大概是这样一个流程。

感想

应该是自己第一次做 ctf reverse 题,感觉难度比在网上搜罗到的所有rc4/mfc题解都难一个等级,各种输入函数和mfc组件函数混在一起,真的感觉很难。

可惜这门课除了这道题外,别的什么也没有教。这就让像我这样的入门小白来说,短时间内做出这种题基本上[我感觉]是没戏的。

不过好的一点是现在会了一丢丢逆向知识,解个入门赛签到题估计应该是可以的?

Categories:

Updated: