今日水印相机自定义水印
@app_version:v2.8.19.10
今日水印相机_v2.8.19.10-E6F9CE50D9CC85A5FD433DD1FEE1F863 Download
apk没有加壳,还是比较好分析的
抓包
打开app开始抓包,有以下几个api
逆向分析
分析天气参数的获取
jeb分析
jeb中搜索相应的URI
选择第二个来到 一个所有request方法的类
摁X进行交叉引用
参数1和参数2即为经纬度坐标
来到这里
发现内部类对其进行了解析,强转为WeatherInfo
1 2 3 4 5 6 7 8 9 10 11 public class WeatherInfo { public String apparentTemperature; public String aqi; public String aqiLevel; public String humidity; public String pressure; public String temperature; public String weather; public String winddirection; public String windpower; }
接下来就是如何hook该内部类了
调试验证
运行fridaserver
使用frida的objection工具
先启动目标app
1 objection -N -h 192.168.0.104 -p 8888 -d -g com.xhey.xcamera explore
然后查找com.xhey.xcamera.e
的类
1 2 3 4 5 6 7 8 9 10 com.xhey.xcamera on (google: 8.1.0) [net] com.xhey.xcamera.e com.xhey.xcamera.e$1 com.xhey.xcamera.e$5 com.xhey.xcamera.e$6 com.xhey.xcamera.e$8 com.xhey.xcamera.e$a com.xhey.xcamera.e$b Found 7 classes
挨个查看每个内部类的方法
1 2 3 4 5 6 7 8 com.xhey.xcamera on (google: 8.1.0) [net] private static void com.xhey.xcamera.e$1 .b(xhey.com.network.model.BaseResponse) public static void com.xhey.xcamera.e$1 .lambda$Wc1CpEPvMWi1606k4HUcLAjQgiw (xhey.com.network.model.BaseResponse) public void com.xhey.xcamera.e$1 .a(xhey.com.network.model.BaseResponse<com.xhey.xcamera.data.model.bean.ConfigStatus>) public void com.xhey.xcamera.e$1 .onError(java.lang.Throwable) public void com.xhey.xcamera.e$1 .onSuccess(java.lang.Object) Found 5 method(s)
发现e$1
,e$5
,e$8
具有相同的方法,且与上反编译源码中方法类似
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function main ( ) { Java.perform(function ( ) { var clazz = Java.use('com.xhey.xcamera.e$5' ); clazz.a.implementation = function (response_argu ) { var result = clazz.a.apply(this , arguments ); var response = Java.cast(response_argu, Java.use("xhey.com.network.model.BaseResponse" )); console .log("BaseResponse.code" , response.code.value); console .log("BaseResponse.data" , response.data.value); console .log("BaseResponse.msg" , response.msg.value); console .log("BaseResponse.toast_msg" , response.toast_msg.value); return result; } }); } setImmediate(main)
frida来运行调试
1 frida -H 192.168.0.104:8888 -f com.xhey.xcamera -l syxj.js --no-pause
打印出如下信息
1 2 3 4 [Remote::com.xhey.xcamera]-> BaseResponse.code 200 BaseResponse.data com.xhey.xcamera.data.model.bean.WeatherInfo@1d5fef8 BaseResponse.msg success BaseResponse.toast_msg
发现WeatherInfo,就是解密后的类
分析时间的获取
部分同天气获取,定位到
其中v1.build()
调用了aes,来对json字符串进行加密
得到key和iv均为"xhey-cc4275fd-43"
,AES模式为"AES/CBC/PKCS5Padding"
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 package xhey.com.network.retrofit2;public enum AESUtil { private static final String ENCRYPTION_IV = "xhey-cc4275fd-43" ; private static final String ENCRYPTION_KEY = "xhey-cc4275fd-43" ; public static String decrypt (String arg4) { } public static String encrypt (String arg5) { if (TextUtils.isEmpty(arg5)) { return arg5; } try { Cipher v0 = Cipher.getInstance("AES/CBC/PKCS5Padding" ); v0.init(1 , AESUtil.makeKey(), AESUtil.makeIv()); Log.e("time" , "==" + Base64.encode(v0.doFinal(arg5.getBytes()), 0 )); return new String(Base64.encode(v0.doFinal(arg5.getBytes()), 2 )).trim(); } catch (Exception v5) { Log.e("AESUtil" , "=encrypt=" + v5.getMessage()); return "" ; } } static AlgorithmParameterSpec makeIv () { return new IvParameterSpec("xhey-cc4275fd-43" .getBytes("utf-8" )); } static SecretKeySpec makeKey () { return new SecretKeySpec("xhey-cc4275fd-43" .getBytes("utf-8" ), "AES" ); } }
此时我们就可以对Fiddler抓到的包来进行加解密了。
requestTimeInChina
继续查看上层引用,同样来到了com.xhey.xcamera.e
类中
同样frida分析后发现是e$8
分析定位
当程序获取天气参数的时候,使用了定位
交叉引用向上追溯
1 2 3 com.xhey.xcamera.network.service.NetWorkServiceKt.requestWeatherInfo com.xhey.xcamera.e.b(java.lang.String[])+2Ah com.xhey.xcamera.e.a(boolean, com.baidu.location.BDLocation)+5D8h
找到赋值的地方
hook这两个方法就可以修改经纬度也就是定位了
而BDLocation是百度地图的api
同理速度也可以hookBDLocation.getSpeed
来修改
插件编写
Frida hook脚本
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 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 function printStack (name ) { Java.perform(function ( ) { var Exception = Java.use("java.lang.Exception" ); var ins = Exception.$new("Exception" ); var straces = ins.getStackTrace(); if (straces != undefined && straces != null ) { var strace = straces.toString(); var replaceStr = strace.replace(/,/g , "\\n" ); console .log("=============================" + name + " Stack strat=======================" ); console .log(replaceStr); console .log("=============================" + name + " Stack end=======================\r\n" ); Exception.$dispose(); } }); } function main ( ) { Java.perform(function ( ) { var clazz = Java.use('com.xhey.xcamera.e$1' ); clazz.a.implementation = function (response_argu ) { var response = Java.cast(response_argu, Java.use("xhey.com.network.model.BaseResponse" )); var result = clazz.a.apply(this , arguments ); return result; } }); Java.perform(function ( ) { Java.openClassFile("/data/local/tmp/r0gson.dex" ).load(); const gson = Java.use('com.r0ysue.gson.Gson' ); var clazz = Java.use('com.xhey.xcamera.e$5' ); clazz.a.implementation = function (response_argu ) { var response = Java.cast(response_argu, Java.use("xhey.com.network.model.BaseResponse" )); var weather_info = Java.cast(response.data.value, Java.use("com.xhey.xcamera.data.model.bean.WeatherInfo" )) weather_info.temperature.value = Java.use("java.lang.String" ).$new("-99" ); var result = clazz.a.apply(this , arguments ); return result; } }); Java.perform(function ( ) { Java.openClassFile("/data/local/tmp/r0gson.dex" ).load(); const gson = Java.use('com.r0ysue.gson.Gson' ); var clazz = Java.use('com.xhey.xcamera.e$8' ); clazz.a.implementation = function (response_argu ) { var response = Java.cast(response_argu, Java.use("xhey.com.network.model.BaseResponse" )); var time_info = Java.cast(response.data.value, Java.use("com.xhey.xcamera.data.model.bean.TimeStatus" )); time_info.timestamp.value = Java.use("java.lang.Integer" ).$new("1912521600" ).intValue(); var result = clazz.a.apply(this , arguments ); return result; } }); Java.perform(function ( ) { var clazz = Java.use('com.baidu.location.BDLocation' ); clazz.getLatitude.implementation = function ( ) { var result = clazz.getLatitude.apply(this , arguments ); result = Java.use("java.lang.Double" ).$new("36.269893" ).doubleValue(); return result; } clazz.getLongitude.implementation = function ( ) { var result = clazz.getLongitude.apply(this , arguments ); result = Java.use("java.lang.Double" ).$new("117.094738" ).doubleValue(); return result; } clazz.getSpeed.implementation = function ( ) { var result = clazz.getSpeed.apply(this , arguments ); result = Java.use("java.lang.Float" ).$new("99.99" ).floatValue(); return result; } }); } function func ( ) { Java.perform(function ( ) { var clazz = Java.use('xhey.com.network.retrofit2.AESUtil' ); clazz.decrypt.implementation = function (data ) { var result = clazz.decrypt.apply(this , arguments ) printStack("AES decrypt" ); console .log("aes decrypt data:" , data); console .log("aes decrypt result: " , result); return result; } }); Java.perform(function ( ) { var clazz = Java.use('xhey.com.network.retrofit2.AESUtil' ); clazz.encrypt.implementation = function (data ) { var result = clazz.encrypt.apply(this , arguments ) printStack("AES encrypt" ); console .log("aes encrypt data:" , data); console .log("aes encrypt result: " , result); return result; } }); } setImmediate(main)
Xposed插件开发
hook的类已经分析完了,懒得造轮子了
效果图
总结
这算是自己分析的第一个app。花了差不多一天的时间,第一次接触这么大的代码量,刚开始有些懵逼。
但分析完发现这个app还是很简单的,没有壳也没有混淆,比较适合我这样的新手。
Frida啥的还是不熟练,疯狂找bug。
刚开始修改时间没有效果,最后发现是因为先执行了原方法result = clazz.a.apply(this, arguments);
,导致时间在方法体内被直接应用了。