├── .gitignore ├── .idea ├── caches │ ├── build_file_checksums.ser │ └── gradle_models.ser ├── codeStyles │ └── Project.xml ├── compiler.xml ├── encodings.xml ├── gradle.xml ├── jarRepositories.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── README.md ├── XServer.js ├── app ├── .gitignore ├── app-release.apk ├── build.gradle ├── lib │ ├── XposedBridgeApi-54.jar │ ├── freemarker.jar │ └── openbeans.jar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── monkeylord │ │ └── XServer │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── fragments │ │ │ ├── Tracer_after │ │ │ └── Tracer_before │ │ ├── js │ │ │ ├── jquery.js │ │ │ └── vue.js │ │ ├── pages │ │ │ ├── .idea │ │ │ │ ├── .gitignore │ │ │ │ ├── misc.xml │ │ │ │ ├── modules.xml │ │ │ │ └── vcs.xml │ │ │ ├── classview.html │ │ │ ├── filemgr.html │ │ │ ├── index.html │ │ │ ├── memory.html │ │ │ ├── methodview.html │ │ │ ├── objmgr.html │ │ │ ├── script.html │ │ │ ├── tracer.html │ │ │ └── tracer2.html │ │ └── xposed_init │ ├── java │ │ └── monkeylord │ │ │ └── XServer │ │ │ ├── MainActivity.java │ │ │ ├── XServer.java │ │ │ ├── XposedEntry.java │ │ │ ├── api │ │ │ ├── BaseOperation.java │ │ │ ├── ClassView.java │ │ │ ├── FileAccess.java │ │ │ ├── Invoke.java │ │ │ ├── Invoke_New.java │ │ │ ├── MemoryView.java │ │ │ ├── MethodView.java │ │ │ ├── ObjManager.java │ │ │ ├── Script.java │ │ │ ├── Tracer.java │ │ │ ├── WsScript.kt │ │ │ ├── wsMethodView.java │ │ │ ├── wsMethodViewNew.java │ │ │ ├── wsTracer.java │ │ │ └── wsTracerNew.java │ │ │ ├── handler │ │ │ ├── ClassHandler.java │ │ │ ├── Hook │ │ │ │ ├── DummyProvider.java │ │ │ │ ├── Unhook.java │ │ │ │ ├── XServer_MethodHook.java │ │ │ │ └── XServer_Param.java │ │ │ ├── HookHandler.java │ │ │ ├── InterceptionHandler.java │ │ │ ├── MemoryHandler.java │ │ │ ├── MethodHandler.java │ │ │ └── ObjectHandler.java │ │ │ ├── objectparser │ │ │ ├── BooleanParser.java │ │ │ ├── ByteArrayParser.java │ │ │ ├── CollectionParser.java │ │ │ ├── GenericParser.java │ │ │ ├── HashMapParser.java │ │ │ ├── IntParser.java │ │ │ ├── JSONParser.java │ │ │ ├── StoredObjectParser.java │ │ │ └── StringParser.java │ │ │ └── utils │ │ │ ├── DexHelper.java │ │ │ ├── NanoHTTPD.java │ │ │ ├── NanoWSD.java │ │ │ ├── Utils.java │ │ │ ├── Xlog.java │ │ │ └── netUtil.java │ └── res │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── monkeylord │ └── XServer │ ├── ExampleUnitTest.java │ └── objectparser │ └── ByteArrayParserTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── rhino-android ├── .gitignore ├── build.gradle ├── libs │ └── dx.jar ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── cn │ └── vove7 │ └── rhino │ ├── RhinoHelper.kt │ ├── api │ ├── AbsApi.java │ ├── RhinoApi.java │ ├── ThreadApi.java │ └── WrappedThread.java │ └── common │ ├── AndroidContextFactory.java │ ├── AssetAndUrlModuleSourceProvider.java │ ├── BaseAndroidClassLoader.java │ ├── FileAndroidClassLoader.java │ ├── GcCollector.java │ ├── InMemoryAndroidClassLoader.java │ ├── MapScriptable.java │ ├── NoSecurityController.java │ └── RhinoAndroidHelper.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkeylord/XServer/1bd4c57b241275aa432672e42e4e81d6f3cdf32f/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/caches/gradle_models.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkeylord/XServer/1bd4c57b241275aa432672e42e4e81d6f3cdf32f/.idea/caches/gradle_models.ser -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | 39 | 40 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 26 | 45 | 46 | 47 | 48 | 49 | 50 | 52 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 monkeylord 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XServer 2 | A Xposed Module for Android Penetration Test, with NanoHttpd. 3 | 4 | ### 背景 5 | 6 | 渗透测试中经常遇到通信协议的分析或者各类混淆。 7 | 8 | 此时,若通过静态的代码分析,则定位关键函数时耗时往往很久。此时,直接根据运行时的参数、结果特征直接定位到关键函数,往往能够节省很多时间。 9 | 10 | 此外,逆向通信协议往往比较麻烦,尤其是测试目的是通信内容而非协议本身时,此时,若不去逆向协议本身,而是直接使用应用内现成的协议,也会省去大量时间。 11 | 12 | #### XServer 13 | 14 | XServer是一个用于对方法进行分析的Xposed插件,它针对的是“方法”接口。由于人类习惯函数式编程,为了可维护性,往往会把各个功能分别封装进各个类与方法,这成为了程序的弱点。 15 | 16 | 利用注入和反射,可以记录并拦截方法的调用,也可以在应用自身的运行环境中调用某个具体方法。这就可以对应用的分析起到辅助。 17 | 18 | 另外,XServer还通过HTTP和WebSocket提供远程动态操作界面,也提供RPC接口供其它工具调用应用内的方法。 19 | 20 | ### 使用说明 21 | 22 | #### 通过Xposed启动 23 | 24 | 1. 确保Xposed框架已经正确安装 25 | 2. 安装XServer并确保在Xposed中启用XServer 26 | 3. 在XServer应用选择器中选中目标应用 27 | 4. 启动目标应用 28 | 1. 如果XServer没有启动,可能是目标应用早已启动,然后才选择的目标应用,已错过目标应用判断时机。此时,可以关闭目标应用重新打开。 29 | 2. 如果切换目标应用,原目标应用中的XServer依然在工作和占用端口。可以关掉原目标应用再启动新目标应用。 30 | 3. 选择应用后直接重启设备最简单,如果你使用模拟器的话。 31 | 5. 通过ADB转发XServer端口:`adb forward tcp:8000 tcp:8000` 32 | 1. 目标应用可能存在多个进程,针对这种情况,XServer在进程PID对应的端口也打开了监听。若8000端口对应的进程不是目标应用主进程,可以使用另一个命令修正:`adb forward tcp:8000 tcp:[目标进程PID]` 33 | 6. 通过http://127.0.0.1:8000/ 访问XServer 34 | 35 | #### 通过Frida启动 36 | 37 | 1. 确保Frida-Server已经启动 38 | 2. 确保XServer已在设备中 39 | 1. 在目标设备中安装XServer(无需Xposed框架) 40 | 2. 将XServer对应APK放置在`/data/local/tmp/xserver.apk` 41 | 3. 使用Frida加载XServer.js以启动XServer:`frida -U [目标应用包名或进程PID] -l XServer.js` 42 | 4. 通过ADB转发XServer端口:`adb forward tcp:8000 tcp:8000` 43 | 5. 通过http://127.0.0.1:8000/ 访问XServer 44 | 45 | #### 通过其他Hook框架启动 46 | 47 | XServer内部使用自己定义的Hook接口,因此可以兼容其他Hook框架,只要其他Hook框架实现HookProvider并注册。 48 | 49 | 1. 将XServer注入并加载到目标应用中 50 | 51 | 2. 修改XServer属性:assetManager、classLoader 52 | 53 | 3. 实现HookProvider 54 | 55 | 1. 支持动态创建类的框架如Frida可自行实现`monkeylord.XServer.handler.HookHandler$HookProvider` 56 | 2. 也可以通过Hook已有的`monkeylord.XServer.handler.Hook.DummyProvider`来实现 57 | 58 | 4. 在XServer中注册HookProvider 59 | 60 | ~~~java 61 | HookHandler.setProvider(yourProviderClass); 62 | ~~~ 63 | 64 | 5. 启动XServer 65 | 66 | ~~~java 67 | new XServer(8000); 68 | ~~~ 69 | 70 | #### 批量跟踪 71 | 72 | 1. XServer首页最下方是当前应用已加载的类清单,通过Filter可以根据名称进行过滤 73 | 1. 这个清单不包括系统类 74 | 2. 这个清单对应的是XServer.classLoader中已加载的类 75 | 2. 点击Begin MassMonitoring进入批量跟踪界面 76 | 3. 在Class Filter中根据类名筛选感兴趣的类(支持正则表达式) 77 | 4. 点击Load Methods for Matched Classes来加载符合条件的类中的方法清单 78 | 5. 在Method Filter中根据方法名筛选感兴趣的方法(支持正则表达式) 79 | 6. 点击Hook Matched Methods监控所有符合以上两个筛选条件的方法。 80 | 7. 收起Method Trace Selector观察日志 81 | 8. 在Class Tree中可以随时通过勾选和取消勾选来调整某个方法是否被Hook 82 | 1. 某些不重要的方法可能反复触发刷屏,可取消勾选,不再关注这些方法。 83 | 9. 可以通过Ctrl+F搜索日志以寻找某些已知的内容是否在某个方法的参数或返回值中。 84 | 85 | #### 拦截、修改、重放方法调用 86 | 87 | 1. 进入目标方法查看界面 88 | 1. 在MassMonitoring界面中点击感兴趣的方法直接进入 89 | 2. 在首页点击对应的类,再点击进入对应方法 90 | 2. 部署并启动中间人工具并设置代理进行抓包 91 | 1. XServer报文会使用系统配置的代理 92 | 3. 操作应用,目标方法被调用时可在中间人工具中拦截到/invoke2调用 93 | 1. 方法查看界面下方会也会记录方法调用情况,此处同时可以查看方法被调用时的堆栈情况 94 | 2. 方法查看界面中的Invoke是一种简单触发调用的方法,用于调用应用中一些简单的工具方法 95 | 4. 在中间人工具中,可以实时修改/invoke2调用中的参数和返回结果,从而改变方法被调用时的参数/返回结果 96 | 5. 可以在中间人工具中直接重放/invoke2请求,这会使对应的方法被再次调用 97 | 1. 对于加解密方法,这可以直接调用加解密 98 | 2. 对于网络请求方法,这可以再次触发网络请求 99 | 100 | #### 注入Js脚本执行方法 101 | 102 | XServer 接入Rhino 可动态注入脚本执行,调用指定方法。 103 | 例如调用监控 Application.onCreate() 方法 104 | ``` 105 | app.onCreate() 106 | ``` 107 | js环境中支持Application变量:app 可直接使用,导入类方式,参考[Rhino官方文档](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino/Scripting_Java) 108 | 109 | ![D8doQA.md.jpg](https://s3.ax1x.com/2020/11/22/D8r6U0.jpg) 110 | 111 | 112 | ### XServer基础结构 113 | 114 | #### 架构 115 | 116 | XServer是一个被注入到目标APP中的、具备Hook等能力的HttpServer。 117 | 118 | 包含两个部分:HttpServer、Xposed入口 119 | 120 | Xposed启动HttpServer并提供Hook能力,WebServer负责界面、RPC及各类业务逻辑。 121 | 122 | 新版的XServer可以在无Xposed的环境下运行,比如使用Frida来启动和Hook。 123 | 124 | #### 关键组件 125 | 126 | Xposed入口:XposedEntry 127 | 128 | 应用选择器:MainActivity 129 | 130 | HttpServer:XServer 131 | 132 | 业务逻辑处理:handler 133 | 134 | 对外接口:api 135 | 136 | 其它功能组件:utils、objectparser 137 | 138 | 资源文件:各类freemarker页面(XServer目前使用freemarker作为模板引擎) 139 | 140 | #### HttpServer 141 | 142 | 对应代码:XServer.java 143 | 144 | 基于NanoHTTPD和NanoWSD开发,实现HTTP路由表与WebSocket路由表,并整合freemarker作为模板引擎。 145 | 146 | 定义了两类API接口,HTTP API和WebSocket API(Operation、wsOperation),启动后加载的各类功能API都要在此注册。 147 | 148 | 模板引擎则很简洁,在assets目录中编写模板,然后`XServer.render(data,templete)`即可。 149 | 150 | XServer可以动态注册第三方插件,比如新增API,或者告诉XServer特殊对象如何处理,不过目前并没有定义这部分能力。 151 | 152 | ### API组件 153 | 154 | #### Viewer 155 | 156 | 包含以下API:MemoryView、ClassView、MethodView和wsMethodView 157 | 158 | MemoryView提供基本的内存修改、Dump能力。 159 | 160 | ClassView提供对类和方法的浏览,用来了解和查看应用的结构。 161 | 162 | MethodView和wsMethodView提供对方法调用与返回的实时监控,同时将被监控的方法变为远程调用。 163 | 164 | #### Tracer 165 | 166 | 包含两个API:Tracer和wsTracer 167 | 168 | 提供对应用执行流的方法粒度跟踪,批量监控并记录方法调用与返回,以及对应的参数和结果。 169 | 170 | 用于分析应用执行流,定位关键函数。 171 | 172 | #### Invoker 173 | 174 | 包含两个API:Invoke和Invoke2 175 | 176 | 提供并完成对应用内方法的远程调用,也管理保存的对象实例。 177 | 178 | MethodView界面里的Invoke提供的是基本调用,适用于一些简单的情况。 179 | 180 | wsMethodView里使用了Invoke2调用,设置了Burp等代理后,可以拦截并修改对应方法的调用,可以处理复杂对象,也支持在Burp等工具中直接重放、爆破等。建议使用Invoke2。 181 | 182 | #### Injector 183 | 184 | API:TODO 185 | 186 | 提供动态注入SO,APK的能力,用于启动frida-gadget或动态启动其它Xposed插件。 187 | 188 | ### Handler组件 189 | 190 | #### 处理反射 191 | 192 | 包含ClassHandler、MethodHandler 193 | 194 | 用于通过反射获取各种类与方法的对象,提取和处理其中信息。 195 | 196 | #### 处理注入 197 | 198 | 包含两个handler:HookHandler、ObjectHandler 199 | 200 | HookHandler用于为其他组件提供Hook能力,并定义了XServer使用的Hook接口。 201 | 202 | ObjectHandler用于管理APP中的各类对象实例,在XServer内部收集和复用各类对象实例。Invoke2依赖此功能,Invoke2处理复杂对象时通常不创建对象,而是通过ObjectHandler复用APP自身使用的对象。 203 | 204 | #### 深度操作 205 | 206 | 目前包含MemoryHandler 207 | 208 | 提供内存操作,利用内部类Libcore实现,没有使用SO。 209 | 210 | 可以用于Dump内存寻找Demo,也可以动态修改内存。 211 | 212 | TODO:提供SO和APK加载 213 | 214 | TODO:提供C层Hook,利用SO实现,或许可以使用substrate,或许可以现场gcc? 215 | 216 | TODO:集成一些比较有用的Hook 217 | 218 | ### 对象处理ObjectParser 219 | 220 | 定义了XServer对于各类对象的序列化、反序列化逻辑。 221 | 222 | 对应的逻辑在XServer中注册。 223 | 224 | 目前包括了字符串、字节数组、整型的处理逻辑,以及使用ObjectHandler来存取对象的处理逻辑。 225 | 226 | 这部分可以较为方便地自行增添和注册,也欢迎PR。 -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /release -------------------------------------------------------------------------------- /app/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkeylord/XServer/1bd4c57b241275aa432672e42e4e81d6f3cdf32f/app/app-release.apk -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | compileSdkVersion 26 6 | defaultConfig { 7 | applicationId 'monkeylord.xserver' 8 | minSdkVersion 19 9 | targetSdkVersion 26 10 | versionCode 2 11 | versionName "0.7.3" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | implementation fileTree(include: ['*.jar'], dir: 'libs') 24 | 25 | implementation 'com.android.support:support-v4:26.1.0' 26 | implementation 'com.android.support:appcompat-v7:26.1.0' 27 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 28 | implementation 'com.alibaba:fastjson:1.1.68.android' 29 | compileOnly files('lib/XposedBridgeApi-54.jar') 30 | implementation files('lib/freemarker.jar') 31 | implementation files('lib/openbeans.jar') 32 | implementation project(":rhino-android") 33 | implementation 'com.blankj:utilcode:1.29.0' 34 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 35 | 36 | } 37 | -------------------------------------------------------------------------------- /app/lib/XposedBridgeApi-54.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkeylord/XServer/1bd4c57b241275aa432672e42e4e81d6f3cdf32f/app/lib/XposedBridgeApi-54.jar -------------------------------------------------------------------------------- /app/lib/freemarker.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkeylord/XServer/1bd4c57b241275aa432672e42e4e81d6f3cdf32f/app/lib/freemarker.jar -------------------------------------------------------------------------------- /app/lib/openbeans.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkeylord/XServer/1bd4c57b241275aa432672e42e4e81d6f3cdf32f/app/lib/openbeans.jar -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\MobileEnvironment\SDK/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/monkeylord/XServer/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.assertEquals; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("monkeylord.XServer", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 26 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/assets/fragments/Tracer_after: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkeylord/XServer/1bd4c57b241275aa432672e42e4e81d6f3cdf32f/app/src/main/assets/fragments/Tracer_after -------------------------------------------------------------------------------- /app/src/main/assets/fragments/Tracer_before: -------------------------------------------------------------------------------- 1 |
2 | if (getMid(method) != null) 3 | log("[" + Process.myTid() + "]" + method.getDeclaringClass().getName() + "." + MethodDescription(param).toString() + ""); 4 | else 5 | log("[" + Process.myTid() + "]" + method.getDeclaringClass().getName() + "." + MethodDescription(param).toString() + ""); 6 | log("
"); 7 | try { 8 | if (args != null) for (int i = 0; i < args.length; i++) { 9 | log("
Argument " + i + "
"); 10 | log("
" + translate(args[i]) + "
"); 11 | } 12 | 13 | } catch (Throwable e) { 14 | log("

" + e.getLocalizedMessage() + "

"); 15 | } finally { 16 | log("
"); -------------------------------------------------------------------------------- /app/src/main/assets/pages/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /app/src/main/assets/pages/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/assets/pages/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/assets/pages/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/assets/pages/classview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ClassView 5 | 6 | 7 | <#if name?exists> 8 |

Current Class:

9 |

${name}

10 | <#if fieldList?exists> 11 |

Fields:

12 | <#list fieldList as field> 13 |

${field}

14 | 15 | 16 | <#if methodList?exists> 17 |

Methods:

18 | <#list methodList as method> 19 |

${method}

20 | 21 | 22 | <#else> 23 |

Class Not Found

24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/assets/pages/filemgr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | File Manager 5 | 6 | 7 | 8 |
9 | 49 | 50 | -------------------------------------------------------------------------------- /app/src/main/assets/pages/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | XServer - ${pid} 5 | 6 | 7 |

Welcome to XServer

8 |

Hello, I'm Monkeylord.

9 |

XServer is a tool for remote reflect invoking which make all methods accessible from WEB 10 | interface.

11 |

It can be used to monitor method calls/returns as well.

12 |

Usages

13 |

Select a class, then select a method, then begin your monitoring/invoking.

14 |

Tips

15 |

It can be used to locate/bypass network traffic encryption/sign.

16 |

Some Application may have several processes.To monitor specified process, you can use 'adb 17 | forward tcp:8000 tcp:[pid]' instead of 'adb forward tcp:8000 tcp:8000'

18 |

Request Infomations:

19 |
20 | Available Operations: 21 | <#list opList as op> 22 | 23 | ${op} 24 | 25 | 26 |
27 | <#if params?exists> 28 |
29 | Your Params: 30 | <#list params?keys as key> 31 |

${key} = ${params[key]}

32 | 33 |
34 | 35 | <#if headers?exists> 36 |
37 | Your Headers: 38 | <#list headers?keys as key> 39 |

${key} = ${headers[key]}

40 | 41 |
42 | 43 | <#if parsers?exists> 44 |
45 | Available Parsers: 46 | <#list parsers?keys as key> 47 |

${key}

48 | 49 |
50 | 51 | <#if objs?exists> 52 |
53 | Captured Objects: 54 | <#list objs?keys as key> 55 |

${key}

56 | 57 |
58 | 59 | <#if objs?exists> 60 |
61 | Known ClassLoaders(click to switch to): 62 | <#list classloaders?keys as key> 63 |

${key}:${classloaders[key]}

64 | 65 |
66 | 67 | 77 |

Advanced

78 |

Memory Editor

79 |

Memory Editor is a basic tool allows you read/write/dump application's memory.

80 |

It can be used to extract .dex or .so file in the runtime.

81 |

Script Engine

82 |

Script Engine is a rhino js engine introduced by Vove7

83 |

It allows you script into java like frida. See Rhino Documents for grammar.

84 | 85 |

View/Monitor Known Classes:

86 |
87 |

88 | Filter: 89 |

90 | 91 |
92 |
93 |
94 | 95 | 113 | -------------------------------------------------------------------------------- /app/src/main/assets/pages/memory.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MemoryReader 5 | 6 | 7 |
8 | Memory Maps 9 | <#list maps as map> 10 |

${map}

11 | 12 |
13 |

Memory Editor:

14 |

Address:

15 |

Count :

16 | 17 | 18 | 19 | 20 |

21 | 22 | 23 | 52 | -------------------------------------------------------------------------------- /app/src/main/assets/pages/methodview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MethodView 5 | 6 | 7 |

Current Class:

8 |

${class}

9 |

Current Method:

10 |

${description}

11 |

${javaName}

12 |

${json}

13 |

Detail:

14 | <#list parametersTypes as param> 15 |

Param:${param.name}

16 | 17 | <#list exceptionTypes as exception> 18 |

Throw:${exception.name}

19 | 20 |

Return:${returnType.name}

21 | 22 |

Invoke

23 |

PS: This is for test and is depreciated, You should use MitM tool to intercept/replay invoke.

24 |

Each invoke on this method will be rerouted to http://127.0.0.1:8000/invoke2, so MitM tool like burp can intercept that once there's a proxy set.

25 |
26 | 27 | 28 | Instance: 29 |
30 | 36 | <#list parametersTypes as param> 37 |

Param${param_index}: ${param.name}

38 | 39 | String 40 | Integer 41 | Byte(in Base64) 42 | Object 43 | Null 44 |
45 | 46 | 47 |
48 |

Monitor

49 |
50 | 51 | 161 | -------------------------------------------------------------------------------- /app/src/main/assets/pages/objmgr.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkeylord/XServer/1bd4c57b241275aa432672e42e4e81d6f3cdf32f/app/src/main/assets/pages/objmgr.html -------------------------------------------------------------------------------- /app/src/main/assets/pages/script.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Script 5 | 6 | 7 | 20 | 21 | 22 | 23 |
24 | 25 |
26 |

Script Content

27 | 45 |

46 | 47 | 48 | 49 |

50 |

51 | app: current application 52 |

53 |

54 | xserver: xserver instance 55 |

56 |

57 | hook: hook provider 58 |

59 |
60 | 61 |
62 |

LOG

63 |

64 | 65 |
66 | 67 |
68 | 69 | 70 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /app/src/main/assets/pages/tracer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mass Monitor 5 | 7 | 8 | 9 | 10 |
11 | Filters/Controls
12 | Class Filter: 13 | Method Filter(RegEx): 14 | Thread Filter: 15 | 16 | 17 | 18 |
19 |
20 |
21 | 22 | 23 | 115 | -------------------------------------------------------------------------------- /app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | monkeylord.XServer.XposedEntry -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/MainActivity.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.content.Context; 6 | import android.content.DialogInterface; 7 | import android.content.SharedPreferences; 8 | import android.content.pm.PackageInfo; 9 | import android.graphics.Color; 10 | import android.os.Bundle; 11 | import android.os.MemoryFile; 12 | import android.util.Log; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.widget.AbsListView; 16 | import android.widget.BaseAdapter; 17 | import android.widget.Button; 18 | import android.widget.EditText; 19 | import android.widget.ImageView; 20 | import android.widget.LinearLayout; 21 | import android.widget.TextView; 22 | 23 | import java.io.BufferedOutputStream; 24 | import java.io.BufferedWriter; 25 | import java.io.File; 26 | import java.io.IOException; 27 | import java.io.OutputStream; 28 | import java.io.OutputStreamWriter; 29 | import java.util.List; 30 | 31 | import monkeylord.XServer.handler.MemoryHandler; 32 | 33 | public class MainActivity extends Activity { 34 | SharedPreferences sp; 35 | String hookee; 36 | boolean isReg; 37 | TextView info; 38 | EditText appname; 39 | //CheckBox regEx; 40 | 41 | private static boolean isModuleActive() { 42 | return false; 43 | } 44 | private static long getSharedMem() {return 0;} 45 | 46 | public void makeWorldReadable(){ 47 | new File("/data/data/" + XServer.class.getPackage().getName().toLowerCase()).setExecutable(true, false); 48 | new File("/data/data/" + XServer.class.getPackage().getName().toLowerCase() + "/shared_prefs/XServer.xml").setReadable(true, false); 49 | } 50 | 51 | @Override 52 | protected void onCreate(Bundle savedInstanceState) { 53 | super.onCreate(savedInstanceState); 54 | LinearLayout layout = new LinearLayout(this); 55 | LinearLayout.LayoutParams param = new LinearLayout.LayoutParams( 56 | ViewGroup.LayoutParams.FILL_PARENT, 57 | ViewGroup.LayoutParams.WRAP_CONTENT); 58 | layout.setOrientation(LinearLayout.VERTICAL); 59 | super.setContentView(layout, param); 60 | sp = getSharedPreferences("XServer", isModuleActive()?MODE_WORLD_READABLE:MODE_PRIVATE); 61 | makeWorldReadable(); 62 | hookee = sp.getString("targetApp", "com."); 63 | //isReg = sp.getBoolean("isReg", false); 64 | final AppAdapter appAdapter = new AppAdapter(this); 65 | final AlertDialog selector = new AlertDialog.Builder(this) 66 | .setTitle("Select App") 67 | .setAdapter(appAdapter, new DialogInterface.OnClickListener() { 68 | @Override 69 | public void onClick(DialogInterface dialogInterface, int i) { 70 | hookee = ((PackageInfo) appAdapter.getItem(i)).packageName; 71 | update(); 72 | dialogInterface.dismiss(); 73 | } 74 | }) 75 | .create(); 76 | TextView welcome = new TextView(this); 77 | welcome.setText("XServer's App Selector"); 78 | welcome.setTextSize(20f); 79 | welcome.setTextColor(Color.BLACK); 80 | info = new TextView(this); 81 | appname = new EditText(this); 82 | //regEx = new CheckBox(this); 83 | //regEx.setText("use RegEx"); 84 | Button apply = new Button(this); 85 | apply.setText("Apply"); 86 | apply.setOnClickListener(new View.OnClickListener() { 87 | @Override 88 | public void onClick(View view) { 89 | hookee = appname.getText().toString(); 90 | //isReg = regEx.isChecked(); 91 | update(); 92 | } 93 | }); 94 | Button selectApp = new Button(this); 95 | selectApp.setText("Select App"); 96 | selectApp.setOnClickListener(new View.OnClickListener() { 97 | @Override 98 | public void onClick(View view) { 99 | selector.show(); 100 | } 101 | }); 102 | TextView tips = new TextView(this); 103 | tips.setText("Note:\n" + 104 | "XServer will listen on 2 ports when target app start: 8000 and PID \n" + 105 | "To connect XServer, ADB command can be used like:\n" + 106 | " adb forward tcp:8000 tcp:8000\n" + 107 | "Or" + 108 | " adb forward tcp:8000 tcp:[PID]\n" + 109 | "Then you can open http://127.0.0.1:8000 in browser to interact with XServer"); 110 | tips.setTextSize(15f); 111 | tips.setTextColor(Color.GRAY); 112 | if (!isModuleActive()) { 113 | TextView alert = new TextView(this); 114 | alert.setText("Be Awared: Module Inactive"); 115 | alert.setTextColor(Color.RED); 116 | layout.addView(alert); 117 | } 118 | layout.addView(welcome); 119 | layout.addView(info); 120 | layout.addView(appname); 121 | //layout.addView(regEx); 122 | layout.addView(apply); 123 | layout.addView(selectApp); 124 | layout.addView(tips); 125 | update(); 126 | } 127 | 128 | public void update() { 129 | SharedPreferences.Editor editor = sp.edit(); 130 | editor.putString("targetApp", hookee); 131 | //editor.putBoolean("isReg", isReg); 132 | editor.commit(); 133 | info.setText("Target App:\r\n" + hookee); 134 | appname.setText(hookee); 135 | try { 136 | long sharedMem = getSharedMem(); 137 | if(sharedMem!=0)MemoryHandler.writeMemory(sharedMem,(hookee+"\0").getBytes()); 138 | } catch (Exception e) { 139 | e.printStackTrace(); 140 | } 141 | //regEx.setChecked(isReg); 142 | } 143 | 144 | class AppAdapter extends BaseAdapter { 145 | Context context; 146 | List packageInfo; 147 | 148 | AppAdapter(Context context) { 149 | this.context = context; 150 | packageInfo = context.getPackageManager().getInstalledPackages(0); 151 | } 152 | 153 | @Override 154 | public int getCount() { 155 | return packageInfo.size(); 156 | } 157 | 158 | @Override 159 | public Object getItem(int i) { 160 | return packageInfo.get(i); 161 | } 162 | 163 | @Override 164 | public long getItemId(int i) { 165 | return 0; 166 | } 167 | 168 | @Override 169 | public View getView(int i, View view, ViewGroup viewGroup) { 170 | //调整了一下应用选择器的外观,感谢@smartdone大佬建议和帮助 171 | //https://github.com/monkeylord/XServer/pull/1/commits/ab718e13a8ef1486f43e1023f62e312b3ff10307 172 | LinearLayout linearLayout = new LinearLayout(context); 173 | linearLayout.setLayoutParams(new AbsListView.LayoutParams( 174 | ViewGroup.LayoutParams.MATCH_PARENT, 175 | ViewGroup.LayoutParams.WRAP_CONTENT)); 176 | linearLayout.setOrientation(LinearLayout.HORIZONTAL); 177 | //创建图标 178 | ImageView iv_app_icon = new ImageView(context); 179 | iv_app_icon.setImageDrawable(packageInfo.get(i).applicationInfo.loadIcon(context.getPackageManager())); 180 | iv_app_icon.setLayoutParams(new ViewGroup.LayoutParams(80, 80)); 181 | //iv_app_icon.setAdjustViewBounds(true); 182 | iv_app_icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 183 | iv_app_icon.setPadding(5,5,5,5); 184 | linearLayout.addView(iv_app_icon); 185 | //创建文本描述 186 | LinearLayout textLayout=new LinearLayout(context); 187 | textLayout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); 188 | textLayout.setOrientation(LinearLayout.VERTICAL); 189 | 190 | TextView app_display_name = new TextView(context); 191 | app_display_name.setPadding(5, 15, 5, 3); 192 | app_display_name.getPaint().setFakeBoldText(true); 193 | app_display_name.setText(packageInfo.get(i).applicationInfo.loadLabel(context.getPackageManager())); 194 | 195 | TextView app_package_name = new TextView(context); 196 | app_package_name.setPadding(5, 3, 5, 5); 197 | app_package_name.setText( packageInfo.get(i).packageName); 198 | 199 | textLayout.addView(app_display_name); 200 | textLayout.addView(app_package_name); 201 | 202 | linearLayout.addView(textLayout); 203 | Log.e("XServer", "Selector Ready"); 204 | return linearLayout; 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/XposedEntry.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Activity; 5 | import android.app.AlertDialog; 6 | import android.content.Context; 7 | import android.content.DialogInterface; 8 | import android.content.pm.ApplicationInfo; 9 | import android.content.res.XModuleResources; 10 | import android.os.Build; 11 | import android.os.Process; 12 | import android.system.OsConstants; 13 | import android.util.Log; 14 | 15 | import com.blankj.utilcode.util.NetworkUtils; 16 | 17 | import java.io.File; 18 | import java.io.FileDescriptor; 19 | import java.io.RandomAccessFile; 20 | import java.lang.reflect.InvocationTargetException; 21 | import java.lang.reflect.Member; 22 | import java.util.HashMap; 23 | 24 | import de.robv.android.xposed.IXposedHookLoadPackage; 25 | import de.robv.android.xposed.IXposedHookZygoteInit; 26 | import de.robv.android.xposed.XC_MethodHook; 27 | import de.robv.android.xposed.XC_MethodReplacement; 28 | import de.robv.android.xposed.XSharedPreferences; 29 | import de.robv.android.xposed.XposedBridge; 30 | import de.robv.android.xposed.XposedHelpers; 31 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 32 | import monkeylord.XServer.handler.Hook.Unhook; 33 | import monkeylord.XServer.handler.Hook.XServer_MethodHook; 34 | import monkeylord.XServer.handler.Hook.XServer_Param; 35 | import monkeylord.XServer.handler.HookHandler; 36 | import monkeylord.XServer.handler.MemoryHandler; 37 | 38 | import static monkeylord.XServer.utils.Utils.getMyIp; 39 | 40 | /* 41 | 某些Android 4版本,需要修改依赖库的配置才能兼容,否则会报pre-verifed错误。 42 | 原因:Framework也提供了XposedBridgeApi,和编译进插件的内容重复。所以要把XposedBridgeApi从编译改为引用。 43 | 修改:Build->Edit Libraries and Dependencies 将XposedBridgeApi的scope从compile改为provided 44 | 45 | */ 46 | 47 | public class XposedEntry implements IXposedHookLoadPackage, IXposedHookZygoteInit { 48 | 49 | public static boolean debug=true; 50 | public static ClassLoader classLoader; 51 | public static XModuleResources res; 52 | public static XSharedPreferences sPrefs; 53 | String packageName; 54 | Boolean isFirstApplication; 55 | String processName; 56 | ApplicationInfo appInfo; 57 | long smAddr; 58 | 59 | @Override 60 | public void initZygote(StartupParam startupParam) throws Throwable { 61 | res = XModuleResources.createInstance(startupParam.modulePath, null); 62 | sPrefs = new XSharedPreferences(this.getClass().getPackage().getName().toLowerCase(), "XServer"); 63 | sPrefs.makeWorldReadable(); 64 | try{ 65 | String targetApp = sPrefs.getString("targetApp", "MadMode"); 66 | File file = new File("/dev/zero"); 67 | RandomAccessFile randomAccessFile = new RandomAccessFile(file,"rw"); 68 | FileDescriptor fd = randomAccessFile.getFD(); 69 | if(!fd.valid())smAddr = 0; 70 | else{ 71 | try { 72 | smAddr = MemoryHandler.mmap(0, 1024, OsConstants.PROT_READ | OsConstants.PROT_WRITE, OsConstants.MAP_SHARED, fd, 0); 73 | MemoryHandler.writeMemory(smAddr,(targetApp+"\0").getBytes()); 74 | }catch (InvocationTargetException e){ 75 | throw e.getTargetException(); 76 | }finally { 77 | randomAccessFile.close(); 78 | } 79 | } 80 | }catch (Exception e){ 81 | Log.e("[XServer Experiment]", e.getMessage()+e.toString()); 82 | } 83 | } 84 | 85 | @SuppressLint("MissingPermission") 86 | @Override 87 | public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { 88 | 89 | //告知界面模块已启动,同时解除Android N以上对MODE_WORLD_READABLE的限制 90 | if (loadPackageParam.packageName.equals("monkeylord.xserver")) { 91 | XposedHelpers.findAndHookMethod("monkeylord.XServer.MainActivity", loadPackageParam.classLoader, "isModuleActive", XC_MethodReplacement.returnConstant(true)); 92 | XposedHelpers.findAndHookMethod("monkeylord.XServer.MainActivity", loadPackageParam.classLoader, "getSharedMem", XC_MethodReplacement.returnConstant(smAddr)); 93 | if (Build.VERSION.SDK_INT >= 24)XposedHelpers.findAndHookMethod("android.app.ContextImpl", loadPackageParam.classLoader, "checkMode",int.class, XC_MethodReplacement.returnConstant(null)); 94 | XposedBridge.log("XServer handleLoadPackage: "+ Build.VERSION.SDK_INT); 95 | } 96 | //获取目标包名 97 | sPrefs.reload(); 98 | String targetApp = sPrefs.getString("targetApp", "MadMode"); 99 | if (targetApp.equals("MadMode")&&smAddr!=0)targetApp = new String(MemoryHandler.readMemory(smAddr,1024)).split("\0")[0]; 100 | //if(targetApp.equals("MadMode"))XposedBridge.log("XServer Cannot Figure Out TargetApp...Hooking Everyone Now!!"); 101 | if (!targetApp.equals("MadMode")&&!loadPackageParam.packageName.equals(targetApp)) return; 102 | gatherInfo(loadPackageParam); 103 | //启动XServer 104 | setXposedHookProvider(); 105 | if(!targetApp.equals("MadMode"))new XServer(8000); 106 | new XServer(Process.myPid()); 107 | final String ip = getMyIp(); 108 | XposedBridge.log("XServer Listening... " + loadPackageParam.packageName + " --> http://" + ip + ":" + Process.myPid()); 109 | XposedBridge.log("Using XposedHook...@" + Process.myPid()); 110 | if(!targetApp.equals("MadMode"))XposedHelpers.findAndHookMethod(Activity.class, "onStart", new XC_MethodHook() { 111 | @Override 112 | public void afterHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable { 113 | new AlertDialog.Builder((Context) param.thisObject) 114 | .setTitle("XServer activated") 115 | .setMessage("Open:http://" + ip + ":" + Process.myPid()) 116 | .setPositiveButton("ok", new DialogInterface.OnClickListener() { 117 | @Override 118 | public void onClick(DialogInterface dialogInterface, int i) { 119 | dialogInterface.dismiss(); 120 | } 121 | }) 122 | .create().show(); 123 | XposedBridge.unhookMethod(param.method,this); 124 | } 125 | }); 126 | 127 | } 128 | 129 | void setXposedHookProvider(){ 130 | XServer.classLoader = classLoader; 131 | XServer.assetManager = res.getAssets(); 132 | HookHandler.setProvider(new HookHandler.HookProvider() { 133 | // 复用Hook,否则在大量Hook时会OOM 134 | HashMap pairs = new HashMap<>(); 135 | @Override 136 | public Unhook hookMethod(Member hookMethod, final XServer_MethodHook mycallback) { 137 | XC_MethodHook myCallback = pairs.get(mycallback); 138 | if(myCallback==null)myCallback = new XC_MethodHook() { 139 | XServer_MethodHook callback = mycallback; 140 | @Override 141 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 142 | super.beforeHookedMethod(param); 143 | XServer_Param xsParam = new XServer_Param(); 144 | xsParam.args = param.args==null ? new Object[0] : param.args; 145 | xsParam.method = param.method; 146 | xsParam.thisObject = param.thisObject; 147 | xsParam.throwable = param.getThrowable(); 148 | xsParam.result = param.getResult(); 149 | callback.beforeHookedMethod(xsParam); 150 | if (xsParam.returnEarly) { 151 | if (xsParam.hasThrowable()) param.setThrowable(xsParam.getThrowable()); 152 | else param.setResult(xsParam.result); 153 | } 154 | } 155 | 156 | @Override 157 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 158 | super.afterHookedMethod(param); 159 | XServer_Param xsParam = new XServer_Param(); 160 | xsParam.args = param.args; 161 | xsParam.method = param.method; 162 | xsParam.thisObject = param.thisObject; 163 | xsParam.throwable = param.getThrowable(); 164 | xsParam.result = param.getResult(); 165 | callback.afterHookedMethod(xsParam); 166 | if (xsParam.hasThrowable()) param.setThrowable(xsParam.getThrowable()); 167 | else param.setResult(xsParam.result); 168 | } 169 | }; 170 | pairs.put(mycallback,myCallback); 171 | Object unhook = XposedBridge.hookMethod(hookMethod, myCallback); 172 | return new Unhook(hookMethod, unhook); 173 | } 174 | 175 | @Override 176 | public void unhookMethod(Member hookMethod, Object additionalObj) { 177 | if(additionalObj!=null)((XC_MethodHook.Unhook)additionalObj).unhook(); 178 | } 179 | }); 180 | } 181 | 182 | private void gatherInfo(XC_LoadPackage.LoadPackageParam loadPackageParam) { 183 | packageName = loadPackageParam.packageName; 184 | isFirstApplication = loadPackageParam.isFirstApplication; 185 | classLoader = loadPackageParam.classLoader; 186 | processName = loadPackageParam.processName; 187 | appInfo = loadPackageParam.appInfo; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/api/BaseOperation.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.api; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import monkeylord.XServer.XServer; 7 | import monkeylord.XServer.utils.NanoHTTPD; 8 | 9 | import static monkeylord.XServer.utils.NanoHTTPD.newFixedLengthResponse; 10 | 11 | public abstract class BaseOperation implements XServer.Operation { 12 | public NanoHTTPD.Response handle(NanoHTTPD.IHTTPSession session){ 13 | Map files = new HashMap(); 14 | Map headers = null; 15 | try { 16 | headers = session.getHeaders(); 17 | session.parseBody(files); 18 | } catch (Exception e) { 19 | e.printStackTrace(); 20 | } 21 | String uri = session.getUri(); 22 | return newFixedLengthResponse(handle(uri, session.getParms(), headers, files)); 23 | } 24 | 25 | abstract String handle(String url, Map parms, Map headers, Map files); 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/api/ClassView.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.api; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | 5 | import java.util.Map; 6 | 7 | import monkeylord.XServer.XServer; 8 | import monkeylord.XServer.handler.ClassHandler; 9 | import monkeylord.XServer.utils.DexHelper; 10 | 11 | //类详情查看页面 12 | public class ClassView extends BaseOperation { 13 | @Override 14 | public String handle(String url, Map parms, Map headers, Map files) { 15 | try { 16 | if(parms.get("op")!=null) { 17 | JSONObject res = new JSONObject(); 18 | switch (parms.get("op")){ 19 | case "getclass": 20 | res.put("class", 21 | ClassHandler.getClassDetail(ClassHandler.findClassbyName(parms.get("class"), XServer.classLoader))); 22 | return res.toJSONString(); 23 | case "getclasses": 24 | res.put("classes", 25 | DexHelper.getClassesInDex(XServer.classLoader)); 26 | return res.toJSONString(); 27 | case "setclassloader": 28 | res.put("success",ClassHandler.setXServerClassloader(parms.get("classloader"))); 29 | return " "; 30 | default: 31 | return ""; 32 | } 33 | }else{ 34 | return XServer.render( 35 | ClassHandler.getClassDetail( 36 | ClassHandler.findClassbyName(parms.get("class"), XServer.classLoader)), 37 | "pages/classview.html"); 38 | } 39 | } catch (Exception e) { 40 | return e.getLocalizedMessage(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/api/FileAccess.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.api; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | 6 | import java.io.File; 7 | import java.io.FileInputStream; 8 | import java.io.FileWriter; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | import monkeylord.XServer.XServer; 13 | import monkeylord.XServer.utils.NanoHTTPD; 14 | 15 | import static monkeylord.XServer.utils.NanoHTTPD.newChunkedResponse; 16 | import static monkeylord.XServer.utils.NanoHTTPD.newFixedLengthResponse; 17 | 18 | public class FileAccess implements XServer.Operation { 19 | @Override 20 | public NanoHTTPD.Response handle(NanoHTTPD.IHTTPSession session) { 21 | Map files = new HashMap(); 22 | try { 23 | session.parseBody(files); 24 | } catch (Exception e) { 25 | e.printStackTrace(); 26 | } 27 | if(session.getParms().get("op")!=null) { 28 | try { 29 | File file = new File(session.getParms().get("path")); 30 | switch (session.getParms().get("op")) { 31 | case "mkdir": 32 | if(file.mkdirs())return newFixedLengthResponse("Done"); 33 | else throw new Exception("Fail to mkdir."); 34 | case "readdir": 35 | HashMap fileList = new HashMap<>(); 36 | for (File listfile:file.listFiles()) { 37 | JSONObject fileObj = new JSONObject(); 38 | fileObj.put("isDirectory",listfile.isDirectory()); 39 | fileObj.put("lastModified",listfile.lastModified()); 40 | fileObj.put("length",listfile.length()); 41 | fileObj.put("canRead",listfile.canRead()); 42 | fileObj.put("canWrite",listfile.canWrite()); 43 | fileObj.put("canExecute",listfile.canExecute()); 44 | fileList.put(listfile.getName(),fileObj); 45 | } 46 | if(fileList==null)throw new Exception("Cannot access directory"); 47 | return newFixedLengthResponse(JSON.toJSONString(fileList)); 48 | case "read": 49 | //if(!file.canRead())file.setReadable(true); 50 | if(!file.canRead())throw new Exception("Cannot access file"); 51 | FileInputStream fis = new FileInputStream(file); 52 | NanoHTTPD.Response res = newChunkedResponse(NanoHTTPD.Response.Status.OK,"application/octet–stream",fis); 53 | res.addHeader("Content-Disposition","attachment;filename="+file.getName()); 54 | return res; 55 | case "write": 56 | FileWriter fw = new FileWriter(file); 57 | fw.write(files.get("postData")); 58 | fw.close(); 59 | return newFixedLengthResponse("Done"); 60 | case "delete": 61 | if(file.delete())return newFixedLengthResponse("Done"); 62 | else throw new Exception("Fail to delete."); 63 | default: 64 | return newFixedLengthResponse("Unknown Operation"); 65 | } 66 | }catch (Exception e){ 67 | return newFixedLengthResponse(NanoHTTPD.Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_HTML, e.getLocalizedMessage()); 68 | } 69 | }else{ 70 | HashMap map = new HashMap<>(); 71 | String appName = XServer.currentApp; 72 | map.put("dir", (appName!=null)?"/data/data/"+ appName: "/"); 73 | try { 74 | return newFixedLengthResponse(XServer.render(map, "pages/filemgr.html")); 75 | } catch (Exception e) { 76 | return newFixedLengthResponse(NanoHTTPD.Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_HTML, e.getLocalizedMessage()); 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/api/Invoke.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.api; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.Map; 5 | 6 | import monkeylord.XServer.XServer; 7 | import monkeylord.XServer.handler.ObjectHandler; 8 | 9 | //处理反射调用 10 | public class Invoke extends BaseOperation { 11 | @Override 12 | public String handle(String url, Map parms, Map headers, Map files) { 13 | StringBuilder sb = new StringBuilder(); 14 | Object thisobj = null; 15 | Object[] params; 16 | if ("app".equalsIgnoreCase(parms.get("thisobj"))) { 17 | thisobj = XServer.currentApplication; 18 | } else { 19 | thisobj = parms.get("thisobj").equals("null") ? null : ObjectHandler.objects.get(parms.get("thisobj")); 20 | } 21 | 22 | try { 23 | Method[] methods = Class.forName(parms.get("class"), false, XServer.classLoader).getDeclaredMethods(); 24 | Method m = methods[Integer.parseInt(parms.get("method"))]; 25 | params = new Object[m.getParameterTypes().length]; 26 | for (int i = 0; i < m.getParameterTypes().length; i++) { 27 | params[i] = XServer.parsers.get(parms.get("parser" + i)).parse(parms.get("param" + i)); 28 | } 29 | m.setAccessible(true); 30 | sb.append(m.invoke(thisobj, params)); 31 | } catch (Exception e) { 32 | sb.append(e.getLocalizedMessage()); 33 | } 34 | return sb.toString(); 35 | } 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/api/Invoke_New.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.api; 2 | 3 | import android.util.Log; 4 | 5 | import com.alibaba.fastjson.JSON; 6 | import com.alibaba.fastjson.JSONArray; 7 | import com.alibaba.fastjson.JSONObject; 8 | 9 | import java.lang.reflect.Method; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import monkeylord.XServer.XServer; 14 | import monkeylord.XServer.handler.MethodHandler; 15 | import monkeylord.XServer.handler.ObjectHandler; 16 | import monkeylord.XServer.utils.NanoHTTPD; 17 | 18 | //处理反射调用的另一个接口 19 | public class Invoke_New implements XServer.Operation { 20 | @Override 21 | public NanoHTTPD.Response handle(NanoHTTPD.IHTTPSession session){ 22 | Map files = new HashMap(); 23 | Map headers = null; 24 | try { 25 | headers = session.getHeaders(); 26 | session.parseBody(files); 27 | } catch (Exception e) { 28 | e.printStackTrace(); 29 | } 30 | String uri = session.getUri(); 31 | StringBuilder sb = new StringBuilder(); 32 | try { 33 | Log.d("XServer", "Invoke2 postData:"+files.get("postData")); 34 | JSONObject data = JSON.parseObject(files.get("postData")); 35 | Method method= MethodHandler.getMethodbyJavaName(data.getString("method")); 36 | //method.setAccessible(true); 37 | Object thisobj = (data.getString("this")!=null)?ObjectHandler.parseObject(data.getString("this")):null; 38 | Object[] params = new Object[method.getParameterTypes().length]; 39 | JSONArray paramList = data.getJSONArray("params"); 40 | for (int i = 0; i < params.length; i++) { 41 | params[i]=ObjectHandler.parseObject(paramList.getString(i)); 42 | Log.d("XServer", "Invoke2 Arg"+i+":"+paramList.getString(i)); 43 | } 44 | method.setAccessible(true); 45 | sb.append(ObjectHandler.saveObject(method.invoke(thisobj, params))); 46 | } catch (Exception e) { 47 | sb.append(e.getMessage()); 48 | sb.append("\r\n"); 49 | for (StackTraceElement st:e.getStackTrace()) { 50 | sb.append(st.toString()); 51 | sb.append('\n'); 52 | } 53 | e.printStackTrace(); 54 | Log.i("[XServer Debug]", sb.toString() ); 55 | return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.INTERNAL_ERROR,NanoHTTPD.MIME_PLAINTEXT,ObjectHandler.saveObject(new XServerWrappedThrowable(e.getCause(),true))); 56 | } 57 | Log.d("XServer", "Invoke2 return:"+sb.toString()); 58 | return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.OK,NanoHTTPD.MIME_PLAINTEXT,sb.toString()); 59 | } 60 | public static boolean isMe(){ 61 | StackTraceElement[] stacks = Thread.currentThread().getStackTrace(); 62 | for (int i = 4; i parms, Map headers, Map files) { 21 | try { 22 | Method method=null; 23 | if(parms.get("op")!=null) { 24 | switch (parms.get("op")){ 25 | case "maps": 26 | JSONObject res = new JSONObject(); 27 | res.put("maps",MemoryHandler.getMaps()); 28 | return res.toJSONString(); 29 | case "dump": 30 | byte[] binary=MemoryHandler.readMemory(Long.parseLong(parms.get("addr")),Integer.parseInt(parms.get("count"))); 31 | FileOutputStream fo = new FileOutputStream(new File("/sdcard/XServer.dump")); 32 | fo.write(binary); 33 | return "Dumped at /sdcard/XServer.dump"; 34 | case "read": 35 | byte[] bytes=MemoryHandler.readMemory(Long.parseLong(parms.get("addr")),Integer.parseInt(parms.get("count"))); 36 | return new String(encodeHex(bytes)); 37 | case "write": 38 | MemoryHandler.writeMemory(Long.parseLong(parms.get("addr")),hexStringToBytes(files.get("postData"))); 39 | byte[] bytes_res=MemoryHandler.readMemory(Long.parseLong(parms.get("addr")),hexStringToBytes(files.get("postData")).length); 40 | return new String(encodeHex(bytes_res)); 41 | default: 42 | return ""; 43 | } 44 | }else{ 45 | HashMap map = new HashMap<>(); 46 | map.put("maps", MemoryHandler.getMaps()); 47 | return XServer.render(map, "pages/memory.html"); 48 | } 49 | } catch (Exception e) { 50 | return e.getLocalizedMessage(); 51 | } 52 | } 53 | 54 | public static String bytesToHexString(byte[] src){ 55 | StringBuffer stringBuffer = new StringBuffer(src.length*2+2); 56 | byte[] hexs = "0123456789ABCDEF".getBytes(); 57 | if (src == null || src.length <= 0) { 58 | return null; 59 | } 60 | for (int i = 0; i < src.length; i++) { 61 | //Log.e("XServer", "bytesToHexString: "+((src[i]&0xFF)>>4)); 62 | //Log.e("XServer", "bytesToHexString: "+((src[i]&0x0F))); 63 | stringBuffer.append((char) (hexs[(src[i]&0xFF)>>4])); 64 | stringBuffer.append((char) (hexs[src[i]&0x0F])); 65 | /* 66 | int v = src[i] & 0xFF; 67 | String hv = Integer.toHexString(v); 68 | if (hv.length() < 2) { 69 | stringBuffer.append(0); 70 | } 71 | stringBuffer.append(hv); 72 | */ 73 | } 74 | return stringBuffer.toString(); 75 | } 76 | 77 | private static final char[] DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; 78 | public static char[] encodeHex(byte[] data) { 79 | int l = data.length; 80 | char[] out = new char[l << 1]; 81 | int i = 0; 82 | for (int j = 0; i < l; i++) { 83 | out[(j++)] = DIGITS[((0xF0 & data[i]) >>> 4)]; 84 | out[(j++)] = DIGITS[(0xF & data[i])]; 85 | } 86 | return out; 87 | } 88 | 89 | public static byte[] hexStringToBytes(String hexString) { 90 | if (hexString == null || hexString.equals("")) { 91 | return null; 92 | } 93 | hexString = hexString.toUpperCase(); 94 | int length = hexString.length() / 2; 95 | char[] hexChars = hexString.toCharArray(); 96 | byte[] d = new byte[length]; 97 | for (int i = 0; i < length; i++) { 98 | int pos = i * 2; 99 | d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1])); 100 | } 101 | return d; 102 | } 103 | /** 104 | * Convert char to byte 105 | * @param c char 106 | * @return byte 107 | */ 108 | private static byte charToByte(char c) { 109 | return (byte) "0123456789ABCDEF".indexOf(c); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/api/MethodView.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.api; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | import monkeylord.XServer.XServer; 8 | import monkeylord.XServer.handler.ClassHandler; 9 | import monkeylord.XServer.handler.MethodHandler; 10 | import monkeylord.XServer.handler.ObjectHandler; 11 | import monkeylord.XServer.utils.Utils; 12 | 13 | //查看方法详情页面 14 | public class MethodView extends BaseOperation { 15 | @Override 16 | public String handle(String url, Map parms, Map headers, Map files) { 17 | try { 18 | Method method=null; 19 | if(parms.get("javaname")!=null) { 20 | method = MethodHandler.getMethodbyJavaName(parms.get("javaname")); 21 | for (int i = 0; i < method.getDeclaringClass().getDeclaredMethods().length; i++) { 22 | if(Utils.getJavaName(method.getDeclaringClass().getDeclaredMethods()[i]).equals(parms.get("javaname"))){ 23 | parms.put("method",""+i); 24 | } 25 | } 26 | } 27 | if(method==null)method=ClassHandler.findClassbyName(parms.get("class"),XServer.classLoader).getDeclaredMethods()[Integer.parseInt(parms.get("method"))]; 28 | HashMap map = MethodHandler.getMethodDetail(method); 29 | map.put("method",parms.get("method")); 30 | map.put("objList", ObjectHandler.objects.keySet()); 31 | return XServer.render(map, "pages/methodview.html"); 32 | } catch (Exception e) { 33 | return e.getLocalizedMessage(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/api/ObjManager.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.api; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import monkeylord.XServer.XServer; 7 | 8 | //对象管理器页面 9 | //TODO 增删改查编辑暂存的对象 10 | public class ObjManager extends BaseOperation { 11 | @Override 12 | public String handle(String url, Map parms, Map headers, Map files) { 13 | try { 14 | Map map = new HashMap(); 15 | if (parms.containsKey("type")) { 16 | switch (parms.get("type")) { 17 | case "show": 18 | break; 19 | case "edit": 20 | break; 21 | default: 22 | return ""; 23 | } 24 | } else return XServer.render(map, "pages/objmgr.html"); 25 | return null; 26 | } catch (Exception e) { 27 | return e.getLocalizedMessage(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/api/Script.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.api; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import monkeylord.XServer.XServer; 7 | 8 | /** 9 | * Created by Vove on 2020/11/22 10 | */ 11 | public class Script extends BaseOperation { 12 | @Override 13 | String handle(String url, Map parms, Map headers, Map files) { 14 | try { 15 | Map map = new HashMap(); 16 | map.put("filter", parms.get("filter")); 17 | return XServer.render(map, "pages/script.html"); 18 | } catch (Exception e) { 19 | return e.getLocalizedMessage(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/api/Tracer.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.api; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import monkeylord.XServer.XServer; 7 | 8 | //MassTracer页面 9 | public class Tracer extends BaseOperation { 10 | @Override 11 | public String handle(String url, Map parms, Map headers, Map files) { 12 | try { 13 | Map map = new HashMap(); 14 | map.put("filter", parms.get("filter")); 15 | return XServer.render(map, "pages/tracer2.html"); 16 | } catch (Exception e) { 17 | return e.getLocalizedMessage(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/api/WsScript.kt: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.api 2 | 3 | import android.os.Handler 4 | import android.os.HandlerThread 5 | import cn.vove7.rhino.RhinoHelper 6 | import cn.vove7.rhino.api.RhinoApi 7 | import monkeylord.XServer.XServer 8 | import monkeylord.XServer.XServer.wsOperation 9 | import monkeylord.XServer.handler.HookHandler 10 | import monkeylord.XServer.utils.NanoHTTPD.IHTTPSession 11 | import monkeylord.XServer.utils.NanoWSD.WebSocket 12 | import monkeylord.XServer.utils.NanoWSD.WebSocketFrame 13 | import monkeylord.XServer.utils.NanoWSD.WebSocketFrame.CloseCode 14 | import java.io.IOException 15 | import java.text.SimpleDateFormat 16 | import java.util.* 17 | 18 | /** 19 | * # WsScript 20 | * 21 | * @author liben 22 | * 2020/11/22 23 | */ 24 | class WsScript : wsOperation { 25 | companion object { 26 | @JvmStatic 27 | val PATH = "/wsScript" 28 | 29 | val engineThread by lazy { 30 | HandlerThread("WsScript").also { 31 | it.start() 32 | } 33 | } 34 | val engineHandler by lazy { 35 | Handler(engineThread.looper) 36 | } 37 | 38 | private val engine by lazy { 39 | RhinoHelper( 40 | XServer.getCurrentApplication(), 41 | mapOf("app" to XServer.getCurrentApplication() 42 | ,"xserver" to XServer.instance 43 | ,"hook" to HookHandler.getProvider()) 44 | ) 45 | } 46 | } 47 | 48 | private var ws: WebSocketHandler? = null 49 | private val sf = SimpleDateFormat("HH:mm:ss:SSS", Locale.getDefault()) 50 | 51 | val onPrint: RhinoApi.OnPrint = RhinoApi.OnPrint { l, s -> 52 | ws?.trySend("[$l] $s") 53 | } 54 | 55 | 56 | override fun handle(handshake: IHTTPSession?): WebSocket { 57 | ws = WebSocketHandler(handshake); 58 | return ws!! 59 | } 60 | 61 | fun handleMessage(data: String?) = engineHandler.post { 62 | kotlin.runCatching { 63 | engine.evalString(data ?: "", null as Array<*>?) 64 | }.onFailure { 65 | it.printStackTrace() 66 | ws?.trySend(it.toString()) 67 | } 68 | } 69 | 70 | private inner class WebSocketHandler( 71 | handshakeRequest: IHTTPSession? 72 | ) : WebSocket(handshakeRequest) { 73 | fun trySend(payload: String?) { 74 | try { 75 | send(sf.format(Date()) + ": " + payload) 76 | } catch (e: IOException) { 77 | e.printStackTrace() 78 | } 79 | } 80 | 81 | override fun onMessage(message: WebSocketFrame) { 82 | handleMessage(message.textPayload) 83 | } 84 | 85 | override fun onOpen() { 86 | RhinoApi.regPrint(onPrint) 87 | trySend("Connect Success...") 88 | } 89 | 90 | override fun onClose(code: CloseCode, reason: String, initiatedByRemote: Boolean) { 91 | RhinoApi.unregPrint(onPrint) 92 | engine.release() 93 | } 94 | 95 | override fun onPong(pong: WebSocketFrame) { 96 | 97 | } 98 | 99 | override fun onException(exception: IOException) { 100 | exception.printStackTrace() 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/api/wsMethodViewNew.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.api; 2 | 3 | import android.os.Process; 4 | import android.util.Log; 5 | 6 | import com.alibaba.fastjson.JSONArray; 7 | import com.alibaba.fastjson.JSONObject; 8 | 9 | import java.io.IOException; 10 | import java.lang.reflect.InvocationTargetException; 11 | import java.lang.reflect.Member; 12 | import java.lang.reflect.Method; 13 | import java.util.ArrayList; 14 | import java.util.HashMap; 15 | 16 | import monkeylord.XServer.XServer; 17 | import monkeylord.XServer.handler.Hook.Unhook; 18 | import monkeylord.XServer.handler.Hook.XServer_MethodHook; 19 | import monkeylord.XServer.handler.Hook.XServer_Param; 20 | import monkeylord.XServer.handler.HookHandler; 21 | import monkeylord.XServer.handler.MethodHandler; 22 | import monkeylord.XServer.handler.ObjectHandler; 23 | import monkeylord.XServer.utils.NanoHTTPD; 24 | import monkeylord.XServer.utils.NanoWSD; 25 | import monkeylord.XServer.utils.Utils; 26 | import monkeylord.XServer.utils.netUtil; 27 | 28 | //单方法监控的WebSocket处理 29 | public class wsMethodViewNew implements XServer.wsOperation { 30 | 31 | final boolean unhookOnClose = true; 32 | //HashMap unhooks = new HashMap<>(); 33 | HashMap wss = new HashMap<>(); 34 | HashMap methods = new HashMap<>(); 35 | //ws websocket; 36 | //MethodHook hook; 37 | //public Method m = null; 38 | public String server="http://127.0.0.1:8000";//TODO +Process.myPid(); 39 | 40 | @Override 41 | public NanoWSD.WebSocket handle(NanoHTTPD.IHTTPSession handshake) { 42 | return new ws(handshake); 43 | } 44 | 45 | public Object wsInvoke(Object[] params) throws InvocationTargetException, IllegalAccessException { 46 | return methods.get(this).invoke(params); 47 | } 48 | 49 | public class ws extends NanoWSD.WebSocket { 50 | 51 | Unhook unhook = null; 52 | boolean modify = true; 53 | HashMap objs = new HashMap<>(); 54 | 55 | public ws(NanoHTTPD.IHTTPSession handshakeRequest) { 56 | super(handshakeRequest); 57 | Method m = MethodHandler.getMethodbyJavaName((handshakeRequest.getParms().get("javaname"))); 58 | methods.put(this,m); 59 | } 60 | 61 | public void trySend(String payload) {try {send(payload);} catch (IOException e) {e.printStackTrace();}} 62 | 63 | @Override 64 | protected void onOpen() { 65 | Method m = methods.get(this); 66 | MethodHook hook = new MethodHook(m); 67 | wss.put(hook,this); 68 | JSONObject res = new JSONObject(); 69 | res.put("type","hook"); 70 | res.put("method", Utils.getJavaName(m)); 71 | res.put("ishooked", unhook!=null); 72 | res.put("relatedObjects",new JSONArray()); 73 | trySend(res.toJSONString()); 74 | } 75 | 76 | @Override 77 | protected void onClose(NanoWSD.WebSocketFrame.CloseCode code, String reason, boolean initiatedByRemote) { 78 | // TODO: should unhook on close 79 | } 80 | @Override 81 | protected void onMessage(NanoWSD.WebSocketFrame message) {} 82 | @Override 83 | protected void onPong(NanoWSD.WebSocketFrame pong) {} 84 | @Override 85 | protected void onException(IOException exception) {} 86 | } 87 | 88 | public class MethodHook extends XServer_MethodHook { 89 | public Member method; //被Hook的方法 90 | public Object thisObject; //方法被调用时的this对象 91 | public Object[] args; //方法被调用时的参数 92 | private Object result = null; //方法被调用后的返回结果 93 | private int tid = 0; 94 | 95 | MethodHook(Method method) { HookHandler.getProvider().hookMethod(method, this); } 96 | 97 | public void setTid(int tid) { 98 | this.tid = tid; 99 | } 100 | 101 | private void gatherInfo(XServer_Param param) { 102 | method = param.method; 103 | thisObject = param.thisObject; 104 | args = param.args; 105 | } 106 | 107 | public void beforeHookedMethod(XServer_Param param) throws Throwable { 108 | super.beforeHookedMethod(param); 109 | if (tid > 0 && tid != Process.myPid()) return; 110 | gatherInfo(param); 111 | 112 | // 如果是自己调用的路过的信息,就忽略 113 | if(Invoke_New.isMe())return; 114 | 115 | // 先把信息通过WebSocket发送出去 116 | // 详细信息去ObjectManager查 117 | Method m = methods.get(wss.get(this)); 118 | JSONObject res = new JSONObject(); 119 | JSONArray params = new JSONArray(); 120 | for (Object arg:param.args) { params.add(ObjectHandler.saveObject(arg)); } 121 | res.put("type","before"); 122 | res.put("method", Utils.getJavaName(m)); 123 | res.put("this", ObjectHandler.saveObject(param.thisObject)); 124 | res.put("params",params); 125 | res.put("stack", Thread.currentThread().getStackTrace()); 126 | res.put("isproxy",Invoke_New.isMe()); 127 | res.put("threadid", Process.myTid()); 128 | wss.get(this).trySend(res.toJSONString()); 129 | 130 | if (wss.get(this).modify) { 131 | JSONObject call = new JSONObject(); 132 | 133 | call.put("method", Utils.getJavaName(m)); 134 | if(thisObject!=null)call.put("this",ObjectHandler.saveObject(thisObject)); 135 | call.put("params",params); 136 | ArrayList stacks=new ArrayList(); 137 | for (StackTraceElement element:Thread.currentThread().getStackTrace()) { 138 | stacks.add(element.getClassName() + "." + element.getMethodName() + " : " + element.getLineNumber()); 139 | } 140 | call.put("stackTrace",stacks); 141 | Object result = ObjectHandler.parseObject(new netUtil(server + "/invoke2", new org.json.JSONObject(call.toJSONString()).toString(2)).getRet()); 142 | Log.e("[XServer Debug]", "result: " + result); 143 | if(result instanceof Invoke_New.XServerWrappedThrowable){ 144 | if(((Invoke_New.XServerWrappedThrowable)result).shouldPassthough) 145 | param.setThrowable(((Invoke_New.XServerWrappedThrowable)result).getThrowable()); 146 | } 147 | else param.setResult(result); 148 | } 149 | } 150 | 151 | public void afterHookedMethod(XServer_Param param) throws Throwable { 152 | super.beforeHookedMethod(param); 153 | if (tid > 0 && tid != Process.myPid()) return; 154 | gatherInfo(param); 155 | // 如果是自己调用的路过的信息,就忽略 156 | if(Invoke_New.isMe())return; 157 | Method m = methods.get(wss.get(this)); 158 | result = param.getResult(); 159 | JSONObject res = new JSONObject(); 160 | res.put("type","after"); 161 | res.put("method", Utils.getJavaName(m)); 162 | res.put("isproxy",Invoke_New.isMe()); 163 | res.put("threadid", Process.myTid()); 164 | 165 | if(param.getThrowable() == null){ 166 | res.put("result", ObjectHandler.saveObject(param.getResult())); 167 | }else { 168 | res.put("throws",ObjectHandler.briefObject(param.getThrowable())); 169 | } 170 | 171 | wss.get(this).trySend(res.toJSONString()); 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/api/wsTracerNew.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.api; 2 | 3 | import android.os.Process; 4 | import android.util.Log; 5 | 6 | import com.alibaba.fastjson.JSON; 7 | import com.alibaba.fastjson.JSONArray; 8 | import com.alibaba.fastjson.JSONObject; 9 | 10 | import java.io.IOException; 11 | import java.lang.reflect.Method; 12 | import java.util.ArrayList; 13 | import java.util.HashMap; 14 | 15 | import monkeylord.XServer.XServer; 16 | import monkeylord.XServer.handler.ClassHandler; 17 | import monkeylord.XServer.handler.Hook.Unhook; 18 | import monkeylord.XServer.handler.Hook.XServer_MethodHook; 19 | import monkeylord.XServer.handler.Hook.XServer_Param; 20 | import monkeylord.XServer.handler.HookHandler; 21 | import monkeylord.XServer.handler.MethodHandler; 22 | import monkeylord.XServer.handler.ObjectHandler; 23 | import monkeylord.XServer.utils.DexHelper; 24 | import monkeylord.XServer.utils.NanoHTTPD; 25 | import monkeylord.XServer.utils.NanoWSD; 26 | import monkeylord.XServer.utils.Utils; 27 | 28 | //MassTracer的WebSocket处理重构,交互式。 29 | //TODO 完成增删改查,兼容之前接口,然后替换。 30 | public class wsTracerNew implements XServer.wsOperation { 31 | final boolean unhookOnClose = true; 32 | HashMap unhooks = new HashMap<>(); 33 | ws websocket; 34 | hook hook; 35 | 36 | @Override 37 | public NanoWSD.WebSocket handle(NanoHTTPD.IHTTPSession handshake) { websocket = new ws(handshake);hook = new hook();return websocket; } 38 | 39 | public void handleMessage(String msg){ 40 | JSONObject req = JSON.parseObject(msg); 41 | try { 42 | JSONObject call = new JSONObject(); 43 | String[] methods ={}; 44 | String[] classes = {}; 45 | ArrayList clzArr = new ArrayList<>(); 46 | ArrayList methodArr = new ArrayList<>(); 47 | ArrayList errArr = new ArrayList<>(); 48 | switch ((String) req.get("type")) { 49 | case "hook": 50 | methods = ((JSONArray) req.get("methods")).toArray(methods); 51 | for (String mtd:methods) { 52 | try { 53 | Method m = MethodHandler.getMethodbyJavaName(mtd); 54 | if (!unhooks.containsKey(mtd)) 55 | unhooks.put(mtd, HookHandler.getProvider().hookMethod(m, hook)); 56 | }catch (Throwable e){ 57 | e.printStackTrace(); 58 | errArr.add(mtd+":"+e.getMessage()); 59 | } 60 | } 61 | call.put("type","hook"); 62 | call.put("errors",errArr); 63 | call.put("hooks",unhooks.keySet()); 64 | break; 65 | case "hookClass": 66 | classes = ((JSONArray) req.get("classes")).toArray(methods); 67 | for (String clzname:classes) { 68 | try { 69 | Class clz = Class.forName(clzname, false, XServer.classLoader); 70 | for (Method m : clz.getDeclaredMethods()) { 71 | String mtd = Utils.getJavaName(m); 72 | if (!unhooks.containsKey(mtd)) 73 | unhooks.put(mtd, HookHandler.getProvider().hookMethod(m, hook)); 74 | } 75 | }catch (Throwable e){ 76 | e.printStackTrace(); 77 | errArr.add(clzname+":"+e.getMessage()); 78 | } 79 | } 80 | call.put("type","hook"); 81 | call.put("errors",errArr); 82 | call.put("hooks",unhooks.keySet()); 83 | break; 84 | case "unhook": 85 | methods = ((JSONArray) req.get("methods")).toArray(methods); 86 | for (String mtd:methods) { 87 | if(unhooks.containsKey(mtd))unhooks.remove(mtd).unhook(); 88 | } 89 | call.put("type","unhook"); 90 | call.put("hooks",unhooks.keySet()); 91 | break; 92 | case "classes": 93 | call.put("type","classes"); 94 | call.put("classes",DexHelper.getClassesInDex(XServer.classLoader)); 95 | break; 96 | case "methods": 97 | classes = ((JSONArray) req.get("classes")).toArray(classes); 98 | for (String clz:classes) { 99 | try{ 100 | for (Method method:Class.forName(clz,false,XServer.classLoader).getDeclaredMethods()) { 101 | methodArr.add(Utils.getJavaName(method)); 102 | } 103 | clzArr.add(clz); 104 | }catch (Throwable e){ 105 | e.printStackTrace(); 106 | errArr.add(clz+" "+e.getLocalizedMessage()); 107 | } 108 | } 109 | call.put("type","methods"); 110 | call.put("methods",methodArr); 111 | call.put("errors",errArr); 112 | call.put("classes",clzArr); 113 | break; 114 | } 115 | websocket.trySend(call.toJSONString()); 116 | }catch (Exception e){ 117 | e.printStackTrace(); 118 | } 119 | Log.e("[XServer Debug]", (String)req.get("type")); 120 | } 121 | 122 | // 包装一下就好,将显示相关的内容交给前端去处理 123 | private class hook extends XServer_MethodHook { 124 | boolean loopLockBefore = false; 125 | boolean loopLockAfter = false; 126 | 127 | @Override 128 | public void beforeHookedMethod(XServer_Param param) throws Throwable { 129 | super.beforeHookedMethod(param); 130 | if(loopLockBefore || loopLockAfter){ 131 | Log.e("[XServer Debug]", "Avoiding Loop on " + Utils.getJavaName((Method) param.method)); 132 | return; 133 | } 134 | try { 135 | loopLockBefore = true; 136 | JSONObject call = new JSONObject(); 137 | call.put("type", "call"); 138 | call.put("tid", Process.myTid()); 139 | call.put("elapsed", Process.getElapsedCpuTime()); 140 | call.put("method", Utils.getJavaName((Method) param.method)); 141 | call.put("this", ObjectHandler.briefObject(param.thisObject)); 142 | JSONArray params = new JSONArray(); 143 | for (Object arg : param.args) { 144 | params.add(ObjectHandler.briefObject(arg)); 145 | } 146 | call.put("params", params); 147 | websocket.trySend(call.toJSONString()); 148 | }finally { 149 | loopLockBefore = false; 150 | } 151 | } 152 | 153 | @Override 154 | public void afterHookedMethod(XServer_Param param) throws Throwable { 155 | super.afterHookedMethod(param); 156 | if(loopLockBefore || loopLockAfter){ 157 | Log.e("[XServer Debug]", "Avoiding Loop on " + Utils.getJavaName((Method) param.method)); 158 | return; 159 | } 160 | try { 161 | loopLockAfter = true; 162 | JSONObject result = new JSONObject(); 163 | result.put("type","result"); 164 | result.put("tid", Process.myTid()); 165 | result.put("elapsed", Process.getElapsedCpuTime()); 166 | result.put("method",Utils.getJavaName((Method) param.method)); 167 | result.put("this", ObjectHandler.briefObject(param.thisObject)); 168 | result.put("result",ObjectHandler.briefObject(param.getResult())); 169 | if(param.hasThrowable())result.put("throw",ObjectHandler.briefObject(param.getThrowable())); 170 | 171 | websocket.trySend(result.toJSONString()); 172 | }finally { 173 | loopLockAfter = false; 174 | } 175 | } 176 | } 177 | 178 | private class ws extends NanoWSD.WebSocket { 179 | public ws(NanoHTTPD.IHTTPSession handshakeRequest) { 180 | super(handshakeRequest); 181 | } 182 | public void trySend(String payload) {try {send(payload);} catch (IOException e) {e.printStackTrace();}} 183 | @Override 184 | protected void onMessage(NanoWSD.WebSocketFrame message) { handleMessage(message.getTextPayload()); } 185 | @Override 186 | protected void onOpen() { 187 | trySend("{\"type\":\"message\",\"message\":\"XServer wsTrace Connected.\"}"); 188 | // 返回所有类 189 | JSONObject classes = new JSONObject(); 190 | classes.put("type","classes"); 191 | classes.put("classes",ClassHandler.getAllClasses(XServer.classLoader)); 192 | } 193 | @Override 194 | protected void onClose(NanoWSD.WebSocketFrame.CloseCode code, String reason, boolean initiatedByRemote) { 195 | if (unhookOnClose) { 196 | for (String methodname : unhooks.keySet()) { 197 | unhooks.remove(methodname).unhook(); 198 | } 199 | } 200 | } 201 | @Override 202 | protected void onPong(NanoWSD.WebSocketFrame pong) {} 203 | @Override 204 | protected void onException(IOException exception) {} 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/handler/ClassHandler.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.handler; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | 5 | import java.lang.reflect.Field; 6 | import java.lang.reflect.Member; 7 | import java.lang.reflect.Method; 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | import java.util.Random; 12 | 13 | import monkeylord.XServer.XServer; 14 | import monkeylord.XServer.handler.Hook.XServer_MethodHook; 15 | import monkeylord.XServer.handler.Hook.XServer_Param; 16 | import monkeylord.XServer.utils.DexHelper; 17 | import monkeylord.XServer.utils.Utils; 18 | 19 | //处理类相关的内容 20 | public class ClassHandler { 21 | public static HashMap classLoaders = new HashMap<>(); 22 | 23 | public static boolean monitorClassloaders(final ClassLoader classLoader){ 24 | try { 25 | Class classLoaderClz = ClassLoader.class; 26 | Member m = classLoaderClz.getDeclaredConstructor(ClassLoader.class); 27 | HookHandler.getProvider().hookMethod(m, new XServer_MethodHook() { 28 | @Override 29 | public void beforeHookedMethod(XServer_Param param) throws Throwable { 30 | super.beforeHookedMethod(param); 31 | classLoaders.put("classloader"+ param.thisObject.hashCode(), (ClassLoader) param.thisObject); 32 | if(((ClassLoader) param.thisObject).getParent()!=null)classLoaders.put("classloader"+ ((ClassLoader) param.thisObject).getParent().hashCode(), (ClassLoader) ((ClassLoader) param.thisObject).getParent()); 33 | } 34 | }); 35 | return true; 36 | }catch (Exception e){ 37 | e.printStackTrace(); 38 | return false; 39 | } 40 | } 41 | 42 | public static boolean setXServerClassloader(String name){ 43 | if(classLoaders.get(name)!=null){ 44 | XServer.classLoader = classLoaders.get(name); 45 | return true; 46 | } 47 | return false; 48 | } 49 | 50 | public static HashMap getClassLoaderDescriptions(){ 51 | HashMap map = new HashMap<>(); 52 | for (Map.Entry entry:classLoaders.entrySet()) { 53 | try { 54 | map.put(entry.getKey(), entry.getValue().toString()); 55 | }catch (Exception e){ 56 | e.printStackTrace(); 57 | map.put(entry.getKey(),entry.getValue().getClass().getName()); 58 | } 59 | } 60 | return map; 61 | } 62 | 63 | public static String[] getAllClasses(ClassLoader classLoader) { 64 | return DexHelper.getClassesInDex(classLoader); 65 | } 66 | 67 | public static HashMap getClassDetail(Class clz) { 68 | HashMap detail = new HashMap(); 69 | if (clz == null) return detail; 70 | detail.put("name", clz.getName()); 71 | detail.put("methods", clz.getDeclaredMethods()); 72 | detail.put("fields", clz.getDeclaredFields()); 73 | detail.put("json", JSON.toJSON(clz)); 74 | ArrayList methodDescriptions = new ArrayList(); 75 | ArrayList fieldDescriptions = new ArrayList(); 76 | for (Method method : clz.getDeclaredMethods()) { 77 | methodDescriptions.add(Utils.MethodDescription(method)); 78 | } 79 | for (Field field : clz.getFields()) { 80 | fieldDescriptions.add(Utils.FieldDescription(field)); 81 | } 82 | detail.put("methodList", methodDescriptions); 83 | detail.put("fieldList", fieldDescriptions); 84 | 85 | return detail; 86 | } 87 | 88 | public static Class findClassbyName(String clzName, ClassLoader classLoader) { 89 | try { 90 | return Class.forName(clzName, false, classLoader); 91 | } catch (ClassNotFoundException e) { 92 | return null; 93 | } 94 | } 95 | 96 | public static Class findClassbyJavaNameEx(String javaName, ClassLoader classLoader) { 97 | try { 98 | String clzName = javaName.replace('/', '.').replace(";", ""); 99 | clzName = (clzName.charAt(0) == 'L') ? clzName.substring(1) : clzName; 100 | return Class.forName(clzName, false, classLoader); 101 | } catch (ClassNotFoundException e) { 102 | return null; 103 | } 104 | } 105 | 106 | /* 107 | * copied from libcore.reflect.InternalNames 108 | * */ 109 | public static java.lang.Class findClassbyJavaName(java.lang.String internalName,java.lang.ClassLoader classLoader) { 110 | if (internalName.startsWith("[")) { 111 | return java.lang.reflect.Array.newInstance(findClassbyJavaName(internalName.substring(1), classLoader), 0).getClass(); 112 | } 113 | if (internalName.equals("Z")) { 114 | return java.lang.Boolean.TYPE; 115 | } 116 | if (internalName.equals("B")) { 117 | return java.lang.Byte.TYPE; 118 | } 119 | if (internalName.equals("S")) { 120 | return java.lang.Short.TYPE; 121 | } 122 | if (internalName.equals("I")) { 123 | return java.lang.Integer.TYPE; 124 | } 125 | if (internalName.equals("J")) { 126 | return java.lang.Long.TYPE; 127 | } 128 | if (internalName.equals("F")) { 129 | return java.lang.Float.TYPE; 130 | } 131 | if (internalName.equals("D")) { 132 | return java.lang.Double.TYPE; 133 | } 134 | if (internalName.equals("C")) { 135 | return java.lang.Character.TYPE; 136 | } 137 | if (internalName.equals("V")) { 138 | return java.lang.Void.TYPE; 139 | } 140 | java.lang.String name = internalName.substring(1, internalName.length() - 1).replace('/', '.'); 141 | try { 142 | return classLoader.loadClass(name); 143 | } catch (java.lang.ClassNotFoundException e) { 144 | java.lang.NoClassDefFoundError error = new java.lang.NoClassDefFoundError(name); 145 | error.initCause(e); 146 | throw error; 147 | } 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/handler/Hook/DummyProvider.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.handler.Hook; 2 | 3 | import java.lang.reflect.Member; 4 | 5 | import monkeylord.XServer.handler.HookHandler; 6 | 7 | // This is a dummy provider, if a hook framework cannot register its own class, it can hook and use this provider. 8 | public class DummyProvider implements HookHandler.HookProvider { 9 | @Override 10 | public Unhook hookMethod(Member hookMethod, XServer_MethodHook callback) { 11 | return null; 12 | } 13 | 14 | @Override 15 | public void unhookMethod(Member hookMethod, Object additionalObj) { 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/handler/Hook/Unhook.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.handler.Hook; 2 | 3 | import monkeylord.XServer.handler.HookHandler; 4 | 5 | public class Unhook { 6 | private final java.lang.reflect.Member hookMethod; 7 | private final Object additionalObj; 8 | 9 | public Unhook(java.lang.reflect.Member hookMethod, Object additionalObj) { 10 | this.hookMethod = hookMethod; 11 | this.additionalObj = additionalObj; 12 | } 13 | public void unhook() { 14 | HookHandler.getProvider().unhookMethod(this.hookMethod, this.additionalObj); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/handler/Hook/XServer_MethodHook.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.handler.Hook; 2 | 3 | public abstract class XServer_MethodHook { 4 | public void beforeHookedMethod(XServer_Param param) throws Throwable {} 5 | public void afterHookedMethod(XServer_Param param) throws Throwable {} 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/handler/Hook/XServer_Param.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.handler.Hook; 2 | 3 | public class XServer_Param { 4 | // Copied from Xposed MethodHookParam 5 | public java.lang.Object[] args; 6 | public java.lang.reflect.Member method; 7 | public java.lang.Object result = null; 8 | public boolean returnEarly = false; 9 | public java.lang.Object thisObject; 10 | public java.lang.Throwable throwable = null; 11 | 12 | public java.lang.Object getResult() { 13 | return this.result; 14 | } 15 | 16 | public void setResult(java.lang.Object result) { 17 | this.result = result; 18 | this.throwable = null; 19 | this.returnEarly = true; 20 | } 21 | 22 | public java.lang.Throwable getThrowable() { 23 | return this.throwable; 24 | } 25 | 26 | public boolean hasThrowable() { 27 | return this.throwable != null; 28 | } 29 | 30 | public void setThrowable(java.lang.Throwable throwable) { 31 | this.throwable = throwable; 32 | this.result = null; 33 | this.returnEarly = true; 34 | } 35 | 36 | public java.lang.Object getResultOrThrowable() throws java.lang.Throwable { 37 | if (this.throwable == null) { 38 | return this.result; 39 | } 40 | throw this.throwable; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/handler/HookHandler.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.handler; 2 | 3 | import monkeylord.XServer.handler.Hook.Unhook; 4 | import monkeylord.XServer.handler.Hook.XServer_MethodHook; 5 | 6 | //处理hook相关内容 7 | public class HookHandler { 8 | static HookProvider provider = null; 9 | 10 | public static HookProvider getProvider() throws NullPointerException{ 11 | if(provider==null)throw new NullPointerException("No provider available"); 12 | return provider; 13 | } 14 | 15 | public static boolean setProvider(HookProvider newProvider){ 16 | if(provider!=null || newProvider==null)return false; 17 | provider=newProvider; 18 | return true; 19 | } 20 | 21 | public interface HookProvider{ 22 | Unhook hookMethod(java.lang.reflect.Member hookMethod, XServer_MethodHook callback); 23 | void unhookMethod(java.lang.reflect.Member hookMethod, Object additionalObj); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/handler/InterceptionHandler.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.handler; 2 | 3 | import android.util.Log; 4 | 5 | import com.alibaba.fastjson.JSONArray; 6 | import com.alibaba.fastjson.JSONObject; 7 | 8 | import java.io.File; 9 | import java.lang.reflect.Method; 10 | import java.util.ArrayList; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | 14 | import monkeylord.XServer.api.Invoke_New; 15 | import monkeylord.XServer.handler.Hook.Unhook; 16 | import monkeylord.XServer.handler.Hook.XServer_MethodHook; 17 | import monkeylord.XServer.handler.Hook.XServer_Param; 18 | import monkeylord.XServer.utils.Utils; 19 | import monkeylord.XServer.utils.netUtil; 20 | 21 | public class InterceptionHandler { 22 | static InterceptionHandler instance = null; 23 | ArrayList shouldIntercept = new ArrayList(); 24 | ArrayList methods = new ArrayList<>(); 25 | File logFile; 26 | HashMap hooks = new HashMap(); 27 | Interceptor interceptor; 28 | 29 | final String server = "http://127.0.0.1:8000"; 30 | 31 | InterceptionHandler(){ 32 | interceptor = new Interceptor(); 33 | try { 34 | Class DexFile = Class.forName("dalvik.system.DexFile"); 35 | Method defineClass = DexFile.getDeclaredMethod("defineClass",String.class, ClassLoader.class, long.class, List.class); 36 | HookHandler.getProvider().hookMethod(defineClass, new XServer_MethodHook() { 37 | @Override 38 | public void afterHookedMethod(XServer_Param param) throws Throwable { 39 | super.afterHookedMethod(param); 40 | if(param.getResult()==null)return; 41 | Log.e("TEST", param.args[0].toString()); 42 | if(shouldIntercept.contains(param.args[0].toString())){ 43 | Class loadedClass = (Class) param.getResult(); 44 | for (Method method : loadedClass.getDeclaredMethods()) { 45 | if(methods.contains(Utils.getJavaName(method)))intercept(method); 46 | } 47 | } 48 | } 49 | }); 50 | } catch (ClassNotFoundException e) { 51 | e.printStackTrace(); 52 | } catch (NoSuchMethodException e) { 53 | e.printStackTrace(); 54 | } 55 | 56 | } 57 | 58 | public void setInterceptMethods(String[] itcpMethods){ 59 | for (String itcpMethod : itcpMethods) { 60 | shouldIntercept.add(itcpMethod.substring(itcpMethod.indexOf("L")+1,itcpMethod.indexOf(";"))); 61 | methods.add(itcpMethod); 62 | } 63 | } 64 | 65 | public static InterceptionHandler getInstance(){ 66 | if(instance==null)instance = new InterceptionHandler(); 67 | return instance; 68 | } 69 | 70 | public void intercept(Method method){ 71 | String javaname = Utils.getJavaName(method); 72 | Log.e("TEST", "intercept: "+javaname); 73 | if(hooks.get(javaname)!=null)return; 74 | else hooks.put(javaname,HookHandler.getProvider().hookMethod(method, interceptor)); 75 | } 76 | 77 | public void stopIntercept(Method method){ 78 | String javaname = Utils.getJavaName(method); 79 | if(hooks.get(javaname)==null)return; 80 | else hooks.remove(javaname).unhook(); 81 | } 82 | 83 | class Interceptor extends XServer_MethodHook{ 84 | @Override 85 | public void beforeHookedMethod(XServer_Param param) throws Throwable { 86 | super.beforeHookedMethod(param); 87 | if(Invoke_New.isMe())return; 88 | 89 | JSONObject call = new JSONObject(); 90 | JSONArray params = new JSONArray(); 91 | for (Object arg:param.args) { params.add(ObjectHandler.saveObject(arg)); } 92 | call.put("method", Utils.getJavaName((Method) param.method)); 93 | if(param.thisObject!=null)call.put("this",ObjectHandler.saveObject(param.thisObject)); 94 | call.put("params",params); 95 | ArrayList stacks=new ArrayList(); 96 | for (StackTraceElement element:Thread.currentThread().getStackTrace()) { 97 | stacks.add(element.getClassName() + "." + element.getMethodName() + " : " + element.getLineNumber()); 98 | } 99 | call.put("stackTrace",stacks); 100 | Object result = ObjectHandler.parseObject(new netUtil(server + "/invoke2", new org.json.JSONObject(call.toJSONString()).toString(2)).getRet()); 101 | Log.e("[XServer Debug]", "result: " + result); 102 | if(result instanceof Invoke_New.XServerWrappedThrowable){ 103 | if(((Invoke_New.XServerWrappedThrowable)result).shouldPassthough) 104 | param.setThrowable(((Invoke_New.XServerWrappedThrowable)result).getThrowable()); 105 | } 106 | else param.setResult(result); 107 | } 108 | } 109 | 110 | 111 | 112 | } 113 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/handler/MemoryHandler.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.handler; 2 | 3 | import android.system.ErrnoException; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.File; 7 | import java.io.FileDescriptor; 8 | import java.io.FileInputStream; 9 | import java.io.IOException; 10 | import java.io.InputStreamReader; 11 | import java.lang.reflect.Constructor; 12 | import java.lang.reflect.InvocationTargetException; 13 | import java.lang.reflect.Method; 14 | import java.util.HashMap; 15 | 16 | public class MemoryHandler { 17 | static HashMap Memory = new HashMap<>(); 18 | static HashMap OS = new HashMap<>(); 19 | static Object osObject = null; 20 | static { 21 | try { 22 | // http://androidxref.com/4.4_r1/xref/libcore/luni/src/main/java/libcore/io/Memory.java 23 | Class clzMemory = Class.forName("libcore.io.Memory"); 24 | for (Method m:clzMemory.getDeclaredMethods()) { 25 | Memory.put(m.getName(),m); 26 | } 27 | // http://androidxref.com/9.0.0_r3/xref/libcore/luni/src/main/java/android/system/Os.java 28 | Class clzOS = null; 29 | try { 30 | clzOS = Class.forName("libcore.io.Linux"); 31 | }catch (ClassNotFoundException e){ 32 | // for Android 4.4 33 | clzOS = Class.forName("libcore.io.Posix"); 34 | } 35 | Constructor osConstructor = clzOS.getDeclaredConstructor(); 36 | osConstructor.setAccessible(true); 37 | osObject = osConstructor.newInstance(); 38 | for (Method m:clzOS.getDeclaredMethods()) { 39 | OS.put(m.getName(),m); 40 | } 41 | } catch (ClassNotFoundException e) { 42 | e.printStackTrace(); 43 | } catch (InstantiationException e) { 44 | e.printStackTrace(); 45 | } catch (InvocationTargetException e) { 46 | e.printStackTrace(); 47 | } catch (NoSuchMethodException e) { 48 | e.printStackTrace(); 49 | } catch (IllegalAccessException e) { 50 | e.printStackTrace(); 51 | } 52 | } 53 | public static byte[] readMemory(long address,int count) throws InvocationTargetException, IllegalAccessException { 54 | if(Memory.get("peekByteArray")==null)throw new IllegalAccessException(); 55 | byte[] data=new byte[count]; 56 | Memory.get("peekByteArray").invoke(null,address, data, 0, count); 57 | return data; 58 | } 59 | public static void writeMemory(long address,byte[] data) throws IllegalAccessException, InvocationTargetException { 60 | if(Memory.get("pokeByteArray")==null)throw new IllegalAccessException(); 61 | byte[] mem = readMemory(address,data.length); 62 | for (int i = 0; i < mem.length; i++) { 63 | if(mem[i]!=data[i]){ 64 | Memory.get("pokeByte").invoke(null,address+i,data[i]); 65 | } 66 | } 67 | //Memory.get("pokeByteArray").invoke(null,address, mem, 0, mem.length); 68 | } 69 | 70 | public static long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws IllegalAccessException, InvocationTargetException { 71 | if(OS.get("mmap")==null)throw new IllegalAccessException(); 72 | return (long)OS.get("mmap").invoke(osObject,address,byteCount,prot,flags,fd,offset); 73 | } 74 | public static void munmap(long address, long byteCount) throws IllegalAccessException, InvocationTargetException { 75 | if(OS.get("munmap")==null)throw new IllegalAccessException(); 76 | OS.get("munmap").invoke(osObject,address,byteCount); 77 | } 78 | 79 | public static String[] getMaps(){ 80 | File maps=new File("/proc/self/maps"); 81 | StringBuilder sb = new StringBuilder(); 82 | try { 83 | FileInputStream is = new FileInputStream(maps); 84 | BufferedReader reader = new BufferedReader(new InputStreamReader(is)); 85 | String str; 86 | while((str=reader.readLine())!=null){ 87 | sb.append(str+"\r\n"); 88 | } 89 | /* 90 | Process p = Runtime.getRuntime().exec("cat /proc/self/maps"); 91 | p.waitFor(); 92 | BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); 93 | String str; 94 | while((str=reader.readLine())!=null){ 95 | sb.append(str+"\r\n"); 96 | } 97 | */ 98 | } catch (IOException e) { 99 | e.printStackTrace(); 100 | } 101 | String[] mapstrs=sb.toString().split("\r\n"); 102 | for (int i = 0; i < mapstrs.length; i++) { 103 | String[] addrs =mapstrs[i].substring(0,17).split("-"); 104 | long size = Long.parseLong(addrs[1],16)-Long.parseLong(addrs[0],16); 105 | mapstrs[i]=mapstrs[i].replaceFirst(" ",":"+Long.toHexString(size)+" "); 106 | } 107 | return mapstrs; 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/handler/MethodHandler.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.handler; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.serializer.SerializerFeature; 5 | 6 | import java.lang.reflect.Method; 7 | import java.util.ArrayList; 8 | import java.util.HashMap; 9 | 10 | import monkeylord.XServer.XServer; 11 | import monkeylord.XServer.utils.Utils; 12 | 13 | //处理方法相关内容 14 | public class MethodHandler { 15 | public static HashMap getMethodDetail(Method method) { 16 | HashMap detail = new HashMap(); 17 | detail.put("name", method.getName()); 18 | detail.put("class", method.getDeclaringClass().getName()); 19 | detail.put("javaName", Utils.getJavaName(method)); 20 | detail.put("parametersTypes", method.getParameterTypes()); 21 | detail.put("returnType", method.getReturnType()); 22 | detail.put("exceptionTypes", method.getExceptionTypes()); 23 | detail.put("description", Utils.MethodDescription(method)); 24 | detail.put("json", JSON.toJSONString(method, SerializerFeature.PrettyFormat)); 25 | return detail; 26 | } 27 | 28 | public static Method getMethod(String className, String methodName) { 29 | try { 30 | for (Method method : Class.forName(className, false, XServer.classLoader).getMethods()) { 31 | if (method.getName().equals(methodName)) return method; 32 | } 33 | return null; 34 | } catch (ClassNotFoundException e) { 35 | e.printStackTrace(); 36 | return null; 37 | } 38 | } 39 | 40 | public static Method getMethodbyJavaName(String javaName) { 41 | String classname=javaName.substring(0,javaName.indexOf("->")); 42 | String method=javaName.substring(javaName.indexOf("->")+2); 43 | Class clz=ClassHandler.findClassbyJavaName(classname,XServer.classLoader); 44 | if(clz==null)return null; 45 | for (Method m:clz.getDeclaredMethods()) { 46 | if(Utils.getJavaName(m).equals(javaName))return m; 47 | } 48 | return null; 49 | } 50 | 51 | public static Method[] getMethodsInClasses(Class[] clzs) { 52 | ArrayList methods = new ArrayList(); 53 | for (Class clazz : clzs) { 54 | for (Method method : clazz.getDeclaredMethods()) { 55 | methods.add(method); 56 | } 57 | } 58 | return (Method[]) methods.toArray(); 59 | } 60 | 61 | static Object invokeMethod(Method method, Object thisObj, Object[] params) { 62 | try { 63 | method.setAccessible(true); 64 | return ObjectHandler.storeObject(method.invoke(thisObj, params), "invokeResult"); 65 | } catch (Exception e) { 66 | e.printStackTrace(); 67 | return null; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/handler/ObjectHandler.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.handler; 2 | 3 | import java.io.Console; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import java.util.Random; 7 | import java.util.regex.Pattern; 8 | 9 | import monkeylord.XServer.XServer; 10 | import monkeylord.XServer.objectparser.GenericParser; 11 | import monkeylord.XServer.objectparser.StoredObjectParser; 12 | import monkeylord.XServer.utils.Utils; 13 | 14 | import static monkeylord.XServer.XServer.parsers; 15 | 16 | //处理对象相关内容 17 | public class ObjectHandler { 18 | public static HashMap objects = new HashMap(); 19 | 20 | public static Object storeObject(Object obj, String name) { 21 | return objects.put(name, obj); 22 | } 23 | 24 | public static String saveObject(Object obj){ 25 | if(obj==null)return "Null"; 26 | XServer.ObjectParser parser = parsers.get(Utils.getTypeSignature(obj.getClass())); 27 | if(parser==null)parser=parsers.get("store"); 28 | return Utils.getTypeSignature(obj.getClass())+"#"+parser.generate(obj); 29 | } 30 | public static String briefObject(Object obj){ 31 | if(obj==null)return "Null"; 32 | XServer.ObjectParser parser = parsers.get(Utils.getTypeSignature(obj.getClass())); 33 | if(parser==null)parser=parsers.get("generic"); 34 | return Utils.getTypeSignature(obj.getClass())+"#"+parser.generate(obj); 35 | } 36 | 37 | public static Object getObject(String name) { 38 | return objects.get(name); 39 | } 40 | 41 | public static Object parseObject(String Object){ 42 | if(Object.equals("Null"))return null; 43 | if(Object==null)return null; 44 | if(Object.indexOf("#")<0)return null; 45 | String type=Object.substring(0,Object.indexOf("#")); 46 | String raw=Object.substring(Object.indexOf("#")+1); 47 | XServer.ObjectParser parser = parsers.get(type); 48 | if(parser==null)parser=parsers.get("store"); 49 | return parser.parse(raw); 50 | } 51 | 52 | public static Object[] getObjects(String name, String type) { 53 | return null; 54 | } 55 | 56 | public static Object removeObject(String name) { 57 | return objects.remove(name); 58 | } 59 | 60 | public static Object removeObject(Object object) { 61 | for (Map.Entry entry : objects.entrySet()) { 62 | if (entry.getValue().equals(object)) return objects.remove(entry.getKey()); 63 | } 64 | return null; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/objectparser/BooleanParser.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.objectparser; 2 | 3 | import monkeylord.XServer.XServer; 4 | 5 | public class BooleanParser implements XServer.ObjectParser{ 6 | 7 | @Override 8 | public Object parse(String data) { 9 | return Boolean.parseBoolean(data); 10 | } 11 | 12 | @Override 13 | public String generate(Object obj) { 14 | return obj.toString(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/objectparser/ByteArrayParser.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.objectparser; 2 | 3 | import android.util.Base64; 4 | import android.util.Log; 5 | 6 | import java.nio.Buffer; 7 | import java.util.Arrays; 8 | 9 | import monkeylord.XServer.XServer; 10 | 11 | public class ByteArrayParser implements XServer.ObjectParser { 12 | @Override 13 | public java.lang.Object parse(java.lang.String data) { 14 | if(data.indexOf('#')<0){ 15 | return Base64.decode(data, Base64.DEFAULT); 16 | }else{ 17 | String type=data.substring(0,data.indexOf("#")); 18 | String raw=data.substring(data.indexOf("#")+1); 19 | if(type.equals("raw"))return raw.getBytes(); 20 | else if(type.equals("base64"))return Base64.decode(raw, Base64.DEFAULT); 21 | else return URLDecode(raw); 22 | } 23 | } 24 | @Override 25 | public String generate(Object obj) { 26 | Log.d("ByteArray", ""+new String((byte[])obj).getBytes().length+":"+((byte[])obj).length); 27 | if(Arrays.equals((byte[])obj,new String((byte[])obj).getBytes()))return "raw#"+new String((byte[])obj); 28 | //else return "base64#"+Base64.encodeToString((byte[])obj,Base64.DEFAULT); 29 | else return "URLEncoding#"+URLEncode((byte[])obj); 30 | } 31 | private static final char[] HEX_CHAR = {'0', '1', '2', '3', '4', '5', 32 | '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; 33 | static public String URLEncode(byte[] bytes){ 34 | StringBuffer sb=new StringBuffer(); 35 | for (int i = 0; i < bytes.length; i++) { 36 | if((bytes[i]>='0'&&bytes[i]<='9')||(bytes[i]>='a'&&bytes[i]<='z')||(bytes[i]>='A'&&bytes[i]<='Z')||bytes[i]=='-'||bytes[i]=='_'||bytes[i]=='.'||bytes[i]=='~') 37 | sb.append((char)bytes[i]); 38 | else { 39 | sb.append('%'); 40 | sb.append(HEX_CHAR[(256+bytes[i])/16%16]); 41 | sb.append(HEX_CHAR[(256+bytes[i])%16]); 42 | } 43 | } 44 | return sb.toString(); 45 | } 46 | static public byte[] URLDecode(String str){ 47 | byte[] org=str.getBytes(); 48 | byte[] dst=new byte[org.length]; 49 | byte[] fin; 50 | int i=0,j=0; 51 | for (i = 0; i < org.length; i++) { 52 | if(org[i]=='%'){ 53 | dst[j]=(byte)Integer.parseInt(""+(char)org[i+1]+(char)org[i+2],16); 54 | j++; 55 | i=i+2; 56 | }else{ 57 | dst[j]=org[i]; 58 | j++; 59 | } 60 | } 61 | fin=new byte[j]; 62 | for (j--; j >=0 ; j--) { 63 | fin[j]=dst[j]; 64 | } 65 | return fin; 66 | } 67 | } -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/objectparser/CollectionParser.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.objectparser; 2 | 3 | import com.alibaba.fastjson.JSONArray; 4 | 5 | import java.util.Collection; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | 9 | import monkeylord.XServer.XServer; 10 | import monkeylord.XServer.handler.ObjectHandler; 11 | 12 | public class CollectionParser implements XServer.ObjectParser { 13 | public static HashMap objects = new HashMap(); 14 | @Override 15 | public Object parse(String data) { 16 | String name=data.substring(0,data.indexOf("=>")); 17 | String array=data.substring(data.indexOf("=>")+2); 18 | Collection collection = (Collection) objects.get(name); 19 | collection.clear(); 20 | for (Object item : JSONArray.parseArray(array)) { 21 | collection.add(ObjectHandler.parseObject(item.toString())); 22 | } 23 | return collection; 24 | } 25 | 26 | @Override 27 | public String generate(Object obj) { 28 | if(obj instanceof Collection){ 29 | JSONArray array = new JSONArray(); 30 | for (Object o : ((Collection) obj)) { 31 | array.add(ObjectHandler.saveObject(o)); 32 | } 33 | objects.put(""+obj.hashCode(),obj); 34 | return obj.hashCode()+"=>"+array.toJSONString(); 35 | }else return "Error Not Collection"; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/objectparser/GenericParser.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.objectparser; 2 | 3 | import monkeylord.XServer.XServer; 4 | 5 | public class GenericParser implements XServer.ObjectParser { 6 | @Override 7 | public java.lang.Object parse(java.lang.String data) { 8 | //无法还原 9 | return null; 10 | } 11 | 12 | @Override 13 | public String generate(Object obj) { 14 | return obj.toString(); 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/objectparser/HashMapParser.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.objectparser; 2 | 3 | import android.util.Log; 4 | 5 | import com.alibaba.fastjson.JSON; 6 | import com.alibaba.fastjson.JSONArray; 7 | import com.alibaba.fastjson.JSONObject; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | import java.util.Set; 12 | 13 | import monkeylord.XServer.XServer; 14 | import monkeylord.XServer.handler.ObjectHandler; 15 | 16 | import static monkeylord.XServer.XServer.parsers; 17 | 18 | public class HashMapParser implements XServer.ObjectParser { 19 | public static HashMap objects = new HashMap(); 20 | @Override 21 | public Object parse(String data) { 22 | //HashMap obj = new HashMap(); 23 | String name=data.substring(0,data.indexOf("=>")); 24 | String kvs=data.substring(data.indexOf("=>")+2); 25 | HashMap map = (HashMap) objects.get(name); 26 | if(map==null)map = new HashMap<>(); 27 | map.clear(); 28 | for (Map.Entry entry : JSON.parseObject(kvs).entrySet()) { 29 | map.put(ObjectHandler.parseObject(entry.getKey()),ObjectHandler.parseObject(entry.getValue().toString())); 30 | } 31 | return map; 32 | } 33 | 34 | @Override 35 | public String generate(Object obj) { 36 | HashMap map = (HashMap) obj; 37 | 38 | JSONObject jsonObject = new JSONObject(); 39 | for (Map.Entry entry: (Set)map.entrySet()) { 40 | Log.e("[XServer]", entry.getKey().toString()); 41 | Log.e("[XServer]", entry.getValue().toString()); 42 | jsonObject.put(ObjectHandler.saveObject(entry.getKey()),ObjectHandler.saveObject(entry.getValue())); 43 | } 44 | 45 | objects.put(""+obj.hashCode(),obj); 46 | Log.e("[XServer]", jsonObject.toJSONString()); 47 | Log.e("[XServer]", jsonObject.toString()); 48 | return obj.hashCode()+"=>"+jsonObject.toJSONString(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/objectparser/IntParser.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.objectparser; 2 | 3 | import monkeylord.XServer.XServer; 4 | 5 | public class IntParser implements XServer.ObjectParser { 6 | @Override 7 | public java.lang.Object parse(java.lang.String data) { 8 | return Integer.parseInt(data); 9 | } 10 | 11 | @Override 12 | public String generate(Object obj) { 13 | return obj.toString(); 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/objectparser/JSONParser.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.objectparser; 2 | 3 | import monkeylord.XServer.XServer; 4 | 5 | public class JSONParser implements XServer.ObjectParser { 6 | @Override 7 | public java.lang.Object parse(java.lang.String data) { 8 | //TODO 9 | return null; 10 | } 11 | 12 | @Override 13 | public String generate(Object obj) { 14 | return null; 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/objectparser/StoredObjectParser.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.objectparser; 2 | 3 | import android.util.Log; 4 | 5 | import com.alibaba.fastjson.JSON; 6 | 7 | import java.lang.reflect.Field; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | import java.util.Random; 11 | 12 | import monkeylord.XServer.XServer; 13 | import monkeylord.XServer.handler.ObjectHandler; 14 | import monkeylord.XServer.utils.Utils; 15 | 16 | import static monkeylord.XServer.XServer.parsers; 17 | 18 | public class StoredObjectParser implements XServer.ObjectParser { 19 | public static HashMap objects = new HashMap(); 20 | @Override 21 | public Object parse(String data) { 22 | String objname=data.substring(0,data.indexOf("=>")); 23 | String fieldmapStr=data.substring(data.indexOf("=>")+2); 24 | Object obj = objects.get(objname); 25 | if(obj==null)obj = ObjectHandler.objects.get(objname); 26 | if(obj==null)return null; 27 | HashMap fieldmap = (HashMap)JSON.parseObject(fieldmapStr,HashMap.class); 28 | if(!fieldmap.isEmpty()){ 29 | try { 30 | Class objClass = obj.getClass(); 31 | for (Map.Entry entry : fieldmap.entrySet()) { 32 | Field field = objClass.getDeclaredField(entry.getKey()); 33 | field.setAccessible(true); 34 | field.set(obj,ObjectHandler.parseObject(entry.getValue())); 35 | } 36 | }catch (Exception e){ 37 | Log.e("XServer", "SOParser: " + e.getLocalizedMessage() ); 38 | } 39 | } 40 | return obj; 41 | } 42 | 43 | @Override 44 | public String generate(Object obj) { 45 | String objname; 46 | try{ 47 | objname=""+obj.hashCode(); 48 | }catch (Exception e){ 49 | objname= ""+new Random().nextLong(); 50 | } 51 | HashMap fieldmap = new HashMap(); 52 | try{ 53 | Field fields[] = obj.getClass().getDeclaredFields(); 54 | for (Field field:fields) { 55 | // 只处理public属性 56 | // Handle public field only 57 | //if(field.isAccessible()){ 58 | field.setAccessible(true); 59 | Object fieldObj = field.get(obj); 60 | Log.i("XServer", "SOParser Field: " + field.getName() + "@" + Utils.getTypeSignature(fieldObj.getClass())); 61 | // 只处理原始类型,避免循环引用 62 | // Handle primitive type only, avoid cell. 63 | if(fieldObj.getClass().isPrimitive()||parsers.get(Utils.getTypeSignature(fieldObj.getClass()))!=null){ 64 | fieldmap.put(field.getName(),ObjectHandler.saveObject(fieldObj)); 65 | } 66 | //} 67 | } 68 | }catch (Exception e){ 69 | Log.e("XServer", "SOParser: " + e.getLocalizedMessage() ); 70 | } 71 | String fieldmapStr = JSON.toJSONString(fieldmap,true); 72 | objects.put(""+objname,obj); 73 | return objname+"=>"+fieldmapStr; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/objectparser/StringParser.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.objectparser; 2 | 3 | import monkeylord.XServer.XServer; 4 | 5 | public class StringParser implements XServer.ObjectParser { 6 | @Override 7 | public Object parse(java.lang.String data) { 8 | return data; 9 | } 10 | 11 | @Override 12 | public String generate(Object obj) { 13 | return obj.toString(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/utils/DexHelper.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.utils; 2 | 3 | import java.lang.reflect.Array; 4 | import java.lang.reflect.Field; 5 | import java.lang.reflect.Method; 6 | 7 | public class DexHelper { 8 | public static String[] getClassesInDex(ClassLoader CL) { 9 | String[] result = {}; 10 | try { 11 | Field field3 = CL.getClass().getSuperclass().getDeclaredField("pathList"); 12 | field3.setAccessible(true); 13 | Object pathList = field3.get(CL); 14 | Field field2 = pathList.getClass().getDeclaredField("dexElements"); 15 | field2.setAccessible(true); 16 | Object elements = field2.get(pathList); 17 | for (int i = 0; i < Array.getLength(elements); i++) { 18 | Object element = Array.get(elements, i); 19 | Field field1 = element.getClass().getDeclaredField("dexFile"); 20 | field1.setAccessible(true); 21 | Object DexFile = field1.get(element); 22 | //XposedBridge.log(DexFile.getClass().getName()); 23 | for (Method m : DexFile.getClass().getDeclaredMethods()) { 24 | // XposedBridge.log("DEXFILE:"+m.getName()); 25 | if (m.getName().equalsIgnoreCase("getClassNameList")) { 26 | m.setAccessible(true); 27 | Field field = DexFile.getClass().getDeclaredField("mCookie"); 28 | field.setAccessible(true); 29 | Object clist = m.invoke(DexFile, 30 | field.get(DexFile)); 31 | int length1 = result.length; 32 | int length2 = ((String[]) clist).length; 33 | int totalLength = length1 + length2; 34 | String[] totalArr = new String[totalLength]; 35 | for (int i1 = 0; i1 < length1; i1++) { 36 | totalArr[i1] = result[i1]; 37 | } 38 | for (int i1 = 0; i1 < length2; i1++) { 39 | totalArr[i1 + length1] = ((String[]) clist)[i1]; 40 | } 41 | result = totalArr; 42 | //return (String[]) clist; 43 | } 44 | } 45 | } 46 | } catch (Exception e) { 47 | 48 | } finally { 49 | return result; 50 | } 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.utils; 2 | 3 | import com.blankj.utilcode.util.NetworkUtils; 4 | 5 | import java.lang.reflect.Field; 6 | import java.lang.reflect.Method; 7 | import java.lang.reflect.Modifier; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | import static java.lang.reflect.Modifier.STATIC; 12 | 13 | public class Utils { 14 | private static final Map, String> PRIMITIVE_TO_SIGNATURE; 15 | 16 | static { 17 | PRIMITIVE_TO_SIGNATURE = new HashMap, String>(9); 18 | PRIMITIVE_TO_SIGNATURE.put(byte.class, "B"); 19 | PRIMITIVE_TO_SIGNATURE.put(char.class, "C"); 20 | PRIMITIVE_TO_SIGNATURE.put(short.class, "S"); 21 | PRIMITIVE_TO_SIGNATURE.put(int.class, "I"); 22 | PRIMITIVE_TO_SIGNATURE.put(long.class, "J"); 23 | PRIMITIVE_TO_SIGNATURE.put(float.class, "F"); 24 | PRIMITIVE_TO_SIGNATURE.put(double.class, "D"); 25 | PRIMITIVE_TO_SIGNATURE.put(void.class, "V"); 26 | PRIMITIVE_TO_SIGNATURE.put(boolean.class, "Z"); 27 | } 28 | 29 | public static String getMyIp(){ 30 | try { 31 | return NetworkUtils.getIPAddress(true); 32 | }catch (Exception e){ 33 | return "127.0.0.1"; 34 | } 35 | } 36 | 37 | public static String MethodDescription(Method m) { 38 | StringBuilder sb = new StringBuilder(); 39 | sb.append(Modifier.toString(m.getModifiers())); 40 | sb.append(" "); 41 | sb.append(m.getReturnType().getName()); 42 | sb.append(" "); 43 | sb.append(m.getName()); 44 | sb.append("("); 45 | for (int i = 0; i < m.getParameterTypes().length; i++) { 46 | if (i != 0) sb.append(","); 47 | sb.append(m.getParameterTypes()[i].getName()); 48 | sb.append(" param" + i); 49 | } 50 | sb.append(")"); 51 | if (m.getExceptionTypes().length > 0) { 52 | sb.append("throws "); 53 | boolean first = true; 54 | for (Class type : m.getExceptionTypes()) { 55 | if (!first) sb.append(","); 56 | else first = false; 57 | sb.append(type.getName()); 58 | } 59 | } 60 | return sb.toString(); 61 | } 62 | 63 | public static String FieldDescription(Field field) { 64 | String s = Modifier.toString(field.getModifiers()) + " " + field.getType() + " " + field.getName(); 65 | 66 | if ((field.getModifiers() & STATIC) != 0) { 67 | try { 68 | field.setAccessible(true); 69 | s += " :: " + field.get(null); 70 | } catch (IllegalAccessException e) { 71 | e.printStackTrace(); 72 | } 73 | } 74 | return s; 75 | } 76 | 77 | public static String getJavaName(Method method) { 78 | StringBuilder result = new StringBuilder(); 79 | result.append(getTypeSignature(method.getDeclaringClass())); 80 | result.append("->"); 81 | result.append(method.getName()); 82 | result.append(getMethodSignature(method)); 83 | return result.toString(); 84 | } 85 | 86 | public static String getMethodSignature(Method method) { 87 | StringBuilder result = new StringBuilder(); 88 | 89 | result.append('('); 90 | Class[] parameterTypes = method.getParameterTypes(); 91 | for (Class parameterType : parameterTypes) { 92 | result.append(getTypeSignature(parameterType)); 93 | } 94 | result.append(')'); 95 | result.append(getTypeSignature(method.getReturnType())); 96 | 97 | return result.toString(); 98 | } 99 | 100 | public static String getTypeSignature(Class clazz) { 101 | String primitiveSignature = PRIMITIVE_TO_SIGNATURE.get(clazz); 102 | if (primitiveSignature != null) { 103 | return primitiveSignature; 104 | } else if (clazz.isArray()) { 105 | return "[" + getTypeSignature(clazz.getComponentType()); 106 | } else { 107 | // TODO: this separates packages with '.' rather than '/' 108 | return "L" + clazz.getName() + ";"; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/utils/Xlog.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.utils; 2 | 3 | import java.io.FileWriter; 4 | import java.io.IOException; 5 | import java.io.PrintWriter; 6 | 7 | public class Xlog { 8 | PrintWriter xlog; 9 | int count = 0; 10 | 11 | public Xlog(String logfile) throws IOException { 12 | xlog = new PrintWriter(new FileWriter(logfile, false)); 13 | } 14 | 15 | public void log(String line) { 16 | xlog.println(line); 17 | if (count++ > 20) xlog.flush(); 18 | } 19 | 20 | public void log(String line, boolean flush) { 21 | xlog.println(line); 22 | if (flush) xlog.flush(); 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/monkeylord/XServer/utils/netUtil.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.utils; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.PrintWriter; 6 | import java.net.HttpURLConnection; 7 | import java.net.InetSocketAddress; 8 | import java.net.MalformedURLException; 9 | import java.net.Proxy; 10 | import java.net.ProxySelector; 11 | import java.net.URI; 12 | import java.net.URISyntaxException; 13 | import java.net.URL; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | 18 | /** 19 | * Created by secneo on 2017/3/6. 20 | */ 21 | public class netUtil extends Thread { 22 | URL url; 23 | String postData; 24 | String ret; 25 | String error; 26 | List proxys; 27 | int status; 28 | 29 | public netUtil(String URL, String data) { 30 | try { 31 | postData = new String(data); 32 | ret = new String(postData); 33 | url = new URL(URL); 34 | error = null; 35 | proxys = ProxySelector.getDefault().select(new URI("http://www.baidu.com")); 36 | } catch (MalformedURLException e) { 37 | error = "MalformedURLException"; 38 | } catch (URISyntaxException e) { 39 | error = "URISyntaxException"; 40 | } 41 | 42 | } 43 | public netUtil(String URL, String data,List proxies) { 44 | this(URL,data); 45 | if(proxies==null)proxys=new ArrayList(); 46 | else proxys=proxies; 47 | } 48 | 49 | public void run() { 50 | doPost(); 51 | } 52 | 53 | public String getRet() { 54 | try { 55 | start(); 56 | join(); 57 | } catch (InterruptedException e) { 58 | error = "InterruptedException"; 59 | } 60 | return ret; 61 | } 62 | 63 | public boolean isError() { 64 | if (error.equals("")) return true; 65 | else return false; 66 | } 67 | 68 | public String getError() { 69 | return error; 70 | } 71 | 72 | private void doPost() { 73 | try { 74 | //proxys.add(new Proxy(Proxy.Type.HTTP,new InetSocketAddress("127.0.0.1",8080))); 75 | HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection((proxys.size()>0)?proxys.get(0):Proxy.NO_PROXY); 76 | httpURLConnection.setRequestMethod("POST");// 提交模式 77 | httpURLConnection.setRequestProperty("Content-Type","application/json; charset=utf-8"); 78 | httpURLConnection.setConnectTimeout(10000);//连接超时 单位毫秒 79 | //httpURLConnection.setReadTimeout(2000);//读取超时 单位毫秒 80 | // 发送POST请求必须设置如下两行 81 | httpURLConnection.setDoOutput(true); 82 | httpURLConnection.setDoInput(true); 83 | // 获取URLConnection对象对应的输出流 84 | PrintWriter printWriter = new PrintWriter(httpURLConnection.getOutputStream()); 85 | // 发送请求参数 86 | printWriter.write(postData);//post的参数 xx=xx&yy=yy 87 | // flush输出流的缓冲 88 | printWriter.flush(); 89 | //开始获取数据 90 | status = httpURLConnection.getResponseCode(); 91 | BufferedInputStream bis = new BufferedInputStream((httpURLConnection.getResponseCode()!=200)?httpURLConnection.getErrorStream():httpURLConnection.getInputStream()); 92 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 93 | int len; 94 | byte[] arr = new byte[1024]; 95 | while ((len = bis.read(arr)) != -1) { 96 | bos.write(arr, 0, len); 97 | bos.flush(); 98 | } 99 | bos.close(); 100 | ret = bos.toString(); 101 | } catch (Exception e) { 102 | error = "Exception"; 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkeylord/XServer/1bd4c57b241275aa432672e42e4e81d6f3cdf32f/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkeylord/XServer/1bd4c57b241275aa432672e42e4e81d6f3cdf32f/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkeylord/XServer/1bd4c57b241275aa432672e42e4e81d6f3cdf32f/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkeylord/XServer/1bd4c57b241275aa432672e42e4e81d6f3cdf32f/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkeylord/XServer/1bd4c57b241275aa432672e42e4e81d6f3cdf32f/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkeylord/XServer/1bd4c57b241275aa432672e42e4e81d6f3cdf32f/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkeylord/XServer/1bd4c57b241275aa432672e42e4e81d6f3cdf32f/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkeylord/XServer/1bd4c57b241275aa432672e42e4e81d6f3cdf32f/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkeylord/XServer/1bd4c57b241275aa432672e42e4e81d6f3cdf32f/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkeylord/XServer/1bd4c57b241275aa432672e42e4e81d6f3cdf32f/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | XServer 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/monkeylord/XServer/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer; 2 | 3 | import org.junit.Test; 4 | 5 | import monkeylord.XServer.handler.ObjectHandler; 6 | import monkeylord.XServer.objectparser.IntParser; 7 | import monkeylord.XServer.utils.Utils; 8 | 9 | import static monkeylord.XServer.XServer.parsers; 10 | import static org.junit.Assert.assertEquals; 11 | 12 | /** 13 | * Example local unit test, which will execute on the development machine (host). 14 | * 15 | * @see Testing documentation 16 | */ 17 | public class ExampleUnitTest { 18 | @Test 19 | public void addition_isCorrect() throws Exception { 20 | parsers.put("I",new IntParser()); 21 | System.out.print(Utils.getTypeSignature(int.class)); 22 | System.out.print(Utils.getTypeSignature(byte[].class)); 23 | System.out.print(Utils.getTypeSignature(String.class)); 24 | System.out.println(ObjectHandler.parseObject("I#24")); 25 | assertEquals(4, 2 + 2); 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /app/src/test/java/monkeylord/XServer/objectparser/ByteArrayParserTest.java: -------------------------------------------------------------------------------- 1 | package monkeylord.XServer.objectparser; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | public class ByteArrayParserTest { 8 | 9 | @Test 10 | public void URLEncode() { 11 | byte[] toEncode={3,' ','a',56,21,'A','.','#',0x33,-80}; 12 | System.out.println("Encode:"); 13 | System.out.println(ByteArrayParser.URLEncode(toEncode)); 14 | } 15 | 16 | @Test 17 | public void URLDecode() { 18 | String toDecode="%f0%23 1234.sa%20a8%2aA.%233"; 19 | System.out.println("Decode:"); 20 | System.out.println(new String(ByteArrayParser.URLDecode(toDecode))); 21 | System.out.println(ByteArrayParser.URLEncode(ByteArrayParser.URLDecode(toDecode))); 22 | } 23 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.4.10' 5 | 6 | repositories { 7 | jcenter() 8 | google() 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.4.1' 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 | 14 | // NOTE: Do not place your application dependencies here; they belong 15 | // in the individual module build.gradle files 16 | } 17 | } 18 | 19 | configurations.all { 20 | resolutionStrategy { 21 | force "com.android.support:appcompat-v7:26.0.0" 22 | force "com.android.support:support-v4:26.0.0" 23 | } 24 | } 25 | allprojects { 26 | repositories { 27 | jcenter() 28 | google() 29 | } 30 | } 31 | 32 | task clean(type: Delete) { 33 | delete rootProject.buildDir 34 | } 35 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkeylord/XServer/1bd4c57b241275aa432672e42e4e81d6f3cdf32f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /rhino-android/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /rhino-android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 26 7 | 8 | 9 | defaultConfig { 10 | minSdkVersion 19 11 | targetSdkVersion 26 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | release_x86 { 25 | 26 | } 27 | release_ndk_all { 28 | } 29 | debugWithMinify {} 30 | } 31 | compileOptions { 32 | sourceCompatibility = '1.8' 33 | targetCompatibility = '1.8' 34 | } 35 | 36 | } 37 | dependencies { 38 | implementation fileTree(include: ['*.jar'], dir: 'libs') 39 | implementation 'com.android.support:appcompat-v7:26.1.0' 40 | implementation 'com.android.support:support-v4:26.1.0' 41 | 42 | implementation 'com.android.support:support-annotations:26.0.0' 43 | api 'org.mozilla:rhino:1.7.10' 44 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 45 | } 46 | -------------------------------------------------------------------------------- /rhino-android/libs/dx.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkeylord/XServer/1bd4c57b241275aa432672e42e4e81d6f3cdf32f/rhino-android/libs/dx.jar -------------------------------------------------------------------------------- /rhino-android/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /rhino-android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /rhino-android/src/main/java/cn/vove7/rhino/api/AbsApi.java: -------------------------------------------------------------------------------- 1 | package cn.vove7.rhino.api; 2 | 3 | import org.mozilla.javascript.ScriptableObject; 4 | 5 | /** 6 | * Created by 17719247306 on 2018/8/28 7 | */ 8 | public abstract class AbsApi { 9 | 10 | protected abstract String[] funs(); 11 | public void define(ScriptableObject global) { 12 | global.defineFunctionProperties(funs(), this.getClass(), 13 | ScriptableObject.DONTENUM); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /rhino-android/src/main/java/cn/vove7/rhino/api/RhinoApi.java: -------------------------------------------------------------------------------- 1 | package cn.vove7.rhino.api; 2 | 3 | import android.util.Log; 4 | 5 | import org.mozilla.javascript.Context; 6 | import org.mozilla.javascript.Function; 7 | import org.mozilla.javascript.NativeJavaArray; 8 | import org.mozilla.javascript.Scriptable; 9 | import org.mozilla.javascript.annotations.JSFunction; 10 | 11 | import java.lang.reflect.Field; 12 | import java.util.Arrays; 13 | import java.util.HashSet; 14 | import java.util.Set; 15 | 16 | import cn.vove7.rhino.common.GcCollector; 17 | 18 | /** 19 | * Created by 17719247306 on 2018/8/28 20 | */ 21 | public class RhinoApi extends AbsApi { 22 | 23 | @Override 24 | protected String[] funs() { 25 | return new String[]{ 26 | //"loadAsset", 27 | "quit", 28 | "print", 29 | "log", 30 | }; 31 | } 32 | 33 | public static void quit(Context cx, Scriptable thisObj,//global 34 | Object[] args, Function funObj) { 35 | System.err.println("over"); 36 | GcCollector.gc(thisObj); 37 | } 38 | // 39 | ///** 40 | // * 从Asset加载 41 | // */ 42 | // 43 | //public static void loadAsset(Context cx, Scriptable thisObj, 44 | // Object[] args, Function funObj) { 45 | // for (Object arg : args) { 46 | // String file = Context.toString(arg); 47 | // try { 48 | // Log.d("RhinoApi :", "loadAsset ----> " + file); 49 | // Reader reader = new InputStreamReader(ctx.getAssets().open(file)); 50 | // cx.evaluateReader(thisObj, reader, "load_" + file, 1, null); 51 | // } catch (Exception ex) { 52 | // onException(ex); 53 | // } 54 | // } 55 | //} 56 | 57 | public static void onException(Throwable e) { 58 | notifyOutput(OnPrint.ERROR, e.getMessage()); 59 | } 60 | 61 | public interface OnPrint { 62 | int ERROR = Log.ERROR; 63 | int LOG = Log.INFO; 64 | 65 | void onPrint(int level, String msg); 66 | } 67 | 68 | private static final Set printList = new HashSet<>(); 69 | 70 | public static void regPrint(OnPrint print) { 71 | synchronized (printList) { 72 | printList.add(print); 73 | } 74 | } 75 | 76 | public static void unregPrint(OnPrint print) { 77 | synchronized (printList) { 78 | printList.remove(print); 79 | } 80 | } 81 | 82 | private static void notifyOutput(int l, String o) { 83 | synchronized (printList) { 84 | for (OnPrint p : printList) { 85 | p.onPrint(l, o); 86 | } 87 | } 88 | } 89 | 90 | @JSFunction 91 | public static void log(Context cx, Scriptable thisObj, 92 | Object[] args, Function funObj) { 93 | Log.d("Rhino", Arrays.toString(args)); 94 | } 95 | 96 | 97 | @JSFunction 98 | public synchronized static void print(Context cx, Scriptable thisObj, 99 | Object[] args, Function funObj) { 100 | StringBuilder builder = new StringBuilder(); 101 | for (int i = 0; i < args.length; i++) { 102 | 103 | if (i > 0) 104 | builder.append(' '); 105 | // Convert the arbitrary JavaScript value into a string form. 106 | Object obj = args[i]; 107 | String s; 108 | if (obj instanceof NativeJavaArray) { 109 | try { 110 | Field ar = obj.getClass().getDeclaredField("array"); 111 | ar.setAccessible(true); 112 | s = Arrays.toString((Object[]) ar.get(obj)); 113 | } catch (Exception e) { 114 | s = Context.toString(obj); 115 | e.printStackTrace(); 116 | } 117 | } else { 118 | s = Context.toString(obj); 119 | } 120 | builder.append(s); 121 | } 122 | builder.append("\n"); 123 | doLog(builder.toString()); 124 | //Log.d("Vove :", " out ----> " + builder.toString()); 125 | } 126 | 127 | public static void doLog(String m) { 128 | notifyOutput(OnPrint.LOG, m); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /rhino-android/src/main/java/cn/vove7/rhino/api/ThreadApi.java: -------------------------------------------------------------------------------- 1 | package cn.vove7.rhino.api; 2 | 3 | import org.mozilla.javascript.Scriptable; 4 | 5 | import cn.vove7.rhino.common.GcCollector; 6 | 7 | /** 8 | * Created by Vove on 2018/8/28 9 | */ 10 | public class ThreadApi extends AbsApi { 11 | @Override 12 | protected String[] funs() { 13 | return new String[0]; 14 | } 15 | 16 | public static Thread start(Scriptable scrop, Runnable runnable) { 17 | Thread t = new WrappedThread(runnable); 18 | t.start(); 19 | GcCollector.reg(scrop, t); 20 | return t; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /rhino-android/src/main/java/cn/vove7/rhino/api/WrappedThread.java: -------------------------------------------------------------------------------- 1 | 2 | 3 | package cn.vove7.rhino.api; 4 | 5 | 6 | import android.os.Handler; 7 | import android.os.Looper; 8 | import android.support.annotation.Nullable; 9 | 10 | import org.mozilla.javascript.WrappedException; 11 | 12 | /** 13 | * Handy class for starting a new thread that has a looper. The looper can then be 14 | * used to create handler classes. Note that start() must still be called. 15 | */ 16 | public class WrappedThread extends Thread { 17 | 18 | Looper mLooper; 19 | private @Nullable 20 | Handler mHandler; 21 | Runnable runnable; 22 | 23 | public WrappedThread(String name) { 24 | super(name); 25 | 26 | } 27 | 28 | public WrappedThread(Runnable runnable) { 29 | this.runnable = runnable; 30 | } 31 | 32 | /** 33 | * Call back method that can be explicitly overridden if needed to execute some 34 | * setup before Looper loops. 35 | */ 36 | protected void onLooperPrepared() { 37 | mHandler = new Handler(getLooper()); 38 | mHandler.post(runnable); 39 | } 40 | 41 | @Override 42 | public void run() { 43 | //mTid = Process.myTid(); 44 | Looper.prepare(); 45 | 46 | synchronized (this) { 47 | mLooper = Looper.myLooper(); 48 | notifyAll(); 49 | } 50 | onLooperPrepared(); 51 | try { 52 | Looper.loop(); 53 | } catch (WrappedException ie) { 54 | Throwable e = ie.getWrappedException(); 55 | if (!(e instanceof InterruptedException)) { 56 | RhinoApi.onException(e); 57 | } 58 | } catch (Exception e) { 59 | // e.printStackTrace(); 60 | RhinoApi.onException(e); 61 | } 62 | 63 | } 64 | 65 | /** 66 | * This method returns the Looper associated with this thread. If this thread not been started 67 | * or for any reason isAlive() returns false, this method will return null. If this thread 68 | * has been started, this method will block until the looper has been initialized. 69 | * 70 | * @return The looper. 71 | */ 72 | public Looper getLooper() { 73 | if (!isAlive()) { 74 | return null; 75 | } 76 | 77 | // If the thread has been started, wait until the looper has been created. 78 | synchronized (this) { 79 | while (isAlive() && mLooper == null) { 80 | try { 81 | wait(); 82 | } catch (InterruptedException e) { 83 | } 84 | } 85 | } 86 | return mLooper; 87 | } 88 | 89 | 90 | public Handler getThreadHandler() { 91 | if (mHandler == null) { 92 | mHandler = new Handler(getLooper()); 93 | } 94 | return mHandler; 95 | } 96 | 97 | 98 | public boolean quit() { 99 | Looper looper = getLooper(); 100 | if (looper != null) { 101 | looper.quit(); 102 | return true; 103 | } 104 | return false; 105 | } 106 | 107 | 108 | public boolean quitSafely() { 109 | Looper looper = getLooper(); 110 | if (looper != null) { 111 | looper.quitSafely(); 112 | return true; 113 | } 114 | return false; 115 | } 116 | 117 | 118 | } 119 | -------------------------------------------------------------------------------- /rhino-android/src/main/java/cn/vove7/rhino/common/AndroidContextFactory.java: -------------------------------------------------------------------------------- 1 | package cn.vove7.rhino.common; 2 | 3 | 4 | 5 | import android.os.Build; 6 | import android.support.annotation.VisibleForTesting; 7 | 8 | import org.mozilla.javascript.Context; 9 | import org.mozilla.javascript.tools.shell.ShellContextFactory; 10 | 11 | import java.io.File; 12 | 13 | /** 14 | * Ensures that the classLoader used is correct 15 | * 16 | * @author F43nd1r 17 | * @since 11.01.2016 18 | */ 19 | 20 | public class AndroidContextFactory extends ShellContextFactory { 21 | private final File cacheDirectory; 22 | 23 | /** 24 | * Create a new factory. It will cache generated code in the given directory 25 | * 26 | * @param cacheDirectory the cache directory 27 | */ 28 | public AndroidContextFactory(File cacheDirectory) { 29 | this.cacheDirectory = cacheDirectory; 30 | initApplicationClassLoader(createClassLoader(AndroidContextFactory.class.getClassLoader())); 31 | } 32 | 33 | /** 34 | * Create a ClassLoader which is able to deal with bytecode 35 | * 36 | * @param parent the parent of the create classloader 37 | * @return a new ClassLoader 38 | */ 39 | /** 40 | * Create a ClassLoader which is able to deal with bytecode 41 | * 42 | * @param parent the parent of the create classloader 43 | * @return a new ClassLoader 44 | */ 45 | @VisibleForTesting 46 | @Override 47 | public BaseAndroidClassLoader createClassLoader(ClassLoader parent) { 48 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 49 | return new InMemoryAndroidClassLoader(parent); 50 | } 51 | return new FileAndroidClassLoader(parent, cacheDirectory); 52 | } 53 | 54 | 55 | @Override 56 | protected void observeInstructionCount(Context cx, int instructionCount) { 57 | if (Thread.currentThread().isInterrupted()) { 58 | throw new RuntimeException("Interrupted"); 59 | } 60 | } 61 | 62 | @Override 63 | protected Context makeContext() { 64 | Context cx = super.makeContext(); 65 | cx.setInstructionObserverThreshold(10000); 66 | return cx; 67 | } 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /rhino-android/src/main/java/cn/vove7/rhino/common/AssetAndUrlModuleSourceProvider.java: -------------------------------------------------------------------------------- 1 | package cn.vove7.rhino.common; 2 | 3 | import org.mozilla.javascript.commonjs.module.provider.ModuleSource; 4 | import org.mozilla.javascript.commonjs.module.provider.UrlModuleSourceProvider; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStreamReader; 8 | import java.net.URI; 9 | import java.net.URISyntaxException; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | 13 | /** 14 | * ... 15 | * Created by Stardust on 2017/5/9. 16 | * 重定向require资源 17 | */ 18 | 19 | public class AssetAndUrlModuleSourceProvider extends UrlModuleSourceProvider { 20 | 21 | private static final String MODULES_PATH = "rhino_require"; 22 | private android.content.Context mContext; 23 | private List mModules; 24 | private final URI mBaseURI = URI.create("file:///android_asset/" + MODULES_PATH); 25 | 26 | public AssetAndUrlModuleSourceProvider(android.content.Context context, List list) { 27 | super(list, null); 28 | mContext = context; 29 | try { 30 | mModules = Arrays.asList(mContext.getAssets().list(MODULES_PATH)); 31 | } catch (IOException e) { 32 | throw new RuntimeException(e); 33 | } 34 | } 35 | 36 | @Override 37 | protected ModuleSource loadFromPrivilegedLocations(String moduleId, Object validator) throws IOException, URISyntaxException { 38 | String moduleIdWithExtension = moduleId; 39 | if (!moduleIdWithExtension.endsWith(".js")) { 40 | moduleIdWithExtension += ".js"; 41 | } 42 | if (mModules.contains(moduleIdWithExtension)) { 43 | return new ModuleSource(new InputStreamReader(mContext.getAssets() 44 | .open(MODULES_PATH + "/" + moduleIdWithExtension)), null, 45 | URI.create(moduleIdWithExtension), mBaseURI, validator); 46 | } 47 | return super.loadFromPrivilegedLocations(moduleId, validator); 48 | } 49 | } -------------------------------------------------------------------------------- /rhino-android/src/main/java/cn/vove7/rhino/common/BaseAndroidClassLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Lukas Morawietz 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package cn.vove7.rhino.common; 18 | 19 | import android.support.annotation.NonNull; 20 | import android.support.annotation.Nullable; 21 | 22 | import com.android.dex.Dex; 23 | import com.android.dx.cf.direct.DirectClassFile; 24 | import com.android.dx.cf.direct.StdAttributeFactory; 25 | import com.android.dx.dex.DexOptions; 26 | import com.android.dx.dex.cf.CfOptions; 27 | import com.android.dx.dex.cf.CfTranslator; 28 | import com.android.dx.dex.file.DexFile; 29 | import com.android.dx.merge.CollisionPolicy; 30 | import com.android.dx.merge.DexMerger; 31 | 32 | import org.mozilla.javascript.GeneratedClassLoader; 33 | 34 | import java.io.IOException; 35 | 36 | /** 37 | * Compiles java bytecode to dex bytecode and loads it 38 | * 39 | * @author F43nd1r 40 | * @since 11.01.2016 41 | */ 42 | abstract class BaseAndroidClassLoader extends ClassLoader implements GeneratedClassLoader { 43 | 44 | /** 45 | * Create a new instance with the given parent classloader 46 | * 47 | * @param parent the parent 48 | */ 49 | public BaseAndroidClassLoader(ClassLoader parent) { 50 | super(parent); 51 | } 52 | 53 | /** 54 | * {@inheritDoc} 55 | */ 56 | @Override 57 | public Class defineClass(String name, byte[] data) { 58 | try { 59 | DexOptions dexOptions = new DexOptions(); 60 | DexFile dexFile = new DexFile(dexOptions); 61 | DirectClassFile classFile = new DirectClassFile(data, name.replace('.', '/') + ".class", true); 62 | classFile.setAttributeFactory(StdAttributeFactory.THE_ONE); 63 | classFile.getMagic(); 64 | dexFile.add(CfTranslator.translate(classFile, null, new CfOptions(), dexOptions, dexFile)); 65 | Dex dex = new Dex(dexFile.toDex(null, false)); 66 | Dex oldDex = getLastDex(); 67 | if (oldDex != null) { 68 | dex = new DexMerger(new Dex[]{dex, oldDex}, CollisionPolicy.KEEP_FIRST).merge(); 69 | } 70 | return loadClass(dex, name); 71 | } catch (IOException | ClassNotFoundException e) { 72 | throw new FatalLoadingException(e); 73 | } 74 | } 75 | 76 | protected abstract Class loadClass(@NonNull Dex dex, @NonNull String name) throws ClassNotFoundException; 77 | 78 | @Nullable 79 | protected abstract Dex getLastDex(); 80 | 81 | protected abstract void reset(); 82 | 83 | /** 84 | * Does nothing 85 | * 86 | * @param aClass ignored 87 | */ 88 | @Override 89 | public void linkClass(Class aClass) { 90 | //doesn't make sense on android 91 | } 92 | 93 | /** 94 | * Try to load a class. This will search all defined classes, all loaded jars and the parent class loader. 95 | * 96 | * @param name the name of the class to load 97 | * @param resolve ignored 98 | * @return the class 99 | * @throws ClassNotFoundException if the class could not be found in any of the locations 100 | */ 101 | @Override 102 | public Class loadClass(String name, boolean resolve) 103 | throws ClassNotFoundException { 104 | Class loadedClass = findLoadedClass(name); 105 | if (loadedClass == null) { 106 | Dex dex = getLastDex(); 107 | if (dex != null) { 108 | loadedClass = loadClass(dex, name); 109 | } 110 | if (loadedClass == null) { 111 | loadedClass = getParent().loadClass(name); 112 | } 113 | } 114 | return loadedClass; 115 | } 116 | 117 | /** 118 | * Might be thrown in any Rhino method that loads bytecode if the loading failed 119 | */ 120 | public static class FatalLoadingException extends RuntimeException { 121 | FatalLoadingException(Throwable t) { 122 | super("Failed to define class", t); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /rhino-android/src/main/java/cn/vove7/rhino/common/FileAndroidClassLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Lukas Morawietz 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package cn.vove7.rhino.common; 18 | 19 | 20 | import android.support.annotation.NonNull; 21 | import android.support.annotation.Nullable; 22 | 23 | import com.android.dex.Dex; 24 | 25 | import java.io.File; 26 | import java.io.IOException; 27 | 28 | import dalvik.system.PathClassLoader; 29 | 30 | /** 31 | * @author F43nd1r 32 | * @since 24.10.2017 33 | */ 34 | @SuppressWarnings("ResultOfMethodCallIgnored") 35 | class FileAndroidClassLoader extends BaseAndroidClassLoader { 36 | private static int instanceCounter = 0; 37 | private final File dexFile; 38 | 39 | /** 40 | * Create a new instance with the given parent classloader 41 | * 42 | * @param parent the parent 43 | */ 44 | public FileAndroidClassLoader(ClassLoader parent, File cacheDir) { 45 | super(parent); 46 | int id = instanceCounter++; 47 | dexFile = new File(cacheDir, id + ".dex"); 48 | cacheDir.mkdirs(); 49 | reset(); 50 | } 51 | 52 | @Override 53 | protected Class loadClass(@NonNull Dex dex, @NonNull String name) throws ClassNotFoundException { 54 | try { 55 | dex.writeTo(dexFile); 56 | } catch (IOException e) { 57 | e.printStackTrace(); 58 | } 59 | return new PathClassLoader(dexFile.getPath(), getParent()).loadClass(name); 60 | } 61 | 62 | @Nullable 63 | @Override 64 | protected Dex getLastDex() { 65 | if (dexFile.exists()) { 66 | try { 67 | return new Dex(dexFile); 68 | } catch (IOException e) { 69 | e.printStackTrace(); 70 | } 71 | } 72 | return null; 73 | } 74 | 75 | @Override 76 | protected void reset() { 77 | dexFile.delete(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /rhino-android/src/main/java/cn/vove7/rhino/common/GcCollector.java: -------------------------------------------------------------------------------- 1 | package cn.vove7.rhino.common; 2 | 3 | import org.mozilla.javascript.Scriptable; 4 | import org.mozilla.javascript.annotations.JSFunction; 5 | 6 | import java.util.ArrayList; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | 10 | /** 11 | * Created by 17719247306. 12 | * Date: 2018/8/26 13 | */ 14 | public class GcCollector { 15 | 16 | public static final GcCollector Ins = new GcCollector(); 17 | 18 | private static HashMap colls = new HashMap<>(); 19 | 20 | static class RunData { 21 | Thread oriThread; 22 | List gcs = new ArrayList<>(); 23 | 24 | public RunData(Thread oriThread) { 25 | this.oriThread = oriThread; 26 | } 27 | } 28 | 29 | public static void regMainThread(Scriptable scope, Thread thread) { 30 | RunData data = new RunData(thread); 31 | colls.put(scope, data); 32 | } 33 | 34 | @JSFunction 35 | public static void reg(Scriptable scope, Object o) { 36 | if (colls.containsKey(scope)) { 37 | colls.get(scope).gcs.add(o); 38 | } 39 | } 40 | 41 | public static void gc(Scriptable scope) { 42 | RunData data = colls.get(scope); 43 | if (data == null) { 44 | System.out.println("no data"); 45 | return; 46 | } 47 | colls.remove(scope); 48 | 49 | List gcs = data.gcs; 50 | if (gcs != null) { 51 | for (Object gc : gcs) { 52 | if (gc instanceof Thread && ((Thread) gc).isAlive()) { 53 | ((Thread) gc).interrupt(); 54 | } 55 | } 56 | gcs.clear(); 57 | } 58 | Thread or = data.oriThread; 59 | if (or != null) { 60 | or.interrupt(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /rhino-android/src/main/java/cn/vove7/rhino/common/InMemoryAndroidClassLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Lukas Morawietz 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package cn.vove7.rhino.common; 18 | 19 | import android.os.Build; 20 | import android.support.annotation.NonNull; 21 | import android.support.annotation.Nullable; 22 | import android.support.annotation.RequiresApi; 23 | 24 | import com.android.dex.Dex; 25 | 26 | import java.nio.ByteBuffer; 27 | 28 | import dalvik.system.InMemoryDexClassLoader; 29 | 30 | /** 31 | * @author F43nd1r 32 | * @since 24.10.2017 33 | */ 34 | 35 | @RequiresApi(api = Build.VERSION_CODES.O) 36 | class InMemoryAndroidClassLoader extends BaseAndroidClassLoader { 37 | @Nullable 38 | private Dex last; 39 | 40 | public InMemoryAndroidClassLoader(ClassLoader parent) { 41 | super(parent); 42 | } 43 | 44 | @Override 45 | protected Class loadClass(@NonNull Dex dex, @NonNull String name) throws ClassNotFoundException { 46 | last = dex; 47 | return new InMemoryDexClassLoader(ByteBuffer.wrap(dex.getBytes()), getParent()).loadClass(name); 48 | } 49 | 50 | @Nullable 51 | @Override 52 | protected Dex getLastDex() { 53 | return last; 54 | } 55 | 56 | @Override 57 | protected void reset() { 58 | last = null; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /rhino-android/src/main/java/cn/vove7/rhino/common/MapScriptable.java: -------------------------------------------------------------------------------- 1 | package cn.vove7.rhino.common; 2 | 3 | import org.mozilla.javascript.Scriptable; 4 | 5 | import java.util.Collection; 6 | import java.util.Map; 7 | import java.util.Set; 8 | 9 | /** 10 | * 支持 map['key'] 11 | *

12 | * [https://stackoverflow.com/questions/7519399/how-to-convert-java-map-to-a-basic-javascript-object] 13 | */ 14 | public class MapScriptable implements Scriptable, Map { 15 | public final Map map; 16 | 17 | public MapScriptable(Map map) { 18 | this.map = map; 19 | } 20 | 21 | public void clear() { 22 | map.clear(); 23 | } 24 | 25 | public boolean containsKey(Object key) { 26 | return map.containsKey(key); 27 | } 28 | 29 | public boolean containsValue(Object value) { 30 | return map.containsValue(value); 31 | } 32 | 33 | public Set entrySet() { 34 | return map.entrySet(); 35 | } 36 | 37 | public boolean equals(Object o) { 38 | return map.equals(o); 39 | } 40 | 41 | public Object get(Object key) { 42 | return map.get(key); 43 | } 44 | 45 | public int hashCode() { 46 | return map.hashCode(); 47 | } 48 | 49 | public boolean isEmpty() { 50 | return map.isEmpty(); 51 | } 52 | 53 | public Set keySet() { 54 | return map.keySet(); 55 | } 56 | 57 | public Object put(Object key, Object value) { 58 | return map.put(key, value); 59 | } 60 | 61 | public void putAll(Map m) { 62 | map.putAll(m); 63 | } 64 | 65 | public Object remove(Object key) { 66 | return map.remove(key); 67 | } 68 | 69 | public int size() { 70 | return map.size(); 71 | } 72 | 73 | public Collection values() { 74 | return map.values(); 75 | } 76 | 77 | @Override 78 | public void delete(String name) { 79 | map.remove(name); 80 | } 81 | 82 | @Override 83 | public void delete(int index) { 84 | map.remove(index); 85 | } 86 | 87 | @Override 88 | public Object get(String name, Scriptable start) { 89 | return map.get(name); 90 | } 91 | 92 | @Override 93 | public Object get(int index, Scriptable start) { 94 | return map.get(index); 95 | } 96 | 97 | @Override 98 | public String getClassName() { 99 | return map.getClass().getName(); 100 | } 101 | 102 | @Override 103 | public Object getDefaultValue(Class hint) { 104 | return toString(); 105 | } 106 | 107 | @Override 108 | public Object[] getIds() { 109 | Object[] res = new Object[map.size()]; 110 | int i = 0; 111 | for (Object k : map.keySet()) { 112 | res[i] = k; 113 | i++; 114 | } 115 | return res; 116 | } 117 | 118 | @Override 119 | public Scriptable getParentScope() { 120 | return null; 121 | } 122 | 123 | @Override 124 | public Scriptable getPrototype() { 125 | return null; 126 | } 127 | 128 | @Override 129 | public boolean has(String name, Scriptable start) { 130 | return map.containsKey(name); 131 | } 132 | 133 | @Override 134 | public boolean has(int index, Scriptable start) { 135 | return map.containsKey(index); 136 | } 137 | 138 | @Override 139 | public boolean hasInstance(Scriptable instance) { 140 | return false; 141 | } 142 | 143 | @Override 144 | public void put(String name, Scriptable start, Object value) { 145 | map.put(name, value); 146 | } 147 | 148 | @Override 149 | public void put(int index, Scriptable start, Object value) { 150 | map.put(index, value); 151 | } 152 | 153 | @Override 154 | public void setParentScope(Scriptable parent) { 155 | } 156 | 157 | @Override 158 | public void setPrototype(Scriptable prototype) { 159 | } 160 | 161 | @Override 162 | public String toString() { 163 | return map.toString(); 164 | } 165 | } -------------------------------------------------------------------------------- /rhino-android/src/main/java/cn/vove7/rhino/common/NoSecurityController.java: -------------------------------------------------------------------------------- 1 | package cn.vove7.rhino.common; 2 | 3 | import org.mozilla.javascript.Context; 4 | import org.mozilla.javascript.GeneratedClassLoader; 5 | import org.mozilla.javascript.SecurityController; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * Created by Stardust on 2017/4/5. 11 | */ 12 | 13 | public class NoSecurityController extends SecurityController implements Serializable { 14 | @Override 15 | public GeneratedClassLoader createClassLoader(ClassLoader classLoader, Object o) { 16 | return Context.getCurrentContext().createClassLoader(classLoader); 17 | } 18 | 19 | @Override 20 | public Object getDynamicSecurityDomain(Object o) { 21 | return null; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /rhino-android/src/main/java/cn/vove7/rhino/common/RhinoAndroidHelper.java: -------------------------------------------------------------------------------- 1 | package cn.vove7.rhino.common; 2 | 3 | 4 | import org.mozilla.javascript.Context; 5 | import org.mozilla.javascript.ContextFactory; 6 | import org.mozilla.javascript.SecurityController; 7 | 8 | import java.io.File; 9 | 10 | /** 11 | * Created by Stardust on 2017/4/5. 12 | */ 13 | 14 | public class RhinoAndroidHelper { 15 | 16 | private final File cacheDirectory; 17 | 18 | /** 19 | * Constructs a new helper using the default temporary directory. 20 | * Note: It is recommended to use a custom directory, so no permission problems occur. 21 | */ 22 | public RhinoAndroidHelper() { 23 | this(new File(System.getProperty("java.io.tmpdir", "."), "classes")); 24 | } 25 | 26 | /** 27 | * Constructs a new helper using a directory in the applications cache. 28 | * 29 | * @param context any context 30 | */ 31 | public RhinoAndroidHelper(android.content.Context context) { 32 | this(new File(context.getCacheDir(), "classes")); 33 | } 34 | 35 | /** 36 | * Constructs a helper using the specified directory as cache. 37 | * 38 | * @param cacheDirectory the cache directory to use 39 | */ 40 | public RhinoAndroidHelper(File cacheDirectory) { 41 | this.cacheDirectory = cacheDirectory; 42 | } 43 | 44 | /** 45 | * call this instead of {@link Context#enter()} 46 | * 47 | * @return a context prepared for android 48 | */ 49 | public Context enterContext() { 50 | if (!SecurityController.hasGlobal()) 51 | SecurityController.initGlobal(new NoSecurityController()); 52 | return getContextFactory().enterContext(); 53 | } 54 | 55 | /** 56 | * @return The Context factory which has to be used on android. 57 | */ 58 | //@VisibleForTesting 59 | public AndroidContextFactory getContextFactory() { 60 | AndroidContextFactory factory; 61 | if (!ContextFactory.hasExplicitGlobal()) { 62 | factory = new AndroidContextFactory(cacheDirectory); 63 | ContextFactory.getGlobalSetter().setContextFactoryGlobal(factory); 64 | } else if (!(ContextFactory.getGlobal() instanceof AndroidContextFactory)) { 65 | throw new IllegalStateException("Cannot initialize factory for Android Rhino: There is already another factory"); 66 | } else { 67 | factory = (AndroidContextFactory) ContextFactory.getGlobal(); 68 | } 69 | return factory; 70 | } 71 | 72 | /** 73 | * @return a context prepared for android 74 | * @deprecated use {@link #enterContext()} instead 75 | */ 76 | @Deprecated 77 | public static Context prepareContext() { 78 | return new RhinoAndroidHelper().enterContext(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | include ':rhino-android' --------------------------------------------------------------------------------