-
-
[原创]某谷米APP 请求接口sign值生成流程分析
-
发表于: 3天前 636
-
地址:4oCLaHR0cHM6Ly9xaWd1bWkuY29t
对我这种新人很友好的一个逆向,顺便练练so逆向,算是新手村级别了
抓包检测
首先,抓不到包。
发现检测方式是NetworkSecurityTrustManager.checkPins,结合okhttp的一些检测:
一段frida脚本献上,spawn模式注入,直接干废
Java.perform(function () {
console.log("[*] Starting SSL Pinning Bypass for OkHttp 4.x");
// ============================================================
// OkHttp CertificatePinner bypass
// ============================================================
try {
var CertificatePinner = Java.use("okhttp3.CertificatePinner");
CertificatePinner.check$okhttp.overload(
"java.lang.String",
"java.util.List"
).implementation = function (hostname, peerCertificates) {
console.log("[+] OkHttp CertificatePinner.check bypassed for: " + hostname);
return;
};
} catch (e) {
console.log("[-] CertificatePinner.check$okhttp not found: " + e);
}
try {
var CertificatePinner2 = Java.use("okhttp3.CertificatePinner");
CertificatePinner2.check.overload(
"java.lang.String",
"java.util.List"
).implementation = function (hostname, peerCertificates) {
console.log("[+] OkHttp CertificatePinner.check bypassed for: " + hostname);
return;
};
} catch (e) {
console.log("[-] CertificatePinner.check overload not found: " + e);
}
// ============================================================
// TrustManager bypass
// ============================================================
try {
var X509TrustManager = Java.use("javax.net.ssl.X509TrustManager");
var SSLContext = Java.use("javax.net.ssl.SSLContext");
var TrustManager = Java.registerClass({
name: "com.custom.TrustManager",
implements: [X509TrustManager],
methods: {
checkClientTrusted: function (chain, authType) {},
checkServerTrusted: function (chain, authType) {},
getAcceptedIssuers: function () {
return [];
}
}
});
var TrustManagers = [TrustManager.$new()];
var sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, TrustManagers, null);
var sslSocketFactory = sslContext.getSocketFactory();
console.log("[+] Custom TrustManager registered");
} catch (e) {
console.log("[-] TrustManager bypass failed: " + e);
}
// ============================================================
// OkHttpClient.Builder SSLSocketFactory bypass
// ============================================================
try {
var Builder = Java.use("okhttp3.OkHttpClient$Builder");
Builder.sslSocketFactory.overload(
"javax.net.ssl.SSLSocketFactory",
"javax.net.ssl.X509TrustManager"
).implementation = function (sslSocketFactory, trustManager) {
console.log("[+] OkHttpClient.Builder.sslSocketFactory hooked");
var X509TrustManager = Java.use("javax.net.ssl.X509TrustManager");
var TrustManager = Java.registerClass({
name: "com.custom.TrustManager2",
implements: [X509TrustManager],
methods: {
checkClientTrusted: function (chain, authType) {},
checkServerTrusted: function (chain, authType) {},
getAcceptedIssuers: function () {
return [];
}
}
});
var SSLContext = Java.use("javax.net.ssl.SSLContext");
var ctx = SSLContext.getInstance("TLS");
ctx.init(null, [TrustManager.$new()], null);
return this.sslSocketFactory(ctx.getSocketFactory(), TrustManager.$new());
};
} catch (e) {
console.log("[-] OkHttpClient Builder hook failed: " + e);
}
// ============================================================
// HostnameVerifier bypass
// ============================================================
try {
var HostnameVerifier = Java.use("javax.net.ssl.HostnameVerifier");
var OkHostnameVerifier = Java.use("okhttp3.internal.tls.OkHostnameVerifier");
OkHostnameVerifier.verify.overload(
"java.lang.String",
"javax.net.ssl.SSLSession"
).implementation = function (hostname, session) {
console.log("[+] OkHostnameVerifier bypassed for: " + hostname);
return true;
};
} catch (e) {
console.log("[-] OkHostnameVerifier bypass failed: " + e);
}
// ============================================================
// WebViewClient SSL Error bypass
// ============================================================
try {
var WebViewClient = Java.use("android.webkit.WebViewClient");
WebViewClient.onReceivedSslError.implementation = function (
webView,
sslErrorHandler,
sslError
) {
console.log("[+] WebViewClient.onReceivedSslError bypassed");
sslErrorHandler.proceed();
};
} catch (e) {
console.log("[-] WebViewClient hook failed: " + e);
}
// ============================================================
// NetworkSecurityConfig bypass
// ============================================================
try {
var NetworkSecurityTrustManager = Java.use(
"android.security.net.config.NetworkSecurityTrustManager"
);
NetworkSecurityTrustManager.checkPins.implementation = function (chain) {
console.log("[+] NetworkSecurityTrustManager.checkPins bypassed");
return;
};
} catch (e) {
console.log("[-] NetworkSecurityTrustManager not found: " + e);
}
console.log("[*] SSL Pinning Bypass script loaded successfully");
});sign算法在哪里?
然后就可以开始寻找sign算法了。首先我们会想到直接hook messagedigest算法,但是很遗憾,这么一hook下来什么都找不到。静态分析搜索sign,找到一个请求拼接的函数。混淆的很气人,全部都是o,O,0三个字符,难以分辨。找到一个okhttp拦截器。smali还原后大概如下。这个包com.uxin看起来像个第三方sdk,但是实际上是他们自家的东西。
package com.uxin.collect.certpin;
import android.content.Context;
import android.text.TextUtils;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import okhttp3.FormBody;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class OkHttpInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
// 1. 获取原始请求
Request originalRequest = chain.request();
Request.Builder requestBuilder = originalRequest.newBuilder();
// 2. 读取全局配置类
OooOOO0 config = OooOOO.OooO0OO();
String header_c = config.OooOOo0();
String header_identify = config.OooOOoo();
String header_appId = config.OooOOO();
String header_visitorId = config.OooOo0o();
String authToken = OooOOO.OooO0O0();
// 3. 读取设备信息
String requestId = com.uxin.base.utils.device.OooO00o.Oooo00O();
Context context = o00O0o0O.o00OOO00.OooO0o0().OooO0Oo();
String ua = com.uxin.base.utils.device.OooO00o.OoooOO0(context, requestId);
String deviceName = com.uxin.base.utils.device.OooO00o.OooOO0();
String isV8aAbi = com.uxin.base.utils.device.OooO00o.OooooOO();
String deviceId = com.uxin.base.utils.device.OooO00o.OoooO00();
boolean isDarkMode = false;
if (o00O0oOo.o0000.OooO0Oo() != null) {
isDarkMode = o00O0oOo.o0000.OooO0Oo().OooOO0();
}
// 4. 统一添加公共请求头
requestBuilder.addHeader("ua", ua);
requestBuilder.addHeader("_c", header_c);
requestBuilder.addHeader("Connection", "keep-alive");
requestBuilder.addHeader("Accept", "*/*");
requestBuilder.addHeader("device_name", deviceName);
requestBuilder.addHeader("requestId", requestId);
requestBuilder.addHeader("isV8aAbi", isV8aAbi);
// 5. 条件添加请求头
if (!TextUtils.isEmpty(header_identify)) {
requestBuilder.addHeader("identify", header_identify);
}
if (!TextUtils.isEmpty(authToken)) {
requestBuilder.addHeader("x-auth-token", authToken);
}
if (!TextUtils.isEmpty(header_appId)) {
requestBuilder.addHeader("appId", header_appId);
}
if (!TextUtils.isEmpty(header_visitorId)) {
requestBuilder.addHeader("visitor_id", header_visitorId);
}
// 6. expand 头拼接(设备信息 + did)
String expandHeader = "";
if (!TextUtils.isEmpty(config.OooOOo()) && !"unknown".equals(config.OooOOo())) {
expandHeader = deviceId + "&did=" + config.OooOOo();
}
requestBuilder.addHeader("expand", expandHeader);
// 7. ie 头(环境开关)
boolean ieFlag = o00O0o0O.o00OOO0O.OooO0oO().OooOOO0();
requestBuilder.addHeader("ie", ieFlag ? "1" : "0");
// 8. dark_mode 头
requestBuilder.addHeader("dark_mode", String.valueOf(isDarkMode));
// 9. GET 请求:参数签名,自动加 sign 参数
Map<String, String> paramMap = new HashMap<>();
if ("GET".equals(originalRequest.method())) {
HttpUrl url = originalRequest.url();
if (url.querySize() > 0) {
Set<String> names = url.queryParameterNames();
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
String key = iterator.next();
String value = url.queryParameter(key);
paramMap.put(key, value);
}
// 生成签名
String sign = o00OO0.OooO0O0.OooO00o(paramMap);
// 追加 sign 参数
HttpUrl newUrl = url.newBuilder().addQueryParameter("sign", sign).build();
requestBuilder.url(newUrl);
}
}
// 10. POST Form 请求:参数签名,自动加 sign 字段
if ("POST".equals(originalRequest.method())) {
RequestBody body = originalRequest.body();
if (body instanceof FormBody) {
FormBody formBody = (FormBody) body;
FormBody.Builder newFormBuilder = new FormBody.Builder();
// 遍历原有表单参数
for (int i = 0; i < formBody.size(); i++) {
String name = formBody.encodedName(i);
String value = formBody.encodedValue(i);
newFormBuilder.addEncoded(name, value);
paramMap.put(name, value);
}
// 生成签名并添加 sign
String sign = o00OO0.OooO0O0.OooO00o(paramMap);
newFormBuilder.addEncoded("sign", sign);
requestBuilder.post(newFormBuilder.build());
}
}
// 11. 执行最终请求
Request newRequest = requestBuilder.build();
return chain.proceed(newRequest);
}
}发现了sign字段的添加处。顺藤摸瓜,sign的函数找出来!
method static constructor <clinit>()V
.registers 1
.line 1
const-string v0, "anonymous"
.line 3
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
.line 6
return-void
.end method
(省略)
method private native nativeEncode(JLjava/util/Map;)Ljava/lang/String;
.annotation system Ldalvik/annotation/Signature;
value = {
"(J",
"Ljava/util/Map<",
"Ljava/lang/String;",
"Ljava/lang/String;",
">;)",
"Ljava/lang/String;"
}
.end annotation由construct可知这里进入了native层,其实我对so层逆向不太懂。先hook拿个sign的示例出来。
Java.perform(function () {
var Tool = Java.use("com.uxin.anonymous.AnonymousTool");
var HashMap = Java.use("java.util.HashMap");
var Iterator = Java.use("java.util.Iterator");
Tool.OooO00o.overload('java.util.Map').implementation = function (map) {
console.log(" AnonymousTool.OooO00o called");
try {
// 把 map 转型为 HashMap(或 Map 接口也行)
var castedMap = Java.cast(map, Java.use("java.util.Map"));
var entrySet = castedMap.entrySet();
var iter = entrySet.iterator();
while (iter.hasNext()) {
var entry = Java.cast(iter.next(), Java.use("java.util.Map$Entry"));
console.log("", entry.getKey(), "=", entry.getValue());
}
} catch (e) {
console.log("map dump error:", e);
}
var result = this.OooO00o(map);
console.log(" SIGN =", result);
return result;
};
});这个hook,卡拦截器上,直接拿到原始输入map和输出的sign。我给的示例,这应该是一个统计接口,也可以得知通向服务器的所有需要sign的请求都在此被签名。
clientVersion = 32 deviceInfo = totalMem=6&cpuCount=4 SIGN = c7857b30b57c36f25c497e1a8f3584f0
so层分析
本来打算直接unidbg了,但看sign出来应该是个md5,整个签名流程应该也没多复杂,ida,启动!
std::string *__usercall CAnonymous::Encrypt@<X0>(
std::string *__return_ptr retstr@<X8>,
CAnonymous *this@<X0>,
std::map<std::string,std::string> *data@<X1>,
int version@<W2>)
{
size_t v4; // x0
std::string *result; // x0
unsigned __int8 *buffer; // [xsp+18h] [xbp-198h]
const std::string *v7; // [xsp+30h] [xbp-180h]
std::string v12; // [xsp+80h] [xbp-130h] BYREF
std::string __str; // [xsp+98h] [xbp-118h] BYREF
std::string v14; // [xsp+B0h] [xbp-100h] BYREF
std::string v15; // [xsp+C8h] [xbp-E8h] BYREF
std::string inputStr; // [xsp+E0h] [xbp-D0h] BYREF
std::string __rhs; // [xsp+F8h] [xbp-B8h] BYREF
std::string __lhs; // [xsp+110h] [xbp-A0h] BYREF
std::string v19; // [xsp+128h] [xbp-88h] BYREF
std::string p_msg; // [xsp+140h] [xbp-70h] BYREF
std::string v21; // [xsp+158h] [xbp-58h] BYREF
std::string salt; // [xsp+170h] [xbp-40h] BYREF
EncodeTool1 encodetool1; // [xsp+188h] [xbp-28h] BYREF
__int64 v24; // [xsp+1A8h] [xbp-8h]
v24 = *(_QWORD *)(_ReadStatusReg(TPIDR_EL0) + 40);
if ( version )
v7 = &_encodedsalt;
else
v7 = &_encodedsalt_old;
std::string::basic_string();
EncryptSalt(&salt, v7, &v21); //这里记住,下文要考
std::string::~string(&v21);
std::to_string(&__rhs, version);
std::operator+[abi:ne180000]<char,std::char_traits<char>,std::allocator<char>>(&__lhs, "version:", &__rhs);
std::operator+[abi:ne180000]<char,std::char_traits<char>,std::allocator<char>>(&v19, &__lhs, ",actual salt was:");
std::operator+[abi:ne180000]<char,std::char_traits<char>,std::allocator<char>>(&p_msg, &v19, &salt);
CAnonymous::PrintLog(this, &p_msg);
std::string::~string(&p_msg);
std::string::~string(&v19);
std::string::~string(&__lhs);
std::string::~string(&__rhs);
mapToQuery(&v15, data);
std::operator+[abi:ne180000]<char,std::char_traits<char>,std::allocator<char>>(&inputStr, &salt, &v15);
std::string::~string(&v15);
std::operator+<char>(&v14, "input string was:", &inputStr);
CAnonymous::PrintLog(this, &v14);
std::string::~string(&v14);
EncodeTool1::EncodeTool1(&encodetool1);
std::string::basic_string[abi:ne180000](retstr);
if ( this->_useExtEncrypt )
{
std::string::operator=(retstr, &inputStr);
}
else
{
buffer = (unsigned __int8 *)std::string::data[abi:ne180000](&inputStr);
v4 = std::string::length[abi:ne180000](&inputStr);
EncodeTool1::GenerateEncodeTool1(&encodetool1, buffer, v4);
EncodeTool1::ToString(&__str, &encodetool1);
std::string::operator=[abi:ne180000](retstr, &__str);
std::string::~string(&__str);
}
std::operator+<char>(&v12, "output string was:", retstr);
CAnonymous::PrintLog(this, &v12);
std::string::~string(&v12);
std::string::~string(&inputStr);
result = (std::string *)std::string::~string(&salt);
_ReadStatusReg(TPIDR_EL0);
return result;
}加密流程之encodetool1
前面的加密流程,应该是对这个函数进行调用。首先我们要知道:EncodeTool1 是什么算法,名字像是 MD5/SHA/HMAC 的封装,看起来sign出来很md5,但万一是魔改算法呢?要看看情况。
void __fastcall EncodeTool1::encode_process(
EncodeTool1 *this,
EncodeTool1::encodetool1_context *ctx,
unsigned __int8 *data)
{
unsigned __int64 v3; // [xsp+0h] [xbp-C0h]
__int64 v4; // [xsp+0h] [xbp-C0h]
unsigned __int64 v5; // [xsp+0h] [xbp-C0h]
__int64 v6; // [xsp+0h] [xbp-C0h]
unsigned __int64 v7; // [xsp+0h] [xbp-C0h]
__int64 v8; // [xsp+0h] [xbp-C0h]
__int64 v9; // [xsp+0h] [xbp-C0h]
__int64 v10; // [xsp+0h] [xbp-C0h]
__int64 v11; // [xsp+0h] [xbp-C0h]
__int64 v12; // [xsp+0h] [xbp-C0h]
__int64 v13; // [xsp+0h] [xbp-C0h]
__int64 v14; // [xsp+0h] [xbp-C0h]
__int64 v15; // [xsp+0h] [xbp-C0h]
__int64 v16; // [xsp+0h] [xbp-C0h]
__int64 v17; // [xsp+0h] [xbp-C0h]
__int64 v18; // [xsp+0h] [xbp-C0h]
__int64 v19; // [xsp+0h] [xbp-C0h]
__int64 v20; // [xsp+0h] [xbp-C0h]
__int64 v21; // [xsp+0h] [xbp-C0h]
__int64 v22; // [xsp+0h] [xbp-C0h]
__int64 v23; // [xsp+0h] [xbp-C0h]
__int64 v24; // [xsp+0h] [xbp-C0h]
__int64 v25; // [xsp+0h] [xbp-C0h]
__int64 v26; // [xsp+0h] [xbp-C0h]
__int64 v27; // [xsp+0h] [xbp-C0h]
__int64 v28; // [xsp+0h] [xbp-C0h]
__int64 v29; // [xsp+0h] [xbp-C0h]
__int64 v30; // [xsp+0h] [xbp-C0h]
__int64 v31; // [xsp+0h] [xbp-C0h]
__int64 v32; // [xsp+0h] [xbp-C0h]
__int64 v33; // [xsp+0h] [xbp-C0h]
__int64 v34; // [xsp+0h] [xbp-C0h]
__int64 v35; // [xsp+0h] [xbp-C0h]
unsigned __int64 v36; // [xsp+8h] [xbp-B8h]
__int64 v37; // [xsp+8h] [xbp-B8h]
unsigned __int64 v38; // [xsp+8h] [xbp-B8h]
__int64 v39; // [xsp+8h] [xbp-B8h]
unsigned __int64 v40; // [xsp+8h] [xbp-B8h]
__int64 v41; // [xsp+8h] [xbp-B8h]
__int64 v42; // [xsp+8h] [xbp-B8h]
__int64 v43; // [xsp+8h] [xbp-B8h]
__int64 v44; // [xsp+8h] [xbp-B8h]
__int64 v45; // [xsp+8h] [xbp-B8h]
__int64 v46; // [xsp+8h] [xbp-B8h]
__int64 v47; // [xsp+8h] [xbp-B8h]
__int64 v48; // [xsp+8h] [xbp-B8h]
__int64 v49; // [xsp+8h] [xbp-B8h]
__int64 v50; // [xsp+8h] [xbp-B8h]
__int64 v51; // [xsp+8h] [xbp-B8h]
__int64 v52; // [xsp+8h] [xbp-B8h]
__int64 v53; // [xsp+8h] [xbp-B8h]
__int64 v54; // [xsp+8h] [xbp-B8h]
__int64 v55; // [xsp+8h] [xbp-B8h]
__int64 v56; // [xsp+8h] [xbp-B8h]
__int64 v57; // [xsp+8h] [xbp-B8h]
__int64 v58; // [xsp+8h] [xbp-B8h]
__int64 v59; // [xsp+8h] [xbp-B8h]
__int64 v60; // [xsp+8h] [xbp-B8h]
__int64 v61; // [xsp+8h] [xbp-B8h]
__int64 v62; // [xsp+8h] [xbp-B8h]
__int64 v63; // [xsp+8h] [xbp-B8h]
__int64 v64; // [xsp+8h] [xbp-B8h]
__int64 v65; // [xsp+8h] [xbp-B8h]
__int64 v66; // [xsp+8h] [xbp-B8h]
__int64 v67; // [xsp+8h] [xbp-B8h]
__int64 v68; // [xsp+8h] [xbp-B8h]
unsigned __int64 v69; // [xsp+10h] [xbp-B0h]
__int64 v70; // [xsp+10h] [xbp-B0h]
unsigned __int64 v71; // [xsp+10h] [xbp-B0h]
__int64 v72; // [xsp+10h] [xbp-B0h]
unsigned __int64 v73; // [xsp+10h] [xbp-B0h]
__int64 v74; // [xsp+10h] [xbp-B0h]
__int64 v75; // [xsp+10h] [xbp-B0h]
__int64 v76; // [xsp+10h] [xbp-B0h]
__int64 v77; // [xsp+10h] [xbp-B0h]
__int64 v78; // [xsp+10h] [xbp-B0h]
__int64 v79; // [xsp+10h] [xbp-B0h]
__int64 v80; // [xsp+10h] [xbp-B0h]
__int64 v81; // [xsp+10h] [xbp-B0h]
__int64 v82; // [xsp+10h] [xbp-B0h]
__int64 v83; // [xsp+10h] [xbp-B0h]
__int64 v84; // [xsp+10h] [xbp-B0h]
__int64 v85; // [xsp+10h] [xbp-B0h]
__int64 v86; // [xsp+10h] [xbp-B0h]
__int64 v87; // [xsp+10h] [xbp-B0h]
__int64 v88; // [xsp+10h] [xbp-B0h]
__int64 v89; // [xsp+10h] [xbp-B0h]
__int64 v90; // [xsp+10h] [xbp-B0h]
__int64 v91; // [xsp+10h] [xbp-B0h]
__int64 v92; // [xsp+10h] [xbp-B0h]
__int64 v93; // [xsp+10h] [xbp-B0h]
__int64 v94; // [xsp+10h] [xbp-B0h]
__int64 v95; // [xsp+10h] [xbp-B0h]
__int64 v96; // [xsp+10h] [xbp-B0h]
__int64 v97; // [xsp+10h] [xbp-B0h]
__int64 v98; // [xsp+10h] [xbp-B0h]
__int64 v99; // [xsp+10h] [xbp-B0h]
__int64 v100; // [xsp+10h] [xbp-B0h]
unsigned __int64 v101; // [xsp+18h] [xbp-A8h]
unsigned __int64 v102; // [xsp+18h] [xbp-A8h]
__int64 v103; // [xsp+18h] [xbp-A8h]
unsigned __int64 v104; // [xsp+18h] [xbp-A8h]
__int64 v105; // [xsp+18h] [xbp-A8h]
unsigned __int64 v106; // [xsp+18h] [xbp-A8h]
__int64 v107; // [xsp+18h] [xbp-A8h]
__int64 v108; // [xsp+18h] [xbp-A8h]
__int64 v109; // [xsp+18h] [xbp-A8h]
__int64 v110; // [xsp+18h] [xbp-A8h]
__int64 v111; // [xsp+18h] [xbp-A8h]
__int64 v112; // [xsp+18h] [xbp-A8h]
__int64 v113; // [xsp+18h] [xbp-A8h]
__int64 v114; // [xsp+18h] [xbp-A8h]
__int64 v115; // [xsp+18h] [xbp-A8h]
__int64 v116; // [xsp+18h] [xbp-A8h]
__int64 v117; // [xsp+18h] [xbp-A8h]
__int64 v118; // [xsp+18h] [xbp-A8h]
__int64 v119; // [xsp+18h] [xbp-A8h]
__int64 v120; // [xsp+18h] [xbp-A8h]
__int64 v121; // [xsp+18h] [xbp-A8h]
__int64 v122; // [xsp+18h] [xbp-A8h]
__int64 v123; // [xsp+18h] [xbp-A8h]
__int64 v124; // [xsp+18h] [xbp-A8h]
__int64 v125; // [xsp+18h] [xbp-A8h]
__int64 v126; // [xsp+18h] [xbp-A8h]
__int64 v127; // [xsp+18h] [xbp-A8h]
__int64 v128; // [xsp+18h] [xbp-A8h]
__int64 v129; // [xsp+18h] [xbp-A8h]
__int64 v130; // [xsp+18h] [xbp-A8h]
__int64 v131; // [xsp+18h] [xbp-A8h]
__int64 v132; // [xsp+18h] [xbp-A8h]
__int64 v133; // [xsp+38h] [xbp-88h]
__int64 v134; // [xsp+40h] [xbp-80h]
__int64 v135; // [xsp+48h] [xbp-78h]
__int64 v136; // [xsp+50h] [xbp-70h]
__int64 v137; // [xsp+58h] [xbp-68h]
__int64 v138; // [xsp+60h] [xbp-60h]
__int64 v139; // [xsp+68h] [xbp-58h]
__int64 v140; // [xsp+70h] [xbp-50h]
__int64 v141; // [xsp+78h] [xbp-48h]
__int64 v142; // [xsp+80h] [xbp-40h]
__int64 v143; // [xsp+88h] [xbp-38h]
__int64 v144; // [xsp+90h] [xbp-30h]
__int64 v145; // [xsp+98h] [xbp-28h]
__int64 v146; // [xsp+A0h] [xbp-20h]
__int64 v147; // [xsp+A8h] [xbp-18h]
__int64 v148; // [xsp+B0h] [xbp-10h]
_ReadStatusReg(TPIDR_EL0);
v133 = *(unsigned int *)data;
v134 = *((unsigned int *)data + 1);
v135 = *((unsigned int *)data + 2);
v136 = *((unsigned int *)data + 3);
v137 = *((unsigned int *)data + 4);
v138 = *((unsigned int *)data + 5);
v139 = *((unsigned int *)data + 6);
v140 = *((unsigned int *)data + 7);
v141 = *((unsigned int *)data + 8);
v142 = *((unsigned int *)data + 9);
v143 = *((unsigned int *)data + 10);
v144 = *((unsigned int *)data + 11);
v145 = *((unsigned int *)data + 12);
v146 = *((unsigned int *)data + 13);
v147 = *((unsigned int *)data + 14);
v148 = *((unsigned int *)data + 15);
v69 = ctx->state[1];
v36 = ctx->state[2];
v3 = ctx->state[3];
v101 = (v36 & v69 | v3 & ~v69) + v133 + ctx->state[0] + 3614090360u;//md5round1的常数1
v102 = (((unsigned int)v101 >> 25) | (v101 << 7)) + v69;
v4 = (v69 & v102 | v36 & ~v102) + v134 + v3 + 3905402710u; //md5round1的常数2
……
ctx->state[1] += (((unsigned int)v100 >> 11) | (unsigned __int64)(v100 << 21)) + v68;
ctx->state[2] += v68;
ctx->state[3] += v35;
}注意到:
3614090360 = 0xD76AA478 ✅ MD5 Round1 第1个常数
3905402710 = 0xE8C7B756 ✅ MD5 Round1 第2个常数
好,不用往下看了,应该是md5,没啥问题。
加密流程之maptoquery
bool __usercall mapToQuery@<W0>(
std::string *__return_ptr retstr@<X8>,
const std::map<std::string,std::string> *cppMap@<X0>)
{
_BOOL8 result; // x0
std::__map_const_iterator<std::__tree_const_iterator<std::__value_type<std::string,std::string >,std::__tree_node<std::__value_type<std::string,std::string >,void *> *,long> >::pointer v3; // [xsp+20h] [xbp-B0h]
std::__map_const_iterator<std::__tree_const_iterator<std::__value_type<std::string,std::string >,std::__tree_node<std::__value_type<std::string,std::string >,void *> *,long> >::pointer __lhs; // [xsp+28h] [xbp-A8h]
std::__map_const_iterator<std::__tree_const_iterator<std::__value_type<std::string,std::string >,std::__tree_node<std::__value_type<std::string,std::string >,void *> *,long> > v7; // [xsp+70h] [xbp-60h] BYREF
std::__tree_const_iterator<std::__value_type<std::string,std::string >,std::__tree_node<std::__value_type<std::string,std::string >,void *> *,long>::__iter_pointer ptr; // [xsp+78h] [xbp-58h]
std::__map_const_iterator<std::__tree_const_iterator<std::__value_type<std::string,std::string >,std::__tree_node<std::__value_type<std::string,std::string >,void *> *,long> > __x; // [xsp+80h] [xbp-50h] BYREF
std::string v10; // [xsp+88h] [xbp-48h] BYREF
std::string __str; // [xsp+A0h] [xbp-30h] BYREF
std::__map_const_iterator<std::__tree_const_iterator<std::__value_type<std::string,std::string >,std::__tree_node<std::__value_type<std::string,std::string >,void *> *,long> > __y; // [xsp+B8h] [xbp-18h] BYREF
std::map<std::string,std::string>::const_iterator it; // [xsp+C0h] [xbp-10h] BYREF
__int64 v14; // [xsp+C8h] [xbp-8h]
v14 = *(_QWORD *)(_ReadStatusReg(TPIDR_EL0) + 40);
std::string::basic_string[abi:ne180000](retstr);
for ( it.__i_.__ptr_ = std::map<std::string,std::string>::begin[abi:ne180000](cppMap).__i_.__ptr_;
;
std::__map_const_iterator<std::__tree_const_iterator<std::__value_type<std::string,std::string>,std::__tree_node<std::__value_type<std::string,std::string>,void *> *,long>>::operator++[abi:ne180000](&it) )
{
__y.__i_.__ptr_ = std::map<std::string,std::string>::end[abi:ne180000](cppMap).__i_.__ptr_;
result = std::operator!=[abi:ne180000](&it, &__y);
if ( !result )
break;
__lhs = std::__map_const_iterator<std::__tree_const_iterator<std::__value_type<std::string,std::string>,std::__tree_node<std::__value_type<std::string,std::string>,void *> *,long>>::operator->[abi:ne180000](&it);
std::operator+[abi:ne180000]<char,std::char_traits<char>,std::allocator<char>>(&v10, &__lhs->first, "=");
v3 = std::__map_const_iterator<std::__tree_const_iterator<std::__value_type<std::string,std::string>,std::__tree_node<std::__value_type<std::string,std::string>,void *> *,long>>::operator->[abi:ne180000](&it);
std::operator+[abi:ne180000]<char,std::char_traits<char>,std::allocator<char>>(&__str, &v10, &v3->second);
std::string::operator+=[abi:ne180000](retstr, &__str);
std::string::~string(&__str);
std::string::~string(&v10);
ptr = it.__i_.__ptr_;
__x.__i_.__ptr_ = ZNSt6__ndk14nextB8ne180000INS_20__map_const_iteratorINS_21__tree_const_iteratorINS_12__value_typeINS_12basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEES9_EEPNS_11__tree_nodeISA_PvEElEEEETnNS_9enable_ifIXsr29__has_input_iterator_categoryIT_EE5valueEiE4typeELi0EEESI_SI_NS_15iterator_traitsISI_E15difference_typeE(
it,
1).__i_.__ptr_;
v7.__i_.__ptr_ = std::map<std::string,std::string>::end[abi:ne180000](cppMap).__i_.__ptr_;
if ( std::operator!=[abi:ne180000](&__x, &v7) )
std::string::operator+=[abi:ne180000](retstr, "&");
}
_ReadStatusReg(TPIDR_EL0);
return result;
}得到这个maptoquery后,map拼接的方式也就出来了。
saltkey1=val1&key2=val2&key3=val3
salt在哪里?
加密流程之salt
可恶!这一段卡我好久!!!
_encodedsalt点开可以获得一个字符串。本来想ida神力,这次出问题啦!
bss:0000000000104D08.0 _ZL12_encodedsalt % 1 ; __r_..__value_..__s..__is_long_ .bss:0000000000104D08.0 ; DATA XREF: __cxx_global_var_init.1+10↑o .bss:0000000000104D08.0 ; CAnonymous::Encrypt(std::map<std::string,std::string> &,int)+58↑o .bss:0000000000104D08.1 % 1 ; __r_..__value_..__s..__size_ .bss:0000000000104D09 % 0x17 ; __r_..__value_..__s.__data_ .bss:0000000000104D20 ; const std::string _saltKey
这里面哪有啊?一看ref是签名的globalvarinit,需要去找那个函数
; Attributes: static bp-based frame
; void __cdecl _cxx_global_var_init_1()
__cxx_global_var_init.1
obj= -8
var_s0= 0
; __unwind {
SUB SP, SP, #0x20
STP X29, X30, [SP,#0x10+var_s0]
ADD X29, SP, #0x10
NOP
ADR X0, _ZL12_encodedsalt ; this
STR X0, [SP,#0x10+obj]
ADRL X1, a6zf0r8 ; "6zF0R8;/"
BL _ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEC2B8ne180000ILi0EEEPKc ; std::string::basic_string<0>(char const*)
LDR X1, [SP,#0x10+obj] ; obj
ADRP X0, #_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEED1Ev_ptr@PAGE
LDR X0, [X0,#_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEED1Ev_ptr@PAGEOFF] ; lpfunc
NOP
ADR X2, __dso_handle ; lpdso_handle
BL .__cxa_atexit
LDP X29, X30, [SP,#0x10+var_s0]
ADD SP, SP, #0x20 ; ' '
RET
; } // starts at 737FC
; End of function __cxx_global_var_init.1看中间的adrl,有个salt!直接拼接美滋滋!
不对!生成的sign有问题!
加密流程之encryptsalt
EncryptSalt(&salt, v7, &v21);
第一次做的时候,我并没有注意这个小小的玩意。
直到sign生产出来用不了,我才发现,我去还有个这函数!不多说了,ida双击!
std::string *__cdecl EncryptSalt(std::string *__return_ptr retstr, const std::string *text, const std::string *p_key)
{
std::string *result; // x0
unsigned __int8 __ch; // [xsp+3Fh] [xbp-41h]
std::string::const_iterator __end1; // [xsp+68h] [xbp-18h] BYREF
std::string::const_iterator __begin1; // [xsp+70h] [xbp-10h] BYREF
__int64 v9; // [xsp+78h] [xbp-8h]
v9 = *(_QWORD *)(_ReadStatusReg(TPIDR_EL0) + 40);
std::string::basic_string[abi:ne180000](retstr);
__begin1.__i_ = std::string::begin[abi:ne180000](text).__i_;
__end1.__i_ = std::string::end[abi:ne180000](text).__i_;
while ( 1 )
{
result = (std::string *)std::operator!=[abi:ne180000]<char const*>(&__begin1, &__end1);
if ( ((unsigned __int8)result & 1) == 0 )
break;
__ch = *std::__wrap_iter<char const*>::operator*[abi:ne180000](&__begin1);
if ( isalpha(__ch) )
isupper(__ch);
else
isdigit(__ch);
std::string::push_back(retstr);
std::__wrap_iter<char const*>::operator++[abi:ne180000](&__begin1);
}
_ReadStatusReg(TPIDR_EL0);
return result;
}这段代码的逻辑看似简单:遍历输入字符串 text 的每一个字符:
如果是字母 → 忽略大小写判断
如果是数字 → 也判断一下
然后把这个字符 【处理一下】 塞进结果 retstr 里 最后返回结果
但是这个“处理”怎么处理呢?在这里我们看不到,因为反编译会“吃掉”优化后的一些东西。怀疑encryptsalt这段代码原本是一个字符替换 / 异或 / 凯撒类加密,但反编译后丢失了真正的加密运算,只保留了遍历 + 判定字符类型。
看汇编。
=============== S U B R O U T I N E =======================================
; Attributes: bp-based frame
; std::string *__cdecl EncryptSalt(std::string *__return_ptr __struct_ptr retstr, const std::string *text, const std::string *p_key)
EXPORT _Z11EncryptSaltRKNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEES5_
_Z11EncryptSaltRKNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEES5_
; CODE XREF: EncryptSalt(std::string const&,std::string)+C↓j
; DATA XREF: LOAD:00000000000027E8↑o ...
var_80 = -0x80
var_74 = -0x74
var_70 = -0x70
var_6C = -0x6C
var_68 = -0x68
var_60 = -0x60
var_54 = -0x54
var_50 = -0x50
__ch = -0x41
var_31 = -0x31
p_key = -0x30
text = -0x28
p_encrypted = -0x20
__end1 = -0x18
__begin1 = -0x10
var_8 = -8
var_s0 = 0
; __unwind { // __gxx_personality_v0
SUB SP, SP, #0x90
STP X29, X30, [SP,#0x80+var_s0]
ADD X29, SP, #0x80
STR X0, [SP,#0x80+var_68]
MOV X0, X8 ; this
LDR X8, [SP,#0x80+var_68]
STR X0, [SP,#0x80+var_60]
MRS X9, TPIDR_EL0
LDR X9, [X9,#0x28]
STUR X9, [X29,#var_8]
STUR X0, [X29,#p_encrypted]
STUR X8, [X29,#text]
STUR X1, [X29,#p_key]
MOV W8, WZR
STURB W8, [X29,#var_31]
BL _ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEC2B8ne180000Ev ; std::string::basic_string(void)
LDUR X8, [X29,#text]
STR X8, [SP,#0x80+__ch+1]
LDR X0, [SP,#0x80+__ch+1] ; this
BL _ZNKSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE5beginB8ne180000Ev ; std::string::begin(void)
STUR X0, [X29,#__begin1]
LDR X0, [SP,#0x80+__ch+1] ; this
BL _ZNKSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE3endB8ne180000Ev ; std::string::end(void)
STUR X0, [X29,#__end1]
B loc_7390C
; ---------------------------------------------------------------------------
loc_7390C ; CODE XREF: EncryptSalt(std::string const&,std::string)+60↑j
; EncryptSalt(std::string const&,std::string)+174↓j
SUB X0, X29, #-__begin1 ; __x
SUB X1, X29, #-__end1 ; __y
BL _ZNSt6__ndk1neB8ne180000IPKcEEbRKNS_11__wrap_iterIT_EES7_ ; std::operator!=<char const*>(std::__wrap_iter<char const*> const&,std::__wrap_iter<char const*> const&)
TBZ W0, #0, loc_73A20
B loc_73920
; ---------------------------------------------------------------------------
loc_73920 ; CODE XREF: EncryptSalt(std::string const&,std::string)+74↑j
SUB X0, X29, #-__begin1 ; this
BL _ZNKSt6__ndk111__wrap_iterIPKcEdeB8ne180000Ev ; std::__wrap_iter<char const*>::operator*(void)
LDRB W8, [X0]
STRB W8, [SP,#0x80+__ch]
LDRB W0, [SP,#0x80+__ch] ; __ch
; try {
BL _ZL7isalphai ; isalpha(int)
STR W0, [SP,#0x80+var_6C]
B loc_73940
; ---------------------------------------------------------------------------
loc_73940 ; CODE XREF: EncryptSalt(std::string const&,std::string)+94↑j
LDR W8, [SP,#0x80+var_6C]
CBZ W8, loc_739C0
B loc_7394C
; ---------------------------------------------------------------------------
loc_7394C ; CODE XREF: EncryptSalt(std::string const&,std::string)+A0↑j
LDRB W0, [SP,#0x80+__ch] ; __ch
BL _ZL7isupperi ; isupper(int)
STR W0, [SP,#0x80+var_70]
B loc_7395C
; ---------------------------------------------------------------------------
loc_7395C ; CODE XREF: EncryptSalt(std::string const&,std::string)+B0↑j
LDR W8, [SP,#0x80+var_70]
CBZ W8, loc_739A0
B loc_73968
; ---------------------------------------------------------------------------
loc_73968 ; CODE XREF: EncryptSalt(std::string const&,std::string)+BC↑j
LDR X0, [SP,#0x80+var_60] ; this
LDRB W9, [SP,#0x80+__ch]
MOV W8, #0x9B
SUBS W1, W8, W9
BL ._ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE9push_backEc ; std::string::push_back(char)
B loc_73980
; ---------------------------------------------------------------------------
loc_73980 ; CODE XREF: EncryptSalt(std::string const&,std::string)+D4↑j
B loc_739BC
; ---------------------------------------------------------------------------
; cleanup() // owned by 73934
MOV X8, X0
LDR X0, [SP,#0x80+var_60]
STR X8, [SP,#0x80+var_50]
MOV W8, W1
STR W8, [SP,#0x80+var_54]
BL ._ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEED1Ev ; std::string::~string()
B loc_73A64
; ---------------------------------------------------------------------------
loc_739A0 ; CODE XREF: EncryptSalt(std::string const&,std::string)+B8↑j
LDR X0, [SP,#0x80+var_60] ; this
LDRB W9, [SP,#0x80+__ch]
MOV W8, #0xDB
SUBS W1, W8, W9
BL ._ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE9push_backEc ; std::string::push_back(char)
B loc_739B8
; ---------------------------------------------------------------------------
loc_739B8 ; CODE XREF: EncryptSalt(std::string const&,std::string)+10C↑j
B loc_739BC
; ---------------------------------------------------------------------------
loc_739BC ; CODE XREF: EncryptSalt(std::string const&,std::string):loc_73980↑j
; EncryptSalt(std::string const&,std::string):loc_739B8↑j
B loc_73A10
; ---------------------------------------------------------------------------
loc_739C0 ; CODE XREF: EncryptSalt(std::string const&,std::string)+9C↑j
LDRB W0, [SP,#0x80+__ch] ; __ch
BL _ZL7isdigiti ; isdigit(int)
STR W0, [SP,#0x80+var_74]
B loc_739D0
; ---------------------------------------------------------------------------
loc_739D0 ; CODE XREF: EncryptSalt(std::string const&,std::string)+124↑j
LDR W8, [SP,#0x80+var_74]
CBZ W8, loc_739F8
B loc_739DC
; ---------------------------------------------------------------------------
loc_739DC ; CODE XREF: EncryptSalt(std::string const&,std::string)+130↑j
LDR X0, [SP,#0x80+var_60] ; this
LDRB W9, [SP,#0x80+__ch]
MOV W8, #0x69 ; 'i'
SUBS W1, W8, W9
BL ._ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE9push_backEc ; std::string::push_back(char)
B loc_739F4
; ---------------------------------------------------------------------------
loc_739F4 ; CODE XREF: EncryptSalt(std::string const&,std::string)+148↑j
B loc_73A0C
; ---------------------------------------------------------------------------
loc_739F8 ; CODE XREF: EncryptSalt(std::string const&,std::string)+12C↑j
LDR X0, [SP,#0x80+var_60] ; this
LDRB W1, [SP,#0x80+__ch]
BL ._ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE9push_backEc ; std::string::push_back(char)
; } // starts at 73934
B loc_73A08
; ---------------------------------------------------------------------------
loc_73A08 ; CODE XREF: EncryptSalt(std::string const&,std::string)+15C↑j
B loc_73A0C
; ---------------------------------------------------------------------------
loc_73A0C ; CODE XREF: EncryptSalt(std::string const&,std::string):loc_739F4↑j
; EncryptSalt(std::string const&,std::string):loc_73A08↑j
B loc_73A10
; ---------------------------------------------------------------------------
loc_73A10 ; CODE XREF: EncryptSalt(std::string const&,std::string):loc_739BC↑j
; EncryptSalt(std::string const&,std::string):loc_73A0C↑j
B loc_73A14
; ---------------------------------------------------------------------------
loc_73A14 ; CODE XREF: EncryptSalt(std::string const&,std::string):loc_73A10↑j
SUB X0, X29, #-__begin1 ; this
BL _ZNSt6__ndk111__wrap_iterIPKcEppB8ne180000Ev ; std::__wrap_iter<char const*>::operator++(void)
B loc_7390C
; ---------------------------------------------------------------------------
loc_73A20 ; CODE XREF: EncryptSalt(std::string const&,std::string)+70↑j
MOV W8, #1
STURB W8, [X29,#var_31]
LDURB W8, [X29,#var_31]
TBNZ W8, #0, loc_73A40
B loc_73A34
; ---------------------------------------------------------------------------
loc_73A34 ; CODE XREF: EncryptSalt(std::string const&,std::string)+188↑j
LDR X0, [SP,#0x80+var_60]
BL ._ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEED1Ev ; std::string::~string()
B loc_73A40
; ---------------------------------------------------------------------------
loc_73A40 ; CODE XREF: EncryptSalt(std::string const&,std::string)+184↑j
; EncryptSalt(std::string const&,std::string)+194↑j
MRS X8, TPIDR_EL0
LDR X8, [X8,#0x28]
LDUR X9, [X29,#var_8]
SUBS X8, X8, X9
B.NE loc_73A8C
B loc_73A58
; ---------------------------------------------------------------------------
loc_73A58 ; CODE XREF: EncryptSalt(std::string const&,std::string)+1AC↑j
LDP X29, X30, [SP,#0x80+var_s0]
ADD SP, SP, #0x90
RET
; ---------------------------------------------------------------------------
loc_73A64 ; CODE XREF: EncryptSalt(std::string const&,std::string)+F4↑j
LDR X0, [SP,#0x80+var_50]
STR X0, [SP,#0x80+var_80]
MRS X8, TPIDR_EL0
LDR X8, [X8,#0x28]
LDUR X9, [X29,#var_8]
SUBS X8, X8, X9
B.NE loc_73A8C
B loc_73A84
; ---------------------------------------------------------------------------
loc_73A84 ; CODE XREF: EncryptSalt(std::string const&,std::string)+1D8↑j
LDR X0, [SP,#0x80+var_80]
BL _Unwind_Resume
; ---------------------------------------------------------------------------
loc_73A8C ; CODE XREF: EncryptSalt(std::string const&,std::string)+1A8↑j
; EncryptSalt(std::string const&,std::string)+1D4↑j
BL .__stack_chk_fail
; } // starts at 738A8
; End of function EncryptSalt(std::string const&,std::string)其实最关键的几行:
; 大写字母 (isupper = true) MOV W8, #0x9B SUBS W1, W8, W9 ; push_back(0x9B - ch) ; 小写字母 (isalpha but not isupper) MOV W8, #0xDB SUBS W1, W8, W9 ; push_back(0xDB - ch) ; 数字 (isdigit) MOV W8, #0x69 ; 'i' = 105 SUBS W1, W8, W9 ; push_back(0x69 - ch) ; 其他字符 push_back(ch) ; 原样保留
所以算法也就出来了。拿我们hook出来的东西试试看:
import hashlib
def encrypt_salt(raw: str) -> str:
result = []
for ch in raw:
c = ord(ch)
if ch.isalpha():
if ch.isupper():
result.append(chr(0x9B - c))
else:
result.append(chr(0xDB - c))
elif ch.isdigit():
result.append(chr(0x69 - c))
else:
result.append(ch)
return result
def encrypt_salt_filtered(raw: str) -> str:
chars = encrypt_salt(raw)
return "".join(chars)
def map_to_query(params: dict) -> str:
return "&".join(f"{k}={v}" for k, v in sorted(params.items()))
def sign(params: dict) -> str:
salt = encrypt_salt_filtered("6zF0R8;/")
print("salt:", salt)
input_str = salt + map_to_query(params)
print("input:", input_str)
return hashlib.md5(input_str.encode()).hexdigest()
params = {
"clientVersion": "32",
"deviceInfo": "totalMem=6&cpuCount=4",
}
print("sign:", sign(params))
print("expected: c7857b30b57c36f25c497e1a8f3584f0")可以看到是符合的。完结撒花。