选自:
摘要:Hook的出现为开发者希望通过一个程序改变其他程序的某些行为的想法开拓了解决道路,而作为一款基于Hook的代码修改框架,Cydia Substrate可以修改任何主进程的代码,本文作者以广告注入的实战详细介绍了Hook的过程。
了解Hook
还没有接触过Hook技术读者一定会对Hook一词感觉到特别的陌生,Hook英文翻译过来就是“钩子”的意思,那我们在什么时候使用这个“钩子”呢?
我们知道,在Android操作系统中系统维护着自己的一套事件分发机制。应用程序,包括应用触发事件和后台逻辑处理,也是根据事件流程一步步的向下执行。而“钩子”的意思,就是在事件传送到终点前截获并监控事件的传输,像个钩子勾上事件一样。并且能够在勾上事件时,处理一些自己特定的事件。如下图所示:
Hook的这个本领,使它能够将自身的代码“融入”被勾住(Hook)的程序的进程中,成为目标进程的一个部分。我们也知道,在Android系统中使用了沙箱机制,普通用户程序的进程空间都是独立的,程序的运行彼此间都不受干扰。
这就使我们希望通过一个程序改变其他程序的某些行为的想法不能直接实现,但是Hook的出现给我们开拓了解决此类问题的道路。当然,根据Hook对象与Hook后处理的事件方式不同,Hook还分为不同的种类,如消息Hook、API Hook等。
hook框架
1-CydiaSubstrate框架
http://www.cydiasubstrate.com/
http://www.cydiasubstrate.com/id/20cf4700-6379-4a14-9bc2-853fde8cc9d1/
如果使用过苹果手机的用户应该对Cydiasubstrate框架来说一点都不会陌生,因为Cydiasubstrate框架为苹果用户提供了越狱相关的服务框架。
Cydiasubstrate原名MobileSubstrate(类库中都是以MS开头),作者为大名鼎鼎的Jay Freeman(saurik)。
当然 Cydiasubstrate 也推出了 Android版。Cydia Substrate是一个代码修改平台。它可以修改任何主进程的代码,不管是用Java还是C/C++(native代码)编写的。
1.1 Cydiastrate框架的配置
- 在Android设备中安装Cydiasubstrate框架的本地服务应用substrate.apk
我们可以再其官网下载到:
http://www.cydiasubstrate.com/download/com.saurik.substrate.apk
当然,我们安装substrate后,需要“Link Substrate Files”(连接本地的Substrate服务文件),这一步是需要Root权限的,连接后还需要重启设备才能够生效。
- 开发环境中配置Cydiasubstrate库
- Cydiasubstrate官方建议在Android SDK Manager中添加它们插件地址的方式进行更新下载。
如:在用户自定义网址中添加
http://asdk.cydiasubstrate.com/addon.xml。
通过使用Android SDK Manager工具下载完Cydiasubstrate框架后,其存储于目录${ANDROID_HOME}\sdk\extras\saurikit\cydia_substrate下。
- 但是,由于Android SDK Manager在国内使用起来存在很多的限制,下载的时候也不是非常稳定,所以还是建议大家直接去官网下载开发库。
官方下载地址为:http://asdk.cydiasubstrate.com/zips/cydia_substrate-r2.zip。
下载完成后,将得到的所有文件(很多的jar包与so库),都拷贝都Android项目下的libs文件夹中,就可以直接使用了。
其中的substrate.h头文件与lib文件夹下的so文件是提供在使用NDK进行原生Hook程序开发中的函数支持库。
TIPS:CydiaSubstrate框架对于inline Hook的操作目前还是存在一些bug,使用的时候可能会出现崩溃的现象,部分使用了国内定制的ROM的设备在使用时必现bug
- Cydiasubstrate官方建议在Android SDK Manager中添加它们插件地址的方式进行更新下载。
1.2 CydiaSubstrate的API简易版
CydiaSubstrate怎么用?其实很简单,CydiaSubstrate提供了三个静态的方法工具类,我们只需要学会使用它就好 。
MS.hookClassLoad 拿到指定Class载入时的通知
MS.hookMethod 使用一个Java方法去替换另一个Java方法
MS.moveUnderClassLoader 使用不同的ClassLoder重载对象
/**
* Hook一个指定的Class
*
* @param name Class的包名+类名,如android.content.res.Resources
* @param hook 成功Hook一个Class后的回调
*/
void hookClassLoad(String name, MS.ClassLoadHook hook);
/**
* Hook一个指定的方法,并替换方法中的代码
*
* @param _class Hook的calss
* @param member Hook class的方法参数
* @param hook 成功Hook方法后的回调
* @param old Hook前方法,类似C中的方法指针
*/
void hookMethod(Class _class, Member member, MS.MethodHook hook, MS.MethodPointer old);
/**
* Hook一个指定的方法,并替换方法中的代码
*
* @param _class Hook的calss
* @param member Hook class的方法参数
* @param alteration
*/
void hookMethod(Class _class, Member member, MS.MethodAlteration alteration);
/**
* 使用一个ClassLoader重载一个对象
*
* @param loader 使用的ClassLoader
* @param object 带重载的对象
* @return 重载后的对象
*/
<T> T moveUnderClassLoader(ClassLoader loader, T object);
1.3 实战演练a:文字变色——系统api修改
这个工程的目标是,hook手机上面的文字颜色,即短信,联系人之类的文字颜色
- AndroidManifest.xml配置使用Cydiasubstrate相关的配置与权限
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<meta-data android:name="com.saurik.substrate.main"
android:value=".Main"/>
</application>
<uses-permission android:name="cydia.permission.SUBSTRATE"/>
</manifest>
1.我们使用Application对象,程序最优先启动的
2.这里设置主类,跟正常的安卓工程不同,这里相当于开发一个插件
3.这个权限,代表着cydia能够识别他为自己的插件,可以启动它
- 获取欲勾的第三方包的类名
android.content.res.Resources系统包名
- 主类操作
这里initialize()是初始化相关东西,这个相当于main函数入口
package com.example.cydiaexample;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import android.util.Log;
import android.widget.Toast;
import com.saurik.substrate.MS;
import com.saurik.substrate.MS.ClassLoadHook;
//hook 方法
public class Main {
public static void initialize() {
//设置需要hook的类
MS.hookClassLoad("android.content.res.Resources", new MS.ClassLoadHook() {
public void classLoaded(Class<?> resources) {
// ... code to modify the class when loaded
//定义方法
Method getColor;
try {
//hook的方法为获取颜色的方法
getColor = resources.getMethod("getColor", Integer.TYPE);
} catch (NoSuchMethodException e) {
getColor = null;
}
if (getColor != null) {
//新建一个MethodPointer对象,hookMethod方法中要使用
final MS.MethodPointer old = new MS.MethodPointer();
//开始hook方法,写入自己想改变的数据
MS.hookMethod(resources, getColor, new MS.MethodHook() {
public Object invoked(Object resources, Object... args)
throws Throwable
{
int color = (Integer) old.invoke(resources, args);
//返回颜色
return color & ~0x0000ff00 | 0x00ff0000;
}
}, old);
}
}
});
}
}
- 测试
这里我们没有Main和launch的入口,所以这个程序是没有图标显示到手机桌面的,因为我们已经指定了他是cydia的一个插件,所以这个时候,我们重启手机即可启动此插件
1.3 实战演练b:广告浮层——勾他人应用
听到这个题目,我估计很多打包党也已经迫不及待了,稍安勿躁。靠广告是赚不了大钱的,笔者也是一个打包党。程序员还是以成长发展为主,一时的快钱带会让你在编程的路上越走越远。
回到正题,使用Cydiasubstrate框架我们能够任意的Hook系统中的Java API,当然其中也用到了很多的反射机制,那么除了系统中给开发者提供的API以外,我们能否也Hook应用程序中的一些方法呢?答案是肯定的。下面我们就以一个实际的例子讲解一下如何Hook一个应用程序。
下面我们针对Android操作系统的浏览器应用,Hook其首页Activity的onCreate方法(其他方法不一定存在,但是onCreate方法一定会有),并在其中注入我们的广告。根据上面对Cydiasubstrate的介绍,我们有了一个简单的思路。
- AndroidManifest.xml配置
- 首先,我们根据某广告平台的规定,在我们的AndroidManifest.xml文件中填入一些广告相关的ID
- 并且在AndroidManifest.xml文件中填写一些使用Cydiasubstrate相关的配置与权限。
- 当然,我们还会声明一个广告的Activity,并设置此Activity为背景透明的Activity,为什么设置透明背景的Activity,如下图:
<!-- 广告相关的权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.GET_TASKS" />
<!-- 加入substrate权限 -->
<uses-permission android:name="cydia.permission.SUBSTRATE" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<!-- 广告相关参数 -->
<meta-data
android:name="APP_ID"
android:value="c62bd976138fa4f2ec853bb408bb38af" />
<meta-data
android:name="APP_PID"
android:value="DEFAULT" />
<!-- 声明substrate的注入口味Main类 -->
<meta-data
android:name="com.saurik.substrate.main"
android:value="com.example.hookad.Main" />
<!-- 透明无动画的广告Activity -->
<activity
android:name="com.example.hookad.MainActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<!-- 广告的action -->
<action android:name="com.example.hook.AD" />
</intent-filter>
</activity>
</application>
- 获取欲勾的第三方包的类名
我们希望使用MS.hookClassLoad方式找到浏览器主页的Activity名称。
这里我们使用adb shell下使用dumpsys activity命令找到浏览器主页的Activity名称为com.android.browser.BrowserActivity。
- 主类操作
Cydiasubstrate的主入口Main类,依照之前的步骤新建一个包含有initialize方法的Main类
public class Main {
/**
* substrate 初始化后的入口
*/
static void initialize() {
//Hook 浏览器的主Activity,BrowserActivity
MS.hookClassLoad("com.android.browser.BrowserActivity", new MS.ClassLoadHook() {
public void classLoaded(Class<?> resources) {
Log.e("test", "com.android.browser.BrowserActivity");
// 获取BrowserActivity的onCreate方法
Method onCreate;
try {
onCreate = resources.getMethod("onCreate", Bundle.class);
} catch (NoSuchMethodException e) {
onCreate = null;
}
if (onCreate != null) {
final MS.MethodPointer old = new MS.MethodPointer();
// hook onCreate方法
MS.hookMethod(resources, onCreate, new MS.MethodHook() {
public Object invoked(Object object, Object...args) throws Throwable {
Log.e("test", "show ad");
// 执行Hook前的onCreate方法,保证浏览器正常启动
Object result = old.invoke(object, args);
// 没有Context
// 执行一个shell 启动我们的广告Activity
CMD.run("am start -a com.example.hook.AD");
return result;
}
}, old);
}
}
});
}
}
- 测试
对于启动的广告MainActivity,在其中就是弹出一个插屏广告。当然可也可是其他形式的广告或者浮层,内容比较简单这里不做演示了。对整个项目进行编译,运行。这个时候我们重新启动Android自带的浏览器的时候发现,浏览器会弹出一个广告弹框。
从上面的图片我们可以看出来了,之前我们设置插屏广告MainActivity为无标题透明(Theme.Translucent.NoTitleBar)就是为了使得弹出来的广告与浏览器融为一体,让用户感觉是浏览器弹出的广告。也是恶意广告程序为了防止自身被卸载掉的一些通用隐藏手段。
这里演示的注入广告是通过Hook指定的Activity中的onCreate方法来启动一个广告Activity。当然,这里我们演示的Activity只是简单的弹出来了一个广告。如果启动的Activity带有恶意性,如将Activity做得与原Activity一模一样的钓鱼Activity,那么对于移动设备用户来说是极具欺骗性的。