今日水印相机自定义水印

@app_version:v2.8.19.10

今日水印相机_v2.8.19.10-E6F9CE50D9CC85A5FD433DD1FEE1F863 Download

apk没有加壳,还是比较好分析的

抓包

打开app开始抓包,有以下几个api

image-20210810004740692

逆向分析

分析天气参数的获取

jeb分析

jeb中搜索相应的URI

image-20210810004938096

选择第二个来到 一个所有request方法的类

image-20210810005040994

摁X进行交叉引用

参数1和参数2即为经纬度坐标

image-20210810005122554

来到这里

image-20210810005156768

发现内部类对其进行了解析,强转为WeatherInfo

1
2
3
4
5
6
7
8
9
10
11
public class WeatherInfo {
public String apparentTemperature; // 体感温度:30
public String aqi; // 空气质量指数:37
public String aqiLevel; // 空气质量等级:优
public String humidity; // 湿度:75%
public String pressure; // 压强:1001
public String temperature; // 温度:27
public String weather; // 天气:晴
public String winddirection; // 风向:西南风
public String windpower; // 风力:1级
}

接下来就是如何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] # android hooking search classes com.xhey.xcamera.e                                                       
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] # android hooking list class_methods com.xhey.xcamera.e$1                                                 
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,就是解密后的类

分析时间的获取

部分同天气获取,定位到

image-20210810012037704

其中v1.build()调用了aes,来对json字符串进行加密

image-20210810012124362

得到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类中

image-20210810012605219

同样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

找到赋值的地方

image-20210810013246491

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 () {
/**
* 解析 /next/android/config
*/
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"));
// 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);

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');
/**
* 解析天气 /next/GetCurrentWeather
* public class WeatherInfo {
* public String apparentTemperature; // 体感温度:30
* public String aqi; // 空气质量指数:37
* public String aqiLevel; // 空气质量等级:优
* public String humidity; // 湿度:75%
* public String pressure; // 压强:1001
* public String temperature; // 温度:27
* public String weather; // 天气:晴
* public String winddirection; // 风向:西南风
* public String windpower; // 风力:1级
* }
*/
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");
//

// console.log("BaseResponse.code", response.code.value);
// console.log("weather_info", gson.$new().toJson(weather_info));
// console.log("BaseResponse.msg", response.msg.value);
// console.log("BaseResponse.toast_msg", response.toast_msg.value);

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');
/**
* 解析时间 /next/inchina
* public class TimeStatus {
* private boolean in_china; // 是否在中国:true
* private boolean stopDevice; // false
* private int timestamp; // 时间戳
* }
*/
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"));

// console.log("=== TIME ===")
// 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);
// console.log("Time", gson.$new().toJson(time_info));

// console.log("TimeStatus.timestamp", time_info.timestamp.value);

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);
// console.log("Latitude", result);

result = Java.use("java.lang.Double").$new("36.269893").doubleValue();
// console.log("Change Latitude", result);

return result;
}
// 经度
clazz.getLongitude.implementation = function () {
var result = clazz.getLongitude.apply(this, arguments);
// console.log("Longitude", result);

result = Java.use("java.lang.Double").$new("117.094738").doubleValue();
// console.log("Change Longitude", result);

return result;
}

// 速度
clazz.getSpeed.implementation = function () {
var result = clazz.getSpeed.apply(this, arguments);
// console.log("Speed", result);

result = Java.use("java.lang.Float").$new("99.99").floatValue();
// console.log("Change Speed", result);

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的类已经分析完了,懒得造轮子了

效果图

image-20210810022207796

总结

这算是自己分析的第一个app。花了差不多一天的时间,第一次接触这么大的代码量,刚开始有些懵逼。

但分析完发现这个app还是很简单的,没有壳也没有混淆,比较适合我这样的新手。

Frida啥的还是不熟练,疯狂找bug。

刚开始修改时间没有效果,最后发现是因为先执行了原方法result = clazz.a.apply(this, arguments);,导致时间在方法体内被直接应用了。