工具类
资源汇总
reverse-engineering
Android App Reverse Engineering 101
Awesome-Android-Reverse-Engineering *****
Awesome-Hacking
Radare2
Radare2是一个强大的、开源的、跨平台的逆向工程框架和命令行工具。它主要用于分析、调试和操作二进制文件,包括但不限于 ELF、PE、Mach-O、DEX 和 RAW 等多种格式。
以下是一些Radare2的主要功能:
反汇编:Radare2能够对二进制文件进行反汇编,并提供多种汇编语言的支持。
调试:Radare2包含了一个功能齐全的调试器,支持断点、单步执行、查看寄存器和内存内容等功能。
静态分析:Radare2可以进行静态分析,包括控制流图(CFG)生成、数据流分析(DFA)、函数识别和字符串提取等。
动态分析:除了静态分析外,Radare2还可以进行动态分析,如代码覆盖率分析、插桩调试和运行时内存分析等。
插件系统:Radare2有一个强大的插件系统,允许用户扩展其功能以满足特定的需求。
脚本支持:Radare2支持多种脚本语言,包括Python、Lua、Ruby和JavaScript等,使得自动化分析和处理成为可能。
跨平台:Radare2可以在多种操作系统上运行,包括Linux、Windows、macOS和BSD等。
以下是使用Radare2进行基本操作的示例:
安装Radare2:
对于Ubuntu/Debian系统,可以使用以下命令安装:
sudo apt-get install radare2
对于macOS系统,可以使用Homebrew安装:
brew install radare2
打开一个二进制文件进行分析:
r2 -A my_binary
其中my_binary是你要分析的二进制文件。这将以默认配置打开二进制文件。
查看反汇编代码:
p my_binary
这将显示二进制文件的反汇编代码。你可以使用方向键在代码之间导航。按q退出反汇编视图。
设置断点:
b main
这将在名为main的函数入口处设置一个断点。
bt(“breakpoint list”)命令查看当前设置的所有断点。要删除一个断点,可以使用d命令,例如:db main。要禁用一个断点,可以使用dw命令,例如:dw main。要启用一个已禁用的断点,可以使用ew命令,例如:ew main。要删除所有断点,可以使用da命令。要清除所有断点并禁用它们,可以使用dea命令。要清除所有断点并启用它们,可以使用dear命令。要清除所有断点并删除它们,可以使用dab命令。要清除所有断点并禁用它们并删除它们,可以使用dadab命令。要清除所有断点并启用它们并删除它们,可以使用dadadab命令。
PANDA
PANDA是一个用于动态分析的开放源代码平台,它不依赖于特定的硬件架构。
它基于QEMU全系统模拟器,QEMU是一个著名的开源处理器模拟器和虚拟化技术。
PANDA能够记录和分析在虚拟机中执行的所有代码和数据,包括操作系统、应用程序和任何运行在虚拟机上的代码。
它支持重放功能,这意味着可以重复执行分析过程,这对于深入理解和分析系统行为非常有用。
重放日志文件是紧凑的,这意味着它们占用的空间很小,便于共享和传输。
PANDA可以利用QEMU支持的多达十三种不同的CPU架构,这意味着它可以分析不同类型的处理器和指令集。
PANDA的分析代码是可重用的,因为它们采用插件架构编写,这有助于简化复杂分析的开发过程。
插件之间可以通过共享机制来共享功能,这有助于提高代码的模块化和可维护性。
特点:
动态分析工具:PANDA首先捕捉到整个系统执行的记录,然后以插件的形式编写分析代码,在适当的时候注册回调函数,以供PANDA调用。这些插件收集数据并咨询或控制其他插件。
迭代编写:插件通常是快速而迭代地编写的,反复运行回放,以便在重新执行任务的情况下,对系统执行的重要方面有越来越深入的了解。
污点分析:进一步的迭代可能会调用污点分析,该分析有选择地标记感兴趣的数据,并在它在系统中流动时跟踪它。
PANDA的用途包括但不限于:
启用旧代码以继续运行。
识别关键漏洞。
理解代码的真正目的和行为。如果不逆向,就很难区分不重要的bug和关键漏洞。
审查代码以确定它是否做了它所声称要做的事情,而没有其他事情。
此外,PANDA最初被作为金融数据分析工具而开发出来,因此,PANDA为时间序列分析提供了很好的支持。
Cuckoo Sandbox
Cuckoo Sandbox是一个基于GPLv3开源的恶意软件(malware)分析系统,它允许安全研究人员和分析师自动化地分析可疑文件,并收集关于这些文件在执行过程中的详细行为数据。这种技术能够帮助安全专家更好地理解恶意软件的功能和目的,从而提高检测和防御恶意活动的效率。
有以下特点:
自动化分析:Cuckoo Sandbox能够自动执行恶意软件,并记录其行为,大大提高了分析效率。
详细日志记录:Cuckoo Sandbox能够记录恶意软件的详细行为,包括文件操作、网络流量、系统调用等,为分析人员提供全面的数据。
多平台支持:Cuckoo Sandbox支持多种操作系统和平台,如Windows、Linux、macOS等,能够适应不同的分析需求。
可扩展性:Cuckoo Sandbox具有良好的扩展性,可以通过插件和模块来增加新的功能和特性,方便用户进行定制化开发。
社区支持:Cuckoo Sandbox是一款开源项目,拥有庞大的用户群体和开发者社区,能够提供丰富的支持和帮助。
Cuckoo的核心功能是提供一个隔离的环境(沙箱),在这个环境中,可疑文件被执行并监控其行为。Cuckoo的沙箱架构包括两个主要部分:中央管理软件(Host)和分析虚拟机(Guest)。
中央管理软件(Host):
管理虚拟机生命周期,包括启动、停止和重新配置。
控制样本的分析流程,包括将样本传递到虚拟机、启动分析以及收集结果。
监控网络流量,捕获和分析虚拟机之间的数据传输。
存储和分析数据,以及生成报告。
分析虚拟机(Guest):
提供一个运行可疑文件的安全环境,确保不会影响主机系统或其他网络。
实时监控和分析可疑文件的行为,包括系统调用、网络通信、文件操作等。
收集系统信息,如内存转储、屏幕截图和系统日志。
Cuckoo能够分析的文件类型包括但不限于:
Windows可执行文件(.exe)和动态链接库(.dll)
PDF和Microsoft Office文档
URL和HTML文档
PHP、VB脚本
ZIP文件、Java JAR、Python文件等
Cuckoo生成的分析报告详细记录了样本的行为,包括API调用、文件操作和网络活动。报告格式多样,包括JSON、HTML、MAEC等,便于不同的安全工具和平台使用。
Cuckoo沙箱通常部署在Linux环境下,支持多种虚拟化技术,如VirtualBox、KVM、Xen等。对于希望部署Cuckoo的分析师来说,了解系统架构和安装过程是非常重要的。虽然Cuckoo的文档和社区提供了大量的支持,但在不同的操作系统和配置上部署Cuckoo可能会遇到一些挑战,如依赖关系、网络配置和权限设置等。因此,用户需要仔细阅读和遵循安装指南,确保所有必要的依赖和环境都已正确设置。
如何发现ROP gadgets和指针
静态分析:
使用ROP分析工具:例如ROPgadget、Ropper或Cutter。这些工具可以自动识别二进制文件中的ROP gadgets,并提供它们的地址。
手动分析:使用IDA Pro或Ghidra等反汇编器手动分析二进制文件,寻找可以用来执行特定操作的代码片段。
动态分析:
使用调试器:如GDB或OllyDbg。通过在程序的执行过程中设置断点,可以观察程序的行为,找到ROP gadgets的地址。
使用内存分析工具:如WinDbg或Mona。这些工具可以帮助分析程序的内存布局,找到有用的代码片段。
利用已知信息:
利用已有漏洞:已知漏洞通常伴随着ROP利用链。通过研究这些漏洞的利用细节,可以发现ROP gadgets和指针。
参考 exploitDB 或其他漏洞利用数据库:这些数据库包含了大量已知的ROP利用链,可以作为发现ROP gadgets的起点。
编写脚本或工具:
自动化脚本:编写脚本来自动化搜索过程,例如使用Python的pycparser库来解析二进制文件。
自定义工具:开发自定义工具来识别特定的ROP patterns或常见的数据结构。
理解程序逻辑:
控制流图:绘制程序的控制流图,帮助理解程序的执行路径,从而发现可能的ROP gadgets。
数据流分析:分析程序中的数据流,了解不同数据结构之间的关系,这有助于找到关键的pointers。
利用汇编语言知识:
理解汇编指令:对汇编语言有深入理解可以帮助识别ROP gadgets,因为许多ROP利用依赖于对底层机器码的精确控制。
ROP gadget finder
rp-win.exe -f file_path -r 8 --unique
rp-win.exe -f file_path --search-hexa “.tab”
FRIDA
TRACE
android系统中,如何判断进程使用了ASLR或PIE
在Linux系统中,可以通过检查进程的内存布局和可执行文件属性来判断一个进程是否启用了地址空间布局随机化(Address Space Layout Randomization, ASLR)以及是否使用了位置无关可执行(Position Independent Executables, PIE)技术。
-
判断ASLR:
- 使用
cat /proc/sys/kernel/randomize_va_space
命令查看系统全局的ASLR设置。如果输出为0,则表示ASLR已关闭;如果输出为1或2,则表示ASLR已开启。 - 对于特定进程,可以结合
readelf -l <executable>
命令查看程序头部信息。虽然这不能直接确认该进程运行时是否启用ASLR,但若ELF头包含GNU_STACK
段且其标志设置了RWX
权限为RWE
(表明栈不可执行),则说明系统支持ASLR功能,但这并不能证明针对这个特定进程的ASLR一定是启用状态。 - 若要更准确地检测单个进程是否实际启用了ASLR,通常需要通过调试工具(如gdb)附加到进程上,并获取堆、栈和关键数据区域的具体地址,对比多次启动后的地址变化来间接验证。
- 使用
-
判断PIE:
- 使用
file <executable>
命令查看可执行文件类型。如果输出包含“position independent executable”或“shared object, pie”等字样,则说明该可执行文件是PIE格式。 - 使用
readelf -h <executable>
查看ELF头信息,如果Type
字段显示为ET_DYN
而非ET_EXEC
,那么该程序是一个动态链接的PIE文件,意味着它在加载时可以被加载器放置在内存中的任意位置执行。
- 使用
综上所述,要确切知道一个正在运行的进程是否已经启用了ASLR并采用了PIE编译,除了分析系统的ASLR配置外,还需要结合具体可执行文件的特性进行综合判断。对于确定进程运行时是否应用了ASLR,往往需要借助底层的调试手段。
IDA
Segment registers(shift+F8)
当IDA遇到改变段寄存器的指令时,它会创建一个段寄存器变化点。所以,大多数变化点都由IDA自己维护。IDA假设段寄存器在变化点之间不会改变其值。如果您发现IDA无法找到段寄存器的变化,或者想要更改寄存器的值,则可以使用Change Segment Register命令创建一个变化点。您也可以使用Set default segment register value命令更改段寄存器的值。
IDA对变化点进行分类。在变化点列表中,您可以看到在寄存器值后面的以下后缀:
a(自动)-由IDA创建。之后可能会由IDA更改。
u(由用户)-由用户创建。IDA不会更改它。
如果未被相应命令禁用,则IDA会为变化点生成相应的“assume”指令。
JUMP
Jump to operand:Enter:这个操作允许你跳转到程序中某个特定操作数的实际地址。你只需要在IDA Pro的界面上选中你想要跳转到的操作数,然后按下 Enter 键。这将会把你的视图跳转到该操作数的实际地址。
Jump to previous position:Esc:这个操作会将你的视图跳转到上一个位置,也就是你之前查看过的地址。这个操作在你想要回到之前查看过的内容时非常有用。你只需要按下 Esc 键即可执行这个操作。
Jump to segment…:Ctrl+S:这个操作允许你跳转到程序中的特定段。你可以在弹出的对话框中选择要跳转到的段,然后按下 Ctrl+S 键。这将会把你的视图跳转到所选段的起始地址。
Jump to xref to operand…:X:这个操作允许你跳转到程序中某个特定操作数的交叉引用(xref)。交叉引用是指程序中其他地方引用了这个操作数的地址。你只需要在IDA Pro的界面上选中你想要查看交叉引用的操作数,然后按下 X 键。这将会把你的视图跳转到第一个交叉引用的地址。
Jump in a new window:这个操作会在一个新的窗口中打开目标地址。这样,你就可以在新的窗口中查看目标地址的详细信息,而不会影响到当前窗口的内容。这个操作在你想要同时查看多个地址的内容时非常有用。
Jump in a new hex window:这个操作与 “Jump in a new window” 类似,只不过它会在一个新的十六进制窗口中打开目标地址。这个操作在你想要查看目标地址的十六进制内容时非常有用。
CODE XREF 注释
在IDA Pro等汇编语言分析工具中,它表示代码交叉引用。这种注释用来指示一个函数或代码段调用了另一个函数或代码段。它通常包含了被调用函数的名称和调用者的地址,以及一些其他的信息。
具体来说,"CODE XREF"后面的信息通常分为几个部分:
被调用函数的名称:这是被调用函数的符号名称,它可以帮助理解调用关系。
调用者的地址:这是调用指令的内存地址,它可以帮助定位调用发生的位置。
交叉引用的类型:有时候,"CODE XREF"后面会跟着一个或多个字符,这些字符表示交叉引用的类型,例如"C"表示普通调用,"E"表示尾调用,"I"表示间接调用,等等。
例如,如果看到一个"CODE XREF: sub401000"的注释,这意味着在当前代码段中有指令调用了名为"sub401000"的子程序。
在IDA Pro中,可以通过查看"Code"窗口中的交叉引用来获取更详细的信息,包括调用者和被调用者之间的关系。这有助于理解程序的流程和控制结构。
WinGraph32
Wingraph32 是一个与 IDA Pro 反汇编工具集成的图形化工具,它用于显示程序的流程图和控制流图。Wingraph32 可以帮助用户可视化地理解程序的执行流程,包括函数调用、循环、条件分支等。
Wingraph32 通常与 IDA Pro 的插件一起使用,例如 OllyFlow 插件,它是一个用于动态分析程序执行过程的插件。OllyFlow 可以生成控制流图,然后使用 Wingraph32 来显示这些图。这些图形化表示可以帮助逆向工程师更好地理解程序的逻辑结构和行为。
Wingraph32 的界面相对简单,它显示了一个程序的静态控制流图,包括函数、循环和条件判断等。用户可以通过点击不同的节点来查看详细的汇编代码或跳转到对应的代码位置。
Wingraph32 的一个主要优点是它的空间效率,因为它可以紧凑地表示大型程序的复杂结构。然而,它也有一些局限性,例如缺乏与 IDA Pro 的深度集成,以及有限的可交互性。
随着 IDA Pro 的发展,Wingraph32 已经被更先进的图形化工具所取代,如 IDA Pro 内置的图形化功能,这些功能提供了更丰富的可视化选项和更好的用户体验。尽管如此,Wingraph32 仍然在一些特定的场景和插件中发挥作用。
这些替代工具包括:
流程图(Flowcharts):IDA Pro 可以生成程序的流程图,显示函数调用、循环、条件分支等。这些流程图可以直接在 IDA Pro 的环境中查看和编辑。
调用图(Call Graph):这个工具可以生成程序的调用图,显示函数之间的调用关系。
交叉引用图(Xref Graph):这个工具可以生成程序的交叉引用图,显示代码段之间的引用关系。
数据流图(Data Flow Graph):这个工具可以生成程序的数据流图,显示数据如何在程序中流动。
还有一些第三方应用可以提供类似的功能,例如:
Ghidra:这是一个开源的逆向工程框架,它提供了自己的图形化工具来分析程序的控制流和数据流。
Binary Ninja:这是一个现代的逆向工程平台,它提供了丰富的图形化功能来帮助理解程序的结构和执行流程。
x64dbg:这是一个开源的 x64/x32调试器,它也有一些图形化插件来显示程序的控制流。
OllyDbg:这是一个流行的调试器,它有插件可以生成控制流图
反调试
利用IO重定向来绕过反调试
IO重定向是一种在操作系统级别上修改输入输出流向的技术。在某些情况下,它可以用来绕过反调试措施。反调试技术通常用于检测和阻止调试器或其他分析工具的接入,以保护软件不被篡改或逆向工程。以下是一些基本的步骤和技巧,用于利用IO重定向来绕过反调试:
启用IO重定向:在一些系统中,你可以通过修改系统的IO端口或使用特定的指令来启用IO重定向。这通常涉及到向 IO_BASE 寄存器写入一个特定的值,以启用重定向。
修改内存映射:修改内存映射是一种常见的技术,用于将内存中的某些区域映射到其他区域。这可以通过修改 GOT(全局偏移表)或 PLT(程序链接表)来实现。
使用原始指令:在一些情况下,原始指令(如 NOP 指令)可以用来绕过反调试检查,因为它们不修改任何寄存器或内存状态。
利用代码覆盖:通过覆盖特定的代码区域,可以使得反调试器认为程序正在执行正常的代码路径,从而绕过防护。
修改调试信息:修改程序的调试信息,如函数名、变量名等,可以使得反调试器难以识别程序的结构,从而降低逆向工程的效果。
使用第三方工具:有些第三方工具,如 Frida、DBI 等,可以用来动态修改程序的行为,从而绕过反调试防护。
EBPF
IDA中的ebpf插件
reverse-engineering-ebpfkit-rootkit-with-blackberrys-free-ida-processor-tool
使用eBPF完成安卓App hook
使用 eBPF (Extended Berkeley Packet Filter) 完成安卓 App 的 hook 是一个高级的逆向工程任务,它通常需要对 Linux 内核、eBPF 编程以及安卓系统的内部工作原理有深入的了解。以下是一个简化的步骤指南,用于在安卓应用中实现 eBPF hook:
设置 eBPF 环境:
确保你的开发环境支持 eBPF,例如使用最新的 Linux 内核。
安装必要的工具,如 clang、llvm、bpftool 等。
编写 eBPF 程序:
使用 eBPF 语言编写你的 hook 程序,它将运行在安卓设备的内核空间。
编写程序以过滤和处理你感兴趣的事件,例如系统调用、网络流量等。
编译 eBPF 程序:
使用 clang 编译你的 eBPF 程序为二进制格式。
安装 eBPF 程序到安卓设备:
使用 adb 或其他工具将编译后的 eBPF 二进制文件安装到安卓设备上。
确保你有权限在设备上安装内核模块。
加载 eBPF 程序:
使用 bpftool 或系统调用将 eBPF 程序加载到内核中。
配置 eBPF 程序的参数,如挂钩点、过滤条件等。
测试和验证:
运行你的安卓应用,并验证 eBPF 程序是否按预期工作。
调试和调整 eBPF 程序,直到它能够稳定地提供你想要的数据。
持久化 eBPF 程序:
如果需要,将 eBPF 程序打包到安卓系统的启动脚本中,使其在设备启动时自动加载。
请注意,这个过程涉及到对系统内核的修改,可能会对设备的保修状态产生影响,并且在某些情况下可能违反软件的服务条款。在进行这些操作之前,请确保你有足够的权限,并且对你的行为负责。此外,由于安卓系统的复杂性和安全性,eBPF hook 的实现可能需要特定的知识和技能,因此在实际应用中可能需要专业的逆向工程师来完成。
以下是一个简单的示例,展示了如何使用eBPF在安卓应用中插入代码:
首先,创建一个名为hook_example.c的C文件,内容如下:
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>
#include <bcc/proto.h>
int count = 0;
int trace_syscalls(struct pt_regs *ctx, struct kernel_syscall_args *) {
count++;
return 0;
}
char __license[] __section("license") = "GPL";
编译这个C文件为eBPF程序:
bash
$ clang -target bpf -O2 -Wall -Werror -c hook_example.c -o hook_example.o
$ llc -march=bpf -filetype=obj -o hook_example.o.bc hook_example.o
将生成的.o.bc文件转换为eBPF字节码:
$ sudo bpftool c-to-bpf hook_example.o.bc
加载并运行eBPF程序:
$ sudo bpftool prog load hook_example.o
$ sudo bpftool map create count_map pinned /sys/fs/bpf/count_map type long
$ sudo bpftool map dump count_map
在你的安卓应用中,使用JNI调用上述命令来加载和运行eBPF程序。例如,你可以在你的Java代码中添加以下方法:
public native void runEbpf();
static {
System.loadLibrary("your_native_library");
}
然后在你的C或C++代码中实现这个方法:
#include <jni.h>
#include "your_header_file.h"
JNIEXPORT void JNICALL Java_your_package_name_YourClassName_runEbpf(JNIEnv *env, jobject obj) {
// 在这里调用上述命令来加载和运行eBPF程序
}
这样,每当你的安卓应用调用runEbpf()方法时,eBPF程序就会被加载并运行,从而实现对安卓应用的hook。
HOOK
PLT hook 和 inline hook的区别
PLT hook(Procedure Linkage Table Hook)和inline hook是两种不同的代码hook技术,它们在实现机制和使用场景上各有特点。
实现机制:
PLT hook:它利用动态链接库(DLL)中的Procedure Linkage Table(函数地址表)来拦截函数调用。当程序调用一个函数时,会首先查找PLT表中的地址,然后跳转到对应的函数实现。通过修改PLT表中的条目,我们可以将函数的调用重定向到自定义的函数中。这种hook不需要修改目标函数的代码,因此更加稳定,但是它只能hook通过PLT表跳转的函数。
inline hook:它通过直接修改目标函数的代码来实现hook。通常是在目标函数的开始处插入一个跳转指令,跳转到自定义的函数中,然后自定义函数执行完毕后,再跳回到目标函数被hook指令之后的代码继续执行。这种方法可以实现更精细的控制,但是因为涉及到修改代码,所以可能会因为各种保护机制(如代码签名、权限设置等)而难以实施。
使用场景:
PLT hook适用于那些需要在线上环境中大规模使用的场景,例如字节跳动开源的Android PLT hook方案bhook就广泛应用于其Android应用中。PLT hook的优点是稳定性和兼容性好,可以确保在多种不同的环境中都能可靠地工作。
inline hook则更适用于对性能要求高,或者需要深入控制函数执行流程的场景。例如,一些安全相关的操作,如杀毒软件的病毒检测,或者游戏外挂的开发,可能会使用inline hook来深入干预程序的执行。
兼容性与稳定性:
PLT hook由于不修改目标代码,所以兼容性和稳定性较好,但是它对于新加载的代码需要重新hook,灵活性较低。
inline hook的兼容性和稳定性依赖于目标代码的布局和指令的长度,如果目标代码有所变动,可能会导致hook失败。但是,它的灵活性更高,可以针对特定需求进行定制。
性能影响:
PLT hook通常性能影响较小,因为它仅仅修改了函数调用的入口点,没有修改函数本身。
inline hook可能会对性能产生影响,因为它改变了目标函数的执行流程,并且在自定义函数中可能需要执行额外的代码。
xHook
xHook 是一个用于Android原生ELF(可执行文件和共享库)的PLT(过程链接表)钩子库。它具有以下特点:
支持Android 4.0至10(API级别14至29)。
支持armeabi、armeabi-v7a、arm64-v8a、x86和x86_64。
支持ELF HASH和GNU HASH索引的符号。
支持SLEB128编码的相对定位信息。
支持通过正则表达式设置钩子信息。
不需要root权限或任何系统权限。
不依赖任何第三方共享库。
xHook的功能包括:
注册钩子信息:通过xhookregister函数,可以注册需要钩子的ELF文件和符号。
忽略钩子信息:通过xhookignore函数,可以忽略某些钩子信息。
执行钩子操作:通过xhookrefresh函数,可以实际执行钩子操作。
清除缓存:通过xhookclear函数,可以清除xHook持有的所有缓存,将所有全局标志重置为默认值。
启用/禁用调试信息:通过xhookenabledebug函数,可以启用或禁用调试信息。
启用/禁用SFP(段错误保护):通过xhookenablesigsegvprotection函数,可以启用或禁用SFP。
xHook的使用案例主要包括:
修改ELF文件中的函数实现:可以通过钩子机制,在加载的ELF文件中替换原始函数实现。
忽略特定ELF文件的钩子:可以根据正则表达式忽略特定ELF文件的钩子。
总之,xHook是一个在Android原生ELF文件上实现钩子操作的库,可以用于修改、忽略或执行特定ELF文件的钩子。它具有较高的兼容性和稳定性,适用于不同版本的Android系统和多种架构。
elf动态库注入取证
elf动态库注入取证是指对共享库(.so 文件)在运行时被注入到进程中的情况进行分析和调查的一种技术。这种技术通常用于安全研究和数字取证,以确定是否有恶意代码被注入到正常的应用程序中,或者是否有未授权的共享库被加载到进程中。
在 Linux 系统中,共享库是动态链接的重要组成部分,它们在程序运行时被加载,而不是在编译时。这意味着,即使一个程序在编译时没有包含特定的功能,它也可以在运行时通过加载相应的共享库来获得这些功能。
共享库的注入可以通过多种方式实现,包括:
环境变量:通过设置 LD_LIBRARY_PATH 等环境变量,可以指定动态链接器在运行时搜索共享库的路径。
rpath:在编译时,可以通过 -Wl,-rpath,‘ORIGIN’ 等选项设置 rpath,使得运行时能够找到对应的共享库。ORIGIN 指的是可执行文件(或共享库)所在的目录。
共享库劫持:通过替换系统中的共享库,或者通过 LD_PRELOAD 环境变量,可以使得在运行时加载特定的共享库。
在进行 ELF shared library injection forensics 时,通常会使用一些工具和技术,如:
readelf:查看共享库的依赖关系和运行时路径。
strace:跟踪程序执行时的系统调用和接收到的信号。
gdb:对程序进行调试,查看在运行时加载的共享库。
memory forensics:对进程的内存空间进行分析,查找异常的共享库加载情况。
通过这些技术和工具,可以有效地分析和调查注入的行为和影响,为安全防护和数字取证提供重要的信息。
一般步骤:
获取目标程序的ELF文件:首先需要获取目标程序的可执行文件,以便进行分析。
分析ELF文件:使用适当的工具和技术对ELF文件进行分析,以确定是否存在被注入的共享库。这可能涉及到检查文件头、节表、动态链接器信息等。
识别被注入的共享库:通过分析ELF文件中的动态链接器信息和加载路径,可以确定哪些共享库被注入到目标程序中。这些共享库可能是恶意的或未经授权的。
分析共享库的功能和行为:一旦确定了被注入的共享库,可以使用逆向工程和调试技术来分析其功能和行为。这可能涉及到反汇编代码、跟踪函数调用、分析控制流等。
提取证据:根据分析的结果,可以提取出有关恶意软件或攻击者的证据。这可能包括共享库的名称、版本号、功能描述、通信协议等。
报告和响应:最后,将取证结果整理成报告,并根据需要采取相应的安全措施来应对发现的恶意软件或攻击活动。
参看这篇文章:ELF shared library injection forensics
UNIDBG
补环境
Unidbg 是一个强大的逆向调试工具,它支持多种调试技术和插件,可以模拟执行 Android 和 iOS 平台上的应用程序。为了能够正确地调试和分析应用程序,Unidbg 需要一个完整的补环境(bootstrap environment),这个环境包括了 JNI 补环境、文件访问补环境以及补系统调用和库函数。以下是对这些补环境的详细介绍:
JNI 补环境: JNI(Java Native Interface)是 Java 用于与本地代码(如 C/C++)交互的接口。在逆向 Android 应用程序时,如果应用程序使用了 JNI 调用本地库,Unidbg 需要一个 JNI 补环境来模拟这些调用。这个补环境包括了 JNI 函数的声明和实现,它们可以被 Unidbg 用来拦截和模拟 JNI 调用。
文件访问补环境: 应用程序可能需要访问文件系统来读取或写入文件。Unidbg 的文件访问补环境允许模拟这些文件操作,包括文件路径、权限和文件内容。这样,即使在模拟环境中,应用程序也能够像在真实设备上一样进行文件访问,从而保证调试的连贯性。
补系统调用和库函数: 系统调用和库函数是操作系统和应用程序交互的接口。Unidbg 提供了补系统调用和库函数的功能,这允许它模拟系统调用和库函数的行为。这对于调试那些依赖于特定操作系统功能的应用程序至关重要,如权限检查、进程管理和硬件访问等。
为了创建这些补环境,通常需要编写相应的脚本和插件。Unidbg 社区提供了许多现成的补环境脚本和插件,这些可以被下载和使用。用户也可以根据自己的需要定制和扩展这些补环境,以支持特定的应用程序或平台。
使用 Unidbg 的补环境时,用户可以加载和执行应用程序的 SO 文件,设置断点,观察程序的执行,以及跟踪和修改程序的状态。这些功能使得 Unidbg 成为一个强大的工具,用于 Android 和 iOS 应用程序的逆向工程和调试。
文件结构
ELF
ELF 资源汇总
awesome-elf
Making our own executable packer-针对intel
section和segment的关系
在ELF(Executable and Linkable Format)文件中,section和segment是描述文件中数据组织的两个概念。它们之间的关系可以这样理解:
Section(节):
Section是编译器创建的,用于将程序的不同部分逻辑地组织在一起。例如,.text节包含程序的代码,.data节包含初始化的数据,.bss节包含未初始化的数据等。
Section的边界在编译时是固定的,它们是目标文件(object file)中的一个连续的数据块。
Section头表(Section Header Table)包含了有关每个section的信息,如大小、位置和属性等,这些信息对于链接器(linker)来说是重要的,因为链接器需要它们来将不同的目标文件合并成一个可执行文件。
Segment(段):
Segment是加载器(loader)创建的,它们代表了程序在内存中的物理布局。每个段都有特定的访问权限,如读(Read)、写(Write)和执行(Execute)。
Segment的边界在加载时是动态的,它们可以跨越多个section。例如,代码段可能包含.text和.plt节。
Segment头表(Program Header Table)包含了有关每个segment的信息,如大小、文件偏移量、虚拟地址和物理地址等,这些信息对于加载器来说是重要的,因为加载器需要它们来将程序的各个部分映射到内存中的正确位置。
总结来说,section是编译器层面的概念,用于源代码的编译和链接,而segment是加载器层面的概念,用于程序的加载和执行。在编译过程中,编译器会生成section,然后在链接过程中,链接器会根据section信息来创建segment。加载器在程序运行时会使用segment信息来将程序的各个部分映射到内存中。
.dynamic段
.dynamic段包含了一些用于动态链接的信息。.dynamic段不是标准ELF段名,但它通常出现在可执行文件和共享库文件中,用于存储与动态链接相关的数据。.dynamic段包含了三个主要的部分:.dynsym、.dynstr和.hash。
.dynsym(动态符号表):
.dynsym段包含了动态链接时需要的符号信息。这些符号是动态符号表的一部分,用于在运行时将程序中的符号解析为相应的函数和变量。
动态符号表中的条目通常包括符号的名字、类型、属性(如是否为外部符号)、大小和位置等信息。
.dynstr(动态字符串表):
.dynstr段包含了动态链接时需要的字符串字面量。这些字符串通常用于动态符号表中的符号名称和版本控制。
动态字符串表中的条目包含了字符串的内容和它们在文件中的偏移量。
.hash(动态哈希表):
.hash段包含了用于加速动态链接过程的哈希表。这个哈希表是根据符号的名称和属性来组织的,它使得查找符号的速度更快。
动态哈希表通常包括符号的哈希值、名称和偏移量等信息。
这些段落通常在程序被动态链接器处理时会用到。当程序被加载时,动态链接器会使用.dynamic段中的信息来解析符号和字符串,并创建相应的符号表和哈希表。这使得程序能够在运行时动态地引用其他共享库中的函数和变量。
需要注意的是,这些段落的布局和内容可能会根据不同的编译器和目标系统有所不同。因此,当处理.dynamic段时,需要参考特定编译器和目标系统的文档。
.init和.init_array段
.init和.init_array段是用于初始化代码和初始化调用序列的特殊段。
.init 段
.init段包含了程序的初始化代码。这部分代码在程序启动时被调用,用于执行任何必要的初始化任务。这通常包括设置全局变量、初始化静态数据结构、注册信号处理程序等。
.init段中的代码是程序启动时执行的第一批代码之一。在Linux系统上,.init段通常会由系统初始化程序(如init)或者脚本(如startup.script)调用。
.init_array 段
.init_array段是一个特殊的节,它包含了初始化函数的数组。这些函数在程序启动时按顺序被调用。.init_array中的函数通常用于初始化共享库或者程序的特定部分。
在Linux系统上,当一个共享库被加载时,动态链接器会查找.init_array段,并按照其中的函数列表调用它们。这些函数可以用来进行全局初始化,比如设置全局变量、初始化数据结构等。
.init_array段中的函数通常以弱符号(weak symbols)的形式声明,这意味着如果同一个符号在其他的.init段或者.init_array段中被更强地定义,那么这个弱符号的初始化函数就不会被调用。
需要注意的是,.init和.init_array段是可选的,并不是所有的ELF程序都会使用它们。如果一个程序或者共享库不需要在启动时执行初始化代码,那么这些段就可以省略。
在 objdump 工具的输出中,你可以看到.init和.init_array段的内容,包括它们包含的函数和数据。这些信息对于理解程序的初始化流程和调试初始化问题时非常有用。
.init和.init_array两个section用于在so文件被加载时做初始化工作,系统在加载so文件时会先执行.init中的代码,然后再顺序执行.init_array中的各个代码块,且.init和.init_array的执行时机均早于JNI_OnLoad方法,所以这两个section的执行时间是比较早的,所以有时解密函数会放在.init或者.init_array中.
.rel.dyn和.rel.plt
在ELF(Executable and Linkable Format)文件中,.rel.dyn和.rel.plt是两种不同类型的重定位表,它们用于动态链接过程中,但它们的用途和格式有所不同。
.rel.dyn(Dynamic Relocation Table):
.rel.dyn是一个包含动态链接的重定位信息的表。
它用于记录那些在程序运行时可能需要改变地址的动态变量和函数。
.rel.dyn通常包含基地址(base address)和偏移量(offset),以及需要重定位的符号的名称。
当动态链接器加载动态库时,它会使用.rel.dyn表中的信息来调整内存中的地址,确保程序能够正确地引用动态库中的符号。
.rel.plt(Procedure Linkage Table):
.rel.plt是一个用于动态链接的另一个重定位表,它通常与.plt(Procedure Linkage Table)一起使用。
.plt是一个包含函数和变量地址的表,而.rel.plt则是.plt的一个可选部分,用于存储额外的重定位信息。
.rel.plt可以包含对.plt中条目的引用,以及用于重定位的其他信息。
在一些链接器中,.rel.plt可以用于处理复杂的动态链接情况,例如递归依赖或其他高级链接策略。
总的来说,.rel.dyn和.rel.plt都是用于实现动态链接的重定位机制,但它们的格式和使用的数据结构(如.plt和.rela.dyn)有所不同。在实际的链接过程中,链接器会根据需要选择使用哪种类型的重定位表,以确保程序的正确运行。
修复从内存中dump 出来的so
从内存中dump出来的so文件可能因为某些原因无法直接使用,需要进行修复。以下是一般步骤来修复这样的so文件:
检查ELF头部:首先,检查ELF头部信息是否完整。如果ELF头部有缺失或错误,需要根据标准ELF头部的格式进行修复。比如,确保魔数、大小端序、操作系统和硬件架构等字段正确设置。
修复Loadable Segment:对比正常so文件的Loadable Segment,检查其poffset和pvaddr是否一致。如果不一致,需要将poffset的值改为与pvaddr相同。
修复Dynamic Segment:同样地,对比Dynamic Segment中的数据,确保其poffset和pvaddr一致。
重定位数据修复:如果so文件在内存中展开后,重定位数据被修改,那么这些数据需要被修复。通常这涉及到对重定位表的分析和修改。
添加Section Header Table:如果dump出来的so文件缺少Section Header Table,需要从原始的so文件中提取这个表,并将其添加到dump文件中。
修改Section Header:修改Section Header中的eshoff,确保其值正确指向Section Header Table。
使用工具辅助修复:可以使用一些工具如IDA Pro等来辅助分析并修复so文件。这些工具可以帮助理解so文件的结构,并定位需要修复的部分。
修复so文件的过程可能需要手动进行,或者编写脚本自动化修复。具体情况取决于so文件损坏的程度和类型。在进行修复时,建议详细记录每一步的操作和修改,以便于后续的检查和验证。
具体操作1
拷贝 Segment(程序段)头到新的so文件中,以便在IDA中进行静态解析。
拷贝Section(节)头到新的so文件中,同样是为了在IDA中进行静态解析。
判断Section头数据的起始地址是否正好处于偏移的数据中,如果是,需要重新计算偏移后的地址,然后拷贝到新的so文件中。
更新在映射到内存过程中不变的数据。
将在.init_array段中出现的,在.rel.dyn的重定位项设置为0,然后将.init_array段全部数据置0。
将修复后的数据写入到新的文件中,比如:liba_repair.so,这样就可以直接放入到原apk中,程序可以正常运行。
修复函数示例
首先,确保你有一个原始的、未损坏的so文件作为参考
使用objdump工具查看so文件中的符号表,找到需要修复的函数名和偏移地址。命令如下:
objdump -x /path/to/your/original.so
使用readelf工具查看so文件中的节表,找到需要修复的函数所在的节。命令如下:
readelf -S /path/to/your/original.so
使用gdb工具加载损坏的so文件,然后使用set address function_name命令将函数指针指向原始函数的地址。例如:
gdb your_broken_app your_broken_so
(gdb) set *0x12345678 function_name
如果需要修改函数的实现,可以使用patch命令将原始函数的代码应用到损坏的函数上。例如:
(gdb) start
(gdb) ptype function_name
(gdb) disassemble function_name
在上述输出中找到损坏函数的起始和结束地址,然后使用dd命令将原始函数的二进制代码复制到损坏函数的内存空间中。例如:
(gdb) dd if=/path/to/your/original.so of=/dev/mem bs=1 seek=start_address count=end_address-start_address conv=notrunc skip=1
最后,使用gdb的quit命令退出调试器,然后尝试运行你的应用程序,看看是否已经修复成功。
Shiva
Shiva是一个用于Linux ELF软件的LEJIT micropatching系统。帮助开发人员对原生ELF软件进行重新编程和修改。Shiva是一个ELF程序解释器,它负责加载和链接附加模块,这些模块包含代码和数据。Shiva支持AArch64架构,并且可以用于链接和安装补丁。Shiva还支持函数插值和变换等高级功能
shiva 项目
GOT 全局偏移表
GOT(Global Offset Table)是ARM架构中的一个特殊表,它在动态链接过程中起着至关重要的作用。GOT表是动态链接器用来存储全局符号(如函数和变量)的内存地址的地方。这些符号可能来自不同的动态链接库,而且它们的地址在程序加载时是未知的,因为它们依赖于程序的实际加载地址。
在ARM架构中,GOT表通常是一个数组,其中每个条目都包含了一个全局符号的地址。这些条目在程序加载时由动态链接器(如ld.so)填充,以确保程序中的所有动态引用都能正确地解析到相应的符号地址。
GOT表的使用与PLT(Program Linkage Table)紧密相关。PLT是一个数组,其中包含了每个动态链接库的入口点(通常是函数)的索引。当程序调用一个动态链接的函数时,它不是直接跳转到函数的地址,而是通过PLT表中的索引来查找GOT表中对应的地址。这样,即使动态链接库被加载到内存的不同位置,程序也能正确地找到并调用函数。
GOT表的特点包括:
动态性:GOT表的内容是在程序加载时动态填充的,而不是在编译时确定的。
全局性:GOT表包含了程序中所有全局符号的地址,这些符号可能来自不同的动态链接库。
地址解析:GOT表用于解析动态链接库中的函数和变量的地址,使得程序能够正确地引用这些符号。
重定位:在程序加载时,动态链接器会根据实际的内存地址修改GOT表中的条目,这个过程称为重定位。
在ARM架构中,GOT表是实现动态链接的关键组件之一,它使得程序能够灵活地使用动态链接库,而无需担心库的加载地址
全局偏移表的主要作用包括:
动态链接:在动态链接(Dynamic Linking)中,全局偏移表用于存储程序中使用的全局变量和函数的地址。这样,当程序运行时,它可以通过全局偏移表来查找这些全局变量和函数的实际地址,而不是在编译时硬编码这些地址。
重定位:程序在编译时并不知道它将被加载到内存的哪个位置,因此编译器不能在程序中生成 absolute addresses。全局偏移表允许在程序加载时(或运行时)进行重定位,即修正程序中的地址引用,使其指向正确的内存位置。
共享库:在共享库(Shared Libraries)中,全局偏移表用于存储共享库中函数和变量的地址。当多个程序使用同一个共享库时,全局偏移表可以帮助它们正确地引用共享库中的资源,而不需要为每个程序单独编译共享库。
可执行文件格式:在ARM架构的可执行文件格式(如ELF)中,全局偏移表是一个重要的组成部分,它使得可执行文件可以在不同的加载地址下运行。
内存管理:全局偏移表还可以用于内存管理,特别是在使用内存分配和释放时,全局偏移表可以帮助跟踪和定位内存中的不同区域。
总之,全局偏移表是编程语言和操作系统用于实现程序地址无关性的一种机制,它使得程序可以在不同的内存环境中运行,而无需修改程序代码。这对于提高软件的可移植性和灵活性非常重要。
PLT:程序链接表(Procedure Link Table),外部调用的跳板,在ELF文件中以独立的段存放,段名通常叫做”.plt”
GOT:全局偏移表(Global Offset Table),用于记录外部调用的入口地址,段名通常叫做”.got”
GOT表和.got、.plt和.got.plt
GOT表、.got、.plt和.got.plt是动态链接过程中的关键概念,它们在ELF(Executable and Linkable Format)文件格式中扮演着重要的角色。这些概念主要用于处理程序中对外部符号(如函数和变量)的引用,特别是在涉及多个动态链接库时。
GOT表:
GOT(Global Offset Table)表是一个在程序运行时维护的表,它用于存储动态链接库中导出的全局符号的内存地址。这些地址是在程序加载时确定的,因为动态链接库可以在不同的地址被加载。GOT表使得程序能够在运行时找到并调用这些全局符号,而无需知道它们的确切地址。
.got节:
在ELF文件格式中,.got节(Global Offset Table section)是一个包含GOT表的节。这个节在动态链接库中是可见的,它包含了动态链接库中所有导出的全局符号的地址。这些地址在链接时是未知的,因此.got节中的条目在程序加载时由动态链接器填充。
.plt节:
.plt(Procedure Linkage Table)节是一个数组,其中包含了每个动态链接库的入口点(通常是函数)的索引。当程序调用一个动态链接的函数时,它通过PLT表中的索引来查找.got节中对应的地址,然后跳转到该地址执行函数。
.got.plt节:
.got.plt节(Global Offset Table for Procedure Linkage Table)是.got节的一个子集,它专门用于存储与PLT相关的GOT条目。这些条目是PLT表中的索引所对应的实际地址。因此,当程序通过PLT表中的索引查找函数地址时,它会查找.got.plt节中的相应条目。
总结来说,GOT表是一个在运行时维护的表,它包含了动态链接库中全局符号的地址。.got节是ELF文件格式中的一个节,它包含了GOT表的内容。.plt节是一个数组,其中包含了每个动态链接库的入口点的索引。而.got.plt节是.got节的一个子集,它专门用于存储与PLT相关的GOT条目,使得程序能够通过PLT表中的索引来查找并调用动态链接的函数。这些概念和结构使得动态链接成为可能,并且使得程序能够在不同的环境中灵活地运行。
花指令
花指令5要素
花指令(Junk Code)是一种用于混淆和干扰逆向工程分析的技巧。它通过在程序中插入无用的、看似随机或者无害的代码来迷惑逆向工程师,使得他们难以理解程序的真实逻辑。花指令的有效性通常取决于以下五个要素:
复杂性:花指令应该足够复杂,以至于不会被轻易识别出其真实的意图。这通常意味着它们包含了一些不常见的指令或者特殊的编码模式。
随机性:花指令通常看起来像是随机生成的,没有明显的模式或者逻辑。这种随机性可以防止逆向工程师通过统计分析来识别代码的模式。
误导性:花指令可能会模仿有效的指令,但是执行时并不会产生预期的效果,或者它们可能会在某些条件下执行,从而误导逆向工程师认为它们是有用的代码。
变化性:花指令可能会根据不同的条件或者执行阶段发生变化,使得同一代码在不同情况下有不同的表现,这增加了逆向分析的难度。
叠加性:花指令可能会与程序中的其他代码片段叠加,形成一个新的代码序列,这个新的序列可能会对逆向工程师造成额外的混淆。
为了提高花指令的效果,通常会结合使用多种技巧,比如在代码中插入特殊的字符串或者模式,使用复杂的跳转逻辑,或者在不同的执行上下文中插入不同的代码片段。逆向工程师在分析花指令时,需要具备深厚的汇编语言和程序设计知识,以便能够识别和理解这些复杂的干扰模式。
Smali
基础
指令:Smali 包含了一系列指令,用于表示程序的控制流、操作数和返回值。例如,nop 用于无操作,move-result 用于将操作结果移动到指定变量。
局部变量:Smali 允许定义局部变量,并使用 . 操作符来访问它们。局部变量通常以数字索引的方式进行引用。
常量池:Smali 中的常量池用于存储字符串、数字和其他常量。常量池索引通常以 # 开头。
方法调用:Smali 中的方法调用使用 invoke 指令。调用可以是有返回值的,也可以是虚拟调用,还可以是静态调用。
控制流:Smali 支持条件分支和循环控制流。条件分支使用 if-eq、if-ne 等指令,循环使用 goto 和 if 指令。
异常处理:Smali 提供了异常处理的机制,使用 try、catch 和 finally 指令来捕获和处理异常。
打包
重打包技巧通常涉及到对 Smali 代码的修改,以实现特定的目的,比如修改应用程序的行为、移除权限要求或者绕过某些安全检查。以下是一些重打包技巧:
修改方法签名:通过改变方法的签名,可以创建一个新的方法,该方法与原始方法具有相同的名称,但参数列表不同。
修改常量池:通过修改常量池中的内容,可以改变代码中的字符串和数字值。
添加或删除指令:通过添加或删除 Smali 指令,可以改变程序的控制流和行为。
修改局部变量和参数:通过修改局部变量和参数的索引,可以改变变量和参数的名称和作用。
使用 Smali 混淆:通过使用 Smali 混淆技术,可以使得代码更难以阅读和理解。
在进行重打包时,需要谨慎操作,因为不当的修改可能导致应用程序崩溃或行为异常。此外,重打包可能会违反软件许可协议,因此在进行此类操作之前,请确保您有权修改和使用该软件。
系统类
LINUX
1、如何查看android 系统是否启用了ASLR
地址空间布局随机化(Address Space Layout Randomization, ASLR)
adb shell cat /proc/sys/kernel/randomize_va_space