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,技術方面就說到這里,作為一個初學者,我想談談一點技術之外的話題。

眾所周知,解決一個問題,並不是唯一的目的,通過解決問題來學習知識才是我們追求的目標。同樣的,我們在分享自己解決某個問題的方法技巧時,最好多花點時間敘述“我為什么要這么做”,而不是僅僅提及“我用什么方法解決了什么問題”。因為只有這樣,才能做到真正的知識分享,我們才能向國外那樣擁有很好的學習氛圍(這個大家應該是深有體會吧~~)。所以我在這里厚顏代表廣大的初學者們向各位大牛請求:在分享方法技術的時候,請多花點時間講解“我為什么要這么做”,以及“該如何學到這方面的知識”吧!對於你們來說可能會耗費半小時的時間,但對新手來說可能就是半個月都不止了….

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM