Skip to content

Commit

Permalink
feat: Add support for packed app
Browse files Browse the repository at this point in the history
Hook will failed when app is protected by packer such as QiHoo 360. Add
new option "Support packed app" to attempt to find the packer's
classloader.
  • Loading branch information
srdr0p committed May 7, 2023
1 parent e044166 commit 8b65c92
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 77 deletions.
4 changes: 4 additions & 0 deletions app/src/main/java/cn/wankkoree/xp/webviewpp/activity/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ class App : AppCompatActivity() {
menuInflater.inflate(R.menu.app_toolbar_menu, menu)
with(modulePrefs("apps_$pkg")) {
menu.findItem(R.id.app_toolbar_menu_debug_mode).isChecked = get(AppSP.debug_mode)
menu.findItem(R.id.app_toolbar_menu_byass_packer).isChecked = get(AppSP.bypass_packer)
}
setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
Expand All @@ -300,6 +301,9 @@ class App : AppCompatActivity() {
}.show()
}
}
R.id.app_toolbar_menu_byass_packer -> {
modulePrefs("apps_$pkg").put(AppSP.bypass_packer, !menuItem.isChecked)
}
R.id.app_toolbar_menu_configure_in_other_apps -> {
startActivity(Intent.createChooser(
Intent(Intent.ACTION_SHOW_APP_INFO).putExtra(Intent.EXTRA_PACKAGE_NAME, pkg),
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/cn/wankkoree/xp/webviewpp/data/AppSP.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
object AppSP {
val is_enabled = PrefsData("is_enabled", false)
val debug_mode = PrefsData("debug_mode", false)
val bypass_packer = PrefsData("bypass_packer", false)
/**
* 哈希去重的 Hook 规则名称集合,以|分隔,并且有多个对应的"hook_entry_$name"子变量
*/
Expand Down
186 changes: 109 additions & 77 deletions app/src/main/java/cn/wankkoree/xp/webviewpp/hook/Main.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package cn.wankkoree.xp.webviewpp.hook

import android.content.Context
import cn.wankkoree.xp.webviewpp.BuildConfig
import cn.wankkoree.xp.webviewpp.data.AppSP
import cn.wankkoree.xp.webviewpp.data.getSet
Expand All @@ -9,8 +10,10 @@ import cn.wankkoree.xp.webviewpp.http.bean.HookRules
import com.google.gson.Gson
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed
import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit
import com.highcapable.yukihookapi.hook.log.*
import com.highcapable.yukihookapi.hook.param.PackageParam
import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookModulePrefs
import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit

@InjectYukiHookWithXposed(entryClassName = "Entry", isUsingResourcesHook = false)
class Main : IYukiHookXposedInit {
Expand Down Expand Up @@ -64,91 +67,120 @@ class Main : IYukiHookXposedInit {

loggerI(msg = "hook $mProcessName which run in $processName")

val cpuArch = with(appInfo.nativeLibraryDir) {
when {
endsWith("arm64") -> "arm64-v8a"
endsWith("arm") -> "armeabi-v7a"
else -> {
loggerE(msg = "the cpuArch(${toString()}) is not supported")
null
val mAppClassname = this.appInfo.className
if (pref.get(AppSP.bypass_packer) && mAppClassname != null && mAppClassname != "android.app.Application") {
loggerI(msg = "Try to get packer's classloader")
mAppClassname.hook {
injectMember {
method {
name = "attachBaseContext"
param("android.content.Context")
}
afterHook {
val context = args(0).any()
if (context != null) {
appClassLoader = (context as Context).classLoader
loggerI(msg = "Get packer's classloader success")
} else {
loggerI(msg = "Get packer's classloader failed! Will use default classloader")
}
doHook(pref)
}
}
}
} else {
doHook(pref)
}
}
}

loggerI(msg = "loading rules")
}

if (pref.get(AppSP.debug_mode)) {
findWebViewMethods()
private fun PackageParam.doHook(pref: YukiHookModulePrefs) {

val cpuArch = with(appInfo.nativeLibraryDir) {
when {
endsWith("arm64") -> "arm64-v8a"
endsWith("arm") -> "armeabi-v7a"
else -> {
loggerE(msg = "the cpuArch(${toString()}) is not supported")
null
}
}
}

pref.getSet(AppSP.hooks).forEach { name ->
val hookJson = pref.getString("hook_entry_$name", "{}")
try {
when(val hookMethod = Gson().fromJson(hookJson, HookRules.HookRule::class.java).name) {
// TODO: 添加更多 hook 方法
"hookWebView" -> {
val hookEntry = Gson().fromJson(hookJson, HookRules.HookRuleWebView::class.java)
hookWebView(
Class_WebView = hookEntry.Class_WebView,
Method_getSettings = hookEntry.Method_getSettings,
Method_setWebContentsDebuggingEnabled = hookEntry.Method_setWebContentsDebuggingEnabled,
Method_setJavaScriptEnabled = hookEntry.Method_setJavaScriptEnabled,
Method_loadUrl = hookEntry.Method_loadUrl,
Method_setWebViewClient = hookEntry.Method_setWebViewClient,
)
}
"hookWebViewClient" -> {
val hookEntry = Gson().fromJson(hookJson, HookRules.HookRuleWebViewClient::class.java)
hookWebViewClient(
Class_WebViewClient = hookEntry.Class_WebViewClient,
Method_onPageFinished = hookEntry.Method_onPageFinished,
Class_WebView = hookEntry.Class_WebView,
Method_evaluateJavascript = hookEntry.Method_evaluateJavascript,
)
}
"replaceNebulaUCSDK" -> {
if (cpuArch != null) {
val hookEntry = Gson().fromJson(hookJson, HookRules.ReplaceNebulaUCSDK::class.java)
replaceNebulaUCSDK(
Class_UcServiceSetup = hookEntry.Class_UcServiceSetup,
Method_updateUCVersionAndSdcardPath = hookEntry.Method_updateUCVersionAndSdcardPath,
Field_sInitUcFromSdcardPath = hookEntry.Field_sInitUcFromSdcardPath,
cpuArch = cpuArch,
)
}
}
"hookCrossWalk" -> {
val hookEntry = Gson().fromJson(hookJson, HookRules.HookCrossWalk::class.java)
hookCrossWalk(
Class_XWalkView = hookEntry.Class_XWalkView,
Method_getSettings = hookEntry.Method_getSettings,
Method_setJavaScriptEnabled = hookEntry.Method_setJavaScriptEnabled,
Method_loadUrl = hookEntry.Method_loadUrl,
Method_setResourceClient = hookEntry.Method_setResourceClient,
Class_XWalkPreferences = hookEntry.Class_XWalkPreferences,
Method_setValue = hookEntry.Method_setValue,
)
}
"hookXWebView" -> {
val hookEntry = Gson().fromJson(hookJson, HookRules.HookXWebView::class.java)
hookXWebView(
Class_XWebView = hookEntry.Class_XWebView,
Method_initWebviewCore = hookEntry.Method_initWebviewCore,
Method_isXWeb = hookEntry.Method_isXWeb,
Method_isSys = hookEntry.Method_isSys,
Class_XWebPreferences = hookEntry.Class_XWebPreferences,
Method_setValue = hookEntry.Method_setValue,
)
}
else -> {
loggerE(msg = "Unknown Hook Method: $hookMethod")
}
loggerI(msg = "loading rules")

if (pref.get(AppSP.debug_mode)) {
findWebViewMethods()
}

pref.getSet(AppSP.hooks).forEach { name ->
val hookJson = pref.getString("hook_entry_$name", "{}")
try {
when(val hookMethod = Gson().fromJson(hookJson, HookRules.HookRule::class.java).name) {
// TODO: 添加更多 hook 方法
"hookWebView" -> {
val hookEntry = Gson().fromJson(hookJson, HookRules.HookRuleWebView::class.java)
hookWebView(
Class_WebView = hookEntry.Class_WebView,
Method_getSettings = hookEntry.Method_getSettings,
Method_setWebContentsDebuggingEnabled = hookEntry.Method_setWebContentsDebuggingEnabled,
Method_setJavaScriptEnabled = hookEntry.Method_setJavaScriptEnabled,
Method_loadUrl = hookEntry.Method_loadUrl,
Method_setWebViewClient = hookEntry.Method_setWebViewClient,
)
}
"hookWebViewClient" -> {
val hookEntry = Gson().fromJson(hookJson, HookRules.HookRuleWebViewClient::class.java)
hookWebViewClient(
Class_WebViewClient = hookEntry.Class_WebViewClient,
Method_onPageFinished = hookEntry.Method_onPageFinished,
Class_WebView = hookEntry.Class_WebView,
Method_evaluateJavascript = hookEntry.Method_evaluateJavascript,
)
}
"replaceNebulaUCSDK" -> {
if (cpuArch != null) {
val hookEntry = Gson().fromJson(hookJson, HookRules.ReplaceNebulaUCSDK::class.java)
replaceNebulaUCSDK(
Class_UcServiceSetup = hookEntry.Class_UcServiceSetup,
Method_updateUCVersionAndSdcardPath = hookEntry.Method_updateUCVersionAndSdcardPath,
Field_sInitUcFromSdcardPath = hookEntry.Field_sInitUcFromSdcardPath,
cpuArch = cpuArch,
)
}
} catch (e : Exception) {
loggerE(msg = "Parse Failed!", e = e)
return@forEach // continue
}
"hookCrossWalk" -> {
val hookEntry = Gson().fromJson(hookJson, HookRules.HookCrossWalk::class.java)
hookCrossWalk(
Class_XWalkView = hookEntry.Class_XWalkView,
Method_getSettings = hookEntry.Method_getSettings,
Method_setJavaScriptEnabled = hookEntry.Method_setJavaScriptEnabled,
Method_loadUrl = hookEntry.Method_loadUrl,
Method_setResourceClient = hookEntry.Method_setResourceClient,
Class_XWalkPreferences = hookEntry.Class_XWalkPreferences,
Method_setValue = hookEntry.Method_setValue,
)
}
"hookXWebView" -> {
val hookEntry = Gson().fromJson(hookJson, HookRules.HookXWebView::class.java)
hookXWebView(
Class_XWebView = hookEntry.Class_XWebView,
Method_initWebviewCore = hookEntry.Method_initWebviewCore,
Method_isXWeb = hookEntry.Method_isXWeb,
Method_isSys = hookEntry.Method_isSys,
Class_XWebPreferences = hookEntry.Class_XWebPreferences,
Method_setValue = hookEntry.Method_setValue,
)
}
else -> {
loggerE(msg = "Unknown Hook Method: $hookMethod")
}
}
} catch (e : Exception) {
loggerE(msg = "Parse Failed!", e = e)
return@forEach // continue
}
}
}
}
6 changes: 6 additions & 0 deletions app/src/main/res/menu/app_toolbar_menu.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
android:id="@+id/app_toolbar_menu_checking_for_rules_updates"
android:title="@string/checking_for_rules_updates"
app:showAsAction="never"/>
<item
android:id="@+id/app_toolbar_menu_byass_packer"
android:title="@string/bypass_packer"
app:showAsAction="never"
android:checkable="true"
/>
<item
android:id="@+id/app_toolbar_menu_debug_mode"
android:title="@string/debug_mode"
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-zh-rCN/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@
<string name="advance_setting_auto_check_update_desc">模块启动时是否检查更新。</string>
<string name="advance_setting_app_center_desc">AppCenter 是一个来自微软的数据匿名收集方案。我们使用它来匿名收集您使用本模块期间的崩溃日志、性能指标、交互习惯等数据。</string>
<string name="dialog_support_desc">对于中国用户:\n\t1. 支付宝红包码可以每天领取免费的红包,你可以把这个红包免费捐赠给我。\n\t2. 推荐通过支付宝捐赠,因为可以使用一些支付宝发放的红包,比如说上面那个(通过相册扫描可能不行,建议通过另一个屏幕扫码)。\n\t3. 不推荐通过微信捐赠,因为它存在手续费。\n\n对于全球用户:\n\t由于手续费等原因,我暂时不支持接受捐款。</string>
<string name="bypass_packer">支持加固应用</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@
<string name="advance_setting_auto_check_update_desc">Auto check update or not when module launched.</string>
<string name="advance_setting_app_center_desc">AppCenter is an anonymous data collection solution from Microsoft. We use it to anonymously collect crash logs, performance indicators, interaction habits and other data during your use of this module.</string>
<string name="dialog_support_desc">For Chinese user:\n\t1. Alipay Red Packet is which you can get a free red packet and pay it to me every day.\n\t2. AliPay is recommended because it can pay with some red packets you got from Alipay just like #1(Scanning through the album may not work, it is recommended to scan through another screen).\n\t3. WeChat is not recommended because of its handling fee for me.\n\nFor global user:\n\tI do not support accepting donations for the time being due to the handling fee and other reasons.</string>
<string name="bypass_packer">Support packed app</string>
</resources>

0 comments on commit 8b65c92

Please sign in to comment.