加入我们的 Discord 频道
Squalr 是一个用 Rust 编写的高性能动态分析逆向工程工具。
首先,Squalr 是一个极速的内存扫描器,允许用户搜索并修改运行中的进程内存。通过多线程与 SIMD 结合实现快速扫描,能够在数秒内处理数 GB 的数据。虽然所有 CPU 都支持,但为了获得最佳性能,你的 CPU 需要支持 SSE、AVX 或 AVX-512。
与大多数逆向工程工具不同,动态分析在 Squalr 中是核心功能。这使得可以通过分析程序随时间的行为,实现其他方式无法完成的工作流程。
Squalr 是 Squalr-Sharp 的精神继承者。正在寻找旧的 C# 仓库?请参见 Squalr-Sharp。请注意,Squalr-Sharp 已不再维护,Squalr 已成为开发重点。
本项目与我们团队成员的雇主无关,无论过去、现在还是将来。

Squalr 可以作为桌面应用、终端工具、Android 应用或作为 Rust crate 用于自定义扫描工作流。
当你需要完整的交互工作流时使用:进程选择、元素扫描、数组/字符串扫描、指针扫描、内存查看器、项目文件、符号布局以及插件支持的工具。
| 平台 | GUI | CLI | TUI | 远程(主机) | 远程(终端) |
|---|---|---|---|---|---|
| Windows | ✅ | ✅ | ✅ | ✅ | ✅ |
| Linux | ✅ | ✅ | ✅ | ✅ | ✅ |
| Mac | ✅ | ✅ | ✅ | ✅ | ✅ |
| Android(已 Root) | ✅ | ✅ | ✅ | ❌ | ✅ |
| iPhone(尚不可用) | ✅ | ✅ | ✅ | ❌ | ✅ |
插件是扩展 Squalr 行为的途径。现有的插件 crate 覆盖了内置插件、24 位数据类型、指令提供程序、二进制符号和 Dolphin 内存视图路由。中期目标是让插件能够扩展数据类型、项目项类型、虚拟模块、中间件和工具,而无需修改核心应用。
脚本编写功能正在规划中,但目前尚无稳定的公共脚本接口。
当你希望嵌入 Squalr 的组件而不是运行 Squalr 前端时,直接使用这些 crate。根据你希望 Squalr 负责的工作流部分选择合适的层级。
squalr-engine-api 是共享模型 crate。它包含其他层使用的公共数据类型:扫描计划、值、快照、结果、命令、符号和面向项目的结构。
如果你已经拥有字节,请停留在字节扫描器层:构造 SnapshotRegion 值,将它们包装为 Snapshot,然后调用 ElementScanner::scan_snapshot。此路径不需要进程句柄或原生目标后端。
如果你希望扫描真实进程,请将目标 I/O 和扫描保持为独立阶段。使用 squalr-engine-targets 作为抽象,当你需要 Squalr 的原生后端时使用 squalr-engine-targets-native,然后将收集到的内存送入 squalr-engine-scanning。
如果你希望获得完整的 Squalr 应用工作流,请使用会话/命令 crate,而不是自行组装较低层级。该路径适用于希望 Squalr 协调目标访问、扫描、项目和响应的使用者。
系统级工作需要用系统级语言。选择 Rust 是因为它消除了整个类别的 bug,并且非常适合这项任务。
在评估了几个前端框架后,我们最终选择了 egui。Egui 为我们提供了较小的二进制文件大小、原生速度、跨平台性以及易于访问应用程序数据进行显示。
中期来看,Squalr 的目标是使用现代插件系统实现可扩展性。不再需要将插件解压到特殊位置并在每次发布时手动升级。这意味着一个真正的市场,包括大量免费且易于安装的插件。虽然目前尚未实现,但 Squalr 的开发已考虑到开发者希望扩展类型系统、项目系统、注册自定义工具以及注册中间件以支持扫描模拟器内存或其他特定场景的需求。
此外,我们很快将支持脚本编写。我们尚未决定使用哪种语言。虽然大多数人会立刻想到 Lua 或 Python,但这些语言缺乏健壮的数据类型,导致需要笨拙的变通方法。因此,我们尚不清楚将使用什么替代方案,或者是否存在可行的替代选择。
最终,Squalr 将在静态分析方面展开竞争,但初期不会。目前,Squalr 刻意不构建 ASM 到 C++ 的反编译器、代码图或调试器。
长期来看,我们确实希望融入 AI 领域,并以切实为用户创造价值的方式进行。以下是我们打算尝试的几个想法:
Linux 构建通过以下入口点验证:
cargo build -p squalr-cli --lockedcargo build -p squalr-tui --lockedcargo build -p squalr --locked构建前安装原生依赖:
pkg-configlibasound2-devlibudev-devlibxkbcommon-devlibwayland-devlibx11-devlibxcursor-devlibxi-devlibxrandr-devlibxinerama-devAndroid 构建目前已在目标 aarch64-linux-android 上验证,API 级别为 30。
目标:安装 GUI APK,推送特权工作进程,并在 Rooted IPC 模式下运行 GUI。
先决条件:
ANDROID_HOME。ANDROID_NDK_ROOT。rustup target add aarch64-linux-android。cargo install cargo-ndkadb 连接的已 Root 设备。然后在工作区根目录运行以下任一命令:
python ./scripts/build_and_deploy.py(完整冒烟验证:预检 + 构建 + 安装 + 启动 + IPC 工作进程检查)python ./scripts/build_and_deploy.py --compile-check(自动只编译路径:预检 + Android Rust 构建 + GameActivity APK 打包,无需设备)python ./scripts/run_apk.pypython ./scripts/debug_run_privileged_shell.py注意:
ANDROID_HOME、ANDROID_NDK_ROOT、目标安装以及 aarch64-linux-android-clang 的可访问性),然后运行:
cargo ndk --target aarch64-linux-android build -p squalr-clicargo ndk --target aarch64-linux-android build -p squalrtarget/android-gameactivity-gradle 下的 Gradle 项目,使用 androidx.games:games-activity,因此 Android 软输入状态由 GameActivity 而非 NativeActivity 处理。third_party/winit-0.30.13 下的工作区补丁,用于 winit 0.30.13。该补丁保持上游 Android 按键事件处理,将 GameActivity 的文本提交转发到 egui 的正常键盘文本路径,在 IME 激活时抑制重复的可打印按键事件,并在每次提交后清除隐藏的 GameActivity 文本缓冲区。target/android-gradle 下固定的本地 Gradle 发行版进行 GameActivity 打包。要覆盖此设置,请将 SQUALR_ANDROID_GRADLE 设置为兼容的 Gradle 可执行文件。squalr-cli 可以暴露一个 JSON HTTP 控制接口,用于自动化和集成测试:```text
squalr-cli --http-ipc 127.0.0.1:49321
在Android上,以root身份运行它,并从主机转发端口:```text
adb shell su 0 sh -c "/data/local/tmp/squalr-cli --http-ipc 127.0.0.1:49321"
adb forward tcp:49321 tcp:49321
初始端点有:
GET /healthPOST /command 使用序列化的 PrivilegedCommand JSON 主体,返回 PrivilegedCommandResultGET /events?after=<event_id> 用于轮询引擎事件为了通过 HTTP IPC 端到端验证 Android 调试器路径,请运行:```text python ./scripts/android_http_debugger_smoke.py
烟雾构建发布Android产物,推送一个原生watch目标进程,该进程广告一个静态计数器地址,通过HTTP驱动`Process.Open`以及读/写/访问调试器跟踪,并验证目标在每个跟踪停止并分离后持续发送心跳。
常见的预检失败示例:```text
> rustup target list --installed
aarch64-linux-android
wasm32-unknown-unknown
x86_64-pc-windows-msvc
ANDROID_NDK_ROOT is not set.
如果出现此情况,请将 ANDROID_NDK_ROOT 设置为您的 Android NDK 路径,然后重新运行脚本。
macOS 构建通过以下入口点进行验证:
cargo build -p squalr-cli --lockedcargo build -p squalr-tui --lockedcargo build -p squalr --locked运行目标:
cargo run -p squalr-cli -- process list -w -l 20cargo run -p squalr-tuicargo run -p squalrSqualr 在 macOS 上使用 Mach API(task_for_pid、mach_vm_read_overwrite、mach_vm_write)来打开和检查目标进程。如果这些调用被阻止,进程的打开/读取/写入操作将失败。
要启用这些功能,必须禁用系统沙箱安全功能。请注意,这会影响您的整个系统,并使您的系统更容易受到恶意软件的攻击!除非您知道自己在做什么,否则不建议这样做。
csrutil disablesudo ./squalr 或 sudo ./squalr-cli 或 sudo ./squalr-tui)。此过程可以随时通过使用 csrutil enable 来撤销安全更改。
Squalr 有两个组件:一个特权接口和一个非特权核心。这自然产生了一种命令/响应架构,实现了清晰的关注点分离。共享命令模型位于 squalr-engine-api 中;命令行解析是 squalr-engine-api::commands::command_line 下的一个 API 适配器,它将 CLI/REPL 风格的输入转换为那些共享命令。
这使我们能够创建多种不同的模式,例如统一的 GUI/CLI/TUI 构建,以及一个潜在的远程主机来控制远程 shell。
快照系统旨在智能地对进程内存进行快照,同时以对扫描最优的方式排列内存。这是通过合并相邻虚拟内存页来实现的。例如,如果我们有 3 个长度为 0x2000 的背靠背区域,则会产生一个 0x6000 的单一区域。我们仍然会拆分进程内存读取调用,但会将字节直接读入更大的数组中,并放在正确的索引位置。
这有几点优势。首先,扫描不再需要担心边界条件。跨页边界的字节数组扫描得到了微不足道的支持!
唯一的复杂之处在于,如果某个读取内存调用失败,我们需要拆分这个区域,并将未读取的部分视为“墓碑”。
此外,我们为快照结果使用“红黑”系统。前两次拍摄快照时,我们必须分配内存,但对于后续的快照,我们可以直接重用现有数组。
一个潜在的优化是决定何时分片快照区域。目前,如果我们有一个 1GB 的快照区域,但只有两个过滤器:一个在开头,一个在结尾,我们在后续扫描中仍然会读取这两个过滤器之间的所有内存。在实践中这很少见,现在解决它有些为时过早,所以暂不作处理。
扫描结果通过过滤器的概念被发现。这是一种支持许多同时扫描(各种数据类型)的巧妙方法。一旦我们有了快照,我们就可以以多种不同的方式解释字节。例如,我们可以将值 1 作为 i8、i16、i32、i64、u8、u16、u32、u64、f32 和 f64 进行扫描!我们只需为每种数据类型创建一组新的过滤器。这也是极其可并行化的,特别是与 SIMD 友好的扫描结合使用时。
事实上,扫描系统的整个瓶颈在于值收集。读取进程内存所花费的时间远多于扫描每种数据类型所需的时间,即使是 1 字节对齐扫描也是如此。
此外,过滤器作为 Vec 的 Vec 进行跟踪,这是一种扫描优化。这使我们能够并行地遍历每个快照区域(高级 Vec),然后将每个区域的结果收集到一个 Vec 中。这避免了后来将这些信息重组为“更整洁”形式时出现任何痛苦的运行时瓶颈。
扫描的工作方式是通过一种游程编码算法。这允许对扫描结果进行极端压缩。大多数进程内存的绝大部分是 0x00,并且字节分布严重偏向于其他常见值(如 0x01 和 0xFF)。因此,如果我们扫描一个内存区域寻找值 0x00,而该区域全部为零,大小为 0x2000,地址为 0x10000,则会产生一个结果(0x10000, 0x2000)。
更好的是,使用游程编码器可以让我们非常快速地丢弃不匹配的结果。例如,如果以 i32 类型扫描值 1,大部分内存将为 0。当我们进行 SIMD 比较时,由于所有比较很可能失败,大多数 SIMD 向量将完全是 false,从而允许我们在游程编码器中跳过 SIMD 大小的块。更好的是,我们也可以对匹配进行同样的处理!如果扫描 0,我们预计会得到许多完全匹配,从而允许我们以积极的方式编码 SIMD 大小的块。这使我们在扫描中获得极高的吞吐量。
此外,其他功能(如对齐)允许我们跳过元素并避免碎片化。想象一下,我们扫描一个 1GB 的巨大内存区域中的 1 字节值 0x00,该区域只是 0x00 和 0xFF 交替,但我们对齐方式为 2 字节。人们会期望得到碎片化的扫描结果和大量的过滤器,但我们可以避免这种情况!这是通过存储对齐方式来实现的。正因为如此,我们实际上只需要存储 1 个结果来覆盖整个区域!
现在,当我们索引到过滤器以检索结果时,我们总是按对齐方式步进,从而避开所有这些忽略的 0xFF 值。
扫描结果不占用有意义的空间开销。空间是稀缺的,因为我们已经存储了进程 RAM 的巨大快照。因此,我们倾向于尽可能避免使用额外空间。相反,我们依赖基本的搜索算法来提取分页的扫描结果。例如,一旦扫描完成,每个快照区域现在都有一个按数据类型组织的快照过滤器集合,以及额外的数据(如对齐方式)。我们可以根据数据类型、过滤器的大小和对齐方式来推导扫描结果的数量。这个值缓存在快照过滤器集合中。获取扫描结果是通过一个高效的两步算法完成的:
结合分页,这在不需要在我们的快照过滤器中存储额外数据的情况下非常快速。我们可以在过滤器集合级别存储一些数据,但这没问题,因为我们预计任何给定时间最多只有大约 10 个过滤器集合。
// TODO: 目前我们实际上构建了一个堆,并尝试进行二分搜索,以将多种数据类型的多个扫描结果合并到同一个结果中。我们实际上最好省略这一点,坚持使用上述的双线性寻道解决方案,并将数据类型作为单独的结果标签。也许吧。如果我们能保持良好效率,将结果拉链在一起可能是值得的,但我需要再考虑一下。
所有扫描都被分解为一种中间形式,允许我们选择最优的扫描策略以获得最大吞吐量。需要考虑许多因素,例如:
在内部,我们使用上述规则引擎为每个待考虑的快照过滤器选择一个扫描器实现。请注意,扫描操作的是快照过滤器,而不是快照区域。对于第一次扫描,快照过滤器将包含整个快照区域。对于后续扫描,随着结果的缩减,扫描实现会扫描越来越小的过滤器。
这就是选择最佳扫描器至关重要的原因,我们有许多扫描器,例如:
项目条目可以在很大程度上面向未来,通过基本上继承一个用于挂钩 get/set 激活的基本特质,并存储任意键值对。
每个项目只有它关心的键集(例如对于 AddressItem,它是地址、任何模块信息等)。
需要注意支持在 AddressItem 中存储结构体,即这意味着存储的不是 DataValue,而是 ValuedStruct,在大多数情况下(对于单值存储),它可能有一个单独的 DataValue。可能是一个匿名结构体,尽管它也应该支持已注册的符号。
现在,关键是从用户体验角度使其不令人痛苦。所有数据类型都已经在符号注册表中,所以没问题。如果我们只为地址项存储一个符号引用,通常可以解决所有情况,除了匿名情况,此时结构体需要完全序列化到条目中。有点糟糕。
强制用户创建新的结构体类型也不一定坏事(再说,用户扫描结构体的频率有多高?)。
需要一些思考,但这可能在发布后很长一段时间内推迟。
===
值化结构体(Valued Struct)尤其令人恼火。支持嵌套似乎很奇怪,也许很愚蠢,但可能是必要的(基本上是一个 JSON 编辑器 GUI)。ValuedStructField 感觉与数据值非常相似,只是多了一点元数据,如只读和嵌套支持(这也许可以通过 DataValues 本身实现,但很复杂)。
此外,DataValueInterpreter 的概念令人困惑。
我认为我们可能可以废弃这个概念,只对匿名值进行操作,每种数据类型支持去匿名化值和受支持的解释类型。
然后我们只需要将一个字符串引用和格式类型传递给 DataType,并得到 Result。甚至可能支持就地更新,以避免重新分配大量数据值。
我认为我们也可以简化匿名值的工作方式。实际上,它是一个字符串 + 一个格式 + 一个用于数组的可选标志。
现在我们还有 DataValue → 显示字符串的问题,我们用于显示的唯一东西就是这个 AnonymousStringValueFormat。DataValue → AnonymousString 是否正确?我们应该将格式捆绑到字符串中吗?重新匿名化是否可行?并且它大概映射到一个通用的字符串格式类型(即不是十进制,而是保持模糊)。
至少如果我们重新匿名化,我们可以保留当前的显示格式,转换可以使用它来“实际上将此匿名字符串作为类型 x,转换为数据值,然后返回类型 y 的匿名字符串”。
从理解的角度来看,似乎没有真正的缺点。
===
结构扫描将非常具有挑战性。想象一下扫描 {float} {float} {float},即 XYZ 坐标作为结构体。由于浮点数容差,您不能简单地序列化为字节并进行扫描。更糟糕的是,如果您执行 X > 2000, Y < 500, Z > 0,这需要逐字段处理。
我们现有的架构非常灵活,但这绝对需要一个特殊的扫描器实现,并且极不可能从任何规则引擎优化中受益。
| 使用者 | 你提供的内容 | Squalr 提供的内容 | Crates |
|---|
| 字节扫描器 | 你自己的字节、快照、文件、模拟器转储或捕获的内存缓冲区 | 扫描计划、数据类型、快照结构、扫描执行和结果过滤 | squalr-engine-api, squalr-engine-scanning |
| 自定义目标集成器 | 你自己的目标实现,例如模拟器、调试器、远程代理、跟踪记录器或沙箱 | 用于内存映射、读取、写入、模块和目标句柄的稳定目标特征 | squalr-engine-api, squalr-engine-targets, squalr-engine-scanning |
| 原生进程扫描器 | 一个本地进程,希望 Squalr 枚举、打开、读取、写入和扫描 | 原生 Windows/macOS/Linux/Android 目标访问加上扫描 crate | squalr-engine-api, squalr-engine-targets, squalr-engine-targets-native, squalr-engine-scanning |
| Squalr 前端或自动化 shell | 一个 GUI、CLI、TUI、机器人或命令运行器,希望使用常规的 Squalr 工作流 | 会话状态、命令调度、应用级编排、目标 I/O 协调、扫描、项目和响应 | squalr-engine-session, squalr-engine |
| 命令行适配器 | 一个 CLI、REPL、远程 shell 或脚本桥,希望将用户输入的命令转化为引擎命令 | 命令语法、别名、帮助/版本处理以及转换为共享命令模型 | squalr-engine-api |
| 项目与符号工具 | 需要读取、写入、检查或转换 Squalr 项目的工具 | 项目文件、符号目录、布局、定位器和共享数据模型 | squalr-engine-api, squalr-engine-projects |
/data/local/tmp/squalr-clisu -c chmod +xBuild in release mode? (y/n [default])。--release 或 --debug 以避免提示。--release 优先使用发布构件;如果未配置发布签名([package.metadata.android.signing.release]),APK 构建会自动回退到调试。adb install 在已有安装上失败,请先卸载:
adb uninstall com.squalr.android