阅读:3637回复:0
Android.Hook框架Cydia篇(脱壳机制作)
注:框架有风险,使用要谨慎.
Cydia Substrate是一个代码修改平台.它可以修改任何主进程的代码,不管是用Java还是C/C++(native代码)编写的.而Xposed只支持HOOK app_process中的java函数,因此Cydia Substrate是一款强大而实用的HOOK工具. 官网地址:http://www.cydiasubstrate.com/ 官方教程:http://www.cydiasubstrate.com/id/38be592b-bda7-4dd2-b049-cec44ef7a73b SDK下载地址:http://asdk.cydiasubstrate.com/zips/cydia_substrate-r2.zip ◆0Hook Java 层 之前讲解过 xposed 的用法为啥还要整这个了,下面简单对比两款框架.想了解之前 xposed 篇的可以看这里:http://drops.wooyun.org/tips/7488 劣势: [*]没啥错误提醒,排错比较麻烦. [*]需要对 NDK 开发有一定了解,相对 xposed 模块的开发学习成本高一些. [*]因为不开源网上(github)上可以参考的模块代码很少. 优势: [*]可以对 native 函数进行 hook . [*]与 xposed hook 原理不一样,因为不是开源具体原理我也不清楚. 结果就是一些Anti hook 可能对 xposed 有效而对 Cydia 无效. 使用方法 1.安装框架app:http://www.cydiasubstrate.com/download/com.saurik.substrate.apk 2.创建一个空的Android工程.由于创建的工程将以插件的形式被加载,所以不需要activity.将SDK中的substrate-api.jar复制到project/libs文件夹中. 3.配置Manifest文件 4.创建一个类,类名为Main.类中包含一个static方法initialize,当插件被加载的时候,该方法中的代码就会运行,完成一些必要的初始化工作. #!java import com.saurik.substrate.MS; public class Main { static void initialize() { // ... code to run when extension is loaded } } 5.hook imei example #!java import com.saurik.substrate.MS; public class Main { static void initialize() { MS.hookClassLoad("android.telephony.TelephonyManager", new MS.ClassLoadHook() { @SuppressWarnings("unchecked") public void classLoaded(Class arg0) { Method hookimei; try { hookimei = arg0.getMethod("getDeviceId", null); } catch (NoSuchMethodException e) { // TODO Auto-generated catch block e.printStackTrace(); hookimei = null; } if (hookimei != null) { final MS.MethodPointer old1 = new MS.MethodPointer(); MS.hookMethod(arg0, hookimei, new MS.MethodHook() { @Override public Object invoked(Object arg0, Object... arg1) throws Throwable { // TODO Auto-generated method stub System.out.println("hook imei----------->"); String imei = (String) old1.invoke(arg0, arg1); System.out.println("imei-------->" + imei); imei = "999996015409998"; return imei; } }, old1); } } }); } } 6.在 cydia app 界面中点击 Link Substrate Files 之后重启手机 图片:2015081410002183631.png 7.使用getimei的小程序验证imei是否被改变 #!java public class MainActivity extends ActionBarActivity { private static final String tag = "MainActivity"; TextView mText ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mText = (TextView) findViewById(R.id.text); TelephonyManager mtelehonyMgr = (TelephonyManager) getSystemService(this.TELEPHONY_SERVICE); Build bd = new Build(); String imei = mtelehonyMgr.getDeviceId(); String imsi = mtelehonyMgr.getSubscriberId(); //getSimSerialNumber() 获取 SIM 序列号 getLine1Number 获取手机号 String androidId = Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID); String id = UUID.randomUUID().toString(); String model = bd.MODEL; StringBuilder sb = new StringBuilder(); sb.append("imei = "+ imei); sb.append("nimsi = " + imsi); sb.append("nandroid_id = " + androidId); sb.append("nuuid = " + id); sb.append("nmodel = " + model); if(imei!=null) mText.setText(sb.toString()); else mText.setText("fail"); } 8.关键api介绍 MS.hookClassLoad:该方法实现在指定的类被加载的时候发出通知(改变其实现方式?).因为一个类可以在任何时候被加载,所以Substrate提供了一个方法用来检测用户感兴趣的类何时被加载. 这个api需要实现一个简单的接口MS.ClassLoadHook,该接口只有一个方法classLoaded,当类被加载的时候该方法会被执行.加载的类以参数形式传入此方法. void hookClassLoad(String name, MS.ClassLoadHook hook);
#!java MS.hookClassLoad("java.net.HttpURLConnection", new MS.ClassLoadHook() { public void classLoaded(Class _class) { /* do something with _class argument */ } } ); MS.hookMethod:该API允许开发者提供一个回调函数替换原来的方法,这个回调函数是一个实现了MS.MethodHook接口的对象,是一个典型的匿名内部类.它包含一个invoked函数. #!java void hookMethod(Class _class, Member member, MS.MethodHook hook, MS.MethodPointer old);
◆1Hook Native 层 这块的功能 xposed 就不能实现啦. 整个流程大致如下: [*]创建工程,添加 NDK 支持 [*]将 cydia 的库和头文件加入工程 [*]修改 AndroidManifest配置文件 [*]修改Android.md [*]开发模块 [*]指定要hook 的 lib 库 [*]保留原来的地址 [*]替换的函数 [*]Substrate entry point [*]MSGetImageByName or dlopen [*]MSFindSymbol or dlsym or nlist 指定方法,得到开始地址 [*]MSHookFunction 替换函数 使用方法 **第零步:添加 ndk 支持,将 cydia 的库和头文件加入工程 有关 ndk 开发的基础可以参考此文: NDK入门篇 图片:2015081410002183631.png 注意要是 xxx.cy.cpp,不要忘记.cy 其实应该是动态链接库名称中的 cy 必须有,所有在 Android.md 中module 处的 .cy 必须带上咯 LOCAL_MODULE := DumpDex2.cy 第一步:修改配置文件 设置 android:hasCode 属性 false,设置android:installLocation属性internalOnly" 第二步:指定要 hook 的 lib 库 #include MSConfig(MSFilterExecutable, "/system/bin/app_process") //MSConfig(MSFilterLibrary, "liblog.so") // this is a macro that uses __attribute__((__constructor__)) MSInitialize { // ... code to run when extension is loaded } 设置要 hook 的可执行文件或者动态库 第三步: 等待 class static void OnResources(JNIEnv *jni, jclass resources, void *data) { // ... code to modify the class when loaded } MSInitialize { MSJavaHookClassLoad(NULL, "android/content/res/Resources", &OnResources); } 第四步:修改实现 static jint (*_Resources$getColor)(JNIEnv *jni, jobject _this, ...); static jint $Resources$getColor(JNIEnv *jni, jobject _this, jint rid) { jint color = _Resources$getColor(jni, _this, rid); return color & ~◆000ff00 | ◆0ff0000; } static void OnResources(JNIEnv *jni, jclass resources, void *data) { jmethodID method = jni->GetMethodID(resources, "getColor", "(I)I"); if (method != NULL) MSJavaHookMethod(jni, resources, method, &$Resources$getColor, &_Resources$getColor); } 下面是步骤是在官网教程基础上对小白同学的一些补充吧. » file libprocess.so libprocess.so: ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), not stripped 第五步 复制libsubstrate-dvm.so(注意 arm 和 x86平台的选择)和substrate.h到 jni 目录下.创建SuperMathHook.cy.cpp文件 第六步 配置Android.mk文件 LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE:= substrate-dvm LOCAL_SRC_FILES := libsubstrate-dvm.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := SuperMathHook.cy LOCAL_SRC_FILES := SuperMathHook.cy.cpp LOCAL_LDLIBS := -llog LOCAL_LDLIBS += -L$(LOCAL_PATH) -lsubstrate-dvm //-L指定库文件的目录,-l指定库文件名,-I指定头文件的目录. include $(BUILD_SHARED_LIBRARY) 加入 c 的 lib LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE:= substrate-dvm LOCAL_SRC_FILES := libsubstrate-dvm.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE:= substrate LOCAL_SRC_FILES := libsubstrate.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := CydiaN.cy LOCAL_SRC_FILES := CydiaN.cy.cpp LOCAL_LDLIBS := -llog LOCAL_LDLIBS += -L$(LOCAL_PATH) -lsubstrate-dvm -lsubstrate include $(BUILD_SHARED_LIBRARY) strings 查看下里面的函数. /data/data/com.jerome.jni/lib # strings libprocess.so < /system/bin/linker __cxa_finalize __cxa_atexit Jstring2CStr malloc memcpy __aeabi_unwind_cpp_pr0 Java_com_jerome_jni_JNIProcess_getInfoMD5 .... 脱壳机模块发开 网上流传的 IDA dump 脱壳流程大致如下: [*]对/system/lib/libdvm.so 方法JNI_OnLoad/dvmLoadNativeCode/dvmDexFileOpenPartial下断点分析 [*]IDA 附加 app (IDA6.5以及之后版本) [*]Ctrl+s 查看基地址+偏移 [*]IDA 分析寻找 dump 点 [*]F8/F9执行到dex完全被解密到内存中时候进行 dump 现在目标就是通过 Cydia 的模块来自动化完成这个功能.这里咱选择对dvmDexFileOpenPartial函数进行 hook.至于为什么要选择这里了?这就需要分析下 android dex优化过程 Android会对每一个安装的应用的dex文件进行优化,生成一个odex文件.相比于dex文件,odex文件多了一个optheader,依赖库信息(dex文件所需要的本地函数库)和辅助信息(类索引信息等). dex的优化过程是一个独立的功能模块来实现的,位于http://androidxref.com/4.4.3_r1.1/xref/dalvik/dexopt/OptMain.cpp#57 其中extractAndProcessZip()函数完成优化操作. http://androidxref.com/4.1.1/xref/dalvik/dexopt/OptMain.cpp OptMain中的main函数就是加载dex的最原始入口 #!c int main(int argc, char* const argv[]) { set_process_name("dexopt"); setvbuf(stdout, NULL, _IONBF, 0); if (argc > 1) { if (strcmp(argv[1], "--zip") == 0) return fromZip(argc, argv); else if (strcmp(argv[1], "--dex") == 0) return fromDex(argc, argv); else if (strcmp(argv[1], "--preopt") == 0) return preopt(argc, argv); } ... return 1; } 可以看到,这里会分别对3中类型的文件做不同处理,我们关心的是dex文件,所以接下来看看fromDex函数: #!c static int fromDex(int argc, char* const argv[]) { ... if (dvmPrepForDexOpt(bootClassPath, dexOptMode, verifyMode, flags) != 0) { ALOGE("VM init failed"); goto bail; } vmStarted = true; /* do the optimization */ if (!dvmContinueOptimization(fd, offset, length, debugFileName, modWhen, crc, (flags & DEXOPT_IS_BOOTSTRAP) != 0)) { ALOGE("Optimization failed"); goto bail; } ... } 这个函数先初始化了一个虚拟机,然后调用dvmContinueOptimization函数 /dalvik/vm/analysis/DexPrepare.cpp,进入这个函数: #!c bool dvmContinueOptimization(int fd, off_t dexOffset, long dexLength, const char* fileName, u4 modWhen, u4 crc, bool isBootstrap) { ... /* * Rewrite the file. Byte reordering, structure realigning, * class verification, and bytecode optimization are all performed * here. * * In theory the file could change size and bits could shift around. * In practice this would be annoying to deal with, so the file * layout is designed so that it can always be rewritten in place. * * This creates the class lookup table as part of doing the processing. */ success = rewriteDex(((u1*) mapAddr) + dexOffset, dexLength, doVerify, doOpt, &pClassLookup, NULL); if (success) { DvmDex* pDvmDex = NULL; u1* dexAddr = ((u1*) mapAddr) + dexOffset; if (dvmDexFileOpenPartial(dexAddr, dexLength, &pDvmDex) != 0) { ALOGE("Unable to create DexFile"); success = false; } else { ... } 这个函数中对Dex文件做了一些优化(如字节重排序,结构对齐等),然后重新写入Dex文件.如果优化成功的话接下来调用dvmDexFileOpenPartial,而这个函数中调用了真正的Dex文件.在具体看看这个函数/dalvik/vm/DvmDex.cpp #!c /* * Create a DexFile structure for a "partial" DEX. This is one that is in * the process of being optimized. The optimization header isn't finished * and we won't have any of the auxillary data tables, so we have to do * the initialization slightly differently. * * Returns nonzero on error. */ int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex) { DvmDex* pDvmDex; DexFile* pDexFile; int parseFlags = kDexParseDefault; int result = -1; /* -- file is incomplete, new checksum has not yet been calculated if (gDvm.verifyDexChecksum) parseFlags |= kDexParseVerifyChecksum; */ pDexFile = dexFileParse((u1*)addr, len, parseFlags); if (pDexFile == NULL) { ALOGE("DEX parse failed"); goto bail; } pDvmDex = allocateAuxStructures(pDexFile); if (pDvmDex == NULL) { dexFileFree(pDexFile); goto bail; } pDvmDex->isMappedReadOnly = false; *ppDvmDex = pDvmDex; result = 0; bail: return result; } 这个函数的前两个参数非常关键,第一个参数是dex文件的起始地址,第二个参数是dex文件的长度,有了这两个参数,就可以从内存中将这个dex文件dump下来了,这也是在此函数下断点的原因.该函数会调用dexFileParse()对dex文件进行解析 所以在dexFileParse函数处来进行 dump 也是可行的.但是因为这个函数的原型是 DexFile* dexFileParse(const u1* data, size_t length, int flags) 其返回值为一个结构体指针struct DexFile { ... },要 hook 这个函数得把结构体从 android 源码中扣出来或者直接改镜像. 找到dvmDexFileOpenPartial函数在 libdvm.so 对应的名称 #!bash » strings libdvm_arm.so|grep dvmDexFileOpenPartial _Z21dvmDexFileOpenPartialPKviPP6DvmDex » strings libdvm_arm.so|grep dexFileParse _Z12dexFileParsePKhji 有了上述理论基础,现在可以正式开发模块了.大致流程如下 [*]指定要hook 的 lib 库 [*]Original method template 原函数模板 [*]Modified method 替换的函数 [*]Substrate entry point [*]MSGetImageByName or dlopen 载入lib得到 image [*]MSFindSymbol or dlsym or nlist 指定方法,得到开始地址 [*]MSHookFunction 替换函数 完整代码 #!c #include "substrate.h" #include #include #include #include #include #include #define BUFLEN 1024 #define TAG "DEXDUMP" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) //get packagename from pid int getProcessName(char * buffer){ char path_t[256]={0}; pid_t pid=getpid(); char str[15]; sprintf(str, "%d", pid); memset(path_t, 0 , sizeof(path_t)); strcat(path_t, "/proc/"); strcat(path_t, str); strcat(path_t, "/cmdline"); //LOG_ERROR("zhw", "path:%s", path_t); int fd_t = open(path_t, O_RDONLY); if(fd_t>0){ int read_count = read(fd_t, buffer, BUFLEN); if(read_count>0){ int processIndex=0; for(processIndex=0;processIndexpClassLookup里面,当然也更新了索引</p> </blockquote> #!c //Substrate entry point MSInitialize { LOGD("Cydia Init"); MSImageRef image; //载入lib image = MSGetImageByName("/system/lib/libdvm.so"); if (image != NULL) { void * dexload=MSFindSymbol(image,"_Z12dexFileParsePKhji"); if(dexload==NULL) { LOGD("error find _Z12dexFileParsePKhji"); } else{ //替换函数 //3.MSHookFunction MSHookFunction(dexload,(void*)&myDexFileParse,(void **)&oldDexFileParse); } } else{ LOGD("ERROR FIND LIBDVM"); } } 脱壳机模块改进二 [*]加入encode [*]优化输出 [*]... github 地址如下,里面已经有一个编译好但是没有签名的 apk 了... https://github.com/WooyunDota/DumpDex 图片:2015081410002183631.png 如果提取的是 encode 版的,需要 decode 一下: base64 -D -i com.ali.tg.testapp_606716.dex.encode.dex -o my.dex 一些错误排除 NDK Symbol 'NULL' could not be resolved NDK环境没有配好,没有找到stddef.h 图片:2015081410002183631.png jni.h头文件找不到 也是NDK环境未配置好,或者编译器 BUG.先强行编译一次若问题未解决就检查下 NDK 环境. 如果遇到一些成员 ref 到两种头文件中,需要配置下 include.我在使用 mkdir 的时候 mode_t 就 ref 到 ndk 和 osx 的头文件中导致编译失败.解决办法下加入了include: android-ndk-r10d/platforms/android-17/arch-arm/usr/include/sys Android Studio 1.3已经开始支持 NDK,完全抛弃 eclipse 的时日即将到来. ◆3参考 http://www.cnblogs.com/goodhacker/p/4014617.html http://www.cnblogs.com/goodhacker/p/4014617.html http://www.cnblogs.com/baizx/p/4254359.html http://www.gitzx.com/android-cydiasubstrate/ 从源码中跟踪Dex的加载流程 https://github.com/bunnyblue/DexExtractor Android逆向之动态调试总结 dex文件的优化解析及装载 Android系统ODEX文件格式解析 DexClassLoader4.4.2动态加载分析(磁盘加载分析) Android4.0内存Dex数据动态加载技术 |
|||||||||||||