百度HI使用XPOSED自动抢红包
xposed是一个安卓的hook框架,这个就不介绍了。大致流程是百度hi接收红包消息,xposed 去hook接收消息的函数,根据接收到的消息类型,如果是红包,则想办法打开它。
1 分析接收红包代码
百度hi作为我厂比较成熟的内部聊天工具,考虑兼容性等,不敢加壳,但是肯定敢混淆。代码零零散散,但是有些关键的地方肯定不会混淆。百度hi/微信等,接收到消息后,第一步会将消息格式化等等,并插入sqlite数据库中。插入数据户的参数一般使用contentvalue,其他两个参数分别是表名,库名等等,类型为Sting。首先使用enjarify 去反编译百度hi,拿到代码后,查找sqlite,insert关键字。可以发现只有一个函数满足要求,函数签名如下
net.sqlcipher.database.SQLiteDatabase", "insert",String.class, String.class, ContentValues.class
然后写一个简单的xposed插件,hook这个函数,拿到ContentValue请求,并遍历ContentValue。可以看到请求如下
多看一下请求,可以得到以下规律
- 函数的第一个参数,为接收的消息类型,message为个人消息,topic_message为讨论组消息,group_message为群消息
- 函数的第二个参数,不明。。。
- ContentValues的msg_body,原始xml格式的百度hi消息,此消息会显示到聊天对话框
- sent_status 1为发送,-1位接收消息
- display_msg,原始xml格式经过解析后的消息体
- display_time ,接收/发送消息的时间
- display_name ,接收/发送的消息人
如果是红包消息的话,msg_body类似于下面:
聪明的你可能也看出来了,luckymoney的id,可能和红包有关。现在开始逆向代码吧。
反正混淆代码我逆向了半天实在看不懂逻辑,下面介绍另一种方法。
首先使用burp suit代理安卓手机请求(具体自己谷歌),我厂百度hi肯定会有https双向认证。然后不出意外就会报错,也就是xxxException。这时候,我们打开adb log | grep err,就可以拿到抛出异常的错误堆栈了,如下所示,
05-23 14:57:04.436 12462 13611 W System.err: org.apache.http.conn.ConnectTimeoutException: Connect to /195.168.2.1:8080 timed out
05-23 14:57:04.447 12462 13611 W System.err: at org.apache.http.conn.scheme.PlainSocketFactory.connectSocket(PlainSocketFactory.java:126)
05-23 14:57:04.449 12462 13611 W System.err: at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:149)
05-23 14:57:04.450 12462 13611 W System.err: at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:169)
05-23 14:57:04.451 12462 13611 W System.err: at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:124)
05-23 14:57:04.452 12462 13611 W System.err: at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:366)
05-23 14:57:04.453 12462 13611 W System.err: at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:560)
05-23 14:57:04.455 12462 13611 W System.err: at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:492)
05-23 14:57:04.456 12462 13611 W System.err: at com.loopj.android.http.AsyncHttpRequest.makeRequest(SourceFile:146)
05-23 14:57:04.457 12462 13611 W System.err: at com.loopj.android.http.AsyncHttpRequest.makeRequestWithRetries(SourceFile:177)
05-23 14:57:04.458 12462 13611 W System.err: at com.loopj.android.http.AsyncHttpRequest.run(SourceFile:106)
05-23 14:57:04.460 12462 13611 W System.err: at com.loopj.android.http.SyncHttpClient.sendRequest(SourceFile:95)
05-23 14:57:04.462 12462 13611 W System.err: at com.loopj.android.http.AsyncHttpClient.post(SourceFile:1087)
05-23 14:57:04.463 12462 13611 W System.err: at com.baidu.hi.luckymoney.channel.c.c.c.load(SourceFile:209)
05-23 14:57:04.463 12462 13611 W System.err: at com.baidu.hi.luckymoney.channel.c.b.a.a(SourceFile:26)
05-23 14:57:04.464 12462 13611 W System.err: at com.baidu.hi.luckymoney.channel.c.b.n.Qn(SourceFile:43)
05-23 14:57:04.465 12462 13611 W System.err: at com.baidu.hi.luckymoney.channel.c.b.n.Qc(SourceFile:10)
05-23 14:57:04.466 12462 13611 W System.err: at com.baidu.hi.luckymoney.channel.c.b.a.Qb(SourceFile:80)
05-23 14:57:04.466 12462 13611 W System.err: at com.baidu.hi.luckymoney.channel.c.b.a.oT(SourceFile:11)
05-23 14:57:04.467 12462 13611 W System.err: at com.baidu.hi.file.a.EP(SourceFile:33)
05-23 14:57:04.468 12462 13611 W System.err: at com.baidu.hi.luckymoney.channel.c.i.Pw(SourceFile:58)
05-23 14:57:04.469 12462 13611 W System.err: at com.baidu.hi.luckymoney.channel.a.Px(SourceFile:15)
05-23 14:57:04.470 12462 13611 W System.err: at com.baidu.hi.luckymoney.channel.a.run(SourceFile:32)
05-23 14:57:04.471 12462 13611 W System.err: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
05-23 14:57:04.471 12462 13611 W System.err: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
05-23 14:57:04.472 12462 13611 W System.err: at java.lang.Thread.run(Thread.java:818)
这样可以愉快地分析啦,原来是因为双向证书校验不通过报错,抛出异常信息。我们可以分析错误对战,找到从点击接受红包到发送http请求的所有逻辑。在接收到红包后,经过一系列复杂的过程,通过com.baidu.hi.luckymoney.c.c.c的load函数组合成合适的参数,并通过https的请求参数,然后调用com.loopj.android.http.AsyncHttpClient.Post函数,去发送post请求。下面是DDMS的详情
下一个问题是,怎么吧post的请求拿出来。虽然有双向认证,但是可以使用xposed的JustTrustme插件啊,就是信任所有的https请求,轻松绕过证书校验。下面是抓取到的https请求
解释一下,首先,百度hi拿到luckymonry的id,也就是xml消息格式的红包id,构造请求字符串,请求tryopen,是否可以打开红包,也就是红包校验,金额等等等等全都在服务器端操作,防止本地出现伪造的情况。返回json格式的字符串,如果返回200,则说明可以打开红包。否则给用户提示,红包已经被抢/xxx等等等。
然后再发送post到/luckymonry/open,去打开红包,post请求参数位json字符串。name为抢红包用户的名字,money_id为上面所说的红包id,user_type未知,user_agent是用户的设备详细信息,cuid等等,不知道,但是貌似重复使用一个也没有问题,imei是手机串号,sim_serial_number是用户sim卡的地区代码,89860为中国。
好了,现在知道红包是怎么打开的了,那我们可以在xposed中判断,如果接收到的msg_body消息中含有luckymoney标签,则构造http请求去打开红包。开干。。
2. 编写xposed代码
还有一个问题,是BDUSS,根据我厂资料,此cookie参数是为了识别用户的,如果这个写死,灵活性怕是大打折扣,于是看一下com.baidu.hi.luckymoney.c.c.c的load函数
原来是初始化cookie类,然后设置key-value为BDUSS,设置cookie的domain为baidu.com。看一下bduss,发现不知道是怎么生成的。并且xposed也不能hook 初始化之后的变量。但是,xposed可以hook构造函数啊,我们可以得出结论,百度hi肯定会初始化cookie类,不管在什么时候。这样的话,我们可以hook org.apache.http.impl.cookie.BasicClientCookie的构造函数啊,如果key包含BDUSS,很有可能是百度hi在构造http请求并访问百度系的网站。
代码如下
// hook sqlite insert function to filter all message and find luckymoney
if (loadPackageParam.packageName.equals("com.baidu.hi")) {
XposedBridge.log("We are in baidu hi!!");
findAndHookMethod("net.sqlcipher.database.SQLiteDatabase", loadPackageParam.classLoader,
"insert",
String.class, String.class, ContentValues.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log(param.getResult().toString());
ContentValues contentValues = (ContentValues) param.args[2];
String msg_type = (String) param.args[0];
String msg_body = contentValues.getAsString("msg_body");
if (msg_body != null && msg_body.contains("luckymoney") && msg_body.contains("sender_name")){
String luckymoney = msg_body.substring(msg_body.indexOf(""));
String Lucky_id = luckymoney.substring(16,48);
XposedBridge.log("hi: found lucky" + Lucky_id);
tryOpen(Lucky_id);
}
}
});
反正是接收到消息中包含luckymoney,相办法拿到id,并调用tryopen函数,反正大部分不重要的参数都写死了,看看就行了。
// try open luckymoney ,if return 200 in response json,then call Open
public void tryOpen(String id){
String json = "{\"money_id\":\"" + id + "\"}";
String post = Post("https://ext.im.baidu.com/luckymoney/tryopen", json);
OpenLucky(id);
}
public void OpenLucky(String id){
String json = "{\"name\":\"11111111\",\"money_id\":\""+id+"\",\"user_type\":1,\"user_agent\":\"Baidu Hi-5.0.3.0-Android-hi_1080_1794_AOSP on BullHead_25_7.1.1_6.12.7_6.12.7.0\",\"cuid\":\"F0DCC1DB2B6CE8500AACE56F961338F2|0\",\"imei\":\"\",\"imsi\":\"460022476553448\",\"sim_serial_num\":\"898600\",\"version\":\"2\"}";
String resp = Post("https://ext.im.baidu.com/luckymoney/open",json);
XposedBridge.log("Baidu HI: luckymoney");
}
public String Post(String url,String json){
String result = "";
OkHttpClient okHttpClient = new OkHttpClient();
//创建一个RequestBody(参数1:数据类型 参数2传递的json串)
RequestBody requestBody = RequestBody.create(JSON, json);
//创建一个请求对象
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.addHeader("Cookie","BDUSS="+BDUSS)
.addHeader("Cookie2","$Version=1")
.build();
try {
Response response=okHttpClient.newCall(request).execute();
if(response.isSuccessful()){
result = response.body().string();
}
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
下面是获取BDUSS的xposed代码
//TRY TO HOOK org.apache.http.impl.cookie.BasicClientCookie CONSTROCTOR TO GET BDUSS
findAndHookConstructor("org.apache.http.impl.cookie.BasicClientCookie",loadPackageParam.classLoader, String.class,
String.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
String name = (String)param.args[0];
if (name.contains("BDUSS")){
BDUSS = (String)param.args[1];
XposedBridge.log("HI:BDUSS :" + BDUSS);
}
}
});
然后,编译好apk,上传手机,激活xposed模块,就可以愉快的抢红包了,再也不怕部门大佬发大红包抢不到了