初探堆栈欺骗之静态欺骗

news/2025/3/15 11:23:56/文章来源:https://www.cnblogs.com/fdxsec/p/18234871

本文首发先知社区:https://xz.aliyun.com/t/14487
首先介绍一下堆栈欺骗的场景,当我们用一个基本的 shellcode loader 加载 cs 的 shellcode,在没有对堆栈做任何事情时,我们的堆栈是不干净的,我们去看一下堆栈时会发现有很多没有被解析的地址在其中,这显然是不正常的,因此 av/edr 会重点扫描这部分内存区域,就可能会导致我们的 loader gg。
image.png
或者说当我们直接系统调用时,和正常程序也是有区别的,如下:

  • 正常程序:主程序模块->kernel32.dll->ntdll.dll->syscall,这样当0环执行结束返回3环的时候,这个返回地址应该是在ntdll所在的地址范围之内
  • 直接进行系统调用:此时当ring0返回的时候,rip将会是你的主程序模块内,而并不是在ntdll所在的范围内。

因此我们需要堆栈欺骗来帮我们隐藏堆栈。
我们先需要 32 位/64 位下堆栈的知识,推荐阅读:https://cloud.tencent.com/developer/article/2149944
https://pyroxenites.github.io/post/diao-yong-zhan-qi-pian/
https://mp.weixin.qq.com/s/_Cr6Ds0vaeGF7DShlq_XJg
https://codemachine.com/articles/x64_deep_dive.html
我们也来简单的说一下,在 32 位下,是通过rbp 来指向堆栈的开始位置,并且每次移动 rbp 时会 push rbp,然后再 mov rbp,rsp,因此我们只需要不断回溯 rbp 就可以回溯完整个堆栈。
在 64 位下,ebp 不再有这样的功能,它现在是一个通用寄存器,下面上两张图简单解释一下吧,这篇文章涉及到的技术为被动欺骗,不需要很深的理解也能看懂大部分。
x64 PE 文件中存在一个名为 .pdata的区段,区别于x32其属于x64独有区段,值的注意的是.pdata的RVA和异常目录表的RVA是相同。pdata中的数据由 多个 _IMAGE_RUNTIME_FUNCTION_ENTRY 结构体组成,具体的声明如下:

typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY {DWORD BeginAddress;DWORD EndAddress;union {DWORD UnwindInfoAddress;DWORD UnwindData;} DUMMYUNIONNAME;
} RUNTIME_FUNCTION, *PRUNTIME_FUNCTION, _IMAGE_RUNTIME_FUNCTION_ENTRY, *_PIMAGE_RUNTIME_FUNCTION_ENTRY;

从每个字段类型位DWORD可以看出,其表示的都是RVA,所以在使用时都需要加上模块基地址,BeginAddress代表函数的起始地址RVA,EndAddress代表函数的结束地址RVA,UnwindInfoAddress指向 _UNWIND_INFO结构体,其声明如下:

typedef struct _UNWIND_INFO {UBYTE Version       : 3;UBYTE Flags         : 5;UBYTE SizeOfProlog;UBYTE CountOfCodes;UBYTE FrameRegister : 4;UBYTE FrameOffset   : 4;UNWIND_CODE UnwindCode[1];
/*  UNWIND_CODE MoreUnwindCode[((CountOfCodes + 1) & ~1) - 1];
*   union {
*       OPTIONAL ULONG ExceptionHandler;
*       OPTIONAL ULONG FunctionEntry;
*   };
*   OPTIONAL ULONG ExceptionData[]; */
} UNWIND_INFO, *PUNWIND_INFO;

Version默认为1,Flags总共包含四个值,UNW_FLAG_NHANDLER,UNW_FLAG_EHANDLER ,UNW_FLAG_UHANDLER,UNW_FLAG_CHAININFO,SizeOfProlog表示序言大小(字节),CountOfCodes代表序言操作中所有指令总共占用的”槽“数量,FrameRegister用到的帧寄存器,FrameOffset帧寄存器距离栈顶的偏移。
UnwindCode表示的是 _UNWIND_CODE联合体,大小为两个字节,其声明如下:

typedef union _UNWIND_CODE {struct {UBYTE CodeOffset;UBYTE UnwindOp : 4;UBYTE OpInfo   : 4;};USHORT FrameOffset;
} UNWIND_CODE, *PUNWIND_CODE;

CodeOffset紧跟序言的代码起始偏移,UnwindOp操作码,Opinfo对应操作码的附加操作信息。
然后就根据UnwindOp 对应不同操作码对栈的影响,即可计算某个函数的栈帧大小了。
下面上两张图帮大家理解一下:
image.png
image.png

我们在这篇文章中先介绍被动欺骗,或者说是静态欺骗,它是关于 sleep 的欺骗,或者说是睡眠时间混淆,并不能说是真正意义的堆栈欺骗,但是对于 beacon 来说也是有一定意义的,而主动欺骗,支持任何函数的堆栈欺骗,将在下一篇文章进行介绍。下面我们一起来看几个项目。

threadStackSpoofer

第一个方式的项目地址在https://github.com/mgeeky/ThreadStackSpoofer。
首先是处理参数和读取 shellcode 的部分,我们不关心。
image.png
然后又调用了 hookSleep 函数,我们跟进去
image.png
在 hookSleep 函数里面,他先准备了一个结构体,结构体里面包含了要 hook 的字段以及将 hook 的函数改写到哪里的字段,然后将 sleep,自实现的 MySleep,buffers 一并传给 fastTrampoline 函数。
image.png
image.png
在接下来构造了一个 trampoline 用于跳转
image.png
调试一个,可以看到 addr 的地址其实就是我们自实现的 MySleep 里面
image.png
然后保存一下原始的 addressToHook 字节,再将我们 trampoline 重写到 addressToHook 的位置,这样调用 Sleep 的时候其实会跳转到我们自实现的 MySleep 里面。
image.png
然后这部分代码相当于对当前进程刷新一下缓存,使得我们修改生效
image.png
然后就是注入 shellcode 的过程,然后当我们的 beacon sleep 时,就会调用到我们的 MySleep 函数,我们接下来再看看 MySleep 是如何处理我们的堆栈的。
image.png
_AddressOfReturnAddress 是编译器提供的一个函数,作用是返回当前函数返回地址的内存地址,给到 overwrite
image.png
然后关键就来了,我们将overwrite 直接改写为 0,这样停止继续回溯栈,然后我们就可以隐藏剩余的栈帧,即我们的 shellcode 栈帧就会被隐藏,当 sleep 结束之后再将栈帧改写回去。
image.png
这是调用堆栈未被欺骗时的样子:
image.png
当启用线程堆栈欺骗时:
image.png
此时帧栈展开到我们的 MySleep 函数,往后 shellcode 的帧栈就被隐藏了,当然我们还可以做更多有趣的事情,比如在 sleep 期间更改 shellcode 内存属性,对 shellcode 内存区域进行加密,或者解除我们对 etw/amsi 的 hook,在 sleep 之后再重新 hook,或者等等等等可以由大家自由发挥。
但是这里还是会有一些问题的,我们将调用堆栈设为不可展开,这意味着它看起来异常,因为系统将无法正确遍历整个调用堆栈帧链。当一个专业的恶意软件分析师在分析时自然会发觉异常,但是那些内存扫描工具就不一定了,它总不能遍历每个线程的堆栈来验证其是否不可展开。

CallStackMasker

这个项目的地址在https://github.com/Cobalt-Strike/CallStackMasker ,cs 官方也写了博客来介绍这个技术https://www.cobaltstrike.com/blog/behind-the-mask-spoofing-call-stacks-dynamically-with-timers
这个项目是计时器欺骗调用堆栈的 PoC ,在 beacon 休眠之前,我们可以对计时器进行排队,用假的调用堆栈覆盖其调用堆栈,然后在恢复执行之前恢复原始调用堆栈。因此,就像我们可以在睡眠期间欺骗属于我们的植入物的内存一样,我们也可以欺骗主线程的调用堆栈。这种方式是比较简单的复制堆栈,避免了主动堆栈欺骗的复杂性。
如果我们考虑一个正在执行任何类型等待的通用线程(waitforsingleobject),它在等待满足之前无法修改自己的堆栈。此外,它的堆栈始终是可读写的。因此,我们可以使用定时器来:

  1. 创建当前线程堆栈的备份
  2. 用假线程堆栈覆盖它
  3. 在恢复执行之前恢复原始线程堆栈

这就是这个技术的核心,PoC 以两种模式运行:静态和动态。静态模式模仿 spoolsv.exe 硬编码调用堆栈。该线程如下所示,通过 KERNELBASE!WaitForSingleObjectEx 可以看到处于‘Wait:UserRequest’ 状态:
image.png
我们的线程的起始地址和调用堆栈与上面 spoolsv.exe 中标识的线程相同:
image.png
静态模式的明显缺点是我们仍然依赖硬编码的调用堆栈。为了解决这个问题,PoC 还实现了动态调用堆栈欺骗。在此模式下,它将枚举主机上所有可访问的线程,并找到一个处于所需目标状态的线程(即通过 WaitForSingleObjectEx 的 UserRequest)。一旦找到合适的线程堆栈,它将复制它并使用它来休眠线程的克隆。同样,PoC 将再次复制克隆线程的起始地址,以确保我们的线程看起来合法。
好的,接下来让我们看看代码:
关于堆栈计算大小等等代码我们先略过,这并不会影响我们理解这项技术,并且解释起来显得太啰嗦。
我们看关键地方,这里创建一个新的线程,并且将 rip 指针指向 go 函数,也就是说要执行我们的 go 函数。
image.png
image.png
我们来看MaskCallStack,先是初始化上下文和句柄,方便后续操作,然后获取 NtContinue 函数地址:通过 GetProcAddress 函数获取 Ntdll 模块中的 NtContinue 函数的地址。这个函数通常用于继续执行线程
image.png
设置定时器,创建定时器,并设置回调函数,以执行一系列操作:备份堆栈、覆盖堆栈、恢复堆栈和设置事件,
当等待事件对象被定时器触发,此时调用堆栈将被遮蔽,然后定时器结束之后又触发事件,堆栈又恢复。
image.png
image.png
image.png

纤程

纤程是一种用户级线程,它允许在一个线程内部进行上下文切换。纤程的切换完全由程序控制,不需要内核的参与,因此效率非常高。纤程的上下文包括寄存器状态和堆栈,当切换纤程时,当前纤程的上下文会被保存,然后加载新纤程的上下文。这意味着,通过纤程切换,可以改变当前线程的堆栈。
一个线程可以创建多个纤程,并通过调用 SwitchToFiber 函数根据需要在它们之间切换。在此之前,当前线程本身必须通过调用 ConvertThreadToFiber 成为纤程,因为只有一个纤程可以创建其他纤程。
所以当我们进行 sleep 时可以切换到新的纤程里面进行 sleep,从而隐藏我们 shellcode 堆栈,当调用返回时,它将再次切换到 shellcode 的纤程,以便可以继续执行。
重要的 api 使用如下:

// 创建纤程
LPVOID lpFiber = CreateFiber(0, FiberFunc, NULL);
// 将当前线程转换为纤程
ConvertThreadToFiber(NULL);
// 切换到新创建的纤程
SwitchToFiber(lpFiber);

项目参考:https://github.com/Kudaes/Fiber
代码实现的话第一个项目改改就可以实现,先 hook sleep 函数,然后调用 sleep 函数的时候就可以将上下文转换到一个新的纤程中,然后 sleep 结束之后,再转回 shellcode 执行的纤程中即可,这里不再分析代码。
但是当我们在执行 shellcode 相关功能时如果被检测到了会直接 gg。

  • LoundSunRun 间接系统调用 调用堆栈合成帧 https://cn-sec.com/archives/2149720.html

0x00000009b0f6f258 {140730941243392}
00000009B0F6F3F0
image.png
image.png

rbp
rsp
push rbp 保存栈,方便回溯
return to x 返回地址 rip

  • RBP 将指向此功能的堆栈帧的开始地方。
  • RBP 将包含前一个堆栈帧的起始地址。
  • (RBP + 0x8)将指向堆栈跟踪中前一个函数的返回地址。
  • (RBP + 0x10)将指向第 7 个参数(如果有)。
  • (RBP + 0x18)将指向第 8 个参数(如果有)。
  • (RBP + 0x20)将指向第 9 个参数(如果有)。
  • (RBP + 0x28)将指向第十个参数(如果有)。
  • RBP-X,其中 X 是 0x8 的倍数,将引用该函数的局部变量。
  • 程序使用 RBP 的偏移量来访问局部变量或函数参数。之所以能这样是因为 RBP 在函数序言中的函数开始处被设置为 RSP 寄存器的值。

RBP 不再用作帧指针。它现在是一个通用寄存器,就像任何其他寄存器(如 RBX、RCX 等)一样。调试器不能再使用 RBP 寄存器来遍历调用堆栈。
ebp:push ebp 所以可以用 ebp 遍历栈帧
编译器优化可能会减少对RBP的依赖,甚至在某些情况下完全不用它,尤其是在优化较小函数时。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/720877.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LLM 扩展开发工具

参考知乎: 大模型Agent智能体25款产品、商业案例随笔记(一) 、 基于大模型的AI搜索15款产品随笔记(二) LLM 扩展开发工具: 1、开源且无需编码(No-Code)的 LLM 应用构建工具: https://flowiseai.com/ ,将永远免费供商业和个人使用。代码库: https://github.com/Flow…

Linux 提权-Cron Jobs

本文通过 Google 翻译 Cron Jobs – Linux Privilege Escalation - Juggernaut-Sec 这篇文章所产生,本人仅是对机器翻译中部分表达别扭的字词进行了校正及个别注释补充。导航0 前言 1 什么是 Cron Job?1.1 了解 Crontabs 和 Cron 目录 1.2 如何在 Crontab 文件中读取 Cron 作…

Spring Boot heapdump泄露内存分析方法

一、查看加密星号信息(适用于数据库密码、ftp、ssh) 1.首先访问地址:http://url/actuator/env 出现一些配置信息,搜索带有******可以看到ftp的ip,username,password 2.接着访问http://url/actuator/heapdump 下载下来heapdump文件 使用java自带的工具进行分析 位置:C:\Pro…

计算机简史-概述

讲解计算机发展的历史,人类如何从手工计算,发明工具,最后演变成如今的计算机。讲解计算机发展的历史,人类如何从手工计算,发明工具,最后演变成如今的计算机。 ‍ 为什么要了解计算机发展历史 简单说说我的看法:了解计算机发展历史,对我们掌握计算机底层的原理是非常有帮…

使用 Hugging Face 推理终端搭建强大的“语音识别 + 说话人分割 + 投机解码”工作流

Whisper 是当前最先进的开源语音识别模型之一,毫无疑问,也是应用最广泛的模型。如果你想部署 Whisper 模型,Hugging Face 推理终端 能够让你开箱即用地轻松部署任何 Whisper 模型。但是,如果你还想叠加其它功能,如用于分辨不同说话人的说话人分割,或用于投机解码的辅助生…

2024年离职和后续发展

目录前言个人情况2023年底2024年初,找工作失败2024年2月,计划开公司家里关系开公司原因2024年4月,说明我要离职的想法5月,开始办理公司6月,开始准备资料,提离职 前言 最近我也快离职回家了,简单说一下最近发送的事情好了,这里按照时间的顺序 个人情况 2022年毕业,来到…

华为matebook 14s笔记本,Chrome浏览器开启硬件加速,屏幕闪屏,黑框,页面屏幕卡死,解决办法

解决办法使用了 https://zhuanlan.zhihu.com/p/644296061 这个连接下的最后一个折中办法解决! 一、现象 Chrome开启“硬件加速模式”后,在观看视频时,尤其是全屏时,会出现短暂黑屏或黑块或闪屏。如果关闭“硬件加速”,则会造成播放某些高清视频(例如HEVC)视频或弹幕卡顿(…

BD10100CS-ASEMI肖特基二极管BD10100CS

BD10100CS-ASEMI肖特基二极管BD10100CS编辑:ll BD10100CS-ASEMI肖特基二极管BD10100CS 型号:BD10100CS 品牌:ASEMI 封装:TO-252 最大平均正向电流(IF):10A 最大循环峰值反向电压(VRRM):100V 最大正向电压(VF):0.80V 工作温度:-65C~175C 芯片个数:2 芯片尺寸:mi…

grafana 密码怎么重置

昨天手抖把sqlite的数据清除了,今天登不上了 进入到grafana的安装目录下的bin 执行 ./grafana-cli admin reset-admin-password 新的密码

SHR工号加1问题解决方案

SHR工号加1问题解决方案查询出有问题的用户,会出现两条数据。select * from T_PM_USER where FNAME_L2 like刘权基%删除s02113 保留 s021131。 然后FForbidden设置为0。 去shr管理平台移除用户的占用,同步后点击占用。就解决了‍

无需搭建环境,零门槛带你体验Open-Sora文生视频应用

基于开源解决方案“Open-Sora 1.0”,在华为云AI Gallery上通过ModelArts体验文字生成视频应用。本文分享自华为云社区《Open-Sora 文生视频原来在AI Gallery上也能体验了》,作者:码上开花_Lancer。 体验链接:Open-Sora 文生视频案例体验 不久前,OpenAI Sora 凭借其惊人的视…

疯狂的麦克斯:狂暴女神迅雷BT下载[百度云AVI/1.26G]高清版[HD720p国语中字

《疯狂的麦克斯:狂暴女神》是由乔治米勒执导,汤姆哈迪和查理兹塞隆主演。这部电影是《疯狂的麦克斯》系列的第四部作品,取材于1980年代的系列电影,以麦克斯和女战士伊梅拉的故事为主线,将观众带入一个被暴力和毁灭统治的废土世界。影片以一个后末日时代的世界为背景,麦克斯…