Binary Ninja+LLDB独孤九剑

文摘   2024-09-24 10:13   湖北  

点击上方蓝字 江湖评谈设为关注/星标




前言

独孤九剑,九剑起势。

起因是进行OpenJDK24 Reverse的时候,在bt(堆栈)上发现用汇编写的解释器,如果仅阅读部分源码非常难以理解。通读JVM自然可以理解的,但代价有点大。用LLDB脚本+软硬断点的形式非常繁琐。Binary Ninja流程图+LLDB破尽天下招式/内功。本篇带你领略。

Binary Ninja+LLDB

Binary Ninja(后简称:BN)在Ubuntu22.04上完全不需要安装,也不需要任何其它组件的支持,非常方便。下载 地址:

https://binary.ninja/free/

选择Download for Linux,下载之后它是一个zip的压缩文件,解压命令:

unzip -x binaryninja_free_linux.zip

进入解压的文件夹,即可直接运行:

#cd binaryninja#./binaryninja

进入之后,需要设置几个地方,你需要设置本地调试器,比如这里选择的是LLDB。菜单栏Debugger->Debugger Adapter settings,Java设置如下图:

HelloWord是你测试的用例,用命令行传递进去。菜单栏File->Open选择上图Executable Path里的可执行文件。

下面是功能:

  1. 下断点:BN左侧边框点击类似虫子的图标,有个BreakPoints里面可以添加/删除/转到断点。

  2. 快捷键:F6启动调试,F7单步,F8步过,F9运行,G跳转到地址。

  3. 菜单栏View->Graph即可打开流程图

  4. 菜单栏Analysis-》Reanalyz对程序进行分析,以便于形成相应的流程图。

以上搞好了之后,下面就可以开启LLDB。Java代码(也即是上面BN命令的HelloWord):

public class HelloWorld {    public static void main(String[] args) {        String name = "Alice";        int age = 0x10;        System.out.printf("Name: %s\n", name);        System.out.printf("Age: %d\n", age);    }}

在Java_java_io_FileOutputStream_writeBytes处下断,以下使用printstack.py脚本打印出来的(printstack脚本参考:天下第一调试利器LLDB)。

(lldb)b Java_java_io_FileOutputStream_writeBytes(lldb)r(lldb)bt(lldb) printstack Function Name:0x7ffff7e48cbf---Java_java_io_FileOutputStream_writeBytesFucntion  ASM:0x7fffe85457ba    0x7fffe85457ba: vzeroupperFucntion  ASM:0x7fffe853edee    0x7fffe853edee: mov    rcx, qword ptr [rbp - 0x10]    //省略部分,便于观看Fucntion  ASM:0x7fffe853ef88    0x7fffe853ef88: mov    rcx, qword ptr [rbp - 0x10]Fucntion  ASM:0x7fffe8537d01    0x7fffe8537d01: mov    rdi, qword ptr [rbp - 0x28]Function Name:0x7ffff5eae505---JavaCalls::call_helper(JavaValue*, methodHandle const&, JavaCallArguments*, JavaThread*)Function Name:0x7ffff641e714---os::os_exception_wrapper(void (*)(JavaValue*, methodHandle const&, JavaCallArguments*, JavaThread*), JavaValue*, methodHandle const&, JavaCallArguments*, JavaThread*)Function Name:0x7ffff5eadea7---JavaCalls::call(JavaValue*, methodHandle const&, JavaCallArguments*, JavaThread*)Function Name:0x7ffff5fa46d9---::jni_invoke_static(JNIEnv *, JavaValue *, jobject, JNICallType, jmethodID, JNI_ArgumentPusher *, JavaThread *)Function Name:0x7ffff5fb2336---::jni_CallStaticVoidMethod(JNIEnv *, jclass, jmethodID, ...)Function Name:0x7ffff7fa8598---invokeStaticMainWithArgsFunction Name:0x7ffff7fa9977---JavaMainFunction Name:0x7ffff7fafda6---ThreadJavaMainFunction Name:0x7ffff7c94ac3---start_threadFunction Name:0x7ffff7d26850---__clone3

JVM调用main前面堆栈还算正常,后面就变成了汇编代码。从JavaCalls::call_helper到0x7fffe8537d01地址就变成汇编。这个地方的JVM调用如下:

call_stub-》generate_call_stub-》generate_normal_entry-》

利用这些函数生成汇编,然后跳转到汇编入口进行解释器方面的执行。这个地址(0x7fffe8537d01)如果通过脚本或者硬件断点来进行debug遇到jmp之类指令跳转向上跟踪,非常麻烦。

要理解这些汇编,需要流程图支撑。

此时我们可以运行上面的Binary Ninja启动调试,启动之后,BN需要加载一段时间进行代码识别。然后G跳转到0x7fffe8537d01地址(注意这里如果地址不识别,可以先下Glibc的函数write让BN进行单步汇编运行,对照LLDB地址同步多运行几次,以此让BN慢慢识别),此时我们就可以愉快的看到了0x7fffe8537d01地址所在的流程图了:

有了它,如虎添翼,把整个JVM倒翻一遍都可以。相较于源码,更喜欢二进制。

结尾

独孤九剑,破尽天下招式。LLDB这类正规调试器极其强大的同时,就因为它过于正规,没有Reverse的效果,所以导致部分功能缺失。Binary Ninja弥补了这方面的遗憾。

往期精彩回顾

C++安全指针,Rust用处何在?

.NET9/Rust编译对比


江湖评谈
记录,分享,自由。
 最新文章