渗透新思路 | 仿真环境下内存转储分析和模糊测试的全记录

本文由安全研究人员hugsy于2024年03月26日更新并发表于blahcat博客上,本文主要记录了其在进行内存安全和模糊测试任务时发现的一些技术实现方式,主要讨论的是仿真环境下内存转储分析和模糊测试方法。本文旨在为红队和蓝队研究人员提供新的思路,仅出于安全教育目的撰写和发布。

介绍

What-The-Fuzz是我个人非常喜欢的一款工具(0vercl0k库),除了其本身的强大功能之外,我更喜欢该工具创建时背后的故事。除此之外,0vercl0k库中的各种其他组件也是我非常喜欢的,比如说kdmp-parser、symbolizer和bochscpu等等。如果你了解并使用过这些工具库,那么你肯定非常熟悉内存转储和模糊测试的相关内容。在这篇文章中,我将跟大家分享一种仿真环境下内存转储分析和模糊测试的方法。

在此之前,我一般会将内存转储作为在程序崩溃前访问程序崩溃条件和执行上下文的最后一种方式。内存转储一般会用于调试或崩溃分析模糊测试,有时还会用于DFIR。据我所知,What-The-Fuzz是第一个使用它们来进行基于快照的模糊测试工具。

就我个人而言,我比较喜欢使用Python来完成我自己的工作任务,主要原因有以下几点:

1、Python是一种用于快速原型设计的语言,附带了一个很棒的REPL(交互式编程环境),同时PyPI也给Python提供了一个强大的生态环境;

2、具有与低级别机器代码交互的强大能力;

3、我个人非常喜欢这门语言;

但我马上就遇到了问题,bochscpu一开始是使用Rust开发的,而kdmp-parser和udmp-parser则是用C++写的,只有kdmp-parser有一个还未开发完成的Ptyhon绑定(很多API都没有,PyPI也未上架)。所以,我似乎找到了一个方向:

1、自己创建umdp-parser和bochscpu的Python绑定;

2、升级kdmp-parser现有的Python绑定;

目前来说,任何人都可以使用“pip install”命令来安装上述包,并直接在Python解释器v3.8+环境中直接使用,因此我们也可以直接使用下列命令直接重新生成上述包:

pip install udmp-parser kdmp-parser bochscpu-python

完全配置好整个实验环境之后,我们就可以开始使用下列方法进行深入挖掘和研究:

1、使用udmp-parser解析用户模式进程转储;

2、使用kdmp-parser解析内核内存转储;

3、使用这些信息重建一个可行的环境(内存布局和CPU上下文等),以便bochspu运行我们选择的任何代码;

整个过程中我们需要确保每一项设置都能够正常工作,并时刻拥有绝对的执行控制权。

我们接下来会单独研究每一个测试用例,但首先我们需要进一步研究手头上的代码库。

代码库快速浏览

Bochs/BochsCPU

众所周知,Bochs模拟器具有极其强大的检测功能,并且被认为非常适用于x86 ABI实现(包括最新的扩展)。另一方面,yrp的BochsCPU是一个Rust库,它封装了Bochs CPU代码,并通过Rust API(以及通过FFI的C++)公开Bochs的所有指令点(包括上下文切换、中断、异常等)。这些所有的特点注定了Bochs会是一个非常有用的工具,适用于开发任何X86模式的代码、处理非常旧的任务关键型软件以及协助逆向和漏洞研究等任务。

udmp-parser/kdmp-parser

udmp-parser和kdmp-parser都是跨平台的C++解析库,由0vercl0k开发,专门用于WIndows内存转储场景。这两款工具分别针对的是用户模式(WinDbg中使用.dump /m)和内核模式(WinDbg中使用.dump /f|/ka)转储。最重要的是,这两个工具都提供了Python3绑定,允许我们快速制作原型。

Windows内核模式仿真

在这些工具库的帮助下,想要实现从Windows内核转储运行模拟器,就相对比较简单了,因为转储只不过是在给定时间内操作系统状态的快照罢了。

首先,从KdNet会话开始,我们可以轻松创建一个转储。在寻找有价值的攻击面时,我一般喜欢使用我自己的IRP监控工具CFB,但对于我们的场景来说,任何其他的工具也能够实现:

kd> bp /w "@$curprocess.Name == \"explorer.exe\"" nt!NtDeviceIoControlFile

[...]

Breakpoint 0 hit

nt!NtDeviceIoControlFile:

fffff807`4f7a4670 4883ec68        sub     rsp,68h

获取转储的方式之一就是使用.dump命令:

kd> .dump /ka c:\temp\ActiveKDump.dmp

但更好的做法是使用yrp的bdump.js脚本:

kd> .scriptload "C:\bdump\bdump.js"

[...]

 

kd> !bdump_active_kernel "C:\\Temp\\ActiveKDump.dmp"

[...]

[bdump] saving mem, get a coffee or have a smoke, this will probably take around 10-15 minutes...

[...]

[bdump] Dump successfully written

[bdump] done!

构建BochsCPU会话

我们可以使用kdmp_parser.KernelDumpParser来解析转储,因此转储的解析工作就交给它了。对于BochsCPU的运行来说,拿到一个PF句柄回调是非常关键的,这也是一个基础需求。完整的内存转储可能会有几个GB,所以将其全部映射到主机上似乎不是非常合理,尤其是我们只需要其中一小部分的时候,因此我们可以这样处理:

dmp = kdmp_parser.KernelDumpParser(pathlib.Path("/path/to/dumpfile.dmp"))

 

def missing_page_cb(pa: int):

  gpa = bochscpu.memory.align_address_to_page(pa)

  if gpa in dmp.pages:          # do we already have the page in the dump?

    # then create & copy the page content, resume execution

    hva = bochscpu.memory.allocate_host_page()

    page = dmp.read_physical_page(gpa)

    bochscpu.memory.page_insert(gpa, hva)

    bochscpu.memory.phy_write(gpa, page)

 

sess = bochscpu.Session()

sess.missing_page_handler = missing_page_cb

这为我们提供了第一次解决缺失页面的机会,而CPU触发的PageFault异常(例如PageFault -> BX_PF_EXCEPTION (14))将为我们提供第二次分析页面故障的机会,因为工具将会填充错误代码,而我们可以使用英特尔手册的“Intel 3A - 4.7”部分检查故障原因。

接下来,我们必须给CPU提供一个bochscpu.State状态,指示其初始上下文,其中包括CR、GPR、标志寄存器和段寄存器等。需要注意的是,我们可以在bochscpu.cpu中找到几个有用的组件来加快整个进程:

regs = json.loads(pathlib.Path("/path/to/regs.json").read_text())

state = bochscpu.State()

bochscpu.cpu.set_long_mode(state)

[...]

state.cr3 = int(regs["cr3"], 16)

state.cr0 = int(regs["cr0"], 16)

state.cr4 = int(regs["cr4"], 16)

[...]

state.rax = int(regs["rax"], 16)

state.rbx = int(regs["rbx"], 16)

state.rcx = int(regs["rcx"], 16)

state.rdx = int(regs["rdx"], 16)

[... snip for brievety]

sess.cpu.state = state

最后,我们还需要对剩下的可挂钩事件定义Bochs回调:

def before_execution_cb(sess: bochscpu.Session, cpu_id: int, _: int):

    state = sess.cpu.state

    logging.info(f"Executing RIP={state.rip:#016x} on {cpu_id=}")

 

hook = bochscpu.Hook()

hook.before_execution = before_execution_cb

hooks = [hook,]

最后,直接使用一个简单的调用触发整个操作即可:

sess.run(hooks)

sess.stop()
$ python kdump_runner.py

Executing RIP=0xfffff80720a9d4c0 on cpu_id=0

Executing RIP=0xfffff80720a9d4c4 on cpu_id=0

Executing RIP=0xfffff80720a9d4cb on cpu_id=0

Executing RIP=0xfffff80720a9d4d0 on cpu_id=0

Executing RIP=0xfffff80720a9d4d4 on cpu_id=0

Executing RIP=0xfffff80720a9d4dc on cpu_id=0

Executing RIP=0xfffff80720a9d4e1 on cpu_id=0

Executing RIP=0xfffff80720a9d4e8 on cpu_id=0

Executing RIP=0xfffff80720a9d4ec on cpu_id=0

[...]

如需获取更加详细和完整的示例,可以参考bochscpu-python库【传送门】中提供的“examples/long_mode_emulate_windows_kdump.py”。

Windows用户模式仿真

在Windows上获取进程快照的方法不止一种(如WinDbg、Task Manager、procdump、processhacker等),所以这部分我打算直接跳过,并假设你已经准备好了快照。

在BochsCPU上模拟用户模式代码比内核模式稍微复杂一些:内核转储包括一个几乎完整的操作系统快照,包括MMU正常工作所需的所有内核部分,然而我们需要的只在需要时将这些页面映射到Bochs。

Windows上的用户模式转储不包括任何这些信息,而只包括与用户模式进程本身相关的信息。我们必须记住的是:BochsCPU只是CPU,这意味着它可以执行任何事情,但它需要设置所有内容,如处理器模式和映射页面等。但是,如果进程以受保护模式运行的话,通过MMU的内存访问也必须正确关闭,以确保VirtualAddress→ PhysicalAddress翻译有效。因此,我们需要为整个流程构建我们自己爹页面表。这一部分网上也有很多方法,因此这里不再赘述。

还记得之前说过,bochscpu-python库能够帮助我们加快整个流程吧?示例如下:

dmp = udmp_parser.UserDumpParser()

assert dmp.Parse(dmp_path)

pt = bochscpu.memory.PageMapLevel4Table()

pa = PA_START_ADDRESS

 

# Collect the memory regions from the Windows dump

# For each region, insert a new PT entry

for _, region in dmp.Memory().items():

    if region.State == MEM_FREE or region.Protect == PAGE_NOACCESS:

        continue

    start, end = region.BaseAddress, region.BaseAddress + region.RegionSize

    for va in range(start, end, PAGE_SIZE):

        flags = convert_region_protection(region.Protect)

        if flags < 0:

            break

        pt.insert(va, pa, flags)

        hva = bochscpu.memory.allocate_host_page()

        bochscpu.memory.page_insert(pa, hva)

        print(f"\bMapped {va=:#x} to {pa=:#x} with {flags=}\r", end="")

        pa += PAGE_SIZE

 

# Commit all the changes, resulting in a valid PT setup for the VM

for hva, gpa in pt.commit(PML4_ADDRESS):

bochscpu.memory.page_insert(gpa, hva)

接下来,就像对内核转储所做的事一样,我们还需要倒入所有寄存器,包括GPR和标志寄存器。另一件事则需要取决于线程的选择:当VM执行即将恢复时,CPU不能在不依赖段寄存器的情况下工作,段寄存器是根据CS、DS、SS段寄存器中设置的值和状态中获取的。值得庆幸的是,这些值可以直接从转储中检索得到:

threads = dmp.Threads()

tids = list(threads.keys())

tid = tids[0] # whatever teh first thread is, but TID can be hardcoded too

switch_to_thread(state, threads[tid])

 

def switch_to_thread(state: bochscpu.State, thread: udmp_parser.Thread):

  # build CS

  _cs = bochscpu.Segment()

  _cs.base = 0

  _cs.limit = 0xFFFF_FFFF

  _cs.selector = thread.Context.SegCs

  _cs_attr = bochscpu.cpu.SegmentFlags()

  _cs_attr.A = True

  _cs_attr.R = True

  _cs_attr.E = True

  _cs_attr.S = True

  _cs_attr.P = True

  _cs_attr.L = True

  _cs.attr = int(_cs_attr)

  state.cs = _cs

 

  # do the same for the others (obvisouly adjusting values/flags)

同样的,Windows也需要FS和GS寄存器。现在,我们已经构建了Windows仿真环境所需的一切了,接下来我们就要好好想想我们需要执行什么任务了。

PGTFO

由于获取进程快照之后,我们能够获取到目标进程PRNG的当前状态,因此我们可以使用仿真来发现下列值:

#include <windows.h>

#include <wincrypt.h>

#include <stdio.h>

#pragma comment(lib, "advapi32.lib")

 

int main()

{

HCRYPTPROV hCryptProv;

CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);

printf("PID=%lu, hProv=%p\nDump and press enter\n", GetCurrentProcessId(), (void *)hCryptProv);

getchar(); // We break here and snapshot the process

 

for (int i = 0; i < 10; i++)

{

BYTE randomBytes[16] = {0};

CryptGenRandom(hCryptProv, sizeof(randomBytes), randomBytes)

printf("Random bytes: ");

for (int i = 0; i < sizeof(randomBytes); i++)

printf("%02X ", randomBytes[i]);

printf("\n");

}

 

CryptReleaseContext(hCryptProv, 0);

return 0;

}

继续执行模拟器,我们将能够直接调用转储中的任何函数/:

logging.debug(f"Resolving 'cryptbase!SystemFunction036'")

fn_start = resolve_function(fn_sym)

fn_end = fn_start + 0x1C # hardcode the end address of the function for now

 

state.rcx = temp_buffer_va

state.rdx = 16

state.rip = fn_start

 

hook = bochscpu.Hook()

hook.before_execution = lambda s, _: s.cpu.state.rip == fn_end and s.stop()

sess.run([hook,])

现在我们就能够成功转储所有的值了:

OK,全部搞定!

Linux该怎么办呢?

俗话说的好...

在lief工具的帮助下,我们能够解析并输入内存布局:

/**

* For demo purpose, compiled with `-static`

*/

#include <stdlib.h>

#include <stdio.h>

#include <stdint.h>

#include <time.h>

 

void generate_random_buffer(uint8_t* buf, size_t sz)

{

for(int i=0; i<sz; i++)

buf[i] = rand() & 0xff;

}

 

int main()

{

srand(time(NULL));

uint8_t buf[0x10] = {0};

generate_random_buffer(buf, sizeof(buf));

getchar(); // get a coredump

for(int i=0; i<sizeof(buf); i++)

printf("%02x ", buf[i]);

puts("");

return 0;

}

编译代码:

然后运行:

不出所料,结果是一样的!

总结

本文详细介绍了内存转储中需要注意的事项,以及一些新的方法,希望可以给广大红队和蓝队研究人员提供一些新的思路。

参考资料

GitHub - bochs-emu/Bochs: Bochs - Cross Platform x86 Emulator Project

GitHub - yrp604/bochscpu

GitHub - 0vercl0k/wtf: wtf is a distributed, code-coverage guided, customizable, cross-platform snapshot-based fuzzer designed for attacking user and / or kernel-mode targets running on Microsoft Windows and Linux user-mode (experimental!).

GitHub - 0vercl0k/kdmp-parser: A Windows kernel dump C++ parser library with Python 3 bindings.

GitHub - 0vercl0k/udmp-parser: A Cross-Platform C++ parser library for Windows user minidumps with Python 3 bindings.

GitHub - googleprojectzero/bochspwn: A Bochs-based instrumentation project designed to log kernel memory references, to identify "double fetches" and other OS vulnerabilities

GitHub - googleprojectzero/bochspwn-reloaded: A Bochs-based instrumentation performing kernel memory taint tracking to detect disclosure of uninitialized memory to ring 3

参考链接

Tapping into the potential of Memory Dump Emulation

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数字化技术可以促进中国企业创新吗?

数字化技术可以显著促进中国企业的创新。数字化技术&#xff0c;包括人工智能&#xff08;AI&#xff09;、区块链&#xff08;Blockchain&#xff09;、云计算&#xff08;Cloud computing&#xff09;、大数据&#xff08;big Data&#xff09;等&#xff0c;被称为ABCD技术&…

TruLens

文章目录 一、关于 TruLensHow it works 二、安装三、快速使用Get DataInCreate Vector StoreBuild RAG from scratchSet up feedback functions.Construct the appRun the app 一、关于 TruLens Evaluate and Track LLM Applications 官网&#xff1a;https://www.trulens.o…

【Redis 开发】详细搭建Redis主从,并了解数据同步原理

Redis主从搭建 Redis主从搭建Redis的主从架构数据全同步原理数据的增量同步优化Redis主从集群 Redis主从 搭建Redis的主从架构 主从节点可以实现读写分离&#xff0c;将都大量的读操作分担与诸多从节点当中去 从节点可以叫做slave或者replica 如何搭建&#xff1a; 在一台…

node 项目启动报 ERROR Failed to compile with 1 errors 错误

问题 node项目通过npm run dev启动时出现ERROR Failed to compile with 1 errors 错误&#xff0c;具体信息如下&#xff1a; ✖ WebpackCompiled with some errors in 366.81msERROR Failed to compile with 1 errors …

如何买到“30元以下”的免备案服务器?

对于预算有限的个人和小型企业来说&#xff0c;30 元以下免备案服务器的价格非常亲民。用户可以以极低的成本获得所需的服务器资源&#xff0c;这对创业者、个人开发者、学生和站长来说简直不要太划算&#xff0c;毕竟配置可以升级真不够后面再付费升级也行。 何为“免备案”&…

xLua背包实践

准备工作 环境&#xff0c;代码 在C#代码方面我们需要准备单例模式基类&#xff0c;AB包管理器&#xff0c;lua解析器管理器 详情请见AB包管理器 xlua详解 然后是Xlua包和AB包&#xff0c;具体导入方法也在上面的链接中 然后是lua的三个文件 具体代码&#xff1a; JsonUtil…

主观赋权法、客观赋权法、组合赋权法、评价指标体系构建

在科研领域&#xff0c;为了对某个研究主题进行深入的探讨和评估&#xff0c;我们往往需要构建一套科学合理的评价体系&#xff0c;并为其中的各项评价指标赋予相应的权重。比如&#xff0c;在评价一项新技术的性能时&#xff0c;我们可能会考虑其创新性、实用性、成本效益等多…

git的学习笔记

git的学习使用 参考了书和好多网上搜到的连接&#xff0c;忘了记录参考的了&#xff0c;太多了&#xff08;&#xff09;捋出来一个大致的较为全面的git个人使用流程。 &#xff08;一&#xff09;基本操作&#xff1a; 安装配置好git 创建目录 init 初始化一个仓库&#xff0…

Golang | Leetcode Golang题解之第55题跳跃游戏

题目&#xff1a; 题解&#xff1a; // 贪心算法 func canJump(nums []int) bool {cover : 0n : len(nums)-1for i : 0; i < cover; i { // 每次与覆盖值比较cover max(inums[i], cover) //每走一步都将 cover 更新为最大值if cover > n {return true}}return false } …

2024通信会|迈向智慧配电网建设新时代,锐捷网络发布双平面配电通信解决方案

近期,以“加快推进通信数智化,助力构建新型能源体系”为主题的2024年能源网络通信创新应用大会在四川成都圆满结束,会议围绕构建新型能源体系和新型电力系统建设需求,探讨能源网络通信创新应用的最新趋势与成果、“主、配、微”通信网一体化融合、配电通信网、通信数智化转型等…

41-数组 _ 数组作为函数参数

41-1 冒泡排序函数的设计 数组传参的时候&#xff0c;形参有2种写法&#xff1a; 1、数组 2、指针 往往我们在写代码的时候&#xff0c;会将数组作为参数传个函数 如&#xff1a;实现一个冒泡排序&#xff0c;将数组的数据排成升序 冒泡排序的核心思想&#xff1a; 1、两…

NGINX发布动态页面的方法

一、建立 [rootserver100 html]# vim index.php [rootserver100 html]# pwd /usr/share/nginx/html 二、下载PHP文件 [rootserver100 conf.d]# dnf install php.x86_64 -y 正在更新 Subscription Management 软件仓库。 无法读取客户身份 本系统尚未在权利服务器中注册。可…

国内十大CRM软件盘点2024:专家推荐+用户真实反馈

Zoho CRM软件即客户关系管理系统&#xff0c;这个概念自1999年由GartnerGroup公司提出以来逐渐演变&#xff0c;最初是为了填补ERP系统在客户关系管理方面的缺失&#xff0c;后来发展成了企业战略中不可或缺的工具。随着企业对客户管理的重视程度不断提升&#xff0c;越来越多的…

Socket套接字(UDP数据报)篇

Socket 概念数据报套接字DatagramSocketDatagramPacketInetSocketAddress 小结 概念 Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元. 基于Socket套接字的网络程序开发就是网络编程. 数据报套接字 使用的是UDP(User Datagram Protocol…

IGM焊接机器人RTE 495伺服电机维修详情一览

在当今科技迅速发展的时代&#xff0c;机器人已成为各行各业不可或缺的重要工具。IGM机器人便是其中之一&#xff0c;其工业机械手伺服马达作为机器人的关键部件&#xff0c;确保机器人能够高效、稳定地运行。当出现IGM焊接机器人RTE 495伺服电机故障问题时&#xff0c;及时进行…

【推荐】2024年必备的技术学习网站

在学习 Java 的过程中&#xff0c;你都在用哪些网站查找资料和学习呢&#xff1f;以下是 V 哥在日常工作和学习中&#xff0c;经常会使用到的网站&#xff0c;有哪些是与 V 哥不约而同都在用的呢&#xff0c;下面来一一介绍一下&#xff1a; 1、百度开发者搜索 你是不是日常在…

[Algorithm][分治 - 归并排序][排序数组][交易逆序对的总数][计算右侧小于当前元素的个数][翻转对]详细讲解

目录 0.原理讲解1.排序数组1.题目链接2.代码实现 2.交易逆序对的总数1.题目链接2.算法原理详解3.代码实现 3.计算右侧小于当前元素的个数1.题目链接2.算法原理详解3.代码实现 4.翻转对1.题目链接2.算法原理详解3.代码实现 0.原理讲解 归并排序的流程充分的体现了**「分⽽治之」…

使用CSgetshell到3389端口远程桌面

中间使用了这个Akagi64.exe提权&#xff0c;网上可以找到&#xff0c;高版本的cs网上也可以找到。

Linux系统编程---线程池并发服务器

模型原理分析&#xff1a; 线程池的关键优势在于它减少了每次任务执行时创建和销毁线程的开销 线程池的组成主要分为 3 个部分&#xff0c;这三部分配合工作就可以得到一个完整的线程池&#xff1a; 1. 任务队列&#xff0c;存储需要处理的任务&#xff0c;由工作的线程来处理…

Linux网络服务-DHCP

一、DHCP工作原理 DHCP&#xff08;Dynamic Host Configuration Protocol&#xff0c;动态主机配置协议&#xff09;&#xff1a;用于自动获取IP地址 1.客户端会发送一个广播DHCP Discover报文去寻找DHCP服务器 2.客户端只会接收第一个回复的DHCP服务器的报文 3.服务器会发…
最新文章