也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大
少走了弯路,也就错过了风景,无论如何,感谢经历
本篇文章遇到排版混乱的地方,可点击文末阅读原文或前往该地址:https://orangey.blog.csdn.net/article/details/126219940
更多关于Android安全的知识,可前往:https://blog.csdn.net/ananasorangey/category11955914.html
由于 Android 原生程序的软件保护技术日趋成熟,很多软件和病毒都开始用加密和混淆技术强化自己,对此,静态分析已很难奏效,要用到动态调试
用 C、C++ 开发的原生程序,其语言的先天特性决定其二进制代码的分析难度比 Java 开发的 DEX 高得多,加上高强度的代码加密和混淆技术,逆向分析更加困难。因此,使用调试器配合脚本自动化技术,实现原生程序的自动化分析、自动化去除混淆与动态脱壳,已成为逆向分析中必须掌握的技能
如图上所示,我们需要使用gdbserver依附到要调试的进程上,gdb通过adbd和手机上的gdbserver 进行socket通信
Android NDK 早期只支持用 gcc 开发原生程序(现在只支持 Clang),那时原生程序的调试器主要是 gdb(The GNU Project Debugger,GNU 工程调试器),即使是现在,用 gdb 调试原生程序也是一种选择
GDB是gnu组织开发的一个强大的unix程序调试工具,可以用它来调试Android上的C、C++代码
GDB主要做四件事情:
随心所欲地启动你的程序
设置断点,程序执行到断点处会停住(断点可以是表达式)
程序被停住后,可以查看此时程序中发生的事
动态改变程序的执行环境
使用gdb进行嵌入式调试的必需品,是gdb和gdbserver二进制文件,对于Android平台而言,Google已经提供了预编译的版本,所以无需自行编译它(非要自己手动去编译的,麻烦的一批,反正我放弃了),你可以在Android SDK的目录下找到它们(高版本已经被舍弃了,版本不要超过Android NDK r23 LTS)
网上的一些教程是说通过Android Studio SDK Tools来安装,但默认会安装最新版本的,我们这里手动安装即可
windows 安装NDK-gdb(版本不要超过Android NDK r23 LTS(2021 年 8 月),在这个版本以上的默认去除了对GDB的支持)
https://developer.android.com/ndk/downloads/revision_history?hl=zh-cn
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
在 Android NDK 目录下有个 ndk-gdb 脚本,它是由 Android NDK 提供且经过配置的 gdb 调试器的启动器,内容如下:
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 调试器前端
gdb 尚在不断更新(变化),要想掌握其使用方法,最好的办法是去 gdb 官网查看其手册(gdb 用户手册)
gdb 调试器成功连接 gdbserver 后,会进入以 (gdb) 显示的 Shell 环境,在这里,既可执行 gdb 提供的所有命令,以驱动 gdb 调试 Android 原生程序,也可执行 help 命令,查看所有可用的命令。在调试场景中按 Tab 键,gdb 会显示当前所有可用命令
在代码的指定位置中断,这个是我们用得最多的一种。设置断点的命令是break,它通常有如下方式:
break
在进入指定函数时停住
break
在指定行号停住。
break +/-offset
在当前行号的前面或后面的offset行停住。offiset为自然数。
break filename:linenum
在源文件filename的linenum行处停住。
break ... if
可以是上述的参数,condition表示条件,在条件成立时停住。比如在循环境体中,可以设置break if i=100,表示当i为100时停住程序
可以通过info breakpoints [n]
命令查看当前断点信息。此外,还有如下几个配套的常用命令:
delete
删除所有断点
delete breakpoint [n]
删除某个断点
disable breakpoint [n]
禁用某个断点
enable breakpoint [n]
使能某个断点
在变量读、写或变化时中断,这类方式常用来定位bug
watch
变量发生变化时中断
rwatch
变量被读时中断
awatch
变量值被读或被写时中断
可以通过info watchpoints [n]
命令查看当前观察点信息
捕捉点用来补捉程序运行时的一些事件。如:载入共享库(动态链接库)、C++的异常等。通常也是用来定位bug
捕捉点的命令格式是:catch
可以是下面的内容
throw
C++抛出的异常时中断
catch
C++捕捉到的异常时中断
exec
调用系统调用exec时(只在某些操作系统下有用)
fork
调用系统调用fork时(只在某些操作系统下有用)
vfork
调用系统调用vfork时(只在某些操作系统下有用)
load 或 load
载入共享库时(只在某些操作系统下有用)
unload 或 unload
卸载共享库时(只在某些操作系统下有用)
另外,还有一个tcatch ,功能类似,不过它只设置一次捕捉点,当程序停住以后,应点被自动删除。捕捉点信息的查看方式和代码断点的命令是一样的
你可以定义你的断点是否在所有的线程上,或是在某个特定的线程。GDB很容易帮你完成这一工作
break
break
linespec指定了断点设置在的源程序的行号。threadno指定了线程的ID,注意,这个ID是GDB分配的,你可以通过"info threads"命令来查看正在运行程序中的线程信息。如果你不指定thread
则表示你的断点设在所有线程上面。还可以为某线程指定断点条件,如:
(gdb) break frik.c:13 thread 28 if bartab > lim
当你的程序被GDB停住时,所有的运行线程都会被停住。这方便你你查看运行程序的总体情况。而在你恢复程序运行时,所有的线程也会被恢复运行。那怕是主进程在被单步调试时。
在gdb中,和调试步进相关的命令主要有如下几条:
continue
:继续运行程序直到下一个断点(类似于VS里的F5)
next
:逐过程步进,不会进入子函数(类似VS里的F10)
setp
:逐语句步进,会进入子函数(类似VS里的F11)
until
:运行至当前语句块结束
finish
:运行至函数结束并跳出,并打印函数的返回值(类似VS的Shift+F11)
常用的 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
用于捕获所有系统调用
与断点相关的命令
与调试相关的命令
与寄存器相关的命令
与堆栈相关的命令
与数据相关的命令
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
有可能上传的时候又会出现如下错误,使用如下命令:
adb remount
adb push gdbserver /system/bin
2)使用adb做端口映射,将pc机上的端口定向到手机上gdbserver监听的端口
adb forward tcp:54321 tcp:54321 #端口映射,将pc机的1234端口映射到手机的1234端口
$ adb shell
# ps #查看要调试进程的PID
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
列出当前进程的所有寄存器
info registers
break 命令设置断点,简写b
break main 在main()函数的入口处设置断点
break 5 在源代码的第5行设置断点
break hello.c:5 指定源码文件的代码第5行设置断点
单个断点:
b+设置断点所在行:设置断点
b+函数名:函数名处设定断点
b+文件名:行号/函数名:在别的函数设置断点
无效断点:对于函数中的,大括号和注释,设置的断点是无效的。看断点是否有效,看Enb这个项(如下图),y就是有效。其中Num为断点的标号
在main()函数的入口处设置断点
b main
条件断点:b+
行号+if
变量==var
:当某行的变量等于某个值的时候停止
b 19 if i==5
info breakpoints
,显示当前全部的断点,简写为i b
i b
单个删除:每个断点号用空格隔开;delete +
断点的数值标识符,delete 1
,删除第1个断点,简写为d 1
d 1
连续删除:断点号范围表示,例如4-7;d+
断点号范围
d 4-7
clear + 函数名 、 +行号、+文件名:行号
,清除断点main()函数处的断点:clear main
或者clear 5
(本质是main函数的第一条语句所在)
disable +
断点的数值标识符,disable 1:
禁用第1个断点,简写为dis+
断点号
enable +
断点的数值标识符,enable 1:
启用第1个断点,简写为enb+
断点号
Enb字段,表明断点是禁用(n)还是启用(y)的
next,n
,越过 函数调用(函数会在背地里自己悄悄运行完),单步执行
step,s
,进入 函数体内部,单步执行
continue
,c,恢复执行,直到遇到下一个断点
continue
命令执行期间,按下CTRL-C瞬间停止
disp,使得每次有暂停,都会输出指定的变量的值;
print,p,只显示一次变量的值;
要求变量名在当前的域是可见的,比如某个变量i是函数foo()的局部变量,那么只有是在进入到这个函数的里面时才可以使用print i 或者 disp i,不然gdb也不知道i是谁;
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
查看当前文件:
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
你以为你有很多路可以选择,其实你只有一条路可以走