0aids/realtime-memory-forensics
GitHub: 0aids/realtime-memory-forensics
一款实时进程内存取证与逆向分析工具,通过多线程扫描、结构体定义、节点图可视化和Python脚本支持,在不分析汇编代码的情况下追踪和解析进程内存中的数据结构与指针关系。
Stars: 1 | Forks: 0
# 实时内存取证
一款实时的程序内存调试器 / 逆向工程工具,具备多线程高吞吐量扫描、带有 Python 绑定/脚本支持的交互式 GUI,并允许通过节点图可视化对关键数据流进行可重现的分析,所有这些均在无需分析执行汇编代码的情况下完成。
# TODO
- [x] feat: 整合 MrpVec 功能(分配和添加节点)
- [x] refac: 修复构建脚本以更好地整合测试
- [x] feat: 改进测试并提高覆盖率
- [x] refac: 移除 PID(应由用户管理,我不知道为什么我要把它存储在 mrps 中)
- [?] refac: 暂时移除命名值以进行简化。
- [x] feat: Python 互操作性 / 绑定
- [x] feat: 集成 Python shell
- [x] feat: Python 中的多线程
- [x] feat: 通过 Batcher 实现更快的多线程。
- [-] feat: 实现内存图算法。
- [x] 让结构体工作起来。
- [ ] MemoryGraphData 包装器 MemoryGraph。
- [ ] 结构体使用方括号语法工作。
- [x] 没有结构体的 MemoryGraphData 基本原型
- [ ] fix: 收窄和符号比较警告
- [ ] feat: GUI 中集成的 Python 脚本和文件保存
- [ ] feat: 悬停时添加链接详情
- [ ] feat: 悬停时添加节点详情
- [ ] feat: 使用输入的 ANALYZER 整合自动链接。
- [ ] feat: 撤销 + 重做
- [ ] feat: 图序列化?
- [x] fix: 找到那个愚蠢的竞态条件
- [ ] feat: 用于降低内存使用率的惰性快照?
唯一的问题是快照之间的延迟,这不会那么容易奏效。
实现这一点的唯一方法是拥有
某种全局向量来存储每个快照
“创建”的时间?或者只是让这种快照与
搜索更改的内存不兼容?
- [ ] 暂时完成?
# 运行测试
请确保您已设置了内存限制,否则在某些失败的测试中它会导致计算机崩溃。
```
cmake -S . -B build -Dbuild_tests=ON && cmake --build build -j 12 && (ulimit -m 1000000 && ulimit -v 1000000 && cd build && ctest)
```
# 计划中的内存图结构/使用
```
using namespace rmf;
using namespace rmf::types;
using namespace rmf::utils;
using namespace rmf::op;
int main() {
const pid_t PID = /*PID here*/;
Analyzer analyzer(6 /*threads*/);
MemoryGraph graph(PID, analyzer, defaultStructRegistry);
int32_t targetValue = 0x98989898;
auto REGIONS = getMapsFromPid(PID);
auto readableRegions = REGIONS.FilterHasPerms("r").FilterActiveRegions(PID);
auto snaps = analyzer.Execute(MemorySnapshot::Make, readableRegions);
MemoryRegionPropertiesVec result;
{
result.clear();
result = analyzer.Execute(findNumeralExact,
snaps, targetValue).flatten();
}
auto newResult = analyzer.Execute(RestructureMrp, MrpRestructure{
.offset = -0xf;
.sizeDelta = +0xf;
});
// And whatever stuff to get the final desired result
MemoryRegionPropertiesVec desiredResult = ...;
// Alternatively we can make use of known structs to generate desired results.
// These known structs must have pointer members at defined offsets.
// This must be done to a "StructRegistry" so structs can refer to eachother.
StructRegistry sr;
sr.registerr("IntLinkedList") // Automatically calculates size.
.field("next", "IntLinkedList*")
.field("data", "int32_t")
.end()
sr.registerr("IntVector")
.field("numElements", "size_t")
.field("data", "int32_t*") // has a list of preregistered fundamental datatypes.
.end();
sr.registerr("flatString1") // Automatically calculates size.
.field("string", "char[10]") // has a list of preregistered fundamental datatypes.
.end();
// Or this
sr.registerTemplated("CustomNameOfStruct");
graph.structRegistry.combine(sr);
// Adds nodes that assumes points to the top of an IntVector.
auto keys = graph.addNodes(desiredResult, "IntVector");
// Alternatively we can say it was part of the data field
// auto keys = graph.addNodes(desiredResult, "IntVector", "data");
// We can then find sources or anything that references it.
// By default these nodes will use a voidpointer struct
auto [newSourceKeys, newLinkKeys] = graph.findSources(keys);
println("{}", graph.structstructIdtoString(graph[newSourceKeys[0]].structId));
// prints "voidpointer"
// We can also prune the graph for stuff that's now out of date.
graph.pruneStale();
// Equivalent to
// auto [invalidLinks, invalidNodes] = graph.getStale();
// graph.removeLinks(invalidLinks);
// graph.removeNodes(invalidNodes);
// Now do whatever you want for visualising the graph
// Unimplemented.
// graphToSvg(graph.getData());
}
```
# 计划的 GUI 示例脚本
基本脚本
```
import rmf_py as rmf # preinjected / run within the embedded interpreter before everything.
import rmf_gui_py as rmfg # preinjected / run within the embedded interpreter before everything.
PID = # pre injected / run within the embedded interpreter before everything.
BATCHER = rmf.Batcher(6) # pre injected / run within the embedded interpreter before everything.
REGIONS = rmf.parseMaps(PID) # pre injected / run within the embedded interpreter before everything.
# Filters for regions actively in memory. This breaks the regions into chunks
# of PAGESIZE.
chunkedRegions = REGIONS.filterActiveRegions()
snapshots = BATCHER(rmf.makeSnapshot, chunkedRegions)
results = BATCHER.findString(snapshots, "hello world!").flatten()
# Dump the results into a graph
mg = rmf.MemoryGraph(BATCHER, PID, structRegistry=rmf.defaultStructRegistry)
# the struct registry has defaults containing one for void* pointers.
# This is called "unknownPointer"
# If a node is not registered with a struct it will assume that it points to
# something at it's origin (if pointer aligned).
mg.structRegistry.register(
Struct("helloWorldStr")
.field("chars", "char[13]")
)
mg.structRegistry.register(
Struct("charstar")
.field("c", "char*")
)
# Coerces the MRP to fit the given struct.
keysToNodes = mg.addNodes(results, structName="helloWorldStr", field="chars")
# Fields is optional, but if not specified it will choose the top of the struct.
# Searches for char* pointing to our helloWorldStr
# Filter means it can only search for those structs.
newSourcesNodeKeys = mg.findSources(keysToNodes, structName="charstar", filter="helloWorldStr")
mg.pruneStale()
# equivalent to:
# deadNodeKeys, deadLinkKeys = mg.getStale()
# mg.removeNodes(deadNodeKeys)
# mg.removeLinks(deadLinkKeys)
# which removes nodes and links that have changed what they point to and no longer valid.
# if you only want to remove links that are now stale
mg.pruneStaleLinks()
# Push the memorygraph into the gui for viewing.
rmfg.pushMemoryGraph(mg, policy="combine")
# Links will not be made between the graphs.
```
查找变化区域(作为之前发生情况的扩展)
```
import rmf_py as rmf # preinjected / run within the embedded interpreter before everything.
import rmf_gui_py as rmfg # preinjected / run within the embedded interpreter before everything.
PID = # pre injected / run within the embedded interpreter before everything.
BATCHER = rmf.Batcher(6) # pre injected / run within the embedded interpreter before everything.
REGIONS = rmf.parseMaps(PID) # pre injected / run within the embedded interpreter before everything.
mg = rmf.MemoryGraph(BATCHER, PID)
chunkedRegions = REGIONS.filterActiveRegions().filterHasPerms("rw")
from copy import deepcopy
results = deepcopy(chunkedRegions)
from time import sleep
while len(results) > 10:
snaps1 = BATCHER.makeSnapshot(results)
sleep(1)
snaps2 = BATCHER.makeSnapshot(results)
results = BATCHER.findChangedRegions(snaps1, snaps2, 32) # using 32 byte chunks.
results = BATCHER.mrpRestructure(sizeDiff = +10, addrDiff = -10)
# Advanced? psuedo creating structs and assigning a node that struct
# Allows for easier accessing and interpreting of data related to that node
# name is not needed.
dataStruct = rmf.Struct.parseC("""
struct testStruct {
int32_t testInteger;
char* string;
int16_t int16array[10];
};
""")
# this say that the root of results was testInteger
results = nodify(results, "testStruct", "testInteger")
mg.pushNodes(results)
# find from testStruct to other existing nodes
mg.findLinks(source="testStruct", target="helloWorldStr")
```
标签:Bash脚本, C++, CMake, CTF工具, DAST, GUI调试工具, JARM, SecList, 二进制分析, 云安全运维, 云资产清单, 内存分析, 内存取证, 内存快照, 内存模式匹配, 内存调试器, 多线程扫描, 威胁情报, 实时内存扫描, 开发者工具, 恶意软件分析, 数据擦除, 漏洞搜索, 系统底层, 结构体解析, 节点图可视化, 进程内存, 逆向工具, 逆向工程