IDA PRO 06 - 动态调试基础04

今早看了一个视频,深得我心,与我更系列文章几乎是一样的理由,不过我想的没有那么透彻。不同的人生阶段会有不同的想法,重新开启一个新的学习旅程对现在的我来说,是一个值得尝试的东西。曾有段时间觉得自己除了写代码,啥都不会,现在发现还能做点事情,输出的应该也算有点价值。

视频链接:https://www.bilibili.com/video/BV1SH4y1q7k6/

本文讨论一下动态调试的一些其他技巧。

  • trace 使用

  • 调试fork进程

  • 调试 Android JNI 的 init_array 中的函数或JNI_OnLoad函数

Trace

trace 是用来记录一个进程在执行过程中发生的特定事件。这些事件会被记录到一个缓存区或者文件中。

我们以一个例子来讲解 trace 的作用,材料已经上传到了 p19。

首先,使用 IDA 打开目标文件,看其函数窗口,发现只有很少的几个函数:

IDA PRO 06 - 动态调试基础04_第1张图片

这是因为该程序里面的花指令太多了,干扰了IDA的静态分析结果。对于花指令,有些文章有比较详细的介绍:

https://www.anquanke.com/post/id/208682

https://www.anquanke.com/post/id/236490

有时间的可以看看,毕竟搞破解的都是搞开发的玩剩下的,出题的总是要高于解题的。

这个程序里面的花指令多到IDA都分析不出来啥东西,我们一个个的去看肯定也不现实,那么根据文章中说的:花指令大致可以分为可执行花指令和不可执行花指令两类。所以我们可以先使用 Trace 来找出不可执行的花指令。

进入调试模式,在 start 方法最前面打上断点,打开菜单项 [Debugger] → [Tracing] → [Tracing Options] 配置 Trace 选项:

IDA PRO 06 - 动态调试基础04_第2张图片

Trace buffer size,如果分析的程序较大,这个数值可以填大一些。

Trace text file,想要将Trace记录保存为文件,可以填入一个文件路径。

Highlight,这个一定要选上,它会将执行过的指令染色,非常的有用。

其他更细节的选项说明可以去查一下文档或者书籍,这里就不细说了。

Trace 的粒度:

第一个是指令级别的trace,就是记录的是每条指令的指令。

第二个是基本块的trace,基本块的概念 ollvm 里面有,可以找下文档。

第三个是函数级别的trace,仅仅记录函数的调用。

调试启动进程后,触发断点,这3个选项就可以选择了,我们选择指令trace,然后让程序继续运行,等待进入下一个断点或者程序结束,这里我们没有设置下一个断点,所以程序会运行直到结束或者等待输入之类的。

IDA PRO 06 - 动态调试基础04_第3张图片

可以看到,有一片黄色出现了,这就表示这些指令是执行到了的指令。

由于指令 trace 会极大的损耗性能,因为它要监视每条指令,所以会等待的时间比较长。

等到进程出现这个界面的时候,说明程序运行了一段逻辑了,它要等待用户输入,我们可以先分析从程序开始到这里的 trace,后面的再说。

所以,我们停止调试,打开 trace 窗口看看,打开菜单项 [Debugger] → [Tracing] → [Tracing Window] 查看 Trace 记录:

IDA PRO 06 - 动态调试基础04_第4张图片

这里有 18w 行指令,还是非常多的。所以一般我们是使用脚本来处理这些指令,但是我们还没有学习如何编写脚本,所以这里就只是简单介绍一下该怎么搞。

上图中,在 Address 这一行,我们右键,发现有个选项是默认勾选了的,如果是我们自己看,这个是有帮助的,但是如果要给 python 处理,这个地址的名字就很不方便,所以我们去掉勾选,看看效果:

IDA PRO 06 - 动态调试基础04_第5张图片

这个时候,地址格式就统一了。

我们简单的观察一下这些指令,发现一个规律,就是这些指令基本可以分为一块一块的,每个块里面的指令会比较对称:

IDA PRO 06 - 动态调试基础04_第6张图片

它的 push 与 pop 都是对称的,观察 rsp 寄存器的值也可以。花指令的一个原则,就是不干扰程序的正常运行。所以从这个方面入手,我们是要找出那些执行了也没啥乱用的指令块即可。

当然,我对花指令的研究也不深入,刚入行,这里也只是说了下我自己的看法。花指令的分析不在本系列范围内,所以暂不深入研究了。

将这些数据导出,可以直接右键 export trace to text file,以文本模式导出来,这样可以直接用 python 处理,比较方便。

调试 fork 进程

在上一篇课程中,我们调试过一个目标apk(blackbox.apk),还分析过它的 Java_com_pandaos_idacourse_MainActivity_enc函数,它的反汇编代码有这样的一段:

if ( !fork() )
  {
    __android_log_print(3, "pandaos", "try DEBUG ME!");
    init();
    init();
    init();
    init();
    init();
    init();
    init();
    init();
    init();
    init();
    init();
    __android_log_print(3, "pandaos", "tell me special_key.");
    exit(0);
  }

这个目的就很明显了,是想让我们找出子进程里面生成的 special_key。那么我们该如何调试这个子进程呢?看代码逻辑,这个子进程执行了一个循环就结束了,根本没有机会 attach 上去。

这个有一个技巧,就是将子进程的第一行指令 patch 为一个死循环,等断点附加上去之后,再将指令还原继续运行即可。

我们来操作一下,先看看子进程要执行的第一条指令:

.text:00000000000170D0 78 71 00 94                   BL              .__android_log_print

这行指令的意思就是跳转到__android_log_print 里面去执行。我们要将这行指令改为一个死循环要如何做呢?

在  arm 指令集里面,跳转指令有2种常用的:

B
BL

B指令是ARM中最基本的跳转指令,它的使用方法如下:

B label

表示跳转到  label 所指向的位置,我们看一个例子:

IDA PRO 06 - 动态调试基础04_第7张图片

我们可以利用这个指令来做一个死循环,看了一下指令的十六进制表示,没有发现B指令的操作符是啥,有点奇怪。

BL指令跟B不同:在跳转之前,会先将当前指令的下一条指令地址保存到LR寄存器中,然后才跳转到标号执行。这样做的好处是:当我们想从标号地方返回时,可以直接将LR寄存器中的返回地址赋值给PC,程序就可以返回到原来的程序中继续执行了。我们并不想更改寄存器,所以不用BL。

有了跳转指令基础,我们就可以 patch 了,首先在 fork 这里加上断点,然后附加上进程,修改函数调用为死循环:

IDA PRO 06 - 动态调试基础04_第8张图片

使用了,keypatch 插件后,我们就不用自己计算偏移了,只需要填目标地址就好了。

点击 patch,再看看伪代码,发现变成了死循环:

我们让程序继续运行,这样fork出来的进程就会进入死循环,这个时候,我们退出主进程的调试(Detach from process),重新附加到子进程上:

IDA PRO 06 - 动态调试基础04_第9张图片

可以看到,这个时候就出现了两个进程,28827 就是子进程,我们先附加上去,再给循环加上断点:

IDA PRO 06 - 动态调试基础04_第10张图片

有需要的可以进行指令还原,但是这里不需要,所以我们直接 set ip,让进程执行下面的 init 代码:

IDA PRO 06 - 动态调试基础04_第11张图片

按 F8 步过所有 init 调用,看看 special key 的值:

发现全部是 0,这样谜题就解出来了。

调试 Android JNI 的 init_array 中的函数或JNI_OnLoad函数

这里我们需要借助 frida,因为 frida 有一个 pause 模式,这个模式就是让 app 启动,没有完全启动,因为此时它还没加载自己的任何库。

由于app自己的库是有 linker 进行加载的,其 init_array 段也是 linker 主动调用的(有兴趣的可以翻翻源码),所以我们可以先找到 linker 中对应的函数,加上断点就能调试到 so 加载的时候执行的 init_array 函数。

我们先使用 frida,让 app 进入暂停模式:

IDA PRO 06 - 动态调试基础04_第12张图片

我是用的是 15.2.2 版本,所以默认就是进入暂停模式,frida 还提示我们,使用 %resume 可以让程序继续,这个时候我们看app窗口就是一个白屏了。

我们使用 ida 附加到进程,在 modules 窗口找到 linker:

IDA PRO 06 - 动态调试基础04_第13张图片

点进去,找到它的 call_array 函数:

IDA PRO 06 - 动态调试基础04_第14张图片

这里可以对比android的源码来看,其中v11的调用是会触发 so 加载时的 init_array 方法的调用。

我们在这里加上断点:

在so加载时执行的 init 函数里面也加上断点:

IDA PRO 06 - 动态调试基础04_第15张图片

断点加好了之后,在 frida 里面输入%resume ,让程序继续运行,就能断点到 init 函数里面了:

IDA PRO 06 - 动态调试基础04_第16张图片

可以看到,确实进入了该函数。

你可能感兴趣的:(IDA,PRO,前端,java)