热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

动手打造Android7.0以上的注入工具

动手打造Android7.0以上的注入工具在不使用Xposed的一些场景下,想要Hook进入目标APK的方法,最直接有效的方法是注入代码到目标APK&

动手打造Android7.0以上的注入工具

在不使用Xposed的一些场景下,想要Hook进入目标APK的方法,最直接有效的方法是注入代码到目标APK,进而完成Hook操作。


面临的挑战

编写注入工具的原理是借助安卓系统的ptrace接口,操纵目标进程的内存,修改进程空间的数据与代码,然后调用dlopen()dlsym()等接口加载与使用动态库,完成达到注入so的目的。

ptrace接口是Linux层面的东西,在网络上可以大量找到这个API的使用介绍与方法,这里不打算深研它的基础原理与使用方法,而是把目光聚集在安卓系统上其特定使用场景上。

把so注入到了目标进程中后,并没有就此完事,而是需要做更多有意义的事情,比如Hook目标APK的代码,so的代码Hook这里不讲,Java层的话,就需要获取其VM环境上下文,从而调用Java的API,手动的在目标进程中加载DEX或APK,最后再执行Hook这个操作。

从安卓7.0对系统的限制,以及注入工具的使用流程上看,我们面临着如下的挑战:


  • 7.0系统的限制与绕过。7.0系统不允许调用很多私有或限制的API,很多函数调用受到了阻碍,再者,SeLinux的限制,让so动态库的注入与加载也遇到了问题,并不能直接加载一个不受系统信任的so动态库到目标不进程中去。

  • 编写注入器与注入代码。如何编写一个通用的框架,可以与注入工具配合好在目标APK中加载so与APK文件,你想好了么?

  • 注入系统进程。有时候为了选择Hook多个目标APK的方法,会选择一劳永逸的注入它们的系统父进程,比如SystemServer与com.android.phone进程。注入这些进程与普通进程有区别吗?

  • NDK编译系统隐藏API。在编写注入工具时,会调用到很多安卓系统中使用频繁,但NDK中却没有提供的接口。这个时候就需要想办法来调用它们了。

  • 代码混淆。最后,作为功能的增强,可以加入OLLVM的自动化编译,对目标so进行代码混淆。


开发实战

这里使用了低版本的NDK r10e进行开发。代码的编译通过命令行完成,编写使用Visual Studio Code。


7.0系统的限制与绕过

首先是7.0系统的限制与绕过。不解决这个问题,后面的开发工作无从谈起。

dlopen()dlsym()的调用限制网络上有一个优雅的绕过方法。代码仓库是:https://github.com/avs333/Nougat_dlfunctions。核心代码位于jni/fake_dlfcn.c文件中,fake_dlopen()fake_dlsym()可以代替dlopen()dlsym()来使用,它的原理是在当前进程的内存中,搜索符号表的方式,在内存中找到函数的内存地址。当然,它是有限制的:只能dlopen()已经加载进入内存的so,即系统或自己预先加载的动态库,并且参数flags加载标志被忽略。

以上解决了调用系统限制API的问题,但加载外部so的限制却还在那。SeLinux的强制实施,使得dlopen()外部的so动态库有可能会失败返回。SeLinux会检测so动态库的label标签与权限是否满足可加载的要求,不满足就会无情的拒绝!为了解决这个问题,需要调用setxattr()修改so的属性信息。这里封装的代码如下:

int setxattr(const char *path, const char *value) {if (!file_exists("/sys/fs/selinux")) {return 0;}return syscall(__NR_setxattr, path, "security.selinux", value, strlen(value),0);
}

当我们注入so前,可以插入如下代码来解决第三方so加载的问题:

snprintf(so_path, sizeof(so_path), "/data/local/tmp/libsvr.so");
...
setxattr(so_path, "u:object_r:system_file:s0");

编写注入器

注入器是一个ELF格式的安卓可执行文件,使用Android.mk配置好它的开发信息如下:

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

LOCAL_C_INCLUDES       := $(LOCAL_PATH)
LOCAL_MODULE           := inject
LOCAL_LDLIBS           := -ldl -llog
LOCAL_CFLAGS           := -std=c99
# 基于pie
LOCAL_CFLAGS           += -fvisibility=hidden
LOCAL_CFLAGS           += -fPIE
LOCAL_LDFLAGS         += -pie -fPIE
LOCAL_SRC_FILES       := inject/inject.c
include $(BUILD_EXECUTABLE)

注入器的代码网络上流传了一个inject。早先的一个版本是由古河放出,后来github上也有了很多的版本。例如https://github.com/shutup/libinject2。当然,它们很多都年久失修,并不能在新的系统上运行起来,需要对代码做一些修正。

其中一处是对ptrace_attach()的处理,如zygote进程,很多时候是不能一次attach成功的,需要进行更加细致的处理。这里修正代码如下:

int ptrace_attach(pid_t pid, bool is_zygote) {struct pt_regs regs;int status = 0;if (ptrace(PTRACE_ATTACH, pid, NULL, 0) <0) {perror("ptrace_attach");return -1;}
​if (is_zygote) {while (waitpid(pid, &status, __WNOTHREAD) == -1 && (EINTR == errno)) {LOGI("waitpid EINTR, status = %d\n", status);}
​int times = 50;while ((times--) != 0) {if (ptrace(PTRACE_SYSCALL, pid, NULL, 0) <0) {perror("ptrace_syscall");ptrace_detach(pid);kill(pid, SIGCONT);return -1;}
​while (waitpid(pid, &status, __WNOTHREAD) == -1 && (EINTR == errno)) {LOGI("waitpid EINTR, status = %d\n", status);}ptrace_getregs(pid, ®s);bool is_async_syscall = false;
#if defined(__arm__)if (regs.ARM_r7 <= NR_faccessat) {if ((NR_ioctl == regs.ARM_r7) ||(NR__newselect == regs.ARM_r7) ||(NR_poll == regs.ARM_r7)) {is_async_syscall = true;}} else {//if (regs.ARM_r7 > NR_epoll_pwait) {//   is_async_syscall = false;//}#define _BYTE unsigned char#define BYTEn(x, n)   (*((_BYTE*)&(x)+n))#define LOBYTE(x) BYTEn(x, 0)is_async_syscall = regs.ARM_r7 > NR_epoll_pwait ? 0 :((1 <<(LOBYTE(regs.ARM_r7) - 0x4F)) & 0x803) != 0;}
#elif defined(__aarch64__)//FIXME aarch64 ptrace_attachis_async_syscall = false;
#endifif (ptrace_continue(pid) <0) {ptrace_detach(pid);kill(pid, SIGCONT);return -1;}if (!is_async_syscall)usleep(100000u);kill(pid, SIGSTOP);while (waitpid(pid, &status, __WNOTHREAD) == -1 && (EINTR == errno)) {LOGI("waitpid EINTR, status = %d\n", status);}
​if (is_async_syscall) {return 0;}}return 0;} else {status = ptrace_wait_for_signal(pid, SIGSTOP);LOGI("ptrace_wait_for_signal: %d %d\n", __LINE__, status);return 0;}
}

然后有一个非常需要注意的地方,是ptrace_call()进行系统调用时,对于libc.so中的函数调用,需要对lr寄存器进行修正,更新为libc.so的起始地址,不然,有些函数是被调用失败的,比如mmap()。代码如下:

int ptrace_call(pid_t pid, uintptr_t addr, long *params, int num_params,struct pt_regs *regs) {int i;
#if defined(__arm__)int num_param_registers = 4;
#elif defined(__aarch64__)int num_param_registers = 8;
#endiffor (i = 0; i uregs[i] = params[i];}
​//// push remained params into stack//if (i ARM_sp -= (num_params - i) * sizeof(long);ptrace_writedata(pid, (void *)regs->ARM_sp, (uint8_t *)¶ms[i],(num_params - i) * sizeof(long));}
​regs->ARM_pc = addr;if (regs->ARM_pc & 1) // thumb{regs->ARM_pc &= (~1u);regs->ARM_cpsr |= CPSR_T_MASK;} else // arm{regs->ARM_cpsr &= ~CPSR_T_MASK;}
​regs->ARM_lr = 0;
​long lr_val = 0;char sdk_ver[32];memset(sdk_ver, 0, sizeof(sdk_ver));__system_property_get("ro.build.version.sdk", sdk_ver);if (atoi(sdk_ver) <= 23) {lr_val = 0;} else { // Android 7.0static long start_ptr = 0;if (start_ptr == 0) {char map_buf[MAX_PATH];char name_buf[0x400];char line[0x400];memset(map_buf, 0, sizeof(map_buf));memset(name_buf, 0, sizeof(name_buf));memset(line, 0, sizeof(line));sprintf(map_buf, "/proc/%d/maps", pid);FILE *fd = fopen(map_buf, "r");if (fd) {while (fgets(line, sizeof(line), fd)) {if (strstr(line, "libc.so")) {if (!fgets(line, sizeof(line), fd) )break;long start_addr;long end_addr;char ownship[8];long ll;char ss[8];long ll2;sscanf(line, "%lx-%lx %4s %lx %5s %ld %s",&start_addr, &end_addr, ownship, &ll, ss, &ll2, name_buf);if (ownship[2] != &#39;x&#39;) {start_ptr = start_addr;}}}fclose(fd);}}lr_val = start_ptr;}
​regs->ARM_lr = lr_val;if (ptrace_setregs(pid, regs) == -1 || ptrace_continue(pid) == -1) {return -1;}
​int stat = 0;waitpid(pid, &stat, WUNTRACED);while (stat != 0xb7f) {if (ptrace_continue(pid) == -1) {LOGE("error\n");return -1;}waitpid(pid, &stat, WUNTRACED);}
​return 0;
}

注入SystemServer与com.android.phone

注入进程的流程是一样的,只是判断是zygote进程时,需要做一些特别的处理。

inject_remote_process()为注入的核心,代码如下:

int inject_remote_process(pid_t target_pid, const char *library_path,const char *function_name, void *param,size_t param_size, bool is_zygote) {int ret = -1;void *dlopen_addr, *dlsym_addr, *dlclose_addr, *dlerror_addr;void *malloc_addr;uint8_t *map_base;
​struct pt_regs regs, old_regs;
​long parameters[10];
​LOGD("[+] Injecting process: %d, %s, %s, %s\n", target_pid, library_path,function_name, (const char*)param);
​if (get_module_base(target_pid, library_path) != 0) {LOGI("[+] target process[%d] injected already\n", target_pid);return EXIT_SUCCESS;}
​if (ptrace_attach(target_pid, is_zygote) == -1) {LOGI("[+] target process[%d] ptrace_attach returned.\n", target_pid);return EXIT_SUCCESS;}
​if (ptrace_getregs(target_pid, ®s) == -1)goto exit;
​// save original registersmemcpy(&old_regs, ®s, sizeof(regs));malloc_addr = get_remote_addr(target_pid, libc_path, (void *)malloc);LOGD("[+] Remote malloc address: 0x%p\n", malloc_addr);
​// call mallocparameters[0] = 0x4000;if (ptrace_call_wrapper(target_pid, "malloc", malloc_addr, parameters, 1, ®s) == -1)goto exit;
​LOGD("[+] get mmap retval\n");map_base = (uint8_t*)ptrace_retval(®s);
​dlopen_addr = get_remote_addr(target_pid, linker_path, (void *)dlopen);dlsym_addr = get_remote_addr(target_pid, linker_path, (void *)dlsym);dlclose_addr = get_remote_addr(target_pid, linker_path, (void *)dlclose);dlerror_addr = get_remote_addr(target_pid, linker_path, (void *)dlerror);
​LOGI("[+] Get imports: dlopen: 0x%p, dlsym: 0x%p, dlclose: 0x%p, dlerror: 0x%p\n",dlopen_addr, dlsym_addr, dlclose_addr, dlerror_addr);
​ptrace_writedata(target_pid, map_base, (uint8_t*)library_path, strlen(library_path) + 1);
​parameters[0] = (long)map_base;parameters[1] = RTLD_NOW | RTLD_GLOBAL;
​// dlopen(path)if (ptrace_call_wrapper(target_pid, "dlopen", dlopen_addr, parameters, 2, ®s) == -1)goto exit;
​void *sohandle = (void*)ptrace_retval(®s);if (!sohandle) {// dlerror()if (ptrace_call_wrapper(target_pid, "dlerror", dlerror_addr, 0, 0, ®s) == -1)goto exit;LOGE("start ptrace_retval");uint8_t *errret = (uint8_t*)ptrace_retval(®s);LOGE("stop ptrace_retval");uint8_t errbuf[100];LOGE("start ptrace_readdata");ptrace_readdata(target_pid, errret, errbuf, 100);LOGE("stop ptrace_readdata");//LOGE("[+] dlopen failed. error code[0x%X], error msg[%s]", *errret, errbuf);LOGE("[+] dlopen failed. ");
​goto exit;}

#define FUNCTION_NAME_ADDR_OFFSET 0x100
#define FUNCTION_PARAM_ADDR_OFFSET 0x200
​ptrace_writedata(target_pid, map_base + FUNCTION_NAME_ADDR_OFFSET,(uint8_t*)function_name, strlen(function_name) + 1);parameters[0] = (long)sohandle;parameters[1] = (long)map_base + FUNCTION_NAME_ADDR_OFFSET;// dlsym(handle, function_name)if (ptrace_call_wrapper(target_pid, "dlsym", dlsym_addr, parameters, 2, ®s) == -1)goto exit;
​void *hook_entry_addr = (void*)ptrace_retval(®s);LOGI("[+] hook_entry_addr = %p\n", hook_entry_addr);
​ptrace_writedata(target_pid, map_base + FUNCTION_PARAM_ADDR_OFFSET, param, param_size + 1);
​// hook_entry(param)parameters[0] = (long)map_base + FUNCTION_PARAM_ADDR_OFFSET;if (ptrace_call_wrapper(target_pid, function_name, hook_entry_addr, parameters, 1, ®s) == -1)goto exit;
​ptrace_setregs(target_pid, &old_regs);ptrace_detach(target_pid);
​ret = 0;
exit:LOGE("exit %d",ret);return ret;
}

inject_com_android_phone()代码如下:

void inject_com_android_phone(const char *path) {char apk_path[512];char so_path[512];
​pid_t pid = find_pid_of("com.android.phone", false);if (pid > 0) {memset(apk_path, 0, sizeof(apk_path));memset(so_path, 0, sizeof(so_path));//snprintf(so_path, sizeof(so_path), "%s/%s", path, SRV_SO_NAME);snprintf(so_path, sizeof(so_path), "/data/local/tmp/libsvr.so");//snprintf(apk_path, sizeof(apk_path), "%s/%s", path, SRV_APK_NAME);snprintf(apk_path, sizeof(apk_path), "/data/local/tmp/svr.apk");
​if (!file_exists(apk_path)) {return;}setxattr(apk_path, "u:object_r:system_file:s0");if (!file_exists(so_path)) {return;}setxattr(so_path, "u:object_r:system_file:s0");
​inject_remote_process(pid, so_path, SRV_INIT_FUNC_NAME, apk_path, strlen(apk_path), false);}
}

inject_system_server()的代码与它一样,只是调用inject_remote_process()的最后一个参数不同。

最后,封装一下接口,编写main()如下:

int main(int argc, char *argv[]) {int type;char path[0x1000];const char *dir;
​type = atoi(argv[1]);memset(path, 0, 0x1000);readlink("/proc/self/exe", path, 4096uLL);
​dir = dirname(path);strcpy(path, dir);symlink_odex(path);switch (type) {case 1:inject_system_server(path);break;case 2:inject_com_android_phone(path);break;case 3:inject_zygote(path);break;default:printf("error\n");break;}
​return 0;
}

编写注入代码

编写注入代码libsrv.so,主要的目的是要在目标APK中加载APK或DEX。它需要一个init初始方法,在加载时就执行这个操作。代码如下:

pthread_t gThread;
__attribute__ ((visibility ("default"))) void clientInit(const char* jarpath) {pthread_create(&gThread, NULL, (void *(*)(void*))_clientInit, (void*)jarpath);
}

新开一个_clientInit的线程,它传入的参数jarpath即为要加载的APK。实现代码如下:

int _clientInit(const char* jarpath) {JNIEnv* env = NULL;JavaVM *javaVM = android::AndroidRuntime::mJavaVM;LOGE("use mJavaVM returned %p\n", javaVM);if (javaVM) {jint result = javaVM->AttachCurrentThread(&env, 0);if ((result == JNI_OK) && (env != NULL)) {LOGE("attach ok. clientInit JavaVM : 0x%p, JNIEnv : 0x%p\n", javaVM, env);// FIXME hook_loadNativeLobrary();// System.setProperty("process_arch", "64");load_dex_and_run(env, jarpath);javaVM->DetachCurrentThread();LOGE("DetachCurrentThread all finished!");} else {LOGE("NOTE: attach of thread failed\n");return -1;}}
/*if (sdk_ver > 23) {
#if defined(__arm__) || defined(__i386__)void *librt_dso = fake_dlopen("/system/lib/libandroid_runtime.so", RTLD_NOW);
#elif defined(__aarch64__) || defined(__x86_64__)void *librt_dso = fake_dlopen("/system/lib64/libandroid_runtime.so", RTLD_NOW);
#endifLOGE("fake_dlopen for libandroid_runtime.so returned %p\n", librt_dso);void *pVM = fake_dlsym(librt_dso, "_ZN7android14AndroidRuntime7mJavaVME");LOGE("fake_dlsym for android::AndroidRuntime::mJavaVM returned %p\n", *(void**)pVM);javaVM = (JavaVM *)*(void**)pVM;
​} else {javaVM = android::AndroidRuntime::getJavaVM();LOGE("use getJavaVM() returned %p\n", javaVM);}
*/
​return 0;
}

android::AndroidRuntime::mJavaVM是一个导出的符号,它表示当前系统运行环境的JavaVM实例,可以通过它来创建JAVA环境,加载APK。注意注释掉的代码,这是尝试通过代码调用libandroid_runtime.so的方式来获取JavaVM实例,这两种方法都是可取的。

如何获取这个系统隐藏的导出符号我们下面再讲,先看看如何加载一个APK。load_dex_and_run()是关键,它在AttachCurrentThread()成功调用后,会返回一个JNIEnv环境,有了它就可以顺序的调用Java层的loadClass()来加载APK了。代码如下:

void load_dex_and_run(JNIEnv *env, const char *jarpath) {jclass clzClassLoader = env->FindClass("java/lang/ClassLoader");LOGI("java/lang/ClassLoader 0x%p\n", clzClassLoader);jmethodID mdgetSystemClassLoader = env->GetStaticMethodID(clzClassLoader, "getSystemClassLoader", "()Ljava/lang/ClassLoader;");LOGI("java/lang/ClassLoader.getSystemClassLoader method 0x%p\n", mdgetSystemClassLoader);jobject systemClassLoader = env->CallStaticObjectMethod(clzClassLoader, mdgetSystemClassLoader);LOGI("java/lang/ClassLoader.getSystemClassLoader 0x%p\n", systemClassLoader);
​if (NULL == systemClassLoader) {LOGE("getSystemClassLoader failed!!!");return;}
​char cmdline[1024];const char* entryName;get_cmdline_from_pid(getpid(), cmdline, sizeof(cmdline));if(strstr(cmdline, "system_server") != NULL) {entryName = "entryServer";} else if(strstr(cmdline, "com.android.phone") != NULL) {entryName ="entryPhone";} else if (strstr(cmdline, "zygote") != NULL) {entryName = "entryZygote";} else {LOGE("wrong cmdLine %s",cmdline);return;}
​jclass clzPathClassLoader = env->FindClass("dalvik/system/PathClassLoader");LOGI("java/lang/ClassLoader 0x%p\n", clzClassLoader);jmethodID mdinitPathCL = env->GetMethodID(clzPathClassLoader, "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V");
​LOGI("PathClassLoader loading jarpath[%s]\n", jarpath);jstring jarpath_str = env->NewStringUTF(jarpath);jobject myClassLoader = env->NewObject(clzPathClassLoader, mdinitPathCL, jarpath_str, NULL, systemClassLoader);env->DeleteLocalRef(jarpath_str);
​LOGI("myClassLoader 0x%p\n", myClassLoader);if (NULL != myClassLoader) {jclass entry_class = findClassFromLoader(env, myClassLoader, "com.app.service.EntryClass");if (NULL != entry_class) {LOGI("Entry Class 0x%p\n", entry_class);/*char dirpath[1024];memset(dirpath, 0, sizeof(dirpath));strcpy(dirpath, jarpath);strcpy(dirpath, dirname(dirpath));LOGI("PathClassLoader loading dirpath[%s]\n", dirpath);*/jmethodID entry_method = env->GetStaticMethodID(entry_class, entryName, "()Ljava/util/List;");if (NULL != entry_method) {//jstring dirpath_str = env->NewStringUTF(dirpath);jobject arr = env->CallStaticObjectMethod(entry_class, entry_method/*, dirpath_str*/);//env->DeleteLocalRef(dirpath_str);if (arr != NULL) {//class ArrayListjclass cls_arraylist = env->GetObjectClass(arr);//method in class ArrayListjmethodID arraylist_get = env->GetMethodID(cls_arraylist,"get","(I)Ljava/lang/Object;");jmethodID arraylist_size = env->GetMethodID(cls_arraylist,"size","()I");jint len = env->CallIntMethod(arr,arraylist_size);LOGI("get java ArrayList object by C++ , then print it...../n");for (int i = 0; i CallObjectMethod(arr,arraylist_get,i);jobject obj_user1 = env->CallObjectMethod(arr,arraylist_get,i+1);enable(env, NULL, obj_user, obj_user1);}} else {LOGE("return binder arr null");}}}}
}

好了,代码就帖这么多了,流程上已经明了。


NDK编译系统隐藏API

为了能够顺利的调用系统隐藏API,我专门弄了一个github仓库来完成这个工作。地址是:https://github.com/feicong/ndk。我们看看使用方法。

比如要使用libandroid_runtime.so中的方法。我们需要引用它的头文件与库文件。库文件的引用方法是在Android.mk中加入如下代码:

LOCAL_C_INCLUDES       := $(LOCAL_PATH)
include $(CLEAR_VARS)
LOCAL_MODULE := android_runtime
LOCAL_SRC_FILES := ndk/lib/$(TARGET_ARCH_ABI)/libandroid_runtime.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)

引入头文件引用只需要如下一行:

LOCAL_C_INCLUDES := $(LOCAL_PATH)/ndk/include

然后链接时加入:

LOCAL_SHARED_LIBRARIES := android_runtime

我们看一下https://github.com/feicong/ndk/blob/master/include/android_runtime/AndroidRuntime.h,会发现如下代码片断:

namespace android {

class AndroidRuntime
{
public:AndroidRuntime();virtual ~AndroidRuntime();
.../* JNI JavaVM pointer */static JavaVM* mJavaVM;

没错,在头文件中就这样声明了,根本不需要去dlsym()找啊找!

如果在编译时遇到了编译错误android::RefBase::decStrong,解决方法是在Android.mk中引入如下代码:

LOCAL_LDFLAGS := -Wl,--unresolved-symbols=ignore-all

android::RefBase::decStrong悬而未决,但咱们的项目中并没有用到它,忽略到这个符号即可。


自动加入OLLVM混淆

加入OLLVM混淆功能是算是个甜点功能,不过多的展开,代码片断如下:

echo &#39;building jni for armeabi-v7a.&#39;
export OLLVMNDK=/usr/local/opt/android-ndk-toolchain-ollvm-arm
export CC=$OLLVMNDK/bin/clang
export CXX=$OLLVMNDK/bin/clang++
export CXX40=$OLLVMNDK/bin/clang-4.0
export STRIP=$OLLVMNDK/bin/arm-linux-androideabi-strip
export SYSROOT=$OLLVMNDK/sysroot

$CC jni/lenovo_inject/inject.c --sysroot=$SYSROOT -target armv7-linux-androideabi -fvisibility=hidden -mllvm -sub -mllvm -fla -mllvm -bcf -std=c99 -Wno-format -Wno-format-extra-args -Wno-int-conversion -Wno-implicit-function-declaration -Wno-incompatible-pointer-types-discards-qualifiers -Wno-int-to-void-pointer-cast -Wno-pointer-sign -pie -fPIE -I$OLLVMNDK/lib/clang/4.0/include -o libs/armeabi-v7a/lenovo_inject

$CXX40 jni/service/service.cpp --sysroot=$SYSROOT -target armv7-linux-androideabi -fvisibility=hidden -mllvm -sub -mllvm -fla -mllvm -bcf -Ijni/ndk/include -Ljni/ndk/lib/armeabi-v7a -landroid_runtime -lbinder -lutils -shared -fPIC -DHAVE_SYS_UIO_H -I$OLLVMNDK/lib/clang/4.0/include -o libs/armeabi-v7a/libsvr.so

$STRIP --strip-unneeded libs/armeabi-v7a/libsvr.so

最后的最后,执行如下命令编译项目:

$ ~/Downloads/android-ndk-r10e/ndk-build -C .
make: Entering directory cpp&#39;
[armeabi-v7a] Prebuilt       : libandroid_runtime.so <= jni/ndk/lib/armeabi-v7a/
[armeabi-v7a] Install       : libandroid_runtime.so => libs/armeabi-v7a/libandroid_runtime.so
[armeabi-v7a] Prebuilt       : libbinder.so <= jni/ndk/lib/armeabi-v7a/
[armeabi-v7a] Install       : libbinder.so => libs/armeabi-v7a/libbinder.so
[armeabi-v7a] Compile thumb : inject <= inject.c
[armeabi-v7a] Executable     : inject
[armeabi-v7a] Install       : inject => libs/armeabi-v7a/inject
[armeabi-v7a] Compile thumb : lenovo_inject <= inject.c
[armeabi-v7a] Executable     : lenovo_inject
[armeabi-v7a] Install       : lenovo_inject => libs/armeabi-v7a/lenovo_inject
[armeabi-v7a] Compile thumb : lenovo_oldinject <= inject.c
[armeabi-v7a] Executable     : lenovo_oldinject
[armeabi-v7a] Install       : lenovo_oldinject => libs/armeabi-v7a/lenovo_oldinject
[armeabi-v7a] Compile thumb : oldinject <= inject.c
[armeabi-v7a] Executable     : oldinject
[armeabi-v7a] Install       : oldinject => libs/armeabi-v7a/oldinject
[armeabi-v7a] Compile++ thumb: svr <= service.cpp
[armeabi-v7a] Compile thumb : svr <= fake_dlfcn.c
[armeabi-v7a] Prebuilt       : libutils.so <= jni/ndk/lib/armeabi-v7a/
[armeabi-v7a] StaticLibrary : libstdc++.a
[armeabi-v7a] SharedLibrary : libsvr.so
[armeabi-v7a] Install       : libsvr.so => libs/armeabi-v7a/libsvr.so
[armeabi-v7a] Install       : libutils.so => libs/armeabi-v7a/libutils.so

执行效果?自己编译测试跑跑吧。


小结

本篇主要介绍了如何开发一款支持Android 7.0以上版本的so注入工具,并讲解了开发中可能遇到的坑,以及应对它们的措施,当然,要想开发一款兼容性很好的注入工具,还需要进行大量的测试与实践,这后续的工作就交给大家了。

文章精美排版PDF与代码,知识星球会员可以在知识星球:【软件安全与逆向分析】(ID: 86753808)中下载。

更多精彩内容,欢迎关注微信公众号【feicong_sec】


推荐阅读
  • Skywalking系列博客1安装单机版 Skywalking的快速安装方法
    本文介绍了如何快速安装单机版的Skywalking,包括下载、环境需求和端口检查等步骤。同时提供了百度盘下载地址和查询端口是否被占用的命令。 ... [详细]
  • 本文介绍了解决Netty拆包粘包问题的一种方法——使用特殊结束符。在通讯过程中,客户端和服务器协商定义一个特殊的分隔符号,只要没有发送分隔符号,就代表一条数据没有结束。文章还提供了服务端的示例代码。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • javascript  – 概述在Firefox上无法正常工作
    我试图提出一些自定义大纲,以达到一些Web可访问性建议.但我不能用Firefox制作.这就是它在Chrome上的外观:而那个图标实际上是一个锚点.在Firefox上,它只概述了整个 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Python语法上的区别及注意事项
    本文介绍了Python2x和Python3x在语法上的区别,包括print语句的变化、除法运算结果的不同、raw_input函数的替代、class写法的变化等。同时还介绍了Python脚本的解释程序的指定方法,以及在不同版本的Python中如何执行脚本。对于想要学习Python的人来说,本文提供了一些注意事项和技巧。 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了在Mac上搭建php环境后无法使用localhost连接mysql的问题,并通过将localhost替换为127.0.0.1或本机IP解决了该问题。文章解释了localhost和127.0.0.1的区别,指出了使用socket方式连接导致连接失败的原因。此外,还提供了相关链接供读者深入了解。 ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
author-avatar
天生骄傲
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有