之前发了使用gdb演示x86平台Linux修改GOT中函数地址实现Hook的文章:《利用gdb演示GOT Hook》既然Android底层本质上也是Linux,只不过换成了ARM架构的CPU,使用类似的方法还是可以达到目的的。本文的演示在Ubuntu 10.04LTS和Android 4.1.1模拟器上进行的。

1. 源码与编译

这里使用稍微“复杂”一些的代码,编译成两个模块(hooktest和libmym.so),libmym.so由mym.c编译得到

?
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
  * filename     : mym.c
  * description  : my simple math library
  */
#include <stdio.h>
  
int add( int a, int b) {
     return a + b;
}
  
int multiply( int a, int b) {
     return a * b;
}

libmym.c导出两个函数,分别是add和multiply,因此头文件mym.h如下

?
1
2
3
4
5
6
/**
  * filename        : mym.h
  * description     : functions exported by libmym.so
  */
int add( int , int );
int multiply( int , int );

Android.mk文件如下

LOCAL_PATH:=$(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES:=\
	mym.c\
	mym.h

LOCAL_PRELINK_MODULE:=false

LOCAL_MODULE_TAGS:=debug
LOCAL_MODULE:=libmym
LOCAL_MODULE_PATH :=  $(TARGET_OUT_SHARED_LIBRIES)

include $(BUILD_SHARED_LIBRARY)

然后在源码环境下使用mmm命令编译模块。

hooktest由hooktest.c编译得到,为了找到头文件mym.h,最好把mym.h放在相同的目录中:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
  * filename     : hooktest.c
  * description  : play around with gdb, again
  */
#include <stdio.h>
#include "mym.h"
  
int main( int argc, char *argv[]) {
     printf ( "5 + 5 = %d\n" , add(5, 5));
     printf ( "5 + 5 = %d\n" , add(5, 5));
  
     return 0;
}

hootest的makefile如下

LOCAL_PATH:=$(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES := \
	hooktest.c \
	mym.h
LOCAL_MODULE_TAGS := debug
LOCAL_MODULE := hooktest

LOCAL_SHARED_LIBRARIES := libc libmym
LOCAL_MODULE_PATH := ./custom_out

include $(BUILD_EXECUTABLE)

同样使用mmm编译这个模块。

2. gdb远程调试

将编译后得到的libmym.so和hootest使用adb push分别导入到Android系统的/system/lib目录和/data/local目录,由于这个目录是不可写的,所以先要重新挂载一下。

?
1
2
3
$ adb remount
$ adb push libmym.so /system/lib
$ adb hooktest /data/local

远程调试时,gdb分为server端和client端。简单地说,server端在Android系统中运行,client端在桌面系统(我使用Ubuntu Linux)中运行,开启gdb的shell,我们在client端输入调试指令,会发送给server执行,server将执行的结果返回给client端。

?
1
2
3
4
5
$ adb shell    # 连接Android的shell
# chmod 755 /data/local/hooktest  # 更改权限
# gdbserver :11111 /data/local/hooktest   # 调试hooktest 
Process hooktest created; pid = 670
Listening on port 11111

Android系统和Ubuntu是两个不同的操作系统,有各自的端口。gdbserver监听Android系统的11111端口,等待client发送过来的命令。而client使用Ubuntu的某个端口,因此需要与Android系统gdbserver使用的端口建立起一个“映射”,这也要使用adb的命令来完成:

?
1
$ adb forward tcp:11111 tcp:11111  # 端口转发,端口号可以不相同

adb forward完成端口数据的“转发”,因此client发送数据到Ubuntu的11111端口的数据就会被转发到Android系统的11111端口。

gdbclient可以在Android系统源码中的路径为

./prebuilts/gcc/linux-x86/arm/arm-eabi-4.6/bin/arm-eabi-gdb

./prebuilts/gcc/linux-x86/arm/arm-eabi-4.6/bin目录中还有其他很有用的工具,比如arm-eabi-objdump和arm-eabi-readelf,因此可以把这个目录加入到环境变量PATH中方便使用里面的工具。

使用arm-eabi-gdb时也需要加载hooktest中,注意这里需要使用包含调试信息的hooktest,在源码中的路径为

./out/target/product/generic/obj/EXECUTABLES/hooktest_intermediates/LINKED/hooktest

因此执行以下命令

$ PATH=$PATH:. /prebuilts/gcc/linux-x86/arm/arm-eabi-4 .6 /bin
$ arm-eabi-gdb . /out/target/product/generic/obj/EXECUTABLES/hooktest_intermediates/LINKED/hooktest
GNU gdb (GDB) 7.1-android-gg2
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http: //gnu .org /licenses/gpl .html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=i686-linux-gnu --target=arm-elf-linux" .
For bug reporting instructions, please see:
<http: //www .gnu.org /software/gdb/bugs/ >...
Reading symbols from /home/wind/droid-4 .1.1_r6 /out/target/product/generic/obj/EXECUTABLES/hooktest_intermediates/LINKED/hooktest ... done .
 

从gdb的输出中可以看到,读取hooktest的符号表成功了。但是调试中还需要Android库的符号信息(包括libmym.so),因此,也要设置符号信息所在的目录,使gdb能够查找到。

(gdb) set solib-absolute-prefix out /target/product/generic/symbols/
(gdb) set solib-search-path out /target/product/generic/symbols/system/lib/
 

然后连接gdbserver开始调试。之前在《Android ELF文件PLT和GOT》说明了如何寻找某个函数对应的GOT表项,这里不再详细说明。在我的环境下,add函数GOT表项的地址为0x2a001ff4

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
(gdb) target remote :11111
Remote debugging using :11111
(gdb) b add   # 在add函数下断点 
(gdb) c
Continuing.
Breakpoint 6, 0x40072384 in add ()
(gdb) p /x *0x2a001ff4
$4 = 0x40072385   # GOT表中的内容为add的地址加1
(gdb) p multiply
$5 = {<text variable, no debug info>} 0x4007238c <multiply>
(gdb) set *0x2a001ff4=0x4007238d  # 修改为multiply的地址加1
(gdb) c
Continuing.
Program exited normally.

然后在gdbserver端的输出如下:

5 + 5 = 10
5 + 5 = 25
Child exited with status 0
GDBserver exiting

可以看到,add函数已经被multiply函数所替代,输出了5 * 5的结果。

至于为何GOT表中存储的是函数的真实地址加1(一开始没注意要加1,修改GOT表项后总是出现段错误),我目前还没有搞清楚原因,还需进一步研究。