怎样让shellcode在内存中始终不可见(shellGhost分析)

渗透技巧 1年前 (2023) admin
329 0 0

前言

逛github的时候看到了一个项目ShellGhost,介绍了一种内存规避技术,可以让shellcode在进程从开始到结束都始终是不可见的。讲的还是挺邪乎的,不过我盲猜是利用异常获取CPU控制权去动态修改shellcode的内容,这就下载学习一下是怎么实现的。

shellcode的预处理

项目提供了一个python脚本 ShellGhost_mapping.py 来对shellcode进行处理,主要是使用ndisasm对shellcode进行反汇编,然后分别获取到每一条指令的相对偏移以及长度,保存在一个数组中。

代码如下:

怎样让shellcode在内存中始终不可见(shellGhost分析)

处理之后的结果示例如下:

怎样让shellcode在内存中始终不可见(shellGhost分析)

RVA保存的就是指令的相对偏移,quota保存的就是当前的指令长度。记住这个表的结构,它会贯穿后续shellcode执行全过程。

最后是输出每条指令被RC4加密之后的结果,注意是每条指令单独加密,而不是所有指令一起加密。将加密后的shellcode,密钥,指令长度,以及上述的表写入到c文件中。

怎样让shellcode在内存中始终不可见(shellGhost分析)好了,数据准备工作到此为止,接下来开始分析执行过程。

初始化过程

首先看一下main函数:

怎样让shellcode在内存中始终不可见(shellGhost分析)

首先,申请的内存并不是可执行的,然后将这块内存都修改为0xcc软中断;

另外比较怪异的一点是启动的新线程的的起始地址是.text节的末尾全是 x00 的地方。

ResolveEndofTextSegment会返回.text末尾全是x00的起始地址,详细代码如下:

怎样让shellcode在内存中始终不可见(shellGhost分析)

为什么要这么做呢?作者在注释里也解释了,靠近entry_point位置的代码或者地址其实是被杀毒软件重点关注的,其实在引擎中很多特征码的定位都是以entry_point 作为起始地址的。

最后注册了一个向量化异常处理函数InterceptShellcodeException ,这个处理函数是shellcode可以执行的关键。

shellcode的执行

InterceptShellcodeException 异常处理函数是shellcode可以正常执行的关键,可以看到在上面创建的线程的起始地址也不是shellcode,接下来怎么控制EIP指向shellcode呢?其实精髓就是在这个异常处理函数。

首先要知道的是.text节末尾全是x00,反汇编结果如下图所示,在这里创建线程一定会触发异常,rax一定不会是一个可写的地址,想一想是为什么?

怎样让shellcode在内存中始终不可见(shellGhost分析)

因为看代码:

hThread = CreateThread(00, (LPTHREAD_START_ROUTINE)ResolveEndofTextSegment(), 000);

RAX此时应该保存的刚好是 ResolveEndofTextSegment()的返回值,text节显然是不可写的。这里处理的还是比较精妙的,需要仔细的琢磨一下。接下来执行流程就进入了异常处理函数:

怎样让shellcode在内存中始终不可见(shellGhost分析)

首先判断异常是不是在自己需要处理的地址范围内,然后判断如果异常发生的地方在.text的默认,就将执行流(eip)修改到堆上,但是当前的堆上有两个问题:

  1. 1. 是只可读写,不可执行的

  2. 2. 全是0xcc

继续阅读代码,看是怎么处理的:

怎样让shellcode在内存中始终不可见(shellGhost分析)

首先是调用 ResolveInstructionByRva 修改当前的指令为解密后的指令,此函数如下:

// Edits current breakpoint to be the next instruction to decrypt
NTSTATUS ResolveInstructionByRva(PVOID pointer) //修改当前指令
{
    DWORD64 rva = ResolveBufferFeature(pointer, INSTRUCTION_OPCODES_RVA); // 获取当前地址的rva
    for (DWORD i = 0; i < instruction[ResolveBufferFeature(pointer, INSTRUCTION_OPCODES_NUMBER)].quota; i++)
    {
        *(BYTE*)((BYTE*)pointer + i) = *(BYTE*)((BYTE*)sh + rva + i);
    }

    return STATUS_SUCCESS;

}

其中ResolveBufferFeature就会利用之前存储的instruction找到当前指令的RVA和quota,然后进行修改。

然后对内存的这一条指令进行解密,解密结束之后修改内存的权限为可执行,然后继续执行这一条指令。

当执行下一条指令时,由于内容是0xCC,所以会再次触发异常,继续走上面的异常函数处理逻辑,达到了单条指令依次执行的目的。

当在执行下一条指令的时候会顺便把上一条指令给抹掉,防止在内存中保留有可见的shellcode。

怎样让shellcode在内存中始终不可见(shellGhost分析)

相信已经讲解的够清楚,为了保险起见,还是来画个图更形象的说明一下。

怎样让shellcode在内存中始终不可见(shellGhost分析)

总结

如此执行shellcode具备隐藏内存,反调试等优点,但是唯一的问题就是执行效率太低了。我虽然没有想到有什么实际的用途,但是作者的代码写的非常的工整,也可以给我们写其他的内存规避提供一些实现思路。

最后欢迎加入知识星球进行深度交流~

怎样让shellcode在内存中始终不可见(shellGhost分析)

咨询微信

怎样让shellcode在内存中始终不可见(shellGhost分析)

原文始发于微信公众号(安全的矛与盾):怎样让shellcode在内存中始终不可见(shellGhost分析)

版权声明:admin 发表于 2023年7月20日 下午5:51。
转载请注明:怎样让shellcode在内存中始终不可见(shellGhost分析) | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...