[CTF Reverse] 初见SMC

初见SMC——smc.exe

题目:

9e798da56338ce136760a03a91f948a5

  • 把附件smc.exe拖入IDA Pro 8.3定位main函数,反编译结果如下:
int __fastcall main(int argc, const char **argv, const char **envp)
{
  puts(Buffer);
  sub_140001140("%s", byte_140005678);
  sub_140001280();
  if ( (unsigned int)sub_1400011C0() )
    sub_140001070(aGoodUDoIt);
  else
    sub_140001070(aSorryTryAgain);
  return 0;
}

容易看出,sub_7FF6AC891140("%s", byte_7FF6AC895678) 是一个请求输入的语句,输入后,byte_7FF6AC895678将存储我们的输入内容。if语句判断了sub_1400011C0()的结果,双击查看该函数。
屏幕截图 2025-08-03 132214
很杂乱,和之前训练的情况不同,这次我们从这里无法读取到有效信息。所以我们转换角度,去看看另一个函数sub_140001280()。

__int64 sub_140001280()
{
  int i; // [rsp+20h] [rbp-28h]
  unsigned int v2; // [rsp+24h] [rbp-24h]
  DWORD flOldProtect[2]; // [rsp+30h] [rbp-18h] BYREF

  flOldProtect[1] = -858993460;
  flOldProtect[0] = (unsigned int)malloc(8ui64);
  v2 = VirtualProtect((char *)sub_1400011C0 - (unsigned int)sub_1400011C0 % 0x1000, 0x1000ui64, 0x80u, flOldProtect);
  for ( i = 0; i < 190; ++i )
  {
    *((_BYTE *)sub_1400011C0 + i) ^= 0x66u;
    v2 = i + 1;
  }
  return v2;
}

我们注意到了VirtualProtect(),经过搜索,这是一个外部方法:

VirtualProtect函数是Windows API中的一个函数,
它用于更改调用进程的虚拟地址空间中已提交页面区域的保护。
这个函数允许开发者修改一个内存区域的保护属性,例如将一个内存区域设置为可执行,
这在某些情况下,如动态代码生成或自修改代码中非常有用。

结合题目简介,我们搜索What is SMC?结果是自定义代码混淆(Self-Modifying Code),代码有反编译处理,我们需要攻破它。
回到sub_1400011C0(),发现JUMPOUT语句后面的地址双击会跳到sub_140001280(),遂搜索JUMPOUT,发现相关博客介绍:【Android安全】IDA 处理伪代码JUMPOUT指令(Undefine + Create Function)
我们的思路已经明确:

  • 设置断点,让程序进行代码解密
  • 捕获解密后的汇编并且反编译
  • 分析代码,编写flag脚本

根据逆向工程基础之代码混淆/反混淆技术(1):SMC混淆原理分析出,sub_140001280()就是解密函数。回到main,在箭头位置打上断点

...
     sub_140001280();
-->  if ( (unsigned int)sub_1400011C0() 
...

F9进行调试,选择Local Windows Debugger。命令行弹出,随便输入点什么,然后回车屏幕截图 2025-08-03 134722
双击sub_7FF6A08911C0(),报错了。image
没关系,点OK。
image
到这里右键,选择Text View。
image
从最上面 .text:00007FF6A08911C0一直选到.text:00007FF6A089127E align 20h(文章最后会解释为什么这样选),按u来取消定义(Undefine),光标回到最上面.text:00007FF6A08911C0,按p重新编译,然后F5反编译。得到:

__int64 sub_7FF6A08911C0()
{
  int i; // [rsp+20h] [rbp-48h]
  unsigned int v2; // [rsp+24h] [rbp-44h]
  char Str[24]; // [rsp+30h] [rbp-38h] BYREF

  strcpy(Str, "EOBDXZFP|V|GL|JW^");
  memset(&Str[18], 0, 2ui64);
  v2 = 1;
  for ( i = 0; i < strlen(Str); ++i )
  {
    if ( (Str[i] ^ 0x23) != byte_7FF6A0895678[i] )
      v2 = 0;
  }
  return v2;
}

这就是解密后得到的函数,不难看出flag就在这里。编写flag脚本。

Str = "EOBDXZFP|V|GL|JW^"
strarr = [ord(m) for m in Str]

for i in range(len(strarr)):
    strarr[i] ^= 0x23

flag = [chr(c) for c in strarr]
print(''.join(flag))

得到flag{yes_u_do_it}

成功!

为什么从.text:00007FF6A08911C0一直选到.text:00007FF6A089127E

解密函数sub_140001280()中的for循环运行了190次,修改了sub_1400011C0(),而190换成hex是BE,11C0+BE = 127E,所以选到127E。

2025 08 04
posted @ 2025-08-04 13:32  Morikomo  阅读(56)  评论(0)    收藏  举报