├── .gitignore ├── LICENSE ├── README.md ├── README_EN.md ├── app ├── .gitignore ├── build.gradle ├── lib │ ├── XposedBridgeApi-54.jar │ ├── freemarker.jar │ └── openbeans.jar ├── proguard-rules.pro ├── release │ └── output.json └── src │ ├── androidTest │ └── java │ │ └── leon │ │ └── qujing │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── pages │ │ │ ├── config.html │ │ │ ├── index.html │ │ │ ├── manualguid.html │ │ │ ├── methodview.html │ │ │ └── packetview.html │ │ ├── statics │ │ │ ├── font-awesome.min.css │ │ │ └── jquery-2.0.2.min.js │ │ └── xposed_init │ ├── ic_launcher-web.png │ ├── java │ │ └── leon │ │ │ └── qujing │ │ │ ├── QuJingServer.java │ │ │ ├── XposedEntry.java │ │ │ ├── api │ │ │ ├── ClassView.java │ │ │ ├── Invoke.java │ │ │ ├── MethodView.java │ │ │ ├── PacketView.java │ │ │ ├── wsMethodView.java │ │ │ └── wsPacketView.java │ │ │ ├── handler │ │ │ ├── ClassHandler.java │ │ │ ├── HookHandler.java │ │ │ ├── MemoryHandler.java │ │ │ ├── MethodHandler.java │ │ │ └── ObjectHandler.java │ │ │ ├── objectparser │ │ │ ├── BooleanParser.java │ │ │ ├── ByteArrayParser.java │ │ │ ├── ContextParser.java │ │ │ ├── DoubleParser.java │ │ │ ├── FloatParser.java │ │ │ ├── GenericParser.java │ │ │ ├── IntParser.java │ │ │ ├── JSONParser.java │ │ │ ├── LongParser.java │ │ │ ├── ShortParser.java │ │ │ ├── StoredObjectParser.java │ │ │ ├── StringArrayListParser.java │ │ │ ├── StringArrayParser.java │ │ │ ├── StringMapParser.java │ │ │ └── StringParser.java │ │ │ └── utils │ │ │ ├── DexHelper.java │ │ │ ├── NanoHTTPD.java │ │ │ ├── NanoWSD.java │ │ │ ├── Utils.java │ │ │ ├── Xlog.java │ │ │ └── netUtil.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── leon │ └── qujing │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── image ├── 手动操作指导.png ├── 执行方法.png ├── 搜索目标类-方法.png ├── 星球.png ├── 监控方法.png ├── 监控方法2.png └── 配置目标应用.png ├── release_key.jks └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.aar 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | # Uncomment the following line in case you need and you don't have the release build type files in your app 18 | # release/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # IntelliJ 40 | *.iml 41 | .idea 42 | .idea/workspace.xml 43 | .idea/tasks.xml 44 | .idea/gradle.xml 45 | .idea/assetWizardSettings.xml 46 | .idea/dictionaries 47 | .idea/libraries 48 | # Android Studio 3 in .gitignore file. 49 | .idea/caches 50 | .idea/modules.xml 51 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 52 | .idea/navEditor.xml 53 | 54 | # Keystore files 55 | # Uncomment the following lines if you do not want to check your keystore files in. 56 | #*.jks 57 | #*.keystore 58 | 59 | # External native build folder generated in Android Studio 2.2 and later 60 | .externalNativeBuild 61 | .cxx/ 62 | 63 | # Google Services (e.g. APIs or Firebase) 64 | # google-services.json 65 | 66 | # Freeline 67 | freeline.py 68 | freeline/ 69 | freeline_project_description.json 70 | 71 | # fastlane 72 | fastlane/report.xml 73 | fastlane/Preview.html 74 | fastlane/screenshots 75 | fastlane/test_output 76 | fastlane/readme.md 77 | 78 | # Version control 79 | vcs.xml 80 | 81 | # lint 82 | lint/intermediates/ 83 | lint/generated/ 84 | lint/outputs/ 85 | lint/tmp/ 86 | # lint/reports/ 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QuJing(曲境) 2 | 3 | [ENGLISH](./README_EN.md) 4 | 5 | 曲境是一个xposed模块,可实现在PC浏览器上动态监控(hook)函数调用和查看堆栈信息,及反射调用(invoke)等功能。避免了频繁写hook代码的麻烦,提供了可视化的界面,对新手更友好。 6 | 7 | ## 实现功能 8 | 9 | 1. 枚举安卓设备所有APP 10 | 2. 根据类名和方法名搜索方法 11 | 3. 方法监控,打印调用栈和出入参 12 | 4. 对目标方法强制执行 13 | 14 | ## 已知问题 15 | 16 | 1. 函数频繁调用时,巨量的堆栈信息和出入参打印在浏览器页面,会导致页面内容过多,如无必要数据,可刷新解决。 17 | 2. 部分前端显示存在布局问题 18 | 3. 不支持开机瞬间hook的场景(因为每次开机后需要设置需要hook的APP) 19 | 4. 对于本身不具备网络权限的APP暂不支持 20 | 5. ~~对LSPOSED的支持好像还有问题,现象是不能打开61000端口,晚点支持~~(LSPOSED已经支持,使用时不仅仅要在Lsposed中勾选目标APP,**还需要勾选“系统框架”哦**[如图](https://github.com/Mocha-L/QuJing/assets/24688287/53906a4e-96ca-4824-83c5-eb5cc70dd2af)) 21 | 22 | ## 使用方法 23 | 24 | 可直接下载apk目录的apk文件安装体验,具体的使用方法可以看[这篇文章](https://mp.weixin.qq.com/s/zXRKNximCk5DDFfFZcxDJQ)。 25 | 26 | 如果帮到你,记得点个star哦。 27 | 28 | ## 效果展示 29 | 30 | 列举手机中的所有APP供选择 31 | 32 | 33 | 34 | 对选中的APP指导手动操作 35 | 36 | 37 | 38 | 进入APP,搜索类和方法进行监控 39 | 40 | 41 | 42 | 进入监控方法,查看基本信息和调用堆栈,打印出入参数。 43 | 44 | 45 | 46 | 47 | 执行方法支持类型 48 | 49 | 50 | 51 | 52 | ## 问题交流 53 | 54 | 如有疑问可以提issue,也欢迎大家进一步优化和提交PR。 55 | 56 | 也欢迎进入我的知识星球“爬虫三十六计”。 57 | 58 | 59 | 60 | ## 鸣谢 61 | 62 | 项目依据[xserver](https://github.com/monkeylord/XServer)优化而来,感谢原作者。 63 | 64 | 感谢[@小黄鸭爱学习](https://github.com/HuRuWo)的帮助优化,解决部分情况下classloader错误的问题。 65 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | # QuJing 2 | 3 | [中文版](./README.md) 4 | 5 | QuJing is an Xposed module that allows you to dynamically monitor (hook) function calls, view stack information, and perform reflective invocations on Android devices through a PC browser. It eliminates the need for writing frequent hook code and provides a visual interface that is more user-friendly, especially for beginners. 6 | 7 | ## Features 8 | 9 | 1. Enumerate all apps on an Android device. 10 | 2. Search for methods based on class name and method name. 11 | 3. Method monitoring with printing of call stacks and input/output parameters. 12 | 4. Forced execution of target methods. 13 | 14 | ## Known Issues 15 | 16 | 1. When functions are frequently called, a large amount of stack information and input/output parameters are printed on the browser page, which may cause the page to be overloaded. If unnecessary data is present, refreshing the page can resolve the issue. 17 | 2. Some layout issues may occur in the frontend display. 18 | 3. Does not support scenarios where hooking immediately after booting (as the target app needs to be set after each boot). 19 | 4. Does not support apps that do not have network permissions. 20 | 5. ~~There seems to be an issue with LSPOSED support; the symptom is that port 61000 cannot be opened. It will be supported later~~ (LSPOSED is now supported. When using, make sure to not only select the target app in Lsposed, **but also check "System Framework" [as shown here](https://github.com/Mocha-L/QuJing/assets/24688287/53906a4e-96ca-4824-83c5-eb5cc70dd2af)~~). 21 | 22 | ## Usage 23 | 24 | You can directly download the APK file from the "apk" directory and install it for testing. For detailed usage instructions, please refer to [this article](https://mp.weixin.qq.com/s/zXRKNximCk5DDFfFZcxDJQ). 25 | 26 | If it helps you, don't forget to give it a star. 27 | 28 | ## Demo 29 | 30 | Enumerating all apps on the phone for selection. 31 | 32 | 33 | 34 | Guiding manual operations on the selected app. 35 | 36 | 37 | 38 | Enter the app, search for classes and methods to monitor. 39 | 40 | 41 | 42 | Enter the monitored method, view basic information and call stack, and print input/output parameters. 43 | 44 | 45 | 48 | 49 | Supported types for method execution. 50 | 51 | 52 | 53 | 54 | ## Issue Discussion 55 | 56 | If you have any questions, feel free to raise an issue. You are also welcome to contribute to further optimizations and submit pull requests. 57 | 58 | You can also join my knowledge planet "爬虫三十六计" for further discussions. 59 | 60 | 61 | 62 | ## Acknowledgements 63 | 64 | This project is based on [xserver](https://github.com/monkeylord/XServer) and optimized accordingly. Thanks to the original author. 65 | 66 | Thanks to [@小黄鸭爱学习](https://github.com/HuRuWo) for helping with optimizations and resolving classloader errors in certain cases. -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | buildToolsVersion '28.0.3' 6 | defaultConfig { 7 | applicationId "leon.qujing" 8 | minSdkVersion 16 9 | targetSdkVersion 29 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | 21 | lintOptions { 22 | checkReleaseBuilds false 23 | abortOnError false 24 | } 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(dir: 'libs', include: ['*.jar']) 29 | implementation 'androidx.appcompat:appcompat:1.1.0' 30 | testImplementation 'junit:junit:4.13' 31 | androidTestImplementation 'androidx.test:runner:1.2.0' 32 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 33 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 34 | implementation 'com.alibaba:fastjson:1.2.68' 35 | compileOnly files('lib/XposedBridgeApi-54.jar') 36 | implementation files('lib/freemarker.jar') 37 | implementation files('lib/openbeans.jar') 38 | } 39 | -------------------------------------------------------------------------------- /app/lib/XposedBridgeApi-54.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/app/lib/XposedBridgeApi-54.jar -------------------------------------------------------------------------------- /app/lib/freemarker.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/app/lib/freemarker.jar -------------------------------------------------------------------------------- /app/lib/openbeans.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/app/lib/openbeans.jar -------------------------------------------------------------------------------- /app/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 | #-------------------------------------------基本不用动区域-------------------------------------------- 23 | #---------------------------------基本指令区---------------------------------- 24 | -optimizationpasses 5 # 指定代码的压缩级别 25 | -dontusemixedcaseclassnames # 是否使用大小写混合 26 | -dontskipnonpubliclibraryclasses # 指定不去忽略非公共的库类 27 | -dontskipnonpubliclibraryclassmembers # 指定不去忽略包可见的库类的成员 28 | -dontpreverify # 混淆时是否做预校验 29 | -verbose # 混淆时是否记录日志 30 | -printmapping proguardMapping.txt 31 | -optimizations !code/simplification/cast,!field/*,!class/merging/* # 混淆时所采用的算法 32 | -keepattributes *Annotation*,InnerClasses 33 | -keepattributes Signature 34 | -keepattributes SourceFile,LineNumberTable 35 | #---------------------------------------------------------------------------- 36 | -ignorewarnings # 是否忽略检测,(是) 37 | #---------------------------------默认保留区--------------------------------- 38 | -keep public class * extends android.app.Activity 39 | -keep public class * extends android.app.Application 40 | -keep public class * extends android.app.Service 41 | -keep public class * extends android.content.BroadcastReceiver 42 | -keep public class * extends android.content.ContentProvider 43 | -keep public class * extends android.app.backup.BackupAgentHelper 44 | -keep public class * extends android.preference.Preference 45 | -keep public class * extends android.view.View 46 | -keep public class com.android.vending.licensing.ILicensingService 47 | -keep class android.support.** {*;} 48 | #-ignorewarnings -keep class * { public private *; } 49 | 50 | #如果有引用v4包可以添加下面这行 51 | -keep class android.support.v4.** { *; } 52 | -keep public class * extends android.support.v4.** 53 | -keep public class * extends android.app.Fragment 54 | 55 | -keepclasseswithmembernames class * { 56 | native ; 57 | } 58 | -keepclassmembers class * extends android.app.Activity{ 59 | public void *(android.view.View); 60 | } 61 | -keepclassmembers enum * { 62 | public static **[] values(); 63 | public static ** valueOf(java.lang.String); 64 | } 65 | -keep public class * extends android.view.View{ 66 | *** get*(); 67 | void set*(***); 68 | public (android.content.Context); 69 | public (android.content.Context, android.util.AttributeSet); 70 | public (android.content.Context, android.util.AttributeSet, int); 71 | } 72 | -keepclasseswithmembers class * { 73 | public (android.content.Context, android.util.AttributeSet); 74 | public (android.content.Context, android.util.AttributeSet, int); 75 | } 76 | -keep class * implements android.os.Parcelable { 77 | public static final android.os.Parcelable$Creator *; 78 | } 79 | -keepclassmembers class * implements java.io.Serializable { 80 | static final long serialVersionUID; 81 | private static final java.io.ObjectStreamField[] serialPersistentFields; 82 | private void writeObject(java.io.ObjectOutputStream); 83 | private void readObject(java.io.ObjectInputStream); 84 | java.lang.Object writeReplace(); 85 | java.lang.Object readResolve(); 86 | } 87 | #表示不混淆R文件中的所有静态字段 88 | -keep class **.R$* { 89 | public static ; 90 | } 91 | -keepclassmembers class * { 92 | void *(**On*Event); 93 | } 94 | #---------------------------------------------------------------------------- 95 | 96 | #---------------------------------webview------------------------------------ 97 | -keepclassmembers class fqcn.of.javascript.interface.for.Webview { 98 | public *; 99 | } 100 | -keepclassmembers class * extends android.webkit.WebViewClient { 101 | public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap); 102 | public boolean *(android.webkit.WebView, java.lang.String); 103 | } 104 | -keepclassmembers class * extends android.webkit.WebViewClient { 105 | public void *(android.webkit.WebView, jav.lang.String); 106 | } -------------------------------------------------------------------------------- /app/release/output.json: -------------------------------------------------------------------------------- 1 | [{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release","dirName":""},"path":"app-release.apk","properties":{}}] -------------------------------------------------------------------------------- /app/src/androidTest/java/leon/qujing/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package leon.qujing; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.InstrumentationRegistry; 6 | import androidx.test.runner.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getTargetContext(); 24 | 25 | assertEquals("leon.qujing", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 11 | 14 | 17 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/assets/pages/config.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 曲境 - 配置页面 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |

配置目标应用

13 |
14 | 15 |
16 |
17 | 21 |
22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | <#list packages as pk> 32 | style="font-weight: bold;" > 33 | 34 | 35 | 36 | 37 | 38 | 39 |
勾选图标应用名包名
${pk["DisplayName"]}${pk["PackageName"]}
40 |
41 |
42 | 46 |
47 |
48 |
49 |

曲境Mocha-Lee奉献

50 |

欢迎关注微信公众号“燕幕自安”(知识星球:爬虫三十六计)与我沟通交流

51 | 56 |
57 | 58 | 84 | 125 | -------------------------------------------------------------------------------- /app/src/main/assets/pages/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 曲境 - ${DisplayName} 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |

13 | 搜索 目标类 14 |

15 |
16 | 17 |
18 |
19 | 23 |
24 |

25 | ${DisplayName}(${ProcessID}) 26 |

27 |
28 | 29 | 30 |
31 | 34 |
35 |

下一步该做什么?

36 |
37 |
如果你还未搜索你的目标类
38 |
搜索栏输入你要查找的类名,点击"曲境一下"进行搜索。
39 |
搜索完成之后呢
40 |
点击搜索结果中对应的目标函数名进入目标函数控制页面。
41 |
如果你需要分析应用的网络报文
42 |
点击右侧的"曲境抓包"按钮即可体验。
43 |
44 |
45 |
46 |
47 |

曲境Mocha-Lee奉献

48 |

欢迎关注微信公众号“燕幕自安”(知识星球:爬虫三十六计)与我沟通交流

49 | 54 |
55 | 56 | 95 | 186 | -------------------------------------------------------------------------------- /app/src/main/assets/pages/manualguid.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 曲境 - 手动操作指导 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

手动操作指导

14 |
15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | <#list packages as pk> 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
图标应用名包名进程ID下一步
${pk["DisplayName"]}${pk["PackageName"]}${pk["ProcessID"]}${pk["nextStep"]}
35 |
36 |

下一步该做什么?

37 |
38 |
如果下一步显示“配置成功”
39 |
点击“配置成功”进入到该应用的控制页面
40 |
如果下一步显示“adb forward......”
41 |
应用启动了,但是你还没有进行端口转发,在cmd中执行这句命令。然后刷新页面。
42 |
如果你按照上述做法做了端口转发,但是依然显示adb forward.....,那么你需要Kill掉这个进程,重启一下APP,再刷新页面。
43 |
还是不行的话,就来曲境提一下issue吧。
44 |
如果下一步显示“APP未打开,请手动打开应用”
45 |
应用还未启动,请手动在手机(或模拟器)中启动应用。然后刷新页面。
46 |
47 |
48 |
49 |
50 |

曲境Mocha-Lee奉献

51 |

欢迎关注微信公众号“燕幕自安”(知识星球:爬虫三十六计)与我沟通交流

52 | 57 |
58 | 59 | 90 | 143 | -------------------------------------------------------------------------------- /app/src/main/assets/pages/methodview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 曲境 - ${DisplayName} 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |

13 | 监控/执行 目标方法 14 |

15 |
16 | 17 |
18 |

19 | ${DisplayName}(${ProcessID}) 20 |

21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | <#list parametersTypes as param> 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
目标方法所属类名${class}
目标方法${description}
参数类型${param.name}
返回值类型${returnType.name}
42 |
43 | 44 |
45 |

执行方法

46 |
47 | 48 | 49 | 50 | 51 | 53 | 61 | 62 | <#list parametersTypes as param> 63 | 64 | 65 | 84 | 85 | 86 |
选择实例 52 | 54 | 60 |
参数${param_index}: ${param.name} 66 | 67 | 83 |
87 |
88 | 92 | 95 |
96 | 97 |
98 |

99 | 100 | 监控方法 101 |

102 |
103 |
104 |
105 |

曲境Mocha-Lee奉献

106 |

欢迎关注微信公众号“燕幕自安”(知识星球:爬虫三十六计)与我沟通交流

107 | 112 |
113 | 114 | 141 | 211 | 271 | -------------------------------------------------------------------------------- /app/src/main/assets/pages/packetview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 曲境报文 - ${DisplayName} 6 | 194 | 316 | 317 | 318 | 319 | 320 |
321 |

曲境报文 - ${DisplayName}

322 |
323 | 324 |
325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 |
流向No.时间FlowID源地址目的地址SSL长度详情StringByte(inBase64)HexDump
343 |
344 | 368 | 369 |
370 |

曲境Mocha-Lee奉献

371 |

欢迎关注微信公众号“燕幕自安”(知识星球:爬虫三十六计)与我沟通交流

372 | 377 |
378 | 379 | -------------------------------------------------------------------------------- /app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | leon.qujing.XposedEntry -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/QuJingServer.java: -------------------------------------------------------------------------------- 1 | package leon.qujing; 2 | 3 | import android.app.AndroidAppHelper; 4 | import android.content.Context; 5 | import android.content.pm.PackageInfo; 6 | import android.content.pm.PackageManager; 7 | import android.os.Process; 8 | import android.util.Log; 9 | 10 | import java.io.File; 11 | import java.io.FileInputStream; 12 | import java.io.FileOutputStream; 13 | import java.io.IOException; 14 | import java.io.InputStreamReader; 15 | import java.io.StringWriter; 16 | import java.util.ArrayList; 17 | import java.util.HashMap; 18 | import java.util.Hashtable; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.concurrent.TimeoutException; 22 | 23 | import freemarker.template.Template; 24 | import freemarker.template.TemplateException; 25 | import leon.qujing.api.ClassView; 26 | import leon.qujing.api.Invoke; 27 | import leon.qujing.api.MethodView; 28 | import leon.qujing.api.PacketView; 29 | import leon.qujing.api.wsMethodView; 30 | import leon.qujing.api.wsPacketView; 31 | import leon.qujing.objectparser.BooleanParser; 32 | import leon.qujing.objectparser.ByteArrayParser; 33 | import leon.qujing.objectparser.ContextParser; 34 | import leon.qujing.objectparser.DoubleParser; 35 | import leon.qujing.objectparser.FloatParser; 36 | import leon.qujing.objectparser.GenericParser; 37 | import leon.qujing.objectparser.IntParser; 38 | import leon.qujing.objectparser.LongParser; 39 | import leon.qujing.objectparser.ShortParser; 40 | import leon.qujing.objectparser.StoredObjectParser; 41 | import leon.qujing.objectparser.StringArrayListParser; 42 | import leon.qujing.objectparser.StringArrayParser; 43 | import leon.qujing.objectparser.StringMapParser; 44 | import leon.qujing.objectparser.StringParser; 45 | import leon.qujing.utils.NanoHTTPD; 46 | import leon.qujing.utils.NanoWSD; 47 | import leon.qujing.utils.Utils; 48 | 49 | 50 | public class QuJingServer extends NanoWSD { 51 | public static HashMap parsers = new HashMap(); 52 | static Hashtable route = new Hashtable(); 53 | static Hashtable wsroute = new Hashtable(); 54 | static String g_StrTargetAPP = ""; 55 | 56 | public QuJingServer(int port) { 57 | this(port, null); 58 | } 59 | 60 | public QuJingServer(int port, Hashtable route) { 61 | super(port); 62 | if(port == 61000) { 63 | QuJingServer.route.put("/", new config()); 64 | QuJingServer.route.put("/settargetapp", new setTargetApp()); 65 | QuJingServer.route.put("/manualguid", new manualGuid()); 66 | QuJingServer.route.put("/querytargetapp", new queryTargetApp()); 67 | } 68 | else { 69 | //注册对象序列化/反序列化处理器 70 | 71 | //通用序列化 72 | parsers.put("store", new StoredObjectParser()); 73 | parsers.put("generic", new GenericParser()); 74 | 75 | //常见类型序列化web 76 | parsers.put("string", new StringParser()); 77 | parsers.put("Context", new ContextParser()); 78 | parsers.put("int", new IntParser()); 79 | parsers.put("short", new ShortParser()); 80 | parsers.put("long", new LongParser()); 81 | parsers.put("float", new FloatParser()); 82 | parsers.put("double", new DoubleParser()); 83 | parsers.put("boolean", new BooleanParser()); 84 | parsers.put("byte", new ByteArrayParser()); 85 | parsers.put("StringArray", new StringArrayParser()); 86 | parsers.put("StringArrayList", new StringArrayListParser()); 87 | parsers.put("StringMap", new StringMapParser()); 88 | 89 | //常见类型序列化android 90 | parsers.put("Ljava.lang.Integer;", new IntParser()); 91 | parsers.put("Ljava.lang.Boolean;", new BooleanParser()); 92 | parsers.put("Ljava.lang.String;", new StringParser()); 93 | parsers.put("I", new IntParser()); 94 | parsers.put("S", new ShortParser()); 95 | parsers.put("J", new LongParser()); 96 | parsers.put("F", new FloatParser()); 97 | parsers.put("D", new DoubleParser()); 98 | parsers.put("Z", new BooleanParser()); 99 | parsers.put("[B", new ByteArrayParser()); 100 | 101 | //注册WebSocket路由 102 | // wsroute.put("/", new wsTracer()); 103 | wsroute.put("/methodview", new wsMethodView()); 104 | wsroute.put("/packetview", new wsPacketView()); 105 | // wsroute.put("/wsTraceNew", new wsTracerNew()); 106 | //注册HTTP请求路由 107 | if (route != null) QuJingServer.route = route; 108 | QuJingServer.route.put("/", new index()); 109 | QuJingServer.route.put("/config", new index()); 110 | QuJingServer.route.put("/status", new status()); 111 | QuJingServer.route.put("/classview", new ClassView()); 112 | QuJingServer.route.put("/methodview", new MethodView()); 113 | QuJingServer.route.put("/packetview", new PacketView()); 114 | // QuJingServer.route.put("/tracer", new Tracer()); 115 | QuJingServer.route.put("/invoke", new Invoke()); 116 | // QuJingServer.route.put("/invoke2", new Invoke_New()); 117 | // QuJingServer.route.put("/memory", new MemoryView()); 118 | } 119 | try { 120 | //启动监听 121 | start(0, false); 122 | } catch (IOException e) { 123 | e.printStackTrace(); 124 | } 125 | } 126 | @Override 127 | protected WebSocket openWebSocket(IHTTPSession handshake) { 128 | //处理WebSocket路由 129 | wsOperation wsop = wsroute.get(handshake.getUri()); 130 | if (wsop != null) return wsop.handle(handshake); 131 | else return wsroute.get("/").handle(handshake); 132 | } 133 | 134 | @Override 135 | public Response serveHttp(IHTTPSession session) { 136 | //处理HTTP请求路由 137 | //先做一下基本解析 138 | Map files = new HashMap(); 139 | Map headers = null; 140 | Response resp = null; 141 | try { 142 | headers = session.getHeaders(); 143 | session.parseBody(files); 144 | } catch (Exception e) { 145 | e.printStackTrace(); 146 | } 147 | String uri = session.getUri(); 148 | //处理路由 149 | Operation operation = route.get(uri.toLowerCase()); 150 | if (operation == null) { 151 | try { 152 | XposedEntry.res.getAssets().open(uri.substring(1)); 153 | operation = new assets(); 154 | String msg = operation.handle(uri, session.getParms(), headers, files); 155 | resp = newFixedLengthResponse(Response.Status.OK, null, msg); 156 | resp.addHeader("Access-Control-Allow-Origin", "*"); 157 | return resp; 158 | } catch (IOException e) { 159 | operation = route.get("/"); 160 | } 161 | } 162 | resp = newFixedLengthResponse(operation.handle(uri, session.getParms(), headers, files)); 163 | resp.addHeader("Access-Control-Allow-Origin", "*"); 164 | return resp; 165 | } 166 | //供动态注册路由使用 167 | public void Register(String uri, Operation op) { 168 | route.put(uri, op); 169 | } 170 | public void Register(String uri, wsOperation op) { 171 | wsroute.put(uri, op); 172 | } 173 | 174 | //简单的模板引擎 175 | public static String render(Map model, String page) throws IOException, TemplateException { 176 | Template tmp = new Template(page, new InputStreamReader(XposedEntry.res.getAssets().open(page)), null); 177 | StringWriter sw = new StringWriter(); 178 | tmp.process(model, sw); 179 | return sw.toString(); 180 | } 181 | public static String file(String page) throws IOException, TemplateException { 182 | InputStreamReader reader = new InputStreamReader(XposedEntry.res.getAssets().open(page)); 183 | int ch; 184 | StringWriter sw = new StringWriter(); 185 | while ((ch = reader.read())!=-1){ 186 | sw.write(ch); 187 | } 188 | return sw.toString(); 189 | } 190 | 191 | //定义序列化/反序列化器 192 | public interface ObjectParser { 193 | Object parse(String data); 194 | String generate(Object obj); 195 | } 196 | //定义HTTP请求处理器 197 | public interface Operation { 198 | String handle(String url, Map parms, Map headers, Map files); 199 | } 200 | //定义WebSocket处理器 201 | public interface wsOperation { 202 | WebSocket handle(IHTTPSession handshake); 203 | } 204 | //默认主页(以及调用模板引擎的示例) 205 | public class index implements QuJingServer.Operation { 206 | @Override 207 | public String handle(String url, Map parms, Map headers, Map files) { 208 | try { 209 | Context context = AndroidAppHelper.currentApplication().getApplicationContext(); 210 | PackageManager pm = context.getPackageManager(); 211 | PackageInfo packageInfo = pm.getPackageInfo(context.getPackageName(), 0); 212 | 213 | Map map = new HashMap(); 214 | map.put("DisplayName", packageInfo.applicationInfo.loadLabel(context.getPackageManager()).toString()); 215 | map.put("IconBase64", Utils.drawableToByte(packageInfo.applicationInfo.loadIcon(context.getPackageManager()))); 216 | map.put("ProcessID", String.valueOf(Process.myPid())); 217 | 218 | return render(map, "pages/index.html"); 219 | } catch (Exception e) { 220 | e.printStackTrace(); 221 | return e.getLocalizedMessage(); 222 | } 223 | } 224 | } 225 | //状态页面 226 | public class status implements QuJingServer.Operation { 227 | @Override 228 | public String handle(String url, Map parms, Map headers, Map files) { 229 | try { 230 | return "OK"; 231 | } catch (Exception e) { 232 | e.printStackTrace(); 233 | return e.getLocalizedMessage(); 234 | } 235 | } 236 | } 237 | //配置主页(以及调用模板引擎的示例) 238 | public class config implements QuJingServer.Operation { 239 | @Override 240 | public String handle(String url, Map parms, Map headers, Map files) { 241 | try { 242 | Map map = new HashMap(); 243 | Context context = AndroidAppHelper.currentApplication().getApplicationContext(); 244 | List packageInfo = context.getPackageManager().getInstalledPackages(0); 245 | List> packages = new ArrayList<>(); 246 | 247 | String TargetAppsStr = g_StrTargetAPP; 248 | 249 | for (PackageInfo pk: packageInfo) { 250 | boolean bIsCheck = TargetAppsStr.contains(pk.packageName + ";"); 251 | Map pkinfo = new HashMap(); 252 | pkinfo.put("PackageName", pk.packageName); 253 | pkinfo.put("Check", bIsCheck?" checked=\"checked\" ":""); 254 | pkinfo.put("DisplayName", pk.applicationInfo.loadLabel(context.getPackageManager()).toString()); 255 | pkinfo.put("IconBase64", Utils.drawableToByte(pk.applicationInfo.loadIcon(context.getPackageManager()))); 256 | if(bIsCheck){ 257 | packages.add(0, pkinfo); 258 | } 259 | else{ 260 | packages.add(pkinfo); 261 | } 262 | 263 | } 264 | map.put("packages", packages); 265 | return render(map, "pages/config.html"); 266 | } catch (Exception e) { 267 | e.printStackTrace(); 268 | return e.getLocalizedMessage(); 269 | } 270 | } 271 | } 272 | 273 | //提交配置 274 | public class setTargetApp implements QuJingServer.Operation { 275 | @Override 276 | public String handle(String url, Map parms, Map headers, Map files) { 277 | try { 278 | Log.i("NANOHTTPD", "setTargetApp"); 279 | 280 | StringBuilder sb = new StringBuilder(); 281 | for(String key: parms.keySet()){ 282 | sb.append(key).append(";"); 283 | } 284 | String TargetAppsStr = sb.toString(); 285 | g_StrTargetAPP = TargetAppsStr; 286 | return "OK"; 287 | } catch (Exception e) { 288 | e.printStackTrace(); 289 | return e.getLocalizedMessage(); 290 | } 291 | } 292 | } 293 | 294 | //手动操作指导 295 | public class manualGuid implements QuJingServer.Operation { 296 | @Override 297 | public String handle(String url, Map parms, Map headers, Map files) { 298 | try { 299 | Map map = new HashMap(); 300 | Context context = AndroidAppHelper.currentApplication().getApplicationContext(); 301 | List packageInfo = context.getPackageManager().getInstalledPackages(0); 302 | List> packages = new ArrayList<>(); 303 | 304 | String TargetAppsStr = g_StrTargetAPP; 305 | 306 | for (PackageInfo pk: packageInfo) { 307 | boolean bIsCheck = TargetAppsStr.contains(pk.packageName + ";"); 308 | if(bIsCheck){ 309 | String dispalyName = pk.applicationInfo.loadLabel(context.getPackageManager()).toString(); 310 | Map pkinfo = new HashMap(); 311 | pkinfo.put("PackageName", pk.packageName); 312 | pkinfo.put("DisplayName", dispalyName); 313 | pkinfo.put("IconBase64", Utils.drawableToByte(pk.applicationInfo.loadIcon(context.getPackageManager()))); 314 | int pid = Utils.getAppPid(context, pk.packageName); 315 | if(pid == 0){ 316 | pkinfo.put("ProcessID", ""); 317 | pkinfo.put("nextStep", String.format("APP未打开,请手动打开应用(%s)", dispalyName)); 318 | }else { 319 | pkinfo.put("ProcessID", "" + pid); 320 | pkinfo.put("nextStep", ""); 321 | } 322 | packages.add(0, pkinfo); 323 | } 324 | } 325 | map.put("packages", packages); 326 | return render(map, "pages/manualguid.html"); 327 | } catch (Exception e) { 328 | e.printStackTrace(); 329 | return e.getLocalizedMessage(); 330 | } 331 | } 332 | } 333 | 334 | //手动操作指导 335 | public class queryTargetApp implements QuJingServer.Operation { 336 | @Override 337 | public String handle(String url, Map parms, Map headers, Map files) { 338 | try { 339 | return g_StrTargetAPP; 340 | } catch (Exception e) { 341 | e.printStackTrace(); 342 | return e.getLocalizedMessage(); 343 | } 344 | } 345 | } 346 | 347 | // 资源文件 348 | public class assets implements QuJingServer.Operation { 349 | @Override 350 | public String handle(String url, Map parms, Map headers, Map files) { 351 | try { 352 | return file(url.substring(1)); 353 | } catch (Exception e) { 354 | e.printStackTrace(); 355 | return e.getLocalizedMessage(); 356 | } 357 | } 358 | } 359 | } -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/XposedEntry.java: -------------------------------------------------------------------------------- 1 | package leon.qujing; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.content.pm.ApplicationInfo; 6 | import android.content.res.XModuleResources; 7 | import android.os.Build; 8 | import android.os.Process; 9 | 10 | import java.io.BufferedReader; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.io.InputStreamReader; 14 | import java.net.HttpURLConnection; 15 | import java.net.MalformedURLException; 16 | import java.net.ProtocolException; 17 | import java.net.URL; 18 | 19 | import de.robv.android.xposed.IXposedHookLoadPackage; 20 | import de.robv.android.xposed.IXposedHookZygoteInit; 21 | import de.robv.android.xposed.XC_MethodHook; 22 | import de.robv.android.xposed.XC_MethodReplacement; 23 | import de.robv.android.xposed.XSharedPreferences; 24 | import de.robv.android.xposed.XposedBridge; 25 | import de.robv.android.xposed.XposedHelpers; 26 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 27 | 28 | 29 | public class XposedEntry implements IXposedHookLoadPackage, IXposedHookZygoteInit { 30 | public static XModuleResources res; 31 | public static ClassLoader classLoader; 32 | public static XSharedPreferences sPrefs; 33 | public static String packageName; 34 | Boolean isFirstApplication; 35 | String processName; 36 | ApplicationInfo appInfo; 37 | public static String StartupAPP = "android"; 38 | 39 | @Override 40 | public void initZygote(StartupParam startupParam) throws Throwable { 41 | res = XModuleResources.createInstance(startupParam.modulePath, null); 42 | try{ 43 | 44 | } 45 | catch (Exception e){ 46 | e.printStackTrace(); 47 | } 48 | } 49 | 50 | private boolean isNeedHook() throws IOException { 51 | BufferedReader reader = null; 52 | try { 53 | HttpURLConnection connection = null; 54 | URL url = new URL("http://127.0.0.1:61000/querytargetapp"); 55 | connection = (HttpURLConnection) url.openConnection(); 56 | connection.setRequestMethod("GET"); 57 | connection.setConnectTimeout(1000); 58 | connection.setReadTimeout(1000); 59 | InputStream in = connection.getInputStream(); 60 | reader = new BufferedReader(new InputStreamReader(in)); 61 | } 62 | catch (Exception e){ 63 | try { 64 | XposedBridge.log("HttpURLConnection Exception:"+e.getMessage()); 65 | XposedBridge.log("try curl access http"); 66 | java.lang.Process p = Runtime.getRuntime().exec("/system/bin/curl http://127.0.0.1:61000/querytargetapp"); 67 | p.waitFor(); 68 | reader = new BufferedReader(new InputStreamReader(p.getInputStream())); 69 | } catch (Exception e2) { 70 | XposedBridge.log("curl Exception:"+e2.getMessage()); 71 | XposedBridge.log(e2); 72 | XposedBridge.log("被注入应用如果没有网络权限,曲境将无法运行"); 73 | return false; 74 | } 75 | } 76 | StringBuilder result = new StringBuilder(); 77 | String line; 78 | while ((line = reader.readLine()) != null) { 79 | result.append(line); 80 | } 81 | String TargetAppsStr = result.toString(); 82 | XposedBridge.log("TargetAppsStr:" + TargetAppsStr); 83 | return TargetAppsStr.contains(XposedEntry.packageName + ";"); 84 | } 85 | 86 | @Override 87 | public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { 88 | if(loadPackageParam.packageName.equals(StartupAPP)) 89 | { 90 | new QuJingServer(61000); 91 | XposedBridge.log("server start at 61000."); 92 | 93 | if (Build.VERSION.SDK_INT >= 24) 94 | { 95 | XposedHelpers.findAndHookMethod("android.app.ContextImpl", loadPackageParam.classLoader, "checkMode",int.class, XC_MethodReplacement.returnConstant(null)); 96 | } 97 | return; 98 | } 99 | 100 | if (loadPackageParam.processName.contains(":")) return; 101 | gatherInfo(loadPackageParam); 102 | XposedHelpers.findAndHookMethod("com.android.okhttp.HttpHandler$CleartextURLFilter", XposedEntry.classLoader,"checkURLPermitted", URL.class, new XC_MethodHook() { 103 | @Override 104 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 105 | XposedBridge.log("HttpHandler$CleartextURLFilter skip, packageName:"+ XposedEntry.packageName); 106 | param.setResult(null); 107 | } 108 | }); 109 | disableHooks(); 110 | XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, 111 | new XC_MethodHook() { 112 | @Override 113 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 114 | Context context = (Context) param.args[0]; 115 | classLoader = context.getClassLoader(); 116 | new Thread(new Runnable() { 117 | @Override 118 | public void run() { 119 | boolean isHook = false; 120 | try { 121 | isHook = isNeedHook(); 122 | } catch (IOException e) { 123 | XposedBridge.log(e); 124 | } 125 | XposedBridge.log(XposedEntry.packageName + " isHook: "+isHook); 126 | if (isHook) { 127 | int pid = Process.myPid(); 128 | new QuJingServer(pid); 129 | XposedBridge.log("QuJingServer Listening @:"+ pid +" packageName: "+XposedEntry.packageName); 130 | } 131 | } 132 | }).start(); 133 | } 134 | }); 135 | } 136 | 137 | private void disableHooks(){ 138 | XposedHelpers.findAndHookMethod(ClassLoader.class,"loadClass",String.class,new XC_MethodHook(){ 139 | @Override 140 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable{ 141 | if(param.args!=null && param.args[0] != null && param.args[0].toString().startsWith("de.robv.android.xposed.")){ 142 | //改成一个不存在的类 143 | param.args[0]="de.robv.android.xposed.ThTest"; 144 | } 145 | super.beforeHookedMethod(param); 146 | } 147 | }); 148 | XposedHelpers.findAndHookMethod(Class.class,"getDeclaredField",String.class,new XC_MethodHook(){ 149 | @Override 150 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable{ 151 | if(param.args!=null && param.args[0] != null && param.args[0].toString().equals("disableHooks")){ 152 | param.args[0]="disableHook"; 153 | } 154 | super.beforeHookedMethod(param); 155 | } 156 | }); 157 | } 158 | 159 | private void gatherInfo(XC_LoadPackage.LoadPackageParam loadPackageParam) { 160 | packageName = loadPackageParam.packageName; 161 | isFirstApplication = loadPackageParam.isFirstApplication; 162 | // classLoader = loadPackageParam.classLoader; 163 | processName = loadPackageParam.processName; 164 | appInfo = loadPackageParam.appInfo; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/api/ClassView.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.api; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import de.robv.android.xposed.XposedBridge; 9 | import leon.qujing.QuJingServer; 10 | import leon.qujing.XposedEntry; 11 | import leon.qujing.handler.ClassHandler; 12 | 13 | //类详情查看页面 14 | public class ClassView implements QuJingServer.Operation { 15 | @Override 16 | public String handle(String url, Map parms, Map headers, Map files) { 17 | try { 18 | XposedBridge.log("find_class:"+parms.get("class")); 19 | HashMap detail = ClassHandler.getClassDetail( 20 | ClassHandler.findClassbyName(parms.get("class"), XposedEntry.classLoader)); 21 | return JSON.toJSONString(detail); 22 | } catch (Exception e) { 23 | return e.getLocalizedMessage(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/api/Invoke.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.api; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.Map; 7 | 8 | import leon.qujing.QuJingServer; 9 | import leon.qujing.XposedEntry; 10 | import leon.qujing.handler.ObjectHandler; 11 | import leon.qujing.utils.Utils; 12 | 13 | import static leon.qujing.QuJingServer.parsers; 14 | 15 | //处理反射调用 16 | public class Invoke implements QuJingServer.Operation { 17 | @Override 18 | public String handle(String url, Map parms, Map headers, Map files) { 19 | StringBuilder sb = new StringBuilder(); 20 | Object thisobj; 21 | Object[] params; 22 | thisobj = parms.get("thisobj").equals("null") ? null : ObjectHandler.objects.get(parms.get("thisobj")); 23 | 24 | try { 25 | Method[] methods = Class.forName(parms.get("class"), false, XposedEntry.classLoader).getDeclaredMethods(); 26 | Method m = methods[Integer.parseInt(parms.get("method"))]; 27 | params = new Object[m.getParameterTypes().length]; 28 | for (int i = 0; i < m.getParameterTypes().length; i++) { 29 | if (parms.get("parser" + i).equals("null")){ 30 | params[i] = null; 31 | } 32 | else { 33 | params[i] = QuJingServer.parsers.get(parms.get("parser" + i)).parse(parms.get("param" + i)); 34 | } 35 | } 36 | m.setAccessible(true); 37 | sb.append(translate(m.invoke(thisobj, params))); 38 | } catch (Exception e) { 39 | sb.append("执行异常,报错信息: ").append(e.getLocalizedMessage()); 40 | } 41 | return sb.toString(); 42 | } 43 | 44 | private String translate(Object obj) { 45 | //Write your translator here. 46 | if (obj == null) return "null"; 47 | if (obj.getClass().getName().equals("java.lang.String")) return obj.toString(); 48 | else if(Utils.getTypeSignature(obj.getClass()).equals("[B"))return parsers.get("[B").generate(obj); 49 | else return JSON.toJSONString(obj); 50 | } 51 | 52 | public static boolean isMe(){ 53 | StackTraceElement[] stacks = Thread.currentThread().getStackTrace(); 54 | for (int i = 4; i parms, Map headers, Map files) { 25 | try { 26 | Context context = AndroidAppHelper.currentApplication().getApplicationContext(); 27 | PackageManager pm = context.getPackageManager(); 28 | PackageInfo packageInfo = pm.getPackageInfo(context.getPackageName(), 0); 29 | Method method = null; 30 | if(parms.get("javaname")!=null) { 31 | method = MethodHandler.getMethodbyJavaName(parms.get("javaname")); 32 | for (int i = 0; i < method.getDeclaringClass().getDeclaredMethods().length; i++) { 33 | if(Utils.getJavaName(method.getDeclaringClass().getDeclaredMethods()[i]).equals(parms.get("javaname"))){ 34 | parms.put("method",""+i); 35 | } 36 | } 37 | } 38 | if(method==null)method=ClassHandler.findClassbyName(parms.get("class"),XposedEntry.classLoader).getDeclaredMethods()[Integer.parseInt(parms.get("method"))]; 39 | HashMap map = MethodHandler.getMethodDetail(method); 40 | map.put("method", parms.get("method")); 41 | map.put("objList", ObjectHandler.objects.keySet()); 42 | map.put("DisplayName", packageInfo.applicationInfo.loadLabel(context.getPackageManager()).toString()); 43 | map.put("IconBase64", Utils.drawableToByte(packageInfo.applicationInfo.loadIcon(context.getPackageManager()))); 44 | map.put("ProcessID", String.valueOf(Process.myPid())); 45 | return QuJingServer.render(map, "pages/methodview.html"); 46 | } catch (Exception e) { 47 | XposedBridge.log(e); 48 | return e.getLocalizedMessage(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/api/PacketView.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.api; 2 | 3 | import android.app.AndroidAppHelper; 4 | import android.content.Context; 5 | import android.content.pm.PackageInfo; 6 | import android.content.pm.PackageManager; 7 | import android.os.Process; 8 | 9 | import java.lang.reflect.Method; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import leon.qujing.QuJingServer; 14 | import leon.qujing.XposedEntry; 15 | import leon.qujing.handler.ClassHandler; 16 | import leon.qujing.handler.MethodHandler; 17 | import leon.qujing.handler.ObjectHandler; 18 | import leon.qujing.utils.Utils; 19 | 20 | //查看方法详情页面 21 | public class PacketView implements QuJingServer.Operation { 22 | @Override 23 | public String handle(String url, Map parms, Map headers, Map files) { 24 | try { 25 | Context context = AndroidAppHelper.currentApplication().getApplicationContext(); 26 | PackageManager pm = context.getPackageManager(); 27 | PackageInfo packageInfo = pm.getPackageInfo(context.getPackageName(), 0); 28 | HashMap map = new HashMap(); 29 | map.put("DisplayName", packageInfo.applicationInfo.loadLabel(context.getPackageManager()).toString()); 30 | map.put("ProcessID", String.valueOf(Process.myPid())); 31 | return QuJingServer.render(map, "pages/packetview.html"); 32 | } catch (Exception e) { 33 | return e.getLocalizedMessage(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/api/wsMethodView.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.api; 2 | 3 | import android.os.Process; 4 | 5 | import com.alibaba.fastjson.JSON; 6 | import org.json.JSONArray; 7 | import org.json.JSONObject; 8 | 9 | import java.io.IOException; 10 | import java.lang.reflect.Member; 11 | import java.lang.reflect.Method; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | import java.util.Random; 15 | import java.util.regex.Pattern; 16 | 17 | import de.robv.android.xposed.XC_MethodHook; 18 | import de.robv.android.xposed.XposedBridge; 19 | import leon.qujing.QuJingServer; 20 | import leon.qujing.XposedEntry; 21 | import leon.qujing.handler.MethodHandler; 22 | import leon.qujing.handler.ObjectHandler; 23 | import leon.qujing.utils.NanoHTTPD; 24 | import leon.qujing.utils.NanoWSD; 25 | import leon.qujing.utils.Utils; 26 | 27 | import static leon.qujing.QuJingServer.parsers; 28 | 29 | //单方法监控的WebSocket处理 30 | public class wsMethodView implements QuJingServer.wsOperation { 31 | @Override 32 | public NanoWSD.WebSocket handle(NanoHTTPD.IHTTPSession handshake) { 33 | return new ws(handshake); 34 | } 35 | 36 | public class ws extends NanoWSD.WebSocket { 37 | public Method m = null; 38 | // public String server="http://127.0.0.1:8000";//TODO +Process.myPid(); 39 | XC_MethodHook.Unhook unhook = null; 40 | MethodHook myHook = new MethodHook(this); 41 | boolean modify = true; 42 | HashMap objs = new HashMap<>(); 43 | 44 | public ws(NanoHTTPD.IHTTPSession handshakeRequest) { 45 | super(handshakeRequest); 46 | try { 47 | Map args = handshakeRequest.getParms(); 48 | if(args.get("javaname")!=null) { 49 | m = MethodHandler.getMethodbyJavaName(args.get("javaname")); 50 | } 51 | if(m==null)m = Class.forName(args.get("class"), false, XposedEntry.classLoader) 52 | .getDeclaredMethods()[Integer.parseInt(args.get("method"))]; 53 | } catch (Exception e) { 54 | e.printStackTrace(); 55 | } 56 | } 57 | 58 | @Override 59 | protected void onOpen() { 60 | unhook = myHook.hook(m); 61 | if (unhook != null) try { 62 | sendLog("

开始监控函数: "+ m.getName() +"

"); 63 | sendUpdateObj(); 64 | } catch (IOException e) { 65 | e.printStackTrace(); 66 | } 67 | } 68 | 69 | @Override 70 | protected void onClose(NanoWSD.WebSocketFrame.CloseCode code, String reason, boolean initiatedByRemote) { 71 | unhook.unhook(); 72 | } 73 | 74 | @Override 75 | protected void onMessage(NanoWSD.WebSocketFrame message) { 76 | 77 | } 78 | 79 | @Override 80 | protected void onPong(NanoWSD.WebSocketFrame pong) { 81 | 82 | } 83 | 84 | @Override 85 | protected void onException(IOException exception) { 86 | 87 | } 88 | 89 | void sendLog(String Msg) throws IOException { 90 | Map object = new HashMap(); 91 | object.put("op", "msg"); 92 | object.put("msg", Msg); 93 | SendThread st = new SendThread(); 94 | st.setObj(object, this); 95 | new Thread(st).start(); 96 | } 97 | 98 | void sendUpdateObj() throws IOException { 99 | Map object = new HashMap(); 100 | object.put("op", "updatethis"); 101 | object.put("data", new JSONArray(ObjectHandler.objects.keySet())); 102 | send(new JSONObject(object).toString()); 103 | } 104 | } 105 | 106 | public class SendThread implements Runnable { 107 | Map mObject = null; 108 | ws myws = null; 109 | public void setObj(Map object, ws ws_){ 110 | mObject = object; 111 | myws = ws_; 112 | } 113 | 114 | @Override 115 | public void run() { 116 | try { 117 | myws.send(new JSONObject(mObject).toString()); 118 | } catch (Exception e) { 119 | e.printStackTrace(); 120 | } 121 | } 122 | } 123 | public class MethodHook extends XC_MethodHook { 124 | public ws myws; 125 | public Member method; //被Hook的方法 126 | public Object thisObject; //方法被调用时的this对象 127 | public Object[] args; //方法被调用时的参数 128 | private Object result = null; //方法被调用后的返回结果 129 | private int pid = 0; 130 | 131 | MethodHook(ws ws) { 132 | myws = ws; 133 | } 134 | 135 | public void setPid(int pid) { 136 | this.pid = pid; 137 | } 138 | 139 | private void gatherInfo(MethodHookParam param) { 140 | method = param.method; 141 | thisObject = param.thisObject; 142 | args = param.args; 143 | } 144 | 145 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 146 | super.beforeHookedMethod(param); 147 | if (pid > 0 && pid != Process.myPid()) return; 148 | gatherInfo(param); 149 | if(Invoke.isMe()){ 150 | StringBuilder sb=new StringBuilder(); 151 | StackTraceElement[] stacks = Thread.currentThread().getStackTrace(); 152 | sb.append("
"); 153 | sb.append("远程反射调用执行"); 154 | for (int i = 0; i "+stacks[i].getClassName()+"."+stacks[i].getMethodName()+"

"); 156 | } 157 | sb.append("/
"); 158 | myws.sendLog(sb.toString()); 159 | return; 160 | } 161 | if (thisObject != null) { 162 | if(!ObjectHandler.objects.containsValue(thisObject)){ 163 | ObjectHandler.objects.put(thisObject.getClass().getName() + "@" + Integer.toHexString(new Random().nextInt()), thisObject); 164 | myws.sendUpdateObj(); 165 | } 166 | } 167 | StringBuilder sb = new StringBuilder(); 168 | sb.append("
"); 169 | sb.append("[" + Utils.getPrecisionStandardTime() + "] " + method.getDeclaringClass().getName() + "." + MethodDescription(param) + " 被调用"); 170 | sb.append("
"); 171 | sb.append("查看调用堆栈"); 172 | StackTraceElement[] strace = Thread.currentThread().getStackTrace(); 173 | for (int i = 4; i < strace.length; i++) { 174 | sb.append(" at " + strace[i].getClassName() + "." + strace[i].getMethodName() + " : " + strace[i].getLineNumber() + "
"); 175 | } 176 | sb.append("
"); 177 | sb.append("
"); 178 | try { 179 | if (args != null) 180 | for (int i = 0; i < args.length; i++) { 181 | if(args[i] == null){ 182 | sb.append("
参数" + i + " " + "null
"); 183 | } 184 | else { 185 | sb.append("
参数" + i + " " + args[i].getClass().getName() + "
"); 186 | sb.append("
" + translate(args[i]) + "
"); 187 | } 188 | } 189 | } catch (Exception e) { 190 | sb.append("

" + e.getLocalizedMessage() + "

"); 191 | } finally { 192 | sb.append("
"); 193 | log(sb.toString()); 194 | } 195 | // if (myws.modify) { 196 | // HashMap json=new HashMap(); 197 | // json.put("method", Utils.getJavaName(myws.m)); 198 | // if(thisObject!=null)json.put("this",ObjectHandler.saveObject(thisObject)); 199 | // ArrayList params=new ArrayList(); 200 | // if(args!=null)for (Object arg:args) { 201 | // params.add(ObjectHandler.saveObject(arg)); 202 | // } 203 | // json.put("params",params.toArray()); 204 | // ArrayList stacks=new ArrayList(); 205 | // for (StackTraceElement element: Thread.currentThread().getStackTrace()) { 206 | // stacks.add(element.getClassName() + "." + element.getMethodName() + " : " + element.getLineNumber()); 207 | // } 208 | // json.put("stack",stacks); 209 | // param.setResult( 210 | // ObjectHandler.parseObject( 211 | // new netUtil(myws.server + "/invoke2", new JSONObject(json).toString(2)).getRet() 212 | // ) 213 | // ); 214 | // } 215 | } 216 | 217 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 218 | super.beforeHookedMethod(param); 219 | if (pid > 0 && pid != Process.myPid()) return; 220 | gatherInfo(param); 221 | if(Invoke.isMe())return; 222 | result = param.getResult(); 223 | //Write your code here. 224 | if (myws.modify) { 225 | //param.setResult(requestResult(result)); 226 | } 227 | StringBuilder sb = new StringBuilder(); 228 | //sb.append("
"); 229 | //sb.append("["+Process.myPid() +"]"+ method.getDeclaringClass().getName() + "." + MethodDescription(param).toString() +" Returned"); 230 | sb.append("
"); 231 | try { 232 | if (param.getThrowable() == null) { 233 | sb.append("
调用返回
" + translate(result) + "
"); 234 | } else { 235 | sb.append("
Throw
" + translate(param.getThrowable()) + "
"); 236 | } 237 | } catch (Throwable e) { 238 | sb.append("

" + e.getLocalizedMessage() + "

"); 239 | } finally { 240 | //log(""); 241 | sb.append("
"); 242 | log(sb.toString()); 243 | } 244 | } 245 | 246 | private void log(String log) { 247 | //You can add your own logger here. 248 | //e.g filelogger like Xlog.log(log); 249 | try { 250 | myws.sendLog(log); 251 | } catch (IOException e) { 252 | e.printStackTrace(); 253 | } 254 | } 255 | 256 | private String translate(Object obj) { 257 | //Write your translator here. 258 | try { 259 | if (obj == null) return "null"; 260 | if (obj.getClass().getName().equals("java.lang.String")) return obj.toString(); 261 | else if(Utils.getTypeSignature(obj.getClass()).equals("[B"))return parsers.get("[B").generate(obj); 262 | else return JSON.toJSONString(obj); 263 | } 264 | catch (Exception e){ 265 | e.printStackTrace(); 266 | return "params translate ERROR."; 267 | } 268 | } 269 | 270 | private String MethodDescription(MethodHookParam param) { 271 | StringBuilder sb = new StringBuilder(); 272 | sb.append(method.getName().toString()); 273 | sb.append("("); 274 | if (args != null) for (Object arg : args) { 275 | if (arg == null) sb.append("UnknownType"); 276 | else if (arg.getClass().isPrimitive()) sb.append(arg.getClass().getSimpleName()); 277 | else sb.append(arg.getClass().getName()); 278 | sb.append(","); 279 | } 280 | sb.append(")"); 281 | return sb.toString(); 282 | } 283 | 284 | public Unhook hook(Member method) { 285 | return XposedBridge.hookMethod(method, this); 286 | } 287 | 288 | public void hook(String clzn, String methodRegEx) throws ClassNotFoundException { 289 | Pattern pattern = Pattern.compile(methodRegEx); 290 | for (Member method : Class.forName(clzn, false, XposedEntry.classLoader).getDeclaredMethods()) { 291 | if (pattern.matcher(method.getName()).matches() && !method.isSynthetic()) 292 | this.hook(method); 293 | } 294 | } 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/api/wsPacketView.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.api; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.text.Html; 5 | import android.util.Base64; 6 | 7 | import com.alibaba.fastjson.JSON; 8 | 9 | import org.json.JSONObject; 10 | import java.io.IOException; 11 | import java.net.InetAddress; 12 | import java.net.Socket; 13 | import java.security.NoSuchAlgorithmException; 14 | import java.util.ArrayList; 15 | import java.util.Arrays; 16 | import java.util.Calendar; 17 | import java.util.HashMap; 18 | import java.util.List; 19 | import java.util.Map; 20 | import de.robv.android.xposed.XC_MethodHook; 21 | import de.robv.android.xposed.XposedBridge; 22 | import de.robv.android.xposed.XposedHelpers; 23 | import leon.qujing.QuJingServer; 24 | import leon.qujing.XposedEntry; 25 | import leon.qujing.utils.NanoHTTPD; 26 | import leon.qujing.utils.NanoWSD; 27 | import leon.qujing.utils.Utils; 28 | 29 | 30 | import static leon.qujing.utils.Utils.md5Hash; 31 | 32 | 33 | //单方法监控的WebSocket处理 34 | public class wsPacketView implements QuJingServer.wsOperation { 35 | @Override 36 | public NanoWSD.WebSocket handle(NanoHTTPD.IHTTPSession handshake) { 37 | return new ws(handshake); 38 | } 39 | 40 | public class ws extends NanoWSD.WebSocket { 41 | List unhookList = new ArrayList<>(); 42 | public XC_MethodHook sslWriteCallback = new XC_MethodHook() { 43 | @SuppressLint("DefaultLocale") 44 | @Override 45 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 46 | byte[] body = (byte[])param.args[0]; 47 | int byteCount = (int)param.args[2]; 48 | Object this_0 = XposedHelpers.getObjectField(param.thisObject, "this$0"); 49 | Socket socket = (Socket)XposedHelpers.getObjectField(this_0, "socket"); 50 | collectAndPack(socket, body, byteCount, "write", 1); 51 | } 52 | }; 53 | public XC_MethodHook sslReadCallback = new XC_MethodHook() { 54 | @SuppressLint("DefaultLocale") 55 | @Override 56 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 57 | byte[] body = (byte[])param.args[0]; 58 | int byteCount = (int)param.args[2]; 59 | Object this_0 = XposedHelpers.getObjectField(param.thisObject, "this$0"); 60 | Socket socket = (Socket)XposedHelpers.getObjectField(this_0, "socket"); 61 | collectAndPack(socket, body, byteCount, "read", 1); 62 | } 63 | }; 64 | 65 | public ws(NanoHTTPD.IHTTPSession handshakeRequest) { 66 | super(handshakeRequest); 67 | } 68 | 69 | @SuppressLint("DefaultLocale") 70 | private void collectAndPack(Socket socket, byte[] body, int byteCount, String flowType, int is_ssl) throws Throwable { 71 | InetAddress localAddr = socket.getLocalAddress(); 72 | InetAddress inetAddr = socket.getInetAddress(); 73 | HashMap msg = new HashMap(); 74 | Calendar calendar = Calendar.getInstance(); 75 | msg.put("flow_type", flowType); 76 | msg.put("flow_id", md5Hash(socket.toString()).substring(8, 16)); 77 | msg.put("is_ssl", 1); 78 | msg.put("time", String.format("%02d:%02d:%02d.%03d", calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), calendar.get(Calendar.SECOND), calendar.get(Calendar.MILLISECOND))); 79 | msg.put("local_ip", localAddr.getHostAddress()); 80 | msg.put("local_host", localAddr.getHostName()); 81 | msg.put("local_port", socket.getLocalPort()+""); 82 | msg.put("remote_ip", inetAddr.getHostAddress()); 83 | msg.put("remote_host", inetAddr.getHostName()); 84 | msg.put("remote_port", socket.getPort()+""); 85 | msg.put("body_plain", Html.escapeHtml(new String(body, 0, byteCount,"UTF-8"))); 86 | msg.put("body_hexdump", Utils.formatHexDump(body, 0, byteCount));; 87 | msg.put("body_base64", Base64.encodeToString(body, 0, byteCount, Base64.NO_WRAP)); 88 | msg.put("body_length", byteCount+""); 89 | sendMsg("add", msg); 90 | } 91 | 92 | @Override 93 | protected void onOpen() { 94 | try { 95 | HashMap msg = new HashMap(); 96 | msg.put("body_plain", "capture packet now."); 97 | sendMsg("add", msg); 98 | makeHook(); 99 | } catch (Exception e) { 100 | XposedBridge.log("onOpen error. e:" + e.toString()); 101 | } 102 | } 103 | 104 | @Override 105 | protected void onClose(NanoWSD.WebSocketFrame.CloseCode code, String reason, boolean initiatedByRemote) { 106 | for (XC_MethodHook.Unhook unhook: unhookList) { 107 | unhook.unhook(); 108 | } 109 | } 110 | 111 | @Override 112 | protected void onMessage(NanoWSD.WebSocketFrame message) { 113 | 114 | } 115 | 116 | @Override 117 | protected void onPong(NanoWSD.WebSocketFrame pong) { 118 | 119 | } 120 | 121 | @Override 122 | protected void onException(IOException exception) { 123 | 124 | } 125 | 126 | void sendMsg(String cmd, HashMap msg) throws IOException { 127 | msg.put("resource", "flows"); 128 | msg.put("cmd", cmd);; 129 | SendThread st = new SendThread(); 130 | st.setObj(msg, this); 131 | new Thread(st).start(); 132 | } 133 | 134 | public void makeHook(){ 135 | XposedBridge.log("curr sdk:"+android.os.Build.VERSION.SDK_INT); 136 | if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.O){ 137 | 138 | unhookList.add(XposedHelpers.findAndHookMethod( 139 | "com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream", 140 | XposedEntry.classLoader, "write", byte[].class, int.class, int.class, sslWriteCallback)); 141 | 142 | unhookList.add(XposedHelpers.findAndHookMethod( 143 | "com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream", 144 | XposedEntry.classLoader, "read", byte[].class, int.class, int.class, sslReadCallback)); 145 | } 146 | else { 147 | unhookList.add(XposedHelpers.findAndHookMethod( 148 | "com.android.org.conscrypt.OpenSSLSocketImpl$SSLOutputStream", 149 | XposedEntry.classLoader, "write", byte[].class, int.class, int.class, sslWriteCallback)); 150 | unhookList.add(XposedHelpers.findAndHookMethod( 151 | "com.android.org.conscrypt.OpenSSLSocketImpl$SSLInputStream", 152 | XposedEntry.classLoader, "read", byte[].class, int.class, int.class, sslReadCallback)); 153 | } 154 | } 155 | 156 | } 157 | 158 | public class SendThread implements Runnable { 159 | Map mObject = null; 160 | ws myws = null; 161 | public void setObj(Map object, ws ws_){ 162 | mObject = object; 163 | myws = ws_; 164 | } 165 | 166 | @Override 167 | public void run() { 168 | try { 169 | myws.send(JSON.toJSONString(mObject)); 170 | } catch (Exception e) { 171 | e.printStackTrace(); 172 | } 173 | } 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/handler/ClassHandler.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.handler; 2 | 3 | import android.util.Log; 4 | 5 | import com.alibaba.fastjson.JSON; 6 | 7 | import java.lang.reflect.Field; 8 | import java.lang.reflect.Method; 9 | import java.util.ArrayList; 10 | import java.util.HashMap; 11 | 12 | import leon.qujing.utils.DexHelper; 13 | import leon.qujing.utils.Utils; 14 | 15 | //处理类相关的内容 16 | public class ClassHandler { 17 | public static String[] getAllClasses(ClassLoader classLoader) { 18 | return DexHelper.getClassesInDex(classLoader); 19 | } 20 | 21 | public static HashMap getClassDetail(Class clz) { 22 | HashMap detail = new HashMap(); 23 | if (clz == null) { 24 | Log.i("ClassHandler", "getClassDetail, clz is null."); 25 | return detail; 26 | } 27 | detail.put("name", clz.getName()); 28 | ArrayList methodDescriptions = new ArrayList(); 29 | ArrayList fieldDescriptions = new ArrayList(); 30 | for (Method method : clz.getDeclaredMethods()) { 31 | methodDescriptions.add(Utils.MethodDescription(method)); 32 | Log.i("ClassHandler", "getClassDetail, MethodDescription" + Utils.MethodDescription(method)); 33 | } 34 | for (Field field : clz.getFields()) { 35 | fieldDescriptions.add(Utils.FieldDescription(field)); 36 | Log.i("ClassHandler", "getClassDetail, FieldDescription" + Utils.FieldDescription(field)); 37 | } 38 | detail.put("methodList", methodDescriptions); 39 | detail.put("fieldList", fieldDescriptions); 40 | return detail; 41 | } 42 | 43 | public static Class findClassbyName(String clzName, ClassLoader classLoader) { 44 | try { 45 | return Class.forName(clzName, false, classLoader); 46 | } catch (ClassNotFoundException e) { 47 | return null; 48 | } 49 | } 50 | 51 | public static Class findClassbyJavaName(String javaName, ClassLoader classLoader) { 52 | try { 53 | String clzName = javaName.replace('/', '.').replace(";", ""); 54 | clzName = (clzName.charAt(0) == 'L') ? clzName.substring(1) : clzName; 55 | return Class.forName(clzName, false, classLoader); 56 | } catch (ClassNotFoundException e) { 57 | return null; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/handler/HookHandler.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.handler; 2 | 3 | import de.robv.android.xposed.XC_MethodHook; 4 | 5 | //处理hook相关内容 6 | public class HookHandler { 7 | static boolean AddHook() { 8 | return false; 9 | } 10 | 11 | static boolean removeHook() { 12 | return false; 13 | } 14 | 15 | static XC_MethodHook[] listHooks() { 16 | return null; 17 | } 18 | 19 | static XC_MethodHook loadHook() { 20 | return null; 21 | } 22 | 23 | static XC_MethodHook wsHook() { 24 | return null; 25 | } 26 | 27 | static XC_MethodHook premadeHook() { 28 | return null; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/handler/MemoryHandler.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.handler; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileInputStream; 6 | import java.io.IOException; 7 | import java.io.InputStreamReader; 8 | import java.lang.reflect.InvocationTargetException; 9 | import java.lang.reflect.Method; 10 | import java.util.HashMap; 11 | 12 | public class MemoryHandler { 13 | static HashMap Memory = new HashMap<>(); 14 | static { 15 | try { 16 | // http://androidxref.com/4.4_r1/xref/libcore/luni/src/main/java/libcore/io/Memory.java 17 | Class clzMemory = Class.forName("libcore.io.Memory"); 18 | for (Method m:clzMemory.getDeclaredMethods()) { 19 | Memory.put(m.getName(),m); 20 | } 21 | } catch (ClassNotFoundException e) { 22 | e.printStackTrace(); 23 | } 24 | } 25 | public static byte[] readMemory(long address,int count) throws InvocationTargetException, IllegalAccessException { 26 | if(Memory.get("peekByteArray")==null)throw new IllegalAccessException(); 27 | byte[] data=new byte[count]; 28 | Memory.get("peekByteArray").invoke(null,address, data, 0, count); 29 | return data; 30 | } 31 | public static void writeMemory(long address,byte[] data) throws IllegalAccessException, InvocationTargetException { 32 | if(Memory.get("pokeByteArray")==null)throw new IllegalAccessException(); 33 | byte[] mem = readMemory(address,data.length); 34 | for (int i = 0; i < mem.length; i++) { 35 | if(mem[i]!=data[i]){ 36 | Memory.get("pokeByte").invoke(null,address+i,data[i]); 37 | } 38 | } 39 | //Memory.get("pokeByteArray").invoke(null,address, mem, 0, mem.length); 40 | } 41 | public static String[] getMaps(){ 42 | File maps=new File("/proc/self/maps"); 43 | StringBuilder sb = new StringBuilder(); 44 | try { 45 | FileInputStream is = new FileInputStream(maps); 46 | BufferedReader reader = new BufferedReader(new InputStreamReader(is)); 47 | String str; 48 | while((str=reader.readLine())!=null){ 49 | sb.append(str+"\r\n"); 50 | } 51 | /* 52 | Process p = Runtime.getRuntime().exec("cat /proc/self/maps"); 53 | p.waitFor(); 54 | BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); 55 | String str; 56 | while((str=reader.readLine())!=null){ 57 | sb.append(str+"\r\n"); 58 | } 59 | */ 60 | } catch (IOException e) { 61 | e.printStackTrace(); 62 | } 63 | String[] mapstrs=sb.toString().split("\r\n"); 64 | for (int i = 0; i < mapstrs.length; i++) { 65 | String[] addrs =mapstrs[i].substring(0,17).split("-"); 66 | long size = Long.parseLong(addrs[1],16)- Long.parseLong(addrs[0],16); 67 | mapstrs[i]=mapstrs[i].replaceFirst(" ",":"+ Long.toHexString(size)+" "); 68 | } 69 | return mapstrs; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/handler/MethodHandler.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.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 leon.qujing.XposedEntry; 11 | import leon.qujing.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 | String method_pretty_json = ""; 25 | try { 26 | method_pretty_json = JSON.toJSONString(method, SerializerFeature.PrettyFormat); 27 | } 28 | catch (Exception e){ 29 | e.printStackTrace(); 30 | } 31 | detail.put("json", method_pretty_json); 32 | return detail; 33 | } 34 | 35 | public static Method getMethod(String className, String methodName) { 36 | try { 37 | for (Method method : Class.forName(className, false, XposedEntry.classLoader).getMethods()) { 38 | if (method.getName().equals(methodName)) return method; 39 | } 40 | return null; 41 | } catch (ClassNotFoundException e) { 42 | e.printStackTrace(); 43 | return null; 44 | } 45 | } 46 | 47 | public static Method getMethodbyJavaName(String javaName) { 48 | String classname=javaName.substring(0,javaName.indexOf("->")); 49 | String method=javaName.substring(javaName.indexOf("->")+2); 50 | Class clz=ClassHandler.findClassbyJavaName(classname,XposedEntry.classLoader); 51 | if(clz==null)return null; 52 | for (Method m:clz.getDeclaredMethods()) { 53 | if(Utils.getJavaName(m).equals(javaName))return m; 54 | } 55 | return null; 56 | } 57 | 58 | public static Method[] getMethodsInClasses(Class[] clzs) { 59 | ArrayList methods = new ArrayList(); 60 | for (Class clazz : clzs) { 61 | for (Method method : clazz.getDeclaredMethods()) { 62 | methods.add(method); 63 | } 64 | } 65 | return (Method[]) methods.toArray(); 66 | } 67 | 68 | static Object invokeMethod(Method method, Object thisObj, Object[] params) { 69 | try { 70 | method.setAccessible(true); 71 | return ObjectHandler.storeObject(method.invoke(thisObj, params), "invokeResult"); 72 | } catch (Exception e) { 73 | e.printStackTrace(); 74 | return null; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/handler/ObjectHandler.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.handler; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import leon.qujing.QuJingServer; 7 | import leon.qujing.utils.Utils; 8 | 9 | import static leon.qujing.QuJingServer.parsers; 10 | 11 | //处理对象相关内容 12 | public class ObjectHandler { 13 | public static HashMap objects = new HashMap(); 14 | 15 | public static Object storeObject(Object obj, String name) { 16 | return objects.put(name, obj); 17 | } 18 | 19 | public static String saveObject(Object obj){ 20 | String typeSign = Utils.getTypeSignature(obj.getClass()); 21 | if(obj==null)return "Null"; 22 | QuJingServer.ObjectParser parser = parsers.get(typeSign); 23 | if(parser==null)parser=parsers.get("store"); 24 | return typeSign+"#"+parser.generate(obj); 25 | } 26 | public static String briefObject(Object obj){ 27 | String typeSign = Utils.getTypeSignature(obj.getClass()); 28 | if(obj==null)return "Null"; 29 | QuJingServer.ObjectParser parser = parsers.get(typeSign); 30 | if(parser==null)parser=parsers.get("generic"); 31 | return typeSign+"#"+parser.generate(obj); 32 | } 33 | 34 | public static Object getObject(String name) { 35 | return objects.get(name); 36 | } 37 | 38 | public static Object parseObject(String Object){ 39 | if(Object.equals("Null"))return null; 40 | if(Object==null)return null; 41 | if(Object.indexOf("#")<0)return null; 42 | String type=Object.substring(0,Object.indexOf("#")); 43 | String raw=Object.substring(Object.indexOf("#")+1); 44 | QuJingServer.ObjectParser parser = parsers.get(type); 45 | if(parser==null)parser=parsers.get("store"); 46 | return parser.parse(raw); 47 | } 48 | 49 | public static Object[] getObjects(String name, String type) { 50 | return null; 51 | } 52 | 53 | public static Object removeObject(String name) { 54 | return objects.remove(name); 55 | } 56 | 57 | public static Object removeObject(Object object) { 58 | for (Map.Entry entry : objects.entrySet()) { 59 | if (entry.getValue().equals(object)) return objects.remove(entry.getKey()); 60 | } 61 | return null; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/objectparser/BooleanParser.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.objectparser; 2 | 3 | import leon.qujing.QuJingServer; 4 | 5 | public class BooleanParser implements QuJingServer.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/leon/qujing/objectparser/ByteArrayParser.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.objectparser; 2 | 3 | import android.util.Base64; 4 | import android.util.Log; 5 | 6 | import java.util.Arrays; 7 | 8 | import leon.qujing.QuJingServer; 9 | 10 | public class ByteArrayParser implements QuJingServer.ObjectParser { 11 | @Override 12 | public java.lang.Object parse(java.lang.String data) { 13 | if(data.indexOf('#')<0){ 14 | return Base64.decode(data, Base64.DEFAULT); 15 | }else{ 16 | String type=data.substring(0,data.indexOf("#")); 17 | String raw=data.substring(data.indexOf("#")+1); 18 | if(type.equals("raw"))return raw.getBytes(); 19 | else if(type.equals("base64"))return Base64.decode(raw, Base64.DEFAULT); 20 | else return URLDecode(raw); 21 | } 22 | } 23 | @Override 24 | public String generate(Object obj) { 25 | Log.d("ByteArray", ""+new String((byte[])obj).getBytes().length+":"+((byte[])obj).length); 26 | if(Arrays.equals((byte[])obj,new String((byte[])obj).getBytes()))return "raw#"+new String((byte[])obj); 27 | else return "Base64#"+Base64.encodeToString((byte[])obj,Base64.DEFAULT); 28 | // else return "URLEncoding#"+URLEncode((byte[])obj); 29 | } 30 | private static final char[] HEX_CHAR = {'0', '1', '2', '3', '4', '5', 31 | '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; 32 | static public String URLEncode(byte[] bytes){ 33 | StringBuffer sb=new StringBuffer(); 34 | for (int i = 0; i < bytes.length; i++) { 35 | 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]=='~') 36 | sb.append((char)bytes[i]); 37 | else { 38 | sb.append('%'); 39 | sb.append(HEX_CHAR[(256+bytes[i])/16%16]); 40 | sb.append(HEX_CHAR[(256+bytes[i])%16]); 41 | } 42 | } 43 | return sb.toString(); 44 | } 45 | static public byte[] URLDecode(String str){ 46 | byte[] org=str.getBytes(); 47 | byte[] dst=new byte[org.length]; 48 | byte[] fin; 49 | int i=0,j=0; 50 | for (i = 0; i < org.length; i++) { 51 | if(org[i]=='%'){ 52 | dst[j]=(byte) Integer.parseInt(""+(char)org[i+1]+(char)org[i+2],16); 53 | j++; 54 | i=i+2; 55 | }else{ 56 | dst[j]=org[i]; 57 | j++; 58 | } 59 | } 60 | fin=new byte[j]; 61 | for (j--; j >=0 ; j--) { 62 | fin[j]=dst[j]; 63 | } 64 | return fin; 65 | } 66 | } -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/objectparser/ContextParser.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.objectparser; 2 | 3 | import android.app.AndroidAppHelper; 4 | 5 | import leon.qujing.QuJingServer; 6 | 7 | public class ContextParser implements QuJingServer.ObjectParser { 8 | @Override 9 | public Object parse(String data) { 10 | return AndroidAppHelper.currentApplication().getApplicationContext(); 11 | } 12 | 13 | @Override 14 | public String generate(Object obj) { 15 | return "Context[INST]"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/objectparser/DoubleParser.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.objectparser; 2 | 3 | import leon.qujing.QuJingServer; 4 | 5 | public class DoubleParser implements QuJingServer.ObjectParser { 6 | @Override 7 | public Object parse(String data) { 8 | return Double.parseDouble(data); 9 | } 10 | 11 | @Override 12 | public String generate(Object obj) { 13 | return obj.toString(); 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/objectparser/FloatParser.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.objectparser; 2 | 3 | import leon.qujing.QuJingServer; 4 | 5 | public class FloatParser implements QuJingServer.ObjectParser { 6 | @Override 7 | public Object parse(String data) { 8 | return Float.parseFloat(data); 9 | } 10 | 11 | @Override 12 | public String generate(Object obj) { 13 | return obj.toString(); 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/objectparser/GenericParser.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.objectparser; 2 | 3 | import leon.qujing.QuJingServer; 4 | 5 | public class GenericParser implements QuJingServer.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/leon/qujing/objectparser/IntParser.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.objectparser; 2 | 3 | import leon.qujing.QuJingServer; 4 | 5 | public class IntParser implements QuJingServer.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/leon/qujing/objectparser/JSONParser.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.objectparser; 2 | 3 | import leon.qujing.QuJingServer; 4 | 5 | public class JSONParser implements QuJingServer.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/leon/qujing/objectparser/LongParser.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.objectparser; 2 | 3 | import leon.qujing.QuJingServer; 4 | 5 | public class LongParser implements QuJingServer.ObjectParser { 6 | @Override 7 | public Object parse(String data) { 8 | return Long.parseLong(data); 9 | } 10 | 11 | @Override 12 | public String generate(Object obj) { 13 | return obj.toString(); 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/objectparser/ShortParser.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.objectparser; 2 | 3 | import leon.qujing.QuJingServer; 4 | 5 | public class ShortParser implements QuJingServer.ObjectParser { 6 | @Override 7 | public Object parse(String data) { 8 | return Short.parseShort(data); 9 | } 10 | 11 | @Override 12 | public String generate(Object obj) { 13 | return obj.toString(); 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/objectparser/StoredObjectParser.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.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 leon.qujing.QuJingServer; 13 | import leon.qujing.handler.ObjectHandler; 14 | import leon.qujing.utils.Utils; 15 | 16 | import static leon.qujing.QuJingServer.parsers; 17 | 18 | public class StoredObjectParser implements QuJingServer.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("QuJingServer", "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 | field.setAccessible(true); 56 | Object fieldObj = field.get(obj); 57 | Log.e("QuJingServer", "SOParser Field: " + field.getName() + "@" + Utils.getTypeSignature(fieldObj.getClass())); 58 | // 只处理原始类型,避免循环引用 59 | // Handle primitive type only, avoid cell. 60 | if(fieldObj.getClass().isPrimitive()||parsers.get(Utils.getTypeSignature(fieldObj.getClass()))!=null){ 61 | fieldmap.put(field.getName(),ObjectHandler.saveObject(fieldObj)); 62 | } 63 | } 64 | }catch (Exception e){ 65 | Log.e("QuJingServer", "SOParser: " + e.getLocalizedMessage() ); 66 | } 67 | String fieldmapStr = JSON.toJSONString(fieldmap,true); 68 | objects.put(""+objname,obj); 69 | return objname+"=>"+fieldmapStr; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/objectparser/StringArrayListParser.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.objectparser; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import java.util.List; 5 | 6 | import leon.qujing.QuJingServer; 7 | 8 | public class StringArrayListParser implements QuJingServer.ObjectParser { 9 | @Override 10 | public Object parse(String data) { 11 | List list = JSONObject.parseArray(data, String.class); 12 | return list; 13 | } 14 | 15 | @Override 16 | public String generate(Object obj) { 17 | return obj.toString(); 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/objectparser/StringArrayParser.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.objectparser; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | 5 | import java.util.List; 6 | 7 | import leon.qujing.QuJingServer; 8 | 9 | public class StringArrayParser implements QuJingServer.ObjectParser { 10 | @Override 11 | public Object parse(String data) { 12 | List list = JSONObject.parseArray(data, String.class); 13 | int size = list.size(); 14 | String[] array = (String[])list.toArray(new String[size]); 15 | return array; 16 | } 17 | 18 | @Override 19 | public String generate(Object obj) { 20 | return obj.toString(); 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/objectparser/StringMapParser.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.objectparser; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | 5 | import java.util.Map; 6 | 7 | import leon.qujing.QuJingServer; 8 | 9 | public class StringMapParser implements QuJingServer.ObjectParser { 10 | @Override 11 | public Object parse(String data) { 12 | JSONObject jsonObject = JSONObject.parseObject(data); 13 | Map map = (Map)jsonObject; 14 | return map; 15 | } 16 | 17 | @Override 18 | public String generate(Object obj) { 19 | return obj.toString(); 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/objectparser/StringParser.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.objectparser; 2 | 3 | import leon.qujing.QuJingServer; 4 | 5 | public class StringParser implements QuJingServer.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/leon/qujing/utils/DexHelper.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.utils; 2 | 3 | import java.lang.reflect.Array; 4 | import java.lang.reflect.Field; 5 | import java.lang.reflect.Method; 6 | 7 | import de.robv.android.xposed.XposedBridge; 8 | 9 | public class DexHelper { 10 | public static String[] getClassesInDex(ClassLoader CL) { 11 | String[] result = {}; 12 | try { 13 | Field field3 = CL.getClass().getSuperclass().getDeclaredField("pathList"); 14 | field3.setAccessible(true); 15 | Object pathList = field3.get(CL); 16 | Field field2 = pathList.getClass().getDeclaredField("dexElements"); 17 | field2.setAccessible(true); 18 | Object elements = field2.get(pathList); 19 | for (int i = 0; i < Array.getLength(elements); i++) { 20 | Object element = Array.get(elements, i); 21 | Field field1 = element.getClass().getDeclaredField("dexFile"); 22 | field1.setAccessible(true); 23 | Object DexFile = field1.get(element); 24 | XposedBridge.log(DexFile.getClass().getName()); 25 | for (Method m : DexFile.getClass().getDeclaredMethods()) { 26 | // XposedBridge.log("DEXFILE:"+m.getName()); 27 | if (m.getName().equalsIgnoreCase("getClassNameList")) { 28 | m.setAccessible(true); 29 | Field field = DexFile.getClass().getDeclaredField("mCookie"); 30 | field.setAccessible(true); 31 | Object clist = m.invoke(DexFile, 32 | field.get(DexFile)); 33 | int length1 = result.length; 34 | int length2 = ((String[]) clist).length; 35 | int totalLength = length1 + length2; 36 | String[] totalArr = new String[totalLength]; 37 | for (int i1 = 0; i1 < length1; i1++) { 38 | totalArr[i1] = result[i1]; 39 | } 40 | for (int i1 = 0; i1 < length2; i1++) { 41 | totalArr[i1 + length1] = ((String[]) clist)[i1]; 42 | } 43 | result = totalArr; 44 | //return (String[]) clist; 45 | } 46 | } 47 | } 48 | } catch (Exception e) { 49 | 50 | } finally { 51 | return result; 52 | } 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.utils; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.ActivityManager; 5 | import android.content.Context; 6 | import android.graphics.Bitmap; 7 | import android.graphics.Canvas; 8 | import android.graphics.PixelFormat; 9 | import android.graphics.drawable.Drawable; 10 | import android.os.Environment; 11 | import android.text.Html; 12 | import android.util.Base64; 13 | 14 | import java.io.ByteArrayOutputStream; 15 | import java.io.DataOutputStream; 16 | import java.io.File; 17 | import java.io.IOException; 18 | import java.io.UnsupportedEncodingException; 19 | import java.lang.reflect.Field; 20 | import java.lang.reflect.Method; 21 | import java.lang.reflect.Modifier; 22 | import java.net.URLEncoder; 23 | import java.security.MessageDigest; 24 | import java.security.NoSuchAlgorithmException; 25 | import java.text.SimpleDateFormat; 26 | import java.util.Date; 27 | import java.util.HashMap; 28 | import java.util.List; 29 | import java.util.Map; 30 | 31 | public class Utils { 32 | private static final Map, String> PRIMITIVE_TO_SIGNATURE; 33 | private static final HashMap contentTypeMapping = new HashMap(); 34 | 35 | static { 36 | PRIMITIVE_TO_SIGNATURE = new HashMap, String>(9); 37 | PRIMITIVE_TO_SIGNATURE.put(byte.class, "B"); 38 | PRIMITIVE_TO_SIGNATURE.put(char.class, "C"); 39 | PRIMITIVE_TO_SIGNATURE.put(short.class, "S"); 40 | PRIMITIVE_TO_SIGNATURE.put(int.class, "I"); 41 | PRIMITIVE_TO_SIGNATURE.put(long.class, "J"); 42 | PRIMITIVE_TO_SIGNATURE.put(float.class, "F"); 43 | PRIMITIVE_TO_SIGNATURE.put(double.class, "D"); 44 | PRIMITIVE_TO_SIGNATURE.put(void.class, "V"); 45 | PRIMITIVE_TO_SIGNATURE.put(boolean.class, "Z"); 46 | contentTypeMapping.put("css", "text/css"); 47 | contentTypeMapping.put("js", "application/javascript"); 48 | contentTypeMapping.put("woff", "font/woff"); 49 | contentTypeMapping.put("woff2", "font/woff2"); 50 | contentTypeMapping.put("ico", "image/x-icon"); 51 | contentTypeMapping.put("json", "application/json"); 52 | } 53 | 54 | public static String MethodDescription(Method m) { 55 | StringBuilder sb = new StringBuilder(); 56 | sb.append(Modifier.toString(m.getModifiers())); 57 | sb.append(" "); 58 | sb.append(m.getReturnType().getName()); 59 | sb.append(" "); 60 | sb.append(m.getName()); 61 | sb.append("("); 62 | for (int i = 0; i < m.getParameterTypes().length; i++) { 63 | if (i != 0) sb.append(","); 64 | sb.append(m.getParameterTypes()[i].getName()); 65 | sb.append(" param" + i); 66 | } 67 | sb.append(")"); 68 | if (m.getExceptionTypes().length > 0) { 69 | sb.append("throws "); 70 | boolean first = true; 71 | for (Class type : m.getExceptionTypes()) { 72 | if (!first) sb.append(","); 73 | else first = false; 74 | sb.append(type.getName()); 75 | } 76 | } 77 | return sb.toString(); 78 | } 79 | 80 | public static String FieldDescription(Field field) { 81 | return Modifier.toString(field.getModifiers()) + " " + field.getType() + " " + field.getName(); 82 | } 83 | 84 | public static String getJavaName(Method method) { 85 | StringBuilder result = new StringBuilder(); 86 | result.append(getTypeSignature(method.getDeclaringClass())); 87 | result.append("->"); 88 | result.append(method.getName()); 89 | result.append(getMethodSignature(method)); 90 | return result.toString(); 91 | } 92 | 93 | public static String getMethodSignature(Method method) { 94 | StringBuilder result = new StringBuilder(); 95 | 96 | result.append('('); 97 | Class[] parameterTypes = method.getParameterTypes(); 98 | for (Class parameterType : parameterTypes) { 99 | result.append(getTypeSignature(parameterType)); 100 | } 101 | result.append(')'); 102 | result.append(getTypeSignature(method.getReturnType())); 103 | 104 | return result.toString(); 105 | } 106 | 107 | public static String getTypeSignature(Class clazz) { 108 | String primitiveSignature = PRIMITIVE_TO_SIGNATURE.get(clazz); 109 | if (primitiveSignature != null) { 110 | return primitiveSignature; 111 | } else if (clazz.isArray()) { 112 | return "[" + getTypeSignature(clazz.getComponentType()); 113 | } else { 114 | // TODO: this separates packages with '.' rather than '/' 115 | return "L" + clazz.getName() + ";"; 116 | } 117 | } 118 | 119 | public static String drawableToByte(Drawable drawable) throws Exception{ 120 | if (drawable != null) { 121 | Bitmap bitmap = Bitmap 122 | .createBitmap( 123 | drawable.getIntrinsicWidth(), 124 | drawable.getIntrinsicHeight(), 125 | drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 126 | : Bitmap.Config.RGB_565); 127 | Canvas canvas = new Canvas(bitmap); 128 | drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), 129 | drawable.getIntrinsicHeight()); 130 | drawable.draw(canvas); 131 | int size = bitmap.getWidth() * bitmap.getHeight() * 4; 132 | // 创建一个字节数组输出流,流的大小为size 133 | ByteArrayOutputStream baos = new ByteArrayOutputStream(size); 134 | // 设置位图的压缩格式,质量为100%,并放入字节数组输出流中 135 | bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos); 136 | // 将字节数组输出流转化为字节数组byte[] 137 | byte[] imagedata = baos.toByteArray(); 138 | return "data:image/png;base64," + Base64.encodeToString(imagedata, Base64.DEFAULT); 139 | } 140 | return ""; 141 | } 142 | 143 | public static int getAppPid(Context ctx, String processName) { 144 | ActivityManager am = ((ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE)); 145 | List processInfos = am.getRunningAppProcesses(); 146 | for (ActivityManager.RunningAppProcessInfo info : processInfos) { 147 | if (processName.equals(info.processName)) { 148 | return info.pid; 149 | } 150 | } 151 | return 0; 152 | } 153 | 154 | public static String getPrecisionStandardTime(){ 155 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); 156 | Date date = new Date(System.currentTimeMillis()); 157 | String time = sdf.format(date); 158 | return time; 159 | } 160 | 161 | public static void chmod_file(File file){ 162 | try { 163 | String command = "chmod 777 " + file.getAbsolutePath(); 164 | Runtime runtime = Runtime.getRuntime(); 165 | 166 | runtime.exec(command).waitFor(); 167 | } catch (IOException | InterruptedException e) { 168 | e.printStackTrace(); 169 | } 170 | } 171 | 172 | public static String getSDPath(){ 173 | File sdDir = null; 174 | boolean sdCardExist = Environment.getExternalStorageState() 175 | .equals(android.os.Environment.MEDIA_MOUNTED);//判断sd卡是否存在 176 | if(sdCardExist) 177 | { 178 | sdDir = Environment.getExternalStorageDirectory();//获取跟目录 179 | return sdDir.toString(); 180 | } 181 | return null; 182 | } 183 | 184 | public static boolean RootCommand(String command) 185 | { 186 | Process process = null; 187 | DataOutputStream os = null; 188 | try 189 | { 190 | process = Runtime.getRuntime().exec("su"); 191 | os = new DataOutputStream(process.getOutputStream()); 192 | os.writeBytes(command + "\n"); 193 | os.writeBytes("exit\n"); 194 | os.flush(); 195 | process.waitFor(); 196 | } catch (Exception e) 197 | { 198 | return false; 199 | } finally 200 | { 201 | try 202 | { 203 | if (os != null) 204 | { 205 | os.close(); 206 | } 207 | process.destroy(); 208 | } catch (Exception e) 209 | { 210 | } 211 | } 212 | return true; 213 | } 214 | 215 | public static String getContentType(String uri){ 216 | String contentType = "text/html"; 217 | String[] result = uri.split("."); 218 | if(result.length < 2){ 219 | return contentType; 220 | } 221 | String fileType = result[result.length -1]; 222 | contentType = contentTypeMapping.get(fileType); 223 | if(contentType == null){ 224 | return "text/html"; 225 | } 226 | return contentType; 227 | } 228 | 229 | public static String md5Hash(String val) throws NoSuchAlgorithmException { 230 | MessageDigest md5 = MessageDigest.getInstance("MD5"); 231 | md5.update(val.getBytes()); 232 | byte[] m = md5.digest(); 233 | return getHexString(m); 234 | } 235 | 236 | private static String getHexString(byte[] b){ 237 | StringBuilder sb = new StringBuilder(); 238 | for (int value : b) { 239 | int temp = value; 240 | if (temp < 0) temp += 256; 241 | if (temp < 16) sb.append("0"); 242 | sb.append(Integer.toHexString(temp)); 243 | } 244 | return sb.toString(); 245 | // return sb.toString().substring(8, 24); 246 | } 247 | 248 | @SuppressLint("DefaultLocale") 249 | public static String formatHexDump(byte[] array, int offset, int length) { 250 | final int width = 16; 251 | 252 | StringBuilder builder = new StringBuilder(); 253 | 254 | for (int rowOffset = offset; rowOffset < offset + length; rowOffset += width) { 255 | builder.append(String.format("%06X | ", rowOffset)); 256 | 257 | for (int index = 0; index < width; index++) { 258 | if (rowOffset + index < offset + length) { 259 | builder.append(String.format("%02x ", array[rowOffset + index])); 260 | } else { 261 | builder.append(" "); 262 | } 263 | } 264 | 265 | if (rowOffset < offset + length) { 266 | int asciiWidth = Math.min(width, offset + length - rowOffset); 267 | builder.append(" | "); 268 | try { 269 | builder.append(Html.escapeHtml(new String(array, rowOffset, asciiWidth, "UTF-8").replaceAll("\r\n", " ").replaceAll("\n", " ").replaceAll("\r", " "))); 270 | } catch (UnsupportedEncodingException ignored) { 271 | //If UTF-8 isn't available as an encoding then what can we do?! 272 | } 273 | } 274 | 275 | builder.append(String.format("%n")); 276 | } 277 | 278 | return builder.toString(); 279 | } 280 | 281 | } 282 | 283 | 284 | -------------------------------------------------------------------------------- /app/src/main/java/leon/qujing/utils/Xlog.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.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/leon/qujing/utils/netUtil.java: -------------------------------------------------------------------------------- 1 | package leon.qujing.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.MalformedURLException; 8 | import java.net.Proxy; 9 | import java.net.ProxySelector; 10 | import java.net.URI; 11 | import java.net.URISyntaxException; 12 | import java.net.URL; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | 17 | /** 18 | * Created by secneo on 2017/3/6. 19 | */ 20 | public class netUtil extends Thread { 21 | URL url; 22 | String postData; 23 | String ret; 24 | String error; 25 | List proxys; 26 | 27 | public netUtil(String URL, String data) { 28 | try { 29 | postData = new String(data); 30 | ret = new String(postData); 31 | url = new URL(URL); 32 | error = null; 33 | proxys = ProxySelector.getDefault().select(new URI("http://www.baidu.com")); 34 | } catch (MalformedURLException e) { 35 | error = "MalformedURLException"; 36 | } catch (URISyntaxException e) { 37 | error = "URISyntaxException"; 38 | } 39 | 40 | } 41 | public netUtil(String URL, String data, List proxies) { 42 | this(URL,data); 43 | if(proxies==null)proxys=new ArrayList(); 44 | else proxys=proxies; 45 | } 46 | 47 | public void run() { 48 | doPost(); 49 | } 50 | 51 | public String getRet() { 52 | try { 53 | start(); 54 | join(); 55 | } catch (InterruptedException e) { 56 | error = "InterruptedException"; 57 | } 58 | return ret; 59 | } 60 | 61 | public boolean isError() { 62 | if (error.equals("")) return true; 63 | else return false; 64 | } 65 | 66 | public String getError() { 67 | return error; 68 | } 69 | 70 | private void doPost() { 71 | try { 72 | //proxys.add(new Proxy(Proxy.Type.HTTP,new InetSocketAddress("127.0.0.1",8080))); 73 | HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection((proxys.size()>0)?proxys.get(0): Proxy.NO_PROXY); 74 | httpURLConnection.setRequestMethod("POST");// 提交模式 75 | httpURLConnection.setRequestProperty("Content-Type","application/json; charset=utf-8"); 76 | httpURLConnection.setConnectTimeout(10000);//连接超时 单位毫秒 77 | //httpURLConnection.setReadTimeout(2000);//读取超时 单位毫秒 78 | // 发送POST请求必须设置如下两行 79 | httpURLConnection.setDoOutput(true); 80 | httpURLConnection.setDoInput(true); 81 | // 获取URLConnection对象对应的输出流 82 | PrintWriter printWriter = new PrintWriter(httpURLConnection.getOutputStream()); 83 | // 发送请求参数 84 | printWriter.write(postData);//post的参数 xx=xx&yy=yy 85 | // flush输出流的缓冲 86 | printWriter.flush(); 87 | //开始获取数据 88 | BufferedInputStream bis = new BufferedInputStream(httpURLConnection.getInputStream()); 89 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 90 | int len; 91 | byte[] arr = new byte[1024]; 92 | while ((len = bis.read(arr)) != -1) { 93 | bos.write(arr, 0, len); 94 | bos.flush(); 95 | } 96 | bos.close(); 97 | ret = bos.toString(); 98 | } catch (Exception e) { 99 | error = "Exception"; 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3861A6 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 曲境 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/leon/qujing/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package leon.qujing; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'} 6 | google() 7 | jcenter() 8 | 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.6.3' 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | mavenCentral() 23 | maven { url "https://jitpack.io" } 24 | maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'} 25 | } 26 | } 27 | 28 | task clean(type: Delete) { 29 | delete rootProject.buildDir 30 | } 31 | -------------------------------------------------------------------------------- /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 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | 21 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed May 06 19:21:10 CST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /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 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 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 Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /image/手动操作指导.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/image/手动操作指导.png -------------------------------------------------------------------------------- /image/执行方法.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/image/执行方法.png -------------------------------------------------------------------------------- /image/搜索目标类-方法.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/image/搜索目标类-方法.png -------------------------------------------------------------------------------- /image/星球.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/image/星球.png -------------------------------------------------------------------------------- /image/监控方法.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/image/监控方法.png -------------------------------------------------------------------------------- /image/监控方法2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/image/监控方法2.png -------------------------------------------------------------------------------- /image/配置目标应用.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/image/配置目标应用.png -------------------------------------------------------------------------------- /release_key.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mocha-L/QuJing/bade07f77a04f6a646ed74f2cfd69a407972a59b/release_key.jks -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------