需要分析支付某打开扫一扫,扫到目标二维码后是如何跳转到转账页面的。
发现扫码从数据包无从下手定位,尝试关键字:QRScan,QRCode,Translate等都找不到对应的函数,所以最终通过View的点击事件。
打印了堆栈,从堆栈结果里面找到了一个疑似创建扫码相机的View:
com.alipay.mobile.scan.ui2.NScanTopView
因为这个类代码量巨大,5000多行,而且都是混淆过的名称,我直接发给ai进行分析,得出了大概扫描后的回调结果是在a方法:
但是代码的过程看不到,只能看Smail指令,并且对应参数r25里面的BQCScanResult也是啥都没有,只能通过方法的执行过程分析(还是给AI分析)随后得出hook代码:
输出结果:
这样就拿到了扫码结果,但是我需要接着跟踪它将扫码结果进行转账通讯的部分,经过分析,最终处理扫描二维码的逻辑是在
输出结果:
然后去查看了 com.alipay.mobile.scan.as.main.MainCaptureActivity 这个类的 a 方法实现过程:
因为无法正常编译成java代码,并且很长,一如既往的让AI帮我分析,AI得出结果这个方法是一个判断扫描结果类型,进行路由分发的
然后在这个混淆的方法里面,找到了关键类com.alipay.phone.scancode.y.a
最后跟踪进 a 方法里面的实现过程(发现也是无法正常编译的java代码) 这个函数的过程作用大致如下: 1、初始化参数处理 2、检查是否由路由传过来的参数 3、检查是否支持离线支付 4、处理路由 5、缓存处理 6、记录支付行为 最后,hook了这个方法里面的相关涉及的类,得出hook代码以及输出结果
通过输出结果,发现最后会生成出路由URI,用于唤起支付界面的,尝试通过adb直接打开这个URI看看是否能直接跳转到转账界面。
adb shell am start -a android.intent.action.VIEW -d "alipays://platformapi/startapp?appId=20001001&bizType=UTP_QR_CODE&pageData=省略参数"
最后成功直接打开转账界面,由此分析出了整个扫码转账的执行环节。。
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
;
};
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
;
};
@Override
/
/
com.alipay.mobile.scan.ui.BaseScanTopView
public final void a(com.alipay.mobile.bqcscanservice.BQCScanResult r25) {
/
/
... existing code ...
}
@Override
/
/
com.alipay.mobile.scan.ui.BaseScanTopView
public final void a(com.alipay.mobile.bqcscanservice.BQCScanResult r25) {
/
/
... existing code ...
}
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);
};
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
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() {}
});
/
/
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) {
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 6天前
被kzzll编辑
,原因: