Full Code of CreditTone/radar-frida for AI

master fd12f37e6597 cached
58 files
72.7 MB
149.5k tokens
267 symbols
1 requests
Download .txt
Showing preview only (508K chars total). Download the full file or copy to clipboard to get everything.
Repository: CreditTone/radar-frida
Branch: master
Commit: fd12f37e6597
Files: 58
Total size: 72.7 MB

Directory structure:
gitextract_c1gzjbhu/

├── .gitignore
├── .hooker_driver
├── README.md
├── README_EN.md
├── hooker.py
├── js/
│   ├── _hook_js_enhance.js
│   ├── _hook_js_prepare.js
│   ├── _hook_js_warp.js
│   ├── activity_events.js
│   ├── android_ui.js
│   ├── apk_shell_scanner.js
│   ├── bypass_frida_svc_detect.js
│   ├── bypass_root_detect.js
│   ├── bypass_vpn_detect.js
│   ├── cipher.js
│   ├── click.js
│   ├── dump_dex.js
│   ├── dump_so.js
│   ├── edit_text.js
│   ├── file.js
│   ├── find_anit_frida_so.js
│   ├── find_boringssl_custom_verify_func.js
│   ├── get_device_info.js
│   ├── hook_artmethod_register.js
│   ├── hook_encryption_algo.js
│   ├── hook_encryption_algo2.js
│   ├── hook_jni_method_trace.js
│   ├── hook_proxy_check.js
│   ├── hook_register_natives.js
│   ├── just_trust_me.js
│   ├── just_trust_me_for_ios.js
│   ├── keystore_dump.js
│   ├── object_store.js
│   ├── param_hook.js
│   ├── r0capture.js
│   ├── replace_dlsym_get_pthread_create.js
│   ├── rpc.js
│   ├── spoof_signature.js
│   ├── ssl_log.js
│   ├── text_view.js
│   ├── trace_init_proc.js
│   ├── url.js
│   └── webview_enable_debug.js
├── mobile-deploy/
│   ├── busybox-armv7m
│   ├── busybox-i686
│   ├── daemon_app.sh
│   ├── frida-server-16.7.19-android-arm
│   ├── frida-server-16.7.19-android-arm64
│   ├── frpc_arm
│   ├── frpc_arm64
│   ├── frpc_x86
│   ├── radar.dex
│   ├── redsocks
│   ├── redsocks.conf
│   ├── tcpforward_linux_arm
│   ├── tcpforward_linux_arm64
│   └── tcpforward_linux_x86
└── requirements.txt

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.iml
*injectXServer.js
*spider.js
com.guazi*
com.ganji*
*.python-version
*/xinit/*.dex
*pydevproject
.DS_Store
log
.*source/.*
com.example.myapplication/
__handlers__/
build/
__pycache__
*.jar
.idea/
.classpath
.project
.settings/
target/
logs/
data/doc/
data/test/
.vscode/
com.*
cn.com.*


================================================
FILE: .hooker_driver
================================================
-U


================================================
FILE: README.md
================================================
[![MseeP.ai Security Assessment Badge](https://mseep.net/pr/credittone-hooker-badge.png)](https://mseep.ai/app/credittone-hooker)

免责声明
本项目的所有内容仅供学习与技术交流使用,旨在帮助开发者理解移动应用的结构和工作原理。

本项目不包含任何针对特定应用的破解操作或侵权内容。
针对某些app存在的简单解包技术内容在各大技术论坛网站均大量存在,并无对这些app造成实际损害
本项目无意协助任何非法用途,包括但不限于绕过版权保护、修改应用功能或获取未经授权的数据。
请确保在使用本项目工具时遵守相关法律法规,并仅用于个人学习或研究目的。

<p>简体中文 | <a href="README_EN.md">English</a></p>

# 欢迎使用hooker逆向工作台
![GitHub stars](https://img.shields.io/github/stars/CreditTone/hooker?style=flat-square)
![GitHub forks](https://img.shields.io/github/forks/CreditTone/hooker?style=flat-square)
![GitHub code size](https://img.shields.io/github/languages/code-size/CreditTone/hooker?style=flat-square)
![Python](https://img.shields.io/badge/python-3.8.8-blue?style=flat-square)
![frida](https://img.shields.io/badge/frida-16.7.19-blue?style=flat-square)

hooker是一个基于frida实现的逆向工具包。旨在为安卓逆向开发人员提供一个舒适的命令行界面和一些常用的通杀脚本、自动化生成hook脚本、内存漫游探测activity和service、frida版JustTrustMe、boringssl unpinning全网app通杀



为什么你需要hooker?
=================
* [1. frida版JustTrustMe,通杀全网APP,且作者一直在持续维护升级](#12-frida版JustTrustMe包括boringgssl)
* [2. 嵌入式webserver支持把App内部能力快速暴露成HTTP接口,便于做自动化和接口化](#7-嵌入式webserver)
* [3. 快捷设置socks5代理,无需额外安装socksdroid等三方app实现无感知代理](#11-快捷设置socks5无感代理)

<img src="https://raw.githubusercontent.com/CreditTone/img_resources/main/gs_show.jpg" width="1000">


---

目录
=================

* [快速开始](#快速开始)
    * [1. git clone项目](#1-git-clone项目)
    * [2. 安装依赖](#2-安装python3依赖)
    * [3. root手机usb连接](#3-root手机usb连接PC)
    * [4. 启动hooker](#4-启动hooker)
    * [5. 输入调试应用包名](#5-输入调试应用包名)
    * [6. 查看help信息](#6-查看help信息)
    * [7. 嵌入式webserver](#7-嵌入式webserver)
    * [8. 自动化生成frida脚本](#8-自动化生成frida脚本)
    * [9. 列出所有frida脚本](#9-列出应用目录所有frida脚本)
    * [10. attach执行frida脚本](#10-attach执行frida脚本)
    * [11. 快捷设置socks5无感代理](#11-快捷设置socks5无感代理)
    * [12. frida版JustTrustMe](#12-frida版JustTrustMe包括boringgssl)
    * [13. spawn执行frida脚本](#13-spawn执行frida脚本)
    * [14. 取消代理设置](#14-取消代理设置)
    * [15. 重启app](#15-重启app)
    * [16. 获取uid和pid](#16-获取uid和pid)
    * [17. pull文件](#17-pull文件)
    * [18. r0capture](#18-r0capture)
    * [19. hooker自动升级](#19-upgrade)
* [开发手机API接口](#开发手机api接口)
* [hooker原生提供的操作API](#hooker原生提供的操作api)
* [应用工作目录脚本](#应用工作目录脚本)
    * [url.js](#urljs)
    * [just_trust_me.js](#just_trust_mejs)
    * [activity_events.js](#activity_eventsjs)
    * [click.js](#clickjs)
    * [android_ui.js](#android_uijs)
    * [keystore_dump.js](#keystore_dumpjs)
    * [edit_text.js](#edit_textjs)
    * [hook_register_natives.js](#hook_register_nativesjs)
    * [text_view.js](#text_viewjs)
    * [ssl_log.js](#ssllogjs)
    * [just_trust_me_for_ios.js](#just_trust_me_for_iosjs)
    * [dump_dex.js](#dump_dexjs)
    * [trace_init_proc.js](#trace_init_procjs)
    * [hook_artmethod_register.js](#hook_artmethod_registerjs)
    * [find_anit_frida_so.js](#find_anit_frida_sojs)
    * [hook_jni_method_trace.js](#hook_jni_method_tracejs)
    * [replace_dlsym_get_pthread_create.js](#replace_dlsym_get_pthread_createjs)
    * [find_boringssl_custom_verify_func.js](#find_boringssl_custom_verify_funcjs)
    * [get_device_info.js](#get_device_infojs)
    * [apk_shell_scanner.js](#apk_shell_scannerjs)
    * [bypass_frida_svc_detect.js](#bypass_frida_svc_detectjs)
    * [bypass_root_detect.js](#bypass_root_detectjs)
    * [bypass_vpn_detect.js](#bypass_vpn_detectjs)
    * [hook_encryption_algo.js](#hook_encryption_algojs)
    * [hook_encryption_algo2.js](#hook_encryption_algo2js)
    * [webview_enable_debug.js](#webview_enable_debugjs)
* [Windows安装WSL](#windows安装wsl)
	  * [1. 安装wsl ubuntn24.04](#1-安装wsl-ubuntn2404)
	  * [2. 进入wsl,配置代理](#2-进入wsl配置代理)
	  * [3. 安装python3.8和frida](#3-安装python38和frida)
* [自定义frida-server](#自定义frida-server)
* [hooker命令行快捷键](#hooker命令行快捷键)

    
    
手机保证root,无需任何手动启动frida-server等一切配置,hooker会帮你搞定一切。x86架构的模拟器不兼容

# Mac/Linux配置hooker运行环境

Windows请先完成[WSL安装](#windows%E5%AE%89%E8%A3%85wsl),然后跳回到这里

### 1. git clone项目
```shell
stephen@Mac:~$ git clone https://github.com/CreditTone/hooker.git
stephen@Mac:~$ cd hooker
```

### 2. 安装python3依赖
```shell
stephen@Mac:~/hooker$ pip3 install -r requirements.txt
```


### 3. root手机usb连接PC
```shell
stephen@Mac:~/hooker$ adb devices
List of devices attached
FA77C0301476	device
```


### 4. 启动hooker

这里注意,不要用绝对路径去执行,一定要cd到hooker目录下执行python3 hooker.py

hooker启动后将收集所有可调试app的信息,字段含义如下
- PID:当前app的主进程id,如果app没有启动则为0
- APP:app的名称
- IDENTIFIER:app的包名
- EXIST_REVERSE_DIRECTORY:如果app曾经被调试过就是✅,从没被调试过就是❌

```shell
stephen@Mac:~/hooker$ python3 hooker.py
hooker Let's enjoy reverse engineering together
-----------------------------------------------------------------------------------------------
PID   	APP                 	IDENTIFIER                         	EXIST_REVERSE_DIRECTORY
0     	全球上网            	com.miui.virtualsim                	❌
0     	爱奇艺              	com.qiyi.video                     	❌
0     	红手指云手机        	com.redfinger.app                  	❌
0     	Reqable             	com.reqable.android                	❌
0     	美团                	com.sankuai.meituan                	✅
0     	得物                	com.shizhuang.duapp                	❌
0     	某皮           	     cxm.shxpxx.sg                      	✅
0     	微博                	com.sina.weibo                     	❌
0     	今日头条            	com.ss.android.article.news        	✅
0     	西瓜视频            	com.ss.android.article.video       	✅
0     	懂车帝              	com.ss.android.auto                	✅
0     	抖音火山版          	com.ss.android.ugc.live            	✅
0     	抖音精选            	com.ss.android.yumme.video         	❌
0     	淘宝                	com.taobao.taobao                  	✅
0     	腾讯视频            	com.tencent.qqlive                 	❌
0     	Termux              	com.termux                         	❌
0     	轻奢                	com.tm.bachelorparty               	✅
0     	WiFi ADB            	com.ttxapps.wifiadb                	❌
0     	VMOS Pro            	com.vmos.pro                       	✅
0     	游戏中心            	com.xiaomi.gamecenter              	❌
0     	小米商城            	com.xiaomi.shop                    	❌
0     	米家                	com.xiaomi.smarthome               	❌
0     	小米有品            	com.xiaomi.youpin                  	✅
0     	小红书              	com.xingin.xhs                     	✅
0     	运满满货主          	com.xiwei.logistics.consignor      	✅
0     	拼多多              	com.xunmeng.pinduoduo              	✅
0     	EnvCheck            	com.yimian.envcheck                	✅
0     	check_env           	com.yuuki.check_env                	❌
0     	TikTok              	com.zhiliaoapp.musically           	❌
0     	XPrivacyLua         	eu.faircode.xlua                   	❌
0     	imToken             	im.token.app                       	❌
0     	SocksDroid          	net.typeblog.socks                 	❌
0     	F-Droid             	org.fdroid.fdroid                  	❌
0     	ProxyDroid          	org.proxydroid                     	❌
3457  	手机管家            	com.miui.securitycenter            	✅
3509  	优信拍              	com.uxin.buyerphone                	✅
18780 	抖音                	com.ss.android.ugc.aweme           	✅
20174 	应用商店            	com.xiaomi.market                  	❌
20913 	设置                	com.android.settings               	❌
30500 	小爱同学            	com.miui.voiceassist               	❌
32163 	相机                	com.android.camera                 	✅
Please enter the identifier that needs to be reversed
hooker(Identifier):
```
***


### 5. 输入调试应用包名

- 输入调试应用包名回车后,如果是第一次调试应用,hooker将创建应用目录,应用目录名称为应用的Identifier,用于存放所有js脚本和快捷命令。

- hooker将帮你检测当前app是否启动且在手机前台,如不在启动帮你启动,如不在前台帮你切到前台

- frida通杀脚本可以在hooker交互式命令行下用attach/spawn执行,也可以手动cd到应用目录用快捷命令或原生的frida命令执行。

- 你可以修改应用工作目录下任何脚本


```shell
hooker(Identifier): cxm.shxpxx.sg
✅ App cxm.shxpxx.sg is already in the foreground
Creating working directory: cxm.shxpxx.sg
Generating frida shortcut command...
Generating built-in frida script...
pull /data/app/cxm.shxpxx.sg-L8zkrpFVICv0-hOrtmPPxA==/base.apk to cxm.shxpxx.sg/ShopeeSG_3.43.40.apk successful
Working directory create successful
just_trust_me.js                                 empty.js                                         keystore_dump.js
edit_text.js                                     activity_events.js                               find_boringssl_custom_verify_func.js
ssl_log.js                                       hook_register_natives.js                         click.js
get_device_info.js                               apk_shell_scanner.js                             dump_dex.js
object_store.js                                  hook_artmethod_register.js                       replace_dlsym_get_pthread_create.js
just_trust_me_for_ios.js                         trace_initproc.js                                android_ui.js
hook_jni_method_trace.js                         url.js                                           just_trust_me_okhttp_hook_finder_for_android.js
text_view.js                                     find_anit_frida_so.js
某皮 > 
```
![hooker_enter_debug.gif](https://raw.githubusercontent.com/CreditTone/img_resources/main/hooker_enter_debug.gif)
***




### 6. 查看help信息

在使用hooker过程中,如不记得命令,可随时调出help查看操作手册。

```shell
某皮 > help
h, help                                      show this help message
a, activitys                                 show the activity stack
s, services                                  show the service stack
o, object [object_id]                        show object info by object_id
v, view [view_id]                            show view info by view_id of view
gs, generatescript [class_name:method_name]  specify the class name and method name to generate a frida hook java script file. For example: generatescript
                                             okhttp3.Request$Builder:addHeader
p, proxy [socks5_proxy_server]               set up a socks5 proxy for this app. For example: proxy socks5://192.168.0.100:9998
up, unproxy                                  remove socks5 proxy for this app
trust, justtrustme                           quickly spawn just_trust_me.js script to kill all ssl pinning
ls                                           list all the frida scripts of the current app
attach [script_file_name]                    quickly execute a frida script, similar to executing the command "frida -U com.example.app -l xxx.js". For example: attach url.js
spawn [script_file_name]                     quickly spawn a frida script, similar to executing the command "frida -U -f -n com.example.app -l xxx.js". For example: spawn
                                             just_trust_me.js
restart                                      restart this app
pid                                          get pid of this app main process
uid                                          get pid of this app
exit                                         return to the previous level
某皮 > 
```
![hooker_help.gif](https://raw.githubusercontent.com/CreditTone/img_resources/main/hooker_help.gif)
***



### 7. 嵌入式webserver

hooker 支持在手机中注入一个轻量级 webserver。启动后会在目标 App 进程内开启一个 HTTP 服务,默认端口是 `8080`。这个服务既可以暴露 patch 项目里自定义的 controller,也会自动挂载一组内置调试接口。

- 启动内置 webserver

```shell
某音火山版 > webserver start
Http server port: 8080
Http server: http://10.112.101.249:8080
```

启动内置 webserver 后,浏览器打开首页即可看到当前已注册的 API 列表。常用能力包括:

- 服务管理:`/` 查看欢迎页和接口清单,`/stop` 停止当前 webserver。
- UI 自动化:`/hooker/ui/...` 提供点击控件、按文本点击、按坐标点击、设置输入框文本、触发返回/Home、启动 Activity、获取屏幕信息、翻页、滚动 RecyclerView、尝试关闭弹窗等能力,适合做半自动化操作和页面联调。
- 页面结构导出:`/hooker/uiauto/dump`、`/hooker/uiauto/window_dump.xml`、`/hooker/uiauto/window_dump.json` 可以导出当前窗口层级,方便定位控件和分析页面结构。
- 截图能力:`/hooker/screencap/screenshot` 可直接调用系统 `screencap` 截图;`/hooker/mediaprojection/...` 支持申请 MediaProjection 权限并抓取整屏 PNG,更适合做远程观察和自动化闭环。
- App 信息读取:`/hooker/appinfo` 可读取包名、版本、组件、权限、签名和目录信息;`/hooker/appinfo/shared_prefs`、`/hooker/appinfo/databases`、`/hooker/appinfo/read_table` 可以直接查看 `shared_prefs`、数据库结构和表数据。
- 类/对象辅助调用:`/hooker/classhelper/invoke_static_method`、`/hooker/classhelper/invoke_method` 支持通过 HTTP 直接调用静态方法或已缓存对象的方法,便于快速验证算法、补环境或调试业务逻辑。
- 文件回传:`/file?filename=...` 可以把绝对路径文件或 webserver 缓存目录里的文件直接通过 HTTP 返回。
- MCP 风格 UI 工具:`/hooker/mcp/ui/tools` 和 `/hooker/mcp/ui/call` 把常用 UI 操作封装成统一工具接口,便于外部脚本或 Agent 通过 HTTP 驱动当前 App。


- 7.1 启动自定义 webserver

给定一个patch工程的jar包,将爬虫接口启动为webserver。

```shell
某宝 > webserver start taxbax-patch.jar
Converting taxbax-patch.jar to taxbax-patch.dex...
Successfully converted to taxbax-patch.dex (7160 bytes)
push file OK /data/user/0/com.taxbax.taxbax/hooker_server.dex
Http server port: 2026
Http server: http://10.112.101.249:2026
```

这里的 `taxbax-patch.jar` 可以理解成“运行在目标 App 进程里的业务插件”。`hooker` 会先把 jar 转成 dex,再注入到目标进程中,扫描其中带注解的类,然后把这些类注册成 HTTP 路由。

- 7.2 自定义 webserver 适合做这类事情:

- 暴露目标 App 内部已经存在的业务能力,比如搜索、评论、详情页、签名、加解密、用户资料、直播接口等。
- 直接复用目标 App 自己的登录态、网络栈、环境参数和对象实例,避免在外部重复补协议。
- 把异步回调、Observable、Listener、页面对象调用收敛成一个同步 HTTP 接口,对外统一返回 JSON 或文本。

你可以像开发springboot一样优雅的开发嵌入式的httpserver,并调用目标app的任何java代码而无需像有些xposed插件一样TMD的反射、反射、再反射,跟SB一样😊

- 用 `@HookerWebServerConfiguration(port = 2026)` 指定端口,不写时默认走 `8080`。
- 用 `@HookerController("/taobao")`、`@HookerController("/douyin")` 这类注解定义业务前缀。
- 用 `@HookerRequestMapping(path = "/getProductDetail")` 之类的注解暴露具体接口。
- 用 `@HookerRequestParam`、`@HookerRequestPostJson` 接收查询参数和 POST JSON。


具体开发文档,将在https://github.com/CreditTone/radar4hooker详细介绍


- 7.3 webserver 持久化

如果你已经为某个 App 开发好了 patch 工程,并且需要在多台设备上长期部署,那么 `frida + hooker` 的临时注入方式会比较重。针对这种场景,作者提供了 Xposed 插件 `HookerServer`:

`https://github.com/CreditTone/HookerServer`

只要手机支持 Xposed/LSPosed,就可以借助这个插件把 webserver 持久化到目标 App 中。

操作步骤:

1. 从 `https://github.com/CreditTone/HookerServer/releases` 下载最新 APK。
2. 把 `patch.dex` 推送到 `/data/user/0/{package}/hooker_server.dex`。
3. 在 Xposed/LSPosed 中启用 `HookerServer`,并勾选目标 App。
4. 重启 App,webserver 即可自动启动。

其中 `patch.dex` 可以在第一次部署 `patch.jar` 后获得,hooker 会自动完成 jar 转 dex,并把生成结果放到目标应用的工作目录中。


***



### 8. 自动化生成frida脚本
自动化生成脚本是hooker的杀器。虽然现在AI大模型也可以写,但是我们离内存近,更快,也不需要联网。生成的脚本自带打印堆栈等信息,和一些你可能需要的扩展方法。
另外在生成脚本的过程中,命令行类名、方法名提示也可以当作搜索使用,能通过关键词快速搜索定位类方法。hooker搜索类比jadx快很多,不信就试试......

![gs_show.jpg](https://raw.githubusercontent.com/CreditTone/img_resources/main/gs_show.jpg)
- Command语法:gs, generatescript [class_name:method_name]


- 8.1 生成指定方法的frida hook脚本:
gs okhttp3.Request$Builder:addHeader,参数部分(String, String)不是必须写的

```shell
某信拍 > gs okhttp3.Request$Builder:addHeader(String, String)
Generating frida script, please wait for a few seconds
frida hook script: okhttp3.Request.Builder.addHeader.js
某信拍 > 
```

```js
//cat okhttp3.Request.Builder.addHeader.js
Java.perform(function() {
    var okhttp3_Request_Builder_clz = Java.use('okhttp3.Request$Builder');
    var okhttp3_Request_Builder_clz_method_addHeader_2grl = okhttp3_Request_Builder_clz.addHeader.overload('java.lang.String', 'java.lang.String');
    okhttp3_Request_Builder_clz_method_addHeader_2grl.implementation = function(string, string_x2) {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.addHeader(java.lang.String,java.lang.String)';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_addHeader_2grl.call(this, string, string_x2);
        console.log("header name:" + string + " header value:" + string_x2);
        printBeat(beat);
        return ret;
    };
});
```
***



- 8.2 生成指定类的所有成员方法的frida hook脚本:
gs okhttp3.Request$Builder

```shell
某信拍 > generatescript okhttp3.Request$Builder
Generating frida script, please wait for a few seconds
frida hook script: okhttp3.Request.Builder.allfunc.js
```
***

```js
//cat okhttp3.Request.Builder.allfunc.js
//okhttp3.Request$Builder
Java.perform(function() {
    var okhttp3_Request_Builder_clz = Java.use('okhttp3.Request$Builder');
    var okhttp3_Request_Builder_clz_method_header_ng3n = okhttp3_Request_Builder_clz.header.overload('java.lang.String', 'java.lang.String');
    okhttp3_Request_Builder_clz_method_header_ng3n.implementation = function(string, string_x2) {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.header(java.lang.String,java.lang.String)';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_header_ng3n.call(this, string, string_x2);
        printBeat(beat);
        return ret;
    };
    var okhttp3_Request_Builder_clz_method_cacheControl_q8q5 = okhttp3_Request_Builder_clz.cacheControl.overload('okhttp3.CacheControl');
    okhttp3_Request_Builder_clz_method_cacheControl_q8q5.implementation = function(cacheControl) {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.cacheControl(okhttp3.CacheControl)';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_cacheControl_q8q5.call(this, cacheControl);
        printBeat(beat);
        return ret;
    };
    var okhttp3_Request_Builder_clz_method_method_bjk9 = okhttp3_Request_Builder_clz.method.overload('java.lang.String', 'okhttp3.RequestBody');
    okhttp3_Request_Builder_clz_method_method_bjk9.implementation = function(string, requestBody) {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.method(java.lang.String,okhttp3.RequestBody)';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_method_bjk9.call(this, string, requestBody);
        printBeat(beat);
        return ret;
    };
    var okhttp3_Request_Builder_clz_method_head_a5nq = okhttp3_Request_Builder_clz.head.overload();
    okhttp3_Request_Builder_clz_method_head_a5nq.implementation = function() {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.head()';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_head_a5nq.call(this);
        printBeat(beat);
        return ret;
    };
    var okhttp3_Request_Builder_clz_method_headers_to5i = okhttp3_Request_Builder_clz.headers.overload('okhttp3.Headers');
    okhttp3_Request_Builder_clz_method_headers_to5i.implementation = function(headers) {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.headers(okhttp3.Headers)';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_headers_to5i.call(this, headers);
        printBeat(beat);
        return ret;
    };
    var okhttp3_Request_Builder_clz_method_post_heaq = okhttp3_Request_Builder_clz.post.overload('okhttp3.RequestBody');
    okhttp3_Request_Builder_clz_method_post_heaq.implementation = function(requestBody) {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.post(okhttp3.RequestBody)';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_post_heaq.call(this, requestBody);
        printBeat(beat);
        return ret;
    };
    var okhttp3_Request_Builder_clz_method_build_rmqx = okhttp3_Request_Builder_clz.build.overload();
    okhttp3_Request_Builder_clz_method_build_rmqx.implementation = function() {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request okhttp3.Request$Builder.build()';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_build_rmqx.call(this);
        printBeat(beat);
        return ret;
    };
    var okhttp3_Request_Builder_clz_method_patch_hp9u = okhttp3_Request_Builder_clz.patch.overload('okhttp3.RequestBody');
    okhttp3_Request_Builder_clz_method_patch_hp9u.implementation = function(requestBody) {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.patch(okhttp3.RequestBody)';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_patch_hp9u.call(this, requestBody);
        printBeat(beat);
        return ret;
    };
    var okhttp3_Request_Builder_clz_method_url_0owi = okhttp3_Request_Builder_clz.url.overload('java.lang.String');
    okhttp3_Request_Builder_clz_method_url_0owi.implementation = function(string) {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.url(java.lang.String)';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_url_0owi.call(this, string);
        printBeat(beat);
        return ret;
    };
    var okhttp3_Request_Builder_clz_method_removeHeader_uzb9 = okhttp3_Request_Builder_clz.removeHeader.overload('java.lang.String');
    okhttp3_Request_Builder_clz_method_removeHeader_uzb9.implementation = function(string) {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.removeHeader(java.lang.String)';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_removeHeader_uzb9.call(this, string);
        printBeat(beat);
        return ret;
    };
    var okhttp3_Request_Builder_clz_method_url_ykbd = okhttp3_Request_Builder_clz.url.overload('java.net.URL');
    okhttp3_Request_Builder_clz_method_url_ykbd.implementation = function(url) {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.url(java.net.URL)';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_url_ykbd.call(this, url);
        printBeat(beat);
        return ret;
    };
    var okhttp3_Request_Builder_clz_method_delete_dqyl = okhttp3_Request_Builder_clz.delete.overload();
    okhttp3_Request_Builder_clz_method_delete_dqyl.implementation = function() {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.delete()';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_delete_dqyl.call(this);
        printBeat(beat);
        return ret;
    };
    //.......省略N行代码
```
***



- 8.3 生成指定类的构造方法的frida hook脚本:
gs okhttp3.Request$Builder:_ 或者gs okhttp3.Request$Builder:\<init\>

```shell
某信拍 > gs okhttp3.Request$Builder:<init>()
Generating frida script, please wait for a few seconds
frida hook script: okhttp3.Request.Builder._init.js
```

```js
//cat okhttp3.Request.Builder._init.js
//okhttp3.Request$Builder:<init>()
Java.perform(function() {
    var okhttp3_Request_Builder_clz = Java.use('okhttp3.Request$Builder');
    var okhttp3_Request_Builder_clz_init_uw3i = okhttp3_Request_Builder_clz.$init.overload();
    okhttp3_Request_Builder_clz_init_uw3i.implementation = function() {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder()';
        var beat = newMethodBeat(beatText, executor);
        var returnObj = okhttp3_Request_Builder_clz_init_uw3i.call(this);
        printBeat(beat);
        return returnObj;
    };
    var okhttp3_Request_Builder_clz_init_e58t = okhttp3_Request_Builder_clz.$init.overload('okhttp3.Request');
    okhttp3_Request_Builder_clz_init_e58t.implementation = function(v0) {
        var executor = this.hashCode();
        var beatText = 'okhttp3.Request$Builder(okhttp3.Request)';
        var beat = newMethodBeat(beatText, executor);
        var returnObj = okhttp3_Request_Builder_clz_init_e58t.call(this, v0);
        printBeat(beat);
        return returnObj;
    };
});
```
***




### 9. 列出应用目录所有frida脚本

- 查看应用目录下所有的脚本,这里有hooker给你生成的通杀脚本,也有您生成的指定hook脚本,您可以修改定制。

- 如果您有自定义的脚本放到应用目录下,ls命令可以将脚本名称刷入命令行提示缓存

```shell
某皮 > ls
just_trust_me.js                                 empty.js                                         keystore_dump.js
okhttp3.Request.Builder.addHeader.js             edit_text.js                                     activity_events.js
find_boringssl_custom_verify_func.js             ssl_log.js                                       hook_register_natives.js
click.js                                         get_device_info.js                               apk_shell_scanner.js
dump_dex.js                                      object_store.js                                  hook_artmethod_register.js
replace_dlsym_get_pthread_create.js              just_trust_me_for_ios.js                         trace_initproc.js
android_ui.js                                    hook_jni_method_trace.js                         url.js
just_trust_me_okhttp_hook_finder_for_android.js  text_view.js                                     find_anit_frida_so.js
某皮 >
```
***

### 10. attach执行frida脚本

在hooker下执行frida脚本,您只需要输入attach【空格】脚本名称会自动弹出提示。上下选择需要的脚本按tab键即可自动输入全名称。
这是hooker在追求极致的工匠精神,帮助您从开波音737到开空客320的跳跃。

> **日志同步**:`attach` / `frida` 执行脚本时,Frida 脚本的 `send()` 输出会自动同步保存到应用工作目录下的同名 `.log` 文件。例如 `attach url.js` 会生成 `url.log`,方便喂给 AI 分析。

```shell
某信拍 > attach url.js
Frida output logging -> com.uxin.buyerphone/url.log
------------startFlag:0755liv1,objectHash:-915348569,thread(id:810,name:Wmda.EventUploadThread),timestamp:1747836814835---------------
url:https://apiwmxx.xxx.com.cn/report/c?api_v=3&sdk_v=1.7.0.0&timestamp=1747836814832&appid=17591177894321&p=2&uuid=248056262e0030b7bb56c0f9237f846d
public java.net.URL(String)
	at java.net.URL.<init>(Native Method)
	at com.wxbx.wmda.e.b.a(SourceFile:5)
	at com.wxbx.wmda.e.b.a(SourceFile:1)
	at com.wxbx.wmda.h.a.a(SourceFile:162)
	at com.wxbx.wmda.h.a.b(SourceFile:19)
	at com.wxbx.wmda.h.a.a(SourceFile:2)
	at com.wxbx.wmda.h.a$b.handleMessage(SourceFile:3)
	at android.os.Handler.dispatchMessage(Handler.java:106)
	at android.os.Looper.loop(Looper.java:201)
	at android.os.HandlerThread.run(HandlerThread.java:65)
------------endFlag:0755liv1,usedtime:1---------------

------------startFlag:1ps6go99,objectHash:-237375819,thread(id:810,name:Wmda.EventUploadThread),timestamp:1747836815192---------------
url:https://apiwmxx.xxx.com.cn/report/c?api_v=3&sdk_v=1.7.0.0&timestamp=1747836815188&appid=17591177894321&p=2&uuid=248056262e0030b7bb56c0f9237f846d
public java.net.URL(String)
	at java.net.URL.<init>(Native Method)
	at com.android.okhttp.HttpUrl.url(HttpUrl.java:327)
	at com.android.okhttp.Request.url(Request.java:53)
	at com.android.okhttp.Request$Builder.build(Native Method)
	at com.android.okhttp.internal.huc.HttpURLConnectionImpl.newHttpEngine(HttpURLConnectionImpl.java:377)
	at com.android.okhttp.internal.huc.HttpURLConnectionImpl.initHttpEngine(HttpURLConnectionImpl.java:332)
	at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:124)
	at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getOutputStream(HttpURLConnectionImpl.java:258)
	at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getOutputStream(DelegatingHttpsURLConnection.java:218)
	at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:26)
	at com.wxbx.wmda.e.b.a(SourceFile:14)
	at com.wxbx.wmda.e.b.a(SourceFile:1)
	at com.wxbx.wmda.h.a.a(SourceFile:162)
	at com.wxbx.wmda.h.a.b(SourceFile:19)
	at com.wxbx.wmda.h.a.a(SourceFile:2)
	at com.wxbx.wmda.h.a$b.handleMessage(SourceFile:3)
	at android.os.Handler.dispatchMessage(Handler.java:106)
	at android.os.Looper.loop(Looper.java:201)
	at android.os.HandlerThread.run(HandlerThread.java:65)
------------endFlag:1ps6go99,usedtime:0---------------
// 这里省略无数日志.............
------------startFlag:i7osxvjl,objectHash:134280600,thread(id:810,name:Wmda.EventUploadThread),timestamp:1747836815193---------------
url:https://apiwmxx.xxx.com.cn/report/c?api_v=3&sdk_v=1.7.0.0&timestamp=1747836815188&appid=17591177894321&p=2&uuid=248056262e0030b7bb56c0f9237f846d
com.android.okhttp.Request.Builder.build()
	at com.android.okhttp.Request$Builder.build(Native Method)
	at com.android.okhttp.internal.huc.HttpURLConnectionImpl.newHttpEngine(HttpURLConnectionImpl.java:377)
	at com.android.okhttp.internal.huc.HttpURLConnectionImpl.initHttpEngine(HttpURLConnectionImpl.java:332)
	at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:124)
	at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getOutputStream(HttpURLConnectionImpl.java:258)
	at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getOutputStream(DelegatingHttpsURLConnection.java:218)
	at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:26)
	at com.wxbx.wmda.e.b.a(SourceFile:14)
	at com.wxbx.wmda.e.b.a(SourceFile:1)
	at com.wxbx.wmda.h.a.a(SourceFile:162)
	at com.wxbx.wmda.h.a.b(SourceFile:19)
	at com.wxbx.wmda.h.a.a(SourceFile:2)
	at com.wxbx.wmda.h.a$b.handleMessage(SourceFile:3)
	at android.os.Handler.dispatchMessage(Handler.java:106)
	at android.os.Looper.loop(Looper.java:201)
	at android.os.HandlerThread.run(HandlerThread.java:65)
------------endFlag:i7osxvjl,usedtime:1---------------
// 这里省略无数日志.............
```
***

### 11. 快捷设置socks5无感代理

通过iptables链路层转发包实现一键设置代理,优势是APP完全无感知被代理。推荐使用charles的socks5

设置代理后必须主动去[关闭代理](#14-取消代理设置),代理不会自动取消


```shell
某音 > proxy socks5://10.112.99.11:9998
proxy socks5://10.112.99.11:9998 OK
某音 > 
```
***


### 12. frida版JustTrustMe(包括boringgssl)

关掉SSL证书校验

```shell
某音 > justtrustme
Package name: com.ss.xxxx.xxx.aweme
android.security.net.config.NetworkSecurityTrustManager.checkPins('java.util.List') was hooked!
android.security.net.config.NetworkSecurityTrustManager.checkPins('java.util.List') was hooked!
android.security.net.config.NetworkSecurityTrustManager.checkPins('java.util.List') was hooked!
android.security.net.config.NetworkSecurityTrustManager.checkPins('java.util.List') was hooked!
javax.net.ssl.TrustManagerFactory.getTrustManagers() was hooked!
javax.net.ssl.SSLContext.init('[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom') was hooked!
javax.net.ssl.TrustManagerFactory.getTrustManagers() was hooked!
javax.net.ssl.SSLContext.init('[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom') was hooked!
javax.net.ssl.TrustManagerFactory.getTrustManagers() was hooked!
javax.net.ssl.SSLContext.init('[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom') was hooked!
javax.net.ssl.SSLContext.init('[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom') was hooked!
static void com.android.org.conscrypt.Platform.checkServerTrusted(javax.net.ssl.X509TrustManager,java.security.cert.X509Certificate[],java.lang.String,com.android.org.conscrypt.AbstractConscryptSocket) throws java.security.cert.CertificateException was hooked!
static void com.android.org.conscrypt.Platform.checkServerTrusted(javax.net.ssl.X509TrustManager,java.security.cert.X509Certificate[],java.lang.String,com.android.org.conscrypt.AbstractConscryptSocket) throws java.security.cert.CertificateException was hooked!
static void com.android.org.conscrypt.Platform.checkServerTrusted(javax.net.ssl.X509TrustManager,java.security.cert.X509Certificate[],java.lang.String,com.android.org.conscrypt.AbstractConscryptSocket) throws java.security.cert.CertificateException was hooked!
static void com.android.org.conscrypt.Platform.checkServerTrusted(javax.net.ssl.X509TrustManager,java.security.cert.X509Certificate[],java.lang.String,com.android.org.conscrypt.AbstractConscryptSocket) throws java.security.cert.CertificateException was hooked!
okhttp3.internal.tls.OkHostnameVerifier.verify('java.lang.String', 'javax.net.ssl.SSLSession') was hooked!
okhttp3.CertificatePinner.check('java.lang.String', 'java.util.List') was hooked!
static void com.android.org.conscrypt.Platform.checkServerTrusted(javax.net.ssl.X509TrustManager,java.security.cert.X509Certificate[],java.lang.String,com.android.org.conscrypt.AbstractConscryptSocket) throws java.security.cert.CertificateException was hooked!
static void com.android.org.conscrypt.Platform.checkServerTrusted(javax.net.ssl.X509TrustManager,java.security.cert.X509Certificate[],java.lang.String,com.android.org.conscrypt.AbstractConscryptSocket) throws java.security.cert.CertificateException was hooked!
okhttp3.internal.tls.OkHostnameVerifier.verify('java.lang.String', 'javax.net.ssl.SSLSession') was hooked!
okhttp3.CertificatePinner.check('java.lang.String', 'java.util.List') was hooked!
okhttp3.internal.tls.OkHostnameVerifier.verify('java.lang.String', 'javax.net.ssl.SSLSession') was hooked!
okhttp3.internal.tls.OkHostnameVerifier.verify('java.lang.String', 'javax.net.ssl.SSLSession') was hooked!
okhttp3.internal.tls.OkHostnameVerifier.verify('java.lang.String', 'javax.net.ssl.SSLSession') was hooked!
okhttp3.CertificatePinner.check('java.lang.String', 'java.util.List') was hooked!
okhttp3.CertificatePinner.check('java.lang.String', 'java.util.List') was hooked!
okhttp3.CertificatePinner.check('java.lang.String', 'java.util.List') was hooked!
static void com.android.org.conscrypt.Platform.checkServerTrusted(javax.net.ssl.X509TrustManager,java.security.cert.X509Certificate[],java.lang.String,com.android.org.conscrypt.AbstractConscryptSocket) throws java.security.cert.CertificateException was hooked!
okhttp3.internal.tls.OkHostnameVerifier.verify('java.lang.String', 'javax.net.ssl.SSLSession') was hooked!
okhttp3.CertificatePinner.check('java.lang.String', 'java.util.List') was hooked!
okhttp3.CertificatePinner.check('java.lang.String', 'java.util.List') was hooked!
okhttp3.CertificatePinner.check('java.lang.String', 'java.util.List') was hooked!
// 这里省略无数日志.............
```
***


### 13. spawn执行frida脚本

> **日志同步**:`spawn` / `fridaf` 执行脚本时,Frida 脚本的 `send()` 输出同样会自动同步保存到同名 `.log` 文件。例如 `spawn just_trust_me.js` 会生成 `just_trust_me.log`。

```shell
某信拍 > spawn just_trust_me.js
Frida output logging -> com.uxin.buyerphone/just_trust_me.log
Package name: com.xxx.buyxxphone
javax.net.ssl.SSLContext.init('[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom') was hooked!
javax.net.ssl.TrustManagerFactory.getTrustManagers() was hooked!
javax.net.ssl.SSLContext.init('[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom') was hooked!
// 这里省略无数日志.............
```
***


### 14. 取消代理设置

一键取消代理

```shell
某音 > unproxy
unproxy OK
某音 > 
```
***



### 15. 重启app


```shell
某信拍 > restart
restarts com.xxx.buyxxphone
```
***


### 16. 获取uid和pid

```shell
某信拍 > uid
10189
某信拍 > pid
3509
```
***


### 17. pull文件

快捷拉取文件到本地应用工作目录

```shell
转某 > pull /data/app/com.xxx.zhuanmou-o1ZYFILxnOCIpvvqJQKrpQ==/lib/arm64/libmsaoaidsec.so
pull /data/app/com.xxx.zhuanmou-o1ZYFILxnOCIpvvqJQKrpQ==/lib/arm64/libmsaoaidsec.so to com.xxx.zhuanmou/libmsaoaidsec.so successful
转某 > pull /data/app/com.xxx.zhuanmou-o1ZYFILxnOCIpvvqJQKrpQ==/lib/arm64/libsignLib.so
pull /data/app/com.xxx.zhuanmou-o1ZYFILxnOCIpvvqJQKrpQ==/lib/arm64/libsignLib.so to com.xxx.zhuanmou/libsignLib.so successful
转某 > pull /data/app/com.xxx.zhuanmou-o1ZYFILxnOCIpvvqJQKrpQ==/lib/arm64/libweconvert.so
pull /data/app/com.xxx.zhuanmou-o1ZYFILxnOCIpvvqJQKrpQ==/lib/arm64/libweconvert.so to com.xxx.zhuanmou/libweconvert.so successful
```
***




### 18. r0capture
hooker集成了r0capture,抓包产生的pcap文件保存在{应用包名}/r0capture_ssl.pcap路径下,如酷安:com.coolapk.market/r0capture_ssl.pcap

```shell
JqgvBRe45o4QLyGguX+eVDoN0CPLTcPXqRVBhh13z2PTch2W7Hgv\/xlp4x2v\/QemWXrjWuifc2el1gzK1+8YPW+1NyTFCC8P10+zpCAPRgBwxpjKp4ecSQngU32yY2daIbaEwj0fvAg12VZNCdtI8jtpGtgds5xe61cihcBaYg\/CTvUIEylZqE6cbWsbuiBf7OuJLAnofXi3JtUaD+kJxFQ4fsZOTxhpZqANHIVv17GPcG4CoJEMws8UzawN3xPMqVYdzv+bpAnbDRhZy6LsVxS5S6yYtrawQdroJqVfsaLXlzgTBQe6RVPYqWG38QKJ1cuOGttk0ukigGIce3QAUcJl0c3fsi973ydYnSY60PBSKumqZAFh4VM0jk5tmRUtZlrqfQDVmfgIaocPQ=="}
2025-06-19 11:35:59.794 | INFO     | __main__:r0capture_on_message:939 - java.lang.Throwable
	at com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream.write(Native Method)
	at com.android.okhttp.okio.Okio$1.write(Okio.java:76)
	at com.android.okhttp.okio.AsyncTimeout$1.write(AsyncTimeout.java:155)
	at com.android.okhttp.okio.RealBufferedSink.flush(RealBufferedSink.java:221)
	at com.android.okhttp.internal.http.Http1xStream.finishRequest(Http1xStream.java:161)
	at com.android.okhttp.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:735)
	at com.android.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:609)
	at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:471)
	at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:407)
	at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:244)
	at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getInputStream(DelegatingHttpsURLConnection.java:210)
	at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:26)
	at com.tencent.msdk.dns.core.rest.share.a.a(AbsHttpDns.java:23)
	at com.tencent.msdk.dns.core.k$a.run(LookupHelper.java:24)
	at com.tencent.msdk.dns.core.c$b.run(CountDownManager.java:3)
	at com.tencent.msdk.dns.core.c$a.run(CountDownManager.java:3)
	at com.tencent.msdk.dns.base.executor.DnsExecutors$a.run(DnsExecutors.java:38)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
	at java.lang.Thread.run(Thread.java:764)

CTRL + C to stop >
Interrupting
flushing com.coolapk.market/r0capture_ssl.pcap successful
r0capture.js detach successful
Restarting 酷安 Please wait for a few seconds
```
***


### 19. upgrade

hooker更新频繁,平均周更约10次。upgrade帮助您随时同步最新代码和相关文件到本地。

```shell
MacBook-Pro-32G-2T:hooker stephen256$ python3 hooker.py upgrade
Upgrading hooker
Repository updated with 'git pull'.
Updating mobile-deploy/libext64.so
Updating mobile-deploy/libext.so
Updating mobile-deploy/libevent-2.1.so
Please restart hooker
```
***


# 应用目录通杀脚本

### url.js

该脚本会 hook 应用中 构造 URL 或 URI 对象的多个关键方法,用于打印或分析网络请求相关的信息(如目标 URL)

Hook 的目标方法
java.net.URI(String) 构造函数

java.net.URL(String) 构造函数

okhttp3.Request.Builder.build() 方法(常用于创建 HTTP 请求)

com.android.okhttp.Request.Builder.build()(系统自带 okhttp)

android.net.Uri.parse(String) 方法(处理 URI 的常用工具方法)

推荐命令:frida url.js

![](https://raw.githubusercontent.com/CreditTone/img_resources/main/url.jpg)
***

### just_trust_me.js
frida版本的just_trust_me,用于绕过 Android 系统中的证书校验逻辑,允许抓取 HTTPS 流量,适用于基于 OkHttp、HttpURLConnection 等网络库的应用。

支持boringssl unpinning,理论上支持全网所有app,除非像美团一样做了登录风控抓不了。

在hooker命令行模式封装了快捷命令justtrustme
这在上文 [执行justtrustme kill掉所有ssl验证](#11-执行justtrustme-kill掉所有ssl验证包括boringgssl)有介绍

亦可直接执行脚本 spawn just_trust_me.js
![](https://raw.githubusercontent.com/CreditTone/img_resources/main/just_trust_me.gif)
***

### activity_events.js
当你需要跟踪start某个Activity启动时可执行,获取startActivity的intent信息和调用堆栈。

推荐命令:frida activity_events.js

![](https://raw.githubusercontent.com/CreditTone/img_resources/main/activity_events.gif)
***

### click.js
用于监听 Android 应用中点击事件(`OnClickListener`),主要用于分析用户交互操作,获取被点击View的真实ViewClass

推荐命令:frida click.js


***

### android_ui.js
封装一些操作原生Android UI的函数。如startActivity(activityName)、home()、back()、finishCurrentActivity()、clickByText(text) 等等,命令使用得用attach './attach android_ui.js' 原理是借助radar.dex作为代理操作Android原生View。

推荐命令:frida android_ui.js

***

### keystore_dump.js
在https双向认证的情况下,dump客户端证书为p12。存储位置:/data/user/0/{packagename}/client_keystore_{nowtime}.p12 证书密码: hooker。原理是hook java.security.KeyStore的getPrivateKey和getCertificate方法,因为客户端向服务发送证书必调这个方法。强烈建议keystore_dump.js用spawn模式启动,

推荐命令:spawn keystore_dump.js

![](https://raw.githubusercontent.com/CreditTone/img_resources/main/https_bothway_01.png)
![](https://raw.githubusercontent.com/CreditTone/img_resources/main/https_bothway_02.png)
![](https://raw.githubusercontent.com/CreditTone/img_resources/main/https_bothway_03.png)
![](https://raw.githubusercontent.com/CreditTone/img_resources/main/https_bothway_04.png)
***

### edit_text.js

用于跟踪获取 EditText 的 `getText()` 事件,并获取其真实 Class 类型。  
EditText 通常绑定搜索按钮或输入事件,是定位“搜索”接口实现代码的有效入口,辅助识别核心业务逻辑。

推荐命令:frida edit_text.js

![](https://raw.githubusercontent.com/CreditTone/img_resources/main/edit_text.png)
***

### hook_register_natives.js
用于拦截 Android JNI RegisterNatives 函数的 Frida 脚本,主要用于分析和修改原生方法(native methods)的注册过程

推荐命令:spawn hook_register_natives.js

![](https://raw.githubusercontent.com/CreditTone/img_resources/main/hook_RN.gif)
***

### text_view.js

用于跟踪 TextView 的 `setText()` 和 `getText()` 调用,并输出其真实 Class。  
常用于提取页面展示的明文数据,以及获取堆栈调用信息,从而追踪业务层的 model 构造逻辑和数据源。

推荐命令:spawn/attach text_view.js  
![](https://raw.githubusercontent.com/CreditTone/img_resources/main/text_view.png)

---


### activity_events.js

用于跟踪 Android Activity 生命周期(如 `onCreate`、`onResume`)的 Frida 脚本,帮助分析 Activity 初始化逻辑。

推荐命令:spawn/attach activity_events.js

---


### ssl_log.js

用于在 native 层跟踪 SSL 握手过程并记录 `CLIENT_RANDOM`,配合 tcpdump 抓包数据后可用于 TLS 明文还原分析。

推荐命令:spawn/attach ssl_log.js

---

### just_trust_me_for_ios.js

iOS 版的证书校验绕过脚本,配合抓包代理使用,适用于 SSL Pinning 场景。

推荐命令:spawn/attach just_trust_me_for_ios.js

---

### dump_dex.js

执行 spawn dump_dex.js 可直接脱壳,适用于多数简单壳场景。  
Android ART 使用 dex2oat 编译 DEX 为 native 指令,有些脱壳失败时建议手动清除 `/data/app/<package>-*/oat/arm64/`。

推荐命令:spawn dump_dex.js

---

### trace_init_proc.js

用于 trace `init_proc` 函数调用流程的脚本。  
需手动指定 `startAddr`、`endAddr` 和模块名 `somodule`,适合分析 native 启动流程。

推荐命令:spawn/attach trace_init_proc.js  
![trace_init_proc.png](https://raw.githubusercontent.com/CreditTone/img_resources/main/trace_init_proc.png)

---

### hook_artmethod_register.js

用于拦截 Android ART 虚拟机中 `ArtMethod` 的注册函数,适合深入分析虚拟机行为与 native 方法绑定。

推荐命令:spawn/attach hook_artmethod_register.js

---

### find_anit_frida_so.js

用于发现 app 中加载的可疑 anti-frida 动态库,识别顺序为:**谁最后加载、谁让 app 崩溃,谁就是反调试的嫌疑人**。

推荐命令:spawn/attach find_anit_frida_so.js  
![find_anti_frida_so.png](https://raw.githubusercontent.com/CreditTone/img_resources/main/find_anti_frida_so.png)

---

### hook_jni_method_trace.js

用于追踪 Native 层回调 Java 方法的行为,可观察 so 层与 Java 的交互,帮助分析 JNI 层调用栈。

推荐命令:spawn/attach hook_jni_method_trace.js

---

### replace_dlsym_get_pthread_create.js

专门对抗 `libmsaoaidsec.so` 中使用 `dlsym` 获取 `pthread_create` 的反调试方式。  
本脚本 hook `dlsym`,用于劫持线程创建行为,从而对抗动态加载的反调试代码。

推荐命令:spawn/attach replace_dlsym_get_pthread_create.js  
![replace_pthread_create.png](https://raw.githubusercontent.com/CreditTone/img_resources/main/replace_pthread_create.png)

---

### find_boringssl_custom_verify_func.js

用于查找 `boringssl` 中注册的自定义证书验证函数,通过 hook `SSL_CTX_set_custom_verify` 定位目标函数,并实现强制信任(返回 0)。

**使用建议:**  
执行前请清除目标 app(如某音)的缓存,以保证函数重新注册。

推荐命令:spawn find_boringssl_custom_verify_func.js  
![find_boringssl_custom_verify.png](https://raw.githubusercontent.com/CreditTone/img_resources/main/find_boringssl_custom_verify.png)  
![hook_verify.png](https://raw.githubusercontent.com/CreditTone/img_resources/main/hook_verify.png)  
![mouyin_capture_33.9.0.png](https://raw.githubusercontent.com/CreditTone/img_resources/main/mouyin_capture_33.9.0.png)

---

### get_device_info.js

用于全面获取设备指纹信息,包括:

- Android ID、IMEI、指纹、厂商、系统信息
- 安装的所有应用(含系统 app)
- 传感器信息(如名称、厂商、延迟)
- 系统状态(是否 root、运行时、内核信息等)

推荐命令:attach get_device_info.js  
![](https://raw.githubusercontent.com/CreditTone/img_resources/main/get_device_info_attach.png)

提供以下 4 个调用方法:

- `getBasicInfo()`:基础信息
- `getInstalledPackages()`:应用列表
- `getSensos()`:传感器信息
- `getSystemInfo()`:系统与运行环境  
  ![](https://raw.githubusercontent.com/CreditTone/img_resources/main/get_device_info_functions.png)

---

### apk_shell_scanner.js

动态识别 APK 加壳技术的脚本,支持多种主流壳,如娜迦、爱加密、360、梆梆、腾讯御、网易易盾等。

推荐命令:attach apk_shell_scanner.js

识别结果示例:
- This app is protected by {爱加密}
- This app is not protected or uses an unknown protection scheme

---

### bypass_frida_svc_detect.js

绕过 app 对 Frida Server 的检测逻辑,适用于反调试保护较强的目标应用。

推荐命令:spawn/attach bypass_frida_svc_detect.js

---

### bypass_root_detect.js

用于绕过 root 检测逻辑,使得 root 环境下也能正常运行 app。

推荐命令:spawn/attach bypass_root_detect.js

---

### bypass_vpn_detect.js

用于绕过 VPN 检测逻辑,避免 app 阻止使用代理、VPN 或抓包工具。

推荐命令:spawn/attach bypass_vpn_detect.js

---

### hook_encryption_algo.js

用于 hook 典型加密算法(如 AES、RSA、HMAC 等)的实现函数,分析加密参数、明文与密文数据。

推荐命令:spawn/attach hook_encryption_algo.js

---

### hook_encryption_algo2.js

拓展版本的加密算法 hook 脚本,适用于更复杂的加密场景或多路加密调用链。

推荐命令:spawn/attach hook_encryption_algo2.js

### webview_enable_debug.js

该脚本用于 Hook Android 应用中的 WebView 行为,特别是在 WebView 初始化和执行 JavaScript 时进行监控和调试。适用于分析 WebView 加载的页面、动态注入的 JavaScript 代码,以及定位 JS 调用位置,常用于逆向分析、抓取数据或识别加固方案中的 Web 容器。

推荐命令:spawn webview_enable_debug.js

<img src="https://raw.githubusercontent.com/CreditTone/img_resources/main/webview_debugging.png" width="1000">

# Windows安装WSL

WSL是适用于Linux 的Windows 子系统(WSL)允许开发人员直接在Windows 上运行GNU/Linux 环境(包括大多数命令行工具、实用工具和应用程序),无需传统虚拟机或双启动设置的开销。

### 1. 安装wsl ubuntn24.04
访问:https://learn.microsoft.com/zh-cn/windows/wsl/install-manual#downloading-distributions

下载Ubuntu24.04

双击Ubuntu2404-240425.AppxBundle 安装Ubuntu


### 2. 进入wsl,配置代理
窗口输入wsl进入ubuntu命令行

- cmd

- wsl

切换到root用户

- sudo su

配置翻墙代理,如果你有

- export http_proxy="http://10.115.164.50:8080"
- export https_proxy="http://10.115.164.50:8080"

### 3. 安装python3.8和frida
- apt update
- apt install -y build-essential libssl-dev zlib1g-dev libbz2-dev 
libreadline-dev libsqlite3-dev wget curl llvm xz-utils tk-dev 
libffi-dev liblzma-dev
- apt install -y git
- apt install -y pyenv
- pyenv install 3.8.8
- pyenv local 3.8


# 自定义frida-server
- 将您自定义的frida-server文件拷贝到mobile-deploy文件夹下
- 修改hooker.py,default_frida_server_arm和default_frida_server_arm64变量的名字为你自定义的文件名

```python
default_frida_server_arm = "your-custom-frida-server-android-arm"
default_frida_server_arm64 = "your-custom-frida-server-android-arm64"
```

# hooker命令行快捷键

- Ctrl + U:整行清空

- Ctrl + W:删除一个单词

- Ctrl + K:从光标删到行尾


================================================
FILE: README_EN.md
================================================
⚠️ Disclaimer
All contents of this project are intended solely for learning and technical exchange purposes. The goal is to help developers understand the structure and internal mechanisms of mobile applications.

This project does not contain any cracking operations or infringing content targeting specific applications.
Some simple unpacking techniques related to certain apps are widely available on major technical forums and do not cause actual harm to the applications.
This project is not intended to assist with any illegal activities, including but not limited to bypassing copyright protection, modifying app functionality, or accessing unauthorized data.
Please ensure compliance with relevant laws and regulations when using the tools provided by this project, and use them only for personal learning or research purposes.

<p>English | <a href="README.md">简体中文</a></p>

# 👋 Welcome to Hooker Reverse Engineering Toolkit
![GitHub stars](https://img.shields.io/github/stars/CreditTone/hooker?style=flat-square)
![GitHub forks](https://img.shields.io/github/forks/CreditTone/hooker?style=flat-square)
![GitHub code size](https://img.shields.io/github/languages/code-size/CreditTone/hooker?style=flat-square)
![Python](https://img.shields.io/badge/python-3.8.8-blue?style=flat-square)
![frida](https://img.shields.io/badge/frida-16.7.19-blue?style=flat-square)

Hooker is a reverse engineering toolkit based on Frida, designed to provide Android reverse engineers with a comfortable command-line interface,

A unified script package management system

Universal (通杀) scripts

Automated hook script generation

In-memory roaming for detecting Activity and Service components

A Frida-based implementation of JustTrustMe

Global app support for boringssl unpinning

One picture to prove why you need Hooker:
![gs_show.jpg](https://raw.githubusercontent.com/CreditTone/img_resources/main/gs_show.jpg)

- 1. A Frida-based implementation of JustTrustMe for universal SSL pinning bypass, with ongoing maintenance.
- 2. An embedded webserver that can quickly expose in-app capabilities as HTTP endpoints for automation and API-style workflows.
- 3. Automated Frida hook script generation with detailed, extensible templates.
- 4. One-click SOCKS5 proxy setup without requiring third-party apps such as SocksDroid.
- 5. A highly streamlined command-line workflow that makes daily reversing much more comfortable.

## 📚 Table of Contents

- [Quick Start](#quick-start)
  - [1. Clone the repository](#1-clone-the-repository)
  - [2. Install Python dependencies](#2-install-python-dependencies)
  - [3. Connect your rooted device via USB](#3-connect-your-rooted-device-via-usb)
  - [4. Launch Hooker](#4-launch-hooker)
  - [5. Enter the package name of the target app](#5-enter-the-package-name-of-the-target-app)
  - [6. View help information](#6-view-help-information)
  - [7. Embedded webserver](#7-embedded-webserver)
  - [8. Generate Frida hook scripts for a specific class and method](#8-generate-frida-hook-scripts-for-a-specific-class-and-method)
  - [9. List all available Frida scripts](#9-list-all-available-frida-scripts)
  - [10. Attach and execute a specific Frida script](#10-attach-and-execute-a-specific-frida-script)
  - [11. Set a SOCKS5 proxy for the app](#11-set-a-socks5-proxy-for-the-app)
  - [12. Run JustTrustMe to disable all SSL pinning (including boringssl)](#12-run-justtrustme-to-disable-all-ssl-pinning-including-boringssl)
  - [13. Spawn the app and execute a specific Frida script](#13-spawn-the-app-and-execute-a-specific-frida-script)
  - [14. Clear the proxy settings](#14-clear-the-proxy-settings)
  - [15. Restart the app](#15-restart-the-app)
  - [16. Get the app's UID and PID](#16-get-uid-and-pid)


# 🚀 Quick Start

Ensure your device is rooted. No need to manually start frida-server or do any configuration—Hooker handles everything for you.


### 1. Clone the repository
```shell
stephen@ubuntu:~$ git clone https://github.com/CreditTone/hooker.git
stephen@ubuntu:~$ cd hooker
```

### 2. Install Python dependencies
```shell
stephen@ubuntu:~/hooker$ pip3 install -r requirements.txt
```


### 3. Connect your rooted device via USB
```shell
stephen@ubuntu:~/hooker$ adb devices
List of devices attached
FA77C0301476	device
```


### 4. Launch Hooker
```shell
stephen@ubuntu:~/hooker$ python3 hooker.py
hooker Let's enjoy reverse engineering together
-----------------------------------------------------------------------------------------------
PID   	APP                 	IDENTIFIER                         	EXIST_REVERSE_DIRECTORY
0     	全球上网            	com.miui.virtualsim                	❌
0     	爱奇艺              	com.qiyi.video                     	❌
0     	红手指云手机        	com.redfinger.app                  	❌
0     	Reqable             	com.reqable.android                	❌
0     	美团                	com.sankuai.meituan                	✅
0     	得物                	com.shizhuang.duapp                	❌
0     	某皮           	     cxm.shxpxx.sg                      	✅
0     	微博                	com.sina.weibo                     	❌
0     	今日头条            	com.ss.android.article.news        	✅
0     	西瓜视频            	com.ss.android.article.video       	✅
0     	懂车帝              	com.ss.android.auto                	✅
0     	抖音火山版          	com.ss.android.ugc.live            	✅
0     	抖音精选            	com.ss.android.yumme.video         	❌
0     	淘宝                	com.taobao.taobao                  	✅
0     	腾讯视频            	com.tencent.qqlive                 	❌
0     	Termux              	com.termux                         	❌
0     	轻奢                	com.tm.bachelorparty               	✅
0     	WiFi ADB            	com.ttxapps.wifiadb                	❌
0     	VMOS Pro            	com.vmos.pro                       	✅
0     	游戏中心            	com.xiaomi.gamecenter              	❌
0     	小米商城            	com.xiaomi.shop                    	❌
0     	米家                	com.xiaomi.smarthome               	❌
0     	小米有品            	com.xiaomi.youpin                  	✅
0     	小红书              	com.xingin.xhs                     	✅
0     	运满满货主          	com.xiwei.logistics.consignor      	✅
0     	拼多多              	com.xunmeng.pinduoduo              	✅
0     	EnvCheck            	com.yimian.envcheck                	✅
0     	check_env           	com.yuuki.check_env                	❌
0     	TikTok              	com.zhiliaoapp.musically           	❌
0     	XPrivacyLua         	eu.faircode.xlua                   	❌
0     	imToken             	im.token.app                       	❌
0     	SocksDroid          	net.typeblog.socks                 	❌
0     	F-Droid             	org.fdroid.fdroid                  	❌
0     	ProxyDroid          	org.proxydroid                     	❌
3457  	手机管家            	com.miui.securitycenter            	✅
3509  	优信拍              	com.uxin.buyerphone                	✅
18780 	抖音                	com.ss.android.ugc.aweme           	✅
20174 	应用商店            	com.xiaomi.market                  	❌
20913 	设置                	com.android.settings               	❌
30500 	小爱同学            	com.miui.voiceassist               	❌
32163 	相机                	com.android.camera                 	✅
Please enter the identifier that needs to be reversed
hooker(Identifier):
```
***

### 5. Enter the package name of the target app
```shell
hooker(Identifier): cxm.shxpxx.sg
✅ App cxm.shxpxx.sg is already in the foreground
Creating working directory: cxm.shxpxx.sg
Generating frida shortcut command...
Generating built-in frida script...
pull /data/app/cxm.shxpxx.sg-L8zkrpFVICv0-hOrtmPPxA==/base.apk to cxm.shxpxx.sg/ShopeeSG_3.43.40.apk successful
Working directory create successful
just_trust_me.js                                 empty.js                                         keystore_dump.js
edit_text.js                                     activity_events.js                               find_boringssl_custom_verify_func.js
ssl_log.js                                       hook_register_natives.js                         click.js
get_device_info.js                               apk_shell_scanner.js                             dump_dex.js
object_store.js                                  hook_artmethod_register.js                       replace_dlsym_get_pthread_create.js
just_trust_me_for_ios.js                         trace_initproc.js                                android_ui.js
hook_jni_method_trace.js                         url.js                                           just_trust_me_okhttp_hook_finder_for_android.js
text_view.js                                     find_anit_frida_so.js
某皮 > 
```
![hooker_enter_debug.gif](https://raw.githubusercontent.com/CreditTone/img_resources/main/hooker_enter_debug.gif)
***




### 6. View help information

```shell
某皮 > help
h, help                                      show this help message
a, activitys                                 show the activity stack
s, services                                  show the service stack
o, object [object_id]                        show object info by object_id
v, view [view_id]                            show view info by view_id of view
gs, generatescript [class_name:method_name]  specify the class name and method name to generate a frida hook java script file. For example: generatescript
                                             okhttp3.Request$Builder:addHeader
p, proxy [socks5_proxy_server]               set up a socks5 proxy for this app. For example: proxy socks5://192.168.0.100:9998
up, unproxy                                  remove socks5 proxy for this app
trust, justtrustme                           quickly spawn just_trust_me.js script to kill all ssl pinning
ls                                           list all the frida scripts of the current app
attach [script_file_name]                    quickly execute a frida script, similar to executing the command "frida -U com.example.app -l xxx.js". For example: attach url.js
spawn [script_file_name]                     quickly spawn a frida script, similar to executing the command "frida -U -f -n com.example.app -l xxx.js". For example: spawn
                                             just_trust_me.js
restart                                      restart this app
pid                                          get pid of this app main process
uid                                          get pid of this app
exit                                         return to the previous level
某皮 > 
```
![hooker_help.gif](https://raw.githubusercontent.com/CreditTone/img_resources/main/hooker_help.gif)
***


### 7. Embedded webserver

Hooker can inject a lightweight webserver into the target app process. Once started, it launches an HTTP service inside the app. The default port is `8080`. This service can expose both your custom patch controllers and a built-in set of debugging endpoints.

- Start the built-in webserver

```shell
某音火山版 > webserver start
Http server port: 8080
Http server: http://10.112.101.249:8080
```

After the built-in webserver starts, open the root page in a browser to see all registered APIs. Common built-in capabilities include:

- Service management: `/` shows the welcome page and API list, and `/stop` stops the current webserver.
- UI automation: `/hooker/ui/...` provides view clicking, text-based clicking, coordinate tapping, setting EditText values, triggering Back/Home, launching activities, querying screen info, swiping pagers, scrolling RecyclerView, and dismissing dialogs.
- UI hierarchy export: `/hooker/uiauto/dump`, `/hooker/uiauto/window_dump.xml`, and `/hooker/uiauto/window_dump.json` export the current window hierarchy for inspection and control discovery.
- Screenshot capture: `/hooker/screencap/screenshot` uses the system `screencap` command, while `/hooker/mediaprojection/...` supports MediaProjection permission flow and full-screen PNG capture.
- App information: `/hooker/appinfo`, `/hooker/appinfo/shared_prefs`, `/hooker/appinfo/databases`, and `/hooker/appinfo/read_table` let you inspect package metadata, permissions, signatures, shared preferences, database schemas, and table rows.
- Class and object helpers: `/hooker/classhelper/invoke_static_method` and `/hooker/classhelper/invoke_method` let you invoke static methods or stored object methods over HTTP.
- File serving: `/file?filename=...` returns an absolute-path file or a file generated in the webserver cache directory.
- MCP-style UI tools: `/hooker/mcp/ui/tools` and `/hooker/mcp/ui/call` package common UI actions into a consistent tool interface for external scripts or agents.

- Start a custom webserver

```shell
某宝 > webserver start taxbax-patch.jar
Converting taxbax-patch.jar to taxbax-patch.dex...
Successfully converted to taxbax-patch.dex (7160 bytes)
push file OK /data/user/0/com.taxbax.taxbax/hooker_server.dex
Http server port: 2026
Http server: http://10.112.101.249:2026
```

Here `taxbax-patch.jar` is essentially a business plugin running inside the target app process. Hooker converts the jar to dex, injects it into the app, scans annotated classes, and registers them as HTTP routes.

Custom webservers are useful for:

- Exposing app-internal business capabilities such as search, comments, product details, signatures, encryption/decryption, user profiles, or live-stream APIs.
- Reusing the target app's own login state, networking stack, environment values, and in-memory objects instead of rebuilding protocol details externally.
- Wrapping asynchronous callbacks, observables, listeners, or page-object calls into synchronous HTTP endpoints that return JSON or plain text.

Patch projects typically define endpoints like this:

- Use `@HookerWebServerConfiguration(port = 2026)` to specify the port. If omitted, the default is `8080`.
- Use `@HookerController("/taobao")` or `@HookerController("/douyin")` to define the route prefix.
- Use `@HookerRequestMapping(path = "/getProductDetail")` to expose concrete endpoints.
- Use `@HookerRequestParam` and `@HookerRequestPostJson` to receive query parameters and POST JSON.

This is also the recommended way to build mobile-facing APIs with Hooker: keep generic debugging features in the main repository, keep app-specific business logic in patch projects, and expose those capabilities through the embedded webserver.

- Webserver persistence

If you already have a patch project for an app and need to deploy it at scale, the temporary `frida + hooker` injection model can become heavy. For this scenario, the author provides an Xposed plugin named `HookerServer`:

`https://github.com/CreditTone/HookerServer`

If the device supports Xposed/LSPosed, you can use this plugin to persist the webserver inside the target app.

Steps:

1. Download the latest APK from `https://github.com/CreditTone/HookerServer/releases`.
2. Push `patch.dex` to `/data/user/0/{package}/hooker_server.dex`.
3. Enable `HookerServer` for the target app in Xposed/LSPosed.
4. Restart the app, and the webserver will start automatically.

The `patch.dex` file becomes available after the first time you deploy `patch.jar`; Hooker automatically converts the jar to dex and places the output in the target app's working directory.
***


### 8. Generate Frida hook scripts for a specific class and method

![gs_show.jpg](https://raw.githubusercontent.com/CreditTone/img_resources/main/gs_show.jpg)

- Command Syntax:gs, generatescript [class_name:method_name]


- 8.1 Generate a Frida hook script for a specific method:
gs okhttp3.Request$Builder:addHeader — the parameter part (String, String) is not required.

```shell
某信拍 > gs okhttp3.Request$Builder:addHeader(String, String)
Generating frida script, please wait for a few seconds
frida hook script: okhttp3.Request.Builder.addHeader.js
某信拍 > 
```

```js
//cat okhttp3.Request.Builder.addHeader.js
Java.perform(function() {
    var okhttp3_Request_Builder_clz = Java.use('okhttp3.Request$Builder');
    var okhttp3_Request_Builder_clz_method_addHeader_2grl = okhttp3_Request_Builder_clz.addHeader.overload('java.lang.String', 'java.lang.String');
    okhttp3_Request_Builder_clz_method_addHeader_2grl.implementation = function(string, string_x2) {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.addHeader(java.lang.String,java.lang.String)';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_addHeader_2grl.call(this, string, string_x2);
        console.log("header name:" + string + " header value:" + string_x2);
        printBeat(beat);
        return ret;
    };
});
```
***



- 8.2 Generate a Frida hook script for all member methods of a specified class:
gs okhttp3.Request$Builder

```shell
某信拍 > generatescript okhttp3.Request$Builder
Generating frida script, please wait for a few seconds
frida hook script: okhttp3.Request.Builder.allfunc.js
```
***

```js
//cat okhttp3.Request.Builder.allfunc.js
//okhttp3.Request$Builder
Java.perform(function() {
    var okhttp3_Request_Builder_clz = Java.use('okhttp3.Request$Builder');
    var okhttp3_Request_Builder_clz_method_header_ng3n = okhttp3_Request_Builder_clz.header.overload('java.lang.String', 'java.lang.String');
    okhttp3_Request_Builder_clz_method_header_ng3n.implementation = function(string, string_x2) {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.header(java.lang.String,java.lang.String)';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_header_ng3n.call(this, string, string_x2);
        printBeat(beat);
        return ret;
    };
    var okhttp3_Request_Builder_clz_method_cacheControl_q8q5 = okhttp3_Request_Builder_clz.cacheControl.overload('okhttp3.CacheControl');
    okhttp3_Request_Builder_clz_method_cacheControl_q8q5.implementation = function(cacheControl) {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.cacheControl(okhttp3.CacheControl)';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_cacheControl_q8q5.call(this, cacheControl);
        printBeat(beat);
        return ret;
    };
    var okhttp3_Request_Builder_clz_method_method_bjk9 = okhttp3_Request_Builder_clz.method.overload('java.lang.String', 'okhttp3.RequestBody');
    okhttp3_Request_Builder_clz_method_method_bjk9.implementation = function(string, requestBody) {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.method(java.lang.String,okhttp3.RequestBody)';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_method_bjk9.call(this, string, requestBody);
        printBeat(beat);
        return ret;
    };
    var okhttp3_Request_Builder_clz_method_head_a5nq = okhttp3_Request_Builder_clz.head.overload();
    okhttp3_Request_Builder_clz_method_head_a5nq.implementation = function() {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.head()';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_head_a5nq.call(this);
        printBeat(beat);
        return ret;
    };
    var okhttp3_Request_Builder_clz_method_headers_to5i = okhttp3_Request_Builder_clz.headers.overload('okhttp3.Headers');
    okhttp3_Request_Builder_clz_method_headers_to5i.implementation = function(headers) {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.headers(okhttp3.Headers)';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_headers_to5i.call(this, headers);
        printBeat(beat);
        return ret;
    };
    var okhttp3_Request_Builder_clz_method_post_heaq = okhttp3_Request_Builder_clz.post.overload('okhttp3.RequestBody');
    okhttp3_Request_Builder_clz_method_post_heaq.implementation = function(requestBody) {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.post(okhttp3.RequestBody)';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_post_heaq.call(this, requestBody);
        printBeat(beat);
        return ret;
    };
    var okhttp3_Request_Builder_clz_method_build_rmqx = okhttp3_Request_Builder_clz.build.overload();
    okhttp3_Request_Builder_clz_method_build_rmqx.implementation = function() {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request okhttp3.Request$Builder.build()';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_build_rmqx.call(this);
        printBeat(beat);
        return ret;
    };
    var okhttp3_Request_Builder_clz_method_patch_hp9u = okhttp3_Request_Builder_clz.patch.overload('okhttp3.RequestBody');
    okhttp3_Request_Builder_clz_method_patch_hp9u.implementation = function(requestBody) {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.patch(okhttp3.RequestBody)';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_patch_hp9u.call(this, requestBody);
        printBeat(beat);
        return ret;
    };
    var okhttp3_Request_Builder_clz_method_url_0owi = okhttp3_Request_Builder_clz.url.overload('java.lang.String');
    okhttp3_Request_Builder_clz_method_url_0owi.implementation = function(string) {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.url(java.lang.String)';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_url_0owi.call(this, string);
        printBeat(beat);
        return ret;
    };
    var okhttp3_Request_Builder_clz_method_removeHeader_uzb9 = okhttp3_Request_Builder_clz.removeHeader.overload('java.lang.String');
    okhttp3_Request_Builder_clz_method_removeHeader_uzb9.implementation = function(string) {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.removeHeader(java.lang.String)';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_removeHeader_uzb9.call(this, string);
        printBeat(beat);
        return ret;
    };
    var okhttp3_Request_Builder_clz_method_url_ykbd = okhttp3_Request_Builder_clz.url.overload('java.net.URL');
    okhttp3_Request_Builder_clz_method_url_ykbd.implementation = function(url) {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.url(java.net.URL)';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_url_ykbd.call(this, url);
        printBeat(beat);
        return ret;
    };
    var okhttp3_Request_Builder_clz_method_delete_dqyl = okhttp3_Request_Builder_clz.delete.overload();
    okhttp3_Request_Builder_clz_method_delete_dqyl.implementation = function() {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder okhttp3.Request$Builder.delete()';
        var beat = newMethodBeat(beatText, executor);
        var ret = okhttp3_Request_Builder_clz_method_delete_dqyl.call(this);
        printBeat(beat);
        return ret;
    };
    //.......省略N行代码
```
***



- 8.3 Generate a Frida hook script for the constructor(s) of a specified class:
gs okhttp3.Request$Builder:_ or gs okhttp3.Request$Builder:\<init\>

```shell
某信拍 > gs okhttp3.Request$Builder:<init>()
Generating frida script, please wait for a few seconds
frida hook script: okhttp3.Request.Builder._init.js
```

```js
//cat okhttp3.Request.Builder._init.js
//okhttp3.Request$Builder:<init>()
Java.perform(function() {
    var okhttp3_Request_Builder_clz = Java.use('okhttp3.Request$Builder');
    var okhttp3_Request_Builder_clz_init_uw3i = okhttp3_Request_Builder_clz.$init.overload();
    okhttp3_Request_Builder_clz_init_uw3i.implementation = function() {
        var executor = this.hashCode();
        var beatText = 'public okhttp3.Request$Builder()';
        var beat = newMethodBeat(beatText, executor);
        var returnObj = okhttp3_Request_Builder_clz_init_uw3i.call(this);
        printBeat(beat);
        return returnObj;
    };
    var okhttp3_Request_Builder_clz_init_e58t = okhttp3_Request_Builder_clz.$init.overload('okhttp3.Request');
    okhttp3_Request_Builder_clz_init_e58t.implementation = function(v0) {
        var executor = this.hashCode();
        var beatText = 'okhttp3.Request$Builder(okhttp3.Request)';
        var beat = newMethodBeat(beatText, executor);
        var returnObj = okhttp3_Request_Builder_clz_init_e58t.call(this, v0);
        printBeat(beat);
        return returnObj;
    };
});
```
***

### 9. List all available Frida scripts
```shell
某皮 > ls
just_trust_me.js                                 empty.js                                         keystore_dump.js
okhttp3.Request.Builder.addHeader.js             edit_text.js                                     activity_events.js
find_boringssl_custom_verify_func.js             ssl_log.js                                       hook_register_natives.js
click.js                                         get_device_info.js                               apk_shell_scanner.js
dump_dex.js                                      object_store.js                                  hook_artmethod_register.js
replace_dlsym_get_pthread_create.js              just_trust_me_for_ios.js                         trace_initproc.js
android_ui.js                                    hook_jni_method_trace.js                         url.js
just_trust_me_okhttp_hook_finder_for_android.js  text_view.js                                     find_anit_frida_so.js
某皮 >
```
***

### 10. Attach and execute a specific Frida script

> **Auto-logging**: When executing scripts via `attach` / `frida`, Frida `send()` output is automatically saved to a `.log` file with the same name in the app working directory. e.g., `attach url.js` generates `url.log`, making it easy to feed into AI for analysis.

```shell
某信拍 > attach url.js
Frida output logging -> com.uxin.buyerphone/url.log
------------startFlag:0755liv1,objectHash:-915348569,thread(id:810,name:Wmda.EventUploadThread),timestamp:1747836814835---------------
url:https://apiwmxx.xxx.com.cn/report/c?api_v=3&sdk_v=1.7.0.0&timestamp=1747836814832&appid=17591177894321&p=2&uuid=248056262e0030b7bb56c0f9237f846d
public java.net.URL(String)
	at java.net.URL.<init>(Native Method)
	at com.wxbx.wmda.e.b.a(SourceFile:5)
	at com.wxbx.wmda.e.b.a(SourceFile:1)
	at com.wxbx.wmda.h.a.a(SourceFile:162)
	at com.wxbx.wmda.h.a.b(SourceFile:19)
	at com.wxbx.wmda.h.a.a(SourceFile:2)
	at com.wxbx.wmda.h.a$b.handleMessage(SourceFile:3)
	at android.os.Handler.dispatchMessage(Handler.java:106)
	at android.os.Looper.loop(Looper.java:201)
	at android.os.HandlerThread.run(HandlerThread.java:65)
------------endFlag:0755liv1,usedtime:1---------------

------------startFlag:1ps6go99,objectHash:-237375819,thread(id:810,name:Wmda.EventUploadThread),timestamp:1747836815192---------------
url:https://apiwmxx.xxx.com.cn/report/c?api_v=3&sdk_v=1.7.0.0&timestamp=1747836815188&appid=17591177894321&p=2&uuid=248056262e0030b7bb56c0f9237f846d
public java.net.URL(String)
	at java.net.URL.<init>(Native Method)
	at com.android.okhttp.HttpUrl.url(HttpUrl.java:327)
	at com.android.okhttp.Request.url(Request.java:53)
	at com.android.okhttp.Request$Builder.build(Native Method)
	at com.android.okhttp.internal.huc.HttpURLConnectionImpl.newHttpEngine(HttpURLConnectionImpl.java:377)
	at com.android.okhttp.internal.huc.HttpURLConnectionImpl.initHttpEngine(HttpURLConnectionImpl.java:332)
	at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:124)
	at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getOutputStream(HttpURLConnectionImpl.java:258)
	at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getOutputStream(DelegatingHttpsURLConnection.java:218)
	at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:26)
	at com.wxbx.wmda.e.b.a(SourceFile:14)
	at com.wxbx.wmda.e.b.a(SourceFile:1)
	at com.wxbx.wmda.h.a.a(SourceFile:162)
	at com.wxbx.wmda.h.a.b(SourceFile:19)
	at com.wxbx.wmda.h.a.a(SourceFile:2)
	at com.wxbx.wmda.h.a$b.handleMessage(SourceFile:3)
	at android.os.Handler.dispatchMessage(Handler.java:106)
	at android.os.Looper.loop(Looper.java:201)
	at android.os.HandlerThread.run(HandlerThread.java:65)
------------endFlag:1ps6go99,usedtime:0---------------
// 这里省略无数日志.............
------------startFlag:i7osxvjl,objectHash:134280600,thread(id:810,name:Wmda.EventUploadThread),timestamp:1747836815193---------------
url:https://apiwmxx.xxx.com.cn/report/c?api_v=3&sdk_v=1.7.0.0&timestamp=1747836815188&appid=17591177894321&p=2&uuid=248056262e0030b7bb56c0f9237f846d
com.android.okhttp.Request.Builder.build()
	at com.android.okhttp.Request$Builder.build(Native Method)
	at com.android.okhttp.internal.huc.HttpURLConnectionImpl.newHttpEngine(HttpURLConnectionImpl.java:377)
	at com.android.okhttp.internal.huc.HttpURLConnectionImpl.initHttpEngine(HttpURLConnectionImpl.java:332)
	at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:124)
	at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getOutputStream(HttpURLConnectionImpl.java:258)
	at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getOutputStream(DelegatingHttpsURLConnection.java:218)
	at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:26)
	at com.wxbx.wmda.e.b.a(SourceFile:14)
	at com.wxbx.wmda.e.b.a(SourceFile:1)
	at com.wxbx.wmda.h.a.a(SourceFile:162)
	at com.wxbx.wmda.h.a.b(SourceFile:19)
	at com.wxbx.wmda.h.a.a(SourceFile:2)
	at com.wxbx.wmda.h.a$b.handleMessage(SourceFile:3)
	at android.os.Handler.dispatchMessage(Handler.java:106)
	at android.os.Looper.loop(Looper.java:201)
	at android.os.HandlerThread.run(HandlerThread.java:65)
------------endFlag:i7osxvjl,usedtime:1---------------
// 这里省略无数日志.............
```
***

### 11. Set a SOCKS5 proxy for the app
```shell
某音 > proxy socks5://10.112.99.11:9998
proxy socks5://10.112.99.11:9998 OK
某音 > 
```
***


### 12. Run JustTrustMe to disable all SSL pinning (including boringssl)

```shell
某音 > justtrustme
Package name: com.ss.xxxx.xxx.aweme
android.security.net.config.NetworkSecurityTrustManager.checkPins('java.util.List') was hooked!
android.security.net.config.NetworkSecurityTrustManager.checkPins('java.util.List') was hooked!
android.security.net.config.NetworkSecurityTrustManager.checkPins('java.util.List') was hooked!
android.security.net.config.NetworkSecurityTrustManager.checkPins('java.util.List') was hooked!
javax.net.ssl.TrustManagerFactory.getTrustManagers() was hooked!
javax.net.ssl.SSLContext.init('[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom') was hooked!
javax.net.ssl.TrustManagerFactory.getTrustManagers() was hooked!
javax.net.ssl.SSLContext.init('[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom') was hooked!
javax.net.ssl.TrustManagerFactory.getTrustManagers() was hooked!
javax.net.ssl.SSLContext.init('[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom') was hooked!
javax.net.ssl.SSLContext.init('[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom') was hooked!
static void com.android.org.conscrypt.Platform.checkServerTrusted(javax.net.ssl.X509TrustManager,java.security.cert.X509Certificate[],java.lang.String,com.android.org.conscrypt.AbstractConscryptSocket) throws java.security.cert.CertificateException was hooked!
static void com.android.org.conscrypt.Platform.checkServerTrusted(javax.net.ssl.X509TrustManager,java.security.cert.X509Certificate[],java.lang.String,com.android.org.conscrypt.AbstractConscryptSocket) throws java.security.cert.CertificateException was hooked!
static void com.android.org.conscrypt.Platform.checkServerTrusted(javax.net.ssl.X509TrustManager,java.security.cert.X509Certificate[],java.lang.String,com.android.org.conscrypt.AbstractConscryptSocket) throws java.security.cert.CertificateException was hooked!
static void com.android.org.conscrypt.Platform.checkServerTrusted(javax.net.ssl.X509TrustManager,java.security.cert.X509Certificate[],java.lang.String,com.android.org.conscrypt.AbstractConscryptSocket) throws java.security.cert.CertificateException was hooked!
okhttp3.internal.tls.OkHostnameVerifier.verify('java.lang.String', 'javax.net.ssl.SSLSession') was hooked!
okhttp3.CertificatePinner.check('java.lang.String', 'java.util.List') was hooked!
static void com.android.org.conscrypt.Platform.checkServerTrusted(javax.net.ssl.X509TrustManager,java.security.cert.X509Certificate[],java.lang.String,com.android.org.conscrypt.AbstractConscryptSocket) throws java.security.cert.CertificateException was hooked!
static void com.android.org.conscrypt.Platform.checkServerTrusted(javax.net.ssl.X509TrustManager,java.security.cert.X509Certificate[],java.lang.String,com.android.org.conscrypt.AbstractConscryptSocket) throws java.security.cert.CertificateException was hooked!
okhttp3.internal.tls.OkHostnameVerifier.verify('java.lang.String', 'javax.net.ssl.SSLSession') was hooked!
okhttp3.CertificatePinner.check('java.lang.String', 'java.util.List') was hooked!
okhttp3.internal.tls.OkHostnameVerifier.verify('java.lang.String', 'javax.net.ssl.SSLSession') was hooked!
okhttp3.internal.tls.OkHostnameVerifier.verify('java.lang.String', 'javax.net.ssl.SSLSession') was hooked!
okhttp3.internal.tls.OkHostnameVerifier.verify('java.lang.String', 'javax.net.ssl.SSLSession') was hooked!
okhttp3.CertificatePinner.check('java.lang.String', 'java.util.List') was hooked!
okhttp3.CertificatePinner.check('java.lang.String', 'java.util.List') was hooked!
okhttp3.CertificatePinner.check('java.lang.String', 'java.util.List') was hooked!
static void com.android.org.conscrypt.Platform.checkServerTrusted(javax.net.ssl.X509TrustManager,java.security.cert.X509Certificate[],java.lang.String,com.android.org.conscrypt.AbstractConscryptSocket) throws java.security.cert.CertificateException was hooked!
okhttp3.internal.tls.OkHostnameVerifier.verify('java.lang.String', 'javax.net.ssl.SSLSession') was hooked!
okhttp3.CertificatePinner.check('java.lang.String', 'java.util.List') was hooked!
okhttp3.CertificatePinner.check('java.lang.String', 'java.util.List') was hooked!
okhttp3.CertificatePinner.check('java.lang.String', 'java.util.List') was hooked!
// 这里省略无数日志.............
```
***


### 13. Spawn the app and execute a specific Frida script

> **Auto-logging**: `spawn` / `fridaf` also auto-saves Frida `send()` output to a `.log` file with the same name. e.g., `spawn just_trust_me.js` generates `just_trust_me.log`.

```shell
某信拍 > spawn just_trust_me.js
Frida output logging -> com.uxin.buyerphone/just_trust_me.log
Package name: com.xxx.buyxxphone
javax.net.ssl.SSLContext.init('[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom') was hooked!
javax.net.ssl.TrustManagerFactory.getTrustManagers() was hooked!
javax.net.ssl.SSLContext.init('[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom') was hooked!
// 这里省略无数日志.............
```
***


### 14. Clear the proxy settings
```shell
某音 > unproxy
unproxy OK
某音 > 
```
***



### 15. Restart the app

```shell
某信拍 > restart
restarts com.xxx.buyxxphone
```
***


### 16. Get UID and PID
```shell
某信拍 > uid
10189
某信拍 > pid
3509
```
***


================================================
FILE: hooker.py
================================================
#!/usr/bin/env python3

'''
Created on 2020年3月23日

@author: stephen
'''

default_frida_server_arm = "frida-server-16.7.19-android-arm"
default_frida_server_arm64 = "frida-server-16.7.19-android-arm64"


import frida, sys
import os
import io
import re
import stat
import time
import json
import getopt
import traceback
import base64
import time
import platform
import threading
import adbutils
import hashlib
import shutil
import textwrap
import zipfile
import queue
import sqlite3
import itertools
import jsbeautifier
import logging
import subprocess
import filecmp
import argparse
import pprint
import random
import signal
import socket
import struct
import binascii
from git import Repo
from pathlib import Path
from loguru import logger
from datetime import datetime
from collections import Counter
from dataclasses import dataclass
from androguard.core import androconf
from androguard.core.bytecodes import apk
from androguard.core.bytecodes import dvm
from androguard.core.analysis.analysis import MethodAnalysis

from typing import Optional, Tuple, List, Dict
from adbutils.errors import AdbError
from prompt_toolkit import PromptSession
from prompt_toolkit.completion import Completer, Completion
from prompt_toolkit.completion import WordCompleter
from prompt_toolkit.completion import NestedCompleter
from prompt_toolkit.patch_stdout import patch_stdout
from wcwidth import wcswidth


def find_android_home() -> Optional[str]:
    """
    在 macOS/Linux 上查找 Android SDK 路径

    Returns:
        Android SDK 路径,如果未找到则返回 None
    """
    # 1. 首先检查环境变量
    for env_var in ['ANDROID_HOME', 'ANDROID_SDK_ROOT']:
        sdk_path = os.environ.get(env_var)
        if sdk_path and os.path.isdir(sdk_path):
            return os.path.abspath(sdk_path)

    # 2. 检查操作系统特定的默认安装路径
    if sys.platform == 'darwin':  # macOS
        default_paths = [
            os.path.join(os.path.expanduser('~'), 'Library', 'Android', 'sdk'),
            '/usr/local/share/android-sdk',
            '/opt/homebrew/share/android-sdk',
            '/opt/android-sdk',
            '/usr/local/opt/android-sdk',
        ]
    else:  # Linux
        default_paths = [
            os.path.join(os.path.expanduser('~'), 'Android', 'Sdk'),
            '/usr/lib/android-sdk',
            '/opt/android-sdk',
            '/usr/local/android-sdk',
            '/opt/local/android-sdk',
        ]

    # 检查默认路径
    for path in default_paths:
        if path and os.path.isdir(path):
            # 验证是否是有效的 Android SDK 目录
            if os.path.exists(os.path.join(path, 'build-tools')):
                return os.path.abspath(path)

    return None


def get_oldest_dx_d8_path(android_home: Optional[str] = None, min_version: str = "30.0.0") -> Tuple[
    Optional[str], Optional[str]]:
    """
    查找最老的 dx 和最老的 d8 编译器路径,但版本号不低于 min_version

    Args:
        android_home: Android SDK 路径,如果为 None 则自动查找
        min_version: 最小版本号,默认 30.0.0

    Returns:
        (最老的d8文件路径, 最老的dx文件路径),如果未找到则返回 (None, None)
    """
    # 1. 获取 Android SDK 路径
    if android_home is None:
        android_home = find_android_home()

    if not android_home:
        return None, None

    # 2. 检查 build-tools 目录
    build_tools_dir = os.path.join(android_home, 'build-tools')
    if not os.path.isdir(build_tools_dir):
        return None, None

    # 3. 解析最小版本号
    def parse_version(version_str: str) -> List[int]:
        """将版本号字符串转换为整数列表便于比较"""
        parts = version_str.split('.')
        result = []
        for part in parts:
            try:
                result.append(int(part))
            except ValueError:
                # 如果包含非数字部分,只取数字部分
                num_part = re.search(r'^\d+', part)
                if num_part:
                    result.append(int(num_part.group()))
                else:
                    result.append(0)
        # 补齐到3位
        while len(result) < 3:
            result.append(0)
        return result

    min_version_parts = parse_version(min_version)

    # 4. 遍历所有 build-tools 版本,分别收集满足条件的 dx 和 d8 文件
    d8_files = []
    dx_files = []

    # 获取所有版本目录
    for item in os.listdir(build_tools_dir):
        version_dir = os.path.join(build_tools_dir, item)
        if not os.path.isdir(version_dir):
            continue

        # 检查是否是有效的版本号格式(如 34.0.0)
        if not re.match(r'^\d+(\.\d+)*$', item):
            continue

        # 检查版本号是否 >= 30.0.0
        version_parts = parse_version(item)

        # 版本比较函数
        def version_gte(v1, v2):
            """比较版本 v1 是否大于等于 v2"""
            for part1, part2 in zip(v1, v2):
                if part1 > part2:
                    return True
                elif part1 < part2:
                    return False
            return True  # 完全相等

        # 如果版本号小于最小版本,跳过
        if not version_gte(version_parts, min_version_parts):
            continue

        # 查找 dx 和 d8 文件
        dx_path = os.path.join(version_dir, 'dx')
        d8_path = os.path.join(version_dir, 'd8')

        # 分别收集 d8 和 dx 文件
        if os.path.isfile(d8_path) and os.access(d8_path, os.X_OK):
            d8_files.append((item, d8_path))

        if os.path.isfile(dx_path) and os.access(dx_path, os.X_OK):
            dx_files.append((item, dx_path))

    # 5. 获取最老的 d8 和 dx(在>=30.0.0的版本中)
    oldest_d8_path = None
    oldest_dx_path = None

    if d8_files:
        # 按版本号升序排列(最老的在前)
        d8_files.sort(key=lambda x: parse_version(x[0]))
        oldest_d8_path = d8_files[0][1]

    if dx_files:
        # 按版本号升序排列(最老的在前)
        dx_files.sort(key=lambda x: parse_version(x[0]))
        oldest_dx_path = dx_files[0][1]

    return oldest_d8_path, oldest_dx_path


def withColor(string, fg, bg=49):
    print("\33[0m\33[%d;%dm%s\33[0m" % (fg, bg, string))
#front color
Red = 1
Green = 2
Yellow = 3
Blue = 4
Magenta = 5
Cyan = 6
White = 7

def red(string):
    return withColor(string, Red+30) # Red
def green(string):
    return withColor(string, Green+30) # Green
def yellow(string):
    return withColor(string, Yellow+30) # Yellow
def blue(string):
    return withColor(string, Blue+30) # Blue
def magenta(string):
    return withColor(string, Magenta+30) # Magenta
def cyan(string):
    return withColor(string, Cyan+30) # Cyan
def white(string):
    return withColor(string, White+30) # White
#日志打印颜色定义
warn = red
info = yellow

def print_js_file(filenames :list):
    if not filenames:
        return
    GREEN = "\033[32m"
    RESET = "\033[0m"
    columns, _ = shutil.get_terminal_size()
    # 计算每个字段宽度
    max_len = max(len(name) for name in filenames) + 2
    items_per_line = max(1, columns // max_len)
    # 输出带颜色的文件名
    for i in range(0, len(filenames), items_per_line):
        line = "".join(f"{GREEN}{name.ljust(max_len)}{RESET}" for name in filenames[i:i + items_per_line])
        print(line)

def read_js_resource(filename):
    return io.open('./js/' + filename,'r',encoding= 'utf8').read()
        
cmd_session = None


adb_device = None

def _init_adb_device():
    global adb_device
    adb_device = adbutils.adb.device()

_init_adb_device()

def _shell(cmd, stream=False):
    return adb_device.shell(cmd, stream=stream)

def run_su_command(cmd, not_read=False):
    try:
        if not_read:
            _shell(f"{cmd} > /dev/null 2>&1 &")
            time.sleep(1)
            return
        output = _shell(cmd).strip()
        if output:
            return output
    except Exception:
        pass
    conn = _shell(["su", "-c", cmd], stream=True)
    try:
        if not_read:
            time.sleep(1)
            return
        output = conn.read_until_close()
        return output.strip()
    finally:
        try:
            conn.close()
        except Exception as e:
            pass


def get_is_magisk_root() -> bool:
    out = run_su_command("su -c id")
    if "uid=0" not in out:
        return False
    # 有些设备不一定带 context 字段,但你这台带了,带了就几乎 100% 是 Magisk
    if "context=u:r:magisk:s0" in out:
        return True
    # 没有 context 也可能是 root(或别的 su),再加个 Magisk 文件特征判断更保险
    magisk_marker = run_su_command("ls /data/adb/magisk.db 2>/dev/null")
    return bool(magisk_marker)

is_magisk_root = get_is_magisk_root()


#初始化frida运行环境
def is_frida_working_via_attach(target_package="com.android.systemui"):
    try:
        __device = frida.get_usb_device(timeout=3)  # or use add_remote_device(ip)
        pid = __device.get_process(target_package).pid  # 先确认包是否存在
        _session = __device.attach(pid)
        _session.detach()
        return True
    except frida.ServerNotRunningError:
        #info("其他异常: ServerNotRunningError")
        return False
    except frida.ProcessNotFoundError:
        #info("⚠️ 找不到进程,说明包名可能错误,但 frida 正常")
        return True  # 仍然说明 frida-server 已连通
    except frida.TimedOutError:
        #info("❌ 连接超时,frida-server 可能未运行或设备未连接")
        return False
    except Exception as e:
        #info(f"其他异常: {e}")
        return False

def check_remote_file_exists(path):
    result = adb_device.shell(f"test -f {path} && echo exists || echo missing")
    return result.strip() == "exists"

def check_remote_dir_exists(path):
    result = adb_device.shell(f"[ -d {path} ] && echo exists || echo not_exists")
    return result.strip() == "exists"

def get_cpu_arch():
    abi = adb_device.shell("getprop ro.product.cpu.abi").strip()
    if "arm64" in abi:
        return "arm64"
    elif "armeabi" in abi:
        return "arm"
    elif "x86_64" in abi:
        return "x86_64"
    elif "x86" in abi:
        return "x86"
    return "arm64"

def choose_frida_server():
    cpu_arch = get_cpu_arch()
    if "arm64" == cpu_arch:
        return default_frida_server_arm64
    elif "arm" == cpu_arch:
        return default_frida_server_arm
    info("For simulator, please start frida-server manually first. Thank you")
    sys.exit(2)
    
def pull_file_to_local(remote_file, local_path, is_debug=True):
    adb_device.sync.pull(remote_file, local_path)
    if is_debug:
        info(f"pull {remote_file} to {local_path} successful")
        
def push_file_to_remote(local_path, remote_path, is_debug=True):
    # info(f"push {local_path} to {remote_path}")
    from adbutils.errors import AdbError
    try:
        # 先尝试标准推送
        adb_device.sync.push(local_path, remote_path)
    except AdbError as e:
        #info("检测到API兼容性问题,降级到更基本的adb push命令")
        # 降级到更基本的adb push命令
        subprocess.run(
            ["adb", "push", local_path, remote_path],
            check=True
        )
    if is_debug:
        info(f"push {local_path} to {remote_path} successful")
    
def is_root():
    output = run_su_command("ls /data/")
    return "cache" in output and "user" in output

def ensure_root():
    if is_root():
        return True
    else:
        try:
            adb_device.root()  # adbutils 内置封装
            if is_root():
                info("Switched to root successfully ✅")
                return True
            else:
                info("❌ Device is not rooted")
                return False
        except Exception as e:
            info(f"❌ Failed to switch to root: {e}")
            return False


# 自动化部署frida-server    
if not is_frida_working_via_attach():
    if not ensure_root():
        info("❌ Cannot deploy frida-server automatically. Please start frida-server manually and try again.")
        sys.exit(2)
    frida_server_file = choose_frida_server()
    remote_frida_server_file = f"/data/mobile-deploy/{frida_server_file}"
    if not check_remote_dir_exists("/data/mobile-deploy/"):
        run_su_command("mkdir /data/mobile-deploy/")
    if not check_remote_file_exists(remote_frida_server_file):
        push_file_to_remote(f"mobile-deploy/{frida_server_file}", "/sdcard/")
        run_su_command(f"mv /sdcard/{frida_server_file} {remote_frida_server_file}")
        run_su_command(f"chmod +x {remote_frida_server_file}")
    run_su_command("setenforce 0")
    run_su_command(f"/data/mobile-deploy/{choose_frida_server()} -D > /sdcard/f_server.log 2>&1", False)
    success = False
    for index in range(20):
        if is_frida_working_via_attach():
            info("frida-server started successfully ✅")
            success = True
            break
        time.sleep(0.5)
    if not success:
        info("❌ Failed to start frida-server automatically. Please start it manually and try again.")
        sys.exit(2)

# 全局变量        
current_identifier = None
current_identifier_name = None
current_identifier_version = None
current_identifier_pid = None
current_identifier_install_path = None
current_identifier_install_apkfilename = None
current_identifier_uid = None
current_local_apk_path = None
current_identifier_cache_db = None
current_identifier_cache_readonly_db = None
current_identifier_stop_event = None
webserver_url = None

frida_device = None

resource_rpc_jscode = read_js_resource("rpc.js")
resource_hook_js_prepare_jscode = read_js_resource("_hook_js_prepare.js")
resource_hook_js_enhance_jscode = read_js_resource("_hook_js_enhance.js")
resource_hook_js_warp_jscode = read_js_resource("_hook_js_warp.js")

def _init_resource_jscode():
    global resource_rpc_jscode
    global resource_hook_js_prepare_jscode
    global resource_hook_js_enhance_jscode
    resource_rpc_jscode = read_js_resource("rpc.js")
    resource_hook_js_prepare_jscode = read_js_resource("_hook_js_prepare.js")
    resource_hook_js_enhance_jscode = read_js_resource("_hook_js_enhance.js")

_init_resource_jscode()

def convert_jar_to_dex(jarfile: str) -> bool:
    """
    将 JAR 文件转换为 DEX 文件

    Args:
        jarfile: JAR 文件路径

    Returns:
        转换成功返回 dex 文件路径,失败返回 None
    """
    # 获取 dx/d8 文件路径
    d8_file, dx_file = get_oldest_dx_d8_path()
    if dx_file is None and d8_file is None:
        warn(
            "error: Not found ANDROID_HOME. Please install android sdk and define ANDROID_HOME environment variable in your system")
        return None
    # 检查输入文件
    if not os.path.isfile(f"{current_identifier}/{jarfile}"):
        warn(f"error: JAR file not found: {jarfile}")
        return None
    # 生成输出文件名(将 .jar 替换为 .dex)
    if jarfile.endswith('.jar'):
        dexfile = jarfile[:-4] + '.dex'
    else:
        dexfile = jarfile + '.dex'
    out_put_dex_file = f'{current_identifier}/{dexfile}'
    try:
        if dx_file:
            # 使用 dx 命令
            cmd = [dx_file, '--dex', f'--output={out_put_dex_file}', f'{current_identifier}/{jarfile}']
        else:
            # 使用 d8 命令
            cmd = [d8_file, '--output', current_identifier, f'{current_identifier}/{jarfile}']
        info(f"Converting {jarfile} to {dexfile}...")
        # 执行转换
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            check=True
        )
        # 检查输出文件
        if os.path.exists(out_put_dex_file):
            file_size = os.path.getsize(out_put_dex_file)
            info(f"Successfully converted to {dexfile} ({file_size} bytes)")
            return dexfile
        else:
            info(f"error: Conversion failed, output file not created")
            if result.stderr:
                info(f"Error output: {result.stderr}")
            return None
    except subprocess.CalledProcessError as e:
        warn(f"error: Conversion failed with exit code {e.returncode}")
        if e.stderr:
            warn(f"Error output: {e.stderr}")
        return None
    except Exception as e:
        warn(f"error: {str(e)}")
        return None

def _init_frida_device():
    global frida_device
    def getRemoteDriver():
        text = io.open(".hooker_driver",'r',encoding= 'utf8').read()
        if not text:
            return None
        searchResult = re.search(r'\d+\.\d+\.\d+\.\d+:\d+', text)
        if searchResult:
            return searchResult.group()
        return None
    if frida_device:
        return
    remoteDriver = getRemoteDriver() #ip:port
    if remoteDriver:
        frida_device = frida.get_device_manager().add_remote_device(remoteDriver)
    else:
        frida_device = frida.get_usb_device(1000)

_init_frida_device()

@dataclass
class AppInfo:
    name: str               # 应用名(label,拿不到时用包名代替)
    identifier: str         # 包名
    pid: Optional[int]      # 运行中才有 pid,否则 None

def _list_third_party_packages() -> List[str]:
    """
    第三方包(-3)最稳定。
    输出行形如:package:com.xxx.yyy
    """
    out = run_su_command("pm list packages -3")
    pkgs = []
    for line in out.splitlines():
        line = line.strip()
        if line.startswith("package:"):
            pkgs.append(line.split("package:", 1)[1])
    return pkgs


def _get_pid_map() -> Dict[str, int]:
    """
    获取运行中进程的 pid 映射:package -> pid
    优先 pidof(快),不行再回退 ps。
    """
    pid_map: Dict[str, int] = {}
    # 方案 A:pidof(Android 8+ 大多支持)
    # pidof 可能返回多个 pid(多进程),这里取第一个
    # 但我们需要逐包调用 pidof,太慢,所以改用 ps 做一次性扫描更划算。
    # 因此优先方案 B:ps -A 解析。
    ps_out = run_su_command("ps -A -o PID,NAME")
    # 输出示例:
    # PID NAME
    #  123 com.xxx.yyy
    for line in ps_out.splitlines()[1:]:
        line = line.strip()
        if not line:
            continue
        parts = re.split(r"\s+", line, maxsplit=1)
        if len(parts) != 2:
            continue
        pid_s, name = parts
        if name.startswith("com."):  # 简单过滤:只收集 app 进程
            try:
                pid_map[name] = int(pid_s)
            except ValueError:
                pass
    return pid_map


def _get_app_label_fast(pkg: str) -> Optional[str]:
    """
    尝试快速拿 label(不保证所有 ROM 都能拿到)。
    方法:cmd package dump + grep
    失败就返回 None(用包名代替)。
    """
    # 有些系统支持:cmd package resolve-activity / dumpsys package
    # 但 label 不一定直接给;dumpsys package <pkg> 里通常能找到 "application-label:"
    out = run_su_command(f"dumpsys package {pkg} | grep -m 1 -E 'application-label:'")
    m = re.search(r"application-label:'(.*)'", out)
    if m:
        return m.group(1).strip()
    return pkg


def enumerate_applications_adbutils(third_party_only: bool = True, include_label: bool = False) -> List[AppInfo]:
    """
    用 adbutils 实现一个“类 enumerate_applications”的方法:
    - identifier: 包名
    - pid: 运行中的进程 pid(取主进程名 == 包名的情况)
    - name: 可选,从 dumpsys 拿 label;默认用包名代替(快很多)
    """
    apps: List[AppInfo] = []
    # pkgs = _list_third_party_packages()
    # info(f"pkgs: {pkgs}")
    # pid_map = _get_pid_map()
    # info(f"is_magisk_root3:{is_magisk_root}")
    # for pkg in pkgs:
    #     pid = pid_map.get(pkg, 0)  # 只有“进程名==包名”的主进程才会命中
    #     if include_label:
    #         info(f"_get_app_label_fast:{pkg}")
    #         label = _get_app_label_fast(pkg) or pkg
    #     else:
    #         label = pkg
    #     apps.append(AppInfo(name=label, identifier=pkg, pid=pid))
    # info(f"enumerate_applications_adbutils: {apps}")
    return frida_device.enumerate_applications()

def start_app(package_name):
    global current_identifier_pid
    shell_result = adb_device.shell(f"dumpsys package {package_name} | grep -A 1 MAIN | grep {package_name}").strip()
    m = re.search(r"\s+([^\s]+)\s+filter", shell_result)
    if m:
        main_activity = m.group(1)
        adb_device.shell(f"am start -n {main_activity}")
    else:
        adb_device.shell(f"monkey -p {package_name} -c android.intent.category.LAUNCHER 1")
    for j in range(20):
        time.sleep(0.5)
        out = adb_device.shell(f"pidof {package_name}").strip()
        if out and out.isdigit():
            current_identifier_pid = int(out)
            return current_identifier_pid, current_identifier_name
    return None, None

def restart_app(package_name):
    global current_identifier_pid
    info(f"Restarting {current_identifier_name} Please wait for a few seconds")
    adb_device.app_stop(package_name)
    time.sleep(1)
    app_pid, app_name = start_app(package_name)
    current_identifier_pid = app_pid

def ensure_app_in_foreground(package_name):
    def get_main_pid(pkg):
        out = adb_device.shell(f"pidof {pkg}").strip()
        if out:
            return int(out.split()[0])
        return None
    uid = None
    shell_result = adb_device.shell(f"dumpsys package {package_name}").strip()
    matchx = re.search(r"(userId|uid|appId)=(\d+)", shell_result)
    if matchx:
        uid = int(matchx.group(2))
    else:
        warn("UID not found.")
    apk_path = adb_device.shell(f"pm path {package_name}").strip().replace("package:", "")
    if "priv-app" in apk_path:
        apk_path = apk_path[:apk_path.index(".apk")+8]
    else:
        apk_path = apk_path[:apk_path.index("base.apk")+8]
    #print(f"apk_path:{apk_path}")
    appinstall_path = apk_path.rsplit("/", 1)[0]
    appinstall_path_apkfilename = apk_path.rsplit("/", 1)[1]
    appinfo = None
    version_name = None
    if 'app_info' in dir(adb_device):
        appinfo = adb_device.app_info(package_name)
        version_name = appinfo.version_name
    else:
        appinfo = adb_device.package_info(package_name)
        version_name = appinfo["version_name"]
    # 获取当前正在运行的所有进程
    proc_map = {}
    apps = enumerate_applications_adbutils(third_party_only=True, include_label=True)
    for app in sorted(apps, key=lambda x: x.pid or 0):
        if app.pid != 0:  # 只列出运行中的
            proc_map[app.identifier] = (app.pid, app.name)
    is_running = package_name in proc_map
    # 获取当前前台 activity
    foreground_output = adb_device.shell("dumpsys activity activities | grep mResumedActivity")
    is_foreground = package_name in foreground_output
    if is_running:
        if is_foreground:
            info(f"✅ App {package_name} is already in the foreground")
        else:
            info(f"📲 App {package_name} is running in the background, bringing it to the foreground...")
            # 通过 am 启动主 Activity,会自动 bring 到前台
            adb_device.shell(f"monkey -p {package_name} -c android.intent.category.LAUNCHER 1")
            #print("proc_map[package_name][1]", proc_map[package_name][1])
        main_pid = get_main_pid(package_name)
        app_pid = main_pid if main_pid is not None else proc_map[package_name][0]
        return app_pid, proc_map[package_name][1], version_name, appinstall_path, appinstall_path_apkfilename, uid
    else:
        info(f"🚀 App {package_name} is not running, starting it now...")
        #adb_device.shell(f"monkey -p {package_name} -c android.intent.category.LAUNCHER 1")
        app_pid, app_name = start_app(package_name)
        main_pid = get_main_pid(package_name)
        app_pid = main_pid if main_pid is not None else app_pid
        return app_pid, app_name, version_name, appinstall_path, appinstall_path_apkfilename, uid

def get_remote_file_md5(file_path):
    # 检查文件是否存在并获取长度
    check_cmd = f"md5sum {file_path}"
    result = run_su_command(check_cmd).strip()
    if "No such file" in result or "Permission denied" in result or not result:
        #warn("No such file")
        return ""
    try:
        # 56cf2745f4884b4dfcc1e193d0118c05  radar.dex
        m = re.search(r"[\w]{32}", result)
        if m:
            return m.group()
        else:
            return ""
    except Exception:
        return ""
    
def get_local_file_md5(filepath, chunk_size=8192):
    md5 = hashlib.md5()
    try:
        with open(filepath, 'rb') as f:
            while True:
                chunk = f.read(chunk_size)
                if not chunk:
                    break
                md5.update(chunk)
        return md5.hexdigest()
    except FileNotFoundError:
        warn(f"File Not Found: {filepath}")
        return None
    
def read_local_file(filename):
    return io.open(filename,'r',encoding= 'utf8').read()
    
def check_dependency_files():
    def process_dex_dependency_files():
        compara_and_update_file("mobile-deploy/radar.dex", "/data/local/tmp/radar.dex")
        compara_and_update_file("mobile-deploy/libext64.so", f"/data/data/{current_identifier}/files/libext64.so")
        compara_and_update_file("mobile-deploy/libext.so", f"/data/data/{current_identifier}/files/libext.so")
    t = threading.Thread(target=process_dex_dependency_files)
    t.daemon = True
    t.start()
             
def compara_and_update_file(local_file, remote_file):
    local_md5 = get_local_file_md5(local_file)
    local_filename = local_file.split("/")[-1]
    filename = remote_file.split("/")[-1]
    #print("filename:", filename)
    sdcard_remote_md5 = get_remote_file_md5(f"/sdcard/{local_filename}")
    #先把radar.dex拷贝到sdcard,后期更新radar.dex直接从sdcard拷过去
    if local_md5 != sdcard_remote_md5:
        push_file_to_remote(local_file, "/sdcard/", False)
    remote_md5 = get_remote_file_md5(remote_file)
    #print(f"local_md5:{local_md5} remote_md5:{remote_md5}")
    if local_md5 != remote_md5:
        #info(f"update file {local_filename} to {remote_file}")
        run_su_command(f"cp /sdcard/{local_filename} {remote_file}", True)
        run_su_command(f"chmod 555 {remote_file}", True)


def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    elif message['type'] == 'error':
        warn("[!] {0}".format(message['stack']))
    else:
        print(message)
        
def attach_rpc(use_v8=False):
    global frida_device
    online_session = None
    online_script = None
    online_session = frida_device.attach(current_identifier_pid)
    if online_session == None:
        warn("attaching fail to device")
        return None, None
    if use_v8:
        online_script = online_session.create_script(resource_rpc_jscode, runtime="v8")
    else:
        online_script = online_session.create_script(resource_rpc_jscode)
    online_script.on('message', on_message)
    online_script.load()
    # online_script.exports_sync.loadradardex()
    return online_session, online_script

def attach(script_file, use_v8=False):
    if not os.path.isfile(script_file):
        warn(f"attach {script_file} File Not found")
        return None, None
    script_jscode = read_local_file(script_file)
    script_jscode = script_jscode + "\n\n\n" + resource_hook_js_warp_jscode
    global frida_device
    online_session = None
    online_script = None
    online_session = frida_device.attach(current_identifier_pid)
    if online_session == None:
        warn("attaching fail to device")
        return None, None
    if use_v8:
        #info("use v8 js engine")
        online_script = online_session.create_script(script_jscode, runtime="v8")
    else:
        online_script = online_session.create_script(script_jscode)
    online_script.on('message', on_message)
    online_script.load()
    #sys.stdin.read()
    return online_session, online_script

def spawn(script_file, use_v8=False):
    if not os.path.isfile(script_file):
        warn(f"{script_file} File Not found")
        return None, None
    script_jscode = read_local_file(script_file)
    script_jscode = script_jscode + "\n\n\n" + resource_hook_js_warp_jscode
    global frida_device
    current_identifier_pid = frida_device.spawn([current_identifier])
    online_script = None
    online_session = frida_device.attach(current_identifier_pid)
    if online_session == None:
        warn("attaching fail to device")
        return None, None
    if use_v8:
        online_script = online_session.create_script(script_jscode, runtime="v8")
    else:
        online_script = online_session.create_script(script_jscode)
    online_script.on('message', on_message)
    online_script.load()
    release_version = int(adb_device.prop.get("ro.build.version.release").split('.', 1)[0])
    if release_version >= 12:
        frida_device.resume(current_identifier_pid)
    else:
        frida_device.resume(current_identifier)
    #sys.stdin.read()
    return online_session, online_script
    

def detach(online_session, online_script):
    if online_script != None:
        try:
            online_script.exports_sync.cleanup()
            online_script.unload()
        except Exception as e:
            info(f"RPC cleanup failed: {e}")
    if online_session != None:
        try:
            online_session.detach()
        except Exception as e:
            info(f"detach failed: {e}")
        
 
def exists_class(target, className):
    online_session = None
    online_script = None
    try:
        online_session, online_script,_ = attach_rpc(target);
        info(online_script.exports_sync.containsclass(className))
    except Exception:
        warn(traceback.format_exc())  
    finally:    
        detach(online_session, online_script)

def create_workingdir_file(filename, text):
    file = None
    try:
        file = open(filename, mode='w+')
        file.write(text)
    except Exception:
        warn(traceback.format_exc())  
    finally:
        if file != None:
            file.close()
            
def create_working_dir_enverment():
    global current_identifier
    global frida_device
    global current_identifier_name
    global current_identifier_version
    packageName = current_identifier
    if not os.path.exists(packageName):
        os.makedirs(packageName)
        info(f"Creating working directory: {packageName}")
        info(f"Generating frida shortcut command...")
        os.makedirs(packageName+"/xinit")
        shellPrefix = "#!/bin/bash\nHOOKER_DRIVER=$(cat ../.hooker_driver)\n"
        logHooking = shellPrefix + "echo \"hooking $1\" > log\ndate | tee -ai log\n" + "frida $HOOKER_DRIVER -l $1 -N " + packageName + " | tee -ai log"
        attach_shell = shellPrefix + "frida $HOOKER_DRIVER -l $1 -N " + packageName
        spawn_shell = f"{shellPrefix}\nfrida $HOOKER_DRIVER --runtime=v8 -f {packageName} -l $1"
        create_workingdir_file(packageName+"/hooking", logHooking)
        create_workingdir_file(packageName+"/attach", attach_shell)
        create_workingdir_file(packageName+"/spawn", spawn_shell)
        create_workingdir_file(packageName + "/kill", shellPrefix + "frida-kill $HOOKER_DRIVER "+packageName)
        create_workingdir_file(packageName+"/objection", shellPrefix + "objection -d -g "+packageName+" explore")
        os.popen('chmod 777 ' + packageName +'/hooking').readlines()
        os.popen('chmod 777 ' + packageName +'/attach').readlines()
        os.popen('chmod 777 ' + packageName +'/objection').readlines()
        os.popen('chmod 777 ' + packageName +'/spawn').readlines()
        info(f"Generating built-in frida script...")
        init_js_files = [
            "url.js",
            "android_ui.js",
            "edit_text.js",
            "text_view.js",
            "click.js",
            "activity_events.js",
            "object_store.js",
            "keystore_dump.js",
            "ssl_log.js",
            "just_trust_me.js",
            "just_trust_me_for_ios.js",
            "hook_register_natives.js",
            "dump_dex.js",
            "trace_init_proc.js",
            "hook_artmethod_register.js",
            "find_anit_frida_so.js",
            "hook_jni_method_trace.js",
            "replace_dlsym_get_pthread_create.js",
            "find_boringssl_custom_verify_func.js",
            "get_device_info.js",
            "apk_shell_scanner.js",
            "bypass_frida_svc_detect.js",
            "bypass_root_detect.js",
            "bypass_vpn_detect.js",
            "hook_encryption_algo.js",
            "hook_encryption_algo2.js",
            "webview_enable_debug.js",
            "hook_proxy_check.js"
        ]
        for js_file in init_js_files:
            if not os.path.exists(f"js/{js_file}"):
                info(f"File not Found: js/{js_file}")
                continue
            jscode = read_js_resource(js_file)
            create_workingdir_file(f"{packageName}/{js_file}", jscode.replace("com.smile.gifmaker", packageName))
        info(f"Copying APK {current_identifier_install_path}/{current_identifier_install_apkfilename} to working directory please waiting for a few seconds")
        global current_local_apk_path
        current_local_apk_path = f"{packageName}/{current_identifier_name.replace(' ', '')}_{current_identifier_version}.apk"
        pull_file_to_local(f"{current_identifier_install_path}/{current_identifier_install_apkfilename}", current_local_apk_path)
        info(f"Working directory create successful")
        
def init_working_dir_enverment():
    global current_local_apk_path
    current_local_apk_path = f"{current_identifier}/{current_identifier_name.replace(' ', '')}_{current_identifier_version}.apk"
    if os.path.isfile(current_local_apk_path):
        return
    if os.path.isdir(current_local_apk_path):
        os.popen(f'rm -rf {current_local_apk_path}').readlines()
    #print(f"current_identifier_install_path:{current_identifier_install_path}")
    pull_file_to_local(f"{current_identifier_install_path}/{current_identifier_install_apkfilename}", current_local_apk_path)
    info(f"Working directory init successful")
        
def hook_js(hookCmdArg, savePath = None):
    online_session = None
    online_script = None
    packageName = current_identifier
    try:
        ganaretoionJscode = ""
        online_session, online_script = attach_rpc(use_v8=False);
        appversion = current_identifier_version
        spaceSpatrater = hookCmdArg.find(":")
        className = hookCmdArg
        toSpace = "*"
        file_method_name = "allfunc"
        if spaceSpatrater > 0:
            className = hookCmdArg[:spaceSpatrater]
            toSpace = hookCmdArg[spaceSpatrater+1:]
            if "<init>" in toSpace:
                toSpace = "_"
                file_method_name = "_init"
            else:
                toSpace = toSpace.split("(")[0]
                file_method_name = toSpace
        if not online_script.exports_sync.containsclass(className):
            warn(f"Class Not Found {className}")
            return
        jscode = online_script.exports_sync.hookjs(className, toSpace);
        ganaretoionJscode += ("\n//"+hookCmdArg+"\n")
        ganaretoionJscode += jscode
        if savePath == None:
            defaultFilename = className.replace(":", ".").replace("$", ".").replace("__", ".")+ "." + file_method_name + ".js"
            savePath = packageName+"/"+defaultFilename;
        else:
            defaultFilename = savePath
            savePath = packageName+"/"+savePath;
        if len(ganaretoionJscode):
            ganaretoionJscode = resource_hook_js_prepare_jscode + "\n" + ganaretoionJscode + "\n\n\n\n\n//---------------------may be you need--------------------\n\n" + resource_hook_js_enhance_jscode
            warpExtraInfo = f"//cracked by {current_identifier_name} {appversion}\n"
            warpExtraInfo += "//"+hookCmdArg + "\n\n"
            warpExtraInfo += ganaretoionJscode
            create_workingdir_file(savePath, jsbeautifier.beautify(warpExtraInfo))
            info("frida hook script: " + defaultFilename)
        else:
            warn("Not found any classes by pattern "+hookCmdArg+".")
    except Exception:
        warn(traceback.format_exc())  
    finally:    
        detach(online_session, online_script)
        
def print_activitys():
    online_session = None
    online_script = None
    try:
        online_session,online_script = attach_rpc();
        info(online_script.exports_sync.activitys())
    except Exception:
        print(traceback.format_exc())  
    finally:
        detach(online_session, online_script)
        
def print_services():
    online_session = None
    online_script = None
    try:
        online_session, online_script = attach_rpc();
        info(online_script.exports_sync.services())
    except Exception:
        print(traceback.format_exc())  
    finally:
        detach(online_session, online_script)

def print_object(objectId):
    online_session = None
    online_script = None
    try:
        online_session, online_script = attach_rpc();
        info(online_script.exports_sync.objectinfo(objectId))
    except Exception:
        print(traceback.format_exc())  
    finally:
        detach(online_session, online_script)
        
def object_to_explain(objectId):
    online_session = None
    online_script = None
    try:
        online_session, online_script = attach_rpc();
        info(online_script.exports_sync.objecttoexplain(objectId))
    except Exception:
        print(traceback.format_exc())  
    finally:
        detach(online_session, online_script)

def print_view(viewId):
    online_session = None
    online_script = None
    try:
        online_session, online_script = attach_rpc();
        report = online_script.exports_sync.viewinfo(viewId)
        info(report);
    except Exception:
        print(traceback.format_exc())  
    finally:
        detach(online_session, online_script)

def rpc_start_web_server(dex_file, all_class):
    global webserver_url
    online_session = None
    online_script = None
    try:
        online_session, online_script = attach_rpc();
        text = online_script.exports_sync.starthttpserver(dex_file, ",".join(all_class))
        info(text)
        m = re.search("http:[^s]+:[\d]+", text)
        if m:
            webserver_url = m.group(0)
        else:
            info("找不到webserver" + text)
    except Exception:
        print(traceback.format_exc())
    finally:
        detach(online_session, online_script)
        

def list_working_dir():
    js_files = {
        filename: None
        for filename in os.listdir(current_identifier)
        if filename.endswith(".js")
    }
    print_js_file(list(js_files.keys()))
                
                
def execute_script(script_file, is_spawn=False):
    if not os.path.isfile(f"{current_identifier}/{script_file}"):
        warn(f"{current_identifier}/{script_file} File Not found")
        return
    online_session = None
    online_script = None
    log_fh = None
    saved_fd = None
    pipe_r = None
    pipe_w = None
    tee_thread = None
    use_v8 = "just_trust_me.js" in script_file
    try:
        log_filename = script_file.rsplit('.', 1)[0] + '.log'
        log_filepath = f"{current_identifier}/{log_filename}"
        log_fh = open(log_filepath, 'w', encoding='utf-8')
        # fd 级别重定向,捕获所有 C 层写 stdout 的输出
        pipe_r, pipe_w = os.pipe()
        saved_fd = os.dup(1)
        os.dup2(pipe_w, 1)
        os.close(pipe_w)
        pipe_w = None
        stop_event = threading.Event()
        def tee_reader():
            while not stop_event.is_set():
                try:
                    data = os.read(pipe_r, 8192)
                    if not data:
                        break
                    os.write(saved_fd, data)
                    log_fh.write(data.decode('utf-8', errors='replace'))
                    log_fh.flush()
                except Exception:
                    break
        tee_thread = threading.Thread(target=tee_reader, daemon=True)
        tee_thread.start()
        if is_spawn:
            online_session, online_script = spawn(f"{current_identifier}/{script_file}", use_v8)
        else:
            online_session, online_script = attach(f"{current_identifier}/{script_file}", use_v8)
        info(f"Frida output logging -> {log_filepath}")
        while online_session != None:
            try:
                with patch_stdout():
                    text = cmd_session.prompt("CTRL + C to stop > ", handle_sigint=True)
            except KeyboardInterrupt:
                info(f"Interrupting {script_file}")
                break
            except EOFError:
                warn("Exiting...")
                break
    except Exception:
        print(traceback.format_exc())
    finally:
        detach(online_session, online_script)
        # 恢复 fd 1
        if saved_fd is not None:
            os.dup2(saved_fd, 1)
            os.close(saved_fd)
        if pipe_r is not None:
            os.close(pipe_r)
        if tee_thread is not None:
            stop_event.set()
        if log_fh is not None:
            log_fh.close()
        info(f"{script_file} detach successful")
        if is_spawn:
            restart_app(current_identifier)
            
def just_trust_me():
    execute_script("just_trust_me.js", True)
    

def r0capture():
    PY3K = sys.version_info >= (3, 0)
    # --- workaround against Python consistency issues
    def normalize_py():
        if sys.platform == "win32":
            # set sys.stdout to binary mode on Windows
            import os, msvcrt
            msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
    
    # --- - chunking helpers
    def chunks(seq, size):
        d, m = divmod(len(seq), size)
        for i in range(d):
            yield seq[i * size:(i + 1) * size]
        if m:
            yield seq[d * size:]
    
    
    def chunkread(f, size):
        c = f.read(size)
        while len(c):
            yield c
            c = f.read(size)
    
    
    def genchunks(mixed, size):
        if hasattr(mixed, 'read'):
            return chunkread(mixed, size)
        else:
            return chunks(mixed, size)
    
    
    # --- - /chunking helpers
    def dehex(hextext):
        if PY3K:
            return bytes.fromhex(hextext)
        else:
            hextext = "".join(hextext.split())
            return hextext.decode('hex')
        
    def dump(binary, size=2, sep=' '):
        hexstr = binascii.hexlify(binary)
        if PY3K:
            hexstr = hexstr.decode('ascii')
        return sep.join(chunks(hexstr.upper(), size))
    
    
    def dumpgen(data, only_str):
        generator = genchunks(data, 16)
        for addr, d in enumerate(generator):
            line = ""
            if not only_str:
                # 00000000:
                line = '%08X: ' % (addr * 16)
                # 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
                dumpstr = dump(d)
                line += dumpstr[:8 * 3]
                if len(d) > 8:  # insert separator if needed
                    line += ' ' + dumpstr[8 * 3:]
                # ................
                # calculate indentation, which may be different for the last line
                pad = 2
                if len(d) < 16:
                    pad += 3 * (16 - len(d))
                if len(d) <= 8:
                    pad += 1
                line += ' ' * pad
    
            for byte in d:
                # printable ASCII range 0x20 to 0x7E
                if not PY3K:
                    byte = ord(byte)
                if 0x20 <= byte <= 0x7E:
                    line += chr(byte)
                else:
                    line += '.'
            yield line
    
    
    def hexdump(data, result='print', only_str=False):
        if PY3K and type(data) == str:
            raise TypeError('Abstract unicode data (expected bytes sequence)')
        gen = dumpgen(data, only_str=only_str)
        if result == 'generator':
            return gen
        elif result == 'return':
            return '\n'.join(gen)
        elif result == 'print':
            for line in gen:
                print(line)
        else:
            raise ValueError('Unknown value of `result` argument')
    banner = (
        "--------------------------------------------------------------------------------------------\n"
        "           .oooo.                                      .                                  \n"
        "          d8P'`Y8b                                   .o8                                  \n"
        "oooo d8b 888    888  .ooooo.   .oooo.   oo.ooooo.  .o888oo oooo  oooo  oooo d8b  .ooooo.  \n"
        "`888\"\"8P 888    888 d88' `\"Y8 `P  )88b   888' `88b   888   `888  `888  `888\"\"8P d88' `88b \n"
        " 888     888    888 888        .oP\"888   888   888   888    888   888   888     888ooo888 \n"
        " 888     `88b  d88' 888   .o8 d8(  888   888   888   888 .  888   888   888     888    .o \n"
        "d888b     `Y8bd8P'  `Y8bod8P' `Y888\"\"8o  888bod8P'   \"888\"  `V88V\"V8P' d888b    `Y8bod8P' \n"
        "                                         888                                               \n"
        "                                        o888o                                              \n"
        "                    https://github.com/r0ysue/r0capture\n"
        "--------------------------------------------------------------------------------------------\n"
    )
    print(banner)
    ssl_sessions = {}
    def ssl_log(pcap=None, verbose=False, ssllib="", wait=0):
        # if platform.system() not in ("Darwin", "Linux"):
        #   raise NotImplementedError("This function is only implemented for Linux and "
        #                             "macOS systems.")
    
        def log_pcap(pcap_file, ssl_session_id, function, src_addr, src_port,
                     dst_addr, dst_port, data):
            t = time.time()
            if ssl_session_id not in ssl_sessions:
                ssl_sessions[ssl_session_id] = (random.randint(0, 0xFFFFFFFF),
                                                random.randint(0, 0xFFFFFFFF))
            client_sent, server_sent = ssl_sessions[ssl_session_id]
            if function == "SSL_read":
                seq, ack = (server_sent, client_sent)
            else:
                seq, ack = (client_sent, server_sent)
            for writes in (
                    # PCAP record (packet) header
                    ("=I", int(t)),  # Timestamp seconds
                    ("=I", int((t * 1000000) % 1000000)),  # Timestamp microseconds
                    ("=I", 40 + len(data)),  # Number of octets saved
                    ("=i", 40 + len(data)),  # Actual length of packet
                    # IPv4 header
                    (">B", 0x45),  # Version and Header Length
                    (">B", 0),  # Type of Service
                    (">H", 40 + len(data)),  # Total Length
                    (">H", 0),  # Identification
                    (">H", 0x4000),  # Flags and Fragment Offset
                    (">B", 0xFF),  # Time to Live
                    (">B", 6),  # Protocol
                    (">H", 0),  # Header Checksum
                    (">I", src_addr),  # Source Address
                    (">I", dst_addr),  # Destination Address
                    # TCP header
                    (">H", src_port),  # Source Port
                    (">H", dst_port),  # Destination Port
                    (">I", seq),  # Sequence Number
                    (">I", ack),  # Acknowledgment Number
                    (">H", 0x5018),  # Header Length and Flags
                    (">H", 0xFFFF),  # Window Size
                    (">H", 0),  # Checksum
                    (">H", 0)):  # Urgent Pointer
                pcap_file.write(struct.pack(writes[0], writes[1]))
            pcap_file.write(data)
            if function == "SSL_read":
                server_sent += len(data)
            else:
                client_sent += len(data)
            ssl_sessions[ssl_session_id] = (client_sent, server_sent)
    
        def r0capture_on_message(message, data):
            if message["type"] == "error":
                logger.info(f"{message}")
                os.kill(os.getpid(), signal.SIGTERM)
                return
            if len(data) == 1:
                logger.info(f'{message["payload"]["function"]}')
                logger.info(f'{message["payload"]["stack"]}')
                return
            p = message["payload"]
            if verbose:
                src_addr = socket.inet_ntop(socket.AF_INET,
                                            struct.pack(">I", p["src_addr"]))
                dst_addr = socket.inet_ntop(socket.AF_INET,
                                            struct.pack(">I", p["dst_addr"]))
                session_id = p['ssl_session_id']
                logger.info(f"SSL Session: {session_id}")
                logger.info("[%s] %s:%d --> %s:%d" % (
                    p["function"],
                    src_addr,
                    p["src_port"],
                    dst_addr,
                    p["dst_port"]))
                gen = hexdump(data, result="generator",only_str=True)
                str_gen = ''.join(gen)
                logger.info(f"{str_gen}")
                logger.info(f"{p['stack']}")
            if pcap:
                log_pcap(pcap_file, p["ssl_session_id"], p["function"], p["src_addr"],
                         p["src_port"], p["dst_addr"], p["dst_port"], data)
        current_identifier_pid = frida_device.spawn([current_identifier])        
        online_session = frida_device.attach(current_identifier_pid)
        time.sleep(1)
        if wait > 0:
            print(f"wait for {wait} seconds")
            time.sleep(wait)
            
        if pcap:
            pcap_file = open(pcap, "wb", 0)
            for writes in (
                    ("=I", 0xa1b2c3d4),  # Magic number
                    ("=H", 2),  # Major version number
                    ("=H", 4),  # Minor version number
                    ("=i", time.timezone),  # GMT to local correction
                    ("=I", 0),  # Accuracy of timestamps
                    ("=I", 65535),  # Max length of captured packets
                    ("=I", 228)):  # Data link type (LINKTYPE_IPV4)
                pcap_file.write(struct.pack(writes[0], writes[1]))
        r0capture_script = read_js_resource("r0capture.js")
        online_script = online_session.create_script(r0capture_script, runtime="v8")
        online_script.on("message", r0capture_on_message)
        online_script.load()
        release_version = int(adb_device.prop.get("ro.build.version.release").split('.', 1)[0])
        if release_version >= 12:
            frida_device.resume(current_identifier_pid)
        else:
            frida_device.resume(current_identifier)
        if ssllib != "":
            online_script.exports.setssllib(ssllib)
        try:
            while online_session != None:
                try:
                    with patch_stdout():
                        text = cmd_session.prompt("CTRL + C to stop > ", handle_sigint=True)
                except KeyboardInterrupt:
                    info(f"Interrupting")
                    break
                except EOFError:
                    warn("Exiting...")
                    break
        except Exception:
            print(traceback.format_exc())  
        finally:
            detach(online_session, online_script)
            if pcap:
                pcap_file.flush()
                pcap_file.close()
                info(f"flushing {current_identifier}/r0capture_ssl.pcap successful")
            info("r0capture.js detach successful")
            restart_app(current_identifier)
    ssl_log(f"{current_identifier}/r0capture_ssl.pcap", True)
    
def un_proxy():
    run_su_command(r"for i in $(iptables -t nat -L OUTPUT --line-numbers | grep REDIRECT |grep 12345 | awk \"{print \$1}\" | sort -rn); do iptables -t nat -D OUTPUT $i; done")
    run_su_command("iptables -t nat -F REDSOCKS")
    run_su_command("iptables -t nat -D OUTPUT -p tcp -j REDSOCKS")
    run_su_command("iptables -t nat -X REDSOCKS")
    run_su_command("killall redsocks")
    run_su_command("pid=$(ps -ef | grep '[r]edsocks' | awk '{print $2}'); [ -n \"$pid\" ] && kill -9 $pid")
        
def set_proxy(proxy):
    pattern = r'(http|socks5)://(\d{2,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)$'
    m = re.search(pattern, proxy.strip())
    if not m:
        warn(f"proxy scheme error: {proxy}")
        return
    proxy_type = m.group(1)
    if proxy_type == "http":
        proxy_type = "http-relay"
    proxy_ip = m.group(2)
    proxy_port = m.group(3)
    config = (
        "base {\n"
        "    log_debug = on;\n"
        "    log_info = on;\n"
        "    daemon = on;\n"
        "    redirector = iptables;\n"
        "}\n\n"
    )
    if proxy_type == "http-relay":
        config += (
            "redsocks {\n"
            "    local_ip = 127.0.0.1;\n"
            "    local_port = 12345;\n"
            f"    ip = {proxy_ip};\n"
            f"    port = {proxy_port};\n"
            f"    type = http-relay;\n"
            "}"
        )
    else:
        config += (
            "redsocks {\n"
            "    local_ip = 127.0.0.1;\n"
            "    local_port = 12345;\n"
            f"    ip = {proxy_ip};\n"
            f"    port = {proxy_port};\n"
            f"    type = {proxy_type};\n"
            "}"
        )
    if not check_remote_file_exists("/sdcard/redsocks"):
        push_file_to_remote(f"mobile-deploy/redsocks", "/sdcard/redsocks", False)
        run_su_command(f"cp /sdcard/redsocks /data/local/tmp/redsocks")
        run_su_command(f"chmod 700 /data/local/tmp/redsocks")
    if not check_remote_file_exists("/sdcard/libevent-2.1.so"):
        push_file_to_remote(f"mobile-deploy/libevent-2.1.so", "/sdcard/libevent-2.1.so", False)
        run_su_command(f"cp /sdcard/libevent-2.1.so /data/local/tmp/libevent-2.1.so")
    if not check_remote_file_exists("/sdcard/libevent_core-2.1.so"):
        push_file_to_remote(f"mobile-deploy/libevent_core-2.1.so", "/sdcard/libevent_core-2.1.so", False)
        run_su_command(f"cp /sdcard/libevent_core-2.1.so /data/local/tmp/libevent_core-2.1.so")
    if not check_remote_file_exists("/data/local/tmp/redsocks"):
        run_su_command(f"cp /sdcard/redsocks /data/local/tmp/redsocks")
        run_su_command(f"cp /sdcard/libevent-2.1.so /data/local/tmp/libevent-2.1.so")
        run_su_command(f"cp /sdcard/libevent_core-2.1.so /data/local/tmp/libevent_core-2.1.so")
        run_su_command(f"chmod 700 /data/local/tmp/redsocks")
    un_proxy()
    adb_device.shell(f"rm -f /data/local/tmp/redsocks.conf")
    adb_device.shell(f"echo '{config}' > /data/local/tmp/redsocks.conf")
    time.sleep(1)
    run_su_command(f"LD_LIBRARY_PATH=/data/local/tmp/ /data/local/tmp/redsocks -c /data/local/tmp/redsocks.conf")
    if proxy_type == "http-relay":
        run_su_command(f"iptables -t nat -N REDSOCKS")
        run_su_command(f"iptables -t nat -A REDSOCKS -d 0.0.0.0/8 -j RETURN")
        run_su_command(f"iptables -t nat -A REDSOCKS -d 127.0.0.0/8 -j RETURN")
        run_su_command(f"iptables -t nat -A REDSOCKS -d 192.168.1.0/24 -j RETURN")
        run_su_command(f"iptables -t nat -A REDSOCKS -p tcp -j REDIRECT --to-ports 12345")
        # run_su_command(f"iptables -t nat -L -nv")
        # run_su_command(f"iptables -t nat -A OUTPUT -p tcp  --dport 80 -j REDIRECT --to 12345")
        # run_su_command(f"iptables -t nat -A OUTPUT -p tcp  --dport 443 -j REDIRECT --to 12345")
    elif proxy_type.startswith("socks"):
        run_su_command(f"iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner {current_identifier_uid} -j REDIRECT --to-ports 12345")
        #run_su_command(f"iptables -t nat -A PREROUTING -i wlan0 -p tcp -j REDIRECT --to-ports 12345")
        info(f"proxy {proxy} OK")
    else:
        warn(f"Cannot set proxy {proxy}")
        return
if not os.path.exists('.cache'):
    os.makedirs('.cache')
    
def open_or_create_db():
    global current_identifier_cache_db
    if current_identifier_cache_db:
        return current_identifier_cache_db
    db_path=f'.cache/{current_identifier}_{current_identifier_version}_class_methods.db'
    conn = sqlite3.connect(db_path, check_same_thread=False)
    current_identifier_cache_db = conn
    cursor = conn.cursor()
    # 检查是否存在 app_methods 表
    cursor.execute('''
        SELECT name FROM sqlite_master 
        WHERE type='table' AND name='app_methods'
    ''')
    table_exists = cursor.fetchone()
    if table_exists:
        cursor.execute("PRAGMA table_info(app_methods)")
        columns = [row[1] for row in cursor.fetchall()]
        if 'filename' not in columns:
            cursor.execute("DROP TABLE IF EXISTS app_methods")
            conn.commit()
            table_exists = False
    if not table_exists:
        # 创建表
        cursor.execute('''
            CREATE TABLE app_methods (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                filename TEXT NOT NULL,
                class_package_name TEXT NOT NULL,
                class_name TEXT NOT NULL,
                method_name TEXT NOT NULL,
                readable_proto_list TEXT
            )
        ''')
        # 创建索引
        cursor.execute('CREATE INDEX idx_class_package_prefix ON app_methods(class_package_name)')
        cursor.execute('CREATE INDEX idx_class_name ON app_methods(class_name)')
        conn.commit()
    return current_identifier_cache_db

def insert_if_not_exists(cursor, filename, class_package_name, class_name,
                         method_name, readable_proto_list):
    try:
        # 查询是否存在相同 class_package_name 和 class_name 的记录
        cursor.execute('''
            SELECT 1 FROM app_methods
            WHERE class_package_name = ? AND class_name = ?
            LIMIT 1
        ''', (class_package_name, class_name))
        exists = cursor.fetchone()
        if not exists:
            cursor.execute('''
                INSERT INTO app_methods (
                    filename, class_package_name, class_name,
                    method_name, readable_proto_list
                ) VALUES (?, ?, ?, ?, ?)
            ''', (
                filename, class_package_name, class_name,
                method_name, readable_proto_list
            ))
            current_identifier_cache_db.commit()
    except sqlite3.DatabaseError as e:
        pass
    return exists
        
def ensure_readonly_copy_fresh():
    """
    确保只读数据库副本是最新的,如果过旧或主库有更新则拷贝。
    设置 current_identifier_cache_readonly_db 为连接对象。
    """
    global current_identifier_cache_readonly_db  # 注意声明为全局变量
    base_path = f'.cache/{current_identifier}_{current_identifier_version}_class_methods.db'
    readonly_path = f'.cache/{current_identifier}_{current_identifier_version}_tmp_readonly_class_methods.db'
    # 主库不存在直接报错
    if not os.path.exists(base_path):
        raise FileNotFoundError(f"主数据库不存在: {base_path}")
    now = time.time()
    # 如果只读文件不存在,则直接拷贝
    if not os.path.exists(readonly_path):
        shutil.copy2(base_path, readonly_path)
    else:
        readonly_ctime = os.path.getctime(readonly_path)
        # 如果只读副本距现在小于30秒,则不更新
        if now - readonly_ctime < 30:
            pass  # 不拷贝
        else:
            base_ctime = os.path.getctime(base_path)
            if base_ctime > readonly_ctime:
                shutil.copy2(base_path, readonly_path)

    # 连接(无论是否拷贝,都会使用只读副本连接)
    readonly_uri = f'file:{readonly_path}?mode=ro'
    current_identifier_cache_readonly_db = sqlite3.connect(readonly_uri, uri=True, check_same_thread=False)

        
def count_methods_by_app_version():
    ensure_readonly_copy_fresh()
    cursor = current_identifier_cache_readonly_db.cursor()
    cursor.execute('SELECT COUNT(*) FROM app_methods')
    count = cursor.fetchone()[0]
    return count

def query_class_name_by_prefix(class_name_prefix, class_name, limit=15):
    # 拷贝最新主库文件(如果需要)
    ensure_readonly_copy_fresh()
    cursor = current_identifier_cache_readonly_db.cursor()
    if class_name and not "." in class_name:
        if class_name_prefix:
            cursor.execute('''
                SELECT class_package_name, class_name, readable_proto_list FROM app_methods
                WHERE class_name = ? AND class_package_name LIKE ?
                LIMIT ?
            ''', (class_name, f'%{class_name_prefix}%', limit))
            #print("1")
            results = cursor.fetchall()
            if results:
                return results
            cursor.execute('''
                SELECT class_package_name, class_name, readable_proto_list FROM app_methods
                WHERE class_name LIKE ? AND class_package_name LIKE ?
                LIMIT ?
            ''', (f'{class_name}%', f'%{class_name_prefix}%', limit))
            #print("2")
            results = cursor.fetchall()
            if results:
                return results
        else:
            cursor.execute('''
                SELECT class_package_name, class_name, readable_proto_list FROM app_methods
                WHERE class_name = ?
                LIMIT ?
            ''', (class_name, limit))
            #print("3")
            results = cursor.fetchall()
            if results:
                return results
            cursor.execute('''
                SELECT class_package_name, class_name, readable_proto_list FROM app_methods
                WHERE class_name LIKE ?
                LIMIT ?
            ''', (f'{class_name}%', limit))
            #print("4")
            results = cursor.fetchall()
            if results:
                return results
        return []
    cursor.execute('''
        SELECT class_package_name, class_name, readable_proto_list FROM app_methods
        WHERE class_package_name LIKE ?
        LIMIT ?
    ''', (f'{class_name_prefix}%', limit))
    #print("5")
    results = cursor.fetchall()
    if results:
        return results
    cursor.execute('''
        SELECT class_package_name, class_name, readable_proto_list FROM app_methods
        WHERE class_package_name LIKE ?
        LIMIT ?
    ''', (f'%{class_name_prefix}%', limit))
    #print("6")
    results = cursor.fetchall()
    if results:
        return results
    if not "." in class_name_prefix:
        return []
    class_name_prefix, class_name = class_name_prefix.rsplit(".", 1)
    return query_class_name_by_prefix(class_name_prefix, class_name, limit)

def get_need_to_cache_pkg_prefix():
    results = {"okhttp3", "retrofit2", "javax.crypto", "java.security"}
    try:
        logging.getLogger("androguard.core.api_specific_resources").setLevel(logging.ERROR)
        a = apk.APK(current_local_apk_path)
        activities = a.get_activities()
        # 取每个activity包名前两段
        prefixes = []
        for activity in activities:
            parts = activity.split('.')
            if len(parts) >= 2:
                prefix = '.'.join(parts[:2])
            else:
                prefix = activity
            prefixes.append(prefix)
        # 统计出现次数
        counter = Counter(prefixes)
        # 找出现次数最多的前两个
        most_common_two = counter.most_common(2)
        for prefix, count in most_common_two:
            results.add(prefix)
        package_name = current_identifier    
        parts = package_name.split('.')
        results.add('.'.join(parts[:2]) if len(parts) >= 2 else package_name)
        #info(f"need_to_cache_pkg_prefix:{results}")
    except Exception as e:
        return []
    return list(results)

def load_dexes_to_cache():
    if not zipfile.is_zipfile(current_local_apk_path):
        warn(f"{current_local_apk_path} is not a legal zip file")
        return
    open_or_create_db()
    def process_dex():
        need_to_cache_pkg_prefix = get_need_to_cache_pkg_prefix()
        with zipfile.ZipFile(current_local_apk_path, 'r') as zip_ref:
            for file_info in zip_ref.infolist():
                if file_info.filename.endswith('.dex'):
                    with zip_ref.open(file_info.filename) as dex_file:
                        dex_data = dex_file.read()  # 读取为 bytes
                        load_classes_and_methods_to_db(file_info.filename, dex_data, need_to_cache_pkg_prefix)
                        if current_identifier_stop_event == None or current_identifier_stop_event.is_set():
                            # info("中断线程3")
                            break
                        if count_methods_by_app_version() > 300000:
                            break
    global current_identifier_stop_event
    current_identifier_stop_event = threading.Event()
    t = threading.Thread(target=process_dex)
    t.daemon = True
    t.start()
    
def load_classes_and_methods_to_db(filename, dex_bytes, need_to_cache_pkg_prefix):
    if not current_identifier_cache_db:
        return
    if current_identifier_stop_event == None or current_identifier_stop_event.is_set():
        return
    cursor = current_identifier_cache_db.cursor()
    need_to_cache_pkg = tuple(need_to_cache_pkg_prefix)
    obfuscated_package_cahce = set()
    def is_obfuscated_package(pkg_name: str) -> bool:
        if pkg_name in obfuscated_package_cahce:
            return True
        parts = pkg_name.split(".")
        if len(parts) > 3 or (len(parts) == 1 and len(parts[0]) > 3):
            return False
        if len(parts) < 2 and len(parts[0]) <= 3:
            obfuscated_package_cahce.add(pkg_name)
            #info(f"混淆包名1:{class_package_name}")
            return True
        short_count = sum(1 for part in parts if len(part) <= 1)
        upper_count = sum(1 for part in parts if part.isupper())
        if short_count >= len(parts) // 2:
            obfuscated_package_cahce.add(pkg_name)
            #info(f"混淆包名2:{class_package_name}")
            return True
        if upper_count >= len(parts) // 2:
            obfuscated_package_cahce.add(pkg_name)
            #info(f"混淆包名3:{class_package_name}")
            return True
        return False
    def should_skip_class_package(class_package_name):
        if is_obfuscated_package(class_package_name):
            return False
        if not class_package_name.startswith(need_to_cache_pkg):
            return True
        return False
    loaded_count = 0
    try:
        dex = dvm.DalvikVMFormat(dex_bytes)
        for cls in dex.get_classes():
            if loaded_count > 2000:
                #info(f"already loaded dex {filename}")
                return
            access_flags = cls.get_access_flags()
            # (0x200 = ACC_INTERFACE)(0x400 = ACC_ABSTRACT)
            if (access_flags & 0x200) or (access_flags & 0x400):
                continue
            if current_identifier_stop_event == None or current_identifier_stop_event.is_set():
                # info("中断线程")
                return
            class_name = cls.get_name().strip('L;').replace('/', '.')
            temp = class_name.rsplit(".", 1)
            class_package_name = temp[0]
            if len(temp) != 2 or should_skip_class_package(class_package_name):
                continue
            class_name = temp[1]
            readable_proto_list = ""
            for method in cls.get_methods():
                method_name = method.get_name()
                proto = method.get_descriptor()  # e.g., (Ljava/lang/String;)V
                # 转换描述符为更易读格式
                readable_proto = convert_descriptor_to_readable(proto)
                readable_proto_list += f"|{method_name}{readable_proto}"
            already_exists = insert_if_not_exists(cursor, filename, class_package_name, class_name, "", readable_proto_list)
            if already_exists:
                loaded_count += 1
    except Exception as e:
        pass
        #print(traceback.format_exc())
        #print(f"[!] Error processing {filename}: {e}")

def convert_descriptor_to_readable(descriptor):
    def type_map(d):
        mapping = {
            'I': 'int', 'Z': 'boolean', 'B': 'byte',
            'S': 'short', 'J': 'long', 'F': 'float',
            'D': 'double', 'C': 'char', 'V': 'void',
        }
        if d.startswith('L') and d.endswith(';'):
            return d[1:-1].replace('/', '.').split('.')[-1]
        return mapping.get(d, d)
    args, ret = descriptor.split(')')
    args = args[1:]  # remove opening '('
    i = 0
    parsed = []
    while i < len(args):
        if args[i] == 'L':
            j = i
            while args[j] != ';':
                j += 1
            parsed.append(type_map(args[i:j+1]))
            i = j + 1
        elif args[i] in "ZBSCIJFD":
            parsed.append(type_map(args[i]))
            i += 1
        elif args[i] == '[':
            dim = 0
            while args[i] == '[':
                dim += 1
                i += 1
            if args[i] == 'L':
                j = i
                while args[j] != ';':
                    j += 1
                base = type_map(args[i:j+1])
                i = j + 1
            else:
                base = type_map(args[i])
                i += 1
            parsed.append(base + '[]' * dim)
        else:
            i += 1  # unknown type, skip
    return f"({', '.join(parsed)})"

def push_file_to_device_with_chmod(local_file, remote_file = None):
    filename = local_file.split("/")[-1]
    if remote_file == None:
        remote_file = f"/data/user/0/{current_identifier}/{filename}"
    compara_and_update_file(f"{current_identifier}/{local_file}", remote_file)
    user_group_id = f"u0_a{(int(current_identifier_uid) - 10000)}"
    run_su_command(f"chown {user_group_id}:{user_group_id} {remote_file}")
    run_su_command(f"chmod 777 {remote_file}")
    info(f"push file OK {remote_file}")
    return remote_file

def start_web_server(jar_file:str = "", with_xposed_daemon = False):
    remote_dex_file = ""
    all_classes = []
    if jar_file:
        dex_file = convert_jar_to_dex(jar_file)
        with open(f"{current_identifier}/{dex_file}", "rb") as f:
            dex = dvm.DalvikVMFormat(f.read())
        all_classes = []
        for cls in dex.get_classes():
            # 判断是否接口、抽象类、注解
            access_flags = cls.get_access_flags()
            # (0x200 = ACC_INTERFACE)(0x400 = ACC_ABSTRACT)(0x2000 = ACC_ANNOTATION)
            if (access_flags & 0x200) or (access_flags & 0x400) or (access_flags & 0x2000):
                continue
            class_name = cls.get_name()[1:-1].replace("/", ".")
            all_classes.append(class_name)
        if len(all_classes) == 0:
            warn(f"Deploy failure. not found any class in {jar_file}")
            return
        remote_file = f"/data/user/0/{current_identifier}/hooker_server.dex"
        remote_dex_file = push_file_to_device_with_chmod(dex_file, remote_file)
    rpc_start_web_server(remote_dex_file, all_classes)

def stop_web_server():
    cmd = "curl --max-time 3 " + webserver_url + "/stop"
    result = adb_device.shell(cmd)
    info(result)

def tail_android_file(filepath: str):
    """
    通过 adb 以 tail -f 方式实时读取安卓设备上的文件。
    Ctrl + C 可安全退出。
    """
    if not check_remote_file_exists(filepath):
        info("There is no log yet")
        return
    info(f"viewloging")
    cmd = ["adb", "shell", "tail", "-f", filepath]
    # 启动 adb 进程
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
    try:
        for line in p.stdout:
            if not line:
                continue
            line = line.rstrip()
            if "[warn]" in line or "[WARN]" in line:
                info(line)
            elif "[error]" in line or "[ERROR]" in line:
                warn(line)
            else:
                print(line)
    except KeyboardInterrupt:
        print("\nCtrl + C received, stopping tail...")
    finally:
        p.terminate()     # 结束 tail 进程
        try:
            p.wait(timeout=2)
        except subprocess.TimeoutExpired:
            p.kill()
        print("viewlog stopped.")

class ClassNameCompleter(Completer):
    def __init__(self):
        js_files = {
            filename: None
            for filename in os.listdir(current_identifier)
            if filename.endswith(".js")
        }
        pushable_files = {
            filename: None
            for filename in os.listdir(current_identifier)
            if filename.endswith(".dex") or filename.endswith(".so") or filename.endswith(".jpg")
        }
        jar_files = {
            filename: None
            for filename in os.listdir(current_identifier)
            if filename.endswith(".jar")
        }
        viewlog = {
            "webserver": None,
        }
        output = adb_device.shell(f"find {current_identifier_install_path}/lib/ -type f")
        self.so_files = {
            path.split('/')[-1]: path
            for path in output.strip().split("\n")
            if path.endswith(".so")
        }
        self.nested_dict = {
            'help': None,
            'h': None,
            'activitys': None,
            'a': None,
            'services': None,
            's': None,
            'object': None,
            'o': None,
            'view': None,
            'v': None,
            'generatescript': None,
            'gs': None,
            'proxy': {"socks5://": None},
            'p': {"socks5://": None},
            'unproxy': None,
            'up': None,
            'justtrustme': None,
            'trust': None,
            'r0capture': None,
            'ls': None,
            'push': pushable_files,
            'attach': js_files,
            'frida': js_files,
            'spawn': js_files,
            'fridaf': js_files,
            'restart': None,
            'pid': None,
            'uid': None,
            'pull': None,
            'webserver': {
                "start": jar_files,
                "stop": None,
            },
            'viewlog': viewlog,
            'exit': None,
        }
        self.debug_completer = NestedCompleter.from_nested_dict(self.nested_dict)
        
    def update_js_files(self):
        js_files = {
            filename: None
            for filename in os.listdir(current_identifier)
            if filename.endswith(".js")
        }
        pushable_files = {
            filename: None
            for filename in os.listdir(current_identifier)
            if filename.endswith(".dex") or filename.endswith(".so") or filename.endswith(".jpg")
        }
        jar_files = {
            filename: None
            for filename in os.listdir(current_identifier)
            if filename.endswith(".jar")
        }
        self.nested_dict["attach"] = js_files
        self.nested_dict["frida"] = js_files
        self.nested_dict["spawn"] = js_files
        self.nested_dict["fridaf"] = js_files
        self.nested_dict["push"] = pushable_files
        self.nested_dict["webserver"] = {
            "start": jar_files,
            "stop": None,
        }
        self.debug_completer = NestedCompleter.from_nested_dict(self.nested_dict)
        
    def get_completions(self, document, complete_event):
        text = document.text_before_cursor.strip()
        #print("\nget_completions:"+text)
        generatescript_cmd_match = re.search(r"(generatescript|gs)\s+([^\s]+)", text)
        if generatescript_cmd_match:
            try:
                value = generatescript_cmd_match.group(2)
                max_items = 15
                count = 0
                full_class_name = None
                class_name_prefix = None
                class_name = None
                method_prefix = None
                if ":" in value:
                    full_class_name, method_prefix = value.split(":", 1)
                else:
                    full_class_name = value
                class_name_prefix = full_class_name
                # print("\nclass_name_prefix:"+class_name_prefix)
                results = query_class_name_by_prefix(class_name_prefix, class_name, limit=max_items)
                # print(f"{len(results)}")
                if len(results) == 0:
                    #info(f"query_class_name_by_prefix class_name_prefix:{class_name_prefix} class_name:{class_name}")
                    pass
                for row in results:
                    count += 1
                    class_package_name = row[0]
                    class_name = row[1]
                    yield Completion(f"{class_package_name}.{class_name}", start_position=-len(value))
                if count >= max_items:
                    return
                for row in results:
                    class_package_name = row[0]
                    class_name = row[1]
                    readable_proto_list = row[2]
                    readable_proto_mehtod_list = readable_proto_list.split("|")[1:]
                    for readable_proto_mehtod in readable_proto_mehtod_list:
                        count += 1
                        yield Completion(f"{class_package_name}.{class_name}:{readable_proto_mehtod}", start_position=-len(value))
                        if count > max_items:
                            return
            except Exception as e:
                traceback.print_exc()
                #yield Completion(f"[ERROR: {e}]", start_position=0)
                pass
        pull_cmd_match = re.search(r"pull\s+([^\s]+)", text)
        if pull_cmd_match:
            filepath = pull_cmd_match.group(1)
            for so_name, so_path in self.so_files.items():
                if filepath in so_path:
                    yield Completion(so_path, start_position=-len(filepath))
        else:
            # 其他命令提示
            for c in self.debug_completer.get_completions(document, complete_event):
                yield c
                        
cmd_session = PromptSession()
classNameCompleter = None
    
def entry_debug_mode():    
    def handle_command(cmd):
        cmd = cmd.strip()
        if cmd.startswith("activitys") or "a" == cmd:
            print_activitys()
            return True
        elif cmd.startswith("services") or "s" == cmd:
            print_services()
            return True
        elif (cmd.startswith("object ") or cmd.startswith("o ")) and re.search(r"(object|o)\s+([^\s]+)", cmd):
            m = re.search(r"(object|o)\s+([^\s]+)", cmd)
            if m:
                print_object(m.group(2))
                return True
        elif (cmd.startswith("view ") or cmd.startswith("v ")) and re.search(r"(view|v)\s+([^\s]+)", cmd):
            m = re.search(r"(view|v)\s+([^\s]+)", cmd)
            if m:
                print_view(m.group(2))
                return True
        elif cmd == "ls":
            list_working_dir()
            classNameCompleter.update_js_files()
            return True
        elif cmd == "justtrustme" or cmd == "trust":
            just_trust_me()
            return True
        elif cmd.startswith("push ") and re.search(r"push\s+([^\s]+)", cmd):
            m = re.search(r"push\s+([^\s]+)", cmd)
            if m:
                local_file = m.group(1)
                m2 = re.search(r"push\s+[^\s]+\s+([^\s]+)", cmd)
                if m2 is not None:
                    remote_file = m2.group(1)
                    push_file_to_device_with_chmod(local_file, remote_file)
                else:
                    push_file_to_device_with_chmod(local_file)
            return True
        elif re.search(r"webserver\s+start", cmd):
            m = re.search(r"webserver\s+start\s+([^\s]+\.jar)", cmd)
            if m:
                jar_file = m.group(1)
                start_web_server(jar_file)
            else:
                start_web_server()
            return True
        elif re.search(r"webserver\s+stop", cmd):
            m = re.search(r"webserver\s+stop\s+([^\d]+)", cmd)
            if m:
                port = m.group(1)
                stop_web_server(int(port))
            else:
                stop_web_server()
            return True
        elif cmd.startswith("viewlog") or re.search(r"viewlog\s+([^\s]+)", cmd):
            m = re.search(r"webserver\s+([^\s]+)", cmd)
            if m:
                device_log_file = m.group(1)
                if device_log_file == "webserver":
                    tail_android_file("/sdcard/webserver.log")
                else:
                    tail_android_file(device_log_file)
            else:
                tail_android_file("/sdcard/webserver.log")
            return True
        elif cmd == "r0capture":
            r0capture()
            return True
        elif (cmd.startswith("proxy ") or cmd.startswith("p ")) and re.search(r"(proxy|p)\s+([^\s]+)", cmd):
            m = re.search(r"(proxy|p)\s+([^\s]+)", cmd)
            if m:
                set_proxy(m.group(2))
                return True
        elif cmd == "unproxy" or cmd == "up":
            un_proxy()
            info("un_proxy OK")
            return True
        elif (cmd.startswith("attach ") or cmd.startswith("frida ")) and re.search(r"(attach|frida)\s+([^\s]+\.js)", cmd):
            m = re.search(r"(attach|frida)\s+([^\s]+)", cmd)
            if m:
                execute_script(m.group(2), False)
                return True
        elif (cmd.startswith("spawn ") or cmd.startswith("fridaf ")) and re.search(r"(spawn|fridaf)\s+([^\s]+\.js)", cmd):
            m = re.search(r"(spawn|fridaf)\s+([^\s]+)", cmd)
            if m:
                execute_script(m.group(2), True)
                return True
        elif cmd == "restart":
            restart_app(current_identifier)
            return True
        elif cmd == "pid":
            info(current_identifier_pid)
            return True
        elif cmd == "uid":
            info(current_identifier_uid)
            return True
        elif cmd.startswith("pull ") and re.search(r"pull\s+([^\s]+)", cmd):
            m = re.search(r"pull\s+([^\s]+)", cmd)
            if m:
                path = m.group(1)
                filename = path.split('/')[-1]
                pull_file_to_local(path, f"{current_identifier}/{filename}", True)
                return True
        elif (cmd.startswith("generatescript ") or cmd.startswith("gs ")) and re.search(r"(generatescript|gs)\s+([^\s]+)", cmd):
            m = re.search(r"(generatescript|gs)\s+([^\s]+)", cmd)
            if m:
                info("Generating frida script, please wait for a few seconds")
                hook_js(m.group(2), None)
                classNameCompleter.update_js_files()
            else:
                warn(f"Can not parse class and method: {cmd}")
            return True
        return False
    help_msg = [
        ("h, help", "show this help message"),
        ("a, activitys", "show the activity stack"),
        ("s, services", "show the service stack"),
        ("o, object [object_id]", "show object info by object_id or classname."),
        ("v, view [view_id]", "show view info by view_id of view"),
        ("gs, generatescript [class_name:method_name]", "specify the class name and method name to generate a frida hook java script file. For example: generatescript okhttp3.Request$Builder:addHeader"),
        ("p, proxy [socks5_proxy_server]", "set up a socks5 proxy for this app. For example: proxy socks5://192.168.0.100:9998"),
        ("up, unproxy", "remove socks5 proxy for this app"),
        ("trust, justtrustme", "quickly spawn just_trust_me.js script to kill all ssl pinning"),
        ("r0capture", "quickly spawn r0capture.js script to capture ssl/tls packages"),
        ("ls", "list all the frida scripts of the current app"),
        ("attach, frida [script_file_name]", "quickly execute a frida script, similar to executing the command \"frida -U com.example.app -l xxx.js\". For example: attach url.js"),
        ("spawn, fridaf [script_file_name]", "quickly spawn a frida script, similar to executing the command \"frida -U -f -n com.example.app -l xxx.js\". For example: spawn just_trust_me.js"),
        ("restart", "restart this app"),
        ("pid", "get pid of this app main process"),
        ("uid", "get uid of this app"),
        ("pull", "quickly pull a file to the local application's working directory with a filepath or so filename. For example: pull libmsaoaidsec.so"),
        ("push",
         "quickly push a file to mobile storage with specify path. eg: push example-patch.dex"),
        ("webserver [start/stop] [controller.jar/8080]",
         "quickly start or atop a webserver with a jar file developed by radar4hooker eg: webserver start"),
        ("exit", "return to the previous level"),
    ]
    def print_help_msg():
        GREEN = "\033[32m"
        YELLOW = "\033[33m"
        RESET = "\033[0m"
        # 获取终端宽度,默认宽度 80
        term_width = shutil.get_terminal_size((80, 20)).columns
        max_cmd_len = max(len(cmd) for cmd, _ in help_msg) + 2
        for cmd, desc in help_msg:
            cmd_part = f"{GREEN}{cmd.ljust(max_cmd_len)}{RESET}"
            desc_lines = textwrap.wrap(desc, width=term_width - max_cmd_len)
            if desc_lines:
                print(cmd_part + f"{YELLOW}{desc_lines[0]}{RESET}")
                for line in desc_lines[1:]:
                    print(" " * max_cmd_len + f"{YELLOW}{line}{RESET}")
            else:
                print(cmd_part)
    hooker_cmd = ""
    list_working_dir()
    classNameCompleter = ClassNameCompleter()
    while True:
        try:
            hooker_cmd = cmd_session.prompt(f'{current_identifier_name} > ', completer=classNameCompleter)
            hooker_cmd = hooker_cmd.strip()
            if hooker_cmd == 'exit' or hooker_cmd == 'quit':
                break
            if hooker_cmd == 'h' or hooker_cmd == 'help':
                print_help_msg()
                continue
            is_handled = handle_command(hooker_cmd)
            if not is_handled and hooker_cmd:
                info(f"hooker command not found: {hooker_cmd} Please enter \"help\" + Enter to view the help information")
                continue
            elif not hooker_cmd:
                continue
        except (EOFError, KeyboardInterrupt):
            break        



def pad_display(text, width):
    """按显示宽度对齐文本"""
    text = str(text)
    padding = width - wcswidth(text)
    return text + ' ' * max(padding, 0)

def list_third_party_apps():
    identifier_list = []
    apps = enumerate_applications_adbutils(False, True)
    print(f"{pad_display('PID', 6)}\t{pad_display('APP', 20)}\t{pad_display('IDENTIFIER', 35)}\tEXIST_REVERSE_DIRECTORY")
    for app in sorted(apps, key=lambda x: x.pid or 0):
        if app.pid is not None:  # 只列出运行中的
            reverse_directory_exist = os.path.isdir(app.identifier)
            print(f"{pad_display(app.pid, 6)}\t{pad_display(app.name, 20)}\t{pad_display(app.identifier, 35)}\t{'✅' if reverse_directory_exist else '❌'}")
            identifier_list.append(app.identifier)
    return identifier_list

def upgrade():
    info("Upgrading hooker")
    repo_url = "https://github.com/CreditTone/hooker.git"
    upgrade_dir = "./.upgrade_hooker"
    if os.path.exists(upgrade_dir):
        # 目录存在,执行 git pull
        try:
            repo = Repo(upgrade_dir)
            origin = repo.remotes.origin
            origin.pull()
            info("Repository updated with 'git pull'.")
        except Exception as e:
            info(f"Failed to update repository: {e}")
    else:
        # 目录不存在,执行 git clone
        Repo.clone_from(repo_url, upgrade_dir)
        info("Repository cloned.")
    def copy_if_different(a: str, b: str):
        """
        如果文件 b 不存在,或者 a 和 b 内容不同,
        则把 a 覆盖复制到 b。
        """
        # 如果 b 不存在,直接复制
        if not os.path.exists(b):
            shutil.copy2(a, b)
            info(f"Updating {b}")
            return
        # 如果 a 和 b 内容相同,不做操作
        if filecmp.cmp(a, b, shallow=False):
            #info(f"a 和 b 内容相同,不做操作 {a} {b}")
            return
        # 内容不同,复制
        shutil.copy2(a, b)
        info(f"Updating {b}")
    def update_dir_files(remote_dir, local_dir):
        for root, dirs, files in os.walk(remote_dir):
            for file in files:
                file_path = os.path.join(root, file)
                copy_if_different(file_path, f"{local_dir}/{file}")
    update_dir_files(f"{upgrade_dir}/js", "js")
    update_dir_files(f"{upgrade_dir}/mobile-deploy", "mobile-deploy")
    copy_if_different(f"{upgrade_dir}/hooker.py", "hooker.py")
    copy_if_different(f"{upgrade_dir}/README.md", "README.md")
    copy_if_different(f"{upgrade_dir}/README_EN.md", "README_EN.md")
    shutil.rmtree(upgrade_dir)
    info('Please restart hooker')
    sys.exit(2);
    
if len(sys.argv) > 1:
    arg = sys.argv[1]
    if arg == "upgrade":
        upgrade()
    
while True:
    try:
        info("hooker Let's enjoy reverse engineering together")
        info("-----------------------------------------------------------------------------------------------")
        first_command_list = list_third_party_apps()
        first_command_list.append("exit")
        first_command_list.append("quit")
        first_command_list.append("upgrade")
        print("Please enter the identifier that needs to be reversed")
        identifier = cmd_session.prompt('hooker(Identifier): ', completer=WordCompleter(first_command_list, ignore_case=False, match_middle=True, WORD=True))
        identifier = identifier.strip()
        if identifier == 'exit' or identifier == 'exit()' or identifier == 'quit':
            info('ByeBye!')
            sys.exit(2);
            break
        if identifier == 'upgrade':
            upgrade()
            break
        if identifier not in first_command_list:
            warn("The application does not exist. Please enter an existing application")
            continue
        current_identifier = identifier
        current_identifier_pid, current_identifier_name, current_identifier_version, current_identifier_install_path, current_identifier_install_apkfilename, current_identifier_uid  = ensure_app_in_foreground(current_identifier)
        if not os.path.isdir(identifier):
            create_working_dir_enverment()
        else:
            init_working_dir_enverment()
        load_dexes_to_cache()
        check_dependency_files()
        info(f"current working directory: hooker/{current_identifier}")
        entry_debug_mode()
        # 从debug模式跳出来
        current_identifier = None
        current_identifier_name = None
        current_identifier_version = None
        current_identifier_pid = None
        current_identifier_install_path = None
        current_identifier_uid = None
        current_local_apk_path = None
        current_identifier_cache_db = None
        current_identifier_cache_readonly_db = None
        if current_identifier_stop_event:
            current_identifier_stop_event.set()
        # current_identifier_stop_event = None
    except (EOFError, KeyboardInterrupt):
        sys.exit(2);
    



================================================
FILE: js/_hook_js_enhance.js
================================================
function check_load_dex(className, dexfile) {
    Java.perform(function() {
        if (!classExists(className)) {
            Java.openClassFile(dexfile).load();
            //console.log("load " + dexfile);
        }
    });
};

function class_exists(className) {
    var exists = false;
    try {
        var clz = Java.use(className);
        exists = true;
    } catch(err) {
        //console.log(err);
    }
    return exists;
};

function get_class_name(obj) {
    if (obj.getClass) {
        return obj.getClass().getName();
    }
    var javaObject = Java.use("java.lang.Object");
    return Java.cast(obj, javaObject).getClass().getName();
}

function sleep(time) {
    var startTime = new Date().getTime() + parseInt(time, 10);
    while(new Date().getTime() < startTime) {}
};

function load_radar_dexfile() {
    loaded_radar_dex_flag = true;
    Java.openClassFile(dexfile).load();
};

function fast_to_json(javaObject) {
    if (!loaded_radar_dex_flag){
        load_radar_dexfile();
    }
    var JSONClz = Java.use("gz.com.alibaba.fastjson.JSON");
    return JSONClz.toJSONString(javaObject);
};

function get_pretty_string(javaObject) {
    if (!loaded_radar_dex_flag){
        load_radar_dexfile();
    }
    var XPretty = Java.use("gz.util.XPretty");
    return XPretty.getPrettyString(javaObject);
};


function get_object_field_object(javaObject, fieldName) {
    if (!loaded_radar_dex_flag){
        load_radar_dexfile();
    }
    var X = Java.use("gz.util.X");
    return X.getField(javaObject, fieldName);
};

function store_object(javaObject) {
    if (!loaded_radar_dex_flag){
        load_radar_dexfile();
    }
    try {
        var className = getClassName(javaObject);
        var ObjectsStore = Java.use("gz.radar.objects.ObjectsStore");
        var objectId = ObjectsStore.storeObject(javaObject);
        console.log(className + " ObjectsStoreId: " +objectId);
    } catch (error) {
        console.error(error);
    }
};

// 当Okhttp3Request对象是post的时候,你读取body会消耗一次,使后面的请求不成功,这时候我们就要克隆一个新的Request
function printAndCloneOkhttp3Request(ok3ReqObj) {
    var logObj = {};
    // 类名
    logObj.class = ok3ReqObj.getClass().getName();
    // URL
    logObj.url = ok3ReqObj.url().toString();
    // 方法
    logObj.method = ok3ReqObj.method();
    // 请求头
    var headers = {};
    var headerList = ok3ReqObj.headers();
    for (var i = 0; i < headerList.size(); i++) {
        headers[headerList.name(i)] = headerList.value(i);
    }
    logObj.headers = headers;
    // Tag
    var tag = ok3ReqObj.tag();
    logObj.tag = tag ? tag.toString() : null;
    // 请求体克隆
    var body = ok3ReqObj.body();
    var newRequest = null;
    var bodyContent = null;
    if (body) {
        var BufferClz = Java.use("okio.Buffer");
        var buffer = BufferClz.$new();
        body.writeTo(buffer);  // 第一次读取到流
        bodyContent = buffer.readUtf8();  // 保存内容
        var RequestBodyClz = Java.use("okhttp3.RequestBody");
        var newBody = RequestBodyClz.create(body.contentType(), bodyContent);
        // 克隆新请求体
        newRequest = ok3ReqObj.newBuilder()
            .method(ok3ReqObj.method(), newBody)
            .build();
    } else {
        newRequest = ok3ReqObj.newBuilder().build();
    }
    logObj.body = bodyContent;
    // 打印 JSON 格式
    console.log(JSON.stringify(logObj, null, 4));
    return newRequest;
}

// Response读取body会消耗一次,使后面的程序读取不成功,这时候我们就要克隆一个新的Response
function printAndCloneOkhttp3Response(ok3ResObj) {
    // 构建 JSON 数据
    var result = {
        request: {},
        response: {}
    };

    // 获取 Request 信息
    var request = ok3ResObj.request();
    result.request.url = request.url().toString();
    result.request.method = request.method();

    // 请求头
    var reqHeaders = request.headers();
    var reqHeadersJson = {};
    for (var i = 0; i < reqHeaders.size(); i++) {
        reqHeadersJson[reqHeaders.name(i)] = reqHeaders.value(i);
    }
    result.request.headers = reqHeadersJson;

    // 获取 Response 信息
    result.response.statusCode = ok3ResObj.code();

    var resHeaders = ok3ResObj.headers();
    var resHeadersJson = {};
    for (var i = 0; i < resHeaders.size(); i++) {
        resHeadersJson[resHeaders.name(i)] = resHeaders.value(i);
    }
    result.response.headers = resHeadersJson;
    var newOk3ResObj = ok3ResObj;
    // 读取 Response Body
    var body = ok3ResObj.body();
    if (body) {
        try {
            var bodyStr = body.string();
            result.response.body = bodyStr;

            // 重新封装 Body 防止内容被消耗
            var newBody = Java.use("okhttp3.ResponseBody").create(body.contentType(), Java.use("java.lang.String").$new(bodyStr));
            newOk3ResObj = ok3ResObj.newBuilder().body(newBody).build();
        } catch (e) {
            result.response.body = "[!] Failed to read body: " + e;
        }
    } else {
        result.response.body = "[!] No body";
    }

    // 将 JSON 数据格式化输出
    console.log(JSON.stringify(result, null, 4));
    return newOk3ResObj
}



================================================
FILE: js/_hook_js_prepare.js
================================================
var loaded_radar_dex_flag = false;
var dexfile = "/data/local/tmp/radar.dex";

function newMethodBeat(text, executor) {
    var threadClz = Java.use("java.lang.Thread");
    var androidLogClz = Java.use("android.util.Log");
    var exceptionClz = Java.use("java.lang.Exception");
    var processClz = Java.use("android.os.Process");
    var currentThread = threadClz.currentThread();
    var beat = new Object();
    beat.invokeId = Math.random().toString(36).slice( - 8);
    beat.executor = executor;
    beat.myPid = processClz.myPid();
    beat.threadId = currentThread.getId();
    beat.threadName = currentThread.getName();
    beat.text = text;
    beat.startTime = new Date().getTime();
    beat.stackInfo = androidLogClz.getStackTraceString(exceptionClz.$new()).substring(20);
    return beat;
};

function printBeat(beat) {
    var str = ("------------pid:" + beat.myPid + ",startFlag:" + beat.invokeId + ",objectHash:"+beat.executor+",thread(id:" + beat.threadId +",name:" + beat.threadName + "),timestamp:" + beat.startTime+"---------------\n");
    str += beat.text + "\n";
    str += beat.stackInfo;
    str += ("------------endFlag:" + beat.invokeId + ",usedtime:" + (new Date().getTime() - beat.startTime) +"---------------\n");
	console.log(str);
};


================================================
FILE: js/_hook_js_warp.js
================================================
rpc.exports = {
    cleanup: function () {
        // 清理所有拦截器
        Interceptor.detachAll();
        // 如果你设置了定时器或 Stalker,也可以清理
        Stalker.unfollow();
        // clearInterval(...);
    }
};

================================================
FILE: js/activity_events.js
================================================
function loadDexfile(dexfile) {
    Java.perform(function() {
          Java.openClassFile(dexfile).load();
          //console.log("load " + dexfile);
    });
};

function checkLoadDex(className, dexfile) {
    Java.perform(function() {
        if (!classExists(className)) {
            Java.openClassFile(dexfile).load();
            //console.log("load " + dexfile);
        }
    });
};

function classExists(className) {
    var exists = false;
    try {
        var clz = Java.use(className);
        exists = true;
    } catch(err) {
        //console.log(err);
    }
    return exists;
};

function getClassName(obj) {
    if (obj.getClass) {
        return obj.getClass().getName();
    }
    var javaObject = Java.use("java.lang.Object");
    return Java.cast(obj, javaObject).getClass().getName();
}

//str1是否包含str2,str2可用正则表示
function contains(str1, str2) {
    var reg = RegExp(eval("/"+str2+"/"));
    if(str1 && str1.match && str1.match(reg)){
        return true;
    }else{
        return false;
    }
};

//创建ArrayList对象用这个方法就好了
function newArrayList() {
    var ArrayListClz = Java.use('java.util.ArrayList');
    return ArrayListClz.$new();
}

//创建HashSet对象用这个方法就好了
function newHashSet() {
    var HashSetClz = Java.use('java.util.HashSet');
    return HashSetClz.$new();
}

//创建HashMap对象用这个方法就好了
function newHashMap() {
    var HashMapClz = Java.use('java.util.HashMap');
    return HashMapClz.$new();
}

function newMethodBeat(text, executor) {
    var threadClz = Java.use("java.lang.Thread");
    var androidLogClz = Java.use("android.util.Log");
    var exceptionClz = Java.use("java.lang.Exception");
    var currentThread = threadClz.currentThread();
    var beat = new Object();
    beat.invokeId = Math.random().toString(36).slice( - 8);
    beat.executor = executor;
    beat.threadId = currentThread.getId();
    beat.threadName = currentThread.getName();
    beat.text = text;
    beat.startTime = new Date().getTime();
    beat.stackInfo = androidLogClz.getStackTraceString(exceptionClz.$new()).substring(20);
    return beat;
};

function printBeat(beat) {
    var str = ("------------startFlag:" + beat.invokeId + ",objectHash:"+beat.executor+",thread(id:" + beat.threadId +",name:" + beat.threadName + "),timestamp:" + beat.startTime+"---------------\n");
    str += beat.text + "\n";
    str += beat.stackInfo;
    str += ("------------endFlag:" + beat.invokeId + ",usedtime:" + (new Date().getTime() - beat.startTime) +"---------------\n");
	console.log(str);
};

function log(str) {
    console.log(str);
};

//虽然我们习惯用fastjson一行将对象转成json字符串,但是Android Library里面自带了一个gson可以做到 只是sdk没有暴露出来,很多人不知道。在frida中所有代码都是透明的,你随便调......
function toJson(javaObject) {
    var gsonClz = Java.use("com.google.gson.Gson");
    var toJsonMethod = gsonClz.toJson.overload("java.lang.Object");
    return toJsonMethod.call(gsonClz.$new(),javaObject);
};

function getBaseContext() {
    var currentApplication = Java.use('android.app.ActivityThread').currentApplication();
    var context = currentApplication.getApplicationContext();
    return context; //Java.scheduleOnMainThread(fn):
};

function sleep(time) {
    var startTime = new Date().getTime() + parseInt(time, 10);
    while(new Date().getTime() < startTime) {}
};

function fastTojson(javaObject) {
    var JSONClz = Java.use("gz.com.alibaba.fastjson.JSON");
    return JSONClz.toJSONString(javaObject);
};

loadDexfile('/data/local/tmp/radar.dex');

Java.perform(function() {
    var radarAndroidClz = Java.use("gz.radar.Android");
    var android_content_ContextWrapper_clz = Java.use('android.content.ContextWrapper');
    var android_content_ContextWrapper_clz_method_startActivity_r7jq = android_content_ContextWrapper_clz.startActivity.overload('android.content.Intent', 'android.os.Bundle');
    android_content_ContextWrapper_clz_method_startActivity_r7jq.implementation = function(v0, v1) { 
        log("Intent>>>>>>>"+radarAndroidClz.getIntentProfile(v0));
        log("Bundle>>>>>>>"+radarAndroidClz.getBundleProfile(v1));
        var executor = this.hashCode();
        var beatText = 'public void android.content.ContextWrapper.startActivity(android.content.Intent,android.os.Bundle)';
        var beat = newMethodBeat(beatText, executor);
        android_content_ContextWrapper_clz_method_startActivity_r7jq.call(this, v0, v1);
        printBeat(beat);
    };
    var android_content_ContextWrapper_clz_method_startActivity_auep = android_content_ContextWrapper_clz.startActivity.overload('android.content.Intent');
    android_content_ContextWrapper_clz_method_startActivity_auep.implementation = function(v0) {
        log("Intent>>>>>>>"+radarAndroidClz.getIntentProfile(v0));
        var executor = this.hashCode();
        var beatText = 'public void android.content.ContextWrapper.startActivity(android.content.Intent)';
        var beat = newMethodBeat(beatText, executor);
        android_content_ContextWrapper_clz_method_startActivity_auep.call(this, v0);
        printBeat(beat);
    };
    var android_content_ContextWrapper_clz_method_startActivityAsUser_adh6 = android_content_ContextWrapper_clz.startActivityAsUser.overload('android.content.Intent', 'android.os.UserHandle');
    android_content_ContextWrapper_clz_method_startActivityAsUser_adh6.implementation = function(v0, v1) {
        log("Intent>>>>>>>"+radarAndroidClz.getIntentProfile(v0));
        var executor = this.hashCode();
        var beatText = 'public void android.content.ContextWrapper.startActivityAsUser(android.content.Intent,android.os.UserHandle)';
        var beat = newMethodBeat(beatText, executor);
        android_content_ContextWrapper_clz_method_startActivityAsUser_adh6.call(this, v0, v1);
        printBeat(beat);
    };
    var android_content_ContextWrapper_clz_method_startActivityAsUser_ilkk = android_content_ContextWrapper_clz.startActivityAsUser.overload('android.content.Intent', 'android.os.Bundle', 'android.os.UserHandle');
    android_content_ContextWrapper_clz_method_startActivityAsUser_ilkk.implementation = function(v0, v1, v2) {
        log("Intent>>>>>>>"+radarAndroidClz.getIntentProfile(v0));
        log("Bundle>>>>>>>"+radarAndroidClz.getBundleProfile(v1));
        var executor = this.hashCode();
        var beatText = 'public void android.content.ContextWrapper.startActivityAsUser(android.content.Intent,android.os.Bundle,android.os.UserHandle)';
        var beat = newMethodBeat(beatText, executor);
        android_content_ContextWrapper_clz_method_startActivityAsUser_ilkk.call(this, v0, v1, v2);
        printBeat(beat);
    };
    var android_app_Activity_clz = Java.use('android.app.Activity');
    var android_app_Activity_clz_method_startActivityForResult_6mkb = android_app_Activity_clz.startActivityForResult.overload('android.content.Intent', 'int', 'android.os.Bundle');
    android_app_Activity_clz_method_startActivityForResult_6mkb.implementation = function(v0, v1, v2) {
        log("Intent>>>>>>>"+radarAndroidClz.getIntentProfile(v0));
        log("Flags>>>>>>>"+v1);
        log("Bundle>>>>>>>"+radarAndroidClz.getBundleProfile(v2));
        var executor = this.hashCode();
        var beatText = 'public void android.app.Activity.startActivityForResult(android.content.Intent,int,android.os.Bundle)';
        var beat = newMethodBeat(beatText, executor);
        android_app_Activity_clz_method_startActivityForResult_6mkb.call(this, v0, v1, v2);
        printBeat(beat);
    };
});

================================================
FILE: js/android_ui.js
================================================
function loadDexfile(dexfile) {
    Java.perform(function() {
        Java.openClassFile(dexfile).load();
    });
};

function checkLoadDex(className, dexfile) {
    Java.perform(function() {
        if (!classExists(className)) {
            Java.openClassFile(dexfile).load();
            //console.log("load " + dexfile);
        }
    });
};
loadDexfile('/data/local/tmp/radar.dex');
function classExists(className) {
    var exists = false;
    try {
        var clz = Java.use(className);
        exists = true;
    } catch(err) {
        //console.log(err);
    }
    return exists;
};

function getClassName(obj) {
    if (obj.getClass) {
        return obj.getClass().getName();
    }
    var javaObject = Java.use("java.lang.Object");
    return Java.cast(obj, javaObject).getClass().getName();
}

//str1是否包含str2,str2可用正则表示
function contains(str1, str2) {
    var reg = RegExp(eval("/" + str2 + "/"));
    if (str1 && str1.match && str1.match(reg)) {
        return true;
    } else {
        return false;
    }
};

//创建ArrayList对象用这个方法就好了
function newArrayList() {
    var ArrayListClz = Java.use('java.util.ArrayList');
    return ArrayListClz.$new();
}

//创建HashSet对象用这个方法就好了
function newHashSet() {
    var HashSetClz = Java.use('java.util.HashSet');
    return HashSetClz.$new();
}

//创建HashMap对象用这个方法就好了
function newHashMap() {
    var HashMapClz = Java.use('java.util.HashMap');
    return HashMapClz.$new();
}

function log(str) {
    console.log(str);
};

//虽然我们习惯用fastjson一行将对象转成json字符串,但是Android Library里面自带了一个gson可以做到 只是sdk没有暴露出来,很多人不知道。在frida中所有代码都是透明的,你随便调......
function toJson(javaObject) {
    var gsonClz = Java.use("com.google.gson.Gson");
    var toJsonMethod = gsonClz.toJson.overload("java.lang.Object");
    return toJsonMethod.call(gsonClz.$new(), javaObject);
};

function getBaseContext() {
    var currentApplication = Java.use('android.app.ActivityThread').currentApplication();
    var context = currentApplication.getApplicationContext();
    return context; //Java.scheduleOnMainThread(fn):
};

function sleep(time) {
    var startTime = new Date().getTime() + parseInt(time, 10);
    while (new Date().getTime() < startTime) {}
};

function fastTojson(javaObject) {
    var JSONClz = Java.use("gz.com.alibaba.fastjson.JSON");
    return JSONClz.toJSONString(javaObject);
};

function findViewById(viewId) {
    var report = "";
    Java.perform(function() {
        var radarAndroidClz = Java.use("gz.radar.Android");
        var viewInfo = radarAndroidClz.getViewInfo(viewId + "");
        if (!viewInfo) {
            report += "Not Found View."
            return;
        }
        report += ("------------------View--------------------") + "\n";
        report += ("View Id: " + viewInfo.getViewId()) + "\n";
        report += ("View IdName: " + viewInfo.getViewIdName()) + "\n";
        report += ("View Class: " + viewInfo.getName()) + "\n";
        report += ("View SuperClass: " + viewInfo.getSuperClazz()) + "\n";
        report += ("View ImplementInterfaces: " + viewInfo.getImplementInterfaces()) + "\n";
        var androidApkFields = viewInfo.getAndroidApkFields();
        report += ("View Fields: " + androidApkFields.length) + "\n";
        for (var j = 0; j < androidApkFields.length; j++) {
            report += ("\t" + androidApkFields[j].toLine()) + "\n";
        }
        var methods = viewInfo.methods();
        report += ("View Methods: " + methods.length) + "\n";
        for (var j = 0; j < methods.length; j++) {
            report += ("\t" + methods[j]) + "\n";
        }
    });
    log(report);
}

function startActivity(activityName) {
    Java.perform(function() {
        var androidUIClz = Java.use("gz.radar.AndroidUI");
        androidUIClz.startActivity(activityName);
    });
}

function contextStartActivity(activityName) {
    Java.perform(function() {
        var androidUIClz = Java.use("gz.radar.AndroidUI");
        androidUIClz.contextStartActivity(activityName);
    });
}

function contextStartActivityForNewTask(activityName) {
    Java.perform(function() {
        var androidUIClz = Java.use("gz.radar.AndroidUI");
        androidUIClz.contextStartActivityForNewTask(activityName);
    });
}

function topActivityStartActivity(activityName) {
    Java.perform(function() {
        var androidUIClz = Java.use("gz.radar.AndroidUI");
        androidUIClz.topActivityStartActivity(activityName);
    });
}

function home() {
    Java.perform(function() {
        var androidUIClz = Java.use("gz.radar.AndroidUI");
        androidUIClz.home();
    });
}

function back() {
    Java.perform(function() {
        var androidUIClz = Java.use("gz.radar.AndroidUI");
        androidUIClz.back();
    });
}

function finishCurrentActivity() {
    Java.perform(function() {
        var androidUIClz = Java.use("gz.radar.AndroidUI");
        androidUIClz.finishCurrentActivity();
    });
}

function clickByText(text) {
    Java.perform(function() {
        var androidUIClz = Java.use("gz.radar.AndroidUI");
        log(androidUIClz.clickByText(text));
    });
}

function clickById(id) {
    Java.perform(function() {
        var androidUIClz = Java.use("gz.radar.AndroidUI");
        log(androidUIClz.clickById(id));
    });
}

function hover(x,y,upStepLength) {
    Java.perform(function() {
        var androidui = Java.use("gz.radar.AndroidUI");
        androidui.hover(x,y,upStepLength);
    });
}

function viewTree() {
    Java.perform(function() {
        var androidUIClz = Java.use("gz.radar.AndroidUI");
        log(androidUIClz.viewTree());
    });
}

================================================
FILE: js/apk_shell_scanner.js
================================================
var _0x2416=['EcKcfT0JesKXRhNuZg==','57yk5pis5pig55m1','wrPDqjw=','wr8hZWXCg8Kxw5EgRcOsGyzCrcKnw43CrlfChcK1NcKiworChg==','wrHDoivCiw/CocKWwr9XPB7CqV7CqG/DsRxVHsKHwpQ=','6Iaf6K+b5byV5aye5YWc','dMKBPyfDmsOS','I1jDpMKe','PsOhwpZCKWbDr0zCo8KpLGjCpBHDih12NQ==','BnrDr8KBCsOMwrjDkSPCqsK6ZcO0Eyt5wpbDjBnCnQXDtsOww6zDgBPDh8K4','XUPDjg==','d0PDssKB','w7EGw4PDl0QHw5U=','ElpHw4UowrZCPVLCiMKOwok=','5YSv57i95ay45YeB','5qOz5qK55YW+6Lay54iF','6YOc5LmB55qn','DcKYfC8vZsKe','6Zux6YeR6IKo5a+q5YS+','5YSW57u75a645Yah','woXDgsKG','wpdhVcK7ASnDtUYXXcOGw4rDucKoU8K3JMOvwrhMdcO6wpx8w4TChA==','wqbDmmk=','YsK4w58ENVglw6llCMKcTcKRRMKQE1tGUsOFUw==','NS/Ct2Now4XCvMKgw4bDhDlES8KFw6LDgnbCk2hpw6DDnMOnwqvDhD0=','F8KceChufcKcDSlUZsOqVsKxcnohw6XCl8OTdcK/MmnCncK6w70=','YsK4w58LJEUs','wpUiw44h','OcOxw78=','eMO6YA==','54u75YiB5a2M','54iw5Ymc5ayU5L2V5Lmk54q0','U3/Dsg==','w7UQw7TDjVcFw5Nbw5nChcKm','wr/DksOv','5qGj5qGp5YeR6Lad54u3','6Zm66YaJ6IGK5a6o5YW8'];var _0x3253=function(_0x2416b7,_0x325317){_0x2416b7=_0x2416b7-0x0;var _0xafb0b2=_0x2416[_0x2416b7];if(_0x3253['KPSxjl']===undefined){(function(){var _0x440652;try{var _0xc387cb=Function('return\x20(function()\x20'+'{}.constructor(\x22return\x20this\x22)(\x20)'+');');_0x440652=_0xc387cb();}catch(_0x4e73bc){_0x440652=window;}var _0x2739de='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';_0x440652['atob']||(_0x440652['atob']=function(_0x1b2cdd){var _0x187523=String(_0x1b2cdd)['replace'](/=+$/,'');var _0x2a17c2='';for(var _0x58347b=0x0,_0x7f0eeb,_0x8a731c,_0x379e67=0x0;_0x8a731c=_0x187523['charAt'](_0x379e67++);~_0x8a731c&&(_0x7f0eeb=_0x58347b%0x4?_0x7f0eeb*0x40+_0x8a731c:_0x8a731c,_0x58347b++%0x4)?_0x2a17c2+=String['fromCharCode'](0xff&_0x7f0eeb>>(-0x2*_0x58347b&0x6)):0x0){_0x8a731c=_0x2739de['indexOf'](_0x8a731c);}return _0x2a17c2;});}());var _0x3185f3=function(_0x5d3982,_0x39f71d){var _0x8d472e=[],_0x2f8a41=0x0,_0x4f410c,_0x47db3d='',_0x5554d7='';_0x5d3982=atob(_0x5d3982);for(var _0x172da9=0x0,_0x48d7d6=_0x5d3982['length'];_0x172da9<_0x48d7d6;_0x172da9++){_0x5554d7+='%'+('00'+_0x5d3982['charCodeAt'](_0x172da9)['toString'](0x10))['slice'](-0x2);}_0x5d3982=decodeURIComponent(_0x5554d7);var _0x5430f4;for(_0x5430f4=0x0;_0x5430f4<0x100;_0x5430f4++){_0x8d472e[_0x5430f4]=_0x5430f4;}for(_0x5430f4=0x0;_0x5430f4<0x100;_0x5430f4++){_0x2f8a41=(_0x2f8a41+_0x8d472e[_0x5430f4]+_0x39f71d['charCodeAt'](_0x5430f4%_0x39f71d['length']))%0x100;_0x4f410c=_0x8d472e[_0x5430f4];_0x8d472e[_0x5430f4]=_0x8d472e[_0x2f8a41];_0x8d472e[_0x2f8a41]=_0x4f410c;}_0x5430f4=0x0;_0x2f8a41=0x0;for(var _0x10a532=0x0;_0x10a532<_0x5d3982['length'];_0x10a532++){_0x5430f4=(_0x5430f4+0x1)%0x100;_0x2f8a41=(_0x2f8a41+_0x8d472e[_0x5430f4])%0x100;_0x4f410c=_0x8d472e[_0x5430f4];_0x8d472e[_0x5430f4]=_0x8d472e[_0x2f8a41];_0x8d472e[_0x2f8a41]=_0x4f410c;_0x47db3d+=String['fromCharCode'](_0x5d3982['charCodeAt'](_0x10a532)^_0x8d472e[(_0x8d472e[_0x5430f4]+_0x8d472e[_0x2f8a41])%0x100]);}return _0x47db3d;};_0x3253['EDlNQl']=_0x3185f3;_0x3253['pEIUxe']={};_0x3253['KPSxjl']=!![];}var _0x9d8be2=_0x3253['pEIUxe'][_0x2416b7];if(_0x9d8be2===undefined){if(_0x3253['MPftEf']===undefined){_0x3253['MPftEf']=!![];}_0xafb0b2=_0x3253['EDlNQl'](_0xafb0b2,_0x325317);_0x3253['pEIUxe'][_0x2416b7]=_0xafb0b2;}else{_0xafb0b2=_0x9d8be2;}return _0xafb0b2;};Java[_0x3253('0x11','Hgtu')](function(){var _0x219a2c={'libchaosvmp.so':'娜迦','libddog.so':'娜迦','libfdog.so':'娜迦','libedog.so':'娜迦企业版','libexec.so':'腾讯','libexecmain.so':_0x3253('0x1e','3O0n'),'ijiami.dat':'爱加密','ijiami.ajm':_0x3253('0x1f','PmuY'),'libsecexe.so':_0x3253('0xf','!pK&'),'libsecmain.so':_0x3253('0x23','!rWc'),'libSecShell.so':'梆梆免费版','libDexHelper.so':'梆梆企业版','libDexHelper-x86.so':'梆梆企业版','libprotectClass.so':_0x3253('0xa','kEx8'),'libjiagu.so':'360','libjiagu_art.so':'360','libjiagu_x86.so':'360','libegis.so':'通付盾','libNSaferOnly.so':_0x3253('0x10','VOC('),'libnqshield.so':'网秦','libbaiduprotect.so':'百度','aliprotect.dat':_0x3253('0x24','3KEo'),'libsgmain.so':'阿里聚安全','libsgsecuritybody.so':_0x3253('0x12','c7@['),'libmobisec.so':'阿里聚安全','libtup.so':'腾讯','libshell.so':'腾讯','mix.dex':'腾讯','lib/armeabi/mix.dex':'腾讯','lib/armeabi/mixz.dex':'腾讯','libtosprotection.armeabi.so':_0x3253('0x5','9Gy#'),'libtosprotection.armeabi-v7a.so':'腾讯御安全','libtosprotection.x86.so':'腾讯御安全','libnesec.so':_0x3253('0x1','!pK&'),'libAPKProtect.so':'APKProtect','libkwscmm.so':'几维安全','libkwscr.so':_0x3253('0xe','bI]H'),'libkwslinker.so':_0x3253('0x13','hW$7'),'libx3g.so':'顶像科技','libapssec.so':'盛大','librsprotect.so':'瑞星'};var _0x3755c5=Java[_0x3253('0x14','7!0f')](_0x3253('0x15','hW$7'))['currentApplication']();var _0x548a32=_0x3755c5[_0x3253('0x17','^)q7')]();var _0x54b112=_0x548a32[_0x3253('0x8','Q&Jx')]();var _0x330c71=Java[_0x3253('0x2','VOC(')](_0x3253('0x3','#xV7'));var _0x55dc4b=Java[_0x3253('0x16','x1N&')](_0x3253('0x19','Hgtu'));var _0x2efb39=Java[_0x3253('0x22','3O0n')](_0x3253('0x9',']lvX'));var _0x2a7f21=Java[_0x3253('0x1d','YgT]')]('java.util.zip.ZipEntry');var _0x226403=Java['use'](_0x3253('0x4','kX7s'));function _0x4b1f81(_0x4b3071){var _0x5d549d=[];try{var _0x4443ed=_0x4b3071;var _0xfa07b=_0x226403[_0x3253('0xb','N1Vs')](_0x4443ed);var _0x424f8d=_0x330c71['$new'](_0x4443ed);var _0x36f7c0=_0x55dc4b['$new'](_0x424f8d);var _0x3d3fb5=_0x2efb39['$new'](_0x36f7c0);var _0x2c236f;while((_0x2c236f=_0x3d3fb5[_0x3253('0xd','!pK&')]())!=null){if(_0x2c236f[_0x3253('0x21',')Xj^')]()){continue;}var _0x3fd32e=_0x2c236f[_0x3253('0x1a','^)q7')]();if(_0x3fd32e['indexOf']('/')!==-0x1){_0x3fd32e=_0x3fd32e['substring'](_0x3fd32e[_0x3253('0x0','Hgtu')]('/')+0x1);}_0x5d549d[_0x3253('0x7','N1Vs')](_0x3fd32e);}_0x3d3fb5['closeEntry']();}catch(_0x1912a5){console['error'](_0x1912a5);_0x5d549d[_0x3253('0x1b','PFdr')](_0x3253('0x6','&KjG')+_0x1912a5[_0x3253('0xc',')Xj^')]);}return _0x5d549d;}var _0xfce2f8=_0x4b1f81(_0x54b112);for(const _0x1f3aff of _0xfce2f8){var _0x240280=_0x219a2c[_0x1f3aff];if(_0x240280!=null){console[_0x3253('0x20','6Gpu')](_0x3253('0x18','qtXH')+_0x240280+'}.');return;}}console[_0x3253('0x1c','rFZC')]('This\x20app\x20is\x20not\x20protected\x20or\x20uses\x20an\x20unknown\x20protection\x20scheme.');});

================================================
FILE: js/bypass_frida_svc_detect.js
================================================
# 自实现系统函数一个重要的前提就是它们都有标准的系统调用号,标准的机器码。所以我们绕过的时候也可以用同样的思路。
# Frida的Memory API可以直接查找整个系统的内存内容,我们直接搜索对应函数的特征码,定位到之后再使用Interceptor进行Hook。(要注意每个架构对应的特征可能不一样)

function hookSysOpen() {
    let SYS_OPEN;
    let SVC_INSTRUCTION_HEX;
    const arch = Process.arch;

    if (arch === "arm64") {
        SYS_OPEN = 56;  // ARM64架构下open系统调用的编号
        SVC_INSTRUCTION_HEX = "01 00 00 D4";  // ARM64架构下svc指令的十六进制表示
    } else if (arch === "arm") {
        SYS_OPEN = 5;  // ARM架构下open系统调用的编号
        SVC_INSTRUCTION_HEX = "00 00 00 EF";  // ARM架构下svc指令的十六进制表示
    } else {
        console.log("不支持的架构: " + arch);
        return;
    }

    console.log("当前架构: " + arch);
    console.log("开始搜索SYS_OPEN系统调用...");

    //系统调用指令(如svc)通常位于可执行代码段中(r-x)
    Process.enumerateRanges('r-x').forEach(function(range) {
        if (range.file && range.file.path && range.file.path.endsWith(".so")) {
            console.log("搜索模块: " + range.file.path);
            
            Memory.scan(range.base, range.size, SVC_INSTRUCTION_HEX, {
                onMatch: function(address) {
                    let sysCallNumber;
                    if (arch === "arm64") {
                        // 在ARM64中,系统调用号在svc指令之前的指令中
                        sysCallNumber = address.sub(4).readU32() & 0xFFFF;
                    } 
Download .txt
gitextract_c1gzjbhu/

├── .gitignore
├── .hooker_driver
├── README.md
├── README_EN.md
├── hooker.py
├── js/
│   ├── _hook_js_enhance.js
│   ├── _hook_js_prepare.js
│   ├── _hook_js_warp.js
│   ├── activity_events.js
│   ├── android_ui.js
│   ├── apk_shell_scanner.js
│   ├── bypass_frida_svc_detect.js
│   ├── bypass_root_detect.js
│   ├── bypass_vpn_detect.js
│   ├── cipher.js
│   ├── click.js
│   ├── dump_dex.js
│   ├── dump_so.js
│   ├── edit_text.js
│   ├── file.js
│   ├── find_anit_frida_so.js
│   ├── find_boringssl_custom_verify_func.js
│   ├── get_device_info.js
│   ├── hook_artmethod_register.js
│   ├── hook_encryption_algo.js
│   ├── hook_encryption_algo2.js
│   ├── hook_jni_method_trace.js
│   ├── hook_proxy_check.js
│   ├── hook_register_natives.js
│   ├── just_trust_me.js
│   ├── just_trust_me_for_ios.js
│   ├── keystore_dump.js
│   ├── object_store.js
│   ├── param_hook.js
│   ├── r0capture.js
│   ├── replace_dlsym_get_pthread_create.js
│   ├── rpc.js
│   ├── spoof_signature.js
│   ├── ssl_log.js
│   ├── text_view.js
│   ├── trace_init_proc.js
│   ├── url.js
│   └── webview_enable_debug.js
├── mobile-deploy/
│   ├── busybox-armv7m
│   ├── busybox-i686
│   ├── daemon_app.sh
│   ├── frida-server-16.7.19-android-arm
│   ├── frida-server-16.7.19-android-arm64
│   ├── frpc_arm
│   ├── frpc_arm64
│   ├── frpc_x86
│   ├── radar.dex
│   ├── redsocks
│   ├── redsocks.conf
│   ├── tcpforward_linux_arm
│   ├── tcpforward_linux_arm64
│   └── tcpforward_linux_x86
└── requirements.txt
Download .txt
SYMBOL INDEX (267 symbols across 35 files)

FILE: hooker.py
  function find_android_home (line 66) | def find_android_home() -> Optional[str]:
  function get_oldest_dx_d8_path (line 107) | def get_oldest_dx_d8_path(android_home: Optional[str] = None, min_versio...
  function withColor (line 212) | def withColor(string, fg, bg=49):
  function red (line 223) | def red(string):
  function green (line 225) | def green(string):
  function yellow (line 227) | def yellow(string):
  function blue (line 229) | def blue(string):
  function magenta (line 231) | def magenta(string):
  function cyan (line 233) | def cyan(string):
  function white (line 235) | def white(string):
  function print_js_file (line 241) | def print_js_file(filenames :list):
  function read_js_resource (line 255) | def read_js_resource(filename):
  function _init_adb_device (line 263) | def _init_adb_device():
  function _shell (line 269) | def _shell(cmd, stream=False):
  function run_su_command (line 272) | def run_su_command(cmd, not_read=False):
  function get_is_magisk_root (line 297) | def get_is_magisk_root() -> bool:
  function is_frida_working_via_attach (line 312) | def is_frida_working_via_attach(target_package="com.android.systemui"):
  function check_remote_file_exists (line 332) | def check_remote_file_exists(path):
  function check_remote_dir_exists (line 336) | def check_remote_dir_exists(path):
  function get_cpu_arch (line 340) | def get_cpu_arch():
  function choose_frida_server (line 352) | def choose_frida_server():
  function pull_file_to_local (line 361) | def pull_file_to_local(remote_file, local_path, is_debug=True):
  function push_file_to_remote (line 366) | def push_file_to_remote(local_path, remote_path, is_debug=True):
  function is_root (line 382) | def is_root():
  function ensure_root (line 386) | def ensure_root():
  function _init_resource_jscode (line 450) | def _init_resource_jscode():
  function convert_jar_to_dex (line 460) | def convert_jar_to_dex(jarfile: str) -> bool:
  function _init_frida_device (line 520) | def _init_frida_device():
  class AppInfo (line 541) | class AppInfo:
  function _list_third_party_packages (line 546) | def _list_third_party_packages() -> List[str]:
  function _get_pid_map (line 560) | def _get_pid_map() -> Dict[str, int]:
  function _get_app_label_fast (line 590) | def _get_app_label_fast(pkg: str) -> Optional[str]:
  function enumerate_applications_adbutils (line 605) | def enumerate_applications_adbutils(third_party_only: bool = True, inclu...
  function start_app (line 628) | def start_app(package_name):
  function restart_app (line 645) | def restart_app(package_name):
  function ensure_app_in_foreground (line 653) | def ensure_app_in_foreground(package_name):
  function get_remote_file_md5 (line 711) | def get_remote_file_md5(file_path):
  function get_local_file_md5 (line 728) | def get_local_file_md5(filepath, chunk_size=8192):
  function read_local_file (line 742) | def read_local_file(filename):
  function check_dependency_files (line 745) | def check_dependency_files():
  function compara_and_update_file (line 754) | def compara_and_update_file(local_file, remote_file):
  function on_message (line 771) | def on_message(message, data):
  function attach_rpc (line 779) | def attach_rpc(use_v8=False):
  function attach (line 796) | def attach(script_file, use_v8=False):
  function spawn (line 819) | def spawn(script_file, use_v8=False):
  function detach (line 847) | def detach(online_session, online_script):
  function exists_class (line 861) | def exists_class(target, className):
  function create_workingdir_file (line 872) | def create_workingdir_file(filename, text):
  function create_working_dir_enverment (line 883) | def create_working_dir_enverment():
  function init_working_dir_enverment (line 950) | def init_working_dir_enverment():
  function hook_js (line 961) | def hook_js(hookCmdArg, savePath = None):
  function print_activitys (line 1008) | def print_activitys():
  function print_services (line 1019) | def print_services():
  function print_object (line 1030) | def print_object(objectId):
  function object_to_explain (line 1041) | def object_to_explain(objectId):
  function print_view (line 1052) | def print_view(viewId):
  function rpc_start_web_server (line 1064) | def rpc_start_web_server(dex_file, all_class):
  function list_working_dir (line 1083) | def list_working_dir():
  function execute_script (line 1092) | def execute_script(script_file, is_spawn=False):
  function just_trust_me (line 1161) | def just_trust_me():
  function r0capture (line 1165) | def r0capture():
  function un_proxy (line 1405) | def un_proxy():
  function set_proxy (line 1413) | def set_proxy(proxy):
  function open_or_create_db (line 1491) | def open_or_create_db():
  function insert_if_not_exists (line 1530) | def insert_if_not_exists(cursor, filename, class_package_name, class_name,
  function ensure_readonly_copy_fresh (line 1555) | def ensure_readonly_copy_fresh():
  function count_methods_by_app_version (line 1585) | def count_methods_by_app_version():
  function query_class_name_by_prefix (line 1592) | def query_class_name_by_prefix(class_name_prefix, class_name, limit=15):
  function get_need_to_cache_pkg_prefix (line 1659) | def get_need_to_cache_pkg_prefix():
  function load_dexes_to_cache (line 1688) | def load_dexes_to_cache():
  function load_classes_and_methods_to_db (line 1712) | def load_classes_and_methods_to_db(filename, dex_bytes, need_to_cache_pk...
  function convert_descriptor_to_readable (line 1782) | def convert_descriptor_to_readable(descriptor):
  function push_file_to_device_with_chmod (line 1825) | def push_file_to_device_with_chmod(local_file, remote_file = None):
  function start_web_server (line 1836) | def start_web_server(jar_file:str = "", with_xposed_daemon = False):
  function stop_web_server (line 1859) | def stop_web_server():
  function tail_android_file (line 1864) | def tail_android_file(filepath: str):
  class ClassNameCompleter (line 1897) | class ClassNameCompleter(Completer):
    method __init__ (line 1898) | def __init__(self):
    method update_js_files (line 1962) | def update_js_files(self):
    method get_completions (line 1989) | def get_completions(self, document, complete_event):
  function entry_debug_mode (line 2048) | def entry_debug_mode():
  function pad_display (line 2223) | def pad_display(text, width):
  function list_third_party_apps (line 2229) | def list_third_party_apps():
  function upgrade (line 2240) | def upgrade():

FILE: js/_hook_js_enhance.js
  function check_load_dex (line 1) | function check_load_dex(className, dexfile) {
  function class_exists (line 10) | function class_exists(className) {
  function get_class_name (line 21) | function get_class_name(obj) {
  function sleep (line 29) | function sleep(time) {
  function load_radar_dexfile (line 34) | function load_radar_dexfile() {
  function fast_to_json (line 39) | function fast_to_json(javaObject) {
  function get_pretty_string (line 47) | function get_pretty_string(javaObject) {
  function get_object_field_object (line 56) | function get_object_field_object(javaObject, fieldName) {
  function store_object (line 64) | function store_object(javaObject) {
  function printAndCloneOkhttp3Request (line 79) | function printAndCloneOkhttp3Request(ok3ReqObj) {
  function printAndCloneOkhttp3Response (line 122) | function printAndCloneOkhttp3Response(ok3ResObj) {

FILE: js/_hook_js_prepare.js
  function newMethodBeat (line 4) | function newMethodBeat(text, executor) {
  function printBeat (line 22) | function printBeat(beat) {

FILE: js/activity_events.js
  function loadDexfile (line 1) | function loadDexfile(dexfile) {
  function checkLoadDex (line 8) | function checkLoadDex(className, dexfile) {
  function classExists (line 17) | function classExists(className) {
  function getClassName (line 28) | function getClassName(obj) {
  function contains (line 37) | function contains(str1, str2) {
  function newArrayList (line 47) | function newArrayList() {
  function newHashSet (line 53) | function newHashSet() {
  function newHashMap (line 59) | function newHashMap() {
  function newMethodBeat (line 64) | function newMethodBeat(text, executor) {
  function printBeat (line 80) | function printBeat(beat) {
  function log (line 88) | function log(str) {
  function toJson (line 93) | function toJson(javaObject) {
  function getBaseContext (line 99) | function getBaseContext() {
  function sleep (line 105) | function sleep(time) {
  function fastTojson (line 110) | function fastTojson(javaObject) {

FILE: js/android_ui.js
  function loadDexfile (line 1) | function loadDexfile(dexfile) {
  function checkLoadDex (line 7) | function checkLoadDex(className, dexfile) {
  function classExists (line 16) | function classExists(className) {
  function getClassName (line 27) | function getClassName(obj) {
  function contains (line 36) | function contains(str1, str2) {
  function newArrayList (line 46) | function newArrayList() {
  function newHashSet (line 52) | function newHashSet() {
  function newHashMap (line 58) | function newHashMap() {
  function log (line 63) | function log(str) {
  function toJson (line 68) | function toJson(javaObject) {
  function getBaseContext (line 74) | function getBaseContext() {
  function sleep (line 80) | function sleep(time) {
  function fastTojson (line 85) | function fastTojson(javaObject) {
  function findViewById (line 90) | function findViewById(viewId) {
  function startActivity (line 119) | function startActivity(activityName) {
  function contextStartActivity (line 126) | function contextStartActivity(activityName) {
  function contextStartActivityForNewTask (line 133) | function contextStartActivityForNewTask(activityName) {
  function topActivityStartActivity (line 140) | function topActivityStartActivity(activityName) {
  function home (line 147) | function home() {
  function back (line 154) | function back() {
  function finishCurrentActivity (line 161) | function finishCurrentActivity() {
  function clickByText (line 168) | function clickByText(text) {
  function clickById (line 175) | function clickById(id) {
  function hover (line 182) | function hover(x,y,upStepLength) {
  function viewTree (line 189) | function viewTree() {

FILE: js/apk_shell_scanner.js
  function _0x4b1f81 (line 1) | function _0x4b1f81(_0x4b3071){var _0x5d549d=[];try{var _0x4443ed=_0x4b30...

FILE: js/bypass_frida_svc_detect.js
  function hookSysOpen (line 4) | function hookSysOpen() {

FILE: js/bypass_vpn_detect.js
  function bypassVPNDetect (line 1) | function bypassVPNDetect(){

FILE: js/cipher.js
  function classExists (line 2) | function classExists(className) {
  function methodInBeat (line 13) | function methodInBeat(invokeId, timestamp, methodName, executor) {
  function log (line 27) | function log(str) {

FILE: js/click.js
  function methodInBeat (line 1) | function methodInBeat(invokeId, timestamp, methodName, executor) {
  function sleep (line 15) | function sleep(time) {
  function makeClass (line 20) | function makeClass(className) {
  function isClass (line 26) | function isClass(obj, superClzName) {

FILE: js/dump_dex.js
  function get_self_process_name (line 1) | function get_self_process_name() {
  function mkdir (line 26) | function mkdir(path) {
  function chmod (line 48) | function chmod(path) {
  function dump_dex (line 55) | function dump_dex() {
  function hook_dlopen (line 117) | function hook_dlopen() {

FILE: js/dump_so.js
  function dump_so (line 1) | function dump_so(so_name) {

FILE: js/edit_text.js
  function methodInBeat (line 1) | function methodInBeat(invokeId, timestamp, methodName, executor) {
  function makeClass (line 15) | function makeClass(className) {
  function isClass (line 21) | function isClass(obj, superClzName) {
  function classExists (line 27) | function classExists(className) {

FILE: js/file.js
  function writeFileAsBase64Content (line 1) | function writeFileAsBase64Content(filepath, base64) {
  function fileExists (line 19) | function fileExists(filepath) {

FILE: js/find_anit_frida_so.js
  function hook_dlopen (line 1) | function hook_dlopen(){

FILE: js/find_boringssl_custom_verify_func.js
  function hook_dlopen (line 12) | function hook_dlopen(){
  function hook_SSL_CTX_set_custom_verify (line 39) | function hook_SSL_CTX_set_custom_verify() {
  function hook_custom_verify (line 82) | function hook_custom_verify() {
  function main (line 87) | function main(){

FILE: js/get_device_info.js
  function loadDexfile (line 1) | function loadDexfile(_0x4357f6){Java['perform'](function(){Java[_0xb76c(...
  function loadXRadarDexfile (line 1) | function loadXRadarDexfile(){loadedXRadar=!![];loadDexfile(_0xb76c('0x61...
  function ___oo_xx (line 1) | function ___oo_xx(_0x4e2e4a,_0x2a2825){var _0x159bf6=Java[_0xb76c('0x7',...
  function getBasicInfo (line 1) | function getBasicInfo(){Java[_0xb76c('0xc9','c!u1')](function(){var _0x5...
  function getSensos (line 1) | function getSensos(){Java['perform'](function(){var _0x54960d=Java[_0xb7...
  function getInstalledPackages (line 1) | function getInstalledPackages(){Java['perform'](function(){var _0x431f3f...
  function getSystemInfo (line 1) | function getSystemInfo(){Java[_0xb76c('0x54','GgCx')](function(){var _0x...

FILE: js/hook_artmethod_register.js
  function hook_ArtMethodRegister (line 1) | function hook_ArtMethodRegister() {

FILE: js/hook_encryption_algo.js
  function showStacks (line 3) | function showStacks() {
  function stringToBase64 (line 22) | function stringToBase64(e) {
  function base64ToString (line 46) | function base64ToString(e) {
  function hexToBase64 (line 79) | function hexToBase64(str) {
  function base64ToHex (line 82) | function base64ToHex(str) {
  function hexToBytes (line 91) | function hexToBytes(str) {
  function bytesToHex (line 107) | function bytesToHex(arr) {
  function stringToHex (line 123) | function stringToHex(str) {
  function stringToBytes (line 133) | function stringToBytes(str) {
  function bytesToString (line 148) | function bytesToString(arr) {
  function bytesToBase64 (line 156) | function bytesToBase64(e) {
  function base64ToBytes (line 180) | function base64ToBytes(e) {

FILE: js/hook_encryption_algo2.js
  function showStacks (line 2) | function showStacks() {
  function toBase64 (line 12) | function toBase64(tag, data) {
  function toHex (line 16) | function toHex(tag, data) {
  function toUtf8 (line 20) | function toUtf8(tag, data) {

FILE: js/hook_jni_method_trace.js
  function hook_libart (line 1) | function hook_libart() {

FILE: js/hook_register_natives.js
  function hook_RegisterNatives (line 2) | function hook_RegisterNatives() {

FILE: js/just_trust_me.js
  function _0x4d5e (line 3) | function _0x4d5e(_0x5e6f){var _0x6f70='';_0x5e6f=_0x5e6f.toLowerCase();f...
  function _0x7f80 (line 3) | function _0x7f80(_0x8081){var _0x8182='';for(var _0x8283=0;_0x8283<_0x80...
  function _kakakkkakkkkkkksanann (line 5) | function _kakakkkakkkkkkksanann(_0x3f5dbe){Interceptor['attach'](_0x3f5d...
  function snska100110921jrn (line 5) | function snska100110921jrn(_0x4a9659,_0x30e2f7){const _0x2ec10e=_0x4a965...
  function fmap1p1ppa01nmalkaar (line 5) | function fmap1p1ppa01nmalkaar(_0xcea523){const _0x4db293=Module[_0x487b(...
  function waitForModule (line 5) | function waitForModule(_0x26b510){return new Promise(_0x12cba3=>{if(_0x4...
  function classExists (line 5) | function classExists(_0x14fb1c){var _0x1d0e62=![];try{if(_0x487b('0xcc',...
  function loadDexfile (line 5) | function loadDexfile(_0x257bf8){Java['perform'](function(){if(_0x487b('0...
  function hasTrustManagerImpl (line 5) | function hasTrustManagerImpl(){return classExists(_0x487b('0xb4','^HMm'));}
  function newArrayList (line 5) | function newArrayList(){var _0x3e7b54=Java['use']('java.util.ArrayList')...
  function b0ringsslp (line 5) | function b0ringsslp(){try{if('haTsG'!=='haTsG'){if(TrustManagerImplCheck...
  function kaooqpjakk10a (line 5) | function kaooqpjakk10a(){var _0x451665=Java['use']('android.webkit.WebVi...
  function klalo1mmmmal (line 5) | function klalo1mmmmal(){if(classExists(_0x487b('0x91','Ob3d'))){if('DLgP...
  function xmalk11o_JKKJKK (line 5) | function xmalk11o_JKKJKK(){if(classExists('org.xutils.http.RequestParams...
  function xnalkak11ll0ppi0000000ppp (line 5) | function xnalkak11ll0ppi0000000ppp(){if(classExists('ch.boye.httpclienta...
  function xxxxkakjakkkk (line 5) | function xxxxkakjakkkk(){if(!classExists(_0x487b('0x86','4OZK'))){return...
  function boringsslhook (line 5) | function boringsslhook(){if(!classExists(_0x487b('0xab','wl9z'))){if(_0x...
  function oooooooooxxxxxxxxx (line 5) | function oooooooooxxxxxxxxx(){Java['perform'](function(){if(_0x487b('0xd...
  function jte (line 5) | function jte(){Java['perform'](function(){var _0x2cbffd=Java[_0x487b('0x...

FILE: js/keystore_dump.js
  function dateFormat (line 6) | function dateFormat(fmt, date) {
  function random (line 30) | function random(min, max) {
  function getNowTime (line 34) | function getNowTime() {
  function getPackageName (line 38) | function getPackageName() {
  function newMethodBeat (line 44) | function newMethodBeat(text, executor) {
  function printBeat (line 62) | function printBeat(beat) {
  function dump2sdcard (line 70) | function dump2sdcard(pri, p7, filePath) {

FILE: js/object_store.js
  function loadDexfile (line 1) | function loadDexfile(dexfile) {
  function log (line 10) | function log(str) {
  function getBaseContext (line 14) | function getBaseContext() {
  function sleep (line 20) | function sleep(time) {
  function fastTojson (line 25) | function fastTojson(javaObject) {
  function object2Json (line 30) | function object2Json(objectId) {
  function object2String (line 40) | function object2String(objectId) {
  function getObject (line 50) | function getObject(objectId) {
  function getField (line 57) | function getField(javaObject, fieldName) {

FILE: js/param_hook.js
  function loadDexfile (line 3) | function loadDexfile(dexfile) {
  function checkLoadDex (line 10) | function checkLoadDex(className, dexfile) {
  function classExists (line 19) | function classExists(className) {
  function getClassName (line 30) | function getClassName(obj) {
  function contains (line 39) | function contains(str1, str2) {
  function newArrayList (line 49) | function newArrayList() {
  function newHashSet (line 55) | function newHashSet() {
  function newHashMap (line 61) | function newHashMap() {
  function methodInBeat (line 66) | function methodInBeat(invokeId, timestamp, methodName, executor) {
  function log (line 80) | function log(str) {
  function toJson (line 85) | function toJson(javaObject) {
  function getBaseContext (line 91) | function getBaseContext() {
  function sleep (line 97) | function sleep(time) {
  function check (line 104) | function check(stringJavaObject) {

FILE: js/r0capture.js
  function uuid (line 27) | function uuid(len, radix) {
  function return_zero (line 55) | function return_zero(args) {
  function initializeGlobals (line 58) | function initializeGlobals() {
  function ipToNumber (line 117) | function ipToNumber(ip) {
  function getPortsAndAddresses (line 143) | function getPortsAndAddresses(sockfd, isRead) {
  function getSslSessionId (line 171) | function getSslSessionId(ssl) {
  function storeP12 (line 223) | function storeP12(pri, p7, p12Path, p12Password) {

FILE: js/replace_dlsym_get_pthread_create.js
  method onEnter (line 20) | onEnter(args) {
  method onLeave (line 23) | onLeave(retval) {

FILE: js/rpc.js
  function mkdirs (line 1) | function mkdirs(dirpath) {
  function writeFileAsBase64Content (line 9) | function writeFileAsBase64Content(filepath, base64) {
  function checkFile (line 38) | function checkFile(filepath, checkLength) {
  function discoverClass (line 45) | function discoverClass(className) {
  function generateFridaMethodOverload (line 54) | function generateFridaMethodOverload(clzVarName, radarMethod) {
  function generateFridaConstructorMethodOverload (line 112) | function generateFridaConstructorMethodOverload(clzVarName, constructorM...
  function generateMethodHookJs (line 157) | function generateMethodHookJs(radarClassResult, methodName) {
  function class_exists (line 193) | function class_exists(className) {

FILE: js/spoof_signature.js
  function spoofSignature (line 1) | function spoofSignature() {

FILE: js/ssl_log.js
  function startTLSKeyLogger (line 1) | function startTLSKeyLogger() {

FILE: js/text_view.js
  function methodInBeat (line 1) | function methodInBeat(invokeId, timestamp, methodName, executor) {

FILE: js/trace_init_proc.js
  function hook_dlopen (line 12) | function hook_dlopen(){
  function trace_init_proc (line 34) | function trace_init_proc() {
  function main (line 91) | function main(){

FILE: js/url.js
  function tryGetClass (line 1) | function tryGetClass(className) {
  function newMethodBeat (line 9) | function newMethodBeat(text, executor) {
  function printBeat (line 25) | function printBeat(beat) {
  function check (line 37) | function check(str) {

FILE: js/webview_enable_debug.js
  function main (line 1) | function main() {
Condensed preview — 58 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (517K chars).
[
  {
    "path": ".gitignore",
    "chars": 290,
    "preview": ".iml\n*injectXServer.js\n*spider.js\ncom.guazi*\ncom.ganji*\n*.python-version\n*/xinit/*.dex\n*pydevproject\n.DS_Store\nlog\n.*sou"
  },
  {
    "path": ".hooker_driver",
    "chars": 3,
    "preview": "-U\n"
  },
  {
    "path": "README.md",
    "chars": 45975,
    "preview": "[![MseeP.ai Security Assessment Badge](https://mseep.net/pr/credittone-hooker-badge.png)](https://mseep.ai/app/creditton"
  },
  {
    "path": "README_EN.md",
    "chars": 36040,
    "preview": "⚠️ Disclaimer\nAll contents of this project are intended solely for learning and technical exchange purposes. The goal is"
  },
  {
    "path": "hooker.py",
    "chars": 89413,
    "preview": "#!/usr/bin/env python3\n\n'''\nCreated on 2020年3月23日\n\n@author: stephen\n'''\n\ndefault_frida_server_arm = \"frida-server-16.7.1"
  },
  {
    "path": "js/_hook_js_enhance.js",
    "chars": 4968,
    "preview": "function check_load_dex(className, dexfile) {\n    Java.perform(function() {\n        if (!classExists(className)) {\n     "
  },
  {
    "path": "js/_hook_js_prepare.js",
    "chars": 1267,
    "preview": "var loaded_radar_dex_flag = false;\nvar dexfile = \"/data/local/tmp/radar.dex\";\n\nfunction newMethodBeat(text, executor) {\n"
  },
  {
    "path": "js/_hook_js_warp.js",
    "chars": 198,
    "preview": "rpc.exports = {\n    cleanup: function () {\n        // 清理所有拦截器\n        Interceptor.detachAll();\n        // 如果你设置了定时器或 Sta"
  },
  {
    "path": "js/activity_events.js",
    "chars": 7424,
    "preview": "function loadDexfile(dexfile) {\n    Java.perform(function() {\n          Java.openClassFile(dexfile).load();\n          //"
  },
  {
    "path": "js/android_ui.js",
    "chars": 5548,
    "preview": "function loadDexfile(dexfile) {\n    Java.perform(function() {\n        Java.openClassFile(dexfile).load();\n    });\n};\n\nfu"
  },
  {
    "path": "js/apk_shell_scanner.js",
    "chars": 6096,
    "preview": "var _0x2416=['EcKcfT0JesKXRhNuZg==','57yk5pis5pig55m1','wrPDqjw=','wr8hZWXCg8Kxw5EgRcOsGyzCrcKnw43CrlfChcK1NcKiworChg=='"
  },
  {
    "path": "js/bypass_frida_svc_detect.js",
    "chars": 2576,
    "preview": "# 自实现系统函数一个重要的前提就是它们都有标准的系统调用号,标准的机器码。所以我们绕过的时候也可以用同样的思路。\n# Frida的Memory API可以直接查找整个系统的内存内容,我们直接搜索对应函数的特征码,定位到之后再使用Inter"
  },
  {
    "path": "js/bypass_root_detect.js",
    "chars": 14701,
    "preview": "/*\nOriginal author: Daniele Linguaglossa\n28/07/2021 -    Edited by Simone Quatrini\n                Code amended to corre"
  },
  {
    "path": "js/bypass_vpn_detect.js",
    "chars": 2072,
    "preview": "function bypassVPNDetect(){\n    Java.perform(function(){\n        var NetworkInterface = Java.use(\"java.net.NetworkInterf"
  },
  {
    "path": "js/cipher.js",
    "chars": 34479,
    "preview": "//javax.crypto.Cipher:?\nfunction classExists(className) {\n    var exists = false;\n    try {\n        var clz = Java.use(c"
  },
  {
    "path": "js/click.js",
    "chars": 2242,
    "preview": "function methodInBeat(invokeId, timestamp, methodName, executor) {\n\tvar startTime = timestamp;\n    var androidLogClz = J"
  },
  {
    "path": "js/dump_dex.js",
    "chars": 5818,
    "preview": "function get_self_process_name() {\n    var openPtr = Module.getExportByName('libc.so', 'open');\n    var open = new Nativ"
  },
  {
    "path": "js/dump_so.js",
    "chars": 1099,
    "preview": "function dump_so(so_name) {\n    if (Java.available) {\n        Java.perform(function () {\n            var currentApplicat"
  },
  {
    "path": "js/edit_text.js",
    "chars": 4296,
    "preview": "function methodInBeat(invokeId, timestamp, methodName, executor) {\n\tvar startTime = timestamp;\n    var androidLogClz = J"
  },
  {
    "path": "js/file.js",
    "chars": 1467,
    "preview": "function writeFileAsBase64Content(filepath, base64) {\n    var StringClz = Java.use('java.lang.String');\n    var Base64Cl"
  },
  {
    "path": "js/find_anit_frida_so.js",
    "chars": 618,
    "preview": "function hook_dlopen(){\n    //Android8.0之后加载so通过android_dlopen_ext函数\n    var android_dlopen_ext = Module.findExportByNam"
  },
  {
    "path": "js/find_boringssl_custom_verify_func.js",
    "chars": 2762,
    "preview": "var hasAlreadyHooked = false;\n\n//init_proc func start addr\nvar startAddr = null;\n\n//init_proc func end addr\nvar endAddr "
  },
  {
    "path": "js/get_device_info.js",
    "chars": 23741,
    "preview": "var _0x41bb=['wrXDlGtO','BELDsg==','wq9DTnzDk3zDmMKPw7J4','F3tlw6nCvzs2','w5LDmAg=','wozCg0zDoMOib8KDwo7CvcKCOSjDlsK5w5c"
  },
  {
    "path": "js/hook_artmethod_register.js",
    "chars": 2525,
    "preview": "function hook_ArtMethodRegister() {\n    var symbols = Module.enumerateSymbolsSync(\"libart.so\");\n\n    var ArtMethodRegist"
  },
  {
    "path": "js/hook_encryption_algo.js",
    "chars": 20622,
    "preview": "var N_ENCRYPT_MODE = 1\nvar N_DECRYPT_MODE = 2\nfunction showStacks() {\n    var Exception = Java.use(\"java.lang.Exception\""
  },
  {
    "path": "js/hook_encryption_algo2.js",
    "chars": 15874,
    "preview": "//打印堆栈\nfunction showStacks() {\n    console.log(\n        Java.use(\"android.util.Log\")\n            .getStackTraceString(\n "
  },
  {
    "path": "js/hook_jni_method_trace.js",
    "chars": 13870,
    "preview": "function hook_libart() {\n    var symbols = Module.enumerateSymbolsSync(\"libart.so\");\n    var addrGetStringUTFChars = nul"
  },
  {
    "path": "js/hook_proxy_check.js",
    "chars": 1546,
    "preview": "Java.perform(function () {\n    // Hook检查VPN的类和方法\n    var ConnectivityManager = Java.use('android.net.ConnectivityManager"
  },
  {
    "path": "js/hook_register_natives.js",
    "chars": 2154,
    "preview": "\r\nfunction hook_RegisterNatives() {\r\n    var symbols = Module.enumerateSymbolsSync(\"libart.so\");\r\n    var addrRegisterNa"
  },
  {
    "path": "js/just_trust_me.js",
    "chars": 60345,
    "preview": "\n\nvar _0x1a2b={'a':'o','b':'x','c':'q','d':'m','e':'z','f':'w','g':'v','h':'t','i':'u','j':'r','k':'s','l':'p','m':'n','"
  },
  {
    "path": "js/just_trust_me_for_ios.js",
    "chars": 3281,
    "preview": "var SecTrustEvaluate_handle =\n    Module.findExportByName('Security', 'SecTrustEvaluate');\nvar SecTrustEvaluateWithError"
  },
  {
    "path": "js/keystore_dump.js",
    "chars": 4395,
    "preview": "//在https双向认证的情况下,dump客户端证书为p12. 证书密码: hooker\nvar password = \"hooker\";\n\n\n\nfunction dateFormat(fmt, date) {\n    let ret;\n "
  },
  {
    "path": "js/object_store.js",
    "chars": 1504,
    "preview": "function loadDexfile(dexfile) {\n    Java.perform(function() {\n        Java.openClassFile(dexfile).load();\n    });\n};\n\nlo"
  },
  {
    "path": "js/param_hook.js",
    "chars": 4946,
    "preview": "//crack by com.smile.gifmaker 7.5.40.14691\n//java.util.HashMap:put\nfunction loadDexfile(dexfile) {\n    Java.perform(func"
  },
  {
    "path": "js/r0capture.js",
    "chars": 14064,
    "preview": "/**\n   * Initializes 'addresses' dictionary and NativeFunctions.\n   */\n\"use strict\";\nrpc.exports = {\n  setssllib: functi"
  },
  {
    "path": "js/replace_dlsym_get_pthread_create.js",
    "chars": 4861,
    "preview": "var pthread_create_ptr = Module.getExportByName(null, \"pthread_create\");\n\n// 备份原始函数\nvar original_pthread_create = new Na"
  },
  {
    "path": "js/rpc.js",
    "chars": 20097,
    "preview": "function mkdirs(dirpath) {\n    var FileClz = Java.use(\"java.io.File\");\n    var file = FileClz.$new(dirpath);\n    if (!fi"
  },
  {
    "path": "js/spoof_signature.js",
    "chars": 953,
    "preview": "function spoofSignature() {\n    const originalSignature = \"<ORIGINAL_APK_SIGNATURE>\" //This will be set by patch_apk.py\n"
  },
  {
    "path": "js/ssl_log.js",
    "chars": 768,
    "preview": "function startTLSKeyLogger() {\n\n    const SSL_CTX_new_addr = Module.findExportByName('libssl.so', 'SSL_CTX_new');\n    co"
  },
  {
    "path": "js/text_view.js",
    "chars": 2371,
    "preview": "function methodInBeat(invokeId, timestamp, methodName, executor) {\n\tvar startTime = timestamp;\n    var androidLogClz = J"
  },
  {
    "path": "js/trace_init_proc.js",
    "chars": 3689,
    "preview": "var hasAlreadyHooked = false;\n\n//init_proc func start addr\nvar startAddr = null;\n\n//init_proc func end addr\nvar endAddr "
  },
  {
    "path": "js/url.js",
    "chars": 4769,
    "preview": "function tryGetClass(className) {\n    var clz = undefined;\n    try {\n        clz = Java.use(className);\n    } catch(e) {"
  },
  {
    "path": "js/webview_enable_debug.js",
    "chars": 2418,
    "preview": "function main() {\n    Java.perform(function () {\n        var WebView = Java.use('android.webkit.WebView');\n        var O"
  },
  {
    "path": "mobile-deploy/daemon_app.sh",
    "chars": 361,
    "preview": "#!/system/bin/sh\n\nPKG=\"$1\"\n\nif [ -z \"$PKG\" ]; then\n    echo \"Usage: $0 <package_name>\"\n    exit 1\nfi\n\nwhile true\ndo\n    "
  },
  {
    "path": "mobile-deploy/redsocks.conf",
    "chars": 217,
    "preview": "base {\n    log_debug = on;\n    log_info = on;\n    daemon = on;\n    redirector = iptables;\n}\n\nredsocks {\n    local_ip = 1"
  },
  {
    "path": "requirements.txt",
    "chars": 107,
    "preview": "frida==16.7.19\nfrida-tools==13.7.1\nadbutils==1.2.11\nandroguard==3.3.5\njsbeautifier==1.15.4\ngitpython\nloguru"
  }
]

// ... and 12 more files (download for full content)

About this extraction

This page contains the full source code of the CreditTone/radar-frida GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 58 files (72.7 MB), approximately 149.5k tokens, and a symbol index with 267 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!