初见SMC——smc.exe
题目:
解
- 把附件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);elsesub_140001070(aSorryTryAgain);return 0;
}
容易看出,sub_7FF6AC891140("%s", byte_7FF6AC895678) 是一个请求输入的语句,输入后,byte_7FF6AC895678将存储我们的输入内容。if语句判断了sub_1400011C0()的结果,双击查看该函数。
很杂乱,和之前训练的情况不同,这次我们从这里无法读取到有效信息。所以我们转换角度,去看看另一个函数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] BYREFflOldProtect[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。命令行弹出,随便输入点什么,然后回车
双击sub_7FF6A08911C0(),报错了。
没关系,点OK。
到这里右键,选择Text View。
从最上面 .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] BYREFstrcpy(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] ^= 0x23flag = [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。