风控部门需要根据借贷人的手机号搜索其微信,对昵称和签名进行分析识别贷款中介或高风险借贷者。比如下图中的借贷人就很有可能是个中介

每次搜索都要进行以下两个步骤

  1. 在微信中查找借贷人手机号
  2. 如果有查到,将其他个性签名记录下来

对于大量借贷人,如果人工记录的话要花费大量人力与时间,为此需要将其进行自动化。本文介绍了通过xposed调用微信相关代码实现用户昵称与签名的批量抓取。

分析思路

先观察微信的搜索过程。在搜索框中输入手机号后点击搜索,如果有用户会直接弹出用户资料的activity,否则提示用户不存在。因此可以判断搜索函数是在搜索按钮的点击事件回调函数内调用的,且搜索过程是异步执行的,当搜索后会有回调函数拉起acitvity或提示用户不存在。

分析过程

启用log

通过查看微信的运行log有助于发现微信的执行逻辑,但默认是关闭的,需要通过xposed启用。可以安装xposed插件。如下是作者写的xlog插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class XlogPlugin {
private Class xlogClz;

public XlogPlugin() {
xlogClz = XposedHelpers.findClass("com.tencent.mars.xlog.Xlog", WechatPackage.getInstance().classLoader);
}

public void onLogMessage(Consumer<LogMessage> consumer){
XposedBridge.hookAllMethods(xlogClz, "logWrite2", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
if(param.args.length!=9){
return;
}
String tag = String.valueOf(param.args[1]),
str = String.valueOf(param.args[8]);
int level = (int)param.args[0];
consumer.accept(new LogMessage(level, tag, str));
}
});
}
}

定位关键代码

首先通过hierachyviewer查看Activity类名,FTSAddFriendUI为搜索用户界面,ContactInfoUI为用户资料Activity。
在手机上搜索用户,查看log发现

1
I/MicroMsg.FTS.FTSBaseAdapter: start search query=xxxx

用关键字“start search query”搜索,定位到

1
com/tencent/mm/plugin/fts/ui/d.smali:164: const-string/jumbo v1, "start search query=%s"

查看com.tencent.mm.plugin.fts.ui.d的反编译代码发现它是一个抽象类,且继承自BaseAdapter。我们知道填充下拉列表的Adapter选项的回调函数是onItemClick

1
2
3
4
5
6
7
8
public void onItemClick(AdapterView<?> adapterView, View view, int i, long j) {
this.mWd.YF();
if (i < getCount()) {
a qA = qA(i);
a(view, qA, qA.BP().a(getContext(), qA));
this.mWd.a(qA);
}
}

通过动态调试发现this.mWd就是FTSAddFriendUI的引用,也就是说FTSAddFriendUI#a方法的作用就是执行搜索,其内容如下

1
2
3
4
5
6
public final void a(com.tencent.mm.plugin.fts.a.d.a.a aVar) {
if (aVar instanceof com.tencent.mm.plugin.fts.ui.a.a) {
this.mBV = aVar.mRx.mRV;
CB(aVar.mRx.mRV);
}
}

其中CB方法的作用就是发起搜索并注册回调,其伪代码如下

1
2
3
g.Dv().a(106, fTSAddFriendUI$5);
final l fVar = new com.tencent.mm.plugin.messenger.a.f(str, 3);
g.Dv().a(fVar, 0);

第一行用于注册搜索事件的回调接口,第二行创建搜索消息对象,第三行发起搜索。

为验证猜测,我们使用frida进行测试。先执行如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
jscode = """
Java.perform(function() {
const clz = Java.use('com.tencent.mm.plugin.fts.ui.FTSAddFriendUI$5');
const interface = Java.use('com.tencent.mm.plugin.messenger.a.f');
clz.a.implementation=function(p1, p2, p3, p4){
console.log('callback invoked');
send({'p1': p1, 'p2': p2, 'p3': p3, 'p4': JSON.stringify(p4)})
var obj = Java.cast(p4.$handle, interface);
var tmp = obj.fOL;
// console.log('username: ' + Java.cast(tmp.$handle, tmp_clz).wBK.xnT.value);
console.log('username' + tmp);

var result = obj.bcG();
send({'city': result.hHn.value, 'region': result.hHo.value, 'signature': result.hHp.value, 'wxid': result.hHr.value,
'country': result.hHv.value, 'nick': result.wVX.value});
this.a(p1, p2, p3, p4);
};
})
"""


def on_message(msg, data):
print(msg)


process = frida.get_usb_device().attach('com.tencent.mm')
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
print('running!!!')
sys.stdin.read()

然后操作微信进行搜索,发现在控制台成功得到搜索结果,证明我们的猜测是正确的。
frida获取用户信息

xposed插件的开发

由于com.tencent.mm.plugin.fts.ui.FTSAddFriendUI$5是匿名内部对象(实现接口com.tencent.mm.ac.e),它的创建依赖于FTSAddFriendUI,但我们需要在不打开微信界面的情况下实现功能调用,所以需要重新实现该接口。但这就出问题了:微信的代码运行于微信的classloader下,我们在xposed模块中是无法直接引用这个类的,更别提直接创建这个接口的类了。这里用java的动态代理就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Object callback = Proxy.newProxyInstance(WechatPackage.getInstance().classLoader, new Class[] { searchCallbackIface }, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
XposedBridge.log("动态代理回调: "+method.getName());
if(method.getName().equals("hashCode")){
return 100;
}
if(method.getName().equals("equals")){
return args[0].hashCode()==100;
}
XposedBridge.log("查询结果: " + args[2]);
// args[0] 错误码; args[1] 状态码; args[2]查询状态: "Everything is OK", 否则为"User do not exist";
// args[3] Lcom/tencent/mm/plugin/messenger/a/f;对象。
consumer.accept(new SearchResult(String.valueOf(args[2]), args[3]));
try {
Object result = getSearchResultMth.invoke(args[3]);
}catch (Exception e){}
return null;
}
});

在微信kernel初始化后执行

1
registerCallbackMth.invoke(requestSenderObj, 106, callback);

注册搜索事件的回调。
至于搜索消息的发送,与发送聊天消息一样,创建消息对象并发送就可以了。

1
2
3
4
5
6
public void searchUser(String query) throws Exception{
XposedBridge.log("search user: "+query);
Object searchReq= newInstance(searchRequestClz, query, 3);
// 添加任务
sendRequestMth.invoke(requestSenderObj, searchReq, 0);
}

本文首发于www_wisedream_net,转载请注明出处!!!