frida分析支付某Alipay扫码转账生成URI流程

介绍

需要分析支付某打开扫一扫,扫到目标二维码后是如何跳转到转账页面的。

定位

发现扫码从数据包无从下手定位,尝试关键字:QRScan,QRCode,Translate等都找不到对应的函数,所以最终通过View的点击事件。

var Button = Java.use("android.widget.Button");
Button.setOnClickListener.overload("android.view.View$OnClickListener").implementation = function (listener) {
        console.log("Button clicked");
        //listener.onClick(this);  // 调用原来的点击事件
        printStack();
        this.setOnClickListener(listener);
        console.log("Button clicked end");
        return;
    };

打印了堆栈,从堆栈结果里面找到了一个疑似创建扫码相机的View:

com.alipay.mobile.scan.ui2.NScanTopView

因为这个类代码量巨大,5000多行,而且都是混淆过的名称,我直接发给ai进行分析,得出了大概扫描后的回调结果是在a方法:

@Override // com.alipay.mobile.scan.ui.BaseScanTopView
public final void a(com.alipay.mobile.bqcscanservice.BQCScanResult r25) {
// ... existing code ...
}

但是代码的过程看不到,只能看Smail指令,并且对应参数r25里面的BQCScanResult也是啥都没有,只能通过方法的执行过程分析(还是给AI分析)随后得出hook代码:

 let NScanTopView = Java.use("com.alipay.mobile.scan.ui2.NScanTopView");
    NScanTopView["a"].overload('com.alipay.mobile.bqcscanservice.BQCScanResult').implementation = function (result) {
        console.log("NScanTopView.a is called");
        
        if(result != null) {
            // 1. 先确认是否是 MultiMaScanResult
            if(result.$className === "com.alipay.mobile.mascanengine.MultiMaScanResult") {
                let multiResult = Java.cast(result, Java.use("com.alipay.mobile.mascanengine.MultiMaScanResult"));
                
                // 2. 获取 maScanResults 数组
                let scanResults = multiResult.maScanResults.value;
                if(scanResults && scanResults.length > 0) {
                    // 3. 获取第一个结果
                    let firstResult = scanResults[0];
                    
                    // 4. 打印扫描文本
                    console.log("Scan Text:", firstResult.text.value);
                    
                    // 5. 打印更多信息(如果需要)
                    if(firstResult.rect) {
                        console.log("Scan Rect:", JSON.stringify({
                            left: firstResult.rect.value.left,
                            top: firstResult.rect.value.top,
                            right: firstResult.rect.value.right,
                            bottom: firstResult.rect.value.bottom
                        }));
                    }
                    
                    if(firstResult.type) {
                        console.log("Scan Type:", firstResult.type.value);
                    }
                }
            }
            
            // 备用方案:通过反射获取所有可能的属性
            try {
                let fields = result.getClass().getDeclaredFields();
                for(let i = 0; i < fields.length; i++) {
                    let field = fields[i];
                    field.setAccessible(true);
                    console.log(`Field ${field.getName()}: ${field.get(result)}`);
                }
            } catch(e) {
                console.log("Error getting fields:", e);
            }
        }
        
        // 调用原始方法
        return this["a"](result);
    };

输出结果:

Scan Text: https://qr.alipay.com/fkx17137rejqeh4j3v4cib0?t=1733923946234
Scan Rect: {"left":{"_p":["<instance: android.graphics.Rect>",2,{"className":"int","name":"I","type":"int32","size":1,"byteSize":4,"defaultValue":0},"0x7048f4b8","0x7ae97bd488","0x7ae97c07a0"]},"top":{"_p":["<instance: android.graphics.Rect>",2,{"className":"int","name":"I","type":"int32","size":1,"byteSize":4,"defaultValue":0},"0x7048f4d8","0x7ae97bd488","0x7ae97c07a0"]},"right":{"_p":["<instance: android.graphics.Rect>",2,{"className":"int","name":"I","type":"int32","size":1,"byteSize":4,"defaultValue":0},"0x7048f4c8","0x7ae97bd488","0x7ae97c07a0"]},"bottom":{"_p":["<instance: android.graphics.Rect>",2,{"className":"int","name":"I","type":"int32","size":1,"byteSize":4,"defaultValue":0},"0x7048f4a8","0x7ae97bd488","0x7ae97c07a0"]}}
Scan Type: QR
Field candidate: false
Field classicFrameCount: 44
Field frameCount: 44
Field frameType: 0
Field maScanResults: [Lcom.alipay.mobile.mascanengine.MaScanResult;@178c311
Field readerParams: null
Field recognizedPerformance: type=Normal^scanType=3^unrecognizedFrame=42^sumDurationOfUnrecognized=1694^durationOfRecognized=51^durationOfBlur=0^durationOfBlurSVM=0^detectFrameCountBlurSVM=0^detectAvgDurationBlurSVM=0.0^durationOfNoNeedCheckBlurSVM=0^whetherGetTheSameLaplaceValue=false^sameLaplaceValueCount=0^
Field rsBinarized: false
Field rsBinarizedCount: 0
Field rsInitTime: 0
Field totalEngineCpuTime: null
Field totalEngineTime: 2811662
Field totalScanTime: 48566

这样就拿到了扫码结果,但是我需要接着跟踪它将扫码结果进行转账通讯的部分,经过分析,最终处理扫描二维码的逻辑是在

   // 1. Hook BaseScanTopView 的所有方法,找到使用 c 的地方
     let BaseScanTopView = Java.use("com.alipay.mobile.scan.ui.BaseScanTopView");
     let NScanTopView = Java.use("com.alipay.mobile.scan.ui2.NScanTopView");
     // Hook 所有方法
     let methods = BaseScanTopView.class.getDeclaredMethods();
     methods.forEach(method => {
         let methodName = method.getName();
         if(BaseScanTopView[methodName]) {
             try {
                 BaseScanTopView[methodName].implementation = function() {
                     console.log(`\n[*] BaseScanTopView.${methodName} 被调用`);
                     
                     // 尝试通过反射获取 c 字段的值
                     try {
                         let field = this.getClass().getSuperclass().getDeclaredField("c");
                         field.setAccessible(true);
                         let cValue = field.get(this);
                         if(cValue) {
                             console.log("[*] c 字段值类型:", cValue.$className);
                             
                             // 如果是 by 接口的实现,hook 它的方法
                             if(Java.use("com.alipay.mobile.scan.ui.by").class.isAssignableFrom(cValue.getClass())) {
                                 console.log("[*] 找到 by 接口实现类:", cValue.$className);
                                 
                                 // Hook 这个实现类的方法
                                 let implClass = Java.use(cValue.$className);
                                 if(implClass.a) {
                                     implClass.a.overload('com.alipay.mobile.mascanengine.MaScanResult', 
                                                        'com.alipay.mobile.scan.util.DataTransChannel')
                                     .implementation = function(maScanResult, dataTransChannel) {
                                         console.log("\n[*] by 实现类的 a 方法被调用");
                                         if(maScanResult != null) {
                                             console.log("[*] 扫描结果:", maScanResult.text.value);
                                         }
                                         return this.a(maScanResult, dataTransChannel);
                                     };
                                 }
                             }
                         }
                     } catch(e) {
                         //console.log("[!] 获取 c 字段失败:", e);
                     }
                     
                     // 调用原方法
                     return this[methodName].apply(this, arguments);
                 };
             } catch(e) {
                 //console.log(`[!] Hook ${methodName} 失败:`, e);
             }
         }
     });
     
     // 2. Hook NScanTopView 的 b 方法
     NScanTopView["b"].overload('com.alipay.mobile.bqcscanservice.BQCScanResult').implementation = function (bQCScanResult) {
         console.log("\n[*] NScanTopView.b 被调用");
         
         // 在调用前尝试获取 c 字段
         try {
             let field = this.getClass().getSuperclass().getDeclaredField("c");
             field.setAccessible(true);
             let cValue = field.get(this);
             if(cValue) {
                 console.log("[*] c 字段实现类:", cValue.$className);
             }
         } catch(e) {
             console.log("[!] 获取 c 字段失败:", e);
         }
         
         let result = this["b"](bQCScanResult);
         return result;
     };
     
     // 3. 动态查找并 hook by 接口的实现类
     Java.choose("com.alipay.mobile.scan.ui.by", {
         onMatch: function(instance) {
             console.log("[*] 找到 by 接口实例:", instance.$className);
             // Hook 这个实例的方法
             let implClass = Java.use(instance.$className);
             if(implClass.a) {
                 implClass.a.overload('com.alipay.mobile.mascanengine.MaScanResult', 
                                    'com.alipay.mobile.scan.util.DataTransChannel')
                 .implementation = function(maScanResult, dataTransChannel) {
                     console.log("\n[*] by 实现类的 a 方法被调用");
                     if(maScanResult != null) {
                         console.log("[*] 扫描结果:", maScanResult.text.value);
                     }
                     return this.a(maScanResult, dataTransChannel);
                 };
             }
         },
         onComplete: function() {}
     });

输出结果:

[*] NScanTopView.b 被调用
[*] c 字段实现类: com.alipay.mobile.scan.as.main.MainCaptureActivity
MainCaptureActivity.a is called: maScanResult=com.alipay.mobile.mascanengine.MaScanResult@80f5308, dataTransChannel=DataTransChannel{isFromAlbum=false, albumImagePath='null', scanCode='null', payLinkToken='null', controlType='camera', isFromRoute=false, isCache=false, bizType='null', custProgressDialog=com.alipay.mobile.scan.ui2.NBluePointView{a02faa1 I.E...... ......I. 0,0-1080,2028}}
MainCaptureActivity.a result=true

[*] BaseScanTopView.p 被调用
[*] c 字段值类型: com.alipay.mobile.scan.as.main.MainCaptureActivity
[*] 找到 by 接口实现类: com.alipay.mobile.scan.as.main.MainCaptureActivity

[*] BaseScanTopView.q 被调用
[*] c 字段值类型: com.alipay.mobile.scan.as.main.MainCaptureActivity
[*] 找到 by 接口实现类: com.alipay.mobile.scan.as.main.MainCaptureActivity

然后去查看了 com.alipay.mobile.scan.as.main.MainCaptureActivity 这个类的 a 方法实现过程:

 public final boolean a(com.alipay.mobile.mascanengine.MaScanResult r17, com.alipay.mobile.scan.util.DataTransChannel r18) {
        /*

            r16 = this;
            r1 = r16
            …………………………
            …………………………
            …………………………
            boolean r0 = r2.a(r3, r4, r5, r6, r7, r8)

        */
    }

因为无法正常编译成java代码,并且很长,一如既往的让AI帮我分析,AI得出结果这个方法是一个判断扫描结果类型,进行路由分发的

// 判断不同的扫码类型
com.alipay.mobile.mascanengine.MaScanType r3 = com.alipay.mobile.mascanengine.MaScanType.PRODUCT
com.alipay.mobile.mascanengine.MaScanType r11 = r2.type

if (r3 == r11) goto Ldb  // 商品条码
// ... 其他类型判断
com.alipay.mobile.mascanengine.MaScanType r3 = com.alipay.mobile.mascanengine.MaScanType.QR  // 二维码

// 二维码处理
if (r3 == r11) goto Lc3  // QR类型
    r10.put(r9, r3)      // 存储扫描文本
    // 处理隐藏数据
    if (!TextUtils.isEmpty(r2.hiddenData)) {
        r10.put("hiddenData", r2.hiddenData)
    }
    
// 收集扫描性能数据
r0.put("totalTime", r8)     
r0.put("scanTime", r8)
r0.put("realTime", r8)

com.alipay.phone.scancode.y.a r0 = r1.d
// ... 参数准备
boolean r0 = r2.a(r3, r4, r5, r6, r7, r8)  // 调用实际的业务处理方法

然后在这个混淆的方法里面,找到了关键类
com.alipay.phone.scancode.y.a

 let ScanCodeHandler = Java.use("com.alipay.phone.scancode.y.a");
    
    ScanCodeHandler.a.overload('java.lang.String', 'java.util.Map', 'java.lang.String', 
                              'java.lang.String', 'com.alipay.mobile.scan.util.DataTransChannel', 
                              'java.lang.String').implementation = function(str1, map, str2, str3, channel, str4) {
        console.log("\n[*] 扫码业务处理被调用");
        console.log("[*] 扫描类型:", str1);
        console.log("[*] 扫描内容:", str3);
        
        let result = this.a(str1, map, str2, str3, channel, str4);
        console.log("[*] 处理结果:", result);
        return result;
    };


[*] 扫码业务处理被调用
[*] 扫描类型: qrCode
[*] 扫描内容: https://qr.alipay.com/fkx17******0?t=1733900000000
[*] 处理结果: true
MainCaptureActivity.a result=true

最后跟踪进 a 方法里面的实现过程(发现也是无法正常编译的java代码)
这个函数的过程作用大致如下:
1、初始化参数处理
2、检查是否由路由传过来的参数
3、检查是否支持离线支付
4、处理路由
5、缓存处理
6、记录支付行为
最后,hook了这个方法里面的相关涉及的类,得出hook代码以及输出结果

   // 1. Hook 主要的路由处理方法
    let CodeRouter = Java.use("com.alipay.phone.scancode.y.a");
    CodeRouter.a.overload('java.lang.String', 'java.util.Map', 'java.lang.String', 'java.lang.String', 'int', 'java.lang.String')
    .implementation = function(type, map, sourceId, content, flag, token) {
        console.log("\n[*] 扫码支付路由被调用");
        console.log("[*] 类型:", type);
        console.log("[*] 内容:", content);
        console.log("[*] 来源ID:", sourceId);
        console.log("[*] Token:", token);
        
        // 打印Map参数
    
        
        // 2. Hook DataTransChannel
        try {
            let channel = this.D.value;
            if(channel != null) {
                console.log("[*] DataTransChannel 信息:");
                console.log("  是否来自相册:", channel.f115683a.value);
                console.log("  图片路径:", channel.b.value);
                console.log("  扫描内容:", channel.c.value);
                console.log("  Channel类型:", channel.e.value);
                
                // 打印额外参数
                if(channel.m != null) {
                    let extraParams = channel.m.value;
                    console.log("[*] 额外参数:");
                    let extraIterator = extraParams.entrySet().iterator();
                    while(extraIterator.hasNext()) {
                        let entry = extraIterator.next();
                        console.log("  ", entry.getKey() + " = " + entry.getValue());
                    }
                }
            }
        } catch(e) {
            console.log("[!] 获取Channel信息失败:", e);
        }
        
        // 3. Hook 离线支付检查
        try {
            if(this.I != null) {
                let offlineHandler = this.I.value;
                let offlineResult = offlineHandler.a(content);
                if(offlineResult != null) {
                    console.log("[*] 离线支付信息:");
                    console.log("  URI:", offlineResult.f);
                    console.log("  Method:", offlineResult.g);
                    console.log("  Type:", offlineResult.d);
                }
            }
        } catch(e) {
            console.log("[!] 获取离线支付信息失败:", e);
        }
        
        // 4. Hook RouteInfo 创建
        try {
            let RouteInfo = Java.use("com.alipay.mobile.scan.biz.RouteInfo");
            RouteInfo.$init.implementation = function() {
                console.log("[*] 创建新的RouteInfo");
                return this.$init();
            };
            
            RouteInfo.setUri.implementation = function(uri) {
                console.log("[*] 设置RouteInfo URI:", uri);
                return this.setUri(uri);
            };
            
            RouteInfo.setMethod.implementation = function(method) {
                console.log("[*] 设置RouteInfo Method:", method);
                return this.setMethod(method);
            };
        } catch(e) {
            console.log("[!] Hook RouteInfo失败:", e);
        }
     
        // 打印调用栈
        console.log("[*] 调用栈:");
        console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
        
        let result = this.a(type, map, sourceId, content, flag, token);
        console.log("[*] 路由处理结果:", result);
        return result;
    };
   
[*] 设置RouteInfo Method: native
[*] 创建新的RouteInfo
[*] 设置RouteInfo Method: native
[*] 设置RouteInfo URI: alipays://platformapi/startapp?appId=20001001&bizType=UTP_QR_CODE&pageData=%7B%22tpl%22%*******

通过输出结果,发现最后会生成出路由URI,用于唤起支付界面的,尝试通过adb直接打开这个URI看看是否能直接跳转到转账界面。

adb shell am start -a android.intent.action.VIEW -d “alipays://platformapi/startapp?appId=20001001&bizType=UTP_QR_CODE&pageData=省略参数”

最后成功直接打开转账界面,由此分析出了整个扫码转账的执行环节。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

诺离

创作不易,令君打赏,一分也是爱

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值