本帖最后由 tzwsoho 于 2021-8-10 12:01 编辑
最近研究了一下几羊的APP,写了个基于frIDA的自动抽奖脚本,这里我单独对其中的签名函数地址的获取做一下解读。
首先,包括几羊APP在内,所有阿里系APP在向服务器发出HTTPS请求时,都会带上一个签名sign,根据大佬的文章(蚂蚁森林自动收能量:https://blog.csdn.net/dieTicket/article/details/102678054)可以知道,这个签名是动态加载libsgmain.so文件,然后调用里面的
[Plain Text] 纯文本查看 复制代码 com.alibaba.wireless.security.open.securesignature.a.signRequest(Lcom/alibaba/wireless/security/open/SecurityGuardParamContext;Ljava/lang/String;)Ljava/lang/String;
得出的,其中SecurityGuardParamContext参数我们可以简单地通过jadx反编译得出是这么一个结构体:
[Java] 纯文本查看 复制代码 public class SecurityGuardParamContext {
public String appKey;
public Map<String, String> paramMap = new HashMap();
public int requestType;
public String reserved1;
public String reserved2;
}
知道了这点,我们先来对这个函数下钩子,看看signRequest的参数分别是什么。
先准备一台root掉的手机,或者雷电模拟器,启动frida-server,这个网上很多教程,这里不再赘述。
然后将以下代码保存为hook.js:
[Asm] 纯文本查看 复制代码
function hook() {
if (!Java.available) {
console.error('Java API not available');
return;
}
Java.perform(function () {
console.log('hooked');
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Java.enumerateClassLoaders({
onMatch: function (loader) {
try {
if (loader.findClass('com.alibaba.wireless.security.open.securesignature.a')) {
// console.log(loader);
Java.classFactory.loader = loader;
Java.use('com.alibaba.wireless.security.open.securesignature.a').signRequest.implementation = function (p0, p1) {
const ret = this.signRequest(p0, p1);
console.log('p0 =', JSON.stringify(p0), 'p1 =', JSON.stringify(p1), 'sign =', ret);
return ret;
};
}
} catch (e) {
// console.error('enumerateClassLoaders err', e.stack);
}
},
// DO NOT REMOVE 'onComplete' FUNCTION
onComplete: function () {
}
});
});
}
hook();
获得几羊APP的PID
[Plain Text] 纯文本查看 复制代码 # frida-ps -Uia
PID Name Identifier
---- ------ ---------------------------
2075 几羊 com.snail.android.lucky
1563 设置 com.android.settings
1911 雷电游戏中心 com.android.flysilkworm
...
将脚本注入几羊APP
[Plain Text] 纯文本查看 复制代码 # frida -U --no-pause -l hook.js -p 2075
然后任意点击几羊APP的界面,使几羊产生HTTPS请求,这时可以看到控制台会输入类似以下的字符串:
[Plain Text] 纯文本查看 复制代码 hooked
p0 = "<instance: com.alibaba.wireless.security.open.SecurityGuardParamContext>" p1 = "" sign = 89aa0266ec4a5e631b82209171d49548
p0 = "<instance: com.alibaba.wireless.security.open.SecurityGuardParamContext>" p1 = "" sign = 3a5774c3aa6ec736334f0c7f11bb5e53
p0 = "<instance: com.alibaba.wireless.security.open.SecurityGuardParamContext>" p1 = "" sign = 0dfca532961a1998237e2903676211f9
p0 = "<instance: com.alibaba.wireless.security.open.SecurityGuardParamContext>" p1 = "" sign = bac3f23414b3345b84c0b7654610d00f
p0 = "<instance: com.alibaba.wireless.security.open.SecurityGuardParamContext>" p1 = "" sign = c6f37ba76d12b97db25946b04039f6ed
p0 = "<instance: com.alibaba.wireless.security.open.SecurityGuardParamContext>" p1 = "" sign = 437fb3b5e9f3026d88347e6b1ff2eb34
p0 = "<instance: com.alibaba.wireless.security.open.SecurityGuardParamContext>" p1 = "" sign = 97793a1b0cdd3108fc4b01d7f2a35361
为了进一步看清p0参数的内容,我们将hook.js脚本改一下:
[JavaScript] 纯文本查看 复制代码
function stringifyMap(m) {
var HashMapEntry = Java.use('java.util.HashMap$HashMapEntry');
var entrySet = m.entrySet();
var it = entrySet.iterator();
var obj = {};
while (it.hasNext()) {
var node = Java.cast(it.next(), HashMapEntry);
var key = node.getKey().toString();
var value = node.getValue().toString();
obj[key] = value;
}
return JSON.stringify(obj);
}
function hook() {
if (!Java.available) {
console.error('Java API not available');
return;
}
Java.perform(function () {
console.log('hooked');
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Java.enumerateClassLoaders({
onMatch: function (loader) {
try {
if (loader.findClass('com.alibaba.wireless.security.open.securesignature.a')) {
// console.log(loader);
Java.classFactory.loader = loader;
Java.use('com.alibaba.wireless.security.open.securesignature.a').signRequest.implementation = function (p0, p1) {
const ret = this.signRequest(p0, p1);
console.log('appKey =', p0.appKey.value, 'paramMap =', stringifyMap(p0.paramMap.value), 'requestType =', p0.requestType.value);
return ret;
};
}
} catch (e) {
// console.error('enumerateClassLoaders err', e.stack);
}
},
// DO NOT REMOVE 'onComplete' FUNCTION
onComplete: function () {
}
});
});
}
hook();
这里不用重新使用frida加载hook.js,frida会自动检测到文件改变,自动重载脚本文件,这也是frida强大的功能之一。
然后我们继续点击几羊界面,可以看到类似以下的输出:
[Plain Text] 纯文本查看 复制代码 hooked
appKey = SNAIL_APP_KEY_ANDROID paramMap = {"INPUT":"Operation-Type=alipay.mobile.aggrbillinfo.user.sign&Request-Data=W3siYXBkaWQiOiJlWU9Ja2tud0tMZndBVFNaQ2VUL3RkdDdpZEQwbERYeEhOM21LWG1Od0ZCcUI2UmlHdnZ4K1B3eSIsImNsaWVudEtleSI6IlVMYmVxMUFQTnciLCJjbGllbnRWZXJzaW9uIjoiMy40LjAuOTciLCJtb2RlbCI6IlBDUlQwMCIsInBsYXRmb3JtIjoiQW5kcm9pZCIsInN5c3RlbVN3aXRjaFN0YXR1cyI6dHJ1ZSwidG9rZW4iOiI3MmQ1YWQ4ZmIwZmIwOWNiMTRjMGI1OWNkNjY2ZTJhNCIsInVzZXJJZCI6IjgwODgwMjcxMDExMDM3MDYiLCJ1dGRpZCI6IllRN2MvYUdnSjlNREFMR1dKMEEzdFhRKyJ9XQ==&Ts=Nii7Fou"} requestType = 4
appKey = SNAIL_APP_KEY_ANDROID paramMap = {"INPUT":"Operation-Type=alipay.mobile.aggrbillinfo.message.new.index&Request-Data=W3siYXBkaWQiOiJlWU9Ja2tud0tMZndBVFNaQ2VUL3RkdDdpZEQwbERYeEhOM21LWG1Od0ZCcUI2UmlHdnZ4K1B3eSIsImNsaWVudEtleSI6IlVMYmVxMUFQTnciLCJjbGllbnRWZXJzaW9uIjoiMy40LjAuOTciLCJtb2RlbCI6IlBDUlQwMCIsInBsYXRmb3JtIjoiQW5kcm9pZCIsInRva2VuIjoiNzJkNWFkOGZiMGZiMDljYjE0YzBiNTljZDY2NmUyYTQiLCJ1c2VySWQiOiI4MDg4MDI3MTAxMTAzNzA2IiwidXRkaWQiOiJZUTdjL2FHZ0o5TURBTEdXSjBBM3RYUSsifV0=&Ts=Nii7Gay"} requestType = 4
我们可以看到appKey的值固定是字符串SNAIL_APP_KEY_ANDROID
paramMap里面固定是INPUT做key,后面的Value是一串由Operation-Type、Request-Data、Ts三个参数组成的请求字符串,其中:
Operation-Type是操作类型名,表示要求服务器做的操作,
Request-Data其实是一串Base64字符串,我们随意解码一段:
[JavaScript] 纯文本查看 复制代码 [{"apdid":"eYOIkknwKLfwATSZCeT/tdt7idD0lDXxHN3mKXmNwFBqB6RiGvvx+Pwy","clientKey":"ULbeq1APNw","clientVersion":"3.4.0.97","model":"PCRT00","platform":"Android","systemSwitchStatus":true,"token":"72d5ad8fb0fb09cb14c0b59cd666e2a4","userId":"8088027101103706","utdid":"YQ7c/aGgJ9MDALGWJ0A3tXQ+"}]
可以看到里面包含了这些内容,那我们可以按这些字段去jadx搜索,就可以知道这些值是怎么得来的。
Ts其实就是取当时时间毫秒数,用自定义的64进制转换方法转换成的字符串,在开头提到的文章《蚂蚁森林自动收能量》里面有说明,这里不再赘述。
最后的requestType,这个值我们通过jadx反编译(com.alipay.mobile.common.netsdkextdepend.security.DefaultSecurityManager.signature(SignRequest signRequest))得知,使用MD5算法时是4。
还有reserved1和reserved2是保留用的,都是空字符串,这里不再列出。
知道了上面这些参数的含义,我们可以写个函数来模拟调用signRequest函数并导出,从而自己计算sign签名的值,最终可以模拟各种各样阿里系APP的HTTPS请求!
[JavaScript] 纯文本查看 复制代码
function signRequest(p) {
// console.log('operationType =', p.operationType);
// console.log('requestData =', p.requestData);
// console.log('ts =', p.ts);
if (!securesignature || !securesignature.signRequest) {
console.error('securesignature instance not found, please click homepage first!');
return '';
}
const clsSecurityGuardParamContext = Java.use('com.alibaba.wireless.security.open.SecurityGuardParamContext');
const instSecurityGuardParamContext = clsSecurityGuardParamContext.$new();
const appKeyField = clsSecurityGuardParamContext.class.getDeclaredField('appKey');
const requestTypeField = clsSecurityGuardParamContext.class.getDeclaredField('requestType');
const paramMapField = clsSecurityGuardParamContext.class.getDeclaredField('paramMap');
// Set fields accessable
appKeyField.setAccessible(true);
requestTypeField.setAccessible(true);
paramMapField.setAccessible(true);
// Make map
const HashMap = Java.use("java.util.HashMap");
const paramHashMap = HashMap.$new();
const INPUT = `Operation-Type=${p.operationType}&Request-Data=${p.requestData}&Ts=${p.ts}`;
paramHashMap.put('INPUT', INPUT);
// Set values
appKeyField.set(instSecurityGuardParamContext, 'SNAIL_APP_KEY_ANDROID');
requestTypeField.setInt(instSecurityGuardParamContext, 4);
paramMapField.set(instSecurityGuardParamContext, paramHashMap);
// Print values and verify
// var appKey = appKeyField.get(instSecurityGuardParamContext);
// var requestType = requestTypeField.get(instSecurityGuardParamContext);
// var paramMap = paramMapField.get(instSecurityGuardParamContext);
// console.log(
// 'appKey =', appKey, '\n',
// 'requestType =', requestType, '\n',
// 'paramMap =', stringifyMap(Java.cast(paramMap, HashMap)),
// '\n', '*'.repeat(100));
return securesignature.signRequest(instSecurityGuardParamContext, '');
}
rpc.exports = {
signRequest,
};
完整的代码在这里:https://github.com/tzwsoho/auto_snail_lucky/tree/master/sign_tool
感谢阅读,请对我的自动抽奖脚本多多支持,多多点Star,谢谢! |