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,技术方面就说到这里,作为一个初学者,我想谈谈一点技术之外的话题。

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

APK包与类更改分析的更多相关文章

  1. Android 4.4(KitKat)中apk包的安装过程

    原文地址:http://blog.csdn.net/jinzhuojun/article/details/25542011 事实上对于apk包的安装.4.4和之前版本号没大的区别. Android中a ...

  2. Unity - Apk包的代码与资源提取

    最近在研究如何给Unity游戏进行加密,让别人不能轻易破解你的apk包,不过网上的加密方法都是有对应的破解方法~_~!!结果加密方法没找到好的,逆向工程倒会了不少.今天就来讲解如何提取一个没做任何保护 ...

  3. Eclipse插件开发中对于Jar包和类文件引用的处理(彻底解决插件开发中的NoClassDefFoundError问题)(转)

    目的:Eclipse插件开发中,经常要引用第三方包或者是引用其他插件中的类,由于插件开发环境引用类路径的设置和运行平台引用类路径的设置不同,经常导致开发过程OK,一旦运行则出现NoClassDefFo ...

  4. websphere OSGi应用环境下服务调用saaj包加载问题分析报告

    websphere OSGi应用环境下服务调用saaj包加载问题分析报告 作者:bingjava 版权声明:本文为博主原创文章,转载请说明出处:http://www.cnblogs.com/bingj ...

  5. php获取apk包信息的方法

    /*解析安卓apk包中的压缩XML文件,还原和读取XML内容 依赖功能:需要PHP的ZIP包函数支持.*/ include('./Apkparser.php'); $appObj = new Apkp ...

  6. Robotium -- 针对apk包的测试

    在使用Robotium测试的时候,有时候,测试人员并没有代码权限,而Robotium也可以在只有apk文件进行测试,下面就介绍一下这个过程. 1.设置环境变量 安装jdk环境和sdk环境 2.安装签名 ...

  7. JAVA获取apk包的package和launchable-activity名称(完善成EXE版)

    出来混迟早是要还的. 在这一篇中https://www.cnblogs.com/sincoolvip/p/5882817.html,只是简单讲了一下获取apk包的package和launchable- ...

  8. Android 演示 DownloadManager——Android 下载 apk 包并安装

    本文内容 环境 项目结构 演示下载 参考资料 本文是 github 上 Trinea-Android-common 和 Trinea-Android-Demo 项目的一部分,将下载部分分离出来,看看如 ...

  9. String类原理分析及部分方法

    //String类原理分析及部分方法 //http://www.cnblogs.com/vamei/archive/2013/04/08/3000914.html //http://www.cnblo ...

随机推荐

  1. ES6 Proxy拦截器详解

    Proxy 拦截器 如有错误,麻烦指正,共同学习 Proxy的原意是"拦截",可以理解为对目标对象的访问和操作之前进行一次拦截.提供了这种机制,所以可以对目标对象进行修改和过滤的操 ...

  2. MySQL - UNION 和 UNION ALL 操作符

    UNION 操作符 UNION 操作符用于合并两个或多个 SELECT 语句的结果集. 请注意,UNION 内部的 SELECT 语句必须拥有相同数量的列.列也必须拥有相似的数据类型.同时,每条 SE ...

  3. tcl之基本语法—1

  4. SourceTree 跳过登录注册,直接使用

    SourceTree下载安装后,运行程序会要求你登录或注册账号才能使用, 然而登录或注册基本都收不到服务器的响应 (在国外嘛,安全起见),于是卡在此处无法使用了. 下面就来介绍一下跳过这尴尬环节的方法 ...

  5. JZOJ 4722. 跳楼机

    Description  DJL为了避免成为一只咸鱼,来找srwudi学习压代码的技巧.Srwudi的家是一幢h层的摩天大楼.由于前来学习的蒟蒻越来越多,srwudi改造了一个跳楼机,使得访客可以更方 ...

  6. psutil——获取系统信息的Python第三方模块

    本文摘自廖雪峰大神个人网站:https://www.liaoxuefeng.com/wiki/1016959663602400/1183565811281984 用Python来编写脚本简化日常的运维 ...

  7. 802. Find Eventual Safe States

    https://leetcode.com/problems/find-eventual-safe-states/description/ class Solution { public: vector ...

  8. B1023 组个最小数 (20分)

    B1023 组个最小数 (20分) 给定数字 0-9各若干个.你可以以任意顺序排列这些数字,但必须全部使用.目标是使得最后得到的数尽可能小(注意 0 不能做首位).例如:给定两个 0,两个 1,三个 ...

  9. 问题 B: 分组统计

    分组统计 问题 B: 分组统计时间限制: 1 Sec 内存限制: 32 MB 提交: 416 解决: 107 [提交][状态][讨论版][命题人:外部导入] 题目描述 先输入一组数,然后输入其分组,按 ...

  10. JVM垃圾回收原理

    原文地址:http://chenchendefeng.iteye.com/blog/455883 一.相关概念 基本回收算法 1. 引用计数(Reference Counting) 比较古老的回收算法 ...