Android三大hook框架

目前Android主流的hook框架有Xposed、Substrate和frida三种,下面依次介绍三种框架的原理和特点:

Xposed

Xposed是一个在andoid平台上比较成熟的hook框架,可以完美的在dalvik虚拟机上做到hook任意java方法

原理

Android系统中所有的app进程都是有zygote进程孵化而来的,Xposed会替换/system/bin/app_process文件,替换后的app_process在启动过程中会加载XposedBridge.jar这个jar包,从而完成对Zygote进程及其创建的Dalvik虚拟机的劫持

运行条件

  1. 由于需要替换app_process,故需要root或在recovery下刷机
  2. 重启手机后插件才会生效

使用方法

  • 1.AndroidManifest.xml中添加Xposed

需在Application Node中添加三个Meta(xposedmodule,xposedminversion和xposeddescription)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"  
package="com.example.xposedtest"
android:versionCode="1"
android:versionName="1.0" >

<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="19" />

<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<span style="color:#ff0000;">
<!-- 以下三个为需要添加的meta-->
<meta-data android:value="true" android:name="xposedmodule"/>
<meta-data android:value="30" android:name="xposedminversion"/>
<meta-data android:value="this is a test" android:name="xposeddescription"/></span>
</application>

</manifest>

  1. assets目录下新建一个xposed_init文件

  2. 重写XC_MethodHook的两个方法beforeHookedMethod和afterHookedMethod

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    package com.example.xposedtest;
    import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
    import android.graphics.Color;
    import android.util.Log;
    import de.robv.android.xposed.XposedBridge;
    import android.widget.TextView;
    import de.robv.android.xposed.IXposedHookLoadPackage;
    import de.robv.android.xposed.XC_MethodHook;
    import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
    public class XposedTest implements IXposedHookLoadPackage{ //实现IXposedHookLoadPackage接口

    public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable{
    //判断Hook的包是否正确
    if(!lpparam.packageName.equals("com.android.systemui"))
    {
    XposedBridge.log("not found package");
    return;
    }
    //找到要Hook的类名和函数,创建自己的类
    findAndHookMethod("com.android.systemui.statusbar.policy.Clock",lpparam.classLoader,"updateClock",new XC_MethodHook(){

    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
    // 这里的调用在正常函数调用之前执行,由于本例是Hook时间显示,需要在显示之后调用,所以省略
    }

    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable{
    // 这里的调用在正常函数调用之后执行
    XposedBridge.log("aaaaaaaaaaaaaa"+lpparam.packageName);
    TextView tv=(TextView)param.thisObject;
    String text=tv.getText().toString();
    tv.setText(text+":)");
    tv.setTextColor(Color.RED);
    }
    });

    }
    }

hook检测

1.通过PackageManager查看安装列表,判断是否有安装Xposed Installer相关的软件包

1
2
3
4
5
6
7
PackageManager packageManager = context.getPackageManager();
List applicationInfoList = packageManager.getInstalledApplications(PackageManager.GET_META_DATA);
for (ApplicationInfo applicationInfo: applicationInfoList) {
if (applicationInfo.packageName.equals("de.robv.android.xposed.installer")) {
// Xposed find
}
}

通常情况下使用Xposed Installer框架都会屏蔽对其的检测,即Hook掉PackageManager的getInstalledApplications方法的返回值,以便过滤掉de.robv.android.xposed.installer来躲避这种检测。

2.根据调用栈判断

Xposed Installer框架对每个由Zygote孵化的App进程都会介入,因此在程序方法异常栈中就会出现Xposed相关的“身影”,我们可以通过制造异常Exception来读取异常堆栈,检查其中是否存在Xposed的调用方法。

1
2
3
4
5
6
7
try {
throw new Exception("blah");
} catch(Exception e) {
for (StackTraceElement stackTraceElement: e.getStackTrace()) {
// stackTraceElement.getClassName() stackTraceElement.getMethodName() 是否存 在Xposed
}
}

以下为使用xposed的调用栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
E/GEnvironment: no such table: preference (code 1): while compiling: SELECT keyguard_show_livewallpaper FROM preference
...
at com.meituan.test.extpackage.ExtPackageManager.checkUpdate(ExtPackageManager.java:127)
at com.meituan.test.MiFGService$1.run(MiFGService.java:41)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5072)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
...
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:609)
at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:132) //发现Xposed模块
at dalvik.system.NativeStart.main(Native Method)

3.读取/proc/self/maps判断

无论在Java层做何种检测,Xposed都可以通过Hook相关的API并返回指定的结果来绕过检测,只要有方法就可以被Hook。如果仅在Java层检测就显得很徒劳,为了有效提搞检测准确率,就须做到Java和Native层同时检测。

在Native层读取/proc/self/maps文件,判断App自身加载的库中是否存在XposedBridge.jar、相关的Dex、Jar和So库等文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
bool is_xposed()
{
bool rel = false;
FILE *fp = NULL;
char* filepath = "/proc/self/maps";
...
string xp_name = "XposedBridge.jar";
fp = fopen(filepath,"r"))
while (!feof(fp))
{
fgets(strLine,BUFFER_SIZE,fp);
origin_str = strLine;
str = trim(origin_str);
if (contain(str,xp_name))
{
rel = true; //检测到Xposed模块
break;
}
}
...
}

Frida

frida是一个跨平台的hook框架,可以hook Java和native层,且不需要每次都重启手机。官网:http://www.frida.re/

hook对抗

  1. 遍历进程,判断frida-server是否运行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public boolean checkRunningProcesses() {
    boolean returnValue = false;
    // Get currently running application processes
    List<RunningServiceInfo> list = manager.getRunningServices(300);
    if(list != null){
    String tempName;
    for(int i=0;i<list.size();++i){
    tempName = list.get(i).process;
    if(tempName.contains("fridaserver")) {
    returnValue = true;
    }
    }
    }
    return returnValue;
    }
  2. fridaserver 默认的 TCP 端口是 27047,可以检查这个端口是否开放

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    boolean is_frida_server_listening() {
    struct sockaddr_in sa;
    memset(&sa, 0, sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_port = htons(27047);
    inet_aton("127.0.0.1", &(sa.sin_addr));
    int sock = socket(AF_INET , SOCK_STREAM , 0);
    if (connect(sock , (struct sockaddr*)&sa , sizeof sa) != -1) {
    /* Frida server detected. Do something… */
    }
    }
  3. 每个开放的端口发送 D-Bus 的认证消息,哪个端口回复了哪个就是 fridaserver

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    for(i = 0 ; i <= 65535 ; i++) {
    sock = socket(AF_INET , SOCK_STREAM , 0);
    sa.sin_port = htons(i);
    if (connect(sock , (struct sockaddr*)&sa , sizeof sa) != -1) {
    __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "FRIDA DETECTION [1]: Open Port: %d", i);
    memset(res, 0 , 7);
    // send a D-Bus AUTH message. Expected answer is “REJECT"
    send(sock, "\x00", 1, NULL);
    send(sock, "AUTH\r\n", 6, NULL);
    usleep(100);
    if (ret = recv(sock, res, 6, MSG_DONTWAIT) != -1) {
    if (strcmp(res, "REJECT") == 0) {
    /* Frida server detected. Do something… */
    }
    }
    }
    close(sock);
    }
  4. 内存特征码检测
    在内存中扫描 frida 的库特征 “gadgets”。例如字符串 “LIBFRIDA”在所有 frida-gadget 和 frida-agent 的版本中都有出现。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    static char keyword[] = "LIBFRIDA";
    num_found = 0;
    int scan_executable_segments(char * map) {
    char buf[512];
    unsigned long start, end;
    sscanf(map, "%lx-%lx %s", &start, &end, buf);
    if (buf[2] == 'x') {
    return (find_mem_string(start, end, (char*)keyword, 8) == 1);
    } else {
    return 0;
    }
    }
    void scan() {
    if ((fd = my_openat(AT_FDCWD, "/proc/self/maps", O_RDONLY, 0)) >= 0) {
    while ((read_one_line(fd, map, MAX_LINE)) > 0) {
    if (scan_executable_segments(map) == 1) {
    num_found++;
    }
    }
    if (num_found > 1) {
    /* Frida Detected */
    }
    }

Substrate

Substrate适用于对native层的hook

hook对抗

  1. 读取/proc/self/maps

libsubstrate.so和libsubstrate-dvm.so两个文件为Substrate必载入的文件,当进程maps表中出现libsubstrate-dvm.so,可以尝试去load该so文件并调用MSJavaHookMethod方法,它会返回该方法的地址即判定为恶意模块(第三方程序)。

1
2
3
4
5
6
7
8
9
10
void* lookup_symbol(char* libraryname,char* symbolname)  
{
void *imagehandle = dlopen(libraryname, RTLD_GLOBAL | RTLD_NOW);
if (imagehandle != NULL){
void * sym = dlsym(imagehandle, symbolname);
if (sym != NULL){
return sym; //发现Cydia Substrate相关模块
}
...
}

  1. 基于特征码的检测

参考资料:

  1. http://www.520monkey.com/archives/1028 Native层的Hook神器Cydia Substrate使用详解