Gargoyle——内存扫描逃逸技术

简介:

0x00 前言

Gargoyle是一种在非可执行内存中隐藏可执行代码的技术。在一些程序员定义的间隔,gargoyle将苏醒,且一些ROP标记它自身为可执行,且做一些事:

这个技术是针对32位的Windows阐述的。在本文中,我们将深入探讨其实现细节。

0x01 实时内存分析

执行实时内存分析是一个相当大代价的操作,如果你使用Windows Defender,你可能在这个问题上就到头了(谷歌的反恶意软件服务)。因为程序必须在可执行的内存中,用于减少计算负担的一种常用技术是只限制可执行代码页的分析。在许多进程中,这将数量级的减少要分析的内存数量。

Gargoyle表明这是个有风险的方式。通过使用Windows异步过程调用,读写内存能被作为可执行内存来执行一些任务。一旦它完成任务,它回到读写内存,直到定时器过期。然后重复循环。

当然,没有Windows API InvokeNonExecutableMemoryOnTimerEx。得到循环需要做一些操作。

0x02 Windows异步过程调用(APC)

异步编程使一些任务延迟执行,在一个独立的线程上下文中执行。每个线程有它自己的APC队列,并且当一个线程进入alertable状态,Windows将从APC队列中分发任务到等待的线程。

有一些方法来插入APC:

ReadFileEx

SetWaitableTimer

SetWaitableTimerEx

WriteFileEx

进入alertable状态的方法:

SleepEx

SignalObjectAndWait

MsgWaitForMultipleObjectsEx

WaitForMultipleObjectsEx

WaitForSingleObjectEx

我们要使用的组合是用CreateWaitableTimer创建一个定时器,然后使用SetWaitableTimer插入APC队列:

默认的安全属性是fine,我们不想手动重置,并且我们不想要一个命名的定时器。因此对于CreateWaitableTimer所有的参数是0或者nullptr。这个函数返回一个HANDLE,表示我们新的定时器。接下来,我们必须配置它:

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

下个参数pfnCompletionRoutine将是我们要努力的主题。这是来自等待线程的Windows调用的地址。听起来很简单,除非在可执行内存中分发的APC没有一个gargoyle代码。如果我们将pfnCompletionRoutine指向gargoyle,我们将触发数据执行保护(DEP)。

取而代之,我们使用一些ROP gadget,将重定向执行线程的栈到lpArgToCompletionRoutine指向的地址。当ROP gadget执行,指定的栈在调用gargoyle第一条指令前调用VirtualProtectEx来标记gargoyle为可执行。

最后一个参数与在定时器到期后是否唤醒计算机有关。我们设置为false。

0x03 Windows数据执行保护和VirtualProtectEx

最后是VirtualProtectEx,用来修改各种内存保护属性:

我们将在两种上下文中调用VirtualProtectEx:在gargoyle完成执行后(在我们触发线程alertable之前)和在gargoyle开始执行之前(在线程分发APC之后)。看资料了解详情。

在这个PoC中,我们将gargoyle,跳板,ROP gadget和我们的读写内存都放进同一个进程中,因此第一个参数hProcess设置为GetCurrentProcess。下一个参数lpAddress与gargoyle的地址一致,dwSize与gargoyle的可执行内存大小一致。我们提供期望的保护属性给flNewProtect。我们不关心老的保护属性,但是不幸的是lpflOldProtect不是一个可选的参数。因此我们将将它设为一些空内存。

唯一根据上下文不同的参数是flNewProtect。当gargoyle进入睡眠,我们想修改它为PAGE_READWRITE或0x04。在gargoyle执行前,我们想标记它为PAGE_EXECUTE_READ或0x20。

0x04 栈跳板

注意:如果你不熟悉x86调用约定,本节将比较难以理解。对于新人,可以参考我的文章x86调用约定。

通常,ROP gadget被用来对抗DEP,通过构建调用VirtualProtectEx来标记栈为可执行,然后调用到栈上的一个地址。这在利用开发中经常很有用,当一个攻击者能写非可执行内存。可以将一定数量的ROP gadget放在一起做相当多的事。

不幸的是,我们不能控制我们的alerted线程的上下文。我们能通过pfnCompletionRoutine控制eip,并且线程栈中的指针位于esp+4,即调用函数的第一个参数(WINAPI/__stdcall调用约定)。

幸运的是,我们已经在APC入队前就执行了,因此我们能在我们的alerted线程中小心的构建一个新的栈(栈跳板)。我们的策略是找到代替esp指向我们栈跳板的ROP gadget。下面的形式的就能工作:

有点诡异,因为函数通常不以pop esp/ret结束,但是由于可变长度的操作码,Intel x86汇编过程会产生非常密集的可执行内存。不管怎样,在32位的mshtml.dll的偏移7165405处有这么一个gadget:

注意:感谢Sascha Schirra的Ropper工具。

在我们调用SetWaitableTimer时,这个gadget将设置esp为我们放入lpArgToCompletionRoutine中的任何值。剩下的事就是将lpArgToCompletionRoutine指向一些构造的栈内存。栈跳板看起来如下:

我们设置lpArgToCompletionRoutine为void* VirtualProtectEx参数,以便ROP gadget能ret到执行VirtualProtectEx。当VirtualProtectEx得到这个调用,esp将指向void* return_address。我们可以设置这个为我们的gargoyle。

0x05 gargoyle

让我们暂停片刻,看下在我们创建定时器和启动循环之前创建的读写Workspace。这个Workspace包含3个主要内容:一些配置帮助gargoyle启动自身,栈空间和StackTrampoline:

你已经看见了StackTrampoline,和stack是一个内存块。SetupConfiguration:

在PoC的main.cpp中,SetupCOnfiguration这么设置:

非常简单。简单的指向多个Windows函数和一些有用的参数。

现在你有了Workspace的大概印象,让我们回到gargoyle。一旦栈跳板被VirtualProtectEx调用,gargoyle将执行。这一刻,esp指向old_protections,因为VirtualProtect使用WINAPI/__stdcall约定。

注意我们放入了一个参数(void* setup_config)在StackTrampoline的末尾。这是方便的地方,因为如果它是以__cdecl/__stdcall约定调用gargoyle的第一个参数。

这将使得gargoyle能在内存中找到它的读写配置:

现在我们准备好了。Esp指向了Workspace.stack。我们在ebx中保存了Configuration对象。如果这是第一次调用gargoyle,我们将需要建立定时器。我们通过Configuration的initialized字段来检查这个:

如果gargoyle已经初始化了,我们跳过定时器创建。

注意,在reset_trampoline中,我们重定位了跳板中VirtualProtectEx的地址。在ROP gadget ret后,执行VirtualProtectEx。当它完成后,它在正常的函数执行期间将破环栈上的地址。

你能执行任意代码。对于PoC,我们弹出一个对话框:

一旦我们完成了执行,我们需要构建调用到VirtualProtectEx,然后WaitForSingleObjectEx。我们实际上构建了两个调用到WaitForSingleObjectEx,因为APC将从第一个返回并继续执行。这启动了我们定义的循环APC:

0x06 测试

PoC的代码在github上,且你能简单的测试,但是你必须安装:

Visual studio 2015 Community,但是其他版本也能用

Netwide Assembler v2.12.02 x64,但是其他版本也能用。确保nasm.exe在你的路径中。

克隆gargoyle:


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

打开Gargoyle.sln并构建。

你必须在和setup.pic相同的目录运行gargoyle.exe。默认解决方案的输出目录是Debug或release。

每15秒,gargoyle将弹框。当你点击确定,gargoyle将完成VirtualProtectEx/WaitForSingleObjectEx调用。

有趣的是,使用Systeminternal的VMMap能验证gargoyle的PIC执行。如果消息框是激活的,gargoyle将被执行。反之则没有只想能够。PIC的地址在执行前使用stdout打印。

作者:myswsun
来源:51CTO

相关文章
|
10月前
|
存储 机器学习/深度学习 PyTorch
119_LLM训练的高效内存管理与优化技术:从ZeRO到Flash Attention
大型语言模型(LLM)的训练面临着前所未有的计算和内存挑战。随着模型规模达到数百亿甚至数千亿参数,高效的内存管理成为训练成功的关键因素之一。2025年,LLM训练的内存优化技术已经取得了显著进展,从ZeRO优化器到Flash Attention等创新技术,为训练超大规模模型提供了可能。
966 159
|
KVM 虚拟化
KVM的热添加技术之内存
文章介绍了KVM虚拟化技术中如何通过命令行调整虚拟机内存配置,包括调小和调大内存的步骤,以及一些相关的注意事项。
536 4
KVM的热添加技术之内存
|
机器学习/深度学习 监控 安全
解密虚拟化弹性内存:五大核心技术与实施策略
本文深入解析虚拟化环境中实现内存弹性管理的五大核心技术与实施策略。内容涵盖内存架构演进、关键技术原理、性能优化方法及典型问题解决方案,助力提升虚拟机密度与资源利用率。
505 0
|
Java 编译器 Go
go的内存逃逸分析
内存逃逸分析是Go编译器在编译期间根据变量的类型和作用域,确定变量分配在堆上还是栈上的过程。如果变量需要分配在堆上,则称作内存逃逸。Go语言有自动内存管理(GC),开发者无需手动释放内存,但编译器需准确分配内存以优化性能。常见的内存逃逸场景包括返回局部变量的指针、使用`interface{}`动态类型、栈空间不足和闭包等。内存逃逸会影响性能,因为操作堆比栈慢,且增加GC压力。合理使用内存逃逸分析工具(如`-gcflags=-m`)有助于编写高效代码。
341 2
|
人工智能 物联网 C语言
SVDQuant:MIT 推出的扩散模型后训练的量化技术,能够将模型的权重和激活值量化至4位,减少内存占用并加速推理过程
SVDQuant是由MIT研究团队推出的扩散模型后训练量化技术,通过将模型的权重和激活值量化至4位,显著减少了内存占用并加速了推理过程。该技术引入了高精度的低秩分支来吸收量化过程中的异常值,支持多种架构,并能无缝集成低秩适配器(LoRAs),为资源受限设备上的大型扩散模型部署提供了有效的解决方案。
1201 5
SVDQuant:MIT 推出的扩散模型后训练的量化技术,能够将模型的权重和激活值量化至4位,减少内存占用并加速推理过程
|
存储 缓存 安全
操作系统中的内存管理:技术与挑战
在数字化时代,操作系统的内存管理成为计算机科学领域中一个至关重要的技术环节。本文将深入探讨现代操作系统中内存管理的基本原理、关键技术及其面临的挑战。通过对分页、分段、虚拟存储和缓存策略等核心概念的介绍,我们旨在揭示内存管理如何优化系统性能,保障数据安全,并提高资源利用率。同时,文章还将讨论内存泄漏、碎片化以及安全性问题等当前内存管理技术所面临的主要挑战。
460 25
|
存储 算法
探索现代操作系统中的虚拟内存管理技术
在数字时代的浪潮中,操作系统的心脏——虚拟内存管理技术,正以它独有的韵律跳动。本文将带你穿梭于操作系统的迷宫,揭开虚拟内存如何巧妙地扩展有限的物理内存之谜。从分页机制的精妙设计到交换空间的策略运用,我们将一探究竟。你将看到,虚拟内存不仅仅是一个存储数据的地方,它是速度与效率的协调者,是多任务处理的幕后英雄。随着技术的演进,虚拟内存管理不断优化,为应用程序提供了一片更为广阔的运行天地。让我们一同走进这个充满智慧的世界,感受操作系统中虚拟内存管理的魅力所在。
391 28
|
缓存 算法 调度
操作系统的虚拟内存管理技术探究
虚拟内存管理是现代操作系统中的一项核心技术,它允许系统使用比物理内存更多的内存。本文将深入探讨虚拟内存的概念、实现机制以及它在操作系统中的应用,旨在为读者提供一个关于虚拟内存管理技术的全面视角。
392 27
ARM64技术 —— MMU处于关闭状态时,内存访问是怎样的?
ARM64技术 —— MMU处于关闭状态时,内存访问是怎样的?
|
物联网 云计算
操作系统中的内存管理技术解析
【7月更文挑战第13天】本文将深入探讨操作系统中至关重要的内存管理技术,包括虚拟内存、分页和分段机制等核心概念。我们将从内存管理的基本原理出发,逐步过渡到高级技术如交换空间和文件映射,最后讨论现代操作系统中内存管理面临的挑战与未来发展方向。文章旨在为读者提供对操作系统内存管理全面而深入的理解。
339 7