• Android平台的so注入--LibInject


    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/53890315

    大牛古河在看雪论坛分享的Android平台的注入代码,相信很多搞Android安全的同学应该都看过。刚接触Android平台的逆向时,我也下载了LibInject代码并且仔细的阅读和分析过,见我前面的博文《Android的so库注入》。我已经基本把Android so注入的原理分析的很清楚了,想学习的同学可以参考一下。虽然作者古河分享了LibInject代码,但是在eclispe下NDK编译还是够呛并且会遇到不少的问题,代码需要修改才能编译和运行成功看到效果。刚接触的时候,信心满满的把作者的代码下载来看,结果编译各种报错也不会修改,本身就对Linux编程不是很熟悉,内心面临崩溃,后面看了几篇博文,终于把问题解决了,可以把代码LibInject编译和运行成功和,有空就记录一下。作者古河分享的代码下载地址:http://bbs.pediy.com/showthread.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)  


    Application.mk文件

    # 编译成功,生成的模块运行支持的平台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}platformsandroid-19arch-armusrinclude
    ${NDKROOT}sourcescxx-stlgnu-libstdc++4.8include 
    ${NDKROOT}sourcescxx-stlgnu-libstdc++4.8libsarmeabiinclude
    ${NDKROOT} oolchainsarm-Linux-androideabi-4.8prebuiltwindowslibgccarm-linux-androideabi4.8include

    我在编译该工程的时候,添加的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-r10dplatformsandroid-21arch-armusrinclude</includepath>
    <includepath>E:android-ndk-r10d	oolchainsaarch64-linux-android-4.9prebuiltwindows-x86_64libgccaarch64-linux-android4.9include</includepath>
    <includepath>E:android-ndk-r10dsourcescxx-stlgnu-libstdc++4.9include</includepath>
    <includepath>E:android-ndk-r10dsourcescxx-stlgnu-libstdc++4.9libsarmeabiinclude</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的下载地址:https://github.com/boyliang/ndk-patch



    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 ",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_ */
    参考链接:

    http://qgjie456.blog.163.com/blog/static/3545136720125297123324/

    http://blog.csdn.net/jinzhuojun/article/details/9833345

    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进程。
         * 参考网址:http://zke1ev3n.me/2015/12/02/Android-so%E6%B3%A8%E5%85%A5/
         */
    
        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.music
    
    cat /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 命令的详细使用说明参考地址:http://www.cnblogs.com/OrdinaryTravel/p/4548471.html


    感谢地址

    http://bbs.pediy.com/showthread.php?t=141355

    http://bbs.pediy.com/showthread.php?t=157419

    https://github.com/boyliang/ndk-patch

    http://blog.csdn.net/qq1084283172/article/details/53869796

    http://blog.csdn.net/qq1084283172/article/details/46859931
    http://blog.csdn.net/jinzhuojun/article/details/9900105
    http://bbs.pediy.com/showthread.php?t=141355&page=4
    http://blog.csdn.net/jinzhuojun/article/details/9833345
    http://qgjie456.blog.163.com/blog/static/3545136720125297123324/

    http://www.cnblogs.com/OrdinaryTravel/p/4548471.html

    http://qgjie456.blog.163.com/blog/static/3545136720125297123324/

    http://blog.csdn.net/jinzhuojun/article/details/9833345


  • 相关阅读:
    << 和>> 的计算公式
    死锁面试题(什么是死锁,产生死锁的原因及必要条件)
    SpringBoot的注解:@SpringBootApplication注解 vs @EnableAutoConfiguration+@ComponentScan+@Configuration
    SpringBoot入门-15(springboot配置freemarker使用YML)
    shiro 登录
    springMVC RedirectAttributes
    IDEA3.5最新版激活码
    求递归算法时间复杂度:递归树
    渐进复杂度
    PL/SQL注册码
  • 原文地址:https://www.cnblogs.com/csnd/p/11800651.html
Copyright © 2020-2023  润新知