本文博客地址:
后面深入学习了古河的Libinject注入Android进程,下边来深入学习一下作者ariesjzj的博文《Android中的so注入(inject)和挂钩(hook)-Forbothx86andarm》,注入的思路和古河的是一样的,而且代码的兼容性更好更好理解,适用于arm和x86模式下so注入和函数的Hook,这份代码自己也测试了一下,确实可以Hook目标函数成功,只是被Hook更改的目标函数,可能没有被系统调用,致使测试疗效和作者ariesjzj给出稍有区别。
一、Androidso注入和函数hook代码的说明1.为inject工程代码添加必要的include文件
根据作者ariesjzj提供的代码文件,在eclipse中建立一个ndk的Inject代码工程(用于实现so的注入)和一个ndk的Inject_so代码工程(被注入的so以及实现函数Hook),并将作者ariesjzj提供的代码分别导出到这两个ndk的代码工程中,并根据我在上面博客中提及的方式为ndk工程添加必要的include头文件(PathsandSymbols),添加方式如下:
windows环境下,NDK编译须要添加的include头文件(依据编译的版本须要进行更改):右击项目-->Properties-->两侧C/C++General-->PathsandSymbols-->两侧Includes-->GNUC++(.cpp)-->Add${NDKROOT}platformsandroid-19arch-armusrinclude${NDKROOT}sourcescxx-stlgnu-libstdc++4.8include${NDKROOT}sourcescxx-stlgnu-libstdc++4.8libsarmeabiinclude${NDKROOT}toolchainsarm-Linux-androideabi-4.8prebuiltwindowslibgccarm-linux-androideabi4.8include
2.在编译Inject的代码工程时,编译可能会报找不到头文件的错误
须要更改头文件#include为#include
3.作者ariesjzj提供的Androidso注入Hook函数是基于ELF文件GOT表的Hook
说明的明白一点,就是被Hook的目标函数在该elf文件格式的so库文件中须要被导入,能被动态查找到(got表里储存的是外部符号的地址,不是所有符号的地址都能在got表找到)。其实了,通常在linux下,函数默认就是被导入的,顺便提一下,在so库文件中,函数没有被导入也是可以Hook的,由于函数调用地址在so库文件中的储存位置偏斜是可以手工估算下来,只不过麻烦一点点,通用性差点。有关so文件的非导入函数的调用地址的估算,可以参考《linux下调用共享库非导入函数》里的方式。
生成共享库的代码share.c:
#include
// 默认的导出函数
void PrintABCD() //__attribute__((visibility("hidden")))
{
printf("ABCDn");
sleep(-1);
}
// +++++++设置为非导出函数++++++++
__attribute__((visibility("hidden"))) void Printabcd()
{
printf("abcdn");
sleep(-1);
}
// 根据参数来确定print ABCD or abcd(默认的导出函数)
void PrintIt(int isPrintCapital)
{
if(isPrintCapital)
{
PrintABCD();
}
else
{
Printabcd();
}
printf("over!n");
}
生成共享库share.so:
gl-linux@ubuntu:~/Desktop/AYR$ gcc -fPIC -shared -o share.so share.c
测试代码test.c:
#include
#include
#include
#define DLL_FILE_NAME "/home/gl-linux/Desktop/AYR/share.so"
int main()
{
long long addr = 0;
void (*func)(int);
void *handle = dlopen(DLL_FILE_NAME, RTLD_NOW);
if (handle == NULL)
{
fprintf(stderr, "Failed to open libaray %s error:%sn", DLL_FILE_NAME, dlerror());
return -1;
}
addr = (long long)dlsym(handle, "PrintABCD");
printf("%lld ",addr);
addr = (long long)dlsym(handle, "Printabcd");
printf("%lld ",addr);
addr = (long long)dlsym(handle, "PrintIt");
printf("%lld ",addr);
func = addr;
func(1);
dlclose(handle);
sleep(-1);
return 0;
}
生成可执行文件test:
gl-linux@ubuntu:~/Desktop/AYR$ gcc test.c -o test -ldl
运行可执行文件test:
139725025953621 0 139725025953687 ABCD
从里面的输出结果可以看出,非导入函数Printabcd的地址没有被dlsym函数获取到,通常非导入函数也被叫做内部函数,应当和函数的生命周期也就是有效范围类似,其实了这个访问的限制是可以被突破的。
查看share.so中的导入函数偏斜:
0000000000201048 B __bss_start
w __cxa_finalize
0000000000201048 D _edata
0000000000201050 B _end
00000000000007cc T _fini
w __gmon_start__
00000000000005e8 T _init
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
w _Jv_RegisterClasses
0000000000000755 T PrintABCD
0000000000000797 T PrintIt
U puts
U sleep
已知导入函数的偏斜地址,又晓得模块加载基址,如何能晓得非导入函数的地址呢?
使用估算后的地址,调用非导入函数Printabcd(),更改测试程序代码TEST.c如下:
#include
#include
#include
#define DLL_FILE_NAME "/home/gl-linux/Desktop/AYR/share.so"
int main()
{
long long addr = 0;
void (*func)();
void *handle = dlopen(DLL_FILE_NAME, RTLD_NOW);
if (handle == NULL)
{
fprintf(stderr, "Failed to open libaray %s error:%sn", DLL_FILE_NAME, dlerror());
return -1;
}
addr = (long long)dlsym(handle, "PrintABCD");
printf("%lld ",addr);
addr = (long long)dlsym(handle, "Printabcd");
printf("%lld ",addr);
addr = (long long)dlsym(handle, "PrintIt");
printf("%lld ",addr);
// 调用共享库share.so中的非导出函数。
func = addr-33;
func();
dlclose(handle);
sleep(-1);
return 0;
}
运行结果:
gl-linux@ubuntu:~/Desktop/AYR$ ./TEST
139670664996693 0 139670664996759 abcd
按照前面的输出结果linux教程下载,很其实调用share.so文件中的非导入函数Printabcd成功。
有网友提示,下边这个Linux版本的nm可以查看so库文件中非导入函数的位置偏斜:
Linuxubuntu3.17.1#1SMPFriOct2409:08:26PDT2014x86_64GNU/Linux版本nm-Dxx.solinux include 找不到头文件,可以见到非导入函数的偏斜,只不过是t不是T
0068cTPrintABCD
006ceTPrintIt006adtPrintabcd
虽然so库文件中的非导入函数的调用地址很简单linux include 找不到头文件,将so文件推入到IDA中剖析,找到静态下非导入函数和导入函数的函数调用地址的位置偏斜offsetOfFunciton或则非导入函数调用地址offsetOfBaseAddrlinux 安装,之后so库文件中非导入函数的地址,即为动态显存的导入函数地址+offsetOfFunciton或则offsetOfBaseAddr+base(so文件的显存加载基地址)。
谢谢联接:
4.作者ariesjzj提供的Androidso注入Hook目标函数的代码的兼容性的考虑的诱因
在arm模式和x86模式下,函数的堆栈工作原理是一样的,而且在具体到工作的实现细节上还是有好多的区别,arm模式的函数是基于寄存器传参,x86模式的函数是基于堆栈传参。
arm模式下,当函数的参数高于4个时,通过R0~R3寄存器传递参数;当函数的参数超过4个时,前4个参数通过R0~R3寄存器传递,超过4个的参数通过函数的堆栈进行传递,函数的程序计数寄存器为PC(储存即将执行的指令)