├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── assets
└── .gitkeep
├── frida-skeleton.py
├── images
└── logo.gif
├── lib
├── core
│ ├── frida_thread.py
│ ├── options.py
│ ├── port_manager.py
│ ├── project.py
│ ├── settings.py
│ ├── types.py
│ └── watch_thread.py
└── utils
│ ├── adb.py
│ ├── iptables.py
│ └── shell.py
├── projects
├── .gitignore
└── default
│ ├── config.yaml
│ └── main.js
├── requirements.txt
├── scripts
├── core
│ ├── android_packages.js
│ ├── common.js
│ └── trace.js
└── utils
│ ├── bypass.js
│ ├── conversion.js
│ ├── format.js
│ └── jav.js
├── tests
└── AndroidToHook
│ ├── .gitignore
│ ├── app
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── io
│ │ │ └── github
│ │ │ └── margular
│ │ │ └── ExampleInstrumentedTest.java
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── io
│ │ │ │ └── github
│ │ │ │ └── margular
│ │ │ │ ├── AsyncRequest.java
│ │ │ │ └── MainActivity.java
│ │ └── res
│ │ │ ├── drawable-v24
│ │ │ └── ic_launcher_foreground.xml
│ │ │ ├── drawable
│ │ │ └── ic_launcher_background.xml
│ │ │ ├── layout
│ │ │ └── activity_main.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ └── values
│ │ │ ├── colors.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ └── test
│ │ └── java
│ │ └── io
│ │ └── github
│ │ └── margular
│ │ └── ExampleUnitTest.java
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
│ ├── gradlew
│ ├── gradlew.bat
│ └── settings.gradle
└── thirdparty
└── attrdict
├── __init__.py
├── default.py
├── dictionary.py
├── mapping.py
├── merge.py
└── mixins.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib64/
18 | parts/
19 | sdist/
20 | var/
21 | wheels/
22 | *.egg-info/
23 | .installed.cfg
24 | *.egg
25 | MANIFEST
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *.cover
46 | .hypothesis/
47 | .pytest_cache/
48 |
49 | # Translations
50 | *.mo
51 | *.pot
52 |
53 | # Django stuff:
54 | *.log
55 | local_settings.py
56 | db.sqlite3
57 |
58 | # Flask stuff:
59 | instance/
60 | .webassets-cache
61 |
62 | # Scrapy stuff:
63 | .scrapy
64 |
65 | # Sphinx documentation
66 | docs/_build/
67 |
68 | # PyBuilder
69 | target/
70 |
71 | # Jupyter Notebook
72 | .ipynb_checkpoints
73 |
74 | # pyenv
75 | .python-version
76 |
77 | # celery beat schedule file
78 | celerybeat-schedule
79 |
80 | # SageMath parsed files
81 | *.sage.py
82 |
83 | # Environments
84 | .env
85 | .venv
86 | env/
87 | venv/
88 | ENV/
89 | env.bak/
90 | venv.bak/
91 |
92 | # Spyder project settings
93 | .spyderproject
94 | .spyproject
95 |
96 | # Rope project settings
97 | .ropeproject
98 |
99 | # mkdocs documentation
100 | /site
101 |
102 | # mypy
103 | .mypy_cache/
104 |
105 | # Built application files
106 | *.apk
107 | *.ap_
108 |
109 | # Files for the ART/Dalvik VM
110 | *.dex
111 |
112 | # Java class files
113 | *.class
114 |
115 | # Generated files
116 | bin/
117 | gen/
118 | out/
119 |
120 | # Gradle files
121 | .gradle/
122 |
123 | # Local configuration file (sdk path, etc)
124 | local.properties
125 |
126 | # Proguard folder generated by Eclipse
127 | proguard/
128 |
129 | # Log Files
130 |
131 | # Android Studio Navigation editor temp files
132 | .navigation/
133 |
134 | # Android Studio captures folder
135 | captures/
136 |
137 | # IntelliJ
138 | *.iml
139 | .idea/workspace.xml
140 | .idea/tasks.xml
141 | .idea/gradle.xml
142 | .idea/assetWizardSettings.xml
143 | .idea/dictionaries
144 | .idea/libraries
145 | .idea/caches
146 |
147 | # Keystore files
148 | # Uncomment the following line if you do not want to check your keystore files in.
149 | #*.jks
150 |
151 | # External native build folder generated in Android Studio 2.2 and later
152 | .externalNativeBuild
153 |
154 | # Google Services (e.g. APIs or Firebase)
155 | google-services.json
156 |
157 | # Freeline
158 | freeline.py
159 | freeline/
160 | freeline_project_description.json
161 |
162 | # fastlane
163 | fastlane/report.xml
164 | fastlane/Preview.html
165 | fastlane/screenshots
166 | fastlane/test_output
167 | fastlane/readme.md
168 |
169 | # logs
170 | logs/
171 |
172 | # certifications
173 | *.crt
174 |
175 | .idea/
176 | release/
177 | frida-server*
178 | *.swp
179 | .vscode
180 | js/
181 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## [Unreleased]
8 |
9 | ## [3.3] - 2022-12-10
10 |
11 | ### Add
12 |
13 | * 新增--no-root选项,有的设备不支持adb root
14 | * frida-server使用随机端口绕过常规检测
15 | * 在初始化FridaThread时校验设备是否root
16 |
17 | ### Changed
18 |
19 | * 使用enumerate_applications替换enumerate_processes
20 |
21 | ### Fixed
22 |
23 | * 修复有的su不支持后面跟一个-
24 | * attrdict本地模块化以兼容3.10及以上版本
25 |
26 | ## [3.2.3] - 2021-06-24
27 |
28 | ### Add
29 |
30 | * 保存hook脚本的整个内容方便调试和查看
31 |
32 | ### Changed
33 |
34 | * 在调用Trace.javaClassByRegex的时候默认跳过hook系统函数,通常来说我们希望hook的是APP自身的函数。可以通过第二个参数传入true强制hook系统函数
35 |
36 | ### Fixed
37 |
38 | * 修复可能无法退出程序的BUG
39 |
40 | ## [3.2.2] - 2021-06-17
41 |
42 | ### Fixed
43 |
44 | * 修复frida-server启动之前就attach进程导致报错的BUG
45 |
46 |
47 | ## [3.2.1] - 2021-04-18
48 |
49 | ### Add
50 |
51 | * 新增指定设备hook的功能
52 |
53 |
54 | ## [3.2.0] - 2020-12-24
55 |
56 | ### Add
57 |
58 | * 新增Jav.describeObject API,很方便地打印java对象的函数和字段信息
59 | * 加入星链计划2.0
60 | * 添加logo
61 |
62 | ### Changed
63 |
64 | * 迁移wiki到github
65 | * 主程序帮助界面改为中文
66 |
67 | ## [3.1.0] - 2020-06-06
68 |
69 | ### Add
70 |
71 | * 新增spawn选项,现在可以根据选项全局spawn模式,或者在项目配置文件里面配置spawn为true
72 | * 新增项目优先级选项,数字越小越优先加载,用于hook有先后关系的场景,默认为0,默认工程为-100,为最优先
73 |
74 | ### Changed
75 |
76 | * 默认工程的bypass代码更新,现在可以根据新老设备自动切换hook脚本,老设备需要自行上传证书到/data/local/tmp/cert-der.crt
77 | * Common.impl函数大改,现在通过指定对象hook而不是字符串
78 |
79 | ## [3.0.0] - 2020-06-02
80 |
81 | ### Add
82 |
83 | * 新增项目概念,现在可以在projects目录下创建自己的项目
84 | * Javascript函数库新增namespace,每个内置函数库都有了自己的namespace,互不影响
85 |
86 | ### Changed
87 |
88 | * 代码结构变化,Javascript函数库分为内置函数库和用户自定义函数库,scripts下为内置函数库,projects目录下为用户自定义函数库
89 |
90 | ## [2.5.0] - 2020-05-28
91 |
92 | ### Add
93 |
94 | - 新增spawn模式,使用-s参数激活
95 | - 新增byte[]和hex格式之间的转化,并在implementationWrapper内部自动判断是否是byte[],如果是则以hex格式输出
96 |
97 | ### Changed
98 |
99 | - 日志输出格式优化,现在会标记是哪个设备的哪个apk打印的
100 | - 下载frida-server的时候显示百分比优化,现在固定xx.xx%的格式
101 | - PortManager在获取随机端口的时候会剔除本机已开启的端口
102 |
103 | ## [2.4.1] - 2020-03-16
104 |
105 | ### Changed
106 |
107 | - 修复没有-p参数时的找不到iptables的bug
108 | - 取消初始化的时候删除iptables,而在退出时再保证清除
109 | - 修复有的手机调用self.device.kill会报错的问题
110 |
111 |
112 |
113 | ## [2.4.0] - 2020-03-14
114 |
115 | ### Add
116 |
117 | - 解决有的设备通过frida.enumerate_devices()无法列出usb设备,改用frida.get_device_manager().add_remote_device()的方式
118 |
119 |
120 |
121 | ### Changed
122 |
123 | - 添加线程管理器使得子线程能够正常全部退出后再退出主线程
124 |
125 |
126 |
127 | ## [2.3.0] - 2020-03-11
128 |
129 | ### Add
130 |
131 | - 优雅地关闭frida-skeleton使其能够在退出的同时自动清除iptables并关闭frida-server以求对设备影响最小
132 | - 解决windows不能通过CTRL+C关闭frida-skeleton的bug
133 |
134 |
135 |
136 | ## [2.2.0] - 2019-12-22
137 |
138 | ### Changed
139 |
140 | - 优化日志格式
141 | - 代码架构调整
142 |
143 |
144 |
145 | ## [2.1.1] - 2019-12-21
146 |
147 | ### Changed
148 |
149 | - 使得手机流量能够被捕获
150 |
151 |
152 |
153 | ## [2.1.0] - 2019-12-21
154 |
155 | ### Changed
156 |
157 | - 更通用的iptables设置用以重定向TCP流量
158 |
159 |
160 |
161 | ## [2.0.0] - 2019-12-20
162 |
163 | ### Added
164 |
165 | - 多彩日志
166 | - 利用iptables和adb实现TCP流量重定向
167 | - 面向对象化
168 | - 捕获异常
169 | - 自动下载/安装/运行frida-server一条龙服务
170 |
171 |
172 |
173 | ## [1.1.0] - 2019-12-16
174 |
175 | ### Added
176 |
177 | - 一次性hook多个usb设备
178 |
179 |
180 |
181 | ## [1.0.0] - 2019-09-15
182 | ### Added
183 | - 通过正则表达式匹配包名
184 | - 自动打印日志
185 | - Frida-Skeleton独有的implementationWrapper更方便地写hook代码
186 | - 自动绕过证书绑定校验
187 | - 内置Java类hook方法
188 | - 内置jni函数hook方法
189 | - 良好的可扩展性
190 |
191 | [Unreleased]: https://github.com/Margular/frida-skeleton/compare/v3.3...HEAD
192 | [3.3]: https://github.com/Margular/frida-skeleton/compare/v3.2.3...v3.3
193 | [3.2.3]: https://github.com/Margular/frida-skeleton/compare/v3.2.2...v3.2.3
194 | [3.2.2]: https://github.com/Margular/frida-skeleton/compare/v3.2.1...v3.2.2
195 | [3.2.1]: https://github.com/Margular/frida-skeleton/compare/v3.2.0...v3.2.1
196 | [3.2.0]: https://github.com/Margular/frida-skeleton/compare/v3.1.0...v3.2.0
197 | [3.1.0]: https://github.com/Margular/frida-skeleton/compare/v3.0.0...v3.1.0
198 | [3.0.0]: https://github.com/Margular/frida-skeleton/compare/v2.5.0...v3.0.0
199 | [2.5.0]: https://github.com/Margular/frida-skeleton/compare/v2.4.1...v2.5.0
200 | [2.4.1]: https://github.com/Margular/frida-skeleton/compare/v2.4.0...v2.4.1
201 | [2.4.0]: https://github.com/Margular/frida-skeleton/compare/v2.3.0...v2.4.0
202 | [2.3.0]: https://github.com/Margular/frida-skeleton/compare/v2.2.0...v2.3.0
203 | [2.2.0]: https://github.com/Margular/frida-skeleton/compare/v2.1.1...v2.2.0
204 | [2.1.1]: https://github.com/Margular/frida-skeleton/compare/v2.1.0...v2.1.1
205 | [2.1.0]: https://github.com/Margular/frida-skeleton/compare/v2.0.0...v2.1.0
206 | [2.0.0]: https://github.com/Margular/frida-skeleton/compare/v1.1.0...v2.0.0
207 | [1.1.0]: https://github.com/Margular/frida-skeleton/compare/v1.0.0...v1.1.0
208 | [1.0.0]: https://github.com/Margular/frida-skeleton/releases/tag/v1.0.0
209 |
210 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Margular
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # frida-skeleton
4 |
5 |
6 |
7 | [![Contributors][contributors-shield]][contributors-url]
8 | [![Forks][forks-shield]][forks-url]
9 | [![Stargazers][stars-shield]][stars-url]
10 | [![Issues][issues-shield]][issues-url]
11 | [![MIT License][license-shield]][license-url]
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | 探索本项目的文档 »
22 |
23 |
24 | 报告Bug
25 | ·
26 | 提出新特性
27 |
28 |
29 |
30 |
31 | ## 目录
32 |
33 | - [简介](#简介)
34 | - [上手指南](#上手指南)
35 | - [开发前的配置要求](#开发前的配置要求)
36 | - [安装步骤](#安装步骤)
37 | - [文件目录说明](#文件目录说明)
38 | - [如何参与开源项目](#如何参与开源项目)
39 | - [版本控制](#版本控制)
40 | - [版权说明](#版权说明)
41 | - [鸣谢](#鸣谢)
42 |
43 | ### 简介
44 |
45 | `frida-skeleton`是基于frida的安卓hook框架,提供了很多frida自身不支持的功能,将hook安卓变成简单便捷,人人都会的事情,主要有:
46 |
47 | - 根据正则表达式批量hook安卓应用,支持多线程,可同时hook多个设备互不影响
48 | - 针对不同的应用可以同时加载不同的hook脚本,且支持优先级配置
49 | - 自动将手机上的所有TCP流量重定向到PC上的抓包工具如BurpSuite,无需手动配置,且自动绕过证书绑定机制
50 | - 丰富的日志记录功能,让你的hook历史永不丢失
51 | - 自动识别当前使用的frida版本并下载对应版本的frida-server到/data/local/tmp运行
52 | - 提供封装好的实用API以减少日常工作中的重复劳动
53 |
54 | ### 上手指南
55 |
56 | ###### 开发前的配置要求
57 |
58 | - Python3
59 |
60 | ###### 安装步骤
61 |
62 | 1. 克隆本项目到本地
63 |
64 | ```sh
65 | git clone https://github.com/Margular/frida-skeleton.git
66 | ```
67 |
68 | 2. 安装第三方依赖库
69 |
70 | ```sh
71 | pip install -r requirements.txt
72 | ```
73 |
74 | ###### 查看说明
75 |
76 | ```sh
77 | python frida-skeleton.py -h
78 | ```
79 |
80 | 详细说明请移步[WIKI](https://github.com/Margular/frida-skeleton/wiki)
81 |
82 | ### 文件目录说明
83 |
84 | ```
85 | 文件目录
86 | ├── CHANGELOG.md 项目改动记录
87 | ├── LICENSE 许可证
88 | ├── README.md 本文档
89 | ├── /assets/ 下载的frida-server存放的位置
90 | ├── frida-skeleton.py 项目入口
91 | ├── /images/ 本项目用到的图像资源文件
92 | ├── /lib/ Python库文件,frida-skeleton核心实现部分
93 | ├── /logs/ hook日志记录文件夹
94 | ├── /projects/ hook脚本存放的文件夹,以目录区分项目
95 | ├── requirements.txt 三方库需求列表
96 | ├── /scripts/ 封装好的实用API
97 | └── /tests/ 提供测试的安卓项目
98 | ```
99 |
100 | ### 如何参与开源项目
101 |
102 | 贡献使开源社区成为一个学习、激励和创造的绝佳场所。你所作的任何贡献都是**非常感谢**的。
103 |
104 |
105 | 1. Fork本项目
106 | 2. 创建开发分支 (`git checkout -b dev`)
107 | 3. 提交更改 (`git commit -m 'Add something'`)
108 | 4. 推送到分支 (`git push origin dev`)
109 | 5. 提[Pull Request](https://github.com/Margular/frida-skeleton/compare)
110 |
111 | ### 版本控制
112 |
113 | 该项目使用Git进行版本管理。您可以在repository参看当前可用版本。
114 |
115 | ### 版权说明
116 |
117 | 该项目签署了MIT 授权许可,详情请参阅 [LICENSE](https://github.com/Margular/frida-skeleton/blob/master/LICENSE)
118 |
119 | ### 鸣谢
120 |
121 | - [frida](https://frida.re/)
122 | - [frida-snippets](https://github.com/iddoeldor/frida-snippets)
123 | - [Img Shields](https://shields.io)
124 | - [Choose an Open Source License](https://choosealicense.com)
125 |
126 |
127 |
128 | [contributors-shield]: https://img.shields.io/github/contributors/Margular/frida-skeleton.svg?style=flat-square
129 | [contributors-url]: https://github.com/Margular/frida-skeleton/graphs/contributors
130 | [forks-shield]: https://img.shields.io/github/forks/Margular/frida-skeleton.svg?style=flat-square
131 | [forks-url]: https://github.com/Margular/frida-skeleton/network/members
132 | [stars-shield]: https://img.shields.io/github/stars/Margular/frida-skeleton.svg?style=flat-square
133 | [stars-url]: https://github.com/Margular/frida-skeleton/stargazers
134 | [issues-shield]: https://img.shields.io/github/issues/Margular/frida-skeleton.svg?style=flat-square
135 | [issues-url]: https://img.shields.io/github/issues/Margular/frida-skeleton.svg
136 | [license-shield]: https://img.shields.io/github/license/Margular/frida-skeleton.svg?style=flat-square
137 | [license-url]: https://github.com/Margular/frida-skeleton/blob/master/LICENSE
138 |
139 | # 404StarLink 2.0 - Galaxy
140 | 
141 |
142 | frida-skeleton 是 404Team [星链计划2.0](https://github.com/knownsec/404StarLink2.0-Galaxy)中的一环,如果对frida-skeleton 有任何疑问又或是想要找小伙伴交流,可以参考星链计划的加群方式。
143 |
144 | - [https://github.com/knownsec/404StarLink2.0-Galaxy#community](https://github.com/knownsec/404StarLink2.0-Galaxy#community)
145 |
146 |
--------------------------------------------------------------------------------
/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Margular/frida-skeleton/82db1b25bd7134cd5917cc95628d057ae28e48a0/assets/.gitkeep
--------------------------------------------------------------------------------
/frida-skeleton.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | import logging
5 | import os
6 | import signal
7 | import sys
8 | import time
9 |
10 | import coloredlogs
11 | import urllib3
12 |
13 | from lib.core.options import options
14 | from lib.core.settings import LOG_DIR, LOG_FILENAME
15 | from lib.core.watch_thread import WatchThread
16 | from lib.utils.adb import Adb
17 |
18 | # disable ssl warnings
19 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
20 |
21 |
22 | class MainExit(Exception):
23 | pass
24 |
25 |
26 | class FridaSkeleton:
27 |
28 | def __init__(self):
29 | self.log = logging.getLogger(self.__class__.__name__)
30 |
31 | def start(self):
32 | try:
33 | if options.list:
34 | for device in Adb.devices().out.split('\n')[1:]:
35 | print(device)
36 | sys.exit(0)
37 |
38 | level = logging.DEBUG if options.verbose else logging.INFO
39 | coloredlogs.install(level=level)
40 |
41 | # set log
42 | os.makedirs(LOG_DIR, mode=0o700, exist_ok=True)
43 | log_file = open(os.path.join(LOG_DIR, LOG_FILENAME), 'a', encoding='utf-8')
44 | coloredlogs.install(level=level, stream=log_file)
45 |
46 | # set handling interrupt exceptions
47 | signal.signal(signal.SIGTERM, self.shutdown)
48 | signal.signal(signal.SIGINT, self.shutdown)
49 |
50 | Adb.start_server()
51 |
52 | watch_thread = WatchThread()
53 |
54 | try:
55 | watch_thread.start()
56 | while True:
57 | time.sleep(1)
58 | except MainExit:
59 | while True:
60 | try:
61 | self.log.info('shutdown command received, wait for clean up please...')
62 | watch_thread.terminate()
63 | while watch_thread.is_alive():
64 | time.sleep(1)
65 | break
66 | except MainExit:
67 | pass
68 | except (KeyboardInterrupt, InterruptedError):
69 | pass
70 |
71 | self.log.info('thank you for using, bye!')
72 |
73 | def shutdown(self, signum, frame):
74 | if signum == signal.SIGINT:
75 | self.log.debug('keyboard interrupt event detected')
76 | elif signum == signal.SIGTERM:
77 | self.log.debug('termination event detected')
78 | else:
79 | self.log.warning('unknown event detected')
80 |
81 | raise MainExit
82 |
83 |
84 | if __name__ == '__main__':
85 | skeleton = FridaSkeleton()
86 | skeleton.start()
87 |
--------------------------------------------------------------------------------
/images/logo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Margular/frida-skeleton/82db1b25bd7134cd5917cc95628d057ae28e48a0/images/logo.gif
--------------------------------------------------------------------------------
/lib/core/frida_thread.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | import logging
5 | import lzma
6 | import os
7 | import re
8 | import sys
9 | import threading
10 | import time
11 | from concurrent.futures.thread import ThreadPoolExecutor
12 |
13 | import frida
14 | import requests
15 |
16 | from lib.core.options import options
17 | from lib.core.port_manager import port_manager
18 | from lib.core.project import Project
19 | from lib.core.settings import ROOT_DIR, FRIDA_SERVER_DEFAULT_PORT
20 | from lib.core.types import FakeDevice
21 | from lib.utils.adb import Adb
22 | from lib.utils.iptables import Iptables
23 |
24 | __lock__ = threading.Lock()
25 |
26 |
27 | class FridaThread(threading.Thread):
28 |
29 | def __init__(self, device):
30 | super().__init__(
31 | name="{}-{}".format(self.__class__.__name__, device.id))
32 | self.thread_tag = self.name
33 |
34 | self.server_executor = ThreadPoolExecutor(max_workers=1)
35 | self.log = logging.getLogger(self.thread_tag)
36 |
37 | self.frida_server_port = FRIDA_SERVER_DEFAULT_PORT
38 |
39 | if options.random_port:
40 | self.frida_server_port = port_manager.acquire_port(
41 | excludes=[FRIDA_SERVER_DEFAULT_PORT])
42 | self.log.info("pick random port {} for frida-server".format(
43 | self.frida_server_port))
44 |
45 | if options.random_port or device.type == FakeDevice.type:
46 | # init remote device
47 | self.log.debug('device {} does not support get_usb_device, '
48 | 'changing to get_remote_device method'
49 | .format(device.id))
50 |
51 | self.forward_port = port_manager.acquire_port(
52 | excludes=[options.port])
53 |
54 | self.device = frida.get_device_manager().add_remote_device(
55 | '127.0.0.1:{}'.format(self.forward_port))
56 |
57 | self.device.id = device.id
58 | else:
59 | self.device = device
60 |
61 | self.adb = Adb(self.device.id)
62 |
63 | self.log.info("frida-server port: {}".format(self.frida_server_port))
64 | if options.random_port or device.type == FakeDevice.type:
65 | result = self.adb.forward(self.forward_port, self.frida_server_port)
66 | # port has been used
67 | if result.err:
68 | port_manager.release_port(self.forward_port)
69 | raise RuntimeError('port {} has been used'.format(
70 | self.forward_port))
71 |
72 | if options.port:
73 | self.iptables = Iptables(self.adb, options.port)
74 |
75 | self.arch = self.adb.unsafe_shell("getprop ro.product.cpu.abi").out
76 | # maybe get 'arm64-v8a', 'arm-v7a' ...
77 | if 'arm64' in self.arch:
78 | self.arch = 'arm64'
79 | elif 'arm' in self.arch:
80 | self.arch = 'arm'
81 | elif 'x86_64' in self.arch:
82 | self.arch = 'x86_64'
83 | elif 'x86' in self.arch:
84 | self.arch = 'x86'
85 | else:
86 | raise RuntimeError('unknown arch: ' + self.arch)
87 |
88 | self.server_name = 'frida-server-{}-android-{}'.format(
89 | frida.__version__, self.arch)
90 |
91 | self._terminate = False
92 |
93 | def run(self) -> None:
94 | self.log.info("start with hook device: id={}, name={}, type={}".format(
95 | self.device.id, self.device.name, self.device.type))
96 |
97 | try:
98 | self.prepare()
99 | self.hook_apps()
100 | except Exception as e:
101 | self.log.error('device {}: {}'.format(self.device.id, e))
102 |
103 | try:
104 | self.shutdown()
105 | except Exception as e:
106 | self.log.error(
107 | 'unexpected error occurred when shutdown device {}: {}'
108 | .format(self.device.id, e))
109 |
110 | self.log.debug('device {} exit'.format(self.device.id))
111 |
112 | # prepare for starting hook
113 | def prepare(self):
114 | if self._terminate:
115 | return
116 |
117 | # get root
118 | if not options.no_root:
119 | self.adb.root()
120 |
121 | # close selinux
122 | self.adb.unsafe_shell('setenforce 0', root=True)
123 |
124 | # install iptables and reverse tcp port
125 | if options.port:
126 | # enable tcp connections between frida server and binding
127 | self.iptables.install()
128 | self.adb.reverse(options.port)
129 |
130 | if options.install:
131 | self.install_frida_server()
132 |
133 | self.kill_frida_servers()
134 | self.run_frida_server()
135 |
136 | def download(self, url, file_path):
137 | # get total size of file
138 | r1 = requests.get(url, stream=True, verify=False)
139 | total_size = int(r1.headers['Content-Length'])
140 |
141 | # check downloaded size
142 | if os.path.exists(file_path):
143 | temp_size = os.path.getsize(file_path)
144 | else:
145 | temp_size = 0
146 |
147 | if temp_size == total_size:
148 | self.log.info('{} has downloaded completely'.format(file_path))
149 | return
150 |
151 | if temp_size > total_size:
152 | self.log.error(
153 | '{} has corrupted, download it again'.format(file_path))
154 | os.remove(file_path)
155 | return self.download(url, file_path)
156 |
157 | self.log.debug('{} of {} needs to be download'.format(
158 | total_size - temp_size, total_size))
159 |
160 | # download from temp size to end
161 | headers = {'Range': 'bytes={}-'.format(temp_size)}
162 |
163 | r = requests.get(url, stream=True, verify=False, headers=headers)
164 |
165 | with open(file_path, "ab") as f:
166 | for chunk in r.iter_content(chunk_size=1024):
167 | if self._terminate:
168 | break
169 |
170 | if chunk:
171 | temp_size += len(chunk)
172 | f.write(chunk)
173 | f.flush()
174 |
175 | # download progress
176 | done = int(50 * temp_size / total_size)
177 | sys.stdout.write(
178 | "\r[{}{}] {:.2f}%".format(
179 | '█' * done,
180 | ' ' * (50 - done),
181 | 100 * temp_size / total_size)
182 | )
183 | sys.stdout.flush()
184 |
185 | sys.stdout.write(os.linesep)
186 |
187 | def install_frida_server(self):
188 | server_path = os.path.join(ROOT_DIR, 'assets', self.server_name)
189 | server_path_xz = server_path + '.xz'
190 |
191 | # if not exist frida server then install it
192 | if not self.adb.unsafe_shell(
193 | "ls /data/local/tmp/" + self.server_name).out:
194 | self.log.info(
195 | 'download {} from github ...'.format(self.server_name))
196 |
197 | with __lock__:
198 | self.download(
199 | 'https://github.com/frida/frida/releases/download/{}/{}.xz'
200 | .format(frida.__version__, self.server_name),
201 | server_path_xz
202 | )
203 |
204 | # extract frida server
205 | with open(server_path, 'wb') as f:
206 | with lzma.open(server_path_xz) as xz:
207 | f.write(xz.read())
208 |
209 | # upload frida server
210 | self.log.info("pushing frida-server-{}...".format(self.server_name))
211 | self.adb.push(server_path, '/data/local/tmp/')
212 |
213 | def kill_frida_servers(self):
214 | try:
215 | apps = self.device.enumerate_processes()
216 | except (frida.ServerNotRunningError, frida.TransportError,
217 | frida.InvalidOperationError):
218 | # frida server has not been started, no need to start
219 | return
220 |
221 | for app in apps:
222 | if app.name == self.server_name:
223 | self.log.debug("killing {}...".format(self.server_name))
224 | self.adb.unsafe_shell(
225 | 'kill -9 {}'.format(app.pid), root=True, quiet=True)
226 | time.sleep(0.5)
227 |
228 | def run_frida_server(self):
229 | self.adb.unsafe_shell('chmod +x /data/local/tmp/' + self.server_name)
230 | self.server_executor.submit(
231 | self.adb.unsafe_shell,
232 | '/data/local/tmp/{} -l 127.0.0.1:{} -D'.format(
233 | self.server_name, self.frida_server_port
234 | ),
235 | True
236 | )
237 |
238 | # waiting for frida server
239 | max_try = 100
240 | while True:
241 | try:
242 | if max_try < 0:
243 | self.log.info(
244 | "{} can't run frida server ".format(self.thread_tag))
245 | self.terminate()
246 | return
247 | time.sleep(0.5)
248 | if not self._terminate:
249 | self.device.enumerate_processes()
250 | self.log.info("frida-server connected")
251 | break
252 | except (frida.ServerNotRunningError, frida.TransportError,
253 | frida.InvalidOperationError):
254 | max_try -= 1
255 | continue
256 |
257 | def hook_apps(self):
258 | apps = set()
259 |
260 | self.log.info("filter: {}".format(options.regexps))
261 | # monitor apps
262 | while True:
263 | if self._terminate:
264 | break
265 |
266 | time.sleep(0.1)
267 |
268 | new_apps = set('{}:{}'.format(p.pid, p.identifier) for p in
269 | self.device.enumerate_applications())
270 |
271 | if not new_apps:
272 | continue
273 |
274 | incremental_apps = new_apps - apps
275 | decremental_apps = apps - new_apps
276 |
277 | for incremental_app in incremental_apps:
278 | pid, name = incremental_app.split(':', 1)
279 |
280 | if self._terminate:
281 | break
282 |
283 | for regexp in options.regexps:
284 | if re.search(regexp, name):
285 | # waiting for app startup completely
286 | time.sleep(0.1)
287 |
288 | try:
289 | self.hook(int(pid), name)
290 | except Exception as e:
291 | self.log.error(
292 | 'error occurred when hook {}@{}: {}'.format(
293 | name, pid, e))
294 | finally:
295 | break
296 |
297 | for decremental_app in decremental_apps:
298 | pid, name = decremental_app.split(':', 1)
299 |
300 | if self._terminate:
301 | break
302 |
303 | for regexp in options.regexps:
304 | if re.search(regexp, name):
305 | self.log.info('{}[pid:{}] has died'.format(name, pid))
306 | break
307 |
308 | apps = new_apps
309 |
310 | def hook(self, pid, name):
311 | if self._terminate:
312 | return
313 |
314 | js = Project.preload()
315 | spawn = options.spawn
316 | projects = []
317 | mode = "attach" if not spawn else "spawn"
318 | self.log.info('hook {}[pid={}] mode: {}'.format(name, pid, mode))
319 |
320 | for project in Project.scan(os.path.join(ROOT_DIR, 'projects')):
321 | projects.append(project)
322 |
323 | projects.sort(key=lambda p: p.priority)
324 |
325 | for project in projects:
326 | if project.enable:
327 | # if app match regexp
328 | if not re.search(project.regexp, name):
329 | continue
330 |
331 | js += project.load(name)
332 | if project.spawn:
333 | spawn = True
334 |
335 | js += Project.postload()
336 |
337 | # save js content
338 | os.makedirs(os.path.join(ROOT_DIR, 'js'), exist_ok=True)
339 | open(os.path.join(ROOT_DIR, 'js', name + ".js"), 'w').write(js)
340 |
341 | while True:
342 | try:
343 | if spawn:
344 | process = self.device.attach(self.device.spawn(name))
345 | else:
346 | process = self.device.attach(pid)
347 | break
348 | except frida.ServerNotRunningError:
349 | if self._terminate:
350 | return
351 |
352 | self.log.warning("frida server not running, wait one second")
353 | time.sleep(1)
354 |
355 | # wait for the app to start otherwise it will not hook the java function
356 | time.sleep(1)
357 |
358 | script = process.create_script(js)
359 | script.on('message', self.on_message(name))
360 | script.load()
361 |
362 | if spawn:
363 | self.device.resume(pid)
364 |
365 | def on_message(self, app: str):
366 | app_log = logging.getLogger(
367 | '{}|{}|{}'.format(self.__class__.__name__, self.device.id, app))
368 |
369 | def on_message_inner(message, data):
370 | try:
371 | if message['type'] == 'error':
372 | text = message['description'].strip()
373 |
374 | if not text:
375 | return
376 |
377 | app_log.error(text)
378 | else:
379 | if message['type'] == 'send':
380 | text = message['payload'].strip()
381 | else:
382 | text = message.strip()
383 |
384 | if not text:
385 | return
386 |
387 | app_log.info(text)
388 | except Exception as e:
389 | app_log.error(e)
390 |
391 | return on_message_inner
392 |
393 | def terminate(self):
394 | self._terminate = True
395 |
396 | def shutdown(self):
397 | self.log.debug('shutdown device ' + self.device.id)
398 |
399 | if self.device.type == 'remote':
400 | port_manager.release_port(self.forward_port)
401 | self.adb.clear_forward(self.forward_port)
402 |
403 | if options.port:
404 | self.iptables.uninstall()
405 | self.adb.clear_reverse(options.port)
406 |
407 | self.kill_frida_servers()
408 | self.server_executor.shutdown()
409 |
--------------------------------------------------------------------------------
/lib/core/options.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | import argparse
5 |
6 |
7 | class Options:
8 | def __init__(self):
9 | parser = argparse.ArgumentParser(description='基于frida的安卓hook框架,提供了很多frida自身不支持的功能,'
10 | '将hook安卓变成简单便捷,人人都会的事情,项目地址:'
11 | 'https://github.com/Margular/frida-skeleton')
12 |
13 | parser.add_argument('regexps', nargs='*', default=[r'^com\.'],
14 | help=r'根据你指定的正则表达式去匹配包名hook对应的程序,支持多个正则表达式')
15 | parser.add_argument('-l', '--list', action='store_true', help='显示设备列表')
16 | parser.add_argument('-d', '--devices', type=str, help='指定hook的设备,多个设备逗号隔开')
17 | parser.add_argument('-i', '--install', action='store_true',
18 | help='自动从github安装对应版本和架构的frida-server到assets目录下,支持断点续传,下载完后自动运行')
19 | parser.add_argument('-p', '--port', type=int,
20 | help='自动利用iptables和adb将所有的TCP流量重定向到PC的指定端口,这样就可以在本机监听该端口来抓包了')
21 | parser.add_argument('-n', '--no-root', action='store_true', help='不尝试使用adb root获取root权限,默认尝试')
22 | parser.add_argument('-s', '--spawn', action='store_true',
23 | help='开启frida的spawn模式并忽略项目配置文件中的spawn选项,开启此选项会导致被hook的进程自动重启')
24 | parser.add_argument("-r", "--random-port",help="随机生成frida-server监听端口,若不开启则使用默认端口27042",
25 | action='store_true')
26 | parser.add_argument('-v', '--verbose', action='store_true', help='输出调试信息')
27 |
28 | args = parser.parse_args()
29 |
30 | self.list = args.list
31 | self.devices = args.devices
32 | self.regexps = args.regexps
33 | self.install = args.install
34 | self.port = args.port
35 | self.no_root = args.no_root
36 | self.spawn = args.spawn
37 | self.random_port = args.random_port
38 | self.verbose = args.verbose
39 |
40 |
41 | options = Options()
42 |
--------------------------------------------------------------------------------
/lib/core/port_manager.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | import secrets
5 | import threading
6 |
7 | __lock__ = threading.Lock()
8 |
9 |
10 | class PortManager:
11 | def __init__(self):
12 | self.port_map = {}
13 |
14 | @classmethod
15 | def secure_rand_port(cls):
16 | while True:
17 | port = secrets.randbits(16)
18 | if port < 1024:
19 | continue
20 | return port
21 |
22 | def acquire_port(self, excludes=None):
23 | with __lock__:
24 | while True:
25 | port = PortManager.secure_rand_port()
26 |
27 | if port in self.port_map.keys() or port in excludes:
28 | continue
29 |
30 | self.port_map[port] = True
31 | return port
32 |
33 | def release_port(self, port):
34 | with __lock__:
35 | assert port in self.port_map.keys()
36 | self.port_map.pop(port)
37 |
38 |
39 | port_manager = PortManager()
40 |
--------------------------------------------------------------------------------
/lib/core/project.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | import logging
4 | import os
5 | import threading
6 |
7 | import yaml
8 |
9 | from lib.core.settings import ROOT_DIR, PROJECT_CONFIG_FILENAME
10 | from thirdparty.attrdict import AttrDict
11 |
12 | __lock__ = threading.Lock()
13 |
14 |
15 | class Project:
16 |
17 | def __init__(self, path: str, enable:bool, name: str, regexp: str, spawn: bool, priority: int):
18 | self.log = logging.getLogger(self.__class__.__name__ + '|' + name)
19 |
20 | self.path = path
21 | self.enable = enable
22 | self.name = name
23 | self.regexp = regexp
24 | self.spawn = spawn
25 | self.priority = priority
26 |
27 | @classmethod
28 | def logger(cls):
29 | with __lock__:
30 | if hasattr(cls, 'log'):
31 | return cls.log
32 | else:
33 | return logging.getLogger(cls.__name__)
34 |
35 | @classmethod
36 | def preload(cls) -> str:
37 | js = 'Java.perform(function() {\n'
38 |
39 | # recursively load js files in the script directory
40 | for (dirpath, dirnames, filenames) in os.walk(os.path.join(ROOT_DIR, 'scripts')):
41 | for filename in filenames:
42 | js += open(os.path.join(dirpath, filename), encoding="utf-8").read() + '\n'
43 |
44 | return js
45 |
46 | @classmethod
47 | def postload(cls) -> str:
48 | return '});'
49 |
50 | @classmethod
51 | def scan(cls, path: str):
52 | for entry in os.scandir(path):
53 | if entry.is_dir():
54 | try:
55 | config = AttrDict(yaml.safe_load(open(os.path.join(entry.path, PROJECT_CONFIG_FILENAME))))
56 | if config.regexp:
57 | yield Project(entry.path,
58 | config.enable if 'enable' in config.keys() else True,
59 | os.path.basename(entry.path),
60 | config.regexp,
61 | config.spawn if 'spawn' in config.keys() else False,
62 | config.priority if 'priority' in config.keys() else 0)
63 | except yaml.YAMLError as e:
64 | cls.logger().error('error in configuration file: {}'.format(e))
65 |
66 | def load(self, app: str) -> str:
67 | self.log.debug('loading script for {}...'.format(app))
68 |
69 | js = ''
70 |
71 | # recursively load js files
72 | for (dirpath, dirnames, filenames) in os.walk(self.path):
73 | for filename in filenames:
74 | if os.path.splitext(filename)[1] != '.js':
75 | continue
76 | js += open(os.path.join(dirpath, filename), encoding="utf-8").read() + '\n'
77 |
78 | return js
79 |
80 |
--------------------------------------------------------------------------------
/lib/core/settings.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | import os
5 | import sys
6 | import time
7 |
8 | import coloredlogs
9 |
10 | # The name of the operating system dependent module imported.
11 | # The following names have currently been registered: 'posix', 'nt', 'mac', 'os2', 'ce', 'java', 'riscos'
12 | PLATFORM = os.name
13 | IS_WIN = PLATFORM == "nt"
14 | ROOT_DIR = os.path.dirname(sys.argv[0])
15 | LOG_DIR = os.path.join(ROOT_DIR, 'logs')
16 | LOG_FILENAME = time.strftime('%Y-%m-%d_%H-%M-%S.log')
17 | FRIDA_SERVER_DEFAULT_PORT = 27042
18 | PROJECT_CONFIG_FILENAME = 'config.yaml'
19 |
20 | # coloredlogs
21 | coloredlogs.DEFAULT_LOG_FORMAT = "[%(asctime)s] [%(levelname)s] [%(name)s] %(message)s"
22 |
--------------------------------------------------------------------------------
/lib/core/types.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 |
5 | class FakeDevice:
6 | id = 'fake id'
7 | type = 'fake'
8 |
--------------------------------------------------------------------------------
/lib/core/watch_thread.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | import logging
5 | import threading
6 | import time
7 |
8 | import frida
9 |
10 | from lib.core.frida_thread import FridaThread
11 | from lib.core.options import options
12 | from lib.core.types import FakeDevice
13 | from lib.utils.adb import Adb
14 |
15 |
16 | class WatchThread(threading.Thread):
17 |
18 | def __init__(self):
19 | super().__init__()
20 |
21 | self.log = logging.getLogger(self.__class__.__name__)
22 |
23 | self.frida_threads = []
24 | self._terminate = False
25 |
26 | def run(self) -> None:
27 | self.log.debug('{} start'.format(self.__class__.__name__))
28 |
29 | while True:
30 | if self._terminate:
31 | break
32 |
33 | devices = frida.enumerate_devices()
34 |
35 | # usb devices from frida api
36 | usb_devices = [device for device in devices if device.type == 'usb']
37 | usb_devices_ids = [device.id for device in usb_devices]
38 |
39 | # devices strings from "adb devices"
40 | adb_devices_strings = Adb.devices().out.split('\n')[1:]
41 | adb_devices_strings = [_.split('\t')[0] for _ in adb_devices_strings]
42 |
43 | # we need to access these devices remotely
44 | remote_devices_strings = set(adb_devices_strings) - set(usb_devices_ids)
45 | remote_devices = []
46 |
47 | for _ in remote_devices_strings:
48 | new_device = FakeDevice()
49 | new_device.id = _
50 | remote_devices.append(new_device)
51 |
52 | for device in usb_devices + remote_devices:
53 | # only hook specified devices
54 | if options.devices and device.id not in options.devices:
55 | continue
56 |
57 | duplicated = False
58 |
59 | for t in self.frida_threads:
60 | if t.device.id == device.id:
61 | if not t.is_alive():
62 | self.frida_threads.remove(t)
63 | break
64 |
65 | duplicated = True
66 | break
67 |
68 | if duplicated:
69 | continue
70 |
71 | try:
72 | frida_thread = FridaThread(device)
73 | except RuntimeError as e:
74 | self.log.error('error occurred when init frida thread: {}'.format(e))
75 | else:
76 | frida_thread.start()
77 | self.frida_threads.append(frida_thread)
78 |
79 | time.sleep(0.1)
80 |
81 | self.shutdown()
82 | self.log.debug('watch thread exit')
83 |
84 | def terminate(self):
85 | self._terminate = True
86 |
87 | def shutdown(self):
88 | for frida_thread in self.frida_threads:
89 | if frida_thread.is_alive():
90 | self.log.debug('waiting for {}'.format(frida_thread.device))
91 | frida_thread.terminate()
92 |
93 | for frida_thread in self.frida_threads:
94 | while frida_thread.is_alive():
95 | time.sleep(1)
96 |
--------------------------------------------------------------------------------
/lib/utils/adb.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | from lib.utils.shell import Shell
5 |
6 |
7 | class Adb(Shell):
8 | def __init__(self, serial):
9 | super().__init__()
10 |
11 | self.serial = serial
12 | # if we are root shell
13 | self.is_root = False
14 | self.check_root()
15 |
16 | @classmethod
17 | def start_server(cls):
18 | return Shell().exec('adb start-server', supress_error=True)
19 |
20 | @classmethod
21 | def devices(cls):
22 | return Shell().exec('adb devices', quiet=True)
23 |
24 | def check_root(self):
25 | if self.unsafe_shell('whoami').out == 'root':
26 | self.is_root = True
27 |
28 | def root(self):
29 | self.exec('adb -s "{}" root'.format(self.serial))
30 | self.check_root()
31 |
32 | def unsafe_shell(self, command, root=False, quiet=False):
33 | return self.exec(r'''adb -s "{}" shell "{}{}"'''.format(
34 | self.serial, 'su -c ' if root and not self.is_root else '', command), quiet)
35 |
36 | def push(self, src, dst):
37 | return self.exec('adb -s "{}" push "{}" "{}"'.format(self.serial, src, dst))
38 |
39 | def reverse(self, port):
40 | return self.exec('adb -s "{0}" reverse tcp:{1} tcp:{1}'.format(self.serial, port))
41 |
42 | def clear_reverse(self, remote_port):
43 | return self.exec('adb -s "{}" reverse --remove tcp:{}'.format(self.serial, remote_port))
44 |
45 | def forward(self, local_port, remote_port):
46 | return self.exec('adb -s "{}" forward tcp:{} tcp:{}'.format(self.serial, local_port, remote_port))
47 |
48 | def clear_forward(self, local_port):
49 | return self.exec('adb -s "{}" forward --remove tcp:{}'.format(self.serial, local_port))
50 |
--------------------------------------------------------------------------------
/lib/utils/iptables.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 |
5 | class Iptables:
6 |
7 | def __init__(self, adb, port):
8 | self.adb = adb
9 | self.port = port
10 | self.install_cmd = 'iptables -t nat -A OUTPUT -p tcp -o lo -j RETURN'
11 | self.install_cmd2 = 'iptables -t nat -A OUTPUT -p tcp -j DNAT --to-destination 127.0.0.1:{}'.format(port)
12 | self.uninstall_cmd = 'iptables -t nat -D OUTPUT -p tcp -o lo -j RETURN'
13 | self.uninstall_cmd2 = 'iptables -t nat -D OUTPUT -p tcp -j DNAT --to-destination 127.0.0.1:{}'.format(port)
14 |
15 | def uninstall(self):
16 | self.adb.unsafe_shell(self.uninstall_cmd, root=True)
17 | self.adb.unsafe_shell(self.uninstall_cmd2, root=True)
18 |
19 | def install(self):
20 | self.adb.unsafe_shell(self.install_cmd, root=True)
21 | self.adb.unsafe_shell(self.install_cmd2, root=True)
22 |
--------------------------------------------------------------------------------
/lib/utils/shell.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | import logging
5 | from subprocess import Popen, PIPE
6 |
7 | from thirdparty.attrdict import AttrDict
8 |
9 |
10 | class Shell:
11 | def __init__(self):
12 | self.log = logging.getLogger(self.__class__.__name__)
13 |
14 | def exec(self, cmd: str, quiet=False, supress_error=False) -> AttrDict:
15 | ret = AttrDict()
16 |
17 | if not quiet:
18 | self.log.debug(cmd)
19 |
20 | p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE, close_fds=True)
21 |
22 | # output processing
23 | out = p.stdout.read().decode().strip()
24 | ret.out = out
25 | err = p.stderr.read().decode().strip()
26 | ret.err = err
27 |
28 | output = '{}