前言
只作學(xué)習(xí)研究,禁止用于非法用途,否則后果自負(fù),如有侵權(quán),請(qǐng)告知?jiǎng)h除,謝謝!
最新的版本應(yīng)該是2.4還是2.5了, 但是用unidbg調(diào)試會(huì)失敗,所以用老版本2.3,但是api請(qǐng)求依然會(huì)403,所以只能構(gòu)造出mtgsig(具體原因后面分析有寫)
更新: 現(xiàn)在這版本的app已經(jīng)無(wú)法正常使用了 讓升級(jí)最新版本 文章看一看就好
開搞:
環(huán)境配置
軟件 | 作用 |
---|---|
jadx-gui | 反編譯 |
frida-16.0.10 | hook |
參考項(xiàng)目: https://github.com/irabbit666666/unidbg-mt-server23
0x1 jadx反編譯分析
搜索mtgsig 找到這個(gè)類
應(yīng)該是在這個(gè)getRequestSignature生成
進(jìn)入這個(gè)a.a()方法,沒有反編譯出java代碼, 但是可以看到是有put操作的
直接hook這個(gè)CommonCandyInterceptor.getRequestSignature會(huì)報(bào)錯(cuò)ClassNotFound,搜了一下好像是動(dòng)態(tài)加載的原因,需要先找到classLoader,根據(jù)源碼分析了一下大致的參數(shù)public String getRequestSignature(String str, URI uri, String str2, String str3, String str4, byte[] bArr)
// 由于是動(dòng)態(tài)加載 需要先找到classLoader 測(cè)試為loader的最后一個(gè)
function hook_getRequestSignature_with_loader() {
// hook 動(dòng)態(tài)加載的dex 類, 以及查看類的類名
Java.perform(function () {
// hook 動(dòng)態(tài)加載的 dex
console.log('----尋找loader start----');
let loaders = new Array();
Java.enumerateClassLoaders({
onMatch(loader) {
console.log(`找到loader: ${loader}`);
loaders.push(loader);
},
onComplete() { }
})
console.log('----尋找loader end----');
console.log(`共找到${loaders.length}個(gè)loader`);
if (loaders.length == 0) {
return;
}
// 測(cè)試為最后一個(gè)loader 設(shè)置為需要使用的類加載器
const currentLoder = loaders[loaders.length - 1]
Java.classFactory.loader = currentLoder;
console.log(`設(shè)置loader為: ${currentLoder}`);
const class_name = "com.meituan.android.common.mtguard.wtscore.plugin.sign.interceptors.CommonCandyInterceptor";
const CommonCandyInterceptor = Java.classFactory.use(class_name);
/**
*
* @param {*} str1 猜測(cè)為post/get 即method 不區(qū)分大小寫
* @param {*} uri 猜測(cè)為url
* @param {*} str2 猜測(cè)為useragent/useragent不存在則為固定值
* @param {*} str3 HttpHeaders.CONTENT_ENCODING gzip等
* @param {*} str4 猜測(cè)為Content-Type
* @param {*} bArr inputStream轉(zhuǎn)byte[]
*/
CommonCandyInterceptor.getRequestSignature.implementation = function (str1, uri, str2, str3, str4, bArr) {
console.log('getRequestSignature start');
console.log(`str1: ${str1}`);
console.log(`uri: ${uri}`);
console.log(`str2: ${str2}`);
console.log(`str3: ${str3}`);
console.log(`str4: ${str4}`);
console.log(`bArr: ${bArr}`);
// 生成的mtgsig
const ret = this.getRequestSignature(str1, uri, str2, str3, str4, bArr);
console.log(ret);
console.log('getRequestSignature end');
return ret;
};
});
}
運(yùn)行沒有報(bào)ClassNotFound, 但是也不打印任何信息, 不應(yīng)該啊,直接hook生成試試
function hook_mock_getRequestSignature() {
console.log('hook start');
const class_name = "com.meituan.android.common.mtguard.wtscore.plugin.sign.interceptors.CommonCandyInterceptor";
const String = Java.use("java.lang.String");
const URI = Java.use("java.net.URI");
Java.perform(() => {
const CommonCandyInterceptor = Java.use(class_name);
const instance = CommonCandyInterceptor.$new();
const str1 = String.$new("post");
const uri = URI.$new("https://test.com/test");
const str2 = String.$new("unknown");
const str3 = String.$new("gzip");
const str4 = String.$new("unknown");
const bArr = Java.array('byte', [13, 37, 42, 66]);
const ret = instance.getRequestSignature(str1, uri, str2, str3, str4, bArr);
console.log(ret);
CommonCandyInterceptor.getRequestSignature.implementation = function (str1, uri, str2, str3, str4, bArr) {
console.log('getRequestSignature start');
console.log(`str1: ${str1}`);
console.log(`uri: ${uri}`);
console.log(`str2: ${str2}`);
console.log(`str3: ${str3}`);
console.log(`str4: ${str4}`);
console.log(`bArr: ${bArr}`);
// 生成的mtgsig
const ret = this.getRequestSignature(str1, uri, str2, str3, str4, bArr);
console.log(ret);
console.log('getRequestSignature end');
return ret;
};
});
}
可以正常生成沒問(wèn)題,但是看了其他人的文章都會(huì)加載一個(gè)so,看看這個(gè)a.a()試試
這里確實(shí)調(diào)用了NBridge.main3()
NBridge.main3()調(diào)用main, main是一個(gè)native函數(shù),果然有蹊蹺
這里的main1和main3都會(huì)調(diào)用native main方法
NBridge里并沒有發(fā)現(xiàn)System.loadLibrary方法, so不是在這個(gè)文件加載的
搜索NBridge.main試試, 這里有一個(gè)Init,點(diǎn)進(jìn)去看看
看樣子確實(shí)是有初始化操作,然后執(zhí)行NBridge.main(4, new Object[1])
這里MTGConfigs.b = "mtguard"
加載的是libmtguard.so
文件, 第二行debug語(yǔ)句也能看出來(lái)
加載完成后會(huì)調(diào)用NBridge.main3(1, new Object[]{sAppKey})
, 這個(gè)也是后續(xù)unidbg要做的事情, 進(jìn)行初始化,這里的sAppKey可以在AndroidManifest里找到
接下來(lái)hook一下main方法
function hook_main() {
Java.perform(() => {
const NBridge = Java.use("com.meituan.android.common.mtguard.NBridge");
const String = Java.use("java.lang.String");
const JavaByte = Java.use("[B");
NBridge.main.implementation = function (i, args) {
console.log('------- start -------');
console.log(`參數(shù)(int)i ${i}`);
console.log(`args length ${args.length}`)
for (let i = 0; i < args.length; i++) {
try {
// 強(qiáng)轉(zhuǎn)為byte 不行的話直接走catch直接輸出
const buffer = Java.cast(args[i], JavaByte);
// 創(chuàng)建byte數(shù)組
const result = Java.array('byte', buffer);
// 轉(zhuǎn)string輸出
const foramtStr = String.$new(result);
console.log(`格式化args[${i}] ${foramtStr}`);
} catch (e) {
console.log(`普通args[${i}] ${args[i]}`);
}
}
const ret = this.main(i, args);
console.log('ret---')
console.log(ret)
console.log('------- end -------');
return ret;
}
});
}
確實(shí)和分析的一樣, 加載so后會(huì)調(diào)用NBridge.main3(1, new Object[]{"appKey"})
, 返回0則init成功
0x2 unidbg調(diào)試
知道邏輯后就開始unidbg, 先搭好架子, 參考這個(gè)項(xiàng)目 (可以直接copy就用), 跑起來(lái)沒問(wèn)題
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.EmulatorBuilder;
import com.github.unidbg.Module;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.memory.Memory;
import java.io.File;
public class MtgsigHook extends AbstractJni implements IOResolver {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private final DvmClass NBridge;
private static final String BASE_PATH = System.getProperty("user.dir") + "/data";
private static final String APK_PATH = BASE_PATH + "/meituan.APK";
public static void main(String[] args) {
MtgsigHook hook = new MtgsigHook(true);
}
public MtgsigHook(boolean debug) {
// 創(chuàng)建模擬器實(shí)例,要模擬32位或者64位,在這里區(qū)分
EmulatorBuilder<AndroidEmulator> builder = AndroidEmulatorBuilder.for32Bit().setProcessName("com.sankuai.meituan");
emulator = builder.build();
// 模擬器的內(nèi)存操作接口
final Memory memory = emulator.getMemory();
// 設(shè)置系統(tǒng)類庫(kù)解析
memory.setLibraryResolver(new AndroidResolver(23));
// 創(chuàng)建Android虛擬機(jī)
// vm = emulator.createDalvikVM(); // 只創(chuàng)建vm,用來(lái)讀so,不加載apk
vm = emulator.createDalvikVM(new File(APK_PATH));
// 設(shè)置是否打印Jni調(diào)用細(xì)節(jié)
vm.setVerbose(debug);
vm.setJni(this);
emulator.getSyscallHandler().addIOResolver(this);
emulator.getSyscallHandler().setEnableThreadDispatcher(true);
// 加載libttEncrypt.so到unicorn虛擬內(nèi)存,加載成功以后會(huì)默認(rèn)調(diào)用init_array等函數(shù),這是直接讀so文件
// DalvikModule dm = vm.loadLibrary(TempFileUtils.getTempFile(LIBTT_ENCRYPT_LIB_PATH), false);
// 這是搜索加載apk里的模塊名,比如 libguard.so 那么模塊名一般是guard
DalvikModule dm = vm.loadLibrary("mtguard", false);
// 手動(dòng)執(zhí)行JNI_OnLoad函數(shù)
dm.callJNI_OnLoad(emulator);
// 加載好的libttEncrypt.so對(duì)應(yīng)為一個(gè)模塊
module = dm.getModule();
dm.callJNI_OnLoad(emulator);
NBridge = vm.resolveClass("com/meituan/android/common/mtguard/NBridge");
}
@Override
public FileResult resolve(Emulator emulator, String s, int i) {
System.out.println("pathname: " + pathname);
return null;
}
}
ps: 注意實(shí)現(xiàn)的這個(gè)接口IOResolver
后期需要用到
根據(jù)之前分析的邏輯, 寫一個(gè)init調(diào)用試試
private void init() {
ArrayObject dvmObject = NBridge.callStaticJniMethodObject(emulator,
"main(I[Ljava/lang/Object;)[Ljava/lang/Object;",
1,
ArrayObject.newStringArray(vm, "9b69f861-e054-4bc4-9daf-d36ae205ed3e"));
String ret = dvmObject.getValue()[0].getValue().toString();
int code = Integer.parseInt(ret);
if (code != 0) {
throw new RuntimeException("init失敗: " + code);
}
System.out.println("init成功.");
}
意料之中, 環(huán)境還沒補(bǔ)全
補(bǔ)了兩個(gè)classLoader后開始調(diào)用main2方法了
main2根據(jù)傳入的int值返回對(duì)應(yīng)的結(jié)果
注意調(diào)用main2的一定要補(bǔ)全, 不然直接返回null或者報(bào)錯(cuò),
類/方法 | 需要補(bǔ)全 |
---|---|
android/os/Build | 補(bǔ)全機(jī)型信息 |
java/lang/System->getProperty | 返回java.io.tmpdir和httpProxy信息 |
android/os/SystemProperties->get | 基本上是獲取一些usb信息 |
android/content/pm/ApplicationInfo->sourceDir | 這里要求返回apk路徑, 需要下一步使用 |
上面實(shí)現(xiàn)的IOResolver
這時(shí)候就要用了, 第三個(gè)調(diào)用的就是apk路徑
這里會(huì)讀取apk,返回null
的話會(huì)返回錯(cuò)誤的狀態(tài)碼,init失敗最新版本的mtgsig這里返回apk會(huì)卡在這里,無(wú)法繼續(xù), 不知道什么原因
unidbg補(bǔ)完apk文件路徑訪問(wèn),并重定向到指定的apk文件后,程序一直卡住,不再往下運(yùn)行了
補(bǔ)一下apk路徑試試
@Override
public FileResult resolve(Emulator emulator, String pathname, int i) {
System.out.println("pathname: " + pathname);
if (pathname.equals("/data/app/com.sankuai.meituan-2nOCxLCJUl7lL3J_S7uSPA==/base.apk")) {
return FileResult.success(new SimpleFileIO(i, new File(APK_PATH), pathname));
}
return null;
}
寫一個(gè)test試試調(diào)用, 生成mtgsig的參數(shù) ->private static native Object[] main(int i, Object[] objArr);
參數(shù) | 類型 | 說(shuō)明 |
---|---|---|
i | int | 初始化為1,生成mtgsig為2,也有其他的功能 |
objArr[0] | string | appKey 固定值 |
objArr[1] | byte[] | 按[method params(按A-za-z排序) ]的byte數(shù)組 |
objArr[2] | byte[] | host -> byte[] |
public void callTest() {
StringObject arg1 = new StringObject(vm, "9b69f861-e054-4bc4-9daf-d36ae205ed3e");
ByteArray arg2 = new ByteArray(vm, "GET /TEST/aaa=aa?bbb=bb".getBytes(StandardCharsets.UTF_8));
ByteArray arg3 = new ByteArray(vm, "test.com".getBytes(StandardCharsets.UTF_8));
ArrayObject dvmObject = NBridge.callStaticJniMethodObject(emulator,
"main(I[Ljava/lang/Object;)[Ljava/lang/Object;",
2,
new ArrayObject(arg1, arg2, arg3));
String ret = dvmObject.getValue()[0].getValue().toString();
JSONObject jsonObject = JSONObject.parseObject(ret);
System.out.println(jsonObject.toString(JSONWriter.Feature.PrettyFormat));
}
報(bào)錯(cuò), 需要補(bǔ)充其他的main2返回值
補(bǔ)充main2() -> 51 8 40
后生成成功
這里也是生成mtgsig里的a7和a8, 這里使用了固定(你用中文都行)
根據(jù)源碼分析和別人的分析應(yīng)該是服務(wù)器返回,還具有失效時(shí)間 只能構(gòu)造出來(lái) 但是不能正常請(qǐng)求接口(最后有寫原因)
if (cmd == 51) {
//StringBuilder sb = new StringBuilder();
//sb.append(b.b());
//return sb.toString();
return new StringObject(vm, "2");
}
if (cmd == 8) {
// return MTGuard.DfpId;
return new StringObject(vm, "DAD72CE6E36048C4F132B89C1A61D3388C232E4BEE586E86458EDC83".toLowerCase());
}
if (cmd == 40) {
// a7,xid
return new StringObject(vm, "ChC0KOFzrx6Q9nknGeNbKBnVmztAE6LW2nMYbiTF+hjmHkf/ToA8SDLjODLlnPYhI4dat1iPZmZtHGm7NiPJLkhSwZzjBhbcTSP7RkK7kNk=");
}
可以看到a7和a8就是上面填寫的 一模一樣
0x3 后續(xù)分析
相對(duì)于初始化,多出來(lái)的3個(gè)參數(shù)
參數(shù) | 實(shí)際參數(shù) | 作用(猜測(cè)) |
---|---|---|
51 | b.b() | 獲取的應(yīng)該是應(yīng)用是否在運(yùn)行, 返回2運(yùn)行中 1沒有運(yùn)行 |
8 | MTGuard.DfpId | 設(shè)備指紋信息 |
40 | SyncStoreManager.sXid.id | 跟上面DfpId一樣,也是一種設(shè)備Id |
main2(51) 獲取的應(yīng)該是應(yīng)用是否在運(yùn)行, 返回2運(yùn)行中 1沒有運(yùn)行
main2(8) 獲取設(shè)備指紋信息, 可以看出就是mtgsig里a7的值,這里的dfpid應(yīng)該是服務(wù)器生成再下發(fā)給客戶端的, 在文章美團(tuán)mtgsig2.1和mtgsig1.5區(qū)別里也提到 a7,a8是后臺(tái)返回的
main2(40)獲取xid, 也是跟設(shè)備信息有關(guān),具有失效時(shí)間。這里也是作為a7的值
所以只能生成mtgsig,用于請(qǐng)求的話a7, a8字段肯定是error的, 會(huì)返回403
所以只能生成mtgsig,用于請(qǐng)求的話a7, a8字段肯定是error的, 會(huì)返回403
所以只能生成mtgsig,用于請(qǐng)求的話a7, a8字段肯定是error的, 會(huì)返回403
通過(guò)main也可以生成xid和dfpId,由于是服務(wù)端下發(fā),當(dāng)然也不會(huì)生效文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-464354.html
public String getRandomDfpId() {
ArrayObject dvmObject = NBridge.callStaticJniMethodObject(emulator, "main(I[Ljava/lang/Object;)[Ljava/lang/Object;", 47, new ArrayObject());
String dfpId = dvmObject.getValue()[0].getValue().toString();
System.out.println("dfpId: " + dfpId);
return dfpId.toLowerCase(Locale.ROOT);
}
public String getRandomXid() {
ArrayObject dvmObject = NBridge.callStaticJniMethodObject(emulator, "main(I[Ljava/lang/Object;)[Ljava/lang/Object;", 48, new ArrayObject());
String xid = dvmObject.getValue()[0].getValue().toString();
System.out.println("xid: " + xid);
return xid;
}
相當(dāng)于main(47) main(48)
是生成a7 a8
而main(8) main(40)
是獲取a7 a8
但是生成的a7 a8可能是要經(jīng)過(guò)服務(wù)器的 你直接構(gòu)造出來(lái)是無(wú)法使用的文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-464354.html
到了這里,關(guān)于某團(tuán)mtgsig2.3-unidbg的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!