├── .gitignore
├── FridaJs
├── JSDEMO.js
└── JSDEMO_Root.js
├── LICENSE
├── Readme.md
├── pom.xml
├── src
├── META-INF
│ └── MANIFEST.MF
└── main
│ ├── java
│ ├── META-INF
│ │ └── MANIFEST.MF
│ └── com
│ │ └── linqi
│ │ ├── AppStart.java
│ │ ├── Main.java
│ │ ├── constant
│ │ ├── CommandFlag.java
│ │ ├── CommonName.java
│ │ ├── SearchCategories.java
│ │ └── TimeConstant.java
│ │ ├── controller
│ │ ├── AboutController.java
│ │ ├── DataListController.java
│ │ ├── DialogController.java
│ │ ├── MainController.java
│ │ ├── PageController.java
│ │ └── ProjectController.java
│ │ ├── dao
│ │ ├── ApplicationInfoDao.java
│ │ ├── ApplicationResultHandler.java
│ │ ├── DatabaseHelper.java
│ │ ├── DetectionResultHandler.java
│ │ ├── ProjectResultBeanHandler.java
│ │ ├── ProjectResultBeanListHandler.java
│ │ └── ProjectResultHandler.java
│ │ ├── entity
│ │ ├── ApplicationInfo.java
│ │ ├── DetectionExportInfo.java
│ │ ├── DetectionInfo.java
│ │ ├── DialogResult.java
│ │ ├── EInjectMode.java
│ │ ├── ProcessInfo.java
│ │ └── ProjectInfo.java
│ │ ├── handler
│ │ ├── IKernel32.java
│ │ ├── IProcessHandler.java
│ │ ├── MainProcessHandler.java
│ │ └── XposedProcessHandler.java
│ │ └── utils
│ │ ├── AlertUtil.java
│ │ ├── CommandUtil.java
│ │ ├── JavaFxUtil.java
│ │ ├── OSUtil.java
│ │ └── PathUtil.java
│ └── resources
│ ├── META-INF
│ └── MANIFEST.MF
│ ├── druid.properties
│ ├── fxml
│ ├── about.fxml
│ ├── datalist.fxml
│ ├── main.fxml
│ ├── page.fxml
│ └── project.fxml
│ └── icon.jpeg
└── target
└── classes
├── META-INF
└── MANIFEST.MF
├── com
└── linqi
│ ├── AppStart.class
│ ├── Main.class
│ ├── constant
│ ├── CommandFlag.class
│ ├── CommonName.class
│ ├── SearchCategories.class
│ └── TimeConstant.class
│ ├── controller
│ ├── AboutController.class
│ ├── DataListController$1.class
│ ├── DataListController$2.class
│ ├── DataListController.class
│ ├── DialogController.class
│ ├── MainController$1.class
│ ├── MainController$2.class
│ ├── MainController$3.class
│ ├── MainController$4.class
│ ├── MainController$5.class
│ ├── MainController.class
│ ├── PageController.class
│ └── ProjectController.class
│ ├── dao
│ ├── ApplicationInfoDao.class
│ ├── ApplicationResultHandler.class
│ ├── DatabaseHelper.class
│ ├── DetectionResultHandler.class
│ ├── ProjectResultBeanHandler.class
│ ├── ProjectResultBeanListHandler.class
│ └── ProjectResultHandler.class
│ ├── entity
│ ├── ApplicationInfo.class
│ ├── DetectionExportInfo.class
│ ├── DetectionInfo.class
│ ├── DialogResult.class
│ ├── EInjectMode.class
│ ├── ProcessInfo.class
│ └── ProjectInfo.class
│ ├── handler
│ ├── IKernel32.class
│ ├── IProcessHandler.class
│ ├── MainProcessHandler.class
│ └── XposedProcessHandler.class
│ └── utils
│ ├── AlertUtil.class
│ ├── CommandUtil.class
│ ├── JavaFxUtil$1.class
│ ├── JavaFxUtil.class
│ ├── OSUtil.class
│ └── PathUtil.class
├── druid.properties
├── fxml
├── about.fxml
├── datalist.fxml
├── main.fxml
├── page.fxml
└── project.fxml
└── icon.jpeg
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | # Package Files #
14 | *.jar
15 | *.war
16 | *.nar
17 | *.ear
18 | *.zip
19 | *.tar.gz
20 | *.rar
21 |
22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23 | hs_err_pid*
24 | replay_pid*
25 |
--------------------------------------------------------------------------------
/FridaJs/JSDEMO.js:
--------------------------------------------------------------------------------
1 | /*请按照下面示例,可以自行添加,编译出Jar包后,将JSDEMO.js放在jar包根目录即可在【选择JS文件】中看到*/
2 |
3 | function getNowDate() {
4 | var myDate = new Date;
5 | var year = myDate.getFullYear();
6 | var mon = myDate.getMonth() + 1;
7 | var date = myDate.getDate();
8 | var hours = myDate.getHours();
9 | var minutes = myDate.getMinutes();
10 | var seconds = myDate.getSeconds();
11 | var now = year + "-" + mon + "-" + date + " " + hours + ":" + minutes + ":" + seconds;
12 | return now;
13 | }
14 |
15 | var startFlag = "【command-start】";
16 | var endFlag = "【command-end】";
17 | var separator = '_';
18 |
19 | function start() {
20 | console.log('\r\n');
21 | console.log(startFlag);
22 | }
23 |
24 | function end() {
25 | console.log(endFlag);
26 | }
27 |
28 | function writeInfo(method, call, className, desc) {
29 | console.log("【time】" + separator + getNowDate());
30 | console.log("【method】" + separator + method);
31 | console.log("【call】" + separator + call);
32 | console.log("【class】" + separator + className);
33 | if (desc){
34 | console.log("【desc】" + separator + desc);
35 | }
36 | console.log("【stack】");
37 |
38 | }
39 |
40 |
41 | function Privacylist() {
42 | var locationManager = Java.use('android.location.LocationManager');
43 | var location = Java.use('android.location.Location');
44 | var telephonyManager = Java.use("android.telephony.TelephonyManager");
45 |
46 | var i = 1
47 | //imei
48 | Java.performNow(function () {
49 | var imeiInfo = Java.use('android.telephony.TelephonyManager');
50 | imeiInfo.getDeviceId.overload('int').implementation = function (a1) {
51 | start();
52 | var ret = this.getDeviceId(a1);
53 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getDeviceId(int)调用用户imei");
54 | writeInfo("getDeviceId(int)", "IMEI",'android.telephony.TelephonyManager','getDeviceId(int)调用用户imei');
55 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
56 | end();
57 | return ret
58 | }
59 | });
60 |
61 | Java.performNow(function () {
62 | var imeiInfo = Java.use('android.telephony.TelephonyManager');
63 | //API level 26 获取单个IMEI的方法
64 | imeiInfo.getDeviceId.overload().implementation = function () {
65 | start();
66 | var ret = this.getDeviceId();
67 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getDeviceId调用用户imei");
68 | writeInfo("getDeviceId", "IMEI",'android.telephony.TelephonyManager','getDeviceId调用用户imei');
69 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
70 | end();
71 | return ret
72 | }
73 | });
74 |
75 | //地理位置
76 | Java.performNow(function () {
77 | var telephonyManager = Java.use('android.telephony.TelephonyManager');
78 | telephonyManager.getAllCellInfo.implementation = function () {
79 | start();
80 | var cellInfoList = this.getAllCellInfo();
81 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getAllCellInfo调用获取地理位置");
82 | writeInfo("getAllCellInfo", "地理位置", 'android.telephony.TelephonyManager', '应用通过getAllCellInfo调用获取地理位置');
83 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
84 | end();
85 | return cellInfoList;
86 | }
87 | });
88 |
89 |
90 |
91 | //imei
92 | Java.performNow(function () {
93 | var imeiInfo = Java.use('android.telephony.TelephonyManager');
94 | imeiInfo.getImei.overload().implementation = function () {
95 | start();
96 | var ret = this.getImei();
97 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getImei调用系统imei");
98 | writeInfo("getImei", "IMEI",'android.telephony.TelephonyManager','getImei调用系统imei');
99 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
100 | end();
101 | return ret
102 | }
103 | });
104 |
105 | //iccid
106 | Java.performNow(function () {
107 | var snInfo = Java.use('android.telephony.TelephonyManager');
108 | snInfo.getSimSerialNumber.overload().implementation = function () {
109 | start();
110 | var ret = this.getSimSerialNumber();
111 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getSimSerialNumber获取Sim卡序列号");
112 | writeInfo("getSimSerialNumber", "Sim卡",'android.telephony.TelephonyManager','getSimSerialNumber调用Sim卡序列号');
113 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
114 | end();
115 | return ret
116 | }
117 | });
118 |
119 |
120 | //imsi/iccid
121 | var TelephonyManager = Java.use("android.telephony.TelephonyManager");
122 | TelephonyManager.getSimSerialNumber.overload('int').implementation = function (p) {
123 | start();
124 | var temp = this.getSimSerialNumber(p);
125 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getSimSerialNumber获取Sim卡序列号");
126 | writeInfo("getSimSerialNumber", "Sim卡",'android.telephony.TelephonyManager','getSimSerialNumber调用Sim卡序列号');
127 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
128 | end();
129 | return temp;
130 | }
131 |
132 | //imsi
133 | Java.performNow(function () {
134 | telephonyManager.getSubscriberId.overload().implementation = function () {
135 | start();
136 | var subscriberId = this.getSubscriberId();
137 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getSubscriberId调用用户IMSI");
138 | writeInfo("getSubscriberId", "IMSI", "android.telephony.TelephonyManager", "应用通过getSubscriberId调用用户IMSI");
139 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
140 | end();
141 | return subscriberId;
142 | }
143 | });
144 |
145 |
146 | //获取粗略定位
147 | Java.performNow(function () {
148 | var telephonyManager = Java.use('android.telephony.TelephonyManager');
149 |
150 | telephonyManager.getCellLocation.implementation = function () {
151 | start();
152 | var ret = this.getCellLocation();
153 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getCellLocation方法调用用户位置信息");
154 | writeInfo("getCellLocation", "定位",'android.telephony.TelephonyManager', 'getCellLocation方法调用用户位置信息');
155 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
156 | end();
157 | return ret;
158 | }
159 | });
160 |
161 | // Hook getNeighboringCellInfo 方法
162 | Java.performNow(function () {
163 | var telephonyManager = Java.use('android.telephony.TelephonyManager');
164 |
165 | telephonyManager.getNeighboringCellInfo.implementation = function () {
166 | start();
167 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getNeighboringCellInfo方法获取附近基站信息");
168 | writeInfo("getNeighboringCellInfo", "定位", 'android.telephony.TelephonyManager', '应用通过getNeighboringCellInfo方法获取附近基站信息');
169 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
170 | end();
171 | return this.getNeighboringCellInfo();
172 | }
173 | });
174 |
175 | // Hook getProvider 方法
176 | Java.performNow(function () {
177 | var locationManager = Java.use('android.location.LocationManager');
178 |
179 | locationManager.getProvider.overload('java.lang.String').implementation = function (name) {
180 | start();
181 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getProvider方法获取位置提供程序的信息");
182 | writeInfo("getProvider", "定位", 'android.location.LocationManager', '应用通过getProvider方法获取位置提供程序的信息');
183 | // console.log("name: " + name);
184 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
185 | end();
186 | return this.getProvider(name);
187 | }
188 | });
189 |
190 |
191 |
192 |
193 |
194 | //SSID
195 | Java.performNow(function () {
196 | var WifiInfo = Java.use('android.net.wifi.WifiInfo');
197 | WifiInfo.getSSID.implementation = function () {
198 | start();
199 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getMacAddress获取Wifi的SSID地址");
200 | writeInfo("SSID", "WiFi",'android.net.wifi.WifiInfo','getMacAddress获取Wifi的SSID地址');
201 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
202 | end();
203 | return this.getSSID()
204 | }
205 | });
206 |
207 | //MEID
208 | Java.performNow(function () {
209 | var telephonyManager = Java.use('android.telephony.TelephonyManager');
210 | telephonyManager.getMeid.overload().implementation = function () {
211 | start();
212 | var meid = this.getMeid();
213 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getMeid调用设备MEID号");
214 | writeInfo("getMeid", "MEID", 'android.telephony.TelephonyManager', '应用通过getMeid调用设备MEID号');
215 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
216 | end();
217 | return meid;
218 | }
219 | });
220 |
221 |
222 | //Wifi
223 | Java.performNow(function () {
224 | var WifiInfo = Java.use('android.net.wifi.WifiInfo');
225 | WifiInfo.getIpAddress.overload().implementation = function () {
226 | start();
227 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getIpAddress获取Wifi的IP地址");
228 | writeInfo("getIpAddress", "WiFi",'android.net.wifi.WifiInfo','getIpAddress获取Wifi的IP地址');
229 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
230 | end();
231 | return this.getIpAddress()
232 | }
233 | });
234 |
235 | //Wifi信息
236 | Java.performNow(function () {
237 | var WifiInfoMan = Java.use('android.net.wifi.WifiManager');
238 | WifiInfoMan.getConnectionInfo.overload().implementation = function () {
239 | start();
240 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getConnectionInfo获取wifi信息");
241 | writeInfo("getConnectionInfo", "WiFi",'android.net.wifi.WifiManager','getConnectionInfo获取wifi信息');
242 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
243 | end();
244 | return this.getConnectionInfo()
245 | }
246 | });
247 |
248 | Java.performNow(function () {
249 | var WifiInfoMano = Java.use('android.net.wifi.WifiManager');
250 | WifiInfoMano.getConfiguredNetworks.overload().implementation = function () {
251 | start();
252 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getConfiguredNetworks获取wifi信息");
253 | writeInfo("getConfiguredNetworks", "WiFi",'android.net.wifi.WifiManager','getConfiguredNetworks获取wifi信息');
254 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
255 | end();
256 | return this.getConfiguredNetworks()
257 | }
258 | });
259 |
260 | Java.performNow(function () {
261 | var WifiInfoManonT = Java.use('android.net.wifi.WifiManager');
262 | WifiInfoManonT.getScanResults.overload().implementation = function () {
263 | start();
264 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getScanResults获取wifi信息");
265 | writeInfo("getScanResults", "WiFi",'android.net.wifi.WifiManager','getScanResults获取wifi信息');
266 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
267 | end();
268 | return this.WifiInfoManonT()
269 | }
270 | });
271 |
272 | Java.performNow(function () {
273 | var Networkip = Java.use('java.net.InetAddress');
274 | Networkip.getHostAddress.overload().implementation = function () {
275 | start();
276 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getHostAddress获取网络IP");
277 | writeInfo("getHostAddress", "MAC",'java.net.InetAddress','getHostAddress获取网络IP');
278 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
279 | end();
280 | return this.Networkip()
281 | }
282 | });
283 |
284 | //网络信息
285 | Java.performNow(function () {
286 | var Networkinfo_1 = Java.use('android.net.NetworkInfo');
287 | Networkinfo_1.isConnected.implementation = function () {
288 | start();
289 | var temp = this.isConnected()
290 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过isConnected获取网络是否连接");
291 | writeInfo("isConnected", "网络",'android.net.NetworkInfo','isConnected获取网络是否连接');
292 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
293 | end();
294 | return temp;
295 | }
296 | });
297 |
298 | Java.performNow(function () {
299 | var Networkinfo_2 = Java.use('android.net.NetworkInfo');
300 | Networkinfo_2.isAvailable.implementation = function () {
301 | start();
302 | var temp = this.isAvailable();
303 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过isConnected获取网络是否可用");
304 | writeInfo("isAvailable", "网络",'android.net.NetworkInfo','isConnected获取网络是否可用');
305 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
306 | end();
307 | return temp;
308 | }
309 | });
310 |
311 | Java.performNow(function () {
312 | var Networkinfo_3 = Java.use('android.net.NetworkInfo');
313 | Networkinfo_3.getExtraInfo.implementation = function () {
314 | start();
315 | var temp = this.getExtraInfo();
316 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getExtraInfo获取网络名称");
317 | writeInfo("getExtraInfo", "网络",'android.net.NetworkInfo','getExtraInfo获取网络名称');
318 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
319 | end();
320 | return temp;
321 | }
322 | });
323 |
324 | Java.performNow(function () {
325 | var Networkinfo_4 = Java.use('android.net.NetworkInfo');
326 | Networkinfo_4.getTypeName.implementation = function () {
327 | start();
328 | var temp = this.getTypeName();
329 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getTypeName获取网络类型名称");
330 | writeInfo("getTypeName", "网络",'android.net.NetworkInfo','getTypeName获取网络类型名称');
331 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
332 | end();
333 | return temp;
334 | }
335 | });
336 |
337 | Java.performNow(function () {
338 | var Networkinfo_5 = Java.use('android.net.NetworkInfo');
339 | Networkinfo_5.getType.implementation = function () {
340 | start();
341 | var temp = this.getType();
342 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getType获取网络类型");
343 | writeInfo("getType", "网络",'android.net.NetworkInfo','getType获取网络类型');
344 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
345 | end();
346 | return temp;
347 | }
348 | });
349 |
350 | //BSSID
351 | Java.performNow(function () {
352 | var WifiInfo = Java.use('android.net.wifi.WifiInfo');
353 | WifiInfo.getBSSID.implementation = function () {
354 | start();
355 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getBSSID获取Wifi的BSSID");
356 | writeInfo("BSSID", "WiFi",'android.net.wifi.WifiInfo','getBSSID获取Wifi的BSSID');
357 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
358 | end();
359 | return this.getBSSID()
360 | }
361 | });
362 |
363 | //mac
364 |
365 | Java.performNow(function () {
366 | var macInfo = Java.use('android.net.wifi.WifiInfo');
367 | macInfo.getMacAddress.overload().implementation = function () {
368 | start();
369 | var ret = this.getMacAddress();
370 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getMacAddress调用系统mac");
371 | writeInfo("getMacAddress", "MAC",'android.net.wifi.WifiInfo','getMacAddress调用系统mac');
372 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
373 | end();
374 | return ret
375 | }
376 | });
377 |
378 | Java.performNow(function () {
379 | var macInfo = Java.use('java.net.NetworkInterface');
380 | macInfo.getHardwareAddress.overload().implementation = function () {
381 | start();
382 | var ret = this.getHardwareAddress();
383 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getHardwareAddress调用系统mac");
384 | writeInfo("getHardwareAddress", "MAC",'java.net.NetworkInterface','getHardwareAddress调用系统mac');
385 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
386 | end();
387 | return ret
388 | }
389 | });
390 |
391 |
392 |
393 |
394 | //安卓ID
395 | Java.performNow(function () {
396 | var CursorWrapper = Java.use('android.provider.Settings$Secure');
397 | CursorWrapper.getString.overload('android.content.ContentResolver','java.lang.String').implementation = function (a1,a2) {
398 | // 2023-09-14检查请求的键是否为 'android_id',这是用于获取Android ID的键
399 | if (a2 && a2.toString() === "android_id") {
400 | start();
401 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getString调用获取安卓ID");
402 | writeInfo("getString", "安卓ID",'android.provider.Settings$Secure','getString调用获取安卓ID');
403 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
404 | end();
405 | }
406 | return this.getString(a1, a2);
407 | }
408 | }
409 |
410 | //获取蓝牙设备信息
411 | try {
412 | var BluetoothDevice = Java.use("android.bluetooth.BluetoothDevice");
413 |
414 | //获取蓝牙设备名称
415 | BluetoothDevice.getName.overload().implementation = function () {
416 | var temp = this.getName();
417 | start();
418 | console.log("【"+ getNowDate() +"】"+ i++ +"获取蓝牙设备名称");
419 | writeInfo("getName", "蓝牙",'android.bluetooth.BluetoothDevice','获取蓝牙设备名称');
420 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
421 | end();
422 | return temp;
423 | }
424 |
425 | // 获取蓝牙设备mac
426 | BluetoothDevice.getAddress.implementation = function () {
427 | var temp = this.getAddress();
428 | start();
429 | console.log("【"+ getNowDate() +"】"+ i++ +"获取蓝牙设备Mac");
430 | writeInfo("getAddress", "蓝牙",'android.bluetooth.BluetoothDevice','获取蓝牙设备Mac');
431 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
432 | end();
433 | return temp;
434 | }
435 | } catch (e) {
436 | console.log(e)
437 | }
438 | try {
439 | var BluetoothAdapter = Java.use("android.bluetooth.BluetoothAdapter");
440 |
441 | //获取蓝牙设备名称
442 | BluetoothAdapter.getName.implementation = function () {
443 | var temp = this.getName();
444 | // console.log("获取蓝牙信息", "获取到的蓝牙设备名称: " + temp)
445 | start();
446 | console.log("【"+ getNowDate() +"】"+ i++ +"获取蓝牙设备名称");
447 | writeInfo("getName", "蓝牙",'android.bluetooth.BluetoothDevice','获取蓝牙设备名称');
448 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
449 | end();
450 | return temp;
451 | };
452 | } catch (e) {
453 | console.log(e)
454 | }
455 |
456 | // Hook getAddress 方法
457 | //Android 8.0开始,getAddress()方法将始终返回02:00:00:00:00:00
458 | Java.performNow(function () {
459 | // var bluetoothAdapter = Java.use('android.bluetooth.BluetoothAdapter');
460 |
461 | BluetoothAdapter.getAddress.implementation = function () {
462 | start();
463 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过 getAddress 方法获取设备的蓝牙 MAC 地址");
464 | writeInfo("getAddress()", "Bluetooth MAC Address", 'android.bluetooth.BluetoothAdapter', '应用通过getAddress方法获取设备的蓝牙 MAC 地址');
465 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
466 | end();
467 | return this.getAddress();
468 | }
469 | });
470 |
471 | }
472 |
473 | // Privacylist()
474 |
475 | //蓝牙协议既有可能卡住某一些App,如果排除不出问题,将蓝牙Api注释掉
476 | setImmediate(Privacylist)
477 | // setTimeout(Privacylist,6000);
478 |
--------------------------------------------------------------------------------
/FridaJs/JSDEMO_Root.js:
--------------------------------------------------------------------------------
1 | /*请按照下面示例,可以自行添加,编译出Jar包后,将JSDEMO.js放在jar包根目录即可在【选择JS文件】中看到*/
2 |
3 | function root(){
4 |
5 | /*
6 | Original author: Daniele Linguaglossa
7 | 28/07/2021 - Edited by Simone Quatrini
8 | Code amended to correctly run on the latest frida version
9 | Added controls to exclude Magisk Manager
10 | */
11 |
12 | Java.perform(function() {
13 | var RootPackages = ["com.noshufou.android.su", "com.noshufou.android.su.elite", "eu.chainfire.supersu",
14 | "com.koushikdutta.superuser", "com.thirdparty.superuser", "com.yellowes.su", "com.koushikdutta.rommanager",
15 | "com.koushikdutta.rommanager.license", "com.dimonvideo.luckypatcher", "com.chelpus.lackypatch",
16 | "com.ramdroid.appquarantine", "com.ramdroid.appquarantinepro", "com.devadvance.rootcloak", "com.devadvance.rootcloakplus",
17 | "de.robv.android.xposed.installer", "com.saurik.substrate", "com.zachspong.temprootremovejb", "com.amphoras.hidemyroot",
18 | "com.amphoras.hidemyrootadfree", "com.formyhm.hiderootPremium", "com.formyhm.hideroot", "me.phh.superuser",
19 | "eu.chainfire.supersu.pro", "com.kingouser.com", "com.topjohnwu.magisk"
20 | ];
21 |
22 | var RootBinaries = ["su", "busybox", "supersu", "Superuser.apk", "KingoUser.apk", "SuperSu.apk", "magisk"];
23 |
24 | var RootProperties = {
25 | "ro.build.selinux": "1",
26 | "ro.debuggable": "0",
27 | "service.adb.root": "0",
28 | "ro.secure": "1"
29 | };
30 |
31 | var RootPropertiesKeys = [];
32 |
33 | for (var k in RootProperties) RootPropertiesKeys.push(k);
34 |
35 | var PackageManager = Java.use("android.app.ApplicationPackageManager");
36 |
37 | var Runtime = Java.use('java.lang.Runtime');
38 |
39 | var NativeFile = Java.use('java.io.File');
40 |
41 | var String = Java.use('java.lang.String');
42 |
43 | var SystemProperties = Java.use('android.os.SystemProperties');
44 |
45 | var BufferedReader = Java.use('java.io.BufferedReader');
46 |
47 | var ProcessBuilder = Java.use('java.lang.ProcessBuilder');
48 |
49 | var StringBuffer = Java.use('java.lang.StringBuffer');
50 |
51 | var loaded_classes = Java.enumerateLoadedClassesSync();
52 |
53 | send("Loaded " + loaded_classes.length + " classes!");
54 |
55 | var useKeyInfo = false;
56 |
57 | var useProcessManager = false;
58 |
59 | send("loaded: " + loaded_classes.indexOf('java.lang.ProcessManager'));
60 |
61 | if (loaded_classes.indexOf('java.lang.ProcessManager') != -1) {
62 | try {
63 | //useProcessManager = true;
64 | //var ProcessManager = Java.use('java.lang.ProcessManager');
65 | } catch (err) {
66 | send("ProcessManager Hook failed: " + err);
67 | }
68 | } else {
69 | send("ProcessManager hook not loaded");
70 | }
71 |
72 | var KeyInfo = null;
73 |
74 | if (loaded_classes.indexOf('android.security.keystore.KeyInfo') != -1) {
75 | try {
76 | //useKeyInfo = true;
77 | //var KeyInfo = Java.use('android.security.keystore.KeyInfo');
78 | } catch (err) {
79 | send("KeyInfo Hook failed: " + err);
80 | }
81 | } else {
82 | send("KeyInfo hook not loaded");
83 | }
84 |
85 | PackageManager.getPackageInfo.overload('java.lang.String', 'int').implementation = function(pname, flags) {
86 | var shouldFakePackage = (RootPackages.indexOf(pname) > -1);
87 | if (shouldFakePackage) {
88 | send("Bypass root check for package: " + pname);
89 | pname = "set.package.name.to.a.fake.one.so.we.can.bypass.it";
90 | }
91 | return this.getPackageInfo.overload('java.lang.String', 'int').call(this, pname, flags);
92 | };
93 |
94 | NativeFile.exists.implementation = function() {
95 | var name = NativeFile.getName.call(this);
96 | var shouldFakeReturn = (RootBinaries.indexOf(name) > -1);
97 | if (shouldFakeReturn) {
98 | send("Bypass return value for binary: " + name);
99 | return false;
100 | } else {
101 | return this.exists.call(this);
102 | }
103 | };
104 |
105 | var exec = Runtime.exec.overload('[Ljava.lang.String;');
106 | var exec1 = Runtime.exec.overload('java.lang.String');
107 | var exec2 = Runtime.exec.overload('java.lang.String', '[Ljava.lang.String;');
108 | var exec3 = Runtime.exec.overload('[Ljava.lang.String;', '[Ljava.lang.String;');
109 | var exec4 = Runtime.exec.overload('[Ljava.lang.String;', '[Ljava.lang.String;', 'java.io.File');
110 | var exec5 = Runtime.exec.overload('java.lang.String', '[Ljava.lang.String;', 'java.io.File');
111 |
112 | exec5.implementation = function(cmd, env, dir) {
113 | if (cmd.indexOf("getprop") != -1 || cmd == "mount" || cmd.indexOf("build.prop") != -1 || cmd == "id" || cmd == "sh") {
114 | var fakeCmd = "grep";
115 | send("Bypass " + cmd + " command");
116 | return exec1.call(this, fakeCmd);
117 | }
118 | if (cmd == "su") {
119 | var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled";
120 | send("Bypass " + cmd + " command");
121 | return exec1.call(this, fakeCmd);
122 | }
123 | return exec5.call(this, cmd, env, dir);
124 | };
125 |
126 | exec4.implementation = function(cmdarr, env, file) {
127 | for (var i = 0; i < cmdarr.length; i = i + 1) {
128 | var tmp_cmd = cmdarr[i];
129 | if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id" || tmp_cmd == "sh") {
130 | var fakeCmd = "grep";
131 | send("Bypass " + cmdarr + " command");
132 | return exec1.call(this, fakeCmd);
133 | }
134 |
135 | if (tmp_cmd == "su") {
136 | var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled";
137 | send("Bypass " + cmdarr + " command");
138 | return exec1.call(this, fakeCmd);
139 | }
140 | }
141 | return exec4.call(this, cmdarr, env, file);
142 | };
143 |
144 | exec3.implementation = function(cmdarr, envp) {
145 | for (var i = 0; i < cmdarr.length; i = i + 1) {
146 | var tmp_cmd = cmdarr[i];
147 | if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id" || tmp_cmd == "sh") {
148 | var fakeCmd = "grep";
149 | send("Bypass " + cmdarr + " command");
150 | return exec1.call(this, fakeCmd);
151 | }
152 |
153 | if (tmp_cmd == "su") {
154 | var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled";
155 | send("Bypass " + cmdarr + " command");
156 | return exec1.call(this, fakeCmd);
157 | }
158 | }
159 | return exec3.call(this, cmdarr, envp);
160 | };
161 |
162 | exec2.implementation = function(cmd, env) {
163 | if (cmd.indexOf("getprop") != -1 || cmd == "mount" || cmd.indexOf("build.prop") != -1 || cmd == "id" || cmd == "sh") {
164 | var fakeCmd = "grep";
165 | send("Bypass " + cmd + " command");
166 | return exec1.call(this, fakeCmd);
167 | }
168 | if (cmd == "su") {
169 | var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled";
170 | send("Bypass " + cmd + " command");
171 | return exec1.call(this, fakeCmd);
172 | }
173 | return exec2.call(this, cmd, env);
174 | };
175 |
176 | exec.implementation = function(cmd) {
177 | for (var i = 0; i < cmd.length; i = i + 1) {
178 | var tmp_cmd = cmd[i];
179 | if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id" || tmp_cmd == "sh") {
180 | var fakeCmd = "grep";
181 | send("Bypass " + cmd + " command");
182 | return exec1.call(this, fakeCmd);
183 | }
184 |
185 | if (tmp_cmd == "su") {
186 | var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled";
187 | send("Bypass " + cmd + " command");
188 | return exec1.call(this, fakeCmd);
189 | }
190 | }
191 |
192 | return exec.call(this, cmd);
193 | };
194 |
195 | exec1.implementation = function(cmd) {
196 | if (cmd.indexOf("getprop") != -1 || cmd == "mount" || cmd.indexOf("build.prop") != -1 || cmd == "id" || cmd == "sh") {
197 | var fakeCmd = "grep";
198 | send("Bypass " + cmd + " command");
199 | return exec1.call(this, fakeCmd);
200 | }
201 | if (cmd == "su") {
202 | var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled";
203 | send("Bypass " + cmd + " command");
204 | return exec1.call(this, fakeCmd);
205 | }
206 | return exec1.call(this, cmd);
207 | };
208 |
209 | String.contains.implementation = function(name) {
210 | if (name == "test-keys") {
211 | send("Bypass test-keys check");
212 | return false;
213 | }
214 | return this.contains.call(this, name);
215 | };
216 |
217 | var get = SystemProperties.get.overload('java.lang.String');
218 |
219 | get.implementation = function(name) {
220 | if (RootPropertiesKeys.indexOf(name) != -1) {
221 | send("Bypass " + name);
222 | return RootProperties[name];
223 | }
224 | return this.get.call(this, name);
225 | };
226 |
227 | Interceptor.attach(Module.findExportByName("libc.so", "fopen"), {
228 | onEnter: function(args) {
229 | var path = Memory.readCString(args[0]);
230 | path = path.split("/");
231 | var executable = path[path.length - 1];
232 | var shouldFakeReturn = (RootBinaries.indexOf(executable) > -1)
233 | if (shouldFakeReturn) {
234 | Memory.writeUtf8String(args[0], "/notexists");
235 | send("Bypass native fopen");
236 | }
237 | },
238 | onLeave: function(retval) {
239 |
240 | }
241 | });
242 |
243 | Interceptor.attach(Module.findExportByName("libc.so", "system"), {
244 | onEnter: function(args) {
245 | var cmd = Memory.readCString(args[0]);
246 | send("SYSTEM CMD: " + cmd);
247 | if (cmd.indexOf("getprop") != -1 || cmd == "mount" || cmd.indexOf("build.prop") != -1 || cmd == "id") {
248 | send("Bypass native system: " + cmd);
249 | Memory.writeUtf8String(args[0], "grep");
250 | }
251 | if (cmd == "su") {
252 | send("Bypass native system: " + cmd);
253 | Memory.writeUtf8String(args[0], "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled");
254 | }
255 | },
256 | onLeave: function(retval) {
257 |
258 | }
259 | });
260 |
261 | /*
262 |
263 | TO IMPLEMENT:
264 |
265 | Exec Family
266 |
267 | int execl(const char *path, const char *arg0, ..., const char *argn, (char *)0);
268 | int execle(const char *path, const char *arg0, ..., const char *argn, (char *)0, char *const envp[]);
269 | int execlp(const char *file, const char *arg0, ..., const char *argn, (char *)0);
270 | int execlpe(const char *file, const char *arg0, ..., const char *argn, (char *)0, char *const envp[]);
271 | int execv(const char *path, char *const argv[]);
272 | int execve(const char *path, char *const argv[], char *const envp[]);
273 | int execvp(const char *file, char *const argv[]);
274 | int execvpe(const char *file, char *const argv[], char *const envp[]);
275 |
276 | */
277 |
278 |
279 | BufferedReader.readLine.overload('boolean').implementation = function() {
280 | var text = this.readLine.overload('boolean').call(this);
281 | if (text === null) {
282 | // just pass , i know it's ugly as hell but test != null won't work :(
283 | } else {
284 | var shouldFakeRead = (text.indexOf("ro.build.tags=test-keys") > -1);
285 | if (shouldFakeRead) {
286 | send("Bypass build.prop file read");
287 | text = text.replace("ro.build.tags=test-keys", "ro.build.tags=release-keys");
288 | }
289 | }
290 | return text;
291 | };
292 |
293 | var executeCommand = ProcessBuilder.command.overload('java.util.List');
294 |
295 | ProcessBuilder.start.implementation = function() {
296 | var cmd = this.command.call(this);
297 | var shouldModifyCommand = false;
298 | for (var i = 0; i < cmd.size(); i = i + 1) {
299 | var tmp_cmd = cmd.get(i).toString();
300 | if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd.indexOf("mount") != -1 || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd.indexOf("id") != -1) {
301 | shouldModifyCommand = true;
302 | }
303 | }
304 | if (shouldModifyCommand) {
305 | send("Bypass ProcessBuilder " + cmd);
306 | this.command.call(this, ["grep"]);
307 | return this.start.call(this);
308 | }
309 | if (cmd.indexOf("su") != -1) {
310 | send("Bypass ProcessBuilder " + cmd);
311 | this.command.call(this, ["justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"]);
312 | return this.start.call(this);
313 | }
314 |
315 | return this.start.call(this);
316 | };
317 |
318 | if (useProcessManager) {
319 | var ProcManExec = ProcessManager.exec.overload('[Ljava.lang.String;', '[Ljava.lang.String;', 'java.io.File', 'boolean');
320 | var ProcManExecVariant = ProcessManager.exec.overload('[Ljava.lang.String;', '[Ljava.lang.String;', 'java.lang.String', 'java.io.FileDescriptor', 'java.io.FileDescriptor', 'java.io.FileDescriptor', 'boolean');
321 |
322 | ProcManExec.implementation = function(cmd, env, workdir, redirectstderr) {
323 | var fake_cmd = cmd;
324 | for (var i = 0; i < cmd.length; i = i + 1) {
325 | var tmp_cmd = cmd[i];
326 | if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id") {
327 | var fake_cmd = ["grep"];
328 | send("Bypass " + cmdarr + " command");
329 | }
330 |
331 | if (tmp_cmd == "su") {
332 | var fake_cmd = ["justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"];
333 | send("Bypass " + cmdarr + " command");
334 | }
335 | }
336 | return ProcManExec.call(this, fake_cmd, env, workdir, redirectstderr);
337 | };
338 |
339 | ProcManExecVariant.implementation = function(cmd, env, directory, stdin, stdout, stderr, redirect) {
340 | var fake_cmd = cmd;
341 | for (var i = 0; i < cmd.length; i = i + 1) {
342 | var tmp_cmd = cmd[i];
343 | if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id") {
344 | var fake_cmd = ["grep"];
345 | send("Bypass " + cmdarr + " command");
346 | }
347 |
348 | if (tmp_cmd == "su") {
349 | var fake_cmd = ["justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"];
350 | send("Bypass " + cmdarr + " command");
351 | }
352 | }
353 | return ProcManExecVariant.call(this, fake_cmd, env, directory, stdin, stdout, stderr, redirect);
354 | };
355 | }
356 |
357 | if (useKeyInfo) {
358 | KeyInfo.isInsideSecureHardware.implementation = function() {
359 | send("Bypass isInsideSecureHardware");
360 | return true;
361 | }
362 | }
363 |
364 | });
365 | }
366 |
367 | function getNowDate() {
368 | var myDate = new Date;
369 | var year = myDate.getFullYear(); //获取当前年
370 | var mon = myDate.getMonth() + 1; //获取当前月
371 | var date = myDate.getDate(); //获取当前日
372 | var hours = myDate.getHours(); //获取当前小时
373 | var minutes = myDate.getMinutes(); //获取当前分钟
374 | var seconds = myDate.getSeconds(); //获取当前秒
375 | var now = year + "-" + mon + "-" + date + " " + hours + ":" + minutes + ":" + seconds;
376 | return now;
377 | }
378 |
379 | var startFlag = "【command-start】";
380 | var endFlag = "【command-end】";
381 | var separator = '_';
382 |
383 | function start() {
384 | console.log('\r\n');
385 | console.log(startFlag);
386 | }
387 |
388 | function end() {
389 | console.log(endFlag);
390 | }
391 |
392 | function writeInfo(method, call, className, desc) {
393 | console.log("【time】" + separator + getNowDate());
394 | console.log("【method】" + separator + method);
395 | console.log("【call】" + separator + call);
396 | console.log("【class】" + separator + className);
397 | if (desc){
398 | console.log("【desc】" + separator + desc);
399 | }
400 | console.log("【stack】");
401 |
402 | }
403 |
404 |
405 | function Privacylist() {
406 | var locationManager = Java.use('android.location.LocationManager');
407 | var location = Java.use('android.location.Location');
408 |
409 | var i = 1
410 | var prifre = 1
411 | //imei
412 | Java.performNow(function () {
413 | var imeiInfo = Java.use('android.telephony.TelephonyManager');
414 | imeiInfo.getDeviceId.overload().implementation = function (a1) {
415 | start();
416 | var ret = this.getDeviceId(a1);
417 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getDeviceId(int)调用用户imei");
418 | writeInfo("getDeviceId(int)", "IMEI",'android.telephony.TelephonyManager','getDeviceId(int)调用用户imei');
419 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
420 | end();
421 | return ret
422 | }
423 | });
424 |
425 | Java.performNow(function () {
426 | var imeiInfo = Java.use('android.telephony.TelephonyManager');
427 | //API level 26 获取单个IMEI的方法
428 | imeiInfo.getDeviceId.overload().implementation = function () {
429 | start();
430 | var ret = this.getDeviceId();
431 | console.log("【" + getNowDate() + "】" + i++ + ". 应用通过getDeviceId调用用户imei");
432 | writeInfo("getDeviceId", "IMEI",'android.telephony.TelephonyManager','getDeviceId调用用户imei');
433 | console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
434 | end();
435 | return ret
436 | }
437 | });
438 |
439 |
440 |
441 |
442 | }
443 |
444 |
445 | // Privacylist()
446 |
447 | //蓝牙协议既有可能卡住某一些App,如果排除不出问题,将蓝牙Api注释掉
448 |
449 | setImmediate(Privacylist)
450 | // setTimeout(Privacylist,6000);
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | ## App privacy detection使用说明
2 |
3 |
4 |
5 | ## 工具界面
6 |
7 | 
8 |
9 | ### 工具环境
10 |
11 | Java 1.8 开发环境(**部分人员开启工具会出现显示错位现象,请保证分辨率处于正常**)
12 | Windows 10(测试没问题)
13 | 自行配置好Windows客户端Frida环境和Android服务端frida环境
14 |
15 | ### 工具使用
16 |
17 | 1.请确保命令行可以启动`Frida`
18 |
19 | 2.确认`Frida`客户端版本和安卓`Frida`服务端版本一致
20 |
21 | #### Android-frida
22 |
23 | 
24 |
25 | 点击`【新建测试方案】`后命名.如:`美团`
26 |
27 | 
28 |
29 | 测试方案命名后会打开菜单栏,点击`【获取】`后`【应用列表】`会将应用程序列出来
30 |
31 | 
32 |
33 | 可以使用搜索功能查找目标`App`,在**方框**中填写`App`,然后点击`【过滤】`
34 |
35 | 
36 |
37 | 选择`App`后,需要选择**注入方式**`【attach】`需要手动开启App后点击、`【Spawing】`无需手动启动App直接点击即可。
38 |
39 | 点击消息框的内容,即可看到详细的内容
40 |
41 | 
42 |
43 | 可以在测试中进行切换,数据会在其他页面中继续堆叠
44 |
45 | 
46 |
47 |
48 |
49 | 同时也新增了
50 |
51 | 1.选择权限可以单独显示选中的内容
52 |
53 | 2.添加了信息栏删除功能
54 |
55 | 3.脚本优化且新加了bypassRoot环境的脚本
56 |
57 | 
58 |
59 |
60 |
61 | #### Android-Xposed
62 |
63 | Xposed检测代码[传送门](https://github.com/base64linqi/COPXposed)
64 |
65 | 
66 |
67 | 创建完成之后,`包名`随意只是数据库中有这个表,测试不填也行~然后点击开始即可捕获,但是如果想切换到其他状态,需要`中止检测`后点击其他状态页面,否则多线程写入会导致数据库锁死从而不记录信息。
68 |
69 | 
70 |
71 | `Xposed插件`需要修改成自己的包名
72 |
73 | 
74 |
75 |
76 |
77 | ### 常见问题
78 |
79 | **一.应用列表内容过少**
80 |
81 | 解决方法:1.将手机手动重启,重新开启frida。2.重新将frida-server kill掉,重新启动
82 |
83 | **二找不到应用程序**
84 |
85 | 解决方法:1.检测Frida是否准确或重新启动frida-server
86 |
87 | **三.应用程序什么内容都没有**
88 |
89 | 在同事测试和开发Debug过程中没有发现任何报错,也未有逻辑错误,因此这个Bug无法修改,同事通过更换系统解决了这个问题,在这里不建议各位使用此方法。
90 |
91 | **四.App打开崩溃(一)**
92 |
93 | 解决方法:
94 |
95 | 1.在我测试中,使用Pixel手机确实会出现App崩溃的情况,在使用小米机器或其他机器暂未发现有此情况
96 |
97 | 2.Frida被检测
98 |
99 | **五.App打开崩溃(二)**
100 |
101 | 解决方法:使用frida-ps -U查看App是否存在双进程,切换版本到frida15试一试
102 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.andy
8 | PrivacyDetectionTool
9 | 1.0-SNAPSHOT
10 |
11 |
12 | 8
13 | 8
14 |
15 |
16 |
17 | org.ini4j
18 | ini4j
19 | 0.5.4
20 |
21 |
22 | org.openjfx
23 | javafx-controls
24 | 11.0.2
25 |
26 |
27 |
28 |
29 | org.openjfx
30 | javafx-fxml
31 | 11.0.2
32 |
33 |
34 | org.projectlombok
35 | lombok
36 | 1.18.22
37 |
38 |
39 | cn.hutool
40 | hutool-all
41 | 5.6.3
42 |
43 |
44 | net.java.dev.jna
45 | jna
46 | 5.6.0
47 |
48 |
49 | org.xerial
50 | sqlite-jdbc
51 | 3.36.0.2
52 |
53 |
54 | com.alibaba
55 | druid
56 | 1.2.4
57 |
58 |
59 | commons-dbutils
60 | commons-dbutils
61 | 1.7
62 |
63 |
64 | org.apache.poi
65 | poi-ooxml
66 | 4.1.1
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/src/META-INF/MANIFEST.MF:
--------------------------------------------------------------------------------
1 | Manifest-Version: 1.0
2 | Main-Class: com.linqi.AppStart
3 |
4 |
--------------------------------------------------------------------------------
/src/main/java/META-INF/MANIFEST.MF:
--------------------------------------------------------------------------------
1 | Manifest-Version: 1.0
2 | Main-Class: com.linqi.Main
3 |
4 |
--------------------------------------------------------------------------------
/src/main/java/com/linqi/AppStart.java:
--------------------------------------------------------------------------------
1 | package com.linqi;
2 |
3 | public class AppStart {
4 | public static void main(String[] args) {
5 | Main.main(args);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/linqi/Main.java:
--------------------------------------------------------------------------------
1 | package com.linqi;
2 |
3 | import javafx.application.Application;
4 | import javafx.fxml.FXMLLoader;
5 | import javafx.scene.Parent;
6 | import javafx.scene.Scene;
7 | import javafx.scene.image.Image;
8 | import javafx.stage.Stage;
9 |
10 | public class Main extends Application {
11 | public static void main(String[] args) {
12 | launch(args);
13 | }
14 | @Override
15 | public void start(Stage stage) throws Exception {
16 | Parent root = FXMLLoader.load(getClass().getResource("/fxml/main.fxml"));
17 | stage.setTitle("隐私检测工具");
18 | Scene scene = new Scene(root, 1000, 700);
19 | stage.getIcons().add(new Image(Main.class.getResourceAsStream("/icon.jpeg")));
20 | stage.setScene(scene);
21 | stage.show();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/linqi/constant/CommandFlag.java:
--------------------------------------------------------------------------------
1 | package com.linqi.constant;
2 |
3 | public class CommandFlag {
4 | public static final String TIME = "【time】";
5 | public static final String METHOD = "【method】";
6 | public static final String CALL_PERMISSION = "【call】";
7 | public static final String DESCRIPTION = "【desc】";
8 | public static final String CALL_CLASS = "【class】";
9 | public static final String STACK = "【stack】";
10 | public static final String COMMAND_START = "【command-start】";
11 | public static final String COMMAND_END = "【command-end】";
12 | public static final String SEPARATOR = "_";
13 | public static final String DEVICE_APPLICATION_NAME = "(\\[)([\\S\\s]+)::(\\S+)(\\]->)([\\s\\S]*)";
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/linqi/constant/CommonName.java:
--------------------------------------------------------------------------------
1 | package com.linqi.constant;
2 |
3 | public class CommonName {
4 |
5 | public static final String MACHINE_TYPE_ANDROID_FRIDA = "android-frida";
6 | public static final String MACHINE_TYPE_ANDROID_XPOSED = "android-xposed";
7 | public static final String MACHINE_TYPE_IOS = "ios";
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/linqi/constant/SearchCategories.java:
--------------------------------------------------------------------------------
1 | package com.linqi.constant;
2 |
3 | public class SearchCategories {
4 | /**
5 | * 全部
6 | */
7 | public static final String ALL = null;
8 |
9 | public static final String CLASS = "类";
10 | public static final String METHOD = "方法";
11 | public static final String STACK = "堆栈";
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/linqi/constant/TimeConstant.java:
--------------------------------------------------------------------------------
1 | package com.linqi.constant;
2 |
3 | public class TimeConstant {
4 | public static final String DATETIME_FORMATTER = "yyyy-M-d H:m:s";
5 |
6 | public static final String DATETIME_FORMATTER_ALL = "yyyy-MM-dd HH:mm:ss";
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/linqi/controller/AboutController.java:
--------------------------------------------------------------------------------
1 | package com.linqi.controller;
2 |
3 | public class AboutController {
4 | }
5 |
--------------------------------------------------------------------------------
/src/main/java/com/linqi/controller/DataListController.java:
--------------------------------------------------------------------------------
1 | package com.linqi.controller;
2 |
3 | import com.linqi.dao.ApplicationInfoDao;
4 | import com.linqi.entity.ProjectInfo;
5 | import com.linqi.utils.AlertUtil;
6 | import javafx.collections.FXCollections;
7 | import javafx.collections.ObservableList;
8 | import javafx.fxml.FXML;
9 | import javafx.fxml.Initializable;
10 | import javafx.scene.control.*;
11 | import javafx.stage.Stage;
12 | import lombok.Setter;
13 |
14 | import java.net.URL;
15 | import java.util.List;
16 | import java.util.Optional;
17 | import java.util.ResourceBundle;
18 |
19 | public class DataListController implements Initializable {
20 | @FXML
21 | private TableView tblList;
22 | private ApplicationInfoDao applicationInfoDao = new ApplicationInfoDao();
23 | @FXML
24 | private TableColumn tcDelete;
25 | @FXML
26 | private TableColumn tcOpen;
27 |
28 | @Setter
29 | private MainController mainController;
30 | @Override
31 | public void initialize(URL location, ResourceBundle resources) {
32 | Object obj = mainController;
33 | refresh();
34 | tcDelete.setCellFactory(col ->{
35 | Button btnDelete = new Button("删除");
36 | TableCell cell = new TableCell(){
37 | @Override
38 | protected void updateItem(ProjectInfo item, boolean empty) {
39 | super.updateItem(item, empty);
40 | if (empty){
41 | setGraphic(null);
42 | }else {
43 | setGraphic(btnDelete);
44 | }
45 | }
46 | };
47 | btnDelete.setOnAction(e ->{
48 | ProjectInfo projectInfo = (ProjectInfo) cell.getTableRow().getItem();
49 | if (projectInfo == null){
50 | AlertUtil.showErrAlert("请重试,数据错误");
51 | return;
52 | }
53 | Optional result = AlertUtil.showConfirm(String.format("确定要删除%s吗?", projectInfo.getProjectName()));
54 | if (!result.isPresent() || result.get() != ButtonType.OK){
55 | return;
56 | }
57 | try {
58 | applicationInfoDao.deleteProject(projectInfo.getId());
59 | refresh();
60 | } catch (Exception exception) {
61 | AlertUtil.showErrAlert("删除出错:" + exception.getMessage());
62 | exception.printStackTrace();
63 | }
64 | });
65 | return cell;
66 | });
67 | tcOpen.setCellFactory(col ->{
68 | Button btnOpen = new Button("打开");
69 | TableCell cell = new TableCell(){
70 | @Override
71 | protected void updateItem(ProjectInfo item, boolean empty) {
72 | super.updateItem(item, empty);
73 | if (empty){
74 | setGraphic(null);
75 | }else {
76 | setGraphic(btnOpen);
77 | }
78 | }
79 | };
80 | btnOpen.setOnAction(e ->{
81 | ProjectInfo projectInfo = (ProjectInfo) cell.getTableRow().getItem();
82 | if (projectInfo == null){
83 | AlertUtil.showErrAlert("请重试,数据错误");
84 | return;
85 | }
86 | try {
87 | applicationInfoDao.setAllChildInfo(projectInfo);
88 | if (projectInfo.getApplicationInfoList().size() > 0){
89 | projectInfo.setCurrentApplicationInfo(projectInfo.getApplicationInfoList().get(0));
90 | }
91 | mainController.getPaneButtons().setDisable(false);
92 | mainController.setProjectInfo(projectInfo);
93 | mainController.getLblProject().setText("方案:" +projectInfo.getProjectName());
94 | mainController.resetPages();
95 | mainController.initByDetectType(projectInfo.getType());
96 | mainController.refreshDatas(projectInfo.getCurrentApplicationInfo());
97 | Stage stage = (Stage)btnOpen.getScene().getWindow();
98 | stage.close();
99 | } catch (Exception exception) {
100 |
101 | exception.printStackTrace();
102 | }
103 | });
104 | return cell;
105 | });
106 | }
107 | private void refresh(){
108 |
109 | try {
110 | List list = applicationInfoDao.getAllProject();
111 | Object obj = list;
112 | ObservableList obsList = FXCollections.observableList(list);
113 | tblList.setItems(obsList);
114 | } catch (Exception e) {
115 | e.printStackTrace();
116 | }
117 | }
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/src/main/java/com/linqi/controller/DialogController.java:
--------------------------------------------------------------------------------
1 | package com.linqi.controller;
2 |
3 | import com.linqi.entity.DialogResult;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 |
7 | @Getter
8 | @Setter
9 | public class DialogController {
10 | private DialogResult dialogResult;
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/linqi/controller/MainController.java:
--------------------------------------------------------------------------------
1 | package com.linqi.controller;
2 |
3 | import cn.hutool.core.bean.BeanUtil;
4 | import cn.hutool.core.collection.CollUtil;
5 | import cn.hutool.core.io.FileUtil;
6 | import cn.hutool.poi.excel.ExcelUtil;
7 | import cn.hutool.poi.excel.ExcelWriter;
8 | import com.linqi.constant.CommonName;
9 | import com.linqi.constant.SearchCategories;
10 | import com.linqi.dao.ApplicationInfoDao;
11 | import com.linqi.dao.DatabaseHelper;
12 | import com.linqi.entity.*;
13 | import com.linqi.handler.MainProcessHandler;
14 | import com.linqi.handler.XposedProcessHandler;
15 | import com.linqi.utils.*;
16 | import javafx.application.Platform;
17 | import javafx.collections.FXCollections;
18 | import javafx.collections.ObservableList;
19 | import javafx.collections.transformation.FilteredList;
20 | import javafx.event.ActionEvent;
21 | import javafx.event.EventHandler;
22 | import javafx.fxml.FXML;
23 | import javafx.fxml.FXMLLoader;
24 | import javafx.fxml.Initializable;
25 | import javafx.geometry.Insets;
26 | import javafx.geometry.Pos;
27 | import javafx.scene.Node;
28 | import javafx.scene.Parent;
29 | import javafx.scene.Scene;
30 | import javafx.scene.control.*;
31 | import javafx.scene.input.MouseButton;
32 | import javafx.scene.input.MouseEvent;
33 | import javafx.scene.layout.*;
34 | import javafx.scene.paint.Color;
35 | import javafx.stage.FileChooser;
36 | import javafx.stage.Modality;
37 | import javafx.stage.Stage;
38 | import javafx.stage.WindowEvent;
39 | import lombok.Getter;
40 | import lombok.Setter;
41 | import org.apache.commons.codec.binary.StringUtils;
42 | import org.apache.poi.util.StringUtil;
43 | import org.ini4j.Ini;
44 | import org.ini4j.Profile;
45 |
46 | import java.io.File;
47 | import java.io.IOException;
48 | import java.net.URL;
49 | import java.util.*;
50 | import java.util.stream.Collectors;
51 |
52 | @Getter
53 | @Setter
54 | public class MainController implements Initializable {
55 |
56 | @FXML
57 | public AnchorPane paneXposedButton;
58 | @FXML
59 | public TextField tbxAppName;
60 | @FXML
61 | private ContextMenu ctxMenuTable;
62 | @FXML
63 | private Label lblProject;
64 | @FXML
65 | private AnchorPane root;
66 |
67 | @FXML
68 | private MenuBar menuBar;
69 |
70 | @FXML
71 | private ComboBox cbxAppList;
72 |
73 | //颜色
74 | @FXML
75 | private void setPinkTheme(ActionEvent event) {
76 | root.setStyle("-fx-background-color: pink;");
77 | menuBar.setStyle("-fx-background-color: pink;");
78 | }
79 |
80 | @FXML
81 | private TextField tbxAppFilter;
82 |
83 | @FXML
84 | private AnchorPane paneButtons;
85 |
86 | @FXML
87 | private TextField tbxSearch;
88 |
89 | /**
90 | * 用于实时处理命令行任务
91 | */
92 | private MainProcessHandler processHandler = null;
93 |
94 | /**
95 | * 退出进程通知
96 | */
97 | private ProcessInfo processInfo;
98 |
99 |
100 | private ObservableList applicationInfos;
101 |
102 | @FXML
103 | private FlowPane paneCalls;
104 |
105 | @FXML
106 | private Label lblAppName;
107 |
108 |
109 | private ObservableList detectionInfos;
110 |
111 | private ObservableList detailDetectionInfos;
112 |
113 | @FXML
114 | private TableView tblList;
115 |
116 | @FXML
117 | private TableView tblDetail;
118 |
119 | @FXML
120 | private TextArea txaStackInfo;
121 |
122 | @FXML
123 | private Label lblCallTimes;
124 |
125 | @FXML
126 | private DataListController dataListController;
127 |
128 | /**
129 | * 当前选中的标签
130 | */
131 | private String selectedLabel;
132 |
133 | @FXML
134 | private HBox boxPages;
135 |
136 | @FXML
137 | private ComboBox cbxSearchCategory;
138 |
139 | /**
140 | *
141 | */
142 | private ProjectInfo projectInfo;
143 |
144 | /**
145 | * 当前选中的页面按钮
146 | */
147 | private Button selectedBtnPage;
148 |
149 | /**
150 | * 数据库操作
151 | */
152 | private ApplicationInfoDao applicationInfoDao = new ApplicationInfoDao();
153 |
154 | /**
155 | * 默认页面名称
156 | */
157 | private String[] defaultPageNames = new String[]{"隐私前", "隐私后", "测试", "生产"};
158 |
159 | private Stage mainStage;
160 |
161 | /**
162 | * 正在检测中
163 | */
164 | private boolean isDetecting = false;
165 |
166 | public void resetPages() {
167 | boxPages.getChildren().clear();
168 | if (projectInfo.getApplicationInfoList() != null) {
169 | for (int i = 0; i < projectInfo.getApplicationInfoList().size(); i++) {
170 | ApplicationInfo app = projectInfo.getApplicationInfoList().get(i);
171 | Button btn = addPageBtn(app.getShowName());
172 | if (i == 0) {
173 | selectedBtnPage = btn;
174 | selectPageBtn(btn);
175 | }
176 | }
177 | }
178 | }
179 |
180 | @Override
181 | public void initialize(URL location, ResourceBundle resources) {
182 |
183 |
184 | String iniPath = null;
185 | try {
186 | iniPath = PathUtil.getIniFilePath();
187 | if (!FileUtil.exist(iniPath)) {
188 | // 不存在ini文件
189 | File file = new File(iniPath);
190 | try {
191 | file.createNewFile();
192 | } catch (Exception ex) {
193 | AlertUtil.showErrAlert(String.format("创建配置文件失败%s", ex.getMessage()));
194 | return;
195 | }
196 | Ini ini = new Ini();
197 | ini.load(file);
198 |
199 | Profile.Section section = ini.add("js");
200 | section.add("yinsi.js", "1");
201 | section.add("other.js", "0");
202 |
203 | ini.store(file);
204 |
205 | }
206 | } catch (Exception ex) {
207 | AlertUtil.showErrAlert(ex.getMessage());
208 | return;
209 | }
210 | JavaFxUtil.addMenu(menuBar, "新建测试方案", event -> {
211 | addUpdateProject("a");
212 | });
213 | JavaFxUtil.addMenu(menuBar, "历史数据", event -> {
214 | try {
215 | FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/datalist.fxml"));
216 | AnchorPane pane = loader.load();
217 | DataListController dataListController = loader.getController();
218 | dataListController.setMainController(this);
219 | Stage stage = new Stage();
220 | stage.setTitle("历史数据");
221 | Scene scene = new Scene(pane, 800, 500);
222 | stage.setScene(scene);
223 | stage.show();
224 | } catch (IOException e) {
225 | e.printStackTrace();
226 | }
227 | });
228 |
229 | JavaFxUtil.addMenu(menuBar, "导出结果", event -> {
230 | System.out.println("导出结果");
231 | exportResult();
232 | });
233 |
234 | Menu menu = JavaFxUtil.addMenu(menuBar, "选择js文件", event -> {
235 |
236 |
237 | });
238 |
239 | // 为搜索类添加项
240 | ObservableList searchCategories = FXCollections.observableArrayList(
241 | SearchCategories.CLASS, SearchCategories.METHOD, SearchCategories.STACK
242 | );
243 | cbxSearchCategory.setItems(searchCategories);
244 |
245 |
246 | // 读取ini文件
247 | try {
248 | iniPath = PathUtil.getIniFilePath();
249 | if (FileUtil.exist(iniPath)) {
250 | // 不存在ini文件
251 | File file = new File(iniPath);
252 | Ini ini = new Ini();
253 | ini.load(file);
254 | Profile.Section section = ini.get("js");
255 | for (String key : section.keySet()) {
256 | MenuItem mi = new MenuItem();
257 | String text = key;
258 | if (section.get(key).equals("1")) {
259 | text += " √";
260 | }
261 | mi.setText(text);
262 | mi.setId(key);
263 | mi.setOnAction(new EventHandler() {
264 | @Override
265 | public void handle(ActionEvent event) {
266 | for (String k : section.keySet()) {
267 | section.put(k, "0");
268 | mi.setText(mi.getId());
269 | if (k.equals(mi.getId())) {
270 | section.put(k, "1");
271 | }
272 | }
273 | for (MenuItem item : menu.getItems()) {
274 | item.setText(item.getId());
275 | }
276 | mi.setText(mi.getId() + " √");
277 | try {
278 | ini.store(file);
279 | } catch (IOException e) {
280 | e.printStackTrace();
281 | }
282 | }
283 | });
284 | menu.getItems().add(mi);
285 | }
286 |
287 | }
288 | } catch (Exception ex) {
289 | AlertUtil.showWarningAlert(String.format("读取配置文件出错%s", ex.getMessage()));
290 | }
291 | // 添加菜单
292 | JavaFxUtil.addMenu(menuBar, "About", event -> {
293 | try {
294 | Parent root = FXMLLoader.load(getClass().getResource("/fxml/about.fxml"));
295 | Stage stage = new Stage();
296 | stage.setTitle("About");
297 | Scene scene = new Scene(root, 390, 350);
298 | stage.setScene(scene);
299 | stage.show();
300 | } catch (IOException e) {
301 | e.printStackTrace();
302 | }
303 | });
304 | // 应用列表发生选择改变的事件
305 | /*
306 | cbxAppList.getSelectionModel().selectedIndexProperty().addListener(new ChangeListener() {
307 | @Override
308 | public void changed(ObservableValue extends Number> observable, Number oldValue, Number newValue) {
309 | Integer index = (int) newValue;
310 | if (index > -1) {
311 | ApplicationInfo applicationInfo = applicationInfos.get(index);
312 | tbxAppId.setText(applicationInfo.getIdentifier());
313 | }
314 | }
315 | });
316 | */
317 | try {
318 | DatabaseHelper.createTables();
319 | } catch (Exception ex) {
320 | ex.printStackTrace();
321 | }
322 | paneButtons.setDisable(true);
323 | for (int i = 0; i < defaultPageNames.length; i++) {
324 | Button btn = addPageBtn(defaultPageNames[i]);
325 | if (i == 0) {
326 | selectPageBtn(btn);
327 | }
328 | }
329 |
330 | }
331 |
332 | /**
333 | * 导出检测结果
334 | */
335 | private void exportResult() {
336 | if (this.projectInfo == null) {
337 | AlertUtil.showErrAlert("没有可以导出的数据");
338 | return;
339 | }
340 | List appList = projectInfo.getApplicationInfoList();
341 | if (appList == null || appList.size() == 0) {
342 | AlertUtil.showErrAlert("没有检测信息");
343 | return;
344 | }
345 | FileChooser fileChooser = new FileChooser();
346 | FileChooser.ExtensionFilter extensionFilter = new FileChooser.ExtensionFilter("Excel", "*.xls");
347 | fileChooser.getExtensionFilters().add(extensionFilter);
348 | File file = fileChooser.showSaveDialog(mainStage);
349 |
350 | ExcelWriter excelWriter = ExcelUtil.getWriter(file);
351 | excelWriter.addHeaderAlias("callTime", "时间点");
352 | excelWriter.addHeaderAlias("callPermission", "操作行为");
353 | excelWriter.addHeaderAlias("method", "行为描述");
354 | excelWriter.addHeaderAlias("stacksShow", "调用堆栈");
355 |
356 | for (int i = 0; i < appList.size(); i++) {
357 | ApplicationInfo app = appList.get(i);
358 | excelWriter.setSheet(app.getShowName());
359 | List exportList = new ArrayList<>();
360 | for (DetectionInfo detectionInfo : app.getAllDetectionInfos()) {
361 | DetectionExportInfo ex = new DetectionExportInfo();
362 | BeanUtil.copyProperties(detectionInfo, ex);
363 | exportList.add(ex);
364 | }
365 | ArrayList arrayList = CollUtil.newArrayList(exportList);
366 | excelWriter.write(arrayList, true);
367 | excelWriter.flush();
368 |
369 | }
370 |
371 | excelWriter.close();
372 | }
373 |
374 | /**
375 | * 清除搜索内容
376 | */
377 | public void onBtnClearSearch() {
378 | System.out.println("do clear search");
379 | tbxSearch.setText("");
380 | ApplicationInfo applicationInfo = projectInfo.getCurrentApplicationInfo();
381 | refreshDatas(applicationInfo);
382 | }
383 |
384 | public void onBtnDoSearch() {
385 | System.out.println("do search");
386 | System.out.println(tbxSearch.getText());
387 | // 如果正在检测中,不允许搜索
388 | if (isDetecting) {
389 | AlertUtil.showWarningAlert("正在检测中,不能执行搜索");
390 | return;
391 | }
392 | ApplicationInfo applicationInfo = projectInfo.getCurrentApplicationInfo();
393 | refreshDatas(applicationInfo);
394 | }
395 |
396 | /**
397 | * 点击了获取应用列表的按钮
398 | *
399 | * @param actionEvent
400 | */
401 | public void onBtnGetAppListAction(ActionEvent actionEvent) {
402 | applicationInfos = null;
403 | List commands = new ArrayList<>();
404 | commands.add("frida-ps");
405 | commands.add("-Uai");
406 | new Thread(() -> {
407 | try {
408 | processInfo = new ProcessInfo();
409 | List results = CommandUtil.executeByProcessBuilder(commands, false, 0, null, processInfo, "GBK");
410 | // 正确的结果是第一行以PID开头
411 | if (results.size() == 0) {
412 | return;
413 | }
414 | // 如果有结果,但不以PID开头,则是异常信息
415 | String firstRow = results.get(0).trim();
416 | if (!firstRow.startsWith("PID")) {
417 | AlertUtil.showWarningAlert(String.join(" ", results));
418 | return;
419 | }
420 | // 返回的数据格式
421 | /*
422 | PID Name Identifier
423 | ---- -------------------- ---------------------------
424 | 2289 设置 com.android.settings
425 | - Amaze com.amaze.filemanager
426 | - EncrptionTool com.example.encrptiontool
427 | - HBuilder io.dcloud.HBuilder
428 | - Lathe Simulator Lite com.virtlab.lathesim_lite
429 | - empmanagement uni.UNIDCE331A
430 | */
431 | List applicationInfoList = new ArrayList<>();
432 | for (int i = 2; i < results.size(); i++) {
433 | String[] arr = results.get(i).trim().split("\\s+");
434 | ApplicationInfo applicationInfo = new ApplicationInfo();
435 | applicationInfo.setAppName(arr[1]);
436 | applicationInfo.setIdentifier(arr[2]);
437 | String uuid = UUID.randomUUID().toString();
438 | applicationInfoList.add(applicationInfo);
439 | }
440 | Platform.runLater(() -> {
441 |
442 | applicationInfos = FXCollections.observableList(applicationInfoList);
443 | cbxAppList.setItems(applicationInfos);
444 | if (applicationInfos != null && applicationInfos.size() > 0) {
445 | cbxAppList.getSelectionModel().select(0);
446 | }
447 | });
448 | } catch (Exception ex) {
449 | AlertUtil.showErrAlert(ex.getMessage());
450 | } finally {
451 | Platform.runLater(() -> {
452 | paneButtons.setDisable(false);
453 |
454 | });
455 | }
456 |
457 | }).start();
458 | paneButtons.setDisable(true);
459 |
460 | }
461 |
462 | /**
463 | * 点击了Spawing
464 | *
465 | * @param actionEvent
466 | */
467 | public void onBtnStartSpawingAction(ActionEvent actionEvent) {
468 | doDetection(EInjectMode.SPAWING);
469 | }
470 |
471 | /**
472 | * 点击了Attach
473 | *
474 | * @param actionEvent
475 | */
476 | public void onBtnStartAttachAction(ActionEvent actionEvent) {
477 | doDetection(EInjectMode.ATTACH);
478 | }
479 |
480 |
481 | /**
482 | * 执行xposed检测
483 | */
484 | private void doXposedDetection() {
485 | if (this.mainStage == null) {
486 | mainStage = (Stage) root.getScene().getWindow();
487 |
488 | mainStage.setOnCloseRequest(new EventHandler() {
489 | @Override
490 | public void handle(WindowEvent event) {
491 | MainController.this.closeProcessAction();
492 | }
493 | });
494 | }
495 | // 设置检测标志
496 | isDetecting = true;
497 | String commandStr = null;
498 | String appName = tbxAppName.getText();
499 | commandStr = "adb logcat -s LSPosed-Bridge";
500 | // 先在解决方案的appList下找,如果没有则新建并加入列表
501 | Optional optApp = projectInfo.getApplicationInfoList().stream().filter(
502 | ap -> ap.getShowName().equals(selectedBtnPage.getText())).findFirst();
503 | if (optApp.isPresent()) {
504 | projectInfo.setCurrentApplicationInfo(optApp.get());
505 | } else {
506 | // 新建一个检测信息
507 | ApplicationInfo app = new ApplicationInfo();
508 | app.setProjectId(projectInfo.getId());
509 | app.setShowName(selectedBtnPage.getText());
510 | projectInfo.getApplicationInfoList().add(app);
511 | projectInfo.setCurrentApplicationInfo(app);
512 | }
513 |
514 | List commands = new ArrayList<>(Arrays.asList(commandStr.split(" ")));
515 | final String appNameFinal = appName;
516 | new Thread(() -> {
517 | executeJsFile(commands, appNameFinal, CommonName.MACHINE_TYPE_ANDROID_XPOSED);
518 | }).start();
519 | paneButtons.setDisable(true);
520 | menuBar.setDisable(true);
521 | }
522 |
523 |
524 | /**
525 | * 执行检测
526 | *
527 | * @param injectMode 注入模式,spawing,attach
528 | */
529 | private void doDetection(EInjectMode injectMode) {
530 |
531 | if (this.mainStage == null) {
532 | mainStage = (Stage) root.getScene().getWindow();
533 |
534 | mainStage.setOnCloseRequest(new EventHandler() {
535 | @Override
536 | public void handle(WindowEvent event) {
537 | MainController.this.closeProcessAction();
538 | }
539 | });
540 | }
541 |
542 | String jsPath = null;
543 | try {
544 | String iniPath = null;
545 | iniPath = PathUtil.getIniFilePath();
546 | // 从配置文件中读取
547 | Ini ini = new Ini();
548 | File file = new File(iniPath);
549 | ini.load(file);
550 | Profile.Section section = ini.get("js");
551 | String js = null;
552 | for (String key : section.keySet()) {
553 | if (section.get(key).equals("1")) {
554 | js = key;
555 | break;
556 | }
557 | }
558 | if (js == null) {
559 | AlertUtil.showErrAlert("配置文件中没有找到有效的js文件");
560 | return;
561 | }
562 | jsPath = PathUtil.getFridaJsPath(js);
563 | if (!FileUtil.exist(jsPath)) {
564 | AlertUtil.showErrAlert(String.format("没有找到文件%s", jsPath));
565 | return;
566 | }
567 | } catch (Exception ex) {
568 | AlertUtil.showErrAlert(ex.getMessage());
569 | return;
570 | }
571 | // 设置检测标志
572 | isDetecting = true;
573 | String commandStr = null;
574 | String appName = null;
575 | if (injectMode == EInjectMode.ATTACH) {
576 | commandStr = String.format("frida -UF -l %s --no-pause", jsPath);
577 | } else {
578 | ApplicationInfo app = ((ApplicationInfo) cbxAppList.getSelectionModel().getSelectedItem());
579 | if (app == null) {
580 | AlertUtil.showErrAlert("没有选择应用");
581 | return;
582 | }
583 | String appId = app.getIdentifier();
584 | if (appId == null || appId.trim() == "") {
585 | AlertUtil.showWarningAlert("没有选择应用或输入应用Identified");
586 | return;
587 | }
588 | commandStr = String.format("Frida -U -f %s -l %s --no-pause", appId, jsPath);
589 | appName = app.getAppName();
590 | }
591 | // 先在解决方案的appList下找,如果没有则新建并加入列表
592 | Optional optApp = projectInfo.getApplicationInfoList().stream().filter(
593 | ap -> ap.getShowName().equals(selectedBtnPage.getText())).findFirst();
594 | if (optApp.isPresent()) {
595 | projectInfo.setCurrentApplicationInfo(optApp.get());
596 | } else {
597 | // 新建一个检测信息
598 | ApplicationInfo app = new ApplicationInfo();
599 | app.setProjectId(projectInfo.getId());
600 | app.setShowName(selectedBtnPage.getText());
601 | projectInfo.getApplicationInfoList().add(app);
602 | projectInfo.setCurrentApplicationInfo(app);
603 | }
604 |
605 | List commands = new ArrayList<>(Arrays.asList(commandStr.split(" ")));
606 | final String appNameFinal = appName;
607 | new Thread(() -> {
608 | executeJsFile(commands, appNameFinal, CommonName.MACHINE_TYPE_ANDROID_FRIDA);
609 | }).start();
610 | paneButtons.setDisable(true);
611 | menuBar.setDisable(true);
612 | }
613 |
614 | /***
615 | * 执行frida命令
616 | * @param commands
617 | */
618 | private void executeJsFile(List commands, String appName, String deleteType) {
619 | String charset = "GBK";
620 | if (deleteType.equals(CommonName.MACHINE_TYPE_ANDROID_XPOSED)) {
621 | charset = "UTF8";
622 | }
623 | try {
624 |
625 |
626 | ApplicationInfo applicationInfo = projectInfo.getCurrentApplicationInfo();
627 |
628 | applicationInfo.setAppName(appName);
629 | Platform.runLater(() -> {
630 | refreshDatas(applicationInfo);
631 | });
632 | processHandler.setApplicationInfo(applicationInfo);
633 | processHandler.setMainController(this);
634 | processInfo = new ProcessInfo();
635 | List results = CommandUtil.executeByProcessBuilder(commands, false, 0, processHandler, processInfo, charset);
636 | if (!processHandler.getFoundApp()) {
637 | Platform.runLater(() -> {
638 |
639 | AlertUtil.showErrAlert("出错,没有找到应用信息,错误信息如下:\r\n" +
640 | String.join("", results));
641 | });
642 | return;
643 | }
644 | System.out.printf("输出完了");
645 | // 检测结束 ,保存结束 信息
646 | processHandler.saveEndDetection();
647 |
648 | } catch (Exception e) {
649 | e.printStackTrace();
650 | } finally {
651 | Platform.runLater(() -> {
652 | paneButtons.setDisable(false);
653 | menuBar.setDisable(false);
654 | });
655 | }
656 | }
657 |
658 | /**
659 | * 中止正在进行的检测,实际上就是把一个标志设置为false
660 | *
661 | * @param actionEvent
662 | */
663 | public void onExitFirdaProcessAction(ActionEvent actionEvent) {
664 | closeProcessAction();
665 | }
666 |
667 | public void closeProcessAction() {
668 | isDetecting = false;
669 | if (processInfo.getProcess() == null) {
670 | AlertUtil.showErrAlert("没有获取到命令行进程");
671 | return;
672 | }
673 | OSUtil.killProcessByPid(processInfo.getProcess());
674 | try {
675 | processInfo.getReader().close();
676 | } catch (IOException e) {
677 | e.printStackTrace();
678 | }
679 | }
680 |
681 | /**
682 | * 刷新界面数据
683 | */
684 | public void refreshDatas(ApplicationInfo applicationInfo) {
685 |
686 | Platform.runLater(() -> {
687 | if (applicationInfo == null) {
688 | tblList.setItems(null);
689 | paneCalls.getChildren().clear();
690 | lblAppName.setText("");
691 | lblCallTimes.setText("调用次数0");
692 | return;
693 | }
694 | // 数据过滤
695 | // 如果选择了标签,进行一次过滤
696 | List list = applicationInfo.getAllDetectionInfos();
697 | if (this.selectedLabel != null) {
698 | list = list
699 | .stream()
700 | .filter(di -> selectedLabel.equals(di.getCallPermission()))
701 | .collect(Collectors.toList()
702 | );
703 |
704 | }
705 | // 如果输入了搜索内容,进行一次过滤
706 | String searchContent = tbxSearch.getText();
707 |
708 | if (searchContent != null && !"".equals(searchContent)) {
709 | searchContent = searchContent.toLowerCase();
710 | String finalSearchContent = searchContent;
711 | // 没有选择搜索种类
712 | if (cbxSearchCategory.getValue() == null || cbxSearchCategory.getValue().equals("")) {
713 | list = list.stream().filter(d -> (d.getCallClass() != null && d.getCallClass().toLowerCase().contains(finalSearchContent))
714 | || (d.getMethod() != null && d.getMethod().toLowerCase().contains(finalSearchContent))
715 | || (d.getStacksShow() != null && d.getStacksShow().toLowerCase().contains(finalSearchContent))
716 | ).collect(Collectors.toList());
717 | } else {
718 | // 只查找类
719 | if (cbxSearchCategory.getValue().equals(SearchCategories.CLASS)) {
720 | list = list.stream().filter(d -> d.getCallClass() != null && d.getCallClass().toLowerCase().contains(finalSearchContent))
721 | .collect(Collectors.toList());
722 | } else if (cbxSearchCategory.getValue().equals(SearchCategories.METHOD)) {
723 | // 只查找方法
724 | list = list.stream().filter(d -> d.getMethod() != null && d.getMethod().toLowerCase().contains(finalSearchContent))
725 | .collect(Collectors.toList());
726 | } else if (cbxSearchCategory.getValue().equals(SearchCategories.STACK)) {
727 | // 只查找堆栈
728 | list = list.stream().filter(d -> d.getStacksShow() != null && d.getStacksShow().toLowerCase().contains(finalSearchContent))
729 | .collect(Collectors.toList());
730 | }
731 | }
732 | }
733 |
734 | applicationInfo.setFilteredDetectionInfos(list);
735 | for (int i = 0; i < applicationInfo.getFilteredDetectionInfos().size(); i++) {
736 | applicationInfo.getFilteredDetectionInfos().get(i).setOrderIndex(i + 1);
737 | }
738 | tblDetail.setItems(null);
739 | txaStackInfo.setText(null);
740 | lblAppName.setText(applicationInfo.getAppName());
741 | lblCallTimes.setText("调用次数:" + applicationInfo.getFilteredDetectionInfos().size());
742 | detectionInfos = FXCollections.observableList(applicationInfo.getFilteredDetectionInfos());
743 | tblList.setItems(detectionInfos);
744 | paneCalls.getChildren().clear();
745 | Map map = applicationInfo.getAllDetectionInfos().stream()
746 | .collect(Collectors.groupingBy(DetectionInfo::getCallPermission, Collectors.counting()));
747 | Object objdd = map;
748 |
749 | map.forEach((name, count) -> {
750 | HBox box = new HBox();
751 | box.setPadding(new Insets(10));
752 | Label lbl = new Label();
753 | lbl.setTextFill(Color.web("#FFFFFF"));
754 | if (name.equals(selectedLabel)) {
755 | lbl.setStyle("-fx-background-color: #FF0000");
756 | } else {
757 | lbl.setStyle("-fx-background-color: #0000FF");
758 | }
759 | lbl.setAlignment(Pos.CENTER);
760 | lbl.setPrefWidth(100);
761 |
762 | lbl.setPrefHeight(30);
763 | lbl.setText(String.format("%s(%d)", name, count));
764 | lbl.setOnMouseClicked(new EventHandler() {
765 | @Override
766 | public void handle(MouseEvent event) {
767 | // 点击后的筛选数据
768 | if (selectedLabel != null) {
769 | // 如果是选中的按钮,取消即可
770 | if (selectedLabel.equals(name)) {
771 | selectedLabel = null;
772 | } else {
773 | // 否则 ,切换选择
774 | selectedLabel = name;
775 | }
776 | } else {
777 | selectedLabel = name;
778 | }
779 | refreshDatas(applicationInfo);
780 | }
781 | });
782 | box.getChildren().add(lbl);
783 | paneCalls.getChildren().add(box);
784 | });
785 | });
786 | }
787 |
788 | /**
789 | * 单击左侧表格
790 | *
791 | * @param mouseEvent
792 | */
793 | public void onTblListClicked(MouseEvent mouseEvent) {
794 | DetectionInfo detectionInfo = (DetectionInfo) tblList.getSelectionModel().getSelectedItem();
795 | if (detectionInfo == null) {
796 | return;
797 | }
798 | List list = new ArrayList<>();
799 | list.add(detectionInfo);
800 | Platform.runLater(() -> {
801 |
802 | detailDetectionInfos = FXCollections.observableList(list);
803 | tblDetail.setItems(detailDetectionInfos);
804 | txaStackInfo.setText(detectionInfo.getStacksShow());
805 | });
806 |
807 | // 如果是右键,弹出删除菜单
808 | if (mouseEvent.getButton() == MouseButton.SECONDARY) {
809 | ctxMenuTable.show(tblList, mouseEvent.getScreenX(), mouseEvent.getScreenY());
810 | } else if (mouseEvent.getButton() == MouseButton.PRIMARY) {
811 | }
812 | }
813 |
814 | /**
815 | * 安装应用
816 | *
817 | * @param actionEvent
818 | */
819 | public void onBtnInstallAppClicked(ActionEvent actionEvent) {
820 | FileChooser fileChooser = new FileChooser();
821 | fileChooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter("apk文件", "*.apk"));
822 | File file = fileChooser.showOpenDialog(this.getCbxAppList().getScene().getWindow());
823 | if (file != null) {
824 | String command = String.format("adb install %s", file.toPath());
825 | List commands = Arrays.asList(command.split(" "));
826 | try {
827 | List results = CommandUtil.executeByProcessBuilder(commands, false, 0, null, null, "GBK");
828 |
829 | AlertUtil.showInfoAlert(String.join(" ", results));
830 | } catch (Exception e) {
831 | e.printStackTrace();
832 | }
833 |
834 | }
835 | }
836 |
837 | /**
838 | * 过滤应用列表
839 | *
840 | * @param actionEvent
841 | */
842 | public void onBtnFilterAppListClicked(ActionEvent actionEvent) {
843 | if (tbxAppFilter.getText().trim().equals("")) {
844 | cbxAppList.setItems(applicationInfos);
845 | } else {
846 | FilteredList filtered = applicationInfos.filtered(a -> a.getAppName()
847 | .toLowerCase(Locale.ROOT).contains(tbxAppFilter.getText().toLowerCase(Locale.ROOT)));
848 | cbxAppList.setItems(filtered);
849 | }
850 | }
851 |
852 | /**
853 | * 添加页面
854 | *
855 | * @param actionEvent
856 | */
857 | public void onAddPageClick(ActionEvent actionEvent) {
858 | addPageBtn("新页面");
859 | }
860 |
861 | /**
862 | * 添加按钮到父容器
863 | *
864 | * @param name
865 | */
866 | private Button addPageBtn(String name) {
867 | Button btnPage = new Button();
868 | btnPage.setVisible(true);
869 | btnPage.setText(name);
870 | btnPage.setOnAction(new EventHandler() {
871 | @Override
872 | public void handle(ActionEvent event) {
873 | selectPageBtn(btnPage);
874 | }
875 | });
876 | btnPage.setAlignment(Pos.CENTER);
877 | boxPages.getChildren().add(btnPage);
878 | return btnPage;
879 | }
880 |
881 | /**
882 | * 添加解决方案
883 | *
884 | * @param actionEvent
885 | */
886 | public void onAddProject(ActionEvent actionEvent) {
887 | addUpdateProject("a");
888 | }
889 |
890 | /**
891 | * 根据检测类型初始化界面控件
892 | *
893 | * @param type
894 | */
895 | public void initByDetectType(String type) {
896 |
897 | if (type.equals(CommonName.MACHINE_TYPE_ANDROID_XPOSED)) {
898 | paneXposedButton.setVisible(true);
899 | paneButtons.setVisible(false);
900 | paneXposedButton.setDisable(false);
901 | processHandler = new XposedProcessHandler();
902 | } else if (type.equals(CommonName.MACHINE_TYPE_ANDROID_FRIDA)) {
903 | paneXposedButton.setVisible(false);
904 | paneButtons.setVisible(true);
905 | paneButtons.setDisable(false);
906 | processHandler = new MainProcessHandler();
907 | }
908 | }
909 |
910 |
911 | private void addUpdateProject(String act) {
912 | try {
913 | Stage mainStage = (Stage) root.getScene().getWindow();
914 | FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/project.fxml"));
915 | if (act.equals("a")) {
916 | projectInfo = new ProjectInfo();
917 | projectInfo.setStartTime(new Date());
918 | }
919 | ProjectController projectController = new ProjectController();
920 | loader.setControllerFactory(param -> {
921 | return projectController;
922 | });
923 | Parent root = (Parent) loader.load();
924 | Stage stage = new Stage();
925 | stage.setTitle(act == "a" ? "新建测试方案" : "修改测试方案");
926 | Scene scene = new Scene(root, 390, 200);
927 | stage.setScene(scene);
928 | stage.initOwner(mainStage);
929 | stage.initModality(Modality.WINDOW_MODAL);
930 | stage.showAndWait();
931 |
932 | if (projectController.getConfirm()) {
933 | projectInfo.setProjectName(projectController.getProjectName());
934 | lblProject.setText(projectInfo.getProjectName());
935 | projectInfo.setType(projectController.getTestType());
936 | // 根据检测类型初始化界面
937 | initByDetectType(projectController.getTestType());
938 | if (act.equals("a")) {
939 | try {
940 | applicationInfoDao.insertProject(projectInfo);
941 | AlertUtil.showInfoAlert("添加成功");
942 | } catch (Exception ex) {
943 | AlertUtil.showErrAlert(ex.getMessage());
944 | }
945 | } else {
946 | try {
947 | applicationInfoDao.updateProject(projectInfo);
948 | AlertUtil.showInfoAlert("修改成功");
949 | } catch (Exception ex) {
950 | AlertUtil.showErrAlert(ex.getMessage());
951 | }
952 | }
953 | }
954 |
955 | } catch (IOException e) {
956 | e.printStackTrace();
957 | }
958 |
959 | }
960 |
961 | /**
962 | * 打开解决方案
963 | *
964 | * @param actionEvent
965 | */
966 | public void onOpenProject(ActionEvent actionEvent) {
967 | addUpdateProject("u");
968 | }
969 |
970 | /**
971 | * 点击了切换页面按钮
972 | *
973 | * @param btnPage
974 | */
975 | private void selectPageBtn(Button btnPage) {
976 | for (Button btn : getAllPageBtns()) {
977 | btn.setTextFill(Color.BLACK);
978 | btn.setBackground(new Background(new BackgroundFill(Color.web("#DDDDDD"), null, null)));
979 | }
980 | selectedBtnPage = btnPage;
981 | btnPage.setTextFill(Color.BLUE);
982 | btnPage.setBackground(new Background(new BackgroundFill(Color.web("#AAAAAA"), null, null)));
983 | if (projectInfo != null) {
984 | // 如果点击后,该页面对应的app存在,则询问是否覆盖旧的数据
985 | Optional optApp = projectInfo.getApplicationInfoList().stream().filter(app -> app.getShowName().equals(selectedBtnPage.getText()))
986 | .findFirst();
987 | if (optApp.isPresent()) {
988 | projectInfo.setCurrentApplicationInfo(optApp.get());
989 | } else {
990 | if (isDetecting) {
991 | // 正在检测中
992 | ApplicationInfo app = new ApplicationInfo();
993 | projectInfo.getApplicationInfoList().add(app);
994 | app.setShowName(btnPage.getText());
995 | app.setProjectId(projectInfo.getId());
996 | app.setStartTime(new Date());
997 | if (projectInfo.getCurrentApplicationInfo() != null) {
998 | app.setAppName(projectInfo.getCurrentApplicationInfo().getAppName());
999 | } else {
1000 | app.setAppName(cbxAppList.getSelectionModel().getSelectedItem().toString());
1001 | }
1002 | projectInfo.setCurrentApplicationInfo(app);
1003 | try {
1004 | applicationInfoDao.insert(app);
1005 | processHandler.setApplicationInfo(app);
1006 | } catch (Exception ex) {
1007 | ex.printStackTrace();
1008 | AlertUtil.showErrAlert(ex.getMessage());
1009 | }
1010 | } else {
1011 | projectInfo.setCurrentApplicationInfo(null);
1012 | }
1013 | }
1014 | refreshDatas(projectInfo.getCurrentApplicationInfo());
1015 | }
1016 | }
1017 |
1018 | /**
1019 | * 获取所有的页面按钮
1020 | *
1021 | * @return
1022 | */
1023 | private List