zoukankan      html  css  js  c++  java
  • APK包与类更改分析

    360APK包与类更改分析

    1 题目要求

    这是360的全球招募无线攻防中的第二题,题目要求如下:

    1)请以重打包的形式将qihootest2.apk的程序包名改为 "com.qihoo.crack.StubApplication",使得在同一手机上面可以重复安装并正确运行;

    2)请写个Application类,并在Manifest里面注册你的Application。同时要求使用该Application加载原包的Application;

    题目所用apk下载地址:

    http://pan.baidu.com/share/link?shareid=644965540&uk=839654349

    2 第一小问更改方法

    首先,我们需要将apk反编译为smali文件。这里推荐使用apkIDE。

    2.1 确定要修改的地方

    显然,哪里用了包名,哪里就需要修改:

    ①AndroidManifest.xml:package, application name, contentProvider。

    ②smali文件中:所有com/qihoo/test改为com/qihoo/crack/StubApplication、所有com.qihoo.test改为com.qihoo.crack.StubApplication。

    ③目录结构:将原目录com.qihoo.test改为com.qihoo.crack,然后在这个目录里面新建子目录StubApplication,最后将原来属于test目录的所有文件copy到StubApplication中。

    至此,在smali中的修改工作就告一段落了。但仅仅这样是不行的,因为在APK中会调用libqihooTest.so中的native函数packageNameCheck()。这个函数是使用动态注册的方式进行注册的,在JNI_OnLoad函数中完成注册功能,使得原APK中的com.qihoo.test.MainActivity.packageNameCheck()同so中的packageNameCheck()函数相关联。我们可以把libqihootest.so拖到ida中查看其中的JNI_OnLoad函数,就可以发现该函数会调用如下JNI方法:

    jclass testClass = (*env)->FindClass(env, “com/qihoo/test/Mainactivity”);

    Findclass的字符串参数使用硬编码写在so中。如果更改后的包名短于原来的包名,那么我们可以使用winhex直接修改这个so,不过这个方法明显不适合于本程序,所以只能另辟蹊径了。

    2.2 通过packageNameCheck函数检查

    前面的分析发现在libqihootest.so中的JNI_OnLoad函数中会调用FindClass(env, “com/qihoo/test/Mainactivity”),而我们更改过后的smali文件中是没有这个类的。所以如果不设法解决这个问题,程序肯定无法正常运行。

    分析到此,解决方法就出来了:

    1)在原来的smali文件中创建一个test.MainActivity类(注意是在com.qihoo目录下新建目录test,再在test目录下新建MainActivity类),然后将native方法都移植到这一个类中。

    2)想法跳过JNI_OnLoad函数:也就是说,我们既需要运行libqihootest.so中的packageNameCheck等native函数,又不运行JNI_OnLoad函数。

    我选择第二种。下面来详细分析如何实现第二种方法。

    我们知道,一般情况下JNI_OnLoad函数是在使用System.loadLibrary载入so的时候第一个运行的native函数,而如果使用javah方式(静态注册)编写native代码的话,就可以省略JNI_OnLoad函数,所以我们有必要弄清JNI_OnLoad的实现机制。

    System.loadLibrary也是一个native方法,它的调用的过程是:

    Dalvik/vm/native/java_lang_Runtime.cpp:

    Dalvik_java_lang_Runtime_nativeLoad ->Dalvik/vm/Native.cpp:dvmLoadNativeCode

    dvmLoadNativeCode

    打开函数dvmLoadNativeCode,可以找到以下代码:

    handle = dlopen(pathName, RTLD_LAZY); //获得指定库文件的句柄,

    //这个库文件就是System.loadLibrary(pathName)传递的参数

    …..

    vonLoad = dlsym(handle, "JNI_OnLoad"); //获取该文件的JNI_OnLoad函数的地址

       if (vonLoad == NULL) { //如果找不到JNI_OnLoad,就说明这是用javah风格的代码了,那么就推迟解析

     LOGD("No JNI_OnLoad found in %s %p, skipping init",pathName, classLoader); //这句话我们在logcat中经常看见!

    }else{

    ….

    }

     

    从上面的代码可以看出:System.loadLibrary函数首先会通过dlopen获取so文件的句柄,然后使用dlsym获取该JNI_OnLoad函数的地址,如果该地址为空,就说明没有此函数(这并不是错误)——隐喻就是so库使用javah的编码方式,此时不需要解析so中的函数,而是等java层调用native函数的时候再解析。

    分析到此,我们就已经找到绕过JNI_OnLoad函数的方法了:参照System.loadLibrary的方式,使用dlopen、dlsym函数直接调用libqihootest.so中的packageNameCheck函数

    C代码如下:

    /*callQihooSo.c*/

     

    #include <string.h>

    #include <stdio.h>

    #include <jni.h>

    #include <dlfcn.h>  //使用dlopen等函数的头文件

    #include <android/log.h>

     

    #define LOG_TAG "360TEST2"

    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

     

    /*这里我直接使用javah的方式编写native代码*/

    JNIEXPORT  Java_com_qihoo_crack_StubApplication_MainActivity_packageNameCheck( JNIEnv* env,  jobject obj){

    void* filehandle =dlopen("/data/data/com.qihoo.crack.StubApplication/lib/libqihooTest.so", RTLD_LAZY ); //获取libqihooTest.so的句柄

          if(filehandle){

            void (*packageNameCheck)(JNIEnv *,jobject);

            packageNameCheck = (void (*)(JNIEnv *,jobject)) dlsym(filehandle, "packageNameCheck"); //找到.so文件中的函数

            if(packageNameCheck){

              packageNameCheck(env, obj); //传递参数      }

            else{

              LOGI("get packageNameCheck func failed!");

            }

            LOGI("success!");

          }else{

              LOGI("get file handle failed!");

          }

         return ;

    }

     

    JNIEXPORT   Java_com_qihoo_crack_StubApplication_MainActivity_applicatioNameCheck( JNIEnv* env,

                                                      jobject obj){

          void*  filehandle = dlopen("/data/data/com.qihoo.crack.StubApplication/lib/libqihooTest.so", RTLD_LAZY );

          if(filehandle){

            void (*applicatioNameCheck)(JNIEnv *,jobject);

            applicatioNameCheck = (void (*)(JNIEnv *,jobject)) dlsym(filehandle, "applicatioNameCheck"); //找到.so文件中的函数

            if(applicatioNameCheck){

            applicatioNameCheck(env, obj); //传递参数

              return ;

            }

            else{

              LOGI("get applicatioNameCheck func failed! ");

            }

            LOGI("success!");

          }else{

              LOGI("get file handle failed!");

          }

        return ;

    }

    Android.mk如下:

    LOCAL_PATH:= $(call my-dir)

    include $(CLEAR_VARS)

    LOCAL_LDLIBS := -L . -ldl -llog  #一定要加入ldl这个库,dyopen等函数需要

    LOCAL_MODULE := callQihooSo

    include $(BUILD_SHARED_LIBRARY)

    接着像正常编译动态库文件一样编译。编译完成后将libcallQihooSo.so和libqihooTest.so一起放到反编译文件夹的lib/armeabi目录中,然后将MainAcitivity.smali中的System.loadLibrary(“qihooTest”),改为System.loadLibrary(“callQihooSo”),回编译、签名即可。

    2.3 总结

    第一种方法个人觉得实用性不高,所以就不加以详细介绍了。第二种方法本质上就是一个调用第三方库的问题。只是有一点不同的就是:一般情况下调用第三方库需要在java层使用System.loadLibrary将第三方库文件加载到内存中,然后就可以直接使用第三方库中的函数,而不需要dlopen等函数了(详情参考http://blog.csdn.net/jiuyueguang/article/details/9450597)。

    但本题是不能使用System.loadLibrary加载libqihooTest.so的,所以只能使用dlopen机制实现了。

    3 第二小问的实现方法

    主要原理就是参考文档:http://blogs.360.cn/blog/proxydelegate-application/

    该文档介绍了Proxy/delegation Application框架的原理和实现。这里详细地描述下它的实现过程。

    3.1 创建一个新的android工程

    创建该工程的目的是为了得到实现这个框架的smali文件(反编译此apk),然后将相关的smali文件添加到题目apk反编译出来的smali文件夹的合适位置(避免我们直接写smali文件,减少工作量)。所以,为了方便文件的移植,我们新建工程的包名命名为“com.qihoo.crack.StubApplication”,工程的结构图如下图所示:

    3.2 开始编写代码

    首先,创建一个ProxyApplication类:

    package com.qihoo.crack.StubApplication;

    import java.lang.reflect.Field;

    import java.lang.reflect.InvocationTargetException;

    import java.lang.reflect.Method;

    import java.util.ArrayList;

    import android.app.Application;

    import android.content.Context;

    import android.content.pm.ApplicationInfo;

    import android.content.pm.PackageManager;

    import android.content.pm.PackageManager.NameNotFoundException;

    import android.os.Bundle;

    import android.text.InputFilter.AllCaps;

    import android.util.Log;

    public abstract class ProxyApplication extends Application{

           protected abstract void initProxyApplication();

           private static Context pContext = null; //保存ProxyApp的mContext,后面有用

           private static String TAG = "proxy";

           @Override

           public void onCreate() {

                  // TODO Auto-generated method stub

                  super.onCreate();

                  String className = "android.app.Application"; //默认的Application名              String key = "DELEGATE_APPLICATION_CLASS_NAME";

                  try {

                         ApplicationInfo appInfo = getPackageManager().getApplicationInfo(super.getPackageName(), PackageManager.GET_META_DATA);

                         Bundle bundle = appInfo.metaData;

                         if(bundle != null && bundle.containsKey(key)){

                                className = bundle.getString(key);

                                if(className.startsWith(".")){

                                       className = super.getPackageName() + className;

                                }

                         }

                        

                         Class delegateClass = Class.forName(className, true, getClassLoader());

                         Application delegate = (Application) delegateClass.newInstance();

                        

              //获取当前Application的applicationContext

    Application proxyApplication = (Application)getApplicationContext();

                        

    /*使用反射一一替换proxyApplicationContext,这是本程序的重难点*/

                         //首先更改proxy的mbaseContext中的成员mOuterContext

                         Class contextImplClass = Class.forName("android.app.ContextImpl");

                         Field mOuterContext = contextImplClass.getDeclaredField("mOuterContext");

                         mOuterContext.setAccessible(true);

                         mOuterContext.set(pContext, delegate);

                        

                         //再获取context的mPackageInfo变量对象

                         Field mPackageInfoField = contextImplClass.getDeclaredField("mPackageInfo");

                         mPackageInfoField.setAccessible(true);

                         Object mPackageInfo = mPackageInfoField.get(pContext);

                         Log.d(TAG, "mPackageInfo: "+ mPackageInfo);

                        

                         //修改mPackageInfo中的成员变量mApplication

                         Class loadedApkClass = Class.forName("android.app.LoadedApk");  //mPackageInfo是android.app.LoadedApk类

                         Field mApplication = loadedApkClass.getDeclaredField("mApplication");

                         mApplication.setAccessible(true);

                         mApplication.set(mPackageInfo, delegate);

                        

                         //然后再获取mPackageInfo中的成员对象mActivityThread

                         Class activityThreadClass = Class.forName("android.app.ActivityThread");

                         Field mAcitivityThreadField = loadedApkClass.getDeclaredField("mActivityThread");

                         mAcitivityThreadField.setAccessible(true);

                         Object mActivityThread = mAcitivityThreadField.get(mPackageInfo);

                        

                         //设置mActivityThread对象中的mInitialApplication

                         Field mInitialApplicationField = activityThreadClass.getDeclaredField("mInitialApplication");

                         mInitialApplicationField.setAccessible(true);

                         mInitialApplicationField.set(mActivityThread, delegate);

                        

                         //最后是mActivityThread对象中的mAllApplications,注意这个是List

                         Field mAllApplicationsField = activityThreadClass.getDeclaredField("mAllApplications");

                         mAllApplicationsField.setAccessible(true);

                         ArrayList<Application> al = (ArrayList<Application>)mAllApplicationsField.get(mActivityThread);

                         al.add(delegate);

                         al.remove(proxyApplication);

                        

                        

                         //设置baseContext并调用onCreate

                         Method attach = Application.class.getDeclaredMethod("attach", Context.class);

                         attach.setAccessible(true);

                         attach.invoke(delegate, pContext);

                         delegate.onCreate();

                                                           

                                             

                  } catch (NameNotFoundException e) {

                         // TODO Auto-generated catch block

                         e.printStackTrace();

                  } catch (ClassNotFoundException e) {

                         // TODO Auto-generated catch block

                         e.printStackTrace();

                  } catch (InstantiationException e) {

                         // TODO Auto-generated catch block

                         e.printStackTrace();

                  } catch (IllegalAccessException e) {

                         // TODO Auto-generated catch block

                         e.printStackTrace();

                  } catch (NoSuchFieldException e) {

                         // TODO Auto-generated catch block

                         e.printStackTrace();

                  } catch (NoSuchMethodException e) {

                         // TODO Auto-generated catch block

                         e.printStackTrace();

                  } catch (IllegalArgumentException e) {

                         // TODO Auto-generated catch block

                         e.printStackTrace();

                  } catch (InvocationTargetException e) {

                         // TODO Auto-generated catch block

                         e.printStackTrace();

                  }     

           }

     

           @Override

           public String getPackageName() {

                  // TODO Auto-generated method stub

                  return "Learning And Sharing!";

           }

           @Override

           protected void attachBaseContext(Context base) {

                  // TODO Auto-generated method stub

                  super.attachBaseContext(base);

                  pContext = base;

                  Log.d(TAG, "attachBaseContext");

                  initProxyApplication();

           }

    }

    这个代码是严格按照参考文档的框架写的。所以应当参照该文档阅读这些代码。这里主要说一说我在替换API层所有Application引用时遇到的困难。

    由于我起先并不了解Android的context相关知识,所以对这一块完全是云里雾里。给大牛们留过小字条,也写过邮件,不过,大牛们都比较忙,所以一直没能得到解答。直到前段时间,请教了群里的“沧海一声呵”朋友(他才大一,你敢信?!!),才得到解决。

    以下部分大牛们可以略过啦,现假设读者也同我一样是个android初学者。那么,要想理解和解决“替换API层的所有Application引用”,我们必须深刻理解android的Context机理。这方面的资料可以参考:

    http://blog.csdn.net/qinjuning/article/details/7310620

    以及我的另一篇博文:

    http://www.cnblogs.com/wanyuanchun/p/3828603.html

    当然,仅仅这篇文档,是不能让我们完全理解context的,我们还需要通过自己阅读分析Android关于context的源码来加以理解。比如在上面的代码中有一句:

    //修改mPackageInfo中的成员变量mApplication

                         Class loadedApkClass = Class.forName("android.app.LoadedApk");  //mPackageInfo是android.app.LoadedApk类

    如果我们不阅读源码的话,是不可能知道mPackageInfo是android.app.LoadedApk类,而非想当然的android.app.PackageInfo类。

    好了,由于篇幅有限,就不过多延伸了。下面继续介绍框架实现。

    ProxyApplication类完成之后,就是编写MyProxyApplication类了。该类继承至ProxyApplication,代码很简单:

    package com.qihoo.crack.StubApplication;

    import android.app.Application;

    import android.content.Context;

    import android.content.ContextWrapper;

    import android.content.pm.ApplicationInfo;

    import android.content.pm.PackageManager;

    import android.content.pm.PackageManager.NameNotFoundException;

    import android.os.Bundle;

    import android.util.Log;

    public class MyProxyApplication extends ProxyApplication{

           @Override

           protected void initProxyApplication() {

                  // TODO Auto-generated method stub

                  //在这里替换surrounding,实现自定义的classloader等功能

                  Log.d("proxy", "initProxyApplication");

           }

    }

    由于题目只是要求加载Delegation Application,所以我们只在initProxyApplication函数中打印log即可。

    最后就是修改AndroidManifest.xml文档了,修改后的文档为:

    <application

            android:name="com.qihoo.crack.StubApplication.MyProxyApplication"

            android:allowBackup="true"

            android:icon="@drawable/ic_launcher"

            android:label="@string/app_name"

            android:theme="@style/AppTheme" >

            <meta-data

                android:name="DELEGATE_APPLICATION_CLASS_NAME"

                android:value="com.qihoo.crack.StubApplication" >   #注意,这里一定要填写正确,否则当我们检测当前application的时候,就会发现得到的application要么是默认的,要么是MyProxyApplication!

            </meta-data>

            <activity

                android:name="com.qihoo.crack.StubApplication.MainActivity"

                android:label="@string/app_name" >

                <intent-filter>

                    <action android:name="android.intent.action.MAIN" />

     

                    <category android:name="android.intent.category.LAUNCHER" />

                </intent-filter>

            </activity>

        </application>

    到此我们的proxyDemo APK已经编写完毕,将其打包成APK之后,反编译这个APK,然后提取出里面的MyProxyApplication.smali和ProxyApplication.smali文档,放到题目APK的smali/com/qihoo/crack/StubApplication目录中。再按照同样的方式修改题目APK的AndroidManifest.xml,编译、签名,生成APK即可。

    最终效果图如下:

      

    注意:第二个图,是错误的!正确的显示结果应该是com.qihoo.crack.StubApplication!错误原因是由于我当时在更改AndroidManifest.xml的时候,将META-DATA里面的value值写错了~~详情可见上面红字部分。

    总结

    根据我个人的理解,此题第二问的应用范围还是很广的,如下文提及的APK加壳方案:

    http://blog.csdn.net/androidsecurity/article/details/8678399

    OK,技术方面就说到这里,作为一个初学者,我想谈谈一点技术之外的话题。

    众所周知,解决一个问题,并不是唯一的目的,通过解决问题来学习知识才是我们追求的目标。同样的,我们在分享自己解决某个问题的方法技巧时,最好多花点时间叙述“我为什么要这么做”,而不是仅仅提及“我用什么方法解决了什么问题”。因为只有这样,才能做到真正的知识分享,我们才能向国外那样拥有很好的学习氛围(这个大家应该是深有体会吧~~)。所以我在这里厚颜代表广大的初学者们向各位大牛请求:在分享方法技术的时候,请多花点时间讲解“我为什么要这么做”,以及“该如何学到这方面的知识”吧!对于你们来说可能会耗费半小时的时间,但对新手来说可能就是半个月都不止了….

  • 相关阅读:
    Ubuntu使用命令行打印文件
    Spring ConditionalOnProperty
    Spring EnableWebMvc vs WebMvcConfigurationSupport
    commons-httpclient中的超时设置
    jdb调试命令
    caching redirect views leads to memory leak (Spring 3.1)
    Clojure web初探
    在现有原生开发Android项目中集成hbuilder开发
    MessageBoard
    CSS布局(五) 圣杯布局
  • 原文地址:https://www.cnblogs.com/wanyuanchun/p/3829918.html
Copyright © 2011-2022 走看看