首页
论坛
专栏
课程

[调试逆向] [系统底层] [原创]使用模拟器进行x64驱动的Safengine脱壳+导入表修复

2019-2-7 11:21 6062
hzqst
3

[调试逆向] [系统底层] [原创]使用模拟器进行x64驱动的Safengine脱壳+导入表修复

2019-2-7 11:21
6062

0x00  概述

近日闲的蛋疼发现一款某地外挂被火绒爆勒索,本来准备安排一哈它的勒索功能

 结果发现sys直接明文存放在exe里,我也懒得花钱,遂dump之





IDA看了下发现是Safengine Shielden v2.4.0.0




0x02  分析


直接加载sys后发现往afd.sys挂了一个注册表回调,回调入口是跳板



由于驱动直接加载会返回C0000001导致立刻被卸载,无法直接dump


于是改用模拟器加载并在真实DriverEntry处dump整个sys内存:


经过调试发现Safengine保护的驱动会使用DriverObject->DriverSection进行PsLoadedModuleList的遍历并修复导入表


如果在模拟器中给上假的 DriverSection会导致SE访问到非法内存




于是我们给模拟器补上自己伪造的 PsLoadedModuleList和 DriverSection :




DriverObject.DriverSection = (PVOID)m_DriverLdrEntry;

补完后可以正常执行到真实入口点,可以看到这个sys执行的第一次API是RtlInitUnicodeString



我们定义代码从加壳sys的.sedata/.vmp节执行到.text 或INIT 就算进入真实入口点
bool bIsUnknownSection = (0 == memcmp((char *)SectionHeader[i].Name, ".text\0\0\0", 8) 
|| 0 == memcmp((char *)SectionHeader[i].Name, "INIT\0\0\0\0", 8)) ? false : true;

if(currentModule == ctx->m_ImageBase && ctx->m_IsPacked && !ctx->m_ImageRealEntry)
{
	FakeSection_t *section = NULL;
	if (ctx->FindSectionByAddress(address, &section) && !section->IsUnknownSection)
	{
		ctx->m_ImageRealEntry = address;
	}
}

至此,我们可以完整dump出刚刚进入真实入口点时的加壳驱动

virtual_buffer_t imagebuf(ctx.m_ImageEnd - ctx.m_ImageBase);

uc_mem_read(uc, ctx.m_ImageBase, imagebuf.GetBuffer(), ctx.m_ImageEnd - ctx.m_ImageBase);

FILE *fp = fopen("dump.sys", "wb");

fwrite(imagebuf.GetBuffer(), ctx.m_ImageEnd - ctx.m_ImageBase, 1, fp);

fclose(fp);

对比加壳sys和dumpsys可以发现 safengine使用了代码自修改





从区段自带可写入属性就可以看出这一点



当然,直接dump的sys是没法脱IDA的,还需要修正一下节表(文件和内存中的RVA完全一致)

auto SectionCount = ntheader->FileHeader.NumberOfSections;
for (USHORT i = 0; i < SectionCount; ++i)
{
	SectionHeader[i].PointerToRawData = SectionHeader[i].VirtualAddress;
	SectionHeader[i].SizeOfRawData = SectionHeader[i].Misc.VirtualSize;
}

以及修正入口点RVA到我们刚才用模拟器跑出的真正入口点

ntheader->OptionalHeader.AddressOfEntryPoint = (ULONG)(ctx.m_ImageRealEntry - ctx.m_ImageBase);



这样入口点就能被IDA正确识别了

然后还需要修复导入表,由于没法用工具修复,我们只能自己手撸代码解决



在可执行节中搜索所有的FF15 FF25(call qword ptr[xxx]和jmp qword ptr [xxx])
using namespace blackbone;

PatternSearch patternFF15("\xFF\x15");
PatternSearch patternFF25("\xFF\x25");

auto SectionCount = ntheader->FileHeader.NumberOfSections;

for (USHORT i = 0; i < SectionCount; ++i)
{
	if (SectionHeader[i].Characteristics & (IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_CNT_CODE))
	{
		patternFF15.Search((PUCHAR)ImageBase + SectionHeader[i].VirtualAddress,
			SectionHeader[i].Misc.VirtualSize, out);

		patternFF25.Search((PUCHAR)ImageBase + SectionHeader[i].VirtualAddress,
			SectionHeader[i].Misc.VirtualSize, out);
	}
}

对搜索到的所有 FF 15 xx xx xx xx 和FF 25 xx xx xx xx 判断后四字节是否是真正的RVA,并且根据RVA算出api地址,看api地址是否是有效API

	for (size_t j = 0; j < out.size(); ++j)
	{
		int insn_rva = (int)(out[j] - (ptr_t)ImageBase);
		int call_rva = insn_rva + *(int *)(out[j] + 2) + 6;
		if (call_rva >= 0 && call_rva < (int)ImageSize)
		{
			auto call_api_addr = *(ULONG_PTR *)(call_rva + (PUCHAR)ImageBase);

			FakeAPI_t *api_ptr = NULL;
			if (FindAPIByAddress(call_api_addr, dllnamew, &api_ptr))
			{
std::cout << "found IAT 0x" << std::hex << insn_rva << " to " << api_ptr->ProcedureName << "\n";
//....


保存这些insn_rva和API名,利用这些数据重建导入表,新加一个.idata节存放导入表


	memset(&SectionHeader[SectionCount], 0, sizeof(IMAGE_SECTION_HEADER));
	SectionHeader[SectionCount].VirtualAddress = SectionHeader[SectionCount].PointerToRawData = NewIATRva;
	SectionHeader[SectionCount].SizeOfRawData = (DWORD)RebuildIATSize;
	SectionHeader[SectionCount].Misc.VirtualSize = PAGE_ALIGN(RebuildIATSize);
	SectionHeader[SectionCount].Characteristics = IMAGE_SCN_MEM_READ | IMAGE_SCN_CNT_CODE;
	memcpy(SectionHeader[SectionCount].Name, ".idata\0\0", 8);
	ntheader->FileHeader.NumberOfSections++;

	ntheader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = RebuildIATRva;
	ntheader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size = (DWORD)RebuildIATDescSize;
	ntheader->OptionalHeader.SizeOfImage += (DWORD)PAGE_ALIGN(RebuildIATSize);

fwrite(RebuildIATBuffer.GetBuffer(), RebuildIATBuffer.GetLength(), 1, fp);

并将新增的节追加到dump的PE文件尾部,修改导入表入口,成功重建部分导入表



至此,该驱动的Safengine脱壳+导入表修复完美完成

0x03  总结


用Safengine加壳驱动并且不vm不混淆任何函数是非常不安全的行为,在模拟器的安排下几乎等同于裸奔。(此时有效的保护只有代码重组,只能干扰一下IDA的F5,如果单看汇编就是裸奔)

用VMProtect 3.X加壳并保护导入表会稍微好一点,不过稍微修改一下本文中的导入表重建算法,像https://bbs.pediy.com/thread-248812.htm一样 识别出VMP的API call,也可以无压力完美脱壳。

样本和脱完壳的sys下载地址已放附件

0x04  相关项目


模拟器:Unicorn PE (https://github.com/hzqst/unicorn_pe)



[推荐]看雪企服平台,提供安全分析、定制项目开发、APP等级保护、渗透测试等安全服务!

最后于 2019-2-7 11:24 被hzqst编辑 ,原因:
上传的附件:
上一主题 下一主题
打赏 + 3.00
打赏次数 2 金额 + 3.00
收起 
赞赏  demoscene   +1.00 2019/02/10 精品文章~
赞赏  junkboy   +2.00 2019/02/07 感谢分享~
最新回复 (19)
FANTASYING 2019-2-7 11:23
2
0
表哥安排!
小艾 2019-2-7 11:31
3
0
表哥牛批
romobin 2019-2-7 12:46
4
0
高深莫测 ,此贴必火
sudami 25 2019-2-7 13:10
5
0
厉害
bxb 2019-2-7 13:19
6
0
大过年的,不好好过年非要研究代码,表哥新年快乐!
taizhong 2019-2-7 13:24
7
0
大表哥你好,大表哥再见

,看不懂
sxpp 1 2019-2-7 14:00
8
0
牛b
chixiaojie 2019-2-7 15:23
9
0
这类技术贴碉堡了。
二娃 2019-2-7 19:56
10
0
表哥牛批
三十二变 3 2019-2-9 22:34
11
0
表哥(楼上都这么称呼,我也跟个风)安排得明明白白!
最后于 2019-2-9 22:35 被三十二变编辑 ,原因:
yeyeshun 2 2019-2-11 10:52
12
0
真牛批!
五天 2019-2-11 15:52
13
0
楼主厉害了
lhglhg 1 2019-2-11 20:50
14
0
楼主厉害了
囧囧 2019-2-12 00:14
15
0
Unicorn PE 666
瀚海云烟 1 2019-2-12 11:29
16
0
表哥牛的一批
kongfubull 2019-2-12 11:42
17
0
看的迷迷糊糊的,Mark下,回头仔细看
exediy 1 6天前
18
0
太溜了
littlewisp 2 6天前
19
0
学习了
cmputer 1天前
20
0
大佬,你那个标题写着反汇编器的软件是啥啊?分享吗
游客
登录 | 注册 方可回帖
返回