篇幅有限 完整内容及源码关注公众号:ReverseCode,发送 冲
脱壳 image-20210529115117279
plain
1 2 3 4 5 6 7 git clone https://github.com/hluwa/FRIDA-DEXDump.git ./fs1280arm64 pyenv local 3.8.0 python main.py app保持最前端,开始脱壳 python merge_dex.py ./com.hay.dreamlover/ livedex 将脱壳后的dex反编译成java,推荐!!! git clone https://github.com/Simp1er/AndroidSec.git python dex2apk.py -a haydream.apk -i ./com.hay.dreamlover -o output.apk 将脱壳后的dex重打包成apk
抓包 charles-postern连接socks5启动vpn 发现任何一个页面只要连接代理或者vpn都无法访问请求
某抢票app逆向续篇之干掉vpn抓包检测 api有java.net.NetworkInterface.getName(),android.net.ConnectivityManager
plain
1 2 3 4 5 6 7 8 9 10 11 objection -g com.hay.dreamlover explore android hooking search classes networkinterface android hooking watch class java.net.NetworkInterface android hooking search classes connectivitymanager android hooking watch class android.net.ConnectivityManager android hooking watch class android.net.IConnectivityManager 进入页面后果然触发了这些方法,说明确实做了vpn检测 android hooking watch class_method android.net.ConnectivityManager.getActiveNetworkInfo --dump-args --dump-backtrace --dump-return 打印调用栈,通过jadx查看LiveNetChecker,尝试使用frida脚本过这段代码逻辑 git clone https://github.com/r0ysue/r0capture.git python3 r0capture.py -U com.hay.dreamlover -v 先打开app后attach,log显示127.0.0.1和127.0.0.1通信,且所有内容都已经加密 frida -U -f com.hay.dreamlover -l script.js -o out.txt python3 r0capture.py -U com.hay.dreamlover -v -w 3 延迟3秒
使用带有kali nethunter底包的nexus 6p,开启ssh服务
plain
1 2 3 4 5 ssh root@192.168.0.104 默认密码toor jnettop 点开直播间,可以看到直播间地址和ip 手机自制路由器,电脑插上网卡连接到虚拟机,nm-connection-editor,新加一个Wi-Fi,设置SSID,Mode设置Hotspot,Band设置B/G(2.4 GHZ),Device选择wlan0,配置IPv4 Settings的Address,手机即可收到新wifi热点 lsusb 查看设备型号 wireshark 抓包
收费直播间分析 直播弹窗倒计时9s后强制退出
plain
1 2 3 4 5 6 7 objection -g com.hay.dreamlover explore android hooking search classes Dialog android hooking search classes AlertDialog android hooking search classes PopupWindow android hooking watch class android.app.Dialog --dump-args --dump-backtrace --dump-return android hooking watch class android.app.AlertDialog --dump-args --dump-backtrace --dump-return android hooking watch class_method android.app.Dialog.show --dump-args --dump-backtrace --dump-return
(agent) [8z3ukmteu2y] Called android.app.Dialog.show() (agent) [8z3ukmteu2y] Backtrace: android.app.Dialog.show(Native Method) com.fanwe.lib.dialog.impl.SDDialogBase.show (SDDialogBase.java:337) com.fanwe.live.activity.room.LiveLayoutViewerExtendActivity.showScenePayJoinDialog(LiveLayoutViewerExtendActivity.java:618) com.fanwe.live.activity.room.LiveLayoutViewerExtendActivity.onScenePayViewerShowWhetherJoin(LiveLayoutViewerExtendActivity.java:516) com.fanwe.pay.LiveScenePayViewerBusiness.dealPayModelRoomInfoSuccess(LiveScenePayViewerBusiness.java:156) com.fanwe.live.activity.room.LiveLayoutViewerExtendActivity.onBsRequestRoomInfoSuccess(LiveLayoutViewerExtendActivity.java:111) com.fanwe.live.activity.room.LivePushViewerActivity.onBsRequestRoomInfoSuccess(LivePushViewerActivity.java:405) com.fanwe.live.business.LiveBusiness.onRequestRoomInfoSuccess (LiveBusiness.java:306) com.fanwe.live.business.LiveViewerBusiness.onRequestRoomInfoSuccess(LiveViewerBusiness.java:79) com.fanwe.live.business.LiveBusiness$2.onSuccess(LiveBusiness.java:257) com.fanwe.library.adapter.http.callback.SDRequestCallback.onSuccessInternal(SDRequestCallback.java:127) com.fanwe.library.adapter.http.callback.SDRequestCallback.notifySuccess(SDRequestCallback.java:175) com.fanwe.hybrid.http.AppHttpUtil$1.onSuccess(AppHttpUtil.java:105) com.fanwe.hybrid.http.AppHttpUtil$1.onSuccess(AppHttpUtil.java:74) org.xutils.http.HttpTask.onSuccess(HttpTask.java:447) org.xutils.common.task.TaskProxy$InternalHandler.handleMessage(TaskProxy.java:198) android.os.Handler.dispatchMessage(Handler.java:106) android.os.Looper.loop(Looper.java:164) android.app.ActivityThread.main(ActivityThread.java:6494) java.lang.reflect.Method.invoke(Native Method) com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
(agent) [8z3ukmteu2y] Return Value: (none)
frida -UF -l trace.js -o hay.txt 打印了多个android.app.Dialog.show
plain
1 traceClass("android.app.Dialog")
结合jadx分析弹窗堆栈,实现逻辑可以干掉弹窗或者干掉倒计时
plain
1 2 3 4 android hooking search methods startCountDown android hooking watch class com.fanwe.pay.appview.PayLiveBlackBgView 进入直播间确实触发了该方法 android hooking watch class_method com.fanwe.pay.appview.PayLiveBlackBgView.startCountDown --dump-args --dump-backtrace --dump-return android hooking list class_methods com.fanwe.lib.dialog.impl.SDDialogBase
frida -UF -l hookVIP.js 破解收费直播间
plain
1 2 3 4 5 6 7 8 9 10 11 12 13 14 setImmediate(function(){ Java.perform(function(){ console.log("Entering hook") // 干掉弹窗 Java.use("com.fanwe.lib.dialog.impl.SDDialogBase").show.implementation = function(){ console.log("hook show ") } // 设置倒计时 Java.use("com.fanwe.pay.appview.PayLiveBlackBgView").startCountDown.implementation = function(x){ console.log("Calling countdown ") return this.startCountDown(1000*3600) } }) })
image-20210529165859370
在 com.fanwe.live.business.LiveBusiness.onRequestRoomInfoSuccess 类中大部分request都来自于CommonInterface类
plain
1 2 3 4 5 6 android hooking watch class com.fanwe.live.common.CommonInterface 每进一个房间或下拉自动触发该类的方法 android hooking watch class_method com.fanwe.live.common.CommonInterface.requestIndex --dump-args --dump-backtrace --dump-return android hooking watch class_method com.fanwe.live.common.CommonInterface.requestRoomInfo --dump-args --dump-backtrace --dump-return 进入直播间,log打印room_id plugin wallbreaker classdump --fullname com.fanwe.live.business.LiveBusiness$2 android hooking search classes com.fanwe.live.business.LiveBusiness android hooking watch class com.fanwe.live.business.LiveBusiness
(agent) [2xhzmmkxx1r] Called com.fanwe.live.common.CommonInterface.requestIndex(int, int, int, java.lang.String, com.fanwe.hybrid.http.AppRequestCallback)
(agent) [2xhzmmkxx1r] Backtrace: com.fanwe.live.common.CommonInterface.requestIndex(Native Method) com.fanwe.live.appview.main.LiveTabHotView.requestData(LiveTabHotView.java:390) com.fanwe.live.appview.main.LiveTabHotView.onLoopRun(LiveTabHotView.java:382) com.fanwe.live.appview.main.LiveTabBaseView$1.run(LiveTabBaseView.java:116) com.fanwe.lib.looper.impl.SDSimpleLooper$1.handleMessage(SDSimpleLooper.java:54) android.os.Handler.dispatchMessage(Handler.java:106) android.os.Looper.loop(Looper.java:164) android.app.ActivityThread.main(ActivityThread.java:6494) java.lang.reflect.Method.invoke(Native Method) com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
(agent) [2xhzmmkxx1r] Arguments com.fanwe.live.common.CommonInterface.requestIndex(1, (none), (none), 热门, com.fanwe.live.appview.main.LiveTabHotView$4@89cbef4) (agent) [2xhzmmkxx1r] Return Value: (none)
plain
1 2 3 android hooking search classes SDResponse plugin wallbreaker classdump --fullname com.fanwe.library.adapter.http.model.SDResponse android hooking watch class_method com.fanwe.library.adapter.http.model.SDResponse.getDecryptedResult --dump-args --dump-backtrace --dump-return
frida -UF -l requestIndex.js 主动调用获取房间列表和详情
plain
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 function hook() { Java.perform(function () { var JSON = Java.use("com.alibaba.fastjson.JSON") var Index_indexActModel = Java.use("com.fanwe.live.model.Index_indexActModel"); var gson = Java.use("com.google.gson.Gson").$new(); var LiveRoomModel = Java.use("com.fanwe.live.model.LiveRoomModel"); Java.use("com.fanwe.live.appview.main.LiveTabHotView$4").onSuccess.implementation = function (resp) { console.log("Entering Room List Parser => ", resp) var result = resp.getDecryptedResult(); // 转成json对象 var resultModel = JSON.parseObject(result, Index_indexActModel.class); // json转成java对象,并调用getList方法 var roomList = Java.cast(resultModel, Index_indexActModel).getList(); console.log("size : ", roomList.size(), roomList.get(0)) for (var i = 0; i < roomList.size(); i++) { var LiveRoomModelInfo = Java.cast(roomList.get(i), LiveRoomModel); console.log("roominfo: ", i, " ", gson.toJson(LiveRoomModelInfo)); } return this.onSuccess(resp) } }) } // 主动调用 function invoke(){ Java.perform(function(){ Java.choose("com.fanwe.live.appview.main.LiveTabHotView",{ onMatch:function(ins){ console.log("found ins => ",ins) ins.requestData(); },onComplete:function(){ console.log("Search completed!") } }) }) } function main() { hook() // invoke() } setImmediate(main)
image-20210529170029292
frida -UF -l requestRoomInfo.js
plain
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 32 33 34 35 36 37 38 39 40 41 // 遍历类所有的域 function inspectObject(obj) { Java.perform(function () { const Class = Java.use("java.lang.Class"); const obj_class = Java.cast(obj.getClass(), Class); const fields = obj_class.getDeclaredFields(); const methods = obj_class.getMethods(); console.log("Inspecting " + obj.getClass().toString()); console.log("\tFields:"); for (var i in fields){ // console.log("\t\t" + fields[i].toString()); var className = obj_class.toString().trim().split(" ")[1] ; // console.log("className is => ",className); var fieldName = fields[i].toString().split(className.concat(".")).pop() ; console.log(fieldName + " => ",obj[fieldName].value); } // console.log("\tMethods:"); // for (var i in methods) // console.log("\t\t" + methods[i].toString()); }) } // 打印调用结果的域及信息,类似wallbreaker function hookROOMinfo() { Java.perform(function () { var JSON = Java.use("com.alibaba.fastjson.JSON") var gson = Java.use("com.google.gson.Gson").$new(); var App_get_videoActModel = Java.use("com.fanwe.live.model.App_get_videoActModel"); Java.use("com.fanwe.live.business.LiveBusiness$2").onSuccess.implementation = function (resp) { console.log("Enter LiveBusiness$2 ... ", resp) var result = resp.getDecryptedResult(); var resultVideoModel = JSON.parseObject(result, App_get_videoActModel.class); var roomDetail = Java.cast(resultVideoModel, App_get_videoActModel); console.log("room id is => ", roomDetail.getRoom_id()); inspectObject(roomDetail); return this.onSuccess(resp); } }) }
image-20210529170418419
当jadx找不到类的时候,说明脱壳没脱全,通过objection去内存即可搜刮wallbreak内存漫游。
plain
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 // 内存中捞不到类 function invoke(){ Java.perform(function(){ Java.choose("com.fanwe.live.business.LiveBusiness",{ onMatch:function(ins){ console.log("found ins => ",ins) // ins.requestData(); },onComplete:function(){ console.log("Search completed!") } }) }) } // 自行调用构造函数创建类 function invoke2(){ Java.perform(function(){ // com.fanwe.live.business.LiveBusiness(ILiveActivity); var ILiveActivity = Java.use("com.fanwe.live.activity.room.ILiveActivity"); // 实现接口 const ILiveActivityImpl = Java.registerClass({ name: 'com.fanwe.live.activity.room.ILiveActivityImpl', implements: [ILiveActivity], methods: { openSendMsg(){}, getCreaterId(){}, getGroupId(){}, getRoomId(){}, getRoomInfo(){}, getSdkType(){}, isAuctioning(){}, isCreater(){}, isPlayback(){}, isPrivate(){} } }); var result = Java.use("com.fanwe.live.business.LiveBusiness").$new(ILiveActivityImpl.$new()); console.log("result is => ",result.requestRoomInfo("123454")) }) } var LiveBusiness = null ; console.log("LiveBusiness is => ", LiveBusiness) function hook3(){ Java.perform(function(){ Java.use("com.fanwe.live.business.LiveBusiness").getLiveQualityData.implementation = function(){ LiveBusiness = this; console.log("now LiveBusiness is => ", LiveBusiness) LiveBusiness.requestRoomInfo("12343"); var result = this.getLiveQualityData() return result; } }) } function invoke3(){ Java.perform(function(){ var result = LiveBusiness.requestRoomInfo("12343"); console.log("result is => ",result) }) } function invoke4(){ Java.perform(function(){ // com.fanwe.live.business.LiveBusiness(ILiveActivity); var ILiveActivity = Java.use("com.fanwe.live.activity.room.ILiveActivity"); const ILiveActivityImpl = Java.registerClass({ name: 'com.fanwe.live.activity.room.ILiveActivityImpl', implements: [ILiveActivity], methods: { openSendMsg(){}, getCreaterId(){}, getGroupId(){}, getRoomId(){}, getRoomInfo(){}, getSdkType(){}, isAuctioning(){}, isCreater(){}, isPlayback(){}, isPrivate(){} } }); var LB = Java.use("com.fanwe.live.business.LiveBusiness").$new(ILiveActivityImpl.$new()); var LB2 = Java.use("com.fanwe.live.business.LiveBusiness$2"); var AppRequestCallback = Java.use('com.fanwe.hybrid.http.AppRequestCallback'); Java.use("com.fanwe.live.common.CommonInterface").requestRoomInfo(1377894,123,"1234",Java.cast(LB2.$new(LB),AppRequestCallback)); }) } function main() { hookROOMinfo(); hook3(); } setImmediate(main)
image-20210529172252177
以上hook2时无法返回数据,是因为roomId为空,获取room_id时发现getRoomId来自于一个接口ILiveInfo,可以通过该接口找到实现类,或者通过jadx的smali查看是不是确实为getRoomId。
plain
1 2 android hooking search methods getRoomId 全局搜索方法 android hooking watch class_method com.fanwe.live.activity.room.LiveActivity.getRoomId --dump-args --dump-backtrace --dump-return
image-20210530232624680
plain
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 32 33 34 35 36 function hookROOMinfo() { Java.perform(function () { var JSON = Java.use("com.alibaba.fastjson.JSON") var gson = Java.use("com.google.gson.Gson").$new(); var App_get_videoActModel = Java.use("com.fanwe.live.model.App_get_videoActModel"); Java.use("com.fanwe.live.business.LiveBusiness$2").onSuccess.implementation = function (resp) { console.log("Enter LiveBusiness$2 ... ", resp) var result = resp.getDecryptedResult(); var resultVideoModel = JSON.parseObject(result, App_get_videoActModel.class); var roomDetail = Java.cast(resultVideoModel, App_get_videoActModel); console.log("room id is => ", roomDetail.getRoom_id()); inspectObject(roomDetail); return this.onSuccess(resp); } // 直接主动调用,设置房间号 // Java.use("com.fanwe.live.common.CommonInterface").requestRoomInfo.implementation = function (roomid, vod, key, ins) { // console.log("Calling common.CommonInterface.requestRoomInfo...") // return this.requestRoomInfo(1379212, vod, key, ins); // } Java.use("com.fanwe.live.LiveInformation").getRoomId.implementation = function(){ console.log("calling com.fanwe.live.activity.room.LiveActivity.getRoomId ...") return 1379212 ; } // com.fanwe.live.ILiveInfo.getRoomId // com.fanwe.live.LiveInformation.getRoomId // com.fanwe.live.activity.room.LiveActivity.getRoomId // com.fanwe.live.appview.room.RoomSelectFriendsView.getRoomId // com.fanwe.live.model.CreateLiveData.getRoomId // com.fanwe.live.model.JoinLiveData.getRoomId // com.fanwe.live.model.JoinPlayBackData.getRoomId }) }
frida -UF -l rquestRoomInfo.js 再次调用invoke2()实现房间详情抓取
主动调用的原则:离数据越远,中间需要自己实现的细节就越多;哪个细节实现不对,APP就崩掉了。
针对单个类AppHttpUtil找不到,使用fart 可以脱单个类
plain
1 2 3 4 5 6 7 android hooking search classes AppHttpUtil git clone https://github.com/hanbinglengyue/FART.git adb push lib/fart* /data/local/tmp adb shell ->cp fart* /data/app && chmod 777 frida -UF -l frida_fart_reflection.js dump("com.fanwe.hybrid.http.AppHttpUtil") adb pull /sdcard/6850924_22686.dex
通过jadx-gui打开看不到源码,file 6850924_22686.dex
是data格式,而非Dalvik dex格式。通过010 Editor打开该dex发现文件魔术字全是00 00 00 00 00 00 00 00, 查看正常dex文件头为64 65 78 0a 30 33 35 00,再次打开即可找到AppHttpUtil,可以看到拼接参数时用的标准的加密库。
image-20210529172701652
使用沙箱安装该apk后查看/data/data/com.hay.dreamlover的加密文件数据,找到Cipher文件中base64定位加解密的方法。
plain
1 android hooking watch class_method com.fanwe.library.utils.MD5Util.MD5 --dump-args --dump-backtrace --dump-return
通过vnc连接kali nethunter后启动wireshark抓包,保存为pcapng格式文件,使用电脑分析,找到最终发出去的包,发现通过本地端口转发出去,获取请求头参数后实现解密
image-20210530235438489
plain
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 ## Time: 2020-7-31 19:41:11 ## com.hay.dreamlover import requests, os, time, sys from lxml import etree import re import json import threading import hashlib import base64 from urllib import parse # import click # import frida # import logging # import traceback import base64 import re from Crypto.Cipher import AES # https://blog.csdn.net/wangziyang777/article/details/104982823 ## aes 加密/解密 class AESECB: def __init__(self, key): self.key = key self.mode = AES.MODE_ECB self.bs = 16 # block size self.PADDING = lambda s: s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs) def encrypt(self, text): generator = AES.new(self.key, self.mode) # ECB模式无需向量iv crypt = generator.encrypt(self.PADDING(text)) crypted_str = base64.b64encode(crypt) result = crypted_str.decode() return result def decrypt(self, text): generator = AES.new(self.key, self.mode) # ECB模式无需向量iv text += (len(text) % 4) * '=' decrpyt_bytes = base64.b64decode(text) meg = generator.decrypt(decrpyt_bytes) # 去除解码后的非法字符 try: result = re.compile('[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f\n\r\t]').sub('', meg.decode()) except Exception: result = '解码失败,请重试!' return result #计算密码的md5值 def get_md5(s): md = hashlib.md5() md.update(s.encode('utf-8')) return md.hexdigest() if __name__ == '__main__': aes = AESECB('8648754518945235') ctl = "index" act = "index" signqt = get_md5("528094&*3564695()" + ctl + "+_" + act + "@!@###@"); timeqt = str(round(time.time() * 1000)); headers = {"X-JSL-API-AUTH":"sha1|1596358731|VOI1X6448Y4f4E|fd941812d5b875b021f92cf2b0044552462d8cd9"}; body = {"screen_width":1080, "screen_height":1794, "sdk_type":"android", "sdk_version_name":"1.3.0", "sdk_version":2020031801, "xpoint":120.107042, "ypoint":30.302162, "ctl":ctl, "act":"new_video", "p":1, "signqt":signqt, "timeqt":timeqt} requestData = aes.encrypt(str(body)); url = "http://hhy2.hhyssing.com:37462/mapi/index.php?requestData=" + requestData + "i_type=1&ctl=" + ctl + "&act=" + act; rsp = requests.post(url, headers = headers); result = json.loads(rsp.text).get("output"); decodeAes = AESECB("7489148794156147"); print(decodeAes.decrypt(result));
python r0capture.py -U com.hay.dreamlover -v -w 3 >> hay.txt 抓包进出直播间,查看请求
netstat -tuulp|grep hay 查看端口
adb forward tcp:37462 tcp:37462 将给本地发请求包转发到手机端的37462端口
nethunter 中wireshark抓lo包,本地对本地的包,因为app都是对本地请求,再转发到服务端(使用vnc viewer连接)
阿里游戏盾SDK的作用: