├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── encodings.xml ├── gradle.xml ├── markdown-navigator │ └── profiles_settings.xml ├── misc.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── carlos │ │ └── cxposed │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── xposed_init │ ├── java │ │ └── com │ │ │ └── carlos │ │ │ └── cxposed │ │ │ ├── MainActivity.kt │ │ │ ├── WechatHook.kt │ │ │ ├── old │ │ │ ├── Alipay10_1_55Hook.kt │ │ │ └── Wechat6_7_3Hook.kt │ │ │ └── utils │ │ │ └── XposedUtils.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── carlos │ └── cxposed │ └── ExampleUnitTest.kt ├── beat.md ├── build.gradle ├── buildsystem ├── debug.keystore └── default.properties ├── demo ├── .gitignore ├── README.md ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── carlos │ │ └── cxposed │ │ └── demo │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── xposed_init │ ├── java │ │ └── com │ │ │ └── carlos │ │ │ └── cxposed │ │ │ └── demo │ │ │ ├── DemoClass.kt │ │ │ ├── MainActivity.kt │ │ │ └── MainHook.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── carlos │ └── cxposed │ └── demo │ └── ExampleUnitTest.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── res ├── demo │ ├── taichi.png │ ├── virtual_xposed.png │ ├── xposed.png │ ├── xposed_code1.png │ └── xposed_reboot.png ├── old │ ├── alipay_money.jpg │ ├── old_README.md │ └── wechat_money.jpg └── wechat │ ├── beat.gif │ ├── beat.mp4 │ ├── beat_background1.jpg │ ├── beat_background2.jpg │ ├── beat_ui.jpg │ ├── get_id.jpg │ ├── jadx1.jpg │ ├── jadx2.jpg │ ├── jadx3.jpg │ ├── monitor_method.jpg │ ├── monitor_trace.jpg │ ├── monitor_view.jpg │ ├── octotree.jpg │ ├── trace_html.jpg │ ├── trace_method.jpg │ ├── wechat.gif │ ├── wechat.mp4 │ ├── wechat_apk.jpg │ ├── wechat_pay1.jpg │ └── wechat_pay_method.jpg └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | /.idea 38 | 39 | # Keystore files 40 | *.jks 41 | 42 | # External native build folder generated in Android Studio 2.2 and later 43 | .externalNativeBuild 44 | 45 | # Google Services (e.g. APIs or Firebase) 46 | google-services.json 47 | 48 | # Freeline 49 | freeline.py 50 | freeline/ 51 | freeline_project_description.json 52 | 53 | # Special neglect 54 | local 55 | /buildsystem/keystore.properties 56 | /buildsystem/carlos.jks -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | xmlns:android 17 | 18 | ^$ 19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 | xmlns:.* 28 | 29 | ^$ 30 | 31 | 32 | BY_NAME 33 | 34 |
35 |
36 | 37 | 38 | 39 | .*:id 40 | 41 | http://schemas.android.com/apk/res/android 42 | 43 | 44 | 45 |
46 |
47 | 48 | 49 | 50 | .*:name 51 | 52 | http://schemas.android.com/apk/res/android 53 | 54 | 55 | 56 |
57 |
58 | 59 | 60 | 61 | name 62 | 63 | ^$ 64 | 65 | 66 | 67 |
68 |
69 | 70 | 71 | 72 | style 73 | 74 | ^$ 75 | 76 | 77 | 78 |
79 |
80 | 81 | 82 | 83 | .* 84 | 85 | ^$ 86 | 87 | 88 | BY_NAME 89 | 90 |
91 |
92 | 93 | 94 | 95 | .* 96 | 97 | http://schemas.android.com/apk/res/android 98 | 99 | 100 | ANDROID_ATTRIBUTE_ORDER 101 | 102 |
103 |
104 | 105 | 106 | 107 | .* 108 | 109 | .* 110 | 111 | 112 | BY_NAME 113 | 114 |
115 |
116 |
117 |
118 | 119 | 121 |
122 |
-------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/markdown-navigator/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Xposed系列之微信装X指南(二) 2 | 3 | - 学习娱乐,分享下如何通过Xposed让自己的资产看起来实现了小目标,文章如果有纰漏欢迎指出改正 4 | - 上篇文章传送门:[Xposed系列之Demo上手指南及源码解析(一)](https://github.com/xbdcc/CXposed/blob/master/demo/README.md) 5 | - 此篇代码仓库:https://github.com/xbdcc/CXposed 6 | 7 | ## 需求 8 | 比如说每个人都有一个小目标,每天看看自己的目标会更有动力,那么现在实现"一个亿"的小目标,我们先从点击我的Tab开始, 9 | 进入支付页面-钱包页面-零钱页面显示的金额都是`¥100000000.00` 10 | 11 | ## 结果 12 | 最后代码实现的效果如下: 13 | 14 | ![](http://xbdcc.cn/image/CXposed/wechat/wechat.gif) 15 | 16 | [旧版本实现效果](https://github.com/xbdcc/CXposed/blob/master/res/old/old_README.md) 17 | 18 | ## 准备工作 19 | ### Root相关 20 | - [TWRP Recovery](https://twrp.me/):一款强大的第三方recovery,有着官方Recovery无法做到的功能,安装Magisk Manager需要用到 21 | - [Magisk Manager](https://magiskmanager.com/):一款强大的刷Root工具,可以替代`SuperSU`进行root权限管理 22 | 23 | 如果用VirtualXposed或太极或其他虚拟环境的Xposed则可以不用Root,如果用Xposed Installer则需要Root权限, 24 | 关于这三者可看上篇文章:[Xposed系列之Demo上手指南及源码解析(一)](https://github.com/xbdcc/CXposed/blob/master/demo/README.md) 25 | 26 | ### 反编译相关 27 | - [apktool](https://github.com/iBotPeaches/Apktool):主要用于反编译APK查看资源文件 28 | - [dex2jar](https://github.com/pxb1988/dex2jar):反编译APK得到Java源代码Jar包 29 | - [jd-gui](http://java-decompiler.github.io/):一般配合`dex2jar`使用,将其反编译得到的jar包拖进gui方便查看源码 30 | - [jadx](https://github.com/skylot/jadx):功能强大的反编译工具,可以直接查看Java代码和资源文件等,同时支持查看Smali 31 | 32 | 推荐使用jadx工具反编译,如从微信官网下载的[32位版本微信](https://dldir1.qq.com/weixin/android/weixin7016android1700.apk),默认都是会下载上面[`weixin7016android1700_arm64.apk`](https://dldir1.qq.com/weixin/android/weixin7016android1700_arm64.apk), 33 | 如果下载arm64版本则有些x86架构的模拟器不支持微信就不能正常使用了,所以用32位主要是为了方便在模拟器上调试 34 | ![](http://xbdcc.cn/image/CXposed/wechat/wechat_apk.jpg) 35 | 然后将下载下来的[`weixin7016android1700.apk`](https://dldir1.qq.com/weixin/android/weixin7016android1700.apk)拖入JadxGUI中,反编译结果如下图: 36 | ![](http://xbdcc.cn/image/CXposed/wechat/jadx1.jpg) 37 | 并且我们还可以另存为Gradle项目,在Android Studio或IDEA中查看,操作入口如下: 38 | ![](http://xbdcc.cn/image/CXposed/wechat/jadx2.jpg) 39 | 40 | ### Hook分析相关 41 | 42 | #### adb命令 43 | adb 命令可以参考https://github.com/xbdcc/CCommand, 这里简单介绍这里需要使用的: 44 | ``` 45 | adb shell dumpsys activity top > activity_top.txt 46 | ``` 47 | 当然如果adb命令你已经很熟悉了想快捷输入也可以在环境变量中设置`alisa`, 48 | - 控制台中输入`vim ~/.bash_profile` 49 | - 在其中加入一行设置`adb shell dumpsys activity top > activity_top.txt`的别名为`activity_top`,如下: 50 | ``` 51 | alias activity_top="adb shell dumpsys activity top > activity_top.txt" 52 | ``` 53 | - 然后按`esc`键,再输入`:wq`回车退出并保存 54 | - 再输入`source ~/.bash_profile`使环境变量生效 55 | - 最后控制台直接输入`activity_top`回车就和刚刚一长串命令一样的效果了 56 | 57 | 该命令可以输出当前Activity信息,如命令抓取的零钱页面Activity的信息如下: 58 | ``` 59 | TASK com.tencent.mm id=184 60 | ACTIVITY com.tencent.mm/.plugin.wallet.balance.ui.WalletBalanceManagerUI 1fcb6708 pid=1326 61 | Local Activity 27bbdf64 State: 62 | mResumed=true mStopped=false mFinished=false 63 | mLoadersStarted=true 64 | mChangingConfigurations=false 65 | mCurrentConfig={1.0 ?mcc?mnc zh_CN ?layoutDir sw360dp w360dp h622dp 480dpi nrml long port finger -keyb/v/h -nav/h s.5mThemeChanged = 0mThemeChangedFlags = 0mFlipFont = 0} 66 | Active Fragments in 247455f2: 67 | #0: ReportFragment{22e48443 #0 android.arch.lifecycle.LifecycleDispatcher.report_fragment_tag} 68 | mFragmentId=#0 mContainerId=#0 mTag=android.arch.lifecycle.LifecycleDispatcher.report_fragment_tag 69 | mState=5 mIndex=0 mWho=android:fragment:0 mBackStackNesting=0 70 | mAdded=true mRemoving=false mResumed=true mFromLayout=false mInLayout=false 71 | mHidden=false mDetached=false mMenuVisible=true mHasMenu=false 72 | mRetainInstance=false mRetaining=false mUserVisibleHint=true 73 | mFragmentManager=FragmentManager{247455f2 in WalletBalanceManagerUI{27bbdf64}} 74 | mActivity=com.tencent.mm.plugin.wallet.balance.ui.WalletBalanceManagerUI@27bbdf64 75 | Child FragmentManager{a6aeac0 in ReportFragment{22e48443}}: 76 | FragmentManager misc state: 77 | mActivity=com.tencent.mm.plugin.wallet.balance.ui.WalletBalanceManagerUI@27bbdf64 78 | mContainer=android.app.Fragment$1@138ca3f9 79 | mParent=ReportFragment{22e48443 #0 android.arch.lifecycle.LifecycleDispatcher.report_fragment_tag} 80 | mCurState=5 mStateSaved=false mDestroyed=false 81 | Added Fragments: 82 | #0: ReportFragment{22e48443 #0 android.arch.lifecycle.LifecycleDispatcher.report_fragment_tag} 83 | FragmentManager misc state: 84 | mActivity=com.tencent.mm.plugin.wallet.balance.ui.WalletBalanceManagerUI@27bbdf64 85 | mContainer=android.app.Activity$1@386ddf3e 86 | mCurState=5 mStateSaved=false mDestroyed=false 87 | ViewRoot: 88 | mAdded=true mRemoved=false 89 | mConsumeBatchedInputScheduled=false 90 | mConsumeBatchedInputImmediatelyScheduled=false 91 | mPendingInputEventCount=0 92 | mProcessInputEventsScheduled=false 93 | mTraversalScheduled=false 94 | android.view.ViewRootImpl$NativePreImeInputStage: mQueueLength=0 95 | android.view.ViewRootImpl$ImeInputStage: mQueueLength=0 96 | android.view.ViewRootImpl$NativePostImeInputStage: mQueueLength=0 97 | Choreographer: 98 | mFrameScheduled=false 99 | mLastFrameTime=16770070 (150010 ms ago) 100 | View Hierarchy: 101 | com.android.internal.policy.impl.PhoneWindow$DecorView{162d5b8c V.ED.... R....... 0,0-1080,1920} 102 | com.tencent.mm.ui.widget.SwipeBackLayout{15900669 VFE..... ........ 0,0-1080,1920 #7f09245b app:id/g2s} 103 | com.tencent.mm.ui.statusbar.b{303be18f V.ED.... ........ 0,0-1080,1920} 104 | android.widget.LinearLayout{1d1aa478 V.E..... ........ 0,54-1080,1920} 105 | android.view.ViewStub{2f44c751 G.E..... ......I. 0,0-0,0 #1020373} 106 | android.widget.FrameLayout{ea9bbb6 V.E..... ........ 0,0-1080,1866} 107 | android.support.v7.widget.ActionBarOverlayLayout{1e0ddd42 V.E..... ........ 0,0-1080,1866 #7f090a81 app:id/b8w} 108 | android.support.v7.widget.ContentFrameLayout{6698a90 V.E..... ........ 0,130-1080,1866 #1020002 android:id/content} 109 | com.tencent.mm.ui.LayoutListenerView{941b6fc V.E..... ........ 0,0-1080,1736 #7f0917b0 app:id/dp5} 110 | android.widget.ScrollView{cc9bba6 VFED.... ........ 0,0-1080,1736} 111 | android.widget.RelativeLayout{3887e994 V.E..... ........ 0,0-1080,1736} 112 | android.widget.TextView{3781bc3d G.ED.... ......I. 0,0-0,0 #7f09289c app:id/guw} 113 | android.widget.LinearLayout{15c40b00 V.E..... ........ 0,0-1080,1736} 114 | android.widget.ImageView{1d2df39 V.ED.... ........ 453,130-626,303 #7f09034d app:id/w5} 115 | android.widget.TextView{25e2997e V.ED.... ........ 448,389-632,451 #7f09259c app:id/ga5} 116 | android.widget.LinearLayout{21b3eedf V.E..... ........ 0,451-1080,721} 117 | android.widget.RelativeLayout{bded72c V.E..... ........ 0,43-1080,157} 118 | com.tencent.mm.plugin.wallet_core.ui.view.WcPayMoneyLoadingView{b7935f5 V.E..... ........ 375,0-705,114 #7f0928aa app:id/gv_} 119 | com.robinhood.ticker.TickerView{21299fb V.ED.... ........ 0,0-330,114 #7f091794 app:id/dod} 120 | android.widget.ProgressBar{1ff83a18 G.ED.... ......I. 0,0-0,0 #7f092905 app:id/gxq} 121 | android.widget.LinearLayout{36ce5a56 G.E..... ......I. 0,0-0,0 #7f09033f app:id/vr} 122 | android.widget.TextView{29e6dad7 V.ED.... ......ID 0,0-0,0 #7f090340 app:id/vs} 123 | android.widget.ImageView{742dfc4 V.ED.... ......ID 0,0-0,0 #7f09033e app:id/vq} 124 | android.widget.LinearLayout{d3f2ead G.E..... ......I. 0,0-0,0 #7f09034b app:id/w3} 125 | com.tencent.mm.pluginsdk.ui.applet.CdnImageView{28544d73 V.ED.... ......I. 0,0-0,0 #7f09034a app:id/w2} 126 | com.tencent.mm.wallet_core.ui.WalletTextView{35703430 V.ED.... ......ID 0,0-0,0 #7f090349 app:id/w1} 127 | com.tencent.mm.pluginsdk.ui.applet.CdnImageView{3dcd08a9 V.ED.... ......I. 0,0-0,0 #7f090348 app:id/w0} 128 | android.widget.Space{111c4dcf I.ED.... ......I. 0,721-1080,1101} 129 | android.widget.LinearLayout{25dc635c V.E..... ........ 0,1187-1080,1295} 130 | android.widget.Button{3a448665 VFED..C. ........ 291,0-788,108 #7f0919c4 app:id/e3i} 131 | android.widget.Button{b7a5948 GFED..C. ......I. 0,0-0,0 #7f09289d app:id/gux} 132 | android.widget.LinearLayout{363e27c7 G.E..... ......I. 0,0-0,0 #7f09141b app:id/d1c} 133 | android.widget.TextView{137fc1f4 V.ED.... ......ID 0,0-0,0 #7f09141c app:id/d1d} 134 | android.widget.ImageView{31361d1d G.ED.... ......I. 0,0-0,0 #7f09153c app:id/d96} 135 | android.widget.LinearLayout{12576b92 V.E..... ........ 464,1554-616,1606} 136 | android.widget.TextView{36eb7963 V.ED..C. ........ 0,0-152,52 #7f09289f app:id/guz} 137 | android.view.View{181c6e19 G.ED.... ......I. 0,0-0,0 #7f09289e app:id/guy} 138 | android.widget.TextView{2dfcaede G.ED.... ......I. 0,0-0,0 #7f09289b app:id/guv} 139 | android.widget.TextView{2f9248bf V.ED.... ........ 0,1628-1080,1671 #7f092951 app:id/gzs} 140 | android.widget.Button{39a3e185 GFED..C. ......ID 0,0-0,0 #7f0917c6 app:id/dpq} 141 | android.support.v7.widget.ActionBarContainer{1e880f89 V.ED.... ........ 0,0-1080,130 #7f090059 app:id/bp} 142 | android.support.v7.widget.Toolbar{1a08b8e V.E..... ........ 0,0-1080,130 #7f090057 app:id/bn} 143 | android.widget.LinearLayout{88b4a54 V.E..... ........ 0,0-810,130 #7f09005b app:id/br} 144 | android.widget.LinearLayout{24ec6dfd V.E...C. ........ 0,0-108,130 #7f0900a0 app:id/dm} 145 | com.tencent.mm.ui.widget.imageview.WeImageView{10dc1af2 V.ED.... ........ 22,0-86,130 #7f0900a1 app:id/dn} 146 | android.widget.LinearLayout{1e96ecf9 G.E..... ......I. 0,0-0,0 #7f090098 app:id/de} 147 | com.tencent.mm.ui.widget.AlbumChooserView{40cf43e V.E...C. ......I. 0,0-0,0 #7f09008a app:id/d1} 148 | android.widget.RelativeLayout{13558fec V.E..... ......I. 0,0-0,0} 149 | android.widget.TextView{9d05fb5 V.ED.... ......ID 0,0-0,0 #7f090120 app:id/h3} 150 | android.widget.FrameLayout{25e0164a V.E..... ......I. 0,0-0,0} 151 | com.tencent.mm.ui.widget.imageview.WeImageView{2e7039bb V.ED.... ......ID 0,0-0,0 #7f09011f app:id/h2} 152 | android.widget.LinearLayout{1901bed8 V.E..... ......I. 108,0-108,130 #7f0925d1 app:id/gbk} 153 | android.widget.LinearLayout{20f44231 V.E..... ......ID 0,0-0,130} 154 | android.widget.ImageView{17114d16 G.ED.... ......I. 0,0-0,0 #7f0925d0 app:id/gbj} 155 | android.widget.TextView{cdeb697 V.ED.... ......ID 0,34-0,96 #1020014 android:id/text1} 156 | android.widget.ProgressBar{263ff084 G.ED.... ......I. 0,0-0,0 #7f091c5e app:id/eki} 157 | android.widget.TextView{364d24a2 G.ED.... ......I. 0,0-0,0 #1020015 android:id/text2} 158 | android.support.v7.widget.ActionMenuView{c9308ac V.E..... ........ 810,0-1080,130} 159 | android.widget.LinearLayout{25f3ae17 V.E..... ........ 0,0-270,130} 160 | android.widget.ImageButton{24965204 GFED..C. ......I. 0,0-0,0 #7f09007c app:id/cn} 161 | android.widget.TextView{141653ed V.ED..CL ........ 0,0-270,130 #7f090079 app:id/ck} 162 | android.widget.LinearLayout{357b14b3 G.E..... ......I. 0,0-0,0 #7f090158 app:id/il} 163 | android.widget.ImageView{3b288a70 V.ED.... ......ID 0,0-0,0} 164 | android.widget.Button{d1141e9 GFED..CL ......I. 0,0-0,0 #7f090076 app:id/ch} 165 | android.widget.RelativeLayout{1908490f V.E..... ......ID 270,65-270,65} 166 | com.tencent.mm.ui.widget.imageview.WeImageView{3f63dd9c G.ED.... ......I. 0,0-0,0 #7f090078 app:id/cj} 167 | android.widget.ImageView{2db313a5 G.ED.... ......I. 0,0-0,0 #7f090b6f app:id/beb} 168 | android.support.v7.widget.ActionBarContextView{18591b45 G.E..... ......I. 0,0-0,0 #7f090065 app:id/c1} 169 | Looper (main, tid 1) {1f1b79df} 170 | Message 0: { when=+6m42s348ms what=26 target=com.tencent.mm.sdk.platformtools.ao$2 } 171 | Message 1: { when=+26m42s267ms what=23 target=com.tencent.mm.sdk.platformtools.ao$2 } 172 | (Total messages: 2, idling=false, quitting=false) 173 | Local FragmentActivity 27bbdf64 State: 174 | mCreated=true mResumed=true mStopped=false FragmentManager misc state: 175 | mHost=android.support.v4.app.FragmentActivity$a@1273f2ec 176 | mContainer=android.support.v4.app.FragmentActivity$a@1273f2ec 177 | mCurState=4 mStateSaved=false mStopped=false mDestroyed=false 178 | ``` 179 | 180 | #### monitor 181 | - ![](http://xbdcc.cn/image/CXposed/wechat/monitor_view.jpg)可以查看布局元素和trace信息,分析布局可以查看之前文章[Android通过辅助功能实现抢微信红包原理简单介绍 182 | ](https://www.jianshu.com/p/e1099a94b979) 183 | - ![](http://xbdcc.cn/image/CXposed/wechat/monitor_method.jpg),分析trace方法调用栈,例如下: 184 | ![](http://xbdcc.cn/image/CXposed/wechat/trace_method.jpg) 185 | - ![](http://xbdcc.cn/image/CXposed/wechat/monitor_trace.jpg),生成生成html格式的trace,可以分析卡顿丢帧等问题,如果有打Trace也可以在上面看出来。可以在Chrome里打开查看,如下: 186 | ![](http://xbdcc.cn/image/CXposed/wechat/trace_html.jpg) 187 | 188 | ### 再介绍三种方便查找id值的方法 189 | 假如我们要查看id为`dod`的值: 190 | 191 | #### 通过`activity_top`查看 192 | 在里面找到元素对应的值,为十六进制值。如上面结果中有这样一行,可以看到id为'dod'的值为十六进制值`7f091794` 193 | ``` 194 | com.robinhood.ticker.TickerView{21299fb V.ED.... ........ 0,0-330,114 #7f091794 app:id/dod} 195 | ``` 196 | #### 通过apk查看 197 | 可以把apk拖进AS中,在`resources.arsc`下选择你要找的id,得到的值为十六进制 198 | ![](http://xbdcc.cn/image/CXposed/wechat/get_id.jpg) 199 | #### 通过jadx查看 200 | 双击`resources.arsc`,可以在里面搜索id,得到的值为十进制。如id为`dod`的值为十进制值`2131302292` 201 | ![](http://xbdcc.cn/image/CXposed/wechat/jadx3.jpg) 202 | 203 | 204 | ## Hook分析 205 | 通过`activity_top`得知: 206 | - 支付页面:com.tencent.mm/.plugin.mall.ui.MallIndexUI 207 | - 钱包页面:com.tencent.mm/.plugin.mall.ui.MallWalletUI 208 | - 零钱页面:com.tencent.mm/.plugin.wallet.balance.ui.WalletBalanceManagerUI 209 | 210 | ### 分析布局 211 | - 首先打开支付页面我们通过monitor的`Dump View Hierarchy`,得知显示钱包的金额的控件id为`dod` 212 | ![支付页面View视图](http://xbdcc.cn/image/CXposed/wechat/wechat_pay1.jpg) 213 | - 然后我们通过`activity_top`找到这个id的地方知道它其实是`com.robinhood.ticker.TickerView`这个控件, 214 | 嗯这个一看就是不是腾讯的自定义View而是用的第三方库,Github上一搜,可以知道它用的是[ticker](https://github.com/robinhood/ticker)这个库, 215 | 后面可以看到钱包页面和零钱页面显示金额的也是用的这个控件。 216 | 知道了这个库我们可以看下TickerView这个类的代码,这里再推荐一个Chrome插件[octotree](https://www.octotree.io/)比较方便在GitHub网页上切换文件, 217 | 如下,可以看到这里有个setText方法,里面执行了`columnManager.setText(targetText);`代码,而`columnManager`最后执行了`columnManager.draw(canvas, textPaint);`把文字绘制到Canvas上了, 218 | `setContentDescription(text);`设置了contentDescription的值,所以这就是我们能看到描述和显示金额的值一样,但是它的text属性值却为空的原因了。 219 | ![](http://xbdcc.cn/image/CXposed/wechat/octotree.jpg) 220 | 221 | 222 | ### 分析支付页面MallIndexUI 223 | 224 | - 首先我们观察到每次进入支付页面它的金额旁边是有个loading的,并且从钱包页面返回到支付页面也都会loading一下,那么可以猜想它可能是在onResume里面做了什么操作 225 | (其实直接看它代码一下就能看出来,假设我们还没看代码先简单猜下)。那么我们看下它的onResume方法调用栈如下: 226 | ![](http://xbdcc.cn/image/CXposed/wechat/wechat_pay_method.jpg) 227 | 228 | 看到了吗?里面主要就执行了`MallIndexBaseUI`(MallIndexUI的父类)的`onResume`和自己的`dbb`方法,这个时候如果你不想看源码继续分析的话其实就已经可以尝试Hook跑起来看看效果了, 229 | 本着保险起见我们还是先继续看看它的源码 230 | ```java 231 | public final void dbb() { 232 | AppMethodBeat.i(66131); 233 | ac.i("MicorMsg.MallIndexUI", "updateBalanceNum"); 234 | ak akVar = new ak(); 235 | if (akVar.erV()) { 236 | this.uCo.setText((String) g.agR().agA().get(ah.a.USERINFO_WALLET_RELEAY_NAME_BALANCE_CONTENT_STRING_SYNC, (Object) getString(R.string.eex))); 237 | this.uCo.setVisibility(0); 238 | this.uDf.setVisibility(8); 239 | this.uDg.setVisibility(8); 240 | AppMethodBeat.o(66131); 241 | return; 242 | } 243 | if (akVar.erX()) { 244 | ac.i("MicorMsg.MallIndexUI", "show balance amount"); 245 | long longValue = ((Long) ((com.tencent.mm.plugin.wxpay.a.a) g.ad(com.tencent.mm.plugin.wxpay.a.a.class)).getWalletCacheStg().get(ah.a.USERINFO_NEW_BALANCE_LONG_SYNC, (Object) 0L)).longValue(); 246 | if (this.uDf != null) { 247 | nQ(akVar.erZ()); 248 | if (this.uDf.getVisibility() == 0) { 249 | this.uDf.setMoney(com.tencent.mm.wallet_core.ui.e.C(com.tencent.mm.wallet_core.ui.e.a(String.valueOf(longValue), "100", 2, RoundingMode.HALF_UP).doubleValue())); 250 | AppMethodBeat.o(66131); 251 | return; 252 | } 253 | } else { 254 | ac.w("MicorMsg.MallIndexUI", "moneyLoadingView is null"); 255 | } 256 | } 257 | AppMethodBeat.o(66131); 258 | } 259 | ``` 260 | 261 | 可以看出ac.i应该就是打印的log方法,根据它的日志`updateBalanceNum`,可以知道这个方法主要是更新余额的,那么我们是不是可以手动拦截这个方法替换为自己设置的呢?我们来试试, 262 | 拿到方法的对象转为Activity,然后通过`findViewById`找到显示金额的控件,通过反射拿到`setText`并且调用赋值,或者通过`XposedHelpers.callMethod(view, "setText", money)` 263 | ```kotlin 264 | private fun hookPayPage() { 265 | XposedHelpers.findAndHookMethod("com.tencent.mm.plugin.mall.ui.MallIndexUI", classLoader, "dbb", object : XC_MethodReplacement() { 266 | override fun replaceHookedMethod(param: MethodHookParam?): Any { 267 | param?.let { 268 | val activity = param.thisObject as Activity 269 | var view = activity.findViewById(0x7f091794) //id:dod的id值为0x7f091794 270 | val method = view.javaClass.getDeclaredMethod("setText", String::class.java) 271 | method.invoke(view, money) 272 | xlog("Hook method dbb of MallIndexUI class and set money.") 273 | } 274 | return "" 275 | } 276 | }) 277 | } 278 | ``` 279 | 280 | - 其实刚刚在`dbb`方法有这样一个方法`this.uDf.setMoney`,通过方法名知道它是设置金额的,所以我们也可以从这里下手,该方法代码如下: 281 | ```java 282 | public void setMoney(String str) { 283 | AppMethodBeat.i(71606); 284 | cc(str, false); 285 | AppMethodBeat.o(71606); 286 | } 287 | ``` 288 | 289 | 再来看`cc`这个方法,第一个if它首先是判断了如果传进来的`str`为`null`则返回,第二个if在`WcPayMoneyLoadingView`类里面我们可以看到`this.BNi`只在`setFirstMoney`方法里面赋值`this.BNi = str;`, 290 | 在`reset`方法里清空该值,并且进到`bs`类里面可以看到`isNullOrNil`方法如果`this.BNi`值为`null`或者字符串长度小于等于0才返回true, 291 | 所以这里其实是判断这个字段是否为空,如果不为空就直接`setFirstMoney`,如果为空就`setNewMoney`,和下面逻辑一致。 292 | ```java 293 | public final void cc(String str, boolean z) { 294 | AppMethodBeat.i(71607); 295 | if (str == null) { 296 | AppMethodBeat.o(71607); 297 | return; 298 | } 299 | if (bs.isNullOrNil(this.BNi)) { 300 | setFirstMoney(str); 301 | if (z) { 302 | removeCallbacks(this.BNk); 303 | AppMethodBeat.o(71607); 304 | return; 305 | } 306 | } else { 307 | setNewMoney(str); 308 | } 309 | AppMethodBeat.o(71607); 310 | } 311 | ``` 312 | 313 | 继续看`if (z)`这个判断,里面主要执行了`removeCallbacks(this.BNk);`,而`this.BNk`是一个Runnable对象如下, 314 | 根据日志`show loading pb`知道这里显示了loading Progress,设置了它为可见的,最终其实可以找到它其实id为`gxq`显示金额控件后面的Progress 315 | 316 | ```java 317 | public Runnable BNk = new Runnable() { 318 | public final void run() { 319 | AppMethodBeat.i(71596); 320 | ac.i("MicroMsg.WcPayMoneyLoadingView", "show loading pb"); 321 | WcPayMoneyLoadingView.this.iIW.setVisibility(0); 322 | boolean unused = WcPayMoneyLoadingView.this.BNj = true; 323 | AppMethodBeat.o(71596); 324 | } 325 | }; 326 | ``` 327 | 328 | 所以我们可以Hook`cc`这个方法,在它执行前修改第一个参数值为你的金额'money`,如下: 329 | ```kotlin 330 | private fun hookPayPage2() { 331 | XposedHelpers.findAndHookMethod("com.tencent.mm.plugin.wallet_core.ui.view.WcPayMoneyLoadingView", classLoader, "cc", String::class.java, Boolean::class.java, 332 | object : XC_MethodHook() { 333 | override fun beforeHookedMethod(param: MethodHookParam?) { 334 | param?.let{ 335 | val view = param.thisObject as View 336 | param.args[0] = money 337 | } 338 | } 339 | }) 340 | } 341 | ``` 342 | 343 | ### 分析钱包页面WalletBalanceManagerUI 344 | - 前面分析支付页面知道`WcPayMoneyLoadingView`中有`setFirstMoney`和`setNewMoney`,其实我们就可以直接Hook替换这两个方法, 345 | 而且该方法通用三个页面都有效,如下: 346 | 347 | ```kotlin 348 | private fun hookMoney() { 349 | val hookClass = classLoader.loadClass(wechatMoneyLoadingView) ?: return 350 | XposedHelpers.findAndHookMethod(hookClass, "setFirstMoney", String::class.java, replaceStr) 351 | XposedHelpers.findAndHookMethod(hookClass, "setNewMoney", String::class.java, replaceStr) 352 | } 353 | 354 | private val replaceStr = object : XC_MethodHook() { 355 | override fun beforeHookedMethod(param: MethodHookParam?) { 356 | param?.let { 357 | val view = param.thisObject as View 358 | when(view.context.javaClass.name) { 359 | wechatWalletActivity -> param.args[0] = "¥$money" 360 | wechatPayActivity, wechatChangeActivity -> param.args[0] = money 361 | } 362 | } 363 | } 364 | } 365 | ``` 366 | 367 | 368 | ### 分析零钱页面WalletBalanceManagerUI 369 | 370 | - 首先我们看下`零钱页面WalletBalanceManagerUI`里面声明的对象,再根据`View Hierarchy`我们知道`AZo`是在`TickerView`外面的`WcPayMoneyLoadingView`, 371 | 继续看`this.AZo.cc`,可以发现它调用的之前支付页面分析的`WcPayMoneyLoadingView`中的`tk`方法,所以我们可以直接Hook这个方法,如下: 372 | 373 | ```kotlin 374 | private fun hookChangePage() { 375 | XposedHelpers.findAndHookMethod("com.tencent.mm.plugin.wallet.balance.ui.WalletBalanceManagerUI", classLoader, "tk", Boolean::class.java, object : XC_MethodReplacement() { 376 | override fun replaceHookedMethod(param: MethodHookParam?): Any { 377 | param?.let { 378 | val activity = param.thisObject as Activity 379 | var view = activity.findViewById(0x7f091794) //id:dod的id值为0x7f091794 380 | XposedHelpers.callMethod(view, "setText", money) 381 | xlog("Hook method tk of WalletBalanceManagerUI class and set money.") 382 | } 383 | return "" 384 | } 385 | }) 386 | } 387 | ``` 388 | 389 | ## 结语 390 | 其实分析的时候可能有点复杂,并且要善于用多种工具一起分析,但是最后实现的代码很简单,整理后代码如下: 391 | 392 | ```kotlin 393 | class WechatHook : IXposedHookLoadPackage { 394 | 395 | private val packageName = "com.tencent.mm" 396 | private lateinit var classLoader: ClassLoader 397 | private val wechatPayActivity = "com.tencent.mm.plugin.mall.ui.MallIndexUI" 398 | private val wechatWalletActivity = "com.tencent.mm.plugin.mall.ui.MallWalletUI" 399 | private val wechatChangeActivity = 400 | "com.tencent.mm.plugin.wallet.balance.ui.WalletBalanceManagerUI" 401 | private val wechatMoneyLoadingView = 402 | "com.tencent.mm.plugin.wallet_core.ui.view.WcPayMoneyLoadingView" 403 | private var money = "100000000.00" 404 | 405 | override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { 406 | if (packageName == lpparam.packageName) { 407 | xlog("Load Wechat app.") 408 | classLoader = lpparam.classLoader 409 | 410 | hookMoney() 411 | 412 | // hookPayPage() 413 | // hookPayPage2() 414 | // 415 | // hookChangePage() 416 | 417 | } 418 | } 419 | 420 | /** 421 | * 改变自定义的文本控件设置文本方法,终极boss,三个都可以 422 | */ 423 | private fun hookMoney() { 424 | val hookClass = classLoader.loadClass(wechatMoneyLoadingView) ?: return 425 | XposedHelpers.findAndHookMethod(hookClass, "setFirstMoney", String::class.java, replaceStr) 426 | XposedHelpers.findAndHookMethod(hookClass, "setNewMoney", String::class.java, replaceStr) 427 | } 428 | 429 | /** 430 | * 支付页面改变文本 431 | */ 432 | private fun hookPayPage() = XposedHelpers.findAndHookMethod( 433 | wechatPayActivity, 434 | classLoader, 435 | "dbb", 436 | replaceViewText 437 | ) 438 | 439 | /** 440 | * 支付页面改变文本另一种方法 441 | */ 442 | private fun hookPayPage2() { 443 | XposedHelpers.findAndHookMethod(wechatMoneyLoadingView, 444 | classLoader, 445 | "cc", 446 | String::class.java, 447 | Boolean::class.java, 448 | object : XC_MethodHook() { 449 | override fun beforeHookedMethod(param: MethodHookParam?) { 450 | param?.let { 451 | val view = param.thisObject as View 452 | param.args[0] = money 453 | } 454 | } 455 | }) 456 | } 457 | 458 | /** 459 | * 零钱页面改变文本 460 | */ 461 | private fun hookChangePage() = XposedHelpers.findAndHookMethod( 462 | wechatChangeActivity, 463 | classLoader, 464 | "tk", 465 | Boolean::class.java, 466 | replaceViewText 467 | ) 468 | 469 | /** 470 | * 在方法调用前手动修改值来改变最后显示的金额 471 | */ 472 | private val replaceStr = object : XC_MethodHook() { 473 | override fun beforeHookedMethod(param: MethodHookParam?) { 474 | param?.let { 475 | xlog("") 476 | val view = param.thisObject as View 477 | when (view.context.javaClass.name) { 478 | wechatWalletActivity -> param.args[0] = "¥$money" 479 | wechatPayActivity, wechatChangeActivity -> param.args[0] = money 480 | } 481 | } 482 | } 483 | } 484 | 485 | /** 486 | * 通过找到显示金额的控件反射拿到它的赋值方法并调用 487 | */ 488 | private val replaceViewText = object : XC_MethodReplacement() { 489 | override fun replaceHookedMethod(param: MethodHookParam?): Any { 490 | param?.let { 491 | val activity = param.thisObject as Activity 492 | var view = activity.findViewById(0x7f091794) //id:dod的id值为0x7f091794 493 | XposedHelpers.callMethod(view, "setText", money) 494 | xlog("find view and set text.") 495 | } 496 | return "" 497 | } 498 | } 499 | } 500 | ``` 501 | 502 | ## 参考链接 503 | - [Xposed原理简介及其精简化](https://www.jianshu.com/p/6b4a80654d4e) 504 | - [装X指南之用 Xposed 把某宝资产改成100w](https://juejin.im/post/5c4531c451882524ff640bbc) 505 | - [xposed微信红包](https://blog.csdn.net/xiao_nian/article/details/79391417) 506 | - [Android "挂逼" 修炼之行---支付宝蚂蚁森林能量自动收取插件开发原理解析](https://blog.csdn.net/jiangwei0910410003/article/details/80107664) 507 | - [Xposed 框架 hook 简介 原理 案例 [MD]](https://www.cnblogs.com/baiqiantao/p/10699552.html) 508 | - [Art模式下Xposed实现原理](https://bbs.pediy.com/thread-257844.htm) -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /local -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | def defaultPropertiesFile = rootProject.file("buildsystem/default.properties") 8 | def defaultProperties = new Properties() 9 | defaultProperties.load(new FileInputStream(defaultPropertiesFile)) 10 | 11 | def keystorePropertiesFile = rootProject.file("buildsystem/keystore.properties") 12 | if (keystorePropertiesFile.exists()) { 13 | defaultProperties.load(new FileInputStream(keystorePropertiesFile)) 14 | } 15 | 16 | android { 17 | compileSdkVersion 28 18 | defaultConfig { 19 | applicationId "com.carlos.cxposed" 20 | minSdkVersion 18 21 | targetSdkVersion 27 22 | versionCode 1 23 | versionName "0.0.3" 24 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 25 | } 26 | signingConfigs { 27 | release { 28 | keyAlias defaultProperties['keyAlias'] 29 | keyPassword defaultProperties['keyPassword'] 30 | storeFile file(defaultProperties['storeFile']) 31 | storePassword defaultProperties['storePassword'] 32 | } 33 | } 34 | buildTypes { 35 | release { 36 | minifyEnabled false 37 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 38 | signingConfig signingConfigs.release 39 | } 40 | debug { 41 | minifyEnabled false 42 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 43 | signingConfig signingConfigs.release 44 | } 45 | } 46 | } 47 | 48 | dependencies { 49 | implementation fileTree(include: ['*.jar'], dir: 'libs') 50 | 51 | compileOnly 'de.robv.android.xposed:api:82' 52 | 53 | implementation 'com.carlos.cutils:cutils:0.0.28' 54 | 55 | implementation 'com.robinhood.ticker:ticker:2.0.2' 56 | 57 | testImplementation 'junit:junit:4.12' 58 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 59 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 60 | 61 | 62 | } 63 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/carlos/cxposed/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.carlos.cxposed 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("com.carlos.cposed", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 26 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | com.carlos.cxposed.WechatHook 2 | com.carlos.cxposed.old.Alipay10_1_55Hook 3 | com.carlos.cxposed.old.Wechat6_7_3Hook 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/carlos/cxposed/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.carlos.cxposed 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.appcompat.app.AppCompatActivity 6 | import com.carlos.cutils.util.LogUtils 7 | import com.carlos.cutils.util.ToastUtil 8 | 9 | class MainActivity : AppCompatActivity() { 10 | 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | super.onCreate(savedInstanceState) 13 | setContentView(R.layout.activity_main) 14 | } 15 | 16 | val url = "https://api.github.com/repos/xbdcc/test/releases/latest" 17 | 18 | fun click(view: View) { 19 | LogUtils.d("click") 20 | // UpdateDefaultImpl().updateDefault(this, url) 21 | 22 | } 23 | 24 | fun test () { 25 | ToastUtil.Builder(this).setText("test").build() 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/carlos/cxposed/WechatHook.kt: -------------------------------------------------------------------------------- 1 | package com.carlos.cxposed 2 | 3 | import android.app.Activity 4 | import android.view.View 5 | import com.carlos.cxposed.utils.xlog 6 | import de.robv.android.xposed.IXposedHookLoadPackage 7 | import de.robv.android.xposed.XC_MethodHook 8 | import de.robv.android.xposed.XC_MethodReplacement 9 | import de.robv.android.xposed.XposedHelpers 10 | import de.robv.android.xposed.callbacks.XC_LoadPackage 11 | 12 | /** 13 | * _ooOoo_ 14 | * o8888888o 15 | * 88" . "88 16 | * (| -_- |) 17 | * O\ = /O 18 | * ____/`---'\____ 19 | * .' \\| |// `. 20 | * / \\||| : |||// \ 21 | * / _||||| -:- |||||- \ 22 | * | | \\\ - /// | | 23 | * | \_| ''\---/'' | | 24 | * \ .-\__ `-` ___/-. / 25 | * ___`. .' /--.--\ `. . __ 26 | * ."" '< `.___\_<|>_/___.' >'"". 27 | * | | : `- \`.;`\ _ /`;.`/ - ` : | | 28 | * \ \ `-. \_ __\ /__ _/ .-` / / 29 | * ======`-.____`-.___\_____/___.-`____.-'====== 30 | * `=---=' 31 | * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 32 | * 佛祖保佑 永无BUG 33 | * 佛曰: 34 | * 写字楼里写字间,写字间里程序员; 35 | * 程序人员写程序,又拿程序换酒钱。 36 | * 酒醒只在网上坐,酒醉还来网下眠; 37 | * 酒醉酒醒日复日,网上网下年复年。 38 | * 但愿老死电脑间,不愿鞠躬老板前; 39 | * 奔驰宝马贵者趣,公交自行程序员。 40 | * 别人笑我忒疯癫,我笑自己命太贱; 41 | * 不见满街漂亮妹,哪个归得程序员? 42 | */ 43 | 44 | /** 45 | * Github: https://github.com/xbdcc/. 46 | * Test in Wechat 7.0.16. 47 | * Created by Carlos on 2020/7/4. 48 | */ 49 | class WechatHook : IXposedHookLoadPackage { 50 | 51 | private val packageName = "com.tencent.mm" 52 | private lateinit var classLoader: ClassLoader 53 | private val wechatPayActivity = "com.tencent.mm.plugin.mall.ui.MallIndexUI" 54 | private val wechatWalletActivity = "com.tencent.mm.plugin.mall.ui.MallWalletUI" 55 | private val wechatChangeActivity = 56 | "com.tencent.mm.plugin.wallet.balance.ui.WalletBalanceManagerUI" 57 | private val wechatMoneyLoadingView = 58 | "com.tencent.mm.plugin.wallet_core.ui.view.WcPayMoneyLoadingView" 59 | private var money = "100000000.00" 60 | 61 | override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { 62 | if (packageName == lpparam.packageName) { 63 | xlog("Load Wechat app.") 64 | classLoader = lpparam.classLoader 65 | 66 | hookMoney() 67 | 68 | // hookPayPage() 69 | // hookPayPage2() 70 | // 71 | // hookChangePage() 72 | 73 | hookBeat() 74 | } 75 | } 76 | 77 | /** 78 | * 屏蔽拍一拍 79 | */ 80 | private fun hookBeat() { 81 | XposedHelpers.findAndHookMethod("com.tencent.mm.ui.chatting.view.AvatarImageView", 82 | classLoader, 83 | "setOnDoubleClickListener", 84 | "com.tencent.mm.plugin.story.api.i\$a", 85 | object : XC_MethodReplacement() { 86 | override fun replaceHookedMethod(param: MethodHookParam?): Any { 87 | xlog("replace double click") 88 | return "" 89 | } 90 | }) 91 | } 92 | 93 | /** 94 | * 改变自定义的文本控件设置文本方法,终极boss,三个都可以 95 | */ 96 | private fun hookMoney() { 97 | val hookClass = classLoader.loadClass(wechatMoneyLoadingView) ?: return 98 | XposedHelpers.findAndHookMethod(hookClass, "setFirstMoney", String::class.java, replaceStr) 99 | XposedHelpers.findAndHookMethod(hookClass, "setNewMoney", String::class.java, replaceStr) 100 | } 101 | 102 | /** 103 | * 支付页面改变文本 104 | */ 105 | private fun hookPayPage() = XposedHelpers.findAndHookMethod( 106 | wechatPayActivity, 107 | classLoader, 108 | "dbb", 109 | replaceViewText 110 | ) 111 | 112 | /** 113 | * 支付页面改变文本另一种方法 114 | */ 115 | private fun hookPayPage2() { 116 | XposedHelpers.findAndHookMethod(wechatMoneyLoadingView, 117 | classLoader, 118 | "cc", 119 | String::class.java, 120 | Boolean::class.java, 121 | object : XC_MethodHook() { 122 | override fun beforeHookedMethod(param: MethodHookParam?) { 123 | param?.let { 124 | val view = param.thisObject as View 125 | param.args[0] = money 126 | } 127 | } 128 | }) 129 | } 130 | 131 | /** 132 | * 零钱页面改变文本 133 | */ 134 | private fun hookChangePage() = XposedHelpers.findAndHookMethod( 135 | wechatChangeActivity, 136 | classLoader, 137 | "tk", 138 | Boolean::class.java, 139 | replaceViewText 140 | ) 141 | 142 | /** 143 | * 在方法调用前手动修改值来改变最后显示的金额 144 | */ 145 | private val replaceStr = object : XC_MethodHook() { 146 | override fun beforeHookedMethod(param: MethodHookParam?) { 147 | param?.let { 148 | xlog("") 149 | val view = param.thisObject as View 150 | when (view.context.javaClass.name) { 151 | wechatWalletActivity -> param.args[0] = "¥$money" 152 | wechatPayActivity, wechatChangeActivity -> param.args[0] = money 153 | } 154 | } 155 | } 156 | } 157 | 158 | /** 159 | * 通过找到显示金额的控件反射拿到它的赋值方法并调用 160 | */ 161 | private val replaceViewText = object : XC_MethodReplacement() { 162 | override fun replaceHookedMethod(param: MethodHookParam?): Any { 163 | param?.let { 164 | val activity = param.thisObject as Activity 165 | var view = activity.findViewById(0x7f091794) //id:dod的id值为0x7f091794 166 | XposedHelpers.callMethod(view, "setText", money) 167 | xlog("find view and set text.") 168 | } 169 | return "" 170 | } 171 | } 172 | } -------------------------------------------------------------------------------- /app/src/main/java/com/carlos/cxposed/old/Alipay10_1_55Hook.kt: -------------------------------------------------------------------------------- 1 | package com.carlos.cxposed.old 2 | 3 | import de.robv.android.xposed.IXposedHookLoadPackage 4 | import de.robv.android.xposed.XC_MethodHook 5 | import de.robv.android.xposed.XposedBridge 6 | import de.robv.android.xposed.XposedHelpers 7 | import de.robv.android.xposed.callbacks.XC_LoadPackage 8 | 9 | /** 10 | * _ooOoo_ 11 | * o8888888o 12 | * 88" . "88 13 | * (| -_- |) 14 | * O\ = /O 15 | * ____/`---'\____ 16 | * .' \\| |// `. 17 | * / \\||| : |||// \ 18 | * / _||||| -:- |||||- \ 19 | * | | \\\ - /// | | 20 | * | \_| ''\---/'' | | 21 | * \ .-\__ `-` ___/-. / 22 | * ___`. .' /--.--\ `. . __ 23 | * ."" '< `.___\_<|>_/___.' >'"". 24 | * | | : `- \`.;`\ _ /`;.`/ - ` : | | 25 | * \ \ `-. \_ __\ /__ _/ .-` / / 26 | * ======`-.____`-.___\_____/___.-`____.-'====== 27 | * `=---=' 28 | * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 29 | * 佛祖保佑 永无BUG 30 | * 佛曰: 31 | * 写字楼里写字间,写字间里程序员; 32 | * 程序人员写程序,又拿程序换酒钱。 33 | * 酒醒只在网上坐,酒醉还来网下眠; 34 | * 酒醉酒醒日复日,网上网下年复年。 35 | * 但愿老死电脑间,不愿鞠躬老板前; 36 | * 奔驰宝马贵者趣,公交自行程序员。 37 | * 别人笑我忒疯癫,我笑自己命太贱; 38 | * 不见满街漂亮妹,哪个归得程序员? 39 | */ 40 | 41 | /** 42 | * Created by Carlos on 2019/1/25. 43 | * Change the total money and yesterday profit in wealth tab. 44 | * Test in Alipay version 10.1.55. 45 | * Refer to artic:https://juejin.im/post/5c4531c451882524ff640bbc. 46 | */ 47 | class Alipay10_1_55Hook : IXposedHookLoadPackage { 48 | 49 | private val packageName = "com.eg.android.AlipayGphone" 50 | private val TAG = "AlipayHook-" 51 | 52 | override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { 53 | if (packageName == lpparam.packageName) { 54 | XposedBridge.log("${TAG}Load Alipay app.") 55 | val classLoader = lpparam.classLoader 56 | val hookClass = classLoader.loadClass("com.alipay.android.render.engine.viewbiz.AssetsHeaderV2View") 57 | val assetsCardModel = classLoader.loadClass("com.alipay.android.render.engine.model.AssetsCardModel") 58 | 59 | dumpClass(hookClass) 60 | 61 | if (hookClass == null) return 62 | XposedHelpers.findAndHookMethod( 63 | hookClass, 64 | "setData", 65 | assetsCardModel, 66 | Boolean::class.javaPrimitiveType, 67 | Boolean::class.javaPrimitiveType, 68 | Boolean::class.javaPrimitiveType, 69 | object : XC_MethodHook() { 70 | override fun beforeHookedMethod(param: MethodHookParam) { 71 | super.beforeHookedMethod(param) 72 | XposedBridge.log("This is dividing line.") 73 | XposedBridge.log("----------------------------------------------") 74 | XposedBridge.log("Start to change data.") 75 | val data = param.args[0] 76 | XposedBridge.log( "${TAG}data:"+data.javaClass.getField("latestTotalView").get(data).toString()) 77 | XposedBridge.log("${TAG}data:"+data.javaClass.getField("totalYesterdayProfitView").get(data).toString()) 78 | 79 | data.javaClass.getField("latestTotalView").set(data, "36162848.69") 80 | data.javaClass.getField("totalYesterdayProfitView").set(data, "1003.81") 81 | 82 | } 83 | }) 84 | 85 | } 86 | } 87 | 88 | /** 89 | * Pring all fileds,methods and classes in class. 90 | */ 91 | private fun dumpClass(cClass: Class<*>) { 92 | XposedBridge.log("${TAG}Fileds:") 93 | val cFileds = cClass.declaredFields 94 | for (filed in cFileds) { 95 | XposedBridge.log(TAG +filed.toString()) 96 | } 97 | 98 | XposedBridge.log("${TAG}Methods:") 99 | val cMethods = cClass.declaredMethods 100 | for (method in cMethods) { 101 | XposedBridge.log(TAG + method.toString()) 102 | } 103 | 104 | XposedBridge.log("${TAG}Classes:") 105 | val childClass = cClass.declaredClasses 106 | for (cChildClass in childClass) { 107 | XposedBridge.log(TAG +cChildClass.toString()) 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /app/src/main/java/com/carlos/cxposed/old/Wechat6_7_3Hook.kt: -------------------------------------------------------------------------------- 1 | package com.carlos.cxposed.old 2 | 3 | import android.widget.TextView 4 | import de.robv.android.xposed.IXposedHookLoadPackage 5 | import de.robv.android.xposed.XC_MethodHook 6 | import de.robv.android.xposed.XposedBridge 7 | import de.robv.android.xposed.XposedHelpers 8 | import de.robv.android.xposed.callbacks.XC_LoadPackage 9 | 10 | /** 11 | * _ooOoo_ 12 | * o8888888o 13 | * 88" . "88 14 | * (| -_- |) 15 | * O\ = /O 16 | * ____/`---'\____ 17 | * .' \\| |// `. 18 | * / \\||| : |||// \ 19 | * / _||||| -:- |||||- \ 20 | * | | \\\ - /// | | 21 | * | \_| ''\---/'' | | 22 | * \ .-\__ `-` ___/-. / 23 | * ___`. .' /--.--\ `. . __ 24 | * ."" '< `.___\_<|>_/___.' >'"". 25 | * | | : `- \`.;`\ _ /`;.`/ - ` : | | 26 | * \ \ `-. \_ __\ /__ _/ .-` / / 27 | * ======`-.____`-.___\_____/___.-`____.-'====== 28 | * `=---=' 29 | * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 30 | * 佛祖保佑 永无BUG 31 | * 佛曰: 32 | * 写字楼里写字间,写字间里程序员; 33 | * 程序人员写程序,又拿程序换酒钱。 34 | * 酒醒只在网上坐,酒醉还来网下眠; 35 | * 酒醉酒醒日复日,网上网下年复年。 36 | * 但愿老死电脑间,不愿鞠躬老板前; 37 | * 奔驰宝马贵者趣,公交自行程序员。 38 | * 别人笑我忒疯癫,我笑自己命太贱; 39 | * 不见满街漂亮妹,哪个归得程序员? 40 | */ 41 | 42 | /** 43 | * Created by Carlos on 2019/1/27. 44 | * Change the change and chang wallet in change page. 45 | * Test in Wechat version 6.7.3. 46 | */ 47 | class Wechat6_7_3Hook : IXposedHookLoadPackage { 48 | 49 | private val packageName = "com.tencent.mm" 50 | private val TAG = "WechatHook-" 51 | 52 | override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { 53 | if (packageName == lpparam.packageName) { 54 | XposedBridge.log("${TAG}Load Wechat app.") 55 | 56 | val classLoader = lpparam.classLoader 57 | val hookClass = 58 | classLoader.loadClass("com.tencent.mm.plugin.wallet.balance.ui.WalletBalanceManagerUI") ?: return 59 | 60 | dumpClass(hookClass) 61 | 62 | getData(hookClass) 63 | 64 | XposedHelpers.findAndHookMethod(hookClass, "aZ", object : XC_MethodHook() { 65 | override fun afterHookedMethod(param: MethodHookParam) { 66 | XposedBridge.log("This is dividing line.") 67 | XposedBridge.log("----------------------------------------------") 68 | XposedBridge.log("Start to change data.") 69 | var textview = XposedHelpers.getObjectField(param.thisObject, "qha") as TextView 70 | textview.text = "67348723.06" 71 | textview = XposedHelpers.getObjectField(param.thisObject, "qhi") as TextView 72 | XposedBridge.log("${TAG}data:" + textview.text) 73 | textview.text = "-6.66" 74 | super.afterHookedMethod(param) 75 | } 76 | }) 77 | } 78 | } 79 | 80 | private fun getData(hookClass: Class<*>) { 81 | XposedHelpers.findAndHookMethod(hookClass, "onResume", object : XC_MethodHook() { 82 | override fun afterHookedMethod(param: MethodHookParam) { 83 | XposedBridge.log("This is dividing line.") 84 | XposedBridge.log("----------------------------------------------") 85 | XposedBridge.log("Start to get data") 86 | var textview = XposedHelpers.getObjectField(param.thisObject, "qgx") as TextView 87 | XposedBridge.log("${TAG}data:" + textview.text) 88 | // This is amount of money. 89 | textview = XposedHelpers.getObjectField(param.thisObject, "qha") as TextView 90 | XposedBridge.log("${TAG}data:" + textview.text) 91 | textview = XposedHelpers.getObjectField(param.thisObject, "qhe") as TextView 92 | XposedBridge.log("${TAG}data:" + textview.text) 93 | textview = XposedHelpers.getObjectField(param.thisObject, "qhh") as TextView 94 | XposedBridge.log("${TAG}data:" + textview.text) 95 | // This is amount of money pass . 96 | textview = XposedHelpers.getObjectField(param.thisObject, "qhi") as TextView 97 | XposedBridge.log("${TAG}data:" + textview.text) 98 | super.afterHookedMethod(param) 99 | } 100 | }) 101 | } 102 | 103 | 104 | private fun dumpClass(cClass: Class<*>) { 105 | XposedBridge.log("${TAG}Fileds") 106 | val cFileds = cClass.declaredFields 107 | for (filed in cFileds) { 108 | XposedBridge.log(TAG + filed.toString()) 109 | } 110 | 111 | XposedBridge.log("${TAG}Methods") 112 | 113 | val cMethods = cClass.declaredMethods 114 | for (method in cMethods) { 115 | XposedBridge.log(TAG + method.toString()) 116 | } 117 | 118 | XposedBridge.log("${TAG}Classes") 119 | 120 | val childClass = cClass.declaredClasses 121 | for (cChildClass in childClass) { 122 | XposedBridge.log(TAG + cChildClass.toString()) 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /app/src/main/java/com/carlos/cxposed/utils/XposedUtils.kt: -------------------------------------------------------------------------------- 1 | package com.carlos.cxposed.utils 2 | 3 | import android.util.Log 4 | import android.view.ViewGroup 5 | import androidx.core.view.forEach 6 | import de.robv.android.xposed.XposedBridge 7 | 8 | /** 9 | * _ooOoo_ 10 | * o8888888o 11 | * 88" . "88 12 | * (| -_- |) 13 | * O\ = /O 14 | * ____/`---'\____ 15 | * .' \\| |// `. 16 | * / \\||| : |||// \ 17 | * / _||||| -:- |||||- \ 18 | * | | \\\ - /// | | 19 | * | \_| ''\---/'' | | 20 | * \ .-\__ `-` ___/-. / 21 | * ___`. .' /--.--\ `. . __ 22 | * ."" '< `.___\_<|>_/___.' >'"". 23 | * | | : `- \`.;`\ _ /`;.`/ - ` : | | 24 | * \ \ `-. \_ __\ /__ _/ .-` / / 25 | * ======`-.____`-.___\_____/___.-`____.-'====== 26 | * `=---=' 27 | * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 28 | * 佛祖保佑 永无BUG 29 | * 佛曰: 30 | * 写字楼里写字间,写字间里程序员; 31 | * 程序人员写程序,又拿程序换酒钱。 32 | * 酒醒只在网上坐,酒醉还来网下眠; 33 | * 酒醉酒醒日复日,网上网下年复年。 34 | * 但愿老死电脑间,不愿鞠躬老板前; 35 | * 奔驰宝马贵者趣,公交自行程序员。 36 | * 别人笑我忒疯癫,我笑自己命太贱; 37 | * 不见满街漂亮妹,哪个归得程序员? 38 | */ 39 | 40 | /** 41 | * Github: https://github.com/xbdcc/. 42 | * Created by Carlos on 2020/7/6. 43 | */ 44 | 45 | var TAG = "CXposed->" 46 | const val line = "----------------------------------------------" 47 | 48 | fun log(string: String) { 49 | Log.d(TAG, string) 50 | } 51 | 52 | fun xlog(string: String) { 53 | XposedBridge.log(TAG + string) 54 | } 55 | 56 | private fun dumpChild(view: ViewGroup) { 57 | log("${TAG}viewGroup:${view}") 58 | view.forEach { 59 | if (it is ViewGroup) dumpChild(it) 60 | log("${TAG}view:${it}------:${it.id}") 61 | } 62 | } 63 | 64 | /** 65 | * Pring all fileds, methods and classes in class. 66 | */ 67 | fun dumpClass(cClass: Class<*>) { 68 | XposedBridge.log("${TAG}Print start$line") 69 | XposedBridge.log("${TAG}Fileds:") 70 | val cFileds = cClass.declaredFields 71 | for (filed in cFileds) { 72 | XposedBridge.log(TAG + filed.toString()) 73 | } 74 | 75 | XposedBridge.log("${TAG}Methods:") 76 | val cMethods = cClass.declaredMethods 77 | for (method in cMethods) { 78 | XposedBridge.log(TAG + method.toString()) 79 | } 80 | 81 | XposedBridge.log("${TAG}Classes:") 82 | val childClass = cClass.declaredClasses 83 | for (cChildClass in childClass) { 84 | XposedBridge.log(TAG + cChildClass.toString()) 85 | } 86 | XposedBridge.log("$TAG${line}Print end") 87 | } -------------------------------------------------------------------------------- /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 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 22 | 29 | -------------------------------------------------------------------------------- /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/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/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/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | CXposed 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/carlos/cxposed/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.carlos.cxposed 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /beat.md: -------------------------------------------------------------------------------- 1 | # Xposed系列之微信屏蔽拍一拍(三) 2 | 3 | 原文链接:https://github.com/xbdcc/CXposed/blob/master/beat.md 4 | 5 | Xposed系列之前文章如下: 6 | - [Xposed系列之Demo上手指南及源码解析(一)](https://github.com/xbdcc/CXposed/blob/master/demo/README.md) 7 | - [Xposed系列之微信装X指南(二)](https://github.com/xbdcc/CXposed/blob/master/README.md) 8 | 9 | 10 | ## 需求 11 | 需求来自于看到的热搜,微信拍一拍好多人说不支持关闭,容易误触。所以想着Hook下可以屏蔽自己的拍一拍 12 | 13 | 14 |
15 |      16 | 17 |
18 | 19 | 20 | ## 结果 21 | 最后代码实现的效果如下,屏蔽了双击"拍一拍": 22 | 23 | ![](http://xbdcc.cn/image/CXposed/wechat/beat.gif) 24 | 25 | 26 | ## 分析UI 27 | 可以拦截的地方有很多处,这里就从最直观容易分析Hook的地方入手,分析方法上篇讲了很多了,这里就不再详细介绍了。 28 | 找到拍一拍双击点,可以看到id为`aku`的控件就是显示的头像,而它的点击和长按属性都为true,这个应该也是双击的控件 29 | ![](/images/beat_ui.jpg) 30 | 31 | 然后通过通过`adb shell dumpsys activity top > activity_top.txt`,找到id为`aku`的那一行信息如下: 32 | ```xml 33 | com.tencent.mm.ui.chatting.view.AvatarImageView{8c74400 V.ED..CL. ........ 0,0-122,122 #7f090708 app:id/aku} 34 | ``` 35 | 可以看到该ImageView为自定义控件`AvatarImageView`,所以我们就可以去到这个自定义控件里面去看它的代码了 36 | 37 | ## 分析代码 38 | 反编译APK查看`AvatarImageView`类的完整代码如下: 39 | ```java 40 | public class AvatarImageView extends AppCompatImageView implements m { 41 | private boolean Ihh; 42 | private final String TAG; 43 | private int pageType; 44 | private i yXY; 45 | private String zkN; 46 | 47 | public AvatarImageView(Context context, AttributeSet attributeSet) { 48 | this(context, attributeSet, 0); 49 | } 50 | 51 | public AvatarImageView(Context context, AttributeSet attributeSet, int i) { 52 | super(context, attributeSet, i); 53 | AppMethodBeat.i(36689); 54 | this.TAG = "MicroMsg.AvatarImageView"; 55 | this.pageType = -1; 56 | this.yXY = null; 57 | this.zkN = ""; 58 | this.Ihh = true; 59 | this.yXY = ((e) g.ad(e.class)).getStoryUIFactory().gq(context); 60 | this.yXY.aZ(this); 61 | setLayerType(1, (Paint) null); 62 | AppMethodBeat.o(36689); 63 | } 64 | 65 | /* access modifiers changed from: protected */ 66 | public void onDraw(Canvas canvas) { 67 | AppMethodBeat.i(36690); 68 | super.onDraw(canvas); 69 | if (this.Ihh) { 70 | this.yXY.a(canvas, true, 0); 71 | AppMethodBeat.o(36690); 72 | return; 73 | } 74 | this.yXY.a(canvas, false, 0); 75 | AppMethodBeat.o(36690); 76 | } 77 | 78 | /* access modifiers changed from: protected */ 79 | public void onMeasure(int i, int i2) { 80 | AppMethodBeat.i(36691); 81 | super.onMeasure(i, i2); 82 | AppMethodBeat.o(36691); 83 | } 84 | 85 | public void setOnClickListener(View.OnClickListener onClickListener) { 86 | AppMethodBeat.i(36692); 87 | super.setOnClickListener(this.yXY.dUH()); 88 | this.yXY.setOnClickListener(onClickListener); 89 | AppMethodBeat.o(36692); 90 | } 91 | 92 | public void setOnDoubleClickListener(i.a aVar) { 93 | AppMethodBeat.i(36693); 94 | this.yXY.setOnDoubleClickListener(aVar); 95 | AppMethodBeat.o(36693); 96 | } 97 | 98 | public void setShowStoryHint(boolean z) { 99 | AppMethodBeat.i(36694); 100 | this.yXY.setShowStoryHint(z); 101 | AppMethodBeat.o(36694); 102 | } 103 | 104 | public final void eM(String str, int i) { 105 | AppMethodBeat.i(36695); 106 | this.yXY.eM(str, i); 107 | this.zkN = str; 108 | AppMethodBeat.o(36695); 109 | } 110 | 111 | public void setChattingBG(boolean z) { 112 | this.Ihh = z; 113 | } 114 | 115 | public final void bO(String str, boolean z) { 116 | AppMethodBeat.i(36696); 117 | if (TextUtils.isEmpty(str) || getContext() == null) { 118 | AppMethodBeat.o(36696); 119 | return; 120 | } 121 | if (str.equals(this.zkN)) { 122 | setShowStoryHint(!z); 123 | } 124 | AppMethodBeat.o(36696); 125 | } 126 | 127 | /* access modifiers changed from: protected */ 128 | public void onDetachedFromWindow() { 129 | AppMethodBeat.i(36697); 130 | super.onDetachedFromWindow(); 131 | if (this.pageType != -1) { 132 | a.b(this.pageType, this.zkN, this); 133 | } 134 | AppMethodBeat.o(36697); 135 | } 136 | } 137 | ``` 138 | 139 | 可以看到其中有个`setOnDoubleClickListener`方法,很明显这个就是我们双击事件的监听,所以我们可以Hook这个方法做拦截, 140 | 让他后面拍一拍的操作都不执行就可以了,当然你还可以继续往里面分析拍一拍执行网络请求或数据库操作显示UI等的地方做拦截。 141 | 我们看到`setOnDoubleClickListener`方法里面传了参数`i.a aVar`,需要先知道下这个参数是什么,继续看`i`的代码如下: 142 | ```java 143 | public interface i { 144 | 145 | public interface a { 146 | boolean fl(View view); 147 | } 148 | 149 | void a(Canvas canvas, boolean z, int i); 150 | 151 | void aZ(View view); 152 | 153 | View.OnClickListener dUH(); 154 | 155 | void eM(String str, int i); 156 | 157 | void setOnClickListener(View.OnClickListener onClickListener); 158 | 159 | void setOnDoubleClickListener(a aVar); 160 | 161 | void setShowStoryHint(boolean z); 162 | 163 | void setWeakContext(Context context); 164 | } 165 | ``` 166 | 查看代码得知`i`为一个接口,并且内部有一个接口`a`,因为是内部类,所以要加`$`,找到后就可以开始写Hook代码了 167 | 168 | ## 编写代码 169 | 最终实现代码如下: 170 | ```kotlin 171 | private fun hookBeat() { 172 | val hookClass = classLoader.loadClass("com.tencent.mm.plugin.story.api.i\$a") 173 | XposedHelpers.findAndHookMethod("com.tencent.mm.ui.chatting.view.AvatarImageView", 174 | classLoader, 175 | "setOnDoubleClickListener", 176 | hookClass, 177 | object : XC_MethodReplacement() { 178 | override fun replaceHookedMethod(param: MethodHookParam?): Any { 179 | xlog("replace double click") 180 | return "" 181 | } 182 | }) 183 | } 184 | ``` 185 | 到这里其实已经结束了,但是代码还可以做下优化,我们查看`findAndHookMethod`方法源码及注释,可以看到注释里有例子`parameterTypesAndCallback`参数也可以直接传如`"com.example.MyClass"`, 186 | 所以内部提供了这种方法我们就不用外部去loadClass,直接把类名传进来就是了。仔细看源码注释也会发现这个例子有个小bug,Hook的时候少传了方法名`doSomething`。 187 | ```java 188 | /** 189 | * Look up a method and hook it. The last argument must be the callback for the hook. 190 | * 191 | *

This combines calls to {@link #findMethodExact(Class, String, Object...)} and 192 | * {@link XposedBridge#hookMethod}. 193 | * 194 | *

The method must be declared or overridden in the given class, inherited 195 | * methods are not considered! That's because each method implementation exists only once in 196 | * the memory, and when classes inherit it, they just get another reference to the implementation. 197 | * Hooking a method therefore applies to all classes inheriting the same implementation. You 198 | * have to expect that the hook applies to subclasses (unless they override the method), but you 199 | * shouldn't have to worry about hooks applying to superclasses, hence this "limitation". 200 | * There could be undesired or even dangerous hooks otherwise, e.g. if you hook 201 | * {@code SomeClass.equals()} and that class doesn't override the {@code equals()} on some ROMs, 202 | * making you hook {@code Object.equals()} instead. 203 | * 204 | *

There are two ways to specify the parameter types. If you already have a reference to the 205 | * {@link Class}, use that. For Android framework classes, you can often use something like 206 | * {@code String.class}. If you don't have the class reference, you can simply use the 207 | * full class name as a string, e.g. {@code java.lang.String} or {@code com.example.MyClass}. 208 | * It will be passed to {@link #findClass} with the same class loader that is used for the target 209 | * method, see its documentation for the allowed notations. 210 | * 211 | *

Primitive types, such as {@code int}, can be specified using {@code int.class} (recommended) 212 | * or {@code Integer.TYPE}. Note that {@code Integer.class} doesn't refer to {@code int} but to 213 | * {@code Integer}, which is a normal class (boxed primitive). Therefore it must not be used when 214 | * the method expects an {@code int} parameter - it has to be used for {@code Integer} parameters 215 | * though, so check the method signature in detail. 216 | * 217 | *

As last argument to this method (after the list of target method parameters), you need 218 | * to specify the callback that should be executed when the method is invoked. It's usually 219 | * an anonymous subclass of {@link XC_MethodHook} or {@link XC_MethodReplacement}. 220 | * 221 | *

Example 222 | *

223 | 	 * // In order to hook this method ...
224 | 	 * package com.example;
225 | 	 * public class SomeClass {
226 | 	 *   public int doSomething(String s, int i, MyClass m) {
227 | 	 *     ...
228 | 	 *   }
229 | 	 * }
230 | 	 *
231 | 	 * // ... you can use this call:
232 | 	 * findAndHookMethod("com.example.SomeClass", lpparam.classLoader, String.class, int.class, "com.example.MyClass", new XC_MethodHook() {
233 | 	 *   @Override
234 | 	 *   protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
235 | 	 *     String oldText = (String) param.args[0];
236 | 	 *     Log.d("MyModule", oldText);
237 | 	 *
238 | 	 *     param.args[0] = "test";
239 | 	 *     param.args[1] = 42; // auto-boxing is working here
240 | 	 *     setBooleanField(param.args[2], "great", true);
241 | 	 *
242 | 	 *     // This would not work (as MyClass can't be resolved at compile time):
243 | 	 *     //   MyClass myClass = (MyClass) param.args[2];
244 | 	 *     //   myClass.great = true;
245 | 	 *   }
246 | 	 * });
247 | 	 * 
248 | * 249 | * @param className The name of the class which implements the method. 250 | * @param classLoader The class loader for resolving the target and parameter classes. 251 | * @param methodName The target method name. 252 | * @param parameterTypesAndCallback The parameter types of the target method, plus the callback. 253 | * @throws NoSuchMethodError In case the method was not found. 254 | * @throws ClassNotFoundError In case the target class or one of the parameter types couldn't be resolved. 255 | * @return An object which can be used to remove the callback again. 256 | */ 257 | public static XC_MethodHook.Unhook findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback) { 258 | return findAndHookMethod(findClass(className, classLoader), methodName, parameterTypesAndCallback); 259 | } 260 | ``` 261 | 262 | 优化后代码如下: 263 | ```kotlin 264 | private fun hookBeat() { 265 | XposedHelpers.findAndHookMethod("com.tencent.mm.ui.chatting.view.AvatarImageView", 266 | classLoader, 267 | "setOnDoubleClickListener", 268 | "com.tencent.mm.plugin.story.api.i\$a", 269 | object : XC_MethodReplacement() { 270 | override fun replaceHookedMethod(param: MethodHookParam?): Any { 271 | xlog("replace double click") 272 | return "" 273 | } 274 | }) 275 | } 276 | ``` 277 | 278 | 接下来分析下为什么也可以直接传String类型的类的名称,我们跟进`findAndHookMethod`找用到`parameterTypesAndCallback`参数的方法, 279 | 会发现它最终又会调用`findMethodExact`如下: 280 | ```java 281 | public static XC_MethodHook.Unhook findAndHookMethod(Class clazz, String methodName, Object... parameterTypesAndCallback) { 282 | if (parameterTypesAndCallback.length == 0 || !(parameterTypesAndCallback[parameterTypesAndCallback.length-1] instanceof XC_MethodHook)) 283 | throw new IllegalArgumentException("no callback defined"); 284 | 285 | XC_MethodHook callback = (XC_MethodHook) parameterTypesAndCallback[parameterTypesAndCallback.length-1]; 286 | Method m = findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback)); 287 | 288 | return XposedBridge.hookMethod(m, callback); 289 | } 290 | ``` 291 | 292 | 继续看`getParameterClasses`方法如下,可以看到首先判断了如果type如果为空抛异常,如果为`XC_MethodHook`则不往下执行, 293 | 如果为`Class`则强转为Class,如果为`String`则调用`findClass((String) type, classLoader)`找到Class,方法最后返回Class, 294 | 所以`parameterTypesAndCallback`也可以直接传包名+类名: 295 | ```java 296 | private static Class[] getParameterClasses(ClassLoader classLoader, Object[] parameterTypesAndCallback) { 297 | Class[] parameterClasses = null; 298 | for (int i = parameterTypesAndCallback.length - 1; i >= 0; i--) { 299 | Object type = parameterTypesAndCallback[i]; 300 | if (type == null) 301 | throw new ClassNotFoundError("parameter type must not be null", null); 302 | 303 | // ignore trailing callback 304 | if (type instanceof XC_MethodHook) 305 | continue; 306 | 307 | if (parameterClasses == null) 308 | parameterClasses = new Class[i+1]; 309 | 310 | if (type instanceof Class) 311 | parameterClasses[i] = (Class) type; 312 | else if (type instanceof String) 313 | parameterClasses[i] = findClass((String) type, classLoader); 314 | else 315 | throw new ClassNotFoundError("parameter type must either be specified as Class or String", null); 316 | } 317 | 318 | // if there are no arguments for the method 319 | if (parameterClasses == null) 320 | parameterClasses = new Class[0]; 321 | 322 | return parameterClasses; 323 | } 324 | ``` 325 | 326 | 327 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.3.72' 5 | repositories { 6 | google() 7 | jcenter() 8 | 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:4.0.0' 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 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 | maven { url 'https://jitpack.io' } 23 | } 24 | } 25 | 26 | task clean(type: Delete) { 27 | delete rootProject.buildDir 28 | } 29 | -------------------------------------------------------------------------------- /buildsystem/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/buildsystem/debug.keystore -------------------------------------------------------------------------------- /buildsystem/default.properties: -------------------------------------------------------------------------------- 1 | # keystore 2 | keyAlias= androiddebugkey 3 | keyPassword= android 4 | storeFile= ../buildsystem/debug.keystore 5 | storePassword= android 6 | 7 | # other 8 | JPUSH_APPKEY = 9 | 10 | #测试的 11 | UMENG_APPKEY_DEV = 12 | #正式的 13 | UMENG_APPKEY = 14 | 15 | BUGLY_KEY_DEV = 16 | BUGLY_KEY = 17 | 18 | #sentry 19 | SENTRY_DSN_DEV = 20 | SENTRY_DSN = 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # Xposed系列之Demo上手指南及源码解析(一) 2 | 3 | 先附上Demo代码地址:https://github.com/xbdcc/CXposed/tree/master/demo 4 | 5 | ## Xposed简介 6 | 百度百科介绍: 7 | > Xposed框架(Xposed Framework)是一套开源的、在Android高权限模式下运行的框架服务,可以在不修改APK文件的情况下影响程序运行(修改系统)的框架服务,基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作。` 8 | 9 | ## Xposed相关工具 10 | - [Xposed Installer](https://github.com/rovo89/XposedInstaller/):为安装在手机上的Xposed的主体运行框架, 11 | 手机需要Root权限安装Xposed框架,可以管理Xposed模块 12 | - [VirtualXposed](https://github.com/android-hacker/VirtualXposed):一个简单的应用程序,无需root用户即可使用Xposed,解锁引导程序或修改系统映像等 13 | - [太极](https://github.com/taichi-framework/TaiChi):一个带有或不带有Root/BL锁的Xposed模块框架,支持Android 5.0〜10 14 | 15 | 三个工具首页分别长这样: 16 | 17 | Xposed Installer|VirtualXposed|太极 18 | :-:|:-:|:-: 19 | ![](http://xbdcc.cn/image/CXposed/xposed.png)|![](http://xbdcc.cn/image/CXposed/virtual_xposed.png)|![](http://xbdcc.cn/image/CXposed/taichi.png) 20 | 21 | 22 | ## Xposed Demo 23 | 24 | ### 新建项目配置Xposed环境 25 | - 首先创建创建一个Android Studio项目,然后New一个Module,我们要使用Xposed就需要引入Xposed库,在`build.gradle`加入 26 | ``` 27 | compileOnly 'de.robv.android.xposed:api:82' 28 | ``` 29 | - 然后在`AndroidManifest.xml`的`application`节点下加入如下配置 30 | 31 | ```xml 32 | 35 | 38 | 41 | ``` 42 | 43 | ### 编写Hook代码 44 | 45 | - 新建一个类,里面写一些方法和变量后面使用,如`DemoClass`: 46 | ```kotlin 47 | class DemoClass { 48 | 49 | private val name = "carlos" 50 | 51 | fun printlnName() { 52 | log("name is:$name") 53 | } 54 | 55 | companion object { 56 | 57 | @JvmStatic 58 | fun printlnHelloWorld() { 59 | log("hello world!") 60 | } 61 | 62 | } 63 | 64 | } 65 | ``` 66 | 67 | - 接下来新建一个继承自`IXposedHookLoadPackage`的类,如: 68 | ```kotlin 69 | class MainHook : IXposedHookLoadPackage { 70 | 71 | override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) { 72 | } 73 | 74 | } 75 | 76 | ``` 77 | 78 | - 使用`XposedHelpers`调用APP的方法,使用到的就是反射,Java反射也能实现该功能 79 | ```kotlin 80 | private fun hookDemoClass(lpparam: XC_LoadPackage.LoadPackageParam) { 81 | // 通过类加载器加载DemoClass类 82 | val hookClass = lpparam.classLoader.loadClass("com.carlos.cxposed.demo.DemoClass") ?: return 83 | // 通过XposedHelpers调用静态方法printlnHelloWorld 84 | XposedHelpers.callStaticMethod(hookClass, "printlnHelloWorld") 85 | // 获取DemoClass的类对象 86 | val demoClass = hookClass.newInstance() 87 | // 获取私有字段name 88 | val field = hookClass.getDeclaredField("name") 89 | // 私有字段name访问属性改为公有 90 | field.isAccessible = true 91 | // 给字段name赋值为"xbd" 92 | field.set(demoClass, "xbd") 93 | // 通过XposedHelpers调用非静态方法printlnName 94 | XposedHelpers.callMethod(hookClass.newInstance(), "printlnName") 95 | } 96 | ``` 97 | 98 | - 然后在`MainActivity`中增加方法被Hook,如: 99 | ```kotlin 100 | class MainActivity : AppCompatActivity() { 101 | 102 | override fun onCreate(savedInstanceState: Bundle?) { 103 | super.onCreate(savedInstanceState) 104 | setContentView(R.layout.activity_main) 105 | log(getLog()) 106 | } 107 | 108 | private fun getLog() : String { 109 | return "hello world" 110 | } 111 | 112 | fun click(view: View) { 113 | log("click") 114 | } 115 | 116 | } 117 | ``` 118 | 119 | - 在`MainHook`中增加如下代码对`MainActivity`的`getLog`和`click`方法进行Hook: 120 | ```kotlin 121 | private fun hookMainActivity(lpparam: XC_LoadPackage.LoadPackageParam) { 122 | val hookClass = lpparam.classLoader.loadClass("com.carlos.cxposed.demo.MainActivity") 123 | 124 | XposedHelpers.findAndHookMethod(hookClass, "getLog", object : XC_MethodHook() { 125 | override fun beforeHookedMethod(param: MethodHookParam?) { 126 | xlog("hook before getLog") 127 | // 修改方法返回值 128 | param?.result = "this is a hook message." 129 | } 130 | 131 | override fun afterHookedMethod(param: MethodHookParam?) { 132 | xlog("hook after getLog") 133 | } 134 | }) 135 | 136 | XposedHelpers.findAndHookMethod(hookClass, "click", View::class.java, 137 | object : XC_MethodReplacement() { 138 | override fun replaceHookedMethod(param: MethodHookParam?): Any { 139 | xlog("hook replace click") 140 | val thisObject = param?.thisObject ?: return "" 141 | // 修改textView的显示内容 142 | val activity = thisObject as Activity 143 | val textView = activity.findViewById(R.id.textview) 144 | textView.text = "carlos" 145 | return "" 146 | } 147 | }) 148 | 149 | } 150 | ``` 151 | 152 | - 最后在`main`下新建`assets`目录,在里面创建`xposed_init`文件(文件名必须是这个,后面介绍为什么必须为这个),文件里面就可以添加我们刚刚的类了,如: 153 | ``` 154 | com.carlos.cxposed.demo.MainHook 155 | ``` 156 | 157 | - 模拟器安装好`Xposed Installer`后,运行项目,可以看出来弹出框: 158 | ![](http://xbdcc.cn/image/CXposed/xposed_reboot.png)
159 | 点击重启或软重启生效,然后执行操作可以看到打印日志如下,Hook成功: 160 | ``` 161 | 07-06 22:28:43.470 3965-3965/? I/Xposed: MainHook->MainHook->hook an app start:com.carlos.cxposed.demo 162 | 07-06 22:28:43.472 3965-3965/? D/MainHook->: hello world! 163 | 07-06 22:28:43.472 3965-3965/? D/MainHook->: name is:xbd 164 | 07-06 22:28:45.352 3965-3965/com.carlos.cxposed.demo I/Xposed: MainHook->hook before getLog 165 | 07-06 22:28:45.352 3965-3965/com.carlos.cxposed.demo I/Xposed: MainHook->hook after getLog 166 | 07-06 22:28:45.352 3965-3965/com.carlos.cxposed.demo D/MainHook->: this is a hook message. 167 | 07-06 22:28:51.193 3965-3965/com.carlos.cxposed.demo I/Xposed: MainHook->hook replace click 168 | ``` 169 | 170 | ## Xposed原理 171 | Xposed还有C库,我们这里简单分析下我们引用的他的Java层`de.robv.android.xposed:api:82`,看下我们用到的两个类`XposedHelpers`和`XposedBridge`的源码 172 | 173 | ### XposedBridge解析 174 | 175 | - 首先找个入口,就从我们Hook类实现的`IXposedHookLoadPackage`接口开始吧,我们查看到该接口被`Xposed`自己的jar包调用的有如下几个地方: 176 | ![](http://xbdcc.cn/image/CXposed/xposed_code1.png) 177 | 178 | - 跟进去new出这个接口的地方,调用到的代码块如下: 179 | ```java 180 | hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance)); 181 | ``` 182 | 183 | - 跟进`hookLoadPackage`方法,可以看到这里将该接口添加到集合里存起来了 184 | ```java 185 | public static void hookLoadPackage(XC_LoadPackage callback) { 186 | synchronized (sLoadedPackageCallbacks) { 187 | sLoadedPackageCallbacks.add(callback); 188 | } 189 | } 190 | ``` 191 | 192 | - 继续回来看`hookLoadPackage`方法,可以看到他是被`XposedBridge`的`loadModule`方法调用: 193 | ```java 194 | /** 195 | * Load a module from an APK by calling the init(String) method for all classes defined 196 | * in assets/xposed_init. 197 | */ 198 | private static void loadModule(String apk) { 199 | log("Loading modules from " + apk); 200 | 201 | if (!new File(apk).exists()) { 202 | log(" File does not exist"); 203 | return; 204 | } 205 | 206 | ClassLoader mcl = new PathClassLoader(apk, BOOTCLASSLOADER); 207 | InputStream is = mcl.getResourceAsStream("assets/xposed_init"); 208 | if (is == null) { 209 | log("assets/xposed_init not found in the APK"); 210 | return; 211 | } 212 | 213 | BufferedReader moduleClassesReader = new BufferedReader(new InputStreamReader(is)); 214 | try { 215 | String moduleClassName; 216 | while ((moduleClassName = moduleClassesReader.readLine()) != null) { 217 | moduleClassName = moduleClassName.trim(); 218 | if (moduleClassName.isEmpty() || moduleClassName.startsWith("#")) 219 | continue; 220 | 221 | try { 222 | log (" Loading class " + moduleClassName); 223 | Class moduleClass = mcl.loadClass(moduleClassName); 224 | 225 | if (!IXposedMod.class.isAssignableFrom(moduleClass)) { 226 | log (" This class doesn't implement any sub-interface of IXposedMod, skipping it"); 227 | continue; 228 | } else if (disableResources && IXposedHookInitPackageResources.class.isAssignableFrom(moduleClass)) { 229 | log (" This class requires resource-related hooks (which are disabled), skipping it."); 230 | continue; 231 | } 232 | 233 | final Object moduleInstance = moduleClass.newInstance(); 234 | if (isZygote) { 235 | if (moduleInstance instanceof IXposedHookZygoteInit) { 236 | IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam(); 237 | param.modulePath = apk; 238 | param.startsSystemServer = startsSystemServer; 239 | ((IXposedHookZygoteInit) moduleInstance).initZygote(param); 240 | } 241 | 242 | if (moduleInstance instanceof IXposedHookLoadPackage) 243 | hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance)); 244 | 245 | if (moduleInstance instanceof IXposedHookInitPackageResources) 246 | hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper((IXposedHookInitPackageResources) moduleInstance)); 247 | } else { 248 | if (moduleInstance instanceof IXposedHookCmdInit) { 249 | IXposedHookCmdInit.StartupParam param = new IXposedHookCmdInit.StartupParam(); 250 | param.modulePath = apk; 251 | param.startClassName = startClassName; 252 | ((IXposedHookCmdInit) moduleInstance).initCmdApp(param); 253 | } 254 | } 255 | } catch (Throwable t) { 256 | log(t); 257 | } 258 | } 259 | } catch (IOException e) { 260 | log(e); 261 | } finally { 262 | try { 263 | is.close(); 264 | } catch (IOException ignored) {} 265 | } 266 | } 267 | ``` 268 | 269 | - 通过这里可以得知,他是会找APK下`assets/xposed_init`是否存在,如果不存在则会打印日志并且`return`返回不往下执行了,所以前面说的`文件名必须为xposed_init`是因为这里做了判断,如果不按规则来则Hook都会无效 270 | ```java 271 | InputStream is = mcl.getResourceAsStream("assets/xposed_init"); 272 | if (is == null) { 273 | log("assets/xposed_init not found in the APK"); 274 | return; 275 | } 276 | ``` 277 | 278 | - 继续往上跟会发现`loadModule`方法被`loadModules`调用,而`loadModules`被`main`函数调用,也就是Java的主函数入口,代码如下: 279 | ```java 280 | protected static void main(String[] args) { 281 | // Initialize the Xposed framework and modules 282 | try { 283 | SELinuxHelper.initOnce(); 284 | SELinuxHelper.initForProcess(null); 285 | 286 | runtime = getRuntime(); 287 | if (initNative()) { 288 | XPOSED_BRIDGE_VERSION = getXposedVersion(); 289 | if (isZygote) { 290 | startsSystemServer = startsSystemServer(); 291 | initForZygote(); 292 | } 293 | 294 | loadModules(); 295 | } else { 296 | log("Errors during native Xposed initialization"); 297 | } 298 | } catch (Throwable t) { 299 | log("Errors during Xposed initialization"); 300 | log(t); 301 | disableHooks = true; 302 | } 303 | 304 | // Call the original startup code 305 | if (isZygote) 306 | ZygoteInit.main(args); 307 | else 308 | RuntimeInit.main(args); 309 | } 310 | ``` 311 | 312 | - 接下来我们看`MainHook`里我们用到的`XposedHelpers.callStaticMethod`和`XposedHelpers.callMethod`, 313 | 可以看到他们调用到的方法都是`callMethod`,而在其内部调用了`findMethodBestMatch`方法, 314 | 最终通过`invoke(obj, args)`反射来执行Hook的方法: 315 | ```java 316 | public static Object callMethod(Object obj, String methodName, Object... args) { 317 | try { 318 | return findMethodBestMatch(obj.getClass(), methodName, args).invoke(obj, args); 319 | } catch (IllegalAccessException e) { 320 | // should not happen 321 | XposedBridge.log(e); 322 | throw new IllegalAccessError(e.getMessage()); 323 | } catch (IllegalArgumentException e) { 324 | throw e; 325 | } catch (InvocationTargetException e) { 326 | throw new InvocationTargetError(e.getCause()); 327 | } 328 | } 329 | ``` 330 | 331 | - 我们继续看`XposedHelpers.findAndHookMethod`,可以看到调用的是`XposedHelpers`的findAndHookMethod方法,将方法传入的最后一个对象转为`XC_MethodHook`, 332 | 接着通过`findMethodExact`方法传入最后一个参数前的所有参数,在`findMethodExact`方法内部又通过反射`clazz.getDeclaredMethod`找出具体的方法, 333 | 并且设置该方法访问权限为公有,所有`XposedHelpers.findAndHookMethod`公有私有方法都能Hook 334 | ```java 335 | public static XC_MethodHook.Unhook findAndHookMethod(Class clazz, String methodName, Object... parameterTypesAndCallback) { 336 | if (parameterTypesAndCallback.length == 0 || !(parameterTypesAndCallback[parameterTypesAndCallback.length-1] instanceof XC_MethodHook)) 337 | throw new IllegalArgumentException("no callback defined"); 338 | 339 | XC_MethodHook callback = (XC_MethodHook) parameterTypesAndCallback[parameterTypesAndCallback.length-1]; 340 | Method m = findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback)); 341 | 342 | return XposedBridge.hookMethod(m, callback); 343 | } 344 | ``` 345 | 346 | - 接着来看看刚刚的`XposedBridge.hookMethod`,可以看到该方法内部最终通过`hookMethodNative`调用了Native层的Hook方法。 347 | 继续跟`XposedBridge.hookMethod`,会发现他又是被`hookResources`方法调用,而`hookResources`是被`initForZygote`方法调用, 348 | 而刚刚我们知道在`main`函数里调用了`initForZygote`方法,接下来我们就再看下`initForZygote`方法 349 | 350 | - 首先我们看到该类中有一个main方法入口,主要看其中的`initForZygote`和`loadModules`方法,我们看下其中这段代码, 351 | 可以看到他主要是Hook了`ActivityThread`类的`handleBindApplication`这个方法, 352 | 而我们看下`ActivityThread`的源码就会发现`handleBindApplication`里面调用了`mInstrumentation.callApplicationOnCreate(app)`, 353 | 其实就是`Application`的`onCreate`,所以在`Application`的`onCreate`方法前Xposed就做了Hook拦截执行自己的方法 354 | 355 | ```java 356 | // normal process initialization (for new Activity, Service, BroadcastReceiver etc.) 357 | findAndHookMethod(ActivityThread.class, "handleBindApplication", "android.app.ActivityThread.AppBindData", new XC_MethodHook() { 358 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 359 | ActivityThread activityThread = (ActivityThread) param.thisObject; 360 | ApplicationInfo appInfo = (ApplicationInfo) getObjectField(param.args[0], "appInfo"); 361 | String reportedPackageName = appInfo.packageName.equals("android") ? "system" : appInfo.packageName; 362 | SELinuxHelper.initForProcess(reportedPackageName); 363 | ComponentName instrumentationName = (ComponentName) getObjectField(param.args[0], "instrumentationName"); 364 | if (instrumentationName != null) { 365 | XposedBridge.log("Instrumentation detected, disabling framework for " + reportedPackageName); 366 | disableHooks = true; 367 | return; 368 | } 369 | CompatibilityInfo compatInfo = (CompatibilityInfo) getObjectField(param.args[0], "compatInfo"); 370 | if (appInfo.sourceDir == null) 371 | return; 372 | 373 | setObjectField(activityThread, "mBoundApplication", param.args[0]); 374 | loadedPackagesInProcess.add(reportedPackageName); 375 | LoadedApk loadedApk = activityThread.getPackageInfoNoCheck(appInfo, compatInfo); 376 | XResources.setPackageNameForResDir(appInfo.packageName, loadedApk.getResDir()); 377 | 378 | LoadPackageParam lpparam = new LoadPackageParam(sLoadedPackageCallbacks); 379 | lpparam.packageName = reportedPackageName; 380 | lpparam.processName = (String) getObjectField(param.args[0], "processName"); 381 | lpparam.classLoader = loadedApk.getClassLoader(); 382 | lpparam.appInfo = appInfo; 383 | lpparam.isFirstApplication = true; 384 | XC_LoadPackage.callAll(lpparam); 385 | 386 | if (reportedPackageName.equals(INSTALLER_PACKAGE_NAME)) 387 | hookXposedInstaller(lpparam.classLoader); 388 | } 389 | }); 390 | ``` 391 | -------------------------------------------------------------------------------- /demo/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | android { 5 | compileSdkVersion 28 6 | 7 | 8 | defaultConfig { 9 | applicationId "com.carlos.cxposed.demo" 10 | minSdkVersion 15 11 | targetSdkVersion 28 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | } 27 | 28 | dependencies { 29 | implementation fileTree(dir: 'libs', include: ['*.jar']) 30 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 31 | implementation 'com.android.support:appcompat-v7:28.0.0-rc02' 32 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 33 | testImplementation 'junit:junit:4.12' 34 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 35 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 36 | 37 | compileOnly 'de.robv.android.xposed:api:82' 38 | } 39 | -------------------------------------------------------------------------------- /demo/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 | -------------------------------------------------------------------------------- /demo/src/androidTest/java/com/carlos/cxposed/demo/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.carlos.cxposed.demo 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Github: https://github.com/xbdcc/. 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * See [testing documentation](http://d.android.com/tools/testing). 16 | */ 17 | @RunWith(AndroidJUnit4::class) 18 | class ExampleInstrumentedTest { 19 | @Test 20 | fun useAppContext() { 21 | // Context of the app under test. 22 | val appContext = InstrumentationRegistry.getTargetContext() 23 | assertEquals("com.carlos.test", appContext.packageName) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /demo/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 26 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /demo/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | com.carlos.cxposed.demo.MainHook 2 | -------------------------------------------------------------------------------- /demo/src/main/java/com/carlos/cxposed/demo/DemoClass.kt: -------------------------------------------------------------------------------- 1 | package com.carlos.cxposed.demo 2 | 3 | /** 4 | * _ooOoo_ 5 | * o8888888o 6 | * 88" . "88 7 | * (| -_- |) 8 | * O\ = /O 9 | * ____/`---'\____ 10 | * .' \\| |// `. 11 | * / \\||| : |||// \ 12 | * / _||||| -:- |||||- \ 13 | * | | \\\ - /// | | 14 | * | \_| ''\---/'' | | 15 | * \ .-\__ `-` ___/-. / 16 | * ___`. .' /--.--\ `. . __ 17 | * ."" '< `.___\_<|>_/___.' >'"". 18 | * | | : `- \`.;`\ _ /`;.`/ - ` : | | 19 | * \ \ `-. \_ __\ /__ _/ .-` / / 20 | * ======`-.____`-.___\_____/___.-`____.-'====== 21 | * `=---=' 22 | * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 23 | * 佛祖保佑 永无BUG 24 | * 佛曰: 25 | * 写字楼里写字间,写字间里程序员; 26 | * 程序人员写程序,又拿程序换酒钱。 27 | * 酒醒只在网上坐,酒醉还来网下眠; 28 | * 酒醉酒醒日复日,网上网下年复年。 29 | * 但愿老死电脑间,不愿鞠躬老板前; 30 | * 奔驰宝马贵者趣,公交自行程序员。 31 | * 别人笑我忒疯癫,我笑自己命太贱; 32 | * 不见满街漂亮妹,哪个归得程序员? 33 | */ 34 | 35 | /** 36 | * Github: https://github.com/xbdcc/. 37 | * Created by Carlos on 2020/7/6. 38 | */ 39 | class DemoClass { 40 | 41 | private val name = "carlos" 42 | 43 | fun printlnName() { 44 | log("name is:$name") 45 | } 46 | 47 | companion object { 48 | 49 | @JvmStatic 50 | fun printlnHelloWorld() { 51 | log("hello world!") 52 | } 53 | 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /demo/src/main/java/com/carlos/cxposed/demo/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.carlos.cxposed.demo 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.appcompat.app.AppCompatActivity 6 | 7 | /** 8 | * _ooOoo_ 9 | * o8888888o 10 | * 88" . "88 11 | * (| -_- |) 12 | * O\ = /O 13 | * ____/`---'\____ 14 | * .' \\| |// `. 15 | * / \\||| : |||// \ 16 | * / _||||| -:- |||||- \ 17 | * | | \\\ - /// | | 18 | * | \_| ''\---/'' | | 19 | * \ .-\__ `-` ___/-. / 20 | * ___`. .' /--.--\ `. . __ 21 | * ."" '< `.___\_<|>_/___.' >'"". 22 | * | | : `- \`.;`\ _ /`;.`/ - ` : | | 23 | * \ \ `-. \_ __\ /__ _/ .-` / / 24 | * ======`-.____`-.___\_____/___.-`____.-'====== 25 | * `=---=' 26 | * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 27 | * 佛祖保佑 永无BUG 28 | * 佛曰: 29 | * 写字楼里写字间,写字间里程序员; 30 | * 程序人员写程序,又拿程序换酒钱。 31 | * 酒醒只在网上坐,酒醉还来网下眠; 32 | * 酒醉酒醒日复日,网上网下年复年。 33 | * 但愿老死电脑间,不愿鞠躬老板前; 34 | * 奔驰宝马贵者趣,公交自行程序员。 35 | * 别人笑我忒疯癫,我笑自己命太贱; 36 | * 不见满街漂亮妹,哪个归得程序员? 37 | */ 38 | 39 | /** 40 | * Github: https://github.com/xbdcc/. 41 | * Created by Carlos on 2019/1/22. 42 | */ 43 | class MainActivity : AppCompatActivity() { 44 | 45 | override fun onCreate(savedInstanceState: Bundle?) { 46 | super.onCreate(savedInstanceState) 47 | setContentView(R.layout.activity_main) 48 | log(getLog()) 49 | } 50 | 51 | private fun getLog(): String { 52 | return "hello world" 53 | } 54 | 55 | fun click(view: View) { 56 | log("click") 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /demo/src/main/java/com/carlos/cxposed/demo/MainHook.kt: -------------------------------------------------------------------------------- 1 | package com.carlos.cxposed.demo 2 | 3 | import android.app.Activity 4 | import android.util.Log 5 | import android.view.View 6 | import android.widget.TextView 7 | import de.robv.android.xposed.* 8 | import de.robv.android.xposed.callbacks.XC_LoadPackage 9 | 10 | /** 11 | * _ooOoo_ 12 | * o8888888o 13 | * 88" . "88 14 | * (| -_- |) 15 | * O\ = /O 16 | * ____/`---'\____ 17 | * .' \\| |// `. 18 | * / \\||| : |||// \ 19 | * / _||||| -:- |||||- \ 20 | * | | \\\ - /// | | 21 | * | \_| ''\---/'' | | 22 | * \ .-\__ `-` ___/-. / 23 | * ___`. .' /--.--\ `. . __ 24 | * ."" '< `.___\_<|>_/___.' >'"". 25 | * | | : `- \`.;`\ _ /`;.`/ - ` : | | 26 | * \ \ `-. \_ __\ /__ _/ .-` / / 27 | * ======`-.____`-.___\_____/___.-`____.-'====== 28 | * `=---=' 29 | * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 30 | * 佛祖保佑 永无BUG 31 | * 佛曰: 32 | * 写字楼里写字间,写字间里程序员; 33 | * 程序人员写程序,又拿程序换酒钱。 34 | * 酒醒只在网上坐,酒醉还来网下眠; 35 | * 酒醉酒醒日复日,网上网下年复年。 36 | * 但愿老死电脑间,不愿鞠躬老板前; 37 | * 奔驰宝马贵者趣,公交自行程序员。 38 | * 别人笑我忒疯癫,我笑自己命太贱; 39 | * 不见满街漂亮妹,哪个归得程序员? 40 | */ 41 | 42 | /** 43 | * Github: https://github.com/xbdcc/. 44 | * Created by Carlos on 2019/1/22. 45 | */ 46 | class MainHook : IXposedHookLoadPackage { 47 | 48 | private val packageName = "com.carlos.cxposed.demo" 49 | 50 | override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { 51 | 52 | if (packageName == lpparam.packageName) { 53 | xlog(TAG + "hook an app start:$packageName") 54 | 55 | hookDemoClass(lpparam) 56 | 57 | hookMainActivity(lpparam) 58 | } 59 | } 60 | 61 | private fun hookMainActivity(lpparam: XC_LoadPackage.LoadPackageParam) { 62 | val hookClass = lpparam.classLoader.loadClass("com.carlos.cxposed.demo.MainActivity") 63 | 64 | XposedHelpers.findAndHookMethod(hookClass, "getLog", object : XC_MethodHook() { 65 | override fun beforeHookedMethod(param: MethodHookParam?) { 66 | xlog("hook before getLog") 67 | // 修改方法返回值 68 | param?.result = "this is a hook message." 69 | } 70 | 71 | override fun afterHookedMethod(param: MethodHookParam?) { 72 | xlog("hook after getLog") 73 | } 74 | }) 75 | 76 | XposedHelpers.findAndHookMethod(hookClass, "click", View::class.java, 77 | object : XC_MethodReplacement() { 78 | override fun replaceHookedMethod(param: MethodHookParam?): Any { 79 | xlog("hook replace click") 80 | val thisObject = param?.thisObject ?: return "" 81 | // 修改textView的显示内容 82 | val activity = thisObject as Activity 83 | val textView = activity.findViewById(R.id.textview) 84 | textView.text = "carlos" 85 | return "" 86 | } 87 | }) 88 | 89 | } 90 | 91 | private fun hookDemoClass(lpparam: XC_LoadPackage.LoadPackageParam) { 92 | // 通过类加载器加载DemoClass类 93 | val hookClass = lpparam.classLoader.loadClass("com.carlos.cxposed.demo.DemoClass") ?: return 94 | // 通过XposedHelpers调用静态方法printlnHelloWorld 95 | XposedHelpers.callStaticMethod(hookClass, "printlnHelloWorld") 96 | // 获取DemoClass的类对象 97 | val demoClass = hookClass.newInstance() 98 | // 获取私有字段name 99 | val field = hookClass.getDeclaredField("name") 100 | // 私有字段name访问属性改为公有 101 | field.isAccessible = true 102 | // 给字段name赋值为"xbd" 103 | field.set(demoClass, "xbd") 104 | // 通过XposedHelpers调用非静态方法printlnName 105 | XposedHelpers.callMethod(demoClass, "printlnName") 106 | } 107 | 108 | } 109 | 110 | const val TAG = "MainHook->" 111 | 112 | fun log(string: String) { 113 | Log.d(TAG, string) 114 | } 115 | 116 | fun xlog(string: String) { 117 | XposedBridge.log(TAG + string) 118 | } 119 | -------------------------------------------------------------------------------- /demo/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /demo/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 21 | 22 | -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/demo/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/demo/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/demo/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/demo/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/demo/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /demo/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /demo/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | CXposed Demo 3 | 4 | -------------------------------------------------------------------------------- /demo/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /demo/src/test/java/com/carlos/cxposed/demo/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.carlos.cxposed.demo 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /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 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Jul 04 20:14:09 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-6.1.1-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 | -------------------------------------------------------------------------------- /res/demo/taichi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/demo/taichi.png -------------------------------------------------------------------------------- /res/demo/virtual_xposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/demo/virtual_xposed.png -------------------------------------------------------------------------------- /res/demo/xposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/demo/xposed.png -------------------------------------------------------------------------------- /res/demo/xposed_code1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/demo/xposed_code1.png -------------------------------------------------------------------------------- /res/demo/xposed_reboot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/demo/xposed_reboot.png -------------------------------------------------------------------------------- /res/old/alipay_money.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/old/alipay_money.jpg -------------------------------------------------------------------------------- /res/old/old_README.md: -------------------------------------------------------------------------------- 1 | ## Screenshots 2 | ### Wechat6.7.3 3 | ![wechat](http://xbdcc.cn/image/CXposed/old/wechat_money.jpg) 4 | ### Alipay10.1.55 5 | ![alipay](http://xbdcc.cn/image/CXposed/old/alipay_money.jpg) 6 | -------------------------------------------------------------------------------- /res/old/wechat_money.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/old/wechat_money.jpg -------------------------------------------------------------------------------- /res/wechat/beat.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/wechat/beat.gif -------------------------------------------------------------------------------- /res/wechat/beat.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/wechat/beat.mp4 -------------------------------------------------------------------------------- /res/wechat/beat_background1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/wechat/beat_background1.jpg -------------------------------------------------------------------------------- /res/wechat/beat_background2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/wechat/beat_background2.jpg -------------------------------------------------------------------------------- /res/wechat/beat_ui.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/wechat/beat_ui.jpg -------------------------------------------------------------------------------- /res/wechat/get_id.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/wechat/get_id.jpg -------------------------------------------------------------------------------- /res/wechat/jadx1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/wechat/jadx1.jpg -------------------------------------------------------------------------------- /res/wechat/jadx2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/wechat/jadx2.jpg -------------------------------------------------------------------------------- /res/wechat/jadx3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/wechat/jadx3.jpg -------------------------------------------------------------------------------- /res/wechat/monitor_method.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/wechat/monitor_method.jpg -------------------------------------------------------------------------------- /res/wechat/monitor_trace.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/wechat/monitor_trace.jpg -------------------------------------------------------------------------------- /res/wechat/monitor_view.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/wechat/monitor_view.jpg -------------------------------------------------------------------------------- /res/wechat/octotree.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/wechat/octotree.jpg -------------------------------------------------------------------------------- /res/wechat/trace_html.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/wechat/trace_html.jpg -------------------------------------------------------------------------------- /res/wechat/trace_method.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/wechat/trace_method.jpg -------------------------------------------------------------------------------- /res/wechat/wechat.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/wechat/wechat.gif -------------------------------------------------------------------------------- /res/wechat/wechat.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/wechat/wechat.mp4 -------------------------------------------------------------------------------- /res/wechat/wechat_apk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/wechat/wechat_apk.jpg -------------------------------------------------------------------------------- /res/wechat/wechat_pay1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/wechat/wechat_pay1.jpg -------------------------------------------------------------------------------- /res/wechat/wechat_pay_method.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xbdcc/CXposed/360b90f280c75c430b854dfce1d20e4f6b1c7541/res/wechat/wechat_pay_method.jpg -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':demo' 2 | --------------------------------------------------------------------------------