xx银行app数据加解密
使用定制rom机绕过frida检测
但是这个app还有一些环境检测,在打开app一段时间后会弹窗一个弹窗,点击确定后就会退出

因为这是一个弹窗,所以可以从java的弹窗类进行入手
Java.perform(function () {
// 获取 AlertDialog 类
var AlertDialog = Java.use('android.app.AlertDialog');
if (AlertDialog) {
// Hook show 方法
AlertDialog.show.implementation = function () {
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
console.log('[*] AlertDialog is about to show.');
// 可以在这里添加自定义逻辑,比如阻止显示
//return;
// 调用原始的 show 方法
return this.show();
};
}
});
打印出调用弹窗的堆栈
这个类名含有risk,很像环境的检测的地方,jadx打开看一下代码

直接定位过来,代码很少,弹窗代码主要是通过a(relativeLayout)进行赋值

会返回一个AlertDialog类,那弹窗就是在这里,这里定位到builder.setPositiveButton(17039370, new a()); 看看弹窗的处理逻辑

定位到这个onClick方法监听,调用了so层的方法处理弹窗的逻辑,这里尝试先看看能不能将AlertDialog的show方法直接return,不让弹窗show出来
Java.perform(function () {
// 获取 AlertDialog 类
var AlertDialog = Java.use('android.app.AlertDialog');
if (AlertDialog) {
// Hook show 方法
AlertDialog.show.implementation = function () {
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
console.log('[*] AlertDialog is about to show.');
// 可以在这里添加自定义逻辑,比如阻止显示
return;
// 调用原始的 show 方法
//return this.show();
};
}
});

发现弹窗虽然不显示了,但是app会卡死,判断是弹窗的按钮处理逻辑处影响了后续的代码执行,得先看看so层代码是如何处理的

查看native方法调用处

是加载了这个bangcle_risk这个so文件,因为这个app frida检测已经绕过,并且app不会立马退出,可以尝试从内存dump出so文件,这样即使有加密后的so文件也能更清晰的分析代码
这里先frida调用app

修改一下py代码中的id,然后使用attach的方式将so dump出来
import frida
js_script = """
function dump_so(so_name) {
console.log("开始dump")
const module = Process.findModuleByName(so_name);
const module_size = module.size;
Memory.protect(ptr(module.base), module_size, 'rwx');
const soMemory = Memory.readByteArray(module.base, module_size);
send({name: so_name, base: module.base, size: module_size}, soMemory);
}
dump_so("libbangcle_risk.so")
"""
def on_message(message, data):
if message['type'] == 'send':
payload = message['payload']
so_name = payload['name']
base_address = payload['base']
size = payload['size']
print(f"Dumping {so_name} (Base: {base_address}, Size: {size})")
with open(so_name, "wb") as f:
f.write(data)
print(f"{so_name} dumped successfully!")
else:
print(f"Error: {message}")
def main():
# 附加到目标进程
device = frida.get_usb_device()
session = device.attach(10387)
# 加载Frida脚本
script = session.create_script(js_script)
# 设置消息处理函数
script.on("message", on_message)
# 加载并执行脚本
script.load()
if __name__ == "__main__":
main()
dump出来的so文件先修复一下
.\SoFixer-Windows-64.exe -s libbangcle_risk.so-o libbangcle_risk_fix.so
然后载入ida,搜索一下java

发现没有结果

并且存在大量的混淆方法名,猜测不是静态注册的代码,hook一下RegisterNatives看看动态注册
var ENV = null;
var JCLZ = null;
var method01addr = null;
var method02addr = null;
var method02 = null;
var addrNewStringUTF = null;
var NewStringUTF = null;
function hook_RegisterNatives() {
var symbols = Module.enumerateSymbolsSync("libart.so");
var addrRegisterNatives = null;
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
//_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("NewStringUTF") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0) {
addrNewStringUTF = symbol.address;
console.log("NewStringUTF is at ", symbol.address, symbol.name);
NewStringUTF = new NativeFunction(addrNewStringUTF,'pointer',['pointer','pointer'])
}
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("RegisterNatives") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0) {
addrRegisterNatives = symbol.address;
console.log("RegisterNatives is at ", symbol.address, symbol.name);
}
}
if (addrRegisterNatives != null) {
Interceptor.attach(addrRegisterNatives, {
onEnter: function (args) {
console.log("[RegisterNatives] method_count:", args[3]);
var env = args[0];
ENV = args[0];
var java_class = args[1];
JCLZ = args[1];
var class_name = Java.vm.tryGetEnv().getClassName(java_class);
//console.log(class_name);
var methods_ptr = ptr(args[2]);
var method_count = parseInt(args[3]);
for (var i = 0; i < method_count; i++) {
var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));
var name = Memory.readCString(name_ptr);
var sig = Memory.readCString(sig_ptr);
var find_module = Process.findModuleByAddress(fnPtr_ptr);
console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, "module_name:", find_module.name, "module_base:", find_module.base, "offset:", ptr(fnPtr_ptr).sub(find_module.base));
if(name.indexOf("method01")>=0){
// method01addr = fnPtr_ptr;
continue;
}else if (name.indexOf("decrypt")>=0){
method02addr = fnPtr_ptr;
method02 = new NativeFunction(method02addr,'pointer',['pointer','pointer','pointer']);
method01addr = Module.findExportByName("libroysue.so", "Java_com_roysue_easyso1_MainActivity_method01")
}else{
continue;
}
}
}
});
}
}
function invokemethod01(contents){
console.log("method01_addr is =>",method01addr)
var method01 = new NativeFunction(method01addr,'pointer',['pointer','pointer','pointer']);
var NewStringUTF = new NativeFunction(addrNewStringUTF,'pointer',['pointer','pointer'])
var result = null;
Java.perform(function(){
console.log("Java.vm.getEnv()",Java.vm.getEnv())
var JSTRING = NewStringUTF(Java.vm.getEnv(),Memory.allocUtf8String(contents))
result = method01(Java.vm.getEnv(),JSTRING,JSTRING);
console.log("result is =>",result)
console.log("result is ",Java.vm.getEnv().getStringUtfChars(result, null).readCString())
result = Java.vm.getEnv().getStringUtfChars(result, null).readCString();
})
return result;
}
function invokemethod02(contents){
var result = null;
Java.perform(function(){
var JSTRING = NewStringUTF(Java.vm.getEnv(),Memory.allocUtf8String(contents))
result = method02(Java.vm.getEnv(),JSTRING,JSTRING);
result = Java.vm.getEnv().getStringUtfChars(result, null).readCString();
})
return result;
}
rpc.exports = {
invoke1:invokemethod01,
invoke2:invokemethod02
};
setImmediate(hook_RegisterNatives);
/*
java_class: com.example.demoso1.MainActivity name: method01 sig: (Ljava/lang/String;)Ljava/lang/String; fnPtr: 0x73e2cd1018 module_name: libnative-lib.so module_base: 0x73e2cc1000 offset: 0x10018
java_class: com.example.demoso1.MainActivity name: method02 sig: (Ljava/lang/String;)Ljava/lang/String; fnPtr: 0x73e2cd0efc module_name: libnative-lib.so module_base: 0x73e2cc1000 offset: 0xfefc
function hookmethod(addr){
Interceptor.attach(addr,{
onEnter:function(args){
console.log("args[0]=>",args[0])
console.log("args[1]=>",args[1])
console.log("args[2]=>",Java.vm.getEnv().getStringUtfChars(args[2], null).readCString())
},onLeave:function(retval){
console.log(Java.vm.getEnv().getStringUtfChars(retval, null).readCString())
}
})
}
function replacehook(addr){
//> 能够hook上,就能主动调用
var addrfunc = new NativeFunction(addr,'pointer',['pointer','pointer','pointer']);
Interceptor.replace(addr,new NativeCallback(function(arg1,arg2,arg3){
// 确定主动调用可以成功,只要参数合法,地址正确
var result = addrfunc(arg1,arg2,arg3)
console.log(arg1,arg2,arg3)
console.log("result is ",Java.vm.getEnv().getStringUtfChars(result, null).readCString())
return result;
},'pointer',['pointer','pointer','pointer']))
}
*/

确实是动态注册,查看该处的函数偏移0x15574

JNIEnv *__fastcall cV(JNIEnv *result, __int64 a2, _QWORD *a3)
{
__int64 v3; // x9
unsigned int v6; // w8
int v7; // w25
void **v8; // x27
__int64 *v9; // x23
__int64 v10; // x26
_QWORD *v11; // x21
__int64 v12; // x0
__int64 v13; // x0
__int64 v14; // x0
void *__ptr32 *v15; // x12
void *__ptr32 *v16; // x11
__int64 v17; // x9
int v18; // w9
unsigned int v19; // w9
void *__ptr32 *v20; // x19
__int64 v21; // x0
int v22; // w9
unsigned int v23; // w8
int v24; // w22
__int64 v25; // x8
__int64 v26; // x8
unsigned int v27; // w8
int v28; // w9
__int64 v29; // d8
unsigned int v30; // s8
__int64 v31; // x0
void **v32; // x20
unsigned __int16 v33; // w27
int v34; // w8
__int64 v35; // x0
__int64 v36; // x2
__int64 v37; // x0
unsigned __int64 v38; // x9
__int64 v39; // x28
unsigned int v40; // [xsp+14h] [xbp-2FCh]
__int64 v41; // [xsp+18h] [xbp-2F8h]
__int64 v42; // [xsp+20h] [xbp-2F0h]
__int64 v43; // [xsp+28h] [xbp-2E8h]
int v44; // [xsp+34h] [xbp-2DCh]
JNIEnv *v45; // [xsp+38h] [xbp-2D8h]
__int64 v46; // [xsp+40h] [xbp-2D0h]
int v47; // [xsp+4Ch] [xbp-2C4h]
__int64 v48; // [xsp+50h] [xbp-2C0h]
int v49; // [xsp+5Ch] [xbp-2B4h]
__int64 v50; // [xsp+60h] [xbp-2B0h]
__int64 v51[64]; // [xsp+68h] [xbp-2A8h]
unsigned __int16 v52; // [xsp+268h] [xbp-A8h] BYREF
unsigned __int16 v53; // [xsp+26Ah] [xbp-A6h]
__int16 v54; // [xsp+26Ch] [xbp-A4h]
__int64 v55; // [xsp+270h] [xbp-A0h]
__int64 v56; // [xsp+278h] [xbp-98h]
int v57; // [xsp+280h] [xbp-90h]
int v58; // [xsp+284h] [xbp-8Ch]
JNIEnv *v59; // [xsp+288h] [xbp-88h]
bool v60; // [xsp+290h] [xbp-80h]
int v61; // [xsp+29Ch] [xbp-74h]
__int64 v62; // [xsp+2A0h] [xbp-70h] BYREF
int v63; // [xsp+2ACh] [xbp-64h]
v6 = 0;
v61 = 0;
v7 = 14621;
v8 = j__Il0SIIIOI_IlIIS5SIIS0II_IlSl___I_0Il_0Il_0_lll0OS5__ptr;
v9 = (_QWORD *)(&stru_DD8 + 6);
v10 = 2LL;
v50 = v3;
LABEL_3:
while ( v6 != 1 )
{
if ( !v6 )
{
v40 = v7 - 139 * ((unsigned int)(60350 * v7) >> 23);
v11 = result;
v45 = result;
v44 = ((__int64 (__fastcall *)(JNIEnv *, __int64))(*result)->GetArrayLength)(result, a2) - 1;
v12 = (*(__int64 (__fastcall **)(_QWORD *, __int64))(*v11 + 1384LL))(v11, a2);
v13 = sub_8690(v11, v12, *(_QWORD *)((char *)&elf_hash_bucket[116] + (_QWORD)v8));
v14 = sub_8A30(v13);
v16 = &jpt_15924;
v42 = v14;
v7 = 3550 - v40;
v17 = v50;
while ( 2 )
{
v50 = v17;
v43 = (int)v17;
if ( (int)v17 >= v44 )
v18 = 2;
else
v18 = 3;
v47 = v18;
v19 = v7;
LABEL_12:
v7 = v19;
LABEL_14:
v19 = 17638;
switch ( v47 )
{
case 0:
v17 = (unsigned int)(v50 + 1);
continue;
case 1:
goto LABEL_12;
case 2:
v41 = a2;
v63 = 0;
v22 = 20945;
v23 = 22;
v46 = 4294967267LL;
v49 = -29;
v48 = 4294967267LL;
while ( 2 )
{
v24 = v22;
switch ( v23 )
{
case 0u:
v23 = 30;
continue;
case 1u:
v25 = (unsigned __int8)sub_8A80(v59, v10, *(_QWORD *)((char *)&elf_hash_bucket[124] + (_QWORD)v8)) != 0;
goto LABEL_55;
case 2u:
v10 = v51[v49];
v23 = 23;
++v49;
continue;
case 3u:
v23 = 17;
continue;
case 4u:
v46 = v51[v49];
v23 = 24 - v22 + 13 * ((unsigned int)(40330 * v22) >> 19);
++v49;
continue;
case 5u:
goto LABEL_65;
case 6u:
v23 = 18;
continue;
case 7u:
v49 = 0;
if ( v60 )
v23 = 20;
else
v23 = 4;
v9 = (__int64 *)(v11[1] + 8 * (v52 - (unsigned __int64)v53));
v22 = 11933;
continue;
case 8u:
v26 = v56;
v11[6] = v59;
*v11 = v26 + 16;
sub_8DF0(v11, &v52, a3, v62);
*a3 = v11[2];
v23 = 24;
v22 = v24;
continue;
case 9u:
v22 = 13710;
v23 = 14;
continue;
case 0xAu:
v10 = 0LL;
v23 = 10;
continue;
case 0xBu:
case 0x11u:
*v9++ = v10;
((void (__fastcall *)(__int64 *, __int64))loc_C484)(&v62, v10);
goto LABEL_17;
case 0xCu:
*v9++ = v46;
((void (__fastcall *)(__int64 *))loc_C484)(&v62);
v23 = 20;
v22 = v24;
continue;
case 0xDu:
*v9++ = (int)sub_8690(v59, v10, *(_QWORD *)((char *)&elf_hash_bucket[116] + (_QWORD)v8));
(*v59)->DeleteLocalRef(v59, (jobject)v10);
v27 = 17 * ((unsigned int)(61681 * v24) >> 20);
v28 = 32;
goto LABEL_32;
case 0xEu:
v29 = COERCE_UNSIGNED_INT128(sub_8860(v59, v10, *(_QWORD *)((char *)&elf_hash_bucket[130] + (_QWORD)v8)));
(*v59)->DeleteLocalRef(v59, (jobject)v10);
*v9 = v29;
v9[1] = 0LL;
v9 += 2;
v27 = 17 * ((unsigned int)(61681 * v24) >> 20);
v28 = 24;
LABEL_32:
v23 = v28 - v24 + v27;
v22 = v24;
continue;
case 0xFu:
v22 = 24765;
v23 = 31;
continue;
case 0x10u:
if ( *(_BYTE *)v48 )
v23 = 2;
else
v23 = 8;
continue;
case 0x12u:
v30 = COERCE_UNSIGNED_INT128(sub_8710(v59, v10, *(_QWORD *)((char *)&elf_hash_bucket[128] + (_QWORD)v8)));
(*v59)->DeleteLocalRef(v59, (jobject)v10);
*v9++ = v30;
goto LABEL_17;
case 0x13u:
v23 = 26;
continue;
case 0x14u:
v10 = 0LL;
goto LABEL_17;
case 0x15u:
v23 = 28;
continue;
case 0x16u:
v48 = v55 + 1;
v62 = 0LL;
v31 = sub_88C0(64LL);
v32 = v8;
v33 = v52;
v11 = (_QWORD *)v31;
*(_QWORD *)(v31 + 8) = sub_88C0(8LL * v52);
sub_8B70();
*((_WORD *)v11 + 28) = v33;
v8 = v32;
v23 = 7;
v22 = v24;
continue;
case 0x17u:
v34 = *(unsigned __int8 *)v48++;
if ( v34 > 89 && v34 == 90 )
goto LABEL_64;
LABEL_65:
v23 = 27;
continue;
case 0x18u:
sub_8DE0(v11[1]);
sub_8DE0(v11);
sub_D9F8(&v62);
v23 = 32;
v22 = v24;
continue;
case 0x19u:
v22 = 8431;
v23 = 13;
continue;
case 0x1Au:
v35 = (__int64)v59;
v36 = *(_QWORD *)((char *)&elf_hash_bucket[122] + (_QWORD)v8);
goto LABEL_53;
case 0x1Bu:
v37 = (__int64)v59;
do
{
do
v38 = __ldaxr((unsigned __int64 *)&qword_8C9A0);
while ( !v38 && __stlxr(1uLL, (unsigned __int64 *)&qword_8C9A0) );
}
while ( v38 == 1 );
if ( v38 != 2 )
{
WORD1(off_8CD18) = 24912;
strcpy((char *)&qword_8CD20, "args err type");
HIDWORD(off_8CD18) = 543519602;
qword_8C9A0 = 2LL;
}
(*(void (__fastcall **)(__int64, char *))(*(_QWORD *)v37 + 144LL))(v37, (char *)&off_8CD18 + 2);
goto LABEL_17;
case 0x1Cu:
v35 = (__int64)v59;
v36 = *(_QWORD *)((char *)&elf_hash_bucket[118] + (_QWORD)v8);
LABEL_53:
v25 = (int)sub_8690(v35, v10, v36);
goto LABEL_55;
case 0x1Du:
LABEL_64:
v23 = 1;
continue;
case 0x1Eu:
v25 = (unsigned __int16)sub_86F0(v59, v10, *(_QWORD *)((char *)&elf_hash_bucket[120] + (_QWORD)v8));
LABEL_55:
*v9++ = v25;
(*v59)->DeleteLocalRef(v59, (jobject)v10);
LABEL_17:
v23 = 16;
v22 = v24;
continue;
case 0x1Fu:
v39 = sub_8B40(v59, v10, *(_QWORD *)((char *)&elf_hash_bucket[126] + (_QWORD)v8));
(*v59)->DeleteLocalRef(v59, (jobject)v10);
*v9 = v39;
v9[1] = 0LL;
v9 += 2;
v23 = 121 - v24 + 137 * ((unsigned int)(61231 * v24) >> 23);
v22 = v24;
continue;
case 0x20u:
v19 = 8789 - (-29 * ((unsigned int)(36158 * v7) >> 20) + v7);
a2 = v41;
v9 = (_QWORD *)(&stru_DD8 + 6);
v10 = 2LL;
v16 = v15;
goto LABEL_12;
default:
continue;
}
}
case 3:
v11 = a3;
v20 = v16;
v21 = (__int64)(*v45)->GetObjectArrayElement(v45, (jobjectArray)a2, v50);
v16 = v20;
a3 = v11;
v51[v43] = v21;
goto LABEL_14;
case 4:
v17 = 0LL;
v60 = *(_DWORD *)(v42 + 28) != 0;
v59 = v45;
continue;
case 5:
v52 = *(_DWORD *)(v42 + 16);
v53 = *(_DWORD *)(v42 + 20);
v54 = *(_DWORD *)(v42 + 24);
v55 = *(_QWORD *)(v42 + 32);
v56 = *(_QWORD *)(v42 + 8);
v57 = *(_DWORD *)v42;
v58 = *(_DWORD *)(v42 + 4);
goto LABEL_14;
case 6:
v6 = 27 - v40;
result = v45;
goto LABEL_3;
default:
goto LABEL_14;
}
}
}
}
return result;
}
代码非常冗杂,先hook这个方法看看传进了什么数据
let JniLib = Java.use("com.fort.andJni.JniLib1700472038");
JniLib["cV"].implementation = function (objArr) {
console.log(`JniLib.cV is called: objArr=${objArr}`);
this["cV"](objArr);
};

会打印出很多信息,可能so层不仅仅是处理了弹窗逻辑,直接分析检测代码不太可行,直接patch掉这个方法也不行,回到java方法

这里会传入一个AlertDialog对象,看能不能通过过滤参数进行patch,在处理弹窗逻辑的时候进行return
function hook_tmp() {
Java.perform(function () {
// 获取 JniLib1700472038 类
let JniLib = Java.use("com.fort.andJni.JniLib1700472038");
if (JniLib) {
// 保存原始的 cV 方法
let originalCV = JniLib["cV"];
// 重写 cV 方法
JniLib["cV"].implementation = function (objArr) {
console.log(`JniLib.cV is called: objArr=${objArr}`);
// 检查参数中是否包含 AlertDialog 对象
let hasAlertDialog = false;
for (let i = 0; i < objArr.length; i++) {
if (Java.isInstanceOf(objArr[i], Java.use('android.app.AlertDialog'))) {
hasAlertDialog = true;
break;
}
}
if (hasAlertDialog) {
console.log("Parameter contains an AlertDialog object, skipping original method call.");
return null; // 可以根据实际需求返回合适的值
} else {
// 调用原始的 cV 方法
return originalCV.call(this, objArr);
}
};
}
});
}
发现app可以正常运行,不会卡住,也没有弹窗
使用小黄鸟抓一下包,发现业务请求包处有"body"的加密字段,尝试在jadx搜索一下

能搜出来挺多的,但是看这些方法名都不太像,还是先hook toString和getBytes方法看看有没有突破点
function hook_toString(){
Java.perform(function(){
var strCls = Java.use("java.lang.StringBuilder");
strCls.toString.implementation = function(){
var result = this.toString();
if(result.indexOf("136929")!=-1){
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
console.log(result);
}
return result;
}
});
}

这个方法很像返回数据包接收处,jadx过去看一下

这里有个log方法,log方法会打印处返回数据,先hook 一下这个log方法看看数据先
function hook_log(){
Java.perform(function(){
let Logs = Java.use("com.yitong.mobile.component.logging.Logs");
Logs["i"].overload('java.lang.String', 'java.lang.String').implementation = function (str, str2) {
console.log(`Logs.i is called: str=${str}, str2=${str2}`);
this["i"](str, str2);
};
})
}


确实打印出了参数信息,这里的网关原始报文是解密前的数据,接口原始报文就是解密后的,那先来看看jadx代码

查看n的赋值和定义

是通过这个getResponseString获取的,写出hook代码
function hook_response1(){
Java.perform(function(){
let APPResponseHandler = Java.use("com.yitong.mobile.network.http.APPResponseHandler");
APPResponseHandler["getResponseString"].implementation = function () {
console.log(`APPResponseHandler.getResponseString is called`);
let result = this["getResponseString"]();
console.log(`APPResponseHandler.getResponseString result=${result}`);
return result;
};
})
}
经测试确实能hook出数据,但是不全,比如个人信息处查询不到数据,猜测可能还有其它方法进行处理,回去再看看log

这里还有一个报文解密字段,猜测是个人信息加密处,直接定位

这里的str4是经过this.f.decrypt方法,点进去f方法查看

发现是一个接口,直接hook肯定没有数据,得找到哪里使用了这个接口,直接看f是什么对象即可

f对象在类定义中找不到,那得看继承的父类AppGatewayBaseResponseHandler

父类这里定义了f对象

在setDataProcessor这里进行了赋值,那还得看看传进来的IDataProcessor对象,但是IDataProcesso是接口,还还得看看setDataProcessor的调用,看看是哪里对这个方法进行赋值

只有3处调用,经过hook定位,确定到了此处代码

最终方法是这个V2DataProcessor类下的decrypt方法

function hook_decrypt2(){
Java.perform(function(){
let V2DataProcessor = Java.use("com.yitong.mobile.network.process.V2DataProcessor");
V2DataProcessor["decrypt"].implementation = function (str) {
console.log(`V2DataProcessor.decrypt is called: str=${str}`);
let result = this["decrypt"](str);
console.log(`V2DataProcessor.decrypt result=${result}`);
return result;
};
})
}
参数请求比较容易,直接搜索"参数信息"即可定位

直接hook post方法即可
function hook_post(){
Java.perform(function(){
let APPRestClient = Java.use("com.yitong.mobile.network.http.APPRestClient");
APPRestClient["post"].overload('java.lang.String', 'java.lang.String', 'java.lang.String', 'com.yitong.http.ResponseHandlerInterface', 'java.lang.String').implementation = function (str, str2, str3, responseHandlerInterface, str4) {
console.log(`APPRestClient.post is called: str=${str}, str2=${str2}, str3=${str3}, responseHandlerInterface=${responseHandlerInterface}, str4=${str4}`);
this["post"](str, str2, str3, responseHandlerInterface, str4);
};
})
}
完整的渗透脚本如下
'use strict';
var colors = {
"resetColor": "\x1b[0m",
"green": "\x1b[32m",
"yellow": "\x1b[33m",
"red": "\x1b[31m"
}
Java.perform(function () {
// Request Class & Method
var request_class = Java.use('com.yitong.mobile.network.http.APPRestClient');
var request_method = request_class.post.overload('java.lang.String', 'java.lang.String', 'java.lang.String', 'com.yitong.http.ResponseHandlerInterface', 'java.lang.String');
// Response Class & Method
var response_class = Java.use('com.yitong.mobile.network.http.APPResponseHandler');
var response_method = response_class.getResponseString.overload();
var response_class2 = Java.use('com.yitong.mobile.network.process.V2DataProcessor');
var response_method2 = response_class2.decrypt.overload('java.lang.String');
var path = null;
request_method.implementation = function (str1,str2,str3,responseHandlerInterface,str4) {
path = str1;
var jsString = str2;
console.log(colors.green, "[Original Request Body]\n", colors.resetColor, jsString, '\n');
send({ from: '/http', payload: jsString, api_path: path });
var rcv_data = "FAILURE";
var op = recv('input', function (value) {
console.log(colors.green, "Data type: " + typeof value.payload);
if (typeof value.payload === 'string') {
try {
rcv_data = value.payload;
} catch (e) {
console.error("Error parsing JSON for request:", e);
}
} else {
console.error("Received payload is not a string:", value.payload);
}
}).wait(5000); // 等待 5 秒,防止阻塞
if (rcv_data === "FAILURE") {
var result = this.post(str1, str2, str3, responseHandlerInterface, str4);
} else {
var result = this.post(str1, rcv_data, str3, responseHandlerInterface, str4);
}
return result;
}
response_method.implementation = function () {
var js = this.getResponseString();
console.log(colors.green, "[Original Response Body]\n", colors.resetColor, js, '\n');
path = "response";
send({ from: '/http', payload: js, api_path: path});
var rcv_data = "FAILURE";
var op = recv('input', function (value) {
console.log(colors.green, "Data type: " + typeof value.payload);
if (typeof value.payload === 'string') {
try {
// 尝试将字符串转换为对象
rcv_data = value.payload
console.log("调用了string处代码")
} catch (e) {
console.error("Error parsing string payload:", e);
}
} else if (typeof value.payload === 'object') {
rcv_data = value.payload;
} else {
console.error("Received payload has an unexpected type:", typeof value.payload);
}
}).wait(5000);
if (rcv_data === "FAILURE") {
var result = this.getResponseString();
return result;
} else {
return rcv_data;
}
}
response_method2.implementation = function (encrypted_data) {
//console.log(colors.green, "[Original Response Body]\n", colors.resetColor, JSON.stringify(response_data), '\n');
var js = this.decrypt(encrypted_data);
path = "response";
send({ from: '/http', payload: js, api_path: path});
var rcv_data = "FAILURE";
var op = recv('input', function (value) {
console.log(colors.green, "Data type: " + typeof value.payload);
if (typeof value.payload === 'string') {
try {
// 尝试将字符串转换为对象
rcv_data = value.payload
console.log("调用了string处代码")
} catch (e) {
console.error("Error parsing string payload:", e);
}
} else if (typeof value.payload === 'object') {
rcv_data = value.payload;
} else {
console.error("Received payload has an unexpected type:", typeof value.payload);
}
}).wait(5000);
if (rcv_data === "FAILURE") {
var result = this.decrypt(response_data);
return result;
} else {
return rcv_data;
}
//return result;
}
});
function hook_tmp() { //绕过环境检测
Java.perform(function () {
// 获取 JniLib1700472038 类
let JniLib = Java.use("com.fort.andJni.JniLib1700472038");
if (JniLib) {
// 保存原始的 cV 方法
let originalCV = JniLib["cV"];
// 重写 cV 方法
JniLib["cV"].implementation = function (objArr) {
//console.log(`JniLib.cV is called: objArr=${objArr}`);
// 检查参数中是否包含 AlertDialog 对象
let hasAlertDialog = false;
for (let i = 0; i < objArr.length; i++) {
if (Java.isInstanceOf(objArr[i], Java.use('android.app.AlertDialog'))) {
hasAlertDialog = true;
break;
}
}
if (hasAlertDialog) {
//console.log("Parameter contains an AlertDialog object, skipping original method call.");
return null; // 可以根据实际需求返回合适的值
} else {
// 调用原始的 cV 方法
return originalCV.call(this, objArr);
}
};
}
});
}
hook_tmp()
import frida
import requests
import time
import sys
import os
import socket
import argparse
from log import *
import psutil
print ('''\033[1;31m \n
___ _ _ _ ____ ___
|_ _|_ __ | |_ ___ _ __ ___ ___ _ __ | |_ / \ | _ \_ _|
| || '_ \| __/ _ \ '__/ __/ _ \ '_ \| __| / _ \ | |_) | |
| || | | | || __/ | | (_| __/ |_) | |_ / ___ \| __/| |
|___|_| |_|\__\___|_| \___\___| .__/ \__| /_/ \_\_| |___|
|_|
https://noobpk.github.io #noobboy
Intercept Api in iOS/Android Application
''')
print ("\033[1;34m[*]___author___: @noobpk\033[1;37m")
print ("\033[1;34m[*]___version___: 1.3\033[1;37m")
print ("")
BURP_HOST = "127.0.0.1"
BURP_PORT = 26080
def check_platform():
try:
platforms = {
'linux' : 'Linux',
'linux1' : 'Linux',
'linux2' : 'Linux',
'darwin' : 'OS X',
'win32' : 'Windows'
}
if sys.platform not in platforms:
sys.exit(logger.error("[x_x] Your platform currently does not support."))
except Exception as e:
logger.error("[x_x] Something went wrong, please check your error message.\n Message - {0}".format(e))
# def check_ps_for_win32():
# try:
# if sys.platform == "win32":
# PROCESSNAME = "iTunes.exe"
# for proc in psutil.process_iter():
# try:
# if proc.name() == PROCESSNAME:
# return True
# except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess) as e:
# pass
# return sys.exit(logger.error("[x_x] Please install iTunes on MicrosoftStore or run iTunes frist."))
# except Exception as e:
# logger.error("[x_x] Something went wrong, please check your error message.\n Message - {0}".format(e))
def check_echo_server():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
result = sock.connect_ex(('127.0.0.1',27080))
if result == 0:
logger.info("[*] Connect to echoServer successfully.")
else:
sock.close()
sys.exit(logger.error("[x_x] Please start echoServer."))
def run():
#check platform support
check_platform()
#check process iTunes for Win32s
# check_ps_for_win32()
#check python version
if sys.version_info < (3, 0):
logger.error("[x_x] iOS hook requires Python 3.x")
sys.exit(0)
else:
handle_del_log()
main()
def handle_del_log():
try:
pwd = os.getcwd()
path = pwd + '/errors.log'
file_stats = os.stat(path)
if (file_stats.st_size > 1024000000): #delete errors.log if file size > 1024 MB
os.remove(path)
else:
return True
except Exception as e:
logger.error("[x_x] Something went wrong when clear error log. Please clear error log manual.\n [Error Message] - {0}".format(e))
def main():
def frida_process_message(message, data):
handled = False
print ('message:', message)
if message['type'] == 'input':
handled = True
print (message["payload"])
elif message['type'] == 'send':
body = message['payload']
API_PATH = body['api_path']
if body['from'] == '/http':
try:
req = requests.request('FRIDA', 'http://%s:%d/%s' % (BURP_HOST, BURP_PORT, API_PATH), headers={'content-type':'text/plain'}, data=body['payload'].encode('utf-8'))
script.post({ 'type': 'input', 'payload': req.text })
handled = True
except requests.exceptions.RequestException as e:
logger.error("[x_x] Connection refused, please check configurage on BurpSute.\n [Error Message] - {0}".format(e))
parser = argparse.ArgumentParser()
parser.add_argument("-p", "--package")
parser.add_argument("-n", "--name")
parser.add_argument("-s", "--script", help='custom handler script')
args, leftovers = parser.parse_known_args()
try:
# Spawing application with custom script
if args.package is not None and args.script is not None:
#check echoServer
check_echo_server()
#
if os.path.isfile(args.script):
logger.info('[*] Spawning: ' + args.package)
logger.info('[*] Script: ' + args.script)
time.sleep(2)
device = frida.get_device_manager().add_remote_device('127.0.0.1:11223')
pid = device.spawn(args.package)
session = device.attach(pid)
# device.resume(pid)
time.sleep(1)
# session = device.attach(pid)
# device = frida.get_local_device()
# pid = device.spawn(args.package)
# session = device.attach(pid)
# device.resume(pid)
# time.sleep(1)
with open(args.script,'r',encoding="utf-8") as f:
script = session.create_script(f.read())
script.on("message", frida_process_message)
script.load()
device.resume(pid)
input()
else:
logger.error('[?] Script not found!')
#Attaching default script to application
if args.name is not None and args.script is not None:
#check echoServer
check_echo_server()
#
logger.info('[*] Attaching: ' + args.name)
logger.info('[*] Script: ' + args.script)
time.sleep(2)
process = frida.get_usb_device().attach(args.name)
with open(args.script) as f:
script = process.create_script(f.read())
script.on("message", frida_process_message)
script.load()
input()
#EXCEPTION FOR FRIDA
except frida.ServerNotRunningError:
logger.error("Frida server is not running.")
except frida.TimedOutError:
logger.error("Timed out while waiting for device to appear.")
except frida.TransportError:
logger.error("[x_x] The application may crash or lose connection.")
#EXCEPTION FOR OPTIONPARSING
#EXCEPTION FOR SYSTEM
except Exception as e:
logger.error("[x_x] Something went wrong, please check your error message.\n Message - {0}".format(e))
except KeyboardInterrupt:
logger.info("Bye bro!!")
# sys.exit(0)
if __name__ == '__main__':
run()


浙公网安备 33010602011771号