├── .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 | ![image-20230314094908182](https://images-j3.oss-cn-guangzhou.aliyuncs.com/img/image-20230314094908182.png) 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 | ![image-20230314095033228](https://images-j3.oss-cn-guangzhou.aliyuncs.com/img/image-20230314095033228.png) 24 | 25 | 点击`【新建测试方案】`后命名.如:`美团` 26 | 27 | ![image-20220728160858445](https://images-j3.oss-cn-guangzhou.aliyuncs.com/img/image-20220728160858445.png) 28 | 29 | 测试方案命名后会打开菜单栏,点击`【获取】`后`【应用列表】`会将应用程序列出来 30 | 31 | ![image-20220306144036840](https://images-j3.oss-cn-guangzhou.aliyuncs.com/img/image-20220306144036840.png) 32 | 33 | 可以使用搜索功能查找目标`App`,在**方框**中填写`App`,然后点击`【过滤】` 34 | 35 | ![image-20220306144221029](https://images-j3.oss-cn-guangzhou.aliyuncs.com/img/image-20220306144221029.png) 36 | 37 | 选择`App`后,需要选择**注入方式**`【attach】`需要手动开启App后点击、`【Spawing】`无需手动启动App直接点击即可。 38 | 39 | 点击消息框的内容,即可看到详细的内容 40 | 41 | ![image-20220306144557036](https://images-j3.oss-cn-guangzhou.aliyuncs.com/img/image-20220306144557036.png) 42 | 43 | 可以在测试中进行切换,数据会在其他页面中继续堆叠 44 | 45 | ![image-20220728161205528](https://images-j3.oss-cn-guangzhou.aliyuncs.com/img/image-20220728161205528.png) 46 | 47 | 48 | 49 | 同时也新增了 50 | 51 | 1.选择权限可以单独显示选中的内容 52 | 53 | 2.添加了信息栏删除功能 54 | 55 | 3.脚本优化且新加了bypassRoot环境的脚本 56 | 57 | ![image-20220728162219888](https://images-j3.oss-cn-guangzhou.aliyuncs.com/img/image-20220728162219888.png) 58 | 59 | 60 | 61 | #### Android-Xposed 62 | 63 | Xposed检测代码[传送门](https://github.com/base64linqi/COPXposed) 64 | 65 | ![image-20230314095308372](https://images-j3.oss-cn-guangzhou.aliyuncs.com/img/image-20230314095308372.png) 66 | 67 | 创建完成之后,`包名`随意只是数据库中有这个表,测试不填也行~然后点击开始即可捕获,但是如果想切换到其他状态,需要`中止检测`后点击其他状态页面,否则多线程写入会导致数据库锁死从而不记录信息。 68 | 69 | ![image-20230314095446365](https://images-j3.oss-cn-guangzhou.aliyuncs.com/img/image-20230314095446365.png) 70 | 71 | `Xposed插件`需要修改成自己的包名 72 | 73 | ![image-20230314095657422](https://images-j3.oss-cn-guangzhou.aliyuncs.com/img/image-20230314095657422.png) 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 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