#逆向-SMC篇(待补
🤡5ea1
在写花指令的时候我提到过SMC代码自解密,书接上回,今天就研究下这玩意。🤓
和花指令类似的,这两种方法都能对静态分析产生较大的干扰,不一样之处在于,花指令是在原有代码的基础上加入了一些多余指令,来干扰反汇编器,使其报错;而SMC则是修改代码本身进行混淆
SMC代码自解密
SMC(Software-Based Memory Encryption)作为一种局部代码加密技术,在当今的软件安全领域具有重要地位。其核心目的在于通过特定的加密机制对可执行文件中的指定区段实施加密处理,以此显著提升恶意代码分析的难度,并有效降低恶意攻击得逞的概率。
基本原理
代码即数据,数据即代码
在编译可执行文件的进程中,SMC 技术会将那些需要加密的代码部分,诸如特定函数或代码块等,单独编译成独立的 section(段)。此 section 初始被设定为可读、可写、不可执行(readable, writable, non-executable)的属性状态。而在程序运行之际,借助特定手段将该 section 解密转换为可执行代码,并同时将其属性更新为可读、可执行、不可写(readable, executable, non-writable)。如此一来,黑客即便获取程序内存数据,也难以寻觅到处于加密状态的代码,更无法直接执行或篡改这些加密代码。
实现方法
SMC 技术的实现途径呈现多样化。例如,可通过修改 PE 文件的 Section Header 来达成加密目的;也能够运用 API Hook 技术实现代码的加密与解密操作;还可以借助诸如 VMProtect 等第三方加密工具予以实现。在加密算法方面,多采用异或这类相对简洁的加密算法,相应地,解密时则运用相同算法对密文进行逆向处理。
实现步骤
SMC的实现主要涵盖以下步骤
- 精准读取 PE 文件并精准定位到需要加密的代码段;
- 运用加密算法对代码段内容予以加密,并将加密后的结果更新至内存中的对应代码段
- 对代码段的内存地址实施重定向操作,以确保加密后的代码能够在内存中得以正确无误地执行
- 执行已加密的代码段
简单而言,就是把代码段改为可读可写可执行的,此时代码段就可以被视作数据段,即能够进行正常数据操作,从而对代码的硬编码做一个修改😉
SMC0:原始人
一个简单的异或加密代码
#include <iostream>
constexpr auto N = 23;;
int key = 10;
char enc[] = "eobdxPN@\\2p\\p3\\fb0z\"\"\"~",data[50];
//flag{SMC_1s_s0_ea3y!!!}
void SMC_Test(char* enc)
{
int i;
key = 3;
for (i = 0; i < N; i++)
{
*(enc + i) ^= key;
}
}
int main()
{
scanf_s("%50s", data, (unsigned)sizeof(data));
if (strlen(data) != 23)
{
exit(0);
}
SMC_Test(data);
if (!strcmp(data, enc))
{
printf("Y");
}
else
{
printf("N");
}
return 0;
}
一看就懂对吧,什么都没有藏,太捞了啊sir,一个异或一个密文,静态分析秒的啊
SMC0-1
佛曰:要有SMC,方可阻止静态分析
根据上面的定义,先写一段代码观察硬指令
int* SMC_Test_addr = (int*)SMC_Test;
for (int i = 0; i <= 10; i++)
{
printf("%x \n", *(SMC_Test_addr+i));
}
这段代码借助了函数指针,把上文中SMC_Test()
函数的地址赋值给了SMC_Test_addr
指针,然后将该函数处在的段当作数据集直接读取输出
运行后首先发现输出的数据的长度不行,int型一次会输出4字节,然后由于小端序,不太好看,于是换成char型,再做了一个&0xFF
防止出现0xFFFFFFE8之类
可以看到指令被成功输出,但是和从ida上看到的完全不一样...
这个结果可能是因为VS2022采用了更高级的优化算法,根据它的一套逻辑对函数内的语句进行重新排列、合并或者简化操作,导致最终生成的机器指令在内容和顺序上存在差异。
为了能读取和ida一致的硬指令,我最后选择了使用DevC++的gcc编译器来完成任务
可以看到和上面ida中的硬指令一致
SMC0-2
既然已经能读取到相关的字节码了,那么我尝试了直接对其进行修改,
for (int i = 0; i <= 0x40160C - 0x4015B2; i++)
{
*(SMC_Test_addr+i)&=0xff;
*(SMC_Test_addr+i)^=0x3;
}
很可惜,内存报错了,放进ida里调试后猜测是权限问题,代码段本身是没有读写权限,但这里明显将代码段当成了数据段进行了写入操作
所以我们要对代码进行提权,通过查询资料,我们可以用以下函数进行提权
VirtualProtect(Address, Size, PAGE_EXECUTE_READWRITE, &oldProtect);
Address
是要提权的地址起始处,Size
是提权部分的大小,PAGE_EXECUTE_READWRITE
即可执行(Execute)、可读(Read)以及可写(Write)权限,oldProtect
则用于存储旧的保护属性,便于还原。
所以我们可以写出如下代码提权
LPVOID lpAddress = (LPVOID)SMC_Test_addr;
SIZE_T dwSize = 0x40160C - 0x4015B2 + 1 ;
DWORD oldProtect; //存储旧的保护属性
// 调用函数提权,提权后把返回值放进bResult里
BOOL bResult = VirtualProtect(lpAddress, dwSize, PAGE_EXECUTE_READWRITE, &oldProtect);
if (!bResult) {
//获得详细的错误代码
DWORD errorCode = GetLastError();
// 根据不同的错误代码输出详细的错误信息
switch (errorCode) {
case ERROR_INVALID_ADDRESS:
printf("The specified address is invalid. Please check the address you provided.\n");
break;
case ERROR_INVALID_PARAMETER:
printf("Invalid parameter passed to VirtualProtect. Check the address and size values.\n");
break;
case ERROR_ACCESS_DENIED:
printf("Access is denied. Make sure the process has appropriate privileges to change the memory protection.\n");
break;
default:
printf("An unknown error occurred while trying to change memory protection. Error code: %lu\n", errorCode);
break;
}
return -1;
}
```
运行代码无报错,提权成功
# SMC0-3
综合上文,我们可以得到下面这个代码
```c++
#include<stdio.h>
#include<string.h>
#include <windows.h>
int N=23;
int key = 10;
char enc[] = "eobdxPN@\\2p\\p3\\fb0z\"\"\"~", data[50];
void SMC_Test(char* enc)
{
int i;
key = 3;
for (i = 0; i < N; i++)
{
*(enc + i) ^= key;
}
}
int main()
{
unsigned char* SMC_Test_addr = (unsigned char*)SMC_Test;
LPVOID lpAddress = (LPVOID)SMC_Test_addr;
SIZE_T dwSize = 0x40160C - 0x4015B2 + 1 ;
DWORD oldProtect;
BOOL bResult = VirtualProtect(lpAddress, dwSize, PAGE_EXECUTE_READWRITE, &oldProtect);
if (!bResult) {
DWORD errorCode = GetLastError();
switch (errorCode) {
case ERROR_INVALID_ADDRESS:
printf("The specified address is invalid. Please check the address you provided.\n");
break;
case ERROR_INVALID_PARAMETER:
printf("Invalid parameter passed to VirtualProtect. Check the address and size values.\n");
break;
case ERROR_ACCESS_DENIED:
printf("Access is denied. Make sure the process has appropriate privileges to change the memory protection.\n");
break;
default:
printf("An unknown error occurred while trying to change memory protection. Error code: %lu\n", errorCode);
break;
}
return -1;
}
scanf("%50s", data);
if (strlen(data) != 23)
{
exit(0);
}
for (int i = 0; i <= 0x40160C - 0x4015B2; i++)
{
*(SMC_Test_addr+i)&=0xff;
*(SMC_Test_addr+i)^=0x3;
}
SMC_Test(data);
if (!strcmp(data, enc))
{
printf("Y");
}
else
{
printf("N");
}
return 0;
}
直接运行会报错啊,进ida调试一下就知道了,权限修改完以后代码可以被修改了,导致运行失败,相当于代码本身被加密了
由于我们用的是异或加密,加密解密对称,所以可以将现有代码中的加密代码当成解密用,只要先把代码本身加密了就行,此处我选择了idc脚本加密,脚本如下
#include<idc.idc>
static main()
{
auto i,begin,end;
begin=0x401530;
end=0x40158a;
for(i=begin;i<=end;i++)
{
PatchByte(i,Byte(i)^0x3);
}
return 0;
}
运行后可以发现SMC_Test函数被加密
动态调试到解密结束部分
解包 分析 打包 包装函数 ,按F5反汇编(其实可以用idc的某些函数来自动二次分析)
可以看到加密的代码被还原了,程序可以运行
至此一个简单SMC自解密模板就完成了,可以使用简单异或来隐藏代码的静态逻辑,但是解密函数还是过于显眼,后期可以尝试使用HOOK来加以隐藏,不过也可以尝试一下花指令和SMC的结合
- 花指令垃圾指令作为SMC的开始结束标志
- SMC加密花指令
- SMC加密SMC嵌套加密,只有按照一定的顺序解密才能解出来(得画一次流程图)