状态
Done
一、SMC简介:
SMC,即Self Modifying Code,动态代玛加密技术,简单来说就是将一段正常的代码加密保存,在需要运行这段代码时调用解密函数对该加密代码进行解密,等运行完这段代码后又调用加密函数将代码重新加密保存,这样做的好处就是当对该段代码进行静态分析时啥也看不出来,以此达到保护代码的效果。
二、SMC技术实现:
ASC CODE
三、SMC逆向:
题目附件
tips:将后缀改回exe即可
以HWS的一道讲题为例,先运行程序可以看到提示要输入授权码:

放到EXEinfope,64位程序,无壳:

用IDA打开,搜索字符串
Authorize:
,定位其所在函数:
下方调用了两个函数,函数一:

函数二:

函数二没啥好分析 的就是输入一堆字符串,主要分析函数一:

稍微调整一下变量名,主要加密逻辑在40到48行的for循环,输入的flag和key值经过42行的运算得到的值和cipher数组要相同,想要获得flag的值在简单的办法就是考虑通过枚举的方式来求解,脚本如下:
运行脚本得出结果:

Caucasus@s_ability
得到这串字符串,打开终端尝试运行:

发现程序运行后什么也显示就退出程序了,这里是程序中有检测调试器的函数,还是shift+f12查看字符串:

可以看到有以下几个调试工具的字符串查看其交叉引用:

这里调用了
GetWindowTextA
API,如果窗口标题为上图几个逆向工具则将a2赋值为1,它作为返回值,返回到下图,作为是否退出程序的依据。
patch掉这个exit:

再次运程序,就可以正常运行了:

但是新的问题又来了,输入完授权码之后只是将前面函数二的内容输出就又退出程序了,这里可以回到函数一,发现一些端倪:

接收用户输入的变量flag并不是在该函数定义的,它是一个全局变量,查看其交叉引用:

flag变量在另外一个函数也被调用了,更过去看一下:

VirtualProtect()函数参数
参数介绍:
- lpAddress:需要修改权限的内存起始地址
- dwSize:需要修改的区域大小
- flNewProtect:需要修改的内存权限
- 具体参数:
常量/值 | 说明 |
PAGE_EXECUTE0x10 | 启用对已提交页面区域的执行访问。 |
PAGE_EXECUTE_READ0x20 | 启用对页面已提交区域的执行或只读访问。 |
PAGE_EXECUTE_READWRITE0x40 | 启用对页面已提交区域的执行、只读或读/写访问权限。 |
PAGE_EXECUTE_WRITECOPY0x80 | 启用对文件映射对象的映射视图执行、只读或复制写入访问。 |
PAGE_NOACCESS0x01 | 禁用对页面已提交区域的所有访问。 |
PAGE_READONLY0x02 | 启用对页面已提交区域的只读访问。 |
PAGE_READWRITE0x04 | 启用对已提交页面区域的只读或读/写访问权限。 |
PAGE_WRITECOPY0x08 | 启用对文件映射对象的映射视图的只读或复制写入访问权限。 |
PAGE_TARGETS_INVALID0x40000000 | 将页面中的所有位置设置为 CFG 的无效目标。 |
PAGE_TARGETS_NO_UPDATE0x40000000 | 当 VirtualProtect 的保护更改时,区域中的页面不会更新其 CFG 信息。 |
- lpflOldProtect:指向一个变量的指针,该变量接收页面指定区域中第一页的上一个访问保护值
返回值:
- 如果该函数成功,则返回值为非零值。
- 如果函数失败,则返回值为零。
这个函数的作用为某段数据解密并赋予运行权限。在for循环之后下断点就可以查看到被加密的函数,但是在此之前还需要看一下该函数的交叉引用,因为需要找到程序直接退出的原因,找到调用该函数的上层函数:

查看该函数调用的第二个函数,大概逻辑是如果运行该程序时有图形窗口就退出程序,这个也很好理解,该程序本身是在命令行中运行,不需要图


形界面,这也算另外一种检查是否被调试的方法。这里依然是将其patch掉。

patch掉exit后,程序就正常显示出来之后的内容了,回到SMC处在For循环之后下断点:

运行调试器,定位到加密代码:

选中加密的代码按c转换为汇编代码,然后按p转换为函数,最后F5就可以看到伪代码了

这里还提供另外一种常见的解密方法,就是利用IDAPython对代码直接解密具体的解密脚本为:
查看解密后的代码:
解密的逻辑和之前一样还是暴力破解:

flag{Thousandriver_is_1000%_stronger_than_zero-one}
实例程序:
- 简单的SMC:
- Linux下的SMC: