邻居合伙人Hook

对邻居合伙人APP登录sign签名算法的hook。

1577081242331

在登录处进行抓包操作,可以看到登录数据报中有apisign字段。

1577081216180

对APK进行反编译,搜索apisign关键字。

1577082970426

1
2
3
4
5
6
newBodyBuilder.add("data", data.toString());
newBodyBuilder.add("apisign", MD5Util.ToMD5(Constants.MD5_KEY, data.toString()));
L.d("请求地址RequestUrl=====", oldUrl.url().toString());
L.d("请求参数Params=========", data.toString());
L.json(data.toString());
return newBodyBuilder.build();

根据代码可以看到,apisign内容主要是通过MD5Util类的ToMD5()方法生成的,跳转到ToMD5()方法内容:

public static String ToMD5(String secretKey, String pstr) {
    pstr = secretKey + pstr;
    char[] hexDigits = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
    try {
        MessageDigest md5Temp = MessageDigest.getInstance("MD5");
        md5Temp.update(pstr.getBytes("UTF8"));
        char[] str = new char[(j * 2)];
        int k = 0;
        for (byte byte0 : md5Temp.digest()) {
            int i = k + 1;
            str[k] = hexDigits[(byte0 >>> 4) & 15];
            k = i + 1;
            str[i] = hexDigits[byte0 & 15];
        }
        return new String(str).toLowerCase();
    } catch (Exception e) {
        return null;
    }
}

这个方法有两个参数,一个secretkey和pstr。且为普通函数。通过静态分析,MD5Util.ToMD5()传入两个值,一个是Constants.MD5_KEY,跳转发现是Constants类的静态变量。

1577083722889

其值为:d367f4699214cec412f7c2a1d513fe05。另外一个变量则是app登录信息。

而用frida主要是对MD5Util.ToMD5()方法的两个变量进行hook。

编写如下hook脚本。

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
import frida
import sys
import time

jscode = """
Java.perform(function () {
var md5 = Java.use('com.softgarden.baselibrary.utils.MD5Util');
md5.ToMD5.implementation = function (a, b) {
send("Hook Start...");
console.log("arg1: " + a );
console.log("arg2: " + b);
var res = this.ToMD5(a, b);
console.log("return: " + res);
send("Success!");
return res;
}
});
"""

def message(message, data):
if message["type"] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)


device = frida.get_remote_device()
pid = device.spawn(["com.ljhhr.mobile"])
device.resume(pid)
time.sleep(1) # Without it Java.perform silently fails
session = device.attach(pid)
script = session.create_script(jscode)
script.on("message", message)
script.load()
sys.stdin.read()

运行如上脚本进行hook,可以看到如下结果。

1577086081315

通过多次hook可以看到第一个变量为固定的,即我们静态分析代码得到的:d367f4699214cec412f7c2a1d513fe05

第二个参数为输入登录的输入内容。至此,完成对该APP的sign算法的hook。

嘟嘟牛在线Hook

同样在登录界面,进行抓包操作。

同时对apk进行反编译。可以搜索url路径关键字:user/login,也可以搜索数据报关键字:Encrypt。

搜索user/login定位到类名:com.dodonew.online.ui.LoginActivity中的requestNetwork方法。

1577088736128

分析代码,可知数据报中的内容通过addRequestMap方法添加。跳转到com.dodonew.online.http.JsonRequest类的addRequestMap方法。

定位到我们在数据报中看到的关键字:Encrypt。

1577088836168

此处的encrypt即加密后的内容。

1
String encrypt = RequestUtil.encodeDesMap(RequestUtil.paraMap(addMap, Config.BASE_APPEND, "sign"), this.desKey, this.desIV);

通过des算法进行加密后的数据。该方法为encodeDesMap。其有三个参数。

跟踪变量desKey,desIV。跳转到com.dodonew.online.config.Config类。

1577089081679

可以得到des加密算法的key值为65102933,偏移值IV为:32028092。

跟踪encodeDesMap方法的第一个参数。RequestUtil.paraMap(addMap, Config.BASE_APPEND, “sign”)

跟进paraMap方法到com.dodonew.online.http.RequestUtil类。

1577089875523

发现存在sign,跟进md5方法到com.dodonew.online.util.Utils类。

1577090272134

可以看到md5是一个普通的方法。如果需要进行hook,直接hook参数值和返回值就行了。

hook部分的js代码如下:

1
2
3
4
5
6
7
8
9
var Utils = Java.use('com.dodonew.online.util.Utils');
Utils.md5.implementation = function (a) {
send("Hook Start md5...");
console.log("md5-arg: " + a);
var result = this.md5(a);
console.log("md5-result: " + result);
send("Success!");
return result;
}

而com.dodonew.online.http.RequestUtil类下的encodeDesMap方法是一个重载方法。

1577090685066

所以在传入需要hook的方法是要用到overload。且这里的重载参数类型为String。所以要用overload(‘java.lang.String’,’java.lang.String’,’java.lang.String’)。因为String在Java中是以类的形式存在的数据类型。同时String类在java.lang包中。
故此处的js代码为:

1
2
3
4
5
6
7
8
9
10
var RequestUtil = Java.use('com.dodonew.online.http.RequestUtil');    RequestUtil.encodeDesMap.overload('java.lang.String','java.lang.String','java.lang.String').implementation = function (data, key ,iv) {
send("Hook Start encodeDesMap...");
console.log("encodeDesMap-data: " + data);
console.log("encodeDesMap-key: " + key);
console.log("encodeDesMap-iv: " + iv);
var result = this.encodeDesMap(data, key ,iv);
console.log("encodeDesMap-result: " + result);
send("Success!");
return result;
}

python通用hook模版加载两段js代码即可对该APP进行hook。

成功hook到所有参数。

1577091207270

Soul Hook

Soul是一款社交app,此APP在模拟器中是无法启动的,如下:

1577092404464

显示SoulApp暂不支持模拟器,请稍后再试~

搜索关键字SoulApp暂不支持模拟器,请稍后再试~定位到cn.soulapp.android.ui.splash.SplashActivity类下。

1577092850497

可见此处判断if (cn.soulapp.android.utils.j.e())返回的是true。

跟进方法e到类。cn.soulapp.android.utils.j

1
2
3
4
5
6
7
8
9
10
11
12

public static boolean e() {
try {
SoulApp b = SoulApp.b();
Intent intent = new Intent();
intent.setData(Uri.parse("tel:123456"));
intent.setAction("android.intent.action.DIAL");
return Build.FINGERPRINT.startsWith("generic") || Build.FINGERPRINT.toLowerCase().contains("vbox") || Build.FINGERPRINT.toLowerCase().contains("test-keys") || Build.MODEL.contains("google_sdk") || Build.MODEL.contains("Emulator") || Build.SERIAL.equalsIgnoreCase("unknown") || Build.SERIAL.equalsIgnoreCase(DispatchConstants.ANDROID) || Build.MODEL.contains("Android SDK built for x86") || Build.MANUFACTURER.contains("Genymotion") || ((Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) || "google_sdk".equals(Build.PRODUCT) || ((TelephonyManager) b.getSystemService("phone")).getNetworkOperatorName().toLowerCase().equals(DispatchConstants.ANDROID) || (intent.resolveActivity(b.getPackageManager()) != null ? 1 : null) == null);
} catch (Exception e) {
return false;
}
}

这里我们只需要把e方法的返回值改为false,即可跳过模拟器验证。

js代码如下:

1
2
3
4
5
var j = Java.use('cn.soulapp.android.utils.j');
j.e.implementation = function (a) {
send("Hook Start ...");
return false;
}

运行hook脚本,成功进入登录界面

1577093544446