看雪论坛

[翻译]gargoyle,一个逃过内存扫描的技术

Green奇 2017-3-16 1238

gargoyle,一个逃过内存扫描的技术

201734

  gargoyle是一项将一个程序的所有可执行代码都隐藏在不可执行的内存块中的技术。在一些程序员定义区间内,gargoyle会活跃起来——结合一些ROP欺骗——把自己标记为可执行并发挥作用。

  在扫描内存来寻找异常时,通常会扫描可执行的内存块。gargoyle可以在Windows上实现将数据隐藏在不可执行的内存块中。

  1.gargoyle产生一些任意代码后,设置一些尾递归。

  2.VirtualProtectEx函数标记gargoyle为不可执行并返回到WaitForSingleObjectEx函数,该函数在等待我们发送Windows计时器给它。

  3.计时器的结束程序是一个ROP配置,pop *pop espret。它会把堆栈指针移到一个我们可以仔细地手动控制的堆栈中。

  4.我们特殊的堆栈让ret调用VirtualProtectEx函数,使我们可以读//执行。

  5.VirtualProtectEx函数返回到gargoyle,继续下一个循环。

  此处只展示了该技术在32Windows系统上的应用。下面,我们来挖掘该技术实现的所有细节。


动态内存分析

  动态的分析内存确实是一项代价很高的操作——如果你在使用Windows Defender,可能已经面临这个问题了(Google搜索 Antimalware Service Executable)(译者注:这是Windows Defender的一个进程,在后台执行扫描时会出现CPU和内存使用率居高不下的情况)。因为程序必须在可执行内存块中常驻,所以一种减少计算负担的技术就是只分析可执行代码页。在许多进程中,需要分析的内存数量会减少一个数量级。

  gargoyle显示这是一个有风险的行为。通过使用Windows异步过程调用,只读/写内存可以被调用为可执行内存来执行一些任务。一旦它完成了任务,就会恢复到读/写内存,直到计时器到期再重复这个循环。

  当然,Windows API中不存在InvokeNonExecutableMemoryOnTimerEx函数。重复这个循环需要其他的一些工作......


Windows异步过程调用(APCWindows Asynchronous Procedure

  异步编程允许一些任务推迟执行,可能是在一个单独线程的执行上下文中。每个线程都有自己的APC队列,当一个线程进入alertable状态时,Windows会从线程的APC队列调度工作到等待线程中。

有很多方法排队APC

   ·ReadFileEx

   ·SetWaitableTimer

   ·SetWaitableTimerEx

   ·WriteFileEx

  有很多方法让线程进入alertable状态:

   ·SleepEx

   ·SignalObjectAndWait

   ·MsgWaitForMultipleObjectsEx

   ·WaitForMultipleObjectsEx

   ·WaitForSingleObjectEx

  我们采用一个组合,用CreateWaitableTimer创建一个计时器,然后用SetWaitableTimer排队APC


  这些默认的安全属性就很好,我们不用手动重置,也不用命名计时器。所以CreateWaitableTimer的所有参数都默认为0nullptr。函数返回一个句柄给我们新定义的计时器。接着,我们需要排队APC

  第一个参数是我们从CreateWaitableTimer得到的句柄。pDueTime参数是一个指向LARGE_INTEGER的指针,指定第一个计时器到期的时间。例如,我们将它简单设置为0(马上到期)。lPeriod参数定义了毫秒级的到期间隔。这决定了时钟频率,即哪个gargoyle会被调用。

  下一个参数pfnCompletionRoutine是我们重点考虑的部分。这表示Windows调用等待线程的地址。当APC在被派遣时,可执行内存块中是没有gargoyle代码的。如果我们想在gargoyle中找到pfnCompletionRoutine,我们需要以一个数据执行保护(DEP)冲突来解决。

  相反,我们使用一种特殊的返回导向编程(ROP)二进制指令代码块(gadget)(译者注:gadget是从函数开始到ret返回指令之间的代码块,ROP技术按照一定顺序拼接这些代码,即通过代码复用,来执行想要的操作),通过lpArgToCompletionRoutine,将可执行线程的堆栈定位到SetWaitableTimer的第二个参数的地址指针。当ROP代码块中执行返回指令ret S,预先准备好的特殊的返回栈会调用VirtualProtectEx函数来标记gargoyle 为可执行。

  最后一个参数表示是否在计时器到期时唤醒一个休眠的计算机。我们在这里将其设置为false


Windows数据执行保护(DEP)和VirtualProtectEx函数

  最后使用的是VirtualProtectEx函数,用来标记内存中的各种安全属性:

  我们会两次调用VirtualProtectExgargoyle执行完之后(我们将线程标记为alertable状态之前)和gargoyle开始执行时(线程派遣APC完成之后)。更多细节请看信息图。

  在本文的证明中,gargoyletrampolineROP代码块和读/写内存都是同一个进程的,所以第一个参数hProcess相当于 GetCurrentProcess。参数lpAddress等于gargoyle的地址,参数dwSize等于gargoyle可执行内存块的大小。我们将想要的内存保护属性赋给参数flNewProtect。我们不用关心原先的保护属性是什么,但是lpflOldProtect不是一个可选属性,所以我们把它指向一些预先留出的空内存。

  唯一取决于上下文的参数是flNewProtect。当gargoyle转为休眠状态时,我们将其标记为PAGE_READWRITE0x04。在gargoyle转为可执行之前,我们将其标记为PAGE_EXECUTE_READ0x20


堆栈跳板

  注:如果你不熟悉x86调用规则,这部分理解会有些困难。 可以先读一下我的文章 x86 calling conventions

  通常情况下,ROP代码块通过逐条执行一系列工作来破坏数据执行保护,比如调用VirtualProtectEx函数将堆栈标记为可执行后,尾调用该堆栈中的一个地址。当攻击者在不可执行的内存块中写入一段代码并需要激活它时,这种方法很有用。攻击者还有可能将一定数量的ROP代码块拼接到一起来做相当多的工作。

  不幸的是,我们不能控制等待线程中非常多的代码。我们可以控制:(a)指令指针eip,通过pfnCompletionRoutine函数控制;(b)等待线程的堆栈中指向esp+4的指针,即指向函数的第一个参数,因为这个函数是采用WINAPI/__stdcall调用约定的。

  幸运的是,我们在APC排好队之前就可以完全执行了,所以可以为我们的等待队列小心的构建一个新的堆栈——堆栈跳板。我们的方法是找到一段ROP代码,让esp指针指向我们的堆栈跳板。代码形式如下:


  这有一点奇怪,因为函数并不会经常以pop esp/ret结尾,但是幸运的是,Intel x86在使用变长操作码时总是产生非常密集的可执行内存块。不管怎样,在32位系统mshtml.dll文件中相对基址偏移7165405处有这样一段代码块:


注:感谢 Sascha Schirra提供的优秀工具Ropper

  这段代码块会在我们调用SetWaitableTimer时,设置esp的值为我们放入lpArgToCompletionRoutine的任何值。现在要做的就是让lpArgToCompletionRoutine指向一些精心构造的看起来像堆栈的内存块。这些堆栈跳板看起来像这样:

  我们设置lpArgToCompletionRoutine的值与void* VirtualProtectEx参数的值相等,这样ROP代码块可以执行返回指令ret S并调用VirtualProtectEx 。当VirtualProtectEx得到这个调用时,esp会指向void* return_address。我们已经方便的将这个设置为——你猜到了——我们现在可执行的gargoyle,万事大吉!


gargoyle

  让我们暂停一下,一起看看在创建计时器和开始循环之前构建的读/Workspace。该Workspace包括三个主要的部分:一些配置来帮助gargoyle引导自己,堆栈空间,和StackTrampoline

  你看到StackTrampoline了,stack只是一个内存块。SetupConfiguration是这样的:

  不像在PoC开发的main.cpp中那样,SetupConfiguration是这样设置的:

  很简单。这很基础的指向了不同的Windows函数和一些有用的参数。

  现在你知道Workspace长什么样了,让我们回到gargoyle。一旦堆栈跳板调用VirtualProtectEx且尾调用发挥作用,gargoyle就执行。这时,esp指向old_protections,因为VirtualProtectEx采用WINAPI/__stdcall调用约定,会在返回前自己清理内存栈。

  注意我们已经将一个额外的参数void* setup_config放在了StackTrampoline的末尾。它很容易被放置,比如它作为调用gargoyle的某个函数(采用__cdecl__stdcall调用约定)的第一个参数。

  这让gargoyle可以在内存中找到其读/写配置:

  现在我们准备好了。esp指向Workspace.stackEbx保存了Configuration对象。如果这是gargoyle第一次被调用,我们需要设置计时器。我们通过Configurationinitialized字段来检查:

  如果gargoyle已经初始化,我们跳过所有计时器设置。

  注意在reset_trampoline中,我们将VirtualProtectEx的地址重新设置为堆栈跳板。在ROP代码块执行返回命令ret S后,VirtualProtectEx执行。它会在正常的函数执行时,破坏堆栈跳板上的地址。

  这时,你可以执行任意代码了。在PoC中,我们弹出一个消息框:

  一旦执行完成,我们需要尾调用VirtualProtectEx接着调用WaitForSingleObjectEx。我们实际调用了两次WaitForSingleObjectEx,因为APC会在第一次调用后返回并继续执行。这让我们进入了APC的无限循环:


测试

  PoC的源码在github,你可以很轻松的测试它,但是你需要安装:

  ·Visual Studio2015 Community已经被测试过了,可能在其他版本上也能用。

  ·Netwide Assembler v2.12.02 x64 已经被测试过了,可能在其他版本也能用。确保nasm.exe在你的路径上。

  克隆 gargoyle

     git clone  https://github.com/JLospinoso/gargoyle.git

     打开Gargoyle.sln并构建

  你必须在setup.pic同一目录下运行gargoyle.exe。解决方案的默认输出目录是DebugRelease

 每15秒,gargoyle会弹出一个消息框。当你点击okgargoyle会完成VirtualProtectEx/WaitForSingleObjectEx的调用。

  有趣的是,当gargoylePIC执行时,可以用 Sysinternals的优秀工具VMMap进行监测。如果一个消息框弹出,gargoyle就是可执行的。如果没有弹出,gargoyle就不是可执行的。当PIC调用控制之前,PIC的地址会在stdout输出。



原文链接:https://jlospinoso.github.io/security/assembly/c/cpp/developing/software/2017/03/04/gargoyle-memory-analysis-evasion.html

本文由 看雪翻译小组 Green奇 编译

最新回复 (7)
1
落笔飞花 2017-3-16
2
MARK
wang王 2017-3-17
3
一语中的 柳暗花明
御剑残风 2017-3-17
4
mark
lynnux 2017-3-17
5
粗略看了下,貌似是标题党啊
萌克力 2017-3-17
6
隐藏模块的技术终于要公开了啊哈哈哈哈哈哈
天天乐玩 2017-3-17
7
不错的分析
syser 6天前
8
lynnux 粗略看了下,貌似是标题党啊[em_16]
。。。没任何意义。。。就是完整的标题党。
返回



©2000-2017 看雪学院 | Based on Xiuno BBS | 知道创宇带宽支持 | 微信公众号:ikanxue
Time: 0.012, SQL: 6 / 京ICP备10040895号-17