SMC的实现方法
以前以为SMC只能用汇编写,现在翻blog发现可以用vs写
SMC的原理就是程序中的部分代码在运行之前是一坨乱七八糟的数据,但在执行了相关解密函数对其解密后,它就变成了正常的可执行代码。
可以大致认为是下面这个逻辑
proc main:
............
IF .运行条件满足
CALL DecryptProc(Address of MyProc);对某个函数代码解密
........
CALL MyProc ;调用这个函数
........
CALL EncryptProc(Address of MyProc);再对代码进行加密,防止程序被Dump
......
end main
大概包括加解密算法以及寻找到加解密部分的地址这俩部分。加解密想怎么写都行,重点在于如何寻址。
下面介绍几种实现方法,以MyProc代指需要SMC保护的代码
给定固定地址
这种写法是先把代码写好,然后动调拿到MyProc的首地址RVA,然后通过函数的ret指令计算出代码块的大小。把MyProc首地址RVA写入解密代码,再根据代码块大小进行加解密。
在保存程序后,再在二进制文件中找到MyProc的地址FOA,对其字节码加密,然后将其替换。
另外,还有一种简单的写法是:先将要进行SMC的部分的代码块取出来,将其加密后再放入数组中,在MyProc内先执行解密代码将该数据块解密,再通过指针调用这一块代码。
取RVA然后直接改程序文件这种写炸了,指针的写出来了,大概是这样
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>
char a[] = "TSCTF-J{do_you_think_this_is_a_real_flag}";
//char flag[] = "TSCTF-J{W3lC0M3_2_ReVEr$E_xOr_1s_$O0o_e2}";
int data[50] = //
{
18, 20, 7, 17, 4, 110, 10, 58, 25, 124, 32, 14, 122, 6, 123, 22, 100, 8, 6, 48, 4, 22, 34, 117, 27, 0, 36, 18, 40, 4, 105, 42, 57, 67, 43, 85, 13, 60, 5, 83, 19
};
char code[] = // 416140
{
0x13, 0xe, 0xcf, 0xa3, 0xe, 0xc5, 0xaa, 0x56, 0xe, 0xcf, 0xb, 0x56, 0xe, 0xcf, 0x13, 0x5e, 0x81, 0x3, 0xba, 0x46, 0x46, 0x46, 0x46, 0x81, 0x3, 0xba, 0x46, 0x46, 0x46, 0x46, 0xad, 0x7d, 0xcd, 0x3, 0xba, 0xe, 0x25, 0x96, 0xe, 0xcd, 0x3, 0x56, 0xe, 0x47, 0x96, 0x49, 0xf0, 0x46, 0x49, 0xf8, 0x86, 0x75, 0x3, 0xba, 0xc5, 0xb6, 0x0, 0xcf, 0x84, 0xcd, 0x3, 0xba, 0xe, 0xde, 0xe, 0xcb, 0x4a, 0xc3, 0x46, 0x46, 0x46, 0x46, 0xe, 0xcd, 0x3, 0x5e, 0xe, 0x47, 0x8e, 0xcd, 0x46, 0x7f, 0x84, 0x32, 0x44, 0xad, 0x4c, 0xc5, 0x3, 0xba, 0x47, 0xc5, 0x3b, 0xba, 0x6e, 0x38, 0xf9, 0xc5, 0x3b, 0xba, 0x6f, 0x32, 0x41, 0xfe, 0x46, 0x46, 0x46, 0x46, 0xad, 0x43, 0xfe, 0x47, 0x46, 0x46, 0x46, 0xe, 0xc5, 0x82, 0x56, 0x1b, 0x85,
};
//int run(char* a, int* b)// 4015C6 40154E
//{
// int i = 0;
// for (i = 0; i < 41; ++i)
// {
// if ((a[i] ^ i ^ 0x46) != b[i]) break;
// }
// if (i != 41) return 0;
// else return 1;
//}
void dec(char *a, int len)
{
int i;
for (i = 0; i < len; ++i) a[i] ^= 0x46;
}
int main()
{
char s[50];
scanf("%s", s);
int len = 0x4015C6 - 0x40154E + 1;
DWORD *newbloc = (DWORD *)malloc(32 * len);
VirtualProtect((void *)0x416140, len, 0x40, newbloc);
dec(code, len);
int (*runcode)(char*, int*) = (int(*)(char*, int*))&code;
if (runcode(s, data)) printf("Wow, You Win!");
else printf("OOps, Wrong!");
dec(code, len);
return 0;
}
注释掉的run函数是MyProc的源码,code数组是MyProc异或0x46后的数据。
值得注意的是,在进行SMC之前需要使用VirtualProtect等方法修改页面属性,这里的0x40就是可读可写可执行。这种写法写出来的runcode()实际上是在.data段而非.text段上的。.data段并不能执行程序,如果不修改的话程序根本跑不动。
新增一个段
pargma code_seg可以添加一个代码段,这里命名为.SMC,注意段名不能超过8字节
#pragma code_seg(".SMC")
void __cdecl run(char *a, int *b)
{
int i;
for (i = 0; i < 41; ++i)
{
if ((a[i] ^ i ^ 0x46) != b[i]) break;
}
if (i != 41) printf("OOps, Wrong!");
else printf("Wow, You Win!");;
}
#pragma code_seg()
#pragma comment(linker,"/SECTION:.SMC,ERW")
ERW是可读可写可执行,编译后可以看到多了个.SMC段
那么考虑遍历程序自身的PE结构,找到该.SMC段,并直接对其进行SMC操作
#include <stdio.h>
#include <windows.h>
char a[] = "TSCTF-J{do_you_think_this_is_a_real_flag}";
//char flag[] = "TSCTF-J{W3lC0M3_2_ReVEr$E_xOr_1s_$O0o_e2}";
int data[50] = //
{
18, 20, 7, 17, 4, 110, 10, 58, 25, 124, 32, 14, 122, 6, 123, 22, 100, 8, 6, 48, 4, 22, 34, 117, 27, 0, 36, 18, 40, 4, 105, 42, 57, 67, 43, 85, 13, 60, 5, 83, 19
};
#pragma code_seg(".SMC")
void __cdecl run(char *a, int *b)
{
int i;
for (i = 0; i < 41; ++i)
{
if ((a[i] ^ i ^ 0x46) != b[i]) break;
}
if (i != 41) printf("OOps, Wrong!");
else printf("Wow, You Win!");;
}
#pragma code_seg()
#pragma comment(linker,"/SECTION:.scode,ERW")
void decode()
{
// 寻址操作,板子
LPVOID pModule = GetModuleHandle(NULL); // 获得进程句柄
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pModule;
PIMAGE_NT_HEADERS32 pNtHeader = (PIMAGE_NT_HEADERS32)((DWORD)pDosHeader + pDosHeader->e_lfanew);
PIMAGE_FILE_HEADER pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pNtHeader + 4);
PIMAGE_OPTIONAL_HEADER pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)((DWORD)pNtHeader + IMAGE_SIZEOF_FILE_HEADER + 4);
PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);
// 遍历表头找到名为“.SMC”的段地址
while (strcmp((char*)pSectionHeader->Name, ".SMC")) pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pSectionHeader + IMAGE_SIZEOF_SECTION_HEADER);
PBYTE pSection = (PBYTE)((DWORD)pModule + pSectionHeader->VirtualAddress); // 该段的VA
// 对.SMC段的加密
DWORD* newbloc = (DWORD*)malloc(32 * pSectionHeader->SizeOfRawData);
VirtualProtect((void*)pSection, pSectionHeader->SizeOfRawData, 0x40, newbloc);
for (DWORD i = 0; i < pSectionHeader->SizeOfRawData; i++)
*(pSection + i) = *(pSection + i) ^ 0x46;
// 写.SMC修改后的shellcode
//FILE* pFile = NULL;
//char FileName[] = "./shellcode";
//pFile = fopen(FileName, "wb");
//if (!pFile)
//{
// printf("Failed to Write File\n");
// return;
//}
//fwrite(pSection, 1, pSectionHeader->SizeOfRawData, pFile);
//fclose(pFile);
//printf("Write Success\n");
}
int main()
{
char s[50];
scanf("%s", s);
decode();
run(s, data);
decode();
return 0;
}
把第一次经过加密的.SMC段覆盖程序的原SMC段,然后就能跑起来了
但是这个方法有一点不好:程序里面莫名其妙多了个奇怪的段,逆向人员一看就知道有鬼。
一些想法
SMC确实好用,但是逆向人员一看一大片垃圾数据,肯定会起疑。或许可以结合一些特殊的花指令的原理对少部分 机器码进行修改,来让ida等工具能正常反编译出伪代码,来达到误导静态调试的目的。
另外,写SMC一定要避免加密系统函数或一切带有动态地址的玩意:重定位表会先修正内存中的地址,然后才会执行代码;SMC会将修正完毕的地址再次加密,导致地址错误(例如VS的__CheckForDebuggerJustMyCode)。
SMC记得改页面属性,最好改成可读可写可执行