Repository: firerpa/lamda
Branch: 9.0
Commit: 1b22a228541c
Files: 96
Total size: 460.3 KB
Directory structure:
gitextract_h60805my/
├── .gitignore
├── CHANGELOG.txt
├── DISCLAIMER.TXT
├── LICENSE
├── README.md
├── SECURITY.md
├── all-llms.txt
├── examples/
│ ├── README.md
│ ├── activity_jump.py
│ └── search_in_taobao.py
├── extensions/
│ ├── README.md
│ ├── example_http_extension.py
│ ├── example_mcp_extension.py
│ ├── firerpa.py
│ ├── mcp_return_types.py
│ └── mcp_sms_reader.py
├── lamda/
│ ├── __init__.py
│ ├── client.py
│ ├── const.py
│ ├── exceptions.py
│ ├── google/
│ │ └── protobuf/
│ │ ├── any.proto
│ │ ├── api.proto
│ │ ├── compiler/
│ │ │ └── plugin.proto
│ │ ├── descriptor.proto
│ │ ├── duration.proto
│ │ ├── empty.proto
│ │ ├── field_mask.proto
│ │ ├── source_context.proto
│ │ ├── struct.proto
│ │ ├── timestamp.proto
│ │ ├── type.proto
│ │ └── wrappers.proto
│ ├── rpc/
│ │ ├── application.proto
│ │ ├── debug.proto
│ │ ├── file.proto
│ │ ├── policy.proto
│ │ ├── proxy.proto
│ │ ├── services.proto
│ │ ├── settings.proto
│ │ ├── shell.proto
│ │ ├── status.proto
│ │ ├── storage.proto
│ │ ├── types.proto
│ │ ├── uiautomator.proto
│ │ ├── util.proto
│ │ └── wifi.proto
│ └── types.py
├── properties.local.example
├── scripts/
│ ├── disable_flag_secure.yaml
│ └── disable_ssl_pinning_simple.yaml
├── setup.py
└── tools/
├── README.md
├── adb_pubkey.py
├── cert.py
├── debugimage.py
├── discover.py
├── firerpa.yml
├── frida_script_generate.py
├── fridarpc.py
├── globalmitm/
│ ├── DNS2SOCKS.c
│ ├── Dockerfile
│ └── entry
├── id_rsa
├── ida.py
├── magisk/
│ ├── META-INF/
│ │ └── com/
│ │ └── google/
│ │ └── android/
│ │ ├── update-binary
│ │ └── updater-script
│ ├── common/
│ │ ├── adb_keys
│ │ ├── properties.local
│ │ ├── server/
│ │ │ └── .keep
│ │ └── service.sh
│ ├── install.sh
│ ├── module.prop
│ └── uninstall.sh
├── objection-1.11.0-command-patch.diff
├── openvpn/
│ ├── Dockerfile
│ ├── config.ovpn
│ ├── entry
│ ├── ovpn-client-new
│ ├── ovpn-client-profile
│ ├── ovpn-client-renew
│ ├── ovpn-client-revoke
│ ├── ovpn-server-new
│ └── vars
├── paddle_ocr_http_api.py
├── requirements.txt
├── root.crt
├── root.key
├── rsync.sh
├── scp.sh
├── socks5/
│ ├── Dockerfile
│ └── entry
├── ssh.sh
├── startmitm.py
├── startmitm.spec
├── test-fridarpc.js
└── test.pem
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
================================================
FILE: CHANGELOG.txt
================================================
CHANGELOG
Version 9.20
-----------
* TOP Bridge now uniformly uses SAPI requests.
* MQTT connection adds max_inflight_messages and session_expiry_interval parameters.
* Added control tasks for system restart, file download, command execution, software update, etc.
* Added OpenAI task executor supporting semantic task execution.
* Integrated MCP extensions (API path /mcp/)
* Updated built-in Frida version.
BREAKING CHANGES
=================================================================
Version 9.20 includes some breaking changes, which only affect users who utilize hub and hub-bridge. The main issue is an adjustment made to the response data format of hub-bridge (this adjustment does not affect the underlying database). This will cause versions prior to 9.15 to fail to connect correctly to the new version of hub-bridge. However, the new version of lamda-server remains compatible with the old version of hub-bridge.
The solutions are as follows:
1. Use the new version of hub and hub-bridge (v3, which supports connecting to both local and remote devices), but all devices must be upgraded to version 9.20 or later.
2. Continue using the old version of hub and hub-bridge, which can normally connect to all versions around 9.20 and earlier.
=================================================================
Version 9.9
-----------
* Task and event system logic adjustments.
Version 9.8
-----------
* Added Python MMKV read/write library.
* Fixed auto exit of top bridge after removing device from hub.
* Improved stealth performance.
Version 9.5
-----------
* Fixed functional abnormalities on some device models.
* Fixed illegal instruction issue with Frida.
Version 9.4
-----------
* Added support for terminating all running tasks.
* Optimized memory usage.
Version 9.3
-----------
* Other optimizations and fixes.
Version 9.2
-----------
* Fixed stack overflow issue on some devices.
* Other optimizations and fixes.
Version 9.0
-----------
* Optimized network performance of the service.
* Switched MCP transport protocol to streamable-http with support for notifications and progress.
* Proxy now supports HTTPS and Shadowsocks protocols.
* Proxy now supports IPv6 and UDP protocols.
* Added P2P Bridge (peer-to-peer connectivity) support.
* Built-in distributed task system.
* Other optimizations and fixes.
Version 8.45
-----------
* Updated Frida version.
* Other compatibility fixes.
Version 8.44
-----------
* Optimized underlying Python compatibility.
* Added interface for playing WAV audio.
* Disabled H.264 screen projection by default.
Version 8.40
-----------
* Fixed incomplete /data mounting issue.
* Improved stability of enhanced automation.
Version 8.38
-----------
* Enhanced automation features.
* Fixed compatibility issues with some Samsung models.
* Optimized audio real-time performance.
Version 8.35
-----------
* Remote desktop now supports real-time audio streaming.
* Fixed segment fault in hex_patch.
Version 8.30
-----------
* Added binary patching interface.
* Added support for using child and sibling in Selector.
* Added support for viewing XML tree layout in remote desktop.
* Updated Frida to fix some issues.
Version 8.28
-----------
* Fixed install_local_file.
* Fixed Frida reporting ID increment issue.
* Improved built-in TensorFlow inference performance.
* Updated some third-party modules.
Version 8.25
-----------
* Added hexedit command.
* Permanently fixed Permission Loophole (maybe).
* Added on-device AI framework (tflite-runtime).
* Updated Frida for better stealth.
Version 8.22
-----------
* Switched to a new SQLite version.
* Remote desktop inspector now displays current coordinates and RGB values.
* Added plugin setup logic.
Version 8.20
-----------
* Added official MCP plugin.
* Improved Frida compatibility.
* Optimized MCP protocol implementation.
* Fixed self-recovery logic.
Version 8.18
-----------
* Reverted problematic Frida version.
* Added support for MCP and HTTP extension plugins.
Version 8.15
-----------
* Fixed service unavailability issue.
* Added support for calling exported scripts via jsonrpc.
* Fixed SSH user directory issue.
* Updated some submodules.
Version 8.12
-----------
* Fixed touch abnormality.
* Added some utility scripts.
* Enhanced stability.
Version 8.10
-----------
* Optimized self-recovery logic.
* Optimized touch compatibility.
Version 8.9
-----------
* Fixed parsing error.
Version 8.8
-----------
* Frida data reporting now supports AMQP.
* Fixed certificate issue caused by upstream library change in cert.py.
* Fixed resource release issue on service restart.
Version 8.5
-----------
* Optimized clipboard sharing logic.
* Added Frida script crash logs.
* Added support for Android 15.
Version 8.0
-----------
* Interfaces now fully support multi-instance applications.
* Remote desktop now supports shared clipboard.
* Added fix configuration for device models that cannot open apps.
* Added Yaml Frida script persistence.
* Fixed compatibility with lower version systems like Android 6.0.
* Fixed automation-related abnormalities on higher version systems.
* Removed/renamed some methods.
* Updated underlying implementation.
Version 7.90
-----------
* Persistent scripts now support spawn mode.
* Added support for output logs from persistent scripts.
* Fixed dump_window_hierarchy.
* Fixed Frida instance retrieval logic error.
Version 7.85
-----------
* Added support for mDNS service broadcasting.
* Added support for enumerating all elements selected by a selector.
* Client now includes an automatic retry mechanism.
* Fixed Bound comparison logic error.
* Allowed loading certificates remotely.
Version 7.80
-----------
* Optimized real-time screen projection smoothness.
* Added support for persistent hook scripts.
* Added Hook RPC support.
* Added data reporting support.
Version 7.76
-----------
* Fixed tool version dependencies.
* Fixed Python version matching issue.
* Updated some submodules.
Version 7.75
-----------
* Added OCR recognition interface.
* Added get_application_by_name.
* Updated some submodules and dependency versions.
Version 7.73
-----------
* Fixed white screen issue for some applications.
Version 7.72
-----------
* Updated some submodules.
* Fixed known issues.
Version 7.71
-----------
* Fixed Permission Loophole #95.
* Fixed enumerate_all_pkg_names.
Version 7.70
-----------
* Updated some submodules.
* Fixed known issues.
Version 7.68
-----------
* Optimized H.264 real-time screen projection.
Version 7.67
-----------
* Removed some invalid program logic.
* Fixed excessively long auto-recovery time.
* Other optimizations and fixes.
Version 7.65
-----------
* Removed IDA-related tools and interfaces.
* Fixed startup failure in some cases.
* Other optimizations and fixes.
* Added enhanced stealth mode.
Version 7.60
-----------
* Optimized image search speed.
* Added support for area screenshot in remote desktop.
* Fixed some remote desktop issues.
Version 7.57
-----------
* Added client interfaces for feature and template-based image search.
* Other optimizations and fixes.
Version 7.55
-----------
* Fixed abnormal display on screen rotation.
* Fixed disconnection on initial remote desktop connection.
* Fixed element existence check.
* Added Meta key definition.
* Other optimizations and fixes.
Version 7.52
-----------
* Fixed Magisk version Leidian emulator compatibility.
* Fixed service not exiting properly.
Version 7.50
-----------
* Permanently fixed Nox emulator compatibility.
* Fixed zombie process caused by logic error.
* New network subscription service, enabling networking without Frp/OpenVPN.
* Fixed multi-resolution system issue.
* Optimized system certificate injection logic for Android 13/14.
* Added support for multi-instance applications (user).
* OpenVPN now supports IPv6.
Version 7.30
-----------
* Fixed Leidian/Nox emulator compatibility.
* Minor adjustments.
Version 7.28
-----------
* Added show_toast interface.
* Built-in proxy now supports DNS traffic.
* startmitm now supports DNS via upstream proxy.
* Fixed Android 10+ Frida spawn.
Version 7.25
-----------
* Fixed scheduled task execution failure.
* Fixed startup failure from Termux.
* Updated built-in Frida version.
Version 7.22
-----------
* Added automatic system time synchronization.
* Updated some built-in modules.
* Minor fixes.
Version 7.20
-----------
* Reduced detection likelihood.
* Optimized lock mechanism, allowing locking for all API resources.
* Fixed emulator compatibility.
* Other minor modifications and fixes.
Version 7.15
-----------
* Added support for Android 14 (SDK 34).
* Fixed monitor registration exception.
* Improved remote desktop compatibility (theoretically supports all devices).
* Fixed scroll_from_bottom_to_top_to_end anomaly. Thanks ThanhPham.
* Fixed code errors in drag_to, long_click.
* Built-in OpenVPN now supports user/password login.
* Remote desktop now supports up to 60 FPS.
* Updated DISCLAIMER.TXT.
* Other minor modifications and fixes.
Version 5.6
-----------
* Fixed incomplete layout export. Thanks ThanhPham.
Version 5.5
-----------
* Fixed file corruption with adb push.
* Added install_local_file interface.
* Optimized code structure.
Version 5.3
-----------
* Added support for custom remote desktop password after using certificate.
* Fixed port multiplexing unsupported on some devices. Thanks alex han.
* Fixed issues with Magisk installation script.
* Fixed Debian launcher compatibility.
Version 5.2
-----------
* Fixed Selector containing False value being invalid. Thanks ThanhPham.
* LAMDA can now be used with other accessibility services simultaneously (Android >= 8.0 only).
Version 5.0
-----------
* Fixed a series of issues related to login certificates.
* Fixed Magisk module configuration reading strategy.
* Remote desktop and RPC now fully support TLS.
* Built-in Debian module can launch a Debian subsystem.
* Fixed remote desktop bugs and made simple layout adjustments.
* Adjusted service internal permissions and related directories.
* Improved server-side stability.
* Adjusted service installation method.
* Proxy service nameserver now supports specifying port.
* Added internal storage (memory configuration) read/write interfaces.
* A series of other updates and fixes.
=================================================================
Note: Version 5.0 client is not fully compatible with version 3.0. Please update both simultaneously.
=================================================================
Version 3.157
-----------
* Added UI element highlighting in inspector.
* Added support for system crash count.
Version 3.155
-----------
* Added Tab key traversal for UI elements.
* Added English character input support in remote desktop.
* Added remote desktop touch support.
Version 3.153
-----------
* Fixed screenshot failure in some scenarios.
* Minor changes.
Version 3.152
-----------
* Minor UI style adjustments.
Version 3.151
-----------
* Fixed high-DPI screen projection stretching issue #41.
Version 3.150
-----------
* Modified scheduled task reload logic.
* Fixed Scapy routing issue.
* Improved compatibility with some Xiaomi devices.
* Fixed Android 11 interface compatibility (Thanks Kate Swan).
* Added support for using 4G as proxy while connected to WiFi.
* Added new UI controls.
Version 3.138
-----------
* Fixed gRPC dependency issue.
* Added retrieval of last system toast.
Version 3.135
-----------
* Fixed remote desktop loading issue.
* Completely fixed race condition in protocol.
* Fixed Windows Python 3.10 compatibility.
* Allowed cross-site calls for HTTP interfaces.
* Added some missing modules.
* Added service status indicator to remote desktop.
* Added remote desktop responsive layout.
* Pre-release next version.
Version 3.123
-----------
* Fixed incomplete retrieval of recent activities.
Version 3.120-1
-----------
* LAMDA can now act as a proxy itself.
* Added interface to get system's recent activities.
* Fixed a potential race condition in protocol.
* Added some commands, removed SQLite db view.
* Experimental H.264 screen projection.
Version 3.108
-----------
* Optimized network disconnection handling.
* Added Redroid (remote android) support.
* Partial compatibility with uiautomator2.
* Added folder upload support.
Version 3.102
-----------
* Fixed file descriptor leak.
* Added support for loading startup config from remote file server.
* Now provides armeabi-v7a server build.
* Fixed root certificate installation failure under Magisk.
* Fixed configuration parsing error.
* Minor UI adjustments.
Version 3.98
-----------
* Added crontab, vi commands.
Version 3.95
-----------
* Fixed build process issues.
* Minor changes.
Version 3.93
-----------
* Added Android constant definitions.
Version 3.90
-----------
* Removed unused libraries to reduce size.
* Removed macOS-incompatible command line history feature from client.
* Updated DISCLAIMER.TXT.
* Updated some dependency versions.
Version 3.83
-----------
* Added WSA support #24 @aimardcr.
* Fixed black screen on note7pro MIUI10 @yu1chaofan.
* Minor changes.
Version 3.80
-----------
* Fixed SSH disconnection issue.
* Reduced package size.
Version 3.78
-----------
* Fixed issue #21 @yu1chaofan.
* Updated frida-server.
Version 3.76
-----------
* Default built-in shell changed to bash.
* Fixed remote desktop touch unusable after network disconnect.
* Fixed OpenVPN zombie process issue.
* startmitm.py now supports specifying adb serial number.
* Added Magisk auto-start support.
Version 3.0.59
-----------
* All UI prompts now in English.
* Fixed an unauthenticated web interface issue.
* Fixed compatibility with older versions.
Version 3.0.55
-----------
* Fixed crash caused by wide-character request headers.
* Merged mitmweb into startmitm process.
* Minor Docker image modifications.
* Added layout inspection support.
Version 3.0.50
-----------
* Added child, sibling selector support.
Version 3.0.48
-----------
* Added portable Windows startmitm command.
* Added support for uploading/downloading files from/to memory.
* Added screenshot() alias.
Version 3.0.47
-----------
* Simplified globalmitm, added HTTP, SOCKS5 proxy support.
* Enhanced webview node finding.
Version 3.0.46
-----------
* Added two-finger zoom support.
* Simplified startmitm DNS man-in-the-middle operations.
Version 3.0.45
-----------
* Added custom server port support (--port).
* Fixed hang on special files during directory index file type detection.
* globalmitm now checks DNS service availability.
* startmitm.py selected wrong network interface when multiple networks present.
* Client communication no longer automatically uses system proxy.
Version 3.0.35
-----------
* Improved built-in ADB performance.
* OpenVPN service now supports auth parameter (default SHA1).
* Fixed issue with scrcpy via built-in ADB.
================================================
FILE: DISCLAIMER.TXT
================================================
DISCLAIMER
To use this service, you (hereinafter referred to as "the user") must agree to all terms of this agreement and complete the entire application process as prompted on the page. You can find DISCLAIMER.TXT in the source code or the released program, or view the copy below.
To download and use the LAMDA software (hereinafter referred to as "this service"), developed by firerpa (address: github.com/firerpa, email: lamda.devel@gmail.com, hereinafter referred to as "the developer"), you must carefully read and agree to all terms in this agreement. Please ensure you have fully understood and agreed to the following content before downloading, installing, or using this software.
You are not authorized to download, install, or use this software and its related services until you have fully read and accepted the terms of this agreement. Once you download, install, or use this software, you are deemed to have read and agreed to all terms of this agreement and are willing to be bound by them.
Risk Disclosure:
This service requires the device to have root access to run, and the default communication protocols and related certificate files are open information, which may increase the risk of your device being compromised.
This service may contain unknown logical errors that could lead to potential risks such as data loss, system crashes, etc. The user decides whether to download and use this service at their own risk.
1. The purpose of this service is to improve the work efficiency of security analysis and testing personnel for tasks such as application behavior and compliance analysis. The related tools provided are for legitimate and compliant app testing, analysis, and mock scenarios.
This service itself does not provide any functionality to intrude, modify, or capture memory and network data of other applications. It integrates services provided by various major open-source frameworks for users to choose from, to facilitate security analysts and to reduce users' repetitive work and management costs.
This service is not for profit. Users can download and use it according to their own needs, and no fees will be charged during the download or use process.
2. This service respects and protects the user's personal privacy and will not steal any information from the user's device. The startup of this service and any rights to read, store, or transmit device data are entirely under the user's control.
3. Users must use this service on a virtual device or a dedicated device with no private data. When using this service, users must comply with the laws and regulations of the People's Republic of China or the user's country or region of residence.
This service must not be used for any illegal purposes, nor for any activities detrimental to others.
4. Users may only use this service for legitimate study, research, or legally authorized application analysis, testing, and other similar activities. If a user violates the above principles and causes losses to a third party while using the software service, the user shall bear all responsibility.
5. The developer shall not be held legally responsible for any accident, negligence, contract breach, defamation, copyright or intellectual property infringement, or any resulting losses (including but not limited to direct, indirect, incidental, or consequential losses) incurred by any entity or individual as a result of downloading or using this service.
6. You may use this service for commercial purposes, but only for extending functionality or developing products based on the features, interfaces, or related services provided by this service. You agree not to use this service, its related services, or interfaces for any purpose that violates local laws and regulations, or to engage in activities that harm the interests of others.
7. The user explicitly agrees to all the contents listed in the terms of this agreement and shall solely bear any potential risks and consequences arising from the use of this service. The developer assumes no legal liability.
8. The developer has the right to unilaterally change, interrupt, or terminate part or all of this service and the terms of this disclaimer and its attachments at any time. Such changes will be announced via message push, web announcements, or other means and will take effect immediately upon publication without separate notification. If you continue to use the service after the disclaimer has been changed, it indicates that you have fully read, understood, and accepted the modified content.
9. If any part of this disclaimer is deemed invalid or unenforceable, that part shall be amended in a manner consistent with applicable law to reflect, as closely as possible, the developer's original intent, and the remaining portions shall remain in full force and effect. The unenforceability of any part of this disclaimer does not constitute a waiver by the developer of the right to enforce that provision.
10. Reservation of Rights: All other rights not expressly granted herein are reserved by the developer.
Please confirm that you have read and accepted all the terms of this agreement. Otherwise, you are not authorized to download, install, or use this software and its related services.
================================================
FILE: LICENSE
================================================
Copyright (c) 2021 - present, firerpa
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# **FIRERPA Android** | AI-Powered Automation
An all-in-one next-gen Android automation framework blending robust on-device services with AI-ready agents and extensible tool-calling capabilities.
Documentation | 使用文档 | TELEGRAM | QQ Group | llms.txt | llms-full.txt
Core Capabilities
FIRERPA is a lightweight, on-device Android automation stack with no external dependencies. It unifies low-latency remote desktop with 160+ APIs for device discovery/status/logs, system and app control, UI automation, OCR/image matching, file I/O, storage, scheduling, and shell execution, plus distributed deployment via Hub/FRP/OpenVPN. Built-in ADB/SSH/SCP, logging, API locking, certificates, script encryption, proxy/VPN/MITM utilities, and reverse tooling (Frida persistence/export/report, IDA debugging, binary patching) round out production workflows, alongside MCP/Agent tool-calls and extensions.
Deployment & Stability
FIRERPA delivers stable automation across Android 6.0 to 16, including emulators and cloud phones. Built to run non-intrusively, it requires no complex configuration and supports rooted, long-running services with clean install/upgrade/uninstall flows and repeatable lifecycle control.
160+ APIs & Python SDK
FIRERPA offers extensive APIs covering system config, app/process control, UI selectors/gestures, file I/O, encrypted key-value storage, OCR, and image matching. The Python SDK mirrors the full surface and provides higher-level helpers to build reliable automation quickly.
Remote Desktop & Diagnostics
Monitor and control devices visually with minimal setup. Includes file upload/download, WebSocket video (MJPEG/H.264), touch streaming, UI inspection, and event monitoring for validation and real-time diagnostics.
---
Our project is not entirely open source to prevent increased countermeasure costs. We remain committed to keeping it free for the community. We guarantee no malicious code; you are welcome to reverse-engineer and analyze its security. See our [Privacy Policy](https://device-farm.com/privacy) or [Contact Us](https://device-farm.com/contact) for enterprise support.
================================================
FILE: SECURITY.md
================================================
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 9.x | :white_check_mark: |
## Reporting a Vulnerability
mailto:lamda.devel@gmail.com
================================================
FILE: all-llms.txt
================================================
FIRERPA (FIRERPAλ) - COMPLETE DOCUMENTATION
========================================
TABLE OF CONTENTS
========================================
1. OVERVIEW
2. SETUP & INSTALLATION
- Requirements
- Pre-installation Checklist
- Server Installation Methods
- Client Installation
- Network Configuration
- Starting/Stopping/Uninstalling
3. BASIC USAGE
- Preparation for Use
- Connecting via Python
4. REMOTE DESKTOP
- Access & Connection
- Security Configuration
- File Management
- Customization Options
- Limitations
5. DEVICE DISCOVERY (mDNS)
6. SCHEDULED TASKS
7. BUILT-IN TERMINAL
8. VIRTUAL DEBIAN ENVIRONMENT
9. PROGRAMMING INTERFACE (API)
- Basic Operations
- Device Information
- Screen Control
- Input Operations
10. ADVANCED INTERFACE OPERATIONS
11. IMAGE MATCHING OPERATIONS
12. TEXT RECOGNITION (OCR)
13. INTERFACE MONITOR
14. BUILT-IN FRIDA
- Three Usage Methods
- Persistent Frida Scripts
- Frida Data Reporting
- Frida Exported Interfaces (RPC)
15. BINARY PATCH
16. BUILT-IN ADB MANAGEMENT
17. CONNECTIVITY
- SSH Connection
- File Transfer (SCP)
18. ONE-CLICK PACKET CAPTURE
19. ENCRYPTION CERTIFICATES
20. DISTRIBUTED DEPLOYMENT
21. DEPLOYING OPENVPN SERVICE
22. INTERFACE LOCKING
23. VERSION HISTORY
========================================
1. OVERVIEW
========================================
FIRERPAλ is an Android device management and automation system that requires rooted devices.
It provides:
- Remote desktop access via browser
- Comprehensive automation APIs (160+ interfaces)
- Advanced debugging capabilities
- Built-in Frida with anti-detection
- Packet capture and network manipulation
- Device farm management
ARCHITECTURE:
- Server component: Runs on rooted Android devices
- Client library: Python 3.6-3.12
- Default port: 65000
- Browser-based remote desktop (Chrome 95+)
PHILOSOPHY:
The documentation emphasizes sequential reading from first chapter. Users should read
documentation when encountering issues before seeking community support.
========================================
2. SETUP & INSTALLATION
========================================
REQUIREMENTS
------------
- Android phone with root privileges
- Minimum: 2GB+ RAM, 1GB+ available storage
- Python 3.6-3.12 for client
- Recommended emulators: NoxPlayer, LDPlayer, AVD
- Chrome 95+ browser for remote desktop
PRE-INSTALLATION CHECKLIST
---------------------------
CRITICAL - Must complete before installation:
1. Turn off Magisk Hide
2. Turn off frida-server
3. Restart the device after confirmation
4. Disable or uninstall all accessibility services (talkback, autojs, etc.)
5. Configure timezone and time correctly
- Settings > Date and Time
- Verify timezone matches location
- If automatic fails, set manually
Skipping these steps may cause compatibility issues with FIRERPA interfaces and
remote desktop functionality.
NETWORK CONFIGURATION
---------------------
Real Devices:
- Ensure computer and phone share the same network
Emulators:
- Android x86 (VMWare): Set bridge mode in virtual machine settings
- LDPlayer/NoxPlayer: Install drivers, enable bridge mode, restart
- Android Studio AVD: Execute `adb forward tcp:65000 tcp:65000`, use localhost to connect
- WSA (Windows Subsystem Android):
* Minimum version: 2210.40000 (rooted)
* Settings: Continuous subsystem resources, disable Advanced Networking
* Enable Developer Mode and "Support single machine UI automation"
* Restart subsystem
- Redroid (Docker-based Android):
* For Ubuntu 20.04:
apt install linux-modules-extra-`uname -r`
* Edit /etc/modules, add: mac80211_hwsim, binder_linux, ashmem_linux
* Restart host machine
* Launch container mapping port 65001 to container's 65000
* Access via: http://127.0.0.1:65001
Note: Use localhost instead of 127.0.0.1 for tools requiring USB-based detection.
SERVER INSTALLATION METHODS
----------------------------
METHOD 1: APP Installation (Easiest)
-------------------------------------
"This installation method is the easiest and doesn't require you to do any extra operations."
Steps:
1. Download lamda-autorun.apk from the provided link
2. Install and grant root privileges within the app
3. Enable auto-start switch
4. Add app to auto-start whitelist if device settings restrict auto-launching
5. Restart the device
6. Wait 1-2 minutes after restart
7. Find device's IP address in WIFI settings (example: 192.168.1.8)
8. Navigate to http://192.168.1.8:65000 in browser to access remote desktop
Troubleshooting:
- If connection fails, check network connectivity and simulator bridging
- Manual startup: adb shell su root sh /data/server/bin/launch.sh
METHOD 2: Magisk Module Installation
-------------------------------------
For devices running Magisk 20.4 or newer:
1. Download lamda-magisk-module.zip from releases page
2. Use Magisk App → Modules → Install from local
3. Restart device
The system waits 30 seconds before launching to prevent crashes.
Auto-starts at boot.
METHOD 3: Manual Installation
------------------------------
For devices without Magisk support:
Step 1: Get Device Architecture
getprop ro.product.cpu.abi
Common outputs: arm64-v8a, x86, x86_64, armeabi-v7a
Step 2: Push Files
adb push lamda-server-[arch].tar.gz /data/local/tmp
adb push busybox-[arch] /data/local/tmp
Step 3: Extract and Configure
After entering `adb shell` and running `su`:
chmod 755 /data/local/tmp/busybox-[arch]
/data/local/tmp/busybox-[arch] tar -C /data -xzf /data/local/tmp/lamda-server-[arch].tar.gz
rm /data/local/tmp/lamda-server-[arch].tar.gz
rm /data/local/tmp/busybox-[arch]
IMPORTANT CONFIGURATION NOTES:
- Directory Location: /data/usr serves as FIRERPA's user data directory
- Must be manually created before first startup if pre-configuring
- Startup Configuration: Use properties.local file for automatic connections to OpenVPN,
proxies, and port forwarding
- Security Warning: "FIRERPA installed by default does not enable any authentication"
Requires trusted network usage unless encryption certificates are enabled
CLIENT INSTALLATION
-------------------
Requirements: Python 3.6-3.12
Installation:
pip3 install -U lamda[full]
Alternative (if above fails):
pip3 install -U 'lamda[full]'
Tip: Add -i parameter to specify nearby PIP mirror for faster installation
Verification:
python3 -m lamda.client
COMMON ISSUES AND SOLUTIONS:
1. UnicodeEncodeError with Chinese characters:
- Occurs when system paths contain non-ASCII characters
- gRPC cannot process correctly
- Verify: import sys; print(sys.path)
- Remove Chinese paths from environment variables like PYTHONPATH
2. ImportError problems:
pip3 install -U --force-reinstall 'lamda[full]'
3. Persistent issues:
- Use a virtual environment (uv recommended based on user settings)
Post-installation:
Update third-party Frida-dependent libraries (frida-tools, objection, etc.)
to prevent runtime anomalies.
STARTING THE SERVER
-------------------
Auto-starts if installed via Magisk or auto-start APP after device restart.
Manual installations require:
sh /data/server/bin/launch.sh
Steps:
1. Enter adb shell and switch to root: su
2. Run launch command
3. Wait for process completion
4. Success message: "llllaamDaa started"
5. Service operates in background mode
TROUBLESHOOTING ERROR MESSAGES:
Error | Resolution
-----------------------|--------------------------------------------------
already running | Service is currently active
invalid TZ area | System timezone requires configuration
not run as root | Command must execute with root privileges
unsupported sdk | Android version incompatibility detected
abi not match | Incorrect architecture package installed
file broken | Reinstall required due to corruption
Notes:
- Initial remote desktop access may experience loading delays; device restart may help
- After successful startup, close terminal and proceed with other tasks
- Failed startups require troubleshooting based on displayed error messages
EXITING THE SERVER
------------------
"FIRERPAλ is intended for continuous 24/7 operation; frequent starts/stops are not recommended"
METHOD 1: Command Line (Primary)
kill -SIGUSR2 $(cat /data/usr/lamda.pid)
METHOD 2: Interface-Based
Use "Shutdown and Restart" section in documentation
IMPORTANT WARNINGS:
- Service Design: Intended for continuous operation; frequent cycling not recommended
- Exit Duration: Complete shutdown may require more than 10 seconds
- Command Caution: Execute kill command only once; repeating consecutively can cause instability
- Risk Advisory: Frequent service cycling can easily introduce system instability
UNINSTALLING THE SERVER
------------------------
Prerequisites:
Follow "Exiting the Server" and wait at least 30 seconds to ensure service exits normally.
STANDARD INSTALLATION:
rm -rf /data/server /data/usr
MAGISK INSTALLATION:
Simply remove from Magisk modules—no additional commands needed.
WARNING:
"Please be cautious when removing the /data/usr directory, as this directory contains
relevant data for your use of the FIRERPA service."
After uninstallation:
"FIRERPA will be completely removed from your device without leaving any other files."
Recommend restarting device afterward to ensure clean completion.
========================================
3. BASIC USAGE
========================================
PREPARATION FOR USE
-------------------
Before continuing with automation and API usage:
1. Ensure FIRERPA server on mobile device has been started
2. Default port: 65000
3. Obtain device IP address from WLAN settings
4. Port specification not necessary unless modified from default
5. NAT Considerations: Some devices behind NAT; refer to Installation Preparation if
IP identification problematic
Example: Documentation assumes reference device IP of 192.168.0.2
CONNECTING VIA PYTHON
----------------------
Basic connection:
from lamda.client import *
d = Device("192.168.0.2")
With encryption certificate:
from lamda.client import *
d = Device("192.168.0.2", certificate="/path/to/lamda.pem")
WORKING WITH API RESPONSES:
Many interfaces return native proto classes. Access values through output properties:
result = status.get_battery_info()
print(result.batt_temperature) # Access specific field
SIMPLE EXAMPLES:
Display message on device:
d.show_toast("Hello from Lamda!")
Make device beep (useful for locating devices):
d.beep()
========================================
4. REMOTE DESKTOP
========================================
ACCESS & CONNECTION
-------------------
Network-based device control via browser.
Access: http://192.168.0.2:65000
Requirements: Chrome 95+ browsers
Mode: Single active operator - only first connected user can control device
Subsequent connections display in view-only mode
SECURITY CONFIGURATION
----------------------
When launching server with encryption certificates via --certificate:
- HTTPS becomes mandatory
- Convert URL to https://
- Authenticate with certificate's embedded password (line one of certificate file)
- Custom credentials in properties.local: ssl-web-credential=12345
FILE MANAGEMENT
---------------
Upload Capability:
- Drag files/folders directly onto right-side terminal
- Supports up to 2,000 simultaneous uploads
- 256MB per-file limit
- All uploaded content receives 644 permissions
- Default storage: /data/usr/uploads (configurable)
Download Method:
- Access http://192.168.0.2:65000/fs/ to browse device files in index format
- Or use folder icon in remote desktop's top-right corner
CUSTOMIZATION OPTIONS
----------------------
Settings gear (upper-right) provides adjustments for:
- Upload directory
- Frame rate
- Resolution scaling
- Image quality
- Clipboard sharing
Video Encoding Options:
- H.264: Reduces bandwidth
- Hardware acceleration: Select "System" backend
- "Default": Uses software encoding
LIMITATIONS
-----------
- Platform doesn't support Chinese character input—only standard English text works
- For enhanced keyboard functionality, explore "Connecting to Built-in ADB" documentation
- Tools like Genymobile/scrcpy recommended for advanced input
========================================
5. DEVICE DISCOVERY (mDNS)
========================================
OVERVIEW
--------
mDNS-based discovery of FIRERPA devices on local networks.
Requirements: FIRERPA version 7.85 or higher
Enables: Locate devices and access services via domain names like {ro.serialno}.local
DEVICE DISCOVERY METHODS
------------------------
Using mdns-beacon Tool:
1. Install: pip install mdns-beacon
2. Execute: mdns-beacon listen --service _lamda._tcp.local.
Displays all devices running FIRERPA on current network.
Note: Results may vary based on network configuration and device model compatibility.
Accessing via Domain Names:
Format: {android_id}.local:65000
Retrieve Android device ID: adb shell settings get secure android_id
Programmatic Discovery:
Use zeroconf library (python-zeroconf) for automated device discovery and
Android device information enumeration.
CONFIGURATION OPTIONS
---------------------
Customize via properties.local configuration file:
mdns.meta=true - Enable broadcasting of device information (ID, ABI, Android version, model)
mdns.service - Modify mDNS service name from default "lamda"
mdns.name - Assign fixed, unique server names per device for enhanced privacy
========================================
6. SCHEDULED TASKS
========================================
OVERVIEW
--------
Enable periodic execution of scripts following standard Linux Crontab syntax.
"All rules will be executed with root privileges."
REQUIREMENTS:
- Access via FIRERPA's remote desktop terminal, built-in ADB terminal, or SSH terminal
- Basic crontab knowledge required
- Device should remain powered on for reliable task execution
IMPLEMENTATION STEPS
--------------------
1. Enter Edit Mode: crontab -e
2. Write Rules: Press 'i' (English input mode), then add crontab expressions
3. Save: Press ESC, then SHIFT+:, type 'wq' and press Enter
COMMON CRONTAB EXAMPLES
-----------------------
Schedule | Command
----------------------------------|------------------------------------------------
System startup | @reboot echo Execute when framework starts
Hourly | 0 */1 * * * echo Execute every hour
Every minute | * * * * * echo Execute every minute
Daily at 8 AM | 0 8 * * * echo Execute at 8 o'clock daily
OUTPUT REDIRECTION:
To save task output:
* * * * * echo hello >/data/usr/script.log 2>&1
IMPORTANT NOTE:
"Due to Android's sleep mechanism, scheduled tasks may not run at the time you expect
after the screen is turned off."
VALIDATION:
Use an online Crontab verification website to confirm rule accuracy before deployment.
========================================
7. BUILT-IN TERMINAL
========================================
OVERVIEW
--------
Key FIRERPA feature allowing real-time command execution.
Access: Through remote desktop, SSH, or ADB connections
Note: "The terminal supports command completion but not parameter completion."
AVAILABLE ALIASES
-----------------
l → ls
ll → ls -l
la → ls -la
py → python
.. → Navigate parent directory
... → Navigate parent directories
t → Go to /data/local/tmp
p → Return to previous directory
BUILT-IN COMMANDS
-----------------
The system includes numerous pre-installed tools:
Development & Analysis:
Python, strace, ltrace, frida tools suite, capstone, keystone, unicorn
Networking:
curl, scapy, tcpdump, socat, stunnel, redir, iperf3
System Tools:
busybox, ncdu, nano, vi, sqlite3, MemDumper, fsmon
PYTHON LIBRARIES
----------------
Pre-installed third-party modules:
Cryptography:
Crypto, OpenSSL, bcrypt, cryptography
Image Processing:
PIL, cv2
Data Handling:
protobuf, msgpack, ujson
Networking:
requests, websocket
Specialized Tools:
frida, numpy, redis, peewee
CRITICAL LIMITATION:
"You cannot install additional libraries through PIP or APT in the built-in terminal environment."
Users requiring additional dependencies should utilize the Virtual Debian Environment.
========================================
8. VIRTUAL DEBIAN ENVIRONMENT
========================================
OVERVIEW
--------
Complete Debian environment within Android devices, comparable to Termux or androdeb.
Allows: apt package installation, software compilation, BPF program development on Android.
INSTALLATION STEPS
------------------
1. Download Package:
Obtain lamda-mod-debian-arm64-v8a.tar.gz from project's release page
(select architecture-appropriate version)
2. Upload via Remote Desktop:
Use remote desktop interface to drag and upload file
Default location: /data/usr/uploads
3. Extract Installation (one-time command):
tar -C /data/usr/modules -xzf /data/usr/uploads/lamda-mod-debian-arm64-v8a.tar.gz
BASIC USAGE
-----------
Entering Interactive Terminal:
debian /bin/bash
Executing Single Commands:
debian /bin/bash -c id
IMPORTANT LIMITATION:
"Only one instance can enter the virtual environment at a time. After you execute
debian /bin/bash and keep using it, if you continue to execute this command in other
terminals, it will return an error, unless you exit the first started debian /bin/bash."
ADVANCED SETUP (SSH + Python)
------------------------------
1. Enter environment and install packages
2. Configure SSH settings in sshd_config
3. Set root password
4. Start SSH daemon: debian /usr/sbin/sshd -D -e
5. Automate via scheduled tasks:
@reboot debian /usr/sbin/sshd -D -e >/data/usr/sshd.log 2>&1
6. Connect remotely: ssh root@[device-ip]
Default password: lamda
========================================
9. PROGRAMMING INTERFACE (API)
========================================
OVERVIEW
--------
"Up to 160 programming API interfaces, allowing you to manage and operate Android
devices meticulously."
Coverage: Command execution, system settings, application management, automation,
proxy services, file operations.
BASIC OPERATIONS
----------------
Display Message:
d.show_toast("message")
Beep (useful for locating devices):
d.beep()
DEVICE INFORMATION
------------------
Get Device Details:
d.device_info()
Returns: productName, sdkInt, displayHeight, displaySizeDpX, displaySizeDpY,
displayWidth, screenOn, naturalOrientation, currentPackageName
Server Information:
d.server_info()
Returns: uniqueId, version, architecture, uptime, secure metrics
Screen Status Checks:
d.is_screen_on() - Determines if display is active
d.is_screen_locked() - Verifies lock state
SCREEN CONTROL
--------------
d.sleep() - Deactivates display (power button equivalent)
d.wake_up() - Activates display
d.screenshot(60) - Captures with quality parameter
d.screenshot(60, Bound(top, left, right, bottom)) - Regional cropping
INPUT OPERATIONS
----------------
Touch Actions:
d.click(Point(x=100, y=100)) - Single tap
d.drag(Point_A, Point_B) - Press and drag motion
d.swipe(Point_A, Point_B) - Swipe gesture
d.swipe_points(p1, p2, p3) - Multi-point swipe path
Physical Keys:
d.press_key(Keys.KEY_BACK) - Supports KEY_BACK, KEY_HOME, KEY_VOLUME_UP,
KEY_POWER, and others
d.press_keycode(code) - Additional Android KeyEvent codes
SYSTEM INTERFACE
----------------
d.get_clipboard() - Retrieve clipboard content (Android 9 and below)
d.set_clipboard("text") - Write clipboard data
d.dump_window_hierarchy() - Extract current page XML layout
d.wait_for_idle(5000) - Pause until UI stabilizes (milliseconds)
d.get_last_toast() - Retrieve latest system toast
SETTINGS & NOTIFICATIONS
------------------------
d.open_quick_settings() - Display settings panel (half-open state)
d.open_notification() - Expand notification drawer
========================================
10. ADVANCED INTERFACE OPERATIONS
========================================
OVERVIEW
--------
Deep automation interfaces for detailed operations on mobile device interfaces.
GETTING ELEMENTS
----------------
Elements located via selectors.
Important: "The element you click directly on the left interface may not be the actual
element" due to overlapping components.
Selection Methods:
element = d(text="同意")
element = d(resourceId="com.tencent.news:id/btm_first_agree")
ELEMENT OPERATIONS
------------------
Click Operations:
element.click() - Standard click
element.click_exists(corner=Corner.COR_TOPLEFT) - Click with position specification
element.long_click() - Extended press
element.click_exists() - Conditional click without exception throwing
Information Retrieval:
element.exists() - Existence check
element.info() - Detailed metadata (bounds, className, text)
element.count() - Quantity of matching elements
element.get(3) - Retrieve specific nth element
Coordinate Access:
info.bounds.center() - Center point
info.bounds.corner("top-left") - Corner coordinates
info.bounds.width, info.bounds.height - Dimensions
TEXT INPUT
----------
Important: "When getting input box elements, your input method must be in a popped-up
state before finding relevant elements."
Operations:
element.set_text("你好世界") - Input text
element.get_text() - Retrieve current content
element.clear_text_field() - Clear field
NAVIGATION
----------
Normal Swipe:
d().swipe(direction=Direction.DIR_UP, step=32)
Fast Swipe:
d().fling_from_top_to_bottom()
d().fling_from_bottom_to_top()
d().fling_from_left_to_right()
d().fling_from_right_to_left()
Scroll (mechanical swiping):
d().scroll_from_top_to_bottom(step)
d().scroll_from_bottom_to_top(step)
d().scroll_from_left_to_right(step)
d().scroll_from_right_to_left(step)
ADVANCED FEATURES
-----------------
element.wait_for_exists(10*1000) - Wait for appearance (milliseconds)
element.wait_until_gone(10*1000) - Wait for disappearance
element.screenshot(quality=60) - Capture element-level screenshots
element.child(index=1) - Locate child elements
element.sibling() - Find sibling elements
element.drag_to(Selector(...)) - Drag operations
========================================
11. IMAGE MATCHING OPERATIONS
========================================
OVERVIEW
--------
Locate and interact with UI elements through image matching.
Two primary methods: Template matching and Feature point matching (SIFT)
KEY CONCEPTS:
- Template matching: Better performance, consistent resolutions
- Feature point matching: Adapts to varying screen resolutions, requires threshold tuning
- Processing: Server-side (preserves local resources)
- Limitation: Mobile device performance may limit efficiency
MAIN FUNCTION SYNTAX
--------------------
d.find_similar_image(
data,
threshold=0.0,
distance=250,
scale=1.0,
area=FindImageArea.FIA_WHOLE_SCREEN,
method=FindImageMethod.FIM_TEMPLATE
)
PARAMETERS
----------
Parameter | Purpose
-----------|------------------------------------------------------------------
data | Image byte data for matching
threshold | Similarity filter level
distance | Maximum feature point tolerance
scale | Performance optimization ratio
area | Screen region restriction
method | Algorithm selection
MATCHING METHODS
----------------
FIM_TEMPLATE:
- Fast, texture-dependent
- Limited rotation/scale tolerance
FIM_FEATURE:
- Robust against transformations
- Suits complex scenes
MATCHING AREAS
--------------
Nine predefined regions optimize performance:
- Full-screen
- Halves: left, right, top, bottom
- Corners: top-left, top-right, bottom-left, bottom-right
========================================
12. TEXT RECOGNITION (OCR)
========================================
OVERVIEW
--------
Leverage OCR technology for interface automation when conventional selectors don't work.
Particularly useful in gaming applications.
OCR BACKEND SETUP
-----------------
PaddleOCR with GPU:
d.setup_ocr_backend(
"paddleocr",
quality=80,
use_gpu=True,
drop_score=0.85,
use_space_char=True
)
EasyOCR for Chinese and English:
d.setup_ocr_backend("easyocr", ["ch_sim", "en"], quality=80)
Custom HTTP Backend:
For systems managing multiple devices, implement custom backend as HTTP service
to avoid excessive resource consumption from repeated initialization.
OCR SELECTOR TYPES
------------------
Method | Purpose
------------------------|--------------------------------------------------
text="Mine" | Exact text matching
textContains="Mine" | Partial text matching
textMatches=".*?Mine" | Regex pattern matching
SUPPORTED OPERATIONS
--------------------
- click() - Activate matched element
- click_exists() - Conditional activation
- exists() - Verify presence
- screenshot() - Capture element image
- info() - Retrieve OCR metadata
KEY LIMITATION:
"OCR recognition methods only support checking if elements exist, clicking, taking
screenshots, and other operations."
More complex interactions aren't available through this interface.
========================================
13. INTERFACE MONITOR
========================================
OVERVIEW
--------
Real-time listener for UI changes that performs automatic actions when conditions match.
Functions similarly to ad-skipping automation tools.
Caution: Performs automatic clicks and key presses that may interfere with manual tasks.
CORE FUNCTIONS
--------------
Enable Monitoring:
d.set_watcher_loop_enabled(True)
Check Status:
d.get_watcher_loop_enabled()
Disable Monitoring:
d.set_watcher_loop_enabled(False)
Clear All Events:
d.remove_all_watchers()
EVENT REGISTRATION TYPES
------------------------
Click Events - Auto-clicks matching UI elements:
d.register_click_target_selector_watcher("EventName", [Selector conditions], target_selector)
Key Press Events - Triggers keyboard actions:
d.register_press_key_watcher("EventName", [Selector conditions], Keys.KEY_CODE)
Count Events - Tallies element appearances:
d.register_none_op_watcher("EventName", [Selector conditions])
Retrieve counts:
d.get_watcher_triggered_count("EventName")
EVENT MANAGEMENT
----------------
Enable: d.set_watcher_enabled("name", True)
Disable: d.set_watcher_enabled("name", False)
Remove: d.remove_watcher(name)
Check status: d.get_watcher_enabled(name)
CRITICAL NOTE:
"Any matching event operations that occur on the interface before enabling the monitor
or enabling monitor events will not be processed!"
========================================
14. BUILT-IN FRIDA
========================================
OVERVIEW
--------
FIRERPA includes integrated, latest-version Frida with:
- Built-in anti-detection patches
- Custom hiding features
- No separate frida-server setup required
IMPORTANT: "Since FIRERPA version 7.18, the built-in FRIDA requires a token parameter
to connect."
THREE USAGE METHODS
-------------------
METHOD 1: Code-Based Integration
---------------------------------
Access Frida through FIRERPA client API:
conn = d.frida
conn.enumerate_processes()
Alternative approach using direct token retrieval:
token = d._get_session_token()
manager = frida.get_device_manager()
conn = manager.add_remote_device("192.168.0.2:65000", token=token)
METHOD 2: Command-Line Usage
-----------------------------
Documentation emphasizes using frida commands through remote desktop for simplicity
(no additional parameters needed there).
For local machine usage, command structure requires:
- Host parameter: -H 192.168.0.2:65000 (not -U)
- Token parameter: --token xxxxxxxxxxxxxxxx
- Optional certificate: --certificate /path/to/lamda.pem (if encryption enabled)
Example command:
frida -H 192.168.0.2:65000 -f com.android.settings --token xxxxxxxxxxxxxxxx
METHOD 3: Objection Tool Integration
-------------------------------------
Patched version available at provided GitHub repository.
Usage pattern:
objection -N -h 192.168.0.2 -p 65000 --token xxxxxxxxxxxxxxxx explore
CRITICAL DETAILS:
- Default FIRERPA port is 65000 (not Frida's 27042)
- Tokens are fixed 16-character strings
- Encryption certificates require additional command parameters for security
========================================
PERSISTENT FRIDA SCRIPTS
========================================
OVERVIEW
--------
FIRERPA enables persistent Frida script injection with automatic management.
Scripts remain active even after app crashes or process exits.
Automatic reinitiation upon app restart.
Feature launched in version 7.80.
INSTALLING SCRIPTS
------------------
Deploy scripts to applications:
app = d.application("com.android.settings")
app.attach_script(script, runtime=ScriptRuntime.RUNTIME_QJS, standup=5)
IMPORTANT LIMITATION:
"The script manager only allows one script to be injected for each APP at a time."
Parameters:
- script: Frida script content (supports bytecode)
- runtime: Execution environment (default: qjs)
- standup: Delay in seconds (1-300) to prevent early injection issues
- spawn: Optional mode for automatic app restart on failure
UNINSTALLING SCRIPTS
--------------------
Remove injected scripts and halt monitoring:
app = d.application("com.android.settings")
app.detach_script()
STATUS VERIFICATION
-------------------
Two diagnostic methods:
Installation check:
app.is_attached_script()
Injection verification (determines if script actively runs in process):
app.is_script_alive()
SCRIPT LOGGING
--------------
Monitor console output and errors by filtering logs:
grep SCRIPT /data/local/tmp/server.log
OFFLINE PERSISTENCE
-------------------
Configure scripts as YAML files in /data/usr/modules/script/ for automatic loading
without API calls.
Configuration template:
enable: true
application: "com.android.settings"
version: "2.10"
runtime: "qjs"
script: !!binary "[base64-encoded content]"
standup: 10
spawn: false
The system monitors this directory for real-time updates, additions, and deletions.
========================================
FRIDA DATA REPORTING
========================================
OVERVIEW
--------
Enable automatic capture of method call data through custom scripts.
Automated submission to Redis, HTTP interfaces, or RabbitMQ queues.
Supports data compression via zlib for improved network performance.
WRITING REPORTING SCRIPTS
--------------------------
Scripts use emit(name, content) method to submit intercepted data.
Parameters:
- name: Identifies data type (e.g., "product_info")
- content: Actual data as string or byte array
Example structure:
Java.perform function intercepts okhttp traffic, extracts URL and body information,
then emits data as JSON string.
DATA REPORTING DESTINATIONS
----------------------------
Three supported options:
- Redis queues (simplest, no metadata)
- HTTP interfaces (includes metadata via query parameters)
- RabbitMQ queues (metadata in headers)
METADATA FIELDS:
Reports include: application name, device ID, encoding type, data name, script ID,
sequence number, timestamp, multi-instance application ID.
Note: Redis reporting excludes metadata due to protocol limitations.
IMPLEMENTATION
--------------
Script Injection:
app = d.application("com.android.settings")
app.attach_script(script, emit="redis://192.168.1.10/0")
Alternative HTTP endpoint:
app.attach_script(script, emit="http://192.168.1.10/dataReport")
Compression Option:
app.attach_script(..., encode=DataEncode.DATA_ENCODE_ZLIB)
Decompression:
import zlib
zlib.decompress(data)
Removal:
app.detach_script()
KEY REQUIREMENTS:
- HTTP services must return "OK" or "SUCCESS" with status 200
- RabbitMQ queues require manual creation
- Only standalone Redis is supported (no clusters)
- HTTP requests are multi-threaded and may arrive out-of-sequence
========================================
FRIDA EXPORTED INTERFACES (RPC)
========================================
OVERVIEW
--------
Enables developers to extend FIRERPA functionality by implementing custom Frida RPC methods.
Users write Hook code for ultimate app control.
Requires familiarity with Frida scripting.
WRITING EXPORT SCRIPTS
-----------------------
Scripts must follow specific formatting requirements:
- Function names use camelCase with lowercase first letter
- Avoid all-caps naming (e.g., use sendHttpRequest not sendHTTPRequest)
- Wrap code in Java.perform() blocks
- Use mandatory return performRpc patterns
THREE EXECUTION CONTEXTS:
performRpcJVMCall:
"Execute on JVM thread" - supports Java operations like Java.use()
performRpcJVMCallOnMain:
"Execute on UI thread" - necessary for UI operations; avoid blocking main thread
performRpcCall:
Basic JavaScript only - no Java layer logic
INJECTING SCRIPTS
-----------------
Inject using persistent Frida script method:
app = d.application("com.android.settings")
app.attach_script(script, runtime=ScriptRuntime.RUNTIME_QJS, standup=5)
Verify injection status:
app.is_script_alive()
CALLING EXPORTED METHODS
-------------------------
Direct API calls:
app = d.application("com.android.settings")
app.exampleFunc1("FIRE", "RPA")
Method names support both camelCase and snake_case variations:
exampleFunc1 equals example_func1
Multi-instance applications: Specify UID parameter to target specific instances
HTTP INTERFACE CALLS
--------------------
Starting version 8.15, supports JSON-RPC 2.0 protocol:
import jsonrpclib
server = jsonrpclib.Server('http://192.168.0.2:65000/script/com.android.settings/0')
server.example_func1("FIRE", "RPA")
Legacy protocol also supported via POST requests with JSON-serialized arguments.
For secured servers:
- Include X-Token header with certificate password
- Use HTTPS
HTTP STATUS CODES:
Code | Meaning
-----|--------------------------------------------------------------
200 | Success
410 | Script not injected/installed
500 | Script/parameter error
400 | Invalid parameters
TROUBLESHOOTING:
Hangs or timeouts typically occur when apps run backgrounded and enter sleep state.
Keep apps in foreground during testing.
========================================
15. BINARY PATCH
========================================
OVERVIEW
--------
Modify device files using hexadecimal search-and-replace operations with wildcard support.
WILDCARD SYNTAX
---------------
System supports flexible pattern matching:
- ?? matches any byte
- B? matches bytes starting with B (like BA, B1, B9)
- Example: 49 BA ?? ?C matches 49 BA [any] [any ending in C]
BASIC USAGE
-----------
Standard replacement:
d.hex_patch("AA BB CC D?", "AA BB CC DD", "/data/test.bin")
Returns object containing:
- count: number of replacements made
- replaces[].offset: byte position of each replacement
PARAMETERS
----------
maxreplace - Limits replacement count (default: all matches replaced):
d.hex_patch("AA BB CC D?", "AA BB CC DD", "/data/test.bin", maxreplace=2)
dryrun - Test mode that identifies matches without modifying files:
d.hex_patch("AA BB ?? ??", "AA BB 00 00", "/data/test.bin", dryrun=True)
FILE PATH WILDCARDS
-------------------
Paths support glob patterns:
/data/app/*/test.bin matches test.bin in any first-level subdirectory under /data/app
REQUIREMENTS:
Patterns require "at least two valid matching digits" for functionality.
========================================
16. BUILT-IN ADB MANAGEMENT
========================================
OVERVIEW
--------
FIRERPA system includes independent ADB service that operates separately from system's
native ADB implementation.
Key feature: "Connect to the highest privilege ADB without enabling developer mode"
through wireless connectivity.
IMPORTANT LIMITATION:
Built-in ADB service does not currently support JDWP debugging functions
(conflicts with system's native implementation).
INSTALLING ADB PUBLIC KEYS
---------------------------
Key Location:
- Linux/Mac: ~/.android/ directory
- Windows: C:\Users\[username]\.android\ directory
- Filename: adbkey.pub
If only adbkey exists, generate public key:
adb pubkey adbkey >adbkey.pub
Installation Process:
d.install_adb_pubkey("/path/to/adbkey.pub")
After successful installation, connect wirelessly:
adb connect 192.168.0.2:65000
REMOVING ADB PUBLIC KEYS
-------------------------
Uninstall public key from built-in ADB service:
d.uninstall_adb_pubkey("/path/to/adbkey.pub")
Note: System-authorized key from developer mode settings is incompatible with
built-in ADB service.
========================================
17. CONNECTIVITY
========================================
CONNECTING TO BUILT-IN ADB
---------------------------
Install Key:
python3 -u adb_pubkey.py install 192.168.1.2
Connect:
adb connect 192.168.1.2:65000
Uninstall Key:
python3 -u adb_pubkey.py uninstall 192.168.1.2
CONNECTING TO BUILT-IN SSH
---------------------------
Connection command:
bash ssh.sh 192.168.1.2
COPYING FILES VIA SCP
---------------------
Retrieve from device:
bash scp.sh 192.168.1.2:/sdcard/DCIM .
Send to device:
bash scp.sh test/ 192.168.1.2:/sdcard
Technical Details:
- Tool uses wrapper script: scp.sh
- Operations target device IP addresses
- File paths follow standard SCP syntax with colon-separated device locations
- Supports both directory and individual file transfers
========================================
18. ONE-CLICK PACKET CAPTURE
========================================
OVERVIEW
--------
Automated man-in-the-middle packet capture for Android 6.0-14.
No manual certificate or proxy configuration required.
Supports real-time packet modification.
Guarantees: "No packets cannot be captured (except certificate pinning)."
KEY REQUIREMENTS
----------------
- Computer and device on same network or USB-connected
- mitmproxy installed (mitmdump command available)
- Network firewall temporarily disabled
- Python 3 (or use pre-packaged startmitm.exe)
BASIC COMMANDS
--------------
Simple packet capture:
python3 -u startmitm.py 192.168.0.2
Shared analysis (colleagues view via browser):
python3 -u startmitm.py 192.168.0.2 --web-port 7890 --web-host 0.0.0.0
Specific application only:
python3 -u startmitm.py 192.168.0.2:com.some.package
With real-time modification script:
python3 -u startmitm.py 192.168.0.2 -s http_flow_hook.py
NETWORK SCENARIOS
-----------------
USB ADB connection:
python3 -u startmitm.py localhost
python3 -u startmitm.py localhost --serial [device_serial]
International/Upstream proxy:
python3 -u startmitm.py 192.168.0.2 --upstream http://127.0.0.1:7890 --dns https://dns.google/dns-query
DNS packet capture:
python3 -u startmitm.py 192.168.0.2 --dns 114.114.114.114
CRITICAL NOTES
--------------
- Close target applications completely before starting capture (not just via taskbar)
- Exit with CONTROL+C once only
- SSL pinning requires Frida bypass scripts
- QUIC auto-downgrades but may cause temporary lag
========================================
19. ENCRYPTION CERTIFICATES
========================================
OVERVIEW
--------
Create encryption certificates for FIRERPA to secure communication between users
and service.
"This certificate is used to encrypt the communication traffic between you and the
FIRERPA service, preventing unauthorized access."
KEY DISTINCTION:
Certificate discussed here differs from those used in packet capture operations.
Purpose: Protecting remote desktop access and service communication rather than
intercepting network traffic.
GENERATION PROCESS
------------------
Location: cert.py script resides in project's tools directory.
Command:
python3 cert.py mydevice.local
Prerequisites: Install necessary environment and dependencies beforehand.
Regular updates recommended to avoid compatibility issues.
GENERATED FILES
---------------
- mydevice.local.pem - The encryption certificate
- root.crt and root.key - Root certificates (require secure storage but aren't directly used)
CERTIFICATE FORMAT
------------------
Output contains RSA private key and certificate blocks in PEM format.
Includes default remote desktop password embedded as parameter: PASSWD=...
IMPLEMENTATION EFFECTS
----------------------
Once applied to FIRERPA:
- Remote desktop access requires HTTPS protocol
- Authorization demands password from certificate
- SSH login also requires certificate-based authentication
IMPORTANT: Users shouldn't manually edit certificate file.
========================================
20. DISTRIBUTED DEPLOYMENT
========================================
OVERVIEW
--------
Enable remote access to devices located elsewhere using distributed deployment methods.
Three primary approaches: FRP forwarding, OpenVPN networking, Xinghuo Platform integration.
FRP-BASED SOLUTIONS
-------------------
Internal Port Forwarding:
Download FRP server from fatedier/frp repository (v0.45.0+).
Launch with parameters controlling token authentication, bind addresses, allowed port
ranges (10000-15000).
Configuration - properties.local variables:
fwd.enable=true
fwd.host=your-server.com # server-address
fwd.port=6009 # server-port
fwd.rport=12345 # remote-port
fwd.token=your-token
fwd.protocol=tcp
Connecting via FRP:
Device("127.0.0.1", port=12345)
Or browser access:
http://[frp-server-ip]:12345
https://[frp-server-ip]:12345 (with certificate)
PUBLIC NETWORK ACCESS WARNING:
Documentation advises against exposing devices directly to public networks.
If necessary:
- FIRERPA must use encryption certificates
- Proxy bind address must change from localhost to 0.0.0.0
- Without certificates: "anyone will be able to access it, which is extremely dangerous"
OPENVPN APPROACH
----------------
Setup involves deploying OpenVPN server (detailed in separate documentation).
Connect personal devices to same virtual network.
XINGHUO PLATFORM
-----------------
Simplified alternative requiring free access code from Device Farm WeChat account.
========================================
21. DEPLOYING OPENVPN SERVICE
========================================
OVERVIEW
--------
Deploy OpenVPN service using Docker on Linux systems (tested on Debian 9).
Default port: 1190/UDP
SERVER PREPARATION
------------------
Enable IP forwarding:
echo net.ipv4.ip_forward=1 >>/etc/sysctl.conf
sysctl -p
For UFW firewall systems:
Add to /etc/ufw/before.rules before *filter rules:
*nat
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s 172.27.27.0/24 -o eth0 -j MASQUERADE
COMMIT
Change /etc/default/ufw:
DEFAULT_FORWARD_POLICY="ACCEPT"
Then reload:
ufw reload
For systems without UFW:
iptables -P FORWARD ACCEPT
CONFIGURATION SETUP
-------------------
Create directory and initialize:
mkdir -p ~/lamda-openvpn-server
docker run -it --rm --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn ovpn-server-new
Key configurable fields in config.ovpn:
- VPN network: server 172.27.27.0 255.255.255.0
- Port: port 1190
- DNS: push "dhcp-option DNS 114.114.114.114"
CLIENT MANAGEMENT
-----------------
Create client credentials:
docker run -it --rm --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn ovpn-client-new myname
Generate client profile:
docker run -it --rm --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn ovpn-client-profile ovpn myname >myname.ovpn
Revoke client access:
docker run -it --rm --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn ovpn-client-revoke myname
SERVICE OPERATION
-----------------
Foreground mode (testing):
docker run -it --rm --name openvpn-server --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn run
Background mode (production):
docker run -d --rm --name openvpn-server --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn run
========================================
22. INTERFACE LOCKING
========================================
OVERVIEW
--------
Control API access through interface locking mechanisms.
Allows device instances to restrict API usage to prevent unauthorized access.
KEY CONCEPT:
"The interfaces in this chapter are used to lock all API interfaces, allowing you to
restrict interfaces to only be used by the current Device instance."
Recommended approach: Acquire lock, maintain through periodic refreshes, release when done.
THREE MAIN FUNCTIONS
--------------------
1. Acquire Lock
Command: d._acquire_lock(leaseTime=60)
- Automatically releases after 60 seconds by default
- Reentrant capability (subsequent calls refresh existing lock)
- Warning: Setting excessively high lease times risks permanent device lockout if scripts crash
2. Release Lock
Command: d._release_lock()
- Manually relinquishes API lock
- Allows other clients to acquire access
3. Refresh Lock
Command: d._refresh_lock(leaseTime=60)
- Extends lock duration by resetting expiration time
- Intended for periodic calls to maintain continuous access
BEST PRACTICE:
Secure sequence:
1. Acquire lock
2. Spawn thread to refresh periodically
3. Release when finished
This approach prevents unexpected disconnections while maintaining safety against
script failures.
========================================
23. VERSION HISTORY
========================================
CURRENT VERSION: 8.28
---------------------
Latest release includes:
- Fixes for local file installation
- Improvements to Frida ID increment reporting
- Enhanced TensorFlow inference performance
- Updated third-party modules
MAJOR VERSION HIGHLIGHTS
------------------------
Version 8.25:
- hexedit command functionality
- Permission loophole fixes
- On-device AI framework support with tflite-runtime
Version 8.0:
- Full interface support for multiple applications
- Shared clipboard functionality in remote desktop
- Improved compatibility with Android 6.0 through 15
Version 7.50:
- Complete NoxPlayer emulator compatibility
- Network subscription service eliminating FRP/OpenVPN requirements
- Multi-instance app support
Version 5.0:
- Critical security vulnerabilities addressed
- TLS support for remote desktop and RPC
- Python 3.11 client support
FEATURE EVOLUTION
-----------------
Early versions (3.0 series):
Established core functionality including ADB integration, SSH support, file operations.
5.x series:
Introduced significant security improvements and certificate management.
Version 7.x:
Focused on stability, compatibility with various Android versions and emulators,
advanced features like Frida integration.
Recent 8.x versions:
Emphasize AI capabilities and enhanced stealth features.
DEVELOPMENT TRAJECTORY:
Consistent attention to security, compatibility across diverse Android devices and
emulators, progressive feature expansion for automation and system manipulation capabilities.
========================================
BASIC KNOWLEDGE - AUTOMATION FUNDAMENTALS
========================================
OVERVIEW
--------
Android automation fundamentals using FIRERPA.
Emphasized as superior to alternatives like AutoJS, Appium, and uiautomator2 for
enterprise-scale automation projects.
KEY AUTOMATION CONCEPTS
-----------------------
Mobile vs. Web Automation:
"Mobile automation requires you to have both a mobile phone and a computer, while web
automation can be done just on your own computer."
Both use similar principles (element location, clicking, screenshots) but employ
different tools and locating methods.
TOOL COMPARISON
---------------
AutoJS:
Self-controlling, APK-based, suitable for beginners but lacks enterprise scalability.
Appium:
Cross-platform but "bulky and bloated, very unsuitable for large-scale deployment"
uiautomator2:
Streamlined but unstable in multi-device scenarios.
BASIC AUTOMATION WORKFLOW
--------------------------
Three primary approaches:
1. Man-in-the-Middle Capture:
Using certificate installation and proxies for HTTP/HTTPS interception
2. Hook Capture:
Requires reverse engineering knowledge
Uses Frida scripts to intercept function calls
3. Automation Code:
Implements UI interaction logic through selectors
TECHNICAL COMPONENTS
--------------------
Interface Selectors:
Elements located using parameters:
- resourceId
- text
- textContains
- description
- clickable
- scrollable
Example syntax: d(text="Agree").click()
Coordinate System:
- Origin point (0,0) at top-left
- Extends right and down
- Regions defined using Bound with parameters: top, left, bottom, right
- All measured from axis origins
Layout Inspection:
- Remote desktop provides eye-icon toggle for interactive element inspection
- Displays properties usable as selector parameters
- Press CTRL + R to refresh layouts
ANDROID DATA MANAGEMENT
------------------------
Application Data Location:
/data/user/0/[package-name]
Contains:
- Databases directory: SQLite files (mmssms.db for SMS)
- Shared preferences: XML configuration files
- Media/files: Additional application assets
Database Support:
Platform supports reading encrypted databases:
- WeChat: sqlcipher
- Enterprise WeChat: aes-128
- Alipay: sqlcrypto
(Requires obtaining decryption keys)
ADVANCED ASSISTANCE
-------------------
For non-selector-compatible interfaces (games, real-time rendered content):
FIRERPA provides OCR and image matching solutions using SIFT template matching.
========================================
ADDITIONAL NOTES AND BEST PRACTICES
========================================
SECURITY CONSIDERATIONS
-----------------------
- Default FIRERPA installation has no authentication
- Use encryption certificates for any non-trusted network
- Never expose to public internet without proper security
- Keep software updated to latest version
- Protect /data/usr directory (contains configuration and user data)
PERFORMANCE OPTIMIZATION
------------------------
- Use image matching scale parameter to optimize performance
- Server-side OCR/image processing preserves client resources
- Mobile device performance may limit complex operations
- Consider custom HTTP backends for multi-device OCR to avoid resource duplication
AUTOMATION BEST PRACTICES
--------------------------
- Always refresh layouts (CTRL+R) before element inspection
- Ensure input method is visible before text input operations
- Keep monitored apps in foreground during testing
- Close apps completely (not just taskbar) before packet capture
- Use dryrun parameter to test binary patches before applying
- Validate crontab rules with online verification tools
DEBUGGING TIPS
--------------
- Monitor logs: grep SCRIPT /data/local/tmp/server.log
- Check server.log for troubleshooting
- Verify server is running before API calls
- Ensure proper root permissions
- Check timezone configuration for scheduled tasks
- Verify network connectivity for distributed deployments
LIMITATIONS TO REMEMBER
-----------------------
- Remote desktop: English input only (no Chinese characters)
- Built-in terminal: Cannot install additional PIP/APT packages
- Virtual Debian: Only one instance at a time
- Built-in ADB: No JDWP debugging support
- OCR: Limited to basic operations (exists, click, screenshot)
- Persistent Frida: One script per app at a time
- Scheduled tasks: May not run as expected when screen off
RECOMMENDED WORKFLOW
--------------------
1. Read documentation sequentially from first chapter
2. Keep FIRERPA and client libraries updated
3. Use virtual environment for client installation
4. Test configurations in safe environment first
5. Implement proper error handling in automation scripts
6. Use interface locking for exclusive device access
7. Monitor script execution through logging
8. Regular backups of /data/usr directory
========================================
END OF DOCUMENTATION
========================================
This comprehensive documentation covers all major aspects of FIRERPAλ for Android
device management, automation, debugging, and security testing.
For latest updates and additional information, refer to:
https://device-farm.com/doc/en/
Copyright © 2021-present, firerpa
================================================
FILE: examples/README.md
================================================
We’re not targeting any specific application; we’re just using it as a convenient example for the demo.
API Document: https://device-farm.com/doc/
================================================
FILE: examples/activity_jump.py
================================================
# Copyright 2025 rev1si0n (lamda.devel@gmail.com). All rights reserved.
#encoding=utf-8
from lamda.client import *
import time
d = Device("localhost")
app = d.application("com.taobao.taobao")
app.start()
while True:
goodsid = input("Please input a taobao goods id (item_id) (eg. 123456): ")
if goodsid.isdigit():
intent["package"] = "com.taobao.taobao"
intent["action"] = "android.intent.action.VIEW"
intent["component"] = "com.taobao.taobao/com.taobao.android.detail.alittdetail.TTDetailActivity"
intent["data"] = f"http://internal.tt.detail.taobao.com/detail/index.html?id={goodsid}"
d.start_activity(**intent)
time.sleep(2)
================================================
FILE: examples/search_in_taobao.py
================================================
# Copyright 2025 rev1si0n (lamda.devel@gmail.com). All rights reserved.
#encoding=utf-8
from lamda.client import *
import time
"""
This is a simple demo for performing keyword searches on Taobao.
"""
d = Device("localhost")
app = d.application("com.taobao.taobao")
if not app.is_installed():
print ("taobao app is not installed")
exit (1)
if app.info().versionName != "10.48.0":
print ("please intall taaobao 10.48.0")
exit (1)
# ensure the app is stopped
app.stop()
time.sleep(1.5)
app.start()
time.sleep(10) # wait for app fully started
if not d(description="我的淘宝").exists():
print ("is taobao home page?")
exit (1)
# click to activate input
d(description="搜索栏").click()
# wait for search input activated
d(resourceId="com.taobao.taobao:id/searchbtn").wait_for_exists(15*1000)
# input search keyword: 苹果手机
d(resourceId="com.taobao.taobao:id/searchEdit").set_text("苹果手机")
# click "Search"
d(resourceId="com.taobao.taobao:id/searchbtn").click()
# wait for goods showsup
d(description="筛选").wait_for_exists(15*1000)
# do a simple swipe
d().swipe()
# ...
================================================
FILE: extensions/README.md
================================================
API Document: https://device-farm.com/doc/
================================================
FILE: extensions/example_http_extension.py
================================================
# Copyright 2025 rev1si0n (lamda.devel@gmail.com). All rights reserved.
#
# Distributed under MIT license.
# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
from lamda.extensions import *
# In order to accommodate users of various technical levels and avoid impacting the operation of the firerpa service, all HTTP rewrite methods must be implemented synchronously. These methods will run within threads and will not block the overall service.
# BaseHttpExtension is based on tornado.web.RequestHandler. If you need to use or override other methods such as set_header, initialize, etc., please refer to the official Tornado documentation.
# It is strongly discouraged to override Tornado-related methods like prepare and initialize.
# If you encounter the There is no current event loop in thread XXX exception, please call self.prepare_loop() in your overridden http_xxx method.
# 为了兼容各种技术层级的使用者以及不影响到 firerpa 服务的运行,所有 HTTP 重写方法均须为同步写法,方法将在线程内运行,不会阻塞整体服务。
# BaseHttpExtension 基于 tornado.web.RequestHandler,如需使用或重写其他方法如 set_header、initialize 等,请参照 tornado 官方文档。
# 我们不建议您重写 tornado 相关 prepare、initialize 方法。
# 如果您遇到 There is no current event loop in thread XXX 异常,请在您重写的 http_xxx 方法中调用 `self.prepare_loop()`
# REF: https://www.tornadoweb.org/en/stable/web.html
class ExampleHttpExtension(BaseHttpExtension):
route = "/api/v1/hello-world" # API route
def http_get(self, *args, **kwargs):
""" GET Method Handler """
self.write("Hello World")
def http_post(self, *args, **kwargs):
""" POST Method Handler """
self.write("Hello World")
def http_put(self, *args, **kwargs):
""" PUT Method Handler """
self.write("Hello World")
def http_delete(self, *args, **kwargs):
""" DELETE Method Handler """
self.write("Hello World")
def http_patch(self, *args, **kwargs):
""" PATCH Method Handler """
self.write("Hello World")
================================================
FILE: extensions/example_mcp_extension.py
================================================
# Copyright 2025 rev1si0n (lamda.devel@gmail.com). All rights reserved.
#
# Distributed under MIT license.
# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
import base64
from lamda.utils import getprop
from lamda.extensions import BaseMcpExtension
from lamda.mcp import mcp, Annotated, TextContent, BlobResourceContents
class ExampleMcpExtension(BaseMcpExtension):
route = "/model-context-protocol/mcp/"
name = "example-mcp-extension"
version = "1.0.0"
@mcp("tool", description="Send a greeting to others.")
def greeting(self, ctx, msg: Annotated[str, "Greeting message"],
to: Annotated[str, "Greeting to who"] = "John"):
return TextContent(text=f"mcp greeting! {msg}, {to}!")
@mcp("tool", description="Read android system property by name.")
def getprop(self, ctx, name: Annotated[str, "Android system property name."]):
return TextContent(text=getprop(name) or "")
@mcp("resource", uri="file://{absolute_path}")
def get_file(self, ctx, absolute_path: Annotated[str, "Absolute file path"]):
""" Read file content on the device by full path """
blob = base64.b64encode(open(absolute_path, "rb").read()).decode()
return BlobResourceContents(blob=blob, uri=f"file://{absolute_path}",
mimeType="text/plain")
================================================
FILE: extensions/firerpa.py
================================================
# Copyright 2025 rev1si0n (lamda.devel@gmail.com). All rights reserved.
#
# Distributed under MIT license.
# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
#
# ===================================================================
# Official FireRPA MCP extension, turning your phone into an AI AGENT
# ===================================================================
#
import json
import xml.etree.ElementTree as etree
from lamda.mcp import *
from lamda.client import *
from lamda.utils import getprop
from lamda.extensions import BaseMcpExtension, to_json_string
from google.protobuf.json_format import MessageToDict
from xmltodict import parse as xml2dict
prompt = """
# Android Automation Guidelines
You are an expert in Android automation, capable of using specialized tools to accurately complete user-requested operations. You understand and can precisely execute each step in the automation process.
## Quality Assurance
- Prioritize using layout information for identifying operational elements. You should always use non-repeating criteria for judgment.
- Resource-id may be duplicated, and duplicate ids should not be used.
- Each operation should have a certain interval; otherwise, the page may not be fully loaded.
- Never use screenshot to detect coordinates.
## Communication
- Follow the user's language preferences"""
class FireRpaMcpExtension(BaseMcpExtension):
"""Your primary task is to help users automate Android device control using AI through this MCP service."""
route = "/firerpa/mcp/"
name = "firerpa"
version = "1.0"
@mcp("tool", description="Dumps android window's layout hierarchy as JSON string.")
def dump_window_hierarchy(self, ctx, compressed: Annotated[bool, "Enables or disables layout hierarchy compression, default true."] = True):
data = self.device.dump_window_hierarchy(compressed).getvalue()
return self.remove_attrs_and_empty(data)
@mcp("tool", description="Perform a click at arbitrary coordinates on the display.")
def click(self, ctx, pointX: Annotated[int, "X coordinate."], pointY: Annotated[int, "Y coordinate."]):
result = self.device.click(Point(x=pointX, y=pointY))
return str(result).lower()
@mcp("tool", description="Perform a swipe between two points.")
def swipe(self, ctx, fromX: Annotated[int, "Swipe-from X coordinate."], fromY: Annotated[int, "Swipe-from Y coordinate."], toX: Annotated[int, "Swipe-to X coordinate."], toY: Annotated[int, "Swipe-to Y coordinate."], step: Annotated[int, "Step to inject between two points"] = 32):
result = self.device.swipe(Point(x=fromX, y=fromY), Point(x=toX, y=toY), step=step)
return str(result).lower()
@mcp("tool", description="Perform a drag between two points.")
def drag(self, ctx, fromX: Annotated[int, "Drag-from X coordinate."], fromY: Annotated[int, "Drag-from Y coordinate."], toX: Annotated[int, "Drag-to X coordinate."], toY: Annotated[int, "Drag-to Y coordinate."]):
result = self.device.drag(Point(x=fromX, y=fromY), Point(x=toX, y=toY))
return str(result).lower()
@mcp("tool", description="Get device information such as screen width, height, brand, etc.")
def get_deviec_info(self, ctx):
info = self.device.device_info()
return to_json_string(MessageToDict(info))
@mcp("tool", description="Display a toast message on the screen.")
def show_toast(self, ctx, message: Annotated[str, "The toast message."]):
result = self.device.show_toast(message)
return str(result).lower()
@mcp("tool", description="Execute script in the device's shell foreground.")
def execute_shell_script_foreground(self, ctx, scrip: Annotated[str, "Shell script content."]):
result = self.device.execute_script(scrip)
return to_json_string(MessageToDict(result))
@mcp("tool", description="Wake up the device.")
def wake_up(self, ctx):
result = self.device.wake_up()
return str(result).lower()
@mcp("tool", description="Turn off the device screen.")
def sleep(self, ctx):
result = self.device.sleep()
return str(result).lower()
@mcp("tool", description="Check if the device screen is lit up.")
def is_screen_on(self, ctx):
result = self.device.is_screen_on()
return str(result).lower()
@mcp("tool", description="Check is the device screen locked.")
def is_screen_locked(self, ctx):
result = self.device.is_screen_locked()
return str(result).lower()
@mcp("tool", description="Get the device clipboard content.")
def get_clipboard_text(self, ctx):
result = self.device.get_clipboard()
return result
@mcp("tool", description="Set the device clipboard content.")
def set_clipboard_text(self, ctx, text: Annotated[str, "The text to set."]):
result = self.device.set_clipboard(text)
return str(result).lower()
@mcp("tool", description="Simulates a short press using a key code.")
def press_key_code(self, ctx, key_code: Annotated[int, "The Android's KeyEvent keycode."]):
result = self.device.press_keycode(key_code)
return str(result).lower()
@mcp("tool", description="Get the last displayed toast on the system.")
def get_last_toast(self, ctx):
result = self.device.get_last_toast()
return to_json_string(MessageToDict(result))
@mcp("tool", description="Read android system property by name.")
def getprop(self, ctx, name: Annotated[str, "Android system property name."]):
return getprop(name) or ""
@mcp("tool", description="Use full text matching to click on an element.")
def click_by_text(self, ctx, text: Annotated[str, "The full text field."]):
result = self.device(text=text).click_exists()
return str(result).lower()
@mcp("tool", description="Use text contains matching to click on an element.")
def click_by_text_contains(self, ctx, substring: Annotated[str, "The substring to be matched."]):
result = self.device(textContains=substring).click_exists()
return str(result).lower()
@mcp("tool", description="Use text regex matching to click on an element.")
def click_by_text_matches(self, ctx, regex: Annotated[str, "The string matching the element's text."]):
result = self.device(textMatches=regex).click_exists()
return str(result).lower()
@mcp("tool", description="Use full description matching to click on an element.")
def click_by_description(self, ctx, text: Annotated[str, "The full description field."]):
result = self.device(description=text).click_exists()
return str(result).lower()
@mcp("tool", description="Use description contains matching to click on an element.")
def click_by_description_contains(self, ctx, substring: Annotated[str, "The substring to be matched."]):
result = self.device(descriptionContains=substring).click_exists()
return str(result).lower()
@mcp("tool", description="Use description regex matching to click on an element.")
def click_by_description_matches(self, ctx, regex: Annotated[str, "The string matching the element's description."]):
result = self.device(descriptionMatches=regex).click_exists()
return str(result).lower()
@mcp("tool", description="Use resourceId to click on an element, if the resource-id is duplicated, it cannot be used.")
def click_by_resource_id(self, ctx, resource_id: Annotated[str, "Elements's resourceId (resourceName)."]):
result = self.device(resourceId=resource_id).click_exists()
return str(result).lower()
@mcp("tool", description="Use resourceId to input text into an input element, if the resource-id is duplicated, it cannot be used.")
def set_text_by_resource_id(self, ctx, resource_id: Annotated[str, "Input elements's resourceId (resourceName)."], text: Annotated[str, "The input text."]):
result = self.device(resourceId=resource_id).set_text(text)
return str(result).lower()
@mcp("tool", description="Use className to input text into an input element.")
def set_text_by_class_name(self, ctx, class_name: Annotated[str, "Input elements's className. eg: android.widget.EditText"], text: Annotated[str, "The input text."]):
result = self.device(className=class_name).set_text(text)
return str(result).lower()
@mcp("tool", description="Get information about the currently running foreground application.")
def current_top_application_info(self, ctx):
result = self.device.current_application().info()
return to_json_string(MessageToDict(result))
@mcp("tool", description="Use the package name to launch an Android app.")
def start_application_by_id(self, ctx, package_name: Annotated[str, "The package name, such as com.android.settings."]):
result = self.device.application(package_name).start()
return str(result).lower()
@mcp("tool", description="Use the package name to close an Android app.")
def stop_application_by_id(self, ctx, package_name: Annotated[str, "The package name, such as com.android.settings."]):
result = self.device.application(package_name).stop()
return str(result).lower()
@mcp("tool", description="Use the package name to check if the application is installed.")
def is_application_installed(self, ctx, package_name: Annotated[str, "The package name, such as com.android.settings."]):
result = self.device.application(package_name).is_installed()
return str(result).lower()
@mcp("tool", description="Check if the application is running in the foreground using the package name.")
def is_application_running_foreground(self, ctx, package_name: Annotated[str, "The package name, such as com.android.settings."]):
result = self.device.application(package_name).is_foreground()
return str(result).lower()
@mcp("tool", description="Get all manifest permissions of the application using the package name.")
def list_application_permissions(self, ctx, package_name: Annotated[str, "The package name, such as com.android.settings."]):
result = self.device.application(package_name).permissions()
return to_json_string(result)
@mcp("tool", description="Grant the application runtime permissions.")
def grant_application_permission(self, ctx, package_name: Annotated[str, "The package name, such as com.android.settings."], permission: Annotated[str, "The android runtime permissions."]):
result = self.device.application(package_name).grant(permission)
return str(result).lower()
@mcp("tool", description="Revoke the application's runtime permissions.")
def revoke_application_permission(self, ctx, package_name: Annotated[str, "The package name, such as com.android.settings."], permission: Annotated[str, "The android runtime permissions."]):
result = self.device.application(package_name).revoke(permission)
return str(result).lower()
@mcp("tool", description="Check if the application has been granted runtime permissions.")
def is_permission_granted(self, ctx, package_name: Annotated[str, "The package name, such as com.android.settings."], permission: Annotated[str, "The android runtime permissions."]):
result = self.device.application(package_name).is_permission_granted(permission)
return str(result).lower()
@mcp("prompt")
def agent(self, ctx):
return PromptMessage(role="user", content=TextContent(text=prompt))
def remove_attrs_and_empty(self, xml):
""" remove unnecessery node info to reduce token usage """
specified_attrs = {"clickable", "scrollable", "checkable", "enabled",
"focusable", "long-clickable", "password", "visible-to-user",
"drawing-order", "hint", "display-id", "package", "focused"}
def clean_element(element, parent=None):
if element.attrib.get("package") == "com.android.systemui" and parent is not None:
parent.remove(element)
return True
attrs_to_remove = {attr for attr, value in element.attrib.items()
if attr in specified_attrs or not value.strip()}
for attr in attrs_to_remove:
del element.attrib[attr]
for i in range(len(element) - 1, -1, -1):
clean_element(element[i], element)
root = etree.fromstring(xml)
clean_element(root)
return json.dumps(xml2dict(etree.tostring(root, encoding="utf-8").decode(),
attr_prefix=""), ensure_ascii=False, separators=(",", ":"))
================================================
FILE: extensions/mcp_return_types.py
================================================
#!/usr/bin/env python3.9
# Copyright 2025 rev1si0n (lamda.devel@gmail.com). All rights reserved.
#
# Distributed under MIT license.
# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
#
# THIS IS THE OFFICIAL MCP TYPES equivalent made by firerpa authors (FUCKOFF pydantic)
# github.com/modelcontextprotocol/python-sdk/blob/v1.13.1/src/mcp/types.py
# WE ONLY LIST THE TYPES THAT YOU CAN USE
import msgspec
from msgspec import Struct, Meta
from typing import Dict, Any, Literal, List, Union, Annotated
Role = Literal["user", "assistant"]
LoggingLevel = Literal["debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"]
class BaseModel(Struct, omit_defaults=False):
def validate(self):
return msgspec.json.decode(msgspec.json.encode(self),
type=type(self))
class BaseStructuredModel(BaseModel):
""" StructuredModel """
class Result(BaseModel):
"""Base class for JSON-RPC results."""
class Annotations(BaseModel):
audience: Union[list[Role], None] = None
priority: Union[Annotated[float, Meta(ge=0.0, le=1.0)], None] = None
class ResourceContents(BaseModel):
"""The contents of a specific resource or sub-resource."""
uri: Annotated[str, Meta(min_length=5, max_length=2**16,
pattern="^[a-z0-9A-Z_-]+://.*$")]
"""The URI of this resource."""
mimeType: Union[str, None] = None
"""The MIME type of this resource, if known."""
class TextResourceContents(ResourceContents, kw_only=True):
"""Text contents of a resource."""
text: str
"""The text of the item."""
class BlobResourceContents(ResourceContents, kw_only=True):
"""Binary contents of a resource."""
blob: str
"""A base64-encoded string representing the binary data of the item."""
class TextContent(BaseModel, kw_only=True):
"""Text content for a message."""
type: Literal["text"] = "text"
text: str
"""The text content of the message."""
annotations: Union[Annotations, None] = None
class ImageContent(BaseModel, kw_only=True):
"""Image content for a message."""
type: Literal["image"] = "image"
data: str
"""The base64-encoded image data."""
mimeType: str
"""The MIME type of the image."""
annotations: Union[Annotations, None] = None
class EmbeddedResource(BaseModel, kw_only=True):
"""
The contents of a resource, embedded into a prompt or tool call result.
It is up to the client how best to render embedded resources for the benefit
of the LLM and/or the user.
"""
type: Literal["resource"] = "resource"
resource: Union[TextResourceContents, BlobResourceContents]
annotations: Union[Annotations, None] = None
class PromptMessage(BaseModel):
"""Describes a message returned as part of a prompt."""
role: Role
content: Union[TextContent, ImageContent, EmbeddedResource]
class GetPromptResult(Result, kw_only=True):
"""The server's response to a prompts/get request from the client."""
description: Union[str, None] = ""
"""An optional description for the prompt."""
messages: list[PromptMessage]
class EmptyResult(Result):
"""A response that indicates success but carries no data."""
class CallToolResult(Result):
"""The server's response to a tool call."""
content: list[Union[TextContent, ImageContent, EmbeddedResource]]
structuredContent: Union[Dict[str, Any], None] = None
isError: bool = False
class ReadResourceResult(Result):
"""The server's response to a resources/read request from the client."""
contents: List[Union[TextResourceContents, BlobResourceContents]]
================================================
FILE: extensions/mcp_sms_reader.py
================================================
# Copyright 2025 rev1si0n (lamda.devel@gmail.com). All rights reserved.
#
# Distributed under MIT license.
# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
#
# ===================================================================
# MCP for reading local SMS messages. 用于读取本机短信的 MCP 扩展
# ===================================================================
#
import json
import sqlite3
from lamda.mcp import mcp, Annotated, TextContent
from lamda.extensions import BaseMcpExtension
db_path = "/data/data/com.android.providers.telephony/databases/mmssms.db"
class SmsMcpExtension(BaseMcpExtension):
route = "/sms/mcp/"
name = "sms-reader-extension"
version = "1.0"
@mcp("tool", description="""Reads the SMS database using SQL statements in SQLite syntax; read-only, no write operations allowed.
The database is standard android mmssms.db, you should always learn the tables or table structure if needed.""")
def read_sms_database_by_sql(self, ctx, sql: Annotated[str, "A raw SQL (SQLite) query string for read-only operations."]):
db = sqlite3.connect(db_path)
db.row_factory = sqlite3.Row
db.execute("PRAGMA query_only")
try:
items = db.execute(sql)
results = json.dumps([dict(row) for row in items.fetchall()])
finally:
db.close()
return TextContent(text=results)
================================================
FILE: lamda/__init__.py
================================================
# Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
#
# Distributed under MIT license.
# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
__version__ = "9.20"
================================================
FILE: lamda/client.py
================================================
# Copyright 2022 rev1si0n (https://github.com/rev1si0n). All rights reserved.
#
# Distributed under MIT license.
# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
import os
import io
import re
import sys
import copy
import time
import uuid
import json
import base64
import hashlib
import platform
import warnings
import builtins
import logging
import msgpack
# fix protobuf>=4.0/win32, #10158
if sys.platform == "win32":
os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python"
import grpc
import pem as Pem
import collections.abc
# fix pyreadline, py310, Windows
collections.Callable = collections.abc.Callable
from urllib.parse import quote
from collections import defaultdict
from cryptography.fernet import Fernet
from os.path import basename, dirname, expanduser, join as joinpath
from google.protobuf.json_format import MessageToDict, MessageToJson
from grpc_interceptor import ClientInterceptor
from google.protobuf.message import Message
from asn1crypto import pem, x509
try:
import frida
_frida_dma = frida.get_device_manager()
except (ImportError, AttributeError):
_frida_dma = None
from . import __version__
from . types import AttributeDict, BytesIO
from . exceptions import (UnHandledException, DuplicateEntryError,
InvalidArgumentError, UiObjectNotFoundException,
IllegalStateException)
from . import exceptions
handler = logging.StreamHandler()
logger = logging.getLogger("lamda.client")
formatter = logging.Formatter("%(asctime)s %(process)d %(levelname)7s@%(module)s:%(funcName)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
sys.path.append(joinpath(dirname(__file__)))
sys.path.append(joinpath(dirname(__file__), "rpc"))
# use native resolver to support mDNS
os.environ["GRPC_DNS_RESOLVER"] = "native"
protos, services = grpc.protos_and_services("services.proto")
__all__ = [
"Corner",
"Direction",
"GproxyType",
"GrantType",
"Group",
"CustomOcrBackend",
"OcrEngine",
"Key",
"Keys",
"KeyCode",
"KeyCodes",
"MetaKeyCode",
"MetaKeyCodes",
"BaseCryptor",
"FernetCryptor",
"OpenVPNAuth",
"OpenVPNEncryption",
"OpenVPNKeyDirection",
"FindImageMethod",
"FindImageArea",
"ToastDuration",
"OpenVPNCipher",
"OpenVPNProto",
"Orientation",
"OpenVPNProfile",
"GproxyProfile",
"TouchBuilder",
"ScriptRuntime",
"DataEncode",
"AudioStreamType",
"PlayAudioProfile",
"ApplicationInfo",
"Selector",
"TouchWait",
"TouchMove",
"TouchDown",
"TouchUp",
"TouchAction",
"TouchSequence",
"Point",
"Bound",
"load_proto",
"to_dict",
"Device",
"logger",
]
def getXY(p):
return p.x, p.y
def checkDupEntry(a, entries):
if a in entries:
raise DuplicateEntryError(a)
def checkArgumentTyp(a, types):
if not isinstance(a, types):
raise InvalidArgumentError(a)
def touchSequenceSave(s, fpath):
return BytesIO(s.SerializeToString()).save(fpath)
def touchSequenceLoad(s, fpath):
return s.FromString(BytesIO.load(fpath).getvalue())
def touchSequenceIndexer(s, index):
return s.sequence[index]
def touchSequenceIter(s):
yield from s.sequence
def touchSequenceAppendAction(s, **kwargs):
action = TouchAction(**kwargs)
s.sequence.append(action)
def touchSequenceAppendDown(s, **kwargs):
touchSequenceAppendAction(s, down=TouchDown(**kwargs))
def touchSequenceAppendMove(s, **kwargs):
touchSequenceAppendAction(s, move=TouchMove(**kwargs))
def touchSequenceAppendWait(s, **kwargs):
touchSequenceAppendAction(s, wait=TouchWait(**kwargs))
def touchSequenceAppendUp(s, **kwargs):
touchSequenceAppendAction(s, up=TouchUp(**kwargs))
def touchActionRealAction(a):
return getattr(a, a.type)
def touchActionType(a):
return a.WhichOneof("action")
def touchMoveShiftX(a, offset):
a.x = a.x + offset
return a.x
def touchMoveShiftY(a, offset):
a.y = a.y + offset
return a.y
def touchWaitShift(w, offset):
w.wait = w.wait + offset
return w.wait
def applicationInfoSet(application, app):
application.CopyFrom(app.info())
def height(b):
return b.bottom - b.top
def width(b):
return b.right - b.left
def center(b):
x = int(b.left + (b.right - b.left)/2)
y = int(b.top + (b.bottom - b.top)/2)
return Point(x=x, y=y)
def contain(a, b):
return all([b.top >= a.top,
b.left >= a.left,
b.bottom <= a.bottom,
b.right <= a.right])
def equal(a, b):
if not isinstance(b, protos.Bound):
return False
return all([b.top == a.top,
b.left == a.left,
b.bottom == a.bottom,
b.right == a.right])
def corner(b, position):
ca, cb = position.split("-")
return Point(x=getattr(b, cb),
y=getattr(b, ca))
# enum types
Corner = protos.Corner
Direction = protos.Direction
GproxyType = protos.GproxyType
GrantType = protos.GrantType
ScriptRuntime = protos.ScriptRuntime
DataEncode = protos.DataEncode
Group = protos.Group
Key = protos.Key
Keys = protos.Key # make an alias
KeyCode = protos.KeyCode
KeyCodes = protos.KeyCode # make an alias
MetaKeyCode = protos.MetaKeyCode
MetaKeyCodes = protos.MetaKeyCode # make an alias
OpenVPNAuth = protos.OpenVPNAuth
OpenVPNEncryption = protos.OpenVPNEncryption
OpenVPNKeyDirection = protos.OpenVPNKeyDirection
OpenVPNCipher = protos.OpenVPNCipher
OpenVPNProto = protos.OpenVPNProto
ToastDuration = protos.ToastDuration
Orientation = protos.Orientation
AudioStreamType = protos.AudioStreamType
PlayAudioProfile = protos.PlayAudioRequest
# proxy request alias
OpenVPNProfile = protos.OpenVPNConfigRequest
GproxyProfile = protos.GproxyConfigRequest
# multitouch
TouchMove = protos.TouchMove
TouchWait = protos.TouchWait
TouchDown = protos.TouchDown
TouchUp = protos.TouchUp
TouchSequence = protos.TouchSequence
TouchAction = protos.TouchAction
ApplicationInfo = protos.ApplicationInfo
# uiautomator types
_Selector = protos.Selector
Bound = protos.Bound
Point = protos.Point
Point.getXY = getXY
ApplicationInfo.set = applicationInfoSet
TouchWait.shift = touchWaitShift
TouchMove.shiftX = touchMoveShiftX
TouchMove.shiftY = touchMoveShiftY
TouchDown.shiftX = touchMoveShiftX
TouchDown.shiftY = touchMoveShiftY
TouchAction.type = property(touchActionType)
TouchAction.action = property(touchActionRealAction)
TouchSequence.load = classmethod(touchSequenceLoad)
TouchSequence.save = touchSequenceSave
TouchSequence.appendAction = touchSequenceAppendAction
TouchSequence.appendDown = touchSequenceAppendDown
TouchSequence.appendMove = touchSequenceAppendMove
TouchSequence.appendWait = touchSequenceAppendWait
TouchSequence.appendUp = touchSequenceAppendUp
TouchSequence.__getitem__ = touchSequenceIndexer
TouchSequence.__iter__ = touchSequenceIter
HookRpcRequest = protos.HookRpcRequest
HookRpcResponse = protos.HookRpcResponse
Bound.width = property(width)
Bound.height = property(height)
FindImageMethod = protos.FindImageMethod
FindImageArea = protos.FindImageArea
Bound.center = center
Bound.corner = corner
Bound.__contains__ = contain
Bound.__eq__ = equal
def load_proto(name):
""" 载入包下面的相关 proto 文件 """
return grpc.protos_and_services(name)
def to_dict(prot):
""" 将 proto 返回值转换为字典 """
r = MessageToJson(prot, preserving_proto_field_name=True)
return json.loads(r)
def Selector(**kwargs):
""" Selector wrapper """
kwargs.pop("fields", None)
sel = _Selector(**kwargs, fields=kwargs.keys())
return sel
def child_sibling(s, name, **selector):
s = copy.deepcopy(s)
s.childOrSibling.append(name)
s.childOrSiblingSelector.append(Selector(**selector))
return s
def child(s, **selector):
return child_sibling(s, "child", **selector)
def sibling(s, **selector):
return child_sibling(s, "sibling", **selector)
# bind Selector level child sibling
_Selector.child = child
_Selector.sibling = sibling
class CustomOcrBackend(object):
def __init__(self, *args, **kwargs):
raise NotImplementedError
def ocr(self, image):
raise NotImplementedError
class BaseCryptor(object):
def encrypt(self, data):
return data
def decrypt(self, data):
return data
class BaseServiceStub(object):
def __init__(self, stub):
self.stub = stub
class FernetCryptor(BaseCryptor):
def __init__(self, key=None):
key = self._get_key(key)
self.encoder = Fernet(key)
def encrypt(self, data):
return self.encoder.encrypt(data)
def decrypt(self, data):
return self.encoder.decrypt(data)
def _get_key(self, key):
key = (key or "").encode()
key = hashlib.sha256(key).digest()
key = base64.b64encode(key)
return key
class TouchBuilder(object):
def __init__(self):
self.s = TouchSequence()
def down(self, x, y, pressure=50, track=0):
self.s.appendDown(tid=track, x=x, y=y,
pressure=pressure)
return self
def move(self, x, y, pressure=50, track=0):
self.s.appendMove(tid=track, x=x, y=y,
pressure=pressure)
return self
def up(self, track=0):
self.s.appendUp(tid=track)
return self
def wait(self, mills):
self.s.appendWait(wait=mills)
return self
def build(self):
sequence = TouchSequence()
sequence.CopyFrom(self.s)
return sequence
class ClientLoggingInterceptor(ClientInterceptor):
def truncate_string(self, s):
return "{:.1024}...".format(s) if len(s) > 1024 else s
def intercept(self, function, request, details):
"""
日志记录各个接口的调用及参数
"""
displayable = isinstance(request, Message)
args = MessageToDict(request) if displayable else "-"
args = json.dumps(args, ensure_ascii=False, separators=(",", ":"))
args = self.truncate_string(args)
logger.debug("rpc {} {}".format(details.method, args))
res = function(request, details)
return res
class ClientSessionMetadataInterceptor(ClientInterceptor):
def __init__(self, session):
super(ClientSessionMetadataInterceptor, self).__init__()
self.session = session
def intercept(self, function, request, details):
metadata = {}
metadata["version"] = __version__
default = (self.session, platform.node())
session, name = self.session() if callable(self.session) else default
metadata["instance"] = session
metadata["hostname"] = quote(name)
details = details._replace(metadata=metadata.items())
return function(request, details)
class GrpcRemoteExceptionInterceptor(ClientInterceptor):
def intercept(self, function, request, details):
"""
处理远程调用中发生的异常并抛出本地异常
"""
res = function(request, details)
self.raise_remote_exception(res)
return res
def remote_exception(self, exception):
exc = json.loads(exception)
name, args = exc["name"], exc["args"]
default = lambda *p: UnHandledException(name, *p)
clazz = getattr(builtins, name, default)
clazz = getattr(exceptions, name, clazz)
return clazz(*args)
def raise_remote_exception(self, res):
metadata = dict(res.initial_metadata() or [])
exception = metadata.get("exception", None)
if exception != None:
raise self.remote_exception(exception)
class ObjectUiAutomatorOpStub:
def __init__(self, caller, selector):
"""
UiAutomator 子接口,用来模拟出实例的意味
"""
self._selector = selector
self.selector = Selector(**selector)
self.stub = caller.stub
self.caller = caller
def __str__(self):
selector = ", ".join(["{}={}".format(k, v) \
for k, v in self._selector.items()])
return "Object: {}".format(selector)
__repr__ = __str__
def child(self, **selector):
"""
匹配选择器里面的子节点
"""
selector = self.selector.child(**selector)
s = MessageToDict(selector, preserving_proto_field_name=True)
return self.__class__(self.caller, s)
def sibling(self, **selector):
"""
匹配选择器的同级节点
"""
selector = self.selector.sibling(**selector)
s = MessageToDict(selector, preserving_proto_field_name=True)
return self.__class__(self.caller, s)
def take_screenshot(self, quality=100):
"""
对选择器选中元素进行截图
"""
req = protos.SelectorTakeScreenshotRequest(selector=self.selector,
quality=quality)
r = self.stub.selectorTakeScreenshot(req)
return BytesIO(r.value)
def screenshot(self, quality=100):
return self.take_screenshot(quality=quality)
def get_text(self):
"""
获取选择器选中输入控件中的文本
"""
req = protos.SelectorOnlyRequest(selector=self.selector)
r = self.stub.selectorGetText(req)
return r.value
def clear_text_field(self):
"""
清空选择器选中输入控件中的文本
"""
req = protos.SelectorOnlyRequest(selector=self.selector)
r = self.stub.selectorClearTextField(req)
return r.value
def set_text(self, text):
"""
向选择器选中输入控件中填入文本
"""
req = protos.SelectorSetTextRequest(selector=self.selector,
text=text)
r = self.stub.selectorSetText(req)
return r.value
def click(self, corner=Corner.COR_CENTER):
"""
点击选择器选中的控件
"""
req = protos.SelectorClickRequest(selector=self.selector,
corner=corner)
r = self.stub.selectorClick(req)
return r.value
def click_exists(self, corner=Corner.COR_CENTER):
"""
点击选择器选中的控件(不存在将不会产生异常)
"""
req = protos.SelectorClickRequest(selector=self.selector,
corner=corner)
r = self.stub.selectorClickExists(req)
return r.value
def long_click(self, corner=Corner.COR_CENTER):
"""
长按选择器选中的控件
"""
req = protos.SelectorClickRequest(selector=self.selector,
corner=corner)
r = self.stub.selectorLongClick(req)
return r.value
def exists(self):
"""
是否存在选择器选中的控件
"""
req = protos.SelectorOnlyRequest(selector=self.selector)
r = self.stub.selectorExists(req)
return r.value
def info(self):
"""
获取选择器选中控件的信息
"""
req = protos.SelectorOnlyRequest(selector=self.selector)
return self.stub.selectorObjInfo(req)
def _new_object(self, **kwargs):
selector = copy.deepcopy(self._selector)
child_sibling = selector.get("childOrSiblingSelector")
target = child_sibling[-1] if child_sibling else selector
target.update(**kwargs)
return self.caller(**selector)
def text(self, txt):
return self._new_object(text=txt)
def resourceId(self, name):
return self._new_object(resourceId=name)
def description(self, desc):
return self._new_object(description=desc)
def packageName(self, name):
return self._new_object(packageName=name)
def className(self, name):
return self._new_object(className=name)
def textContains(self, needle):
return self._new_object(textContains=needle)
def descriptionContains(self, needle):
return self._new_object(descriptionContains=needle)
def textStartsWith(self, needle):
return self._new_object(textStartsWith=needle)
def descriptionStartsWith(self, needle):
return self._new_object(descriptionStartsWith=needle)
def textMatches(self, match):
return self._new_object(textMatches=match)
def descriptionMatches(self, match):
return self._new_object(descriptionMatches=match)
def resourceIdMatches(self, match):
return self._new_object(resourceIdMatches=match)
def packageNameMatches(self, match):
return self._new_object(packageNameMatches=match)
def classNameMatches(self, match):
return self._new_object(classNameMatches=match)
def checkable(self, value):
return self._new_object(checkable=value)
def clickable(self, value):
return self._new_object(clickable=value)
def focusable(self, value):
return self._new_object(focusable=value)
def scrollable(self, value):
return self._new_object(scrollable=value)
def longClickable(self, value):
return self._new_object(longClickable=value)
def enabled(self, value):
return self._new_object(enabled=value)
def checked(self, value):
return self._new_object(checked=value)
def focused(self, value):
return self._new_object(focused=value)
def selected(self, value):
return self._new_object(selected=value)
def index(self, idx):
return self._new_object(index=idx)
def instance(self, idx):
return self._new_object(instance=idx)
def get(self, idx):
"""
获取匹配的第 N 个索引的元素
"""
return self.instance(idx)
def __iter__(self):
"""
遍历所有符合选择器条件的元素实例
"""
yield from [self.instance(i) for i in \
range(self.count())]
def count(self):
"""
获取选择器选中控件的数量
"""
req = protos.SelectorOnlyRequest(selector=self.selector)
r = self.stub.selectorCount(req)
return r.value
def _set_target_Point(self, req, target):
req.point.CopyFrom(target)
def _set_target_Selector(self, req, target):
req.target.CopyFrom(target)
def drag_to(self, target, step=32):
"""
将选择器选中的控件拖动到另一个选择器或者点上
"""
checkArgumentTyp(target, (Point, _Selector))
func = "_set_target_{}".format(target.DESCRIPTOR.name)
req = protos.SelectorDragToRequest(selector=self.selector,
step=step)
getattr(self, func)(req, target)
r = self.stub.selectorDragTo(req)
return r.value
def wait_for_exists(self, timeout):
"""
等待选择器选中控件出现
"""
req = protos.SelectorWaitRequest(selector=self.selector,
timeout=timeout)
r = self.stub.selectorWaitForExists(req)
return r.value
def wait_until_gone(self, timeout):
"""
等待选择器选中控件消失
"""
req = protos.SelectorWaitRequest(selector=self.selector,
timeout=timeout)
r = self.stub.selectorWaitUntilGone(req)
return r.value
def swipe(self, direction=Direction.DIR_UP, step=32):
"""
在选择器选中的元素上进行滑动操作
"""
req = protos.SelectorSwipeRequest(selector=self.selector,
direction=direction,
step=step)
r = self.stub.selectorSwipe(req)
return r.value
def pinch_in(self, percent, step=16):
"""
双指捏紧(缩小)
"""
req = protos.SelectorPinchRequest(selector=self.selector,
percent=percent, step=step)
r = self.stub.selectorPinchIn(req)
return r.value
def pinch_out(self, percent, step=16):
"""
双指放开(放大)
"""
req = protos.SelectorPinchRequest(selector=self.selector,
percent=percent, step=step)
r = self.stub.selectorPinchOut(req)
return r.value
def scroll_to(self, target, is_vertical=True):
"""
滚动 scrollable 直到匹配目标元素的选择器
"""
checkArgumentTyp(target, _Selector)
req = protos.SelectorScrollRequest(selector=self.selector,
vertical=is_vertical,
target=target)
r = self.stub.selectorScrollTo(req)
return r.value
def _fling_forward(self, is_vertical=True):
req = protos.SelectorFlingRequest(selector=self.selector,
vertical=is_vertical)
r = self.stub.selectorFlingForward(req)
return r.value
def _fling_backward(self, is_vertical=True):
req = protos.SelectorFlingRequest(selector=self.selector,
vertical=is_vertical)
r = self.stub.selectorFlingBackward(req)
return r.value
def _fling_to_end(self, max_swipes, is_vertical=True):
req = protos.SelectorFlingRequest(selector=self.selector,
maxSwipes=max_swipes,
vertical=is_vertical)
r = self.stub.selectorFlingToEnd(req)
return r.value
def _fling_to_beginning(self, max_swipes, is_vertical=True):
req = protos.SelectorFlingRequest(selector=self.selector,
maxSwipes=max_swipes,
vertical=is_vertical)
r = self.stub.selectorFlingToBeginning(req)
return r.value
def fling_from_top_to_bottom(self):
"""
在选择器选中元素上进行从上至下阅读式滑动(单次)
"""
return self._fling_backward(is_vertical=True)
def fling_from_bottom_to_top(self):
"""
在选择器选中元素上进行从下至上阅读式滑动(单次)
"""
return self._fling_forward(is_vertical=True)
def fling_from_left_to_right(self):
"""
在选择器选中元素上进行从左至右阅读式滑动(单次)
"""
return self._fling_backward(is_vertical=False)
def fling_from_right_to_left(self):
"""
在选择器选中元素上进行从右至左阅读式滑动(单次)
"""
return self._fling_forward(is_vertical=False)
def fling_from_top_to_bottom_to_end(self, max_swipes):
"""
在选择器选中元素上进行从上至下阅读式滑动直至无法滑动或达到 max_swipes 次
"""
return self._fling_to_beginning(max_swipes, is_vertical=True)
def fling_from_bottom_to_top_to_end(self, max_swipes):
"""
在选择器选中元素上进行从下至上阅读式滑动直至无法滑动或达到 max_swipes 次
"""
return self._fling_to_end(max_swipes, is_vertical=True)
def fling_from_left_to_right_to_end(self, max_swipes):
"""
在选择器选中元素上进行从左至右阅读式滑动直至无法滑动或达到 max_swipes 次
"""
return self._fling_to_beginning(max_swipes, is_vertical=False)
def fling_from_right_to_left_to_end(self, max_swipes):
"""
在选择器选中元素上进行从右至左阅读式滑动直至无法滑动或达到 max_swipes 次
"""
return self._fling_to_end(max_swipes, is_vertical=False)
def _scroll_forward(self, step, is_vertical=True):
req = protos.SelectorScrollRequest(selector=self.selector,
vertical=is_vertical,
step=step)
r = self.stub.selectorScrollForward(req)
return r.value
def _scroll_backward(self, step, is_vertical=True):
req = protos.SelectorScrollRequest(selector=self.selector,
vertical=is_vertical,
step=step)
r = self.stub.selectorScrollBackward(req)
return r.value
def _scroll_to_end(self, max_swipes, step, is_vertical=True):
req = protos.SelectorScrollRequest(selector=self.selector,
maxSwipes=max_swipes,
vertical=is_vertical,
step=step)
r = self.stub.selectorScrollToEnd(req)
return r.value
def _scroll_to_beginning(self, max_swipes, step, is_vertical=True):
req = protos.SelectorScrollRequest(selector=self.selector,
maxSwipes=max_swipes,
vertical=is_vertical,
step=step)
r = self.stub.selectorScrollToBeginning(req)
return r.value
def scroll_from_top_to_bottom(self, step):
"""
在选择器选中元素上进行从上至下普通滑动
"""
return self._scroll_backward(step, is_vertical=True)
def scroll_from_bottom_to_top(self, step):
"""
在选择器选中元素上进行从下至上普通滑动
"""
return self._scroll_forward(step, is_vertical=True)
def scroll_from_left_to_right(self, step):
"""
在选择器选中元素上进行从左至右普通滑动
"""
return self._scroll_backward(step, is_vertical=False)
def scroll_from_right_to_left(self, step):
"""
在选择器选中元素上进行从右至左普通滑动
"""
return self._scroll_forward(step, is_vertical=False)
def scroll_from_top_to_bottom_to_end(self, max_swipes, step):
"""
在选择器选中元素上进行从上至下普通滑动直至无法滑动或达到 max_swipes 次
"""
return self._scroll_to_beginning(max_swipes, step, is_vertical=True)
def scroll_from_bottom_to_top_to_end(self, max_swipes, step):
"""
在选择器选中元素上进行从下至上普通滑动直至无法滑动或达到 max_swipes 次
"""
return self._scroll_to_end(max_swipes, step, is_vertical=True)
def scroll_from_left_to_right_to_end(self, max_swipes, step):
"""
在选择器选中元素上进行从左至右普通滑动直至无法滑动或达到 max_swipes 次
"""
return self._scroll_to_beginning(max_swipes, step, is_vertical=False)
def scroll_from_right_to_left_to_end(self, max_swipes, step):
"""
在选择器选中元素上进行从右至左普通滑动直至无法滑动或达到 max_swipes 次
"""
return self._scroll_to_end(max_swipes, step, is_vertical=False)
class UiAutomatorStub(BaseServiceStub):
def __init__(self, *args, **kwargs):
super(UiAutomatorStub, self).__init__(*args, **kwargs)
self.watchers = defaultdict(dict)
def device_info(self):
"""
获取设备基本/分辨率等信息
"""
r = self.stub.deviceInfo(protos.Empty())
return r
def set_watcher_loop_enabled(self, enabled):
"""
设置是否启用设备上的 watcher UI 检测
"""
req = protos.Boolean(value=enabled)
r = self.stub.setWatcherLoopEnabled(req)
return r.value
def get_watcher_loop_enabled(self):
"""
获取是否启用设备上的 watcher UI 检测
"""
r = self.stub.getWatcherLoopEnabled(protos.Empty())
return r.value
def get_watcher_triggered_count(self, name):
"""
获取这个 watcher 被触发的次数
"""
req = protos.String(value=name)
r = self.stub.getWatcherTriggeredCount(req)
return r.value
def reset_watcher_triggered_count(self, name):
"""
重置这个 watcher 的触发次数为 0
"""
req = protos.String(value=name)
r = self.stub.resetWatcherTriggeredCount(req)
return r.value
def get_applied_watchers(self):
"""
获取已经在系统应用的 watcher 名称列表
"""
r = self.stub.getAppliedWatchers(protos.Empty())
return r.watchers
# 注意:下面这些 watcher 实现不是安全的
# 注册时都是统一存储到本地实例的变量中,直至 enable 时才会应用至服务端
# 这样做的原因是让你知道你都干了什么,过多的 watcher 会影响性能
def remove_all_watchers(self):
"""
移除所有应用/未应用的 watcher
"""
for name in list(self.get_applied_watchers()):
self.remove_watcher(name)
for name in list(self.watchers.keys()):
self.remove_watcher(name)
def register_click_target_selector_watcher(self, name, conditions,
target):
"""
注册一个满足条件点击 selector 的 watcher
"""
checkDupEntry(name, self.watchers)
req = protos.WatcherRegistRequest(name=name, selectors=conditions,
target=target)
self.watchers[name]["enabled"] = False
func = lambda: self.stub.registerClickUiObjectWatcher(req).value
self.watchers[name]["enable"] = func
def register_press_key_watcher(self, name, conditions, key):
"""
注册一个满足条件点击 key 的 watcher
"""
checkDupEntry(name, self.watchers)
req = protos.WatcherRegistRequest(name=name, selectors=conditions,
key=key)
self.watchers[name]["enabled"] = False
func = lambda: self.stub.registerPressKeysWatcher(req).value
self.watchers[name]["enable"] = func
def register_none_op_watcher(self, name, conditions):
"""
注册一个满足条件无操作的 watcher(用来检测是否出现过某个场景)
"""
checkDupEntry(name, self.watchers)
req = protos.WatcherRegistRequest(name=name, selectors=conditions)
self.watchers[name]["enabled"] = False
func = lambda: self.stub.registerNoneOpWatcher(req).value
self.watchers[name]["enable"] = func
def _remove_watcher(self, name):
return self.stub.removeWatcher(protos.String(value=name)).value
def set_watcher_enabled(self, name, enable):
"""
设置是否启用此 watcher
"""
if name not in self.watchers:
return False
self.watchers[name]["enabled"] = enable
if self.watchers[name]["enabled"]:
return self.watchers[name]["enable"]()
return self._remove_watcher(name)
def get_watcher_enabled(self, name):
"""
获取此 watcher 是否启用
"""
return self.watchers.get(name, {}).get("enable")
def get_last_toast(self):
"""
获取系统中最后一个 toast 消息
"""
r = self.stub.getLastToast(protos.Empty())
return r
def remove_watcher(self, name):
"""
移除一个 watcher
"""
self.watchers.pop(name, None)
return self._remove_watcher(name)
def click(self, point):
"""
点击屏幕中的某个点(Point)
"""
req = protos.ClickPointRequest(point=point)
r = self.stub.click(req)
return r.value
def drag(self, A, B, step=32):
"""
从点(Point) A 拖动到点(Point) B
"""
req = protos.DragPointRequest(A=A, B=B, step=step)
r = self.stub.drag(req)
return r.value
def swipe(self, A, B, step=32):
"""
从点(Point) A 滑动到点(Point) B
"""
req = protos.SwipePointRequest(A=A, B=B, step=step)
r = self.stub.swipe(req)
return r.value
def swipe_points(self, *points, step=32):
"""
滑动一个点(Point)序列(超过两个点)
"""
req = protos.SwipePointsRequest(points=points, step=step)
r = self.stub.swipePoints(req)
return r.value
def open_notification(self):
"""
打开通知栏(状态栏)
"""
r = self.stub.openNotification(protos.Empty())
return r.value
def open_quick_settings(self):
"""
打开设置栏(状态栏)
"""
r = self.stub.openQuickSettings(protos.Empty())
return r.value
def wake_up(self):
"""
唤醒设备(点亮屏幕)
"""
r = self.stub.wakeUp(protos.Empty())
return r.value
def sleep(self):
"""
关闭设备(熄灭屏幕)
"""
r = self.stub.sleep(protos.Empty())
return r.value
def is_screen_on(self):
"""
设备是否处于唤醒状态
"""
r = self.stub.isScreenOn(protos.Empty())
return r.value
def is_screen_locked(self):
"""
设备屏幕是否已经锁定
"""
r = self.stub.isScreenLocked(protos.Empty())
return r.value
def set_clipboard(self, text):
"""
设置剪切板文字
"""
req = protos.ClipboardRequest(ID=str(uuid.uuid4()), value=text)
r = self.stub.setClipboard(req)
return r.value
def get_clipboard(self):
"""
获取剪切板文字(小于 Android10)
"""
r = self.stub.getClipboard(protos.Empty())
return r.value
def _set_target_Area(self, req, area):
req.area = area
def _set_target_Bound(self, req, bound):
req.bound.CopyFrom(bound)
def find_similar_image(self, data, threshold=0.0, distance=250,
scale=1.0, area=FindImageArea.FIA_WHOLE_SCREEN,
method=FindImageMethod.FIM_TEMPLATE):
"""
根据提供的目标图片从屏幕中获取相似图片位置
"""
req = protos.FindImageRequest()
checkArgumentTyp(area, (Bound, int))
name = getattr(getattr(area, "DESCRIPTOR", None),
"name", "Area")
func = "_set_target_{}".format(name)
getattr(self, func)(req, area)
req.method = method
req.distance = distance
req.threshold = threshold
req.scale = scale
req.partial = data
r = self.stub.findSimilarImage(req)
return r.bounds
def freeze_rotation(self, freeze=True):
"""
锁定屏幕旋转
"""
r = self.stub.freezeRotation(protos.Boolean(value=freeze))
return r.value
def set_orientation(self, orien=Orientation.ORIEN_NATURE):
"""
设置屏幕旋转方向
"""
req = protos.OrientationRequest(orientation=orien)
r = self.stub.setOrientation(req)
return r.value
def press_key(self, key):
"""
按下设备物理按键(HOME/VOLUME/BACK)
"""
req = protos.PressKeyRequest(key=key)
r = self.stub.pressKey(req)
return r.value
def press_keycode(self, code, meta=0):
"""
通过 Keycode(整数)按下未定义的按键
ref: https://developer.android.com/reference/android/view/KeyEvent
"""
req = protos.PressKeyRequest(code=code, meta=meta)
r = self.stub.pressKeyCode(req)
return r.value
def take_screenshot(self, quality, bound=None):
"""
截取全屏幕截图
"""
req = protos.TakeScreenshotRequest(quality=quality,
bound=bound)
r = self.stub.takeScreenshot(req)
return BytesIO(r.value)
def screenshot(self, quality, bound=None):
return self.take_screenshot(quality, bound=bound)
def dump_window_hierarchy(self, compressed=False):
"""
获取屏幕界面布局 XML 文档
"""
req = protos.Boolean(value=compressed)
r = self.stub.dumpWindowHierarchy(req)
return BytesIO(r.value)
def wait_for_idle(self, timeout):
"""
等待当前屏幕处于闲置状态(无频繁活动切换)
"""
r = self.stub.waitForIdle(protos.Integer(value=timeout))
return r.value
def __call__(self, **kwargs):
return ObjectUiAutomatorOpStub(self, kwargs)
class AppScriptRpcInterface(object):
def __init__(self, stub, application,
name):
self.application = application
self.stub = stub
self.name = name
def __str__(self):
return "{}:Script:{}".format(self.application,
self.name)
__repr__ = __str__
def __call__(self, *args):
call_args = dict()
call_args["method"] = self.name
call_args["args"] = args
req = HookRpcRequest()
req.package = self.application.applicationId
req.user = self.application.user
req.callinfo = json.dumps(call_args)
result = self.stub.callScript(req)
data = json.loads(result.callresult)
return data
class ApplicationOpStub:
def __init__(self, stub, applicationId, user=0):
"""
Application 子接口,用来模拟出实例的意味
"""
self.user = user
self.applicationId = applicationId
self.stub = stub
def __str__(self):
return "Application:{}@{}".format(self.applicationId,
self.user)
__repr__ = __str__
def is_foreground(self):
"""
应用是否正处于前台运行
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.isForeground(req)
return r.value
def permissions(self):
"""
获取应用的所有权限列表
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.getPermissions(req)
return r.permissions
def grant(self, permission, mode=GrantType.GRANT_ALLOW):
"""
授予应用某个权限(应用需要运行时获取的权限)
"""
req = protos.ApplicationRequest(name=self.applicationId,
permission=permission,
mode=mode)
req.user = self.user
r = self.stub.grantPermission(req)
return r.value
def revoke(self, permission):
"""
撤销授予应用的权限(应用需要运行时获取的权限)
"""
req = protos.ApplicationRequest(name=self.applicationId,
permission=permission)
req.user = self.user
r = self.stub.revokePermission(req)
return r.value
def query_launch_activity(self):
"""
获取应用的启动 activity 信息
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.queryLaunchActivity(req)
return to_dict(r)
def is_permission_granted(self, permission):
"""
检查是否已经授予应用某权限(应用需要运行时获取的权限)
"""
req = protos.ApplicationRequest(name=self.applicationId,
permission=permission)
req.user = self.user
r = self.stub.isPermissionGranted(req)
return r.value
def clear_cache(self):
"""
清空应用的缓存数据(非数据仅缓存)
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.deleteApplicationCache(req)
return r.value
def reset(self):
"""
清空应用的所有数据
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.resetApplicationData(req)
return r.value
def start(self):
"""
启动应用
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.startApplication(req)
return r.value
def stop(self):
"""
停止应用
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.stopApplication(req)
return r.value
def info(self):
"""
获取应用信息
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.applicationInfo(req)
return r
def uninstall(self):
"""
卸载应用 (always return true)
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.uninstallApplication(req)
return r.value
def enable(self):
"""
启用应用
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.enableApplication(req)
return r.value
def disable(self):
"""
禁用应用(这将使应用从启动器消失)
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.disableApplication(req)
return r.value
def add_to_doze_mode_whitelist(self):
"""
将APP加入省电白名单(可以一直运行,可能不会覆盖所有系统)
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.addToDozeModeWhiteList(req)
return True
def remove_from_doze_mode_whitelist(self):
"""
将APP移除省电白名单 (always return true)
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.removeFromDozeModeWhiteList(req)
return True
def is_installed(self):
"""
检查应用是否已经安装
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.isInstalled(req)
return r.value
def attach_script(self, script, runtime=ScriptRuntime.RUNTIME_QJS,
emit="",
encode=DataEncode.DATA_ENCODE_NONE,
spawn=False,
standup=5):
"""
向应用注入持久化 Hook 脚本
"""
s = isinstance(script, str)
script = script.encode() if s else script
req = protos.HookRequest()
req.package = self.applicationId
req.script = script
req.runtime = runtime
req.standup = standup
req.spawn = spawn
req.destination = emit
req.encode = encode
req.user = self.user
r = self.stub.attachScript(req)
return r.value
def detach_script(self):
"""
移除注入应用的 Hook 脚本
"""
req = protos.HookRequest()
req.package = self.applicationId
req.user = self.user
r = self.stub.detachScript(req)
return r.value
def is_attached_script(self):
"""
检查使用在此应用注入了 Hook 脚本
"""
req = protos.HookRequest()
req.package = self.applicationId
req.user = self.user
r = self.stub.isScriptAttached(req)
return r.value
def is_script_alive(self):
"""
检查应用中的 Hook 脚本是否正常
"""
req = protos.HookRequest()
req.package = self.applicationId
req.user = self.user
r = self.stub.isScriptAlive(req)
return r.value
def __getattr__(self, name):
"""
调用注入应用 Hook 脚本的导出方法
"""
return AppScriptRpcInterface(self.stub, self,
name)
class ApplicationStub(BaseServiceStub):
def current_application(self):
"""
获取当前处于前台的应用的信息
"""
top = self.stub.currentApplication(protos.Empty())
app = self.__call__(top.packageName, user=top.user)
app.activity = top.activity
return app
def get_application_by_name(self, name, user=0):
req = protos.String(value=name)
r = self.stub.getIdentifierByLabel(req)
app = self.__call__(r.value, user=user)
return app
def enumerate_running_processes(self):
"""
列出设备上所有正在运行的安卓应用进程
"""
r = self.stub.enumerateRunningProcesses(protos.Empty())
return r.processes
def enumerate_all_pkg_names(self):
"""
列出所有已安装的应用的 applicationId
"""
r = self.stub.enumerateAllPkgNames(protos.Empty())
return r.names
def get_last_activities(self, count=3):
"""
获取系统中最后一个活动的详细信息
"""
req = protos.Integer(value=count)
r = self.stub.getLastActivities(req).activities
return list(map(to_dict, r))
def start_activity(self, **activity):
"""
启动 activity(总是返回 True)
"""
activity.setdefault("extras", {})
extras = activity.pop("extras")
req = protos.ApplicationActivityRequest(**activity)
req.extras.update(extras)
r = self.stub.startActivity(req)
return r.value
def install_local_file(self, fpath, user=0):
"""
安装设备上的 apk 文件(注意此路径为设备上的 apk 路径)
"""
req = protos.ApplicationRequest(path=fpath)
req.user = user
r = self.stub.installFromLocalFile(req)
return r
def __call__(self, applicationId, user=0):
return ApplicationOpStub(self.stub, applicationId, user)
class StorageOpStub:
# 用于容器值序列化的方法
def _decrypt(self, data):
return self.cryptor.decrypt(data)
def _encrypt(self, data):
return self.cryptor.encrypt(data)
def _unpack(self, value):
return msgpack.loads(self._decrypt(value))
def _pack(self, value):
return self._encrypt(msgpack.dumps(value))
# 注意:此接口可能并不是跨语言通用
def __init__(self, stub, name, cryptor=None):
self.cryptor = cryptor
self.name = name
self.stub = stub
def delete(self, key):
"""
删除一个 KEY
"""
req = protos.StorageRequest(key=key)
req.container = self.name
res = self.stub.delete(req)
return res.value
def exists(self, key):
"""
检查一个 KEY 是否存在
"""
req = protos.StorageRequest(key=key)
req.container = self.name
res = self.stub.exists(req)
return res.value
def get(self, key, default=None):
"""
获取 KEY 对应的键值
"""
req = protos.StorageRequest(key=key)
req.container = self.name
val = self.stub.get(req).value
res = self._unpack(val) if val else default
return res
def set(self, key, value):
"""
设置 KEY 对应的键值
"""
value = self._pack(value)
req = protos.StorageRequest(key=key, value=value)
req.container = self.name
res = self.stub.set(req)
return res.value
def setex(self, key, value, ttl):
"""
设置 KEY 对应的键值,该 KEY 在 TTL 秒后自动删除
"""
value = self._pack(value)
req = protos.StorageRequest(key=key, value=value)
req.container = self.name
req.ttl = ttl
res = self.stub.setex(req)
return res.value
def setnx(self, key, value):
"""
设置 KEY 对应的键值 (仅当该键不存在时)
"""
value = self._pack(value)
req = protos.StorageRequest(key=key, value=value)
req.container = self.name
res = self.stub.setnx(req)
return res.value
def expire(self, key, ttl):
"""
设置 KEY 在 TTL 秒后过期
"""
req = protos.StorageRequest(key=key, ttl=ttl)
req.container = self.name
res = self.stub.expire(req)
return res.value
def ttl(self, key):
"""
获取 KEY 的 TTL (过期时间)
"""
req = protos.StorageRequest(key=key)
req.container = self.name
res = self.stub.ttl(req)
return res.value
class StorageStub(BaseServiceStub):
def clear(self):
"""
删除所有 Storage 容器
"""
r = self.stub.clearAll(protos.Empty())
return r.value
def use(self, name, cryptor=BaseCryptor, **kwargs):
"""
使用一个 Storage 容器
"""
return StorageOpStub(self.stub, name, cryptor(**kwargs))
def remove(self, name):
"""
删除一个 Storage 容器
"""
req = protos.String(value=name)
r = self.stub.clearContainer(req)
return r.value
class UtilStub(BaseServiceStub):
def _get_file_content(self, certfile):
with open(certfile, "rb") as fd:
return fd.read()
def is_ca_certificate_installed(self, certfile):
"""
安装系统证书(用于 MITM)
"""
data = self._get_file_content(certfile)
req = protos.CertifiRequest(cert=data)
r = self.stub.isCACertificateInstalled(req)
return r.value
def install_ca_certificate(self, certfile):
"""
安装系统证书(用于 MITM)
"""
data = self._get_file_content(certfile)
req = protos.CertifiRequest(cert=data)
r = self.stub.installCACertificate(req)
return r.value
def uninstall_ca_certificate(self, certfile):
"""
移除系统证书(用于 MITM)
"""
data = self._get_file_content(certfile)
req = protos.CertifiRequest(cert=data)
r = self.stub.uninstallCACertificate(req)
return r.value
def record_touch(self):
"""
录制滑动轨迹
"""
r = self.stub.recordTouch(protos.Empty())
return r
def perform_touch(self, tas, wait=True):
"""
在设备上进行真实滑动(重放录制的滑动轨迹)
"""
checkArgumentTyp(tas, TouchSequence)
req = protos.PerformTouchRequest(sequence=tas, wait=wait)
r = self.stub.performTouch(req)
return r.value
def reboot(self):
"""
重启系统(宿主设备)
"""
r = self.stub.reboot(protos.Empty())
return r.value
def shutdown(self):
"""
关闭系统(宿主设备)
"""
r = self.stub.shutdown(protos.Empty())
return r.value
def reload(self, clean=False):
"""
重载设备上运行的服务端
"""
req = protos.Boolean(value=clean)
r = self.stub.reload(req)
return r.value
def exit(self):
"""
退出设备上运行的服务端
"""
r = self.stub.exit(protos.Empty())
return r.value
def beep(self):
"""
播放一声蜂鸣(物理查找)
"""
r = self.stub.beepBeep(protos.Empty())
return r.value
def play_audio(self, file, type=AudioStreamType.AST_SYSTEM,
loop=1, interval=0):
"""
播放 wav 音频
"""
profile = PlayAudioProfile()
profile.file = file
profile.type = type
profile.loop = loop
profile.interval = interval
r = self.stub.playAudio(profile)
return r.value
def show_toast(self, text, duration=ToastDuration.TD_SHORT):
"""
在系统界面底部显示一个 Toast 消息
"""
req = protos.ShowToastRequest(text=text, duration=duration)
r = self.stub.showToast(req)
return r.value
def setprop(self, name, value):
"""
设置系统属性(aka: setprop,支持设置 ro.xx 只读属性)
"""
req = protos.SetPropRequest(name=name, value=value)
r = self.stub.setProp(req)
return r.value
def getprop(self, name):
"""
获取系统属性(aka: getprop)
"""
req = protos.String(value=name)
r = self.stub.getProp(req)
return r.value
def server_info(self):
"""
获取服务端ID、版本等信息
"""
r = self.stub.serverInfo(protos.Empty())
return r
def hex_patch(self, pattern, replacement, path,
maxreplace=-1,
dryrun=False):
"""
对设备上的文件进行十六进制字节替换
"""
req = protos.HexPatchRequest()
req.pattern = pattern
req.replacement = replacement
req.path = path
req.maxreplace = maxreplace
req.dryrun = dryrun
return self.stub.hexPatch(req)
class DebugStub(BaseServiceStub):
def _read_pubkey(self, pubkey):
with open(pubkey, "rb") as fd:
return fd.read()
def install_adb_pubkey(self, pubkey):
"""
给内置 adb 服务添加公钥
"""
req = protos.ADBDConfigRequest()
req.adb_pubkey = self._read_pubkey(pubkey)
r = self.stub.installADBPubKey(req)
return r.value
def uninstall_adb_pubkey(self, pubkey):
"""
从内置 adb 服务移除公钥
"""
req = protos.ADBDConfigRequest()
req.adb_pubkey = self._read_pubkey(pubkey)
r = self.stub.uninstallADBPubKey(req)
return r.value
def is_android_debug_bridge_running(self):
"""
远端 adb daemon 是否在运行
"""
r = self.stub.isAndroidDebugBridgeRunning(protos.Empty())
return r.value
def start_android_debug_bridge(self):
"""
启动内置 adbd (默认随框架启动)
"""
r = self.stub.startAndroidDebugBridge(protos.Empty())
return r.value
def stop_android_debug_bridge(self):
"""
停止内置 adb daemon
"""
r = self.stub.stopAndroidDebugBridge(protos.Empty())
return r.value
class SettingsStub(BaseServiceStub):
def _put(self, group, name, value):
req = protos.SettingsRequest(group=group, name=name,
value=value)
r = self.stub.putSettings(req)
return r.value
def _get(self, group, name):
req = protos.SettingsRequest(group=group,name=name)
r = self.stub.getSettings(req)
return r.value
def get_system(self, name):
"""
等价于 settings get system xxxx
"""
return self._get(Group.GROUP_SYSTEM, name)
def put_system(self, name, value):
"""
等价于 settings put system xxxx xxxx
"""
return self._put(Group.GROUP_SYSTEM, name, value)
def get_global(self, name):
"""
等价于 settings get global xxxx
"""
return self._get(Group.GROUP_GLOBAL, name)
def put_global(self, name, value):
"""
等价于 settings put global xxxx xxxx
"""
return self._put(Group.GROUP_GLOBAL, name, value)
def get_secure(self, name):
"""
等价于 settings get secure xxxx
"""
return self._get(Group.GROUP_SECURE, name)
def put_secure(self, name, value):
"""
等价于 settings put secure xxxx xxxx
"""
return self._put(Group.GROUP_SECURE, name, value)
class ShellStub(BaseServiceStub):
def execute_script(self, script, alias=None,
timeout=60):
"""
前台执行一段脚本(支持标准的多行脚本)
"""
req = protos.ShellRequest(name=alias, script=script,
timeout=timeout)
r = self.stub.executeForeground(req)
return r
def execute_background_script(self, script, alias=None):
"""
后台执行一段脚本(支持标准的多行脚本)
"""
req = protos.ShellRequest(name=alias, script=script)
r = self.stub.executeBackground(req)
return r.tid
def is_background_script_finished(self, tid):
"""
后台脚本是否已经结束
"""
req = protos.ShellTask(tid=tid)
r = self.stub.isBackgroundFinished(req)
return r.value
def kill_background_script(self, tid):
"""
强行停止后台脚本
"""
req = protos.ShellTask(tid=tid)
r = self.stub.killBackground(req)
return r.value
class StatusStub(BaseServiceStub):
def get_boot_time(self):
"""
获取设备启动时间 Unix 时间戳
"""
r = self.stub.getBootTime(protos.Empty())
return r.value
def get_disk_usage(self, mountpoint="/data"):
"""
获取分区数据使用情况
"""
req = protos.String(value=mountpoint)
r = self.stub.getDiskUsage(req)
return r
def get_battery_info(self):
"""
获取电池信息
"""
r = self.stub.getBatteryInfo(protos.Empty())
return r
def get_cpu_info(self):
"""
获取 CPU 用量等信息
"""
r = self.stub.getCpuInfo(protos.Empty())
return r
def get_overall_disk_io_info(self):
"""
获取全局的设备磁盘读写状况
"""
r = self.stub.getOverallDiskIOInfo(protos.Empty())
return r
def get_overall_net_io_info(self):
"""
获取全局的设备网络收发状况
"""
r = self.stub.getOverallNetIOInfo(protos.Empty())
return r
def get_userdata_disk_io_info(self):
"""
获取用户数据设备磁盘读写状况
"""
r = self.stub.getUserDataDiskIOInfo(protos.Empty())
return r
def get_net_io_info(self, interface):
"""
获取特定接口的网络收发状况
"""
req = protos.String(value=interface)
r = self.stub.getNetIOInfo(req)
return r
def get_mem_info(self):
"""
获取设备内存状况
"""
r = self.stub.getMemInfo(protos.Empty())
return r
class ProxyStub(BaseServiceStub):
def is_openvpn_running(self):
"""
检查 OPENVPN 是否正在运行
"""
r = self.stub.isOpenVPNRunning(protos.Empty())
return r.value
def is_gproxy_running(self):
"""
检查 GPROXY 是否正在运行
"""
r = self.stub.isGproxyRunning(protos.Empty())
return r.value
def start_openvpn(self, profile):
"""
启动 OPENVPN
"""
checkArgumentTyp(profile, OpenVPNProfile)
r = self.stub.startOpenVPN(profile)
return r.value
def start_gproxy(self, profile):
"""
启动 GPROXY
"""
checkArgumentTyp(profile, GproxyProfile)
r = self.stub.startGproxy(profile)
return r.value
def stop_openvpn(self):
"""
停止 OPENVPN
"""
r = self.stub.stopOpenVPN(protos.Empty())
return r.value
def stop_gproxy(self):
"""
停止 GPROXY
"""
r = self.stub.stopGproxy(protos.Empty())
return r.value
class SelinuxPolicyStub(BaseServiceStub):
def allow(self, source, target, tclass, action):
"""
selinux allow
"""
req = protos.SelinuxPolicyRequest(source=source, target=target,
tclass=tclass, action=action)
r = self.stub.policySetAllow(req)
return r.value
def disallow(self, source, target, tclass, action):
"""
selinux disallow
"""
req = protos.SelinuxPolicyRequest(source=source, target=target,
tclass=tclass, action=action)
r = self.stub.policySetDisallow(req)
return r.value
def get_enforce(self):
"""
获取当前 selinux enforce 状态
"""
r = self.stub.getEnforce(protos.Empty())
return r.value
def set_enforce(self, enforced=True):
"""
设置当前 selinux enforce 状态 (aka: setenforce 0/1)
"""
req = protos.Boolean(value=enforced)
r = self.stub.setEnforce(req)
return r.value
def enabled(self):
"""
获取设备上的 selinux 是否已经启用
"""
r = self.stub.isEnabled(protos.Empty())
return r.value
def enforce(self, name):
"""
设置一个域为 enforce
"""
req = protos.String(value=name)
r = self.stub.policySetEnforce(req)
return r.value
def permissive(self, name):
"""
设置一个域为 permissive
"""
req = protos.String(value=name)
r = self.stub.policySetPermissive(req)
return r.value
def create_domain(self, name):
"""
新建一个 selinux 域
"""
req = protos.String(value=name)
r = self.stub.policyCreateDomain(req)
return r.value
class FileStub(BaseServiceStub):
def _fd_stream_read(self, fd, chunksize):
for chunk in iter(lambda: fd.read(chunksize), bytes()):
yield chunk
def _fd_streaming_send(self, fd, dest, chunksize):
yield protos.FileRequest(path=dest)
for chunk in self._fd_stream_read(fd, chunksize):
yield protos.FileRequest(payload=chunk)
def _fd_streaming_recv(self, fd, iterator):
for chunk in iterator:
fd.write(chunk.payload)
def download_fd(self, fpath, fd):
"""
从设备下载文件到文件描述符
"""
req = protos.FileRequest(path=fpath)
iterator = self.stub.downloadFile(req)
self._fd_streaming_recv(fd, iterator)
st = self.file_stat(fpath)
return st
def upload_fd(self, fd, dest):
"""
上传文件描述符至设备
"""
chunksize = 1024*1024*1
streaming = self._fd_streaming_send(fd, dest,
chunksize)
self.stub.uploadFile(streaming)
st = self.file_stat(dest)
return st
def download_file(self, fpath, dest):
"""
从设备下载文件到本地
"""
with io.open(dest, mode="wb") as fd:
return self.download_fd(fpath, fd)
def upload_file(self, fpath, dest):
"""
上传本地文件至设备
"""
with io.open(fpath, mode="rb") as fd:
return self.upload_fd(fd, dest)
def delete_file(self, fpath):
"""
删除设备上的文件
"""
req = protos.FileRequest(path=fpath)
r = self.stub.deleteFile(req)
return r.value
def file_chmod(self, fpath, mode=0o644):
"""
更改设备上文件的权限
"""
req = protos.FileRequest(path=fpath, mode=mode)
r = self.stub.fileChmod(req)
return r
def file_stat(self, fpath):
"""
获取设备上文件的信息
"""
req = protos.FileRequest(path=fpath)
r = self.stub.fileStat(req)
return r
class LockStub(BaseServiceStub):
def acquire_lock(self, leaseTime=60):
"""
获取用于控制设备的锁,成功返回 true,被占用则会引发异常提示
"""
req = protos.Integer(value=leaseTime)
r = self.stub.acquireLock(req)
return r.value
def get_session_token(self):
"""
获取当前会话的动态令牌
"""
r = self.stub.getSessionToken(protos.Empty())
return r.value
def refresh_lock(self, leaseTime=60):
"""
刷新用于控制设备的锁,应该在定时任务每60s内调用以保持会话
"""
req = protos.Integer(value=leaseTime)
r = self.stub.refreshLock(req)
return r.value
def release_lock(self):
"""
释放控制设备的锁,释放后该设备可被其他客户端控制
"""
r = self.stub.releaseLock(protos.Empty())
return r.value
class WifiStub(BaseServiceStub):
def status(self):
"""
获取当前已连接 WIFI 的信息
"""
r = self.stub.status(protos.Empty())
return r
def blacklist_add(self, bssid):
"""
将 BSSID 加入 WIFI BSSID 黑名单(将不会在WIFI列表显示)
"""
r = self.stub.blacklistAdd(protos.String(value=bssid))
return r.value
def blacklist_clear(self):
"""
清空 WIFI BSSID 黑名单
"""
r = self.stub.blacklistClear(protos.Empty())
return r.value
def blacklist_get_all(self):
"""
获取在 WIFI BSSID 黑名单中的所有 BSSID
"""
r = self.stub.blacklistAll(protos.Empty())
return r.bssids
def scan(self):
"""
请求扫描附近 WIFI
"""
r = self.stub.scan(protos.Empty())
return r.value
def scan_results(self):
"""
获取已扫描到的附近 WIFI
"""
r = self.stub.scanResults(protos.Empty())
return r.stations
def get_mac_addr(self):
"""
获取当前 WIFI 的 MAC 地址
"""
r = self.stub.getMacAddr(protos.Empty())
return r.value
def signal_poll(self):
"""
获取当前已连接 WIFI 的信号强度等信息
"""
r = self.stub.signalPoll(protos.Empty())
return r
def list_networks(self):
"""
列出已连接过的 WIFI 网络
"""
r = self.stub.listNetworks(protos.Empty())
return r.networks
def select_network(self, networkId):
raise NotImplementedError
def enable_network(self, networkId):
raise NotImplementedError
def disable_network(self, networkId):
raise NotImplementedError
def add_network(self):
raise NotImplementedError
def remove_network(self, networkId):
raise NotImplementedError
def set_network_config(self, networkId, name, value):
raise NotImplementedError
def get_network_config(self, networkId, name):
raise NotImplementedError
def disconnect(self):
"""
断开 WIFI 连接
"""
r = self.stub.disconnect(protos.Empty())
return r.value
def reconnect(self):
"""
重连 WIFI
"""
r = self.stub.reconnect(protos.Empty())
return r.value
def set_config(self, name, value):
raise NotImplementedError
def set_auto_connect(self, auto=True):
raise NotImplementedError
def save_config(self):
raise NotImplementedError
class OcrOperator(object):
def __init__(self, device, elements=None,
**kwargs):
self.elements = elements
self.index = kwargs.pop("index", 0)
self.func, self.rule = kwargs.popitem()
self.match = getattr(self, self.func)
self.device = device
def text(self, item):
return self.rule == item["text"]
def textMatches(self, item):
return bool(re.match(self.rule, item["text"],
re.DOTALL))
def textContains(self, item):
return self.rule in item["text"]
def find_target_item(self):
m = [e for e in self.elements \
if self.match(e)]
o = (m and len(m) > self.index) != True
return None if o else m[self.index]
def find_item_or_throw(self):
item = self.find_target_item()
msg = "OcrSelector[{}={}]".format(self.func, self.rule)
item or self.throw(UiObjectNotFoundException, msg)
return item
def find_cb(self, func, ret, *args):
item = self.find_target_item()
return func(item, *args) if item else ret
def find_or_throw_cb(self, func, *args):
item = self.find_item_or_throw()
return func(item, *args)
def throw(self, exception, *args):
raise exception(*args)
def _screenshot(self, item, quality):
return self.device.screenshot(quality,
bound=item["bound"])
def _click(self, item):
point = item["bound"].center()
return self.device.click(point)
def __str__(self):
return "Ocr: {}={}".format(self.func, self.rule)
__repr__ = __str__
def exists(self):
"""
OCR - 检查元素是否存在
"""
return bool(self.find_target_item())
def click(self):
"""
OCR - 点击元素(不存在则报错)
"""
return self.find_or_throw_cb(self._click)
def click_exists(self):
"""
OCR - 点击元素(不存在将不会产生异常)
"""
return self.find_cb(self._click, False)
def screenshot(self, quality=100):
"""
OCR - 对元素进行截图
"""
return self.find_or_throw_cb(self._screenshot,
quality)
def take_screenshot(self, quality=100):
"""
OCR - 对元素进行截图
"""
return self.screenshot(quality)
def info(self):
"""
OCR - 获取匹配元素的信息
"""
item = self.find_item_or_throw()
return item
class OcrEngine(object):
def __init__(self, service, *args,
**kwargs):
args = list(args)
if type(service) == type:
args.insert(0, service)
service = "custom"
func = getattr(self, "init_{}".format(service))
func(*args, **kwargs)
def init_paddleocr(self, *args, **kwargs):
from paddleocr import PaddleOCR
self._service = PaddleOCR(*args, **kwargs)
self._ocr = self.ocr_paddleocr
def init_easyocr(self, *args, **kwargs):
from easyocr import Reader
self._service = Reader(*args, **kwargs)
self._ocr = self.ocr_easyocr
def init_custom(self, service, *args, **kwargs):
self._service = service(*args, **kwargs)
self._ocr = self.ocr_custom
def ocr_custom(self, image):
result = self._service.ocr(image)
return result
def ocr_paddleocr(self, image):
r = self._service.ocr(image)
n = bool(r and r[0] and type(r[0][-1])==float)
result = (r if n else r[0]) or []
output = [[n[0], n[1][0], n[1][1]] for n in result]
return output
def ocr_easyocr(self, image):
result = self._service.readtext(image)
return result
def ocr(self, screenshot):
img = screenshot.getvalue()
result = self._ocr(img) or []
output = [self.format(*n) for n in result]
return output
def format(self, box, text, confidence):
bound = Bound()
bound.left = int(min(p[0] for p in box))
bound.top = int(min(p[1] for p in box))
bound.bottom = int(max(p[1] for p in box))
bound.right = int(max(p[0] for p in box))
info = dict(text=text, confidence=confidence,
bound=bound)
return info
class Device(object):
def __init__(self, host, port=65000,
certificate=None,
session=None):
self.certificate = certificate
self.server = "{0}:{1}".format(host, port)
policy = dict()
policy["maxAttempts"] = 5
policy["retryableStatusCodes"] = ["UNAVAILABLE"]
policy["backoffMultiplier"] = 2
policy["initialBackoff"] = "0.5s"
policy["maxBackoff"] = "15s"
config = json.dumps(dict(methodConfig=[{"name": [{}],
"retryPolicy": policy,}]))
option = dict()
option["grpc.max_send_message_length"] = 64*1024*1024
option["grpc.max_receive_message_length"] = 128*1024*1024
option["grpc.keepalive_time_ms"] = 30*1000
option["grpc.keepalive_timeout_ms"] = 15*1000
option["grpc.keepalive_permit_without_calls"] = True
option["grpc.max_pings_without_data"] = 0
option["grpc.service_config"] = config
option["grpc.enable_http_proxy"] = 0
if certificate is not None:
with open(certificate, "rb") as fd:
key, crt, ca = self._parse_certdata(fd.read())
creds = grpc.ssl_channel_credentials(root_certificates=ca,
certificate_chain=crt,
private_key=key)
self._chan = grpc.secure_channel(self.server, creds,
options=(("grpc.ssl_target_name_override",
self._parse_cname(crt)),
*tuple(option.items()),))
else:
self._chan = grpc.insecure_channel(self.server,
options=(*tuple(option.items()),)
)
session = session or uuid.uuid4().hex
interceptors = [ClientSessionMetadataInterceptor(session),
GrpcRemoteExceptionInterceptor(),
ClientLoggingInterceptor()]
self._ocr = None
self._ocr_img_quality = 75
self.channel = grpc.intercept_channel(self._chan,
*interceptors)
self.session = session
@property
def frida(self):
if _frida_dma is None:
raise ModuleNotFoundError("frida")
try:
device = _frida_dma.get_device_matching(
lambda d: d.name==self.server)
# make a call to check server connectivity
device.query_system_parameters()
return device
except:
""" No-op """
kwargs = {}
if self.certificate is not None:
kwargs["certificate"] = self.certificate
if self._get_session_token():
kwargs["token"] = self._get_session_token()
try:
_frida_dma.remove_remote_device(self.server)
except frida.InvalidArgumentError:
""" No-op """
device = _frida_dma.add_remote_device(self.server,
**kwargs)
return device
def __str__(self):
return "Device@{}".format(self.server)
__repr__ = __str__
def _parse_certdata(self, data):
key, crt, ca = Pem.parse(data)
ca = ca.as_bytes()
crt = crt.as_bytes()
key = key.as_bytes()
return key, crt, ca
def _parse_cname(self, crt):
_, _, der = pem.unarmor(crt)
subject = x509.Certificate.load(der).subject
return subject.native["common_name"]
def _get_service_stub(self, module):
stub = getattr(services, "{0}Stub".format(module))
return stub(self.channel)
def stub(self, module):
modu = sys.modules[__name__]
stub = self._get_service_stub(module)
wrap = getattr(modu, "{0}Stub".format(module))
inst = getattr(self, module, wrap(stub))
self.__setattr__(module, inst)
return inst
# 快速调用: File
def download_fd(self, fpath, fd):
return self.stub("File").download_fd(fpath, fd)
def upload_fd(self, fd, dest):
return self.stub("File").upload_fd(fd, dest)
def download_file(self, fpath, dest):
return self.stub("File").download_file(fpath, dest)
def upload_file(self, fpath, dest):
return self.stub("File").upload_file(fpath, dest)
def delete_file(self, fpath):
return self.stub("File").delete_file(fpath)
def file_chmod(self, fpath, mode=0o644):
return self.stub("File").file_chmod(fpath, mode=mode)
def file_stat(self, fpath):
return self.stub("File").file_stat(fpath)
# 快速调用: Application
def install_local_file(self, rpath, user=0):
return self.stub("Application").install_local_file(rpath, user=user)
def current_application(self):
return self.stub("Application").current_application()
def enumerate_all_pkg_names(self):
return self.stub("Application").enumerate_all_pkg_names()
def enumerate_running_processes(self):
return self.stub("Application").enumerate_running_processes()
def get_last_activities(self, count=3):
return self.stub("Application").get_last_activities(count=count)
def start_activity(self, **activity):
return self.stub("Application").start_activity(**activity)
def get_application_by_name(self, name):
return self.stub("Application").get_application_by_name(name)
def application(self, applicationId, user=0):
return self.stub("Application")(applicationId, user=user)
# 快速调用: Util
def record_touch(self):
return self.stub("Util").record_touch()
def perform_touch(self, sequence, wait=True):
return self.stub("Util").perform_touch(sequence, wait=wait)
def show_toast(self, text, duration=ToastDuration.TD_SHORT):
return self.stub("Util").show_toast(text, duration=duration)
def is_ca_certificate_installed(self, certdata):
return self.stub("Util").is_ca_certificate_installed(certdata)
def uninstall_ca_certificate(self, certfile):
return self.stub("Util").uninstall_ca_certificate(certfile)
def install_ca_certificate(self, certfile):
return self.stub("Util").install_ca_certificate(certfile)
def reboot(self):
return self.stub("Util").reboot()
def shutdown(self):
return self.stub("Util").shutdown()
def exit(self):
return self.stub("Util").exit()
def reload(self, clean=False):
return self.stub("Util").reload(clean)
def beep(self):
return self.stub("Util").beep()
def play_audio(self, file, type=AudioStreamType.AST_SYSTEM,
loop=1, interval=0):
return self.stub("Util").play_audio(file, type=type, loop=loop,
interval=interval)
def setprop(self, name, value):
return self.stub("Util").setprop(name, value)
def getprop(self, name):
return self.stub("Util").getprop(name)
def hex_patch(self, pattern, replacement, path,
maxreplace=-1, dryrun=False):
return self.stub("Util").hex_patch(pattern, replacement, path,
maxreplace=maxreplace,
dryrun=dryrun)
# 快速调用: Debug
def install_adb_pubkey(self, pubkey):
return self.stub("Debug").install_adb_pubkey(pubkey)
def uninstall_adb_pubkey(self, pubkey):
return self.stub("Debug").uninstall_adb_pubkey(pubkey)
def start_android_debug_bridge(self):
return self.stub("Debug").start_android_debug_bridge()
def is_android_debug_bridge_running(self):
return self.stub("Debug").is_android_debug_bridge_running()
def stop_android_debug_bridge(self):
return self.stub("Debug").stop_android_debug_bridge()
# 快速调用: Proxy
def is_openvpn_running(self):
return self.stub("Proxy").is_openvpn_running()
def is_gproxy_running(self):
return self.stub("Proxy").is_gproxy_running()
def start_openvpn(self, profile):
return self.stub("Proxy").start_openvpn(profile)
def start_gproxy(self, profile):
return self.stub("Proxy").start_gproxy(profile)
def stop_openvpn(self):
return self.stub("Proxy").stop_openvpn()
def stop_gproxy(self):
return self.stub("Proxy").stop_gproxy()
# 快速调用: Shell
def execute_script(self, script, alias=None, timeout=60):
return self.stub("Shell").execute_script(script, alias=alias,
timeout=timeout)
def execute_background_script(self, script, alias=None):
return self.stub("Shell").execute_background_script(script, alias=alias)
def is_background_script_finished(self, tid):
return self.stub("Shell").is_background_script_finished(tid)
def kill_background_script(self, tid):
return self.stub("Shell").kill_background_script(tid)
# 快速调用: UiAutomator
def click(self, point):
return self.stub("UiAutomator").click(point)
def drag(self, A, B, step=32):
return self.stub("UiAutomator").drag(A, B, step=step)
def swipe(self, A, B, step=32):
return self.stub("UiAutomator").swipe(A, B, step=step)
def swipe_points(self, *points, step=32):
return self.stub("UiAutomator").swipe_points(*points, step=step)
def open_notification(self):
return self.stub("UiAutomator").open_notification()
def open_quick_settings(self):
return self.stub("UiAutomator").open_quick_settings()
def wake_up(self):
return self.stub("UiAutomator").wake_up()
def sleep(self):
return self.stub("UiAutomator").sleep()
def is_screen_on(self):
return self.stub("UiAutomator").is_screen_on()
def is_screen_locked(self):
return self.stub("UiAutomator").is_screen_locked()
def set_clipboard(self, text):
return self.stub("UiAutomator").set_clipboard(text)
def get_clipboard(self):
return self.stub("UiAutomator").get_clipboard()
def freeze_rotation(self, freeze=True):
return self.stub("UiAutomator").freeze_rotation(freeze=freeze)
def set_orientation(self, orien=Orientation.ORIEN_NATURE):
return self.stub("UiAutomator").set_orientation(orien)
def press_key(self, key):
return self.stub("UiAutomator").press_key(key)
def press_keycode(self, code, meta=0):
return self.stub("UiAutomator").press_keycode(code, meta)
def take_screenshot(self, quality=100, bound=None):
return self.stub("UiAutomator").take_screenshot(quality, bound=bound)
def screenshot(self, quality=100, bound=None):
return self.stub("UiAutomator").screenshot(quality, bound=bound)
def dump_window_hierarchy(self, compressed=False):
return self.stub("UiAutomator").dump_window_hierarchy(compressed=compressed)
def wait_for_idle(self, timeout):
return self.stub("UiAutomator").wait_for_idle(timeout)
def get_last_toast(self):
return self.stub("UiAutomator").get_last_toast()
def find_similar_image(self, data, threshold=0.0, distance=250,
scale=1.0, area=FindImageArea.FIA_WHOLE_SCREEN,
method=FindImageMethod.FIM_TEMPLATE):
return self.stub("UiAutomator").find_similar_image(data, threshold=threshold,
distance=distance, scale=scale,
area=area, method=method)
# watcher
def remove_all_watchers(self):
return self.stub("UiAutomator").remove_all_watchers()
def set_watcher_loop_enabled(self, enabled):
return self.stub("UiAutomator").set_watcher_loop_enabled(enabled)
def get_watcher_loop_enabled(self):
return self.stub("UiAutomator").get_watcher_loop_enabled()
def get_watcher_triggered_count(self, name):
return self.stub("UiAutomator").get_watcher_triggered_count(name)
def reset_watcher_triggered_count(self, name):
return self.stub("UiAutomator").reset_watcher_triggered_count(name)
def get_applied_watchers(self):
return self.stub("UiAutomator").get_applied_watchers()
def register_click_target_selector_watcher(self, name, conditions,
target):
return self.stub("UiAutomator").register_click_target_selector_watcher(
name, conditions, target
)
def register_press_key_watcher(self, name, conditions, key):
return self.stub("UiAutomator").register_press_key_watcher(
name, conditions, key
)
def register_none_op_watcher(self, name, conditions):
return self.stub("UiAutomator").register_none_op_watcher(
name, conditions
)
def set_watcher_enabled(self, name, enable):
return self.stub("UiAutomator").set_watcher_enabled(name, enable)
def get_watcher_enabled(self, name):
return self.stub("UiAutomator").get_watcher_enabled(name)
def remove_watcher(self, name):
return self.stub("UiAutomator").remove_watcher(name)
def device_info(self):
return self.stub("UiAutomator").device_info()
def server_info(self):
return self.stub("Util").server_info()
def __call__(self, **kwargs):
return self.stub("UiAutomator")(**kwargs)
# OCR 功能扩展
def ocr(self, index=0, **kwargs):
if not isinstance(self._ocr, OcrEngine):
raise IllegalStateException("Ocr engine is not setted up")
if any(r not in ["text", "textContains", "textMatches"] \
for r in kwargs.keys()):
raise InvalidArgumentError("Only text* matches are supported")
if len(kwargs) != 1:
raise InvalidArgumentError("Only or at least one rule can be used")
image = self.screenshot(self._ocr_img_quality)
return OcrOperator(self,
elements=self._ocr.ocr(image),
index=index,
**kwargs
)
def setup_ocr_backend(self, service, *args, quality=75,
**kwargs):
self._ocr_img_quality = quality
self._ocr = OcrEngine(service, *args,
**kwargs)
# 日志打印
def set_debug_log_enabled(self, enable):
level = logging.DEBUG if enable else logging.WARN
logger.setLevel(level)
return enable
# 接口锁定
def _get_session_token(self):
return self.stub("Lock").get_session_token()
def _acquire_lock(self, leaseTime=60):
return self.stub("Lock").acquire_lock(leaseTime)
def _refresh_lock(self, leaseTime=60):
return self.stub("Lock").refresh_lock(leaseTime)
def _release_lock(self):
return self.stub("Lock").release_lock()
def __enter__(self):
self._acquire_lock(leaseTime=sys.maxsize)
return self
def __exit__(self, type, value, traceback):
self._release_lock()
if __name__ == "__main__":
import code
import readline
import rlcompleter
import argparse
parser = argparse.ArgumentParser()
crt = os.environ.get("CERTIFICATE", None)
port = int(os.environ.get("PORT", 65000))
parser.add_argument("-device", type=str, default="127.0.0.1",
help="service ip address")
parser.add_argument("-port", type=int, default=port,
help="service port")
parser.add_argument("-cert", type=str, default=crt,
help="ssl cert")
args = parser.parse_args()
readline.parse_and_bind("tab: complete")
d = Device(args.device, port=args.port,
certificate=args.cert)
code.interact(local=globals())
================================================
FILE: lamda/const.py
================================================
# Copyright 2022 rev1si0n (https://github.com/rev1si0n). All rights reserved.
#
# Distributed under MIT license.
# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
# Android runtime permissions
PERMISSION_READ_SMS = "android.permission.READ_SMS"
PERMISSION_READ_CALENDAR = "android.permission.READ_CALENDAR"
PERMISSION_READ_CALL_LOG = "android.permission.READ_CALL_LOG"
PERMISSION_ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION"
PERMISSION_ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS"
PERMISSION_RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH"
PERMISSION_BODY_SENSORS = "android.permission.BODY_SENSORS"
PERMISSION_READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS"
PERMISSION_RECEIVE_MMS = "android.permission.RECEIVE_MMS"
PERMISSION_RECEIVE_SMS = "android.permission.RECEIVE_SMS"
PERMISSION_READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE"
PERMISSION_ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"
PERMISSION_READ_PHONE_STATE = "android.permission.READ_PHONE_STATE"
PERMISSION_SEND_SMS = "android.permission.SEND_SMS"
PERMISSION_CALL_PHONE = "android.permission.CALL_PHONE"
PERMISSION_WRITE_CONTACTS = "android.permission.WRITE_CONTACTS"
PERMISSION_ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER"
PERMISSION_CAMERA = "android.permission.CAMERA"
PERMISSION_WRITE_CALENDAR = "android.permission.WRITE_CALENDAR"
PERMISSION_WRITE_CALL_LOG = "android.permission.WRITE_CALL_LOG"
PERMISSION_USE_SIP = "android.permission.USE_SIP"
PERMISSION_PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS"
PERMISSION_READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS"
PERMISSION_GET_ACCOUNTS = "android.permission.GET_ACCOUNTS"
PERMISSION_WRITE_EXTERNAL_STORAGE = "android.permission.WRITE_EXTERNAL_STORAGE"
PERMISSION_ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION"
PERMISSION_RECORD_AUDIO = "android.permission.RECORD_AUDIO"
PERMISSION_READ_CONTACTS = "android.permission.READ_CONTACTS"
PERMISSION_ACCESS_BACKGROUND_LOCATION = "android.permission.ACCESS_BACKGROUND_LOCATION"
PERMISSION_ACCESS_MEDIA_LOCATION = "android.permission.ACCESS_MEDIA_LOCATION"
# Android activity flags
FLAG_ACTIVITY_BROUGHT_TO_FRONT = 0x00400000
FLAG_ACTIVITY_CLEAR_TASK = 0x00008000
FLAG_ACTIVITY_CLEAR_TOP = 0x04000000
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS = 0x00800000
FLAG_ACTIVITY_FORWARD_RESULT = 0x02000000
FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY = 0x00100000
FLAG_ACTIVITY_LAUNCH_ADJACENT = 0x00001000
FLAG_ACTIVITY_MATCH_EXTERNAL = 0x00000800
FLAG_ACTIVITY_MULTIPLE_TASK = 0x08000000
FLAG_ACTIVITY_NEW_DOCUMENT = 0x00080000
FLAG_ACTIVITY_NEW_TASK = 0x10000000
FLAG_ACTIVITY_NO_ANIMATION = 0x00010000
FLAG_ACTIVITY_NO_HISTORY = 0x40000000
FLAG_ACTIVITY_NO_USER_ACTION = 0x00040000
FLAG_ACTIVITY_PREVIOUS_IS_TOP = 0x01000000
FLAG_ACTIVITY_REORDER_TO_FRONT = 0x00020000
FLAG_ACTIVITY_REQUIRE_DEFAULT = 0x00000200
FLAG_ACTIVITY_REQUIRE_NON_BROWSER = 0x00000400
FLAG_ACTIVITY_RESET_TASK_IF_NEEDED = 0x00200000
FLAG_ACTIVITY_RETAIN_IN_RECENTS = 0x00002000
FLAG_ACTIVITY_SINGLE_TOP = 0x20000000
FLAG_ACTIVITY_TASK_ON_HOME = 0x00004000
================================================
FILE: lamda/exceptions.py
================================================
# Copyright 2022 rev1si0n (https://github.com/rev1si0n). All rights reserved.
#
# Distributed under MIT license.
# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
class CompatibilityException(Exception):
""" Exception """
class DeadSystemException(Exception):
""" Exception """
class DeviceUnavailable(Exception):
""" Exception """
class DuplicateEntryError(Exception):
""" Exception """
class IllegalArgumentException(Exception):
""" Exception """
class IllegalStateException(Exception):
""" Exception """
class InstallPackageFailed(Exception):
""" Exception """
class InternalRpcException(Exception):
""" Exception """
class InvalidAndroidPackage(Exception):
""" Exception """
class InvalidArgumentError(Exception):
""" Exception """
class InvalidOperationError(Exception):
""" Exception """
class InvalidRootCertificate(Exception):
""" Exception """
class MethodNotFoundException(Exception):
""" Exception """
class NameNotFoundException(Exception):
""" Exception """
class NotImplementedException(Exception):
""" Exception """
class NullPointerException(Exception):
""" Exception """
class SecurityException(Exception):
""" Exception """
class ServiceUnavailable(Exception):
""" Exception """
class StaleObjectException(Exception):
""" Exception """
class StartupActivityNotFound(Exception):
""" Exception """
class StorageOutOfMemory(Exception):
""" Exception """
class UiAutomatorException(Exception):
""" Exception """
class UiObjectNotFoundException(Exception):
""" Exception """
class UnHandledException(Exception):
""" Exception """
================================================
FILE: lamda/google/protobuf/any.proto
================================================
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
package google.protobuf;
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
option go_package = "google.golang.org/protobuf/types/known/anypb";
option java_package = "com.google.protobuf";
option java_outer_classname = "AnyProto";
option java_multiple_files = true;
option objc_class_prefix = "GPB";
// `Any` contains an arbitrary serialized protocol buffer message along with a
// URL that describes the type of the serialized message.
//
// Protobuf library provides support to pack/unpack Any values in the form
// of utility functions or additional generated methods of the Any type.
//
// Example 1: Pack and unpack a message in C++.
//
// Foo foo = ...;
// Any any;
// any.PackFrom(foo);
// ...
// if (any.UnpackTo(&foo)) {
// ...
// }
//
// Example 2: Pack and unpack a message in Java.
//
// Foo foo = ...;
// Any any = Any.pack(foo);
// ...
// if (any.is(Foo.class)) {
// foo = any.unpack(Foo.class);
// }
//
// Example 3: Pack and unpack a message in Python.
//
// foo = Foo(...)
// any = Any()
// any.Pack(foo)
// ...
// if any.Is(Foo.DESCRIPTOR):
// any.Unpack(foo)
// ...
//
// Example 4: Pack and unpack a message in Go
//
// foo := &pb.Foo{...}
// any, err := anypb.New(foo)
// if err != nil {
// ...
// }
// ...
// foo := &pb.Foo{}
// if err := any.UnmarshalTo(foo); err != nil {
// ...
// }
//
// The pack methods provided by protobuf library will by default use
// 'type.googleapis.com/full.type.name' as the type URL and the unpack
// methods only use the fully qualified type name after the last '/'
// in the type URL, for example "foo.bar.com/x/y.z" will yield type
// name "y.z".
//
//
// JSON
// ====
// The JSON representation of an `Any` value uses the regular
// representation of the deserialized, embedded message, with an
// additional field `@type` which contains the type URL. Example:
//
// package google.profile;
// message Person {
// string first_name = 1;
// string last_name = 2;
// }
//
// {
// "@type": "type.googleapis.com/google.profile.Person",
// "firstName": ,
// "lastName":
// }
//
// If the embedded message type is well-known and has a custom JSON
// representation, that representation will be embedded adding a field
// `value` which holds the custom JSON in addition to the `@type`
// field. Example (for message [google.protobuf.Duration][]):
//
// {
// "@type": "type.googleapis.com/google.protobuf.Duration",
// "value": "1.212s"
// }
//
message Any {
// A URL/resource name that uniquely identifies the type of the serialized
// protocol buffer message. This string must contain at least
// one "/" character. The last segment of the URL's path must represent
// the fully qualified name of the type (as in
// `path/google.protobuf.Duration`). The name should be in a canonical form
// (e.g., leading "." is not accepted).
//
// In practice, teams usually precompile into the binary all types that they
// expect it to use in the context of Any. However, for URLs which use the
// scheme `http`, `https`, or no scheme, one can optionally set up a type
// server that maps type URLs to message definitions as follows:
//
// * If no scheme is provided, `https` is assumed.
// * An HTTP GET on the URL must yield a [google.protobuf.Type][]
// value in binary format, or produce an error.
// * Applications are allowed to cache lookup results based on the
// URL, or have them precompiled into a binary to avoid any
// lookup. Therefore, binary compatibility needs to be preserved
// on changes to types. (Use versioned type names to manage
// breaking changes.)
//
// Note: this functionality is not currently available in the official
// protobuf release, and it is not used for type URLs beginning with
// type.googleapis.com.
//
// Schemes other than `http`, `https` (or the empty scheme) might be
// used with implementation specific semantics.
//
string type_url = 1;
// Must be a valid serialized protocol buffer of the above specified type.
bytes value = 2;
}
================================================
FILE: lamda/google/protobuf/api.proto
================================================
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
package google.protobuf;
import "google/protobuf/source_context.proto";
import "google/protobuf/type.proto";
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
option java_package = "com.google.protobuf";
option java_outer_classname = "ApiProto";
option java_multiple_files = true;
option objc_class_prefix = "GPB";
option go_package = "google.golang.org/protobuf/types/known/apipb";
// Api is a light-weight descriptor for an API Interface.
//
// Interfaces are also described as "protocol buffer services" in some contexts,
// such as by the "service" keyword in a .proto file, but they are different
// from API Services, which represent a concrete implementation of an interface
// as opposed to simply a description of methods and bindings. They are also
// sometimes simply referred to as "APIs" in other contexts, such as the name of
// this message itself. See https://cloud.google.com/apis/design/glossary for
// detailed terminology.
message Api {
// The fully qualified name of this interface, including package name
// followed by the interface's simple name.
string name = 1;
// The methods of this interface, in unspecified order.
repeated Method methods = 2;
// Any metadata attached to the interface.
repeated Option options = 3;
// A version string for this interface. If specified, must have the form
// `major-version.minor-version`, as in `1.10`. If the minor version is
// omitted, it defaults to zero. If the entire version field is empty, the
// major version is derived from the package name, as outlined below. If the
// field is not empty, the version in the package name will be verified to be
// consistent with what is provided here.
//
// The versioning schema uses [semantic
// versioning](http://semver.org) where the major version number
// indicates a breaking change and the minor version an additive,
// non-breaking change. Both version numbers are signals to users
// what to expect from different versions, and should be carefully
// chosen based on the product plan.
//
// The major version is also reflected in the package name of the
// interface, which must end in `v`, as in
// `google.feature.v1`. For major versions 0 and 1, the suffix can
// be omitted. Zero major versions must only be used for
// experimental, non-GA interfaces.
//
//
string version = 4;
// Source context for the protocol buffer service represented by this
// message.
SourceContext source_context = 5;
// Included interfaces. See [Mixin][].
repeated Mixin mixins = 6;
// The source syntax of the service.
Syntax syntax = 7;
}
// Method represents a method of an API interface.
message Method {
// The simple name of this method.
string name = 1;
// A URL of the input message type.
string request_type_url = 2;
// If true, the request is streamed.
bool request_streaming = 3;
// The URL of the output message type.
string response_type_url = 4;
// If true, the response is streamed.
bool response_streaming = 5;
// Any metadata attached to the method.
repeated Option options = 6;
// The source syntax of this method.
Syntax syntax = 7;
}
// Declares an API Interface to be included in this interface. The including
// interface must redeclare all the methods from the included interface, but
// documentation and options are inherited as follows:
//
// - If after comment and whitespace stripping, the documentation
// string of the redeclared method is empty, it will be inherited
// from the original method.
//
// - Each annotation belonging to the service config (http,
// visibility) which is not set in the redeclared method will be
// inherited.
//
// - If an http annotation is inherited, the path pattern will be
// modified as follows. Any version prefix will be replaced by the
// version of the including interface plus the [root][] path if
// specified.
//
// Example of a simple mixin:
//
// package google.acl.v1;
// service AccessControl {
// // Get the underlying ACL object.
// rpc GetAcl(GetAclRequest) returns (Acl) {
// option (google.api.http).get = "/v1/{resource=**}:getAcl";
// }
// }
//
// package google.storage.v2;
// service Storage {
// rpc GetAcl(GetAclRequest) returns (Acl);
//
// // Get a data record.
// rpc GetData(GetDataRequest) returns (Data) {
// option (google.api.http).get = "/v2/{resource=**}";
// }
// }
//
// Example of a mixin configuration:
//
// apis:
// - name: google.storage.v2.Storage
// mixins:
// - name: google.acl.v1.AccessControl
//
// The mixin construct implies that all methods in `AccessControl` are
// also declared with same name and request/response types in
// `Storage`. A documentation generator or annotation processor will
// see the effective `Storage.GetAcl` method after inheriting
// documentation and annotations as follows:
//
// service Storage {
// // Get the underlying ACL object.
// rpc GetAcl(GetAclRequest) returns (Acl) {
// option (google.api.http).get = "/v2/{resource=**}:getAcl";
// }
// ...
// }
//
// Note how the version in the path pattern changed from `v1` to `v2`.
//
// If the `root` field in the mixin is specified, it should be a
// relative path under which inherited HTTP paths are placed. Example:
//
// apis:
// - name: google.storage.v2.Storage
// mixins:
// - name: google.acl.v1.AccessControl
// root: acls
//
// This implies the following inherited HTTP annotation:
//
// service Storage {
// // Get the underlying ACL object.
// rpc GetAcl(GetAclRequest) returns (Acl) {
// option (google.api.http).get = "/v2/acls/{resource=**}:getAcl";
// }
// ...
// }
message Mixin {
// The fully qualified name of the interface which is included.
string name = 1;
// If non-empty specifies a path under which inherited HTTP paths
// are rooted.
string root = 2;
}
================================================
FILE: lamda/google/protobuf/compiler/plugin.proto
================================================
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Author: kenton@google.com (Kenton Varda)
//
// WARNING: The plugin interface is currently EXPERIMENTAL and is subject to
// change.
//
// protoc (aka the Protocol Compiler) can be extended via plugins. A plugin is
// just a program that reads a CodeGeneratorRequest from stdin and writes a
// CodeGeneratorResponse to stdout.
//
// Plugins written using C++ can use google/protobuf/compiler/plugin.h instead
// of dealing with the raw protocol defined here.
//
// A plugin executable needs only to be placed somewhere in the path. The
// plugin should be named "protoc-gen-$NAME", and will then be used when the
// flag "--${NAME}_out" is passed to protoc.
syntax = "proto2";
package google.protobuf.compiler;
option java_package = "com.google.protobuf.compiler";
option java_outer_classname = "PluginProtos";
option go_package = "google.golang.org/protobuf/types/pluginpb";
import "google/protobuf/descriptor.proto";
// The version number of protocol compiler.
message Version {
optional int32 major = 1;
optional int32 minor = 2;
optional int32 patch = 3;
// A suffix for alpha, beta or rc release, e.g., "alpha-1", "rc2". It should
// be empty for mainline stable releases.
optional string suffix = 4;
}
// An encoded CodeGeneratorRequest is written to the plugin's stdin.
message CodeGeneratorRequest {
// The .proto files that were explicitly listed on the command-line. The
// code generator should generate code only for these files. Each file's
// descriptor will be included in proto_file, below.
repeated string file_to_generate = 1;
// The generator parameter passed on the command-line.
optional string parameter = 2;
// FileDescriptorProtos for all files in files_to_generate and everything
// they import. The files will appear in topological order, so each file
// appears before any file that imports it.
//
// protoc guarantees that all proto_files will be written after
// the fields above, even though this is not technically guaranteed by the
// protobuf wire format. This theoretically could allow a plugin to stream
// in the FileDescriptorProtos and handle them one by one rather than read
// the entire set into memory at once. However, as of this writing, this
// is not similarly optimized on protoc's end -- it will store all fields in
// memory at once before sending them to the plugin.
//
// Type names of fields and extensions in the FileDescriptorProto are always
// fully qualified.
repeated FileDescriptorProto proto_file = 15;
// The version number of protocol compiler.
optional Version compiler_version = 3;
}
// The plugin writes an encoded CodeGeneratorResponse to stdout.
message CodeGeneratorResponse {
// Error message. If non-empty, code generation failed. The plugin process
// should exit with status code zero even if it reports an error in this way.
//
// This should be used to indicate errors in .proto files which prevent the
// code generator from generating correct code. Errors which indicate a
// problem in protoc itself -- such as the input CodeGeneratorRequest being
// unparseable -- should be reported by writing a message to stderr and
// exiting with a non-zero status code.
optional string error = 1;
// A bitmask of supported features that the code generator supports.
// This is a bitwise "or" of values from the Feature enum.
optional uint64 supported_features = 2;
// Sync with code_generator.h.
enum Feature {
FEATURE_NONE = 0;
FEATURE_PROTO3_OPTIONAL = 1;
}
// Represents a single generated file.
message File {
// The file name, relative to the output directory. The name must not
// contain "." or ".." components and must be relative, not be absolute (so,
// the file cannot lie outside the output directory). "/" must be used as
// the path separator, not "\".
//
// If the name is omitted, the content will be appended to the previous
// file. This allows the generator to break large files into small chunks,
// and allows the generated text to be streamed back to protoc so that large
// files need not reside completely in memory at one time. Note that as of
// this writing protoc does not optimize for this -- it will read the entire
// CodeGeneratorResponse before writing files to disk.
optional string name = 1;
// If non-empty, indicates that the named file should already exist, and the
// content here is to be inserted into that file at a defined insertion
// point. This feature allows a code generator to extend the output
// produced by another code generator. The original generator may provide
// insertion points by placing special annotations in the file that look
// like:
// @@protoc_insertion_point(NAME)
// The annotation can have arbitrary text before and after it on the line,
// which allows it to be placed in a comment. NAME should be replaced with
// an identifier naming the point -- this is what other generators will use
// as the insertion_point. Code inserted at this point will be placed
// immediately above the line containing the insertion point (thus multiple
// insertions to the same point will come out in the order they were added).
// The double-@ is intended to make it unlikely that the generated code
// could contain things that look like insertion points by accident.
//
// For example, the C++ code generator places the following line in the
// .pb.h files that it generates:
// // @@protoc_insertion_point(namespace_scope)
// This line appears within the scope of the file's package namespace, but
// outside of any particular class. Another plugin can then specify the
// insertion_point "namespace_scope" to generate additional classes or
// other declarations that should be placed in this scope.
//
// Note that if the line containing the insertion point begins with
// whitespace, the same whitespace will be added to every line of the
// inserted text. This is useful for languages like Python, where
// indentation matters. In these languages, the insertion point comment
// should be indented the same amount as any inserted code will need to be
// in order to work correctly in that context.
//
// The code generator that generates the initial file and the one which
// inserts into it must both run as part of a single invocation of protoc.
// Code generators are executed in the order in which they appear on the
// command line.
//
// If |insertion_point| is present, |name| must also be present.
optional string insertion_point = 2;
// The file contents.
optional string content = 15;
// Information describing the file content being inserted. If an insertion
// point is used, this information will be appropriately offset and inserted
// into the code generation metadata for the generated files.
optional GeneratedCodeInfo generated_code_info = 16;
}
repeated File file = 15;
}
================================================
FILE: lamda/google/protobuf/descriptor.proto
================================================
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Author: kenton@google.com (Kenton Varda)
// Based on original Protocol Buffers design by
// Sanjay Ghemawat, Jeff Dean, and others.
//
// The messages in this file describe the definitions found in .proto files.
// A valid .proto file can be translated directly to a FileDescriptorProto
// without any other information (e.g. without reading its imports).
syntax = "proto2";
package google.protobuf;
option go_package = "google.golang.org/protobuf/types/descriptorpb";
option java_package = "com.google.protobuf";
option java_outer_classname = "DescriptorProtos";
option csharp_namespace = "Google.Protobuf.Reflection";
option objc_class_prefix = "GPB";
option cc_enable_arenas = true;
// descriptor.proto must be optimized for speed because reflection-based
// algorithms don't work during bootstrapping.
option optimize_for = SPEED;
// The protocol compiler can output a FileDescriptorSet containing the .proto
// files it parses.
message FileDescriptorSet {
repeated FileDescriptorProto file = 1;
}
// Describes a complete .proto file.
message FileDescriptorProto {
optional string name = 1; // file name, relative to root of source tree
optional string package = 2; // e.g. "foo", "foo.bar", etc.
// Names of files imported by this file.
repeated string dependency = 3;
// Indexes of the public imported files in the dependency list above.
repeated int32 public_dependency = 10;
// Indexes of the weak imported files in the dependency list.
// For Google-internal migration only. Do not use.
repeated int32 weak_dependency = 11;
// All top-level definitions in this file.
repeated DescriptorProto message_type = 4;
repeated EnumDescriptorProto enum_type = 5;
repeated ServiceDescriptorProto service = 6;
repeated FieldDescriptorProto extension = 7;
optional FileOptions options = 8;
// This field contains optional information about the original source code.
// You may safely remove this entire field without harming runtime
// functionality of the descriptors -- the information is needed only by
// development tools.
optional SourceCodeInfo source_code_info = 9;
// The syntax of the proto file.
// The supported values are "proto2" and "proto3".
optional string syntax = 12;
}
// Describes a message type.
message DescriptorProto {
optional string name = 1;
repeated FieldDescriptorProto field = 2;
repeated FieldDescriptorProto extension = 6;
repeated DescriptorProto nested_type = 3;
repeated EnumDescriptorProto enum_type = 4;
message ExtensionRange {
optional int32 start = 1; // Inclusive.
optional int32 end = 2; // Exclusive.
optional ExtensionRangeOptions options = 3;
}
repeated ExtensionRange extension_range = 5;
repeated OneofDescriptorProto oneof_decl = 8;
optional MessageOptions options = 7;
// Range of reserved tag numbers. Reserved tag numbers may not be used by
// fields or extension ranges in the same message. Reserved ranges may
// not overlap.
message ReservedRange {
optional int32 start = 1; // Inclusive.
optional int32 end = 2; // Exclusive.
}
repeated ReservedRange reserved_range = 9;
// Reserved field names, which may not be used by fields in the same message.
// A given name may only be reserved once.
repeated string reserved_name = 10;
}
message ExtensionRangeOptions {
// The parser stores options it doesn't recognize here. See above.
repeated UninterpretedOption uninterpreted_option = 999;
// Clients can define custom options in extensions of this message. See above.
extensions 1000 to max;
}
// Describes a field within a message.
message FieldDescriptorProto {
enum Type {
// 0 is reserved for errors.
// Order is weird for historical reasons.
TYPE_DOUBLE = 1;
TYPE_FLOAT = 2;
// Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if
// negative values are likely.
TYPE_INT64 = 3;
TYPE_UINT64 = 4;
// Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if
// negative values are likely.
TYPE_INT32 = 5;
TYPE_FIXED64 = 6;
TYPE_FIXED32 = 7;
TYPE_BOOL = 8;
TYPE_STRING = 9;
// Tag-delimited aggregate.
// Group type is deprecated and not supported in proto3. However, Proto3
// implementations should still be able to parse the group wire format and
// treat group fields as unknown fields.
TYPE_GROUP = 10;
TYPE_MESSAGE = 11; // Length-delimited aggregate.
// New in version 2.
TYPE_BYTES = 12;
TYPE_UINT32 = 13;
TYPE_ENUM = 14;
TYPE_SFIXED32 = 15;
TYPE_SFIXED64 = 16;
TYPE_SINT32 = 17; // Uses ZigZag encoding.
TYPE_SINT64 = 18; // Uses ZigZag encoding.
}
enum Label {
// 0 is reserved for errors
LABEL_OPTIONAL = 1;
LABEL_REQUIRED = 2;
LABEL_REPEATED = 3;
}
optional string name = 1;
optional int32 number = 3;
optional Label label = 4;
// If type_name is set, this need not be set. If both this and type_name
// are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP.
optional Type type = 5;
// For message and enum types, this is the name of the type. If the name
// starts with a '.', it is fully-qualified. Otherwise, C++-like scoping
// rules are used to find the type (i.e. first the nested types within this
// message are searched, then within the parent, on up to the root
// namespace).
optional string type_name = 6;
// For extensions, this is the name of the type being extended. It is
// resolved in the same manner as type_name.
optional string extendee = 2;
// For numeric types, contains the original text representation of the value.
// For booleans, "true" or "false".
// For strings, contains the default text contents (not escaped in any way).
// For bytes, contains the C escaped value. All bytes >= 128 are escaped.
// TODO(kenton): Base-64 encode?
optional string default_value = 7;
// If set, gives the index of a oneof in the containing type's oneof_decl
// list. This field is a member of that oneof.
optional int32 oneof_index = 9;
// JSON name of this field. The value is set by protocol compiler. If the
// user has set a "json_name" option on this field, that option's value
// will be used. Otherwise, it's deduced from the field's name by converting
// it to camelCase.
optional string json_name = 10;
optional FieldOptions options = 8;
// If true, this is a proto3 "optional". When a proto3 field is optional, it
// tracks presence regardless of field type.
//
// When proto3_optional is true, this field must be belong to a oneof to
// signal to old proto3 clients that presence is tracked for this field. This
// oneof is known as a "synthetic" oneof, and this field must be its sole
// member (each proto3 optional field gets its own synthetic oneof). Synthetic
// oneofs exist in the descriptor only, and do not generate any API. Synthetic
// oneofs must be ordered after all "real" oneofs.
//
// For message fields, proto3_optional doesn't create any semantic change,
// since non-repeated message fields always track presence. However it still
// indicates the semantic detail of whether the user wrote "optional" or not.
// This can be useful for round-tripping the .proto file. For consistency we
// give message fields a synthetic oneof also, even though it is not required
// to track presence. This is especially important because the parser can't
// tell if a field is a message or an enum, so it must always create a
// synthetic oneof.
//
// Proto2 optional fields do not set this flag, because they already indicate
// optional with `LABEL_OPTIONAL`.
optional bool proto3_optional = 17;
}
// Describes a oneof.
message OneofDescriptorProto {
optional string name = 1;
optional OneofOptions options = 2;
}
// Describes an enum type.
message EnumDescriptorProto {
optional string name = 1;
repeated EnumValueDescriptorProto value = 2;
optional EnumOptions options = 3;
// Range of reserved numeric values. Reserved values may not be used by
// entries in the same enum. Reserved ranges may not overlap.
//
// Note that this is distinct from DescriptorProto.ReservedRange in that it
// is inclusive such that it can appropriately represent the entire int32
// domain.
message EnumReservedRange {
optional int32 start = 1; // Inclusive.
optional int32 end = 2; // Inclusive.
}
// Range of reserved numeric values. Reserved numeric values may not be used
// by enum values in the same enum declaration. Reserved ranges may not
// overlap.
repeated EnumReservedRange reserved_range = 4;
// Reserved enum value names, which may not be reused. A given name may only
// be reserved once.
repeated string reserved_name = 5;
}
// Describes a value within an enum.
message EnumValueDescriptorProto {
optional string name = 1;
optional int32 number = 2;
optional EnumValueOptions options = 3;
}
// Describes a service.
message ServiceDescriptorProto {
optional string name = 1;
repeated MethodDescriptorProto method = 2;
optional ServiceOptions options = 3;
}
// Describes a method of a service.
message MethodDescriptorProto {
optional string name = 1;
// Input and output type names. These are resolved in the same way as
// FieldDescriptorProto.type_name, but must refer to a message type.
optional string input_type = 2;
optional string output_type = 3;
optional MethodOptions options = 4;
// Identifies if client streams multiple client messages
optional bool client_streaming = 5 [default = false];
// Identifies if server streams multiple server messages
optional bool server_streaming = 6 [default = false];
}
// ===================================================================
// Options
// Each of the definitions above may have "options" attached. These are
// just annotations which may cause code to be generated slightly differently
// or may contain hints for code that manipulates protocol messages.
//
// Clients may define custom options as extensions of the *Options messages.
// These extensions may not yet be known at parsing time, so the parser cannot
// store the values in them. Instead it stores them in a field in the *Options
// message called uninterpreted_option. This field must have the same name
// across all *Options messages. We then use this field to populate the
// extensions when we build a descriptor, at which point all protos have been
// parsed and so all extensions are known.
//
// Extension numbers for custom options may be chosen as follows:
// * For options which will only be used within a single application or
// organization, or for experimental options, use field numbers 50000
// through 99999. It is up to you to ensure that you do not use the
// same number for multiple options.
// * For options which will be published and used publicly by multiple
// independent entities, e-mail protobuf-global-extension-registry@google.com
// to reserve extension numbers. Simply provide your project name (e.g.
// Objective-C plugin) and your project website (if available) -- there's no
// need to explain how you intend to use them. Usually you only need one
// extension number. You can declare multiple options with only one extension
// number by putting them in a sub-message. See the Custom Options section of
// the docs for examples:
// https://developers.google.com/protocol-buffers/docs/proto#options
// If this turns out to be popular, a web service will be set up
// to automatically assign option numbers.
message FileOptions {
// Sets the Java package where classes generated from this .proto will be
// placed. By default, the proto package is used, but this is often
// inappropriate because proto packages do not normally start with backwards
// domain names.
optional string java_package = 1;
// If set, all the classes from the .proto file are wrapped in a single
// outer class with the given name. This applies to both Proto1
// (equivalent to the old "--one_java_file" option) and Proto2 (where
// a .proto always translates to a single class, but you may want to
// explicitly choose the class name).
optional string java_outer_classname = 8;
// If set true, then the Java code generator will generate a separate .java
// file for each top-level message, enum, and service defined in the .proto
// file. Thus, these types will *not* be nested inside the outer class
// named by java_outer_classname. However, the outer class will still be
// generated to contain the file's getDescriptor() method as well as any
// top-level extensions defined in the file.
optional bool java_multiple_files = 10 [default = false];
// This option does nothing.
optional bool java_generate_equals_and_hash = 20 [deprecated=true];
// If set true, then the Java2 code generator will generate code that
// throws an exception whenever an attempt is made to assign a non-UTF-8
// byte sequence to a string field.
// Message reflection will do the same.
// However, an extension field still accepts non-UTF-8 byte sequences.
// This option has no effect on when used with the lite runtime.
optional bool java_string_check_utf8 = 27 [default = false];
// Generated classes can be optimized for speed or code size.
enum OptimizeMode {
SPEED = 1; // Generate complete code for parsing, serialization,
// etc.
CODE_SIZE = 2; // Use ReflectionOps to implement these methods.
LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime.
}
optional OptimizeMode optimize_for = 9 [default = SPEED];
// Sets the Go package where structs generated from this .proto will be
// placed. If omitted, the Go package will be derived from the following:
// - The basename of the package import path, if provided.
// - Otherwise, the package statement in the .proto file, if present.
// - Otherwise, the basename of the .proto file, without extension.
optional string go_package = 11;
// Should generic services be generated in each language? "Generic" services
// are not specific to any particular RPC system. They are generated by the
// main code generators in each language (without additional plugins).
// Generic services were the only kind of service generation supported by
// early versions of google.protobuf.
//
// Generic services are now considered deprecated in favor of using plugins
// that generate code specific to your particular RPC system. Therefore,
// these default to false. Old code which depends on generic services should
// explicitly set them to true.
optional bool cc_generic_services = 16 [default = false];
optional bool java_generic_services = 17 [default = false];
optional bool py_generic_services = 18 [default = false];
optional bool php_generic_services = 42 [default = false];
// Is this file deprecated?
// Depending on the target platform, this can emit Deprecated annotations
// for everything in the file, or it will be completely ignored; in the very
// least, this is a formalization for deprecating files.
optional bool deprecated = 23 [default = false];
// Enables the use of arenas for the proto messages in this file. This applies
// only to generated classes for C++.
optional bool cc_enable_arenas = 31 [default = true];
// Sets the objective c class prefix which is prepended to all objective c
// generated classes from this .proto. There is no default.
optional string objc_class_prefix = 36;
// Namespace for generated classes; defaults to the package.
optional string csharp_namespace = 37;
// By default Swift generators will take the proto package and CamelCase it
// replacing '.' with underscore and use that to prefix the types/symbols
// defined. When this options is provided, they will use this value instead
// to prefix the types/symbols defined.
optional string swift_prefix = 39;
// Sets the php class prefix which is prepended to all php generated classes
// from this .proto. Default is empty.
optional string php_class_prefix = 40;
// Use this option to change the namespace of php generated classes. Default
// is empty. When this option is empty, the package name will be used for
// determining the namespace.
optional string php_namespace = 41;
// Use this option to change the namespace of php generated metadata classes.
// Default is empty. When this option is empty, the proto file name will be
// used for determining the namespace.
optional string php_metadata_namespace = 44;
// Use this option to change the package of ruby generated classes. Default
// is empty. When this option is not set, the package name will be used for
// determining the ruby package.
optional string ruby_package = 45;
// The parser stores options it doesn't recognize here.
// See the documentation for the "Options" section above.
repeated UninterpretedOption uninterpreted_option = 999;
// Clients can define custom options in extensions of this message.
// See the documentation for the "Options" section above.
extensions 1000 to max;
reserved 38;
}
message MessageOptions {
// Set true to use the old proto1 MessageSet wire format for extensions.
// This is provided for backwards-compatibility with the MessageSet wire
// format. You should not use this for any other reason: It's less
// efficient, has fewer features, and is more complicated.
//
// The message must be defined exactly as follows:
// message Foo {
// option message_set_wire_format = true;
// extensions 4 to max;
// }
// Note that the message cannot have any defined fields; MessageSets only
// have extensions.
//
// All extensions of your type must be singular messages; e.g. they cannot
// be int32s, enums, or repeated messages.
//
// Because this is an option, the above two restrictions are not enforced by
// the protocol compiler.
optional bool message_set_wire_format = 1 [default = false];
// Disables the generation of the standard "descriptor()" accessor, which can
// conflict with a field of the same name. This is meant to make migration
// from proto1 easier; new code should avoid fields named "descriptor".
optional bool no_standard_descriptor_accessor = 2 [default = false];
// Is this message deprecated?
// Depending on the target platform, this can emit Deprecated annotations
// for the message, or it will be completely ignored; in the very least,
// this is a formalization for deprecating messages.
optional bool deprecated = 3 [default = false];
// Whether the message is an automatically generated map entry type for the
// maps field.
//
// For maps fields:
// map map_field = 1;
// The parsed descriptor looks like:
// message MapFieldEntry {
// option map_entry = true;
// optional KeyType key = 1;
// optional ValueType value = 2;
// }
// repeated MapFieldEntry map_field = 1;
//
// Implementations may choose not to generate the map_entry=true message, but
// use a native map in the target language to hold the keys and values.
// The reflection APIs in such implementations still need to work as
// if the field is a repeated message field.
//
// NOTE: Do not set the option in .proto files. Always use the maps syntax
// instead. The option should only be implicitly set by the proto compiler
// parser.
optional bool map_entry = 7;
reserved 8; // javalite_serializable
reserved 9; // javanano_as_lite
// The parser stores options it doesn't recognize here. See above.
repeated UninterpretedOption uninterpreted_option = 999;
// Clients can define custom options in extensions of this message. See above.
extensions 1000 to max;
}
message FieldOptions {
// The ctype option instructs the C++ code generator to use a different
// representation of the field than it normally would. See the specific
// options below. This option is not yet implemented in the open source
// release -- sorry, we'll try to include it in a future version!
optional CType ctype = 1 [default = STRING];
enum CType {
// Default mode.
STRING = 0;
CORD = 1;
STRING_PIECE = 2;
}
// The packed option can be enabled for repeated primitive fields to enable
// a more efficient representation on the wire. Rather than repeatedly
// writing the tag and type for each element, the entire array is encoded as
// a single length-delimited blob. In proto3, only explicit setting it to
// false will avoid using packed encoding.
optional bool packed = 2;
// The jstype option determines the JavaScript type used for values of the
// field. The option is permitted only for 64 bit integral and fixed types
// (int64, uint64, sint64, fixed64, sfixed64). A field with jstype JS_STRING
// is represented as JavaScript string, which avoids loss of precision that
// can happen when a large value is converted to a floating point JavaScript.
// Specifying JS_NUMBER for the jstype causes the generated JavaScript code to
// use the JavaScript "number" type. The behavior of the default option
// JS_NORMAL is implementation dependent.
//
// This option is an enum to permit additional types to be added, e.g.
// goog.math.Integer.
optional JSType jstype = 6 [default = JS_NORMAL];
enum JSType {
// Use the default type.
JS_NORMAL = 0;
// Use JavaScript strings.
JS_STRING = 1;
// Use JavaScript numbers.
JS_NUMBER = 2;
}
// Should this field be parsed lazily? Lazy applies only to message-type
// fields. It means that when the outer message is initially parsed, the
// inner message's contents will not be parsed but instead stored in encoded
// form. The inner message will actually be parsed when it is first accessed.
//
// This is only a hint. Implementations are free to choose whether to use
// eager or lazy parsing regardless of the value of this option. However,
// setting this option true suggests that the protocol author believes that
// using lazy parsing on this field is worth the additional bookkeeping
// overhead typically needed to implement it.
//
// This option does not affect the public interface of any generated code;
// all method signatures remain the same. Furthermore, thread-safety of the
// interface is not affected by this option; const methods remain safe to
// call from multiple threads concurrently, while non-const methods continue
// to require exclusive access.
//
//
// Note that implementations may choose not to check required fields within
// a lazy sub-message. That is, calling IsInitialized() on the outer message
// may return true even if the inner message has missing required fields.
// This is necessary because otherwise the inner message would have to be
// parsed in order to perform the check, defeating the purpose of lazy
// parsing. An implementation which chooses not to check required fields
// must be consistent about it. That is, for any particular sub-message, the
// implementation must either *always* check its required fields, or *never*
// check its required fields, regardless of whether or not the message has
// been parsed.
optional bool lazy = 5 [default = false];
// Is this field deprecated?
// Depending on the target platform, this can emit Deprecated annotations
// for accessors, or it will be completely ignored; in the very least, this
// is a formalization for deprecating fields.
optional bool deprecated = 3 [default = false];
// For Google-internal migration only. Do not use.
optional bool weak = 10 [default = false];
// The parser stores options it doesn't recognize here. See above.
repeated UninterpretedOption uninterpreted_option = 999;
// Clients can define custom options in extensions of this message. See above.
extensions 1000 to max;
reserved 4; // removed jtype
}
message OneofOptions {
// The parser stores options it doesn't recognize here. See above.
repeated UninterpretedOption uninterpreted_option = 999;
// Clients can define custom options in extensions of this message. See above.
extensions 1000 to max;
}
message EnumOptions {
// Set this option to true to allow mapping different tag names to the same
// value.
optional bool allow_alias = 2;
// Is this enum deprecated?
// Depending on the target platform, this can emit Deprecated annotations
// for the enum, or it will be completely ignored; in the very least, this
// is a formalization for deprecating enums.
optional bool deprecated = 3 [default = false];
reserved 5; // javanano_as_lite
// The parser stores options it doesn't recognize here. See above.
repeated UninterpretedOption uninterpreted_option = 999;
// Clients can define custom options in extensions of this message. See above.
extensions 1000 to max;
}
message EnumValueOptions {
// Is this enum value deprecated?
// Depending on the target platform, this can emit Deprecated annotations
// for the enum value, or it will be completely ignored; in the very least,
// this is a formalization for deprecating enum values.
optional bool deprecated = 1 [default = false];
// The parser stores options it doesn't recognize here. See above.
repeated UninterpretedOption uninterpreted_option = 999;
// Clients can define custom options in extensions of this message. See above.
extensions 1000 to max;
}
message ServiceOptions {
// Note: Field numbers 1 through 32 are reserved for Google's internal RPC
// framework. We apologize for hoarding these numbers to ourselves, but
// we were already using them long before we decided to release Protocol
// Buffers.
// Is this service deprecated?
// Depending on the target platform, this can emit Deprecated annotations
// for the service, or it will be completely ignored; in the very least,
// this is a formalization for deprecating services.
optional bool deprecated = 33 [default = false];
// The parser stores options it doesn't recognize here. See above.
repeated UninterpretedOption uninterpreted_option = 999;
// Clients can define custom options in extensions of this message. See above.
extensions 1000 to max;
}
message MethodOptions {
// Note: Field numbers 1 through 32 are reserved for Google's internal RPC
// framework. We apologize for hoarding these numbers to ourselves, but
// we were already using them long before we decided to release Protocol
// Buffers.
// Is this method deprecated?
// Depending on the target platform, this can emit Deprecated annotations
// for the method, or it will be completely ignored; in the very least,
// this is a formalization for deprecating methods.
optional bool deprecated = 33 [default = false];
// Is this method side-effect-free (or safe in HTTP parlance), or idempotent,
// or neither? HTTP based RPC implementation may choose GET verb for safe
// methods, and PUT verb for idempotent methods instead of the default POST.
enum IdempotencyLevel {
IDEMPOTENCY_UNKNOWN = 0;
NO_SIDE_EFFECTS = 1; // implies idempotent
IDEMPOTENT = 2; // idempotent, but may have side effects
}
optional IdempotencyLevel idempotency_level = 34
[default = IDEMPOTENCY_UNKNOWN];
// The parser stores options it doesn't recognize here. See above.
repeated UninterpretedOption uninterpreted_option = 999;
// Clients can define custom options in extensions of this message. See above.
extensions 1000 to max;
}
// A message representing a option the parser does not recognize. This only
// appears in options protos created by the compiler::Parser class.
// DescriptorPool resolves these when building Descriptor objects. Therefore,
// options protos in descriptor objects (e.g. returned by Descriptor::options(),
// or produced by Descriptor::CopyTo()) will never have UninterpretedOptions
// in them.
message UninterpretedOption {
// The name of the uninterpreted option. Each string represents a segment in
// a dot-separated name. is_extension is true iff a segment represents an
// extension (denoted with parentheses in options specs in .proto files).
// E.g.,{ ["foo", false], ["bar.baz", true], ["qux", false] } represents
// "foo.(bar.baz).qux".
message NamePart {
required string name_part = 1;
required bool is_extension = 2;
}
repeated NamePart name = 2;
// The value of the uninterpreted option, in whatever type the tokenizer
// identified it as during parsing. Exactly one of these should be set.
optional string identifier_value = 3;
optional uint64 positive_int_value = 4;
optional int64 negative_int_value = 5;
optional double double_value = 6;
optional bytes string_value = 7;
optional string aggregate_value = 8;
}
// ===================================================================
// Optional source code info
// Encapsulates information about the original source file from which a
// FileDescriptorProto was generated.
message SourceCodeInfo {
// A Location identifies a piece of source code in a .proto file which
// corresponds to a particular definition. This information is intended
// to be useful to IDEs, code indexers, documentation generators, and similar
// tools.
//
// For example, say we have a file like:
// message Foo {
// optional string foo = 1;
// }
// Let's look at just the field definition:
// optional string foo = 1;
// ^ ^^ ^^ ^ ^^^
// a bc de f ghi
// We have the following locations:
// span path represents
// [a,i) [ 4, 0, 2, 0 ] The whole field definition.
// [a,b) [ 4, 0, 2, 0, 4 ] The label (optional).
// [c,d) [ 4, 0, 2, 0, 5 ] The type (string).
// [e,f) [ 4, 0, 2, 0, 1 ] The name (foo).
// [g,h) [ 4, 0, 2, 0, 3 ] The number (1).
//
// Notes:
// - A location may refer to a repeated field itself (i.e. not to any
// particular index within it). This is used whenever a set of elements are
// logically enclosed in a single code segment. For example, an entire
// extend block (possibly containing multiple extension definitions) will
// have an outer location whose path refers to the "extensions" repeated
// field without an index.
// - Multiple locations may have the same path. This happens when a single
// logical declaration is spread out across multiple places. The most
// obvious example is the "extend" block again -- there may be multiple
// extend blocks in the same scope, each of which will have the same path.
// - A location's span is not always a subset of its parent's span. For
// example, the "extendee" of an extension declaration appears at the
// beginning of the "extend" block and is shared by all extensions within
// the block.
// - Just because a location's span is a subset of some other location's span
// does not mean that it is a descendant. For example, a "group" defines
// both a type and a field in a single declaration. Thus, the locations
// corresponding to the type and field and their components will overlap.
// - Code which tries to interpret locations should probably be designed to
// ignore those that it doesn't understand, as more types of locations could
// be recorded in the future.
repeated Location location = 1;
message Location {
// Identifies which part of the FileDescriptorProto was defined at this
// location.
//
// Each element is a field number or an index. They form a path from
// the root FileDescriptorProto to the place where the definition. For
// example, this path:
// [ 4, 3, 2, 7, 1 ]
// refers to:
// file.message_type(3) // 4, 3
// .field(7) // 2, 7
// .name() // 1
// This is because FileDescriptorProto.message_type has field number 4:
// repeated DescriptorProto message_type = 4;
// and DescriptorProto.field has field number 2:
// repeated FieldDescriptorProto field = 2;
// and FieldDescriptorProto.name has field number 1:
// optional string name = 1;
//
// Thus, the above path gives the location of a field name. If we removed
// the last element:
// [ 4, 3, 2, 7 ]
// this path refers to the whole field declaration (from the beginning
// of the label to the terminating semicolon).
repeated int32 path = 1 [packed = true];
// Always has exactly three or four elements: start line, start column,
// end line (optional, otherwise assumed same as start line), end column.
// These are packed into a single field for efficiency. Note that line
// and column numbers are zero-based -- typically you will want to add
// 1 to each before displaying to a user.
repeated int32 span = 2 [packed = true];
// If this SourceCodeInfo represents a complete declaration, these are any
// comments appearing before and after the declaration which appear to be
// attached to the declaration.
//
// A series of line comments appearing on consecutive lines, with no other
// tokens appearing on those lines, will be treated as a single comment.
//
// leading_detached_comments will keep paragraphs of comments that appear
// before (but not connected to) the current element. Each paragraph,
// separated by empty lines, will be one comment element in the repeated
// field.
//
// Only the comment content is provided; comment markers (e.g. //) are
// stripped out. For block comments, leading whitespace and an asterisk
// will be stripped from the beginning of each line other than the first.
// Newlines are included in the output.
//
// Examples:
//
// optional int32 foo = 1; // Comment attached to foo.
// // Comment attached to bar.
// optional int32 bar = 2;
//
// optional string baz = 3;
// // Comment attached to baz.
// // Another line attached to baz.
//
// // Comment attached to qux.
// //
// // Another line attached to qux.
// optional double qux = 4;
//
// // Detached comment for corge. This is not leading or trailing comments
// // to qux or corge because there are blank lines separating it from
// // both.
//
// // Detached comment for corge paragraph 2.
//
// optional string corge = 5;
// /* Block comment attached
// * to corge. Leading asterisks
// * will be removed. */
// /* Block comment attached to
// * grault. */
// optional int32 grault = 6;
//
// // ignored detached comments.
optional string leading_comments = 3;
optional string trailing_comments = 4;
repeated string leading_detached_comments = 6;
}
}
// Describes the relationship between generated code and its original source
// file. A GeneratedCodeInfo message is associated with only one generated
// source file, but may contain references to different source .proto files.
message GeneratedCodeInfo {
// An Annotation connects some span of text in generated code to an element
// of its generating .proto file.
repeated Annotation annotation = 1;
message Annotation {
// Identifies the element in the original source .proto file. This field
// is formatted the same as SourceCodeInfo.Location.path.
repeated int32 path = 1 [packed = true];
// Identifies the filesystem path to the original source .proto.
optional string source_file = 2;
// Identifies the starting offset in bytes in the generated code
// that relates to the identified object.
optional int32 begin = 3;
// Identifies the ending offset in bytes in the generated code that
// relates to the identified offset. The end offset should be one past
// the last relevant byte (so the length of the text = end - begin).
optional int32 end = 4;
}
}
================================================
FILE: lamda/google/protobuf/duration.proto
================================================
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
package google.protobuf;
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
option cc_enable_arenas = true;
option go_package = "google.golang.org/protobuf/types/known/durationpb";
option java_package = "com.google.protobuf";
option java_outer_classname = "DurationProto";
option java_multiple_files = true;
option objc_class_prefix = "GPB";
// A Duration represents a signed, fixed-length span of time represented
// as a count of seconds and fractions of seconds at nanosecond
// resolution. It is independent of any calendar and concepts like "day"
// or "month". It is related to Timestamp in that the difference between
// two Timestamp values is a Duration and it can be added or subtracted
// from a Timestamp. Range is approximately +-10,000 years.
//
// # Examples
//
// Example 1: Compute Duration from two Timestamps in pseudo code.
//
// Timestamp start = ...;
// Timestamp end = ...;
// Duration duration = ...;
//
// duration.seconds = end.seconds - start.seconds;
// duration.nanos = end.nanos - start.nanos;
//
// if (duration.seconds < 0 && duration.nanos > 0) {
// duration.seconds += 1;
// duration.nanos -= 1000000000;
// } else if (duration.seconds > 0 && duration.nanos < 0) {
// duration.seconds -= 1;
// duration.nanos += 1000000000;
// }
//
// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code.
//
// Timestamp start = ...;
// Duration duration = ...;
// Timestamp end = ...;
//
// end.seconds = start.seconds + duration.seconds;
// end.nanos = start.nanos + duration.nanos;
//
// if (end.nanos < 0) {
// end.seconds -= 1;
// end.nanos += 1000000000;
// } else if (end.nanos >= 1000000000) {
// end.seconds += 1;
// end.nanos -= 1000000000;
// }
//
// Example 3: Compute Duration from datetime.timedelta in Python.
//
// td = datetime.timedelta(days=3, minutes=10)
// duration = Duration()
// duration.FromTimedelta(td)
//
// # JSON Mapping
//
// In JSON format, the Duration type is encoded as a string rather than an
// object, where the string ends in the suffix "s" (indicating seconds) and
// is preceded by the number of seconds, with nanoseconds expressed as
// fractional seconds. For example, 3 seconds with 0 nanoseconds should be
// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should
// be expressed in JSON format as "3.000000001s", and 3 seconds and 1
// microsecond should be expressed in JSON format as "3.000001s".
//
//
message Duration {
// Signed seconds of the span of time. Must be from -315,576,000,000
// to +315,576,000,000 inclusive. Note: these bounds are computed from:
// 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
int64 seconds = 1;
// Signed fractions of a second at nanosecond resolution of the span
// of time. Durations less than one second are represented with a 0
// `seconds` field and a positive or negative `nanos` field. For durations
// of one second or more, a non-zero value for the `nanos` field must be
// of the same sign as the `seconds` field. Must be from -999,999,999
// to +999,999,999 inclusive.
int32 nanos = 2;
}
================================================
FILE: lamda/google/protobuf/empty.proto
================================================
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
package google.protobuf;
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
option go_package = "google.golang.org/protobuf/types/known/emptypb";
option java_package = "com.google.protobuf";
option java_outer_classname = "EmptyProto";
option java_multiple_files = true;
option objc_class_prefix = "GPB";
option cc_enable_arenas = true;
// A generic empty message that you can re-use to avoid defining duplicated
// empty messages in your APIs. A typical example is to use it as the request
// or the response type of an API method. For instance:
//
// service Foo {
// rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty);
// }
//
// The JSON representation for `Empty` is empty JSON object `{}`.
message Empty {}
================================================
FILE: lamda/google/protobuf/field_mask.proto
================================================
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
package google.protobuf;
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
option java_package = "com.google.protobuf";
option java_outer_classname = "FieldMaskProto";
option java_multiple_files = true;
option objc_class_prefix = "GPB";
option go_package = "google.golang.org/protobuf/types/known/fieldmaskpb";
option cc_enable_arenas = true;
// `FieldMask` represents a set of symbolic field paths, for example:
//
// paths: "f.a"
// paths: "f.b.d"
//
// Here `f` represents a field in some root message, `a` and `b`
// fields in the message found in `f`, and `d` a field found in the
// message in `f.b`.
//
// Field masks are used to specify a subset of fields that should be
// returned by a get operation or modified by an update operation.
// Field masks also have a custom JSON encoding (see below).
//
// # Field Masks in Projections
//
// When used in the context of a projection, a response message or
// sub-message is filtered by the API to only contain those fields as
// specified in the mask. For example, if the mask in the previous
// example is applied to a response message as follows:
//
// f {
// a : 22
// b {
// d : 1
// x : 2
// }
// y : 13
// }
// z: 8
//
// The result will not contain specific values for fields x,y and z
// (their value will be set to the default, and omitted in proto text
// output):
//
//
// f {
// a : 22
// b {
// d : 1
// }
// }
//
// A repeated field is not allowed except at the last position of a
// paths string.
//
// If a FieldMask object is not present in a get operation, the
// operation applies to all fields (as if a FieldMask of all fields
// had been specified).
//
// Note that a field mask does not necessarily apply to the
// top-level response message. In case of a REST get operation, the
// field mask applies directly to the response, but in case of a REST
// list operation, the mask instead applies to each individual message
// in the returned resource list. In case of a REST custom method,
// other definitions may be used. Where the mask applies will be
// clearly documented together with its declaration in the API. In
// any case, the effect on the returned resource/resources is required
// behavior for APIs.
//
// # Field Masks in Update Operations
//
// A field mask in update operations specifies which fields of the
// targeted resource are going to be updated. The API is required
// to only change the values of the fields as specified in the mask
// and leave the others untouched. If a resource is passed in to
// describe the updated values, the API ignores the values of all
// fields not covered by the mask.
//
// If a repeated field is specified for an update operation, new values will
// be appended to the existing repeated field in the target resource. Note that
// a repeated field is only allowed in the last position of a `paths` string.
//
// If a sub-message is specified in the last position of the field mask for an
// update operation, then new value will be merged into the existing sub-message
// in the target resource.
//
// For example, given the target message:
//
// f {
// b {
// d: 1
// x: 2
// }
// c: [1]
// }
//
// And an update message:
//
// f {
// b {
// d: 10
// }
// c: [2]
// }
//
// then if the field mask is:
//
// paths: ["f.b", "f.c"]
//
// then the result will be:
//
// f {
// b {
// d: 10
// x: 2
// }
// c: [1, 2]
// }
//
// An implementation may provide options to override this default behavior for
// repeated and message fields.
//
// In order to reset a field's value to the default, the field must
// be in the mask and set to the default value in the provided resource.
// Hence, in order to reset all fields of a resource, provide a default
// instance of the resource and set all fields in the mask, or do
// not provide a mask as described below.
//
// If a field mask is not present on update, the operation applies to
// all fields (as if a field mask of all fields has been specified).
// Note that in the presence of schema evolution, this may mean that
// fields the client does not know and has therefore not filled into
// the request will be reset to their default. If this is unwanted
// behavior, a specific service may require a client to always specify
// a field mask, producing an error if not.
//
// As with get operations, the location of the resource which
// describes the updated values in the request message depends on the
// operation kind. In any case, the effect of the field mask is
// required to be honored by the API.
//
// ## Considerations for HTTP REST
//
// The HTTP kind of an update operation which uses a field mask must
// be set to PATCH instead of PUT in order to satisfy HTTP semantics
// (PUT must only be used for full updates).
//
// # JSON Encoding of Field Masks
//
// In JSON, a field mask is encoded as a single string where paths are
// separated by a comma. Fields name in each path are converted
// to/from lower-camel naming conventions.
//
// As an example, consider the following message declarations:
//
// message Profile {
// User user = 1;
// Photo photo = 2;
// }
// message User {
// string display_name = 1;
// string address = 2;
// }
//
// In proto a field mask for `Profile` may look as such:
//
// mask {
// paths: "user.display_name"
// paths: "photo"
// }
//
// In JSON, the same mask is represented as below:
//
// {
// mask: "user.displayName,photo"
// }
//
// # Field Masks and Oneof Fields
//
// Field masks treat fields in oneofs just as regular fields. Consider the
// following message:
//
// message SampleMessage {
// oneof test_oneof {
// string name = 4;
// SubMessage sub_message = 9;
// }
// }
//
// The field mask can be:
//
// mask {
// paths: "name"
// }
//
// Or:
//
// mask {
// paths: "sub_message"
// }
//
// Note that oneof type names ("test_oneof" in this case) cannot be used in
// paths.
//
// ## Field Mask Verification
//
// The implementation of any API method which has a FieldMask type field in the
// request should verify the included field paths, and return an
// `INVALID_ARGUMENT` error if any path is unmappable.
message FieldMask {
// The set of field mask paths.
repeated string paths = 1;
}
================================================
FILE: lamda/google/protobuf/source_context.proto
================================================
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
package google.protobuf;
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
option java_package = "com.google.protobuf";
option java_outer_classname = "SourceContextProto";
option java_multiple_files = true;
option objc_class_prefix = "GPB";
option go_package = "google.golang.org/protobuf/types/known/sourcecontextpb";
// `SourceContext` represents information about the source of a
// protobuf element, like the file in which it is defined.
message SourceContext {
// The path-qualified name of the .proto file that contained the associated
// protobuf element. For example: `"google/protobuf/source_context.proto"`.
string file_name = 1;
}
================================================
FILE: lamda/google/protobuf/struct.proto
================================================
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
package google.protobuf;
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
option cc_enable_arenas = true;
option go_package = "google.golang.org/protobuf/types/known/structpb";
option java_package = "com.google.protobuf";
option java_outer_classname = "StructProto";
option java_multiple_files = true;
option objc_class_prefix = "GPB";
// `Struct` represents a structured data value, consisting of fields
// which map to dynamically typed values. In some languages, `Struct`
// might be supported by a native representation. For example, in
// scripting languages like JS a struct is represented as an
// object. The details of that representation are described together
// with the proto support for the language.
//
// The JSON representation for `Struct` is JSON object.
message Struct {
// Unordered map of dynamically typed values.
map fields = 1;
}
// `Value` represents a dynamically typed value which can be either
// null, a number, a string, a boolean, a recursive struct value, or a
// list of values. A producer of value is expected to set one of that
// variants, absence of any variant indicates an error.
//
// The JSON representation for `Value` is JSON value.
message Value {
// The kind of value.
oneof kind {
// Represents a null value.
NullValue null_value = 1;
// Represents a double value.
double number_value = 2;
// Represents a string value.
string string_value = 3;
// Represents a boolean value.
bool bool_value = 4;
// Represents a structured value.
Struct struct_value = 5;
// Represents a repeated `Value`.
ListValue list_value = 6;
}
}
// `NullValue` is a singleton enumeration to represent the null value for the
// `Value` type union.
//
// The JSON representation for `NullValue` is JSON `null`.
enum NullValue {
// Null value.
NULL_VALUE = 0;
}
// `ListValue` is a wrapper around a repeated field of values.
//
// The JSON representation for `ListValue` is JSON array.
message ListValue {
// Repeated field of dynamically typed values.
repeated Value values = 1;
}
================================================
FILE: lamda/google/protobuf/timestamp.proto
================================================
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
package google.protobuf;
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
option cc_enable_arenas = true;
option go_package = "google.golang.org/protobuf/types/known/timestamppb";
option java_package = "com.google.protobuf";
option java_outer_classname = "TimestampProto";
option java_multiple_files = true;
option objc_class_prefix = "GPB";
// A Timestamp represents a point in time independent of any time zone or local
// calendar, encoded as a count of seconds and fractions of seconds at
// nanosecond resolution. The count is relative to an epoch at UTC midnight on
// January 1, 1970, in the proleptic Gregorian calendar which extends the
// Gregorian calendar backwards to year one.
//
// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
// second table is needed for interpretation, using a [24-hour linear
// smear](https://developers.google.com/time/smear).
//
// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
// restricting to that range, we ensure that we can convert to and from [RFC
// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
//
// # Examples
//
// Example 1: Compute Timestamp from POSIX `time()`.
//
// Timestamp timestamp;
// timestamp.set_seconds(time(NULL));
// timestamp.set_nanos(0);
//
// Example 2: Compute Timestamp from POSIX `gettimeofday()`.
//
// struct timeval tv;
// gettimeofday(&tv, NULL);
//
// Timestamp timestamp;
// timestamp.set_seconds(tv.tv_sec);
// timestamp.set_nanos(tv.tv_usec * 1000);
//
// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
//
// FILETIME ft;
// GetSystemTimeAsFileTime(&ft);
// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
//
// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
// Timestamp timestamp;
// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
//
// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
//
// long millis = System.currentTimeMillis();
//
// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
// .setNanos((int) ((millis % 1000) * 1000000)).build();
//
//
// Example 5: Compute Timestamp from Java `Instant.now()`.
//
// Instant now = Instant.now();
//
// Timestamp timestamp =
// Timestamp.newBuilder().setSeconds(now.getEpochSecond())
// .setNanos(now.getNano()).build();
//
//
// Example 6: Compute Timestamp from current time in Python.
//
// timestamp = Timestamp()
// timestamp.GetCurrentTime()
//
// # JSON Mapping
//
// In JSON format, the Timestamp type is encoded as a string in the
// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
// where {year} is always expressed using four digits while {month}, {day},
// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
// is required. A proto3 JSON serializer should always use UTC (as indicated by
// "Z") when printing the Timestamp type and a proto3 JSON parser should be
// able to accept both UTC and other timezones (as indicated by an offset).
//
// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
// 01:30 UTC on January 15, 2017.
//
// In JavaScript, one can convert a Date object to this format using the
// standard
// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
// method. In Python, a standard `datetime.datetime` object can be converted
// to this format using
// [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with
// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use
// the Joda Time's [`ISODateTimeFormat.dateTime()`](
// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D
// ) to obtain a formatter capable of generating timestamps in this format.
//
//
message Timestamp {
// Represents seconds of UTC time since Unix epoch
// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
// 9999-12-31T23:59:59Z inclusive.
int64 seconds = 1;
// Non-negative fractions of a second at nanosecond resolution. Negative
// second values with fractions must still have non-negative nanos values
// that count forward in time. Must be from 0 to 999,999,999
// inclusive.
int32 nanos = 2;
}
================================================
FILE: lamda/google/protobuf/type.proto
================================================
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
package google.protobuf;
import "google/protobuf/any.proto";
import "google/protobuf/source_context.proto";
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
option cc_enable_arenas = true;
option java_package = "com.google.protobuf";
option java_outer_classname = "TypeProto";
option java_multiple_files = true;
option objc_class_prefix = "GPB";
option go_package = "google.golang.org/protobuf/types/known/typepb";
// A protocol buffer message type.
message Type {
// The fully qualified message name.
string name = 1;
// The list of fields.
repeated Field fields = 2;
// The list of types appearing in `oneof` definitions in this type.
repeated string oneofs = 3;
// The protocol buffer options.
repeated Option options = 4;
// The source context.
SourceContext source_context = 5;
// The source syntax.
Syntax syntax = 6;
}
// A single field of a message type.
message Field {
// Basic field types.
enum Kind {
// Field type unknown.
TYPE_UNKNOWN = 0;
// Field type double.
TYPE_DOUBLE = 1;
// Field type float.
TYPE_FLOAT = 2;
// Field type int64.
TYPE_INT64 = 3;
// Field type uint64.
TYPE_UINT64 = 4;
// Field type int32.
TYPE_INT32 = 5;
// Field type fixed64.
TYPE_FIXED64 = 6;
// Field type fixed32.
TYPE_FIXED32 = 7;
// Field type bool.
TYPE_BOOL = 8;
// Field type string.
TYPE_STRING = 9;
// Field type group. Proto2 syntax only, and deprecated.
TYPE_GROUP = 10;
// Field type message.
TYPE_MESSAGE = 11;
// Field type bytes.
TYPE_BYTES = 12;
// Field type uint32.
TYPE_UINT32 = 13;
// Field type enum.
TYPE_ENUM = 14;
// Field type sfixed32.
TYPE_SFIXED32 = 15;
// Field type sfixed64.
TYPE_SFIXED64 = 16;
// Field type sint32.
TYPE_SINT32 = 17;
// Field type sint64.
TYPE_SINT64 = 18;
}
// Whether a field is optional, required, or repeated.
enum Cardinality {
// For fields with unknown cardinality.
CARDINALITY_UNKNOWN = 0;
// For optional fields.
CARDINALITY_OPTIONAL = 1;
// For required fields. Proto2 syntax only.
CARDINALITY_REQUIRED = 2;
// For repeated fields.
CARDINALITY_REPEATED = 3;
}
// The field type.
Kind kind = 1;
// The field cardinality.
Cardinality cardinality = 2;
// The field number.
int32 number = 3;
// The field name.
string name = 4;
// The field type URL, without the scheme, for message or enumeration
// types. Example: `"type.googleapis.com/google.protobuf.Timestamp"`.
string type_url = 6;
// The index of the field type in `Type.oneofs`, for message or enumeration
// types. The first type has index 1; zero means the type is not in the list.
int32 oneof_index = 7;
// Whether to use alternative packed wire representation.
bool packed = 8;
// The protocol buffer options.
repeated Option options = 9;
// The field JSON name.
string json_name = 10;
// The string value of the default value of this field. Proto2 syntax only.
string default_value = 11;
}
// Enum type definition.
message Enum {
// Enum type name.
string name = 1;
// Enum value definitions.
repeated EnumValue enumvalue = 2;
// Protocol buffer options.
repeated Option options = 3;
// The source context.
SourceContext source_context = 4;
// The source syntax.
Syntax syntax = 5;
}
// Enum value definition.
message EnumValue {
// Enum value name.
string name = 1;
// Enum value number.
int32 number = 2;
// Protocol buffer options.
repeated Option options = 3;
}
// A protocol buffer option, which can be attached to a message, field,
// enumeration, etc.
message Option {
// The option's name. For protobuf built-in options (options defined in
// descriptor.proto), this is the short name. For example, `"map_entry"`.
// For custom options, it should be the fully-qualified name. For example,
// `"google.api.http"`.
string name = 1;
// The option's value packed in an Any message. If the value is a primitive,
// the corresponding wrapper type defined in google/protobuf/wrappers.proto
// should be used. If the value is an enum, it should be stored as an int32
// value using the google.protobuf.Int32Value type.
Any value = 2;
}
// The syntax in which a protocol buffer element is defined.
enum Syntax {
// Syntax `proto2`.
SYNTAX_PROTO2 = 0;
// Syntax `proto3`.
SYNTAX_PROTO3 = 1;
}
================================================
FILE: lamda/google/protobuf/wrappers.proto
================================================
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Wrappers for primitive (non-message) types. These types are useful
// for embedding primitives in the `google.protobuf.Any` type and for places
// where we need to distinguish between the absence of a primitive
// typed field and its default value.
//
// These wrappers have no meaningful use within repeated fields as they lack
// the ability to detect presence on individual elements.
// These wrappers have no meaningful use within a map or a oneof since
// individual entries of a map or fields of a oneof can already detect presence.
syntax = "proto3";
package google.protobuf;
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
option cc_enable_arenas = true;
option go_package = "google.golang.org/protobuf/types/known/wrapperspb";
option java_package = "com.google.protobuf";
option java_outer_classname = "WrappersProto";
option java_multiple_files = true;
option objc_class_prefix = "GPB";
// Wrapper message for `double`.
//
// The JSON representation for `DoubleValue` is JSON number.
message DoubleValue {
// The double value.
double value = 1;
}
// Wrapper message for `float`.
//
// The JSON representation for `FloatValue` is JSON number.
message FloatValue {
// The float value.
float value = 1;
}
// Wrapper message for `int64`.
//
// The JSON representation for `Int64Value` is JSON string.
message Int64Value {
// The int64 value.
int64 value = 1;
}
// Wrapper message for `uint64`.
//
// The JSON representation for `UInt64Value` is JSON string.
message UInt64Value {
// The uint64 value.
uint64 value = 1;
}
// Wrapper message for `int32`.
//
// The JSON representation for `Int32Value` is JSON number.
message Int32Value {
// The int32 value.
int32 value = 1;
}
// Wrapper message for `uint32`.
//
// The JSON representation for `UInt32Value` is JSON number.
message UInt32Value {
// The uint32 value.
uint32 value = 1;
}
// Wrapper message for `bool`.
//
// The JSON representation for `BoolValue` is JSON `true` and `false`.
message BoolValue {
// The bool value.
bool value = 1;
}
// Wrapper message for `string`.
//
// The JSON representation for `StringValue` is JSON string.
message StringValue {
// The string value.
string value = 1;
}
// Wrapper message for `bytes`.
//
// The JSON representation for `BytesValue` is JSON string.
message BytesValue {
// The bytes value.
bytes value = 1;
}
================================================
FILE: lamda/rpc/application.proto
================================================
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
import "google/protobuf/struct.proto";
enum GrantType {
GRANT_ALLOW = 0;
GRANT_DENY = 1;
GRANT_IGNORE = 2;
}
enum DataEncode {
DATA_ENCODE_NONE = 0;
DATA_ENCODE_ZLIB = 1;
}
enum ScriptRuntime {
RUNTIME_QJS = 0;
RUNTIME_V8 = 1;
}
message ApplicationRequest {
string name = 1;
string permission = 2;
GrantType mode = 3;
string path = 4;
uint32 user = 5;
}
message ApplicationActivityRequest {
string package = 1;
string action = 2;
string category = 3;
string component = 4;
google.protobuf.Struct extras = 5;
repeated string categories = 6;
int64 flags = 7;
bool debug = 8;
string data = 9;
uint32 user = 10;
}
message ApplicationActivityInfo {
string package = 1;
string action = 2;
string category = 3;
string component = 4;
google.protobuf.Struct extras = 5;
repeated string categories = 6;
int64 flags = 7;
bool debug = 8;
string data = 9;
uint32 user = 10;
}
message ApplicationActivityInfoList {
repeated ApplicationActivityInfo activities = 1;
}
message ApplicationPermissions {
repeated string permissions = 1;
}
message ApplicationInfo {
string packageName = 1;
uint32 uid = 2;
bool enabled = 3;
string processName = 4;
string sourceDir = 5;
string dataDir = 6;
uint32 baseRevisionCode = 7;
int64 firstInstallTime = 8;
int64 lastUpdateTime = 9;
uint32 versionCode = 10;
string versionName = 11;
string activity = 12;
uint32 user = 13;
}
message ApplicationProcess {
repeated string packages = 1;
string processName = 2;
int64 uid = 3;
int64 pid = 4;
}
message ApplicationProcesses {
repeated ApplicationProcess processes = 1;
}
message ApplicationPkgNames {
repeated string names = 1;
}
message HookRequest {
string package = 1;
bytes script = 2;
ScriptRuntime runtime = 3;
string destination = 4;
DataEncode encode = 5;
uint32 standup = 6;
bool spawn = 7;
uint32 user = 8;
}
message HookRpcRequest {
string package = 1;
string callinfo = 2;
uint32 user = 3;
}
message HookRpcResponse {
string package = 1;
string callresult = 2;
}
================================================
FILE: lamda/rpc/debug.proto
================================================
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
import "google/protobuf/struct.proto";
message ADBDConfigRequest {
string adb_pubkey = 1;
}
================================================
FILE: lamda/rpc/file.proto
================================================
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
message FileStat {
string name = 1;
string path = 2;
bool directory = 3;
int64 st_mode = 4;
int64 st_atime = 5;
int64 st_mtime = 6;
int64 st_ctime = 7;
int32 st_uid = 8;
int32 st_gid = 9;
int64 st_size = 10;
}
message FileRequest {
string path = 1;
uint64 mode = 2;
bytes payload = 3;
}
message FileDataResponse {
bytes payload = 1;
}
================================================
FILE: lamda/rpc/policy.proto
================================================
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
message SelinuxPolicyRequest {
string source = 1;
string target = 2;
string tclass = 3;
string action = 4;
}
================================================
FILE: lamda/rpc/proxy.proto
================================================
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
import "application.proto";
// Supported Shadowsocks Ciphers (no ref)
enum ShadowCipher {
SS_AES_128_CFB = 0;
SS_AES_192_CFB = 1;
SS_AES_256_CFB = 2;
SS_AES_128_CTR = 3;
SS_AES_192_CTR = 4;
SS_AES_256_CTR = 5;
SS_CAMELLIA_128_CFB = 6;
SS_CAMELLIA_192_CFB = 7;
SS_CAMELLIA_256_CFB = 8;
SS_DES_CFB = 9;
SS_AES_128_GCM = 20;
SS_AES_192_GCM = 21;
SS_AES_256_GCM = 22;
SS_CHACHA20_IETF_POLY1305 = 23;
}
enum GproxyType {
SOCKS5 = 0;
HTTP_CONNECT = 1;
HTTP_RELAY = 2;
HTTPS_CONNECT = 3;
SHADOWSOCKS = 4;
}
message GproxyConfigRequest {
ApplicationInfo application = 1;
GproxyType type = 2;
string nameserver = 3;
string login = 4;
string password = 5;
string host = 6;
uint32 port = 7;
bool bypass_local_subnet = 8;
bool drop_udp = 9;
bool udp_proxy = 10;
bool dns_proxy = 11;
}
enum OpenVPNProto {
TCP = 0;
UDP = 1;
}
enum OpenVPNAuth {
SHA1 = 0;
SHA224 = 1;
SHA256 = 2;
SHA384 = 3;
SHA512 = 4;
RIPEMD160 = 5;
RSA_SHA1 = 6;
RSA_SHA224 = 7;
RSA_SHA256 = 8;
RSA_SHA384 = 9;
RSA_SHA512 = 10;
RSA_RIPEMD160 = 11;
}
enum OpenVPNCipher {
AES_128_GCM = 0;
AES_256_GCM = 1;
CHACHA20_POLY1305= 2;
AES_128_CBC = 3;
AES_256_CBC = 4;
}
enum OpenVPNKeyDirection {
// because 0 is a default value
// so we use 1 as key-direction 0
KEY_DIRECTION_NONE = 0;
KEY_DIRECTION_0 = 1;
KEY_DIRECTION_1 = 2;
}
enum OpenVPNEncryption {
TLS_NONE = 0;
TLS_AUTH = 1;
TLS_CRYPT = 2;
TLS_CRYPT_V2 = 3;
}
message OpenVPNConfigRequest {
bool all_traffic = 1;
OpenVPNProto proto = 2;
string host = 3;
uint32 port = 4;
OpenVPNCipher cipher = 5;
string ca = 6;
string cert = 7;
string key = 8;
OpenVPNEncryption tls_encryption = 9;
OpenVPNKeyDirection tls_key_direction = 10;
string tls_key = 11;
OpenVPNAuth auth = 12;
string login = 13;
string password = 14;
}
================================================
FILE: lamda/rpc/services.proto
================================================
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
import public "google/protobuf/empty.proto";
import public "google/protobuf/any.proto";
import public "types.proto";
import public "util.proto";
import public "shell.proto";
import public "policy.proto";
import public "debug.proto";
import public "settings.proto";
import public "status.proto";
import public "application.proto";
import public "uiautomator.proto";
import public "storage.proto";
import public "proxy.proto";
import public "file.proto";
import public "wifi.proto";
service Application {
rpc isForeground(ApplicationRequest) returns (Boolean) {}
rpc currentApplication(google.protobuf.Empty) returns (ApplicationInfo) {}
rpc enumerateAllPkgNames(google.protobuf.Empty) returns (ApplicationPkgNames) {}
rpc enumerateRunningProcesses(google.protobuf.Empty) returns (ApplicationProcesses) {}
rpc queryLaunchActivity(ApplicationRequest) returns (ApplicationActivityInfo) {}
rpc getPermissions(ApplicationRequest) returns (ApplicationPermissions) {}
rpc resetApplicationData(ApplicationRequest) returns (Boolean) {}
rpc deleteApplicationCache(ApplicationRequest) returns (Boolean) {}
rpc getLastActivities(Integer) returns (ApplicationActivityInfoList) {}
rpc startActivity(ApplicationActivityRequest) returns (Boolean) {}
rpc applicationInfo(ApplicationRequest) returns (ApplicationInfo) {}
rpc startApplication(ApplicationRequest) returns (Boolean) {}
rpc stopApplication(ApplicationRequest) returns (Boolean) {}
rpc uninstallApplication(ApplicationRequest) returns (Boolean) {}
rpc installFromLocalFile(ApplicationRequest) returns (ApplicationInfo) {}
rpc enableApplication(ApplicationRequest) returns (Boolean) {}
rpc disableApplication(ApplicationRequest) returns (Boolean) {}
rpc grantPermission(ApplicationRequest) returns (Boolean) {}
rpc revokePermission(ApplicationRequest) returns (Boolean) {}
rpc isPermissionGranted(ApplicationRequest) returns (Boolean) {}
rpc isInstalled(ApplicationRequest) returns (Boolean) {}
rpc addToDozeModeWhiteList(ApplicationRequest) returns (Boolean) {}
rpc removeFromDozeModeWhiteList(ApplicationRequest) returns (Boolean) {}
rpc getIdentifierByLabel(String) returns (String) {}
rpc callScript(HookRpcRequest) returns (HookRpcResponse) {}
rpc isScriptAlive(HookRequest) returns (Boolean) {}
rpc isScriptAttached(HookRequest) returns (Boolean) {}
rpc attachScript(HookRequest) returns (Boolean) {}
rpc detachScript(HookRequest) returns (Boolean) {}
}
service Debug {
rpc isAndroidDebugBridgeRunning(google.protobuf.Empty) returns (Boolean) {}
rpc installADBPubKey(ADBDConfigRequest) returns (Boolean) {}
rpc uninstallADBPubKey(ADBDConfigRequest) returns (Boolean) {}
rpc startAndroidDebugBridge(google.protobuf.Empty) returns (Boolean) {}
rpc stopAndroidDebugBridge(google.protobuf.Empty) returns (Boolean) {}
}
service Proxy {
rpc isOpenVPNRunning(google.protobuf.Empty) returns (Boolean) {}
rpc isGproxyRunning(google.protobuf.Empty) returns (Boolean) {}
rpc stopOpenVPN(google.protobuf.Empty) returns (Boolean) {}
rpc stopGproxy(google.protobuf.Empty) returns (Boolean) {}
rpc startOpenVPN(OpenVPNConfigRequest) returns (Boolean) {}
rpc startGproxy(GproxyConfigRequest) returns (Boolean) {}
}
service SelinuxPolicy {
rpc setEnforce(Boolean) returns (Integer) {}
rpc getEnforce(google.protobuf.Empty) returns (Integer) {}
rpc isEnabled(google.protobuf.Empty) returns (Boolean) {}
rpc policySetEnforce(String) returns (Boolean) {}
rpc policySetPermissive(String) returns (Boolean) {}
rpc policySetAllow(SelinuxPolicyRequest) returns (Boolean) {}
rpc policySetDisallow(SelinuxPolicyRequest) returns (Boolean) {}
rpc policyCreateDomain(String) returns (Boolean) {}
}
service Settings {
rpc putSettings(SettingsRequest) returns (Boolean) {}
rpc getSettings(SettingsRequest) returns (String) {}
}
service Shell {
rpc executeForeground(ShellRequest) returns (ShellResult) {}
rpc executeBackground(ShellRequest) returns (ShellTask) {}
rpc isBackgroundFinished(ShellTask) returns (Boolean) {}
rpc killBackground(ShellTask) returns (Boolean) {}
}
service Status {
rpc getBootTime(google.protobuf.Empty) returns (Integer) {}
rpc getDiskUsage(String) returns (DiskUsage) {}
rpc getBatteryInfo(google.protobuf.Empty) returns (BatteryInfo) {}
rpc getCpuInfo(google.protobuf.Empty) returns (CpuInfo) {}
rpc getOverallDiskIOInfo(google.protobuf.Empty) returns (DiskIOInfo) {}
rpc getOverallNetIOInfo(google.protobuf.Empty) returns (NetIOInfo) {}
rpc getMemInfo(google.protobuf.Empty) returns (MemInfo) {}
rpc getUserDataDiskIOInfo(google.protobuf.Empty) returns (DiskIOInfo) {}
rpc getNetIOInfo(String) returns (NetIOInfo) {}
}
service UiAutomator {
rpc click(ClickPointRequest) returns (Boolean) {}
rpc drag(DragPointRequest) returns (Boolean) {}
rpc swipe(SwipePointRequest) returns (Boolean) {}
rpc swipePoints(SwipePointsRequest) returns (Boolean) {}
rpc openNotification(google.protobuf.Empty) returns (Boolean) {}
rpc openQuickSettings(google.protobuf.Empty) returns (Boolean) {}
rpc wakeUp(google.protobuf.Empty) returns (Boolean) {}
rpc sleep(google.protobuf.Empty) returns (Boolean) {}
rpc isScreenOn(google.protobuf.Empty) returns (Boolean) {}
rpc isScreenLocked(google.protobuf.Empty) returns (Boolean) {}
rpc setClipboard(ClipboardRequest) returns (Boolean) {}
rpc getClipboard(google.protobuf.Empty) returns (String) {}
rpc freezeRotation(Boolean) returns (Boolean) {}
rpc setOrientation(OrientationRequest) returns (Boolean) {}
rpc pressKey(PressKeyRequest) returns (Boolean) {}
rpc pressKeyCode(PressKeyRequest) returns (Boolean) {}
rpc deviceInfo(google.protobuf.Empty) returns (DeviceInfo) {}
rpc takeScreenshot(TakeScreenshotRequest) returns (Bytes) {}
rpc dumpWindowHierarchy(Boolean) returns (Bytes) {}
rpc waitForIdle(Integer) returns (Boolean) {}
rpc selectorTakeScreenshot(SelectorTakeScreenshotRequest) returns (Bytes) {}
rpc selectorGetText(SelectorOnlyRequest) returns (String) {}
rpc selectorClearTextField(SelectorOnlyRequest) returns (Boolean) {}
rpc selectorSetText(SelectorSetTextRequest) returns (Boolean) {}
rpc selectorClick(SelectorClickRequest) returns (Boolean) {}
rpc selectorClickExists(SelectorClickRequest) returns (Boolean) {}
rpc selectorLongClick(SelectorClickRequest) returns (Boolean) {}
rpc selectorExists(SelectorOnlyRequest) returns (Boolean) {}
rpc getLastToast(google.protobuf.Empty) returns (ToastInfo) {}
rpc selectorObjInfo(SelectorOnlyRequest) returns (ObjInfo) {}
rpc selectorObjInfoOfAllInstances(SelectorOnlyRequest) returns (ObjInfoList) {}
rpc selectorCount(SelectorOnlyRequest) returns (Integer) {}
rpc selectorDragTo(SelectorDragToRequest) returns (Boolean) {}
rpc selectorWaitForExists(SelectorWaitRequest) returns (Boolean) {}
rpc selectorWaitUntilGone(SelectorWaitRequest) returns (Boolean) {}
rpc selectorSwipe(SelectorSwipeRequest) returns (Boolean) {}
rpc selectorFlingToBeginning(SelectorFlingRequest) returns (Boolean) {}
rpc selectorFlingToEnd(SelectorFlingRequest) returns (Boolean) {}
rpc selectorFlingBackward(SelectorFlingRequest) returns (Boolean) {}
rpc selectorFlingForward(SelectorFlingRequest) returns (Boolean) {}
rpc selectorScrollToBeginning(SelectorScrollRequest) returns (Boolean) {}
rpc selectorScrollToEnd(SelectorScrollRequest) returns (Boolean) {}
rpc selectorScrollBackward(SelectorScrollRequest) returns (Boolean) {}
rpc selectorScrollForward(SelectorScrollRequest) returns (Boolean) {}
rpc selectorScrollTo(SelectorScrollRequest) returns (Boolean) {}
rpc selectorPinchOut(SelectorPinchRequest) returns (Boolean) {}
rpc selectorPinchIn(SelectorPinchRequest) returns (Boolean) {}
rpc setWatcherLoopEnabled(Boolean) returns (Boolean) {}
rpc getWatcherLoopEnabled(google.protobuf.Empty) returns (Boolean) {}
rpc getWatcherTriggeredCount(String) returns (Integer) {}
rpc resetWatcherTriggeredCount(String) returns (Boolean) {}
rpc removeWatcher(String) returns (Boolean) {}
rpc getAppliedWatchers(google.protobuf.Empty) returns (WatcherNameList) {}
rpc registerClickUiObjectWatcher(WatcherRegistRequest) returns (Boolean) {}
rpc registerPressKeysWatcher(WatcherRegistRequest) returns (Boolean) {}
rpc registerNoneOpWatcher(WatcherRegistRequest) returns (Boolean) {}
rpc findSimilarImage(FindImageRequest) returns (FindImageResponse) {}
}
service Wifi {
rpc status(google.protobuf.Empty) returns (WifiStatus) {}
rpc blacklistAdd(String) returns (Boolean) {}
rpc blacklistClear(google.protobuf.Empty) returns (Boolean) {}
rpc blacklistAll(google.protobuf.Empty) returns (WifiBlacklist) {}
rpc scan(Boolean) returns (Boolean) {}
rpc scanResults(google.protobuf.Empty) returns (ScanResult) {}
rpc listNetworks(google.protobuf.Empty) returns (NetworkList) {}
rpc selectNetwork(Network) returns (Boolean) {}
rpc enableNetwork(Network) returns (Boolean) {}
rpc disableNetwork(Network) returns (Boolean) {}
rpc addNetwork(google.protobuf.Empty) returns (Network) {}
rpc removeNetwork(Network) returns (Boolean) {}
rpc setNetworkConfig(NetworkConfig) returns (Boolean) {}
rpc getNetworkConfig(NetworkConfig) returns (NetworkConfig) {}
rpc disconnect(google.protobuf.Empty) returns (Boolean) {}
rpc reconnect(google.protobuf.Empty) returns (Boolean) {}
rpc setConfig(WifiConfig) returns (Boolean) {}
rpc setAutoConnect(Boolean) returns (Boolean) {}
rpc saveConfig(google.protobuf.Empty) returns (Boolean) {}
rpc getMacAddr(google.protobuf.Empty) returns (String) {}
rpc signalPoll(google.protobuf.Empty) returns (SignalPoll) {}
}
service Util {
rpc recordTouch(google.protobuf.Empty) returns (TouchSequence) {}
rpc performTouch(PerformTouchRequest) returns (Boolean) {}
rpc serverInfo(google.protobuf.Empty) returns (ServerInfoResponse) {}
rpc reboot(google.protobuf.Empty) returns (Boolean) {}
rpc beepBeep(google.protobuf.Empty) returns (Boolean) {}
rpc isCACertificateInstalled(CertifiRequest) returns (Boolean) {}
rpc installCACertificate(CertifiRequest) returns (Boolean) {}
rpc uninstallCACertificate(CertifiRequest) returns (Boolean) {}
rpc shutdown(google.protobuf.Empty) returns (Boolean) {}
rpc showToast(ShowToastRequest) returns (Boolean) {}
rpc reload(Boolean) returns (Boolean) {}
rpc exit(google.protobuf.Empty) returns (Boolean) {}
rpc setProp(SetPropRequest) returns (Boolean) {}
rpc getProp(String) returns (String) {}
rpc hexPatch(HexPatchRequest) returns (HexPatchResponse) {}
rpc playAudio(PlayAudioRequest) returns (Boolean) {}
}
service File {
rpc uploadFile(stream FileRequest) returns (FileStat) {}
rpc downloadFile(FileRequest) returns (stream FileDataResponse) {}
rpc deleteFile(FileRequest) returns (Boolean) {}
rpc fileChmod(FileRequest) returns (FileStat) {}
rpc fileStat(FileRequest) returns (FileStat) {}
}
service Lock {
rpc releaseLock(google.protobuf.Empty) returns (Boolean) {}
rpc getSessionToken(google.protobuf.Empty) returns (String) {}
rpc acquireLock(Integer) returns (Boolean) {}
rpc refreshLock(Integer) returns (Boolean) {}
}
service Storage {
rpc clearAll(StorageRequest) returns (Boolean) {}
rpc clearContainer(StorageRequest) returns (Boolean) {}
rpc exists(StorageRequest) returns (Boolean) {}
rpc delete(StorageRequest) returns (Boolean) {}
rpc expire(StorageRequest) returns (Boolean) {}
rpc ttl(StorageRequest) returns (Integer) {}
rpc set(StorageRequest) returns (Boolean) {}
rpc setnx(StorageRequest) returns (Boolean) {}
rpc setex(StorageRequest) returns (Boolean) {}
rpc get(StorageRequest) returns (Bytes) {}
}
================================================
FILE: lamda/rpc/settings.proto
================================================
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
enum Group {
GROUP_SYSTEM = 0;
GROUP_SECURE = 1;
GROUP_GLOBAL = 2;
}
message SettingsRequest {
Group group = 1;
string name = 2;
string value = 3;
}
================================================
FILE: lamda/rpc/shell.proto
================================================
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
message ShellRequest {
string tid = 1;
string name = 2;
string script = 3;
int32 timeout = 4;
}
message ShellResult {
int32 exitstatus = 1;
bytes stdout = 2;
bytes stderr = 3;
}
message ShellTask {
string tid = 1;
string name = 2;
int32 pid = 3;
}
================================================
FILE: lamda/rpc/status.proto
================================================
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
message BatteryInfo {
bool batt_charging = 1;
int32 batt_percent = 2;
float batt_temperature = 3;
}
message CpuInfo {
float cpu_percent = 1;
int32 cpu_count = 2;
float cpu_freq_current = 3;
float cpu_freq_max = 4;
float cpu_freq_min = 5;
float cpu_times_user = 6;
float cpu_times_system = 7;
float cpu_times_idle = 8;
}
message DiskUsage {
int64 disk_total = 1;
int64 disk_used = 2;
int64 disk_free = 3;
float disk_percent = 4;
}
message DiskIOInfo {
int64 disk_io_read_bytes = 1;
int64 disk_io_read_count = 2;
int64 disk_io_write_bytes = 3;
int64 disk_io_write_count = 4;
int64 disk_io_read_time = 5;
int64 disk_io_write_time = 6;
int64 disk_io_busy_time = 7;
}
message NetIOInfo {
int64 net_io_bytes_sent = 1;
int64 net_io_packets_sent = 2;
int64 net_io_bytes_recv = 3;
int64 net_io_packets_recv = 4;
int64 net_io_dropin = 5;
int64 net_io_dropout = 6;
int64 net_io_errin = 7;
int64 net_io_errout = 8;
}
message MemInfo {
int64 mem_total = 1;
int64 mem_available = 2;
float mem_percent = 3;
int64 mem_used = 4;
int64 mem_free = 5;
int64 mem_active = 6;
int64 mem_inactive = 7;
int64 mem_buffers = 8;
int64 mem_cached = 9;
int64 mem_shared = 10;
int64 mem_slab = 11;
}
================================================
FILE: lamda/rpc/storage.proto
================================================
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
message StorageRequest {
string container = 1;
string key = 2;
bytes value = 3;
uint32 ttl = 4;
}
================================================
FILE: lamda/rpc/types.proto
================================================
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
message Boolean {
bool value = 1;
}
message Integer {
int64 value = 1;
}
message String {
string value = 1;
}
message Bytes {
bytes value = 1;
}
message Empty {
}
================================================
FILE: lamda/rpc/uiautomator.proto
================================================
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
message Point {
uint32 x = 1;
uint32 y = 2;
}
message Bound {
uint32 top = 1;
uint32 left = 2;
uint32 bottom = 3;
uint32 right = 4;
}
enum Orientation {
ORIEN_NATURE = 0;
ORIEN_LEFT = 1;
ORIEN_UPSIDEDOWN = 2;
ORIEN_RIGHT = 3;
}
enum Key {
KEY_BACK = 0;
KEY_CAMERA = 1;
KEY_CENTER = 2;
KEY_DELETE = 3;
KEY_DOWN = 4;
KEY_ENTER = 5;
KEY_HOME = 6;
KEY_LEFT = 7;
KEY_MENU = 8;
KEY_POWER = 9;
KEY_RECENT = 10;
KEY_RIGHT = 11;
KEY_SEARCH = 12;
KEY_UP = 13;
KEY_VOLUME_DOWN = 14;
KEY_VOLUME_MUTE = 15;
KEY_VOLUME_UP = 16;
}
enum KeyCode {
KEYCODE_UNKNOWN = 0;
KEYCODE_SOFT_LEFT = 1;
KEYCODE_SOFT_RIGHT = 2;
KEYCODE_HOME = 3;
KEYCODE_BACK = 4;
KEYCODE_CALL = 5;
KEYCODE_ENDCALL = 6;
KEYCODE_0 = 7;
KEYCODE_1 = 8;
KEYCODE_2 = 9;
KEYCODE_3 = 10;
KEYCODE_4 = 11;
KEYCODE_5 = 12;
KEYCODE_6 = 13;
KEYCODE_7 = 14;
KEYCODE_8 = 15;
KEYCODE_9 = 16;
KEYCODE_STAR = 17;
KEYCODE_POUND = 18;
KEYCODE_DPAD_UP = 19;
KEYCODE_DPAD_DOWN = 20;
KEYCODE_DPAD_LEFT = 21;
KEYCODE_DPAD_RIGHT = 22;
KEYCODE_DPAD_CENTER = 23;
KEYCODE_VOLUME_UP = 24;
KEYCODE_VOLUME_DOWN = 25;
KEYCODE_POWER = 26;
KEYCODE_CAMERA = 27;
KEYCODE_CLEAR = 28;
KEYCODE_A = 29;
KEYCODE_B = 30;
KEYCODE_C = 31;
KEYCODE_D = 32;
KEYCODE_E = 33;
KEYCODE_F = 34;
KEYCODE_G = 35;
KEYCODE_H = 36;
KEYCODE_I = 37;
KEYCODE_J = 38;
KEYCODE_K = 39;
KEYCODE_L = 40;
KEYCODE_M = 41;
KEYCODE_N = 42;
KEYCODE_O = 43;
KEYCODE_P = 44;
KEYCODE_Q = 45;
KEYCODE_R = 46;
KEYCODE_S = 47;
KEYCODE_T = 48;
KEYCODE_U = 49;
KEYCODE_V = 50;
KEYCODE_W = 51;
KEYCODE_X = 52;
KEYCODE_Y = 53;
KEYCODE_Z = 54;
KEYCODE_COMMA = 55;
KEYCODE_PERIOD = 56;
KEYCODE_ALT_LEFT = 57;
KEYCODE_ALT_RIGHT = 58;
KEYCODE_SHIFT_LEFT = 59;
KEYCODE_SHIFT_RIGHT = 60;
KEYCODE_TAB = 61;
KEYCODE_SPACE = 62;
KEYCODE_SYM = 63;
KEYCODE_EXPLORER = 64;
KEYCODE_ENVELOPE = 65;
KEYCODE_ENTER = 66;
KEYCODE_DEL = 67;
KEYCODE_GRAVE = 68;
KEYCODE_MINUS = 69;
KEYCODE_EQUALS = 70;
KEYCODE_LEFT_BRACKET = 71;
KEYCODE_RIGHT_BRACKET = 72;
KEYCODE_BACKSLASH = 73;
KEYCODE_SEMICOLON = 74;
KEYCODE_APOSTROPHE = 75;
KEYCODE_SLASH = 76;
KEYCODE_AT = 77;
KEYCODE_NUM = 78;
KEYCODE_HEADSETHOOK = 79;
KEYCODE_FOCUS = 80;
KEYCODE_PLUS = 81;
KEYCODE_MENU = 82;
KEYCODE_NOTIFICATION = 83;
KEYCODE_SEARCH = 84;
KEYCODE_MEDIA_PLAY_PAUSE= 85;
KEYCODE_MEDIA_STOP = 86;
KEYCODE_MEDIA_NEXT = 87;
KEYCODE_MEDIA_PREVIOUS = 88;
KEYCODE_MEDIA_REWIND = 89;
KEYCODE_MEDIA_FAST_FORWARD = 90;
KEYCODE_MUTE = 91;
KEYCODE_PAGE_UP = 92;
KEYCODE_PAGE_DOWN = 93;
KEYCODE_PICTSYMBOLS = 94;
KEYCODE_SWITCH_CHARSET = 95;
KEYCODE_BUTTON_A = 96;
KEYCODE_BUTTON_B = 97;
KEYCODE_BUTTON_C = 98;
KEYCODE_BUTTON_X = 99;
KEYCODE_BUTTON_Y = 100;
KEYCODE_BUTTON_Z = 101;
KEYCODE_BUTTON_L1 = 102;
KEYCODE_BUTTON_R1 = 103;
KEYCODE_BUTTON_L2 = 104;
KEYCODE_BUTTON_R2 = 105;
KEYCODE_BUTTON_THUMBL = 106;
KEYCODE_BUTTON_THUMBR = 107;
KEYCODE_BUTTON_START = 108;
KEYCODE_BUTTON_SELECT = 109;
KEYCODE_BUTTON_MODE = 110;
KEYCODE_ESCAPE = 111;
KEYCODE_FORWARD_DEL = 112;
KEYCODE_CTRL_LEFT = 113;
KEYCODE_CTRL_RIGHT = 114;
KEYCODE_CAPS_LOCK = 115;
KEYCODE_SCROLL_LOCK = 116;
KEYCODE_META_LEFT = 117;
KEYCODE_META_RIGHT = 118;
KEYCODE_FUNCTION = 119;
KEYCODE_SYSRQ = 120;
KEYCODE_BREAK = 121;
KEYCODE_MOVE_HOME = 122;
KEYCODE_MOVE_END = 123;
KEYCODE_INSERT = 124;
KEYCODE_FORWARD = 125;
KEYCODE_MEDIA_PLAY = 126;
KEYCODE_MEDIA_PAUSE = 127;
KEYCODE_MEDIA_CLOSE = 128;
KEYCODE_MEDIA_EJECT = 129;
KEYCODE_MEDIA_RECORD = 130;
KEYCODE_F1 = 131;
KEYCODE_F2 = 132;
KEYCODE_F3 = 133;
KEYCODE_F4 = 134;
KEYCODE_F5 = 135;
KEYCODE_F6 = 136;
KEYCODE_F7 = 137;
KEYCODE_F8 = 138;
KEYCODE_F9 = 139;
KEYCODE_F10 = 140;
KEYCODE_F11 = 141;
KEYCODE_F12 = 142;
KEYCODE_NUM_LOCK = 143;
KEYCODE_NUMPAD_0 = 144;
KEYCODE_NUMPAD_1 = 145;
KEYCODE_NUMPAD_2 = 146;
KEYCODE_NUMPAD_3 = 147;
KEYCODE_NUMPAD_4 = 148;
KEYCODE_NUMPAD_5 = 149;
KEYCODE_NUMPAD_6 = 150;
KEYCODE_NUMPAD_7 = 151;
KEYCODE_NUMPAD_8 = 152;
KEYCODE_NUMPAD_9 = 153;
KEYCODE_NUMPAD_DIVIDE = 154;
KEYCODE_NUMPAD_MULTIPLY = 155;
KEYCODE_NUMPAD_SUBTRACT = 156;
KEYCODE_NUMPAD_ADD = 157;
KEYCODE_NUMPAD_DOT = 158;
KEYCODE_NUMPAD_COMMA = 159;
KEYCODE_NUMPAD_ENTER = 160;
KEYCODE_NUMPAD_EQUALS = 161;
KEYCODE_NUMPAD_LEFT_PAREN = 162;
KEYCODE_NUMPAD_RIGHT_PAREN = 163;
KEYCODE_VOLUME_MUTE = 164;
KEYCODE_INFO = 165;
KEYCODE_CHANNEL_UP = 166;
KEYCODE_CHANNEL_DOWN = 167;
KEYCODE_ZOOM_IN = 168;
KEYCODE_ZOOM_OUT = 169;
KEYCODE_TV = 170;
KEYCODE_WINDOW = 171;
KEYCODE_GUIDE = 172;
KEYCODE_DVR = 173;
KEYCODE_BOOKMARK = 174;
KEYCODE_CAPTIONS = 175;
KEYCODE_SETTINGS = 176;
KEYCODE_TV_POWER = 177;
KEYCODE_TV_INPUT = 178;
KEYCODE_STB_POWER = 179;
KEYCODE_STB_INPUT = 180;
KEYCODE_AVR_POWER = 181;
KEYCODE_AVR_INPUT = 182;
KEYCODE_PROG_RED = 183;
KEYCODE_PROG_GREEN = 184;
KEYCODE_PROG_YELLOW = 185;
KEYCODE_PROG_BLUE = 186;
KEYCODE_APP_SWITCH = 187;
KEYCODE_BUTTON_1 = 188;
KEYCODE_BUTTON_2 = 189;
KEYCODE_BUTTON_3 = 190;
KEYCODE_BUTTON_4 = 191;
KEYCODE_BUTTON_5 = 192;
KEYCODE_BUTTON_6 = 193;
KEYCODE_BUTTON_7 = 194;
KEYCODE_BUTTON_8 = 195;
KEYCODE_BUTTON_9 = 196;
KEYCODE_BUTTON_10 = 197;
KEYCODE_BUTTON_11 = 198;
KEYCODE_BUTTON_12 = 199;
KEYCODE_BUTTON_13 = 200;
KEYCODE_BUTTON_14 = 201;
KEYCODE_BUTTON_15 = 202;
KEYCODE_BUTTON_16 = 203;
KEYCODE_LANGUAGE_SWITCH = 204;
KEYCODE_MANNER_MODE = 205;
KEYCODE_3D_MODE = 206;
KEYCODE_CONTACTS = 207;
KEYCODE_CALENDAR = 208;
KEYCODE_MUSIC = 209;
KEYCODE_CALCULATOR = 210;
KEYCODE_ZENKAKU_HANKAKU = 211;
KEYCODE_EISU = 212;
KEYCODE_MUHENKAN = 213;
KEYCODE_HENKAN = 214;
KEYCODE_KATAKANA_HIRAGANA = 215;
KEYCODE_YEN = 216;
KEYCODE_RO = 217;
KEYCODE_KANA = 218;
KEYCODE_ASSIST = 219;
KEYCODE_BRIGHTNESS_DOWN = 220;
KEYCODE_BRIGHTNESS_UP = 221;
KEYCODE_MEDIA_AUDIO_TRACK = 222;
KEYCODE_SLEEP = 223;
KEYCODE_WAKEUP = 224;
KEYCODE_PAIRING = 225;
KEYCODE_MEDIA_TOP_MENU = 226;
KEYCODE_11 = 227;
KEYCODE_12 = 228;
KEYCODE_LAST_CHANNEL = 229;
KEYCODE_TV_DATA_SERVICE = 230;
KEYCODE_VOICE_ASSIST = 231;
KEYCODE_TV_RADIO_SERVICE = 232;
KEYCODE_TV_TELETEXT = 233;
KEYCODE_TV_NUMBER_ENTRY = 234;
KEYCODE_TV_TERRESTRIAL_ANALOG = 235;
KEYCODE_TV_TERRESTRIAL_DIGITAL = 236;
KEYCODE_TV_SATELLITE = 237;
KEYCODE_TV_SATELLITE_BS = 238;
KEYCODE_TV_SATELLITE_CS = 239;
KEYCODE_TV_SATELLITE_SERVICE = 240;
KEYCODE_TV_NETWORK = 241;
KEYCODE_TV_ANTENNA_CABLE = 242;
KEYCODE_TV_INPUT_HDMI_1 = 243;
KEYCODE_TV_INPUT_HDMI_2 = 244;
KEYCODE_TV_INPUT_HDMI_3 = 245;
KEYCODE_TV_INPUT_HDMI_4 = 246;
KEYCODE_TV_INPUT_COMPOSITE_1 = 247;
KEYCODE_TV_INPUT_COMPOSITE_2 = 248;
KEYCODE_TV_INPUT_COMPONENT_1 = 249;
KEYCODE_TV_INPUT_COMPONENT_2 = 250;
KEYCODE_TV_INPUT_VGA_1 = 251;
KEYCODE_TV_AUDIO_DESCRIPTION = 252;
KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP = 253;
KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN = 254;
KEYCODE_TV_ZOOM_MODE = 255;
KEYCODE_TV_CONTENTS_MENU = 256;
KEYCODE_TV_MEDIA_CONTEXT_MENU = 257;
KEYCODE_TV_TIMER_PROGRAMMING = 258;
KEYCODE_HELP = 259;
KEYCODE_NAVIGATE_PREVIOUS = 260;
KEYCODE_NAVIGATE_NEXT = 261;
KEYCODE_NAVIGATE_IN = 262;
KEYCODE_NAVIGATE_OUT = 263;
KEYCODE_STEM_PRIMARY = 264;
KEYCODE_STEM_1 = 265;
KEYCODE_STEM_2 = 266;
KEYCODE_STEM_3 = 267;
KEYCODE_DPAD_UP_LEFT = 268;
KEYCODE_DPAD_DOWN_LEFT = 269;
KEYCODE_DPAD_UP_RIGHT = 270;
KEYCODE_DPAD_DOWN_RIGHT = 271;
KEYCODE_MEDIA_SKIP_FORWARD = 272;
KEYCODE_MEDIA_SKIP_BACKWARD = 273;
KEYCODE_MEDIA_STEP_FORWARD = 274;
KEYCODE_MEDIA_STEP_BACKWARD = 275;
KEYCODE_SOFT_SLEEP = 276;
KEYCODE_CUT = 277;
KEYCODE_COPY = 278;
KEYCODE_PASTE = 279;
KEYCODE_SYSTEM_NAVIGATION_UP = 280;
KEYCODE_SYSTEM_NAVIGATION_DOWN = 281;
KEYCODE_SYSTEM_NAVIGATION_LEFT = 282;
KEYCODE_SYSTEM_NAVIGATION_RIGHT = 283;
KEYCODE_ALL_APPS = 284;
KEYCODE_REFRESH = 285;
KEYCODE_THUMBS_UP = 286;
KEYCODE_THUMBS_DOWN = 287;
KEYCODE_PROFILE_SWITCH = 288;
}
enum MetaKeyCode {
META_UNKNOWN = 0;
META_SHIFT_ON = 1;
META_ALT_ON = 2;
META_SYM_ON = 4;
META_FUNCTION_ON = 8;
META_ALT_LEFT_ON = 16;
META_ALT_RIGHT_ON = 32;
META_ALT_MASK = 50;
META_SHIFT_LEFT_ON = 64;
META_SHIFT_RIGHT_ON = 128;
META_SHIFT_MASK = 193;
META_CTRL_ON = 4096;
META_CTRL_LEFT_ON = 8192;
META_CTRL_RIGHT_ON = 16384;
META_CTRL_MASK = 28672;
META_META_ON = 65536;
META_META_LEFT_ON = 131072;
META_META_RIGHT_ON = 262144;
META_META_MASK = 458752;
META_CAPS_LOCK_ON = 1048576;
META_NUM_LOCK_ON = 2097152;
META_SCROLL_LOCK_ON = 4194304;
}
enum Corner {
COR_CENTER = 0;
COR_BOTTOMRIGHT = 1;
COR_TOPLEFT = 2;
}
enum Direction {
DIR_UP = 0;
DIR_LEFT = 1;
DIR_DOWN = 2;
DIR_RIGHT = 3;
}
message ObjInfo {
Bound bounds = 1;
bool checkable = 2;
bool checked = 3;
uint32 childCount = 4;
string className = 5;
bool clickable = 6;
string contentDescription = 7;
bool enabled = 8;
bool focusable = 9;
bool focused = 10;
bool longClickable = 11;
string packageName = 12;
string resourceName = 13;
bool scrollable = 14;
bool selected = 15;
string text = 16;
Bound visibleBounds = 17;
}
message ToastInfo {
uint64 timestamp = 1;
string package = 2;
string message = 3;
}
message ObjInfoList {
repeated ObjInfo objects = 1;
}
message Selector {
uint32 mask = 1;
string text = 2;
string textContains = 3;
string textMatches = 4;
string textStartsWith = 5;
string className = 6;
string classNameMatches = 7;
string description = 8;
string descriptionContains = 9;
string descriptionMatches = 10;
string descriptionStartsWith = 11;
bool checkable = 12;
bool checked = 13;
bool clickable = 14;
bool longClickable = 15;
bool scrollable = 16;
bool enabled = 17;
bool focusable = 18;
bool focused = 19;
bool selected = 20;
string packageName = 21;
string packageNameMatches = 22;
string resourceId = 23;
string resourceIdMatches = 24;
uint32 index = 25;
uint32 instance = 26;
repeated string childOrSibling = 27;
repeated Selector childOrSiblingSelector = 28;
repeated string fields = 50;
}
message DeviceInfo {
string productName = 1;
uint32 sdkInt = 2;
uint32 displayHeight = 3;
uint32 displayRotation = 4;
uint32 displaySizeDpX = 5;
uint32 displaySizeDpY = 6;
uint32 displayWidth = 7;
bool screenLocked = 8;
bool screenOn = 9;
bool naturalOrientation = 10;
string currentPackageName = 11;
}
message SelectorTakeScreenshotRequest {
Selector selector = 1;
uint32 quality = 3;
}
message SelectorSetTextRequest {
Selector selector = 1;
string text = 3;
}
message SelectorClickRequest {
Selector selector = 1;
Corner corner = 3;
}
message SelectorOnlyRequest {
Selector selector = 1;
}
message SelectorDragToRequest {
Selector selector = 1;
oneof _target {
Selector target = 3;
Point point = 4;
}
uint32 step = 5;
}
message SelectorWaitRequest {
Selector selector = 1;
uint32 timeout = 3;
}
message SelectorSwipeRequest {
Selector selector = 1;
Direction direction = 3;
uint32 step = 4;
}
message SelectorFlingRequest {
Selector selector = 1;
bool vertical = 3;
uint32 maxSwipes = 4;
}
message SelectorScrollRequest {
Selector selector = 1;
Selector target = 2;
bool vertical = 3;
uint32 maxSwipes = 4;
uint32 step = 5;
}
message SelectorPinchRequest {
Selector selector = 1;
uint32 percent = 3;
uint32 step = 4;
}
message ClickPointRequest {
Point point = 1;
}
message DragPointRequest {
Point A = 1;
Point B = 2;
uint32 step = 3;
}
message SwipePointRequest {
Point A = 1;
Point B = 2;
uint32 step = 3;
}
message SwipePointsRequest {
repeated Point points = 1;
uint32 step = 2;
}
message OrientationRequest {
Orientation orientation = 1;
}
message PressKeyRequest {
Key key = 1;
uint32 code = 2;
uint32 meta = 3;
}
message TakeScreenshotRequest {
Bound bound = 1;
uint32 quality = 2;
}
message ClipboardRequest {
string ID = 1;
string value = 2;
}
message WatcherNameList {
repeated string watchers = 1;
}
message WatcherRegistRequest {
string name = 1;
repeated Selector selectors = 2;
oneof _target {
Selector target = 3;
Key key = 4;
}
}
enum FindImageMethod {
FIM_TEMPLATE = 0;
FIM_FEATURE = 1;
}
enum FindImageArea {
FIA_WHOLE_SCREEN = 0;
FIA_LEFT = 1;
FIA_TOP_LEFT = 2;
FIA_TOP = 3;
FIA_TOP_RIGHT = 4;
FIA_RIGHT = 5;
FIA_BOTTOM_RIGHT = 6;
FIA_BOTTOM = 7;
FIA_BOTTOM_LEFT = 8;
}
message FindImageRequest {
bytes partial = 1;
FindImageMethod method = 2;
oneof _area {
FindImageArea area = 3;
Bound bound = 4;
}
uint32 distance = 5;
float threshold = 6;
float scale = 7;
}
message FindImageResponse {
repeated Bound bounds = 1;
}
================================================
FILE: lamda/rpc/util.proto
================================================
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
enum ToastDuration {
TD_SHORT = 0;
TD_LONG = 1;
}
enum AudioStreamType {
AST_ALARM = 0;
AST_MEDIA = 1;
AST_NOTIFICATION = 2;
AST_RING = 3;
AST_SYSTEM = 4;
AST_VOICE = 5;
}
message TouchDown {
int32 tid = 1;
int32 x = 2;
int32 y = 3;
int32 pressure = 4;
}
message TouchUp {
int32 tid = 1;
}
message TouchMove {
int32 tid = 1;
int32 x = 2;
int32 y = 3;
int32 pressure = 4;
}
message TouchWait {
uint32 wait = 1;
}
message TouchAction {
oneof action {
TouchDown down = 1;
TouchMove move = 2;
TouchWait wait = 3;
TouchUp up = 4;
}
}
message TouchSequence {
repeated TouchAction sequence = 1;
}
message PerformTouchRequest {
TouchSequence sequence = 1;
bool wait = 2;
}
message SetPropRequest {
string name = 1;
string value = 2;
}
message CertifiRequest {
bytes cert = 1;
}
message ShowToastRequest {
string text = 1;
ToastDuration duration = 2;
}
message ServerInfoResponse {
string uniqueId = 1;
string version = 2;
string architecture = 3;
uint64 uptime = 4;
bool secure = 5;
}
message HexPatchRequest {
string pattern = 1;
string replacement = 2;
string path = 3;
int32 maxreplace = 4;
bool dryrun = 5;
}
message HexPatchItem {
string path = 1;
int32 index = 2;
uint64 offset = 3;
}
message HexPatchResponse {
int32 count = 1;
repeated HexPatchItem replaces = 2;
}
message PlayAudioRequest {
string file = 1;
AudioStreamType type = 2;
int32 loop = 3;
int32 interval = 4;
}
================================================
FILE: lamda/rpc/wifi.proto
================================================
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
message WifiStatus {
string id = 1;
string address = 2;
string bssid = 3;
string freq = 4;
string group_cipher = 5;
string ip_address = 6;
string key_mgmt = 7;
string mode = 8;
string pairwise_cipher = 9;
string ssid = 10;
string wifi_generation = 11;
string wpa_state = 12;
}
message SignalPoll {
string RSSI = 1;
string LINKSPEED = 2;
string NOISE = 3;
string FREQUENCY = 4;
string WIDTH = 5;
string AVG_RSSI = 6;
string AVG_BEACON_RSSI = 7;
string CENTER_FRQ1 = 8;
}
message WifiInfo {
string id = 1;
string bssid = 2;
string ssid = 3;
string freq = 4;
string noise = 5;
string level = 6;
string tsf = 7;
string flags = 8;
}
message ScanResult {
repeated WifiInfo stations = 1;
}
message Network {
int32 nid = 1;
string bssid = 2;
string ssid = 3;
string flags = 4;
}
message NetworkList {
repeated Network networks = 1;
}
message NetworkConfig {
Network network = 1;
string name = 2;
string value = 3;
}
message WifiConfig {
string name = 1;
string value = 2;
}
message WifiBlacklist {
repeated string bssids = 1;
}
================================================
FILE: lamda/types.py
================================================
# Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
#
# Distributed under MIT license.
# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
import io
import codecs
__all__ = ["AttributeDict", "BytesIO"]
class AttributeDict(dict):
def __getattr__(self, attr):
return self[attr]
def __setattr__(self, attr, value):
self[attr] = value
def remove(self, key):
key in self and self.pop(key)
class BytesIO(io.BytesIO):
@classmethod
def decode_from(cls, data, encoding):
return cls(codecs.decode(data, encoding))
def encode(self, encoding):
return codecs.encode(self.getvalue(), encoding)
def decode(self, encoding):
return codecs.decode(self.getvalue(), encoding)
def save(self, fpath):
with open(fpath, "wb") as fd:
return fd.write(self.getvalue())
@classmethod
def load(cls, fpath):
with open(fpath, "rb") as fd:
return cls(fd.read())
================================================
FILE: properties.local.example
================================================
# properties.local configuration file
#
# DO NOT DIRECTLY COPY THIS FILE, PLEASE SELECT THE NECESSARY CONFIGURATION LINES TO CREATE A NEW FILE
#
# This file is the LAMDA service configuration file. You can use
# it to automatically start some services when LAMDA starts.
# The file should be located in /data or /data/usr (this directory
# will not exist before the first startup, please create it if it does not exist).
# Lines starting with # and ; are comment lines and will be ignored.
# Please ensure that each line follows the format a=b and does not contain spaces.
# If there are duplicate configuration items across multiple files,
# the last duplicate will be used.
# Set the default listening port for LAMDA.
port=65000
# You can change the name displayed on the remote desktop for
# LAMDA to any name you prefer (up to 10 characters).
brandname=FIRERPA
# Set the LAMDA certificate. It will encrypt your remote desktop
# and API traffic, and enable password authentication. You can obtain
# the following configuration value by running 'base64 -w0 lamda.pem'.
# It is the base64-encoded certificate.
cert=TEFNREEgU1NMIENFUlRJRklDQVRFIChDTj10ZXN0LFBBU1NXRD1hMWMwZTNlYTcwN2E1NGRlN2EwZjk1KQotLS0tLUJFR0lOIFBSSVZBVEUgS0VZLS0tLS0KTUlJRXZnSUJBREFOQmdrcWhraUc5dzBCQVFFRkFBU0NCS2d3Z2dTa0FnRUFBb0lCQVFEVmNMWlA5b0xRWkRIRgp5V0pTa2U0Z0crSUpmSCtMWlk0cXUzdS9OckRwSHZCN2k5V01rMWxRL2FMSGI0V3ZqelBLK1RITm9rRzc2MENRClhBTUpWS0dmYXRwcmNLUXdvMGhvWDZ2NlhsTVlZUlNRbW9wN3pSaUtnN3ZxKzV2S09DQ2RKcDFlSVNiZXcyTEgKTmYxL3JZelpwa1Q1bHoxTGZkem00eXJBS3VNa0tyZ3pnTzJRcE9CQVdYdmdiWG9BMDdidDdOODZZOGdNZFUwdAp6Ui9EcmhLTi9JMVdYSk11MU4wQW5UbDJRdEhEb0dCN0UyS0xpdmwybDZJdnRrYWJ4RE55Y2lHbUxOUGlMRklrCllPSHlnMUg3MUJ2NU04NE5TWDc1c2xuOXVNUGUzOVNFVlJoU0ptNHcvT2tXZnA5dGpZRUx3dHphdFhoSWJoS3MKaC9OZU1lOWRBZ01CQUFFQ2dnRUFCYlFuWUdlcFdKYjAydURtSnhLNGx2OFNhL0o1dzJpSldMYjk0dW1SUExRKwpTa1E5c1Zpc0JiQU1JNHc2dWFyNEFBVTh3WGxaTndPekMvM2V6dWNHRXFreEFReXM0VDB4SXRPUkF2WExxVlowCkl1WnpxNW53Si9OeFFzeEtmaWhBZkRLYlRmZjdmcG5MWlV0dlpNbG5LWUhQVExtRlFua3drckwyRE5YdDVVOTYKYXJUUDVOY0x1aDQ2dU93alJUOWhaNytYQi9ubU9LeTV4V3hoNWVMQmJJUTJrS0UvdWViUlVYRGZNdG5tbTh2aApRSG9VM3N5dzlZcE1xRDlWQWppcGlqQXRwbUlwa2w1emRWSE52Q1dCSGk2NjZxKzF6cUpHeFVUODBseHo1N2R1ClRvRFFQc1l0OFFEL3ZjNGkxajd0bDZyRzNQWkJNM05LNVR5ZFYyRnlnUUtCZ1FEYUcycnV3aWxyYUdZRzZNQWwKNEF2WW1BY0hHQWUwQjR0ZmtkdS9QandlRWQyWUF2TjJIQWV1Z1ZUSWg1eFplUlIwNFE4ZVNGenBTaEpwREpkNAp1TEhHcXJ2cmpXL1greEVIc081NnNjNjRiem1weWJWQXBlQnA4NlFGSTk4V3FmZkFyN3FzbzhweFJjNmdTRU1uCk5TcXV1Z2psYU05TlJmcUo4ci82RklmQkxRS0JnUUQ2aGJ0Z2dIdVJoTHhHYWNNaERvOVppcXJlNi9HN1dvZnQKR2FGZmFQM2xZNTNmM0hPdSsyNTZMWDY1ZWFYRTdrbXlQOGRDM2VWL24xT3dvTHdBYm1BZ2pEWVd1N09KdGk4QQpPbG9VNmtnTkVwNWcwOHpVWlBaR3NSMkZMd2VpUkgrQ3ZOdFBZakJydmFIQUtVU2lLa3BKdEpWeFpIdUl6SlpGCjVUZkM2VjNrOFFLQmdEeWp0TlpPKzA4V2hvOVROT0VTNnBnOHBHK1BlY3pPOEN3UkZJU1dYQWFNTnd6bGZTVVEKWS81YmpPUDMrRHRVRTZEdlZkRzRrc1IxeUtxV1NxTFF6dlNLVVpjTEN0YUV3bFplRmQvZEFibDdpdyt1dWdzUQpVMVdCM005bENzaDFWeUdtZWdNM3dyZzlqVlk0NFJyTWlHSnQ3TDFEcDZjM1ZwSDJBUFFac3lpOUFvR0JBTk9pCmpmeWtEYitNNXBDRllEWlkybmpHVURzcUQzZzZyb0Y2R1gxRWNOaU1JeDZ1V1h3RkkvdEsyN2RNTU9JQWUzbDkKcjVPcGFPczdhYlBZMVhsM3hQVTUvYWVPd2NrZ2d1d3FYMWN6NDlKSFhFeG9JSzE4N1NBakY5RWZQYyt6RmhVWAovaDA5MGJIeTdPWXM5cklZRDlIY0lETStzNjJKUjVtY1hsTG1Xay9CQW9HQkFKRVhQV05IWEwra1l6My91R1c3CnRKd0hUQzFlbEJjclcvaHpJMWt4ZEhXem5VaXNTWlcyVnA5b0wwSWNrQXVWQkx6eGUvR1h6OGJRTjZkOWwyZDAKdGtmUmo1TmpDOTUzS2N1cTNSekRmVU40cTcyUlVWTWlFOHVvSTBkVVZpalczN0tVMEhLcm1pbDBocU01eW9iNQpVZlhPQ2Q5SlRRSWx5Y2dNWER6Tm00S3oKLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQotLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ3FEQ0NBWkNnQXdJQkFnSVJBUHNjMVBRNXBuSDNhNk1GZkdVTXA2WXdEUVlKS29aSWh2Y05BUUVMQlFBdwpFREVPTUF3R0ExVUVDZ3dGVEVGTlJFRXdIaGNOTWpBd01UQXhNREF3TURBeFdoY05Namt4TWpJNU1EQXdNREF4CldqQVBNUTB3Q3dZRFZRUUREQVIwWlhOME1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0MKQVFFQTFYQzJUL2FDMEdReHhjbGlVcEh1SUJ2aUNYeC9pMldPS3J0N3Z6YXc2Ujd3ZTR2VmpKTlpVUDJpeDIrRgpyNDh6eXZreHphSkJ1K3RBa0Z3RENWU2huMnJhYTNDa01LTklhRityK2w1VEdHRVVrSnFLZTgwWWlvTzc2dnViCnlqZ2duU2FkWGlFbTNzTml4elg5ZjYyTTJhWkUrWmM5UzMzYzV1TXF3Q3JqSkNxNE00RHRrS1RnUUZsNzRHMTYKQU5PMjdlemZPbVBJREhWTkxjMGZ3NjRTamZ5TlZseVRMdFRkQUowNWRrTFJ3NkJnZXhOaWk0cjVkcGVpTDdaRwptOFF6Y25JaHBpelQ0aXhTSkdEaDhvTlIrOVFiK1RQT0RVbCsrYkpaL2JqRDN0L1VoRlVZVWladU1QenBGbjZmCmJZMkJDOExjMnJWNFNHNFNySWZ6WGpIdlhRSURBUUFCTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFBWEQ0L1cKQjBhSW1aWGpQbTRxUnBOazJmUnpjU1g4MGw2TlZaWWxJV3ZYalFxUXdXZnMvSGczZDVzYUpickFmcWVPa1lQdQpjeXJEWFZPdC9RTEVDOTFBSGtjRWJ1R0dPMGNFU2YyOHdUM1UzRnJJb2cxS1VyTURqWFFIb09vZEJpOGdNaVBmCmROcWhMSTdkNDJBTXJKU3dZUTlSUG9vWG9UZ2xDa0d3R291RDhuS0V5MmNHeVMxM3lQcDRseC9TWTR1QkRFU0sKRlErR0ZRTExGQktQZHZNc2x0cHYyQWFMWmR3clF4aFQ2aTU1U1puNStLb3c1TGxYL0RHdUw5UnRPdmZ2T0tzZQpRZ3pOQUg3QkYzbGdvQmJjYk9yZkVQazY1ZEZRN0NXYi91aDZjVmlmSjdxQzkvL0xhdElmb1VQVnJiRXdZL2dRCk5BRXFYclduMGZuYUc0cUEKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQotLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ2xEQ0NBWHdDQVFBd0RRWUpLb1pJaHZjTkFRRUxCUUF3RURFT01Bd0dBMVVFQ2d3RlRFRk5SRUV3SGhjTgpNakF3TVRBeE1EQXdNREF4V2hjTk1qa3hNakk1TURBd01EQXhXakFRTVE0d0RBWURWUVFLREFWTVFVMUVRVENDCkFTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTG5xZkJadnJHWmFxZ2s1bXNDUlJwUHoKcC8rNDY0akJrbmxtVEtldE9ja0RUVXE4VjZmSC8yR2ZiNkhqam9ENXBrQ3RENW1TS2thSE5odXhMWHNGZkVmYwpLbG1ubjNacGp5Tk9IRUEvaUFPMkR5RVlhMDh4U2V2TTdXb2piRjdjTmo1L0RZZzdlYjBpMCsvL2JCbGg4bmxPCmdoU1VoQ1RNNVBDb2ZMRFU4c1ZYdVlBaUdVNlV6QnJJQzB2SEVsdERraUpWTHBjQ3RzS2pFWk9za1BkQWM3dTYKL2FBMFA1R29uWjVVa1JEWXBhK2plSlVhYnFXWlFRRWd0bXZqbG1VVWlYd3UwalJuajFuMFQzZlBRRDNnQStMSQp2QUU5dmd2cFk1WFFqNm90cEJ2c1ozTUpKTktjVU1RdTF6T0FOVHpPMThUbEE4S29CTnNCeThaOURRWktYRjhDCkF3RUFBVEFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBZUduL055cUlSSS8wQUdxdkhQOTdLdFE3NlRqNmFjaGIKMzBMSVhXcCtZSFVhTWVBVmpkMlo3alNRcDVtWlpGbCtrMWZiMzM3SWVhR1hvZlZJMjFlSzUyUVgydGVOb0JrQQovVi9PMUh1MzUvK2FpejB4c2RENndXdndvNEZ5MWpsbWFlSmh3ZFFhY0JsREdGQTJqRkp4dUVwYWhmeFp2VXNiCjNqNXpVMFdLVFVDZkVEZ1hGd0J3MTJ4a3UvN1RNZENFYlJzWWFaM3pGVEMyMjZsUWJVRE43d2VxRndTRCt0QjYKUnVoSXhlOCtjRndBc0FXSENsZXJLZ1pucjN0NVFGMDc4cFcyR0h5OENzSjdWM01aVDVsWjQzbFM1TklCOUp6WgpTWXhaL2l6aFJ5aDVxUjczdUFnc0phTDU2QmorY1Fxbm9UcWhMWlZsN0orTTZXaFdLem9qc0E9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
# Set the remote desktop login password for LAMDA, 6-32 characters.
# This password will only take effect if a certificate (--certificate)
# is used. It serves as a backup password to the one generated in
# the certificate (for easier memorization).
ssl-web-credential=password123
# Set Access-Control-Allow-Origin header of firerpa webui
# and it's apis to allow you embed firerpa's features
# see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
allow_origin=https://example.com
# Setting this option will also disable the LAMDA Python client API.
# This option should be set only if the device crashes after starting LAMDA,
# or if you do not need to use the API. This option will also disable
# any components that might impact system stability.
disable-client-api=false
# Enhanced stealth mode, designed to protect LAMDA and its associated
# components from detection in cases where there is a risk of damaging
# itself or third-party components.
# Use only if necessary (introduced in version 7.65).
enhanced-stealth-mode=false
# Enhanced automation.
enhanced-automation=false
# Remote Desktop Touch Fix
# If there is an offset or no response when you touch the screen on
# the remote desktop, please set the following configuration:
# available options are: system | native (default)
touch.backend=system
# The switch for get_last_activities, default is false and does not intercept.
# introduced in 8.20
intercept-intent=false
# Set the log file location (directory must exists)
logfile=/data/local/tmp/server.log
# ---------- OpenVPN Configuration ----------
# Do not manually write the following configuration. You should use
# our accompanying OpenVPN server setup solution to set it up and
# generate this configuration using its built-in commands.
openvpn.proto=udp
openvpn.cipher=AES-256-GCM
openvpn.host=123.123.123.123
openvpn.port=1190
openvpn.ca=LS0tLS1CRU...
openvpn.cert=LS0tLS1CRU...
openvpn.key=LS0tLS1CRU...
openvpn.tls_encryption=
openvpn.tls_key_direction=
openvpn.tls_key=
# ONLY THE FOLLOWING CONFIGURATION ITEMS CAN BE MANUALLY CONFIGURED.
# Whether to enable global VPN.
openvpn.global=false
# Whether to enable the service true | false
openvpn.enable=true
# ---------- Proxy Configuration ----------
# This configuration is used to make LAMDA automatically connect
# to the proxy server at startup.
#
# Whether to enable the service true | false
gproxy.enable=true
# The proxy type can be either http-connect or socks5.
gproxy.type=http-connect
# Proxy server address
gproxy.host=172.1.1.1
# Proxy server port
gproxy.port=8080
# Proxy server login password (leave empty for no authentication)
gproxy.password=
# Proxy server login username (leave empty for no authentication)
gproxy.login=
# ---------- CRONTAB Scheduled Task Service ----------
# If you do not need scheduled tasks, you can disable this service.
# Whether to enable the service true | false
cron.enable=true
# ---------- SSHD Service ----------
# If you do not need to connect to the device via SSH, you can disable this service.
# Whether to enable the service true | false
sshd.enable=true
# ---------- Port Forwarding (frp) Service ----------
# Whether to enable the service true | false
fwd.enable=true
# Port to forward to the remote (0 means randomly allocated)
fwd.rport=0
# FRP server address
fwd.host=123.123.123.123
# FRP server port
fwd.port=9911
# FRP protocol
fwd.protocol=tcp
# FRP login authentication
fwd.token=abc123
# ---------- ADB Service ----------
# Whether to enable the service true | false
adb.enable=true
# Default working directory for built-in ADB (adb shell working directory)
adb.directory=/data/local/tmp
# If set to true, the ADB connection will have root privileges,
# otherwise it will have shell privileges. When this option is set to false,
# you will be using a native-like adb shell, and you will not be able to use
# LAMDA's built-in commands. Please note that since ADB does not use TLS connections,
# traffic may be monitored. For security reasons, when the LAMDA service is
# started with a certificate, this value will be set to false by default.
# However, if you specify it in the properties.local file, the configuration
# in the file will take precedence.
# You are responsible for ensuring security.
adb.privileged=true
# ---------- Bridge Proxy Service ----------
# Whether to enable the service true | false
tunnel2.enable=true
# The bridge proxy will require login authentication only if both login and password
# are set. If either one is left empty, no login authentication will be required.
tunnel2.login=lamda
# Login password for the bridge proxy
tunnel2.password=1234
# Outbound interface (rmnet|wlan)
# When the outbound interface is rmnet, the proxy will attempt to forward your requests through mobile data.
# When the outbound interface is wlan, requests will be forwarded through the wlan interface.
# If the configuration is empty, the default network will be used to forward the requests.
tunnel2.iface=rmnet
# ---------- mDNS Broadcast Service ----------
# Enable or disable true | false
mdns.enable=false
# Add TXT metadata for mDNS. When enabled, it will support querying device information
# such as model, ABI, and device ID using tools like python-zeroconf. Disabled by default.
mdns.meta=false
# Set the broadcast domain name using a locally unique ID, default is {DEVICEID-UNIQUE}.lamda.
# If the name duplicates in the local network, a suffix ID will be automatically added.
mdns.name=DEVICEID-UNIQUE.lamda
# Set the broadcast service name, default is lamda, i.e., _lamda._tcp.local.
mdns.service=lamda
================================================
FILE: scripts/disable_flag_secure.yaml
================================================
# EXAMPLE EXTENSION OF FIRERPA (https://github.com/firerpa/lamda)
# This script is used to disable the android FLAG_SECURE flag.
# Replace YOUR_APP_ID with the ID (eg. com.android.settings) of your target application.
enable: true
application: "YOUR_APP_ID"
version: "N/A"
script: !!binary "SmF2YS5wZXJmb3JtKGZ1bmN0aW9uICgpIHsKICAgICAgICB2YXIgRkxBR19TRUNVUkUgPSAweDIwMDA7CiAgICAgICAgdmFyIFdpbmRvdyA9IEphdmEudXNlKCJhbmRyb2lkLnZpZXcuV2luZG93Iik7CiAgICAgICAgdmFyIHNldEZsYWdzID0gV2luZG93LnNldEZsYWdzOwoKICAgICAgICBzZXRGbGFncy5pbXBsZW1lbnRhdGlvbiA9IGZ1bmN0aW9uIChmbGFncywgbWFzaykgewogICAgICAgICAgICBjb25zb2xlLmxvZygiRGlzYWJsaW5nIEZMQUdfU0VDVVJFLi4uIik7CiAgICAgICAgICAgIGZsYWdzICY9IH5GTEFHX1NFQ1VSRTsKICAgICAgICAgICAgc2V0RmxhZ3MuY2FsbCh0aGlzLCBmbGFncywgbWFzayk7CiAgICAgICAgfTsKfSk7Cg=="
standup: 0
spawn: false
================================================
FILE: scripts/disable_ssl_pinning_simple.yaml
================================================
# EXAMPLE EXTENSION OF FIRERPA (https://github.com/firerpa/lamda)
# This script is used to disable simple ssl pinning (checkServerTrusted).
# Replace YOUR_APP_ID with the ID (eg. com.android.settings) of your target application.
enable: true
application: "YOUR_APP_ID"
version: "N/A"
script: !!binary "SmF2YS5wZXJmb3JtKGZ1bmN0aW9uKCkgewpKYXZhLnVzZSgiYW5kcm9pZC5zZWN1cml0eS5uZXQuY29uZmlnLlJvb3RUcnVzdE1hbmFnZXIiKS5jaGVja1NlcnZlclRydXN0ZWQub3ZlcmxvYWQoIltMamF2YS5zZWN1cml0eS5jZXJ0Llg1MDlDZXJ0aWZpY2F0ZTsiLCAiamF2YS5sYW5nLlN0cmluZyIsICJqYXZhLm5ldC5Tb2NrZXQiKS5pbXBsZW1lbnRhdGlvbiA9IGZ1bmN0aW9uIChhLCBiLCBjKSB7CiAgICAgICAgcmV0dXJuCn0KfSkK"
standup: 0
spawn: false
================================================
FILE: setup.py
================================================
#!/usr/bin/python3
# Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
import setuptools
exec(open("lamda/__init__.py", "rt").read())
setuptools.setup(
name = "lamda",
version = "{}".format(__version__),
description = "Android reverse engineering & automation framework (Client API)",
url = "https://github.com/firerpa/lamda",
author = "rev1si0n",
python_requires = ">=3.6,<3.14",
zip_safe = False,
extras_require = {
"full": ["frida>=17.0.0,<18.0.0"],
":sys_platform == \"win32\"": [
"pyreadline==2.1",
],
},
install_requires= [
"grpcio-tools>=1.35.0,<=1.74.0",
"grpc-interceptor>=0.13.0,<=0.15.4",
"grpcio>=1.35.0,<=1.74.0",
"cryptography>=35.0.0",
"msgpack>=1.0.0",
"asn1crypto>=1.0.0,<2",
"pem==23.1.0",
],
classifiers = [
"Environment :: Console",
"Intended Audience :: Developers",
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Information Technology",
"Intended Audience :: Science/Research",
"Programming Language :: Python :: 3",
"Operating System :: Android",
"Topic :: Security",
],
package_data = {
"lamda": ["*.py", "*.proto"],
"lamda.google.protobuf.compiler": ["*.proto"],
"lamda.google.protobuf": ["*.proto"],
"lamda.rpc": ["*.proto"],
},
packages = [
"lamda.google.protobuf.compiler",
"lamda.google.protobuf",
"lamda.rpc",
"lamda",
],
)
================================================
FILE: tools/README.md
================================================
Document: https://device-farm.com/doc/
================================================
FILE: tools/adb_pubkey.py
================================================
#!/usr/bin/env python3
#encoding=utf-8
import os
import argparse
from os.path import isfile
from lamda.client import *
certfile = os.environ.get("CERTIFICATE", None)
port = int(os.environ.get("PORT", 65000))
android_path = os.path.join("~", ".android")
abs_android_path = os.path.expanduser(android_path)
f = "adbkey.pub"
argp = argparse.ArgumentParser()
argp.add_argument("action", nargs=1)
argp.add_argument("device", nargs=1)
args = argp.parse_args()
d = Device(args.device[0], port=port,
certificate=certfile)
cmd = args.action[0]
os.chdir(abs_android_path)
# try generate pubkey
pubkey = os.popen("adb pubkey adbkey").read()
open("adbkey.lamda", "w").write(pubkey)
f = ("adbkey.lamda", f)[isfile(f)]
call = getattr(d, "%s_adb_pubkey" % cmd)
exit(not call(f))
================================================
FILE: tools/cert.py
================================================
#!/usr/bin/env python3
import os
import sys
import random
import datetime
from hashlib import sha256
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.backends import default_backend
from cryptography.x509 import (
Name,
NameAttribute,
CertificateBuilder,
CertificateSigningRequestBuilder,
DNSName,
SubjectAlternativeName,
load_pem_x509_certificate,
BasicConstraints,
KeyUsage,
)
from cryptography.x509.oid import NameOID
CN = "lamda"
if len(sys.argv) == 2:
CN = sys.argv[1]
if os.path.isfile("root.key"):
with open("root.key", "rb") as f:
rk = serialization.load_pem_private_key(
f.read(), password=None, backend=default_backend()
)
else:
rk = rsa.generate_private_key(
public_exponent=65537, key_size=2048, backend=default_backend()
)
with open("root.key", "wb") as f:
f.write(
rk.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
)
if os.path.isfile("root.crt"):
with open("root.crt", "rb") as f:
root = load_pem_x509_certificate(f.read(), default_backend())
else:
subject = issuer = Name(
[
NameAttribute(NameOID.ORGANIZATION_NAME, "LAMDA"),
NameAttribute(NameOID.COMMON_NAME, "FireRPA LAMDA Root Trust"),
]
)
root = (
CertificateBuilder()
.subject_name(subject)
.issuer_name(issuer)
.not_valid_before(datetime.datetime.utcnow())
.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=3650))
.public_key(rk.public_key())
.serial_number(random.randint(1, 2**128))
.add_extension(BasicConstraints(ca=True, path_length=None), critical=True)
.sign(rk, hashes.SHA256(), default_backend())
)
with open("root.crt", "wb") as f:
f.write(root.public_bytes(serialization.Encoding.PEM))
if not os.path.isfile(f"{CN}.pem"):
pk = rsa.generate_private_key(
public_exponent=65537, key_size=2048, backend=default_backend()
)
csr = (
CertificateSigningRequestBuilder()
.subject_name(Name([NameAttribute(NameOID.COMMON_NAME, CN)]))
.sign(pk, hashes.SHA256(), default_backend())
)
cert = (
CertificateBuilder()
.subject_name(csr.subject)
.issuer_name(root.subject)
.not_valid_before(datetime.datetime.utcnow())
.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=3650))
.public_key(csr.public_key())
.serial_number(random.randint(1, 2**128))
.sign(rk, hashes.SHA256(), default_backend())
)
with open(f"{CN}.pem", "wb") as output:
pem_private_key = pk.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
pem_cert = cert.public_bytes(serialization.Encoding.PEM)
pem_root = root.public_bytes(serialization.Encoding.PEM)
d = pk.private_numbers().d
pd = d.to_bytes((d.bit_length() + 7) // 8, "little")
cred = sha256(pd).hexdigest()[::3]
header = f"LAMDA SSL CERTIFICATE (CN={CN},PASSWD={cred})\n"
output.write(header.encode())
output.write(pem_private_key.strip())
output.write(b"\n")
output.write(pem_cert.strip())
output.write(b"\n")
output.write(pem_root.strip())
output.write(b"\n")
================================================
FILE: tools/debugimage.py
================================================
#!/usr/bin/env python3
import os
import argparse
from PIL import Image, ImageDraw
from lamda.client import *
cert = os.environ.get("CERTIFICATE", None)
port = int(os.environ.get("PORT", 65000))
parser = argparse.ArgumentParser()
parser.add_argument("-d", type=str, dest="device",
help="service ip address", required=True)
parser.add_argument("-p", "--port", type=str,
default=port, help="service port")
parser.add_argument("-f", "--method", type=str,
help="find method", default="0")
parser.add_argument("-i", "--image", type=argparse.FileType("rb"),
help="find image path", required=True)
parser.add_argument("-a", "--area", type=str,
help="area", default="0")
parser.add_argument("-s", "--scale", type=float,
help="scale", default=1.0)
parser.add_argument("-t", "--threshold", type=float,
help="threshold", default=0)
parser.add_argument("-m", "--distance", type=int,
help="max distance", default=0)
parser.add_argument("-cert", type=str, default=cert,
help="ssl cert")
args = parser.parse_args()
d = Device(args.device, port=args.port,
certificate=args.cert)
image = Image.open(d.screenshot(95))
draw = ImageDraw.Draw(image)
for r in d.find_similar_image(args.image.read(),
area=eval(args.area),
method=eval(args.method),
distance=args.distance,
threshold=args.threshold,
scale=args.scale):
p1 = r.corner("top-left")
p2 = r.corner("bottom-right")
draw.rectangle(((p1.x, p1.y), (p2.x, p2.y)),
outline="#00ff00", width=3)
image.show()
================================================
FILE: tools/discover.py
================================================
#!/usr/bin/env python3
import os
import struct
from socket import *
from lamda import __version__
from lamda.client import load_proto
protos, services = load_proto("bcast.proto")
BcastHeader = protos.BcastHeader
BcastDiscoverInfo = protos.BcastDiscoverInfo
BcastResponse = protos.BcastResponse
BcastRequest = protos.BcastRequest
port = int(os.environ.get("PORT", 65000))
def BcastCallMethod(method):
req = BcastRequest(method=method)
# ASTBCAST + length (body,4byte) + body + feeedeed
hdr = BcastHeader(magic=0x54534143,
version=__version__)
req.header.CopyFrom(hdr)
body = req.SerializeToString()
buffer = []
r = struct.pack("QH", 0x5453414342545341, len(body))
buffer.append(r)
r = struct.pack("{}s".format(len(body)), body)
buffer.append(r)
r = struct.pack("I", 0xeedeeefe)
buffer.append(r)
r = bytes().join(buffer)
return r
sock = socket(AF_INET, SOCK_DGRAM)
sock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
message = BcastCallMethod("DISCOVER")
sock.sendto(message, ("255.255.255.255", port))
sock.settimeout(3.0)
while True:
try:
data, remote = sock.recvfrom(4096)
except timeout:
break
fmt = "
#include
#include
#include
#include
#else //#ifdef _WIN32
//other OS includes and some tricks to fake Win32 API
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef int SOCKET;
#define SOCKET_ERROR (-1)
#define closesocket(hSock) close(hSock)
#define WSAGetLastError() errno
#define CRITICAL_SECTION pthread_mutex_t
#define InitializeCriticalSection(criticalSection) pthread_mutex_init(criticalSection, NULL)
#define EnterCriticalSection(criticalSection) pthread_mutex_lock(criticalSection)
#define LeaveCriticalSection(criticalSection) pthread_mutex_unlock(criticalSection)
#endif //#ifdef _WIN32
#endif //#ifndef _STDAFX_H
// end "stdafx.h"
//helpful URLs:
// http://www.networksorcery.com/enp/protocol/dns.htm
// http://www.freesoft.org/CIE/RFC/1035/43.htm
// https://tools.ietf.org/html/rfc2671
// https://tools.ietf.org/html/rfc3225
// https://tools.ietf.org/html/rfc7871
//application name and version
#define APP_NAME "DNS2SOCKS V2.1"
//first output line in console
#define APP_STRING "\n" APP_NAME " (free software, use parameter /? to display help)\n"
//log file header line
#define LOG_HEADER APP_NAME " log opened"
//date/time format string for ISO 8601 format, but leave away the T delimiter as it's bad to read
#define DATE_TIME_FORMAT "%u-%02u-%02u %02u:%02u:%02u "
//127.0.0.1 for default SOCKS5 server
#define DEFAULT_SOCKS_SERVER "127.0.0.1"
//9050 for default SOCKS5 port
#define DEFAULT_SOCKS_PORT "9050"
//46.182.19.48 for default DNS server supporting TCP (Digitalcourage e.V.)
#define DEFAULT_DNS_SERVER "46.182.19.48"
//127.0.0.1 for local IP address for listening
#define DEFAULT_LISTEN_IP "127.0.0.1"
//53 for default DNS port
#define DEFAULT_DNS_PORT "53"
//defines for OutputToLog (bits)
#define OUTPUT_LINE_BREAK (1)
#define OUTPUT_DATE_TIME (2)
#define OUTPUT_CONSOLE (4)
#define OUTPUT_ALL (OUTPUT_LINE_BREAK|OUTPUT_DATE_TIME|OUTPUT_CONSOLE)
//on some systems a failing send command raises a signal; we want to disable that by passing MSG_NOSIGNAL
//some systems (Windows) don't define that -> define it to 0
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL (0)
#endif
union UClient
{
struct sockaddr_storage sAddr; //address of requesting client for UDP
SOCKET hSock; //socket handle for TCP
};
//entry for DNS request and answer (cache entry)
struct SEntry
{
struct SEntry* psNext; //next list entry or NULL
uint16_t* u16aAnswer; //pointer to answer or NULL
time_t iTime; //time when the answer was deliviered last time
union UClient client; //information on how to send response back to client
unsigned char uAddrLen; //length of used part of sAddr for UDP, sizeof(SOCKET) for TCP
uint16_t u16aRequest[1]; //extended dynamically at malloc for "struct SEntry" (use "uint16_t" to ensure according alignment, first element contains length in big endian format)
};
static struct SEntry* g_psFirst=NULL; //list of DNS requests and answers (cache)
static unsigned int g_uCacheCount=0; //amout of entries in list g_psFirst
static int g_bCacheEnabled=1; //!=0 when cache is enabled
static struct sockaddr_storage g_sDnsSrvAddr; //DNS server supporting TCP
static struct sockaddr_storage g_sSocksAddr; //SOCKS5 server
static CRITICAL_SECTION g_sCritSect; //to protect the list g_psFirst and g_uCacheCount
static SOCKET g_hSockUdp; //UDP socket
static unsigned char* g_uaUsrPwd=NULL; //authentication package for SOCKS
static int g_iUsrPwdLen; //length of g_caUsrPwd
static int g_iHttpProxyConnectLen=0; //length of CONNECT command in g_caHttpProxyConnect
static char g_caHttpProxyConnect[300]; //CONNECT command in case of using HTTP proxy
static uint8_t g_u8aEcsOption[8+16]; //EDNS Client Subnet EDNS0 option (maximum necessary bytes reserved, actual amount in g_iEcsOptionLen)
static int g_iEcsOptionLen=0; //actual amount bytes used of g_u8aEcsOption
static int g_bForceEcs=0; //!=0 to replace existing EDNS Client Subnet option by our one
//OS specific functionality
#ifdef _WIN32
//for Windows we can create an own console window, so here we use some OS specific functions;
//also for the file access we use the WIN32 API - we could use the C API as we do for the other OSes
//but as we need to take special care about the line break (\r\n) anyway and WIN32 should be a little
//bit faster, we use WIN32
static HANDLE g_hConsole=NULL; //handle for console output
static HANDLE g_hLogFile=INVALID_HANDLE_VALUE; //handle for log file
//passes a string and its length to a function
#define STRING_AND_LEN(szString) szString, sizeof(szString)-1
static char* GetSysError(int iErrNo)
{
char* szBuffer;
size_t uLen;
if(!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, NULL, iErrNo, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&szBuffer, 0, NULL))
{
szBuffer=(char*)LocalAlloc(LMEM_FIXED, 14);
memcpy(szBuffer, "Unknown error", 14);
}
else
{
//remove line breaks at the end
uLen=strlen(szBuffer);
while(uLen>0 && (szBuffer[uLen-1]=='\n' || szBuffer[uLen-1]=='\r'))
szBuffer[--uLen]='\0';
}
return szBuffer;
}
static void FreeSysError(char* szString)
{
LocalFree(szString);
}
static void OutputToLog(unsigned int uOutputSettingBits, const char* szFormatString, ...)
{
va_list pArgs;
int iLenPrefix;
int iLen;
DWORD uDummy;
char szBuf[1024];
//nothing to do?
if((!g_hConsole || !(uOutputSettingBits&OUTPUT_CONSOLE)) && g_hLogFile==INVALID_HANDLE_VALUE)
return;
//add line break?
if(uOutputSettingBits&OUTPUT_LINE_BREAK)
{
szBuf[0]='\r';
szBuf[1]='\n';
iLenPrefix=2;
}
else
iLenPrefix=0;
if(uOutputSettingBits&OUTPUT_DATE_TIME)
{
SYSTEMTIME sTime;
//add date/time string
GetLocalTime(&sTime);
iLen=_snprintf_s(szBuf+iLenPrefix, ARRAYSIZE(szBuf)-iLenPrefix, _TRUNCATE, DATE_TIME_FORMAT, sTime.wYear, sTime.wMonth, sTime.wDay, sTime.wHour, sTime.wMinute, sTime.wSecond);
if(iLen<0) //error?
return;
iLenPrefix+=iLen;
}
//add log string
va_start(pArgs, szFormatString);
iLen=_vsnprintf_s(szBuf+iLenPrefix, ARRAYSIZE(szBuf)-iLenPrefix, _TRUNCATE, szFormatString, pArgs);
if(iLen<0) //error?
return;
iLen+=iLenPrefix;
//output
if(g_hConsole && (uOutputSettingBits&OUTPUT_CONSOLE))
{
wchar_t wcBuf[1024];
//convert to wide char to avoid trouble with special characters
iLen=MultiByteToWideChar(CP_ACP, 0, szBuf, iLen, wcBuf, ARRAYSIZE(wcBuf));
if(iLen)
WriteConsoleW(g_hConsole, wcBuf, iLen, &uDummy, NULL);
}
if(g_hLogFile)
WriteFile(g_hLogFile, szBuf, iLen, &uDummy, NULL);
}
static void OpenLogFile(const char* szFilePath, int bAppend)
{
g_hLogFile=CreateFile(szFilePath, GENERIC_WRITE, FILE_SHARE_READ, NULL, bAppend?OPEN_ALWAYS:CREATE_ALWAYS, 0, NULL);
if(g_hLogFile==INVALID_HANDLE_VALUE)
{
char* szErrMsg=GetSysError(GetLastError());
OutputToLog(OUTPUT_ALL, "Failed to open log file \"%s\": %s", szFilePath, szErrMsg);
FreeSysError(szErrMsg);
}
else
{
//append and no new file was created?
if(bAppend && GetLastError()==ERROR_ALREADY_EXISTS)
{
DWORD uDummy;
//go to end of file for appending
SetFilePointer(g_hLogFile, 0, NULL, FILE_END);
//add line breaks for appending
WriteFile(g_hLogFile, STRING_AND_LEN("\r\n\r\n"), &uDummy, NULL);
}
//output header string in log
OutputToLog(OUTPUT_DATE_TIME, LOG_HEADER);
}
}
static void OpenConsole()
{
if(g_hConsole) //already openend?
return;
AllocConsole();
g_hConsole=GetStdHandle(STD_OUTPUT_HANDLE);
if(g_hConsole)
{
DWORD uDummy;
WriteConsole(g_hConsole, STRING_AND_LEN(APP_STRING), &uDummy, NULL);
}
}
static void OutputFatal(const char* szFormatString, ...)
{
HANDLE hReadConsole;
va_list pArgs;
int iLen;
DWORD uDummy;
INPUT_RECORD sRecord;
char szBuf[4096];
OpenConsole();
if(!g_hConsole) //console available?
return;
va_start(pArgs, szFormatString);
iLen=_vsnprintf_s(szBuf, ARRAYSIZE(szBuf), _TRUNCATE, szFormatString, pArgs);
if(iLen<0) //error?
return;
//output
WriteConsole(g_hConsole, szBuf, iLen, &uDummy, NULL);
WriteConsole(g_hConsole, STRING_AND_LEN("\nPress any key to close the application..."), &uDummy, NULL);
hReadConsole=GetStdHandle(STD_INPUT_HANDLE);
//wait for key
while(ReadConsoleInput(hReadConsole, &sRecord, 1, &uDummy) && (sRecord.EventType!=KEY_EVENT || !sRecord.Event.KeyEvent.bKeyDown))
;
}
//thread handling for Windows
#define THREAD_FUNCTION(threadFunction, pParam) static DWORD __stdcall threadFunction(LPVOID pParam)
int ThreadCreate(LPTHREAD_START_ROUTINE pThreadFunction, void* pParam)
{
DWORD uId;
HANDLE hThread=CreateThread(NULL, 0, pThreadFunction, pParam, 0, &uId);
if(!hThread)
{
char* szErrMsg=GetSysError(GetLastError());
OutputToLog(OUTPUT_ALL, "Creating new thread has failed: %s", szErrMsg);
FreeSysError(szErrMsg);
return 0; //error
}
CloseHandle(hThread);
return 1; //o.k.
}
#else //#ifdef _WIN32
//on other OS create no own console window
static int g_bConsole=0; //console output enabled?
static FILE* g_pLogFile=NULL; //log file pointer
#define GetSysError(iErrNo) strerror(iErrNo)
#define FreeSysError(szString) //nothing to do
static void OutputToLog(unsigned int uOutputSettingBits, const char* szFormatString, ...)
{
va_list pArgs;
int iLenPrefix;
int iLen;
char szBuf[4096];
//nothing to do?
if((!g_bConsole || !(uOutputSettingBits&OUTPUT_CONSOLE)) && !g_pLogFile)
return;
//add line break?
if(uOutputSettingBits&OUTPUT_LINE_BREAK)
{
szBuf[0]='\n';
iLenPrefix=1;
}
else
iLenPrefix=0;
if(uOutputSettingBits&OUTPUT_DATE_TIME)
{
//add date/time string
time_t iTime;
struct tm* psTime;
if(time(&iTime)==(time_t)-1)
return; //error
psTime=localtime(&iTime);
if(!psTime) //error?
return;
iLen=snprintf(szBuf+iLenPrefix, sizeof(szBuf)-iLenPrefix, DATE_TIME_FORMAT, psTime->tm_year+1900, psTime->tm_mon+1, psTime->tm_mday, psTime->tm_hour, psTime->tm_min, psTime->tm_sec);
if(iLen<0) //error?
return;
iLenPrefix+=iLen;
}
//add log string
va_start(pArgs, szFormatString);
iLen=vsnprintf(szBuf+iLenPrefix, sizeof(szBuf)-iLenPrefix, szFormatString, pArgs);
va_end(pArgs);
if(iLen<0) //error?
return;
iLen+=iLenPrefix;
if(iLen>=sizeof(szBuf)) //truncated? -> make sure to add \0
{
szBuf[sizeof(szBuf)-1]='\0';
iLen=sizeof(szBuf)-1;
}
//output
if(g_bConsole && (uOutputSettingBits&OUTPUT_CONSOLE))
fputs(szBuf, stdout);
if(g_pLogFile)
fwrite(szBuf, 1, iLen, g_pLogFile);
}
static void OpenLogFile(const char* szFilePath, int bAppend)
{
g_pLogFile=fopen(szFilePath, bAppend?"ab":"wb");
if(!g_pLogFile)
OutputToLog(OUTPUT_ALL, "Failed to open log file \"%s\": %s", szFilePath, strerror(errno));
else
{
//append and file is not empty?
if(bAppend && ftell(g_pLogFile))
{
//add line breaks for appending
fputs("\n\n", g_pLogFile);
}
setbuf(g_pLogFile, NULL); //disable buffering (no newline needed for actual output)
//output header string in log
OutputToLog(OUTPUT_DATE_TIME, LOG_HEADER);
}
}
static void OpenConsole()
{
if(g_bConsole) //already openend?
return;
g_bConsole=1;
setbuf(stdout, NULL); //disable buffering (no newline needed for actual output)
printf(APP_STRING);
}
//for OutputFatal we can just use a macro to prefix the parameters
#define OutputFatal OpenConsole(); printf
//thread handling via pthread
#define THREAD_FUNCTION(threadFunction, pParam) static void* threadFunction(void* pParam)
int ThreadCreate(void* (*pThreadFunction)(void*), void* pParam)
{
pthread_t hThread;
int iErrNo=pthread_create(&hThread, NULL, pThreadFunction, pParam);
if(iErrNo)
{
OutputToLog(OUTPUT_ALL, "Creating new thread has failed: error code %d", iErrNo);
return 0; //error
}
pthread_detach(hThread);
return 1; //o.k.
}
#endif //#ifdef _WIN32
//returns the internal length of a socket address (IPv4 / IPv6)
static unsigned int GetAddrLen(const struct sockaddr_storage* psAddr)
{
if(psAddr->ss_family==AF_INET)
return sizeof(struct sockaddr_in);
return sizeof(struct sockaddr_in6);
}
//sends UDP answer
static void SendAnswer(struct SEntry* psEntry)
{
//ignore error here
//UDP?
if(psEntry->uAddrLen!=sizeof(SOCKET))
//+2 because DNS on UDP doesn't include the length
sendto(g_hSockUdp, (char*)(psEntry->u16aAnswer+1), ntohs(*psEntry->u16aAnswer), MSG_NOSIGNAL, (struct sockaddr*)&psEntry->client.sAddr, psEntry->uAddrLen);
else
{
//TCP
send(*(SOCKET*)&psEntry->client.hSock, (char*)psEntry->u16aAnswer, ntohs(*psEntry->u16aAnswer)+2, MSG_NOSIGNAL);
closesocket(psEntry->client.hSock);
}
}
//searches for an entry in the cache list and removes it - also closes a socket and outputs an error message
static void RemoveEntry(struct SEntry* psEntry, SOCKET hSock, int bUseCriticalSection)
{
struct SEntry** ppsPrev;
struct SEntry* psEntry2;
//close the socket created by the caller
if(hSock!=SOCKET_ERROR)
closesocket(hSock);
if(bUseCriticalSection)
EnterCriticalSection(&g_sCritSect);
ppsPrev=&g_psFirst;
for(psEntry2=g_psFirst; psEntry2; psEntry2=psEntry2->psNext)
{
//found entry?
if(psEntry==psEntry2)
{
//remove entry from list
*ppsPrev=psEntry->psNext;
--g_uCacheCount;
break;
}
ppsPrev=&psEntry2->psNext;
}
//free the entry
free(psEntry->u16aAnswer); //should be NULL anyway
//close socket in case of TCP
if(psEntry->uAddrLen==sizeof(SOCKET))
closesocket(psEntry->client.hSock);
free(psEntry);
if(bUseCriticalSection)
LeaveCriticalSection(&g_sCritSect);
}
//outputs error for TTL check (i32TimeOffset>=0) or searching OPT pseudo-RR (i32TimeOffset<0)
static int InvalidDnsMsgErrorOutput(int32_t i32TimeOffset)
{
if(i32TimeOffset>=0)
OutputToLog(OUTPUT_ALL, "Invalid DNS answer detected while calculating TTL");
else
OutputToLog(OUTPUT_ALL, "Invalid DNS requested detected while searching OPT pseudo-RR");
return 0; //error
}
//iterates through DNS message parts
//either to update/check TTL (i32TimeOffset>=0) or to find OPT pseudo-RR (i32TimeOffset<0)
//returns 0 on error/TTL expiration, 1 on success or offset of OPT pseudo-RR
static int IterateDnsMessage(uint16_t* u16aMessage, int32_t i32TimeOffset)
{
int32_t i32TimeToLive;
uint16_t u16ContentLen;
uint16_t u16AmountQuestions;
uint16_t u16Len;
uint8_t* pu8Pos;
uint8_t* pu8MessageEnd;
uint8_t u8NameLen;
u16Len=ntohs(*u16aMessage);
if(u16Len<=12)
return InvalidDnsMsgErrorOutput(i32TimeOffset); //answer has no useful information
pu8Pos=(uint8_t*)u16aMessage+2;
pu8MessageEnd=pu8Pos+u16Len;
u16AmountQuestions=ntohs(((uint16_t*)pu8Pos)[2]);
pu8Pos+=12; //go behind header
//ignore questions
for(; u16AmountQuestions; --u16AmountQuestions)
{
//ignore name
for(;;)
{
if(pu8Pos>=pu8MessageEnd)
return InvalidDnsMsgErrorOutput(i32TimeOffset); //failed
u8NameLen=*pu8Pos;
if(!u8NameLen)
break; //end of name
if(u8NameLen>=0xc0) //compression used? (reference to other name via offset)
{
++pu8Pos; //ignore 2nd part of offset
break;
}
pu8Pos+=u8NameLen+1;
}
pu8Pos+=5; //ignore type and class
}
//for finding OPT pseudo-RR the DNS request might end after the questions
if(i32TimeOffset<0 && pu8Pos==pu8MessageEnd)
return 1; //found no OPT pseudo-RR
//for all records
do
{
//ignore name of resource record
for(;;)
{
if(pu8Pos>=pu8MessageEnd)
return InvalidDnsMsgErrorOutput(i32TimeOffset); //failed
u8NameLen=*pu8Pos;
if(!u8NameLen)
break; //end of name
if(u8NameLen>=0xc0) //compression used? (reference to other name via offset)
{
++pu8Pos; //ignore 2nd part of offset
break;
}
pu8Pos+=u8NameLen+1;
}
pu8Pos+=5; //jump behind type and class
if(pu8Pos>pu8MessageEnd-6) //6: TTL + RDLEN
return InvalidDnsMsgErrorOutput(i32TimeOffset); //failed
//type is OPT pseudo-RR which has no real TTL value?
if(ntohs(*(uint16_t*)(pu8Pos-4))==41)
{
//searching OPT pseudo-RR?
if(i32TimeOffset<0)
return (int)((pu8Pos+6)-(uint8_t*)u16aMessage); //return offset to data
}
else if(i32TimeOffset>=0)
{
//check time to live field (0 means "omitted")
i32TimeToLive=ntohl(*(uint32_t*)pu8Pos);
if(i32TimeToLive>0)
{
i32TimeToLive-=i32TimeOffset;
if(i32TimeToLive<=0)
return 0; //expired
*(uint32_t*)pu8Pos=htonl(i32TimeToLive); //update field
}
else if(i32TimeToLive<0)
return 0; //failed, TTL must be positive; mark as expired, however: no error output in this case
}
//ignore record content
u16ContentLen=ntohs(*(uint16_t*)(pu8Pos+4));
pu8Pos+=6+u16ContentLen;
if(pu8Pos>pu8MessageEnd)
return InvalidDnsMsgErrorOutput(i32TimeOffset); //failed
}
while(pu8PosiTime==(time_t)-1)
return 1; //getting time failed last time; without a working timer we ignore time to live stuff completely
//calculate amount of seconds since last delivery
if(time(&iCurTime)==(time_t)-1)
return 1; //error, can't get current time -> ignore time to live stuff completely
i32TimeOffset=(int32_t)(iCurTime-psEntry->iTime);
if(i32TimeOffset<0)
return 0; //time increased; overflow? Mark as expired
psEntry->iTime=iCurTime; //store current time for next delivery
return IterateDnsMessage(psEntry->u16aAnswer, i32TimeOffset);
}
//receives a specific amount of bytes
static int ReceiveBytes(SOCKET hSock, unsigned int uAmount, uint16_t* u16aBuf)
{
unsigned int uPos=0;
int iLen;
char* szErrMsg;
for(;;)
{
iLen=recv(hSock, (char*)u16aBuf+uPos, uAmount, 0);
switch(iLen)
{
case SOCKET_ERROR:
szErrMsg=GetSysError(WSAGetLastError());
OutputToLog(OUTPUT_ALL, "Receiving from SOCKS server has failed: %s", szErrMsg);
FreeSysError(szErrMsg);
return 0; //failed
case 0:
OutputToLog(OUTPUT_ALL, "The SOCKS server has closed the connection unexpectedly");
return 0; //failed
default:
uAmount-=iLen;
if(!uAmount)
return 1; //succeeded
uPos+=iLen;
}
}
}
//for sending DNS requests where we add/manipulate the ECS option
//returns 0 on error, -1 to send original message or the new message length otherwise
static int AddEcsOption(uint16_t* u16aRequest, uint16_t* u16aDestBuf)
{
//try to find OPT pseudo-RR
int iOffset=IterateDnsMessage(u16aRequest, -1);
switch(iOffset)
{
case 0: //error
return 0;
case 1: //no OPT pseudo-RR
{
//add it
uint16_t u16Len=ntohs(*(uint16_t*)u16aRequest);
uint32_t u32NewLen=u16Len+g_iEcsOptionLen+11;
uint8_t* u8aDest;
//new length exceeds maximum?
if(u32NewLen>65535)
{
OutputToLog(OUTPUT_ALL, "Cannot add EDNS client subnet info to DNS request as it would exceed the maximum size");
return 0;
}
//create new DNS request; starts with length as we use TCP
*u16aDestBuf=htons((uint16_t)u32NewLen);
memcpy(u16aDestBuf+1, u16aRequest+1, u16Len); //copy original request
u16aDestBuf[6]=htons(ntohs(u16aDestBuf[6])+1); //increase "additional count"
u8aDest=((uint8_t*)(u16aDestBuf+1))+u16Len; //jump after original message
//add OPT pseudo-RR
u8aDest[0]=0; //name -> empty
u8aDest[1]=0;
u8aDest[2]=41; //type -> OPT pseudo-RR
u8aDest[3]=0x10;//max UDP size: 4096
u8aDest[4]=0;
*(uint32_t*)(u8aDest+5)=0; //TTL (here: extended RCODE and flags -> all 0 -> no DNSSEC)
*(uint16_t*)(u8aDest+9)=htons((uint16_t)g_iEcsOptionLen); //option length
memcpy(u8aDest+11, g_u8aEcsOption, g_iEcsOptionLen); //add option
return (int)(u32NewLen+2); //+2 for length field
}
default: //found OPT pseudo-RR
{
//try to find EDNS Client Subnet option
uint8_t* pu8Pos=((uint8_t*)u16aRequest)+iOffset; //offset points to option data
uint16_t u16OptLen=ntohs(((uint16_t*)pu8Pos)[-1]); //length before data
uint8_t* pu8OptionsEnd=pu8Pos+u16OptLen;
uint16_t u16Len=ntohs(*(uint16_t*)u16aRequest);
uint8_t* pu8MessageEnd=((uint8_t*)u16aRequest)+2+u16Len;
uint16_t u16LenIncludingOptionsToCopy;
uint16_t u16CurOptLen;
uint32_t u32NewLen;
//pu8OptionsEnd beyond end of message? -> error
if(pu8OptionsEnd>pu8MessageEnd)
return InvalidDnsMsgErrorOutput(-1);
for(;; pu8Pos+=u16CurOptLen)
{
if(pu8Pos>=pu8OptionsEnd)
{
//no EDNS Client Subnet option -> add it
break;
}
u16CurOptLen=ntohs(*(uint16_t*)(pu8Pos+2))+4;
if(u16CurOptLen<4 || pu8Pos>pu8OptionsEnd-u16CurOptLen) //overflow or beyond end of message?
return InvalidDnsMsgErrorOutput(-1); //failed
//EDNS Client Subnet option?
if(ntohs(*(uint16_t*)pu8Pos)==8)
{
if(!g_bForceEcs)
return -1; //special return value to send original DNS message as we shall not replace ECS
//replace existing option
//correct parameters for lower message creation
pu8OptionsEnd=pu8Pos+u16CurOptLen; //behind EDNS Client Subnet option
u16OptLen-=u16CurOptLen;
u16Len-=u16CurOptLen;
break;
}
}
u32NewLen=u16Len+g_iEcsOptionLen;
//new length exceeds maximum?
if(u32NewLen>65535)
{
OutputToLog(OUTPUT_ALL, "Cannot add EDNS client subnet info to DNS request as it would exceed the maximum size");
return 0;
}
*u16aDestBuf=htons((uint16_t)u32NewLen);
u16LenIncludingOptionsToCopy=(uint16_t)(pu8Pos-(uint8_t*)u16aRequest);
memcpy(u16aDestBuf+1, u16aRequest+1, u16LenIncludingOptionsToCopy-2); //copy part of original request without length field
memcpy(((uint8_t*)u16aDestBuf)+u16LenIncludingOptionsToCopy, g_u8aEcsOption, g_iEcsOptionLen); //add ECS option
memcpy(((uint8_t*)u16aDestBuf)+u16LenIncludingOptionsToCopy+g_iEcsOptionLen, pu8OptionsEnd, pu8MessageEnd-pu8OptionsEnd); //copy rest of original message
//correct length of options
*(uint16_t*)(((uint8_t*)u16aDestBuf)+iOffset-2)=htons((uint16_t)(u16OptLen+g_iEcsOptionLen));
return (int)(u32NewLen+2); //+2 for length field
}
}
}
//sends CONNECT command to HTTP proxy and checks answer
static int HandleHttpProxy(SOCKET hSock, char* caBuf)
{
char* szErrMsg;
char* szPosEnd;
const char* szPosStatus;
int iRet;
int iPos;
iRet=send(hSock, g_caHttpProxyConnect, g_iHttpProxyConnectLen, MSG_NOSIGNAL);
if(iRet!=g_iHttpProxyConnectLen)
{
szErrMsg=(iRet==SOCKET_ERROR)?GetSysError(WSAGetLastError()):"Invalid amount of sent bytes";
OutputToLog(OUTPUT_ALL, "Sending to HTTP proxy has failed: %s", szErrMsg);
if(iRet==SOCKET_ERROR)
FreeSysError(szErrMsg);
return 0; //error
}
//receive answer
iPos=0;
for(;;)
{
iRet=recv(hSock, caBuf+iPos, 2000-iPos, 0);
if(iRet<=0)
{
if(iRet==SOCKET_ERROR)
OutputToLog(OUTPUT_ALL, "The HTTP proxy has closed the connection unexpectedly");
else
{
szErrMsg=GetSysError(WSAGetLastError());
OutputToLog(OUTPUT_ALL, "Receiving from HTTP proxy has failed: %s", szErrMsg);
FreeSysError(szErrMsg);
}
return 0; //error
}
iPos+=iRet;
if(iPos<4)
continue; //continue receiving
//check header, must begin with HTTP
if(caBuf[0]=='H' || caBuf[1]=='T' || caBuf[2]=='T' || caBuf[3]=='P')
{
caBuf[iPos]='\0'; //terminate the string
//try to find empty line that marks the end of the HTTP proxy answer
szPosEnd=strstr(caBuf, "\r\n\r\n");
if(!szPosEnd)
continue; //continue receiving
//extract status code -> scan for space character (also stops on any control character)
szPosStatus=caBuf+4;
while(*(unsigned char*)szPosStatus>' ')
++szPosStatus;
if(*szPosStatus==' ')
{
//we want: 200 Connection established
if(atoi(++szPosStatus)==200)
break; //header reception complete
//find end of line (must be there, otherwise upper search for empty line would have failed
szPosEnd=strchr(szPosStatus, '\r');
*szPosEnd='\0';
OutputToLog(OUTPUT_ALL, "Connecting DNS server has failed: %s", szPosStatus);
return 0; //error
}
}
OutputToLog(OUTPUT_ALL, "Invalid answer from HTTP proxy");
return 0; //error
}
//so far there shouldn't be an answer from the DNS server (nothing after \r\n\r\n)
if(szPosEnd[4])
{
OutputToLog(OUTPUT_ALL, "DNS server answered before request");
return 0; //error
}
return 1; //o.k.
}
//thread for connecting the SOCKS server and resolving the DNS request
THREAD_FUNCTION(DnsThread, pEntry)
{
uint16_t u16aBuf[32769]; //max DNS packet length 2(length)+65535(data) bytes - using uint16_t here for alignment
struct SEntry* psEntry=(struct SEntry*)pEntry;
char* szErrMsg;
SOCKET hSock=socket(g_sSocksAddr.ss_family, SOCK_STREAM, IPPROTO_TCP);
int iRet;
int iPos;
int iLen;
if(hSock==SOCKET_ERROR)
{
szErrMsg=GetSysError(WSAGetLastError());
OutputToLog(OUTPUT_ALL, "Creating a TCP socket has failed: %s", szErrMsg);
FreeSysError(szErrMsg);
RemoveEntry(psEntry, hSock, g_bCacheEnabled);
return 0;
}
//connect SOCKS server or HTTP proxy
if(connect(hSock, (struct sockaddr*)&g_sSocksAddr, GetAddrLen(&g_sSocksAddr))==SOCKET_ERROR)
{
szErrMsg=GetSysError(WSAGetLastError());
OutputToLog(OUTPUT_ALL, "Connecting the SOCKS server has failed: %s", szErrMsg);
FreeSysError(szErrMsg);
RemoveEntry(psEntry, hSock, g_bCacheEnabled);
return 0;
}
//using HTTP proxy instead of SOCKS?
if(g_iHttpProxyConnectLen)
{
if(!HandleHttpProxy(hSock, (char*)u16aBuf))
{
RemoveEntry(psEntry, hSock, g_bCacheEnabled);
return 0;
}
}
else
{
((uint8_t*)u16aBuf)[0]=5; //version 5 as we use SOCKS5
((uint8_t*)u16aBuf)[1]=1; //number of authentication methods supported
((uint8_t*)u16aBuf)[2]=g_uaUsrPwd?2:0; //user/password authentication or no authentication
iLen=send(hSock, (const char*)u16aBuf, 3, MSG_NOSIGNAL);
if(iLen!=3)
{
szErrMsg=(iLen==SOCKET_ERROR)?GetSysError(WSAGetLastError()):"Invalid amount of sent bytes";
OutputToLog(OUTPUT_ALL, "Sending to SOCKS server has failed: %s", szErrMsg);
if(iLen==SOCKET_ERROR)
FreeSysError(szErrMsg);
RemoveEntry(psEntry, hSock, g_bCacheEnabled);
return 0;
}
//check respons
if(!ReceiveBytes(hSock, 2, u16aBuf+8))
{
RemoveEntry(psEntry, hSock, g_bCacheEnabled);
return 0;
}
if(((uint8_t*)u16aBuf)[16]!=5 || ((uint8_t*)u16aBuf)[17]!=((uint8_t*)u16aBuf)[2])
{
if(((uint8_t*)u16aBuf)[16]!=5)
OutputToLog(OUTPUT_ALL, "The SOCKS server has answered with a SOCKS version number unequal to 5");
else if(g_uaUsrPwd)
OutputToLog(OUTPUT_ALL, "The SOCKS server does not support user/password authentication");
else
OutputToLog(OUTPUT_ALL, "The SOCKS server wants an authentication");
RemoveEntry(psEntry, hSock, g_bCacheEnabled);
return 0;
}
//send authentication if enabled
if(g_uaUsrPwd)
{
iLen=send(hSock, (const char*)g_uaUsrPwd, g_iUsrPwdLen, MSG_NOSIGNAL);
if(iLen!=g_iUsrPwdLen)
{
szErrMsg=(iLen==SOCKET_ERROR)?GetSysError(WSAGetLastError()):"Invalid amount of sent bytes";
OutputToLog(OUTPUT_ALL, "Sending to SOCKS server has failed: %s", szErrMsg);
if(iLen==SOCKET_ERROR)
FreeSysError(szErrMsg);
RemoveEntry(psEntry, hSock, g_bCacheEnabled);
return 0;
}
//check response
if(!ReceiveBytes(hSock, 2, u16aBuf+8))
{
RemoveEntry(psEntry, hSock, g_bCacheEnabled);
return 0;
}
if(((uint8_t*)u16aBuf)[17]) //2nd byte of answer must be 0 for "success"
{
OutputToLog(OUTPUT_ALL, "The SOCKS server authentication has failed (error code %u)", (unsigned int)((uint8_t*)u16aBuf)[17]);
RemoveEntry(psEntry, hSock, g_bCacheEnabled);
return 0;
}
}
//connect DNS server via SOCKS, the DNS server must support TCP
((uint8_t*)u16aBuf)[1]=1; //establish a TCP/IP stream connection
((uint8_t*)u16aBuf)[2]=0; //reserved, must be 0x00
switch(g_sDnsSrvAddr.ss_family)
{
case AF_UNSPEC: //use name
((uint8_t*)u16aBuf)[3]=3; //name
iLen=(int)((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_flowinfo; //length in sin6_flowinfo (see ParseIpAndPort)
((uint8_t*)u16aBuf)[4]=(uint8_t)iLen; //maximum length is 255
memcpy(((uint8_t*)u16aBuf)+5, *(char**)&((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_addr, iLen); //copy name (see ParseIpAndPort)
*(uint16_t*)(((uint8_t*)u16aBuf)+iLen+5)=((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_port; //port
iPos=iLen+7;
break;
case AF_INET: //use IPv4
((uint8_t*)u16aBuf)[3]=1; //IPv4 address
*(uint32_t*)(u16aBuf+2)=((struct sockaddr_in*)&g_sDnsSrvAddr)->sin_addr.s_addr; //address
*(uint16_t*)(u16aBuf+4)=((struct sockaddr_in*)&g_sDnsSrvAddr)->sin_port; //port
iPos=10;
break;
default: //use IPv6
((uint8_t*)u16aBuf)[3]=4; //IPv6 address
memcpy(u16aBuf+2, &((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_addr, 16); //address
*(uint16_t*)(u16aBuf+10)=((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_port; //port
iPos=22;
}
iLen=send(hSock, (const char*)u16aBuf, iPos, MSG_NOSIGNAL);
if(iLen!=iPos)
{
szErrMsg=(iLen==SOCKET_ERROR)?GetSysError(WSAGetLastError()):"Invalid amount of sent bytes";
OutputToLog(OUTPUT_ALL, "Connecting through SOCKS server has failed: %s", szErrMsg);
if(iLen==SOCKET_ERROR)
FreeSysError(szErrMsg);
RemoveEntry(psEntry, hSock, g_bCacheEnabled);
return 0;
}
//check expected answer (get first 5 bytes to detect address type)
if(!ReceiveBytes(hSock, 5, u16aBuf))
{
RemoveEntry(psEntry, hSock, g_bCacheEnabled);
return 0;
}
if(((uint8_t*)u16aBuf)[0]!=5 || ((uint8_t*)u16aBuf)[1]!=0 || (((uint8_t*)u16aBuf)[3]!=1 && ((uint8_t*)u16aBuf)[3]!=3 && ((uint8_t*)u16aBuf)[3]!=4))
{
szErrMsg="Unexpected answer from SOCKS server";
//correct version -> try to resolve the error code
if(((uint8_t*)u16aBuf)[0]==5)
switch(((uint8_t*)u16aBuf)[1])
{
case 2:
szErrMsg="Connection not allowed by ruleset";
break;
case 3:
szErrMsg="Network unreachable";
break;
case 4:
szErrMsg="Host unreachable";
break;
case 5:
szErrMsg="Connection refused by destination host";
break;
case 6:
szErrMsg="TTL expired";
break;
case 7:
szErrMsg="Command not supported / protocol error";
break;
case 8:
szErrMsg="Address type not supported";
}
OutputToLog(OUTPUT_ALL, "Connecting through SOCKS server has failed: %s", szErrMsg);
RemoveEntry(psEntry, hSock, g_bCacheEnabled);
return 0;
}
//get rest of answer
//all bytes after that are part of the "normal" communication
switch(((uint8_t*)u16aBuf)[3])
{
case 1: //IPv4
iLen=5;
break;
case 4: //IPv6
iLen=17;
break;
default: //name
iLen=2+((uint8_t*)u16aBuf)[4]; //port length plus length of name
}
if(!ReceiveBytes(hSock, iLen, u16aBuf))
{
RemoveEntry(psEntry, hSock, g_bCacheEnabled);
return 0;
}
}
//send DNS request via SOCKS; TCP variant starts with 2 byte length field
//add ECS?
if(g_iEcsOptionLen)
iPos=AddEcsOption(psEntry->u16aRequest, u16aBuf);
else
iPos=-1; //no ECS -> send original message
switch(iPos)
{
case 0: //error
RemoveEntry(psEntry, hSock, g_bCacheEnabled);
return 0;
case -1: //send original message
iPos=2+ntohs(*psEntry->u16aRequest);
iLen=send(hSock, (const char*)psEntry->u16aRequest, iPos, MSG_NOSIGNAL);
break;
default: //send message created by AddEcsOption
iLen=send(hSock, (const char*)u16aBuf, iPos, MSG_NOSIGNAL);
}
//error or not sent all bytes?
if(iLen!=iPos)
{
szErrMsg=(iLen==SOCKET_ERROR)?GetSysError(WSAGetLastError()):"Invalid amount of sent bytes";
OutputToLog(OUTPUT_ALL, "Sending through SOCKS server has failed: %s", szErrMsg);
if(iLen==SOCKET_ERROR)
FreeSysError(szErrMsg);
RemoveEntry(psEntry, hSock, g_bCacheEnabled);
return 0;
}
//receive answer
iPos=0;
for(;;)
{
iRet=recv(hSock, (char*)u16aBuf+iPos, sizeof(u16aBuf)-iPos, 0);
if(iRet<=0)
{
szErrMsg=(iRet==SOCKET_ERROR)?GetSysError(WSAGetLastError()):"Server has closed the connection unexpectedly";
OutputToLog(OUTPUT_ALL, "Broken answer from DNS server: %s", szErrMsg);
if(iRet==SOCKET_ERROR)
FreeSysError(szErrMsg);
RemoveEntry(psEntry, hSock, g_bCacheEnabled);
return 0;
}
iPos+=iRet;
//first 2 bytes contain length (Big Endian)
if(iPos>=2)
{
iLen=2+ntohs(*u16aBuf); //iLen maximum is 2+65535 (smaller than sizeof(u16aBuf))
if(iPos>=iLen)
break; //answer completely received
}
}
//invalid answer?
if(iPos<=4)
{
OutputToLog(OUTPUT_ALL, "Answer from DNS server too short!");
RemoveEntry(psEntry, hSock, g_bCacheEnabled);
return 0;
}
closesocket(hSock);
if(g_bCacheEnabled)
{
//store answer in cache
EnterCriticalSection(&g_sCritSect);
psEntry->u16aAnswer=(uint16_t*)malloc(iLen);
memcpy(psEntry->u16aAnswer, u16aBuf, iLen);
//copy ID
psEntry->u16aAnswer[1]=psEntry->u16aRequest[1];
//remember current time for time to live calculations
psEntry->iTime=time(NULL);
//send DNS answer to original requesting client via UDP or TCP
SendAnswer(psEntry);
LeaveCriticalSection(&g_sCritSect);
}
else
{
//send DNS answer to original requesting client via UDP or TCP
psEntry->u16aAnswer=u16aBuf;
SendAnswer(psEntry);
free(psEntry);
}
return 0;
}
//searches the cache for the same request and sends the answer if there is a cache hit
//or creates a thread for forwarding the request to the DNS server via SOCKS
static void HandleDnsRequest(uint16_t* u16aRequest, int iLen, void* pClientAddr, socklen_t iAddrLen)
{
uint8_t* pu8Pos;
uint8_t* pu8End;
struct SEntry* psEntry;
unsigned char uLen;
//search in cache
EnterCriticalSection(&g_sCritSect);
for(psEntry=g_psFirst; ; psEntry=psEntry->psNext)
{
if(!psEntry)
{
//create new entry (length of struct SEntry up to u16aRequest plus request length plus 2 for length)
psEntry=(struct SEntry*)malloc(((uint8_t*)psEntry->u16aRequest-(uint8_t*)psEntry)+iLen+2);
psEntry->iTime=(time_t)-1;
psEntry->u16aAnswer=NULL;
psEntry->uAddrLen=(unsigned char)iAddrLen;
memcpy(&psEntry->client, pClientAddr, iAddrLen);
*psEntry->u16aRequest=htons((uint16_t)iLen);
memcpy(psEntry->u16aRequest+1, u16aRequest, iLen);
//add entry to cache list in case cache is enabled
psEntry->psNext=g_psFirst;
if(g_bCacheEnabled)
g_psFirst=psEntry;
//create thread to resolve entry
if(ThreadCreate(DnsThread, psEntry))
{
++g_uCacheCount;
//output amount of entries and current entry
pu8Pos=(uint8_t*)u16aRequest+12;
pu8End=(uint8_t*)u16aRequest+iLen;
while(pu8Pos=0xc0) //compression used? (reference to other name via offset)
break; //no output in this case
*pu8Pos='.'; //replace length by .
pu8Pos+=uLen+1;
}
}
else
{
//remove entry from cache list
g_psFirst=psEntry->psNext;
free(psEntry);
}
break;
}
//cache hit? (do not compare ID in first 2 bytes of request)
if((uint16_t)iLen==ntohs(*psEntry->u16aRequest) && memcmp(u16aRequest+1, psEntry->u16aRequest+2, iLen-2)==0)
{
//answer already received?
if(psEntry->u16aAnswer)
{
//answer to current address
psEntry->uAddrLen=(unsigned char)iAddrLen;
memcpy(&psEntry->client, pClientAddr, iAddrLen);
//check if expired
if(!CalculateTimeToLive(psEntry))
{
//expired -> kill last answer
free(psEntry->u16aAnswer);
psEntry->u16aAnswer=NULL;
//copy current ID
psEntry->u16aRequest[1]=*u16aRequest;
//create thread to resolve request again
if(!ThreadCreate(DnsThread, psEntry))
RemoveEntry(psEntry, (SOCKET)SOCKET_ERROR, 0);
}
else
{
//use current ID
psEntry->u16aAnswer[1]=*u16aRequest;
SendAnswer(psEntry);
}
}
else
{
//copy current ID so the thread uses that one if it gets the answer
psEntry->u16aRequest[1]=*u16aRequest;
//overwrite address; currently we can only handle one request address while waiting for an answer through SOCKS
//current address is TCP? -> need to close it before overwriting it
if(psEntry->uAddrLen==sizeof(SOCKET))
closesocket(psEntry->client.hSock);
psEntry->uAddrLen=(unsigned char)iAddrLen;
memcpy(&psEntry->client, pClientAddr, iAddrLen);
}
break;
}
}
LeaveCriticalSection(&g_sCritSect);
}
//outputs an error caused by "bind" and closes the according socket
static void OutputBindError(SOCKET hSock, struct sockaddr_storage* psAddr, int bUdp)
{
char szAddr[256];
char szNo[16];
char* szErrMsg=GetSysError(WSAGetLastError());
closesocket(hSock);
if(getnameinfo((struct sockaddr*)psAddr, GetAddrLen(psAddr), szAddr, sizeof(szAddr), szNo, sizeof(szNo), NI_NUMERICHOST|NI_NUMERICSERV))
{
//should never happen
strcpy(szAddr, "unknown address");
strcpy(szNo, "unknown");
}
//UDP is mandatory, TCP is optional
if(bUdp)
{
//need { ... } on *nix as OutputFatal is a multi-line macro there
OutputFatal("\nBinding on %s, UDP port %s has failed: %s\n", szAddr, szNo, szErrMsg);
}
else
OutputToLog(OUTPUT_ALL, "Binding on %s, TCP port %s has failed: %s", szAddr, szNo, szErrMsg);
FreeSysError(szErrMsg);
}
//thread for receiving DNS requests via TCP
THREAD_FUNCTION(TcpThread, pAddr)
{
SOCKET hSockServer;
SOCKET hSock;
uint16_t u16aBuf[32769]; //maximum possible DNS request size plus one byte - using uint16_t here for alignment; the actual size is stored in the first 2 bytes which can have a maximum value of 0xffff
socklen_t iAddrLen;
int iCurBufLen;
int iLen;
uint16_t u16ReqLen;
struct sockaddr_storage sAddr;
char* szErrMsg;
hSockServer=socket(((struct sockaddr_storage*)pAddr)->ss_family, SOCK_STREAM, IPPROTO_TCP);
if(hSockServer==SOCKET_ERROR)
{
szErrMsg=GetSysError(WSAGetLastError());
OutputToLog(OUTPUT_ALL, "Creating a TCP socket has failed: %s", szErrMsg);
FreeSysError(szErrMsg);
return 0;
}
//bind+listen on local TCP port (get DNS requests)
if(bind(hSockServer, (struct sockaddr*)pAddr, GetAddrLen((struct sockaddr_storage*)pAddr))==SOCKET_ERROR)
{
OutputBindError(hSockServer, (struct sockaddr_storage*)pAddr, 0);
return 0;
}
if(listen(hSockServer, 5)==SOCKET_ERROR)
{
szErrMsg=GetSysError(WSAGetLastError());
closesocket(hSockServer);
OutputToLog(OUTPUT_ALL, "Listening on TCP socket has failed: %s", szErrMsg);
FreeSysError(szErrMsg);
return 0;
}
//as long as "accept" is working
for(;;)
{
iAddrLen=sizeof(sAddr);
hSock=accept(hSockServer, (struct sockaddr*)&sAddr, &iAddrLen);
if(hSock==SOCKET_ERROR)
{
szErrMsg=GetSysError(WSAGetLastError());
OutputToLog(OUTPUT_ALL, "Accepting new connection on TCP socket has failed: %s", szErrMsg);
FreeSysError(szErrMsg);
break;
}
//collect the whole DNS request
iCurBufLen=0;
for(;;)
{
iLen=recv(hSock, (char*)u16aBuf+iCurBufLen, sizeof(u16aBuf)-iCurBufLen, 0);
if(iLen<=0)
{
szErrMsg=GetSysError(WSAGetLastError());
closesocket(hSock);
OutputToLog(OUTPUT_ALL, "DNS request on TCP broken: %s", szErrMsg);
FreeSysError(szErrMsg);
break;
}
iCurBufLen+=iLen;
//got the whole DNS request?
if(iCurBufLen>=2 && (u16ReqLen=ntohs(*u16aBuf))+2>=iCurBufLen)
{
if(u16ReqLen>12) //12 bytes header plus at least one byte of data
HandleDnsRequest(u16aBuf+1, u16ReqLen, &hSock, sizeof(hSock)); //HandleDnsRequest takes care of hSock now
else
closesocket(hSock);
break;
}
}
}
closesocket(hSockServer);
return 0;
}
//parses parameter /e:IP/Bits
static int ParseEcs(char* szIpAndAmountBits)
{
struct addrinfo sHint;
struct addrinfo* psResult;
char* szPos=strchr(szIpAndAmountBits, '/');
int iBits;
int iMaxBits;
if(!szPos)
return 0; //error
*szPos='\0'; //overwrite '/' for getaddrinfo below
//prepare hint: only numeric values
memset(&sHint, 0, sizeof(sHint));
sHint.ai_family=AF_UNSPEC;
sHint.ai_flags=AI_NUMERICHOST;
//now resolve it
if(getaddrinfo(szIpAndAmountBits, NULL, &sHint, &psResult))
{
//some getaddrinfo implementations seem to have some trouble, so try the old IPv4 variant additionally
//use sHint for that
((struct in_addr*)(g_u8aEcsOption+8))->s_addr=inet_addr(szIpAndAmountBits);
if(INADDR_NONE==((struct in_addr*)(g_u8aEcsOption+8))->s_addr)
{
OutputFatal("\nInvalid address '%s' specified for EDNS Client Subnet!\n", szIpAndAmountBits);
return 0; //error
}
g_u8aEcsOption[5]=1; //FAMILY IPv4
iMaxBits=32;
}
else
{
switch(psResult->ai_family)
{
case AF_INET:
memcpy(g_u8aEcsOption+8, &((struct sockaddr_in*)psResult->ai_addr)->sin_addr, 4);
g_u8aEcsOption[5]=1; //FAMILY IPv4
iMaxBits=32;
break;
case AF_INET6:
memcpy(g_u8aEcsOption+8, &((struct sockaddr_in6*)psResult->ai_addr)->sin6_addr, 16);
g_u8aEcsOption[5]=2; //FAMILY IPv6
iMaxBits=128;
break;
default:
OutputFatal("\nInvalid address '%s' specified for EDNS Client Subnet!\n", szIpAndAmountBits);
return 0; //error
}
}
//check amount of bits to use
iBits=atoi(szPos+1);
if(iBits<=0 || iBits>iMaxBits)
{
OutputFatal("\nInvalid amount of bits '%s' specified for EDNS Client Subnet!\n", szPos+1);
return 0; //error
}
//OPTION-CODE
g_u8aEcsOption[0]=0;
g_u8aEcsOption[1]=8;
//OPTION-LENGTH
g_u8aEcsOption[2]=0;
g_iEcsOptionLen=8+(iBits+7)/8; //round up to amount bytes
g_u8aEcsOption[3]=(uint8_t)(g_iEcsOptionLen-4);
//mask last byte of address
g_u8aEcsOption[g_iEcsOptionLen-1]&=(uint8_t)(0xff<<(7-((iBits+7)&7)));
//set rest to 0
memset(g_u8aEcsOption+g_iEcsOptionLen, 0, sizeof(g_u8aEcsOption)-g_iEcsOptionLen);
//1st byte of FAMILY always 0
g_u8aEcsOption[4]=0;
//SOURCE PREFIX-LENGTH
g_u8aEcsOption[6]=(uint8_t)iBits;
//SCOPE PREFIX-LENGTH always 0
g_u8aEcsOption[7]=0;
return 1; //o.k.
}
//parses a command line parameter of the format IPv4 or IPv4:port or IPv6 or [IPv6]:port
static int ParseIpAndPort(int iFlag, const char* szParamName, const char* szPort, char* szIpAndPort, struct sockaddr_storage* psAddr)
{
struct addrinfo sHint;
struct addrinfo* psResult;
char* szPos;
if(strchr(szIpAndPort, '.'))
{
//seems to be IPv4
szPos=strchr(szIpAndPort, ':');
if(szPos)
{
*szPos='\0'; //overwrite ':' for getaddrinfo below
szPort=szPos+1;
}
}
else
{
//seems to be IPv6
//format [IPv6]:port?
if(*szIpAndPort=='[')
{
++szIpAndPort;
szPos=strchr(szIpAndPort, ']');
if(szPos)
{
*szPos='\0'; //overwrite ']' for getaddrinfo below
//is there a port specification?
if(szPos[1]==':')
szPort=szPos+2;
}
}
}
//prepare hint: only numeric values
memset(&sHint, 0, sizeof(sHint));
sHint.ai_family=AF_UNSPEC;
sHint.ai_flags=iFlag|AI_NUMERICHOST|AI_NUMERICSERV;
//now resolve it
if(getaddrinfo(szIpAndPort, szPort, &sHint, &psResult))
{
//some getaddrinfo implementations seem to have some trouble, so try the old IPv4 variant additionally
((struct sockaddr_in*)psAddr)->sin_addr.s_addr=inet_addr(szIpAndPort);
if(INADDR_NONE!=((struct sockaddr_in*)psAddr)->sin_addr.s_addr)
{
((struct sockaddr_in*)psAddr)->sin_port=htons((uint16_t)atoi(szPort));
if(((struct sockaddr_in*)psAddr)->sin_port)
{
((struct sockaddr_in*)psAddr)->sin_family=AF_INET;
return 1; //o.k.
}
}
//only for the DNS server also support name
if(&g_sDnsSrvAddr==psAddr)
{
size_t uLen=strlen(szIpAndPort);
if(uLen<256 && uLen)
{
((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_port=htons((uint16_t)atoi(szPort));
if(((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_port)
{
g_sDnsSrvAddr.ss_family=AF_UNSPEC; //this marks usage of name
*(char**)&((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_addr=szIpAndPort; //use sin6_addr for pointer to name (128 bit -> large enough, alignment should also be fine)
((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_flowinfo=(uint8_t)uLen; //use sin6_flowinfo for length
return 1; //o.k.
}
}
}
OutputFatal("\nInvalid address '%s' and port '%s' specified for %s!\n", szIpAndPort, szPort, szParamName);
return 0; //error
}
if(psResult->ai_addrlen>sizeof(*psAddr))
{
//should never happen
OutputFatal("\nAddress '%s' and port '%s' specified for %s too long for internal storage!\n", szIpAndPort, szPort, szParamName);
return 0; //error
}
//copy 1st result
memcpy(psAddr, psResult->ai_addr, psResult->ai_addrlen);
freeaddrinfo(psResult);
return 1; //o.k.
}
//creates CONNECT command for HTTP proxy containing DNS address
static int CreateHttpProxyConnectCommand()
{
memcpy(g_caHttpProxyConnect, "CONNECT ", 8);
g_iHttpProxyConnectLen=8;
if(g_sDnsSrvAddr.ss_family==AF_UNSPEC)
{
//sin6_addr contains pointer to name, see ParseIpAndPort (max. length is 255)
int iLenDnsAddr=sprintf(g_caHttpProxyConnect+g_iHttpProxyConnectLen, "%s:%u", *(char**)&((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_addr, ntohs(((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_port));
if(iLenDnsAddr<=0)
return 0; //error
g_iHttpProxyConnectLen+=iLenDnsAddr;
}
else
{
//add IP and port to g_caHttpProxyConnect
char szPort[6];
int iLenPort;
if(g_sDnsSrvAddr.ss_family==AF_INET6)
g_caHttpProxyConnect[g_iHttpProxyConnectLen++]='['; //enclose with [] for IPv6
if(getnameinfo((struct sockaddr*)&g_sDnsSrvAddr, GetAddrLen(&g_sDnsSrvAddr), g_caHttpProxyConnect+g_iHttpProxyConnectLen, 128, szPort, sizeof(szPort), NI_NUMERICHOST|NI_NUMERICSERV))
return 0; //error
g_iHttpProxyConnectLen+=(int)strlen(g_caHttpProxyConnect+g_iHttpProxyConnectLen);
if(g_sDnsSrvAddr.ss_family==AF_INET6)
g_caHttpProxyConnect[g_iHttpProxyConnectLen++]=']'; //enclose with [] for IPv6
g_caHttpProxyConnect[g_iHttpProxyConnectLen++]=':';
iLenPort=(int)strlen(szPort);
memcpy(g_caHttpProxyConnect+g_iHttpProxyConnectLen, szPort, iLenPort);
g_iHttpProxyConnectLen+=iLenPort;
}
//complete command
memcpy(g_caHttpProxyConnect+g_iHttpProxyConnectLen, " HTTP/1.0\r\n\r\n", 13);
g_iHttpProxyConnectLen+=13;
return 1; //o.k.
}
int main(int iArgCount, char** szaArgs)
{
static struct sockaddr_storage sAddr; //make it static so the lower array assignment causes no compiler warning
const char* szUser;
const char* szPassword;
char** pszCurArg;
char* szCurArg;
char* szLogFilePath;
char* szErrMsg;
size_t uUserLen;
size_t uPasswordLen;
socklen_t iAddrLen;
int iAddrCount;
int iLen;
int bAppend; //append log file?
int bQuiet; //parameter q specified?
uint16_t u16aBuf[32754]; //max UDP packet length on Windows plus one byte - using uint16_t here for alignment
//struct array for the three addresses passed via command line
struct SAddr
{
struct sockaddr_storage* psAddr;
const char* szDefaultAddress;
const char* szDefaultPort;
const char* szName;
int iFlag;
} saAddresses[3]=
{
{ &g_sSocksAddr, DEFAULT_SOCKS_SERVER, DEFAULT_SOCKS_PORT, "SOCKS server", 0 },
{ &g_sDnsSrvAddr, DEFAULT_DNS_SERVER, DEFAULT_DNS_PORT, "DNS server", 0 },
{ &sAddr, DEFAULT_LISTEN_IP, DEFAULT_DNS_PORT, "listening", AI_PASSIVE } //AI_PASSIVE for "getaddrinfo" as we use it for "bind"
};
//parse command line - use e.g. "/?" to display the usage
bQuiet=0;
bAppend=0;
iAddrCount=0;
szLogFilePath=NULL;
szUser=NULL;
szPassword=NULL;
for(pszCurArg=szaArgs+1; --iArgCount; ++pszCurArg)
{
szCurArg=*pszCurArg;
//no address parameter?
if(*szCurArg=='-' || *szCurArg=='/')
{
switch(szCurArg[1])
{
case 'd': //disable cache?
case 'D':
if(!szCurArg[2])
{
g_bCacheEnabled=0;
continue; //correct parameter, go to next one
}
break;
case 'q': //no console output?
case 'Q':
if(!szCurArg[2])
{
bQuiet=1;
continue; //correct parameter, go to next one
}
break;
case 'l': //log output?
case 'L':
if(!szLogFilePath) //only allowed once
{
bAppend=(szCurArg[2]=='a' || szCurArg[2]=='A'); //append?
if(szCurArg[2+bAppend]==':')
{
szLogFilePath=szCurArg+3+bAppend;
continue; //correct parameter, go to next one
}
}
break;
case 'u': //user?
case 'U':
if(!szUser && szCurArg[2]==':') //only allowed once and : must be 2nd char
{
szUser=szCurArg+3;
continue; //correct parameter, go to next one
}
break;
case 'p': //password?
case 'P':
if(!szPassword && szCurArg[2]==':') //only allowed once and : must be 2nd char
{
szPassword=szCurArg+3;
continue; //correct parameter, go to next one
}
break;
case 't': //HTTP proxy?
case 'T':
g_iHttpProxyConnectLen=1; //used as boolean here; later it contains the real length of the CONNECT command
continue; //correct parameter, go to next one
case 'e': //EDNS Client Subnet
case 'E':
//only allowed once
if(!g_iEcsOptionLen)
{
g_bForceEcs=(szCurArg[2]=='f' || szCurArg[2]=='F'); //force?
szCurArg+=g_bForceEcs;
if(szCurArg[2]==':' && ParseEcs(szCurArg+3))
continue; //correct parameter, go to next one
}
}
//wrong/unknown parameter; display help
}
else
{
//try to parse address
if(iAddrCount display usage and stop
OutputFatal("\nDNS2SOCKS tunnels DNS requests via SOCKS5 and caches the answers.\n\n\n"
"Usage:\n\n"
"DNS2SOCKS [/?] [/t] [/d] [/q] [/l[a]:FilePath] [/u:User /p:Password]\n"
" [/e[f]:IP/Bits]\n"
" [Socks5ServerIP[:Port]] [DNSServerIPorName[:Port]] [ListenIP[:Port]]\n\n"
"/? to view this help\n"
"/t to use a HTTP proxy instead of a SOCKS server\n"
" (here: Socks5ServerIP = HttpProxyIP, no support for /u and /p)\n"
"/d to disable the cache\n"
"/q to suppress the text output\n"
"/l:FilePath to create a new log file \"FilePath\"\n"
"/la:FilePath to create a new log file or append to the existing \"FilePath\"\n"
"/u:User user name if your SOCKS server uses user/password authentication\n"
"/p:Password password if your SOCKS server uses user/password authentication\n"
"/e:IP/Bits to enable and specify EDNS client subnet in DNS queries\n"
"/ef:IP/Bits same as above but also replaces EDNS client subnet if it exists\n\n"
"Default Socks5ServerIP:Port = %s:%s\n"
"Default DNSServerIPorName:Port = %s:%s\n"
"Default ListenIP:Port = %s:%s\n",
DEFAULT_SOCKS_SERVER, DEFAULT_SOCKS_PORT,
DEFAULT_DNS_SERVER, DEFAULT_DNS_PORT,
DEFAULT_LISTEN_IP, DEFAULT_DNS_PORT);
return 1;
}
if(szPassword && !szUser)
{
OutputFatal("\nPassword specified but no user!\n");
return 1;
}
if(!szPassword && szUser)
{
OutputFatal("\nUser specified but no password!\n");
return 1;
}
if(g_iHttpProxyConnectLen && szPassword)
{
OutputFatal("\nAuthentication not supported for HTTP proxy!\n");
return 1;
}
if(szUser)
{
uUserLen=strlen(szUser);
if(uUserLen>255)
{
OutputFatal("\nUser exceeds 255 characters!\n");
return 1;
}
uPasswordLen=strlen(szPassword);
if(uPasswordLen>255)
{
OutputFatal("\nPassword exceeds 255 characters!\n");
return 1;
}
}
else
{
//initialize uUserLen and uPasswordLen - otherwise VC++ 2010 outputs a wrong warning
uUserLen=0;
uPasswordLen=0;
}
//fill unspecified addresses with default values
while(iAddrCountsin6_addr);
sprintf((char*)u16aBuf+768, "%hu", ntohs(((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_port));
}
else if(getnameinfo((struct sockaddr*)&g_sDnsSrvAddr, GetAddrLen(&g_sDnsSrvAddr), (char*)u16aBuf+512, 256, (char*)u16aBuf+768, 256, NI_NUMERICHOST|NI_NUMERICSERV))
{
//should never happen
strcpy((char*)u16aBuf+512, "unknown address");
strcpy((char*)u16aBuf+768, "unknown");
}
if(getnameinfo((struct sockaddr*)&sAddr, GetAddrLen(&sAddr), (char*)u16aBuf+1024, 256, (char*)u16aBuf+1280, 256, NI_NUMERICHOST|NI_NUMERICSERV))
{
//should never happen
strcpy((char*)u16aBuf+1024, "unknown address");
strcpy((char*)u16aBuf+1280, "unknown");
}
//use (char*)u16aBuf+1536 as string buffer to output information about ECS
if(g_iEcsOptionLen)
{
struct sockaddr_storage sEcsAddr;
socklen_t uSockLen;
memset(&sEcsAddr, 0, sizeof(sEcsAddr));
//IPv6?
if(g_u8aEcsOption[5]==2)
{
uSockLen=sizeof(struct sockaddr_in6);
((struct sockaddr_in6*)&sEcsAddr)->sin6_family=AF_INET6;
memcpy(&((struct sockaddr_in6*)&sEcsAddr)->sin6_addr, g_u8aEcsOption+8, 16);
}
else
{
uSockLen=sizeof(struct sockaddr_in);
((struct sockaddr_in*)&sEcsAddr)->sin_family=AF_INET;
memcpy(&((struct sockaddr_in*)&sEcsAddr)->sin_addr, g_u8aEcsOption+8, 4);
}
if(getnameinfo((struct sockaddr*)&sEcsAddr, uSockLen, (char*)u16aBuf+1536, 256, NULL, 0, NI_NUMERICHOST))
{
//should never happen
strcpy((char*)u16aBuf+1536, "unknown address");
}
else
//add amount of bits
sprintf((char*)u16aBuf+1536+strlen((char*)u16aBuf+1536), "/%u", (unsigned int)g_u8aEcsOption[6]);
//add " forced" if g_bForceEcs
if(g_bForceEcs)
strcat((char*)u16aBuf+1536, " forced");
}
else
strcpy((char*)u16aBuf+1536, "disabled");
//output configuration
OutputToLog(OUTPUT_LINE_BREAK|OUTPUT_CONSOLE, "%s %s port %s\n"
"DNS server %s port %s\n"
"listening on %s port %s\n"
"cache %s\n"
"authentication %s\n"
"EDNS client subnet %s\n",
g_iHttpProxyConnectLen?"HTTP proxy ":"SOCKS server", (char*)u16aBuf, (char*)u16aBuf+256,
(char*)u16aBuf+512, (char*)u16aBuf+768,
(char*)u16aBuf+1024, (char*)u16aBuf+1280,
g_bCacheEnabled?"enabled":"disabled",
szUser?"enabled":"disabled",
(char*)u16aBuf+1536);
InitializeCriticalSection(&g_sCritSect);
//log file was requested?
if(szLogFilePath)
OpenLogFile(szLogFilePath, bAppend);
//create authentication package if user/password was specified
if(szUser)
{
g_iUsrPwdLen=(int)(uUserLen+uPasswordLen+3);
g_uaUsrPwd=(unsigned char*)malloc(g_iUsrPwdLen);
g_uaUsrPwd[0]=1; //version 1
g_uaUsrPwd[1]=(unsigned char)uUserLen;
memcpy(g_uaUsrPwd+2, szUser, uUserLen);
g_uaUsrPwd[uUserLen+2]=(unsigned char)uPasswordLen;
memcpy(g_uaUsrPwd+uUserLen+3, szPassword, uPasswordLen);
}
//create thread for TCP connection
ThreadCreate(TcpThread, &sAddr);
//endless loop
for(;;)
{
//receive DNS request
iAddrLen=sizeof(sAddr);
iLen=recvfrom(g_hSockUdp, (char*)u16aBuf, sizeof(u16aBuf), 0, (struct sockaddr*)&sAddr, &iAddrLen);
if(iLen>12) //12 bytes header plus at least one byte of data
HandleDnsRequest(u16aBuf, iLen, &sAddr, iAddrLen);
}
/*no cleanup code as we have an endless loop
we would need something like this:
terminate all threads and clear their resources
free(g_uaUsrPwd);
DeleteCriticalSection(&g_sCritSect);
closesocket(g_hSockUdp);
free all cache entries
close the console
close the log file
return 0;*/
}
#ifdef _WIN32
//entry function for Windows applications - just forwards to "main"
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int iCmdShow)
{
WSADATA sData;
char* szErrMsg;
int iErrNo;
hInstance;
hPrevInstance;
szCmdLine;
iCmdShow;
iErrNo=WSAStartup(0x202, &sData);
if(iErrNo)
{
szErrMsg=GetSysError(iErrNo);
OutputFatal("\nWSAStartup has failed: %s\n", szErrMsg);
FreeSysError(szErrMsg);
return 1;
}
//call "main" with the already parsed command line parameters stored in global variables by CRT
iErrNo=main(__argc, __argv);
WSACleanup();
return iErrNo;
}
#endif //#ifdef _WIN32
================================================
FILE: tools/globalmitm/Dockerfile
================================================
FROM debian:bullseye-slim
LABEL maintainer="rev1si0n "
ENV VERSION=2.11.4
ENV PLAT=linux-386
ENV PYPIMIRROR=https://mirrors.ustc.edu.cn/pypi/web/simple
ENV GOST=https://github.com/ginuerzh/gost/releases/download/v${VERSION}/gost-${PLAT}-${VERSION}.gz
ENV BINDIR=/usr/local/bin
WORKDIR /tmp
RUN apt-get update && apt-get -y upgrade && apt install -y adb gcc wget dnsutils python3 python3-pip
RUN wget ${GOST} -O - | gzip -d> ${BINDIR}/gost
COPY startmitm.py ${BINDIR}
COPY globalmitm/entry ${BINDIR}
COPY globalmitm/DNS2SOCKS.c /tmp
COPY requirements.txt /tmp
RUN gcc -pthread DNS2SOCKS.c -o ${BINDIR}/DNS2SOCKS
RUN pip3 install -i ${PYPIMIRROR} --no-cache-dir -r requirements.txt
RUN chmod 755 ${BINDIR}/*
ENV PATH=${BINDIR}:${PATH}
WORKDIR /root
EXPOSE 53/udp 8118/tcp 1234/tcp
ENTRYPOINT [ "entry" ]
================================================
FILE: tools/globalmitm/entry
================================================
#!/bin/bash
set -e
export GRPC_DNS_RESOLVER=native
export PROXYPORT=${PROXYPORT:-8118}
die () {
echo $@; exit 1
}
IP=${1:-}
if [ -z "${IP}" ]; then
die "no device provided"
fi
DNS=${DNS:-8.8.8.8}
PDNS=$(((RANDOM % 10000) + 10000))
if [ ! -z "${SOCKS5}" ] || [ ! -z "${HTTP}" ]; then
PIDS=
if [ ! -z "${SOCKS5}" ]; then
PHTTP=$(((RANDOM % 10000) + 10000))
UPSTREAM=http://127.0.0.1:${PHTTP}
gost -L=${UPSTREAM} -F=socks5://${SOCKS5} >/dev/null 2>&1 &
PIDS="$PIDS $!"
DNS2SOCKS ${SOCKS5} ${DNS} 127.0.0.1:${PDNS} /q &
PIDS="$PIDS $!"
elif [ ! -z "${HTTP}" ]; then
UPSTREAM=http://${HTTP}
DNS2SOCKS ${HTTP} ${DNS} 127.0.0.1:${PDNS} /t /q &
PIDS="$PIDS $!"
else
die
fi
trap "kill -9 ${PIDS} 2>/dev/null" SIGINT
if ! dig +time=3 google.com @127.0.0.1 -p ${PDNS}; then
die "BAD PROXY (CANNOT RESOLVE THROUGH PROXY)"
fi
set -- "$@" --dns 127.0.0.1:${PDNS}
set -- "$@" --mode upstream:${UPSTREAM}
fi
# common arguments
set -- "$@" --web-host=0.0.0.0
set -- "$@" --web-port=${WEBPORT:-1234}
set -- "$@" --set block_global=false
startmitm.py $@
================================================
FILE: tools/id_rsa
================================================
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA4QHmY32OT+F+maERMn1cvBRuIOIXH9yOALG+GMCngtjRJzSR
n09dInmXE+PjiAqNRWvknVEjFywv0v1v/H2qSRJKR/togPgySjiABhigqDHdirNd
Dh63oN2e+d0yythoLzsQrH5BSVtw05Atpkr7bW4KdfMveWuddvDACnQ3mvCXq50X
IK3cOlmHXwcJrX55BhEXxgHIqw0upf0A7DC3Afz5xjOA6+K/O2EzZIIJ+sWw7/Ko
5+3m98Et0zwcxe20uNxzYf7JSMu3490YNckLiQcDrZVRUXNS70HO9HWCXKdjFJPi
GgPtKUQNHjChwzSCQQGzqitXdwa60i9Sy1U09wIBIwKCAQAzbiYIHLLQbg5PAD5x
8MS9Rn+SfNIV6UUHeRWCACZJyyh9/WMdGXRfpsNyQreqEQpZArfpcaGe5YdGK0zL
/3dhKMCFe0sWKhofl+K/kJnAC2XWj2W6FaZQp69PDfz7KiZxMhJwkeMJc/yIIPR9
x/70cOxyuz4NH+l6Rag8510qujhxn6lGc1+ZNHGeAYmUD7wFz9/QYDZU9C0YcylA
2+Q5woU3L4b3y3JKqB6/dnsP9uF7R5KBR+qiqOwVm0nhvVU5uwbpQwTXeklopXyN
rI/NeYcsDoEyb8NquVXX/GkOgY0FhqblGUR9kSdTLHq6VamelHc+dczZgMxcsq1c
mnnLAoGBAP2FPssFpHJRhSr38Fq5A7mEjQeiPq2WgYV7kCpJT5F5OymcCCETIjEH
4pTk5zCWUuIBx5LlzSa0XnQSYb0100ZzvDgfm1NPmqdkpPwkkbh2xyoHYTTPJ3WG
TDur8Qyi83NteveszO1TCAGBTe3zN+2ov9qzl5Y7QHF94GVFDo4NAoGBAOM1Q8eG
0KeqjutTz/UMtejoFpz0Hi1g32PfdQInHx8MDslYu3Fcpnos3xf59H7+mrRy0fUM
hh27v7DiUvxUfhlojf0F3kDKeg9VZBslZF3vTCpFdKdFouZ2Cru3lCoaPSau69BC
6HQw4P+RABrgxc6CeE9FUGEEMss+wTcRItITAoGBAO8ImkpkZ9mAD9gOV6X+5kEz
1W2Y+USVOEqns9AZPGSW4AKpDvqdAvsHbzvtxAk9RtUXnumW118B1WYf9cEG3SUr
SxBYUJ8B6ZaDdvxc/mwYOCBQGdK0sCz692t2OwvqGL1J95kQo/W0r8bnoT9wSqzg
72fN5rI33azVxPHExJSPAoGAGfd1deOFj4E057HOn6muY8LAwXr8InF4nbMjULQD
joUJARF0gfv1xNHtnFcUoMyj912UVoUWpE/4poBD/5SgsnJZXr7XkmBIdsfuLvz1
hxQItF+1j3WsN5h2QVbPGsErj2RyuLcwgk66oN1fGQO+1cXEm1hg9SUNHorUQM7C
JqMCgYEAofxXJ8dOWUaFIHmKLE7Y+0+i3D1yXVIyu/puuaQGbNFHxjcJ9ZdubhLN
IyzJvngtM7mC90FtUETxvErMGdTzFeKtSKBZsJ8BiLCszRCEuJf5RX6uNrFUQ2pT
PEmns088Gs4KUDwjTG0zQtj3pNc5zDynDMpFKp96spefqLJqw3s=
-----END RSA PRIVATE KEY-----
================================================
FILE: tools/ida.py
================================================
#!/usr/bin/env python3
#encoding=utf-8
import os
import sys
import time
import argparse
import subprocess
from shlex import split as s
from lamda.client import *
certfile = os.environ.get("CERTIFICATE", None)
port = int(os.environ.get("PORT", 65000))
argp = argparse.ArgumentParser()
argp.add_argument("-d", type=str, required=True)
argp.add_argument("-a", type=str, required=True)
args = argp.parse_args()
certfile = os.environ.get("CERTIFICATE", None)
d = Device(args.d, certificate=certfile)
app = d.application(args.a)
d.start_activity(**app.query_launch_activity(), debug=True)
print (time.ctime(), "{} is started as debuggable mode".format(args.a))
print (time.ctime(), "Waitting for 'Waitting For Debugger' popup")
if not d(textContains="Waiting").wait_for_exists(25*1000):
print (time.ctime(), "No debugger prompt detected, please ensure "\
"you already run 'setdebuggable' in firerpa terminal." )
exit (1)
pName = app.info().processName
processes = d.enumerate_running_processes()
p = list(filter(lambda p: p.processName == pName,
processes))[0]
print (time.ctime(), "Found pid: {}".format(p.pid))
# Build forward cmd
print (time.ctime(), "Forwarding jwdp pid")
cmd = s("adb forward tcp:8700 jdwp:%s" % p.pid)
forward = subprocess.Popen(cmd, stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
shell=False)
forward.wait()
print (time.ctime(), "--------------------------------")
print (time.ctime(), "Now please use your IDA to attach this target")
print (time.ctime(), "name: {} pid: {}".format(pName, p.pid))
print (time.ctime(), "and wait for IDA 'Downloading symbols' to finish")
print (time.ctime(), "when all is finished, press ENTER to continue")
print (time.ctime(), "--------------------------------")
_ = input()
cmd = s("jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=8700")
debug = subprocess.Popen(cmd, stdin=sys.stdin,
stdout=sys.stdout,
stderr=sys.stderr,
bufsize=0,
shell=False)
debug.wait()
================================================
FILE: tools/magisk/META-INF/com/google/android/update-binary
================================================
#!/sbin/sh
source /data/adb/magisk/util_functions.sh
if [ ${MAGISK_VER_CODE} -lt 20400 ]; then
abort "Please install Magisk v20.4 +"
exit 1
fi
if [ "${BOOTMODE}" != "true" ]; then
abort "Must install from Magisk app"
fi
OUTFD=$2
ZIPFILE=$3
install_module
exit 0
================================================
FILE: tools/magisk/META-INF/com/google/android/updater-script
================================================
#MAGISK
================================================
FILE: tools/magisk/common/adb_keys
================================================
================================================
FILE: tools/magisk/common/properties.local
================================================
brandname=FIRERPA
================================================
FILE: tools/magisk/common/server/.keep
================================================
================================================
FILE: tools/magisk/common/service.sh
================================================
#!/system/bin/sh
base=${0%/*}
cert=/data/usr/lamda.pem
launch="sh ${base}/server/bin/launch.sh"
port=65000
sleep 25
export ca_store_remount=true
if [ -f "${cert}" ]; then
$launch --port=${port} --certificate=${cert}
else
$launch --port=${port}
fi
================================================
FILE: tools/magisk/install.sh
================================================
#!/system/bin/sh
ABI=$(getprop ro.product.cpu.abi)
SERVER=$TMPDIR/lamda-server-$ABI.tar.gz
BB="/data/adb/magisk/busybox"
USRDIR=/data/usr
if [ -d "/data/adb/ksu/" ]; then
BB="/data/adb/ksu/bin/busybox"
fi
if [ -d "/data/adb/ap/" ]; then
BB="/data/adb/ap/bin/busybox"
fi
export LATESTARTSERVICE=true
ui_print ".____ ________ _____ "
ui_print "| | _____ _____ \______ \ / _ \ "
ui_print "| | \__ \ / \ | | \ / /_\ \ "
ui_print "| |___ / __ \_| Y Y \ | | \/ | \ "
ui_print "|_______ \(____ /|__|_| / /_______ /\____|__ / "
ui_print " \/ \/ \/ \/ \/ "
ui_print " installer "
pushd $(pwd)
cd $MODPATH
if [ ! -f $SERVER ]; then
abort "lamda-server-${ABI}.tar.gz not in archive, please download and drop it to common/server."
fi
ui_print "- Extracting server files"
$BB tar -xzf $SERVER
ui_print "- Placing configs"
mkdir -p ${USRDIR}
cp -af $TMPDIR/adb_keys ${USRDIR}/.adb_keys
cp -af $TMPDIR/properties.local ${USRDIR}
cp -af $TMPDIR/lamda.pem ${USRDIR}
ui_print "- Please reboot your device"
popd
================================================
FILE: tools/magisk/module.prop
================================================
id=lamda
name=LAMDA
version=VERSION
versionCode=VERSIONCODE
author=rev1si0n
description=Android reverse engineering & automation framework
================================================
FILE: tools/magisk/uninstall.sh
================================================
#!/system/bin/sh
MODPATH=${0%/*}
echo "/data/usr will not be removed, please remove it manually"
================================================
FILE: tools/objection-1.11.0-command-patch.diff
================================================
diff --git a/console/cli.py b/console/cli.py
index 1fc22fb..80e52dc 100644
--- a/console/cli.py
+++ b/console/cli.py
@@ -31,8 +31,10 @@ from ..utils.helpers import normalize_gadget_name, print_frida_connection_help,
@click.option('--serial', '-S', required=False, default=None, help='A device serial to connect to.')
@click.option('--debug', '-d', required=False, default=False, is_flag=True,
help='Enable debug mode with verbose output. (Includes agent source map in stack traces)')
+@click.option('--certificate', '-c', required=False, default=None, help="Frida connection certificate")
+@click.option('--token', '-t', required=False, default=None, help="Frida connection token")
def cli(network: bool, host: str, port: int, api_host: str, api_port: int,
- gadget: str, serial: str, debug: bool) -> None:
+ gadget: str, serial: str, debug: bool, certificate: str, token: str) -> None:
"""
\b
_ _ _ _
@@ -56,6 +58,8 @@ def cli(network: bool, host: str, port: int, api_host: str, api_port: int,
state_connection.use_network()
state_connection.host = host
state_connection.port = port
+ state_connection.certificate = certificate
+ state_connection.token = token
if serial:
state_connection.device_serial = serial
diff --git a/utils/agent.py b/utils/agent.py
index 6d88e3a..fbe9b40 100644
--- a/utils/agent.py
+++ b/utils/agent.py
@@ -126,8 +126,13 @@ class Agent(object):
return device
if state_connection.get_comms_type() == state_connection.TYPE_REMOTE:
+ kwargs = {}
+ if state_connection.certificate:
+ kwargs["certificate"] = state_connection.certificate
+ if state_connection.token:
+ kwargs["token"] = state_connection.token
device = frida.get_device_manager().add_remote_device('{host}:{port}'.format(
- host=state_connection.host, port=state_connection.port))
+ host=state_connection.host, port=state_connection.port), **kwargs)
click.secho('Using networked device @`{n}`'.format(n=device.name), bold=True)
return device
================================================
FILE: tools/openvpn/Dockerfile
================================================
FROM alpine:3.15
LABEL maintainer="rev1si0n "
ENV VARS=/etc/openvpn/easy-rsa/vars
ENV OVPNCONFIG=/etc/openvpn/config.ovpn
ENV SOURCESMIRROR=mirrors.ustc.edu.cn
RUN sed -i "s/dl-cdn.alpinelinux.org/${SOURCESMIRROR}/g" /etc/apk/repositories
COPY entry /usr/bin/run
COPY ovpn-server-new /usr/bin
COPY ovpn-client-profile /usr/bin
COPY ovpn-client-revoke /usr/bin
COPY ovpn-client-new /usr/bin
COPY config.ovpn /root
COPY vars /root
RUN apk add openvpn easy-rsa bash curl jq
RUN ln -sf /usr/share/easy-rsa/easyrsa /usr/bin/easyrsa
RUN ln -sf ${VARS} /usr/share/easy-rsa/vars
WORKDIR /etc/openvpn
================================================
FILE: tools/openvpn/config.ovpn
================================================
# openvpn Docker image config
# https://github.com/rev1si0n/lamda/tree/master/tools/openvpn
#
topology subnet
server 172.27.27.0 255.255.255.0
proto udp
port 1190
dev tun
# 不要编辑下面几行 easy-rsa/pki 配置
ca /etc/openvpn/easy-rsa/pki/ca.crt
dh /etc/openvpn/easy-rsa/pki/dh.pem
cert /etc/openvpn/easy-rsa/pki/issued/lamda.crt
key /etc/openvpn/easy-rsa/pki/private/lamda.key
crl-verify /etc/openvpn/easy-rsa/pki/crl.pem
# TLS crypt 配置二选一
;tls-auth /etc/openvpn/ta.key 0
tls-crypt /etc/openvpn/ta.key
cipher AES-256-GCM
keepalive 15 120
persist-tun
persist-key
ifconfig-pool-persist ipp.txt 10
client-to-client
client-config-dir ccd
script-security 2
compress lz4-v2
push "compress lz4-v2"
max-clients 254
# 如果你想路由其他服务器上的网段到客户端
;push "route 192.168.1.0 255.255.255.0"
# 如果你想要让每个客户端使用特定 DNS
;push "dhcp-option DNS 114.114.114.114"
verb 4
================================================
FILE: tools/openvpn/entry
================================================
#!/bin/bash
if [ -f "${OVPNCONFIG}" ] && [ -f "${VARS}" ]; then
exec openvpn --config config.ovpn
fi
================================================
FILE: tools/openvpn/ovpn-client-new
================================================
#!/bin/bash
TC_CLIENT=/etc/openvpn/tls-crypt-v2-client/$1
TC_SERVER=/etc/openvpn/tls-crypt-v2-server.key
openvpn --genkey tls-crypt-v2-client ${TC_CLIENT} --tls-crypt-v2 ${TC_SERVER}
easyrsa build-client-full "$1" nopass
================================================
FILE: tools/openvpn/ovpn-client-profile
================================================
#!/bin/bash
export CLIENT=$2
if [ -f "easy-rsa/pki/issued/${CLIENT}.crt" ]; then
CRYPT_cfg_n="$(grep -Eo '^tls-(auth|crypt|crypt-v2)' config.ovpn | wc -l)"
if [ 0"${CRYPT_cfg_n}" -gt 1 ]; then
echo "Multiple tls-auth/crypt/crypt-v2 config detected"; exit 1
fi
TLS_ENCRYPTION=
if [ 0"${CRYPT_cfg_n}" -eq 1 ]; then
TLS_ENCRYPTION=$(grep -Eo '^tls-(auth|crypt|crypt-v2)' config.ovpn)
TLS_CRYPT=$(echo ${TLS_ENCRYPTION}|tr '-' '_')
else
TLS_CRYPT=tls_none
fi
PROTO=$(sed -n -e 's/^proto *\(.\+\).*/\1/p' config.ovpn)
CIPHER=$(sed -n -e 's/^cipher *\(.\+\).*/\1/p' config.ovpn)
PORT=$(sed -n -e 's/^port *\([[:digit:]]\+\).*/\1/p' config.ovpn)
WANIP=$(curl -s https://httpbin.org/ip | jq -r .origin)
CA=$(cat easy-rsa/pki/ca.crt)
CERT=$(openssl x509 -in easy-rsa/pki/issued/${CLIENT}.crt)
KEY=$(cat easy-rsa/pki/private/${CLIENT}.key)
SERVER_KEY_DIRECTION_n=$(sed -n -e 's/^tls-auth *.* \([[:digit:]]\).*/\1/p' config.ovpn)
if [ x"${SERVER_KEY_DIRECTION_n}"x = xx ]; then
SERVER_KEY_DIRECTION_n=$(sed -n -e 's/^key-direction *\(.\+\).*/\1/p' config.ovpn)
fi
CLIENT_KEY_DIRECTION_n=
if [ x"${SERVER_KEY_DIRECTION_n}"x != xx ]; then
CLIENT_KEY_DIRECTION_n=$((1^SERVER_KEY_DIRECTION_n))
fi
CLIENT_KEY_DIRECTION_inline=
CLIENT_KEY_DIRECTION=KEY_DIRECTION_NONE
if [ x"${CLIENT_KEY_DIRECTION_n}"x != xx ]; then
CLIENT_KEY_DIRECTION_inline="key-direction ${CLIENT_KEY_DIRECTION_n}"
CLIENT_KEY_DIRECTION=KEY_DIRECTION_${CLIENT_KEY_DIRECTION_n}
fi
if [ "${TLS_ENCRYPTION}" = "tls-crypt-v2" ]; then
TA=$(cat /etc/openvpn/tls-crypt-v2-client/${CLIENT})
elif [ ! -z "${TLS_ENCRYPTION}" ]; then
TA=$(sed '/^#/d' ta.key)
else
TA=
fi
if [ "g$1" = govpn ]; then
cat <&1
client
float
nobind
dev tun
proto ${PROTO}
remote ${WANIP} ${PORT}
resolv-retry infinite
cipher ${CIPHER}
keepalive 15 60
remote-cert-tls server
${CLIENT_KEY_DIRECTION_inline}
<${TLS_ENCRYPTION}>
${TA}
${TLS_ENCRYPTION}>
${CA}
${CERT}
${KEY}
EOL
else
cat <&1
profile = OpenVPNProfile()
profile.all_traffic = False
profile.proto = OpenVPNProto.${PROTO^^}
profile.host = "${WANIP}"
profile.port = ${PORT}
profile.cipher = OpenVPNCipher.$(echo ${CIPHER}|tr '-' '_')
# auto-generated lamda OpenVPN profile, CN=${CLIENT}
profile.tls_encryption = OpenVPNEncryption.${TLS_CRYPT^^}
profile.tls_key_direction = OpenVPNKeyDirection.${CLIENT_KEY_DIRECTION}
profile.tls_key = """
${TA}
"""
profile.ca = """
${CA}
"""
profile.cert = """
${CERT}
"""
profile.key = """
${KEY}
"""
"""
# auto-generated lamda OpenVPN prop, CN=${CLIENT}
# copy lines below to properties.local to let openvpn
# auto connect when lamda started
openvpn.proto=${PROTO}
openvpn.cipher=${CIPHER}
openvpn.global=false
openvpn.host=${WANIP}
openvpn.port=${PORT}
openvpn.ca=$(echo -n "${CA}" | base64 -w 0)
openvpn.cert=$(echo -n "${CERT}" | base64 -w 0)
openvpn.key=$(echo -n "${KEY}" | base64 -w 0)
openvpn.tls_encryption=${TLS_ENCRYPTION}
openvpn.tls_key_direction=${CLIENT_KEY_DIRECTION_n}
openvpn.tls_key=$(echo -n "${TA}" | base64 -w 0)
openvpn.enable=true
"""
EOL
fi
fi
================================================
FILE: tools/openvpn/ovpn-client-renew
================================================
#!/bin/bash
easyrsa renew "$1"
================================================
FILE: tools/openvpn/ovpn-client-revoke
================================================
#!/bin/bash
easyrsa revoke "$1"
easyrsa gen-crl
================================================
FILE: tools/openvpn/ovpn-server-new
================================================
#!/bin/bash
mkdir -p /etc/openvpn/ccd
mkdir -p /etc/openvpn/easy-rsa
if [ ! -f ${OVPNCONFIG} ]; then
cp /root/config.ovpn ${OVPNCONFIG}
fi
if [ ! -f ${VARS} ]; then
cp /root/vars ${VARS}
fi
set -ex
openvpn --genkey --secret /etc/openvpn/ta.key
openvpn --genkey tls-crypt-v2-server /etc/openvpn/tls-crypt-v2-server.key
mkdir -p /etc/openvpn/tls-crypt-v2-client
easyrsa init-pki
easyrsa build-ca nopass
easyrsa gen-dh
easyrsa build-server-full lamda nopass
easyrsa gen-crl
================================================
FILE: tools/openvpn/vars
================================================
# Easy-RSA 3 parameter settings
# NOTE: If you installed Easy-RSA from your distro's package manager, don't edit
# this file in place -- instead, you should copy the entire easy-rsa directory
# to another location so future upgrades don't wipe out your changes.
# HOW TO USE THIS FILE
#
# vars.example contains built-in examples to Easy-RSA settings. You MUST name
# this file 'vars' if you want it to be used as a configuration file. If you do
# not, it WILL NOT be automatically read when you call easyrsa commands.
#
# It is not necessary to use this config file unless you wish to change
# operational defaults. These defaults should be fine for many uses without the
# need to copy and edit the 'vars' file.
#
# All of the editable settings are shown commented and start with the command
# 'set_var' -- this means any set_var command that is uncommented has been
# modified by the user. If you're happy with a default, there is no need to
# define the value to its default.
# NOTES FOR WINDOWS USERS
#
# Paths for Windows *MUST* use forward slashes, or optionally double-escaped
# backslashes (single forward slashes are recommended.) This means your path to
# the openssl binary might look like this:
# "C:/Program Files/OpenSSL-Win32/bin/openssl.exe"
# DO YOUR EDITS BELOW THIS POINT
# This variable is used as the base location of configuration files needed by
# easyrsa. More specific variables for specific files (e.g., EASYRSA_SSL_CONF)
# may override this default.
#
# The default value of this variable is the location of the easyrsa script
# itself, which is also where the configuration files are located in the
# easy-rsa tree.
set_var EASYRSA "/etc/openvpn/easy-rsa"
# If your OpenSSL command is not in the system PATH, you will need to define the
# path to it here. Normally this means a full path to the executable, otherwise
# you could have left it undefined here and the shown default would be used.
#
# Windows users, remember to use paths with forward-slashes (or escaped
# back-slashes.) Windows users should declare the full path to the openssl
# binary here if it is not in their system PATH.
set_var EASYRSA_OPENSSL "openssl"
#
# This sample is in Windows syntax -- edit it for your path if not using PATH:
#set_var EASYRSA_OPENSSL "C:/Program Files/OpenSSL-Win32/bin/openssl.exe"
# Edit this variable to point to your soon-to-be-created key directory. By
# default, this will be "$PWD/pki" (i.e. the "pki" subdirectory of the
# directory you are currently in).
#
# WARNING: init-pki will do a rm -rf on this directory so make sure you define
# it correctly! (Interactive mode will prompt before acting.)
set_var EASYRSA_PKI "/etc/openvpn/easy-rsa/pki"
# Define directory for temporary subdirectories.
set_var EASYRSA_TEMP_DIR "$EASYRSA_PKI"
# Define X509 DN mode.
# This is used to adjust what elements are included in the Subject field as the DN
# (this is the "Distinguished Name.")
# Note that in cn_only mode the Organizational fields further below aren't used.
#
# Choices are:
# cn_only - use just a CN value
# org - use the "traditional" Country/Province/City/Org/OU/email/CN format
set_var EASYRSA_DN "cn_only"
# Organizational fields (used with 'org' mode and ignored in 'cn_only' mode.)
# These are the default values for fields which will be placed in the
# certificate. Don't leave any of these fields blank, although interactively
# you may omit any specific field by typing the "." symbol (not valid for
# email.)
set_var EASYRSA_REQ_COUNTRY ""
set_var EASYRSA_REQ_PROVINCE ""
set_var EASYRSA_REQ_CITY ""
set_var EASYRSA_REQ_ORG ""
set_var EASYRSA_REQ_OU ""
set_var EASYRSA_REQ_EMAIL ""
# Choose a size in bits for your keypairs. The recommended value is 2048. Using
# 2048-bit keys is considered more than sufficient for many years into the
# future. Larger keysizes will slow down TLS negotiation and make key/DH param
# generation take much longer. Values up to 4096 should be accepted by most
# software. Only used when the crypto alg is rsa (see below.)
set_var EASYRSA_KEY_SIZE 2048
# The default crypto mode is rsa; ec can enable elliptic curve support.
# Note that not all software supports ECC, so use care when enabling it.
# Choices for crypto alg are: (each in lower-case)
# * rsa
# * ec
# * ed
#set_var EASYRSA_ALGO rsa
# Define the named curve, used in ec & ed modes:
#set_var EASYRSA_CURVE secp384r1
# In how many days should the root CA key expire?
# 50 years
set_var EASYRSA_CA_EXPIRE 18250
# In how many days should certificates expire?
set_var EASYRSA_CERT_EXPIRE 3650
# How many days until the next CRL publish date? Note that the CRL can still be
# parsed after this timeframe passes. It is only used for an expected next
# publication date.
set_var EASYRSA_CRL_DAYS 3650
# How many days before its expiration date a certificate is allowed to be
# renewed?
set_var EASYRSA_CERT_RENEW 30
# Random serial numbers by default, set to no for the old incremental serial numbers
#
#set_var EASYRSA_RAND_SN "yes"
# Support deprecated "Netscape" extensions? (choices "yes" or "no".) The default
# is "no" to discourage use of deprecated extensions. If you require this
# feature to use with --ns-cert-type, set this to "yes" here. This support
# should be replaced with the more modern --remote-cert-tls feature. If you do
# not use --ns-cert-type in your configs, it is safe (and recommended) to leave
# this defined to "no". When set to "yes", server-signed certs get the
# nsCertType=server attribute, and also get any NS_COMMENT defined below in the
# nsComment field.
#set_var EASYRSA_NS_SUPPORT "no"
# When NS_SUPPORT is set to "yes", this field is added as the nsComment field.
# Set this blank to omit it. With NS_SUPPORT set to "no" this field is ignored.
#set_var EASYRSA_NS_COMMENT "Easy-RSA Generated Certificate"
# A temp file used to stage cert extensions during signing. The default should
# be fine for most users; however, some users might want an alternative under a
# RAM-based FS, such as /dev/shm or /tmp on some systems.
#set_var EASYRSA_TEMP_FILE "$EASYRSA_PKI/extensions.temp"
# !!
# NOTE: ADVANCED OPTIONS BELOW THIS POINT
# PLAY WITH THEM AT YOUR OWN RISK
# !!
# Broken shell command aliases: If you have a largely broken shell that is
# missing any of these POSIX-required commands used by Easy-RSA, you will need
# to define an alias to the proper path for the command. The symptom will be
# some form of a 'command not found' error from your shell. This means your
# shell is BROKEN, but you can hack around it here if you really need. These
# shown values are not defaults: it is up to you to know what you're doing if
# you touch these.
#
#alias awk="/alt/bin/awk"
#alias cat="/alt/bin/cat"
# X509 extensions directory:
# If you want to customize the X509 extensions used, set the directory to look
# for extensions here. Each cert type you sign must have a matching filename,
# and an optional file named 'COMMON' is included first when present. Note that
# when undefined here, default behaviour is to look in $EASYRSA_PKI first, then
# fallback to $EASYRSA for the 'x509-types' dir. You may override this
# detection with an explicit dir here.
#
set_var EASYRSA_EXT_DIR "/usr/share/easy-rsa/x509-types"
# If you want to generate KDC certificates, you need to set the realm here.
#set_var EASYRSA_KDC_REALM "CHANGEME.EXAMPLE.COM"
# OpenSSL config file:
# If you need to use a specific openssl config file, you can reference it here.
# Normally this file is auto-detected from a file named openssl-easyrsa.cnf from the
# EASYRSA_PKI or EASYRSA dir (in that order.) NOTE that this file is Easy-RSA
# specific and you cannot just use a standard config file, so this is an
# advanced feature.
set_var EASYRSA_SSL_CONF "/usr/share/easy-rsa/openssl-easyrsa.cnf"
# Default CN:
# This is best left alone. Interactively you will set this manually, and BATCH
# callers are expected to set this themselves.
set_var EASYRSA_REQ_CN "LAMDA"
# Cryptographic digest to use.
# Do not change this default unless you understand the security implications.
# Valid choices include: md5, sha1, sha256, sha224, sha384, sha512
#set_var EASYRSA_DIGEST "sha256"
# Batch mode. Leave this disabled unless you intend to call Easy-RSA explicitly
# in batch mode without any user input, confirmation on dangerous operations,
# or most output. Setting this to any non-blank string enables batch mode.
set_var EASYRSA_BATCH "yes"
================================================
FILE: tools/paddle_ocr_http_api.py
================================================
#!/usr/bin/env python3
# THIS IS EXAMPLE HTTP OCR BACKEND,
# DO NOT USE IN PRODUCTION
import uvicorn
import asyncio
import paddle as paddle
from paddleocr import PaddleOCR
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/ocr")
async def ocr(request: Request):
image = await request.body()
r = await asyncio.to_thread(ocr.ocr, image)
n = bool(r and r[0] and type(r[0][-1])==float)
result = (r if n else r[0]) or []
output = [[n[0], n[1][0], n[1][1]] for n in result]
return output
if __name__ == "__main__":
ocr = PaddleOCR(use_gpu=False, drop_score=0.85,
use_space_char=True)
uvicorn.run(app, host="0.0.0.0", port=8000)
================================================
FILE: tools/requirements.txt
================================================
mitmproxy>=9.0.0,<=10.2.0
dnspython
httpx[socks]<0.28.0
packaging
Pillow
================================================
FILE: tools/root.crt
================================================
-----BEGIN CERTIFICATE-----
MIIClDCCAXwCAQAwDQYJKoZIhvcNAQELBQAwEDEOMAwGA1UECgwFTEFNREEwHhcN
MjAwMTAxMDAwMDAxWhcNMjkxMjI5MDAwMDAxWjAQMQ4wDAYDVQQKDAVMQU1EQTCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALnqfBZvrGZaqgk5msCRRpPz
p/+464jBknlmTKetOckDTUq8V6fH/2Gfb6HjjoD5pkCtD5mSKkaHNhuxLXsFfEfc
Klmnn3ZpjyNOHEA/iAO2DyEYa08xSevM7WojbF7cNj5/DYg7eb0i0+//bBlh8nlO
ghSUhCTM5PCofLDU8sVXuYAiGU6UzBrIC0vHEltDkiJVLpcCtsKjEZOskPdAc7u6
/aA0P5GonZ5UkRDYpa+jeJUabqWZQQEgtmvjlmUUiXwu0jRnj1n0T3fPQD3gA+LI
vAE9vgvpY5XQj6otpBvsZ3MJJNKcUMQu1zOANTzO18TlA8KoBNsBy8Z9DQZKXF8C
AwEAATANBgkqhkiG9w0BAQsFAAOCAQEAeGn/NyqIRI/0AGqvHP97KtQ76Tj6achb
30LIXWp+YHUaMeAVjd2Z7jSQp5mZZFl+k1fb337IeaGXofVI21eK52QX2teNoBkA
/V/O1Hu35/+aiz0xsdD6wWvwo4Fy1jlmaeJhwdQacBlDGFA2jFJxuEpahfxZvUsb
3j5zU0WKTUCfEDgXFwBw12xku/7TMdCEbRsYaZ3zFTC226lQbUDN7weqFwSD+tB6
RuhIxe8+cFwAsAWHClerKgZnr3t5QF078pW2GHy8CsJ7V3MZT5lZ43lS5NIB9JzZ
SYxZ/izhRyh5qR73uAgsJaL56Bj+cQqnoTqhLZVl7J+M6WhWKzojsA==
-----END CERTIFICATE-----
================================================
FILE: tools/root.key
================================================
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC56nwWb6xmWqoJ
OZrAkUaT86f/uOuIwZJ5ZkynrTnJA01KvFenx/9hn2+h446A+aZArQ+ZkipGhzYb
sS17BXxH3CpZp592aY8jThxAP4gDtg8hGGtPMUnrzO1qI2xe3DY+fw2IO3m9ItPv
/2wZYfJ5ToIUlIQkzOTwqHyw1PLFV7mAIhlOlMwayAtLxxJbQ5IiVS6XArbCoxGT
rJD3QHO7uv2gND+RqJ2eVJEQ2KWvo3iVGm6lmUEBILZr45ZlFIl8LtI0Z49Z9E93
z0A94APiyLwBPb4L6WOV0I+qLaQb7GdzCSTSnFDELtczgDU8ztfE5QPCqATbAcvG
fQ0GSlxfAgMBAAECggEAUVmJj3Ww7Z6JXXz/ung29kE9BxfetBnjYkyBiTMyoELa
HLQZ9Nf95rURbzh3n/bdKNqxA0UiS3cZlXdrC7QERBtRyqYej/0zfULN+Mzz0o/k
wdoCBoZuLogecH1si3t3HtSQUsaXh8uICjFtFxQ1OaBBKgksYz/mg2luGiAr1G5F
yzCjKOpPo9rjgka0OMx5PnHjd9d6rf2ckB9VlLxxEVaYoGnXJTAe+FU0sWIFIfZY
E1kknQ9F+MegGEZyE/r8kRUm4+81WWPChwWlXT8TpHp1HXtI0j9PY4vlWjDBB2cO
6w8nPiHT3Hy0BYpa3Vee7a9kHljLa3ZBgq2DMnoaqQKBgQDVGKAuRbQ7CXRinRou
Lh/iDwzdWLVfIB/EpClZqhIQt+YcuqKixvBtlksRewJfC7lbSM5rcNYp+NFq2z1F
ol6EezmVcylAfHf9jHJrY37uR7ilwJnl7FMqNAw6hwRUm2Zhj+PgWPCVO8VpRBFE
j80fLhD1rrD3TqBBvQGTs6h2OQKBgQDfWPBgFTJsgmp6XLsgLiahAKVVtHQk4lXI
7WytdNgrf7U9rT9hGsRc5TJ/BagnUKRe3u/oIxnFw3QgFg1vPRWGxd8ckA3F4Z9V
RgMUJcvnSDoLNctd19sXclC7KAIEAg5VZEEPPXFrmudlbO7AVDZ2/nqWsVxbJ4ie
PolKMkOnVwKBgHhBRWPXgjYux7c+4R9eeC+iPqhK/0+HWWYnHg6zoy48GWftCwrB
Kb0uK37Z5CSvARM6qwKG0tUszUF4J8O8NYqd34kvTABQPYagU5G2vFLLtrATWMYf
727JoH0G5LK6X7R+9yIMDviommclOnlujVsjK/75bnShsl5mwxUPNepBAoGBAIW0
V4odhmchF58S3k7gce2WVmPdOF5QRGyZfaLFPRelRxmaGkQGYyD9cGJMP6LxisTk
jxgX0zjpzh3bNcWu+rEIyYzuy9/3Now08mjgS5W0TlMhrBIMw7Lgk2XHCJXfqTcD
YlM+RCTXwcidErx+bXCE3VxS5ugG3all5IhPWOLXAoGBAJ+qUklRlmmTds7RhpeH
dX8UlAW8/AtJfTfWoA5EPLX8jZp2v8FSSKk4OME95jVS6G8mAevHjdpaw1iwrxDX
szwh9hcWxT4i2HrAwYMI2HM0HqEIjgCx+1kzDrcdsU96rLDhbaRoN9JBMh3yHI05
H0KRgTsIBx2u3aMQAe3AHZOd
-----END PRIVATE KEY-----
================================================
FILE: tools/rsync.sh
================================================
#!/bin/bash
[ $# -lt 2 ] && exit 1
DEFAULT_ID_RSA=$(mktemp)
PORT=${PORT:-65000}
case "$1" in
*':'*)
p1=root@$1
p2=$2
;;
*)
p1=$1
p2=root@$2
;;
esac
umask 077
if [ ! -f "${CERTIFICATE}" ]; then
# this is the default id_rsa for ssh service
cat <$DEFAULT_ID_RSA
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA4QHmY32OT+F+maERMn1cvBRuIOIXH9yOALG+GMCngtjRJzSR
n09dInmXE+PjiAqNRWvknVEjFywv0v1v/H2qSRJKR/togPgySjiABhigqDHdirNd
Dh63oN2e+d0yythoLzsQrH5BSVtw05Atpkr7bW4KdfMveWuddvDACnQ3mvCXq50X
IK3cOlmHXwcJrX55BhEXxgHIqw0upf0A7DC3Afz5xjOA6+K/O2EzZIIJ+sWw7/Ko
5+3m98Et0zwcxe20uNxzYf7JSMu3490YNckLiQcDrZVRUXNS70HO9HWCXKdjFJPi
GgPtKUQNHjChwzSCQQGzqitXdwa60i9Sy1U09wIBIwKCAQAzbiYIHLLQbg5PAD5x
8MS9Rn+SfNIV6UUHeRWCACZJyyh9/WMdGXRfpsNyQreqEQpZArfpcaGe5YdGK0zL
/3dhKMCFe0sWKhofl+K/kJnAC2XWj2W6FaZQp69PDfz7KiZxMhJwkeMJc/yIIPR9
x/70cOxyuz4NH+l6Rag8510qujhxn6lGc1+ZNHGeAYmUD7wFz9/QYDZU9C0YcylA
2+Q5woU3L4b3y3JKqB6/dnsP9uF7R5KBR+qiqOwVm0nhvVU5uwbpQwTXeklopXyN
rI/NeYcsDoEyb8NquVXX/GkOgY0FhqblGUR9kSdTLHq6VamelHc+dczZgMxcsq1c
mnnLAoGBAP2FPssFpHJRhSr38Fq5A7mEjQeiPq2WgYV7kCpJT5F5OymcCCETIjEH
4pTk5zCWUuIBx5LlzSa0XnQSYb0100ZzvDgfm1NPmqdkpPwkkbh2xyoHYTTPJ3WG
TDur8Qyi83NteveszO1TCAGBTe3zN+2ov9qzl5Y7QHF94GVFDo4NAoGBAOM1Q8eG
0KeqjutTz/UMtejoFpz0Hi1g32PfdQInHx8MDslYu3Fcpnos3xf59H7+mrRy0fUM
hh27v7DiUvxUfhlojf0F3kDKeg9VZBslZF3vTCpFdKdFouZ2Cru3lCoaPSau69BC
6HQw4P+RABrgxc6CeE9FUGEEMss+wTcRItITAoGBAO8ImkpkZ9mAD9gOV6X+5kEz
1W2Y+USVOEqns9AZPGSW4AKpDvqdAvsHbzvtxAk9RtUXnumW118B1WYf9cEG3SUr
SxBYUJ8B6ZaDdvxc/mwYOCBQGdK0sCz692t2OwvqGL1J95kQo/W0r8bnoT9wSqzg
72fN5rI33azVxPHExJSPAoGAGfd1deOFj4E057HOn6muY8LAwXr8InF4nbMjULQD
joUJARF0gfv1xNHtnFcUoMyj912UVoUWpE/4poBD/5SgsnJZXr7XkmBIdsfuLvz1
hxQItF+1j3WsN5h2QVbPGsErj2RyuLcwgk66oN1fGQO+1cXEm1hg9SUNHorUQM7C
JqMCgYEAofxXJ8dOWUaFIHmKLE7Y+0+i3D1yXVIyu/puuaQGbNFHxjcJ9ZdubhLN
IyzJvngtM7mC90FtUETxvErMGdTzFeKtSKBZsJ8BiLCszRCEuJf5RX6uNrFUQ2pT
PEmns088Gs4KUDwjTG0zQtj3pNc5zDynDMpFKp96spefqLJqw3s=
-----END RSA PRIVATE KEY-----
EOL
else
DEFAULT_ID_RSA=$CERTIFICATE
fi
exec rsync -avz ${@:3} -e "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=ERROR -i $DEFAULT_ID_RSA -p $PORT" $p1 $p2
================================================
FILE: tools/scp.sh
================================================
#!/bin/bash
[ $# -lt 2 ] && exit 1
DEFAULT_ID_RSA=$(mktemp)
PORT=${PORT:-65000}
case "$1" in
*':'*)
p1=root@$1
p2=$2
;;
*)
p1=$1
p2=root@$2
;;
esac
umask 077
if [ ! -f "${CERTIFICATE}" ]; then
# this is the default id_rsa for ssh service
cat <$DEFAULT_ID_RSA
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA4QHmY32OT+F+maERMn1cvBRuIOIXH9yOALG+GMCngtjRJzSR
n09dInmXE+PjiAqNRWvknVEjFywv0v1v/H2qSRJKR/togPgySjiABhigqDHdirNd
Dh63oN2e+d0yythoLzsQrH5BSVtw05Atpkr7bW4KdfMveWuddvDACnQ3mvCXq50X
IK3cOlmHXwcJrX55BhEXxgHIqw0upf0A7DC3Afz5xjOA6+K/O2EzZIIJ+sWw7/Ko
5+3m98Et0zwcxe20uNxzYf7JSMu3490YNckLiQcDrZVRUXNS70HO9HWCXKdjFJPi
GgPtKUQNHjChwzSCQQGzqitXdwa60i9Sy1U09wIBIwKCAQAzbiYIHLLQbg5PAD5x
8MS9Rn+SfNIV6UUHeRWCACZJyyh9/WMdGXRfpsNyQreqEQpZArfpcaGe5YdGK0zL
/3dhKMCFe0sWKhofl+K/kJnAC2XWj2W6FaZQp69PDfz7KiZxMhJwkeMJc/yIIPR9
x/70cOxyuz4NH+l6Rag8510qujhxn6lGc1+ZNHGeAYmUD7wFz9/QYDZU9C0YcylA
2+Q5woU3L4b3y3JKqB6/dnsP9uF7R5KBR+qiqOwVm0nhvVU5uwbpQwTXeklopXyN
rI/NeYcsDoEyb8NquVXX/GkOgY0FhqblGUR9kSdTLHq6VamelHc+dczZgMxcsq1c
mnnLAoGBAP2FPssFpHJRhSr38Fq5A7mEjQeiPq2WgYV7kCpJT5F5OymcCCETIjEH
4pTk5zCWUuIBx5LlzSa0XnQSYb0100ZzvDgfm1NPmqdkpPwkkbh2xyoHYTTPJ3WG
TDur8Qyi83NteveszO1TCAGBTe3zN+2ov9qzl5Y7QHF94GVFDo4NAoGBAOM1Q8eG
0KeqjutTz/UMtejoFpz0Hi1g32PfdQInHx8MDslYu3Fcpnos3xf59H7+mrRy0fUM
hh27v7DiUvxUfhlojf0F3kDKeg9VZBslZF3vTCpFdKdFouZ2Cru3lCoaPSau69BC
6HQw4P+RABrgxc6CeE9FUGEEMss+wTcRItITAoGBAO8ImkpkZ9mAD9gOV6X+5kEz
1W2Y+USVOEqns9AZPGSW4AKpDvqdAvsHbzvtxAk9RtUXnumW118B1WYf9cEG3SUr
SxBYUJ8B6ZaDdvxc/mwYOCBQGdK0sCz692t2OwvqGL1J95kQo/W0r8bnoT9wSqzg
72fN5rI33azVxPHExJSPAoGAGfd1deOFj4E057HOn6muY8LAwXr8InF4nbMjULQD
joUJARF0gfv1xNHtnFcUoMyj912UVoUWpE/4poBD/5SgsnJZXr7XkmBIdsfuLvz1
hxQItF+1j3WsN5h2QVbPGsErj2RyuLcwgk66oN1fGQO+1cXEm1hg9SUNHorUQM7C
JqMCgYEAofxXJ8dOWUaFIHmKLE7Y+0+i3D1yXVIyu/puuaQGbNFHxjcJ9ZdubhLN
IyzJvngtM7mC90FtUETxvErMGdTzFeKtSKBZsJ8BiLCszRCEuJf5RX6uNrFUQ2pT
PEmns088Gs4KUDwjTG0zQtj3pNc5zDynDMpFKp96spefqLJqw3s=
-----END RSA PRIVATE KEY-----
EOL
else
DEFAULT_ID_RSA=$CERTIFICATE
fi
ssh-add $DEFAULT_ID_RSA >/dev/null 2>&1 || true
exec scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \
-o LogLevel=ERROR -i $DEFAULT_ID_RSA -P $PORT -pr $p1 $p2
================================================
FILE: tools/socks5/Dockerfile
================================================
FROM alpine:3.15
# this will produce large image but IDC
LABEL maintainer="rev1si0n "
ENV SOURCESMIRROR=mirrors.ustc.edu.cn
RUN sed -i "s/dl-cdn.alpinelinux.org/${SOURCESMIRROR}/g" /etc/apk/repositories
COPY entry /usr/bin
RUN apk add bash make g++ git
RUN mkdir -p /tmp/workdir
WORKDIR /tmp/workdir
RUN wget https://www.inet.no/dante/files/dante-1.4.3.tar.gz -O - | tar -xz
WORKDIR /tmp/workdir/dante-1.4.3
ENV ac_cv_func_sched_setscheduler=no
RUN ./configure --disable-client && make -j $(nproc)
RUN cp sockd/sockd /usr/bin
RUN rm -rf /tmp/workdir
WORKDIR /
EXPOSE 1080/tcp 1080/udp 50000:55000/udp
ENTRYPOINT ["entry"]
================================================
FILE: tools/socks5/entry
================================================
#!/bin/bash
LOGIN=${LOGIN:-lamda}
PWD=${PASSWORD:-lamda}
UDPRANGE=${UDPRANGE:-50000-55000}
BIND=${BIND:-0.0.0.0}
PORT=${PORT:-1080}
# the default output interface
DEFAULT_DEV=$(ip route | awk '/default/ { print $5 }')
DEV=${DEV:-"${DEFAULT_DEV}"}
deluser --remove-home ${LOGIN} 2>/dev/null
adduser -D -H -s /bin/false ${LOGIN} 2>/dev/null
echo "${LOGIN}:${PWD}" | chpasswd 2>/dev/null
echo "SOCKS5 LOGIN: ${LOGIN}"
echo "SOCKS5 PASSWORD: ${PWD}"
cat </etc/danted.conf
internal: ${BIND} port = ${PORT}
external: ${DEV}
clientmethod: none
socksmethod: username
user.privileged: root
user.unprivileged: nobody
timeout.tcp_fin_wait: 15
timeout.negotiate: 15
timeout.connect: 15
logoutput: /proc/self/fd/2
socks pass {
from: 0.0.0.0/0 to: 0.0.0.0/0
command: bind connect udpassociate
udp.portrange: ${UDPRANGE}
log: error connect
}
socks pass {
from: 0.0.0.0/0 to: 0.0.0.0/0
command: bindreply udpreply
udp.portrange: ${UDPRANGE}
log: error connect
}
client pass {
from: 0.0.0.0/0 to: 0.0.0.0/0
log: error
}
EOL
exec sockd -f /etc/danted.conf
================================================
FILE: tools/ssh.sh
================================================
#!/bin/bash
TARGET=${1:-localhost}
PORT=${PORT:-65000}
DEFAULT_ID_RSA=$(mktemp)
umask 077
if [ ! -f "${CERTIFICATE}" ]; then
# this is the default id_rsa for ssh service
cat <$DEFAULT_ID_RSA
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA4QHmY32OT+F+maERMn1cvBRuIOIXH9yOALG+GMCngtjRJzSR
n09dInmXE+PjiAqNRWvknVEjFywv0v1v/H2qSRJKR/togPgySjiABhigqDHdirNd
Dh63oN2e+d0yythoLzsQrH5BSVtw05Atpkr7bW4KdfMveWuddvDACnQ3mvCXq50X
IK3cOlmHXwcJrX55BhEXxgHIqw0upf0A7DC3Afz5xjOA6+K/O2EzZIIJ+sWw7/Ko
5+3m98Et0zwcxe20uNxzYf7JSMu3490YNckLiQcDrZVRUXNS70HO9HWCXKdjFJPi
GgPtKUQNHjChwzSCQQGzqitXdwa60i9Sy1U09wIBIwKCAQAzbiYIHLLQbg5PAD5x
8MS9Rn+SfNIV6UUHeRWCACZJyyh9/WMdGXRfpsNyQreqEQpZArfpcaGe5YdGK0zL
/3dhKMCFe0sWKhofl+K/kJnAC2XWj2W6FaZQp69PDfz7KiZxMhJwkeMJc/yIIPR9
x/70cOxyuz4NH+l6Rag8510qujhxn6lGc1+ZNHGeAYmUD7wFz9/QYDZU9C0YcylA
2+Q5woU3L4b3y3JKqB6/dnsP9uF7R5KBR+qiqOwVm0nhvVU5uwbpQwTXeklopXyN
rI/NeYcsDoEyb8NquVXX/GkOgY0FhqblGUR9kSdTLHq6VamelHc+dczZgMxcsq1c
mnnLAoGBAP2FPssFpHJRhSr38Fq5A7mEjQeiPq2WgYV7kCpJT5F5OymcCCETIjEH
4pTk5zCWUuIBx5LlzSa0XnQSYb0100ZzvDgfm1NPmqdkpPwkkbh2xyoHYTTPJ3WG
TDur8Qyi83NteveszO1TCAGBTe3zN+2ov9qzl5Y7QHF94GVFDo4NAoGBAOM1Q8eG
0KeqjutTz/UMtejoFpz0Hi1g32PfdQInHx8MDslYu3Fcpnos3xf59H7+mrRy0fUM
hh27v7DiUvxUfhlojf0F3kDKeg9VZBslZF3vTCpFdKdFouZ2Cru3lCoaPSau69BC
6HQw4P+RABrgxc6CeE9FUGEEMss+wTcRItITAoGBAO8ImkpkZ9mAD9gOV6X+5kEz
1W2Y+USVOEqns9AZPGSW4AKpDvqdAvsHbzvtxAk9RtUXnumW118B1WYf9cEG3SUr
SxBYUJ8B6ZaDdvxc/mwYOCBQGdK0sCz692t2OwvqGL1J95kQo/W0r8bnoT9wSqzg
72fN5rI33azVxPHExJSPAoGAGfd1deOFj4E057HOn6muY8LAwXr8InF4nbMjULQD
joUJARF0gfv1xNHtnFcUoMyj912UVoUWpE/4poBD/5SgsnJZXr7XkmBIdsfuLvz1
hxQItF+1j3WsN5h2QVbPGsErj2RyuLcwgk66oN1fGQO+1cXEm1hg9SUNHorUQM7C
JqMCgYEAofxXJ8dOWUaFIHmKLE7Y+0+i3D1yXVIyu/puuaQGbNFHxjcJ9ZdubhLN
IyzJvngtM7mC90FtUETxvErMGdTzFeKtSKBZsJ8BiLCszRCEuJf5RX6uNrFUQ2pT
PEmns088Gs4KUDwjTG0zQtj3pNc5zDynDMpFKp96spefqLJqw3s=
-----END RSA PRIVATE KEY-----
EOL
else
DEFAULT_ID_RSA=$CERTIFICATE
fi
ssh-add $DEFAULT_ID_RSA >/dev/null 2>&1 || true
exec ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \
-o LogLevel=ERROR -i $DEFAULT_ID_RSA -p $PORT root@$TARGET ${@:2}
================================================
FILE: tools/startmitm.py
================================================
#!/usr/bin/env python3
# Copyright 2025 rev1si0n (lamda.devel@gmail.com). All rights reserved.
#
# Distributed under MIT license.
# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
#encoding=utf-8
import os
import re
import sys
import time
import uuid
import logging
import asyncio
import argparse
import subprocess
import threading
from socket import *
from random import randint
from multiprocessing import Process
from urllib.parse import urlparse
from functools import partial
from mitmproxy.certs import CertStore
from mitmproxy.tools.main import mitmweb as web
from mitmproxy.options import CONF_DIR, CONF_BASENAME, KEY_SIZE
from mitmproxy.version import VERSION
from packaging.version import parse as ver
from lamda import __version__
from lamda.client import *
serial = None
cleaning = False
def cleanup(*args, **kwargs):
global cleaning
if cleaning is True:
return
cleaning = True
log ("uninstall certificate")
d.uninstall_ca_certificate(ca)
log ("disable proxy")
d.stop_gproxy()
os._exit (0)
def add_server(command, spec):
spec and command.append("--mode")
spec and command.append(spec)
def add_upstream(args, ext):
u = urlparse(args.upstream)
upstream = "upstream:{}://{}:{}".format(u.scheme,
u.hostname,
u.port)
args.mode = upstream
cred = "{}:{}".format(u.username, u.password)
u.username and ext.append("--upstream-auth")
u.username and ext.append(cred)
def log(*args):
print (time.ctime(), *args)
def die(*args):
print (time.ctime(), *args)
sys.exit (1)
def adb(*args):
command = ["adb"]
if serial is not None:
command.extend(["-s", serial])
command.extend(args)
log (" ".join(command))
proc = subprocess.Popen(command)
return proc
def adb_tcp(action, aport, bport):
p = adb(action, "tcp:{}".format(aport),
"tcp:{}".format(bport))
return p
def reverse(aport, bport):
return adb_tcp("reverse", aport, bport)
def forward(aport, bport):
return adb_tcp("forward", aport, bport)
def get_default_interface_ip_imp(target):
s = socket(AF_INET, SOCK_DGRAM)
s.connect(( target, lamda ))
return s.getsockname()[0]
def get_default_interface_ip(target):
default = get_default_interface_ip_imp(target)
ip = os.environ.get("LANIP", default)
return ip
print (r" __ __ .__ __ ")
print (r" _______/ |______ ________/ |_ _____ |__|/ |_ _____ ")
print (r" / ___/\ __\__ \\_ __ \ __\ / \| \ __\/ \ ")
print (r" \___ \ | | / __ \| | \/| | | Y Y \ || | | Y Y \ ")
print (r" /____ > |__| (____ /__| |__| |__|_| /__||__| |__|_| / ")
print (r" \/ \/ \/ \/ ")
print (r" Android HTTP Traffic Capture ")
print (r"%60s" % ("lamda#v%s BY firerpa" % (__version__)))
pkgName = None
argp = argparse.ArgumentParser()
login = "lamda"
psw = uuid.uuid4().hex[::3]
cert = os.environ.get("CERTIFICATE")
proxy = int(os.environ.get("PROXYPORT",
randint(28080, 58080)))
webport = randint(28080, 58080)
lamda = int(os.environ.get("PORT",
65000))
argp.add_argument("device", nargs=1)
mod = argp.add_mutually_exclusive_group(required=False)
mod.add_argument("-m", "--mode", default="regular")
mod.add_argument("--upstream", type=str, default=None,
help="Upstream http proxy")
argp.add_argument("--proxy-dns", type=str, default=None,
help="Resolve dns(tcp) through proxy")
argp.add_argument("--serial", type=str, default=None,
help="Adb device serial")
args, extras = argp.parse_known_args()
serial = args.serial
host = args.device[0]
if ":" in host:
host, pkgName = host.split(":")
server = get_default_interface_ip(host)
usb = server in ("127.0.0.1", "::1")
if cert:
log ("ssl:", cert)
if args.upstream:
add_upstream(args, extras)
if usb and forward(lamda, lamda).wait() != 0:
die ("adb forward failed")
if usb and reverse(proxy, proxy).wait() != 0:
die ("adb forward failed")
# Create instance
d = Device(host, port=lamda,
certificate=cert)
logger.setLevel(logging.WARN)
# Concat mitmproxy cert path
DIR = os.path.expanduser(CONF_DIR)
CertStore.from_store(DIR, CONF_BASENAME, KEY_SIZE)
ca = os.path.join(DIR, "mitmproxy-ca-cert.pem")
log ("install cacert: %s" % ca)
d.install_ca_certificate(ca)
# Initialize proxy profile
profile = GproxyProfile()
profile.type = GproxyType.HTTP_CONNECT
profile.bypass_local_subnet = True
if args.proxy_dns: profile.nameserver = args.proxy_dns
if args.proxy_dns: profile.dns_proxy = True
# Prevent DNS from being intercepted
if args.proxy_dns: extras.extend(["--ignore-host", args.proxy_dns])
# HTTP MITM Proxy not support udp so drop it
profile.drop_udp = True
profile.host = server
profile.port = proxy
profile.login = login
profile.password = psw
log ("set proxy: %s:%s@%s:%s/%s" % (
login, psw,
server, proxy,
pkgName or "all"))
if pkgName is not None:
profile.application.set(d.application(pkgName))
d.start_gproxy(profile)
command = []
add_server(command, args.mode)
command.append("--ssl-insecure")
# Simple random auth
command.append("--proxyauth")
command.append("{}:{}".format(login, psw))
# Random web-port
command.append("--web-port")
command.append(str(webport))
command.append("--no-rawtcp")
command.append("--listen-port")
command.append(str(proxy))
# Append extra command line
command.extend(extras)
log (" ".join(command))
sys.exit = cleanup
log ("press CONTROL + C to stop")
proc = Process(target=web, name="mitmweb",
args=(command,), daemon=True)
proc.run()
sys.exit(0)
================================================
FILE: tools/startmitm.spec
================================================
# -*- mode: python ; coding: utf-8 -*-
from PyInstaller.building.build_main import Analysis
from PyInstaller.building.api import PYZ, EXE
from PyInstaller.utils.hooks import collect_data_files
a = Analysis(
["startmitm.py"],
pathex=[],
binaries=[],
datas=collect_data_files("lamda"),
hiddenimports=[],
hookspath=[],
hooksconfig={},
excludes=["tcl", "tk", "tkinter"],
win_no_prefer_redirects=False,
win_private_assemblies=False,
noarchive=False,
)
exe = EXE(
PYZ(a.pure, a.zipped_data),
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name="startmitm",
icon=["startmitm.ico"],
debug=False,
bootloader_ignore_signals=False,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
)
================================================
FILE: tools/test-fridarpc.js
================================================
Java.perform(function() {
var String = Java.use("java.lang.String")
rpc.exports = {
getMyString: function (paramA, paramB) {
return performRpcJVMCall(function() {
// 可以使用 Frida java 相关功能,Java.use 等
var newParam = String.$new("helloWorld").toString()
return newParam + ":" + paramA + paramB
})
},
getMyString1: function (paramA, paramB) {
return performRpcJVMCallOnMain(function() {
// 可以使用 Frida java 相关功能,Java.use 等
// 执行于应用的主进程,适用于涉及到 UI 主线程相关的功能
var newParam = String.$new("helloWorld").toString()
return newParam + ":" + paramA + paramB
})
},
getMyString2: function (paramA, paramB) {
return performRpcCall(function() {
// 这里不能使用 Java 相关功能
return paramA + paramB
})
},
}
});
================================================
FILE: tools/test.pem
================================================
LAMDA SSL CERTIFICATE (CN=test,PASSWD=a1c0e3ea707a54de7a0f95)
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDVcLZP9oLQZDHF
yWJSke4gG+IJfH+LZY4qu3u/NrDpHvB7i9WMk1lQ/aLHb4WvjzPK+THNokG760CQ
XAMJVKGfatprcKQwo0hoX6v6XlMYYRSQmop7zRiKg7vq+5vKOCCdJp1eISbew2LH
Nf1/rYzZpkT5lz1Lfdzm4yrAKuMkKrgzgO2QpOBAWXvgbXoA07bt7N86Y8gMdU0t
zR/DrhKN/I1WXJMu1N0AnTl2QtHDoGB7E2KLivl2l6IvtkabxDNyciGmLNPiLFIk
YOHyg1H71Bv5M84NSX75sln9uMPe39SEVRhSJm4w/OkWfp9tjYELwtzatXhIbhKs
h/NeMe9dAgMBAAECggEABbQnYGepWJb02uDmJxK4lv8Sa/J5w2iJWLb94umRPLQ+
SkQ9sVisBbAMI4w6uar4AAU8wXlZNwOzC/3ezucGEqkxAQys4T0xItORAvXLqVZ0
IuZzq5nwJ/NxQsxKfihAfDKbTff7fpnLZUtvZMlnKYHPTLmFQnkwkrL2DNXt5U96
arTP5NcLuh46uOwjRT9hZ7+XB/nmOKy5xWxh5eLBbIQ2kKE/uebRUXDfMtnmm8vh
QHoU3syw9YpMqD9VAjipijAtpmIpkl5zdVHNvCWBHi666q+1zqJGxUT80lxz57du
ToDQPsYt8QD/vc4i1j7tl6rG3PZBM3NK5TydV2FygQKBgQDaG2ruwilraGYG6MAl
4AvYmAcHGAe0B4tfkdu/PjweEd2YAvN2HAeugVTIh5xZeRR04Q8eSFzpShJpDJd4
uLHGqrvrjW/X+xEHsO56sc64bzmpybVApeBp86QFI98WqffAr7qso8pxRc6gSEMn
NSquugjlaM9NRfqJ8r/6FIfBLQKBgQD6hbtggHuRhLxGacMhDo9Ziqre6/G7Woft
GaFfaP3lY53f3HOu+256LX65eaXE7kmyP8dC3eV/n1OwoLwAbmAgjDYWu7OJti8A
OloU6kgNEp5g08zUZPZGsR2FLweiRH+CvNtPYjBrvaHAKUSiKkpJtJVxZHuIzJZF
5TfC6V3k8QKBgDyjtNZO+08Who9TNOES6pg8pG+PeczO8CwRFISWXAaMNwzlfSUQ
Y/5bjOP3+DtUE6DvVdG4ksR1yKqWSqLQzvSKUZcLCtaEwlZeFd/dAbl7iw+uugsQ
U1WB3M9lCsh1VyGmegM3wrg9jVY44RrMiGJt7L1Dp6c3VpH2APQZsyi9AoGBANOi
jfykDb+M5pCFYDZY2njGUDsqD3g6roF6GX1EcNiMIx6uWXwFI/tK27dMMOIAe3l9
r5OpaOs7abPY1Xl3xPU5/aeOwckgguwqX1cz49JHXExoIK187SAjF9EfPc+zFhUX
/h090bHy7OYs9rIYD9HcIDM+s62JR5mcXlLmWk/BAoGBAJEXPWNHXL+kYz3/uGW7
tJwHTC1elBcrW/hzI1kxdHWznUisSZW2Vp9oL0IckAuVBLzxe/GXz8bQN6d9l2d0
tkfRj5NjC953Kcuq3RzDfUN4q72RUVMiE8uoI0dUVijW37KU0HKrmil0hqM5yob5
UfXOCd9JTQIlycgMXDzNm4Kz
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIICqDCCAZCgAwIBAgIRAPsc1PQ5pnH3a6MFfGUMp6YwDQYJKoZIhvcNAQELBQAw
EDEOMAwGA1UECgwFTEFNREEwHhcNMjAwMTAxMDAwMDAxWhcNMjkxMjI5MDAwMDAx
WjAPMQ0wCwYDVQQDDAR0ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEA1XC2T/aC0GQxxcliUpHuIBviCXx/i2WOKrt7vzaw6R7we4vVjJNZUP2ix2+F
r48zyvkxzaJBu+tAkFwDCVShn2raa3CkMKNIaF+r+l5TGGEUkJqKe80YioO76vub
yjggnSadXiEm3sNixzX9f62M2aZE+Zc9S33c5uMqwCrjJCq4M4DtkKTgQFl74G16
ANO27ezfOmPIDHVNLc0fw64SjfyNVlyTLtTdAJ05dkLRw6BgexNii4r5dpeiL7ZG
m8QzcnIhpizT4ixSJGDh8oNR+9Qb+TPODUl++bJZ/bjD3t/UhFUYUiZuMPzpFn6f
bY2BC8Lc2rV4SG4SrIfzXjHvXQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAXD4/W
B0aImZXjPm4qRpNk2fRzcSX80l6NVZYlIWvXjQqQwWfs/Hg3d5saJbrAfqeOkYPu
cyrDXVOt/QLEC91AHkcEbuGGO0cESf28wT3U3FrIog1KUrMDjXQHoOodBi8gMiPf
dNqhLI7d42AMrJSwYQ9RPooXoTglCkGwGouD8nKEy2cGyS13yPp4lx/SY4uBDESK
FQ+GFQLLFBKPdvMsltpv2AaLZdwrQxhT6i55SZn5+Kow5LlX/DGuL9RtOvfvOKse
QgzNAH7BF3lgoBbcbOrfEPk65dFQ7CWb/uh6cVifJ7qC9//LatIfoUPVrbEwY/gQ
NAEqXrWn0fnaG4qA
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIClDCCAXwCAQAwDQYJKoZIhvcNAQELBQAwEDEOMAwGA1UECgwFTEFNREEwHhcN
MjAwMTAxMDAwMDAxWhcNMjkxMjI5MDAwMDAxWjAQMQ4wDAYDVQQKDAVMQU1EQTCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALnqfBZvrGZaqgk5msCRRpPz
p/+464jBknlmTKetOckDTUq8V6fH/2Gfb6HjjoD5pkCtD5mSKkaHNhuxLXsFfEfc
Klmnn3ZpjyNOHEA/iAO2DyEYa08xSevM7WojbF7cNj5/DYg7eb0i0+//bBlh8nlO
ghSUhCTM5PCofLDU8sVXuYAiGU6UzBrIC0vHEltDkiJVLpcCtsKjEZOskPdAc7u6
/aA0P5GonZ5UkRDYpa+jeJUabqWZQQEgtmvjlmUUiXwu0jRnj1n0T3fPQD3gA+LI
vAE9vgvpY5XQj6otpBvsZ3MJJNKcUMQu1zOANTzO18TlA8KoBNsBy8Z9DQZKXF8C
AwEAATANBgkqhkiG9w0BAQsFAAOCAQEAeGn/NyqIRI/0AGqvHP97KtQ76Tj6achb
30LIXWp+YHUaMeAVjd2Z7jSQp5mZZFl+k1fb337IeaGXofVI21eK52QX2teNoBkA
/V/O1Hu35/+aiz0xsdD6wWvwo4Fy1jlmaeJhwdQacBlDGFA2jFJxuEpahfxZvUsb
3j5zU0WKTUCfEDgXFwBw12xku/7TMdCEbRsYaZ3zFTC226lQbUDN7weqFwSD+tB6
RuhIxe8+cFwAsAWHClerKgZnr3t5QF078pW2GHy8CsJ7V3MZT5lZ43lS5NIB9JzZ
SYxZ/izhRyh5qR73uAgsJaL56Bj+cQqnoTqhLZVl7J+M6WhWKzojsA==
-----END CERTIFICATE-----