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记得改页面属性,最好改成可读可写可执行

posted @ 2023-02-24 20:48  iPlayForSG  阅读(125)  评论(0编辑  收藏  举报