GDB动态调试APP

admin 2022年6月18日09:59:51评论94 views字数 8208阅读27分21秒阅读模式

0x01 前言

由于 Android 原生程序的软件保护技术日趋成熟,很多软件和病毒都开始用加密和混淆技术强化自己,对此,静态分析已很难奏效,要用到动态调试

用 C、C++ 开发的原生程序,其语言的先天特性决定其二进制代码的分析难度比 Java 开发的 DEX 高得多,加上高强度的代码加密和混淆技术,逆向分析更加困难。因此,使用调试器配合脚本自动化技术,实现原生程序的自动化分析、自动化去除混淆与动态脱壳,已成为逆向分析中必须掌握的技能


1.1 GDB远程调试原理图

GDB动态调试APP

如图上所示,我们需要使用gdbserver依附到要调试的进程上,gdb通过adbd和手机上的gdbserver 进行socket通信


1.2 什么是 GDB 调试器?

Android NDK 早期只支持用 gcc 开发原生程序(现在只支持 Clang),那时原生程序的调试器主要是 gdb(The GNU Project Debugger,GNU 工程调试器),即使是现在,用 gdb 调试原生程序也是一种选择

GDB是gnu组织开发的一个强大的unix程序调试工具,可以用它来调试Android上的C、C++代码

GDB主要做四件事情

  • 随心所欲地启动你的程序

  • 设置断点,程序执行到断点处会停住(断点可以是表达式)

  • 程序被停住后,可以查看此时程序中发生的事

  • 动态改变程序的执行环境


1.3 Android SDK GDB 安装路径

使用gdb进行嵌入式调试的必需品,是gdb和gdbserver二进制文件,对于Android平台而言,Google已经提供了预编译的版本,所以无需自行编译它(非要自己手动去编译的,麻烦的一批,反正我放弃了),你可以在Android SDK的目录下找到它们(高版本已经被舍弃了,版本不要超过Android NDK r23 LTS)

网上的一些教程是说通过Android Studio SDK Tools来安装,但默认会安装最新版本的,我们这里手动安装即可

GDB动态调试APP
GDB动态调试APP

  • windows 安装NDK-gdb(版本不要超过Android NDK r23 LTS(2021 年 8 月),在这个版本以上的默认去除了对GDB的支持)

https://developer.android.com/ndk/downloads/revision_history?hl=zh-cn

GDB动态调试APP
GDB动态调试APP

  • Android SDK 工具包的gdb和不同系统版本下的gdbserver路径


android_ndk_r23b/prebuilt/windows-x86_64/bin/gdb.exe

android_ndk_r23b/prebuilt/android-x86_64/gdbserver/gdbserver
android_ndk_r23b/prebuilt/android-x86/gdbserver/gdbserve
android_ndk_r23b/prebuilt/android-arm64/gdbserver/gdbserve
android_ndk_r23b/prebuilt/android-arm/gdbserver/gdbserve

:同时对Android手机需要做的准备工作,则是打开开发者选项以启用ADB调试,并且需要具有root权限。同时在某些机型上,SELinux可能会阻止gdbserver附加到目标进程,这种情况下建议暂时将SELinux改为宽容模式:


adb shell setenforce 0


1.4 NDK-GDB 脚本

在 Android NDK 目录下有个 ndk-gdb 脚本,它是由 Android NDK 提供且经过配置的 gdb 调试器的启动器,内容如下:

GDB动态调试APP

  • ndk-gdb 对应 ndk-gdb.py,ndk-gdb.py 主要执行了如下操作:

    • 解析 Android NDK 工程的工程信息。读取项目的 AndroidManifest.xml,解析原生程序的包名和由 APK 启动的 Activity

    • 解析 Android NDK 工程的 ABI 信息。读取原生程序支持的 ABI,将其与当前连接到计算机中的设备的 ABI 进行比对,找出适合当前设备的 ABI 版本的原生程序

    • 设置调试器的 stl_pretty_printer。当 APP_STL 为 stlport 或 gnustl 时,为 gdb 设置不同的 stl_pretty_printer

    • 获取已安装的原生程序在设备上的数据目录和 gdbserver 的位置

    • 以调试模式启动原生程序所在 APK 的主 Activity

    • 从设备上拉取(pull)一些调试中要用的系统动态库和原生程序

    • 启动 gdbserver

    • 启动 gdb,连接待调试的进程,开始调试

  • 从以上步骤可看出,用 ndk-gdb 动态调试原生程序时有如下限制:

    • 独立的原生程序无法调试。原生程序必须和 APK “绑定”,且 APK 必须包含主 Activity

    • 包含原生程序的 APK 必须是可调试的,且要先安装到设备上

    • 无法便携设置 gdb 调试器前端


0x02 GDB 调试器命令

  • gdb 尚在不断更新(变化),要想掌握其使用方法,最好的办法是去 gdb 官网查看其手册(gdb 用户手册)

  • gdb 调试器成功连接 gdbserver 后,会进入以 (gdb) 显示的 Shell 环境,在这里,既可执行 gdb 提供的所有命令,以驱动 gdb 调试 Android 原生程序,也可执行 help 命令,查看所有可用的命令。在调试场景中按 Tab 键,gdb 会显示当前所有可用命令


2.1 断点(BreakPoint)

在代码的指定位置中断,这个是我们用得最多的一种。设置断点的命令是break,它通常有如下方式:

  • break <function>在进入指定函数时停住

  • break <linenum>在指定行号停住。

  • break +/-offset在当前行号的前面或后面的offset行停住。offiset为自然数。

  • break filename:linenum在源文件filename的linenum行处停住。

  • break ... if <condition> ...可以是上述的参数,condition表示条件,在条件成立时停住。比如在循环境体中,可以设置break if i=100,表示当i为100时停住程序

可以通过info breakpoints [n]命令查看当前断点信息。此外,还有如下几个配套的常用命令:

  • delete删除所有断点

  • delete breakpoint [n]删除某个断点

  • disable breakpoint [n]禁用某个断点

  • enable breakpoint [n]使能某个断点


2.2 观察点(WatchPoint)

在变量读、写或变化时中断,这类方式常用来定位bug

  • watch <expr>变量发生变化时中断

  • rwatch <expr>变量被读时中断

  • awatch <expr>变量值被读或被写时中断

可以通过info watchpoints [n]命令查看当前观察点信息


2.3 捕捉点(CatchPoint)

捕捉点用来补捉程序运行时的一些事件。如:载入共享库(动态链接库)、C++的异常等。通常也是用来定位bug

捕捉点的命令格式是:catch <event>,event可以是下面的内容

  • throwC++抛出的异常时中断

  • catchC++捕捉到的异常时中断

  • exec调用系统调用exec时(只在某些操作系统下有用)

  • fork调用系统调用fork时(只在某些操作系统下有用)

  • vfork调用系统调用vfork时(只在某些操作系统下有用)

  • load 或 load <libname>载入共享库时(只在某些操作系统下有用)

  • unload 或 unload <libname>卸载共享库时(只在某些操作系统下有用)

另外,还有一个tcatch ,功能类似,不过它只设置一次捕捉点,当程序停住以后,应点被自动删除。捕捉点信息的查看方式和代码断点的命令是一样的


2.4 在特定线程中中断

你可以定义你的断点是否在所有的线程上,或是在某个特定的线程。GDB很容易帮你完成这一工作

  • break <linespec> thread <threadno>

  • break <linespec> thread <threadno> if ...

linespec指定了断点设置在的源程序的行号。threadno指定了线程的ID,注意,这个ID是GDB分配的,你可以通过"info threads"命令来查看正在运行程序中的线程信息。如果你不指定thread <threadno>则表示你的断点设在所有线程上面。还可以为某线程指定断点条件,如:


(gdb) break frik.c:13 thread 28 if bartab > lim

当你的程序被GDB停住时,所有的运行线程都会被停住。这方便你你查看运行程序的总体情况。而在你恢复程序运行时,所有的线程也会被恢复运行。那怕是主进程在被单步调试时。


2.5 恢复程序运行和单步调试

在gdb中,和调试步进相关的命令主要有如下几条:

  • continue:继续运行程序直到下一个断点(类似于VS里的F5)

  • next:逐过程步进,不会进入子函数(类似VS里的F10)

  • setp:逐语句步进,会进入子函数(类似VS里的F11)

  • until:运行至当前语句块结束

  • finish:运行至函数结束并跳出,并打印函数的返回值(类似VS的Shift+F11)


2.6 常用的GDB命令

  • 常用的 gdb 命令(按功能区分):

    • append:如append memory用于将内存中的数据添加到指定文件的最后

    • call:调用一个函数,功能类似 IDA 的 AppCall

    • disassemble:反汇编当前栈帧的函数

    • display:每次程序停止运行时打印表达式的值

    • dump:读取内存中的数据和代码,并将其保存到文件中。如dump binary memory用于将指定内存中的数据保存到文件中

    • explore:打印表达式的值(gdb 要能识别它的类型)

    • find:在内存中查找数据

    • print:打印表达式的值

    • printf:不仅和 print 一样可用于打印表达式的值,还可用于指定格式化字符串

    • set:修改表达式的值

    • info:如info frame用于打印当前栈帧的信息

    • backtrace:打印所有栈帧的信息

    • down:选择并打印下一个栈帧的信息

    • up:选择并打印上一个栈帧的信息

    • frame:选择并打印指定栈帧的信息

    • return:将所选栈帧返回调用者

    • info:如info registers、info all-registers可用于查看所有寄存器的信息;info registers x0可用于查看 x0 寄存器的值

    • print:如print/x $pc表示以十六进制形式显示 pc 寄存器的值

    • set:如set $sp += 4用于将栈指针的值加4;set $x0 = 0用于将x0寄存器的值设为 0

    • stepi:单步步入。当遇到函数调用时,可进入此函数体

    • nexti:单步步过。当遇到函数调用时,不进入此函数体

    • until:快速退出循环

    • finish:执行程序,直到当前函数执行结束后返回

    • continue:继续运行被断点中断的程序

    • run:运行程序

    • kill:强行终止当前正在调试的程序

    • quit:退出 gdb

    • break:如break printf表示在printf()上设置断点

    • rbreak:用正则表达式的方式设置断点

    • tbreak:设置临时断点

    • delete:如delete n表示删除第 n 个断点

    • disable:如disable n表示禁用第 n 个断点

    • enable:如enable n表示启用第 n 个断点

    • save:如save breakpoints表示将当前断点信息保存为脚本文件

    • info:如info break用于查看所有断点信息

    • watch:设置监视点(需要硬件支持)。在没有源码的情况下,可对内存地址进行监视,功能类似 OllyDbg(Windows 平台调试器)的硬件读写断点

    • catch:如catch syscall用于捕获所有系统调用

    • 与断点相关的命令

    • 与调试相关的命令

    • 与寄存器相关的命令

    • 与堆栈相关的命令

    • 与数据相关的命令


0x03 GDB调试实例

1)在手机或模拟机启动gdbserver并attach想调试的进程,并指定监听调试命令的端口


# 注意这里的路径,第一个需在当前目录下,否则需带绝对路径,第二个可自己选择
adb push gdbserver /system/bin

:push到/system/bin可能会报错,需确保手机或模拟机已经root,输入命令: adb remount,然后再push进去,同样,下面的chmod权限赋予如果报错,可以使用su命令

  • push到/system/bin可能会报错,赋予gdbserver权限


adb shell
su
chmod 777 /system/bin

或使用mount命令查看,然后更改权限(将/system 从挂载“ro”权限变为挂载“rw”权限)


mount
mount -o rw,remount /dev/block/sda6 /system

GDB动态调试APP

  • 有可能上传的时候又会出现如下错误,使用如下命令:


adb remount
adb push gdbserver /system/bin

GDB动态调试APP

2)使用adb做端口映射,将pc机上的端口定向到手机上gdbserver监听的端口


adb forward tcp:54321 tcp:54321   #端口映射,将pc机的1234端口映射到手机的1234端口


$ adb shell
# ps #查看要调试进程的PID

GDB动态调试APP

3)gdbserver附加进程方式

在手机上启动gdbserver并附加到目标进程


./gdbserver :54321 --attach $(pidof process_name)
  • 在PC上启动gdb


$ gdb
(gdb) target remote 127.0.0.1:54321

4)自行启动进程方式

  • 有时因为某些原因,需要自行启动一个进程进行调试,而不是直接附加到现有进程,参数就直接附加在后面,就像正常启动进程一样


./gdbserver :54321 elf_name[文件名]  params[参数]

5)在PC上启动gdb的方法是相同的


$ gdb
(gdb) target remote 127.0.0.1:54321

6)启动gdb向指定的pc机端口发信息开始调试,运行gdbserver 两种方法

方法1:adb shell; 命令: cd gdbserver路径; 命令: ./gdbserver


./gdbserver :54321 --attach 1419   #:54321是端口号,1419 是进程ID

方法2: 命令 adb shell gdbserver (适用于将gdbserver放在/system/bin下的情况)


adb shell gdbserver :23946 –attach [PID](PID可以在adb shell中使用 ps命令查询)

我们这里选择方法1:


adb shell
cd /system/bin
chmod 777 gdbserver
./gdbserver gdbserver :54321 –attach 1419

:要使用项目下的gdb客户端去连接gdbserver,gdb的类型要选择针对手机或模拟器平台的,版本要和gdbserver一致

  • gdb客户端执行远程链接,成功监听如下所示:


$ gdb
(gdb) target remote 127.0.0.1:54321

GDB动态调试APP

  • 列出当前进程的所有寄存器


info registers

GDB动态调试APP


3.1 设置断点


break 命令设置断点,简写b 
break main 在main()函数的入口处设置断点
break 5 在源代码的第5行设置断点
break hello.c:5 指定源码文件的代码第5行设置断点

单个断点:


b+设置断点所在行:设置断点
b+函数名:函数名处设定断点
b+文件名:行号/函数名:在别的函数设置断点

无效断点:对于函数中的,大括号和注释,设置的断点是无效的。看断点是否有效,看Enb这个项(如下图),y就是有效。其中Num为断点的标号

GDB动态调试APP

  • 在main()函数的入口处设置断点


b main

GDB动态调试APP

条件断点:b+行号+if变量==var:当某行的变量等于某个值的时候停止


b 19 if i==5


3.2 查看断点


info breakpoints,显示当前全部的断点,简写为i b

i b


<p data-line="398" class="sync-line" style="margin:0;"></p>

GDB动态调试APP

3.3 清除断点

单个删除:每个断点号用空格隔开;delete +断点的数值标识符,delete 1,删除第1个断点,简写为d 1

d 1

GDB动态调试APP

连续删除:断点号范围表示,例如4-7;d+断点号范围

d 4-7

clear + 函数名 、 +行号、+文件名:行号,清除断点main()函数处的断点:clear main或者clear 5(本质是main函数的第一条语句所在)

GDB动态调试APP


3.4 启用与禁用断点

  • disable +断点的数值标识符,disable 1:禁用第1个断点,简写为dis+断点号

  • enable +断点的数值标识符,enable 1:启用第1个断点,简写为enb+断点号

  • Enb字段,表明断点是禁用(n)还是启用(y)的

GDB动态调试APP


3.5 单步调试

  • next,n,越过 函数调用(函数会在背地里自己悄悄运行完),单步执行

  • step,s,进入 函数体内部,单步执行


3.6 恢复执行

  • continue,c,恢复执行,直到遇到下一个断点

  • continue命令执行期间,按下CTRL-C瞬间停止


3.7 查看变量

  • disp,使得每次有暂停,都会输出指定的变量的值;

  • print,p,只显示一次变量的值;

  • 要求变量名在当前的域是可见的,比如某个变量i是函数foo()的局部变量,那么只有是在进入到这个函数的里面时才可以使用print i 或者 disp i,不然gdb也不知道i是谁;


3.8 TUI模式,双开 汇编代码 窗口

  • Ctrl + X + A进入TUI模式

  • (gdb) list:显示10行C源码

  • (gdb) layout split:同时显示C源码以及汇编源码

  • (gdb) info registers:显示使用到的寄存器信息

  • (gdb) set disassembly-flavor intel:改变显示的汇编语法

  • 再次输入(gdb) layout split:使语法改变生效


set disassembly-flavor intel
set disassembly-flavor att


3.9 查看文件信息

查看当前文件:

  • l:查看当前文件的调试信息,默认10行

  • l+行号:显示特定行号的上下文

  • l +函数名:查看当前文件特定函数

查看当前文件:

  • l +文件名:行号:查看文件信息

  • l +文件名:函数名:查看特定函数的信息

设置显示行数:

  • show listsize:显示输出行

  • set listsize (number):设置输出行号

参考链接

http://beej.us/guide/bggdb/

https://blog.csdn.net/zlmm741/article/details/105511833

https://blog.csdn.net/weixin_46185705/article/details/114498377

https://blog.csdn.net/weixin_46185705/article/details/114498377

https://www.cnblogs.com/TianFang/archive/2013/01/20/2868889.html

https://www.jianshu.com/p/8a5aade09ec0?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation


推荐阅读

APP隐私合规

Android防逆向基础

Android渗透测试工具

Android常见投屏神器

Objection动态分析App

Android JNI动态库逆向

学抓包就来"哆啦安全"学

Android系统中使用eBPF

Android安全测试工具大全

Android安全IO监控之性能监控

ART在Android安全攻防中的应用

Android Root检测和绕过(浅析)

零基础一对一技术咨询服务(远程指导)

技术咨询服务|Xiaomi Mi A1 (tissot)

Linux内核监控在Android攻防中的应用

ASM插桩实现Android端无埋点性能监控

Android和iOS逆向分析/安全检测/渗透测试框架

使用Hook和插桩技术实现快速排查APP隐私合规问题

eCapture是基于eBPF技术实现用户态数据捕获无需CA证书抓https网络明文通讯

Gradle Plugin+Transform+ASM Hook并替换隐私方法调用(彻底解决隐私不合规问题)


GDB动态调试APP

原文始发于微信公众号(哆啦安全):GDB动态调试APP

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年6月18日09:59:51
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   GDB动态调试APPhttp://cn-sec.com/archives/1126435.html

发表评论

匿名网友 填写信息