├── HookTest.apk ├── LICENSE ├── README.md ├── README_EN.md ├── hook ├── FieldHook.kt ├── HookInit.kt ├── MainHook.kt ├── Tip.kt ├── extension │ ├── ADBHook.kt │ ├── AESHook.kt │ ├── ApplicationHook.kt │ ├── Base64Hook.kt │ ├── BaseHook.kt │ ├── ClickEventHook.kt │ ├── ClipboardHook.kt │ ├── ContactHook.kt │ ├── DialogHook.kt │ ├── ExitHook.kt │ ├── FileHook.kt │ ├── HMACHook.kt │ ├── HotFixHook.kt │ ├── IntentHook.kt │ ├── JSONHook.kt │ ├── PopupWindowHook.kt │ ├── SHAHook.kt │ ├── SensorMangerHook.kt │ ├── SignatureHook.kt │ ├── ToastHook.kt │ ├── VpnCheckHook.kt │ └── WebHook.kt └── util │ ├── ConfigUtil.kt │ ├── Extension.kt │ ├── HookHelper.kt │ ├── HookUtils.kt │ ├── LogUtil.kt │ └── Type.kt └── images ├── config_dialog_screenshot.png ├── config_screenshot.png ├── extension_main_features_shot.png ├── main_extension_print_dialog.png ├── main_extension_screenshot.png └── main_home_screenshot.png /HookTest.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleWhiteDuck/SimpleHook/6d87c0ec606fda00b66e93a141293784912102ac/HookTest.apk -------------------------------------------------------------------------------- /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 | **这里是SimpleHook的hook部分代码** 2 | 3 | # simpleHook使用说明 4 | 5 | **中文文档**|[English](README_EN.md) 6 | 7 | > [simpleHook.apk](https://wwp.lanzoub.com/b0177tlri)(密码:simple) 8 | > 9 | > TG交流群: @simpleHook 10 | > 11 | > 本软件主打简单,如名字一样,如果你追求更复杂的hook操作,推荐使用 [jsHook(你可以实现更复杂的功能)](https://github.com/Xposed-Modules-Repo/me.jsonet.jshook)、[曲境(电脑端浏览器操作)](https://github.com/Mocha-L/QuJing);如果你追求更多的扩展功能,推荐使用算法助手等等类似应用 12 | > 13 | > 功能概述:自定义返回值、参数值等,记录常见各种加密算法、toast、dialog、popupwindow、JSONObject创建增加等 14 | 15 | 16 | ## 1. 功能说明 17 | 18 | ### 页面介绍 19 | 20 | **首页** 21 | 22 | 23 | 24 | 点击加号,可添加配置,点击添加配置进入下面页面 25 | 26 | **配置页面** 27 | 28 | 29 | 30 | 点击‘搜索样式’图标,可进入AppList页面,进行选择应用 31 | 32 | 点击‘下载样式’图标,可保存配置 33 | 34 | 点击右下角加号,可在弹出窗口填写配置 35 | 36 | 37 | 38 | 有多种模式可以选择,输入类名前建议了解设置页面(smali to config),它可以简化填写 39 | 40 | **扩展页面** 41 | 42 | 43 | 44 | **具体功能** 45 | 46 | 47 | 48 | 点击“播放样式”按钮,可打开悬浮窗(需要授予悬浮窗权限),然后打开目标应用,可以显示一些信息(开启了打印参数值、返回值、扩展页面大部分功能) 49 | 50 | 悬浮窗 51 | 52 | 53 | 54 | ## 2.自定义Hook编写规则 55 | 56 | 下面是编写规则:(你可以下载*[HookTest.apk](https://github.com/littleWhiteDuck/HookTest/releases)*,此App应用了所有情况,并内附有配置) 57 | 58 | > 使用前请先了解设置页【smali转配置】,它可以简化你的操作(配合MT管理器等逆向分析软件) 59 | 60 | ### 简要的基本介绍 61 | 62 | - 支持Java语法和Smali语法填写配置信息 63 | 64 | ```java 65 | // java 66 | me.simplehook.MainActivty 67 | // smali 68 | Lme/simplehook/MainActivity; //一定要有 --> ; <-- 69 | ``` 70 | 71 | - 支持基本类型和其它类型参数 72 | 73 | ```java 74 | // 类型 主要用于填写参数类型和变量类型 75 | // 基本类型你可以使用java语法这样填 76 | boolean int long short char byte float double 77 | // 基本类型你也可以使用smali语法这样填 78 | Z I J S C B F D 79 | // 其他类型你可以使用java语法这样填 80 | java.lang.String android.content.Context 81 | // 其他类型你也可以使用smali语法这样填 82 | Ljava/lang/String; Landroid/content/Context; //一定要有 --> ; <-- 83 | ``` 84 | 85 | 86 | ### 结果值的填写规则 87 | 88 | > 此处应注意,本软件不像其他软件一样需要填写返回值、参数值类型,本软件并不需要,你只需要**按照规则填写**,自动判断 89 | 90 | #### 2.1. 基本类型 91 | 92 | | 类型(java、smali) | 值的例子 | 注意事项 | 93 | | ------------------ | -------------------- | ------------------------------ | 94 | | 布尔值(boolean、Z) | true、false | | 95 | | 整数(int、I) | 1、2、3 | | 96 | | 长整型(long、J) | 1l、120000L、123456l | 要注意:数字 + L | 97 | | 短整型(short、S) | 1short、2short | 要注意:数字 + short | 98 | | 字符(char、C) | 195c | 要注意:符合char类型的字符 + c | 99 | | 字节(byte、B) | 2b、3b | 要注意:符合byte类型的字符 + b | 100 | | 单浮点(float、F) | 2f、3f、3.0f | 要注意:数字 + f | 101 | | 双浮点(double、D) | 2d、3d、3.0d | 要注意:数字 + d | 102 | 103 | #### 2.2. null 104 | 105 | > 其他类型只能返回null(字符串除外),null 106 | 107 | #### 2.3. 字符串 108 | 109 | ##### 2.3.1 一般情况 110 | 111 | > 不符合基本类型和null的全部转化为字符串类型 112 | 113 | ##### 2.3.2 特殊情况 114 | 115 | | 特殊的字符串 | 值的例子 | 注意事项 | 116 | | ------------ | -------------------------------- | ------------------------------------------------------------ | 117 | | 数字 | 111s, 2002s | 常见于 "111111" 这种,但是本软件你需要在数字后面加入s,如果你不加s,会被转成数字,可能导致目标应用崩溃 | 118 | | 布尔 | trues、falses | 常见于 "true" 、"false" 这种,但是本软件你需要在布尔值后面加入s,如果你不加s,会被转成布尔值,可能导致目标应用崩溃 | 119 | | null | nulls | 常见于 "null"这种,但是本软件你需要在null后面加入s,如果你不加s,会被转成null,可能导致目标应用空指针 | 120 | | 空字符串 | 英文单词'empty' 或者中文汉字'空' | 如果你直接填空,将无法保存配置,这样做是为了预防你在使用时不填修改值导致无法正常Hook | 121 | 122 | ##### 2.3.3 随机文本返回值 123 | 124 | > 仅用于返回值,返回值填写下面json代码 125 | > 126 | > ```json 127 | > { 128 | > "random": "abcdefgh123456789", 129 | > "length": 9, 130 | > "key": "key", 131 | > "updateTime": 100, 132 | > "defaultValue": "" 133 | > } 134 | > ``` 135 | > 136 | > 上述json格式代码介绍: 137 | > random:字符串,填写随机文本由哪些字符组成 138 | > 139 | > ​ length:整数,代表需要生成多长的随机文本 140 | > 141 | > ​ key:字符串,唯一识别码,可以随便填写,但是一个软件中用到多个随机返回值时需要填不一样的 142 | > 143 | > ​ updateTime:整数,代表着间隔多长时间更新一下随机文本,单位秒, -1代表每次都更新 144 | > 145 | > ​ defaultValue:非必填项 146 | 147 | ## 3.具体的hook模式 148 | 149 | #### hook返回值 150 | 151 | ```java 152 | // 例如1 153 | import simple.example; 154 | Class Example{ 155 | public static boolean isFun() { 156 | boolean result = true; 157 | ... 158 | ... 159 | return result 160 | } 161 | } 162 | /* 163 | 模式选择 Hook返回值 164 | 类名应填:simple.example.Example 165 | 方法名应填:isFun 166 | 参数类型应填:(此处留空,因为没有参数) 167 | 修改值应填:true 或者 false 168 | */ 169 | 170 | /* 171 | 多个参数参数类型的填法(用英语逗号分开,参数类型支持数组): 172 | boolean,int,android.content.Context 173 | */ 174 | // 例如2 175 | import simple.example; 176 | class Example{ 177 | public static String isFun(Sring str, Context context, boolean b) { 178 | String result = str; 179 | ... 180 | ... 181 | return result 182 | } 183 | } 184 | /* 185 | 模式选择 Hook返回值 186 | 类名应填:simple.example.Example 187 | 方法名应填:isFun 188 | 参数类型应填: 189 | java语法: java.lang.String,android.content.Context,boolean (使用参数间使用英文逗号分开,仅一个参数不需要加逗号) 190 | smali语法:Ljava/lang/String;,Landroid/content/Context;,Z 191 | 修改值应填:是个字符串 (应符合结果值的填写规则,不需要加引号) 192 | */ 193 | ``` 194 | 195 | #### hook返回值+ 196 | 197 | >此功能可以将json转为对象(使用Gson),你如果不知道这个对象的Json格式是什么样子的,可以使用【记录返回值】功能,复制返回值即可。这个功能并不是万能的,不适用所有情况,简单的数据类应该是没有问题的,暂不支持数组。 198 | > 199 | >模式:hook返回值+ 200 | > 201 | >返回值的类名:填返回值的类名 202 | > 203 | >修改值:填json代码,如 204 | > 205 | >```json 206 | >{"isHook":false,"level":10000} 207 | >``` 208 | > 举个例子: 209 | > 210 | >```java 211 | >import simple.example; 212 | > 213 | >// 数据类 214 | >public class UserBean { 215 | > private boolean isHook; 216 | > private int level; 217 | > 218 | > public UserBean(boolean isHook, int level) { 219 | > this.isHook = isHook; 220 | > this.level = level; 221 | > } 222 | >} 223 | > 224 | >public class Example{ 225 | > public static UserBean isFun() { 226 | > UserBean userBean = new UserBean(true, 10); 227 | > ... 228 | > ... 229 | > return userBean 230 | > } 231 | > } 232 | >/* 233 | >假如hook isFun的返回值 234 | >模式:hook返回值+ 235 | >类名:simple.example.Example 236 | >方法名:isFun 237 | >参数类型: 238 | >返回值的类名:simple.example.UserBean 239 | >结果值:{"isHook":false,"level":10000} 240 | >*/ 241 | > 242 | >``` 243 | > 244 | > 245 | 246 | #### hook参数值 247 | 248 | ```java 249 | // 类型值同 hook返回值类型 250 | //特殊用法,如下面一段代码 251 | public boolean isModuleLive(Context context, String str, int level){ 252 | 253 | retrun true 254 | } 255 | //如果你只想要hook level的值,你可以在修改值那一栏向下面这样填 256 | ,,99 257 | //如果你只想要hook str的值,你可以在修改值那一栏向下面这样填 258 | ,啦啦啦, 259 | //如果你只想要hook str、level的值,你可以在修改值那一栏向下面这样填 260 | ,啦啦啦,99 261 | //如果你想要全部hook,你可以在修改值那一栏向下面这样填 262 | null,啦啦啦,99 // context为null也许导致闪退 263 | /* 264 | 多个参数参数类型的填法(用英语逗号分开): 265 | android.content.Context,jave.lang.String,int 266 | 或者如下填写 267 | Landroid/content/Context;,Ljave/lang/String;int 268 | */ 269 | ``` 270 | 271 | #### 中断执行 272 | 273 | ```java 274 | // 此模式会拦截方法执行 275 | // 如hook返回值或者hook参数值一样填,不需要填写返回值、参数值 276 | public void printString() { 277 | System.out.println("start"); 278 | testBreakMethod(); 279 | System.out.println("end"); 280 | 281 | /* 282 | 输出结果为 283 | start 284 | end 285 | 286 | test Break Mode 没有被输出 287 | */ 288 | } 289 | 290 | // 假如:此方法被中断 291 | public void testBreakMethod() { 292 | System.out.println("test Break Mode") 293 | } 294 | ``` 295 | 296 | #### Hook所有同名方法 297 | 298 | ```java 299 | /* 300 | Hook一个类所有同名方法,参数类型填写 * 即可 301 | */ 302 | ``` 303 | 304 | #### Hook一个类中所有方法 305 | 306 | ```java 307 | /* 308 | Hook一个类所有方法,方法名填写 * 即可;参数类型可随意填写,有些h不可为空 309 | */ 310 | ``` 311 | 312 | #### 构造方法 313 | 314 | ```java 315 | // 方法名填写: 316 | import simple.example; 317 | public class Example{ 318 | int a; 319 | int b; 320 | public Example(int a, boolean b) { 321 | this.a = a; 322 | this.b = b 323 | } 324 | } 325 | // Hook模式,根据自己的需求选择,一般为hook参数值/记录参数值,其他模式可能造成软件闪退 326 | /* 327 | 方法名填写: 328 | 例如修改两个参数值 329 | 类名填写:simple.example.Example 330 | 方法名填写: 331 | 参数类型填写:int,int 332 | 结果值填写:88,99 333 | */ 334 | ``` 335 | 336 | #### HOOK静态变量 337 | 338 | ```java 339 | import simple.example; 340 | public class Example{ 341 | public static boolean isTest = false; 342 | } 343 | 344 | import simple.example; 345 | public class MainActivity extends Acitvity { 346 | @Override 347 | protected void onCreate(Bundle savedInstanceState) { 348 | super.onCreate(savedInstanceState); 349 | setContentView(R.layout.activity_main); 350 | initData(); 351 | initView(); 352 | } 353 | 354 | private void initData(){ 355 | Example.isTest = false; 356 | } 357 | 358 | private void initView() { 359 | //你想要修改 isTest为true,所以你应当再这个变量被赋值后再去hook 360 | System.out.println(Example.isTest); 361 | } 362 | } 363 | // 具体的值只支持基本类型,和字符串 364 | // 无需填写变量类型;要符合[结果值]填写规则 365 | /* 366 | 模式选择 Hook静态变量 367 | hook点:after/before 根据需要填写,默认after 368 | 类名应填:simple.example.MainActivity; 369 | 方法名应填: initData 370 | 参数类型应填:(什么都是不填,因为这个方法没有参数) 371 | 变量所在类名:simple.example.Example 372 | 变量名应填:isTest 373 | 修改值应填:true/false 374 | */ 375 | ``` 376 | 377 | #### HOOK实例变量 378 | 379 | ```java 380 | import simple.example; 381 | public class UseBean { 382 | private boolean isHook; 383 | private int level; 384 | 385 | public UseBean(boolean isHook, int level) { 386 | this.isHook = isHook; 387 | this.level = level; 388 | } 389 | 390 | public boolean isHook() { 391 | return isHook; 392 | } 393 | 394 | public void setHook(boolean hook) { 395 | isHook = hook; 396 | } 397 | 398 | public int getLevel() { 399 | return level; 400 | } 401 | 402 | public void setLevel(int level) { 403 | this.level = level; 404 | } 405 | } 406 | 407 | import simple.example; 408 | public class MainActivity extends Acitvity { 409 | private User user; 410 | @Override 411 | protected void onCreate(Bundle savedInstanceState) { 412 | super.onCreate(savedInstanceState); 413 | setContentView(R.layout.activity_main); 414 | initData(); 415 | initView(); 416 | } 417 | 418 | private void initData(){ 419 | user = new User(true, 100); 420 | } 421 | 422 | private void initView() { 423 | //你想要修改isHook、level,所以你应当再这个变量被赋值后再去hook 424 | System.out.println(user.isHook()); 425 | System.out.println(user.getLevel()); 426 | } 427 | } 428 | // 具体的值只支持基本类型,和字符串 429 | // 无需填写变量类型;要符合[结果值]填写规则 430 | /* 431 | 模式选择 Hook变量 432 | hook点:after/before 根据需要填写,默认是after 433 | 类名应填:simple.example.UseBean; 434 | 方法名应填: // 表示构造方法 435 | 参数类型应填:boolean,int 436 | 变量名应填:isHook 437 | 修改值应填:true/false 438 | 439 | 实例变量/成员变量:不支持像静态变量一样跨类hook,只能在本类的某个方法执行后,再去hook变量值 440 | */ 441 | ``` 442 | 443 | #### 记录参数值 444 | 445 | > 方法的参数值会被记录,前往记录页面可以查看、 446 | > 若参数是数组或者list会被转成json格式 447 | 448 | #### 记录返回值 449 | 450 | > 方法的返回值会被记录,前往记录页面可以查看 451 | > 若结果是数组或者list会被转成json格式 452 | 453 | #### 记录参返 454 | 455 | > 方法的参数值、返回值会被一同记录,前往记录页面可以查看 456 | > 若结果是数组或者list会被转成json格式 457 | > 若参数是数组或者list会被转成json格式 458 | 459 | #### 扩展Hook 460 | 461 | > 切记**打开总开关** 462 | > 功能请前往app查看 463 | 464 | ## 4.常见问题(FAQ) 465 | 466 | ### 1.hook没有效果 467 | 468 | > - 可看框架日志,是否有报错等 469 | > - 储存文件更新配置某些情况下需要手动刷新,开启、关闭、编辑保存即可刷新 470 | > - 请授予所需权限(android11以下:储存权限,android11及以上:ROOT权限) 471 | 472 | 473 | ### 2.什么是smali转配置 474 | 475 | > 开启此实验功能后,配置页面顶部会增加‘粘贴板’图标,点击可将应用调用代码或签名,转化为配置(防止手动输入错误),增加配置后你需要手动选择合适的模式以及结果值 476 | > 调用代码例子: 477 | > 478 | > ```smali 479 | > iget v0, p0, Lme/duck/hooktest/bean/UseBean;->level:I 480 | > invoke-virtual {v0}, Lme/duck/hooktest/bean/UseBean;->isHook()Z 481 | > ``` 482 | > 483 | > 方法签名、字段签名例子: 484 | > 485 | > ```smali 486 | > Lme/duck/hooktest/bean/UseBean;->level:I 487 | > Lme/duck/hooktest/bean/UseBean;->isHook()Z 488 | > ``` 489 | > 490 | > 上述可在MT管理器导航中长按字段或方法选择**复制签名**或者**查找调用** 491 | 492 | ### 3.为什么目标应用运行很慢 493 | 494 | > 请关闭不必要的扩展HOOK和记录参数、返回值功能, 例如:md5、base64等,这些功能会产生大量的Log 495 | 496 | ### 4. 497 | 498 | ### 5. 499 | 500 | ### 6.什么是hook点 501 | 502 | > hook静态变量、实例变量支持手动填写hook点,hook点就是在方法执行前hook还是在方法执行后hook 503 | > 504 | > before:方法执行前hook; 505 | > after:方法执行后hook 506 | 507 | ### 7.什么是删除遗留配置 508 | 509 | > 当你卸载本应用或者清除数据时,目标应用配置文件仍然可能保存在储存文件中 510 | > 511 | > 1. /data/local/tmp/simpleHook/目标应用包名/config/ 512 | > 2. /storage/emluated/0/Android/data/目标应用包名/simpleHook/config/ 513 | > 514 | > 这个功能就是遍历所有的应用目录并删除无用的配置(本应用内未显示其配置) 515 | > 516 | > 因为需要遍历所有应用会比较慢 517 | 518 | 519 | ### 8.配置说明 520 | 521 | ```json 522 | { 523 | "packageName": "包名", 524 | "appName": "应用名", 525 | "versionName": "版本名", 526 | "description": "描述", 527 | "configs": "配置", 528 | "enable": true, 529 | "id": 0 530 | }, 531 | configs 为下列字符串形式 532 | [ 533 | { 534 | "mode": 0, 535 | "className": "类名", 536 | "methodName": "方法名", 537 | "params": "参数类型", 538 | "fieldName": "变量名", 539 | "fieldClassName": "变量所在类名", 540 | "resultValues": "返回值", 541 | "hookPoint": "hook点", 542 | "returnClassName": "返回值类名", 543 | "fieldType": "变量类型", 544 | "enable": true 545 | } 546 | ] 547 | ``` 548 | 549 | #### mode 550 | 551 | - 0: 552 | 553 | > 代表Hook返回值, 554 | > 555 | > 有效:mode、className、methodName、params、resultValues、enable 556 | > 557 | > params: 多个参数为**,**隔开 558 | > 559 | > resultValues:返回值 560 | 561 | - 1: 562 | 563 | >代表Hook参数值, 564 | > 565 | >有效:mode、className、methodName、params、resultValues、enable 566 | > 567 | >params: 多个参数为**,**隔开 568 | > 569 | >resultValues:多个参数值**,**隔开,留空不hook这个参数 570 | 571 | - 2: 572 | 573 | >代表替换/拦截方法执行 574 | > 575 | >有效:mode、className、methodName、params、enable 576 | 577 | - 3: 578 | 579 | > 代表Hook静态变量 580 | > 581 | > 有效1:mode、className、resultValues、enable、fieldName 582 | > 583 | > ​ 直接Hook变量: 584 | > 585 | > ​ resultValues:变量值 586 | > 587 | > 588 | > 589 | > 有效2:mode、className、methodName、params、resultValues、enable、fieldName、fieldClassName、hookPoint 590 | > 591 | > ​ 某方法Hook前后hook变量: 592 | > 593 | > ​ resultValues: 变量值 594 | > 595 | > ​ hookPoint: before、after 596 | 597 | - 4: 598 | 599 | >代表Hook实例变量 600 | > 601 | >有效:mode、className、methodName、params、resultValues、enable、fieldName、fieldClassName 602 | 603 | - 5: 604 | 605 | >记录参数值 606 | > 607 | >有效:mode、className、methodName、params、enable 608 | 609 | - 6: 610 | 611 | >记录返回值 612 | > 613 | >有效:mode、className、methodName、params、enable 614 | 615 | - 7: 616 | 617 | > 记录参数值和返回值 618 | > 619 | > 有效:mode、className、methodName、params、enable 620 | 621 | - 8: 622 | 623 | > 记录静态变量 624 | > 625 | > 有效:mode、className、methodName、params、resultValues、enable、fieldName、fieldClassName 626 | 627 | - 9: 628 | 629 | > 记录实例变量 630 | > 631 | > 有效:mode、className、methodName、params、resultValues、enable、fieldName、fieldClassNam 632 | 633 | - 10 634 | 635 | > Hook返回值+ 636 | > 637 | > 有效:mode、className、methodName、params、resultValues、enable、returnClassName 638 | > 639 | > 通过Gson将json转为returnClassName的对象 640 | > 641 | > resultValues: json字符串 642 | 643 | 644 | 645 | 646 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | **Here is the code for the hook part of SimpleHook** 2 | 3 | # simpleHook instructions 4 | 5 | [Chinese](README.md)|**English** 6 | 7 | > [simpleHook.apk](https://wwp.lanzoub.com/b0177tlri)(password: simple) 8 | > 9 | > TG Group: @simpleHook 10 | > 11 | > **SimpleHook** is mainly simple, just like the name. If you pursue more complex hook operation, it is recommended to use **[jsHook (You can implement very powerful Hook)](https://github.com/Xposed-Modules-Repo/me.jsonet.jshook)**, [Qujing (computer browser operation)](https://github.com/Mocha-L/QuJing); if you pursue more extended functions, it is recommended to use [算法助手] 12 | > 13 | > Function overview: custom return value, parameter value, etc., record common encryption algorithms, toast, dialog, popupwindow, JSONObject creation and put, etc. 14 | > 15 | 16 | ## 1. Function description 17 | 18 | ### Page Introduction 19 | 20 | **front page** 21 | 22 | 23 | 24 | Click the plus sign to add configuration, click Add configuration to enter the following page 25 | 26 | **Configuration page** 27 | 28 | 29 | 30 | Click the 'Search Style' icon to enter the AppList page and select an application 31 | 32 | Click the 'Download Style' icon to save the configuration 33 | 34 | Click the plus sign in the lower right corner to fill in the configuration in the pop-up window 35 | 36 | 37 | 38 | There are multiple modes to choose from. Before entering the class name, it is recommended to understand the settings page (smali to config), which can simplify filling. 39 | 40 | **Extended page** 41 | 42 | 43 | 44 | **Specific function** 45 | 46 | 47 | 48 | Click the "Play Style" button to open the floating window (you need to grant the permission of the floating window), and then open the target application, you can display some information (the print parameter value, return value, and most functions of the extended page are enabled) 49 | 50 | Floating window 51 | 52 | 53 | 54 | ## 2. Custom Hook writing rules 55 | 56 | The following are the writing rules: (You can download *[HookTest.apk](https://github.com/littleWhiteDuck/HookTest/releases)*, this App applies all the situations and includes configuration) 57 | 58 | > Please understand the setting page **[smali to config]** before use, it can simplify your operation (with decompiler apps such as **MT manager**) 59 | 60 | ### A brief basic introduction 61 | 62 | - Support Java syntax and Smali syntax to fill in configuration information 63 | 64 | ````java 65 | // java 66 | me.simplehook.MainActivty 67 | // smali 68 | Lme/simplehook/MainActivity; //must have --> ; <-- 69 | ```` 70 | 71 | - Support for primitive types and other type parameters 72 | 73 | ````java 74 | // Type is mainly used to fill in the parameter type and variable type 75 | // Basic types you can fill in using java syntax like this 76 | boolean int long short char byte float double 77 | // You can also use smali syntax to fill in basic types like this 78 | Z I J S C B F D 79 | // For other types, you can use java syntax to fill in 80 | jave.lang.String android.content.Context 81 | // You can also use smali syntax to fill in other types like this 82 | Ljava/lang/String; Landroid/content/Context; //must have --> ; <-- 83 | ```` 84 | 85 | 86 | ### Filling rules for result values 87 | 88 | > It should be noted here that this software does not need to fill in the return value and parameter value type like other software, this software does not need it, you only need to **fill in according to the rules**, automatic judgment 89 | 90 | #### 2.1. Primitive type 91 | 92 | | Type (java, smali) | Examples of value ​​ | Note | 93 | | ------------------ | -------------------- | ------------- | 94 | | boolean (boolean, Z) | true, false | | 95 | | int(int, I) | 1, 2, 3 | | 96 | | long(long, J) | 1l, 120000L, 123456l | Note: Number + 'L' | 97 | | short(short, S) | 1short, 2short | Note: number + 'short' | 98 | | char(char, C) | 195c | Note: conforms to char type string + 'c' | 99 | | byte(byte, B) | 2b, 3b | Note: conforms to byte type string + 'b' | 100 | | float(float, F) | 2f, 3f, 3.0f | Note: number + 'f' | 101 | | double(double, D) | 2d, 3d, 3.0d | Note: number + 'd' | 102 | 103 | #### 2.2. null 104 | 105 | > Other types can only return null (except strings), null 106 | 107 | #### 2.3 Strings 108 | 109 | ##### 2.3.1 General 110 | 111 | > Convert everything that does not conform to the primitive type and null to string type 112 | 113 | ##### 2.3.2 Special cases 114 | 115 | | Special Strings | Examples of Values ​​| Notes | 116 | | ------------ | -------------------------------- | ------------------------------------------ | 117 | | Number | 111s, 2002s | Commonly seen in "111111", but in this software you need to add s after the number, if you don't add s, it will be converted into a number, which may cause the target application to crash | 118 | | Boolean | trues, falses | Common in "true" and "false", but in this software, you need to add s after the boolean value. If you do not add s, it will be converted to a boolean value, which may cause the target application to crash | 119 | | null | nulls | Commonly seen in "null", but in this software, you need to add s after null. If you do not add s, it will be converted to null, which may lead to a null pointer in the target application | 120 | | Empty string | English word 'empty' or Chinese character '空' | If you fill in the blank directly, you will not be able to save the configuration, this is to prevent you from not filling in the modified value when you use it, which will cause the normal Hook | 121 | 122 | ##### 2.3.3 Random text return value 123 | 124 | > Fill in the following json code for the return value, for return value only 125 | > 126 | > ```json 127 | > { 128 | > "random": "abcdefgh123456789", 129 | > "length": 9, 130 | > "key": "key", 131 | > "updateTime": 100, 132 | > "defaultValue": "" 133 | > } 134 | > ```` 135 | > 136 | > Introduction to the above json format code: 137 | > 138 | > ```json 139 | > "random": string, fill in which characters the random text consists of 140 | > "length": an integer representing how long random text needs to be generated 141 | > "key": string, unique identification code, can be filled in casually, but when multiple random return values are used in a software, different ones need to be filled in 142 | > "updateTime": an integer, representing how long to update the random text at intervals, in seconds, -1 means update every time 143 | > "defaultValue": optional 144 | > ``` 145 | 146 | ## 3. Hook mode 147 | 148 | #### hook return value 149 | 150 | ````java 151 | // eg 1 152 | import simple.example; 153 | public class Example{ 154 | public static boolean isFun() { 155 | boolean result = true; 156 | ... 157 | ... 158 | return result 159 | } 160 | } 161 | /* 162 | Mode: Hook return value 163 | The class name should be filled in: simple.example.Example 164 | The method name should be filled in: isFun 165 | The parameter type should be filled in: Fill in nothing, because this method has no parameters 166 | The modified value should be filled in: true or false 167 | */ 168 | 169 | /* 170 | Filling method of multiple parameter parameter types (separated by English commas, parameter types support arrays): 171 | boolean,int,android.content.Context 172 | */ 173 | 174 | // eg 2 175 | import simple.example; 176 | public class Example{ 177 | public static String isFun(Sring str, Context context, boolean b) { 178 | String result = str; 179 | ... 180 | ... 181 | return result; 182 | } 183 | } 184 | /* 185 | Mode:Hook return value 186 | The class name should be filled in: simple.example.Example 187 | The method name should be filled in: isFun 188 | Parameter type should be filled in: 189 | java syntax: java.lang.String,android.content.Context,boolean (use commas to separate the parameters, only one parameter does not need a comma) 190 | smali syntax: Ljava/lang/String;,Landroid/content/Context;,Z 191 | The modified value should be filled: it is a string (should meet the filling rules of the result value, no quotation marks are required) 192 | */ 193 | ```` 194 | 195 | #### hook return value+ 196 | 197 | >This function can convert json to object (use **Gson**). If you don't know what the Json format of this object looks like, you can use the function [record return value] and copy the return value. This function is not omnipotent and does not apply to all situations. Simple data classes should be no problem, and arrays are not supported for the time being. 198 | > 199 | >Mode: hook return value+ 200 | > 201 | >Class name of the return value: fill in the class name of the return value 202 | > 203 | >Modify the value: fill in the json code, such as 204 | > 205 | >````json 206 | >{"isHook":false,"level":10000} 207 | >```` 208 | >For example: 209 | > 210 | >```java 211 | >import simple.example; 212 | > 213 | >// data class 214 | >public class UserBean { 215 | > private boolean isHook; 216 | > private int level; 217 | > 218 | > public UserBean(boolean isHook, int level) { 219 | > this.isHook = isHook; 220 | > this.level = level; 221 | > } 222 | >} 223 | > 224 | >public class Example{ 225 | > public static UserBean isFun() { 226 | > UserBean userBean = new UserBean(true, 10); 227 | > ... 228 | > ... 229 | > return userBean; 230 | > } 231 | > } 232 | >/* 233 | >If the return value of hook isFun 234 | >Mode: hook return value + 235 | >Class name: simple.example.Example 236 | >Method name: isFun 237 | >Parameter Type: 238 | >The class name of the return value: simple.example.UserBean 239 | >Result value: {"isHook":false,"level":10000} 240 | >*/ 241 | >``` 242 | > 243 | > 244 | 245 | #### hook parameter value 246 | 247 | ````java 248 | // The type value is the same as the hook return value type 249 | //Special usage, such as the following code 250 | public boolean isModuleLive(Context context, String str, int level){ 251 | 252 | retrun true 253 | } 254 | //If you only want the value of the hook level, you can fill in the following in the column to modify the value 255 | ,,99 256 | //If you only want the value of hook str, you can fill in the following in the column to modify the value 257 | ,hahaha, 258 | //If you only want the value of hook str and level, you can fill in the following in the column to modify the value 259 | ,hahaha,99 260 | //If you want all hooks, you can fill in the following in the modified value column 261 | null,hahaha, 99 // context being null may cause a crash 262 | /* 263 | Filling method of multiple parameter parameter types (separated by English commas): 264 | android.content.Context,jave.lang.String,int 265 | Or fill in as follows 266 | Landroid/content/Context;,Ljave/lang/String;int 267 | */ 268 | ```` 269 | 270 | #### break execution 271 | 272 | ````java 273 | // This mode will intercept method execution 274 | // Fill in the same as the hook return value or hook parameter value, do not need to fill in the return value and parameter value 275 | public void printString() { 276 | System.out.println("start"); 277 | testBreakMethod(); 278 | System.out.println("end"); 279 | 280 | /* 281 | The output is 282 | start 283 | end 284 | 285 | test Break Mode is not output 286 | */ 287 | } 288 | 289 | // if: this method is interrupted 290 | public void testBreakMethod() { 291 | System.out.println("test Break Mode") 292 | } 293 | ```` 294 | 295 | #### Hook all methods with the same name 296 | 297 | ````java 298 | /* 299 | Hook all methods of the same name in a class, fill in * for the parameter type 300 | */ 301 | ```` 302 | 303 | #### Hook all methods in a class 304 | 305 | ````java 306 | /* 307 | Hook all methods in a class, fill in * for the method name. 308 | The parameter type can be filled in freely, some hook types cannot be empty. 309 | */ 310 | ```` 311 | 312 | #### Construction method 313 | 314 | ````java 315 | // Fill in the method name: 316 | import simple.example; 317 | public class Example{ 318 | int a; 319 | int b; 320 | public Example(int a, boolean b) { 321 | this.a = a; 322 | this.b = b 323 | } 324 | } 325 | // Hook mode, choose according to your own needs, generally the hook parameter value/record parameter value, other modes may cause the software to crash 326 | /* 327 | Fill in the method name: 328 | For example, modify two parameter values 329 | Fill in the class name: simple.example.Example 330 | Fill in the method name: 331 | Fill in the parameter type: int, int 332 | Fill in the result value: 88,99 333 | */ 334 | ```` 335 | 336 | #### HOOK static field 337 | 338 | ````java 339 | import simple.example; 340 | public class Example{ 341 | public static boolean isTest = false; 342 | } 343 | 344 | import simple.example; 345 | public class MainActivity extends Acitvity { 346 | @Override 347 | protected void onCreate(Bundle savedInstanceState) { 348 | super.onCreate(savedInstanceState); 349 | setContentView(R.layout.activity_main); 350 | initData(); 351 | initView(); 352 | } 353 | 354 | private void initData(){ 355 | Example.isTest = false; 356 | } 357 | 358 | private void initView() { 359 | //You want to change isTest to true, so you should hook this field after assigning a value 360 | System.out.println(Example.isTest); 361 | } 362 | } 363 | // Concrete values only support primitive types, and strings 364 | // There is no need to fill in the field type; it must comply with the [result value] filling rules 365 | /* 366 | Mode: Hook static field 367 | Hook point: after/before fill in as needed, default after 368 | The class name should be filled in: simple.example.MainActivity; 369 | The method name should be filled in: initData 370 | The parameter type should be filled in: (nothing is filled in, because this method has no parameters) 371 | The class name where the variable is located: simple.example.Example 372 | The variable name should be filled in: isTest 373 | The modified value should be filled in: true/false 374 | */ 375 | ```` 376 | 377 | #### HOOK instance field 378 | 379 | ````java 380 | import simple.example; 381 | public class UseBean { 382 | private boolean isHook; 383 | private int level; 384 | 385 | public UseBean(boolean isHook, int level) { 386 | this.isHook = isHook; 387 | this.level = level; 388 | } 389 | 390 | public boolean isHook() { 391 | return isHook; 392 | } 393 | 394 | public void setHook(boolean hook) { 395 | isHook = hook; 396 | } 397 | 398 | public int getLevel() { 399 | return level; 400 | } 401 | 402 | public void setLevel(int level) { 403 | this.level = level; 404 | } 405 | } 406 | 407 | import simple.example; 408 | public class MainActivity extends Acitvity { 409 | private User user; 410 | @Override 411 | protected void onCreate(Bundle savedInstanceState) { 412 | super.onCreate(savedInstanceState); 413 | setContentView(R.layout.activity_main); 414 | initData(); 415 | initView(); 416 | } 417 | 418 | private void initData(){ 419 | user = new User(true, 100); 420 | } 421 | 422 | private void initView() { 423 | //You want to modify isHook, level, so you should go to hook after this variable is assigned 424 | System.out.println(user.isHook()); 425 | System.out.println(user.getLevel()); 426 | } 427 | } 428 | // Concrete values only support primitive types, and strings 429 | // There is no need to fill in the field type; it must comply with the [result value] filling rules 430 | /* 431 | Mode: Hook instance field 432 | Hook point: after/before fill in as needed, the default is after 433 | The class name should be filled in: simple.example.UseBean; 434 | The method name should be filled in: // represents the constructor 435 | The parameter type should be filled in: boolean, int 436 | The variable name should be filled in: isHook 437 | The modified value should be filled in: true/false 438 | 439 | Instance field/member field: cross-class hooks like static field are not supported. You can only hook the field value after a method of this class is executed. 440 | */ 441 | ```` 442 | 443 | #### record parameter value 444 | 445 | > The parameter values of the method will be recorded, go to the record page to view, 446 | > If the parameter is an array or list, it will be converted to json format 447 | 448 | #### record return value 449 | 450 | > The return value of the method will be recorded, go to the record page to view 451 | > If the result is an array or list, it will be converted to json format 452 | 453 | #### record return 454 | 455 | > The parameter value and return value of the method will be recorded together, go to the record page to view 456 | > If the result is an array or list, it will be converted to json format 457 | > If the parameter is an array or list, it will be converted to json format 458 | 459 | #### Extending Hook 460 | 461 | > Remember to **turn on the main switch** 462 | > Please go to the app to view the functions 463 | 464 | ## Frequently Asked Questions (FAQ) 465 | 466 | ### 1.hook has no effect 467 | 468 | > - You can see the xposed framework(example: LSPosed) log, whether there is an error, etc. 469 | >- In some cases, the storage file update configuration needs to be manually refreshed, open, close, edit and save to refresh 470 | > - Please grant the required permissions (below android11: storage permission, android11 and above: ROOT permission) 471 | 472 | 473 | ### 2. What is smali transfer configuration 474 | 475 | > After this experimental function is enabled, a 'pasteboard' icon will be added to the top of the configuration page. Click to convert the application code or signature into a configuration (to prevent manual input errors). After adding the configuration, you need to manually select the appropriate mode and result value. 476 | > Example of calling code: 477 | > 478 | > ```smali 479 | > iget v0, p0, Lme/duck/hooktest/bean/UseBean;->level:I 480 | > invoke-virtual {v0}, Lme/duck/hooktest/bean/UseBean;->isHook()Z 481 | > ```` 482 | > 483 | > Example of method signature and field signature: 484 | > 485 | > ```smali 486 | > Lme/duck/hooktest/bean/UseBean;->level:I 487 | > Lme/duck/hooktest/bean/UseBean;->isHook()Z 488 | > ```` 489 | > 490 | > The above can be selected by long-pressing a field or method in the MT Manager navigation to select **Copy Signature** or **Find Call** 491 | 492 | ### 3. Why the target application is running slowly 493 | 494 | > Please turn off unnecessary **EXTENSION HOOK** and **record parameters**, **record return value**...etc, such as: md5, base64, etc., these functions will generate a lot of Log 495 | 496 | ### 4. 497 | 498 | ### 5. 499 | 500 | ### 6. What is hook point 501 | 502 | > Some hooks support manually filling in the hook point. The hook point is the hook before the method is executed or the hook after the method is executed. 503 | > 504 | > before: hook before method execution; 505 | > after: hook after the method is executed 506 | 507 | ### 7. What is [Remove interfering configuration] 508 | 509 | > When you uninstall the app or clear data, the target app configuration file may still be saved in the storage file 510 | > 511 | > 1. /data/local/tmp/simpleHook/target application package name/config/ 512 | > 2. /storage/emluated/0/Android/data/target application package name/simpleHook/config/ 513 | > 514 | > This function is to traverse all application directories and delete useless configurations (the configuration is displayed in this application). 515 | > 516 | > Because it needs to traverse all applications will be slower -------------------------------------------------------------------------------- /hook/FieldHook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook 2 | 3 | import com.github.kyuubiran.ezxhelper.utils.* 4 | import de.robv.android.xposed.XC_MethodHook 5 | import de.robv.android.xposed.XposedHelpers 6 | import me.simpleHook.bean.ConfigBean 7 | import me.simpleHook.bean.LogBean 8 | import me.simpleHook.constant.Constant 9 | import me.simpleHook.hook.Tip.getTip 10 | import me.simpleHook.hook.util.* 11 | import me.simpleHook.hook.util.HookHelper.appClassLoader 12 | import me.simpleHook.hook.util.HookHelper.hostPackageName 13 | import me.simpleHook.util.LanguageUtils 14 | 15 | object FieldHook { 16 | /** 17 | * @author littleWhiteDuck 18 | * @param configBean 配置类 19 | */ 20 | @JvmStatic 21 | fun hookStaticField(configBean: ConfigBean) { 22 | configBean.apply { 23 | if (className.isEmpty() && methodName.isEmpty() && params.isEmpty()) { 24 | // 直接hook 25 | if (mode == Constant.HOOK_RECORD_STATIC_FIELD) { 26 | recordStaticField(fieldClassName, fieldName) 27 | } else { 28 | hookStaticField(fieldClassName, resultValues, fieldName) 29 | } 30 | return 31 | } 32 | val hooker: Hooker = if (mode == Constant.HOOK_RECORD_STATIC_FIELD) { 33 | { recordStaticField(fieldClassName, fieldName) } 34 | } else { 35 | { hookStaticField(fieldClassName, resultValues, fieldName) } 36 | } 37 | hookField(hooker) 38 | } 39 | } 40 | 41 | private fun ConfigBean.hookField( 42 | hooker: Hooker 43 | ) { 44 | val isBeforeHook = hookPoint == "before" 45 | try { 46 | if (methodName == "*") { 47 | findAllMethods(className) { 48 | true 49 | }.hook(isBeforeHook, hooker) 50 | } else if (params == "*") { 51 | if (methodName == "") { 52 | findAllConstructors(className) { 53 | true 54 | }.hook(isBeforeHook, hooker) 55 | } else { 56 | findAllMethods(className) { 57 | name == methodName 58 | }.hook(isBeforeHook, hooker) 59 | } 60 | } else { 61 | if (methodName == "") { 62 | findConstructor(className) { 63 | isSearchConstructor(params) 64 | }.hook(isBeforeHook, hooker) 65 | } else { 66 | findMethod(className) { 67 | name == methodName && isSearchMethod(params) 68 | }.hook(isBeforeHook, hooker) 69 | } 70 | } 71 | } catch (e: Throwable) { 72 | LogUtil.outHookError(className, "$methodName($params)", e) 73 | } 74 | } 75 | 76 | private fun recordStaticField( 77 | fieldClassName: String, fieldName: String 78 | ) { 79 | val type = if (LanguageUtils.isNotChinese()) "Static field" else "静态变量" 80 | val hookClass = XposedHelpers.findClass(fieldClassName, appClassLoader) 81 | val result = XposedHelpers.getStaticObjectField(hookClass, fieldName) 82 | val list = listOf(getTip("className") + fieldClassName, 83 | getTip("fieldName") + fieldName, 84 | getTip("fieldValue") + result) 85 | val logBean = LogBean(type = type, other = list, packageName = hostPackageName) 86 | LogUtil.outLogMsg(logBean) 87 | } 88 | 89 | private fun hookStaticField( 90 | fieldClassName: String, values: String, fieldName: String 91 | ) { 92 | val clazz: Class<*> = XposedHelpers.findClass(fieldClassName, appClassLoader) 93 | XposedHelpers.setStaticObjectField(clazz, fieldName, Type.getDataTypeValue(values)) 94 | } 95 | 96 | @JvmStatic 97 | fun hookInstanceField( 98 | configBean: ConfigBean 99 | ) { 100 | configBean.apply { 101 | val hooker: Hooker = if (mode == Constant.HOOK_RECORD_INSTANCE_FIELD) { 102 | { recordInstanceField(className, it, fieldName) } 103 | } else { 104 | { hookInstanceField(it, resultValues, fieldName) } 105 | } 106 | hookField(hooker) 107 | } 108 | } 109 | 110 | private fun recordInstanceField( 111 | className: String, param: XC_MethodHook.MethodHookParam, fieldName: String 112 | ) { 113 | val type = if (LanguageUtils.isNotChinese()) "Instance field" else "实例变量" 114 | val thisObj = param.thisObject 115 | val result = XposedHelpers.getObjectField(thisObj, fieldName) 116 | val list = listOf(getTip("className") + className, 117 | getTip("fieldName") + fieldName, 118 | getTip("fieldValue") + result) 119 | val logBean = LogBean(type = type, other = list, packageName = hostPackageName) 120 | LogUtil.outLogMsg(logBean) 121 | } 122 | 123 | private fun hookInstanceField( 124 | param: XC_MethodHook.MethodHookParam, values: String, fieldName: String 125 | ) { 126 | val thisObj = param.thisObject 127 | XposedHelpers.setObjectField(thisObj, fieldName, Type.getDataTypeValue(values)) 128 | } 129 | } -------------------------------------------------------------------------------- /hook/HookInit.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import com.github.kyuubiran.ezxhelper.init.EzXHelperInit 6 | import com.github.kyuubiran.ezxhelper.utils.findMethod 7 | import com.github.kyuubiran.ezxhelper.utils.hookAfter 8 | import com.github.kyuubiran.ezxhelper.utils.hookReturnConstant 9 | import de.robv.android.xposed.IXposedHookLoadPackage 10 | import de.robv.android.xposed.callbacks.XC_LoadPackage 11 | import me.simpleHook.BuildConfig 12 | import me.simpleHook.constant.Constant 13 | import me.simpleHook.hook.util.ConfigUtil 14 | import me.simpleHook.hook.util.HookHelper 15 | import me.simpleHook.extension.log 16 | 17 | class HookInit : IXposedHookLoadPackage { 18 | 19 | override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { 20 | EzXHelperInit.initHandleLoadPackage(lpparam) 21 | if (lpparam.packageName == BuildConfig.APPLICATION_ID) { 22 | findMethod("me.simpleHook.ui.activity.MainActivity") { 23 | name == "isModuleLive" 24 | }.hookReturnConstant(true) 25 | 26 | } else { 27 | if (HookHelper.isAppContextInitialized) return 28 | findMethod(Application::class.java) { 29 | name == "attach" 30 | }.hookAfter { 31 | HookHelper.initFields(context = it.args[0] as Context, lpparam) 32 | HookHelper.appContext.getExternalFilesDirs(null) 33 | startHook() 34 | } 35 | } 36 | } 37 | 38 | private fun startHook() { 39 | val packageName = HookHelper.hostPackageName 40 | ConfigUtil.getConfigFromFile()?.let { 41 | "get custom config succeed from file".log(packageName) 42 | MainHook.readyHook(it) 43 | } ?: run { 44 | "get custom config failed from file".log(packageName) 45 | ConfigUtil.getCustomConfigFromDB()?.let { 46 | "get custom config succeed from db".log(packageName) 47 | MainHook.readyHook(it) 48 | } ?: "get custom config failed from db".log(packageName) 49 | } 50 | ConfigUtil.getConfigFromFile(Constant.EXTENSION_CONFIG_NAME)?.let { 51 | "get extension config succeed from file".log(packageName) 52 | MainHook.readyExtensionHook(it) 53 | } ?: run { 54 | "get extension config failed from file".log(packageName) 55 | ConfigUtil.getExConfigFromDB()?.let { 56 | "get extension config succeed from db".log(packageName) 57 | MainHook.readyExtensionHook(it) 58 | } ?: "get extension config failed from db".log(packageName) 59 | } 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /hook/MainHook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook 2 | 3 | import android.app.AndroidAppHelper 4 | import android.content.Context 5 | import com.github.kyuubiran.ezxhelper.utils.* 6 | import com.google.gson.Gson 7 | import de.robv.android.xposed.XC_MethodHook 8 | import de.robv.android.xposed.XposedHelpers 9 | import kotlinx.serialization.decodeFromString 10 | import kotlinx.serialization.json.Json 11 | import me.simpleHook.bean.ConfigBean 12 | import me.simpleHook.bean.ExtensionConfig 13 | import me.simpleHook.bean.LogBean 14 | import me.simpleHook.constant.Constant 15 | import me.simpleHook.database.entity.AppConfig 16 | import me.simpleHook.extension.log 17 | import me.simpleHook.extension.random 18 | import me.simpleHook.hook.Tip.getTip 19 | import me.simpleHook.hook.extension.* 20 | import me.simpleHook.hook.util.* 21 | import me.simpleHook.hook.util.HookHelper.appContext 22 | import me.simpleHook.hook.util.HookHelper.hostPackageName 23 | import me.simpleHook.hook.util.HookUtils.getObjectString 24 | import me.simpleHook.hook.util.LogUtil.getStackTrace 25 | import me.simpleHook.hook.util.LogUtil.outHookError 26 | import me.simpleHook.hook.util.LogUtil.outLogMsg 27 | import me.simpleHook.hook.util.Type.getDataTypeValue 28 | import me.simpleHook.util.JsonUtil 29 | import me.simpleHook.util.LanguageUtils 30 | import org.json.JSONObject 31 | 32 | 33 | object MainHook { 34 | 35 | fun readyHook(strConfig: String) { 36 | if (strConfig.isBlank()) return 37 | try { 38 | val appConfig = Json.decodeFromString(strConfig) 39 | if (!appConfig.enable) return 40 | val configs = Json.decodeFromString>(appConfig.configs) 41 | getTip("startCustomHook").log(hostPackageName) 42 | configs.forEach { configBean -> 43 | if (!configBean.enable) return@forEach 44 | configBean.apply { 45 | when (configBean.mode) { 46 | Constant.HOOK_STATIC_FIELD, Constant.HOOK_RECORD_STATIC_FIELD -> { 47 | FieldHook.hookStaticField(configBean) 48 | } 49 | Constant.HOOK_FIELD, Constant.HOOK_RECORD_INSTANCE_FIELD -> { 50 | FieldHook.hookInstanceField(configBean) 51 | } 52 | else -> specificHook(className = className, 53 | methodName = methodName, 54 | values = resultValues, 55 | params = params, 56 | mode = mode, 57 | returnClassName = returnClassName) 58 | } 59 | } 60 | } 61 | } catch (e: Throwable) { 62 | val configTemp = try { 63 | val appConfig = Json.decodeFromString(strConfig) 64 | JsonUtil.formatJson(appConfig.configs) 65 | } catch (e: Throwable) { 66 | strConfig 67 | } 68 | LogUtil.outLog(arrayListOf(getTip("errorType") + getTip("unknownError"), 69 | "config: $configTemp", 70 | getTip("detailReason") + e.stackTraceToString()), "Error Unknown Error") 71 | "config error".log(hostPackageName) 72 | } 73 | } 74 | 75 | 76 | private fun specificHook( 77 | className: String, 78 | methodName: String, 79 | values: String, 80 | params: String, 81 | mode: Int, 82 | returnClassName: String 83 | ) { 84 | val hooker: Hooker = when (mode) { 85 | Constant.HOOK_RETURN -> { 86 | { hookReturnValue(values, it) } 87 | } 88 | Constant.HOOK_RETURN2 -> { 89 | { hookReturnValuePro(values, it, returnClassName) } 90 | } 91 | Constant.HOOK_BREAK -> { 92 | {} 93 | } 94 | Constant.HOOK_PARAM -> { 95 | { hookParamsValue(it, values, className, methodName, params) } 96 | } 97 | Constant.HOOK_RECORD_PARAMS -> { 98 | { recordParamsValue(className, it) } 99 | } 100 | Constant.HOOK_RECORD_RETURN -> { 101 | { recordReturnValue(className, it) } 102 | } 103 | Constant.HOOK_RECORD_PARAMS_RETURN -> { 104 | { recordParamsAndReturn(className, it) } 105 | } 106 | else -> { 107 | throw java.lang.IllegalStateException("读不懂配置") 108 | } 109 | } 110 | try { 111 | if (methodName == "*") { 112 | findAllMethods(className) { 113 | true 114 | }.hook(mode, hooker) 115 | } else if (params == "*") { 116 | if (methodName == "") { 117 | hookAllConstructorBefore(className, hooker = hooker) 118 | } else { 119 | findAllMethods(className) { 120 | name == methodName 121 | }.hook(mode, hooker) 122 | } 123 | } else { 124 | if (methodName == "") { 125 | findConstructor(className) { 126 | isSearchConstructor(params) 127 | }.hookBefore(hooker) 128 | } else { 129 | findMethod(className) { 130 | name == methodName && isSearchMethod(params) 131 | }.hook(mode, hooker) 132 | } 133 | } 134 | } catch (e: Throwable) { 135 | outHookError(className, "$methodName($params)", e) 136 | } 137 | 138 | } 139 | 140 | private fun hookReturnValuePro( 141 | values: String, param: XC_MethodHook.MethodHookParam, returnClassName: String 142 | ) { 143 | val hookClass = XposedHelpers.findClass(returnClassName, HookHelper.appClassLoader) 144 | try { 145 | val hookObject = Gson().fromJson(values, hookClass) 146 | param.result = hookObject 147 | } catch (e: Exception) { 148 | hookReturnValue(values, param) 149 | } 150 | } 151 | 152 | private fun hookReturnValue( 153 | values: String, param: XC_MethodHook.MethodHookParam 154 | ) { 155 | val targetValue = getDataTypeValue(values) 156 | if (targetValue is String) { 157 | try { 158 | val jsonObject = JSONObject(targetValue) 159 | if (jsonObject.has("random") && jsonObject.has("length") && jsonObject.has("key")) { 160 | val randomSeed = jsonObject.optString("random", "a1b2c3d4e5f6g7h8i9k0l") 161 | val len = jsonObject.optInt("length", 10) 162 | val updateTime = jsonObject.optLong("updateTime", -1L) 163 | val key = jsonObject.getString("key") 164 | val defaultValue = jsonObject.optString("defaultValue") 165 | if (updateTime == -1L) { 166 | val result = randomSeed.random(len) 167 | param.result = result 168 | } else { 169 | val sp = AndroidAppHelper.currentApplication() 170 | .getSharedPreferences("me.simpleHook", Context.MODE_PRIVATE) 171 | val oldTime = sp.getLong("time_$key", 0L) 172 | val oldRandom = sp.getString("random_$key", defaultValue) 173 | val currentTime = System.currentTimeMillis() / 1000 174 | if (currentTime - updateTime >= oldTime) { 175 | val result = randomSeed.random(len) 176 | sp.edit().putString("random_$key", result).apply() 177 | sp.edit().putLong("time_$key", currentTime).apply() 178 | param.result = result 179 | } else { 180 | param.result = oldRandom 181 | } 182 | } 183 | } 184 | } catch (e: Exception) { 185 | param.result = targetValue 186 | } 187 | } else { 188 | param.result = targetValue 189 | } 190 | } 191 | 192 | private fun hookParamsValue( 193 | param: XC_MethodHook.MethodHookParam, 194 | values: String, 195 | className: String, 196 | methodName: String, 197 | params: String 198 | ) { 199 | try { 200 | for (i in param.args.indices) { 201 | if (values.split(",")[i] == "") continue 202 | val targetValue = getDataTypeValue(values.split(",")[i]) 203 | param.args[i] = targetValue 204 | } 205 | } catch (e: java.lang.Exception) { 206 | val list = listOf(getTip("errorType") + "HookParamsError", 207 | getTip("solution") + getTip("paramsNotEqualValues"), 208 | getTip("filledClassName") + className, 209 | getTip("filledMethodParams") + "$methodName($params)", 210 | getTip("detailReason") + e.stackTraceToString()) 211 | LogUtil.outLog(list, "Error HookParamsError") 212 | } 213 | } 214 | 215 | private fun recordParamsValue( 216 | className: String, param: XC_MethodHook.MethodHookParam 217 | ) { 218 | val type = if (LanguageUtils.isNotChinese()) "Param value" else "参数值" 219 | val list = mutableListOf() 220 | list.add(getTip("className") + className) 221 | list.add(getTip("methodName") + param.method.name) 222 | val paramLen = param.args.size 223 | if (paramLen == 0) { 224 | list.add(getTip("notHaveParams")) 225 | } else { 226 | for (i in 0 until paramLen) { 227 | list.add("${getTip("param")}${i + 1}: ${getObjectString(param.args[i] ?: "null")}") 228 | } 229 | } 230 | val items = getStackTrace() 231 | val logBean = LogBean(type, list + items, hostPackageName) 232 | outLogMsg(logBean) 233 | } 234 | 235 | private fun recordReturnValue( 236 | className: String, param: XC_MethodHook.MethodHookParam 237 | ) { 238 | val list = mutableListOf() 239 | val type = if (LanguageUtils.isNotChinese()) "Return value" else "返回值" 240 | list.add(getTip("className") + className) 241 | list.add(getTip("methodName") + param.method.name) 242 | val result = getObjectString(param.result ?: "null") 243 | list.add(getTip("returnValue") + result) 244 | val items = getStackTrace() 245 | val logBean = LogBean(type, list + items, hostPackageName) 246 | outLogMsg(logBean) 247 | } 248 | 249 | private fun recordParamsAndReturn( 250 | className: String, param: XC_MethodHook.MethodHookParam 251 | ) { 252 | val type = if (LanguageUtils.isNotChinese()) "Param&Return Value" else "参返" 253 | val list = mutableListOf() 254 | list.add(getTip("className") + className) 255 | list.add(getTip("methodName") + param.method.name) 256 | val paramLen = param.args.size 257 | if (paramLen == 0) { 258 | list.add(getTip("notHaveParams")) 259 | } else { 260 | for (i in 0 until paramLen) { 261 | list.add("${getTip("param")}${i + 1}: ${getObjectString(param.args[i] ?: "null")}") 262 | } 263 | } 264 | val result = getObjectString(param.result ?: "null") 265 | list.add(getTip("returnValue") + result) 266 | val items = getStackTrace() 267 | val logBean = LogBean(type, list + items, hostPackageName) 268 | outLogMsg(logBean) 269 | } 270 | 271 | 272 | fun readyExtensionHook( 273 | strConfig: String 274 | ) { 275 | try { 276 | if (strConfig.trim().isEmpty()) return 277 | getTip("startExtensionHook").log(hostPackageName) 278 | val configBean = Json.decodeFromString(strConfig) 279 | if (!configBean.all) return 280 | if (configBean.tip) appContext.showToast(msg = "SimpleHook: StartHook") 281 | initExtensionHook(configBean, 282 | DialogHook, 283 | PopupWindowHook, 284 | ToastHook, 285 | HotFixHook, 286 | IntentHook, 287 | ClickEventHook, 288 | VpnCheckHook, 289 | Base64Hook, 290 | SHAHook, 291 | HMACHook, 292 | AESHook, 293 | JSONHook, 294 | WebHook, 295 | ClipboardHook, 296 | ApplicationHook, 297 | SignatureHook, 298 | ContactHook, 299 | SensorMangerHook, 300 | ADBHook, 301 | FileHook, 302 | ExitHook) 303 | } catch (e: Throwable) { 304 | LogUtil.outLog(arrayListOf(getTip("errorType") + getTip("unknownError"), 305 | "config: ${JsonUtil.formatJson(strConfig)}", 306 | getTip("detailReason") + e.stackTraceToString()), "Error Unknown Error") 307 | } 308 | } 309 | 310 | private fun initExtensionHook( 311 | configBean: ExtensionConfig, vararg hooks: BaseHook 312 | ) { 313 | hooks.forEach { 314 | if (it.isInit) return@forEach 315 | it.isInit 316 | it.startHook(configBean) 317 | } 318 | } 319 | 320 | } 321 | 322 | -------------------------------------------------------------------------------- /hook/Tip.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook 2 | 3 | import com.google.gson.Gson 4 | import me.simpleHook.util.LanguageUtils 5 | 6 | object Tip { 7 | private const val chineseTip = 8 | "{ \"text\": \"文本:\", \"button\": \"按钮:\", \"callbackType\": \"回调类名:\", \"viewType\": \"控件类型:\", \"encryptType\": \"类型:加密\", \"rawData\": \"原始数据:\", \"encryptResult\": \"加密结果:\", \"decryptResult\": \"解密结果:\", \"key\": \"密钥:\", \"keyAlgorithm\": \"密钥算法:\", \"encrypt\": \"加密\", \"decrypt\": \"解密\", \"isDecrypt\": \"加密/解密:解密\", \"isEncrypt\": \"加密/解密:加密\", \"className\": \"类名:\", \"methodName\": \"方法名:\", \"param\": \"参数\", \"returnValue\": \"返回值:\", \"startCustomHook\": \"开始自定义Hook\", \"startExtensionHook\": \"开始扩展Hook\", \"errorType\": \"错误类型:\", \"solution\": \"解决方案:\", \"filledClassName\": \"所填类名:\", \"filledMethodParams\": \"所填方法(参数):\", \"filledMethodOrField\": \"所填方法(参数)|变量: \", \"detailReason\": \"具体原因:\", \"notFoundClass\": \"请确保填写的类名正确\", \"noSuchMethod\": \"请确保填写的方法名/参数等数据正确\", \"paramsNotEqualValues\": \"请查看修改值个数是否与参数个数相同\", \"useSmali2Config\": \"你的方法/参数/类名填写有问题,请使用【smali转配置】来降低出错的概率\", \"useNormalVersion\": \"请注意,你的机型并不适合使用ROOT版,请使用普通版\", \"notHaveParams\": \"参数:这个方法没有参数!\", \"unknownError\": \"未知错误\", \"encryptOrDecrypt\": \"加密/解密:\", \"result\": \"结果:\", \"setClipboard\": \"写入剪贴板\", \"getClipboard\": \"读取剪贴板\", \"clipboardInfo\": \"信息:\", \"fieldName\": \"变量名:\", \"fieldValue\": \"变量值:\", \"applicationName\": \"Application入口名:\", \"createFile\": \"创建文件\", \"deleteFile\": \"删除文件\", \"readFile\": \"读取文件\", \"writeFile\": \"写出文件\", \"readAssets\": \"读取Assets文件\", \"path\": \"路径:\", \"info\": \"信息(仅显示缓存大小的部分):\", \"notSetCacheSize\": \"没有设置缓存大小\", \"finish\": \"退出: 通过finish()\", \"exit\": \"退出: 通过exit()\", \"killProcess\": \"退出: 通过killProcess()\" }" 9 | private const val englishTip = 10 | "{\n" + "\t\"text\": \"Text: \",\n" + "\t\"button\": \"Button: \",\n" + "\t\"callbackType\": \"callbackType: \",\n" + "\t\"viewType\": \"viewType: \",\n" + "\t\"encryptType\": \"Type: encrypt\",\n" + "\t\"rawData\": \"Raw Data: \",\n" + "\t\"encryptResult\": \"Encrypt result: \",\n" + "\t\"decryptResult\": \"Decrypt result: \",\n" + "\t\"key\": \"Key: \",\n" + "\t\"keyAlgorithm\": \"Key algorithm: \",\n" + "\t\"encrypt\": \"encrypt\",\n" + "\t\"decrypt\": \"decrypt\",\n" + "\t\"isDecrypt\": \"Encrypt/Decrypt: decrypt\",\n" + "\t\"isEncrypt\": \"Encrypt/Decrypt: encrypt\",\n" + "\t\"className\": \"Class name: \",\n" + "\t\"methodName\": \"Method name: \",\n" + "\t\"param\": \"Param\",\n" + "\t\"returnValue\": \"Return value: \",\n" + "\t\"startCustomHook\": \"Start custom hook\",\n" + "\t\"startExtensionHook\": \"start extension hook\",\n" + "\t\"errorType\": \"Error type: \",\n" + "\t\"solution\": \"Solution: \",\n" + "\t\"filledClassName\": \"Filled class name: \",\n" + "\t\"filledMethodParams\": \"Filled method (parameters): \",\n" + "\t\"filledMethodOrField\": \"Filled method(parameters)|Field: \",\n" + "\t\"detailReason\": \"Detail reason: \",\n" + "\t\"notFoundClass\": \"Please make sure the class name is correct\",\n" + "\t\"noSuchMethod\": \"Please make sure that the method name/parameters and other data filled in are correct\",\n" + "\t\"paramsNotEqualValues\": \"Please check whether the number of modified values is the same as the number of parameters\",\n" + "\t\"useSmali2Config\": \"There is a problem with filling in your method/parameter/class name, please use [smali to config] to reduce the probability of errors\",\n" + "\t\"useNormalVersion\": \"Please note that your model is not suitable for the ROOT version, please use the normal version\",\n" + "\t\"notHaveParams\": \"Parameters: This method has no parameters!\",\n" + "\t\"unknownError\": \"Unknown error\",\n" + "\t\"encryptOrDecrypt\": \"Encrypt/Decrypt: \",\n" + "\t\"result\": \"result: \",\n" + "\t\"setClipboard\": \"Write clipboard\",\n" + "\t\"getClipboard\": \"Read clipboard\",\n" + "\t\"clipboardInfo\": \"Info: \",\n" + "\t\"fieldName\": \"Field name: \",\n" + "\t\"fieldValue\": \"Field value: \",\n" + "\t\"applicationName\": \"Application name:\",\n" + "\t\"createFile\": \"Create file\",\n" + "\t\"deleteFile\": \"Delete file\",\n" + "\t\"readFile\": \"Read file\",\n" + "\t\"writeFile\": \"Write file\",\n" + "\t\"readAssets\": \"Read Assets file\",\n" + "\t\"path\": \"Path: \",\n" + "\t\"info\": \"Info(Show only the cache size): \",\n" + "\t\"notSetCacheSize\": \"No cache size set\",\n" + "\t\"finish\": \"Exit: by finish()\",\n" + "\t\"exit\": \"Exit: by exit()\",\n" + "\t\"killProcess\": \"Exit: killProcess()\"\n" + "}" 11 | private val tipMap = Gson().fromJson>(chineseTip, Map::class.java) 12 | private val tipEnglishMap = Gson().fromJson>(englishTip, Map::class.java) 13 | private val isNotChinese = LanguageUtils.isNotChinese() 14 | 15 | fun getTip(key: String): String { 16 | return if (isNotChinese) { 17 | tipEnglishMap[key] ?: key 18 | } else { 19 | tipMap[key] ?: key 20 | } 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /hook/extension/ADBHook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.extension 2 | 3 | import android.provider.Settings.Global 4 | import android.provider.Settings.Secure 5 | import com.github.kyuubiran.ezxhelper.utils.findAllMethods 6 | import com.github.kyuubiran.ezxhelper.utils.hookBefore 7 | import de.robv.android.xposed.XC_MethodHook 8 | import me.simpleHook.bean.ExtensionConfig 9 | 10 | object ADBHook : BaseHook() { 11 | override fun startHook(configBean: ExtensionConfig) { 12 | if (configBean.adb) { 13 | findAllMethods(Secure::class.java) { 14 | name == "getInt" 15 | }.hookBefore { 16 | disableCheckAdb(it) 17 | } 18 | findAllMethods(Global::class.java) { 19 | name == "getInt" 20 | }.hookBefore { 21 | disableCheckAdb(it) 22 | } 23 | } 24 | } 25 | 26 | private fun disableCheckAdb(it: XC_MethodHook.MethodHookParam) { 27 | val keyName = it.args[1] as String 28 | if (keyName == Global.ADB_ENABLED) { 29 | it.result = 0 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /hook/extension/AESHook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.extension 2 | 3 | import de.robv.android.xposed.XC_MethodHook 4 | import de.robv.android.xposed.XposedBridge 5 | import me.simpleHook.bean.ExtensionConfig 6 | import me.simpleHook.bean.LogBean 7 | import me.simpleHook.hook.Tip 8 | import me.simpleHook.hook.util.HookHelper 9 | import me.simpleHook.hook.util.LogUtil 10 | import java.security.spec.EncodedKeySpec 11 | import javax.crypto.Cipher 12 | import javax.crypto.spec.IvParameterSpec 13 | import javax.crypto.spec.SecretKeySpec 14 | 15 | object AESHook : BaseHook() { 16 | override fun startHook(configBean: ExtensionConfig) { 17 | if (!configBean.crypt) return 18 | val map: HashMap = HashMap() 19 | XposedBridge.hookAllConstructors(IvParameterSpec::class.java, object : XC_MethodHook() { 20 | override fun afterHookedMethod(param: MethodHookParam) { 21 | val ivParameterSpec = param.thisObject as IvParameterSpec 22 | val iv = String(ivParameterSpec.iv) 23 | map["iv"] = iv 24 | } 25 | }) 26 | XposedBridge.hookAllConstructors(SecretKeySpec::class.java, object : XC_MethodHook() { 27 | override fun afterHookedMethod(param: MethodHookParam) { 28 | val secretKeySpec = param.thisObject as SecretKeySpec 29 | val keyAlgorithm = secretKeySpec.algorithm 30 | val key = String(secretKeySpec.encoded) 31 | map["keyAlgorithm"] = keyAlgorithm 32 | map["key"] = key 33 | } 34 | }) 35 | XposedBridge.hookAllConstructors(EncodedKeySpec::class.java, object : XC_MethodHook() { 36 | override fun afterHookedMethod(param: MethodHookParam) { 37 | val key = String(param.args[0] as ByteArray) 38 | map["key"] = key 39 | } 40 | }) 41 | 42 | XposedBridge.hookAllMethods(Cipher::class.java, "init", object : XC_MethodHook() { 43 | override fun afterHookedMethod(param: MethodHookParam) { 44 | val opmode = param.args[0] as Int 45 | val cryptType = 46 | if (opmode == Cipher.ENCRYPT_MODE) Tip.getTip("encrypt") else Tip.getTip("decrypt") 47 | map["cryptType"] = cryptType 48 | } 49 | }) 50 | XposedBridge.hookAllMethods(Cipher::class.java, "update", object : XC_MethodHook() { 51 | override fun afterHookedMethod(param: MethodHookParam) { 52 | /* 53 | byte[] update(byte[] input) 54 | byte[] update(byte[] input, int inputOffset, int inputLen) 55 | */ 56 | val paramLen = param.args.size 57 | if (paramLen == 1 || paramLen == 3) { 58 | val input = param.args[0] as ByteArray 59 | var inputOffset = 0 60 | var inputLen = input.size 61 | if (paramLen == 3) { 62 | inputLen = param.args[1] as Int 63 | inputOffset = param.args[2] as Int 64 | } 65 | val rawData = ByteArray(inputLen) 66 | System.arraycopy(input, inputOffset, rawData, 0, inputLen) 67 | map["rawData"] = String(rawData) 68 | } 69 | } 70 | }) 71 | 72 | XposedBridge.hookAllMethods(Cipher::class.java, "doFinal", object : XC_MethodHook() { 73 | override fun afterHookedMethod(param: MethodHookParam) { 74 | /* 75 | byte[] doFinal() 76 | byte[] doFinal(byte[] input) 77 | byte[] doFinal(byte[] input, int inputOffset, int inputLen) 78 | */ 79 | val paramLen = param.args.size 80 | if (paramLen == 0 || paramLen == 1 || paramLen == 3) { 81 | val cipher = param.thisObject as Cipher 82 | val algorithmType = cipher.algorithm 83 | map["algorithmType"] = algorithmType 84 | if (paramLen == 1) { 85 | val rawData = String(param.args[0] as ByteArray) 86 | map["rawData"] = rawData 87 | } else if (paramLen == 3) { 88 | val input = param.args[0] as ByteArray 89 | val inputOffset = param.args[1] as Int 90 | val inputLen = param.args[2] as Int 91 | val rawData = ByteArray(inputLen) 92 | System.arraycopy(input, inputOffset, rawData, 0, inputLen) 93 | map["rawData"] = String(rawData) 94 | } 95 | param.result?.let { 96 | if (map["key"] == null && map["cryptType"] == null) return 97 | val result = String(it as ByteArray) 98 | map["result"] = result 99 | val list = listOf( 100 | Tip.getTip("encryptOrDecrypt") + map["cryptType"], 101 | Tip.getTip("key") + map["key"], 102 | "iv:${map["iv"]}", 103 | Tip.getTip("rawData") + map["rawData"], 104 | Tip.getTip( 105 | map["cryptType"] ?: "error" 106 | ) + Tip.getTip("result") + map["result"] 107 | ) 108 | val items = LogUtil.getStackTrace() 109 | val logBean = LogBean( 110 | map["algorithmType"] ?: "null", list + items, HookHelper.hostPackageName 111 | ) 112 | LogUtil.outLogMsg(logBean) 113 | map.clear() 114 | } 115 | } 116 | } 117 | }) 118 | } 119 | } -------------------------------------------------------------------------------- /hook/extension/ApplicationHook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.extension 2 | 3 | import android.app.Application 4 | import com.github.kyuubiran.ezxhelper.utils.findMethod 5 | import com.github.kyuubiran.ezxhelper.utils.hookAfter 6 | import kotlinx.serialization.decodeFromString 7 | import kotlinx.serialization.json.Json 8 | import me.simpleHook.bean.Exit 9 | import me.simpleHook.bean.ExtensionConfig 10 | import me.simpleHook.bean.LogBean 11 | import me.simpleHook.hook.Tip.getTip 12 | import me.simpleHook.hook.util.HookHelper.hostPackageName 13 | import me.simpleHook.hook.util.LogUtil.outLogMsg 14 | 15 | object ApplicationHook : BaseHook() { 16 | 17 | override fun startHook(configBean: ExtensionConfig) { 18 | if (configBean.application || configBean.exit.enable) { 19 | findMethod(Application::class.java) { 20 | name == "onCreate" 21 | }.hookAfter { 22 | if (configBean.application) { 23 | val className = it.thisObject.javaClass.name 24 | val type = "Application" 25 | outLogMsg(LogBean(type, 26 | listOf(getTip("applicationName") + className), 27 | hostPackageName)) 28 | } 29 | if (configBean.exit.enable) { 30 | val exit = Json.decodeFromString(configBean.exit.info) 31 | if (exit.recordCrash) recordCrash() 32 | } 33 | } 34 | } 35 | 36 | } 37 | 38 | private fun recordCrash() { 39 | Thread.setDefaultUncaughtExceptionHandler { t, e -> 40 | t ?: return@setDefaultUncaughtExceptionHandler 41 | val type = if (isShowEnglish) "CrashCaught" else "错误捕获" 42 | val isMainThread = t.name == "main" 43 | val list = listOf("Thread name(线程名):${t.name}", 44 | "Main thread(主线程): $isMainThread", 45 | e.stackTraceToString()) 46 | outLogMsg(LogBean(type, list, hostPackageName)) 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /hook/extension/Base64Hook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.extension 2 | 3 | import android.util.Base64 4 | import de.robv.android.xposed.XC_MethodHook 5 | import de.robv.android.xposed.XposedHelpers 6 | import me.simpleHook.bean.ExtensionConfig 7 | import me.simpleHook.bean.LogBean 8 | import me.simpleHook.hook.Tip 9 | import me.simpleHook.hook.util.HookHelper 10 | import me.simpleHook.hook.util.LogUtil 11 | import java.nio.charset.Charset 12 | 13 | object Base64Hook : BaseHook() { 14 | 15 | override fun startHook(configBean: ExtensionConfig) { 16 | if (!configBean.base64) return 17 | XposedHelpers.findAndHookMethod("java.util.Base64.Encoder", 18 | HookHelper.appClassLoader, 19 | "encode", 20 | ByteArray::class.java, 21 | object : XC_MethodHook() { 22 | override fun afterHookedMethod(param: MethodHookParam) { 23 | val data = param.args[0] as ByteArray 24 | val items = LogUtil.getStackTrace() 25 | val result = String(param.result as ByteArray) 26 | val logBean = LogBean( 27 | "base64", listOf( 28 | Tip.getTip("isEncrypt"), 29 | Tip.getTip("rawData") + String(data), 30 | Tip.getTip("encryptResult") + result 31 | ) + items, HookHelper.hostPackageName 32 | ) 33 | LogUtil.outLogMsg(logBean) 34 | } 35 | }) 36 | 37 | XposedHelpers.findAndHookMethod("java.util.Base64.Decoder", 38 | HookHelper.appClassLoader, 39 | "decode", 40 | ByteArray::class.java, 41 | object : XC_MethodHook() { 42 | override fun afterHookedMethod(param: MethodHookParam) { 43 | val data = param.args[0] as ByteArray 44 | val items = LogUtil.getStackTrace().toList() 45 | val result = String(param.result as ByteArray) 46 | val logBean = LogBean( 47 | "base64", listOf( 48 | Tip.getTip("isDecrypt"), 49 | Tip.getTip("rawData") + String(data), 50 | Tip.getTip("decryptResult") + result 51 | ) + items, HookHelper.hostPackageName 52 | ) 53 | LogUtil.outLogMsg(logBean) 54 | } 55 | }) 56 | 57 | XposedHelpers.findAndHookMethod(Base64::class.java, 58 | "encode", 59 | ByteArray::class.java, 60 | Int::class.java, 61 | Int::class.java, 62 | Int::class.java, 63 | object : XC_MethodHook() { 64 | override fun afterHookedMethod(param: MethodHookParam) { 65 | /* 66 | byte[] encode(byte[] input, int flags) 67 | byte[] encode(byte[] input, int offset, int len, int flags) 68 | */ 69 | val input = param.args[0] as ByteArray 70 | val offset = param.args[1] as Int 71 | val len = param.args[2] as Int 72 | val rawData = ByteArray(len) 73 | System.arraycopy(input, offset, rawData, 0, len) 74 | val items = LogUtil.getStackTrace() 75 | val result = String(param.result as ByteArray, Charset.forName("US-ASCII")) 76 | val logBean = LogBean( 77 | "base64", listOf( 78 | Tip.getTip("isEncrypt"), 79 | Tip.getTip("rawData") + String(rawData), 80 | Tip.getTip("encryptResult") + result 81 | ) + items, HookHelper.hostPackageName 82 | ) 83 | LogUtil.outLogMsg(logBean) 84 | } 85 | }) 86 | 87 | XposedHelpers.findAndHookMethod(Base64::class.java, 88 | "decode", 89 | ByteArray::class.java, 90 | Int::class.java, 91 | Int::class.java, 92 | Int::class.java, 93 | object : XC_MethodHook() { 94 | override fun afterHookedMethod(param: MethodHookParam) { 95 | val input = param.args[0] as ByteArray 96 | val offset = param.args[1] as Int 97 | val len = param.args[2] as Int 98 | val rawData = ByteArray(len) 99 | System.arraycopy(input, offset, rawData, 0, len) 100 | val items = LogUtil.getStackTrace() 101 | val result = String(param.result as ByteArray, Charset.forName("US-ASCII")) 102 | val logBean = LogBean( 103 | "base64", listOf( 104 | Tip.getTip("isDecrypt"), 105 | Tip.getTip("rawData") + String(rawData), 106 | Tip.getTip("decryptResult") + result 107 | ) + items, HookHelper.hostPackageName 108 | ) 109 | LogUtil.outLogMsg(logBean) 110 | } 111 | }) 112 | } 113 | } -------------------------------------------------------------------------------- /hook/extension/BaseHook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.extension 2 | 3 | import me.simpleHook.bean.ExtensionConfig 4 | import me.simpleHook.util.LanguageUtils 5 | 6 | 7 | abstract class BaseHook { 8 | 9 | protected val isShowEnglish = LanguageUtils.isNotChinese() 10 | var isInit = false 11 | abstract fun startHook(configBean: ExtensionConfig) 12 | } -------------------------------------------------------------------------------- /hook/extension/ClickEventHook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.extension 2 | 3 | import android.view.View 4 | import android.view.ViewGroup 5 | import android.widget.TextView 6 | import com.github.kyuubiran.ezxhelper.utils.findMethod 7 | import com.github.kyuubiran.ezxhelper.utils.hookAfter 8 | import de.robv.android.xposed.XposedHelpers 9 | import me.simpleHook.bean.ExtensionConfig 10 | import me.simpleHook.bean.LogBean 11 | import me.simpleHook.hook.Tip 12 | import me.simpleHook.hook.util.HookHelper 13 | import me.simpleHook.hook.util.HookUtils.getAllTextView 14 | import me.simpleHook.hook.util.LogUtil 15 | import me.simpleHook.extension.log 16 | 17 | object ClickEventHook : BaseHook() { 18 | override fun startHook(configBean: ExtensionConfig) { 19 | if (!configBean.click) return 20 | findMethod(View::class.java) { 21 | name == "performClick" 22 | }.hookAfter { 23 | try { 24 | val list = mutableListOf() 25 | val type = if (isShowEnglish) "Click Event" else "点击事件" 26 | val view = it.thisObject as View 27 | val viewType = view.javaClass.name ?: "未获取到" 28 | val listenerInfoObject = XposedHelpers.getObjectField(view, "mListenerInfo") 29 | val mOnClickListenerObject = 30 | XposedHelpers.getObjectField(listenerInfoObject, "mOnClickListener") 31 | val callbackType = mOnClickListenerObject.javaClass.name 32 | val viewId = 33 | if (view.id == View.NO_ID) "id:NO ID" else "id: " + Integer.toHexString(view.id) 34 | list.add(Tip.getTip("viewType") + viewType) 35 | list.add(Tip.getTip("callbackType") + callbackType) 36 | list.add(viewId) 37 | if (view is TextView) { 38 | list.add(Tip.getTip("text") + view.text.toString()) 39 | } else if (view is ViewGroup) { 40 | list += getAllTextView(view) 41 | } 42 | LogUtil.outLogMsg(LogBean(type, 43 | list + LogUtil.getStackTrace(), 44 | HookHelper.hostPackageName)) 45 | } catch (e: Exception) { 46 | "error: click".log(HookHelper.hostPackageName) 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /hook/extension/ClipboardHook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.extension 2 | 3 | import android.content.ClipData 4 | import android.content.ClipboardManager 5 | import com.github.kyuubiran.ezxhelper.utils.findMethod 6 | import com.github.kyuubiran.ezxhelper.utils.hookAfter 7 | import com.github.kyuubiran.ezxhelper.utils.hookBefore 8 | import kotlinx.serialization.decodeFromString 9 | import kotlinx.serialization.json.Json 10 | import me.simpleHook.bean.ClipboardConfig 11 | import me.simpleHook.bean.ExtensionConfig 12 | import me.simpleHook.bean.LogBean 13 | import me.simpleHook.hook.Tip 14 | import me.simpleHook.hook.util.HookHelper 15 | import me.simpleHook.hook.util.LogUtil 16 | 17 | object ClipboardHook : BaseHook() { 18 | override fun startHook(configBean: ExtensionConfig) { 19 | if (!configBean.filterClipboard.enable) return 20 | val configInfo = configBean.filterClipboard.info 21 | // old config 22 | if (!configInfo.startsWith("{") || !configInfo.endsWith("}")) return 23 | val clipboardConfig = Json.decodeFromString(configInfo) 24 | hookSetClipboard(clipboardConfig) 25 | hookGetClipboard(clipboardConfig) 26 | } 27 | 28 | private fun hookGetClipboard(clipboardConfig: ClipboardConfig) { 29 | if (clipboardConfig.read || clipboardConfig.record) { 30 | // hook @getItemAt maybe get better effect 31 | findMethod(ClipboardManager::class.java) { 32 | name == "getPrimaryClip" 33 | }.hookAfter { 34 | if (clipboardConfig.record) { 35 | val clipData = it.result as ClipData? 36 | val info = getClipInfo(clipData) 37 | val type = Tip.getTip("getClipboard") 38 | val items = listOf(Tip.getTip("clipboardInfo") + info) + LogUtil.getStackTrace() 39 | val logBean = LogBean(type, items, HookHelper.hostPackageName) 40 | LogUtil.outLogMsg(logBean) 41 | } 42 | if (clipboardConfig.read) { 43 | it.result = null 44 | } 45 | } 46 | } 47 | } 48 | 49 | 50 | private fun hookSetClipboard(clipboardConfig: ClipboardConfig) { 51 | if (clipboardConfig.write || clipboardConfig.record) { 52 | findMethod(ClipboardManager::class.java) { 53 | name == "setPrimaryClip" 54 | }.hookBefore { 55 | val clipData = it.args[0] as ClipData 56 | val info = getClipInfo(clipData) 57 | if (clipboardConfig.record) { 58 | val type = Tip.getTip("setClipboard") 59 | val items = listOf(Tip.getTip("clipboardInfo") + info) + LogUtil.getStackTrace() 60 | val logBean = LogBean(type, items, HookHelper.hostPackageName) 61 | LogUtil.outLogMsg(logBean) 62 | } 63 | if (clipboardConfig.write) { 64 | val keywords = Json.decodeFromString>(clipboardConfig.filter) 65 | keywords.forEach { keyword -> 66 | if (keyword == "" || info == "") { 67 | it.result = null 68 | return@forEach 69 | } 70 | runCatching { 71 | if (info.contains(Regex(keyword))) { 72 | it.result = null 73 | return@forEach 74 | } 75 | }.onFailure { _ -> 76 | // illegal format, as normal text 77 | if (info.contains(keyword)) { 78 | it.result = null 79 | return@forEach 80 | } 81 | } 82 | } 83 | } 84 | } 85 | } 86 | } 87 | 88 | 89 | private fun getClipInfo(clipData: ClipData?): String { 90 | val stringBuilder = StringBuilder() 91 | if (clipData != null) { 92 | for (i in 0 until clipData.itemCount) { 93 | // exclude spaces between items 94 | val clipDataItem = clipData.getItemAt(i); 95 | if (clipDataItem.text == null) { 96 | clipDataItem.intent?.let { intent -> 97 | stringBuilder.append(intent.toString()) 98 | } ?: stringBuilder.append(clipDataItem.uri) 99 | } else { 100 | stringBuilder.append(clipData.getItemAt(i).text.toString()) 101 | } 102 | } 103 | } 104 | return stringBuilder.toString() 105 | } 106 | } -------------------------------------------------------------------------------- /hook/extension/ContactHook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.extension 2 | 3 | import android.content.ContentResolver 4 | import android.net.Uri 5 | import android.provider.ContactsContract 6 | import com.github.kyuubiran.ezxhelper.utils.findAllMethods 7 | import com.github.kyuubiran.ezxhelper.utils.hookBefore 8 | import me.simpleHook.bean.ExtensionConfig 9 | 10 | object ContactHook : BaseHook() { 11 | override fun startHook(configBean: ExtensionConfig) { 12 | if (!configBean.contact) return 13 | findAllMethods(ContentResolver::class.java) { 14 | name == "query" 15 | }.hookBefore { 16 | val uri = it.args[0] as Uri 17 | if (uri == ContactsContract.CommonDataKinds.Phone.CONTENT_URI) { 18 | it.result = null 19 | } 20 | } 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /hook/extension/DialogHook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.extension 2 | 3 | import android.app.Dialog 4 | import android.util.Log 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.TextView 8 | import com.github.kyuubiran.ezxhelper.utils.findAllMethods 9 | import com.github.kyuubiran.ezxhelper.utils.findMethod 10 | import com.github.kyuubiran.ezxhelper.utils.hookAfter 11 | import com.github.kyuubiran.ezxhelper.utils.hookReturnConstant 12 | import kotlinx.serialization.decodeFromString 13 | import kotlinx.serialization.json.Json 14 | import me.simpleHook.bean.DialogCancel 15 | import me.simpleHook.bean.ExtensionConfig 16 | import me.simpleHook.bean.LogBean 17 | import me.simpleHook.hook.Tip 18 | import me.simpleHook.hook.util.HookHelper 19 | import me.simpleHook.hook.util.HookUtils.getAllTextView 20 | import me.simpleHook.hook.util.HookUtils.getAllViewIds 21 | import me.simpleHook.hook.util.LogUtil 22 | 23 | object DialogHook : BaseHook() { 24 | 25 | override fun startHook(configBean: ExtensionConfig) { 26 | 27 | if (configBean.stopDialog.enable) { 28 | findAllMethods(Dialog::class.java) { 29 | name == "setOnCancelListener" || name == "setOnDismissListener" || name == "setOnShowListener" 30 | }.hookReturnConstant(null) 31 | } 32 | if (configBean.dialog || configBean.diaCancel || configBean.stopDialog.enable) { 33 | findMethod(Dialog::class.java) { 34 | name == "show" 35 | }.hookAfter { param -> 36 | val dialog = param.thisObject as Dialog 37 | val list = mutableListOf() 38 | val dialogView: View? = dialog.window?.decorView 39 | dialogView?.also { 40 | if (it is ViewGroup) { 41 | list += getAllTextView(it) 42 | } else if (it is TextView) { 43 | list.add(Tip.getTip("text") + it.text.toString()) 44 | } 45 | } 46 | if (configBean.diaCancel) { 47 | dialog.setCancelable(true) 48 | } 49 | if (configBean.stopDialog.enable) { 50 | val info = configBean.stopDialog.info 51 | // new config, not perform old config 52 | if (info[0] == '{' && info[info.length - 1] == '}') { 53 | val dialogCancel = Json.decodeFromString(info) 54 | if (dialogCancel.keywordEnable) { 55 | val showText = list.toString() 56 | val keyWords = 57 | Json.decodeFromString>(dialogCancel.keywords) 58 | keyWords.forEach { 59 | if (it.isNotEmpty() && showText.contains(it)) { 60 | dialog.dismiss() 61 | val type = 62 | if (isShowEnglish) "Dialog(blocked by keyword)" else "弹窗(通过关键词已拦截)" 63 | LogUtil.outLogMsg(LogBean(type, 64 | list + LogUtil.getStackTrace(), 65 | HookHelper.hostPackageName)) 66 | return@hookAfter 67 | } 68 | } 69 | } 70 | if (dialogCancel.idEnable) { 71 | dialogView ?: return@hookAfter 72 | val currentIds = getAllViewIds(dialogView) 73 | val ids = Json.decodeFromString>(dialogCancel.ids) 74 | currentIds.forEach { 75 | if (it in ids) { 76 | dialog.dismiss() 77 | val type = 78 | if (isShowEnglish) "Dialog(blocked by ID)" else "弹窗(通过ID已拦截)" 79 | LogUtil.outLogMsg(LogBean(type, 80 | list + LogUtil.getStackTrace(), 81 | HookHelper.hostPackageName)) 82 | return@hookAfter 83 | } 84 | } 85 | } 86 | } 87 | if (configBean.dialog) { 88 | val type = if (isShowEnglish) "Dialog" else "弹窗" 89 | LogUtil.outLogMsg(LogBean(type, 90 | list + LogUtil.getStackTrace(), 91 | HookHelper.hostPackageName)) 92 | } 93 | } 94 | } 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /hook/extension/ExitHook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.extension 2 | 3 | import android.app.Activity 4 | import com.github.kyuubiran.ezxhelper.utils.findMethod 5 | import com.github.kyuubiran.ezxhelper.utils.hookReplace 6 | import kotlinx.serialization.decodeFromString 7 | import kotlinx.serialization.json.Json 8 | import me.simpleHook.bean.Exit 9 | import me.simpleHook.bean.ExtensionConfig 10 | import me.simpleHook.bean.LogBean 11 | import me.simpleHook.hook.Tip 12 | import me.simpleHook.hook.util.HookHelper 13 | import me.simpleHook.hook.util.LogUtil 14 | 15 | object ExitHook : BaseHook() { 16 | override fun startHook(configBean: ExtensionConfig) { 17 | if (configBean.exit.enable) { 18 | val exit = Json.decodeFromString(configBean.exit.info) 19 | if (exit.exit) { 20 | findMethod(Runtime::class.java) { 21 | name == "exit" 22 | }.hookReplace { 23 | outLog(Tip.getTip("exit")) 24 | } 25 | } 26 | if (exit.kill) { 27 | findMethod(android.os.Process::class.java) { 28 | name == "killProcess" 29 | }.hookReplace { 30 | outLog(Tip.getTip("killProcess")) 31 | } 32 | } 33 | if (exit.finish) { 34 | findMethod(Activity::class.java) { 35 | name == "finish" 36 | }.hookReplace { 37 | outLog(Tip.getTip("finish")) 38 | } 39 | } 40 | } 41 | } 42 | 43 | private fun outLog(tip: String) { 44 | val type = if (isShowEnglish) "Exit" else "退出" 45 | LogUtil.outLogMsg(LogBean(type, 46 | listOf(tip) + LogUtil.getStackTrace(), 47 | HookHelper.hostPackageName)) 48 | } 49 | } -------------------------------------------------------------------------------- /hook/extension/FileHook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.extension 2 | 3 | import android.content.res.AssetManager 4 | import com.github.kyuubiran.ezxhelper.utils.findMethod 5 | import com.github.kyuubiran.ezxhelper.utils.getObjectOrNullAs 6 | import com.github.kyuubiran.ezxhelper.utils.hookAfter 7 | import com.github.kyuubiran.ezxhelper.utils.paramCount 8 | import kotlinx.serialization.decodeFromString 9 | import kotlinx.serialization.json.Json 10 | import me.simpleHook.bean.ExtensionConfig 11 | import me.simpleHook.bean.FileMonitorConfig 12 | import me.simpleHook.bean.LogBean 13 | import me.simpleHook.hook.Tip 14 | import me.simpleHook.hook.util.HookHelper 15 | import me.simpleHook.hook.util.LogUtil 16 | import java.io.File 17 | import java.io.FileInputStream 18 | import java.io.FileOutputStream 19 | import java.nio.charset.Charset 20 | 21 | object FileHook : BaseHook() { 22 | override fun startHook(configBean: ExtensionConfig) { 23 | if (configBean.fileMonitor.enable && configBean.fileMonitor.info.contains("true")) { 24 | val fileMonitorConfig = 25 | Json.decodeFromString(configBean.fileMonitor.info) 26 | if (fileMonitorConfig.createFile) { 27 | findMethod(File::class.java) { 28 | name == "createNewFile" 29 | }.hookAfter { 30 | val file = it.thisObject as File 31 | if (file.path.contains("simpleHook")) return@hookAfter 32 | val type = Tip.getTip("createFile") 33 | val items = listOf(Tip.getTip("path") + file.path) + LogUtil.getStackTrace() 34 | val logBean = LogBean(type, items, HookHelper.hostPackageName) 35 | LogUtil.outLogMsg(logBean) 36 | } 37 | } 38 | 39 | if (fileMonitorConfig.deleteFile) { 40 | findMethod(File::class.java) { 41 | name == "delete" 42 | }.hookAfter { 43 | val file = it.thisObject as File 44 | val type = Tip.getTip("deleteFile") 45 | val items = listOf(Tip.getTip("path") + file.path) + LogUtil.getStackTrace() 46 | val logBean = LogBean(type, items, HookHelper.hostPackageName) 47 | LogUtil.outLogMsg(logBean) 48 | } 49 | } 50 | if (fileMonitorConfig.inputFile) { 51 | findMethod(FileInputStream::class.java) { 52 | name == "read" && paramCount == 3 53 | }.hookAfter { 54 | val inputStream = it.thisObject as FileInputStream 55 | val path = inputStream.getObjectOrNullAs("path", String::class.java) 56 | ?: "FileDescriptor" 57 | if (path.contains("simpleHook")) return@hookAfter 58 | val length = it.args[2] as Int 59 | val offset = it.args[1] as Int 60 | val data = it.args[0] as ByteArray 61 | val info = copyPartData(fileMonitorConfig.cacheSize, length, offset, data) 62 | val type = Tip.getTip("readFile") 63 | val items = listOf(Tip.getTip("path") + path, 64 | Tip.getTip("info") + info) + LogUtil.getStackTrace() 65 | val logBean = LogBean(type, items, HookHelper.hostPackageName) 66 | LogUtil.outLogMsg(logBean) 67 | } 68 | } 69 | if (fileMonitorConfig.outputFile) { 70 | findMethod(FileOutputStream::class.java) { 71 | name == "write" && paramCount == 3 72 | }.hookAfter { 73 | val outputStream = it.thisObject as FileOutputStream 74 | val path = outputStream.getObjectOrNullAs("path", String::class.java) 75 | ?: "FileDescriptor" 76 | if (path.contains("simpleHook")) return@hookAfter 77 | val data = it.args[0] as ByteArray 78 | val offset = it.args[1] as Int 79 | val length = it.args[2] as Int 80 | val info = copyPartData(fileMonitorConfig.cacheSize, length, offset, data) 81 | val type = Tip.getTip("writeFile") 82 | val items = listOf(Tip.getTip("path") + path, 83 | Tip.getTip("info") + info) + LogUtil.getStackTrace() 84 | val logBean = LogBean(type, items, HookHelper.hostPackageName) 85 | LogUtil.outLogMsg(logBean) 86 | } 87 | } 88 | if (fileMonitorConfig.assetsFile) { 89 | findMethod(AssetManager::class.java) { 90 | name == "open" && paramCount == 2 91 | }.hookAfter { 92 | val filePath = it.args[0] as String 93 | val type = Tip.getTip("readAssets") 94 | val items = listOf(Tip.getTip("path") + filePath) + LogUtil.getStackTrace() 95 | val logBean = LogBean(type, items, HookHelper.hostPackageName) 96 | LogUtil.outLogMsg(logBean) 97 | } 98 | } 99 | } 100 | } 101 | 102 | private fun copyPartData( 103 | cacheSize: Int, length: Int, offset: Int, data: ByteArray 104 | ): String { 105 | return if (cacheSize == 0) { 106 | Tip.getTip("notSetCacheSize") 107 | } else if (length - offset <= cacheSize) { 108 | data.copyOfRange(offset, length).toString(Charset.defaultCharset()) 109 | } else { 110 | data.copyOfRange(offset, cacheSize).toString(Charset.defaultCharset()) 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /hook/extension/HMACHook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.extension 2 | 3 | import de.robv.android.xposed.XC_MethodHook 4 | import de.robv.android.xposed.XposedBridge 5 | import me.simpleHook.bean.ExtensionConfig 6 | import me.simpleHook.bean.LogBean 7 | import me.simpleHook.hook.Tip 8 | import me.simpleHook.hook.util.HookHelper 9 | import me.simpleHook.hook.util.LogUtil 10 | import javax.crypto.Mac 11 | import javax.crypto.spec.SecretKeySpec 12 | 13 | object HMACHook : BaseHook() { 14 | override fun startHook(configBean: ExtensionConfig) { 15 | if (!configBean.hmac) return 16 | val hasMap = HashMap() 17 | XposedBridge.hookAllMethods(Mac::class.java, "init", object : XC_MethodHook() { 18 | override fun afterHookedMethod(param: MethodHookParam) { 19 | val secretKeySpec = param.args[0] as SecretKeySpec 20 | val key = String(secretKeySpec.encoded) 21 | val keyAlgorithm = secretKeySpec.algorithm 22 | hasMap["key"] = key 23 | hasMap["keyAlgorithm"] = keyAlgorithm 24 | } 25 | }) 26 | XposedBridge.hookAllMethods(Mac::class.java, "update", object : XC_MethodHook() { 27 | override fun afterHookedMethod(param: MethodHookParam) { 28 | /* 29 | void update(byte input) 30 | void update(byte[] input) 31 | void update(byte[] input, int offset, int len) 32 | */ 33 | val paramLen = param.args.size 34 | if (paramLen == 1) { 35 | when (val param0 = param.args[0]) { 36 | is Byte -> { 37 | val rawData = param0.toString() 38 | hasMap["rawData"] = rawData 39 | } 40 | is ByteArray -> { 41 | val rawData = String(param0) 42 | hasMap["rawData"] = rawData 43 | } 44 | } 45 | } else if (paramLen == 3) { 46 | val input = param.args[0] as ByteArray 47 | val offset = param.args[1] as Int 48 | val len = param.args[2] as Int 49 | val rawData = ByteArray(len) 50 | System.arraycopy(input, offset, rawData, 0, len) 51 | hasMap["rawData"] = String(rawData) 52 | } 53 | } 54 | }) 55 | XposedBridge.hookAllMethods(Mac::class.java, "doFinal", object : XC_MethodHook() { 56 | override fun afterHookedMethod(param: MethodHookParam) { 57 | /* 58 | byte[] doFinal() 59 | byte[] doFinal(byte[] input) 60 | */ 61 | val paramLen = param.args.size 62 | if (paramLen == 2) return 63 | if (paramLen == 1) { 64 | val rawData = param.args[0] as ByteArray 65 | hasMap["rawData"] = String(rawData) 66 | } 67 | val mac = param.thisObject as Mac 68 | val algorithmType = mac.algorithm 69 | hasMap["algorithmType"] = algorithmType 70 | val result = param.result as ByteArray 71 | hasMap["result"] = String(result) 72 | 73 | val list = listOf( 74 | Tip.getTip("key") + hasMap["key"], 75 | Tip.getTip("keyAlgorithm") + hasMap["keyAlgorithm"], 76 | Tip.getTip("rawData") + hasMap["rawData"], 77 | Tip.getTip("encryptResult") + hasMap["result"] 78 | ) 79 | val items = LogUtil.getStackTrace().toList() 80 | val logBean = LogBean( 81 | hasMap["algorithmType"] ?: "null", list + items, HookHelper.hostPackageName 82 | ) 83 | LogUtil.outLogMsg(logBean) 84 | hasMap.clear() 85 | } 86 | }) 87 | } 88 | } -------------------------------------------------------------------------------- /hook/extension/HotFixHook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.extension 2 | 3 | 4 | import dalvik.system.BaseDexClassLoader 5 | import dalvik.system.DexClassLoader 6 | import me.simpleHook.bean.ExtensionConfig 7 | import me.simpleHook.constant.Constant 8 | import me.simpleHook.hook.util.HookHelper 9 | import me.simpleHook.util.FlavorUtils 10 | import me.simpleHook.extension.log 11 | import me.simpleHook.extension.tip 12 | import java.io.File 13 | 14 | object HotFixHook : BaseHook() { 15 | 16 | override fun startHook(configBean: ExtensionConfig) { 17 | if (!configBean.hotFix) return 18 | val dexFilePaths: MutableList = mutableListOf() 19 | val pathName = if (FlavorUtils.normalVersion) { 20 | Constant.ANDROID_DATA_PATH + HookHelper.hostPackageName + "/simpleHook/dex/" 21 | } else { 22 | Constant.ROOT_CONFIG_MAIN_DIRECTORY + HookHelper.hostPackageName + "/dex/" 23 | } 24 | val fileTree: FileTreeWalk = File(pathName).walk() 25 | fileTree.maxDepth(1).filter { it.isFile && it.extension == "dex" }.forEach { 26 | dexFilePaths.add(it.absolutePath) 27 | } 28 | try { 29 | for (index in 0 until dexFilePaths.size) { 30 | dexFilePaths[index].log(HookHelper.hostPackageName) 31 | val originalLoader = HookHelper.appClassLoader 32 | val classLoader = DexClassLoader( 33 | dexFilePaths[index], HookHelper.appContext.cacheDir.path, null, null 34 | ) 35 | val loaderClass: Class<*> = BaseDexClassLoader::class.java 36 | val pathListField = loaderClass.getDeclaredField("pathList") 37 | pathListField.isAccessible = true 38 | val pathListObject = pathListField[classLoader] 39 | val pathListClass: Class<*> = pathListObject.javaClass 40 | val dexElementsField = pathListClass.getDeclaredField("dexElements") 41 | dexElementsField.isAccessible = true 42 | val dexElementsObject = dexElementsField[pathListObject] 43 | val originalPathListObject = pathListField[originalLoader] 44 | val originalDexElementsObject = dexElementsField[originalPathListObject] 45 | 46 | val oldLength = java.lang.reflect.Array.getLength(originalDexElementsObject!!) 47 | val newLength = java.lang.reflect.Array.getLength(dexElementsObject!!) 48 | val concatDexElementsObject = 49 | java.lang.reflect.Array.newInstance(dexElementsObject.javaClass.componentType!!, 50 | oldLength + newLength) 51 | for (i in 0 until newLength) { 52 | java.lang.reflect.Array.set(concatDexElementsObject, 53 | i, 54 | java.lang.reflect.Array.get(dexElementsObject, i)) 55 | } 56 | for (i in 0 until oldLength) { 57 | java.lang.reflect.Array.set(concatDexElementsObject, 58 | newLength + i, 59 | java.lang.reflect.Array.get(originalDexElementsObject, i)) 60 | } 61 | dexElementsField[originalPathListObject] = concatDexElementsObject 62 | } 63 | 64 | } catch (e: Throwable) { 65 | "hot fix error".tip(HookHelper.hostPackageName) 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /hook/extension/IntentHook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.extension 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import de.robv.android.xposed.XC_MethodHook 6 | import de.robv.android.xposed.XposedHelpers 7 | import kotlinx.serialization.encodeToString 8 | import kotlinx.serialization.json.Json 9 | import me.simpleHook.bean.ExtensionConfig 10 | import me.simpleHook.bean.ExtraBean 11 | import me.simpleHook.bean.IntentBean 12 | import me.simpleHook.bean.LogBean 13 | import me.simpleHook.hook.util.HookHelper 14 | import me.simpleHook.hook.util.LogUtil 15 | 16 | private const val ACTIVITY = "android.app.Activity" 17 | private const val CONTEXT_WRAPPER = "android.content.ContextWrapper" 18 | private const val START_ACTIVITY = "startActivity" 19 | private const val START_ACTIVITY_FOR_RESULT = "startActivityForResult" 20 | 21 | object IntentHook : BaseHook() { 22 | override fun startHook(configBean: ExtensionConfig) { 23 | if (!configBean.intent) return 24 | XposedHelpers.findAndHookMethod(ACTIVITY, 25 | HookHelper.appClassLoader, 26 | START_ACTIVITY, 27 | Intent::class.java, 28 | object : XC_MethodHook() { 29 | override fun beforeHookedMethod(param: MethodHookParam) { 30 | val intent = param.args[0] as Intent 31 | saveLog(intent, HookHelper.hostPackageName) 32 | } 33 | }) 34 | 35 | XposedHelpers.findAndHookMethod(CONTEXT_WRAPPER, 36 | HookHelper.appClassLoader, 37 | START_ACTIVITY, 38 | Intent::class.java, 39 | object : XC_MethodHook() { 40 | override fun beforeHookedMethod(param: MethodHookParam) { 41 | val intent = param.args[0] as Intent 42 | saveLog(intent, HookHelper.hostPackageName) 43 | } 44 | }) 45 | 46 | XposedHelpers.findAndHookMethod(CONTEXT_WRAPPER, 47 | HookHelper.appClassLoader, 48 | START_ACTIVITY, 49 | Intent::class.java, 50 | Bundle::class.java, 51 | object : XC_MethodHook() { 52 | override fun beforeHookedMethod(param: MethodHookParam) { 53 | val intent = param.args[0] as Intent 54 | saveLog(intent, HookHelper.hostPackageName) 55 | } 56 | }) 57 | 58 | XposedHelpers.findAndHookMethod(ACTIVITY, 59 | HookHelper.appClassLoader, 60 | START_ACTIVITY_FOR_RESULT, 61 | Intent::class.java, 62 | Int::class.java, 63 | object : XC_MethodHook() { 64 | override fun beforeHookedMethod(param: MethodHookParam) { 65 | val intent = param.args[0] as Intent 66 | saveLog(intent, HookHelper.hostPackageName) 67 | } 68 | }) 69 | XposedHelpers.findAndHookMethod(ACTIVITY, 70 | HookHelper.appClassLoader, 71 | START_ACTIVITY_FOR_RESULT, 72 | Intent::class.java, 73 | Int::class.java, 74 | Bundle::class.java, 75 | object : XC_MethodHook() { 76 | 77 | override fun beforeHookedMethod(param: MethodHookParam) { 78 | val intent = param.args[0] as Intent 79 | saveLog(intent, HookHelper.hostPackageName) 80 | } 81 | }) 82 | } 83 | 84 | @Suppress("DEPRECATION") 85 | fun saveLog(intent: Intent, packName: String) { 86 | val className = intent.component?.className ?: "" 87 | val packageName = intent.component?.packageName ?: "" 88 | val action = intent.action ?: "" 89 | val data = intent.dataString ?: "" 90 | val extraList = ArrayList() 91 | val extras = intent.extras 92 | extras?.keySet()?.forEach { 93 | val type = when (extras.get(it)) { 94 | is Boolean -> "boolean" 95 | is String -> "string" 96 | is Int -> "int" 97 | is Long -> "long" 98 | is Float -> "float" 99 | is Bundle -> "bundle" 100 | else -> "暂未统计" // maybe error 101 | } 102 | extraList.add(ExtraBean(type, it, extras.get(it).toString())) 103 | } 104 | val configBean = IntentBean(packageName, className, action, data, extraList) 105 | val logBean = LogBean("intent", listOf(Json.encodeToString(configBean)), packName) 106 | LogUtil.outLogMsg(logBean) 107 | } 108 | } -------------------------------------------------------------------------------- /hook/extension/JSONHook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.extension 2 | 3 | import com.google.gson.Gson 4 | import de.robv.android.xposed.XC_MethodHook 5 | import de.robv.android.xposed.XposedBridge 6 | import de.robv.android.xposed.XposedHelpers 7 | import me.simpleHook.bean.ExtensionConfig 8 | import me.simpleHook.bean.LogBean 9 | import me.simpleHook.hook.util.HookHelper 10 | import me.simpleHook.hook.util.HookUtils.getObjectString 11 | import me.simpleHook.hook.util.LogUtil 12 | import org.json.JSONArray 13 | import org.json.JSONObject 14 | 15 | object JSONHook : BaseHook() { 16 | 17 | override fun startHook(configBean: ExtensionConfig) { 18 | if (configBean.jsonObject) { 19 | hookJSONObject() 20 | } 21 | if (configBean.jsonArray) { 22 | hookJSONArray() 23 | } 24 | } 25 | 26 | private fun hookJSONObject() { 27 | XposedBridge.hookAllMethods(JSONObject::class.java, "put", object : XC_MethodHook() { 28 | override fun beforeHookedMethod(param: MethodHookParam) { 29 | val type = if (isShowEnglish) "JSON put" else "JSON 增加" 30 | val name = param.args[0] as String 31 | val value = getObjectString(param.args[1] ?: "null") 32 | val list = arrayListOf("Name: $name", "Value: $value") 33 | val items = LogUtil.getStackTrace() 34 | val logBean = LogBean(type, list + items, HookHelper.hostPackageName) 35 | LogUtil.outLogMsg(logBean) 36 | } 37 | }) 38 | 39 | XposedBridge.hookAllConstructors(JSONObject::class.java, object : XC_MethodHook() { 40 | override fun afterHookedMethod(param: MethodHookParam) { 41 | val type = if (isShowEnglish) "JSON creation" else "JSON 创建" 42 | val jsonObject = param.thisObject 43 | 44 | @Suppress("UNCHECKED_CAST") 45 | val map = XposedHelpers.getObjectField(jsonObject, 46 | "nameValuePairs") as LinkedHashMap 47 | if (map.isEmpty()) return 48 | val value = Gson().toJson(map) 49 | val list = arrayListOf("Value: $value") 50 | val items = LogUtil.getStackTrace() 51 | val logBean = LogBean(type, list + items, HookHelper.hostPackageName) 52 | LogUtil.outLogMsg(logBean) 53 | } 54 | }) 55 | } 56 | 57 | private fun hookJSONArray() { 58 | 59 | XposedBridge.hookAllMethods(JSONArray::class.java, "put", object : XC_MethodHook() { 60 | override fun beforeHookedMethod(param: MethodHookParam) { 61 | val type = if (isShowEnglish) "JSONArray put" else "JSONArray 增加" 62 | val name = param.args[0] as String 63 | val value = getObjectString(param.args[1] ?: "null") 64 | val list = arrayListOf("Name: $name", "Value: $value") 65 | val items = LogUtil.getStackTrace() 66 | val logBean = LogBean(type, list + items, HookHelper.hostPackageName) 67 | LogUtil.outLogMsg(logBean) 68 | } 69 | }) 70 | 71 | XposedBridge.hookAllConstructors(JSONArray::class.java, object : XC_MethodHook() { 72 | override fun afterHookedMethod(param: MethodHookParam) { 73 | val type = if (isShowEnglish) "JSONArray creation" else "JSONArray 创建" 74 | val jsonObject = param.thisObject 75 | 76 | @Suppress("UNCHECKED_CAST") 77 | val map = XposedHelpers.getObjectField(jsonObject, "values") as List 78 | if (map.isEmpty()) return 79 | val value = Gson().toJson(map) 80 | val list = arrayListOf("Value: $value") 81 | val items = LogUtil.getStackTrace() 82 | val logBean = LogBean(type, list + items, HookHelper.hostPackageName) 83 | LogUtil.outLogMsg(logBean) 84 | } 85 | }) 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /hook/extension/PopupWindowHook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.extension 2 | 3 | import android.view.ViewGroup 4 | import android.widget.PopupWindow 5 | import android.widget.TextView 6 | import com.github.kyuubiran.ezxhelper.utils.findMethod 7 | import com.github.kyuubiran.ezxhelper.utils.hookBefore 8 | import com.github.kyuubiran.ezxhelper.utils.paramCount 9 | import de.robv.android.xposed.XC_MethodHook 10 | import kotlinx.serialization.decodeFromString 11 | import kotlinx.serialization.json.Json 12 | import me.simpleHook.bean.DialogCancel 13 | import me.simpleHook.bean.ExtensionConfig 14 | import me.simpleHook.bean.LogBean 15 | import me.simpleHook.hook.Tip 16 | import me.simpleHook.hook.util.HookHelper 17 | import me.simpleHook.hook.util.HookUtils 18 | import me.simpleHook.hook.util.HookUtils.getAllTextView 19 | import me.simpleHook.hook.util.LogUtil 20 | 21 | object PopupWindowHook : BaseHook() { 22 | override fun startHook(configBean: ExtensionConfig) { 23 | if (configBean.popup || configBean.popCancel || configBean.stopDialog.enable) { 24 | findMethod(PopupWindow::class.java) { 25 | name == "showAtLocation" && parameterTypes[0].isInterface 26 | }.hookBefore { 27 | hookPopupWindowDetail(it, configBean) 28 | } 29 | findMethod(PopupWindow::class.java) { 30 | name == "showAsDropDown" && paramCount == 4 31 | }.hookBefore { 32 | hookPopupWindowDetail(it, configBean) 33 | } 34 | } 35 | } 36 | 37 | private fun hookPopupWindowDetail( 38 | param: XC_MethodHook.MethodHookParam?, configBean: ExtensionConfig 39 | ) { 40 | val popupWindow = param?.thisObject as PopupWindow 41 | if (configBean.popCancel) { 42 | popupWindow.isFocusable = true 43 | popupWindow.isOutsideTouchable = true 44 | } 45 | val list = mutableListOf() 46 | val contentView = popupWindow.contentView 47 | if (contentView is ViewGroup) { 48 | list += getAllTextView(contentView) 49 | } else if (contentView is TextView) { 50 | list.add(Tip.getTip("text") + contentView.text.toString()) 51 | } 52 | if (configBean.stopDialog.enable) { 53 | val info = configBean.stopDialog.info 54 | // new config, not perform old config 55 | if (info[0] == '{' && info[info.length - 1] == '}') { 56 | val dialogCancel = Json.decodeFromString(info) 57 | if (dialogCancel.keywordEnable) { 58 | val showText = list.toString() 59 | val keyWords = Json.decodeFromString>(dialogCancel.keywords) 60 | keyWords.forEach { 61 | if (it.isNotEmpty() && showText.contains(it)) { 62 | param.result = null 63 | val type = 64 | if (isShowEnglish) "PopupWindow(blocked by keyword)" else "PopupWindow(通过关键词已拦截)" 65 | LogUtil.outLogMsg(LogBean(type, 66 | list + LogUtil.getStackTrace(), 67 | HookHelper.hostPackageName)) 68 | return 69 | } 70 | } 71 | } 72 | if (dialogCancel.idEnable) { 73 | val currentIds = HookUtils.getAllViewIds(contentView) 74 | val ids = Json.decodeFromString>(dialogCancel.ids) 75 | currentIds.forEach { 76 | if (it in ids) { 77 | param.result = null 78 | val type = 79 | if (isShowEnglish) "PopupWindow(blocked by ID)" else "PopupWindow(通过ID已拦截)" 80 | LogUtil.outLogMsg(LogBean(type, 81 | list + LogUtil.getStackTrace(), 82 | HookHelper.hostPackageName)) 83 | return 84 | } 85 | } 86 | } 87 | } 88 | if (configBean.popup) { 89 | val type = "PopupWindow" 90 | LogUtil.outLogMsg(LogBean(type, 91 | list + LogUtil.getStackTrace(), 92 | HookHelper.hostPackageName)) 93 | } 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /hook/extension/SHAHook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.extension 2 | 3 | import de.robv.android.xposed.XC_MethodHook 4 | import de.robv.android.xposed.XposedBridge 5 | import me.simpleHook.bean.ExtensionConfig 6 | import me.simpleHook.bean.LogBean 7 | import me.simpleHook.hook.Tip 8 | import me.simpleHook.hook.util.HookHelper 9 | import me.simpleHook.hook.util.HookUtils.byte2Sting 10 | import me.simpleHook.hook.util.LogUtil 11 | import java.security.MessageDigest 12 | 13 | object SHAHook : BaseHook() { 14 | 15 | override fun startHook(configBean: ExtensionConfig) { 16 | if (!configBean.digest) return 17 | val hashMap = HashMap() 18 | XposedBridge.hookAllMethods(MessageDigest::class.java, "update", object : XC_MethodHook() { 19 | override fun afterHookedMethod(param: MethodHookParam) { 20 | val paramLen = param.args.size 21 | if (paramLen == 1) { 22 | when (val param0 = param.args[0]) { 23 | is Byte -> { 24 | val rawData = param0.toString() 25 | hashMap["rawData"] = rawData 26 | } 27 | is ByteArray -> { 28 | val rawData = String(param0) 29 | hashMap["rawData"] = rawData 30 | } 31 | } 32 | } else if (paramLen == 3) { 33 | val input = param.args[0] as ByteArray 34 | val offset = param.args[1] as Int 35 | val len = param.args[2] as Int 36 | val rawData = ByteArray(len) 37 | System.arraycopy(input, offset, rawData, 0, len) 38 | hashMap["rawData"] = String(rawData) 39 | } 40 | } 41 | }) 42 | 43 | XposedBridge.hookAllMethods(MessageDigest::class.java, "digest", object : XC_MethodHook() { 44 | override fun afterHookedMethod(param: MethodHookParam) { 45 | if (param.args.size == 3) return 46 | if (param.args.size == 1) { 47 | val data = param.args[0] as ByteArray 48 | hashMap["rawData"] = String(data) 49 | } 50 | val md = param.thisObject as MessageDigest 51 | val type = md.algorithm ?: "unknown" 52 | val result = byte2Sting(param.result as ByteArray) 53 | val items = LogUtil.getStackTrace().toList() 54 | val logBean = LogBean( 55 | type, listOf( 56 | Tip.getTip("isEncrypt"), 57 | Tip.getTip("rawData") + hashMap["rawData"], 58 | Tip.getTip("encryptResult") + result 59 | ) + items, HookHelper.hostPackageName 60 | ) 61 | LogUtil.outLogMsg(logBean) 62 | } 63 | }) 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /hook/extension/SensorMangerHook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.extension 2 | 3 | import android.hardware.Sensor 4 | import android.hardware.SensorManager 5 | import com.github.kyuubiran.ezxhelper.utils.findAllMethods 6 | import com.github.kyuubiran.ezxhelper.utils.hookAfter 7 | import me.simpleHook.bean.ExtensionConfig 8 | 9 | object SensorMangerHook : BaseHook() { 10 | private val sensorTypes = 11 | arrayOf(Sensor.TYPE_ACCELEROMETER, Sensor.TYPE_GYROSCOPE, Sensor.TYPE_LINEAR_ACCELERATION) 12 | private val sportSensorTypes = arrayOf(Sensor.TYPE_ACCELEROMETER, 13 | Sensor.TYPE_GYROSCOPE, 14 | Sensor.TYPE_GRAVITY, 15 | Sensor.TYPE_LINEAR_ACCELERATION, 16 | Sensor.TYPE_ROTATION_VECTOR, 17 | Sensor.TYPE_STEP_COUNTER) 18 | // Sensor.TYPE_ACCELEROMETER_UNCALIBRATED 19 | 20 | override fun startHook(configBean: ExtensionConfig) { 21 | if (configBean.disSensorAG || configBean.disSensorSport) { 22 | findAllMethods(SensorManager::class.java) { 23 | name == "getSensorList" || name == "getDynamicSensorList" 24 | }.hookAfter { 25 | val type = it.args[0] as Int 26 | val disableSensorTypes = 27 | if (configBean.disSensorSport) sportSensorTypes else sensorTypes 28 | if (type in disableSensorTypes) { 29 | it.result = null 30 | } else if (type == Sensor.TYPE_ALL) { 31 | @Suppress("UNCHECKED_CAST") 32 | val unmodifiableList = it.result as List 33 | val sensors = ArrayList() 34 | unmodifiableList.forEach { sensor -> 35 | if (sensor.type !in disableSensorTypes) { 36 | sensors.add(sensor) 37 | } 38 | } 39 | it.result = sensors 40 | } 41 | } 42 | } 43 | /* findMethod(SensorManager::class.java) { 44 | name == "registerListener" 45 | }.hookReturnConstant(false)*/ 46 | } 47 | } -------------------------------------------------------------------------------- /hook/extension/SignatureHook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.extension 2 | 3 | import android.content.pm.PackageInfo 4 | import android.content.pm.PackageManager 5 | import android.content.pm.Signature 6 | import com.github.kyuubiran.ezxhelper.utils.findMethod 7 | import com.github.kyuubiran.ezxhelper.utils.hookAfter 8 | import kotlinx.serialization.decodeFromString 9 | import kotlinx.serialization.json.Json 10 | import me.simpleHook.bean.ExtensionConfig 11 | import me.simpleHook.bean.GuiseSignConfig 12 | import me.simpleHook.bean.LogBean 13 | import me.simpleHook.hook.util.HookHelper 14 | import me.simpleHook.hook.util.LogUtil 15 | import me.simpleHook.util.OSUtils 16 | import me.simpleHook.util.ToolUtils 17 | 18 | object SignatureHook : BaseHook() { 19 | @Suppress("DEPRECATION") 20 | override fun startHook(configBean: ExtensionConfig) { 21 | if (configBean.signature || (configBean.guiseSign.enable && configBean.guiseSign.info.isNotEmpty())) { 22 | findMethod("android.app.ApplicationPackageManager") { 23 | name == "getPackageInfo" && parameterTypes[0] == String::class.java 24 | }.hookAfter { 25 | val flag = it.args[1] as Int 26 | @Suppress("DEPRECATION") if (flag != PackageManager.GET_SIGNING_CERTIFICATES && flag != PackageManager.GET_SIGNATURES) return@hookAfter 27 | val packInfo = it.result as PackageInfo 28 | if (configBean.signature) { 29 | val items = LogUtil.getStackTrace() 30 | val byteArray = 31 | if (OSUtils.atLeastP() && flag == PackageManager.GET_SIGNING_CERTIFICATES) { 32 | packInfo.signingInfo.apkContentsSigners[0].toByteArray() 33 | } else { 34 | @Suppress("DEPRECATION") packInfo.signatures[0].toByteArray() 35 | } 36 | val md5 = ToolUtils.getDigest(byteArray) 37 | val sha1 = ToolUtils.getDigest(byteArray, "SHA-1") 38 | val sha256 = ToolUtils.getDigest(byteArray, "SHA-256") 39 | val list = listOf("Signature(MD5): $md5", 40 | "Signature(SHA-1): $sha1", 41 | "Signature(SHA-256): $sha256") 42 | val logBean = LogBean("Signature", list + items, HookHelper.hostPackageName) 43 | LogUtil.outLogMsg(logBean) 44 | } 45 | val signConfigStr = configBean.guiseSign.info 46 | if (configBean.guiseSign.enable && signConfigStr.contains(packInfo.packageName) && signConfigStr.contains( 47 | "true") 48 | ) { 49 | if (OSUtils.atLeastP() && flag == PackageManager.GET_SIGNING_CERTIFICATES) { 50 | val guiseSignConfigs = Json.decodeFromString>(signConfigStr) 51 | guiseSignConfigs.forEach { config -> 52 | if (config.packageName == packInfo.packageName && config.enable) { 53 | packInfo.signingInfo.apkContentsSigners[0] = Signature(config.signData) 54 | it.result = packInfo 55 | return@hookAfter 56 | } 57 | } 58 | } else { 59 | val guiseSignConfigs = Json.decodeFromString>(signConfigStr) 60 | guiseSignConfigs.forEach { config -> 61 | if (config.packageName == packInfo.packageName && config.enable) { 62 | packInfo.signatures[0] = Signature(config.signData) 63 | it.result = packInfo 64 | return@hookAfter 65 | } 66 | } 67 | } 68 | } 69 | } 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /hook/extension/ToastHook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.extension 2 | 3 | import android.view.View 4 | import android.view.ViewGroup 5 | import android.widget.TextView 6 | import android.widget.Toast 7 | import de.robv.android.xposed.XC_MethodHook 8 | import de.robv.android.xposed.XposedHelpers 9 | import me.simpleHook.bean.ExtensionConfig 10 | import me.simpleHook.bean.LogBean 11 | import me.simpleHook.hook.Tip 12 | import me.simpleHook.hook.util.HookHelper 13 | import me.simpleHook.hook.util.HookUtils.getAllTextView 14 | import me.simpleHook.hook.util.LogUtil 15 | import me.simpleHook.extension.log 16 | 17 | object ToastHook : BaseHook() { 18 | 19 | override fun startHook(configBean: ExtensionConfig) { 20 | if (!configBean.toast) return 21 | XposedHelpers.findAndHookMethod(Toast::class.java, "show", object : XC_MethodHook() { 22 | override fun beforeHookedMethod(param: MethodHookParam?) { 23 | val list = mutableListOf() 24 | // not test some cases 25 | val toast: Toast = param?.thisObject as Toast 26 | try { 27 | XposedHelpers.getObjectField(toast, "mText")?.also { 28 | list.add(Tip.getTip("text") + it) 29 | } 30 | } catch (e: NoSuchFieldError) { 31 | "toast error1".log(HookHelper.hostPackageName) 32 | try { 33 | XposedHelpers.getObjectField(toast, "mNextView")?.also { 34 | val toastView = it as View 35 | if (toastView is ViewGroup) { 36 | list += getAllTextView(toastView) 37 | } else if (toastView is TextView) { 38 | list.add(Tip.getTip("text") + toastView.text.toString()) 39 | } 40 | } 41 | } catch (e: NoSuchFieldError) { 42 | "toast error2".log(HookHelper.hostPackageName) 43 | } 44 | } 45 | val type = "Toast" 46 | val logBean = 47 | LogBean(type, list + LogUtil.getStackTrace(), HookHelper.hostPackageName) 48 | LogUtil.outLogMsg(logBean) 49 | } 50 | }) 51 | } 52 | } -------------------------------------------------------------------------------- /hook/extension/VpnCheckHook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.extension 2 | 3 | import de.robv.android.xposed.XC_MethodHook 4 | import de.robv.android.xposed.XposedHelpers 5 | import me.simpleHook.bean.ExtensionConfig 6 | import me.simpleHook.hook.util.HookHelper 7 | 8 | object VpnCheckHook : BaseHook() { 9 | 10 | override fun startHook(configBean: ExtensionConfig) { 11 | if (!configBean.vpn) return 12 | XposedHelpers.findAndHookMethod("java.net.NetworkInterface", 13 | HookHelper.appClassLoader, 14 | "getName", 15 | object : XC_MethodHook() { 16 | override fun beforeHookedMethod(param: MethodHookParam) { 17 | super.beforeHookedMethod(param) 18 | param.result = "are you ok" 19 | } 20 | }) 21 | } 22 | } -------------------------------------------------------------------------------- /hook/extension/WebHook.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.extension 2 | 3 | import android.webkit.WebView 4 | import de.robv.android.xposed.XC_MethodHook 5 | import de.robv.android.xposed.XposedBridge 6 | import de.robv.android.xposed.XposedHelpers 7 | import kotlinx.serialization.encodeToString 8 | import kotlinx.serialization.json.Json 9 | import me.simpleHook.bean.ExtensionConfig 10 | import me.simpleHook.bean.LogBean 11 | import me.simpleHook.hook.util.HookHelper 12 | import me.simpleHook.hook.util.LogUtil 13 | 14 | object WebHook : BaseHook() { 15 | 16 | override fun startHook(configBean: ExtensionConfig) { 17 | if (configBean.webLoadUrl) { 18 | hookWebLoadUrl() 19 | } 20 | if (configBean.webDebug) { 21 | hookWebDebug() 22 | } 23 | } 24 | 25 | 26 | private fun hookWebLoadUrl() { 27 | XposedBridge.hookAllMethods(WebView::class.java, "loadUrl", object : XC_MethodHook() { 28 | override fun afterHookedMethod(param: MethodHookParam) { 29 | val type = "WEB" 30 | val url = param.args[0] as String 31 | val list = mutableListOf() 32 | list.add("Url: $url") 33 | if (param.args.size == 2) { 34 | @Suppress("UNCHECKED_CAST") 35 | val map = param.args[1] as Map 36 | val headers = Json.encodeToString(map) 37 | list.add("Header: $headers") 38 | } 39 | val logBean = LogBean(type, list, HookHelper.hostPackageName) 40 | LogUtil.outLogMsg(logBean) 41 | } 42 | }) 43 | } 44 | 45 | private fun hookWebDebug() { 46 | val webClass = XposedHelpers.findClass("android.webkit.WebView", HookHelper.appClassLoader) 47 | XposedBridge.hookAllConstructors(webClass, object : XC_MethodHook() { 48 | override fun afterHookedMethod(param: MethodHookParam) { 49 | XposedHelpers.callStaticMethod(webClass, "setWebContentsDebuggingEnabled", true) 50 | } 51 | }) 52 | XposedHelpers.findAndHookMethod(webClass, 53 | "setWebContentsDebuggingEnabled", 54 | Boolean::class.java, 55 | object : XC_MethodHook() { 56 | override fun beforeHookedMethod(param: MethodHookParam) { 57 | param.args[0] = true 58 | } 59 | }) 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /hook/util/ConfigUtil.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.util 2 | 3 | import android.annotation.SuppressLint 4 | import android.net.Uri 5 | import com.google.gson.Gson 6 | import me.simpleHook.constant.Constant 7 | import me.simpleHook.database.entity.AppConfig 8 | import me.simpleHook.extension.log 9 | import me.simpleHook.hook.util.HookHelper.appContext 10 | import me.simpleHook.hook.util.HookHelper.hostPackageName 11 | import me.simpleHook.util.FlavorUtils 12 | import me.simpleHook.util.FlavorUtils.PROVIDER_CUSTOM_CONFIG_URI 13 | import me.simpleHook.util.FlavorUtils.PROVIDER_EXTENSION_CONFIG_URI 14 | import java.io.File 15 | 16 | object ConfigUtil { 17 | private val uri = Uri.parse(PROVIDER_CUSTOM_CONFIG_URI) 18 | private val extensionUri = Uri.parse(PROVIDER_EXTENSION_CONFIG_URI) 19 | 20 | fun getConfigFromFile( 21 | configName: String = Constant.CUSTOM_CONFIG_NAME 22 | ): String? { 23 | val configPath = if (FlavorUtils.rootVersion) { 24 | Constant.ROOT_CONFIG_MAIN_DIRECTORY + hostPackageName + "/config/" 25 | } else { 26 | Constant.ANDROID_DATA_PATH + hostPackageName + "/simpleHook/config/" 27 | } + configName 28 | return runCatching { 29 | val strConfig = File(configPath).reader().use { it.readText() } 30 | strConfig 31 | }.onFailure { 32 | "failed: $configPath".log(hostPackageName) 33 | }.getOrNull() 34 | } 35 | 36 | @SuppressLint("Range") 37 | fun getCustomConfigFromDB(): String? { 38 | return try { 39 | var config: String? = null 40 | appContext.contentResolver?.query(uri, 41 | null, 42 | "packageName = ?", 43 | arrayOf(hostPackageName), 44 | null)?.apply { 45 | while (moveToNext()) { 46 | if (getInt(getColumnIndex("enable")) == 1) { 47 | val configString = getString(getColumnIndex("config")) 48 | val appConfig = AppConfig(configs = configString, 49 | packageName = hostPackageName, 50 | appName = "", 51 | versionName = "", 52 | description = "") 53 | config = Gson().toJson(appConfig) 54 | break 55 | } 56 | } 57 | close() 58 | } 59 | config 60 | } catch (e: Throwable) { 61 | null 62 | } 63 | } 64 | 65 | @SuppressLint("Range") 66 | fun getExConfigFromDB(): String? { 67 | return try { 68 | var config: String? = null 69 | appContext.contentResolver?.query(extensionUri, 70 | null, 71 | "packageName = ?", 72 | arrayOf(hostPackageName), 73 | null)?.apply { 74 | while (moveToNext()) { 75 | if (getInt(getColumnIndex("allSwitch")) == 1) { 76 | config = getString(getColumnIndex("config")) 77 | } 78 | } 79 | close() 80 | } 81 | config 82 | } catch (e: Throwable) { 83 | null 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /hook/util/Extension.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.util 2 | 3 | import com.github.kyuubiran.ezxhelper.utils.* 4 | import me.simpleHook.constant.Constant 5 | import java.lang.reflect.Constructor 6 | import java.lang.reflect.Method 7 | 8 | fun Method.hook(hookMode: Int, hooker: Hooker) { 9 | when (hookMode) { 10 | Constant.HOOK_RETURN, Constant.HOOK_RETURN2, Constant.HOOK_PARAM -> hookBefore(hooker) 11 | Constant.HOOK_RECORD_PARAMS, Constant.HOOK_RECORD_RETURN, Constant.HOOK_RECORD_PARAMS_RETURN -> hookAfter( 12 | hooker) 13 | Constant.HOOK_BREAK -> this.hookReplace { it.result == null } 14 | } 15 | } 16 | 17 | fun Method.hook(hookBefore: Boolean, hooker: Hooker) { 18 | if (hookBefore) hookBefore(hooker) else hookAfter(hooker) 19 | } 20 | 21 | fun Constructor<*>.hook(hookBefore: Boolean, hooker: Hooker) { 22 | if (hookBefore) hookBefore(hooker) else hookAfter(hooker) 23 | } 24 | 25 | @JvmName("hookConstructor") 26 | fun List>.hook(hookBefore: Boolean, hooker: Hooker) { 27 | if (hookBefore) hookBefore(hooker) else hookAfter(hooker) 28 | } 29 | 30 | fun List.hook(hookMode: Int, hooker: Hooker) { 31 | when (hookMode) { 32 | Constant.HOOK_RETURN, Constant.HOOK_RETURN2, Constant.HOOK_PARAM -> hookBefore(hooker) 33 | Constant.HOOK_RECORD_PARAMS, Constant.HOOK_RECORD_RETURN, Constant.HOOK_RECORD_PARAMS_RETURN -> hookAfter( 34 | hooker) 35 | Constant.HOOK_BREAK -> this.hookReplace { it.result == null } 36 | } 37 | } 38 | 39 | @JvmName("hookMethod") 40 | fun List.hook(hookBefore: Boolean, hooker: Hooker) { 41 | if (hookBefore) hookBefore(hooker) else hookAfter(hooker) 42 | } 43 | 44 | fun Method.isSearchMethod(params: String): Boolean { 45 | val methodParams = params.split(",") 46 | val realSize = if (params == "") 0 else methodParams.size 47 | if (realSize != paramCount) return false 48 | for (index in 0 until realSize) { 49 | if (parameterTypes[index].name != Type.getClassTypeName(methodParams[index])) return false 50 | } 51 | return true 52 | } 53 | 54 | fun Constructor<*>.isSearchConstructor(params: String): Boolean { 55 | val methodParams = params.split(",") 56 | val realSize = if (params == "") 0 else methodParams.size 57 | if (realSize != paramCount) return false 58 | for (index in 0 until realSize) { 59 | if (parameterTypes[index].name != Type.getClassTypeName(methodParams[index])) return false 60 | } 61 | return true 62 | } -------------------------------------------------------------------------------- /hook/util/HookHelper.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.util 2 | 3 | import android.content.Context 4 | import android.content.pm.ApplicationInfo 5 | import com.github.kyuubiran.ezxhelper.init.EzXHelperInit 6 | import de.robv.android.xposed.callbacks.XC_LoadPackage 7 | 8 | object HookHelper { 9 | 10 | fun initFields(context: Context, lpparam: XC_LoadPackage.LoadPackageParam) { 11 | appContext = context 12 | appClassLoader = context.classLoader 13 | EzXHelperInit.setEzClassLoader(appClassLoader) 14 | hostPackageName = lpparam.packageName 15 | appInfo = lpparam.appInfo 16 | } 17 | 18 | lateinit var appContext: Context 19 | private set 20 | 21 | val isAppContextInitialized: Boolean 22 | get() = this::appContext.isInitialized 23 | 24 | lateinit var appClassLoader: ClassLoader 25 | private set 26 | 27 | 28 | lateinit var hostPackageName: String 29 | private set 30 | 31 | lateinit var appInfo: ApplicationInfo 32 | 33 | var enableRecord: Boolean = true 34 | 35 | 36 | } -------------------------------------------------------------------------------- /hook/util/HookUtils.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.util 2 | 3 | import android.view.View 4 | import android.view.ViewGroup 5 | import android.widget.Button 6 | import android.widget.TextView 7 | import androidx.core.view.children 8 | import com.google.gson.Gson 9 | import me.simpleHook.hook.Tip 10 | 11 | 12 | object HookUtils { 13 | fun getAllTextView(viewGroup: ViewGroup): List { 14 | val list = mutableListOf() 15 | viewGroup.children.forEach { 16 | when (it) { 17 | is Button -> { 18 | if (it.text.toString().isNotEmpty()) { 19 | list.add(Tip.getTip("button") + it.text.toString()) 20 | } 21 | } 22 | is TextView -> { 23 | if (it.text.toString().isNotEmpty()) { 24 | list.add(Tip.getTip("text") + it.text.toString()) 25 | } 26 | } 27 | is ViewGroup -> { 28 | list += getAllTextView(it) 29 | } 30 | } 31 | } 32 | return list 33 | } 34 | 35 | fun getAllViewIds(view: View): List { 36 | val list = mutableListOf() 37 | if (view is ViewGroup) { 38 | view.children.forEach { 39 | list += getAllViewIds(it) 40 | } 41 | } else { 42 | if (view.id != View.NO_ID) list.add(view.id.toString()) 43 | } 44 | return list 45 | } 46 | 47 | fun byte2Sting(bytes: ByteArray): String { 48 | val sb = StringBuilder() 49 | for (b in bytes) { 50 | if (Integer.toHexString(0xFF and b.toInt()).length == 1) { 51 | sb.append("0") 52 | } 53 | sb.append(Integer.toHexString(0xFF and b.toInt())) 54 | } 55 | return sb.toString() 56 | } 57 | 58 | fun getObjectString(value: Any): String { 59 | return if (value is String) value else try { 60 | Gson().toJson(value) 61 | } catch (e: Throwable) { 62 | value.javaClass.name 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /hook/util/LogUtil.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.util 2 | 3 | import android.net.Uri 4 | import android.os.Build.VERSION_CODES 5 | import androidx.core.content.contentValuesOf 6 | import de.robv.android.xposed.XposedHelpers 7 | import kotlinx.serialization.encodeToString 8 | import kotlinx.serialization.json.Json 9 | import me.simpleHook.bean.LogBean 10 | import me.simpleHook.constant.Constant 11 | import me.simpleHook.database.entity.PrintLog 12 | import me.simpleHook.extension.log 13 | import me.simpleHook.extension.tip 14 | import me.simpleHook.hook.Tip 15 | import me.simpleHook.hook.util.HookHelper.hostPackageName 16 | import me.simpleHook.util.* 17 | import me.simpleHook.util.FlavorUtils.PROVIDER_RECORD_URI 18 | 19 | object LogUtil { 20 | private const val filterClass = 21 | """(?i)EdHooker|LspHooker|littleWhiteDuck|me.simpleHook|me.weishu|de.robv.android.xposed|XposedBridge""" 22 | private val PRINT_URI = Uri.parse(PROVIDER_RECORD_URI) 23 | fun outLogMsg(logBean: LogBean) { 24 | if (logBean.type == "null" || !HookHelper.enableRecord) return 25 | val log = Json.encodeToString(logBean) 26 | HookHelper.appContext.getExternalFilesDirs("") 27 | val time = TimeUtil.getTime(System.currentTimeMillis(), "yy-MM-dd HH:mm:ss") 28 | val tempPackageName = 29 | if (logBean.type.startsWith("Error")) "error.hook.tip" else hostPackageName 30 | if (FlavorUtils.liteVersion) { 31 | log.log(hostPackageName) 32 | } else if (HookHelper.appInfo.targetSdkVersion > VERSION_CODES.Q) { 33 | outLogFile(log, tempPackageName, logBean.type, time) 34 | } else { 35 | outLogDB(log, tempPackageName, logBean.type, time) 36 | } 37 | 38 | } 39 | 40 | private fun outLogFile( 41 | log: String, tempPackageName: String, type: String, time: String 42 | ) { 43 | try { 44 | val printLog = 45 | PrintLog(log = log, packageName = tempPackageName, type = type, time = time) 46 | val printLogStr = Json.encodeToString(printLog) 47 | val filePath = 48 | Constant.ANDROID_DATA_PATH + hostPackageName + "/simpleHook/" + Constant.RECORD_TEMP_DIRECTORY 49 | FileUtils.outTextToFile(filePath, 50 | printLogStr, 51 | isNewLine = true, 52 | limitSize = 4096, 53 | append = true) 54 | } catch (e: Exception) { 55 | "error occurred while saving log to the file, 此次log打印在下方".tip(hostPackageName) 56 | log.log(hostPackageName) 57 | } 58 | } 59 | 60 | private fun outLogDB( 61 | log: String, tempPackageName: String, type: String, time: String 62 | ) { 63 | try { 64 | val contentValues = contentValuesOf("packageName" to tempPackageName, 65 | "log" to log, 66 | "read" to 0, 67 | "type" to type, 68 | "time" to time, 69 | "isMark" to 0) 70 | HookHelper.appContext.contentResolver?.insert(PRINT_URI, contentValues) 71 | } catch (e: Exception) { 72 | "error occurred while saving log to the database".tip(hostPackageName) 73 | outLogFile(log, tempPackageName, type, time) 74 | } 75 | } 76 | 77 | fun getStackTrace(): List { 78 | val stackTrace = Throwable().stackTrace 79 | val isNotChinese = LanguageUtils.isNotChinese() 80 | val items = mutableListOf() 81 | var notBug = 0 82 | for (element in stackTrace) { 83 | val className = element.className 84 | if (className.contains(Regex(filterClass))) continue 85 | if (notBug == 0) { 86 | items.add(if (isNotChinese) "Call stack: " else "调用堆栈:") 87 | } 88 | notBug++ 89 | items.add("${if (isNotChinese) "Class : " else "类:"}${element.className} -->${if (isNotChinese) "Method : " else "方法:"}${element.methodName}(line:${element.lineNumber})") 90 | } 91 | return items 92 | } 93 | 94 | 95 | fun outLog( 96 | list: List, type: String 97 | ) { 98 | val logBean = LogBean(type = type, other = list, "error.hook.tip") 99 | outLogMsg(logBean) 100 | } 101 | 102 | private fun notFoundClass( 103 | className: String, methodName: String, error: String 104 | ) { 105 | Tip.getTip("notFoundClass").log(hostPackageName) 106 | val list = listOf(Tip.getTip("errorType") + "ClassNotFoundError", 107 | Tip.getTip("solution") + Tip.getTip("notFoundClass"), 108 | Tip.getTip("filledClassName") + className, 109 | Tip.getTip("filledMethodOrField") + methodName, 110 | Tip.getTip("detailReason") + error) 111 | outLog(list, "Error ClassNotFoundError") 112 | } 113 | 114 | private fun noSuchMethod( 115 | className: String, methodName: String, error: String 116 | ) { 117 | Tip.getTip("noSuchMethod").log(hostPackageName) 118 | val list = listOf(Tip.getTip("errorType") + "NoSuchMethodError", 119 | Tip.getTip("solution") + Tip.getTip("useSmali2Config"), 120 | Tip.getTip("filledClassName") + className, 121 | Tip.getTip("filledMethodParams") + methodName, 122 | Tip.getTip("detailReason") + error) 123 | outLog(list, "Error NoSuchMethodError") 124 | } 125 | 126 | fun outHookError(className: String, methodName: String, exception: Throwable) { 127 | when (exception) { 128 | is NoSuchMethodError, is NoSuchMethodException -> { 129 | noSuchMethod(className, methodName, exception.stackTraceToString()) 130 | } 131 | is XposedHelpers.ClassNotFoundError, is ClassNotFoundException -> { 132 | notFoundClass(className, methodName, exception.stackTraceToString()) 133 | } 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /hook/util/Type.kt: -------------------------------------------------------------------------------- 1 | package me.simpleHook.hook.util 2 | 3 | 4 | import android.content.Context 5 | import me.simpleHook.util.toByteValue 6 | import me.simpleHook.util.toIntValue 7 | import me.simpleHook.util.toLongValue 8 | import me.simpleHook.util.toShortValue 9 | import java.util.regex.Pattern.matches 10 | 11 | object Type { 12 | // 未考虑非法数字 13 | private const val BYTE_PATTERN = """^-?\d+[b|B]$""" 14 | private const val SHORT_PATTERN = """^-?\d+short$""" 15 | private const val INT_PATTERN = """^-?\d+$""" 16 | private const val LONG_PATTERN = """^-?\d+[l|L]$""" 17 | private const val FLOAT_PATTERN = """^-?\d+.?\d*[f|F]$""" 18 | private const val DOUBLE_PATTERN = """^-?\d+.?\d*[d|D]$""" 19 | private const val BOOLEAN_PATTERN = """(?i)true|false""" 20 | private const val CHAR_PATTERN = """^.[c|C]$""" 21 | 22 | private const val STRING_PATTERN_NUMBER = """^-?\d+[s|S]$""" 23 | private const val STRING_PATTERN_BOOLEAN = """^(?i)trues|falses$""" 24 | private const val STRING_PATTERN_NULL = """^(?i)nulls$""" 25 | private const val STRING_EMPTY_PATTERN = """(?i)empty|空""" 26 | private const val STRING_EMPTY_LIST = """empty_list_string""" 27 | private const val NULL_PATTERN = """(?i)null""" 28 | 29 | 30 | fun getDataTypeValue(value: String): Any? = when { 31 | matches(BOOLEAN_PATTERN, value) -> value.toBoolean() 32 | matches(INT_PATTERN, value) -> value.toIntValue 33 | matches(FLOAT_PATTERN, value) -> value.replace("f", "").toFloat() 34 | matches(DOUBLE_PATTERN, value) -> value.replace("d", "").toDouble() 35 | matches(LONG_PATTERN, value) -> value.replace(Regex("""[l|L]"""), "").toLongValue 36 | matches(NULL_PATTERN, value) -> null 37 | matches(STRING_EMPTY_PATTERN, value) -> "" 38 | matches(BYTE_PATTERN, value) -> value.replace(Regex("""[b|B]"""), "").toByteValue 39 | matches(SHORT_PATTERN, value) -> value.replace(Regex("short"), "").toShortValue 40 | matches(CHAR_PATTERN, value) -> value[0] 41 | matches(STRING_PATTERN_NUMBER, value) -> value.replace(Regex("""[s|S]"""), "") 42 | matches(STRING_PATTERN_BOOLEAN, value) -> value.removeRange(value.length - 1, value.length) 43 | .lowercase() 44 | matches(STRING_PATTERN_NULL, value) -> value.replace(Regex("""[s|S]"""), "") 45 | value == STRING_EMPTY_LIST -> emptyList() 46 | else -> value 47 | } 48 | 49 | 50 | fun getClassType(className: String) = when (className) { 51 | "byte", "B", "b" -> Byte::class.java 52 | "int", "I", "i" -> Int::class.java 53 | "short", "S", "s" -> Short::class.java 54 | "long", "J", "j" -> Long::class.java 55 | "float", "F", "f" -> Float::class.java 56 | "double", "D", "d" -> Double::class.java 57 | "boolean", "Z", "z" -> Boolean::class.java 58 | "char", "c", "C" -> Char::class.java 59 | "string" -> String::class.java 60 | "context" -> Context::class.java 61 | else -> null 62 | } 63 | 64 | fun getClassTypeName(className: String) = getClassType(className)?.name ?: className 65 | } -------------------------------------------------------------------------------- /images/config_dialog_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleWhiteDuck/SimpleHook/6d87c0ec606fda00b66e93a141293784912102ac/images/config_dialog_screenshot.png -------------------------------------------------------------------------------- /images/config_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleWhiteDuck/SimpleHook/6d87c0ec606fda00b66e93a141293784912102ac/images/config_screenshot.png -------------------------------------------------------------------------------- /images/extension_main_features_shot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleWhiteDuck/SimpleHook/6d87c0ec606fda00b66e93a141293784912102ac/images/extension_main_features_shot.png -------------------------------------------------------------------------------- /images/main_extension_print_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleWhiteDuck/SimpleHook/6d87c0ec606fda00b66e93a141293784912102ac/images/main_extension_print_dialog.png -------------------------------------------------------------------------------- /images/main_extension_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleWhiteDuck/SimpleHook/6d87c0ec606fda00b66e93a141293784912102ac/images/main_extension_screenshot.png -------------------------------------------------------------------------------- /images/main_home_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleWhiteDuck/SimpleHook/6d87c0ec606fda00b66e93a141293784912102ac/images/main_home_screenshot.png --------------------------------------------------------------------------------