博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android.Hook框架Cydia篇
阅读量:6860 次
发布时间:2019-06-26

本文共 16843 字,大约阅读时间需要 56 分钟。

hot3.png

原文地址:

 

注:框架有风险,使用要谨慎.

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

0x00Hook Java 层

之前讲解过 xposed 的用法为啥还要整这个了,下面简单对比两款框架.想了解之前 xposed 篇的可以看这里:

劣势:

  • 没啥错误提醒,排错比较麻烦.
  • 需要对 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,当插件被加载的时候,该方法中的代码就会运行,完成一些必要的初始化工作.

#!javaimport com.saurik.substrate.MS;public class Main {    static void initialize() {         // ... code to run when extension is loaded    }}

5.hook imei example

#!javaimport 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 之后重启手机

7.使用getimei的小程序验证imei是否被改变

#!javapublic 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);
参数 描述
name 包名+类名,使用java的.符号(被hook的完整类名)
hook MS.ClassLoadHook的一个实例,当这个类被加载的时候,它的classLoaded方法会被执行.
#!javaMS.hookClassLoad("java.net.HttpURLConnection",    new MS.ClassLoadHook() {        public void classLoaded(Class
_class) { /* do something with _class argument */ } });

MS.hookMethod:该API允许开发者提供一个回调函数替换原来的方法,这个回调函数是一个实现了MS.MethodHook接口的对象,是一个典型的匿名内部类.它包含一个invoked函数.

#!javavoid hookMethod(Class _class, Member member, MS.MethodHook hook, MS.MethodPointer old);
参数 描述
_class 加载的目标类,为classLoaded传下来的类参数
member 通过反射得到的需要hook的方法(或构造函数). 注意:不能HOOK字段 (在编译的时候会进行检测).
hook MS.MethodHook的一个实例,其包含的invoked方法会被调用,用以代替member中的代码

0x01Hook Native 层

这块的功能 xposed 就不能实现啦.

整个流程大致如下:

  • 创建工程,添加 NDK 支持
  • 将 cydia 的库和头文件加入工程
  • 修改 AndroidManifest配置文件
  • 修改Android.md
  • 开发模块
    • 指定要hook 的 lib 库
    • 保留原来的地址
    • 替换的函数
    • Substrate entry point
      • MSGetImageByName or dlopen
      • MSFindSymbol or dlsym or nlist 指定方法,得到开始地址
      • MSHookFunction 替换函数

使用方法

**第零步:添加 ndk 支持,将 cydia 的库和头文件加入工程

有关 ndk 开发的基础可以参考此文:  

注意要是 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 & ~0x0000ff00 | 0x00ff0000;}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-dvmLOCAL_SRC_FILES := libsubstrate-dvm.soinclude $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)LOCAL_MODULE    := SuperMathHook.cyLOCAL_SRC_FILES := SuperMathHook.cy.cppLOCAL_LDLIBS := -llogLOCAL_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-dvmLOCAL_SRC_FILES := libsubstrate-dvm.soinclude $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)LOCAL_MODULE:= substrateLOCAL_SRC_FILES := libsubstrate.soinclude $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)LOCAL_MODULE    := CydiaN.cyLOCAL_SRC_FILES := CydiaN.cy.cppLOCAL_LDLIBS := -llogLOCAL_LDLIBS += -L$(LOCAL_PATH) -lsubstrate-dvm -lsubstrateinclude $(BUILD_SHARED_LIBRARY)

strings 查看下里面的函数.

/data/data/com.jerome.jni/lib # strings libprocess.so                                                               

脱壳机模块发开

网上流传的 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的最原始入口

#!cint 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函数:

#!cstatic 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函数 ,进入这个函数:

#!cbool 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文件.在具体看看这个函数

#!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 pidint 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;processIndex

效果如下:

shell@hammerhead:/sdcard $ l |grep dexapp_process_classes_3220.dexcom.ali.tg.testapp_classes_606716.dexcom.chaozh.iReaderFree_classes_4673256.dexcom.secken.app_xg_service_v2_classes_6327832.dex

脱壳机模块改进一

更改 hook 点为 dexFileParse,上文已经讲解了为啥也可以选择这里.也分析了 dex 优化的过程,这里在分析下 dex 加载的过程.

DexClassLoader广泛被开发者用于插件的动态加载.而PathClassLoader几乎没怎么见过.

因为PathClassLoader 没有提供优化 dex 的目录而是固定将 odex 存放到 /data/dalvik-cache 中 ,故它只能加载已经安装到 Android 系统中的 apk 文件,也就是 /data/app 目录下的 apk 文件.

PathClassLoader 和 DexClassLoader 父类为 BaseDexClassLoader

http://androidxref.com/4.4.2_r1/xref/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java

#!c45    public BaseDexClassLoader(String dexPath, File optimizedDirectory,       String libraryPath, ClassLoader parent) {

http://androidxref.com/4.4.2_r1/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

#!cDexPathList(this, dexPath, libraryPath, optimizedDirectory);260    private static DexFile loadDexFile(File file, File optimizedDirectory)

http://androidxref.com/4.4.2_r1/xref/libcore/dalvik/src/main/java/dalvik/system/DexFile.java

#!c141 static public DexFile loadDex(String sourcePathName, String outputPathName, int flags)

调用 native 函数 native private static int openDexFileNative(String sourceName, String outputName, int flags)

#!c294    private static int openDexFile(String sourceName, String outputName,295        int flags) throws IOException {296        return openDexFileNative(new File(sourceName).getCanonicalPath(),297                                 (outputName == null) ? null : new File(outputName).getCanonicalPath(),298                                 flags);299    }

http://androidxref.com/4.4.2_r1/xref/dalvik/vm/native/dalvik_system_DexFile.cpp

#!c151 static void Dalvik_dalvik_system_DexFile_openDexFileNative(const u4* args, JValue* pResult)//249 static void Dalvik_dalvik_system_DexFile_openDexFile_bytearray(const u4* args, JValue* pResult)

http://androidxref.com/4.4.2_r1/xref/dalvik/vm/RawDexFile.cpp

#!c109 int dvmRawDexFileOpen(const char* fileName, const char* odexOutputName, RawDexFile** ppRawDexFile, bool isBootstrap)  //工具类方法打开DEX文件/Jar文件

http://androidxref.com/4.4.4_r1/xref/dalvik/vm/DvmDex.cpp

#!c93 int dvmDexFileOpenFromFd(int fd, DvmDex** ppDvmDex)  //从一个打开的DEX文件,映射到只读共享内存并且解析内容//146 int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex)  //通过地址和长度打开部分DEX文件

http://androidxref.com/4.4.4_r1/xref/dalvik/libdex/DexFile.cpp

#!c289 dexFileParse(const u1* data, size_t length, int flags)  //解析dex文件

方法openDexFile里通过dvmDexFileOpenFromFd函数调用dexFileParse函数,分析Dex文件里每个类名称和类的代码所在索引,然后dexFileParse调用函数dexParseOptData来把类名称写对象pDexFile->pClassLookup里面,当然也更新了索引

#!c//Substrate entry pointMSInitialize{    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 了...

如果提取的是 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

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 的时日即将到来.

0x03参考

转载于:https://my.oschina.net/sfshine/blog/821764

你可能感兴趣的文章
协方差深入解读
查看>>
使用Node.js和WebHDFS REST API访问Hadoop HDFS数据
查看>>
聊聊flink Table的Over Windows
查看>>
基本排序算法
查看>>
Node常用命令备忘
查看>>
阿里云ECS上CentOS系统配置从入门到进门
查看>>
民航业智能升级,安防巨头‘大华’如何利用 AI 保障机场安全
查看>>
支持向量机分类实战
查看>>
java-基础-JNI本地栈
查看>>
中国人工智能学会通讯——增强学习是人工智能的未来 1.3 增强学习的核心技术...
查看>>
2017全球SDNFV技术大会倒计时 大会完整日程曝光!
查看>>
浪潮将在GTC17发布AI超级计算机及软件
查看>>
IBM量子计算机商用化新突破:化学模拟新算法
查看>>
中国人工智能学会通讯——人工智能的商业化挑战
查看>>
免费IDE编辑器才是Java程序员的最爱
查看>>
一张图读懂2016年CIO最关心的IT趋势
查看>>
混合云?行业云?“合脚”的才是好用的云
查看>>
掘金全球最大商务差旅市场 SAP旗下Concur联合中数通进军中国
查看>>
大变样:Firefox新一代UI “Photon”设计曝光
查看>>
云计算、IoT和SDN为企业网带来最大的问题
查看>>