Android平台的so注入
本文博客地址:
大牛古河在看雪论坛分享的Android平台的注入代码,相信很多搞Android安全的同学应该都看过。刚接触Android平台的逆向时,我也下载了LibInject代码并且仔细的阅读和分析过,见我前面的博文《Android的so库注入》。我已经基本把Android so注入的原理分析的很清楚了,想学习的同学可以参考一下。虽然作者古河分享了LibInject代码,但是在eclispe下NDK编译还是够呛并且会遇到不少的问题,代码需要修改才能编译和运行成功看到效果。刚接触的时候,信心满满的把作者的代码下载来看,结果编译各种报错也不会修改,本身就对Linux编程不是很熟悉,内心面临崩溃,后面看了几篇博文,终于把问题解决了,可以把代码LibInject编译和运行成功和,有空就记录一下。作者古河分享的代码下载地址:.php?t=141355。
Android平台的so注入的原理:
android平台要想注入so库成功必须先取得设备的Root权限。
古河大牛这份LibInject注入代码是基于shellcode实现,这里称将被so注入的进程为目标进程,大致的实现思路是:
1.让目标进程调用其mmap函数在其进程内存中申请一段内存空间
2.将要注入的so库的名称字符串和so库中要调用的函数名称字符串写入到目标进程的内存(上面申请的内存)中
3.将编写好的ShellCode汇编代码写入到到目标进程的内存(上面申请的内存)中
4.修改目标进程的PC寄存器的值,让其跳到注入的ShellCode代码中执行,实现so库的注入,然后调用注入的so库中的函数。
一、LibInject代码编译遇到的问题
1).作者古河提供的代码缺少编译配置文件Andorid.mk和Application.mk,下面给出配置文件。
Andorid.mk文件
LOCAL_PATH := $(call my-dir)# 清除变量
include $(CLEAR_VARS)# 编译后生成的模块的名称
LOCAL_MODULE := inject# 参与编译的源码文件
LOCAL_SRC_FILES := inject.c shellcode.s # 对log打印日志消息的支持
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog #LOCAL_FORCE_STATIC_EXECUTABLE := true # 编译生成elf可执行文件
include $(BUILD_EXECUTABLE)
# 编译成功,生成的模块运行支持的平台armeabi-v7a
APP_ABI := armeabi-v7a
2).NDK工程编译需要的头文件路径(Paths and Symbols)
windows环境下,NDK编译需要添加的include头文件(根据编译的版本需要进行修改):
右击项目 --> Properties --> 左侧C/C++ General --> Paths and Symbols --> 右侧Includes --> GNU C++(.cpp) --> Add
${NDKROOT}\platforms\android-19\arch-arm\usr\include
${NDKROOT}\sources\cxx-stl\gnu-libstdc++\4.8\include
${NDKROOT}\sources\cxx-stl\gnu-libstdc++\4.8\libs\armeabi\include
${NDKROOT}\toolchains\arm-Linux-androideabi-4.8\prebuilt\windows\lib\gcc\arm-linux-androideabi\4.8\include
我在编译该工程的时候,添加的Paths and Symbols(导出为mk.xml文件):
<?xml version="1.0" encoding="UTF-8"?>
<cdtprojectproperties>
<section name="org.eclipse.cdt.internal.ui.wizards.settingswizards.IncludePaths">
<language name="c,cpp">
<includepath>E:\android-ndk-r10d\platforms\android-21\arch-arm\usr\include</includepath>
<includepath>E:\android-ndk-r10d\toolchains\aarch64-linux-android-4.9\prebuilt\windows-x86_64\lib\gcc\aarch64-linux-android\4.9\include</includepath>
<includepath>E:\android-ndk-r10d\sources\cxx-stl\gnu-libstdc++\4.9\include</includepath>
<includepath>E:\android-ndk-r10d\sources\cxx-stl\gnu-libstdc++\4.9\libs\armeabi\include</includepath></language>
</section>
<section name="org.eclipse.cdt.internal.ui.wizards.settingswizards.Macros">
<language name="c,cpp"></language>
</section>
</cdtprojectproperties>
3).对Google官方提供的NDK文件进行path(添加常用的include文件)
在对目标pid进程的注入时,基于boyliang的注入代码的考虑,在ptrace目标pid进程时,加了对"zygote"进程ptrace操作的特殊处理,用以解除zygote进程的阻塞状态,方便被ptrace。在工程编译的时候,需要 #include <cutils/sockets.h>、#include <sys/socket.h>、#include <sys/un.h>等头文件,但是google官方的NDK没有提供这几个头文件,需要从Android源码文件中导入。大牛boyliang提供了一个比较好的解决方法 --下载他提供的 ndk-path 对自己编译的目标api版本的NDK头文件进行path,其实就是添加这些常用的需要的头文件。
ndk-path的下载地址:
4).android JNI提示 utils/Log.h 找不到 错误的解决方法
在JNI的c文件中如果用到了 #include <utils/Log.h> ,用NDK 编译的时候会提示error: utils/Log.h: No such file or directory
如果想要他的LOG功能的话
1-修改Android.mk文件配置,添加如下语句
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog
2-在.c文件中修改为如下语句
#include <android/log.h>
3-使用方法
#define LOG_TAG "debug"
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, fmt, ##args)
4-打印语句
LOGI("test log!!!!")
LOGI("the string is: %s \n",buff);
5-错误输出到日志
LOGI(strerror(errno))
原因要追溯到jni的两种编译环境之间的区别:ndk 和 源码环境编译。
关于测试时使用Log。调用JNI进行Native Code的开发有两种环境,完整源码环境以及NDK。两种环境对应的Log输出方式也并不相同,差异则主要体现在需要包含的头文件中。
如果是在完整源码编 译环境下,只要include 头文件(位于Android-src/system/core/include/cutils),就可以使用对应 的LOGI、LOGD等方法了,当然LOG_TAG,LOG_NDEBUG等宏值需要自定义。
如果是在NDK环境下编译,则需要include 头文件(位于ndk/android-ndk-r4/platforms/android-8/arch- arm/usr/include/android/),另外自己定义宏映射。
代码中对log打印日志功能的修改,单独增加了 log.h 文件用于支持log日志的消息打印:
/** log.h** Created on: 2016-12-26* Author: fly2016*/#ifndef LOG_H_
#define LOG_H_#include <android/log.h> // 使用log打印日志#define LOG_TAG "INJECT" // adb logcat -s INJECT// 设置当前模式为调试模式
#define DEBUG 1#ifdef DEBUG
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)
#else
#define LOGI(fmt, args...) while(0)
#define LOGE(fmt, args...) while(0)
#define LOGD(fmt, args...) while(0)
#endif#endif /* LOG_H_ */
参考链接:
/
5).android JNI提示 #include <asm/user.h> 找不到 错误的解决办法
修改头文件#include <asm/user.h>为 #include <sys/user.h>
6).对原LibInject代码的ptrace附加目标pid进程的操作的修改和优化
在对目标pid进程进行ptrace附加时,增加了对"zygote"进程的支持,效果怎么样,有待测试了。
// ------------添加的代码--------------------------------------
// 解除zygote进程的阻塞状态
static void* connect_to_zygote(void* arg){int s, len;struct sockaddr_un remote;LOGI("[+] wait 2s...");// 休眠一下sleep(2);/**** zygote启动后会进入一个死循环,用来接收AMS的请求连接.* 当没有应用启动时,zygote进程一直处于阻塞状态。* 所以我们后面代码中的第三次wait会无法返回,解决办法也很简单,就是主动发起一个zygote的连接。* 我们看到第二个waitpid后面调用了一个connect_to_zygote函数。*/// 创建socket套接字if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) != -1) {// 设置连接的套接字的协议类型remote.sun_family = AF_UNIX;// 设置连接的套接字的目标strcpy(remote.sun_path, "/dev/socket/zygote");// 设置传递的参数的字节长度len = strlen(remote.sun_path) + sizeof(remote.sun_family);LOGI("[+] start to connect zygote socket");// 向"/dev/socket/zygote"目标套接字发起连接connect(s, (struct sockaddr *) &remote, len);LOGI("[+] close socket");// 关闭socket套接字close(s);}/**** 这个函数的功能很简单,先发起socket连接,然后再关闭连接。* 看上去没有做什么有用的事情,但是它却非常重要,* 通过连接zygote,它使zygote进程解除了阻塞状态,* 我们才得以注入进zygote进程。* 参考网址:/*/return NULL ;
}// 获取目标pid进程的名称字符串
const char* get_process_name(pid_t pid) {static char buffer[255];FILE* f;char path[255];// 格式化得到字符串"/proc/pid/cmdline"snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);// 读取文件"/proc/pid/cmdline"的内容,获取进程的命令行参数if ((f = fopen(path, "r")) == NULL) {return NULL;}// 读取文件"/proc/pid/cmdline"的第1行字符串内容--进程的名称if (fgets(buffer, sizeof(buffer), f) == NULL) {return NULL;}// 关闭文件fclose(f);return buffer;
}
// ------------添加的代码--------------------------------------// 在对进程的ptrace操作时,单独处理下zygote进程
int ptrace_attach( pid_t pid )
{char* pZygote = NULL;if ( ptrace( PTRACE_ATTACH, pid, NULL, 0 ) < 0 ){perror( "ptrace_attach" );return -1;}waitpid( pid, NULL, WUNTRACED );/** Restarts the stopped child as for PTRACE_CONT, but arranges for* the child to be stopped at the next entry to or exit from a sys‐* tem call, or after execution of a single instruction, respec‐* tively.*/if ( ptrace( PTRACE_SYSCALL, pid, NULL, 0 ) < 0 ){perror( "ptrace_syscall" );return -1;}waitpid( pid, NULL, WUNTRACED );// ------------添加的代码--------------------------------------// 获取pid进程的名称const char* process_name = get_process_name(pid);// 判断pid进程是否是"zygote"进程pZygote = strstr(process_name, "zygote");// 针对zygote进程的特殊处理if (pZygote) {// 当进程为zygote时,需要考虑为zygote进程解除阻塞状态,使进程注入得以进行connect_to_zygote(NULL);}// 当目标进程在下次进/出系统调用时被附加调试if (ptrace(PTRACE_SYSCALL, pid, NULL, NULL ) < 0) {LOGE("ptrace_syscall");return -1;}// 等待进程附加操作返回waitpid(pid, NULL, WUNTRACED);// ------------添加的代码--------------------------------------return 0;
}
二、LibInject代码工程结构的介绍
1).LibInject工程用于生成so注入的工具inject
2).InjectSo工程用于生成将被注入到目标pid进程中的so库文件(测试)
三、LibInject代码编译和运行
1).执行so注入测试的脚本 run.bat
adb push libhello.so /data/local/tmp
adb push inject /data/local/tmp
adb shell chmod 0777 /data/local/tmp/libhello.so
adb shell chmod 0777 /data/local/tmp/inject
adb shell su -c /data/local/tmp/inject
pause
用到的其他命令:
adb shell
su
ps | grep com.android.musiccat /proc/5366/maps | grep libhello.so adb logcat -s INJECT
2).注入测试的环境
Android模拟器:api 19
被注入的目标进程:com.android.music(Android 19系统自带的app)
注入工具:inject
注入so文件:libhello.so
进行so注入测试,运行 run.bat 脚本:
so注入到目标进程com.android.music中成功, adb logcat -s INJECT 查看打印的log日志消息:
有关 adb logcat 命令的详细使用说明参考地址:.html
感谢地址:
.php?t=141355
.php?t=157419
.php?t=141355&page=4
/
.html
/
Android平台的so注入
本文博客地址:
大牛古河在看雪论坛分享的Android平台的注入代码,相信很多搞Android安全的同学应该都看过。刚接触Android平台的逆向时,我也下载了LibInject代码并且仔细的阅读和分析过,见我前面的博文《Android的so库注入》。我已经基本把Android so注入的原理分析的很清楚了,想学习的同学可以参考一下。虽然作者古河分享了LibInject代码,但是在eclispe下NDK编译还是够呛并且会遇到不少的问题,代码需要修改才能编译和运行成功看到效果。刚接触的时候,信心满满的把作者的代码下载来看,结果编译各种报错也不会修改,本身就对Linux编程不是很熟悉,内心面临崩溃,后面看了几篇博文,终于把问题解决了,可以把代码LibInject编译和运行成功和,有空就记录一下。作者古河分享的代码下载地址:.php?t=141355。
Android平台的so注入的原理:
android平台要想注入so库成功必须先取得设备的Root权限。
古河大牛这份LibInject注入代码是基于shellcode实现,这里称将被so注入的进程为目标进程,大致的实现思路是:
1.让目标进程调用其mmap函数在其进程内存中申请一段内存空间
2.将要注入的so库的名称字符串和so库中要调用的函数名称字符串写入到目标进程的内存(上面申请的内存)中
3.将编写好的ShellCode汇编代码写入到到目标进程的内存(上面申请的内存)中
4.修改目标进程的PC寄存器的值,让其跳到注入的ShellCode代码中执行,实现so库的注入,然后调用注入的so库中的函数。
一、LibInject代码编译遇到的问题
1).作者古河提供的代码缺少编译配置文件Andorid.mk和Application.mk,下面给出配置文件。
Andorid.mk文件
LOCAL_PATH := $(call my-dir)# 清除变量
include $(CLEAR_VARS)# 编译后生成的模块的名称
LOCAL_MODULE := inject# 参与编译的源码文件
LOCAL_SRC_FILES := inject.c shellcode.s # 对log打印日志消息的支持
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog #LOCAL_FORCE_STATIC_EXECUTABLE := true # 编译生成elf可执行文件
include $(BUILD_EXECUTABLE)
# 编译成功,生成的模块运行支持的平台armeabi-v7a
APP_ABI := armeabi-v7a
2).NDK工程编译需要的头文件路径(Paths and Symbols)
windows环境下,NDK编译需要添加的include头文件(根据编译的版本需要进行修改):
右击项目 --> Properties --> 左侧C/C++ General --> Paths and Symbols --> 右侧Includes --> GNU C++(.cpp) --> Add
${NDKROOT}\platforms\android-19\arch-arm\usr\include
${NDKROOT}\sources\cxx-stl\gnu-libstdc++\4.8\include
${NDKROOT}\sources\cxx-stl\gnu-libstdc++\4.8\libs\armeabi\include
${NDKROOT}\toolchains\arm-Linux-androideabi-4.8\prebuilt\windows\lib\gcc\arm-linux-androideabi\4.8\include
我在编译该工程的时候,添加的Paths and Symbols(导出为mk.xml文件):
<?xml version="1.0" encoding="UTF-8"?>
<cdtprojectproperties>
<section name="org.eclipse.cdt.internal.ui.wizards.settingswizards.IncludePaths">
<language name="c,cpp">
<includepath>E:\android-ndk-r10d\platforms\android-21\arch-arm\usr\include</includepath>
<includepath>E:\android-ndk-r10d\toolchains\aarch64-linux-android-4.9\prebuilt\windows-x86_64\lib\gcc\aarch64-linux-android\4.9\include</includepath>
<includepath>E:\android-ndk-r10d\sources\cxx-stl\gnu-libstdc++\4.9\include</includepath>
<includepath>E:\android-ndk-r10d\sources\cxx-stl\gnu-libstdc++\4.9\libs\armeabi\include</includepath></language>
</section>
<section name="org.eclipse.cdt.internal.ui.wizards.settingswizards.Macros">
<language name="c,cpp"></language>
</section>
</cdtprojectproperties>
3).对Google官方提供的NDK文件进行path(添加常用的include文件)
在对目标pid进程的注入时,基于boyliang的注入代码的考虑,在ptrace目标pid进程时,加了对"zygote"进程ptrace操作的特殊处理,用以解除zygote进程的阻塞状态,方便被ptrace。在工程编译的时候,需要 #include <cutils/sockets.h>、#include <sys/socket.h>、#include <sys/un.h>等头文件,但是google官方的NDK没有提供这几个头文件,需要从Android源码文件中导入。大牛boyliang提供了一个比较好的解决方法 --下载他提供的 ndk-path 对自己编译的目标api版本的NDK头文件进行path,其实就是添加这些常用的需要的头文件。
ndk-path的下载地址:
4).android JNI提示 utils/Log.h 找不到 错误的解决方法
在JNI的c文件中如果用到了 #include <utils/Log.h> ,用NDK 编译的时候会提示error: utils/Log.h: No such file or directory
如果想要他的LOG功能的话
1-修改Android.mk文件配置,添加如下语句
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog
2-在.c文件中修改为如下语句
#include <android/log.h>
3-使用方法
#define LOG_TAG "debug"
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, fmt, ##args)
4-打印语句
LOGI("test log!!!!")
LOGI("the string is: %s \n",buff);
5-错误输出到日志
LOGI(strerror(errno))
原因要追溯到jni的两种编译环境之间的区别:ndk 和 源码环境编译。
关于测试时使用Log。调用JNI进行Native Code的开发有两种环境,完整源码环境以及NDK。两种环境对应的Log输出方式也并不相同,差异则主要体现在需要包含的头文件中。
如果是在完整源码编 译环境下,只要include 头文件(位于Android-src/system/core/include/cutils),就可以使用对应 的LOGI、LOGD等方法了,当然LOG_TAG,LOG_NDEBUG等宏值需要自定义。
如果是在NDK环境下编译,则需要include 头文件(位于ndk/android-ndk-r4/platforms/android-8/arch- arm/usr/include/android/),另外自己定义宏映射。
代码中对log打印日志功能的修改,单独增加了 log.h 文件用于支持log日志的消息打印:
/** log.h** Created on: 2016-12-26* Author: fly2016*/#ifndef LOG_H_
#define LOG_H_#include <android/log.h> // 使用log打印日志#define LOG_TAG "INJECT" // adb logcat -s INJECT// 设置当前模式为调试模式
#define DEBUG 1#ifdef DEBUG
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)
#else
#define LOGI(fmt, args...) while(0)
#define LOGE(fmt, args...) while(0)
#define LOGD(fmt, args...) while(0)
#endif#endif /* LOG_H_ */
参考链接:
/
5).android JNI提示 #include <asm/user.h> 找不到 错误的解决办法
修改头文件#include <asm/user.h>为 #include <sys/user.h>
6).对原LibInject代码的ptrace附加目标pid进程的操作的修改和优化
在对目标pid进程进行ptrace附加时,增加了对"zygote"进程的支持,效果怎么样,有待测试了。
// ------------添加的代码--------------------------------------
// 解除zygote进程的阻塞状态
static void* connect_to_zygote(void* arg){int s, len;struct sockaddr_un remote;LOGI("[+] wait 2s...");// 休眠一下sleep(2);/**** zygote启动后会进入一个死循环,用来接收AMS的请求连接.* 当没有应用启动时,zygote进程一直处于阻塞状态。* 所以我们后面代码中的第三次wait会无法返回,解决办法也很简单,就是主动发起一个zygote的连接。* 我们看到第二个waitpid后面调用了一个connect_to_zygote函数。*/// 创建socket套接字if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) != -1) {// 设置连接的套接字的协议类型remote.sun_family = AF_UNIX;// 设置连接的套接字的目标strcpy(remote.sun_path, "/dev/socket/zygote");// 设置传递的参数的字节长度len = strlen(remote.sun_path) + sizeof(remote.sun_family);LOGI("[+] start to connect zygote socket");// 向"/dev/socket/zygote"目标套接字发起连接connect(s, (struct sockaddr *) &remote, len);LOGI("[+] close socket");// 关闭socket套接字close(s);}/**** 这个函数的功能很简单,先发起socket连接,然后再关闭连接。* 看上去没有做什么有用的事情,但是它却非常重要,* 通过连接zygote,它使zygote进程解除了阻塞状态,* 我们才得以注入进zygote进程。* 参考网址:/*/return NULL ;
}// 获取目标pid进程的名称字符串
const char* get_process_name(pid_t pid) {static char buffer[255];FILE* f;char path[255];// 格式化得到字符串"/proc/pid/cmdline"snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);// 读取文件"/proc/pid/cmdline"的内容,获取进程的命令行参数if ((f = fopen(path, "r")) == NULL) {return NULL;}// 读取文件"/proc/pid/cmdline"的第1行字符串内容--进程的名称if (fgets(buffer, sizeof(buffer), f) == NULL) {return NULL;}// 关闭文件fclose(f);return buffer;
}
// ------------添加的代码--------------------------------------// 在对进程的ptrace操作时,单独处理下zygote进程
int ptrace_attach( pid_t pid )
{char* pZygote = NULL;if ( ptrace( PTRACE_ATTACH, pid, NULL, 0 ) < 0 ){perror( "ptrace_attach" );return -1;}waitpid( pid, NULL, WUNTRACED );/** Restarts the stopped child as for PTRACE_CONT, but arranges for* the child to be stopped at the next entry to or exit from a sys‐* tem call, or after execution of a single instruction, respec‐* tively.*/if ( ptrace( PTRACE_SYSCALL, pid, NULL, 0 ) < 0 ){perror( "ptrace_syscall" );return -1;}waitpid( pid, NULL, WUNTRACED );// ------------添加的代码--------------------------------------// 获取pid进程的名称const char* process_name = get_process_name(pid);// 判断pid进程是否是"zygote"进程pZygote = strstr(process_name, "zygote");// 针对zygote进程的特殊处理if (pZygote) {// 当进程为zygote时,需要考虑为zygote进程解除阻塞状态,使进程注入得以进行connect_to_zygote(NULL);}// 当目标进程在下次进/出系统调用时被附加调试if (ptrace(PTRACE_SYSCALL, pid, NULL, NULL ) < 0) {LOGE("ptrace_syscall");return -1;}// 等待进程附加操作返回waitpid(pid, NULL, WUNTRACED);// ------------添加的代码--------------------------------------return 0;
}
二、LibInject代码工程结构的介绍
1).LibInject工程用于生成so注入的工具inject
2).InjectSo工程用于生成将被注入到目标pid进程中的so库文件(测试)
三、LibInject代码编译和运行
1).执行so注入测试的脚本 run.bat
adb push libhello.so /data/local/tmp
adb push inject /data/local/tmp
adb shell chmod 0777 /data/local/tmp/libhello.so
adb shell chmod 0777 /data/local/tmp/inject
adb shell su -c /data/local/tmp/inject
pause
用到的其他命令:
adb shell
su
ps | grep com.android.musiccat /proc/5366/maps | grep libhello.so adb logcat -s INJECT
2).注入测试的环境
Android模拟器:api 19
被注入的目标进程:com.android.music(Android 19系统自带的app)
注入工具:inject
注入so文件:libhello.so
进行so注入测试,运行 run.bat 脚本:
so注入到目标进程com.android.music中成功, adb logcat -s INJECT 查看打印的log日志消息:
有关 adb logcat 命令的详细使用说明参考地址:.html
感谢地址:
.php?t=141355
.php?t=157419
.php?t=141355&page=4
/
.html
/
发布评论