diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7d63010 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.tflite filter=lfs diff=lfs merge=lfs -text diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 39df7c8..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: "[ISSUE]" -labels: '' -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**Generate statistics** - -```bash -wget https://raw.githubusercontent.com/rev1si0n/lamda/master/tools/statistics.sh -adb push statistics.sh /data/local/tmp -# su to root (change launch.sh to LAMDA's launch.sh path) -sh /data/local/tmp/statistics.sh /data/local/tmp/arm64-v8a/bin/launch.sh -# then attach report file /sdcard/statistics.txt -``` diff --git a/CHANGELOG.txt b/CHANGELOG.txt index da47829..f1ce88a 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,144 +1,665 @@ -3.157 -* 界面检视元素高亮 -* 支持系统崩溃计数 - -3.155 -* 支持 tab 键遍历界面元素 -* 支持在远程桌面输入英文字符 -* 支持远程桌面触摸 - -3.153 -* 修复部分场景截图失败的问题 -* 次要更改 - -3.152 -* 次要界面样式调整 - -3.151 -* 高分屏投屏被拉伸的问题 #41 - -3.150 -* 修改定时任务重载时的逻辑 -* 修复 scapy 路由的问题 -* 兼容部分小米设备 -* 修复安卓11接口兼容性问题(感谢 Kate Swan) -* 支持连接 WIFI 的情况下使用 4G 作为代理 -* 新增部分界面控件 - -3.138 -* 修复 gRPC 依赖的问题 -* 获取系统最后一个 toast - -3.135 -* 修复远程桌面加载中的问题 -* 彻底修复协议中的竞争条件问题 -* 修复 Windows Python310 兼容性问题 -* 允许 HTTP 接口跨站调用 -* 添加部分丢失的模块 -* 远程桌面加入服务状态指示 -* 远程桌面响应式布局 -* 预发 next 版本 - -3.123 -* 修复获取最近活动不全的问题 - -3.120-1 -* 现 LAMDA 已支持本身作为代理 -* 新增获取系统最近 Activity 的接口 -* 修复一个协议中的 race condition (maybe) -* 增加部分命令,移除 SQLite db view -* 实验性的 H.264 投屏 - -3.108 -* 优化网络断连处理逻辑 -* 增加 Redroid (remote android) 支持 -* 部分兼容 uiautomator2 -* 支持文件夹上传 - -3.102 -* 修复文件描述符泄漏的问题 -* 支持从远程文件服务器加载启动配置 -* 现在已上传 armeabi-v7a 的服务端 -* 修复 magisk 下安装根证书失败的问题 -* 修复配置解析错误的问题 -* 轻微的UI调整 - -3.98 -* 加入 crontab, vi 命令 - -3.95 -* 修复构建流程存在的问题 -* 次要更改 - -3.93 -* 增加安卓常量定义 - -3.90 -* 移除未使用的库,减小体积 -* 移除 client 中不兼容 MacOS 的命令行历史功能 -* 更新 DISCLAIMER.TXT -* 更新部分依赖库版本 - -3.83 -* 支持 WSA #24 @aimardcr -* 修复 note7pro MIUI10 黑屏 @yu1chaofan -* 次要更改 - -3.80 -* 修复 ssh 断连的问题 -* 减小包体积 - -3.78 -* 修复 #21 @yu1chaofan -* 更新 frida-server - -3.76 -* 默认内置 shell 使用 bash -* 修复断网后远程桌面触摸无法使用的问题 -* 修复 OpenVPN 僵尸进程的问题 -* startmitm.py 支持指定 adb 串号 -* 支持 magisk 自启动 - -3.0.59 -* 所有界面提示使用英文 -* 修复一个 web 接口未认证的问题 -* 修复旧版本兼容性 - -3.0.55 -* 修复宽字符请求头导致的崩溃 -* 合并 mitmweb 到 startmitm 进程 -* docker 镜像小修改 -* 支持布局检视 - -3.0.50 -* 支持 child, sibling 选择器 - -3.0.48 ------------------------ -* 免安装的 windows startmitm 命令 -* 支持从内存上传/下载文件到内存 -* 添加 screenshot() 别名 - -3.0.47 ------------------------ -* 简化 globalmitm,支持 HTTP,SOCKS5 代理 -* 增强 webview 节点查找 - -3.0.46 ------------------------ -* 支持双指缩放 -* 简化 startmitm DNS 中间人操作 - -3.0.45 ------------------------ -* 支持自定义 server 端口 (--port) -* 目录索引检测文件类型时在特殊文件上卡住 -* globalmitm 检查 DNS 服务可用性 -* startmitm.py 当存在多个网络时获取了错误的网络接口 -* client 通信不再自动使用系统代理 - -3.0.35 ------------------------ -* 提高内置 ADB 性能 -* openvpn 服务支持 auth 参数 (默认为 SHA1) -* 通过内置 ADB 使用 scrcpy 异常的问题 +CHANGELOG + +Version 10.8 +----------- + +* Fixed int64 parameter issue in start_activity +* Added support libraries for OpenAI, Anthropic, Ollama models, boto3, etc. +* Temporary multi-version compatibility fix for frida-server +* Reduced memory usage + +Version 10.6 +----------- + +* Upgrade openssl, openssh, etc. +* Upgrade pyopenssl, cryptography, crypto, etc. +* Fix the issue of command execution failure on Debian +* Add built-in mitmproxy library +* Reduce binary size + +Version 10.4 +----------- + +* Built-in environment now includes the adb command +* Added sqlalchemy and paramiko +* Human-friendly query semantics for child, sibling, and index access +* Added fuser, killall, pstree, and run-parts commands +* Other minor bug fixes + +Version 10.2 +----------- + +* Added support for Android 17 +* Added support for Android 16KB page size +* Upgraded runtime core to Python 3.12 +* Added APK/XAPK installation API +* Fixed various issues + +Version 10.0 +----------- + +* Support for background virtual screen automation +* Gproxy proxy supports specifying outbound network interfaces +* Support for non-root execution mode (adb shell) +* Optimized real-time screen mirroring smoothness and audio +* Support for WebRTC screen mirroring +* WebUI supports dark mode +* Support configuring properties via WebUI +* File upload supports streaming, with a maximum file size of 2GB +* Redesigned built-in tunnel2 proxy functionality +* Optimized built-in AI token consumption +* Optimized event system +* Optimized service stability, etc. + +Version 9.25 +----------- + +* Fixed semaphore issues +* Resolved MCP protocol compatibility issues +* Multi-user support for Remote Desktop +* Added standardized touch simulation API +* Compatibility fixes for Android 16 + +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. \ No newline at end of file diff --git a/DISCLAIMER.TXT b/DISCLAIMER.TXT index 7e14275..67dd7c7 100644 --- a/DISCLAIMER.TXT +++ b/DISCLAIMER.TXT @@ -1,32 +1,37 @@ -为了下载使用由 rev1si0n (账号 github.com/rev1si0n,邮箱 ihaven0emmail@gmail.com)(以下简称“本人”) -个人开发的软件 lamda(以下简称“本服务”),您应当阅读并遵守本协议。请您务必审慎阅读、充分理解各条款内容,并选择接受或不接受。 -除非您已阅读并接受本协议所有条款,否则您将无权下载、安装或使用本软件及相关服务。 -您的下载、安装、使用等行为即视为您已阅读并同意受到本声明的约束。 +DISCLAIMER -风险告知: -本服务需要设备获取 root 权限才能运行,且默认的通讯协议及相关证书文件均为开放信息,这可能会增加您设备被入侵的风险。 -本服务由本人自行开发并使用,可能存在潜在的未知的风险如数据丢失、系统崩溃等,由用户自行决定是否下载、使用本服务。 +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. -1、本服务设计目的为提高安全分析及测试人员工作效率,应用行为、应用合规分析等。提供的相关工具均为合法合规的APP测试分析、Mock 场景提供。 -本服务本身不提供任何侵入、修改、抓取其他应用内存及网络数据的功能,整合了各大开源框架提供的服务供用户自行选择,方便安全分析人员使用,减少用户的重复性劳动以及管理成本。 -本服务本身不存在盈利性,用户可根据自己需求自行通过下载获取使用,下载及使用过程中不会收取任何费用。 +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. -2、本服务尊重并保护用户的个人隐私,不会窃取任何用户设备中的信息。本框架的启动及任何对设备数据读取、存储、传输等权利均在用户自己手中。 -3、用户必须在无隐私数据的虚拟设备或者专用设备中使用本服务。在使用本服务时,必须遵守中华人民共和国或者用户所属国家或地区的法律法规, -不得为任何非法目的而使用本服务,不得利用本服务进行任何不利于他人的行为。 +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. -4、用户只可使用本服务进行正规的学习研究或是经过合法授权的应用分析、测试等行为,若用户在使用该软件服务的过程中违背以上原则对第三方造成损失,一切责任由该用户自行承担。 +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. -5、任何单位或个人因下载使用本服务而产生的任何意外、疏忽、合约毁坏、诽谤、版权或知识产权侵犯及其造成的损失 (包括但不限于直接、间接、附带或衍生的损失等),本人不承担任何法律责任。 +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. -6、用户明确并同意本协议条款列举的全部内容,对使用本服务过程中可能存在的风险和后果由用户自行承担,本人不承担任何法律责任。 +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. -7、本人有权随时对本声明条款及附件内容进行单方面的变更、中断或终止部分或全部本服务的权利。并以消息推送、网页公告等方式予以公布, -公布后立即自动生效,无需另行单独通知;若您在本声明内容公告变更后继续使用的,表示您已充分阅读、理解并接受修改后的声明内容。 +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. -8、如果本声明的任何部分被认为无效或不可执行,则该部分应以符合相关法律的方式予以修正,以尽可能地反映出本人的原始意图, -其余部分仍具有完全效力。不可执行的部分声明,并不构成本人放弃执行该声明的权利。 +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. -9、保留权利:未明示授权的其他一切权利均由本人所有。 \ No newline at end of file +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. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..98f49e2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +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. \ No newline at end of file diff --git a/README.en.md b/README.en.md deleted file mode 100644 index 260f1c1..0000000 --- a/README.en.md +++ /dev/null @@ -1,1888 +0,0 @@ -

-LAMDA -

- -

Android reverse engineering & automation framework

- -**English README is translated by deepl, we not guarantee it's the original meaning of the author, all readme is named README.en.md, if you still confused, please reach me at [t.me/lamda_dev](https://t.me/lamda_dev) or by mail/gitter.** - -LAMDA is a helper framework for reverse and automation, designed to reduce the time and trivialities of security analysis and application testers by replacing a lot of manual work with programmatic interfaces, it is not a single feature framework. To give you an idea of its usefulness: do you install all kinds of proxies, plug-ins or point-and-click setups on your mobile phone to do your job? Do you have to operate your phone from a remote location that is thousands of miles away? Do you have the need to program your phone to control it? Are you still buying expensive **IP switching**, **remote ADB debugging**, **RPA automation** or even **logcat logging** services from some cloud phone vendors that you have to pay for? If so, then yes, all these problems can be solved with just one LAMDA. And, LAMDA is even more focused on **distribution** - in fact, you can manage devices scattered around the world in various network environments on a single public network server. Of course, LAMDA can do much more than that. - -* Zero dependency, just **root** is all you need -* Stable production environment tested with over 500 devices under pressure -* Easy root certificate setup via interface, with http/socks5 proxy for man-in-the-middle -* Expose internal Java interface via frida as http api -* near-commercial-grade software quality and stability, full ARM/X86 architecture -* High security, support for interface and login authentication -* Turns your device into a http web proxy -* Partially compatible with uiautomator2 -* Device status/resource consumption readout -* System configuration/property readout modification -* UI layout review -* WIFI ADB with built-in root privileges for wireless connect -* Support for custom startup configuration -* Support for emulators and real phones, cloud phones/headless development boards, redroid (ARM only) -* Support for Android 6.0 (M, API 23) - 13 (T, API 33) -* Support for WSA (Windows Subsystem for Android ™️) -* Support for UDP protocol proxies (socks5 UDP mode) -* Support for OpenVPN and coexistence with http proxy -* Support for Magisk autostart service -* Encapsulates a lot of common interfaces, only Python is required -* Completely networked, no physical USB cable/USB hub, etc. -* Large file uploads and downloads -* Significantly reduces the threshold and time cost of mundane tasks -* Get/replay the most recent Activity on the system -* Send any application Activity -* ssh login to device terminals -* Connect to any device anywhere with LAMDA running -* Run shell commands front and back, grant revocation of application privileges, etc. -* Built-in http/socks5 proxy, can set proxy for global/specific applications -* Built-in frida 15.x, IDA 7.5 server and other tools -* Built-in crontab task service -* Built-in Python 3.9 and some common modules -* Built-in OpenVPN for global/non-global VPN -* WIFI remote desktop (web) -* WEB-side file upload and download -* UI automation with interfaces to automate operations - -## One-click man-in-the-middle traffic analysis - -With support for regular and international APP traffic analysis, DNS traffic analysis, and thanks to the [mitmproxy flow hook](https://docs.mitmproxy.org/stable/api/events.html), you have maximum control over any request, mitmproxy is feature rich enough to export `curl` commands or `HTTPie` commands for specific requests via its `Export` option. It is rich enough that you can export request-specific `curl` commands or `HTTPie` commands via its `Export` option, and the combination of analysis replay, interception modification, and functionality is a good substitute for any commercial/non-commercial software you have used. If you're still not sure what mitmproxy is and what it can do, be sure to look up the documentation first, as LAMDA will be using mitmproxy to present you with application requests. - -This is done via `globalmitm`, `startmitm.py` in the tools/ directory, see the README in the same directory for how to use it. - -![Man-in-the-middle traffic analysis motion graphic demo](image/mitm.gif) - -## Drag and Drop Uploads - -Drag and drop uploads can be done directly from the remote desktop, with support for uploading entire directories up to a single 256MB file, which will always be uploaded to the `/data/local/tmp` directory. - -![Drag and Drop Upload Demo](image/upload.gif) - -## Remote Desktop Connection - -The interface can be operated at any time using your browser even when your phone is not with you, and with Python and related frida tools built in, it is your alternative online shell. - -![Remote desktop motion demo](image/lamda.gif) - -## Automation through code - -Directly through code point and click, it can replace most manual operations. - -![automation motion demo](image/automation.gif) - -## Interface layout review - -You can instantly review the interface layout of your Android application for writing automation code from the remote desktop. You can enter the mode by clicking on the eye icon in the top right corner of the remote desktop. -Press `CTRL + R` to refresh the layout and click the eye icon again to exit. - -![Interface layout view](image/inspect.gif) - -## Device Directory Index - -You can browse the files on your device in your browser and you can also click on the file name to download the desired file. - -![directory indexing animation](image/listing.gif) - -If you wish to continue, please ensure that you have a rooted Android device with more than 2GB of RAM and more than 1GB of free storage or an Android emulator (the latest version of **Nightmare**, **Thunderbolt** emulator, or AVD [Android Studio Virtual Device] is recommended). **Not fully supported** by NetEase Mumu, **Not supported** by Tencent Handy Assistant, Blue Stack and Android internal virtualization such as VMOS). For real devices, we recommend devices running closest to the native system such as Google Series, One Plus, Android development boards, etc., or devices with only lightly modified systems. If you are using an OPPO/VIVO/Huawei/Xiaomi device and it does not run properly after trying, we recommend using an emulator instead. - -For **Cloud Mobile**, AliCloud/Huawei Cloud Mobile is supported, X Finger, X MultiCloud, X Electric, X Cloud Rabbit, X Zixing, X MaCloud and any other brands are not supported. - -
- -# Index - -- [Index](#index) -- [Disclaimer and Terms](#disclaimer-and-terms) -- [Preamble](#preamble) -- [Install LAMDA](#install-lamda) - - [Precautions](#precautions) - - [Basic requirements](#basic-requirements) - - [System settings](#system-settings) - - [Network settings](#network-settings) - - [Other settings](#other-settings) - - [Installing the client](#installing-the-client) - - [Installing the server](#installing-the-server) - - [Installation via Magisk](#installation-via-magisk) - - [Manual installation](#manual-installation) - - [Method 1](#method-1) - - [Method 2](#method-2) - - [Start the server](#start-the-server) - - [Shutdown server](#shutdown-server) - - [Uninstalling server](#uninstalling-server) -- [Using LAMDA](#using-lamda) - - [Remote Desktop](#remote-desktop) - - [File Uploads](#file-uploads) - - [File downloads](#file-downloads) - - [Mobile Proxy](#mobile-proxy) - - [Custom Proxy Configuration](#custom-proxy-configuration) - - [Connecting devices](#connecting-devices) - - [Let's start with a warm up](#lets-start-with-a-warm-up) - - [Setting up a system proxy](#setting-up-a-system-proxy) - - [Installing the man-in-the-middle certificate](#installing-the-man-in-the-middle-certificate) - - [Setting up OpenVPN](#setting-up-openvpn) - - [Connecting to the built-in FRIDA](#connecting-to-the-built-in-frida) - - [Exposing Java interfaces using FRIDA](#exposing-java-interfaces-using-frida) - - [Using the built-in timed tasks](#using-the-built-in-timed-tasks) - - [Make LAMDA connectable from any location](#make-lamda-connectable-from-any-location) - - [Read and write system properties](#read-and-write-system-properties) - - [Read and write system settings](#read-and-write-system-settings) - - [Get device running status](#get-device-running-status) - - [Execute commands on the device](#execute-commands-on-the-device) - - [Make system debuggable](#make-system-debuggable) - - [Start IDA Debugging Service](#start-ida-debugging-service) - - [Wireless connection built-in WIFI ADB](#wireless-connection-built-in-wifi-adb) - - [File manipulation](#file-manipulation) - - [Shutdown and restart](#shutdown-and-restart) - - [Application Operations](#application-operations) - - [WIFI operation](#wifi-operation) - - [Basic UI Operations](#basic-ui-operations) - - [Advanced UI Operations](#advanced-ui-operations) - - [Interface Lock](#interface-lock) - - [Using the internal terminal](#using-the-internal-terminal) -- [Tools and tutorials](#tools-and-tutorials) - - [One-click middleman](#one-click-middleman) - - [International proxies for man-in-the-middle](#international-proxies-for-man-in-the-middle) - - [Installing the ADB public key](#installing-the-adb-public-key) - - [OpenVPN service](#openvpn-service) - - [SOCKS5 service](#socks5-service) - - [Port Forwarding Service](#port-forwarding-service) - - [Injecting Frida RPC scripts](#injecting-frida-rpc-scripts) - - [Generate encrypted connection certificate](#generate-encrypted-connection-certificate) - - [List intranet devices](#list-intranet-devices) - -# Disclaimer and Terms - -In order to download and use the software LAMDA developed by rev1si0n (account github.com/rev1si0n) ("I"), you should read and comply with the User Agreement ("this Agreement") ("Agreement"). You must read and fully understand the terms and conditions, especially those that exclude or limit liability, and choose to accept or not accept them; unless you have read and accepted all of the terms and conditions of this Agreement, you are not authorized to download, install or use the Software and related services. By downloading, installing, using, obtaining an account, logging in, etc., you are deemed to have read and agreed to be bound by the above agreement; if you wish to access the Service, you (hereinafter referred to as "User") shall agree to all of the terms of this agreement and complete the application process as indicated on the page. You can find [DISCLAIMER.TXT](DISCLAIMER.TXT) in the same directory as this document, or here [DISCLAIMER.TXT](DISCLAIMER.TXT). As it is not fully open source, in addition to the above terms:** You are authorised to reverse-analyse LAMDA itself for the purpose of malicious code**. - -Please confirm that you have read and accepted all of the terms of this Agreement, otherwise you are not authorized to download, install or use the Software and related services. - -# Preamble - -LAMDA is free software (freeware) developed by individuals, currently only the client and the protocol are open source, but I promise that it has nothing against you or superfluous, if you still have concerns, you can **leave now** or choose to **pay** for psychological comfort. Please respect each other and use it in accordance with the terms of use. Collaborative exchanges can be [click here to send an email](mailto:ihaven0emmail@gmail.com). - -Why is it partially open source? Because LAMDA is both black and white and can easily be used by unscrupulous people to put the author at risk, so please respect the terms of use. It is recommended that you work with the code in the documentation and samples on a Linux or Mac system. Some of the functions require the use of tools in the `tools/` directory, please refer to [tools/README.en.md](tools/README.en.md). - -**Special Note**: **Do not run on your own device, and when there is a possibility of using it on a public or untrusted network, make sure that you specify the PEM certificate at startup** - -> Side effects after use - -You may write `lambda` in your Python code as lamda, this is normal. - -> Feedback and suggestions - -Because Android is widely used by various devices, it is not possible to guarantee 100% compatibility, and there may be various unknown situations such as running exceptions. The exceptions that occur include: restarting for no reason, app crashing frequently, touch failing or moving around for no reason, etc., frozen screen, etc. If you encounter this frequently, it is recommended to stop using it. -Click here [Report a problem/suggestion](https://github.com/rev1si0n/lamda/issues/new), please describe in detail and include information such as model system. - -Community discussion: [t.me/lamda_dev](https://t.me/lamda_dev) | [gitter.im/lamda-dev](https://gitter.im/lamda-dev/community) - -> Support the author - -If you need to purchase a server, you can choose to purchase cloud services through the **promotional link** below. - -阿里云 -腾讯云 - -
- -# Install LAMDA - -> There is a client side and a server side. The client side is mainly Python related libraries and interfaces, and the server side is the service that runs on the device/phone. - -## Precautions - -### Basic requirements - -LAMDA ideally runs in an environment where you are freshly rooted (e.g. a new emulator, a ROM with its own permissions, Magisk just rooted), and before launching **make sure** that. - -``` -* Magisk Hide must be turned off -* frida-server must be turned off -* It is recommended to disable the Xposed/Magisk plugin -* Reboot the device after confirming -``` - -and will not enable any of the above entries marked as `must` after startup. - -### System settings - -> Check time zone time - -Open System Settings, find Date & Time, check if the time is automatically set to **China Standard Time** or your location's time zone, check if the time is correct or within an acceptable margin of error, if not turn off **Use network-provided time zone** and **Network time** and manually set the time zone and time to your current location's time zone and time. - -> Turn off accessibility - -Open System Settings, find Accessibility (in System or More Settings) and turn off or uninstall all applications that use accessibility (e.g. talkback, autojs, etc.). - -### Network settings - -For real machines, you just need to make sure that your computer is on the same network as your phone. -For emulators, the emulator created by default does not normally interoperate with your local network. If you are using android x86 (VMWare-based Android virtual machine) -If you are using an android x86 (VMWare based Android virtual machine), please try setting the network mode to bridge in the virtual machine settings. For emulators such as Thunderbolt and Nightmare, you will need to follow the instructions in their settings to install the driver and enable bridge mode and then restart the emulator. -For Android Studio Virtual Device, there are no settings. If you need to connect to AVD, please execute `adb forward tcp:65000 tcp:65000` first. -and use `localhost` (do not use 127.0.0.1) to connect. - -### Other settings - -> WSA (Windows Subsystem Android) - -If you are using WSA (Windows Subsystem Android), make sure that the WSA version is not lower than 2210.40000 and that you are rooted, then open WSA Settings -> Subsystem Resources -> select Continuous and turn off Advanced Networking. Then restart the WSA subsystem. - -> AVD (Android Studio Virtual Device) - -If you are using AVD (Android Studio Virtual Device), first extend the default storage size as follows. - -```bash -# Pixel_5_API_29 is the virtual machine ID, which can be listed using the command emulator -list-avds -# -partition-size Some of the newly created AVDs may only have a hundred megabytes of available storage space, change it to 2G here -emulator -avd Pixel_5_API_29 -partition-size 2048 -no-snapshot-load -# Use this command every time you start the virtual machine afterwards -# -# You may encounter a situation where the emulator command is not found. -# See this documentation for the location of this command https://developer.android.com/studio/run/emulator-commandline?hl=zh-cn and add it to the PATH variable -# -# If you are unable to complete the above command, manually click on Virtual Device Manager in Android Studio, create a new virtual machine, then find the corresponding virtual machine and click on the Edit button (a pen symbol) at the back. -# Click on Show Advanced Settings, find Storage -> Internal Storage and set it to at least 2GB. -``` - -> Redroid (android in docker) - -Note that currently **only ARM (aarch64) based hosts** are supported for Redroid, which you can check with the command `uname -m`. - -If you are using Redroid (android in docker), take the officially recommended `Ubuntu 20.04` as an example, first install the relevant modules for linux-modules-extra, (note that the following method may not be suitable for other Linux distributions, and if you are not familiar with Linux, we do not recommend that you continue with the following actions ). - -```bash -apt install linux-modules-extra-`uname -r` -``` - -Edit the file `/etc/modules`, copy the following names and insert them at the bottom of the file and restart the current host. - -```bash -# redroid modules -mac80211_hwsim - -binder_linux -ashmem_linux -``` - -**or** every time the host reboots (note that if you don't use the above method then you have to do it every time you reboot) - -```bash -modprobe mac80211_hwsim -modprobe binder_linux devices="binder,hwbinder,vndbinder" -modprobe ashmem_linux -``` - -Finally use the following command to start, `redroid_gpu_mode` Please modify it as appropriate (note that this is different from the official command written). - -```bash -docker run -itd --rm --privileged --pull always -v /lib/modules:/lib/modules:ro -v ~/redroid:/data -p 127.0.0.1:5555:5555 -p 127.0.0.1:65001:65000 redroid/redroid:12.0.0-latest androidboot.redroid_gpu_mode=guest -``` - -> The container 65000 is mapped here to the local 65001 because some of the tools need to be temporarily bound to port 65000. - -Subsequently, you can access LAMDA on the host through `http://127.0.0.1:65001`. - -## Installing the client - -Please use Python versions 3.6 - 3.10, Python 3.9 is recommended if possible - -```bash -pip3 install -U lamda -## That's it. -# -# If you need to use the built-in frida, be sure to install it using the following method -# You may need extranet access to install frida, otherwise you may be stuck for a long time (~10 minutes) until the installation fails -# Even if you have installed frida before, you should re-run the following command -pip3 install -U --force-reinstall 'lamda[full]' -# Please note that after completing the installation you will need to update any third party libraries that rely on frida using pip at the same time -# any third party libraries such as frida-tools objection etc. (if installed) -# Otherwise you may get undetectable exceptions in later use -``` - -After installation, run the command `python3 -m lamda.client` to check that it is installed correctly. If you get an error like the following - -```python -* AttributeError 'NoneType' object has no.. -* TypeError: Couldn't build proto file.. -``` - -This may be due to a conflict with another package installed that depends on protobuf. Please try the following command - -```bash -pip3 install -U --force-reinstall lamda -``` - -If you still have problems, create virtualenv to use it. - - -## Installing the server - -**LAMDA installed by default does not have any authentication enabled, so others can access any content on your device, listen to your device or even access your network for further control. Please pay particular attention to the `Enabling interface authentication` section and be sure to use it on a network that you can `trust`.** - -Before installing, please select the appropriate architecture. You can get the current system architecture by using the adb shell command `getprop ro.product.cpu.abi`. -Normally, for current generation phones, you can just choose the `arm64-v8a` version, while for emulators such as Thunderbolt, you would choose either the 32 or 64 bit version of the Android system when creating a new emulator. -The 32-bit emulator system corresponds to `x86` and the 64-bit corresponds to `x86_64`. Normally, the Thunderbolt emulator creates an `x86` based Android 7.0 system by default. - -LAMDA supports active reporting of device status, you can write an interface or use grafana to log device status, which includes information about the system, network, memory, CPU, disk, etc. - -```bash -# If you are not sure what this feature is please do not perform it and take care to replace the following link -echo "stat-report.url=http://example.com/report" >>/data/local/tmp/properties.local -``` - -This will cause LAMDA to **POST** device status information (JSON) to this link **every minute** after startup, which will not be listed here due to the number of fields. - -> Configuring automatic updates - -LAMDA has a logic for automatic updates, but due to minute service interruptions, this is currently limited to internal use. Please download and install the latest version from github at your leisure at regular intervals. - -If you really don't care if the service is unavailable by the minute when you update, writing the following configuration file before starting LAMDA will ensure that LAMDA is always up to date. - -```bash -# Go to the adb shell and execute -echo "upgrade.channel=latest" >> /data/local/tmp/properties.local -``` - -> properties.local startup configuration - -Before we start, it is important to introduce the `properties.local` file above. -properties.local is the LAMDA startup configuration file, usually stored on top of the device, which contains strings of type `a=b`. -By writing this file you can implement automatic connections to OpenVPN, proxies, port forwarding etc. when LAMDA starts. -LAMDA will look for this file and load it from `/data/usr`, `/data/local/tmp`, `${CFGDIR:-/data/local}` at boot time. -You can place your properties.local configuration file in any one of these three locations. - -In addition to `properties.local`, there is also a parameter `-properties.remote` that loads the configuration from a remote location, which allows LAMDA to download the configuration from an HTTP server at startup, see the section on starting LAMDA further on. - -How to write the configuration is described in the individual functions. - -> possible errors with launch.sh and how to resolve them - -```bash -# If llllaamDaa started is displayed, the service is in daemon mode and you can exit the terminal - -already running (already running, please do not start it more than once) -invalid TZ area (time zone is not set, set the time zone in the system time settings) -not run as root (not running as root) -unsupported sdk (running on an unsupported Android system) -abi not match (wrong tar.gz package used) -``` - -### Installation via Magisk - -If your device uses Magisk, then you can install it in the easiest way possible and LAMDA can **boot up**. Make sure that Magisk version >= 20.4 and only supports installation in the **Magisk App**. For example, you want all LAMDAs flashed with this magisk module to have the interface authentication enabled, or you want them all to boot with the Magisk App**. -Or if you want them all to automatically connect to the proxy on boot, you can simply write properties.local or generate a PEM certificate and rename it to `lamda.pem` (see tools/ for instructions on how to use the tool). -Then open `lamda-magisk-module.zip` using **compression software** and drag it (`lamda.pem` or `properties.local`) into the `common` folder for automatic configuration at startup! - -Now, download `lamda-magisk-module.zip` from the [lamda/releases](https://github.com/rev1si0n/lamda/releases) page and push it to `/sdcard`, open the Magisk App, click on Modules-> Install from local, select lamda-magisk-module.zip and wait for a while. - -After a successful flash, please reboot the device. After rebooting, LAMDA should start itself on boot. However, to avoid possible crash problems, lamda will start after 30 seconds instead of immediately and you will have enough time to disable the LAMDA module (please wait 2 minutes after booting before connecting to use LAMDA). Once the installation is complete, you don't need to read the next paragraph on manual installation, just skip it. - -### Manual installation - -The difference between the two methods is that some older devices may not be able to decompress the tar.gz suffix with the system's `tar` command, so `*-install.sh` is provided as a supplement, with a built-in busybox to decompress it. The device architecture obtained by getprop is known to be `arm64-v8a`, now connect the device to the current computer and make sure that the ADB is authorised and that you can switch root properly. - -#### Method 1 - -From the `release` page [lamda/releases](https://github.com/rev1si0n/lamda/releases) -Download `arm64-v8a.tar.gz-install.sh`. - -```bash -# /data/local/tmp is the standard service installation path, but not mandatory -# You can put it in any folder with read/write access other than /sdcard -adb push arm64-v8a.tar.gz-install.sh /data/local/tmp -# Enter the adb shell -adb shell -# Type su to make sure you are root -su -# Switch to the directory -cd /data/local/tmp -# Execute the install script and start it (this will unpack and start the service) -sh arm64-v8a.tar.gz-install.sh -# Remove the installer -rm arm64-v8a.tar.gz-install.sh -``` - -#### Method 2 - -From the `release` page [lamda/releases](https://github.com/rev1si0n/lamda/releases) -Download `arm64-v8a.tar.gz`. - -```bash -# /data/local/tmp is the standard service installation path, but not mandatory -# You can put it in any folder with read/write access other than /sdcard -adb push arm64-v8a.tar.gz /data/local/tmp -``` - -When finished, go to the `adb shell` and unpack the files. - -```bash -# You should now be in the adb shell -cd /data/local/tmp -# Unpack the server file -# Note that the included tar command may not support the z option, so you may need to use busybox tar to unpack -# If your system does not come with the busybox command, download a version for the appropriate architecture from https://busybox.net/downloads/binaries/1.20.0 -tar -xzf arm64-v8a.tar.gz -# Remove the installation package -rm arm64-v8a.tar.gz -``` - -## Start the server - -For method 1 installations, the service is started after installation, so you don't need to run the following command after installation** using this method, but it's fine to do it again as described below. -For either of the above installation methods, you will only ever need to do this on the first install, but the process of **starting the service** will need to be done every time the device reboots or after you manually shut down LAMDA, as LAMDA will not run itself. - -Go to the adb shell and switch to `su` root and execute. - -```bash -# You should now be inside the adb shell, switching to the directory -cd /data/local/tmp -# -# Start the server -# Note that the arm64-v8a directory is named differently depending on the platform -# If you are using the x86 version, then the directory name is x86/ and you will need to change the command accordingly -sh arm64-v8a/bin/launch.sh -# -# If you want to enable encrypted transfers -# Use cert.sh/py in tools/ to generate the PEM certificate first -# Push it to the device e.g. /data/local/tmp/lamda.pem -# and set its owner and permissions to root and 600 (chown root:root lamda.pem; chmod 600 lamda.pem) -# and start it with the following command, lamda.pem must be an absolute path -sh arm64-v8a/bin/launch.sh --certificate=/data/local/tmp/lamda.pem -# This will encrypt any traffic generated through the LAMDA client -# but not the webui remote desktop traffic -# This will encrypt any traffic generated by the LAMDA client # but not webui remote desktop traffic. -# Load properties.local from a remote location -# Sometimes you may wish to load the startup configuration from a link, in which case you can upload properties.local to the server -# When LAMDA downloads this configuration, it will provide some information about the current device such as, unique device ID, device model, current version, etc. -# You can also write your own web service to distribute different startup configurations based on these device parameters -# It is recommended to use HTTPS links for added security, please ensure the device time is correct. -# Then start LAMDA as follows -sh arm64-v8a/bin/launch.sh --properties.remote=http://example.com/config/properties.local -# For static file services with Basic Auth enabled, the same username password is supported -sh arm64-v8a/bin/launch.sh --properties.remote=http://user:password@example.com/config/properties.local -# Hint: LAMDA will retry the request if it times out or returns a 50x status code. -# If it still fails 5 times in a row, LAMDA will abandon the attempt and continue to launch. -# You can of course customize the number of retries. -# Of course, it is possible to customise the number of retries but note that if the server continues to be unresponsive, LAMDA will also be stuck here forever -# When to set the retry count: the device may not have a network connection when it is first booted, if you want to start LAMDA at this point you can increase the value -sh arm64-v8a/bin/launch.sh --properties.remote=http://example.com/config/properties.local --properties.tries=30 -# The number of seconds per round n of the retry mechanism will increase as the number of retries increases. So please set this value carefully. -# -# If you need LAMDA to listen to a specific port instead of 65000 -# If you change it, make sure that all intranet devices are started on the same port -# Otherwise functions such as device discovery will not work properly -sh arm64-v8a/bin/launch.sh --port=8123 -# Do not bind to a port below 1024 -``` -Wait for the exit, then close the terminal, and the service start is complete. - -**Note**: There is a chance that the remote desktop will keep loading when starting up for the first time. When this happens, please first try to reboot the device and restart lamda. -If you experience a black screen/restart lag on the device several times during startup or use and other similar situations, it is recommended to stop using it. - -## Shutdown server - -LAMDA is designed to run in the background 24/7 and it is not recommended to start and shut down frequently, if you do need to do so please make sure you shut down in one of the two ways below. To shut down the service using the interface please refer to the `Shutdown and Restart` section below, or you can use the following command as it may not be convenient to use the interface. - -```bash -kill -SIGUSR2 $(cat /data/usr/lamda.pid) -``` - -It may take up to ten seconds for the LAMDA service to exit completely, please do not execute this command more than once in a row. - -## Uninstalling server - -LAMDA is very disciplined about its data planning and will never place random files on your system. -You can completely uninstall lamda with a few commands, before doing so, please follow the ``Shutdown LAMDA service`` above and wait at least 30 seconds to ensure that the service exits properly. - -```bash -# Delete LAMDA related directories -rm -rf /data/local/tmp/arm64-v8a /data/usr -# Reboot the device -reboot -``` - -# Using LAMDA - -Port `65000` on the device is the standard public port for this service and may need to be remembered, but in most cases you will not need to explicitly provide this port number. -You can also use the tools/ directory to list all devices and IPs on your network. -The following will always **assume** that the IP of the device is `192.168.0.2`. - -## Remote Desktop - -The remote desktop feature is designed for Chrome 95+ only, it does not support multiple access and is not guaranteed to be compatible with all browsers. - -Open the link `http://192.168.0.2:65000` in your browser to access the web remote desktop, where you can operate the device and execute commands through the root emulation terminal of the interface. If you specify the PEM certificate `--certificate` when starting the server, the remote desktop will require you to enter a password in order to continue accessing it, which you can find in the last line of the PEM certificate as a fixed 32-bit password. - -You can also customise the video frame rate (fps), resolution scaling (res) and image quality of the Remote Desktop. Also, H.264 soft encoding is supported (in some cases less traffic and smoother, only with the latest version of Chrome). You can adjust this via the small gear in the top right corner of Remote Desktop, but please note that adjusting the above parameters will not necessarily have a positive effect, so please adjust according to the facts. - -For a more user-friendly experience such as keyboard input, see the following section `Wirelessly connecting to a WIFI ADB with built-in root privileges`. -After completing adb connect to LAMDA, install [Genymobile/scrcpy](https://github.com/Genymobile/scrcpy) or [barry-ran/QtScrcpy](https://github.com/barry-ran/QtScrcpy), see their documentation for details. - -## File Uploads - -You can upload files/folders to the device by directly **dragging files or directories to the terminal** on the right from this page, multiple files or folders are supported at the same time, individual files must not exceed a maximum of 256MB, only up to 2k files can be uploaded at the same time, any files uploaded have permission 644, files will always be uploaded to the `/data/local/tmp` directory. - -## File downloads - -LAMDA allows you to browse directories on your device and download files via your browser, simply open the link `http://192.168.0.2:65000/fs/` in your browser (note the `/` at the end). - -## Mobile Proxy - -There may be times when your app may experience errors under certain network conditions, or if you want to do some testing on the same network IP as your device. -LAMDA's tunnel2 feature, which allows you to use the device running LAMDA as an http network proxy, also inherits the power of LAMDA: you can be on nearly the same network as the device from anywhere. You can get a quick taste of this by using the curl command below, or you can set port `192.168.0.2` to `65000` as a proxy in Firefox Settings - Configure Proxy Manually and check `Use this proxy for HTTPS too` so that your Firefox will have the same outgoing IP as the device. - -```bash -# The default agent does not require any authentication, but when you start it with --certificate -# Then the login user name is lamda and the password is the same as the remote desktop login token -curl -x http://192.168.0.2:65000 https://httpbin.org/ip -``` - -### Custom Proxy Configuration - -If you want to use a mobile network (4G/5G) as a proxy outlet. - -```ini -# Append to properties.local configuration file -tunnel2.login=lamda -tunnel2.password=mypassword -# -# There are two configurable values for iface, i.e. wlan, rmnet, when the iface value is wlan -# will automatically detect the available wlan interfaces and select any one to make the request, when iface is rmnet -# will try to enable mobile data (even if WIFI is enabled) and send the request from the mobile network interface. -# When configured as rmnet/wlan but its interface has no network, the proxy will fail. -# When not configured, requests are made using the default network. -#tunnel2.iface=rmnet -``` - -If you want to be able to use the device as a proxy from any location, see the `Making LAMDA connectable from any location` section. - -## Connecting devices - -> Now, this will be accompanied by an introduction to the lamda library, before you start, make sure you have installed the client library correctly according to the `Client installation` section above. - -It is recommended to go through the source code of the client in passing, not so much to understand it, but just so you can get an idea of what parameters are actually available. - -```python -from lamda.client import * - -d = Device("192.168.0.2") -# If certificate is enabled on the server side please connect like this -d = Device("192.168.0.2", certificate="/path/to/lamda.pem") -``` - -Or, if you are familiar with uiautomator2, you can also use some of the automation functions via u2 - -```python -import uiautomator2 as u2 - -# Note: Only about 70% of the major interfaces are compatible and the certificate option is not enabled on the server side -d = u2.connect("http://192.168.0.2:65000") -``` - -Or, just execute the command -```bash -# Note that the DEVICE parameter is an IP, replace it with your own -python3 -m lamda.client -device 192.168.0.2 -# You can then type the following directly into this shell -``` - -> `d` in the following will always be assumed to be the `d = Device("192.168.0.2")` instance. - -## Let's start with a warm up - -The following method will cause your phone to beep once, when there are a bunch of devices and you need to locate one of them, you can call this interface. (Requires the phone to be non-silent) - -```python -d.beep() -``` - -## Setting up a system proxy - -Only http and socks5 proxies are supported, IPv6 is not supported - - -> Assuming you get a proxy from your service provider at http://123.123.123.123:8080 -> Just a few lines of code to allow tcp traffic on the device to pass through this proxy - -```python -profile = GproxyProfile() -profile.type = GproxyType.HTTP_CONNECT - -# This option is optional if you need it or not -profile.drop_udp = True -profile.host = "123.123.123.123" -profile.port = 8080 - -d.start_gproxy(profile) -``` - -> Detailed parameter configuration information - -```python -profile = GproxyProfile() -# socks5 proxy is GproxyType.SOCKS5 -profile.type = GproxyType.HTTP_CONNECT -# If you need to redirect DNS queries to 114.114.114.114 -# Note that this DNS is system global and all DNS issued by the system will be forwarded -# If you are co-locating with OpenVPN, do not set it to be the intranet DNS server for OpenVPN, as this may cause a complete disconnection -# Remove the nameserver configuration line and the system default DNS will be used -# -# Why this option is there: you can modify the dns domain resolution for some applications -profile.nameserver = "114.114.114.114" -profile.host = "Proxy server address" -profile.port = proxy server port - -# If this proxy server requires login information (note: if it does not, comment out or delete the following two lines) -profile.password = "proxy server login password" -profile.login = "Proxy login user" - -# socks5 mode supports udp proxies, but http proxies are not supported -# Since udp will not be proxied in most cases, it is a good idea to disable udp traffic -# Application/system UDP traffic will be blocked when drop_udp is True, default is False -profile.drop_udp = False - -# Whether local traffic needs to *not* go through a proxy, if True, local traffic -# If True, local traffic such as 192.168.x.x 10.x.x.x etc. on the router's internal network will not go through the proxy, default is False -# Note: If udp_proxy is enabled, this option will not work for UDP traffic -profile.bypass_local_subnet = True - -# Whether proxy udp traffic is required -# Note that the http proxy service does not support the proxy udp protocol, to enable this option you must use socks5 as a proxy server -# (GproxyType.SOCKS5), and the socks5 proxy server must be configured to enable udp proxy mode. -# A slightly more complex server configuration is required, to avoid pitfalls please refer to tools/socks5 for installation and configuration -# This setting will be ignored if you are using an http proxy or if the drop_udp option is True -profile.udp_proxy = False - -# If you need to use a proxy only for a specific application (e.g. Android browser, remove these two lines if global) -app = d.application("com.android.browser") -profile.application.set(app) - -# Cautions and tips. -# Running applications will not use the set proxy immediately after it is set -# because the tcp connection was established before the proxy was set -# So, you need to manually close the application and start it before it will establish a connection via the proxy -# That is, if you are doing man-in-the-middle traffic analysis, then after setting up the proxy -# You'll need to close the app and reopen it before you see the app's requests -# -# Note: Local DNS traffic will always not go through the proxy - -# Start the proxy -d.start_gproxy(profile) -# Shut down the proxy -d.stop_gproxy() -``` - -> Quickly setup a socks5 proxy - -LAMDA provides an out-of-the-box socks5 proxy service docker with udp support in tools/, go to tools/socks5 for README.en.md. - -> Automatically connect to the proxy when LAMDA starts - -Copy the following configuration and change the relevant configuration to your proxy information - -```txt -gproxy.enable=true -gproxy.type=http-connect -gproxy.host=123.333.333.333 -gproxy.port=8080 -gproxy.password= -gproxy.login= -``` - -Append or write this to properties.local and restart LAMDA. - -## Installing the man-in-the-middle certificate - -> It is recommended that you use or refer to the startmitm.py, globalmitm tool in the tools/ directory, which is already wrapped, and the interface described here. - -Please make sure you have the certificates from fiddler, mitmproxy and for mitmproxy -For mitmproxy, the certificate given to you is in pem format as in the example below. For fiddler, it may be in crt format, so just provide the file path -as a parameter and you don't need to care about any conversion/filename issues. - -To avoid wasting unnecessary time, we recommend using `mitmproxy` here. -If you are using `Charles` etc, I cannot guarantee that you will be able to set it up in one go. -As these applications are more complex to configure and you may need to understand the various proxy types in order to configure the SSL broker correctly. -If you must use it, it is recommended that you use Charles' socks5 as a proxy protocol. - -Note: Android may not be supported 13 - -```python -import os - -# Splice the path to the mitmproxy-ca-cert.pem file -HOME = os.path.expanduser("~") -cert_path = os.path.join(HOME, ".mitmproxy", "mitmproxy-ca-cert.pem") -# Using mitmproxy as an example, install the certificate using the following code -d.install_ca_certificate(cert_path) - -# Uninstall the certificate using the following code (it is not recommended to install and uninstall frequently if it does not change often) -d.uninstall_ca_certificate(cert_path) -# This certificate installation interface is generic, you can use it to install any certificate that your application requires you to install -# You can also use it to install certificates that Fiddler/Charles asks you to install -# Just provide the file path -``` - -Next, look at the ``Set up http/socks5 proxy`` section and just set the proxy to the address that the middleman application is listening to. -If no traffic is intercepted after following the process please attend the **Special Note** in the section on setting up proxies. - -## Setting up OpenVPN - -> This OpenVPN only supports login with credentials and can coexist with http/socks5 proxies. -> Note that this feature only contains the main functionality of OpenVPN, other than the `DNS` configuration, no other configuration information pushed by the server can be applied at this time. -> These include, but are not limited to, PAC proxy, http proxy configuration, etc. To save you the trouble of installing the service, the -> LAMDA provides an out-of-the-box OpenVPN docker image which has scripts to generate the configuration below, please read on. - -It is highly recommended to use the OpenVPN docker installation in tools and generate the following connection configuration. - -```python -profile = OpenVPNProfile() - -# Whether to have a global VPN or not, False only routes server-side pushes to specific segments -profile.all_traffic = True -# Server-side open connection protocol (or OpenVPNProto.TCP) -profile.proto = OpenVPNProto.UDP -profile.host = "OpenVPN server address" -profile.port = OpenVPN server port -# Server-side channel encryption method -profile.cipher = OpenVPNCipher.AES_256_GCM - -profile.tls_encryption = OpenVPNEncryption.TLS_CRYPT -profile.tls_key_direction = OpenVPNKeyDirection.KEY_DIRECTION_NONE -profile.tls_key = """ ------BEGIN OpenVPN Static key V1----- -tls key / tls auth ------END OpenVPN Static key V1----- -""" - -profile.ca = """ ------BEGIN CERTIFICATE----- -Server-side configured ca certificate ------END CERTIFICATE----- -""" - -profile.cert = """ ------BEGIN CERTIFICATE----- -Client certificate ------END CERTIFICATE----- -""" - -profile.key = """ ------BEGIN PRIVATE KEY----- -Client private key ------END PRIVATE KEY----- -""" - -# Start OpenVPN -d.start_openvpn(profile) -# Shut down OpenVPN -d.stop_openvpn() -``` - -> Setup an OpenVPN service quickly - -LAMDA provides an out-of-the-box OpenVPN docker in tools/, go to tools/openvpn for README.en.md. - -> Automatically connect to the VPN when LAMDA starts - -You can use the command provided in tools/openvpn to generate the properties.local configuration, please do not write it yourself. - -## Connecting to the built-in FRIDA - -> No need to read this section for non-reverse work - -Before and after starting this framework, **please do not** start any frida-server again on your own, otherwise you risk crashing your system. You only need to use the built-in frida via the following code. - -1. connect via the code - -```python -# when using LAMDA -device = d.frida -device.enumerate_processes() -``` - -is equivalent to - -```python -# Just an example, please try to connect using the above method -manager = frida.get_device_manager() -device = manager.add_remote_device("192.168.0.2:65000") -device.enumerate_processes() -``` - -2. Use from the command line - -For all official frida command line tools you just need to add the parameter `-H 192.168.0.2:65000`. - -```bash -frida -H 192.168.0.2:65000 -f com.android.settings -# If you specify the certificate option when starting the server, please note that you also need to add the --certificate parameter here e.g. -frida -H 192.168.0.2:65000 -f com.android.settings --certificate /path/to/lamda.pem -``` - -For objection and r0capture etc, these third party tools may not follow the command line usage of the native frida tools exactly, if you need to use these third party tools you need to make sure that LAMDA is started **without** the `-certificate` parameter (encrypted transfer), as these tools may not have a parameter to pass the parameter for PEM certificates. - -```bash -# objection example connection method (-N -h 192.168.0.2 -p 65000) -objection -N -h 192.168.0.2 -p 65000 -g com.android.settings explore -``` - -```bash -# r0capture Example connection method (-H 192.168.0.2:65000) -python3 r0capture.py -H 192.168.0.2:65000 -f com.some.package -``` - -```bash -# jnitrace example connection method (-R 192.168.0.2:65000) -jnitrace -R 192.168.0.2:65000 -l libc.so com.some.package -``` - -```bash -# frida-dexdump sample connection method (-H 192.168.0.2:65000) -frida-dexdump -H 192.168.0.2:65000 -p PID -``` - -For other third party tools not mentioned please check their usage yourself. - -## Exposing Java interfaces using FRIDA - -This feature requires you to be proficient in writing frida scripts. - -> Please go to the tools directory to see how to use it. - -This feature requires you to be able to write frida scripts. Please refer to the test-fridarpc.js file for the script used in the example, and note in particular that the parameters and return values of the functions defined by rpc.exports in the frida script can only be int/float/string/list/jsdict or any js **serialisable** by JSON. Assume that the device IP is 192.168.0.2. - -> Execute the following command to inject RPC into com.android.settings (watch for errors), the relevant files are in the tools directory - -```bash -python3 fridarpc.py -f test-fridarpc.js -a com.android.settings -d 192.168.0.2 -``` - -Now that the interface is exposed, you can simply request `http://192.168.0.2:65000/fridarpc/myRpcName/getMyString?args=["A", "B"]` to get the results of the methods within the script, the link can also be opened with a browser, the interface supports both POST and GET, the argument list can also use multiple arguments at the same time, an empty list means no arguments, note that the maximum **serialised string in the args argument here cannot exceed ** `32KB`. - -The two string parameters `A`, `B` in the link are the positional parameters of the method `getMyString(paramA, paramB)` in the injected script. - -Note that the parameters are provided in **double quotes**, please **not hand typed** or **string spliced** this parameter, always use json.dumps(["A", "B"]) - -> call with requests - -```python -import json -import requests -url = "http://192.168.0.2:65000/fridarpc/myRpcName/getMyString" -# Request api -res = requests.post(url, data={"args": json.dumps(["A","B"])}) -print (res.status_code, res.json()["result"]) - -#* Status code 200 Everything is fine -#* Status code 410 Script needs to be re-injected or script not injected (automatic re-injection is not currently supported) -#* Status code 500 Script or parameter exception -#* Status code 400 Parameter error -``` - -The format of the response result is fixed and can be opened in a browser for viewing. Again, in conjunction with the following section, you will be able to use the interface directly on the public network. - -## Using the built-in timed tasks - -There is a built-in cron service for executing timed tasks so that you can run scripts on the device at regular intervals and all rules will be executed as root. - -> This feature requires you to be able to write crontab rules, so if you are not familiar with crontab, please find out for yourself first. - -Now, open the web console or a ssh/adb shell connected to your device and execute the command `crontab -e`, you will enter edit mode, press `i` in the English input mode, then write the rule and press `ESC`, `SHIFT` + `:`, type `wq` and press enter to save it. Due to the Android hibernation mechanism, timed tasks may not run at the time you expect after the screen is rested and you may need to set the device to always on. - -> Some example rules - -``` -@reboot echo Executed when the framework is started -0 */1 * * * echo Execute every hour -* * * * * echo Execute every minute -0 8 * * * echo Execute at eight o'clock every day -``` - -## Make LAMDA connectable from any location - -Sometimes you may be in a situation where your phone is at home and you are not how to use it. -Before you start, you may need to prepare a public server. For security reasons, the most conservative configuration is used here, and it will be explained at the end how to do the functions described in full. - -Because of the public server, lamda has a number of ways to do this, using **OpenVPN** for a more elegant implementation. The most convenient way is to use the frp. tools folder, which provides docker images of the service and which can be used to generate the following configuration information with a single click, you can go to tools to see how to use it. - -This service uses the more mature port forwarding program [fatedier/frp](https://github.com/fatedier/frp), please explore how to configure the server side in this project. Note: Do not bind the forwarded port to a public address. Make sure that your public server has all unnecessary ports closed. -Here you are given the easiest and safest configuration to start the server directly using the following command. - -> First start frps on your public server by executing the following command (note that you may also need to configure a firewall) - -```bash -frps --token lamda --bind_addr 0.0.0.0 --bind_port 6009 --proxy_bind_addr 127.0.0.1 --allow_ports 10000-15000 -``` - -> then write properties.local - -Copy the following configuration and change the **server address** to your server's public IP - -```txt -fwd.host=server address -fwd.port=6009 -fwd.rport=12345 -fwd.token=lamda -fwd.protocol=tcp -fwd.enable=true -``` - -Append or write to properties.local, restart LAMDA and you're done. - -> How to use lamda with the above forwarding (needs to be used on the same public server where frps is deployed, as we have bound the forwarding port to 127.0.0.1) - -```python -from lamda.client import * -# Port is rport as above -d = Device("127.0.0.1", port=12345) -# The remote desktop can be accessed by opening a browser at http://127.0.0.1:12345 -# Any remaining interface calls are implemented uniformly, no changes are needed -``` - -> How to bulk forward without rewriting the rport configuration each time - -If you need to set up multiple machines at once and don't care which port each machine is bound to -you can change the `fwd.rport` value in the above configuration to 0, which will cause your devices to be bound to a random port range of `10000-15000`. -You can locate the corresponding port for device forwarding bindings by rotating the port range later. - -> I just want to be able to connect to the device from anywhere - -Firstly, this is not recommended for security reasons, but if you do need to use this, it is recommended that you use OpenVPN to access the device on the same network segment as your computer. -If you are still prepared to use the frp method above for arbitrary access, first ensure that the LAMDA service is started with **PEM certificates** and change the `--proxy_bind_addr 127.0.0.1` to `--proxy_bind_addr 127.0.0.1` when starting the frps command. -to `--proxy_bind_addr 0.0.0.0`, which will cause port 12345 to bind directly to the public network. If you start lamda without the PEM certificate, anyone will be able to access it, which is **very, very dangerous**. -The second thing to note is that web remote desktop traffic is always http, and if someone is manning the middleman between you and the server communication, your login credentials could be stolen. Of course, this is not a problem if the web desktop is not used in the meantime. - - -## Read and write system properties - -> set/read system properties - -```python -## Get the value of ro.secure -d.getprop("ro.secure") - -# Set the value of ro.secure -d.setprop("ro.secure", "0") -``` - -## Read and write system settings - -> Set/read Android system settings - -```python -settings = d.stub("Settings") - -# If you are confused by strings such as screen_brightness below, please see the following documentation. Some constants -# may not be compatible in different versions of Android, as well as some vendors will have custom variables that need to be noted. - -# https://developer.android.com/reference/android/provider/Settings.System -# https://developer.android.com/reference/android/provider/Settings.Secure -# https://developer.android.com/reference/android/provider/Settings.Global - -# You can set the brightness of the system screen to manual using the following code -settings.put_system("screen_brightness_mode", "0") - -# Example: Get and modify the screen brightness to 5 (0-255) -settings.get_system("screen_brightness") -settings.put_system("screen_brightness", "5") - -# Example: Turn off developer options -settings.get_global("development_settings_enabled") -settings.put_global("development_settings_enabled", "0") - -# Example -settings.get_secure("screensaver_enabled") -settings.put_secure("screensaver_enabled", "0) -``` - -## Get device running status - -```python -status = d.stub("Status") - -## Get the device boot time -status.get_boot_time() - -# Get the device's disk usage -status.get_disk_usage(mountpoint="/data") - -# Get battery information -status.get_battery_info() -# Get CPU usage -status.get_cpu_info() -# Get overall disk reads and writes -status.get_overall_disk_io_info() -# Get the user data disk reads and writes (userdata) -status.get_userdata_disk_io_info() -# Get the overall network send/receive situation -status.get_overall_net_io_info() -# Get the network traffic of the wlan0 interface -status.get_net_io_info("wlan0") -# Get memory usage -status.get_mem_info() -``` - -## Execute commands on the device - -> Execute shell scripts/commands in the background of the device, in the foreground - -```python -## Execute foreground scripts (scripts with short execution times (within 0-10 seconds)) -cmd = d.execute_script("whoami") -print (cmd.exitstatus) -print (cmd.stdout) -print (cmd.stderr) - -# Execute background scripts (scripts that take a long time to execute) -# For background scripts, due to the possibility of users writing dead-loop scripts with infinite output that fill up memory, etc. -# The result of the execution is not known at the moment -ID = d.execute_background_script("sleep 100; exit 0;") -# Check if the background script has finished -d.is_background_script_finished(ID) -# Force the background script to end -d.kill_background_script(ID) -``` - -## Make system debuggable - -If you need to use dynamic analysis with JEB, IDA, etc., you may need to set this flag to do so, but of course it is built in and you can do this without having to permanently modify `ro.debuggable`. -But remember, you don't always need to call this interface, only use it if you see any articles/tutorials that tell you to modify `ro.debuggable`. - -Note: After a successful call to this interface, the system will automatically reboot and you may still have to wait a while for the framework to resume as it did when you first started it! - -> use with caution, this feature may be unstable and may be removed at any time - -```python -debug = d.stub("Debug") - -r = debug.set_debuggable() -print (r) -``` - -## Start IDA Debugging Service - -> IDA 7.5 server is built in - -```python -debug = d.stub("Debug") - -## Start IDA 32 server (port is customizable) -debug.start_ida(port=22032) -# Check if it is started -debug.is_ida_running() -# Shut down the IDA 32 server -debug.stop_ida() -# If you are debugging a 64-bit application, replace ida in the method name with ida64 -# For example -debug.start_ida64(port=22064) -#debug.start_ida64(port=22064) -# If you need to customize the ida-server environment variable e.g. IDA_LIBC_PATH (also applies to start_ida) -debug.start_ida64(port=22064, IDA_LIBC_PATH="/apex/com.android.runtime/lib64/bionic/libc.so") -# Use start_ida when the target application you are debugging is 32-bit -# otherwise use start_ida64 -# start_ida64 will be invalid when your device system is a 32-bit platform -``` - -## Wireless connection built-in WIFI ADB - -This ADB is not a full-featured adb, it only supports shell, pull, push, forward, reverse and other common functions. -With this feature you will be able to connect to the highest privileged adb **without having to enable developer mode**. - -> Note: jdwp debugging-related features are unique and conflict with the system's built-in features, so this adb **is not** currently supported. - -```python -# LAMDA's built-in adb service is completely independent of the adb service provided by the system itself -# So before you can use it, you need to install your adb public key on the device by calling the following interface manually -# Otherwise, connecting directly will show that you are not authorised (the secret key authorised in the system settings developer mode is not common to the built-in adb) -# -# adb_pubkey.py in the tools directory encapsulates the installation process for the following interface -# You can use this script to allow local connections with a single click, see its README, the following code is for illustrative purposes only -# -# The secret key file is located on your computer at ~/.android or C:\\Users\xxxx\.android and is called adbkey.pub -# If this file does not exist but the file adbkey exists, switch to that directory and execute the command -# adb pubkey adbkey >adbkey.pub to generate adbkey.pub -# adb pubkey -# Then use python code to stitch together the generated adbkey.pub path -import os -keypath = os.path.join("~", ".android", "adbkey.pub") -abs_keypath = os.path.expanduser(keypath) -print (abs_keypath) -# -# Then install this adbkey.pub to LAMDA -d.install_adb_pubkey(abs_keypath) -# This will allow you to connect to the built-in adb -# Connect to the device via the command adb connect 192.168.0.2:65000 -# You can think of it exactly as a WIFI ADB -# -# Or if you need to remove the public key from the LAMDA built-in adb -d.uninstall_adb_pubkey(abs_keypath) -``` - -## File manipulation - -> Upload files to or download files from a device (large files are supported) - -```python -## Download a file locally -d.download_file("/verity_key", "local file to write to") - -# Download file to memory/opened file -from io import BytesIO -fd = BytesIO() -d.download_fd("/verity_key", fd) -print (fd.getvalue()) - -# Note that the file being written to must be opened in w+b mode -fd = open("Local file written to", "wb") -d.download_fd("/verity_key", fd) - -# Upload a file to the device -d.upload_file("local_file_path.txt", "/data/local/tmp/upload_file_to_device.txt") - -# Upload file from memory/opened file -from io import BytesIO -d.upload_fd(BytesIO(b "fileContent"), "/data/local/tmp/uploaded_file_to_device.txt") - -# Note that the file must be opened using rb mode -fd = open("myfile.txt", "rb") -d.upload_fd(fd, "/data/local/tmp/upload_file_to_device.txt") - -# Delete the file on the device -d.delete_file("/data/local/tmp/file.txt") - -# Modify file permissions on the device -d.file_chmod("/data/local/tmp/files.txt", mode=0o777) - -# Get information about the files on the device -d.file_stat("/data/local/tmp/files.txt") -``` - -## Shutdown and restart - -```python -## Shutdown the system (equal to shutting down the computer) -d.shutdown() -# Reboot the system (equal to reboot) -d.reboot() - -# Shut down the LAMDA service running on the device -d.exit() -``` - -## Application Operations - -> List the IDs of all applications installed on the system - -```python -d.enumerate_all_pkg_names() -``` - -> List all running applications on the device - -```python -d.enumerate_running_processes() -``` - -> Get the applications currently in the foreground - -```python -d.current_application() - -# Equivalent to -d.application(d.current_application().applicationId) - -# Get the current foreground activity -d.current_application().activity -``` - -> Launching, getting Activity - -```python -# import FLAG_ACTIVITY* constant definition -from lamda.const import * - -# Get the 5 most recent activities in the system (up to 12) -activities = d.get_last_activities(count=5) -print (activities) - -# You can just replay the last activity (note that not all activities can be replayed) -activity = activities[-1] -print (activity) -d.start_activity(**activity) - -# Assemble activity information manually -# Only boolean, int, short, long, double, float and string types are supported for appending data -d.start_activity(action="***", category="***", component="***", - extras={"boolean": False, "int": 1, "string": "act", "float": 1.123}, - flags=FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_CLEAR_TASK, - data="***", debug=False) - -# Please refer to the documentation for the definition of flags -# https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_BROUGHT_TO_FRONT -# flags and debug parameters are not required, they are just one more possibility - -# Call 10000 Customer Service -d.start_activity(action="android.intent.action.CALL", data="tel:10000") - -# debug parameter stands for: whether to start the activity in debug mode -# If you know about Waiting for debugger, then it might be useful for you -# You can start an application in debug mode like this (your device or app needs to be debuggable) -la = d.application("com.android.settings").query_launch_activity() -d.start_activity(**la, debug=True) - -# e.g. launch the settings APP (which of course is almost equivalent to launching the app directly) -d.start_activity(action="android.intent.action.MAIN", - category="android.intent.category.LAUNCHER", - component="com.android.settings/.Settings") - -# For example, to access the certificate settings -d.start_activity(action="com.android.settings.TRUSTED_CREDENTIALS") -``` - -> Grant/Revoke APP Permissions - -Note that you should set permissions when the APP is not launched, calling it when the APP requests permissions will not have the effect of helping you click allow. - -```python -app = d.application("com.my.app") - -# Import the PERMISSION_READ_PHONE_STATE constant (version > 3.90) -from lamda.const import * - -# Get all permissions for the app -app.permissions() -# Grant READ_PHONE_STATE permissions -app.grant(PERMISSION_READ_PHONE_STATE, mode=GrantType.GRANT_ALLOW) -# Deny the READ_PHONE_STATE permission -app.grant(PERMISSION_READ_PHONE_STATE, mode=GrantType.GRANT_DENY) -# Check if permission has been granted -app.is_permission_granted(PERMISSION_READ_PHONE_STATE) -# Revoke a permission that has been granted -app.revoke(PERMISSION_READ_PHONE_STATE) -``` - -> clear application cache, reset application - -```python -# Delete the application's cached data -app = d.application("com.my.app") -app.delete_cache() -# Reset the application data -app.reset_data() -``` - -> start/stop the app - -``` python -app = d.application("com.my.app") - -# Start the application -app.start() -# Check if the application is running in the foreground -app.is_foreground() -# Close the app -app.stop() -``` - -> Other - -```python -app = d.application("com.my.app") -# Get information about the application -app.info() - -# Check if the application is installed -app.is_installed() -# Uninstall the application -app.uninstall() - -# Query the app's launch Activity (entry activity) -app.query_launch_activity() - -# Enable the application -app.enable() -# Disable the application -app.disable() -``` - -## WIFI operation - -Currently some of the functions of WIFI operation are not implemented due to possible device anomalies, only some of the implemented functions are described - -```python -wifi = d.stub("Wifi") - -# Get wifi bssid,ssid ip and other related information -wifi.status() - -# Get all bssid's in the blacklist -wifi.blacklist_get_all() - -# Add the bssid to the blacklist (it will not be shown in the wifi list) -wifi.blacklist_add("3c:06:aa:8a:55:66") - -# Clear all blacklists -wifi.blacklist_clear() - -# Perform a wifi scan -wifi.scan() - -# Get the results of a neighborhood wifi scan -wifi.scan_results() - -# Get the mac address of the current wifi -wifi.get_mac_addr() - -# Get wifi signal strength, link rate -wifi.signal_poll() -``` - -## Basic UI Operations - -> Get device information - -```python -d.device_info() -``` - -> screen-off/bright screen related - -```python -# Sleep -d.sleep() -# screen on -d.wake_up() -# Whether the screen is lit or not -d.is_screen_on() -# Whether the screen is locked or not -d.is_screen_locked() -``` - -> clipboard - -``` python -d.set_clipboard("Clipboard contents") - -# Get clipboard contents (not supported on Android 10+) -d.get_clipboard() -``` - -> physical keys - -``` python -# This method works with the following 17 keys -# KEY_BACK -# KEY_CAMERA -# KEY_CENTER -# KEY_DELETE -# KEY_DOWN -# KEY_ENTER -# KEY_HOME -# KEY_LEFT -# KEY_MENU -# KEY_POWER -# KEY_RECENT -# KEY_RIGHT -# KEY_SEARCH -# KEY_UP -# KEY_VOLUME_DOWN -# KEY_VOLUME_MUTE -# KEY_VOLUME_UP -d.press_key(Keys.KEY_BACK) - -# Also to allow more keys to be used, this method can be used -d.press_keycode(KeyCodes.KEYCODE_CALL) -# The KEYCODEs that can be used can be found in this document -# https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_0 -``` - -> Screenshot - -``` python -quality = 60 # screenshot quality, defaults to full quality -d.screenshot(quality).save("screenshot.png") -# Screenshot a specific area of the screen -# Definitions of Bound parameters top, left etc. - -# top: count top pixels down from the top of the screen -# bottom: count down from the top of the screen to the bottom pixel -# left: count left pixels from the left side of the screen to the right -# right: count right pixels to the left of the screen - -# top is always smaller than bottom and left is always smaller than right in normal circumstances -bound = Bound(top=50, bottom=80, left=50, right=80) -d.screenshot(quality, bound=bound).save("partial.png") -``` - -> Click on a point on the screen - -```python -d.click(Point(x=100, y=100)) -``` - -> click on point A and drag it to point B - -```python -A = Point(x=100, y=100) -B = Point(x=500, y=500) - -d.drag(A, B) -``` - -> slide from point A to point B - -```python -A = Point(x=100, y=100) -B = Point(x=500, y=500) - -d.swipe(A, B) -``` - -> Slightly more complex multipoint swipe (nine-box unlock) - -```python -p1 = Point(x=100, y=100) -p2 = Point(x=500, y=500) -p3 = Point(x=200, y=200) - -# Swipe from point P1 to point P2 and then to point P3, any point -d.swipe_points(p1, p2, p3) -``` - -> open the notification bar/shortcut settings bar - -```python -d.open_notification() -d.open_quick_settings() -``` - -> get page layout description XML - -```python -d.dump_window_hierarchy().getvalue() -``` - -> wait for the interface layout to stop refreshing - -```python -# The unit is milliseconds, 5*1000 means 5 seconds -d.wait_for_idle(5*1000) -``` - -> get last toast - -```python -d.get_last_toast() -``` - -## Advanced UI Operations - -> Selector - -To view the layout of the interface, first you need to open the web remote desktop of the device. You can then click on the eye icon in the top right hand corner of the remote desktop to access it, you will no longer be able to swipe the left hand side of the screen, you can click on the dotted box on the screen to view information about the corresponding element, you can use some of these properties as parameters for the Selector. -Clicking on the eye icon again will close the layout view. The layout view will not be refreshed as the page changes, it will always be the layout of the screen at the moment you press the shortcut key, if you need to refresh the layout please press the shortcut key `CTRL+R` manually. - -Normally we would only use `resourceId`, `clickable`, `text`, `description` as parameters. -If the element has a normal resourceId, it will be used as Selector in preference, i.e.: `Selector(resourceId="com.android.systemui:id/mobile_signal_single"`. -For no resourceId, its text will be used, i.e.: `Selector(text="click to enter")`, or more vaguely `Selector(textContains="click")` -Description is the same as text, but description is used less often. - -Of course, Selector can take more than one parameter, and you can make other combinations, e.g. `Selector(text="click through", clickable=True)` - -> Note: it is rare to use Selector() directly; in most cases, d() is used. - -All common match parameters. - -``` -textText Exact match -textContains text contains match -textStartsWith textStarts -className class name match -description Description Exact match -descriptionContains descriptionIncludes match -descriptionStartsWith descriptionStartsMatch -clickable can be clicked -longClickable can be long pressed -scrollable scrollable -resourceId Resource ID match -``` - -For the most part, you won't use `Selector` directly, but indirect use is ubiquitous. - -> Element manipulation - -The above has all covered how to coordinate clicks on something as arbitrary as this, now it's on to how to manipulate fixed target elements. First, you need to know how to select the element. - -```python -# Select the element on the interface that contains the text App under test -element = d(textContains="APP under test") -# Of course, you don't have to assign to element this way, you can also just use d(textContains="APP under test") -``` - -OK, now you know how to get the element, but of course, it's not getting it at this point, it just means that you want to manipulate the element in the current interface, so here goes. - -```python -# We now assume that the app under test in the interface is the name of the app under test icon on the phone (the name below the icon). -element = d(textContains="APP under test") -# Whether or not the element exists -element.exists() -# Click on the element, if it does not exist an exception will be thrown -# COR_CENTER means click on the centre of the element, you can look at the COR_CENTER definition for other clickable positions -element.click(corner=Corner.COR_CENTER) - -# Click on the element, no exception will be thrown if it doesn't exist -element.click_exists(corner=Corner.COR_CENTER) - -# Long click on the element, not present throws an exception -element.long_click(corner=Corner.COR_CENTER) - -# Get information about the element -element.info() - -# Get the centroid of the element -element.info().bounds.center() - -# Get the upper left point of the element -element.info().bounds.corner("top-left") - -# Get the height of the element -element.info().bounds.height - -# Get the width of the element -element.info().bounds.width - -# Get the number of elements -element.count() - -# Wait for an element to appear, up to 10 seconds -element.wait_for_exists(10*1000) - -# Wait for elements to disappear, up to 10 seconds -element.wait_until_gone(10*1000) - -# Get a screenshot of the element (not full screen, just the element) -# quality is the quality of the screenshot 1 - 100 -element.screenshot(quality=60) - -# Drag and drop this APP into the shopping folder (modify as appropriate) -element.drag_to(Selector(text="shopping")) - -######### -# Find sibling or sub-level elements -######### -# Sometimes there are duplicate elements or elements with no obvious features that are difficult to locate -# In this case you can narrow the search by looking for sub/sib elements -# Sub-level elements, e.g. a chat login box with an input box that is a sub-level element of the login box -# sibling elements, e.g. a chat input box where the username and password boxes are siblings (in the normal case) -form = d(resourceId="login_form") -form.child(index=1) -# This will fetch the element under login_form with index 0 -form.child(index=1).sibling() -# You can also do this to find the password retrieval button at the same level as login_form -# (you don't need to do this if you can already tell by the string, this is just a demonstration) -form.sibling(textContains="Retrieve password") -# They are an element in their own right, you can do anything with them as an element - - -############################ -# Now the element changes its meaning and becomes a selected input box -############################ - -# Example: Enter the app under test in the search box on the OnePlus search app screen - -# Note that you should not type directly into what appears to be an input box, it may not be entered -# Some input boxes need to be clicked once to get to the real input box, so use the resource ID of the real input box -element = d(resourceId="net.oneplus.launcher:id/search_all_apps") -element.set_text("App under test") - -# Get the content of the input -element.get_text() - -# Clear the text you just entered -element.clear_text_field() - -# Combine with a click search to complete a human-like search operation. - - -# Sliding operations (sliding the list up and down to turn the page) -# Note that these operations do not guarantee precision, and none of the following methods normally require a selector. -# But you can add your own selectors as appropriate - -# Slide up, step is adjustable, the more you do, the slower it will be, better for precision sliding -d().swipe(direction=Direction.DIR_UP, step=32) -# Other swipe directions. -#DIR_UP Swipe up -#DIR_LEFT Swipe left -#DIR_DOWN slide down -#DIR_RIGHT Swipe to the right - -######### -#fling: flinging, the act of a normal person swiping the screen, faster -######### -# FLINGING from top down -d().fling_from_top_to_bottom() -# from bottom to top -d().fling_from_bottom_to_top() -# left to right -d().fling_from_left_to_right() -# right to left -d().fling_from_right_to_left() - -# Other, keep sliding down/up left/right until it reaches the end -# Because it is not always possible to slide to the end or detect a slide to the end -# So the max_swipes parameter is required -d().fling_from_top_to_bottom_to_end(max_swipes=32) -d().fling_from_bottom_to_top_to_end(max_swipes=32) -d().fling_from_left_to_right_to_end(max_swipes=32) -d().fling_from_right_to_left_to_end(max_swipes=32) - -######### -# scroll: more mechanical sliding -######### -step = 60 -max_swipes = 32 -# scroll from top to bottom step step -d().scroll_from_top_to_bottom(step) -# scroll from bottom to top step -d().scroll_from_bottom_to_top(step) -# Scroll from left to right step -d().scroll_from_left_to_right(step) -# slide from right to left step -d().scroll_from_right_to_left(step) - -# Other, keep sliding down/up left/right until you reach the end -# Same as above fling description -d().scroll_from_top_to_bottom_to_end(max_swipes, step) -d().scroll_from_bottom_to_top_to_end(max_swipes, step) -d().scroll_from_left_to_right_to_end(max_swipes, step) -d().scroll_from_right_to_left_to_end(max_swipes, step) -``` - -> Monitor - -Monitors are used to listen for changes to the interface and perform set actions (clicking on elements or keystrokes) when conditions are met, this can have an impact on performance or when manual intervention is required so please use with caution, it is not enabled by default. - -```python -# Start the watcher loop -d.set_watcher_loop_enabled(True) - -# Get if the watcher is started -d.get_watcher_loop_enabled() - -# Remove all watchers applied to the system, it is recommended that this is done before each use to prevent the normal processing process from being affected by the previous task registration -d.remove_all_watchers() - -# Get a list of all applied watchers in the system -d.get_applied_watchers() - -# Remove a watcher completely -d.remove_watcher(name) - -# Apply the watcher to the system (this watcher will take effect when watcher_loop is started) -d.set_watcher_enabled(name, True) -# Disable the application -d.set_watcher_enabled(name, False) - -# Get whether this watcher is enabled or not -d.get_watcher_enabled(name) -``` - -> watch how many times an element appears in the system interface - -```python -# Do some pre-test cleanup, though of course this is not required for every register -# Just to make sure the testing process is not interrupted -d.remove_all_watchers() -d.set_watcher_loop_enabled(True) - -# The number of times the application watcher interface appears to be good -# The second argument is an array that can be given to multiple Selectors to indicate that the conditions are all met before they are logged -# But it is not recommended to have more than three -d.register_none_op_watcher("RecordElementAppearTimes", [Selector(textContains="good")]) -d.set_watcher_enabled("RecordElementAppearTimes", True) - -# ... Do the operations that satisfy the condition - -# Get the number of times the record was appended -d.get_watcher_triggered_count("RecordElementAppearTimes") - -# The number of times to reset the record -d.reset_watcher_triggered_count("RecordElementAppearTimes") - -# Remove -d.remove_watcher("RecordElementAppearTimes") -``` - -> click on an element when a matching element appears in the interface - -```python -# Do some pre-test cleanup, though of course this is not required for every register -# Just to make sure that the testing process is not interrupted -d.remove_all_watchers() -d.set_watcher_loop_enabled(True) - -# Example is to automatically click agree when the user agreement appears after the app is launched -# The second parameter is an array that can be given to multiple Selectors to indicate that the conditions are all met before the click is made -# But it is not recommended to have more than three -d.register_click_target_selector_watcher("ClickAcceptWhenShowAggrement", [Selector(textContains="User Agreement")], - Selector(textContains="Agree", clickable=True)) -d.set_watcher_enabled("ClickAcceptWhenShowAggrement", True) - -# ... Do the action that satisfies the condition - -# Remove -d.remove_watcher("ClickAcceptWhenShowAggrement") -``` - -> Click the physical button when the matching element appears in the interface - -```python -# Do some pre-test cleanup, though of course this is not required for every register -# Just to make sure that the testing process is not interrupted -d.remove_all_watchers() -d.set_watcher_loop_enabled(True) - -# Example is to return to the start screen by pressing the HOME key when the Personal Centre is present in the interface -# The second argument is an array that can be given to multiple Selectors to indicate that the conditions are all met before the click is made -# But it is not recommended to have more than three -d.register_press_key_watcher("PressBackWhenHomePageShows", [Selector(textContains="Personal Centre")], Keys.KEY_HOME) -d.set_watcher_enabled("PressBackWhenHomePageShows", True) - -# ... Do the actions that satisfy the condition - -# Remove -d.remove_watcher("PressBackWhenHomePageShows") -``` - -## Interface Lock - -The basic functionality here allows you to lock the interface for use only by the current Device instance. - -```python -# # Get a lock, this will be automatically released after 60 seconds and other clients will have access to the lock, you can change this time -# However, if you change it too high because the exception script quits, you will almost never be able to connect to the device and you may need to do a reboot -# This acquire_lock interface can be reentered and is equivalent to _refresh_lock when reentered, it is recommended to call it only once -d._acquire_lock(releaseTime=60) -# Refresh the lock and set the lock expiry time to this leaseTime after each call -# Make periodic calls to keep the device locked -d._refresh_lock(leaseTime=60) -# Release the lock, other clients will have access to it -d._release_lock() -``` - -## Using the internal terminal - -The internal terminal is the terminal you connect to via the web remote desktop or ssh/internal adb, which has some commands and Python modules built in. -You can perform operations or run Python code directly in the terminal, or even perform self-control directly from within the terminal. - -> Now, assuming that you have opened the web remote desktop, you should already see a linux terminal on the page. - -Execute the command `cd` to switch to your home directory (by default `/data/usr`), which is your workspace and where you can store your files. The terminal supports command completion but not parameter completion. You can also enter partial commands in the terminal and subsequently fill in the history of commands automatically with the up and down keys. Also, a number of command aliases are provided internally, these and their functions are listed below. -``` -l = command ls -ll = command ls -l -la = command ls -la -py = the command python -.. = switch to parent directory -... = switch to the parent directory of the parent directory -p = switch to the previous directory -t = switch to /data/local/tmp -``` - -> Some other useful commands. - -``` -* python (Python) -* strace (syscall trace) -* ltrace (libcall trace) -* curl (cURL) -* fsmon (file access monitoring) -* stunnel (traffic encryption) -* redir (port forwarding) -* scapy (traffic analysis) -* iperf3 (network performance testing) -* nano (file editor) -* vi (file editor) -* ncdu (find disk file occupancy) -* socat (network tool) -* sqlite3 (reads SQLite databases, supports cipher) -* tcpdump (traffic analysis) -* busybox (command collection) -* MemDumper (https://github.com/kp7742/MemDumper) -* frida (frida-tools) -* frida-ps (frida-tools) -* frida-trace (frida-tools) -* frida-ls-devices (frida-tools) -* frida-discover (frida-tools) -* frida-kill (frida-tools) -* frida-apk (frida-tools) -* frida-create (frida-tools) -* frida-join (frida-tools) -* and other basic linux commands -``` - -> Python also has some built-in three-party libraries, note that additional libraries cannot be installed via the PIP. - -``` -* lamda (itself) -* capstone (disassembly engine) -* keystone_engine (assembly engine) -* unicorn (CPU emulation engine) -* lief (binary program parsing) -* lxml (xml/html parsing) -* redis (redis client) -* tornado (web framework) -* pyOpenSSL (OpenSSL) -* requests (requests) -* scapy (Traffic analysis) -* frida (frida) -* pyaxmlparser (APK parse) -* xmltodict (xml to dict) -* msgpack_python (msgpack) -``` - -How to use these commands or libraries will not be covered here. - -# Tools and tutorials - -A copy of the instructions for use is available in each of these folders. - -## One-click middleman - -Open [tools/README.en.md](tools/README.en.md) to view. - -## International proxies for man-in-the-middle - -Open [tools/README.en.md](tools/README.en.md) for viewing. - -## Installing the ADB public key - -Open [tools/README.en.md](tools/README.en.md) for viewing. - -## OpenVPN service - -Open [tools/README.en.md](tools/README.en.md) for viewing. - -## SOCKS5 service - -Open [tools/README.en.md](tools/README.en.md) for viewing. - -## Port Forwarding Service - -Open [tools/README.en.md](tools/README.en.md) for viewing. - -## Injecting Frida RPC scripts - -Open [tools/README.en.md](tools/README.en.md) to view. - -## Generate encrypted connection certificate - -Open [tools/README.en.md](tools/README.en.md) to view. - -## List intranet devices - -Open [tools/README.en.md](tools/README.en.md) to view. - - -If you still have questions, join the community discussion: [t.me/lamda_dev](https://t.me/lamda_dev) | [gitter.im/lamda-dev](https://gitter.im/lamda-dev/community) \ No newline at end of file diff --git a/README.md b/README.md index 5595799..a7852f8 100644 --- a/README.md +++ b/README.md @@ -1,1900 +1,104 @@ -

-LAMDA -

- -

Android reverse engineering & automation framework

- -LAMDA 是一个用于逆向及自动化的辅助框架,它设计为减少安全分析以及应用测试人员的时间及琐碎问题,以编程化的接口替代大量手动操作,它并不是一个单一功能的框架。为了让你大概了解它的用处:你是否会在手机上安装各类代理、插件或者点来点去的设置来完成你的工作?你是否要在异地操作远在千里之外的手机?你是否有编程控制手机的需求?是否还在某些云手机厂商那里购买昂贵的**IP切换**、**远程ADB调试**、**RPA自动化**甚至连 **logcat 日志**都要付费的服务?如果有,那么对了,只需一个 LAMDA 即可解决这些问题。并且,LAMDA 更注重**分布式**,事实上,你可以在一台公网服务器上管理散布在世界各地各种网络环境中的设备。当然,LAMDA 可以做到的不止于此。 - -* 零依赖,只需 **root** 即可 -* 前身通过超500台设备压力的稳定生产环境考验 -* 通过接口轻松设置根证书,配合 http/socks5 代理实现中间人 -* 通过 frida 暴露内部 Java 接口(类 [virjar/sekiro](https://github.com/virjar/sekiro) 但基于 frida) -* 近乎商业级软件的质量和稳定性,ARM/X86全架构 -* 较高的安全性,支持接口及登录认证 -* 将你的设备变为移动网络代理 -* 部分兼容 uiautomator2 -* 设备状态/资源消耗读取 -* 系统配置/属性读取修改 -* 界面布局检视 -* 无线连接内置 root 权限的 WIFI ADB -* 支持自定义启动配置 -* 支持模拟器及真机、云手机/无头开发板、redroid (仅ARM) -* 支持安卓 6.0 (M, API 23) - 13 (T, API 33) -* 支持 WSA (Windows Subsystem for Android™️) -* 支持 UDP 协议代理(socks5 UDP 模式) -* 支持 OpenVPN 与代理共存 -* 支持 Magisk 开机自启动 -* 封装了大量常用接口,只需要会写 Python -* 完全网络化,脱离 USB 数据线/USB 集线器等实体 -* 大文件上传下载 -* 大大降低门槛以及闲杂琐事上的时间成本 -* 获取/重放系统中最近的 Activity -* 唤起应用的 Activity -* 可使用 ssh 登录设备终端 -* 只要有网即可连接任意地方运行了 LAMDA 的设备 -* 前后台运行 shell 命令,授予撤销应用权限等 -* 内置 http/socks5 代理,可设置系统/指定应用的代理 -* 内置 frida 15.x, IDA 7.5 server 等工具 -* 内置 crontab 定时任务 -* 内置 Python3.9 及部分常用模块 -* 内置 OpenVPN 可实现全局/非全局的 VPN -* WIFI 远程桌面(web) -* WEB 端文件上传下载 -* UI自动化,通过接口实现自动化操作 - -如果觉得以下教程过于复杂看不懂,可以选择观看网友制作的 [视频教程](https://lamda.run/tutorial/video)。 - -## 无视恶意软件对抗 - -MOMO (vvb2060) 是我们认为目前最强的ROOT特征检测软件,如 MOMO 所说:我们的目标是提高用户的知识水平。注意这段描述的功能**并不属于 LAMDA 框架的能力范畴**,它是由 LAMDA 和**未公开**的基建项目共同组成,我们只是以此来引出:你可以相信 LAMDA。 - -![反检测动图演示](image/detection.gif) - -## 一键中间人流量分析 - -支持常规以及国际APP流量分析,DNS流量分析,得益于 [mitmproxy flow hook](https://docs.mitmproxy.org/stable/api/events.html),你可以对任何请求做到最大限度的掌控,mitmproxy 功能足够丰富,你可以通过其 `Export` 选项导出特定请求的 `curl` 命令或者 `HTTPie` 命令,分析重放、拦截修改、功能组合足以替代你用过的任何此类商业/非商业软件。如果你仍不清楚 mitmproxy 是什么以及其具有的能力,请务必先查找相关文档,因为 LAMDA 将会使用 mitmproxy 为你展现应用请求。 - -通过 tools/ 目录下的 `globalmitm`,`startmitm.py` 实现,使用方法请看其同目录 README。 - -![中间人流量分析动图演示](image/mitm.gif) - -## 拖拽上传 - -可直接在远程桌面拖拽上传,支持上传整个目录,最大支持单个 256MB 的文件,文件将始终被上传到 `/data/local/tmp` 目录下。 - -![拖拽上传动图演示](image/upload.gif) - -## 远程桌面连接 - -即使手机不在身边也可以使用浏览器随时操作界面,并且内置了 Python 以及相关 frida 工具,是你的另一个在线 shell。 - -![远程桌面动图演示](image/lamda.gif) - -## 通过代码自动化 - -直接通过代码点点点,可以替代大部分手动操作。 - -![自动化动图演示](image/automation.gif) - -## 界面布局检视 - -可在远程桌面即时检视安卓应用的界面布局用以编写自动化代码,点击远程桌面右上角的眼睛图标即可进入模式, -按下 `CTRL + R` 刷新布局,再次点击眼睛图标退出。 - -![界面布局检视](image/inspect.gif) - -## 设备目录索引 - -你可以在浏览器浏览设备上的文件,同时你也可以点击文件名来下载所需的文件。 - -![目录索引动图演示](image/listing.gif) - -如果你希望继续看下去,请确保:有一台已经 root 且运行内存大于 2GB,可用存储空间大于 1GB 的安卓设备或者安卓模拟器(推荐使用最新版**夜神**,**雷电**模拟器,或者 AVD [Android Studio Virtual Device])。**不完全支持** 网易 Mumu,**不支持**腾讯手游助手、蓝叠以及安卓内虚拟如 VMOS 等),对于真机,推荐运行最接近原生系统的设备如谷歌系、一加、安卓开发板等,或系统仅经过轻度改造的设备。如果你使用的是OPPO/VIVO/华为/小米的设备,经过尝试后无法正常运行,建议改用模拟器。 - -对于**云手机**,支持阿里云/华为云手机,不支持X手指、X多云、X电、X云兔、X子星、X马云及任何其他品牌。 - -
- -# 目录 - -- [目录](#目录) -- [免责声明及条款](#免责声明及条款) -- [前言](#前言) -- [安装 LAMDA](#安装-lamda) - - [注意事项](#注意事项) - - [基础要求](#基础要求) - - [系统设置](#系统设置) - - [网络设置](#网络设置) - - [其他设置](#其他设置) - - [安装客户端](#安装客户端) - - [安装服务端](#安装服务端) - - [通过 Magisk 安装](#通过-magisk-安装) - - [手动安装](#手动安装) - - [方式 1](#方式-1) - - [方式 2](#方式-2) - - [启动服务端](#启动服务端) - - [退出服务端](#退出服务端) - - [卸载服务端](#卸载服务端) -- [使用 LAMDA](#使用-lamda) - - [远程桌面](#远程桌面) - - [文件上传](#文件上传) - - [文件下载](#文件下载) - - [移动代理](#移动代理) - - [自定义代理配置](#自定义代理配置) - - [连接设备](#连接设备) - - [先来一个热身](#先来一个热身) - - [设置系统代理](#设置系统代理) - - [安装中间人证书](#安装中间人证书) - - [设置 OpenVPN](#设置-openvpn) - - [连接内置的 FRIDA](#连接内置的-frida) - - [使用 FRIDA 暴露 Java 接口](#使用-frida-暴露-java-接口) - - [使用内置的定时任务](#使用内置的定时任务) - - [使 LAMDA 可被任意地点连接](#使-lamda-可被任意地点连接) - - [读写系统属性](#读写系统属性) - - [读写系统设置](#读写系统设置) - - [获取设备运行状态](#获取设备运行状态) - - [在设备上执行命令](#在设备上执行命令) - - [使系统可调试](#使系统可调试) - - [启动 IDA 调试服务](#启动-ida-调试服务) - - [无线连接内置的 WIFI ADB](#无线连接内置的-wifi-adb) - - [文件操作](#文件操作) - - [关机重启](#关机重启) - - [应用操作](#应用操作) - - [WIFI操作](#wifi操作) - - [基本UI操作](#基本ui操作) - - [进阶UI操作](#进阶ui操作) - - [接口锁](#接口锁) - - [使用内置终端](#使用内置终端) -- [工具及教程](#工具及教程) - - [一键中间人](#一键中间人) - - [国际代理进行中间人](#国际代理进行中间人) - - [安装 ADB 公钥](#安装-adb-公钥) - - [OpenVPN 服务](#openvpn-服务) - - [SOCKS5 服务](#socks5-服务) - - [端口转发服务](#端口转发服务) - - [注入 Frida RPC 脚本](#注入-frida-rpc-脚本) - - [生成加密连接证书](#生成加密连接证书) - - [列出内网设备](#列出内网设备) - -# 免责声明及条款 - -为了下载使用由 rev1si0n (账号 github.com/rev1si0n)(以下简称“本人”)个人开发的软件 LAMDA ,您应当阅读并遵守《用户使用协议》(以下简称“本协议”)。请您务必审慎阅读、充分理解各条款内容,特别是免除或者限制责任的条款,并选择接受或不接受;除非您已阅读并接受本协议所有条款,否则您将无权下载、安装或使用本软件及相关服务。您的下载、安装、使用、获取账号、登录等行为即视为您已阅读并同意受到上述协议的约束;若您需要获得本服务,您(以下称"用户")应当同意本协议的全部条款并按照页面上的提示完成全部申请使用程序。您可以在本文档的相同目录找到 [DISCLAIMER.TXT](DISCLAIMER.TXT),或者点此 [免责声明](DISCLAIMER.TXT) 查阅。由于并未完全开源,除以上条款外:**授权您对 LAMDA 本身进行以恶意代码分析为目的的逆向**。 - -请确认您已阅读并接受本协议所有条款,否则您将无权下载、安装或使用本软件及相关服务。 - -# 前言 - -LAMDA 是个人开发的免费软件 (freeware),目前仅客户端及协议是开源的,但个人承诺它没有任何对您违规或多余的行为,如果仍有担心,您可以**立即离开**或者选择**付费**寻求心理安慰。互相尊重,使用请遵守使用条款。合作交流可以 [点此发送邮件](mailto:ihaven0emmail@gmail.com)。 - -为什么部分开源?因为 LAMDA 亦黑亦白,很容易被不法分子利用使作者处于危险之中,所以请尊重条款使用。建议在 Linux 或者 Mac 系统上操作文档及样例中的代码。部分功能需要配合 `tools/` 目录下的工具实现,如何使用请参照 [tools/README.md](tools/README.md)。 - -**特别注意**:**请勿在自用设备上运行,当有可能在公网或不信任的网络中使用时,务必确保在启动时指定了PEM证书** - -> 使用后的副作用 - -你可能会把 Python 代码中的 `lambda` 写成 lamda,这是正常现象。 - -> 问题反馈及建议 - -因为安卓被各种设备广泛使用,无法保证百分百的兼容性,可能会有运行异常等各种未知情况,出现的异常情况包括:无故重启,APP经常崩溃,触摸失效或无故乱动等等,冻屏等情况。如果经常遇到,建议停止使用。 -点此 [报告问题/建议](https://github.com/rev1si0n/lamda/issues/new),请详细描述并附上机型系统等信息。 - -社区讨论:[电报 t.me/lamda_dev](https://t.me/lamda_dev) | [gitter.im/lamda-dev](https://gitter.im/lamda-dev/community) - -> 顺便支持作者 - -如果需要购入服务器,可以选择通过下方的**推广链接**购买云服务。 - -阿里云 -腾讯云 - -
- -# 安装 LAMDA - -> 分为客户端以及服务端,客户端主要是 Python 相关库及接口,服务端则是运行在设备/手机上的服务。 - -## 注意事项 - -### 基础要求 - -LAMDA 最理想的运行环境是你刚刚 root(如:新建模拟器,自带权限的ROM,Magisk 刚 root),启动前**务必确保**: - -``` -* 必须关闭 Magisk Hide -* 必须关闭 frida-server -* 建议关闭 Xposed/Magisk 插件 -* 确认完毕重启设备 -``` - -并且不会在启动后启用任何上述任何标记为`必须`的条目。 - -### 系统设置 - -> 检查时区时间 - -打开系统设置,找到日期与时间,检查是否已自动设置**中国标准时间**或者你所在地的时区,检查时间是否正确或在可接受的误差范围内,如果没有请关闭**使用网络提供的时区** 以及**网络时间**,并手动设置时区及时间为你当前所在地的时区及时间。 - -> 关闭无障碍 - -打开系统设置,找到无障碍(系统或更多设置中),关闭或卸载所有使用无障碍的应用(例如 talkback,autojs 等)。 - -### 网络设置 - -对于真机,你只需要确保电脑与手机在同一网络下即可。 -对于模拟器,默认创建的模拟器正常情况下与你的本机网络并不互通,如果你使用的是 android x86 (基于 VMWare 的安卓虚拟机), -请尝试在虚拟机设置中将网络模式设置为桥接模式。对于雷电,夜神等模拟器,需要在其设置中根据提示安装驱动并开启桥接模式随后重启模拟器。 -对于 Android Studio 的 Virtual Device,则没有相关设置,如果需要连接 AVD,请先执行 `adb forward tcp:65000 tcp:65000`, -并使用 `localhost` (不要使用 127.0.0.1)进行连接。 - - -### 其他设置 - -> WSA (Windows Subsystem Android) - -如果使用的是 WSA (Windows Subsystem Android),请确保 WSA 版本不低于 2210.40000 并且已 root。随后打开 WSA 设置 -> Subsystem Resources -> 选择 Continuous,关闭 Advanced Networking。随后重启 WSA 子系统即可。 - -> AVD (Android Studio Virtual Device) - -如果使用的是 AVD (Android Studio Virtual Device),请先使用如下方式扩展默认存储空间的大小。 - -```bash -# Pixel_5_API_29 为虚拟机ID,可以使用命令 emulator -list-avds 列出 -# -partition-size 部分新建的 AVD 可用存储空间可能只有百兆,这里修改为 2G -emulator -avd Pixel_5_API_29 -partition-size 2048 -no-snapshot-load -# 随后每次启动虚拟机时都使用该命令 -# -# 可能会遇到找不到 emulator 命令的情况, -# 请参阅此文档获知此命令的位置 https://developer.android.com/studio/run/emulator-commandline?hl=zh-cn 并将其加入 PATH 变量中 -# -# 如果你无法完成上面的命令,请手动点击 Android Studio 中的 Virtual Device Manager,新建一个虚拟机,随后找到对应虚拟机并点击后方的编辑按钮(一个笔的符号), -# 点击 Show Advanced Settings,找到 Storage -> Internal Storage 并将其设置为至少 2GB。 -``` - -> Redroid (android in docker) - -注意目前**仅支持基于 ARM (aarch64) 宿主机**的 Redroid,你可以通过命令 `uname -m` 来检查。 - -如果使用的是 Redroid (android in docker),以官方建议的 `Ubuntu 20.04` 为例,首先安装 linux-modules-extra 相关模块,(注意下列方法可能不适合其他 Linux 发行版,如果你不熟悉 Linux,我们不太建议你继续下面的操作)。 - -```bash -apt install linux-modules-extra-`uname -r` -``` - -编辑文件 `/etc/modules`,将下列名称复制并插入文件底部,重启当前宿主机。 - -```bash -# redroid modules -mac80211_hwsim - -binder_linux -ashmem_linux -``` - -**或者**每次宿主机重启后执行(注意如果不用上述的方法那么每次重启都要这么做) - -```bash -modprobe mac80211_hwsim -modprobe binder_linux devices="binder,hwbinder,vndbinder" -modprobe ashmem_linux -``` - -最后使用以下命令启动,`redroid_gpu_mode` 请根据实际进行修改(注意这与官方写的命令有所不同)。 - -```bash -docker run -itd --rm --privileged --pull always -v /lib/modules:/lib/modules:ro -v ~/redroid:/data -p 127.0.0.1:5555:5555 -p 127.0.0.1:65001:65000 redroid/redroid:12.0.0-latest androidboot.redroid_gpu_mode=guest -``` - -> 这里将容器 65000 映射到本机 65001 是因为部分工具需要临时绑定到 65000 端口。 - -随后,你可以在宿主机上通过 `http://127.0.0.1:65001` 访问到 LAMDA。 - -## 安装客户端 - -请使用 3.6 - 3.10 版本的 Python,建议有条件使用 Python 3.9 - -```bash -pip3 install -U lamda -# 即可 -# -# 如果需要使用内置 frida,务必使用下列方法安装 -# 你可能需要外网访问来安装 frida,否则可能会卡住许久(~10分钟)直至安装失败 -# 即使之前安装过 frida,也应该重新执行以下命令 -pip3 install -U --force-reinstall 'lamda[full]' -# 请注意完成安装后,你需要同时使用 pip 更新任何依赖 frida -# 的第三方库例如 frida-tools objection 等(如果安装过的话) -# 否则后期使用可能会出现难以察觉的异常 -``` - -安装完成后,执行命令 `python3 -m lamda.client` 检查是否安装正确。如果出现如下类似报错 - -```python -* AttributeError 'NoneType' object has no.. -* TypeError: Couldn't build proto file.. -``` - -可能是因为安装的其它依赖 protobuf 的包产生冲突。请尝试执行如下命令 - -```bash -pip3 install -U --force-reinstall lamda -``` - -如果仍然存在问题,请创建 virtualenv 来使用。 - - -## 安装服务端 - -**默认方式安装的 LAMDA 没有开启任何认证,其他人可以访问你设备上的任意内容,监听你的设备甚至接入你的网络进行进一步控制。请特别留意`启用接口认证`的部分,请务必在可以`信任的网络`内使用。** - -安装前,请先选择合适的架构,可以通过 adb shell 命令 `getprop ro.product.cpu.abi` 来获取当前的系统架构。 -正常情况下,对于现时代的手机,可以直接选择 `arm64-v8a` 版本,而对于模拟器如雷电,你会在新建模拟器时选择32或64位版本的安卓系统, -32位模拟器系统对应 `x86`,64位则对应 `x86_64`,正常情况下,雷电模拟器默认创建的为基于 `x86` 的安卓 7.0 系统。 - -LAMDA 支持设备状态主动上报,你可以编写接口或使用 grafana 来记录设备运行状况,其中包含了系统、网络、内存、CPU、磁盘等等信息。 - -```bash -# 如果不清楚这个功能是什么请不要执行,注意替换掉以下链接 -echo "stat-report.url=http://example.com/report" >>/data/local/tmp/properties.local -``` - -这样 LAMDA 会在启动后**每分钟**向此链接**POST**设备状态信息(JSON),由于字段较多,将不在此罗列。 - -> 配置自动更新 - -LAMDA 存在一个自动更新的逻辑,但是由于存在分钟级的服务中断,目前仅限于内部自用。除了存在紧急安全问题或者致命BUG等情况,LAMDA 不会进行任何自动更新,请自行定期在闲时从 github 下载并安装最新版本。 - -如果你确实不在意更新时分钟级的服务不可用,启动 LAMDA 之前写入以下配置文件可以确保 LAMDA 始终为最新版本。 - -```bash -# 进入 adb shell 执行 -echo "upgrade.channel=latest" >>/data/local/tmp/properties.local -``` - -> properties.local 启动配置 - -在开始前,有必要介绍一下上面的 `properties.local` 文件, -properties.local 为 LAMDA 的启动配置文件,通常存储于设备之上,其中包含了 `a=b` 类型的字符串, -通过编写此文件,你可以实现在 LAMDA 启动时自动连接到 OpenVPN、代理、端口转发等。 -LAMDA 在启动时,会从 `/data/usr`、`/data/local/tmp`、`${CFGDIR:-/data/local}` 查找该文件并载入。 -你可以在以上三个位置任意一个放置你的 properties.local 配置文件。 - -除了 `properties.local`,还有一个从加载远端配置的参数 `--properties.remote`,它可以让 LAMDA 在启动时从HTTP服务器下载配置,请继续看往启动 LAMDA 的章节。 - -关于如何编写配置,在各个功能中会有介绍。 - -> launch.sh 可能出现的错误及解决方法 - -```bash -# 显示 llllaamDaa started 则服务已经正常进入 daemon 模式,可以退出终端 - -already running (已经在运行,请不要多次启动) -invalid TZ area (时区未设置,在系统时间设置中设置时区即可,可能出现于国外或原生系统上) -not run as root (没有以 root 身份运行) -unsupported sdk (在不支持的安卓系统上运行) -abi not match (使用了错误的 tar.gz 包) -``` - -### 通过 Magisk 安装 - -如果你的设备使用了 Magisk,那么你可以以最简单的方法完成安装,并且 LAMDA 可以**开机自启动**。需要确保 Magisk 版本 >= 20.4 且只支持在 **Magisk App** 中安装。同时,使用 Magisk 安装更便于统一化,你可以自定义配置,例如,你想要所有使用该 magisk 模块刷入后的 LAMDA 都启用接口认证(certificate), -或者希望这些设备都在启动时自动连接代理,你只需要编写 properties.local 或者生成PEM证书并重命名为 `lamda.pem`(请查看 tools/ 中的工具使用方法), -随后使用**压缩软件**打开 `lamda-magisk-module.zip`,并将其(`lamda.pem` 或者 `properties.local`)拖入 `common` 文件夹即可实现启动时自动配置! - -现在,从 [lamda/releases](https://github.com/rev1si0n/lamda/releases) 页面下载 `lamda-magisk-module.zip`,并将其 push 到 `/sdcard`,打开 Magisk App,点击 模块->从本地安装,选择 lamda-magisk-module.zip 稍作等待即可。 - -刷入成功后,请重启设备。重启后,LAMDA 应该会在开机时自启动。但是为了避免可能的崩溃问题,lamda 会在 30 秒后启动而不是立即启动,你将有足够的时间去禁用 LAMDA 模块(请在开机后2分钟再连接使用 LAMDA)。安装完成后,你无需再看下段手动安装的内容,跳过即可。 - -### 手动安装 - -手动安装是通常做法,下面将会介绍两种方式,两种方式的区别是:部分老旧设备可能无法通过系统的 `tar` 命令来解压 tar.gz 后缀的文件,所以提供了 `*-install.sh` 用来作为补充,其内置了一个 busybox 用来解压。已知 getprop 获得的设备架构为 `arm64-v8a`,现在将设备连接到当前电脑并确保已授权 ADB、可以正常切换 root。 - -#### 方式 1 - -从 `release` 页面 [lamda/releases](https://github.com/rev1si0n/lamda/releases) -下载 `arm64-v8a.tar.gz-install.sh`。 - -```bash -# /data/local/tmp 是标准服务安装路径,但并不是强制要求 -# 你可以放到除了 /sdcard 之外任何具备可读写权限的文件夹 -adb push arm64-v8a.tar.gz-install.sh /data/local/tmp -# 进入 adb shell -adb shell -# 输入 su 确保为 root 身份 -su -# 切换到目录 -cd /data/local/tmp -# 执行安装脚本并启动(这将解包并启动服务) -sh arm64-v8a.tar.gz-install.sh -# 删除安装包 -rm arm64-v8a.tar.gz-install.sh -``` - -#### 方式 2 - -从 `release` 页面 [lamda/releases](https://github.com/rev1si0n/lamda/releases) -下载 `arm64-v8a.tar.gz`。 - -```bash -# /data/local/tmp 是标准服务安装路径,但并不是强制要求 -# 你可以放到除了 /sdcard 之外任何具备可读写权限的文件夹 -adb push arm64-v8a.tar.gz /data/local/tmp -``` - -完成后,进入 `adb shell`,解包文件: - -```bash -# 你现在应该在 adb shell 内 -cd /data/local/tmp -# 解包服务端文件 -# 注意自带的 tar 命令可能因为不支持 z 选项导致解包失败,你可能需要使用 busybox tar 来解包 -# 如果系统不附带 busybox 命令,请自行从 https://busybox.net/downloads/binaries/1.20.0 下载合适架构的版本 -tar -xzf arm64-v8a.tar.gz -# 删除安装包 -rm arm64-v8a.tar.gz -``` - -## 启动服务端 - -对于方式 1 安装,安装后会顺带启动服务,所以使用该方法**安装后**你无需执行下面的命令,但是按照下面的操作再来一次也并没有问题。 -对于上面任意一种安装方法,你永远只需要在首次安装时操作,但是**启动服务**的过程则需要在每次 设备重启 或者 你手动关闭 LAMDA 后执行,因为 LAMDA 不会自己运行。 - -进入 adb shell,并切换为 `su` root 身份,执行: - -```bash -# 你现在应该在 adb shell 内,切换到目录 -cd /data/local/tmp -# -# 启动服务端 -# 注意,arm64-v8a 这个目录根据平台不同名称也不同 -# 如果你使用的是 x86 版本,那么这个目录名则是 x86/,你需要对命令做相应修改 -sh arm64-v8a/bin/launch.sh -# -# 如果你想要启用加密传输 -# 请先使用 tools/ 中的 cert.sh/py 来生成PEM证书 -# 将其push到设备例如 /data/local/tmp/lamda.pem -# 并将其属主及权限设置为 root 以及 600 (chown root:root lamda.pem; chmod 600 lamda.pem) -# 并使用以下命令启动,lamda.pem 必须为绝对路径 -sh arm64-v8a/bin/launch.sh --certificate=/data/local/tmp/lamda.pem -# 这将加密任何通过 LAMDA 客户端产生的通信流量 -# 但不包括 webui 远程桌面的流量 -# -# 从远端加载 properties.local -# 有时候你可能希望从链接加载启动配置,这时你可以将 properties.local 上传到服务器 -# LAMDA 在下载此配置时,会提供当前设备的部分信息如,设备唯一ID,设备型号,当前版本等。 -# 你也可以自行编写web服务来根据这些设备参数分发不同的启动配置 -# 建议使用 HTTPS 链接增加安全性,请确保设备时间正确。 -# 随后使用如下方式启动 LAMDA -sh arm64-v8a/bin/launch.sh --properties.remote=http://example.com/config/properties.local -# 对于开启了 Basic Auth 的静态文件服务,同样支持提供用户名密码 -sh arm64-v8a/bin/launch.sh --properties.remote=http://user:password@example.com/config/properties.local -# 提示:LAMDA 会在超时或者返回 50x 状态码时重试请求, -# 如果连续 5 次仍然失败,LAMDA 会放弃尝试并继续启动。 -# -# 当然,可以自定义重试次数但是注意,如果服务器持续无响应,LAMDA 也将永远卡在这里 -# 什么时候需要设置重试次数:刚开机时设备可能并没有网络连接,如果你要在这时启动 LAMDA 你可以增大该值 -sh arm64-v8a/bin/launch.sh --properties.remote=http://example.com/config/properties.local --properties.tries=30 -# 重试机制的每轮等待秒数n会随着重试次数的增加而增加。所以请谨慎设置该值。 -# -# 如果你需要 LAMDA 监听到特定端口而不是 65000 -# 如果修改,请确保所有内网设备均以相同端口启动 -# 否则设备发现等功能无法正常工作 -sh arm64-v8a/bin/launch.sh --port=8123 -# 请不要绑定 1024 以下的端口 -``` - -静待退出,随即关闭终端,至此服务启动完成。 - -**注意**:首次启动时有几率出现远程桌面一直加载。发生这种情况时,请首先尝试重启设备并重新启动 lamda。 -如果在启动或使用中多次遇到设备黑屏/重启卡顿等类似情况,建议停止使用。 - -## 退出服务端 - -LAMDA 设计为一个 7*24 小时后台运行的服务,不建议频繁启动关闭,如果确需这样做,请务必确保你通过以下两种方式关闭。如需使用接口关闭服务请参照下方 `关机重启` 章节,考虑到可能不方便使用接口,你也可以使用以下命令。 - -```bash -kill -SIGUSR2 $(cat /data/usr/lamda.pid) -``` - -LAMDA 服务完全退出可能需要十几秒的时间,请不要连续多次执行此命令。 - -## 卸载服务端 - -LAMDA 对于自身数据的规划非常规范,绝对不会在你的系统中随意放置文件。 -你可以通过几条命令完全卸载 lamda,在进行前,请先按照上方 `关闭 LAMDA 服务` 执行并等待至少30秒以确保服务正常退出。 - -```bash -# 删除 LAMDA 相关目录 -rm -rf /data/local/tmp/arm64-v8a /data/usr -# 重启设备 -reboot -``` - -# 使用 LAMDA - -设备上的 `65000` 端口为本服务的标准公用端口,可能需要记住,但是大部分情况下,你不需要显式提供此端口号。 -下面请先在 WLAN 设置中取得**当前设备的IP地址**,你也可以通过 tools/ 目录里的工具来列出当前网络中的所有设备及IP, -下面将会一直**假设**设备的IP为 `192.168.0.2`。 - -## 远程桌面 - -远程桌面功能仅为 Chrome 95+ 设计,不支持多人访问,不保证兼容所有浏览器,如遇功能不正常请使用 Chrome。 - -在浏览器中打开链接 `http://192.168.0.2:65000` 可进入 web 远程桌面,你可以在此操作设备以及通过该界面的root模拟终端执行命令。如果启动服务端时指定了PEM证书 `--certificate`,远程桌面将需要你输入密码才能继续访问,你可以在PEM证书最后一行找到这个32位的固定密码。 - -你也可以自定义远程桌面的 视频帧率(fps)、分辨率缩放比例(res)以及图像质量(quality)。同时,支持 H.264 软编码(部分情况下使用流量更少更流畅,仅支持最新版 Chrome 浏览器)。你可以通过远程桌面右上角的小齿轮进行调整,但是请注意,调整以上参数并不一定会产生正向效果,请依据事实调整。 - -如果需要键盘输入等更加人性化的操作体验,请先看下面的章节 `无线连接内置 root 权限的 WIFI ADB`, -完成 adb connect 到 LAMDA 后,安装使用 [Genymobile/scrcpy](https://github.com/Genymobile/scrcpy) 或者 [barry-ran/QtScrcpy](https://github.com/barry-ran/QtScrcpy) 即可,具体使用方法请查看其使用文档。 - -## 文件上传 - -你可以在此页面直接**拖动文件或目录到右侧终端**上来上传文件/文件夹到设备,支持同时拖动多个文件或文件夹,单个文件最大不得超过 256MB,最多只支持同时上传 2k 个文件,上传的任何文件权限均为 644,文件将始终上传到 `/data/local/tmp` 目录下。 - -## 文件下载 - -LAMDA 允许你通过浏览器浏览设备上的目录及下载文件,只需要在浏览器打开链接 `http://192.168.0.2:65000/fs/` 即可(注意最后面的 `/`)。 - -## 移动代理 - -有些时候,你的APP可能在某些网络条件下会发生错误,或者你想处在设备相同的网络IP下做一些测试。 -LAMDA 的 tunnel2 功能,支持你将运行 LAMDA 的设备作为 http 网络代理服务器,它同样继承了 LAMDA 的强大功能:你在任何地方都能处在近乎与设备相同的网络之中。你可以通过以下 curl 命令快速体验,你也可以在 Firefox 设置-手动配置代理将 `192.168.0.2` 端口 `65000` 设置为代理并且勾选 `也将此代理用于 HTTPS`,这样你的 Firefox 将会与设备有着相同的出网IP。 - -```bash -# 默认代理无需任何认证,但是当你使用了 --certificate 启动时 -# 那么登录用户名为: lamda,密码与远程桌面登录令牌 (token) 相同 -curl -x http://192.168.0.2:65000 https://httpbin.org/ip -``` - -### 自定义代理配置 - -如果你想使用移动网络(4G/5G)作为代理出口。 - -```ini -# 追加到 properties.local 配置文件 -tunnel2.login=lamda -tunnel2.password=mypassword -# -# iface 存在两个可配置值,即 wlan、rmnet,当 iface 值为 wlan 时, -# 将自动检测可用 wlan 接口并选择任意一个发出请求,当 iface 为 rmnet 时 -# 将尝试启用移动数据(即使 WIFI 已开启),并将请求从移动网络接口发出。 -# 当配置为 rmnet/wlan 但其接口无网络时,代理将失效。 -# 当未配置时,使用默认网络发出请求。 -#tunnel2.iface=rmnet -``` - -如果希望从任何地点都可使用设备作为代理,请查看 `使 LAMDA 可被任意地点连接` 章节。 - -## 连接设备 - -> 现在,将配合 lamda 库进行介绍,在开始前,请先确保你已经根据上文 `客户端安装` 章节正确安装了客户端库。 - -建议顺带翻看客户端的源码,并不是需要理解,仅仅是让你能了解到底有什么参数可以使用。 - -```python -from lamda.client import * - -d = Device("192.168.0.2") -# 如果在服务端启用了 certificate 请这样连接 -d = Device("192.168.0.2", certificate="/path/to/lamda.pem") -``` - -或者,如果你熟悉 uiautomator2,也可以通过 u2 来使用部分自动化功能 - -```python -import uiautomator2 as u2 - -# 注意:只兼容约7成主要接口,并且服务端未启用 certificate 选项 -d = u2.connect("http://192.168.0.2:65000") -``` - -或者,直接执行命令 -```bash -# 注意这个DEVICE参数是IP,自行替换 -python3 -m lamda.client -device 192.168.0.2 -# 随后可以直接在此 shell 中输入下方语句 -``` - -> 下文中的 `d` 将始终假设为 `d = Device("192.168.0.2")` 实例。 - -## 先来一个热身 - -如下方法可以使你的手机发出一声蜂鸣,当有一堆设备的时候,需要定位其中一台,可以调用此接口。(需要手机为非静音状态) - -```python -d.beep() -``` - -## 设置系统代理 - -只支持 http 以及 socks5 代理,不支持 IPv6 - - -> 假设你从服务商处获得的代理为 http://123.123.123.123:8080, -> 仅需如下几行代码来让设备上的 tcp 流量通过此代理 - -```python -profile = GproxyProfile() -profile.type = GproxyType.HTTP_CONNECT - -# 此选项请根据实际情况选择你是否需要 -profile.drop_udp = True -profile.host = "123.123.123.123" -profile.port = 8080 - -d.start_gproxy(profile) -``` - -> 详细的参数配置信息 - -```python -profile = GproxyProfile() -# socks5 代理则为 GproxyType.SOCKS5 -profile.type = GproxyType.HTTP_CONNECT -# 如果你需要重定向 DNS 查询到 114.114.114.114 -# 注意此 DNS 是系统全局的,系统发出的所有DNS将会被转发 -# 如果是与OpenVPN共存的情况,不要设置为OpenVPN的内网DNS服务器,否则可能会导致彻底断网 -# 去掉 nameserver 配置行将使用系统默认 DNS -# -# 为什么有此选项:你可以修改一些应用的 dns 域名解析 -profile.nameserver = "114.114.114.114" -profile.host = "代理服务器地址" -profile.port = 代理服务器端口 - -# 如果这个代理服务器需要登录信息(注意:如果没有,请注释或删除如下两行) -profile.password = "代理服务器登录密码" -profile.login = "代理服务器登录用户" - -# socks5 模式支持 udp 代理,但是 http 代理并不支持 -# 因为 udp 多数情况下并不会被代理,所以禁用 udp 流量是一个不错的选择 -# 当 drop_udp 为 True 时,应用/系统的 UDP 流量将会被屏蔽,默认为 False -profile.drop_udp = False - -# 本地流量是否需要*不经过*代理,如果为 True,本地流量 -# 如 192.168.x.x 10.x.x.x 等路由器内网网段的流量将不会经过代理,默认为 False -# 注意:如果开启了 udp_proxy,此选项对于 UDP 流量无效 -profile.bypass_local_subnet = True - -# 是否需要代理 udp 流量 -# 注意,http 代理服务不支持代理 udp 协议,开启此选项必须使用 socks5 作为代理服务器 -# (GproxyType.SOCKS5),且 socks5 代理服务器必须配置开启 udp 代理模式, -# 需要稍加复杂的服务器配置,为了避坑,请参照 tools/socks5 里的介绍进行安装配置 -# 当你使用 http 代理或者 drop_udp 选项为 True,此设置将会被忽略 -profile.udp_proxy = False - -# 如果需要仅对特定应用使用代理(例如安卓浏览器,如果是全局则删除这两行) -app = d.application("com.android.browser") -profile.application.set(app) - -# 注意事项以及提示: -# 设置代理后,正在运行的应用是不会立即使用设置的代理的 -# 因为这些应用在设置代理之前就已经完成了 tcp 连接的建立 -# 所以,需要你手动关闭应用并启动,应用才会通过代理建立连接 -# 也就是说,如果你是做中间人流量分析,那设置代理后 -# 你需要关闭应用再重新打开才会看到应用的请求 -# -# 注:本机的 DNS 流量始终不会经过代理 - -# 启动代理 -d.start_gproxy(profile) -# 关闭代理 -d.stop_gproxy() -``` - -> 快速搭建一个 socks5 代理 - -LAMDA 在 tools/ 中提供了一个开箱即用同时支持 udp 的 socks5 代理服务 docker,请转到 tools/socks5 目录查看 README.md。 - -> 在 LAMDA 启动时自动连接代理 - -复制下列配置并修改相关配置为你的代理信息 - -```txt -gproxy.enable=true -gproxy.type=http-connect -gproxy.host=123.333.333.333 -gproxy.port=8080 -gproxy.password= -gproxy.login= -``` - -将其追加或者写入到 properties.local,重启 LAMDA 即可。 - -## 安装中间人证书 - -> 建议你使用或参考已经封装好的 tools/ 目录下的 startmitm.py, globalmitm 工具,这里介绍的是相关接口。 - -请先确保你已经准备好 fiddler, mitmproxy 给你的证书,对于 mitmproxy, -给你的证书为 pem 格式如下示例。而对于 fiddler,则可能是 crt 格式,直接将该文件路径 -作为参数提供即可无需关心任何转换/文件名问题。 - -为了避免浪费不必要的时间,在这里推荐使用 `mitmproxy`, -如果你使用的是 `Charles` 等,我无法确保你可以一次性完成设置, -因为此类应用配置项目较为复杂且你可能需要理解各种代理类型才能正确配置SSL中间人, -如果你一定要使用,建议使用 Charles 的 socks5 作为代理协议。 - -注意:有可能不支持安卓 13 - -```python -import os - -# 拼接 mitmproxy-ca-cert.pem 文件的路径 -HOME = os.path.expanduser("~") -cert_path = os.path.join(HOME, ".mitmproxy", "mitmproxy-ca-cert.pem") -# 以 mitmproxy 为例,使用如下代码安装证书 -d.install_ca_certificate(cert_path) - -# 使用如下代码卸载证书(如不常变化不建议频繁安装卸载) -d.uninstall_ca_certificate(cert_path) -# 此证书安装接口是通用的,你可以用它安装任何应用要求你安装的证书 -# 你同样可以用其安装 Fiddler/Charles 要求你安装的证书 -# 只需要提供文件路径即可 -``` - -接着,看 `设置 http/socks5 代理` 节,将代理设置为中间人应用监听的地址即可。 -按照流程完成后如果没有截获到流量请参加设置代理部分的**特别注意**。 - -## 设置 OpenVPN - -> 此 OpenVPN 只支持使用证书登录,可以与 http/socks5 代理共存。 -> 需要注意的是,此功能只包含OpenVPN的主要功能,除了 `DNS` 配置,暂无法应用服务端推送的其他配置信息。 -> 这些配置包括但不限于 PAC 代理,http 代理配置等。为了省却你安装服务的麻烦, -> LAMDA 提供了一个开箱即用的 OpenVPN docker 镜像,它有脚本可以生成下面这个配置,请继续往下看。 - -非常建议使用 tools 里的 OpenVPN docker 安装及生成如下连接配置。 - -```python -profile = OpenVPNProfile() - -# 是否全局 VPN,为 False 时仅路由服务器端推送的特定网段 -profile.all_traffic = True -# 服务器端开启的连接协议 (或者为 OpenVPNProto.TCP) -profile.proto = OpenVPNProto.UDP -profile.host = "OpenVPN 服务器地址" -profile.port = OpenVPN 服务器端口 -# 服务器端通道加密方法 -profile.cipher = OpenVPNCipher.AES_256_GCM - -profile.tls_encryption = OpenVPNEncryption.TLS_CRYPT -profile.tls_key_direction = OpenVPNKeyDirection.KEY_DIRECTION_NONE -profile.tls_key = """ ------BEGIN OpenVPN Static key V1----- -tls key / tls auth ------END OpenVPN Static key V1----- -""" - -profile.ca = """ ------BEGIN CERTIFICATE----- -服务端配置的 ca 证书 ------END CERTIFICATE----- -""" - -profile.cert = """ ------BEGIN CERTIFICATE----- -客户端证书 ------END CERTIFICATE----- -""" - -profile.key = """ ------BEGIN PRIVATE KEY----- -客户端私钥 ------END PRIVATE KEY----- -""" - -# 启动 OpenVPN -d.start_openvpn(profile) -# 关闭 OpenVPN -d.stop_openvpn() -``` - -> 快速搭建一个 OpenVPN 服务 - -LAMDA 在 tools/ 中提供了一个开箱即用的 OpenVPN docker,请转到 tools/openvpn 目录查看 README.md。 - -> 在 LAMDA 启动时自动连接 VPN - -你可以使用 tools/openvpn 提供的命令来生成 properties.local 配置,请不要自行编写。 - -## 连接内置的 FRIDA - -> 非逆向工作无需阅读此节 - -启动本框架前后,**请勿**再次自行启动任何 frida-server,否则有可能会导致系统崩溃。你只需要通过下列代码使用内置 frida 即可。 - -1. 通过代码连接 - -```python -# 使用 LAMDA 时的做法 -device = d.frida -device.enumerate_processes() -``` - -等效于 - -```python -# 只是示例,请尽量使用上述方法连接 -manager = frida.get_device_manager() -device = manager.add_remote_device("192.168.0.2:65000") -device.enumerate_processes() -``` - -2. 通过命令行使用 - -对于所有 frida 官方命令行工具,你只需要加上参数 `-H 192.168.0.2:65000` 即可。 - -```bash -frida -H 192.168.0.2:65000 -f com.android.settings -# 如果你在服务端启动时指定了 certificate 选项,请注意也需要在此加入 --certificate 参数例如 -frida -H 192.168.0.2:65000 -f com.android.settings --certificate /path/to/lamda.pem -``` - -对于 objection 以及 r0capture 等,这些第三方工具可能并不会完全遵循原生 frida 工具的命令行用法,如果你需要使用这些第三方工具,需要确保 LAMDA 启动时**没有使用** `--certificate` 参数(加密传输),因为这些工具可能并没有可以传递PEM证书的参数。 - -```bash -# objection 示例连接方法 (-N -h 192.168.0.2 -p 65000) -objection -N -h 192.168.0.2 -p 65000 -g com.android.settings explore -``` - -```bash -# r0capture 示例连接方法 (-H 192.168.0.2:65000) -python3 r0capture.py -H 192.168.0.2:65000 -f com.some.package -``` - -```bash -# jnitrace 示例连接方法 (-R 192.168.0.2:65000) -jnitrace -R 192.168.0.2:65000 -l libc.so com.some.package -``` - -```bash -# frida-dexdump 示例连接方法 (-H 192.168.0.2:65000) -frida-dexdump -H 192.168.0.2:65000 -p PID -``` - -其他未提及的第三方工具请自行查看其使用方法。 +# **FIRERPA Android** | AI-Powered Automation -## 使用 FRIDA 暴露 Java 接口 - -这个功能类似于 [virjar/sekiro](https://github.com/virjar/sekiro),关于它的用途请参考 virjar 大佬的 -项目。此功能需要你能熟练编写 frida 脚本。 - -> 请转到 tools 目录查看使用方法。 - -此功能需要你能熟练编写 frida 脚本。示例中使用的脚本请参照 test-fridarpc.js 文件,特别注意: frida 脚本中 rpc.exports 定义的函数参数以及返回值只能为 int/float/string/list/jsdict 或者任意 js 中**可以被 JSON序列化**的值。假设设备IP为 192.168.0.2。 - -> 执行以下命令注入 RPC 到 com.android.settings(注意查看是否有报错),下面的相关文件在 tools 目录 - -```bash -python3 fridarpc.py -f test-fridarpc.js -a com.android.settings -d 192.168.0.2 -``` - -现在已经将接口暴露出来了,只需要请求 `http://192.168.0.2:65000/fridarpc/myRpcName/getMyString?args=["A","B"]` 即可得到脚本内方法的返回结果,链接也可以用浏览器打开,接口同时支持 POST 以及 GET,参数列表也可以同时使用多个参数,空列表代表无参数,注意这里的 args 参数序列化后的字符串最长**不能超过** `32KB`。 - -链接中的两个字符串参数 "A", "B" 即为注入的脚本中的方法 `getMyString(paramA, paramB)` 的位置参数。 - -注意参数的提供形式,是**双引号**,请**不要手打**或者**字符串拼接**这个参数,务必使用 json.dumps(["A", "B"]) - -> 用 requests 调用 -```python -import json -import requests -url = "http://192.168.0.2:65000/fridarpc/myRpcName/getMyString" -# 请求接口 -res = requests.post(url, data={"args": json.dumps(["A","B"])}) -print (res.status_code, res.json()["result"]) - -#* 状态码 200 一切正常 -#* 状态码 410 需要重新注入脚本或者脚本未注入(目前不支持自动重新注入) -#* 状态码 500 脚本或参数异常 -#* 状态码 400 参数错误 -``` - -响应结果的格式是固定的,可在浏览器打开查看。同样,配合下面一节的内容,你将可以在公网直接使用接口。 - -## 使用内置的定时任务 - -内置了用于执行定时任务的 cron 服务,这样你可以在设备上定期执行一些脚本,所有规则都将以 root 身份执行。 - -> 此功能需要你会编写 crontab 规则,如果你不清楚 crontab,请先自行了解。 - -现在,请打开 web 控制台或者连接设备的 ssh/adb shell,执行命令 `crontab -e`,你将进入编辑模式,在英文输入模式下按下字母 `i`,随后写下相关规则,并依次按下 `ESC`,`SHIFT` + `:`,输入 `wq` 并按下回车来保存即可。受限于安卓休眠机制,息屏后定时任务可能并不会以你期望的时间运行,你可能需要将设备设置为常亮。 - -> 一些规则示例 - -``` -@reboot echo 框架启动时执行 -0 */1 * * * echo 每一小时执行 -* * * * * echo 每一分钟执行 -0 8 * * * echo 每天八点执行 -``` - -## 使 LAMDA 可被任意地点连接 - -有时候你可能遇到这种情况:你的手机在家里而你不在家该怎么使用呢。 -开始前,你可能需要先准备一台公网服务器。为了安全考虑,这里使用的是最保守的配置,最后会说明如何做到完整介绍的功能。 - -因为有了公网服务器,lamda 有很多方法可以做到这个要求,使用 **OpenVPN** 来实现更加优雅。当然最方便的还是使用 frp。tools 文件夹内都提供了相关服务的 docker 镜像并且这些镜像可以一键命令生成下面的配置信息,可以转到 tools 查看使用方法。 - -本服务使用了较为成熟的端口转发程序 [fatedier/frp](https://github.com/fatedier/frp),关于如何配置服务端,请在此项目中自行探索。注意:请勿将转发后的端口绑定到公网地址,请确保你的公网服务器关闭了所有不必要的端口。 -这里给你一个最简单安全的配置,可以直接使用如下命令启动服务端。 - -> 首先在你的公网服务器上执行以下命令启动 frps(注意你可能还需要配置防火墙) - -```bash -frps --token lamda --bind_addr 0.0.0.0 --bind_port 6009 --proxy_bind_addr 127.0.0.1 --allow_ports 10000-15000 -``` - -> 然后编写 properties.local - -复制下列配置并修改**服务器地址**为你的服务器公网IP - -```txt -fwd.host=服务器地址 -fwd.port=6009 -fwd.rport=12345 -fwd.token=lamda -fwd.protocol=tcp -fwd.enable=true -``` - -将其追加或者写入到 properties.local,重启 LAMDA 即可。 - -> 如何透过上面的转发使用 lamda(需要在部署 frps 的那台公网服务器上使用,因为我们绑定了转发的端口到 127.0.0.1) - -```python -from lamda.client import * -# 端口为上面的 rport -d = Device("127.0.0.1", port=12345) -# 浏览器打开 http://127.0.0.1:12345 即可访问远程桌面 -# 其余任何接口调用实现均统一,无需做任何改动 -``` - -> 如何批量转发而不用每次都改写一下 rport 配置 - -如果你需要一次设置多台机器且不在乎每台机器绑定的端口, -你可以将上方配置中的 `fwd.rport` 值改为 0,这将导致你的设备被随机绑定到 `10000-15000` 的端口范围中, -你可以通过后期轮训端口范围来定位设备转发绑定的对应端口。 - -> 我就是想在任意地方都能连接到设备 - -首先,为了安全起见不建议这么做,如果确实需要这样用的话,建议使用 OpenVPN 将设备和你的电脑置于同一网段的方法来访问。 -如果你仍准备使用上述 frp 的方法实现任意访问,请先确保 LAMDA 服务启动时使用了**PEM证书**,并将启动 frps 命令时的 `--proxy_bind_addr 127.0.0.1` -改为 `--proxy_bind_addr 0.0.0.0`,这将导致 12345 端口直接绑定到公网。如果你未使用PEM证书启动 lamda,任何人都将可以访问,这是**非常非常危险**的。 -其次需要注意,web 远程桌面的流量始终都是 http 的,如果有人在你和服务器通信之间进行中间人,你的登录凭证可能会被窃取。当然,如果此期间不用 web 桌面将不存在这个问题。 - - -## 读写系统属性 - -> 设置/读取系统属性 - -```python -# 获取 ro.secure 的值 -d.getprop("ro.secure") - -# 设置 ro.secure 的值 -d.setprop("ro.secure", "0") -``` - -## 读写系统设置 - -> 设置/读取安卓系统设置 - -```python -settings = d.stub("Settings") - -# 如果你对以下 screen_brightness 等字符串表示疑惑,请查看下列文档。有些常量 -# 在不同版本的安卓可能并不兼容,以及部分厂商会有自定义的变量,需要注意。 - -# https://developer.android.com/reference/android/provider/Settings.System -# https://developer.android.com/reference/android/provider/Settings.Secure -# https://developer.android.com/reference/android/provider/Settings.Global - -# 你可以使用如下代码将系统屏幕的亮度设置为手动 -settings.put_system("screen_brightness_mode", "0") - -# 示例:获取并修改屏幕亮度为 5 (0-255) -settings.get_system("screen_brightness") -settings.put_system("screen_brightness", "5") - -# 示例:关闭开发者选项 -settings.get_global("development_settings_enabled") -settings.put_global("development_settings_enabled", "0") - -# 示例 -settings.get_secure("screensaver_enabled") -settings.put_secure("screensaver_enabled", "0) -``` - -## 获取设备运行状态 - -```python -status = d.stub("Status") - -# 获取设备启动时间 -status.get_boot_time() - -# 获取设备磁盘使用情况 -status.get_disk_usage(mountpoint="/data") - -# 获取电池信息 -status.get_battery_info() -# 获取CPU使用情况 -status.get_cpu_info() -# 获取总体磁盘读写情况 -status.get_overall_disk_io_info() -# 获取用户数据磁盘的读写情况 (userdata) -status.get_userdata_disk_io_info() -# 获取总体网络收发情况 -status.get_overall_net_io_info() -# 获取 wlan0 接口的网络收发情况 -status.get_net_io_info("wlan0") -# 获取内存使用情况 -status.get_mem_info() -``` - -## 在设备上执行命令 - -> 在设备后台,前台执行 shell 脚本/命令 - -```python -# 执行前台脚本(执行时间短(0-10秒内)的脚本) -cmd = d.execute_script("whoami") -print (cmd.exitstatus) -print (cmd.stdout) -print (cmd.stderr) - -# 执行后台脚本(执行时间长的脚本) -# 对于后台脚本,因考虑可能用户写出死循环脚本无限输出导致内存占满等问题 -# 暂时无法获知其执行结果 -ID = d.execute_background_script("sleep 100; exit 0;") -# 检查后台脚本是否结束 -d.is_background_script_finished(ID) -# 强制结束后台脚本 -d.kill_background_script(ID) -``` - -## 使系统可调试 - -如果你需要使用JEB,IDA等动态分析,你可能需要设置此标志才能进行,当然也内置了这个功能,你可以这么做而无需永久修改 `ro.debuggable`。 -但是记住,这个接口你并不是一定需要调用,仅当你看到任何文章/教程让你修改 `ro.debuggable` 时使用。 - -注意:调用此接口成功后,系统会自动重启,你可能仍需像首次启动时等待一段时间到本框架恢复 - -> 慎用,此功能可能不稳定且可能随时移除 - -```python -debug = d.stub("Debug") - -r = debug.set_debuggable() -print (r) -``` - -## 启动 IDA 调试服务 - -> 内置了 IDA 7.5 服务端 - -```python -debug = d.stub("Debug") - -# 启动 IDA 32 服务端(端口可自定义) -debug.start_ida(port=22032) -# 检查是否已启动 -debug.is_ida_running() -# 关闭 IDA 32 服务端 -debug.stop_ida() -# 如果调试的是64位程序,将方法名中的 ida 替换为 ida64 即可 -# 例如 -debug.start_ida64(port=22064) -# -# 如果需要自定义 ida-server 的环境变量例如 IDA_LIBC_PATH (同样适用于 start_ida) -debug.start_ida64(port=22064, IDA_LIBC_PATH="/apex/com.android.runtime/lib64/bionic/libc.so") -# 当你调试的目标程序是32位时使用 start_ida -# 否则使用 start_ida64 -# 当你的设备系统为32位平台时,start_ida64 将会无效 -``` - -## 无线连接内置的 WIFI ADB - -此 ADB 非全功能 adb,仅支持 shell,pull,push,forward,reverse 等常用功能 -通过此功能你将**无需开启开发者模式**即可连接最高权限的 adb。 - -> 注:jdwp 调试相关功能具有唯一性,与系统内置存在冲突所以此 adb **目前**不支持。 - -```python -# LAMDA 内置的 adb 服务完全独立于系统本身提供的 adb 服务 -# 所以在使用之前需要先手动调用以下接口将你的 adb 公钥安装至设备上 -# 否则直接连接将会显示未授权(系统设置开发者模式中授权的秘钥与内置 adb 并不通用) -# -# tools 目录下的 adb_pubkey.py 封装了下面接口的安装过程 -# 你可以使用该脚本一键授权,允许本机连接,请查看其 README,以下代码仅做参考说明 -# -# 这个秘钥文件位于你电脑上的 ~/.android 或者 C:\\Users\xxxx\.android,文件名为 adbkey.pub -# 如果不存在这个文件但是存在文件 adbkey,请切换到该目录并执行命令 -# adb pubkey adbkey >adbkey.pub 来生成 adbkey.pub -# -# 随后使用 python 代码来拼接这个生成的 adbkey.pub 路径 -import os -keypath = os.path.join("~", ".android", "adbkey.pub") -abs_keypath = os.path.expanduser(keypath) -print (abs_keypath) -# -# 然后安装这个 adbkey.pub 到 LAMDA -d.install_adb_pubkey(abs_keypath) -# 这样你就可以连接内置 adb 了 -# 通过命令 adb connect 192.168.0.2:65000 连接到设备 -# 你完全可以将其理解为 WIFI ADB -# -# 或者如果你需要从 LAMDA 内置 adb 移除这个公钥 -d.uninstall_adb_pubkey(abs_keypath) -``` - -## 文件操作 - -> 将文件上传至设备或从其下载文件(支持大文件) - -```python -# 下载文件到本地 -d.download_file("/verity_key", "写入到的本地文件") - -# 下载文件到 内存/已打开的文件 -from io import BytesIO -fd = BytesIO() -d.download_fd("/verity_key", fd) -print (fd.getvalue()) - -# 注意必须使用 w+b 模式打开被写入文件 -fd = open("写入到的本地文件", "wb") -d.download_fd("/verity_key", fd) - -# 上传文件到设备 -d.upload_file("本地文件路径.txt", "/data/local/tmp/上传到设备上的文件.txt") - -# 从 内存/已打开的文件 上传文件 -from io import BytesIO -d.upload_fd(BytesIO(b"fileContent"), "/data/local/tmp/上传到设备上的文件.txt") - -# 注意必须使用 rb 模式打开文件 -fd = open("myfile.txt", "rb") -d.upload_fd(fd, "/data/local/tmp/上传到设备上的文件.txt") - -# 删除设备上的文件 -d.delete_file("/data/local/tmp/文件.txt") - -# 修改设备上的文件权限 -d.file_chmod("/data/local/tmp/文件.txt", mode=0o777) - -# 获取设备上文件的信息 -d.file_stat("/data/local/tmp/文件.txt") -``` - -## 关机重启 - -```python -# 关闭系统(等于关机) -d.shutdown() -# 重启系统(等于重启) -d.reboot() - -# 关闭设备上运行的 LAMDA 服务 -d.exit() -``` - - -## 应用操作 - -> 列出系统上已安装的所有应用的ID - -```python -d.enumerate_all_pkg_names() -``` - -> 列出设备上所有正在运行的应用 - -```python -d.enumerate_running_processes() -``` - -> 获取当前处于前台的应用 - -```python -d.current_application() - -# 等价于 -d.application(d.current_application().applicationId) - -# 获取当前前台的 activity -d.current_application().activity -``` - -> 启动、获取 Activity - -```python -# 导入 FLAG_ACTIVITY* 常量定义 -from lamda.const import * - -# 获取系统中最近的5条活动(最多12条) -activities = d.get_last_activities(count=5) -print (activities) - -# 你可以直接重放最后一条活动(注意并不是所有活动都可以重放) -activity = activities[-1] -print (activity) -d.start_activity(**activity) - -# 手动组装 activity 信息 -# 附加数据只支持 boolean, int, short, long, double, float 以及 string 类型 -d.start_activity(action="***", category="***", component="***", - extras={"boolean": False, "int": 1, "string": "act", "float": 1.123}, - flags=FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_CLEAR_TASK, - data="***", debug=False) - -# flags 的定义请参考文档 -# https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_BROUGHT_TO_FRONT -# flags 以及 debug 参数不是必须的,只是多了一种可能 - -# 拨打 10000 客服电话 -d.start_activity(action="android.intent.action.CALL", data="tel:10000") - -# debug 参数代表:是否以调试模式启动该活动 -# 如果你知道 Waitting for debugger,那么它可能对你有用 -# 你可以像下面这样以调试模式启动一个应用(你的设备或者APP需要是可调试的) -la = d.application("com.android.settings").query_launch_activity() -d.start_activity(**la, debug=True) - -# 例如:启动 设置 APP(当然这几乎等价于直接启动app) -d.start_activity(action="android.intent.action.MAIN", - category="android.intent.category.LAUNCHER", - component="com.android.settings/.Settings") - -# 例如:进入证书设置 -d.start_activity(action="com.android.settings.TRUSTED_CREDENTIALS") -``` - -> 授予/撤销 APP 权限 - -注意,你应在APP未启动时进行权限设置,在APP请求权限时调用并不会产生帮你点击允许的效果。 - -```python -app = d.application("com.my.app") - -#导入 PERMISSION_READ_PHONE_STATE 常量(版本>3.90) -from lamda.const import * - -# 获取应用所有权限 -app.permissions() -# 授予 READ_PHONE_STATE 权限 -app.grant(PERMISSION_READ_PHONE_STATE, mode=GrantType.GRANT_ALLOW) -# 拒绝 READ_PHONE_STATE 权限 -app.grant(PERMISSION_READ_PHONE_STATE, mode=GrantType.GRANT_DENY) -# 检查是否已授予权限 -app.is_permission_granted(PERMISSION_READ_PHONE_STATE) -# 撤销已授予的权限 -app.revoke(PERMISSION_READ_PHONE_STATE) -``` - -> 清除应用缓存,重置应用 - -```python -# 删除应用的缓存数据 -app = d.application("com.my.app") -app.delete_cache() -# 重置应用数据 -app.reset_data() -``` - -> 启动/停止应用 - -```python -app = d.application("com.my.app") - -# 启动应用 -app.start() -# 检查应用是否正在前台运行 -app.is_foreground() -# 关闭应用 -app.stop() -``` - -> 其他 - -```python -app = d.application("com.my.app") -# 获取应用信息 -app.info() - -# 检查应用是否已安装 -app.is_installed() -# 卸载应用 -app.uninstall() - -# 查询该应用的启动 Activity(入口活动) -app.query_launch_activity() - -# 启用应用 -app.enable() -# 禁用应用 -app.disable() -``` - -## WIFI操作 - -目前WIFI操作部分功能由于可能导致设备异常未实现,仅介绍部分实现的功能 - -```python -wifi = d.stub("Wifi") - -# 获取wifi bssid,ssid ip 等相关信息 -wifi.status() - -# 获取黑名单中的所有 bssid -wifi.blacklist_get_all() - -# 将bssid加入黑名单(将不会显示在wifi列表) -wifi.blacklist_add("3c:06:aa:8a:55:66") - -# 清空所有黑名单 -wifi.blacklist_clear() - -# 执行 wifi 扫描 -wifi.scan() - -# 获取周边 wifi 扫描结果 -wifi.scan_results() - -# 获取当前wifi的mac地址 -wifi.get_mac_addr() - -# 获取 wifi 信号强度,链接速率 -wifi.signal_poll() -``` - -## 基本UI操作 - -> 获取设备信息 - -```python -d.device_info() -``` - -> 息屏/亮屏相关 - -```python -# 息屏 -d.sleep() -# 亮屏 -d.wake_up() -# 屏幕是否点亮 -d.is_screen_on() -# 屏幕是否已锁定 -d.is_screen_locked() -``` - -> 剪切板 - -```python -d.set_clipboard("剪切板内容") - -# 获取剪切板内容(不支持安卓10+) -d.get_clipboard() -``` - -> 物理按键 - -```python -# 此方法可使用如下17种按键 -# KEY_BACK -# KEY_CAMERA -# KEY_CENTER -# KEY_DELETE -# KEY_DOWN -# KEY_ENTER -# KEY_HOME -# KEY_LEFT -# KEY_MENU -# KEY_POWER -# KEY_RECENT -# KEY_RIGHT -# KEY_SEARCH -# KEY_UP -# KEY_VOLUME_DOWN -# KEY_VOLUME_MUTE -# KEY_VOLUME_UP -d.press_key(Keys.KEY_BACK) - -# 同时为了可以使用更多按键,也可以使用这个方法 -d.press_keycode(KeyCodes.KEYCODE_CALL) -# 可使用的 KEYCODE 可以自行查看此文档 -# https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_0 -``` - -> 屏幕截图 - -```python -quality = 60 # 截图质量,默认为全画质 -d.screenshot(quality).save("screenshot.png") -# 截取屏幕上特定区域的图像 -# Bound 的参数 top,left 等定义: - -# top: 从距离屏幕顶部向下数 top 个像素 -# bottom: 从距离屏幕顶部向下数 bottom 个像素 -# left: 从距离屏幕左侧向右数 left 个像素 -# right: 到距离屏幕左侧向右数 right 个像素 - -# 正常情况下 top 永远小于 bottom,left 永远小于 right -bound = Bound(top=50, bottom=80, left=50, right=80) -d.screenshot(quality, bound=bound).save("partial.png") -``` - -> 点击屏幕上的一个点 - -```python -d.click(Point(x=100, y=100)) -``` - -> 点按点A并将其拖动到点B - -```python -A = Point(x=100, y=100) -B = Point(x=500, y=500) - -d.drag(A, B) -``` - -> 从点A滑动到点B - -```python -A = Point(x=100, y=100) -B = Point(x=500, y=500) - -d.swipe(A, B) -``` - -> 稍复杂的多点滑动(九宫格解锁) - -```python -p1 = Point(x=100, y=100) -p2 = Point(x=500, y=500) -p3 = Point(x=200, y=200) - -# 从点P1滑动到点P2随后滑动到点P3,可任意个点 -d.swipe_points(p1, p2, p3) -``` - -> 打开通知栏/快捷设置栏 - -```python -d.open_notification() -d.open_quick_settings() -``` - -> 获取页面布局描述XML - -```python -d.dump_window_hierarchy().getvalue() -``` - -> 等待界面布局停止刷新 - -```python -# 单位是毫秒,5*1000 代表5秒 -d.wait_for_idle(5*1000) -``` - -> 获取最近的 toast - -```python -d.get_last_toast() -``` - -## 进阶UI操作 - -> Selector - -界面布局检视,首先你需要打开设备的 web 远程桌面。随后,点击远程桌面右上角的眼睛图标进入,此时你将不能再滑动左侧屏幕,你可以点击屏幕上的虚线框来查看对应元素的信息,你可以将其中的部分属性作为 Selector 的参数。 -再次点击眼睛图标将关闭布局检视,布局检视并不会随着页面的改变而刷新,它始终是你按下快捷键那一刻的屏幕布局,如果需要刷新布局请手动按下快捷键 `CTRL+R`。 - -正常情况下,我们只会使用 `resourceId`, `clickable`, `text`, `description` 作为参数。 -如果元素存在正常的 resourceId,优先使用其作为 Selector,即:`Selector(resourceId="com.android.systemui:id/mobile_signal_single")`。 -对于无 resourceId,则会使用其 text,即:`Selector(text="点击进入")`,或者更模糊一点 `Selector(textContains="点击")` -description 与 text 同理,但是 description 用的会比较少。 - -当然,Selector 不止可以使用一个参数,你可以做其他组合,例如 `Selector(text="点击进入", clickable=True)` - -> 注意:很少直接用 Selector(),大部分情况下,使用 d() 来进行。 - -所有常见的匹配参数: - -``` -text 文本完全匹配 -textContains 文本包含匹配 -textStartsWith 文本起始匹配 -className 类名匹配 -description 描述完全匹配 -descriptionContains 描述包含匹配 -descriptionStartsWith 描述起始匹配 -clickable 可以点击 -longClickable 可以长按 -scrollable 可滚动 -resourceId 资源ID匹配 -``` - -大部分情况下,你不会直接用到 `Selector`,但是间接使用无处不在。 - -> 元素操作 - -上文都是介绍了如何坐标点击这种随意性的东西,现在开始介绍如何操作固定目标元素。首先,你需要知道如何选定元素。 - -```python -# 选择界面上的包含文字 被测APP 的元素 -element = d(textContains="被测APP") -# 当然,你不一定要这样赋值到 element,也可直接使用 d(textContains="被测APP") -``` - -好了,现在你知道了如何获取元素了,当然,这时并没有获取到,只是代表,你想要在当前界面操作这个元素,下面开始操作。 - - -```python -# 我们现在假设,界面上这个 被测APP 是手机上被测APP的图标名称(图标下面的名称)。 -element = d(textContains="被测APP") -# 是否存在该元素 -element.exists() -# 点击该元素,不存在则会抛出异常 -# Corner.COR_CENTER 代表点击该元素中心点,你可查看 COR_CENTER 定义获取其他可点击的位置 -element.click(corner=Corner.COR_CENTER) - -# 点击该元素,不存在不会抛出异常 -element.click_exists(corner=Corner.COR_CENTER) - -# 长按该元素,不存在则会抛出异常 -element.long_click(corner=Corner.COR_CENTER) - -# 获取元素信息 -element.info() - -# 获取元素的中心点坐标 -element.info().bounds.center() - -# 获取元素的左上点坐标 -element.info().bounds.corner("top-left") - -# 获取元素的高度 -element.info().bounds.height - -# 获取元素的宽度 -element.info().bounds.width - -# 获取元素个数 -element.count() - -# 等待元素出现,最多等待10秒 -element.wait_for_exists(10*1000) - -# 等待元素消失,最多等待10秒 -element.wait_until_gone(10*1000) - -# 获取该元素的截图(不是全屏,只是该元素) -# quality 为截图质量 1-100 -element.screenshot(quality=60) - -# 将此 APP 拖动归类到 购物 文件夹(依据实际情况修改) -element.drag_to(Selector(text="购物")) - -######### -# 查找同级或者子级元素 -######### -# 有时候会有一些重复元素或者无明显特征的元素,很难去定位 -# 这时你可以通过查找子级/同级元素的方法来缩小查找范围 -# 子级元素,举例为:一个聊天登录框,里面的输入框即为登录框的子级元素 -# 同级元素,举例为:聊天输入框里面的用户名和密码框为同级原始(正常情况下) -form = d(resourceId="login_form") -form.child(index=1) -# 这将获取到 login_form 下 index 为 0 的元素 -form.child(index=1).sibling() -# 你也这样来找与 login_form 同级的找回密码按钮 -#(其实已经可以通过字符串判断了,就不需要这样做了,这里只是演示) -form.sibling(textContains="找回密码") -# 它们本身就是一个element,你可以对其做任何 element 的操作 - - -############################ -# 现在 element 改变了其意义,变为选择了输入框 -############################ - -# 示例为:在一加搜索应用界面的搜索框输入 被测APP - -# 注意,不要直接往看似输入框的地方输入文字,可能并无法输入 -# 有些输入框需要点击一次才会进入真正的输入框,请使用此真正输入框的资源ID -element = d(resourceId="net.oneplus.launcher:id/search_all_apps") -element.set_text("被测APP") - -# 获取输入的内容 -element.get_text() - -# 清空刚刚输入的内容 -element.clear_text_field() - -# 配合点击搜索,来完成一次类人的搜索操作。 - - -# 滑动操作(列表上下滑动翻页) -# 注意,这些操作并不保证精度,下面这些方法正常情况下都并不需要选择器, -# 但是你可根据实际情况自行加入选择器 - -# 向上滑动, step 自行调整,越多会越慢,比较适合精度要求较高的滑动 -d().swipe(direction=Direction.DIR_UP, step=32) -# 其他滑动方向: -#DIR_UP 向上滑动 -#DIR_LEFT 向左滑动 -#DIR_DOWN 向下滑动 -#DIR_RIGHT 向右滑动 - -######### -# fling:甩动,即正常人滑动屏幕的行为,较快 -######### -# 从上向下 -d().fling_from_top_to_bottom() -# 从下往上 -d().fling_from_bottom_to_top() -# 从左往右 -d().fling_from_left_to_right() -# 从右往左 -d().fling_from_right_to_left() - -# 其他,一直向下/左右上滑,直到滑动到底 -# 因为并不是一定可以滑动到底或者检测到滑动到底 -# 所以 max_swipes 参数是必须的 -d().fling_from_top_to_bottom_to_end(max_swipes=32) -d().fling_from_bottom_to_top_to_end(max_swipes=32) -d().fling_from_left_to_right_to_end(max_swipes=32) -d().fling_from_right_to_left_to_end(max_swipes=32) - -######### -# scroll: 比较机械性的滑动 -######### -step = 60 -max_swipes = 32 -# 从上往下滑动 step 步 -d().scroll_from_top_to_bottom(step) -# 从下往上滑动 step 步 -d().scroll_from_bottom_to_top(step) -# 从左往右滑动 step 步 -d().scroll_from_left_to_right(step) -# 从右往左滑动 step 步 -d().scroll_from_right_to_left(step) - -# 其他,一直向下/左右上滑,直到滑动到底 -# 同上文 fling 描述 -d().scroll_from_top_to_bottom_to_end(max_swipes, step) -d().scroll_from_bottom_to_top_to_end(max_swipes, step) -d().scroll_from_left_to_right_to_end(max_swipes, step) -d().scroll_from_right_to_left_to_end(max_swipes, step) -``` - -> 监视器 - -监视器用来监听界面变化并在满足条件时执行设定的操作(点击元素或者按键),这可能对性能或者需要人工介入时产生影响,所以请谨慎使用,默认未开启。 - -```python -# 启动监视器循环 -d.set_watcher_loop_enabled(True) - -# 获取监视器是否已启动 -d.get_watcher_loop_enabled() - -# 移除系统中应用的所有 watcher,建议每次使用前都执行防止前面任务注册的未删除影响正常处理流程 -d.remove_all_watchers() - -# 获取系统中所有已应用的 watcher 名称列表 -d.get_applied_watchers() - -# 彻底移除一个 watcher -d.remove_watcher(name) - -# 应用watcher到系统中(当 watcher_loop 启动,此watcher将会生效) -d.set_watcher_enabled(name, True) -# 取消应用 -d.set_watcher_enabled(name, False) - -# 获取此 watcher 是否应用 -d.get_watcher_enabled(name) -``` - -> 监视系统界面出现某个元素的次数 - -```python -# 做一些测试前的清理,当然,并不是每 register 一个就需要这样 -# 只是为了确保测试过程不被干扰 -d.remove_all_watchers() -d.set_watcher_loop_enabled(True) - -# 应用监视界面出现 好的 的次数 -# 第二个参数为数组,可以给多个 Selector 表示条件都满足才会记录 -# 但是不建议超过三个 -d.register_none_op_watcher("RecordElementAppearTimes", [Selector(textContains="好的")]) -d.set_watcher_enabled("RecordElementAppearTimes", True) - -# ... 做满足条件的操作 - -# 获取记录的次数 -d.get_watcher_triggered_count("RecordElementAppearTimes") - -# 重置记录的次数 -d.reset_watcher_triggered_count("RecordElementAppearTimes") - -# 移除 -d.remove_watcher("RecordElementAppearTimes") -``` +FIRERPA +

+ + + + +

-> 当界面出现匹配元素时点击某个元素 +
An all-in-one Android automation framework that combines on-device services, AI-ready agents, and extensible tool invocation.
-```python -# 做一些测试前的清理,当然,并不是每 register 一个就需要这样 -# 只是为了确保测试过程不被干扰 -d.remove_all_watchers() -d.set_watcher_loop_enabled(True) +

Documentation | 使用文档 | TELEGRAM | QQ Group | SKILLS | llms-full.txt | 中文版本

-# 示例为,当APP启动后出现用户协议时,自动点击同意 -# 第二个参数为数组,可以给多个 Selector 表示条件都满足才会点击 -# 但是不建议超过三个 -d.register_click_target_selector_watcher("ClickAcceptWhenShowAggrement", [Selector(textContains="用户协议")], - Selector(textContains="同意", clickable=True)) -d.set_watcher_enabled("ClickAcceptWhenShowAggrement", True) +FIRERPA is an **all-in-one Android device control platform**. The server runs directly on the device with **no extra runtime dependencies**; it **supports multiple generations of Android** and works **with or without root**. On the PC side, the Python client library orchestrates UI automation, remote operations, traffic capture, Hook-based reverse engineering, network proxying, distributed networking, AI agents, and **MCP** through a single service and API. Compared with stitching together Appium, mitmproxy, frida-server, adb, uiautomator2, and ad-hoc scripts and ops tools, FIRERPA offers **one source of capabilities, unified configuration, connected workflows, and a stack built for multi-device, long-running, production use**. -# ... 做满足条件的操作 +## Remote Desktop & Live Streaming -# 移除 -d.remove_watcher("ClickAcceptWhenShowAggrement") -``` +FIRERPA provides a browser-based remote desktop: view and control the device in real time without installing a dedicated PC client, over LAN or across networks. Streaming supports both **MJPEG** and **H.264**, with software and hardware encoding backends. Frame rate (up to 60fps), resolution scale, bitrate, and quality can be tuned to device capability and network conditions—reducing bandwidth and improving smoothness on weak links. **WebRTC** is also supported, with configurable STUN/TURN servers for better NAT traversal and lower end-to-end latency in public or cross-region setups. +The remote desktop supports **multi-user concurrent access** for collaborative debugging, demos, and training. **Bidirectional clipboard** sharing and **live audio** (Android 10+) are included, along with an integrated terminal, drag-and-drop uploads, and browser-based file browsing and downloads—all over a single port. Built-in **visual layout inspection** highlights elements, supports Tab traversal, shows coordinates and RGB values, and exports the XML layout tree so you can validate selectors and automation logic in the same UI, shortening the loop from screen to script. -> 当界面出现匹配元素时点击物理按键 +

+demo +

-```python -# 做一些测试前的清理,当然,并不是每 register 一个就需要这样 -# 只是为了确保测试过程不被干扰 -d.remove_all_watchers() -d.set_watcher_loop_enabled(True) +Remote desktop and RPC support **end-to-end TLS** and service-certificate access control, with optional custom WebUI login passwords to reduce exposure on public networks. Remote desktop capabilities can also be embedded into your own web apps over WebSocket (live video, touch, terminal, keys, etc.), with `allow_origin` for cross-origin integration—suitable for productizing real-device control. -# 示例为,当界面存在 个人中心 时,按下HOME键回到启动屏幕 -# 第二个参数为数组,可以给多个 Selector 表示条件都满足才会点击 -# 但是不建议超过三个 -d.register_press_key_watcher("PressBackWhenHomePageShows", [Selector(textContains="个人中心")], Keys.KEY_HOME) -d.set_watcher_enabled("PressBackWhenHomePageShows", True) +## UI Automation -# ... 做满足条件的操作 +FIRERPA provides a full selector-based automation system: text, resourceId, description, scrollable, and other common matchers, plus **child / sibling** chaining for duplicate elements, deep hierarchies, or controls without distinctive attributes. At the element level: screenshots, wait for appear/disappear, corner/center coordinates, exists checks, Unicode input, stepped swipes, fling scrolling, scroll-to-end, and more. It can **coexist with other accessibility services** on Android 8.0+ and improves WebView node discovery for hybrid apps. -# 移除 -d.remove_watcher("PressBackWhenHomePageShows") -``` +The **UI Watcher** listens for UI changes in real time and runs clicks, key events, or counters when conditions match—useful for auto-dismissing agreements, update prompts, ads, and other interruptions. Multiple selector conditions and per-event enable/disable are supported; transient screens can be counted. -## 接口锁 +**Virtual Display** is a differentiator: create isolated background displays on the device and run apps and automation there **without affecting the main screen**—e.g. auto-reply on a virtual display while you use the phone normally. The virtual-display API mirrors the main device API (`d.xxx` → `vd.xxx`); Watchers can be scoped to a virtual display. The WebUI supports **multi-display view and switching** in remote desktop—ideal for background tasks, parallel work, and human-in-the-loop plus machine automation. -这里的基本功能让你可以锁定接口只能为当前 Device 实例使用。 +For scenes without a standard view tree (games, custom-drawn UI), FIRERPA offers **OCR** and **image matching**. OCR supports paddleocr, easyocr, and custom HTTP backends; in clusters, recognition can run centrally instead of loading models on every PC, with GPU acceleration and flexible text matching. Image matching runs **on the device** (template or SIFT), without consuming PC resources; SIFT is robust to rotation, scale, and lighting. **Multi-touch** supports recording, replay, programmatic construction, and binary persistence for complex gestures and pressure. -```python -# 获得锁,此锁将在60秒后被自动释放,其他客户端将可以获得锁,你可以更改此时间 -# 但是,如果改得太高因为异常脚本退出,你将近乎永远无法连接设备,你可能需要进行重启 -# 此获得锁接口可重入,重入时等价于 _refresh_lock,建议只调用一次 -d._acquire_lock(leaseTime=60) -# 刷新锁,每次调用后将锁过期时间设为此 leaseTime -# 做定期调用来保持设备锁定 -d._refresh_lock(leaseTime=60) -# 释放锁,其他客户端将可以获得锁 -d._release_lock() -``` +Architecturally, FIRERPA uses a client/server model—better for **centralized scheduling, versioning, and fleet control** than on-device script runners like AutoJS; lighter than Appium; **more stable than uiautomator2 in multi-device scenarios**. The docs position it as a **functional superset** of common Android automation stacks—automation, capture, Hook, and ops can be chained in code on one platform without switching tools. -## 使用内置终端 +## Packet Capture & MITM -这里的内部终端,指的是你通过 web 远程桌面或者 ssh/内置adb 连接的终端,里面内置了一些命令以及Python模块,你可以 -直接在里面执行一些操作或者运行一些 Python 代码,甚至可以直接在终端内完成自控,由于兼容性考虑,adb 连接的终端不存在部分命令提示等功能。 +FIRERPA offers **one-click MITM capture**: automatic system root CA install, proxy setup, handling of Android version differences, and automatic network restore on exit—no manual cert or proxy toggling. Global capture, per-package capture, live request/response editing, shared mitmweb, and upstream HTTP proxy for **international traffic** are supported. Built-in **QUIC downgrade** reduces QUIC interference. Capture still works when PC and device only share a single FIRERPA port (e.g. ADB connect or frp forwarding). -> 现在假设你已经打开了 web 远程桌面,你应该已经在页面上看到了一个 linux 终端。 -执行命令 `cd` 可以切换到家目录(默认为 `/data/usr`),这是你的工作区,你可以在此存储文件。终端支持命令补全但不支持参数补全,你也可以在终端输入部分命令,随后通过上下键自动填补历史命令。同时,内部提供了一些命令别名,这些命令别名及功能如下。 -``` -l = 命令 ls -ll = 命令 ls -l -la = 命令 ls -la -py = 命令 python -.. = 切换到父目录 -... = 切换到父目录的父目录 -p = 切换到上一个目录 -t = 切换到 /data/local/tmp -``` +Beyond one-click scripts, MITM is fully **API-driven**: install/uninstall system CAs compatible with mitmproxy, Fiddler, Charles, etc.; embed capture in automation pipelines alongside UI steps and Frida hooks. A Windows **startmitm.exe** is available without a Python install. -> 其他一些实用命令。 +## Network, Proxy & Connectivity -``` -* python (Python) -* strace (syscall trace) -* ltrace (libcall trace) -* curl (cURL) -* fsmon (文件访问监控) -* stunnel (流量加密) -* redir (端口转发) -* scapy (流量分析) -* iperf3 (网络性能测试) -* nano (文件编辑器) -* vi (文件编辑器) -* ncdu (查找磁盘文件占用) -* socat (网络工具) -* sqlite3 (读取 SQLite 数据库,支持 cipher) -* tcpdump (流量分析) -* busybox (命令集合) -* MemDumper (https://github.com/kp7742/MemDumper) -* frida (frida-tools) -* frida-ps (frida-tools) -* frida-trace (frida-tools) -* frida-ls-devices (frida-tools) -* frida-discover (frida-tools) -* frida-kill (frida-tools) -* frida-apk (frida-tools) -* frida-create (frida-tools) -* frida-join (frida-tools) -* 等基础 linux 命令 -``` +On the device, FIRERPA provides full proxy support: **HTTP / HTTPS / SOCKS5 / Shadowsocks** (multiple ciphers), per-app proxy (including multi-user clones), DNS proxy, UDP proxy, LAN bypass, coexistence with OpenVPN, and **auto-connect** to configured proxies at startup. Proxies support **IPv6 and UDP** for complex networks. -> Python 也内置了一些三方库,注意无法通过 PIP 安装额外的库。 +**Device HTTP bridge proxy (tunnel2)** provides reverse-proxy capability: point your PC or browser HTTP proxy at the phone and traffic is forwarded through the phone’s network egress; the outbound IP matches the phone. Wi‑Fi or cellular (rmnet) can be chosen as the egress interface—useful for joint debugging, international apps, and multi-device IP pools. -``` -* lamda (自身) -* capstone (反汇编引擎) -* keystone_engine (汇编引擎) -* unicorn (CPU模拟引擎) -* lief (二进制程序解析) -* lxml (xml/html解析) -* redis (redis客户端) -* tornado (web框架) -* pyOpenSSL (OpenSSL) -* requests (requests) -* scapy (流量分析) -* frida (frida) -* pyaxmlparser (APK解析) -* xmltodict (xml转dict) -* msgpack_python (msgpack) -``` +A built-in **OpenVPN client** supports certificate, username/password, and mixed auth, alongside system proxy; an **OpenVPN Docker image** and config generators produce device-side code and autostart settings. A built-in **frp client** forwards device services to a public server via `fwd.*`, with encryption and TLS. -这里不会介绍如何使用这些命令或库。 +**mDNS discovery** enables `{device_id}.local` access to remote desktop and other services, with optional TXT metadata (model, ABI, device ID) for LAN fleet discovery. +## Frida & Reverse Engineering -# 工具及教程 +FIRERPA **bundles the latest Frida**—no separate frida-server. Use frida tooling from the remote desktop terminal without `-U`, `-H`, etc. Built-in hiding patches (open-source plus in-house) are maintained against detection; connections use token auth over the FIRERPA port. objection patches and Frida 17.x java-bridge helpers ease migration. -其中的每个文件夹下都有一份使用说明。 +**Persistent Frida scripts** re-inject after app crash or exit; YAML offline persistence with directory watch and hot reload. Frida RPC maps to **Python methods** and **HTTP + JSON-RPC 2.0** remote calls on JVM, UI main, or pure JS threads. Script `emit` data can go to **HTTP / Redis / RabbitMQ (MQTT)** with device/app metadata, zlib compression, HTTP retries, and MQTT TLS. -## 一键中间人 +## ADB, SSH & Built-in Terminal -打开 [tools/README.md](tools/README.md) 查看。 +FIRERPA includes a **standalone built-in ADB**—wireless high-privilege ADB **without enabling system Developer Options**, useful when apps detect developer mode. Magisk modules can preinstall `adb_keys` for default authorization. Built-in **SSH** plus `ssh.sh` / `scp.sh` for remote shell and file transfer. -## 国际代理进行中间人 +The **built-in terminal** ships with strace, ltrace, tcpdump, scapy, fsmon, frida-tools, MemDumper, and Python stacks such as Crypto, cv2, unicorn, capstone, keystone, redis, and grpc. sqlite3 supports **wxsqlite / sqlcipher / sqlcrypto** for reading encrypted databases from apps such as WeChat, WeCom, and Alibaba-family clients. One environment across remote desktop, SSH, and ADB—analyze and control on-device with less PC shuttling. -打开 [tools/README.md](tools/README.md) 查看。 +## Distributed & Multi-Device Management -## 安装 ADB 公钥 +**FIRERPA StarLink Platform (Hub + hub-bridge)** provides centralized multi-device access for local and remote devices with unified management and **P2P** access—ideal for “device at home, operator elsewhere” and multi-device collaboration. hub v3 uses **SAPI** for unified requests to local and remote devices. Besides Hub, **frp forwarding** and **OpenVPN mesh** are also supported as self-hosted options, with full documentation and Docker support. -打开 [tools/README.md](tools/README.md) 查看。 +Since 9.0, **P2P Bridge** peer connectivity and a **built-in distributed task system** are supported; since 7.50, a networking subscription service is available so you need not self-host frp/OpenVPN. Designed for cloud phone pools, device farms, and emulator clusters—**batch devices, always-on, unattended** operation. -## OpenVPN 服务 +## AI / MCP / Agent -打开 [tools/README.md](tools/README.md) 查看。 +FIRERPA includes an **MCP server** (`/mcp/`) on **streamable-http**, with tool calls, resource reads, prompts, progress notifications, and logs—compatible with **Claude, Cursor**, and other MCP clients. Official MCP extensions live under `~/modules/extension`. -## SOCKS5 服务 +The built-in **`agent` command** drives the device in **natural language** via any OpenAI-compatible API with tool calls, including **vision** mode, plus an **OpenAI semantic task executor**. Combine with **crontab** for scheduled AI tasks on real devices. -打开 [tools/README.md](tools/README.md) 查看。 +

+MCP +

-## 端口转发服务 +## System Control & Extensions -打开 [tools/README.md](tools/README.md) 查看。 +**160+ Python APIs** cover app ops, file I/O, command execution, device status, WiFi, proxy, OpenVPN, shutdown/reboot, service logs, and more. `setprop` can write **`ro.*`** read-only properties; Settings APIs adjust brightness, developer mode, etc.; **SELinux** domain creation, permissive mode, and fine-grained allow/disallow. Full multi-instance app APIs, silent grant/revoke permissions, launching non-exported activities, disabling apps to save resources. -## 注入 Frida RPC 脚本 +**Binary patching** (hex wildcards, glob paths, dryrun) and runtime metrics for disk, battery, CPU, memory, and network I/O. WAV playback and internal storage APIs. On-device **tflite-runtime** with hardware-accelerated compute for edge inference. -打开 [tools/README.md](tools/README.md) 查看。 +A **virtual Debian environment** runs full Debian with apt—compile BPF, deploy OpenSSH, install arbitrary Python deps beyond the built-in terminal’s limits. -## 生成加密连接证书 +## Deployment, Security & Governance -打开 [tools/README.md](tools/README.md) 查看。 +Deploy via **one-click APP** (root or Shizuku), **Magisk module autostart**, manual extract, or **ROM integration**—designed for **24/7** operation. The APP can **auto-generate service certificates**; Magisk zips can ship properties, lamda.pem, and adb_keys. **INI service configuration** centralizes port, WebUI, certificates, OpenVPN, gproxy, frp, tunnel2, cron, sshd, adb, mdns, etc.; WebUI offers visual editing, raw text, and Reload Service, plus remote config file loading. -## 列出内网设备 +Root and shell identities; arm64, armeabi-v7a, x86, x86_64; real devices, emulators (LDPlayer, Nox, etc.), Redroid, WSA, AVD, cloud phones. Clean uninstall paths. **API exclusive lock** prevents client contention. Privacy policy: no collection of contacts, SMS, location, etc.; **offline licensing** supported. -打开 [tools/README.md](tools/README.md) 查看。 +## Tooling & Evolution +**Six-plus years** of continuous development, with Android 14–16 compatibility fixes and ongoing work on multi-user remote desktop, standardized touch APIs, MCP, Frida versions and stealth, proxy IPv6/UDP, P2P Bridge, and distributed tasks. -如果仍有疑问,请加入社区讨论:[电报 t.me/lamda_dev](https://t.me/lamda_dev) | [gitter.im/lamda-dev](https://gitter.im/lamda-dev/community) \ No newline at end of file +FIRERPA’s strength is not any single feature but integrating **remote desktop (WebRTC/H.264/multi-user), UI automation (virtual display, Watcher, OCR, image match, multi-touch), one-click capture, built-in Frida, full-stack proxy and VPN/frp/P2P, ADB/SSH/built-in terminal, AI MCP/Agent, system-level control, virtual Debian, persistent KV, and script encryption** on one Android device, one service, and one Python client—so security testing, compliance analysis, protocol recovery, fleet management, and automation run in **one pipeline** instead of many glued tools. diff --git a/README.zh.md b/README.zh.md new file mode 100644 index 0000000..03e2ac4 --- /dev/null +++ b/README.zh.md @@ -0,0 +1,104 @@ +# **FIRERPA Android** | AI-Powered Automation + +FIRERPA + +

+ + + + +

+ +
一个集下一代技术于一体的全能型 Android 自动化框架,融合了强大的端侧服务、AI 就绪型智能体以及可扩展的工具调用能力。
+ +

Documentation | 使用文档 | TELEGRAM | QQ 群 | SKILLS | llms-full.txt | 中文版本

+ +FIRERPA 是面向 Android 的**一体化设备控制平台**。服务端直接在设备上运行,**无需额外运行时依赖**;**适配多代 Android 系统**,**无论是否 Root 均可使用**。PC 端通过 Python 客户端库统一调度,将 UI 自动化、远程运维、流量截获、Hook 逆向、网络代理、分布式组网、AI Agent / **MCP** 等能力收敛到同一套服务与 API 中。相比把 Appium、mitmproxy、frida-server、adb、uiautomator2 等自研脚本和各类运维工具拼凑使用,FIRERPA 的优势在于:**能力同源、配置统一、链路打通、适合多设备长期运行与工程化管理**。 + +## 远程桌面与实时投屏 + +FIRERPA 提供基于浏览器的远程桌面,无需在 PC 安装专用客户端即可实时查看和操作设备屏幕,支持局域网与跨网络访问。投屏链路同时支持 **MJPEG** 与 **H.264** 编码,并提供软编/硬编双后端,可按设备算力与网络状况调节帧率(最高 60fps)、分辨率缩放、码率与画质,在弱网环境下显著降低带宽占用、提升流畅度。进一步支持 **WebRTC** 传输,并可配置 STUN/TURN 服务器,改善 NAT 穿透与端到端延迟,适合公网、跨地域远程调试场景。 + +远程桌面支持**多用户同时接入**,便于协作排查、演示与培训。支持**双向剪贴板**共享与**实时音频**推送(Android 10+),并在同一 Web 界面集成终端、文件拖拽上传、目录浏览下载等运维能力,单端口即可完成日常操作。内置**可视化布局检视**:元素高亮、Tab 键遍历、坐标与 RGB 显示、XML 布局树导出,可在远程桌面内直接验证选择器与自动化逻辑,大幅缩短「看界面 → 写脚本」的闭环时间。 + +

+demo +

+ +远程桌面与 RPC 接口支持 **TLS 全链路加密**与服务证书访问控制,可自定义 WebUI 登录密码,降低公网暴露风险。远程桌面能力还可通过 WebSocket 嵌入自有 Web 应用(实时视频、触控、终端、按键等),并可通过 `allow_origin` 配置跨域集成,适合将真机控制能力产品化。 + +## UI 自动化 + +FIRERPA 提供完整的选择器自动化体系,支持 text、resourceId、description、scrollable 等常见匹配方式,并支持 **child / sibling** 链式定位,应对重复元素、层级复杂或无显著特征的控件。元素级支持截图、等待出现/消失、角点/中心坐标获取、exists 判断等细粒度操作,支持 Unicode 输入、step 滑动、fling 快速翻页、滑动至底等交互。可与 Android 8.0+ 上**其他无障碍服务共存**,并增强 WebView 节点查找能力,提升混合应用自动化成功率。 + +**界面监视器(Watcher)** 可实时监听 UI 变化,在满足预设条件时自动执行点击、按键或计数,适用于自动跳过协议弹窗、更新提示、广告页等干扰流程;支持多 Selector 条件组合与事件独立启停,可统计一闪而过界面的出现次数。 + +**虚拟屏幕(Virtual Display)** 是 FIRERPA 的差异化能力之一:可在设备上创建独立的后台显示器,在虚拟屏中运行 App 与自动化脚本,**不影响主屏幕正常使用**——例如虚拟屏后台自动处理消息,主屏仍可刷视频、手动操作。虚拟屏 API 与主设备 API 高度一致,现有 `d.xxx` 脚本可平滑切换为 `vd.xxx`;Watcher 也可限定在虚拟屏内生效。WebUI 支持**多屏幕展示与切换**,可在远程桌面中选择操作或查看不同 Display,适合后台保活、并行任务与「前台人工 + 后台机器」混合场景。 + +针对无标准 View 树的场景(如游戏、自绘 UI),FIRERPA 提供 **OCR** 与**图像匹配**能力。OCR 支持 paddleocr、easyocr 及自定义 HTTP 后端,集群场景可将识别算力集中到服务端,避免每台 PC 重复加载模型;支持 GPU 加速与多种文本匹配模式。图像匹配在**设备端**执行模板匹配或 SIFT,不占 PC 资源;SIFT 对旋转、缩放、光照变化更鲁棒。此外提供**多指触控**能力,支持轨迹录制、重放、代码构造与二进制持久化,可表达复杂手势与压力值。 + +在架构定位上,FIRERPA 采用 C/S 模式,相较 AutoJS 等「设备自跑脚本」方案更利于**中心化调度、版本管理与集群管控**;相较 Appium 更轻量;相较 uiautomator2 在多设备场景下**稳定性更好**。文档将其定位为常见安卓自动化方案的**功能超集**——自动化与抓包、Hook、运维可在同一平台用代码串联,而不必在多套工具间切换。 + +## 抓包与 MITM + +FIRERPA 提供**一键 MITM 抓包**能力:自动安装系统级根证书、配置代理、处理 Android 版本差异,并在退出时自动恢复设备网络状态,无需手工装证、切代理。支持全局抓包、按包名抓包、实时改包、共享 mitmweb 界面,并可通过上游 HTTP 代理实现**国际流量**经指定链路抓包。内置 **QUIC 降级**处理,降低 QUIC 对抓包的影响。即使 PC 与设备仅能通过单一 FIRERPA 端口通信(如 ADB connect、frp 转发),仍可实施抓包,适配苛刻网络拓扑。 + +除一键脚本外,MITM 相关能力均可 API 化:系统级根证书安装/卸载通吃 mitmproxy、Fiddler、Charles 等格式,抓包流程可嵌入自动化流水线,与 UI 操作、Frida Hook 在同一任务中完成。提供 Windows 免 Python 的 `startmitm.exe`,降低环境门槛。 + +## 网络、代理与组网 + +FIRERPA 在设备侧提供完整代理能力,支持 **HTTP / HTTPS / SOCKS5 / Shadowsocks**(多种加密算法),可按 App(含多开 user)粒度代理,支持 DNS 代理、UDP 代理、绕过局域网、与 OpenVPN 共存,并可在服务启动时**自动连接**预设代理。代理能力支持 **IPv6 与 UDP**,适配更复杂的网络环境。 + +**设备 HTTP 桥接代理(tunnel2)** 提供反向代理能力:将 PC 或浏览器的 HTTP 代理指向手机后,流量经手机网络出口转发,对外 IP 与手机一致,可选 Wi‑Fi 或 4G(rmnet)作为出站接口,适合联调、国际化业务、多设备 IP 池等场景。 + +内置 **OpenVPN 客户端**,支持证书、用户名密码及混合认证等多种登录模式,可与系统代理共存;配套提供 **OpenVPN Docker 镜像**与配置生成脚本,自动生成设备侧连接代码与自启动配置,降低手写 OpenVPN 配置出错概率。内置 **frp 客户端**,通过 `fwd.*` 配置即可将设备服务转发至公网服务器,支持加密与 TLS。 + +提供 **mDNS 服务发现**,启用后可通过 `{device_id}.local` 域名访问远程桌面等服务,并可附加 TXT 元数据(型号、ABI、设备 ID 等),便于局域网内批量发现与管理设备。 + +## Frida 与逆向 + +FIRERPA **内置最新版 Frida**,无需单独部署 frida-server;在远程桌面终端内可直接使用 frida 相关命令,无需 `-U`、`-H` 等繁琐参数。内置 Frida 集成开源隐藏补丁与自研隐藏能力,并持续迭代对抗检测;连接采用 Token 鉴权,经 FIRERPA 端口安全接入。提供 objection 补丁与 Frida 17.x java-bridge 封装工具,降低官方变更带来的迁移成本。 + +支持 **Frida 脚本持久化**:App 崩溃或退出后可自动重注入;支持 YAML 脱机持久化、目录监控与热更新。Frida RPC 可**映射为 Python 方法**,并支持 **HTTP + JSON-RPC 2.0** 远程调用,可在 JVM 线程、UI 主线程或纯 JS 上下文执行。脚本内 `emit` 数据可上报至 **HTTP / Redis / RabbitMQ(MQTT)**,附带设备与应用元数据,支持 zlib 压缩、HTTP 重试与 MQTT TLS。 + +## ADB、SSH 与内置终端 + +FIRERPA 提供**独立于系统 ADB 的内置 ADB 服务**,可在**不开启系统开发者模式**的情况下无线连接高权限 ADB,有利于规避「开发者选项」类检测;Magisk 模块可预置 `adb_keys` 实现默认授权。内置 **SSH 服务**与 `ssh.sh` / `scp.sh` 工具脚本,便于远程 Shell 与文件传输。 + +**内置终端**预装 strace、ltrace、tcpdump、scapy、fsmon、frida-tools、MemDumper 等常用分析工具,并集成 Crypto、cv2、unicorn、capstone、keystone、redis、grpc 等 Python 生态。sqlite3 支持 **wxsqlite / sqlcipher / sqlcrypto**,为读取微信、企微、阿里系等加密数据库提供路径。内置终端与远程桌面、SSH、ADB 统一,可在设备端直接完成分析与自控,减少 PC ↔ 设备来回切换。 + +## 分布式与多设备管理 + +FIRERPA **星连平台(Hub + hub-bridge)** 提供中心化多设备接入方案,支持本地与远程设备统一管理与 **P2P** 访问,适合「设备在家、人在外」及多机协作场景。hub v3 采用 **SAPI** 统一请求,可同时连接本地与远程设备。除 Hub 外,亦支持 **frp 转发**与 **OpenVPN 组网**两种自建方案,文档与 Docker 配套完整。 + +9.0 起支持 **P2P Bridge 端端互联**与**内置分布式任务系统**;7.50 起提供组网订阅服务,可无需自建 frp/OpenVPN。整体面向云手机池、真机农场、模拟器集群等**批量设备、长期在线、无人值守**场景设计。 + +## AI / MCP / Agent + +FIRERPA **内置 MCP 服务端**(API 路径 `/mcp/`),采用 **streamable-http** 协议,支持 tool call、resource read、prompts、进度通知与日志,可对接 **Claude、Cursor** 等 MCP 客户端。提供官方 MCP 扩展模块,支持在 `~/modules/extension` 自定义插件。 + +内置 **`agent` 命令**,对接任意 OpenAI 兼容 API + tool call,以**自然语言**驱动设备操作,支持 **Vision 视觉模式**;提供 **OpenAI 语义化任务执行器**。可与 **crontab** 组合实现定时 AI 任务,将大模型能力嵌入真机自动化流水线。 + +

+MCP +

+ +## 系统级控制与扩展 + +FIRERPA 提供 **160+ Python API**,覆盖应用操作、文件 I/O、命令执行、设备状态、WiFi、代理、OpenVPN、关机重启、服务日志等。`setprop` 可写入 **`ro.*`** 只读系统属性;Settings API 可读写亮度、开发者模式等系统设置;**SELinux** 支持域创建、permissive 切换与细粒度 allow/disallow 规则。支持多开 App 全 API、静默授权/撤销权限、启动未导出 Activity、禁用 App 以节省资源。 + +提供**二进制补丁**能力(十六进制通配符、glob 路径、dryrun),以及磁盘、电池、CPU、内存、网络 IO 等**运行时指标**采集。支持播放 WAV 音频、内部存储 API。设备端集成 **tflite-runtime** 且支持调用硬件计算,支持端侧推理。 + +**虚拟 Debian 环境**可在设备内运行完整 Debian 与 apt,编译 BPF、部署 OpenSSH、运行任意 Python 依赖,突破内置终端不可 pip/apt 的限制。 + +## 部署、安全与治理 + +FIRERPA 支持 **APP 一键部署**(Root 或 Shizuku)、**Magisk 模块开机自启**、手动解压与 **ROM 内置**等多种落地方式,面向 **7×24** 常驻运行设计。APP 内可**自动生成服务证书**;Magisk zip 可预置 properties 配置、lamda.pem 证书与 adb_keys。全部行为由 **ini 格式服务配置**集中管理:端口、WebUI 参数、证书、OpenVPN、gproxy、frp、tunnel2、cron、sshd、adb、mdns 等;WebUI 支持可视化编辑、Raw 文本模式与 Reload Service 热重载,并支持从远程文件服务器加载配置。 + +支持 root 与 shell 双身份运行;覆盖 arm64、armeabi-v7a、x86、x86_64 等架构;兼容真机、雷电/夜神等模拟器、Redroid、WSA、AVD、云手机等环境。卸载路径规范,不在系统中散落垃圾文件。**API 独占锁**防止多客户端争抢同一设备。隐私政策明确不收集通讯录、短信、地理位置等个人隐私,并支持**离线授权**。 + +## 工具生态与长期演进 + +FIRERPA 持续演进 **6 年+**,覆盖 Android 14–16 兼容修复,在远程桌面多用户、标准化触摸 API、MCP 协议、Frida 版本与隐藏性、代理 IPv6/UDP、P2P Bridge、分布式任务等方面持续迭代。 + +FIRERPA 的核心优势不在于某一单项功能,而在于将**远程桌面(WebRTC/H.264/多用户)、UI 自动化(含虚拟屏/Watcher/OCR/图匹配/多指触控)、一键抓包、内置 Frida、全栈代理与 VPN/frp/P2P 组网、ADB/SSH/内置终端、AI MCP/Agent、系统级控制、虚拟 Debian、持久 KV、脚本加密**等能力集成在同一台 Android 设备、同一套服务与同一 Python 客户端中,使安全测试、合规分析、协议还原、批量管控与自动化业务可以在**一条流水线**里完成,而不是在多个工具之间反复拼接。 \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md index d3b8d9f..2307ed6 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,8 +4,8 @@ | Version | Supported | | ------- | ------------------ | -| 3.x | :white_check_mark: | +| 10.x | :white_check_mark: | ## Reporting a Vulnerability -mailto:ihaven0emmail@gmail.com +mailto:lamda.devel@gmail.com diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..7eb5d87 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,3 @@ +This directory contains scripts that demonstrate how to write a simple automation script, how to create mountable modules, and how to leverage the tflite inference capability within the internal environment. + +API Document: https://device-farm.com/doc/ \ No newline at end of file diff --git a/examples/activity_jump.py b/examples/activity_jump.py new file mode 100644 index 0000000..87df09e --- /dev/null +++ b/examples/activity_jump.py @@ -0,0 +1,19 @@ +# 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) \ No newline at end of file diff --git a/examples/image-detection-and-segmentation/README.md b/examples/image-detection-and-segmentation/README.md new file mode 100644 index 0000000..dc3b23f --- /dev/null +++ b/examples/image-detection-and-segmentation/README.md @@ -0,0 +1 @@ +The scripts in this directory are intended to run in the firerpa built-in shell. They demonstrate how to perform image segmentation and recognition, while also showcasing the ability to execute automation scripts directly on the device itself. \ No newline at end of file diff --git a/examples/image-detection-and-segmentation/detect.py b/examples/image-detection-and-segmentation/detect.py new file mode 100644 index 0000000..58a8e75 --- /dev/null +++ b/examples/image-detection-and-segmentation/detect.py @@ -0,0 +1,21 @@ +import cv2 +import numpy as np +try: + from lamda.tflite import ObjectDetection, draw_detected, create_opencl_delegate, create_cpu_delegate +except: + print ("Please run this script in latest firerpa internal shell") + exit(1) + +image = cv2.imread("detection.png") + + +classes = ['乌龟', '企鹅', '伞', '兔子', '冰激凌', '凤梨', '包', '南瓜', '吉他', '大象', '太阳花', '宇航员', '帐篷', '帽子', '房子', '挂锁', '杯子', '松鼠', '枕头', '树', '树袋熊', '椅子', '气球', '汉堡包', '熊猫', '玫瑰花', '瓢虫', '瓶子', '电话', '皇冠', '篮子', '耳机', '花盆', '苹果', '草莓', '蘑菇', '蛋糕', '蝴蝶', '裙子', '账篷', '足球', '车', '轮胎', '铲土机', '闹钟', '鞋', '马', '鱼', '鸟', '鸭子'] + +delegate = create_opencl_delegate() +detector = ObjectDetection(model="example_detection_model.tflite", delegate=delegate, confidence=0.8, iou=0.45, classes=classes) +results = detector.detect(image) + +for bound, confidence, class_id, name in results: print("confidence:", confidence, "class:", class_id, "name:", name) + +segmented = draw_detected(results, image) +cv2.imwrite("detection_output.jpg", segmented) \ No newline at end of file diff --git a/examples/image-detection-and-segmentation/detection.png b/examples/image-detection-and-segmentation/detection.png new file mode 100644 index 0000000..1b21e2c Binary files /dev/null and b/examples/image-detection-and-segmentation/detection.png differ diff --git a/examples/image-detection-and-segmentation/detection_output.jpg b/examples/image-detection-and-segmentation/detection_output.jpg new file mode 100644 index 0000000..553e411 Binary files /dev/null and b/examples/image-detection-and-segmentation/detection_output.jpg differ diff --git a/examples/image-detection-and-segmentation/example_detection_model.tflite b/examples/image-detection-and-segmentation/example_detection_model.tflite new file mode 100644 index 0000000..a7b2dea --- /dev/null +++ b/examples/image-detection-and-segmentation/example_detection_model.tflite @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fda84b72cafb12e03f93063d29b1b28936cd8dde1598ede2b22b39d3bef12e70 +size 5393795 diff --git a/examples/image-detection-and-segmentation/example_segmentation_model.tflite b/examples/image-detection-and-segmentation/example_segmentation_model.tflite new file mode 100644 index 0000000..c3c602a --- /dev/null +++ b/examples/image-detection-and-segmentation/example_segmentation_model.tflite @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6532fa29ed7019c069f912a137839ee1752d21b43adbf3ee72e75fd871a1ee3 +size 6712740 diff --git a/examples/image-detection-and-segmentation/segmentation.png b/examples/image-detection-and-segmentation/segmentation.png new file mode 100644 index 0000000..dc10d8a Binary files /dev/null and b/examples/image-detection-and-segmentation/segmentation.png differ diff --git a/examples/image-detection-and-segmentation/segmentation.py b/examples/image-detection-and-segmentation/segmentation.py new file mode 100644 index 0000000..5c18322 --- /dev/null +++ b/examples/image-detection-and-segmentation/segmentation.py @@ -0,0 +1,19 @@ +import cv2 +import numpy as np +try: + from lamda.tflite import InstanceSegmentation, draw_segmented, create_opencl_delegate, create_cpu_delegate +except: + print ("Please run this script in latest firerpa internal shell") + exit(1) + +image = cv2.imread("segmentation.png") + +delegate = create_opencl_delegate() +segmenter = InstanceSegmentation(model="example_segmentation_model.tflite", delegate=delegate, min_area=8000, split=True) +results = segmenter.detect(image) + +print("num instances:", len(results)) +for box, score, class_id, class_name, mask, contours in results: print(class_id, class_name, float(score), mask.shape, len(contours)) + +segmented = draw_segmented(results, image) +cv2.imwrite("segmentation_output.jpg", segmented) \ No newline at end of file diff --git a/examples/image-detection-and-segmentation/segmentation_output.jpg b/examples/image-detection-and-segmentation/segmentation_output.jpg new file mode 100644 index 0000000..bacde34 Binary files /dev/null and b/examples/image-detection-and-segmentation/segmentation_output.jpg differ diff --git a/examples/search_in_taobao.py b/examples/search_in_taobao.py new file mode 100644 index 0000000..6fa8722 --- /dev/null +++ b/examples/search_in_taobao.py @@ -0,0 +1,51 @@ +# 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() + +# ... \ No newline at end of file diff --git a/examples/user-home/README.md b/examples/user-home/README.md new file mode 100644 index 0000000..fa4a60f --- /dev/null +++ b/examples/user-home/README.md @@ -0,0 +1,3 @@ +These directory structures are consistent with those on the device; you can view them by running the command `ls -l ~/modules` in the built-in shell. + +See Doc: https://device-farm.com/docs/en/model-extension or https://device-farm.com/docs/en/frida-persist \ No newline at end of file diff --git a/examples/user-home/modules/extension/example_http_extension.py b/examples/user-home/modules/extension/example_http_extension.py new file mode 100644 index 0000000..86d0163 --- /dev/null +++ b/examples/user-home/modules/extension/example_http_extension.py @@ -0,0 +1,37 @@ +# 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") \ No newline at end of file diff --git a/examples/user-home/modules/extension/example_mcp_extension.py b/examples/user-home/modules/extension/example_mcp_extension.py new file mode 100644 index 0000000..d99b08b --- /dev/null +++ b/examples/user-home/modules/extension/example_mcp_extension.py @@ -0,0 +1,28 @@ +# 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") \ No newline at end of file diff --git a/examples/user-home/modules/extension/firerpa.py b/examples/user-home/modules/extension/firerpa.py new file mode 100644 index 0000000..4ce1b74 --- /dev/null +++ b/examples/user-home/modules/extension/firerpa.py @@ -0,0 +1,197 @@ +# 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=(",", ":")) \ No newline at end of file diff --git a/examples/user-home/modules/extension/mcp_return_types.py b/examples/user-home/modules/extension/mcp_return_types.py new file mode 100644 index 0000000..edb1f60 --- /dev/null +++ b/examples/user-home/modules/extension/mcp_return_types.py @@ -0,0 +1,124 @@ +#!/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 +# +# 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]] \ No newline at end of file diff --git a/examples/user-home/modules/extension/mcp_sms_reader.py b/examples/user-home/modules/extension/mcp_sms_reader.py new file mode 100644 index 0000000..60ae36e --- /dev/null +++ b/examples/user-home/modules/extension/mcp_sms_reader.py @@ -0,0 +1,32 @@ +# 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) \ No newline at end of file diff --git a/examples/user-home/modules/script/disable_flag_secure.yaml b/examples/user-home/modules/script/disable_flag_secure.yaml new file mode 100644 index 0000000..1729dc5 --- /dev/null +++ b/examples/user-home/modules/script/disable_flag_secure.yaml @@ -0,0 +1,9 @@ +# 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 \ No newline at end of file diff --git a/examples/user-home/modules/script/disable_ssl_pinning_simple.yaml b/examples/user-home/modules/script/disable_ssl_pinning_simple.yaml new file mode 100644 index 0000000..1208149 --- /dev/null +++ b/examples/user-home/modules/script/disable_ssl_pinning_simple.yaml @@ -0,0 +1,9 @@ +# 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 \ No newline at end of file diff --git a/examples/user-home/modules/script/enhanced_automation_wechat.yaml b/examples/user-home/modules/script/enhanced_automation_wechat.yaml new file mode 100644 index 0000000..8369274 --- /dev/null +++ b/examples/user-home/modules/script/enhanced_automation_wechat.yaml @@ -0,0 +1,10 @@ +enable: true +application: "android" +process: "system" +version: "N/A" +user: 0 +runtime: "qjs" +script: !!binary "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" +encode: "none" +standup: 10 +spawn: false \ No newline at end of file diff --git a/image/automation.gif b/image/automation.gif deleted file mode 100644 index a0bef37..0000000 Binary files a/image/automation.gif and /dev/null differ diff --git a/image/detection.gif b/image/detection.gif deleted file mode 100644 index ac9fb24..0000000 Binary files a/image/detection.gif and /dev/null differ diff --git a/image/inspect.gif b/image/inspect.gif deleted file mode 100644 index d637aa1..0000000 Binary files a/image/inspect.gif and /dev/null differ diff --git a/image/lamda.gif b/image/lamda.gif deleted file mode 100644 index 9848517..0000000 Binary files a/image/lamda.gif and /dev/null differ diff --git a/image/listing.gif b/image/listing.gif deleted file mode 100644 index be0ca40..0000000 Binary files a/image/listing.gif and /dev/null differ diff --git a/image/logo-aliyun.png b/image/logo-aliyun.png deleted file mode 100644 index 684e94d..0000000 Binary files a/image/logo-aliyun.png and /dev/null differ diff --git a/image/logo-tencent.svg b/image/logo-tencent.svg deleted file mode 100644 index 53347bf..0000000 --- a/image/logo-tencent.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - logo - - - - \ No newline at end of file diff --git a/image/logo.svg b/image/logo.svg index adabe3c..a4fc993 100644 --- a/image/logo.svg +++ b/image/logo.svg @@ -1 +1,34 @@ - \ No newline at end of file + + + background + + + + Layer 1 + + + + + + + + + + + + + + + + + + + + + + + + + LAMDA + + \ No newline at end of file diff --git a/image/mitm.gif b/image/mitm.gif deleted file mode 100644 index 211d765..0000000 Binary files a/image/mitm.gif and /dev/null differ diff --git a/image/upload.gif b/image/upload.gif deleted file mode 100644 index 7d3b696..0000000 Binary files a/image/upload.gif and /dev/null differ diff --git a/lamda/__init__.py b/lamda/__init__.py index 7fc7cf7..7bea5e8 100644 --- a/lamda/__init__.py +++ b/lamda/__init__.py @@ -1,5 +1,5 @@ -# Copyright 2022 rev1si0n (ihaven0emmail@gmail.com). All rights reserved. +# 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__ = "3.157" +__version__ = "10.8" diff --git a/lamda/bcast.proto b/lamda/bcast.proto deleted file mode 100644 index 2d368e5..0000000 --- a/lamda/bcast.proto +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2022 rev1si0n (ihaven0emmail@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; - -message BcastHeader { - // magic always 0x54534143 ("CAST") - uint32 magic = 1; - // protocol version - string version = 2; -} - -message BcastDiscoverInfo { - string ID = 1; - uint64 uptime = 2; - string device = 3; - string abi = 4; - uint32 sdk = 5; - string version = 6; -} - -message BcastResponse { - BcastHeader header = 1; -oneof body { - BcastDiscoverInfo discoverinfo = 10; - int32 errno = 11; - bytes data = 12; -} -} - -message BcastRequest { - BcastHeader header = 1; - string method = 2; -} diff --git a/lamda/client.py b/lamda/client.py index 33aaa14..1e9a7fc 100644 --- a/lamda/client.py +++ b/lamda/client.py @@ -10,18 +10,27 @@ import time import uuid import json +import base64 +import posixpath +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 on windows py310 +# 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 @@ -36,14 +45,21 @@ from . import __version__ from . types import AttributeDict, BytesIO -from . exceptions import UnHandledException +from . exceptions import (UnHandledException, DuplicateEntryError, + InvalidArgumentError, UiObjectNotFoundException, + IllegalStateException, InvalidOperationError) from . import exceptions -logger = logging.getLogger("lamda") -FORMAT = "%(asctime)s %(process)d %(levelname)7s@%(module)s:%(funcName)s - %(message)s" +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__ = [ @@ -52,18 +68,34 @@ "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", + "ImePolicy", + "AudioStreamType", + "PlayAudioProfile", + "ApplicationInfo", "Selector", "TouchWait", "TouchMove", @@ -82,6 +114,10 @@ def getXY(p): return p.x, p.y +def checkArgumentTyp(a, types): + if not isinstance(a, types): + raise InvalidArgumentError(a) + def touchSequenceSave(s, fpath): return BytesIO(s.SerializeToString()).save(fpath) @@ -142,6 +178,20 @@ def center(b): 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), @@ -152,6 +202,9 @@ def corner(b, position): Direction = protos.Direction GproxyType = protos.GproxyType GrantType = protos.GrantType +ScriptRuntime = protos.ScriptRuntime +DataEncode = protos.DataEncode +ImePolicy = protos.ImePolicy Group = protos.Group Key = protos.Key @@ -160,13 +213,20 @@ def corner(b, position): 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 @@ -182,7 +242,7 @@ def corner(b, position): ApplicationInfo = protos.ApplicationInfo # uiautomator types -Selector = protos.Selector +_Selector = protos.Selector Bound = protos.Bound Point = protos.Point @@ -202,44 +262,177 @@ def corner(b, position): 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 文件 """ + """Load related proto files from the package.""" return grpc.protos_and_services(name) def to_dict(prot): - """ 将 proto 返回值转换为字典 """ + """Convert a proto response to a dict.""" r = MessageToJson(prot, preserving_proto_field_name=True) return json.loads(r) +def Selector(**kwargs): + """ Selector wrapper """ + fields = set(kwargs.pop("fields", [])) + fields.update(kwargs.keys()) + sel = _Selector(**kwargs, fields=fields) + 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, z=128, contact=0): + self.s.appendDown(tid=contact, x=x, y=y, + pressure=z) + return self + def move(self, x, y, z=128, contact=0): + self.s.appendMove(tid=contact, x=x, y=y, + pressure=z) + return self + def up(self, contact=0): + self.s.appendUp(tid=contact) + 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 MultiTouchContact: + def __init__(self, builder, track): + self.builder = builder + self.track = track + def down(self, x, y, z=128): + self.builder.down(x, y, z=z, contact=self.track) + return self + def move(self, x, y, z=128): + self.builder.move(x, y, z=z, contact=self.track) + return self + def wait(self, mills): + self.builder.wait(mills) + return self + def up(self): + self.builder.up(contact=self.track) + return self + + +class MultiTouchOpStub: + def __init__(self, caller, track=0, + builder=None): + self.stub = caller.stub + self.builder = builder or TouchBuilder() + self.track = track + def contact(self, id): + return MultiTouchContact(self.builder, id) + def wait(self, mills): + self.builder.wait(mills) + def reset(self): + self.builder.s.ClearField("sequence") + def record(self): + ts = self.stub.recordTouch(protos.Empty()) + self.builder.s.CopyFrom(ts) + def load(self, fpath): + ts = self.builder.s.load(fpath) + self.builder.s.CopyFrom(ts) + def save(self, fpath): + return self.builder.s.save(fpath) + def perform(self, wait=True): + tas = self.builder.build() + req = protos.PerformTouchRequest(sequence=tas, wait=wait) + r = self.stub.performTouch(req) + return r.value + + class ClientLoggingInterceptor(ClientInterceptor): def truncate_string(self, s): return "{:.1024}...".format(s) if len(s) > 1024 else s def intercept(self, function, request, details): """ - 日志记录各个接口的调用及参数 + Log API calls and arguments. """ displayable = isinstance(request, Message) args = MessageToDict(request) if displayable else "-" @@ -251,27 +444,24 @@ def intercept(self, function, request, details): class ClientSessionMetadataInterceptor(ClientInterceptor): - def get_instance_ID(self): - return "{:06d}{:010d}".format(os.getpid(), id(self)) - + def __init__(self, session): + super(ClientSessionMetadataInterceptor, self).__init__() + self.session = session def intercept(self, function, request, details): - """ - 在每次远程调用加上本实例的ID用于实现锁功能 - """ metadata = {} metadata["version"] = __version__ - metadata["instance"] = self.get_instance_ID() - metadata["hostname"] = quote(platform.node()) - metadata["python_branch"] = platform.python_branch() + 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()) - res = function(request, details) - return res + return function(request, details) class GrpcRemoteExceptionInterceptor(ClientInterceptor): def intercept(self, function, request, details): """ - 处理远程调用中发生的异常并抛出本地异常 + Handle remote call errors and raise local exceptions. """ res = function(request, details) self.raise_remote_exception(res) @@ -286,47 +476,47 @@ def remote_exception(self, exception): return clazz(*args) def raise_remote_exception(self, res): - metadata = dict(res.initial_metadata()) + metadata = dict(res.initial_metadata() or []) exception = metadata.get("exception", None) if exception != None: raise self.remote_exception(exception) class ObjectUiAutomatorOpStub: - def __init__(self, stub, selector): + def __init__(self, caller, selector, display): """ - UiAutomator 子接口,用来模拟出实例的意味 + UiAutomator sub-interface that behaves like an instance. """ + self.display = display self._selector = selector - self.selector = protos.Selector(**selector) - self.stub = stub + 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) + return "Object@{}: {}".format(self.display, selector) __repr__ = __str__ - def _child_sibling(self, name, **selector): - s = copy.deepcopy(self._selector) - s.setdefault("childOrSibling", []) - s.setdefault("childOrSiblingSelector", []) - s["childOrSiblingSelector"].append(selector) - s["childOrSibling"].append(name) - return self.__class__(self.stub, s) def child(self, **selector): """ - 匹配选择器里面的子节点 + Match child nodes within the selector. """ - return self._child_sibling("child", **selector) + selector = self.selector.child(**selector) + s = MessageToDict(selector, preserving_proto_field_name=True) + return self.__class__(self.caller, s, self.display) def sibling(self, **selector): """ - 匹配选择器的同级节点 + Match sibling nodes of the selector. """ - return self._child_sibling("sibling", **selector) + selector = self.selector.sibling(**selector) + s = MessageToDict(selector, preserving_proto_field_name=True) + return self.__class__(self.caller, s, self.display) def take_screenshot(self, quality=100): """ - 对选择器选中元素进行截图 + Screenshot the selected element. """ - req = protos.SelectorTakeScreenshotRequest(selector=self.selector, + req = protos.SelectorTakeScreenshotRequest(display=self.display, + selector=self.selector, quality=quality) r = self.stub.selectorTakeScreenshot(req) return BytesIO(r.value) @@ -334,218 +524,316 @@ def screenshot(self, quality=100): return self.take_screenshot(quality=quality) def get_text(self): """ - 获取选择器选中输入控件中的文本 + Get text from the selected input field. """ - req = protos.SelectorOnlyRequest(selector=self.selector) + req = protos.SelectorOnlyRequest(display=self.display, + selector=self.selector) r = self.stub.selectorGetText(req) return r.value def clear_text_field(self): """ - 清空选择器选中输入控件中的文本 + Clear text in the selected input field. """ - req = protos.SelectorOnlyRequest(selector=self.selector) + req = protos.SelectorOnlyRequest(display=self.display, + selector=self.selector) r = self.stub.selectorClearTextField(req) return r.value def set_text(self, text): """ - 向选择器选中输入控件中填入文本 + Fill text into the selected input field. """ - req = protos.SelectorSetTextRequest(selector=self.selector, + req = protos.SelectorSetTextRequest(display=self.display, + selector=self.selector, text=text) r = self.stub.selectorSetText(req) return r.value def click(self, corner=Corner.COR_CENTER): """ - 点击选择器选中的控件 + Click the selected widget. """ - req = protos.SelectorClickRequest(selector=self.selector, + req = protos.SelectorClickRequest(display=self.display, + selector=self.selector, corner=corner) r = self.stub.selectorClick(req) return r.value def click_exists(self, corner=Corner.COR_CENTER): """ - 点击选择器选中的控件(不存在将不会产生异常) + Click the selected widget without raising if missing. """ - req = protos.SelectorClickRequest(selector=self.selector, + req = protos.SelectorClickRequest(display=self.display, + selector=self.selector, corner=corner) r = self.stub.selectorClickExists(req) return r.value - def click_exist(self, *args, **kwargs): - # deprecated - warnings.warn("use d(..).click_exists() instead", DeprecationWarning) - return self.click_exists(*args, **kwargs) - def long_click(self, corner=Corner.COR_CENTER): + def long_click(self, corner=Corner.COR_CENTER, timeout=0): """ - 长按选择器选中的控件 + Long-click the selected widget. """ - req = protos.SelectorClickRequest(selector=self.selector, - corner=corner) - r = self.stub.selectorClickExists(req) + req = protos.SelectorClickRequest(display=self.display, + selector=self.selector, + corner=corner, + timeout=timeout) + r = self.stub.selectorLongClick(req) return r.value def exists(self): """ - 是否存在选择器选中的控件 + Check whether the selected widget exists. """ - req = protos.SelectorOnlyRequest(selector=self.selector) + req = protos.SelectorOnlyRequest(display=self.display, + selector=self.selector) r = self.stub.selectorExists(req) return r.value - def exist(self, *args, **kwargs): - # deprecated - warnings.warn("use d(..).exists() instead", DeprecationWarning) - return self.exists(*args, **kwargs) def info(self): """ - 获取选择器选中控件的信息 + Get info for the selected widget. """ - req = protos.SelectorOnlyRequest(selector=self.selector) + req = protos.SelectorOnlyRequest(display=self.display, + selector=self.selector) return self.stub.selectorObjInfo(req) - def info_of_all_instances(self): - """ - 获取选择器选中的所有控件的信息 - """ - req = protos.SelectorOnlyRequest(selector=self.selector) - r = self.stub.selectorObjInfoOfAllInstances(req) - return r.objects + def _chain(self, **kwargs): + selector = copy.deepcopy(self._selector) + child_sibling = selector.get("childOrSiblingSelector") + target = child_sibling[-1] if child_sibling else selector + target.update(**kwargs) + fields = set(target.pop("fields", [])) + fields.update(target.keys()) + target["fields"] = fields + return self.caller(**selector) + def text(self, txt): + return self._chain(text=txt) + def resourceId(self, name): + return self._chain(resourceId=name) + def description(self, desc): + return self._chain(description=desc) + def packageName(self, name): + return self._chain(packageName=name) + def className(self, name): + return self._chain(className=name) + def textContains(self, needle): + return self._chain(textContains=needle) + def descriptionContains(self, needle): + return self._chain(descriptionContains=needle) + def textStartsWith(self, needle): + return self._chain(textStartsWith=needle) + def descriptionStartsWith(self, needle): + return self._chain(descriptionStartsWith=needle) + def textMatches(self, match): + return self._chain(textMatches=match) + def descriptionMatches(self, match): + return self._chain(descriptionMatches=match) + def resourceIdMatches(self, match): + return self._chain(resourceIdMatches=match) + def packageNameMatches(self, match): + return self._chain(packageNameMatches=match) + def classNameMatches(self, match): + return self._chain(classNameMatches=match) + def checkable(self, value): + return self._chain(checkable=value) + def clickable(self, value): + return self._chain(clickable=value) + def focusable(self, value): + return self._chain(focusable=value) + def scrollable(self, value): + return self._chain(scrollable=value) + def longClickable(self, value): + return self._chain(longClickable=value) + def enabled(self, value): + return self._chain(enabled=value) + def checked(self, value): + return self._chain(checked=value) + def focused(self, value): + return self._chain(focused=value) + def selected(self, value): + return self._chain(selected=value) + def resultIndex(self, idx): + return self._chain(resultIndex=idx) + def instance(self, idx): + return self._chain(instance=idx) + def index(self, idx): + return self._chain(index=idx) + def __iter__(self): + """ + Iterate over all elements matching the selector. + """ + yield from [self.resultIndex(i) for i in \ + range(self.count())] + def get(self, idx): + """ + Get the Nth matching result of the selector. + """ + return self.resultIndex(idx) def count(self): """ - 获取选择器选中控件的数量 + Get the number of selected widgets. """ - req = protos.SelectorOnlyRequest(selector=self.selector) + req = protos.SelectorOnlyRequest(display=self.display, + 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): """ - 将选择器选中的控件拖动到另一个选择器上 + Drag the selected widget to another selector or point. """ - req = protos.SelectorDragToRequest(selector=self.selector, - target=target, + checkArgumentTyp(target, (Point, _Selector)) + func = "_set_target_{}".format(target.DESCRIPTOR.name) + req = protos.SelectorDragToRequest(display=self.display, + selector=self.selector, step=step) - r = self.stub.selectorCount(req) + getattr(self, func)(req, target) + r = self.stub.selectorDragTo(req) return r.value def wait_for_exists(self, timeout): """ - 等待选择器选中控件出现 + Wait for the selected widget to appear. """ - req = protos.SelectorWaitRequest(selector=self.selector, + req = protos.SelectorWaitRequest(display=self.display, + selector=self.selector, timeout=timeout) r = self.stub.selectorWaitForExists(req) return r.value def wait_until_gone(self, timeout): """ - 等待选择器选中控件消失 + Wait for the selected widget to disappear. """ - req = protos.SelectorWaitRequest(selector=self.selector, + req = protos.SelectorWaitRequest(display=self.display, + selector=self.selector, timeout=timeout) r = self.stub.selectorWaitUntilGone(req) return r.value def swipe(self, direction=Direction.DIR_UP, step=32): """ - 在选择器选中的元素上进行滑动操作 + Swipe on the selected element. """ - req = protos.SelectorSwipeRequest(selector=self.selector, + req = protos.SelectorSwipeRequest(display=self.display, + selector=self.selector, direction=direction, step=step) r = self.stub.selectorSwipe(req) return r.value def pinch_in(self, percent, step=16): """ - 双指捏紧(缩小) + Pinch in. """ - req = protos.SelectorPinchRequest(selector=self.selector, + req = protos.SelectorPinchRequest(display=self.display, + selector=self.selector, percent=percent, step=step) r = self.stub.selectorPinchIn(req) return r.value def pinch_out(self, percent, step=16): """ - 双指放开(放大) + Pinch out. """ - req = protos.SelectorPinchRequest(selector=self.selector, - percent=percent, step=step) + req = protos.SelectorPinchRequest(display=self.display, + selector=self.selector, + percent=percent, step=step) r = self.stub.selectorPinchOut(req) return r.value + def scroll_to(self, target, is_vertical=True): + """ + Scroll a scrollable view until the target selector matches. + """ + checkArgumentTyp(target, _Selector) + req = protos.SelectorScrollRequest(display=self.display, + 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, + req = protos.SelectorFlingRequest(display=self.display, + 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, + req = protos.SelectorFlingRequest(display=self.display, + 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, + req = protos.SelectorFlingRequest(display=self.display, + 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, + req = protos.SelectorFlingRequest(display=self.display, + selector=self.selector, maxSwipes=max_swipes, vertical=is_vertical) r = self.stub.selectorFlingToBeginning(req) return r.value def fling_from_top_to_bottom(self): """ - 在选择器选中元素上进行从上至下阅读式滑动(单次) + Perform one top-to-bottom reading swipe on the selected element. """ return self._fling_backward(is_vertical=True) def fling_from_bottom_to_top(self): """ - 在选择器选中元素上进行从下至上阅读式滑动(单次) + Perform one bottom-to-top reading swipe on the selected element. """ return self._fling_forward(is_vertical=True) def fling_from_left_to_right(self): """ - 在选择器选中元素上进行从左至右阅读式滑动(单次) + Perform one left-to-right reading swipe on the selected element. """ return self._fling_backward(is_vertical=False) def fling_from_right_to_left(self): """ - 在选择器选中元素上进行从右至左阅读式滑动(单次) + Perform one right-to-left reading swipe on the selected element. """ return self._fling_forward(is_vertical=False) def fling_from_top_to_bottom_to_end(self, max_swipes): """ - 在选择器选中元素上进行从上至下阅读式滑动直至无法滑动或达到 max_swipes 次 + Swipe top-to-bottom until scrolling stops or max_swipes is reached. """ return self._fling_to_beginning(max_swipes, is_vertical=True) def fling_from_bottom_to_top_to_end(self, max_swipes): """ - 在选择器选中元素上进行从下至上阅读式滑动直至无法滑动或达到 max_swipes 次 + Swipe bottom-to-top until scrolling stops or max_swipes is reached. """ return self._fling_to_end(max_swipes, is_vertical=True) def fling_from_left_to_right_to_end(self, max_swipes): """ - 在选择器选中元素上进行从左至右阅读式滑动直至无法滑动或达到 max_swipes 次 + Swipe left-to-right until scrolling stops or max_swipes is reached. """ return self._fling_to_beginning(max_swipes, is_vertical=False) def fling_from_right_to_left_to_end(self, max_swipes): """ - 在选择器选中元素上进行从右至左阅读式滑动直至无法滑动或达到 max_swipes 次 + Swipe right-to-left until scrolling stops or max_swipes is reached. """ return self._fling_to_end(max_swipes, is_vertical=False) def _scroll_forward(self, step, is_vertical=True): - req = protos.SelectorScrollRequest(selector=self.selector, + req = protos.SelectorScrollRequest(display=self.display, + 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, + req = protos.SelectorScrollRequest(display=self.display, + 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, + req = protos.SelectorScrollRequest(display=self.display, + 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, + req = protos.SelectorScrollRequest(display=self.display, + selector=self.selector, maxSwipes=max_swipes, vertical=is_vertical, step=step) @@ -553,472 +841,856 @@ def _scroll_to_beginning(self, max_swipes, step, is_vertical=True): return r.value def scroll_from_top_to_bottom(self, step): """ - 在选择器选中元素上进行从上至下普通滑动 + Perform a normal top-to-bottom swipe on the selected element. """ return self._scroll_backward(step, is_vertical=True) def scroll_from_bottom_to_top(self, step): """ - 在选择器选中元素上进行从下至上普通滑动 + Perform a normal bottom-to-top swipe on the selected element. """ return self._scroll_forward(step, is_vertical=True) def scroll_from_left_to_right(self, step): """ - 在选择器选中元素上进行从左至右普通滑动 + Perform a normal left-to-right swipe on the selected element. """ return self._scroll_backward(step, is_vertical=False) def scroll_from_right_to_left(self, step): """ - 在选择器选中元素上进行从右至左普通滑动 + Perform a normal right-to-left swipe on the selected element. """ return self._scroll_forward(step, is_vertical=False) def scroll_from_top_to_bottom_to_end(self, max_swipes, step): """ - 在选择器选中元素上进行从上至下普通滑动直至无法滑动或达到 max_swipes 次 + Repeat normal top-to-bottom swipes until scrolling stops or max_swipes is reached. """ 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 次 + Repeat normal bottom-to-top swipes until scrolling stops or max_swipes is reached. """ 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 次 + Repeat normal left-to-right swipes until scrolling stops or max_swipes is reached. """ 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 次 + Repeat normal right-to-left swipes until scrolling stops or max_swipes is reached. """ return self._scroll_to_end(max_swipes, step, is_vertical=False) class UiAutomatorStub(BaseServiceStub): - def __init__(self, *args, **kwargs): + def __init__(self, *args, display=0, **kwargs): + self.display = display super(UiAutomatorStub, self).__init__(*args, **kwargs) - self.watchers = defaultdict(dict) def device_info(self): """ - 获取设备基本/分辨率等信息 + Get device and display info. """ r = self.stub.deviceInfo(protos.Empty()) return r def set_watcher_loop_enabled(self, enabled): """ - 设置是否启用设备上的 watcher UI 检测 + Enable or disable watcher UI checks on the device. """ - req = protos.Boolean(value=enabled) + req = protos.WatcherControlRequest(display=self.display, + enable=enabled) r = self.stub.setWatcherLoopEnabled(req) return r.value def get_watcher_loop_enabled(self): """ - 获取是否启用设备上的 watcher UI 检测 + Check whether watcher UI checks are enabled. """ - r = self.stub.getWatcherLoopEnabled(protos.Empty()) + req = protos.WatcherControlRequest(display=self.display) + r = self.stub.getWatcherLoopEnabled(req) return r.value def get_watcher_triggered_count(self, name): """ - 获取这个 watcher 被触发的次数 + Get how many times this watcher was triggered. """ - req = protos.String(value=name) + req = protos.WatcherControlRequest(display=self.display, + name=name) r = self.stub.getWatcherTriggeredCount(req) return r.value def reset_watcher_triggered_count(self, name): """ - 重置这个 watcher 的触发次数为 0 + Reset this watcher's trigger count to 0. """ - req = protos.String(value=name) + req = protos.WatcherControlRequest(display=self.display, + name=name) r = self.stub.resetWatcherTriggeredCount(req) return r.value - def get_applied_watchers(self): + def get_enabled_watchers(self): """ - 获取已经在系统应用的 watcher 名称列表 + Get enabled watchers applied on the system. """ - r = self.stub.getAppliedWatchers(protos.Empty()) - return r.watchers - # 注意:下面这些 watcher 实现不是安全的 - # 注册时都是统一存储到本地实例的变量中,直至 enable 时才会应用至服务端 - # 这样做的原因是让你知道你都干了什么,过多的 watcher 会影响性能 - def remove_all_watchers(self): + req = protos.WatcherControlRequest(display=self.display) + return self.stub.getEnabledWatchers(req).watchers + def get_watchers(self): """ - 移除所有应用/未应用的 watcher + Get registered watchers applied on the system. """ - for name in list(self.get_applied_watchers()): - self.remove_watcher(name) - for name in list(self.watchers.keys()): - self.remove_watcher(name) + req = protos.WatcherControlRequest(display=self.display) + return self.stub.getWatchers(req).watchers + def remove_all_watchers(self): + req = protos.WatcherControlRequest(display=self.display) + r = self.stub.removeAllWatchers(req) + return r.value def register_click_target_selector_watcher(self, name, conditions, target): """ - 注册一个满足条件点击 selector 的 watcher + Register a watcher that clicks a selector when matched. """ - assert name not in self.watchers, "conflict: %s" % name - 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 + req = protos.WatcherControlRequest(display=self.display, + name=name, selectors=conditions, + target=target) + r = self.stub.registerClickUiObjectWatcher(req) + return r.value def register_press_key_watcher(self, name, conditions, key): """ - 注册一个满足条件点击 key 的 watcher + Register a watcher that presses a key when matched. """ - assert name not in self.watchers, "conflict: %s" % name - 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 + req = protos.WatcherControlRequest(display=self.display, + name=name, selectors=conditions, + key=key) + r = self.stub.registerPressKeysWatcher(req) + return r.value def register_none_op_watcher(self, name, conditions): """ - 注册一个满足条件无操作的 watcher(用来检测是否出现过某个场景) + Register a watcher that does nothing when matched. """ - assert name not in self.watchers, "conflict: %s" % name - 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)) + req = protos.WatcherControlRequest(display=self.display, + name=name, selectors=conditions) + r = self.stub.registerNoneOpWatcher(req) + return r.value def set_watcher_enabled(self, name, enable): """ - 设置是否启用此 watcher + Enable or disable this 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) + req = protos.WatcherControlRequest(display=self.display, + name=name, enable=enable) + r = self.stub.setWatcherEnable(req) + return r.value def get_watcher_enabled(self, name): """ - 获取此 watcher 是否启用 + Check whether this watcher is enabled. """ - return self.watchers.get(name, {}).get("enable") + req = protos.WatcherControlRequest(display=self.display, + name=name) + r = self.stub.getWatcherEnable(req) + return r.value def get_last_toast(self): """ - 获取系统中最后一个 toast 消息 + Get the last toast message. """ r = self.stub.getLastToast(protos.Empty()) return r def remove_watcher(self, name): """ - 移除一个 watcher + Remove a watcher. """ - self.watchers.pop(name, None) - return self._remove_watcher(name) + req = protos.WatcherControlRequest(display=self.display, + name=name) + r = self.stub.removeWatcher(req) + return r.value + def long_click(self, point, timeout=0): + req = protos.ClickPointRequest(display=self.display, + point=point, + timeout=timeout) + r = self.stub.pointLongClick(req) + return r.value def click(self, point): """ - 点击屏幕中的某个点(Point) + Click a point on the screen. """ - req = protos.ClickPointRequest(point=point) + req = protos.ClickPointRequest(display=self.display, + point=point) r = self.stub.click(req) return r.value def drag(self, A, B, step=32): """ - 从点(Point) A 拖动到点(Point) B + Drag from point A to point B. """ - req = protos.DragPointRequest(A=A, B=B, step=step) + req = protos.DragPointRequest(display=self.display, + 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 + Swipe from point A to point B. """ - req = protos.SwipePointRequest(A=A, B=B, step=step) + req = protos.SwipePointRequest(display=self.display, + A=A, B=B, step=step) r = self.stub.swipe(req) return r.value def swipe_points(self, *points, step=32): """ - 滑动一个点(Point)序列(超过两个点) + Swipe across a sequence of points. """ - req = protos.SwipePointsRequest(points=points, step=step) + req = protos.SwipePointsRequest(display=self.display, + points=points, step=step) r = self.stub.swipePoints(req) return r.value def open_notification(self): """ - 打开通知栏(状态栏) + Open the notification shade. """ r = self.stub.openNotification(protos.Empty()) return r.value def open_quick_settings(self): """ - 打开设置栏(状态栏) + Open the quick settings shade. """ r = self.stub.openQuickSettings(protos.Empty()) return r.value def wake_up(self): """ - 唤醒设备(点亮屏幕) + Wake the device. """ r = self.stub.wakeUp(protos.Empty()) return r.value def sleep(self): """ - 关闭设备(熄灭屏幕) + Sleep the device. """ r = self.stub.sleep(protos.Empty()) return r.value def is_screen_on(self): """ - 设备是否处于唤醒状态 + Check whether the device is awake. """ r = self.stub.isScreenOn(protos.Empty()) return r.value def is_screen_locked(self): """ - 设备屏幕是否已经锁定 + Check whether the screen is locked. """ r = self.stub.isScreenLocked(protos.Empty()) return r.value def set_clipboard(self, text): """ - 设置剪切板文字 + Set clipboard text. """ req = protos.ClipboardRequest(ID=str(uuid.uuid4()), value=text) r = self.stub.setClipboard(req) return r.value def get_clipboard(self): """ - 获取剪切板文字(小于 Android10) + Get clipboard text before Android 10. """ 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): + """ + Find similar image positions on screen from a target image. + """ + 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.display = self.display + 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): """ - 锁定屏幕旋转 + Lock screen rotation. """ - r = self.stub.freezeRotation(protos.Boolean(value=freeze)) + req = protos.FreezeRotationRequest(freeze=freeze, + display=self.display) + r = self.stub.freezeRotation(req) return r.value def set_orientation(self, orien=Orientation.ORIEN_NATURE): """ - 设置屏幕旋转方向 + Set screen rotation. """ - req = protos.OrientationRequest(orientation=orien) + req = protos.OrientationRequest(orientation=orien, + display=self.display) r = self.stub.setOrientation(req) return r.value def press_key(self, key): """ - 按下设备物理按键(HOME/VOLUME/BACK) + Press a hardware key such as HOME, VOLUME, or BACK. """ - req = protos.PressKeyRequest(key=key) + req = protos.PressKeyRequest(display=self.display, key=key) r = self.stub.pressKey(req) return r.value def press_keycode(self, code, meta=0): """ - 通过 Keycode(整数)按下未定义的按键 + Press an undefined key by integer keycode. ref: https://developer.android.com/reference/android/view/KeyEvent """ - req = protos.PressKeyRequest(code=code, meta=meta) + req = protos.PressKeyRequest(display=self.display, + code=code, meta=meta) r = self.stub.pressKeyCode(req) return r.value def take_screenshot(self, quality, bound=None): """ - 截取全屏幕截图 + Capture a full-screen screenshot. """ - req = protos.TakeScreenshotRequest(quality=quality, + req = protos.TakeScreenshotRequest(display=self.display, + 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): + def dump_window_hierarchy(self, compressed=False): """ - 获取屏幕界面布局 XML 文档 + Get the current UI layout XML. """ - r = self.stub.dumpWindowHierarchy(protos.Empty()) + req = protos.DumpWindowHierarchyRequest(display=self.display, + compressed=compressed) + r = self.stub.dumpWindowHierarchy(req) return BytesIO(r.value) def wait_for_idle(self, timeout): """ - 等待当前屏幕处于闲置状态(无频繁活动切换) + Wait until the current screen is idle. """ r = self.stub.waitForIdle(protos.Integer(value=timeout)) return r.value def __call__(self, **kwargs): - return ObjectUiAutomatorOpStub(self.stub, kwargs) - - -class ObjectApplicationOpStub: - def __init__(self, stub, applicationId): - """ - Application 子接口,用来模拟出实例的意味 - """ + return ObjectUiAutomatorOpStub(self, kwargs, + self.display) + + +class VirtualDisplayStub(UiAutomatorStub): + def __init__(self, *args, display=0, device=None, **kwargs): + self._warning_global = True + super(VirtualDisplayStub, self).__init__(*args, + display=display, **kwargs) + self.device = device + def _create_virtual_display(self, width=None, height=None, + densityDpi=None, name=None): + default = self.stub.getDisplayInfo(protos.Integer(value=0)) + req = protos.CreateVirtualDisplayRequest(densityDpi=densityDpi or default.densityDpi, + width=width or default.width, + height=height or default.height) + req.name = name or uuid.uuid4().hex[::6] + return self.stub.createVirtualDisplay(req).value + def _list_virtual_displays(self): + return self.stub.listAllDisplays(protos.Empty()).displays + def _release_virtual_display(self, display): + req = protos.Integer(value=display) + return self.stub.releaseVirtualDisplay(req).value + def disable_global_method_warning(self): + self._warning_global = False + # Application compat + def enumerate_installed_apps(self, user=0): + self._warning_global_use("enumerate_installed_apps") + return self.device.proxy("Application", display=self.display).enumerate_installed_apps(user=user) + def enumerate_running_processes(self): + self._warning_global_use("enumerate_running_processes") + return self.device.proxy("Application", display=self.display).enumerate_running_processes() + def current_application(self): + return self.device.proxy("Application", display=self.display).current_application() + def start_activity(self, **activity): + return self.device.proxy("Application", display=self.display).start_activity(**activity) + def get_application_by_name(self, name): + return self.device.proxy("Application", display=self.display).get_application_by_name(name) + def application(self, applicationId, user=0): + return self.device.proxy("Application", display=self.display)( + applicationId, user=user) + # Display misc + def release_virtual_display(self): + return self._release_virtual_display(self.display) + def get_display_info(self): + return self.stub.getDisplayInfo(protos.Integer(value=self.display)) + def set_display_ime_policy(self, policy): + req = protos.ImePolicyRequest(display=self.display, policy=policy) + return self.stub.setDisplayImePolicy(req).value + def get_display_ime_policy(self): + req = protos.Integer(value=self.display) + return self.stub.getDisplayImePolicy(req).value + def __enter__(self): + return self + def __exit__(self, type, value, traceback): + self._release_virtual_display(self.display) + def ocr(self, index=0, **kwargs): + return self.device.ocr(index=index, display=self.display, + **kwargs) + # Global-effect method overriding + def device_info(self): + self._warning_global_use("device_info") + return super(VirtualDisplayStub, self).device_info() + def get_last_toast(self): + self._warning_global_use("get_last_toast") + return super(VirtualDisplayStub, self).get_last_toast() + def open_notification(self): + self._warning_global_use("open_notification") + return super(VirtualDisplayStub, self).open_notification() + def open_quick_settings(self): + self._warning_global_use("open_quick_settings") + return super(VirtualDisplayStub, self).open_quick_settings() + def wake_up(self): + self._warning_global_use("wake_up") + return super(VirtualDisplayStub, self).wake_up() + def sleep(self): + self._warning_global_use("sleep") + return super(VirtualDisplayStub, self).sleep() + def is_screen_on(self): + self._warning_global_use("is_screen_on") + return super(VirtualDisplayStub, self).is_screen_on() + def is_screen_locked(self): + self._warning_global_use("is_screen_locked") + return super(VirtualDisplayStub, self).is_screen_locked() + def set_clipboard(self, text): + self._warning_global_use("set_clipboard") + return super(VirtualDisplayStub, self).set_clipboard(text) + def get_clipboard(self): + self._warning_global_use("get_clipboard") + return super(VirtualDisplayStub, self).get_clipboard() + def wait_for_idle(self, timeout): + self._warning_global_use("wait_for_idle") + return super(VirtualDisplayStub, self).wait_for_idle(timeout) + def _warning_global_use(self, method): + if self._warning_global: + logger.warning(f"Method '{method}' cannot be applied specifically to a virtual screen " + "as it has a global effect. Please use the corresponding global method, or call "\ + "disable_global_method_warning() to suppress this warning.") + + +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, display=0): + """ + Application sub-interface that behaves like an instance. + """ + self.user = user + self.display = display self.applicationId = applicationId self.stub = stub def __str__(self): - return "Application: {}".format(self.applicationId) + return "Application:{}:{}@{}".format(self.applicationId, + self.user, self.display) __repr__ = __str__ def is_foreground(self): """ - 应用是否正处于前台运行 + Check whether the app is in the foreground. """ req = protos.ApplicationRequest(name=self.applicationId) + req.user = self.user + req.display = self.display r = self.stub.isForeground(req) return r.value def permissions(self): """ - 获取应用的所有权限列表 + Get all app permissions. """ 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): """ - 授予应用某个权限(应用需要运行时获取的权限) + Grant a runtime permission to the app. """ 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): """ - 撤销授予应用的权限(应用需要运行时获取的权限) + Revoke a runtime permission from the app. """ 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 信息 + Get launch activity info. """ 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): """ - 检查是否已经授予应用某权限(应用需要运行时获取的权限) + Check whether the app has a runtime permission. """ req = protos.ApplicationRequest(name=self.applicationId, permission=permission) + req.user = self.user r = self.stub.isPermissionGranted(req) return r.value - def delete_cache(self): + def clear_cache(self): """ - 清空应用的缓存数据(非数据仅缓存) + Clear app cache data. """ req = protos.ApplicationRequest(name=self.applicationId) + req.user = self.user r = self.stub.deleteApplicationCache(req) return r.value - def reset_data(self): + def reset(self): """ - 清空应用的所有数据 + Clear all app data. """ req = protos.ApplicationRequest(name=self.applicationId) + req.user = self.user r = self.stub.resetApplicationData(req) return r.value def start(self): """ - 启动应用 + Start the app. """ req = protos.ApplicationRequest(name=self.applicationId) + req.user = self.user + req.display = self.display r = self.stub.startApplication(req) return r.value def stop(self): """ - 停止应用 + Stop the app. """ req = protos.ApplicationRequest(name=self.applicationId) + req.user = self.user r = self.stub.stopApplication(req) return r.value def info(self): """ - 获取应用信息 + Get app info. """ req = protos.ApplicationRequest(name=self.applicationId) + req.user = self.user r = self.stub.applicationInfo(req) return r def uninstall(self): """ - 卸载应用 (always return true) + Uninstall the app (always returns true). """ req = protos.ApplicationRequest(name=self.applicationId) + req.user = self.user r = self.stub.uninstallApplication(req) return r.value def enable(self): """ - 启用应用 + Enable the app. """ req = protos.ApplicationRequest(name=self.applicationId) + req.user = self.user r = self.stub.enableApplication(req) return r.value def disable(self): """ - 禁用应用(这将使应用从启动器消失) + Disable the app. """ 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) - r = self.stub.addToDozeModeWhiteList(req) - return True - def remove_from_doze_mode_whitelist(self): - """ - 将APP移除省电白名单 (always return true) - """ - req = protos.ApplicationRequest(name=self.applicationId) - r = self.stub.removeFromDozeModeWhiteList(req) - return True - def install_from_local_file(self, fpath): - """ - 安装设备上的 apk 文件(注意此路径为设备上的 apk 路径) - """ - req = protos.ApplicationRequest(path=fpath) - r = self.stub.installFromLocalFile(req) - return r def is_installed(self): """ - 检查应用是否已经安装 + Check whether the app is installed. """ 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="", + process=None, + encode=DataEncode.DATA_ENCODE_NONE, + spawn=False, + standup=5): + """ + Inject a persistent hook script into the app. + """ + 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 + req.process = process or "" + r = self.stub.attachScript(req) + return r.value + def detach_script(self): + """ + Remove the injected hook script. + """ + req = protos.HookRequest() + req.package = self.applicationId + req.user = self.user + r = self.stub.detachScript(req) + return r.value + def is_attached_script(self): + """ + Check whether a hook script is injected into this app. + """ + req = protos.HookRequest() + req.package = self.applicationId + req.user = self.user + r = self.stub.isScriptAttached(req) + return r.value + def is_script_alive(self): + """ + Check whether the hook script in this app is healthy. + """ + req = protos.HookRequest() + req.package = self.applicationId + req.user = self.user + r = self.stub.isScriptAlive(req) + return r.value + def __getattr__(self, name): + """ + Call an exported method from the injected hook script. + """ + return AppScriptRpcInterface(self.stub, self, + name) + + +class ApplicationInstallSession(object): + def __init__(self, device, session, tmpdir=None): + self.tmpdir = tmpdir or "/data/local/tmp" + self.stub = device.proxy("Application").stub + self.device = device + self.session = session + def _write(self, path, name=None, delete=False): + req = protos.InstallSessionWriteRequest(session=self.session, + path=path, name=name, + delete=delete) + return self.stub.installSessionWrite(req) + def write(self, path, name=None): + suffix = uuid.uuid4().hex[::4] + dest = posixpath.join(self.tmpdir, "{}_{}.apk".format( + self.session, suffix)) + info = self.device.upload_file(path, dest) + self.device.file_chmod(info.path, mode=0o777) + return self._write(info.path, name, True) + def commit(self, wait=True, timeout=0): + req = protos.InstallSessionCommitRequest(session=self.session, + wait=wait, timeout=timeout) + return self.stub.installSessionCommit(req) + def abandon(self): + req = protos.InstallSessionAbandonRequest(session=self.session) + return self.stub.installSessionAbandon(req).value + def status(self): + req = protos.InstallSessionQueryRequest(session=self.session) + return self.stub.installSessionQuery(req) class ApplicationStub(BaseServiceStub): + def __init__(self, *args, display=0, device=None, **kwargs): + super(ApplicationStub, self).__init__(*args, **kwargs) + self.display = display + self.device = device def current_application(self): """ - 获取当前处于前台的应用的信息 + Get the current foreground app info. """ - top = self.stub.currentApplication(protos.Empty()) - app = self.__call__(top.packageName) + req = protos.Integer(value=self.display) + top = self.stub.currentApplication(req) + 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): """ - 列出设备上所有正在运行的安卓应用进程 + List all running Android app processes. """ 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 enumerate_installed_apps(self, user=0): + """ + List application IDs of all installed apps. + """ + req = protos.Integer(value=user) + r = self.stub.enumerateInstalledApps(req) + return r.applications + def _update_extras(self, msg, data): + types = {bool: "bool_value", int: "int64_value", + float: "double_value", + str: "string_value", + type(None): "null_value"} + for k, v in data.items(): setattr(msg.fields[k], types[type(v)], + 0 if v is None else v) def start_activity(self, **activity): """ - 启动 activity(总是返回 True) + Start an activity (always returns True). """ activity.setdefault("extras", {}) extras = activity.pop("extras") req = protos.ApplicationActivityRequest(**activity) - req.extras.update(extras) + self._update_extras(req.extras, extras) + req.display = self.display r = self.stub.startActivity(req) return r.value - def __call__(self, applicationId): - return ObjectApplicationOpStub(self.stub, applicationId) + def _create_install_session(self, user=0, size_bytes=0, package=None, + installer_package_name=None, dont_kill_app=False, + replace_existing=True, allow_test=False, request_downgrade=False, + grant_runtime_permissions=False, tmpdir=None): + req = protos.InstallSessionCreateRequest(user=user) + req.sizeBytes = size_bytes + req.dontKillApp = dont_kill_app + req.requestDowngrade = request_downgrade + req.grantRuntimePermissions = grant_runtime_permissions + req.replaceExisting = replace_existing + req.allowTest = allow_test + req.installerPackageName = installer_package_name or "" + req.package = package or "" + info = self.stub.installSessionCreate(req) + params = dict(device=self.device, session=info.session, + tmpdir=tmpdir) + return ApplicationInstallSession(**params) + def __call__(self, applicationId, user=0): + return ApplicationOpStub(self.stub, applicationId, + user=user, display=self.display) + + +class StorageOpStub: + # Helpers for container value serialization. + 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)) + # This interface may not be portable across languages. + def __init__(self, stub, name, cryptor=None): + self.cryptor = cryptor + self.name = name + self.stub = stub + def delete(self, key): + """ + Delete a key. + """ + req = protos.StorageRequest(key=key) + req.container = self.name + res = self.stub.delete(req) + return res.value + def exists(self, key): + """ + Check whether a key exists. + """ + req = protos.StorageRequest(key=key) + req.container = self.name + res = self.stub.exists(req) + return res.value + def get(self, key, default=None): + """ + Get the value for a 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): + """ + Set the value for a 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): + """ + Set a key value and expire it after TTL seconds. + """ + 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): + """ + Set the value for a key only if it does not exist. + """ + 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): + """ + Set a key to expire after TTL seconds. + """ + req = protos.StorageRequest(key=key, ttl=ttl) + req.container = self.name + res = self.stub.expire(req) + return res.value + def ttl(self, key): + """ + Get the TTL for a key. + """ + req = protos.StorageRequest(key=key) + req.container = self.name + res = self.stub.ttl(req) + return res.value + + +class StorageStub(BaseServiceStub): + def clear(self): + """ + Delete all storage containers. + """ + r = self.stub.clearAll(protos.Empty()) + return r.value + def use(self, name, cryptor=BaseCryptor, **kwargs): + """ + Use a storage container. + """ + return StorageOpStub(self.stub, name, cryptor(**kwargs)) + def remove(self, name): + """ + Delete a storage container. + """ + req = protos.String(value=name) + r = self.stub.clearContainer(req) + return r.value class UtilStub(BaseServiceStub): @@ -1027,7 +1699,7 @@ def _get_file_content(self, certfile): return fd.read() def is_ca_certificate_installed(self, certfile): """ - 安装系统证书(用于 MITM) + Install a system certificate for MITM. """ data = self._get_file_content(certfile) req = protos.CertifiRequest(cert=data) @@ -1035,7 +1707,7 @@ def is_ca_certificate_installed(self, certfile): return r.value def install_ca_certificate(self, certfile): """ - 安装系统证书(用于 MITM) + Install a system certificate for MITM. """ data = self._get_file_content(certfile) req = protos.CertifiRequest(cert=data) @@ -1043,70 +1715,95 @@ def install_ca_certificate(self, certfile): return r.value def uninstall_ca_certificate(self, certfile): """ - 移除系统证书(用于 MITM) + Remove a system certificate used for 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): - """ - 在设备上进行真实滑动(重放录制的滑动轨迹) - """ - assert isinstance(tas, TouchSequence) - req = protos.PerformTouchRequest(sequence=tas, wait=wait) - r = self.stub.performTouch(req) - return r.value def reboot(self): """ - 重启系统(宿主设备) + Reboot the host device. """ r = self.stub.reboot(protos.Empty()) return r.value def shutdown(self): """ - 关闭系统(宿主设备) + Shut down the host device. """ r = self.stub.shutdown(protos.Empty()) return r.value - def reload(self): + def reload(self, clean=False): """ - 重载设备上运行的服务端 + Reload the server running on the device. """ - r = self.stub.reload(protos.Empty()) + req = protos.Boolean(value=clean) + r = self.stub.reload(req) return r.value def exit(self): """ - 退出设备上运行的服务端 + Exit the server running on the device. """ r = self.stub.exit(protos.Empty()) return r.value def beep(self): """ - 播放一声蜂鸣(物理查找) + Play a beep to help locate the device. """ r = self.stub.beepBeep(protos.Empty()) return r.value + def play_audio(self, file, type=AudioStreamType.AST_SYSTEM, + loop=1, interval=0): + """ + Play a WAV file. + """ + 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): + """ + Show a toast message at the bottom of the screen. + """ + req = protos.ShowToastRequest(text=text, duration=duration) + r = self.stub.showToast(req) + return r.value def setprop(self, name, value): """ - 设置系统属性(aka: setprop,支持设置 ro.xx 只读属性) + Set a system property, including read-only ro.xx values. """ req = protos.SetPropRequest(name=name, value=value) r = self.stub.setProp(req) return r.value def getprop(self, name): """ - 获取系统属性(aka: getprop) + Get a system property. """ req = protos.String(value=name) r = self.stub.getProp(req) return r.value + def server_info(self): + """ + Get server ID, version, and related info. + """ + r = self.stub.serverInfo(protos.Empty()) + return r + def hex_patch(self, pattern, replacement, path, + maxreplace=-1, + dryrun=False): + """ + Replace bytes in a file on the device. + """ + 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): @@ -1115,7 +1812,7 @@ def _read_pubkey(self, pubkey): return fd.read() def install_adb_pubkey(self, pubkey): """ - 给内置 adb 服务添加公钥 + Add a public key to the built-in adb service. """ req = protos.ADBDConfigRequest() req.adb_pubkey = self._read_pubkey(pubkey) @@ -1123,7 +1820,7 @@ def install_adb_pubkey(self, pubkey): return r.value def uninstall_adb_pubkey(self, pubkey): """ - 从内置 adb 服务移除公钥 + Remove a public key from the built-in adb service. """ req = protos.ADBDConfigRequest() req.adb_pubkey = self._read_pubkey(pubkey) @@ -1131,68 +1828,22 @@ def uninstall_adb_pubkey(self, pubkey): return r.value def is_android_debug_bridge_running(self): """ - 远端 adb daemon 是否在运行 + Check whether the remote adb daemon is running. """ r = self.stub.isAndroidDebugBridgeRunning(protos.Empty()) return r.value - def is_ida_running(self): - """ - IDA 服务端是否在运行 - """ - r = self.stub.isIDARunning(protos.Empty()) - return r.value - def is_ida64_running(self): - """ - IDA64 服务端是否在运行 - """ - r = self.stub.isIDA64Running(protos.Empty()) - return r.value def start_android_debug_bridge(self): """ - 启动内置 adbd (默认随框架启动) + Start the built-in adbd. """ r = self.stub.startAndroidDebugBridge(protos.Empty()) return r.value - def start_ida(self, port=23932, **env): - """ - 启动 IDA 服务端 - """ - req = protos.IDAConfigRequest(port=port) - req.environment.update(env) - r = self.stub.startIDA(req) - return r.value - def start_ida64(self, port=23964, **env): - """ - 启动 IDA64 服务端 - """ - req = protos.IDAConfigRequest(port=port) - req.environment.update(env) - r = self.stub.startIDA64(req) - return r.value def stop_android_debug_bridge(self): """ - 停止内置 adb daemon + Stop the built-in adb daemon. """ r = self.stub.stopAndroidDebugBridge(protos.Empty()) return r.value - def set_debuggable(self): - """ - 设置系统为 debuggable - """ - r = self.stub.setDebuggable(protos.Empty()) - return r.value - def stop_ida(self): - """ - 停止 IDA 服务端 - """ - r = self.stub.stopIDA(protos.Empty()) - return r.value - def stop_ida64(self): - """ - 停止 IDA64 服务端 - """ - r = self.stub.stopIDA64(protos.Empty()) - return r.value class SettingsStub(BaseServiceStub): @@ -1207,61 +1858,63 @@ def _get(self, group, name): return r.value def get_system(self, name): """ - 等价于 settings get system xxxx + Equivalent to settings get system xxxx. """ return self._get(Group.GROUP_SYSTEM, name) def put_system(self, name, value): """ - 等价于 settings put system xxxx xxxx + Equivalent to settings put system xxxx xxxx. """ return self._put(Group.GROUP_SYSTEM, name, value) def get_global(self, name): """ - 等价于 settings get global xxxx + Equivalent to settings get global xxxx. """ return self._get(Group.GROUP_GLOBAL, name) def put_global(self, name, value): """ - 等价于 settings put global xxxx xxxx + Equivalent to settings put global xxxx xxxx. """ return self._put(Group.GROUP_GLOBAL, name, value) def get_secure(self, name): """ - 等价于 settings get secure xxxx + Equivalent to settings get secure xxxx. """ return self._get(Group.GROUP_SECURE, name) def put_secure(self, name, value): """ - 等价于 settings put secure xxxx xxxx + Equivalent to settings put secure xxxx xxxx. """ return self._put(Group.GROUP_SECURE, name, value) class ShellStub(BaseServiceStub): - def execute_script(self, script, alias=None): + def execute_script(self, script, alias=None, + timeout=60): """ - 前台执行一段脚本(支持标准的多行脚本) + Run a script in the foreground. """ - req = protos.ShellRequest(name=alias, script=script) + req = protos.ShellRequest(name=alias, script=script, + timeout=timeout) r = self.stub.executeForeground(req) return r def execute_background_script(self, script, alias=None): """ - 后台执行一段脚本(支持标准的多行脚本) + Run a script in the background. """ req = protos.ShellRequest(name=alias, script=script) r = self.stub.executeBackground(req) return r.tid def is_background_script_finished(self, tid): """ - 后台脚本是否已经结束 + Check whether the background script has finished. """ req = protos.ShellTask(tid=tid) r = self.stub.isBackgroundFinished(req) return r.value def kill_background_script(self, tid): """ - 强行停止后台脚本 + Force-stop the background script. """ req = protos.ShellTask(tid=tid) r = self.stub.killBackground(req) @@ -1271,57 +1924,57 @@ def kill_background_script(self, tid): class StatusStub(BaseServiceStub): def get_boot_time(self): """ - 获取设备启动时间 Unix 时间戳 + Get the device boot time as a Unix timestamp. """ r = self.stub.getBootTime(protos.Empty()) return r.value def get_disk_usage(self, mountpoint="/data"): """ - 获取分区数据使用情况 + Get partition usage stats. """ req = protos.String(value=mountpoint) r = self.stub.getDiskUsage(req) return r def get_battery_info(self): """ - 获取电池信息 + Get battery info. """ r = self.stub.getBatteryInfo(protos.Empty()) return r def get_cpu_info(self): """ - 获取 CPU 用量等信息 + Get CPU usage and related stats. """ r = self.stub.getCpuInfo(protos.Empty()) return r def get_overall_disk_io_info(self): """ - 获取全局的设备磁盘读写状况 + Get global disk I/O stats. """ r = self.stub.getOverallDiskIOInfo(protos.Empty()) return r def get_overall_net_io_info(self): """ - 获取全局的设备网络收发状况 + Get global network traffic stats. """ r = self.stub.getOverallNetIOInfo(protos.Empty()) return r def get_userdata_disk_io_info(self): """ - 获取用户数据设备磁盘读写状况 + Get user-data disk I/O stats. """ r = self.stub.getUserDataDiskIOInfo(protos.Empty()) return r def get_net_io_info(self, interface): """ - 获取特定接口的网络收发状况 + Get network traffic stats for a specific interface. """ req = protos.String(value=interface) r = self.stub.getNetIOInfo(req) return r def get_mem_info(self): """ - 获取设备内存状况 + Get memory stats. """ r = self.stub.getMemInfo(protos.Empty()) return r @@ -1330,97 +1983,97 @@ def get_mem_info(self): class ProxyStub(BaseServiceStub): def is_openvpn_running(self): """ - 检查 OPENVPN 是否正在运行 + Check whether OPENVPN is running. """ r = self.stub.isOpenVPNRunning(protos.Empty()) return r.value def is_gproxy_running(self): """ - 检查 GPROXY 是否正在运行 + Check whether GPROXY is running. """ r = self.stub.isGproxyRunning(protos.Empty()) return r.value def start_openvpn(self, profile): """ - 启动 OPENVPN + Start OPENVPN. """ - assert isinstance(profile, OpenVPNProfile) + checkArgumentTyp(profile, OpenVPNProfile) r = self.stub.startOpenVPN(profile) return r.value def start_gproxy(self, profile): """ - 启动 GPROXY + Start GPROXY. """ - assert isinstance(profile, GproxyProfile) + checkArgumentTyp(profile, GproxyProfile) r = self.stub.startGproxy(profile) return r.value def stop_openvpn(self): """ - 停止 OPENVPN + Stop OPENVPN. """ r = self.stub.stopOpenVPN(protos.Empty()) return r.value def stop_gproxy(self): """ - 停止 GPROXY + Stop GPROXY. """ r = self.stub.stopGproxy(protos.Empty()) return r.value class SelinuxPolicyStub(BaseServiceStub): - def policy_set_allow(self, source, target, tclass, action): + def allow(self, source, target, tclass, action): """ selinux allow """ req = protos.SelinuxPolicyRequest(source=source, target=target, - tclass=tclass, action=action) + tclass=tclass, action=action) r = self.stub.policySetAllow(req) return r.value - def policy_set_disallow(self, source, target, tclass, action): + def disallow(self, source, target, tclass, action): """ selinux disallow """ req = protos.SelinuxPolicyRequest(source=source, target=target, - tclass=tclass, action=action) + tclass=tclass, action=action) r = self.stub.policySetDisallow(req) return r.value def get_enforce(self): """ - 获取当前 selinux enforce 状态 + Get the current SELinux enforce state. """ r = self.stub.getEnforce(protos.Empty()) return r.value def set_enforce(self, enforced=True): """ - 设置当前 selinux enforce 状态 (aka: setenforce 0/1) + Set the current SELinux enforce state. """ req = protos.Boolean(value=enforced) r = self.stub.setEnforce(req) return r.value - def is_enabled(self): + def enabled(self): """ - 获取设备上的 selinux 是否已经启用 + Check whether SELinux is enabled on the device. """ r = self.stub.isEnabled(protos.Empty()) return r.value - def policy_set_enforce(self, name): + def enforce(self, name): """ - 设置一个域为 enforce + Set a domain to enforce. """ req = protos.String(value=name) r = self.stub.policySetEnforce(req) return r.value - def policy_set_permissive(self, name): + def permissive(self, name): """ - 设置一个域为 permissive + Set a domain to permissive. """ req = protos.String(value=name) r = self.stub.policySetPermissive(req) return r.value - def policy_create_domain(self, name): + def create_domain(self, name): """ - 新建一个 selinux 域 + Create a new SELinux domain. """ req = protos.String(value=name) r = self.stub.policyCreateDomain(req) @@ -1440,7 +2093,7 @@ def _fd_streaming_recv(self, fd, iterator): fd.write(chunk.payload) def download_fd(self, fpath, fd): """ - 从设备下载文件到文件描述符 + Download a file from the device to a file descriptor. """ req = protos.FileRequest(path=fpath) iterator = self.stub.downloadFile(req) @@ -1449,7 +2102,7 @@ def download_fd(self, fpath, fd): return st def upload_fd(self, fd, dest): """ - 上传文件描述符至设备 + Upload a file descriptor to the device. """ chunksize = 1024*1024*1 streaming = self._fd_streaming_send(fd, dest, @@ -1459,33 +2112,33 @@ def upload_fd(self, fd, dest): return st def download_file(self, fpath, dest): """ - 从设备下载文件到本地 + Download a file from the device to local storage. """ with io.open(dest, mode="wb") as fd: return self.download_fd(fpath, fd) def upload_file(self, fpath, dest): """ - 上传本地文件至设备 + Upload a local file to the device. """ with io.open(fpath, mode="rb") as fd: return self.upload_fd(fd, dest) def delete_file(self, fpath): """ - 删除设备上的文件 + Delete a file on the device. """ req = protos.FileRequest(path=fpath) r = self.stub.deleteFile(req) return r.value def file_chmod(self, fpath, mode=0o644): """ - 更改设备上文件的权限 + Change file permissions on the device. """ req = protos.FileRequest(path=fpath, mode=mode) r = self.stub.fileChmod(req) return r def file_stat(self, fpath): """ - 获取设备上文件的信息 + Get file info on the device. """ req = protos.FileRequest(path=fpath) r = self.stub.fileStat(req) @@ -1495,21 +2148,27 @@ def file_stat(self, fpath): class LockStub(BaseServiceStub): def acquire_lock(self, leaseTime=60): """ - 获取用于控制设备的锁,成功返回 true,被占用则会引发异常提示 + Acquire the device control lock and raise if it is busy. """ req = protos.Integer(value=leaseTime) r = self.stub.acquireLock(req) return r.value + def get_session_token(self): + """ + Get the current session token. + """ + r = self.stub.getSessionToken(protos.Empty()) + return r.value def refresh_lock(self, leaseTime=60): """ - 刷新用于控制设备的锁,应该在定时任务每60s内调用以保持会话 + Refresh the device control lock within 60 seconds to keep the session. """ req = protos.Integer(value=leaseTime) r = self.stub.refreshLock(req) return r.value def release_lock(self): """ - 释放控制设备的锁,释放后该设备可被其他客户端控制 + Release the device control lock. """ r = self.stub.releaseLock(protos.Empty()) return r.value @@ -1518,55 +2177,55 @@ def release_lock(self): class WifiStub(BaseServiceStub): def status(self): """ - 获取当前已连接 WIFI 的信息 + Get info for the currently connected Wi-Fi. """ r = self.stub.status(protos.Empty()) return r def blacklist_add(self, bssid): """ - 将 BSSID 加入 WIFI BSSID 黑名单(将不会在WIFI列表显示) + Add a BSSID to the Wi-Fi blacklist. """ r = self.stub.blacklistAdd(protos.String(value=bssid)) return r.value def blacklist_clear(self): """ - 清空 WIFI BSSID 黑名单 + Clear the Wi-Fi BSSID blacklist. """ r = self.stub.blacklistClear(protos.Empty()) return r.value def blacklist_get_all(self): """ - 获取在 WIFI BSSID 黑名单中的所有 BSSID + Get all blacklisted Wi-Fi BSSIDs. """ r = self.stub.blacklistAll(protos.Empty()) return r.bssids def scan(self): """ - 请求扫描附近 WIFI + Request a nearby Wi-Fi scan. """ r = self.stub.scan(protos.Empty()) return r.value def scan_results(self): """ - 获取已扫描到的附近 WIFI + Get scanned nearby Wi-Fi networks. """ r = self.stub.scanResults(protos.Empty()) return r.stations def get_mac_addr(self): """ - 获取当前 WIFI 的 MAC 地址 + Get the current Wi-Fi MAC address. """ r = self.stub.getMacAddr(protos.Empty()) return r.value def signal_poll(self): """ - 获取当前已连接 WIFI 的信号强度等信息 + Get current Wi-Fi signal info. """ r = self.stub.signalPoll(protos.Empty()) return r def list_networks(self): """ - 列出已连接过的 WIFI 网络 + List previously connected Wi-Fi networks. """ r = self.stub.listNetworks(protos.Empty()) return r.networks @@ -1586,13 +2245,13 @@ def get_network_config(self, networkId, name): raise NotImplementedError def disconnect(self): """ - 断开 WIFI 连接 + Disconnect Wi-Fi. """ r = self.stub.disconnect(protos.Empty()) return r.value def reconnect(self): """ - 重连 WIFI + Reconnect Wi-Fi. """ r = self.stub.reconnect(protos.Empty()) return r.value @@ -1604,53 +2263,300 @@ def save_config(self): raise NotImplementedError +class OcrOperator(object): + def __init__(self, device, elements=None, + display=0, + **kwargs): + self.elements = elements + self.index = kwargs.pop("index", 0) + self.func, self.rule = kwargs.popitem() + self.match = getattr(self, self.func) + self.automator = device.proxy("UiAutomator", + display=display) + 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.automator.screenshot(quality, bound=item["bound"]) + def _click(self, item): + point = item["bound"].center() + return self.automator.click(point) + def __str__(self): + return "Ocr: {}={}".format(self.func, self.rule) + __repr__ = __str__ + def exists(self): + """ + OCR: check whether the element exists. + """ + return bool(self.find_target_item()) + def click(self): + """ + OCR: click the element or raise if missing. + """ + return self.find_or_throw_cb(self._click) + def click_exists(self): + """ + OCR: click the element without raising if missing. + """ + return self.find_cb(self._click, False) + def screenshot(self, quality=100): + """ + OCR: screenshot the element. + """ + return self.find_or_throw_cb(self._screenshot, + quality) + def take_screenshot(self, quality=100): + """ + OCR: screenshot the element. + """ + return self.screenshot(quality) + def info(self): + """ + OCR: get info for the matched element. + """ + 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): + 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"] = 60*1000 + option["grpc.keepalive_timeout_ms"] = 20*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: - cer = fd.read() - creds = grpc.ssl_channel_credentials(cer) - chann = grpc.secure_channel(self.server, creds, + 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._ssl_common_name(cer)), - ("grpc.enable_http_proxy", - False))) + self._parse_cname(crt)), + *tuple(option.items()),)) else: - chann = grpc.insecure_channel(self.server) - interceptors = [ClientSessionMetadataInterceptor(), + self._chan = grpc.insecure_channel(self.server, + options=(*tuple(option.items()),) + ) + session = session or uuid.uuid4().hex + interceptors = [ClientSessionMetadataInterceptor(session), GrpcRemoteExceptionInterceptor(), ClientLoggingInterceptor()] - self.chann = grpc.intercept_channel(chann, + 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: - device = _frida_dma.add_remote_device(self.server, - certificate=self.certificate) - else: - device = _frida_dma.add_remote_device(self.server) + 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 fix_execute_only_memory(self): + """ + Restore read permission on execute-only (``--x`` / XOM) segments + of system libraries inside zygote processes. + + Some OEM ROMs (e.g. MIUI) map system libraries as execute-only + memory. When the frida runtime embedded in the server hooks the + zygote fork path, it fails to read the target memory while + building trampolines (SEGV_ACCERR), which makes every spawned + application crash at startup - apps get stuck on the splash + screen or die immediately after ``spawn()`` / ``resume()``. + + This method walks ``--x`` ranges of zygote (64/32-bit) whose + backing file lives under /system, /apex or /vendor and calls + ``mprotect()`` to restore ``r-x`` on them, so that processes + forked from zygote inherit readable segments again. + + It must be re-invoked after each device reboot or server restart, + before spawning/injecting applications. Requires the server to + run as root (default). + + Returns a dict keyed by process name:: + + { + "zygote64": {"fixed": 294, "failed": 0}, + ... + } + + Processes that do not exist on the device are skipped, errors + while attaching are reported as {"error": "..."} entries. + """ + if _frida_dma is None: + raise ModuleNotFoundError("frida") + device = self.frida + results = {} + for proc in device.enumerate_processes(): + if proc.name not in ("zygote64", "zygote"): + continue + try: + session = device.attach(proc.pid) + res = {} + def on_message(msg, data): + if msg.get("type") == "send": + res.update(msg["payload"]) + script = session.create_script(self.XOM_FIX_SCRIPT) + script.on("message", on_message) + script.load() + time.sleep(5) + session.detach() + results[proc.name] = {"fixed": res.get("fixed", 0), + "failed": res.get("failed", 0)} + except Exception as e: + results[proc.name] = {"error": str(e)} + return results + # JS: mprotect --x ranges backed by system libs back to r-x. + # Module.getExportByName is removed since frida 17, fall back + # progressively for older embedded runtimes. + XOM_FIX_SCRIPT = r""" + var resolve = null; + if (typeof Module != "undefined" && Module.getGlobalExportByName) + resolve = function (n) { return Module.getGlobalExportByName(n); }; + else if (typeof Module != "undefined" && Module.getExportByName) + resolve = function (n) { return Module.getExportByName(null, n); }; + else + resolve = function (n) { return Module.findExportByName(null, n); }; + var mprotect = new NativeFunction(resolve("mprotect"), + "int", ["pointer", "uint", "int"]); + var fixed = 0, failed = 0; + Process.enumerateRanges("--x").forEach(function (r) { + var p = r.file ? (r.file.path || "") : ""; + if (p.indexOf("/system") === 0 || p.indexOf("/apex") === 0 + || p.indexOf("/vendor") === 0) { + if (mprotect(r.base, r.size, 5) === 0) fixed++; + else failed++; + } + }); + send({fixed: fixed, failed: failed}); + """ def __str__(self): return "Device@{}".format(self.server) __repr__ = __str__ - def _ssl_common_name(self, cer): - _, _, der = pem.unarmor(cer) + 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.chann) def stub(self, module): - modu = sys.modules[__name__] - stub = self._get_service_stub(module) - wrap = getattr(modu, "{0}Stub".format(module)) - return wrap(stub) - # 快速调用: File + return self.proxy(module) + def proxy(self, module, clazz=None, **kwargs): + this = sys.modules[__name__] + stub = getattr(services, "{0}Stub".format(module))(self.channel) + wrap = getattr(this, "{0}Stub".format(clazz or module)) + return wrap(stub, **kwargs) + # Shortcut: File def download_fd(self, fpath, fd): return self.stub("File").download_fd(fpath, fd) def upload_fd(self, fd, dest): @@ -1665,24 +2571,40 @@ 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 + # Shortcut: Application + def create_install_session(self, user=0, size_bytes=0, package=None, + installer_package_name=None, dont_kill_app=False, + replace_existing=True, allow_test=False, request_downgrade=False, + grant_runtime_permissions=False, tmpdir=None): + kwargs = dict(user=user) + kwargs["size_bytes"] = size_bytes + kwargs["package"] = package + kwargs["request_downgrade"] = request_downgrade + kwargs["grant_runtime_permissions"] = grant_runtime_permissions + kwargs["installer_package_name"] = installer_package_name + kwargs["dont_kill_app"] = dont_kill_app + kwargs["replace_existing"] = replace_existing + kwargs["allow_test"] = allow_test + kwargs["tmpdir"] = tmpdir + proxy = self.proxy("Application", device=self) + return proxy._create_install_session(**kwargs) 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_installed_apps(self, user=0): + return self.stub("Application").enumerate_installed_apps(user=user) 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 application(self, applicationId): - return self.stub("Application")(applicationId) - # 快速调用: 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 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) + # Shortcut: Util + def touch(self): + return MultiTouchOpStub(self.stub("Util")) + 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): @@ -1695,15 +2617,24 @@ def shutdown(self): return self.stub("Util").shutdown() def exit(self): return self.stub("Util").exit() - def reload(self): - return self.stub("Util").reload() + 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) - # 快速调用: Debug + def hex_patch(self, pattern, replacement, path, + maxreplace=-1, dryrun=False): + return self.stub("Util").hex_patch(pattern, replacement, path, + maxreplace=maxreplace, + dryrun=dryrun) + # Shortcut: Debug def install_adb_pubkey(self, pubkey): return self.stub("Debug").install_adb_pubkey(pubkey) def uninstall_adb_pubkey(self, pubkey): @@ -1714,7 +2645,7 @@ 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 + # Shortcut: Proxy def is_openvpn_running(self): return self.stub("Proxy").is_openvpn_running() def is_gproxy_running(self): @@ -1727,18 +2658,29 @@ 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): - return self.stub("Shell").execute_script(script, alias=alias) + # Virtual Display + def get_virtual_display_by_id(self, display): + return self.proxy("UiAutomator", clazz="VirtualDisplay", + display=display, device=self) + def create_virtual_display(self, width=None, height=None, densityDpi=None, name=None): + display = self.proxy("UiAutomator", clazz="VirtualDisplay")._create_virtual_display( + width, height, densityDpi, name=name) + return self.get_virtual_display_by_id(display) + # Shortcut: 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 + # Shortcut: UiAutomator def click(self, point): return self.stub("UiAutomator").click(point) + def long_click(self, point, timeout=0): + return self.stub("UiAutomator").long_click(point, timeout=timeout) def drag(self, A, B, step=32): return self.stub("UiAutomator").drag(A, B, step=step) def swipe(self, A, B, step=32): @@ -1773,12 +2715,18 @@ 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): - return self.stub("UiAutomator").dump_window_hierarchy() + 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() @@ -1790,8 +2738,10 @@ 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 get_enabled_watchers(self): + return self.stub("UiAutomator").get_enabled_watchers() + def get_watchers(self): + return self.stub("UiAutomator").get_watchers() def register_click_target_selector_watcher(self, name, conditions, target): return self.stub("UiAutomator").register_click_target_selector_watcher( @@ -1813,15 +2763,40 @@ 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) - def setup_log_format(self): - logging.basicConfig(format=FORMAT) + # OCR extension. + def ocr(self, index=0, display=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.proxy("UiAutomator", display=display).screenshot( + self._ocr_img_quality) + return OcrOperator(self, + elements=self._ocr.ocr(image), + index=index, + display=display, + **kwargs + ) + def setup_ocr_backend(self, service, *args, quality=75, + **kwargs): + self._ocr_img_quality = quality + self._ocr = OcrEngine(service, *args, + **kwargs) + # Logging control. def set_debug_log_enabled(self, enable): level = logging.DEBUG if enable else logging.WARN logger.setLevel(level) return enable - # 接口锁定 + # Lock API. + 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): @@ -1844,7 +2819,7 @@ def __exit__(self, type, value, traceback): parser = argparse.ArgumentParser() crt = os.environ.get("CERTIFICATE", None) port = int(os.environ.get("PORT", 65000)) - parser.add_argument("-device", type=str, default="localhost", + 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") @@ -1853,7 +2828,6 @@ def __exit__(self, type, value, traceback): args = parser.parse_args() readline.parse_and_bind("tab: complete") - d = Device(args.device, port=args.port, - certificate=args.cert) + certificate=args.cert) code.interact(local=globals()) \ No newline at end of file diff --git a/lamda/exceptions.py b/lamda/exceptions.py index 9450898..e921898 100644 --- a/lamda/exceptions.py +++ b/lamda/exceptions.py @@ -1,4 +1,4 @@ -# Copyright 2022 rev1si0n (ihaven0emmail@gmail.com). All rights reserved. +# 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 @@ -8,6 +8,8 @@ class DeadSystemException(Exception): """ Exception """ class DeviceUnavailable(Exception): """ Exception """ +class DuplicateEntryError(Exception): + """ Exception """ class IllegalArgumentException(Exception): """ Exception """ class IllegalStateException(Exception): @@ -18,6 +20,10 @@ class InternalRpcException(Exception): """ Exception """ class InvalidAndroidPackage(Exception): """ Exception """ +class InvalidArgumentError(Exception): + """ Exception """ +class InvalidOperationError(Exception): + """ Exception """ class InvalidRootCertificate(Exception): """ Exception """ class MethodNotFoundException(Exception): @@ -36,9 +42,13 @@ class StaleObjectException(Exception): """ Exception """ class StartupActivityNotFound(Exception): """ Exception """ +class StorageOutOfMemory(Exception): + """ Exception """ class UiAutomatorException(Exception): """ Exception """ class UiObjectNotFoundException(Exception): """ Exception """ class UnHandledException(Exception): """ Exception """ +class UnsupportedOperationException(Exception): + """ Exception """ \ No newline at end of file diff --git a/lamda/google/protobuf/any.proto b/lamda/google/protobuf/any.proto index 6ed8a23..eff44e5 100644 --- a/lamda/google/protobuf/any.proto +++ b/lamda/google/protobuf/any.proto @@ -32,12 +32,12 @@ 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"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; // `Any` contains an arbitrary serialized protocol buffer message along with a // URL that describes the type of the serialized message. @@ -63,6 +63,10 @@ option objc_class_prefix = "GPB"; // if (any.is(Foo.class)) { // foo = any.unpack(Foo.class); // } +// // or ... +// if (any.isSameTypeAs(Foo.getDefaultInstance())) { +// foo = any.unpack(Foo.getDefaultInstance()); +// } // // Example 3: Pack and unpack a message in Python. // @@ -93,7 +97,6 @@ option objc_class_prefix = "GPB"; // 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 @@ -146,7 +149,8 @@ message Any { // // 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. + // type.googleapis.com. As of May 2023, there are no widely used type server + // implementations and no plans to implement one. // // Schemes other than `http`, `https` (or the empty scheme) might be // used with implementation specific semantics. diff --git a/lamda/google/protobuf/api.proto b/lamda/google/protobuf/api.proto index 3d598fc..afc9cc1 100644 --- a/lamda/google/protobuf/api.proto +++ b/lamda/google/protobuf/api.proto @@ -35,11 +35,11 @@ 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 csharp_namespace = "Google.Protobuf.WellKnownTypes"; option go_package = "google.golang.org/protobuf/types/known/apipb"; // Api is a light-weight descriptor for an API Interface. @@ -82,7 +82,6 @@ message Api { // 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 diff --git a/lamda/google/protobuf/compiler/plugin.proto b/lamda/google/protobuf/compiler/plugin.proto index 9242aac..033fab2 100644 --- a/lamda/google/protobuf/compiler/plugin.proto +++ b/lamda/google/protobuf/compiler/plugin.proto @@ -1,38 +1,12 @@ // 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. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd // 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. @@ -50,6 +24,7 @@ package google.protobuf.compiler; option java_package = "com.google.protobuf.compiler"; option java_outer_classname = "PluginProtos"; +option csharp_namespace = "Google.Protobuf.Compiler"; option go_package = "google.golang.org/protobuf/types/pluginpb"; import "google/protobuf/descriptor.proto"; @@ -78,6 +53,11 @@ message CodeGeneratorRequest { // they import. The files will appear in topological order, so each file // appears before any file that imports it. // + // Note: the files listed in files_to_generate will include runtime-retention + // options only, but all other files will include source-retention options. + // The source_file_descriptors field below is available in case you need + // source-retention options for files_to_generate. + // // 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 @@ -90,9 +70,13 @@ message CodeGeneratorRequest { // fully qualified. repeated FileDescriptorProto proto_file = 15; + // File descriptors with all options, including source-retention options. + // These descriptors are only provided for the files listed in + // files_to_generate. + repeated FileDescriptorProto source_file_descriptors = 17; + // The version number of protocol compiler. optional Version compiler_version = 3; - } // The plugin writes an encoded CodeGeneratorResponse to stdout. @@ -115,8 +99,21 @@ message CodeGeneratorResponse { enum Feature { FEATURE_NONE = 0; FEATURE_PROTO3_OPTIONAL = 1; + FEATURE_SUPPORTS_EDITIONS = 2; } + // The minimum edition this plugin supports. This will be treated as an + // Edition enum, but we want to allow unknown values. It should be specified + // according the edition enum value, *not* the edition number. Only takes + // effect for plugins that have FEATURE_SUPPORTS_EDITIONS set. + optional int32 minimum_edition = 3; + + // The maximum edition this plugin supports. This will be treated as an + // Edition enum, but we want to allow unknown values. It should be specified + // according the edition enum value, *not* the edition number. Only takes + // effect for plugins that have FEATURE_SUPPORTS_EDITIONS set. + optional int32 maximum_edition = 4; + // Represents a single generated file. message File { // The file name, relative to the output directory. The name must not diff --git a/lamda/google/protobuf/descriptor.proto b/lamda/google/protobuf/descriptor.proto index 9f0ce6c..ac3de48 100644 --- a/lamda/google/protobuf/descriptor.proto +++ b/lamda/google/protobuf/descriptor.proto @@ -36,7 +36,6 @@ // 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; @@ -56,6 +55,49 @@ option optimize_for = SPEED; // files it parses. message FileDescriptorSet { repeated FileDescriptorProto file = 1; + + // Extensions for tooling. + extensions 536000000 [declaration = { + number: 536000000 + type: ".buf.descriptor.v1.FileDescriptorSetExtension" + full_name: ".buf.descriptor.v1.buf_file_descriptor_set_extension" + }]; +} + +// The full set of known editions. +enum Edition { + // A placeholder for an unknown edition value. + EDITION_UNKNOWN = 0; + + // A placeholder edition for specifying default behaviors *before* a feature + // was first introduced. This is effectively an "infinite past". + EDITION_LEGACY = 900; + + // Legacy syntax "editions". These pre-date editions, but behave much like + // distinct editions. These can't be used to specify the edition of proto + // files, but feature definitions must supply proto2/proto3 defaults for + // backwards compatibility. + EDITION_PROTO2 = 998; + EDITION_PROTO3 = 999; + + // Editions that have been released. The specific values are arbitrary and + // should not be depended on, but they will always be time-ordered for easy + // comparison. + EDITION_2023 = 1000; + EDITION_2024 = 1001; + + // Placeholder editions for testing feature resolution. These should not be + // used or relied on outside of tests. + EDITION_1_TEST_ONLY = 1; + EDITION_2_TEST_ONLY = 2; + EDITION_99997_TEST_ONLY = 99997; + EDITION_99998_TEST_ONLY = 99998; + EDITION_99999_TEST_ONLY = 99999; + + // Placeholder for specifying unbounded edition support. This should only + // ever be used by plugins that can expect to never require any changes to + // support a new edition. + EDITION_MAX = 0x7FFFFFFF; } // Describes a complete .proto file. @@ -86,8 +128,19 @@ message FileDescriptorProto { optional SourceCodeInfo source_code_info = 9; // The syntax of the proto file. - // The supported values are "proto2" and "proto3". + // The supported values are "proto2", "proto3", and "editions". + // + // If `edition` is present, this value must be "editions". + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. optional string syntax = 12; + + // The edition of the proto file. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional Edition edition = 14; } // Describes a message type. @@ -129,6 +182,51 @@ message ExtensionRangeOptions { // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; + message Declaration { + // The extension number declared within the extension range. + optional int32 number = 1; + + // The fully-qualified name of the extension field. There must be a leading + // dot in front of the full name. + optional string full_name = 2; + + // The fully-qualified type name of the extension field. Unlike + // Metadata.type, Declaration.type must have a leading dot for messages + // and enums. + optional string type = 3; + + // If true, indicates that the number is reserved in the extension range, + // and any extension field with the number will fail to compile. Set this + // when a declared extension field is deleted. + optional bool reserved = 5; + + // If true, indicates that the extension must be defined as repeated. + // Otherwise the extension must be defined as optional. + optional bool repeated = 6; + + reserved 4; // removed is_repeated + } + + // For external users: DO NOT USE. We are in the process of open sourcing + // extension declaration and executing internal cleanups before it can be + // used externally. + repeated Declaration declaration = 2 [retention = RETENTION_SOURCE]; + + // Any features defined in the specific edition. + optional FeatureSet features = 50; + + // The verification state of the extension range. + enum VerificationState { + // All the extensions of the range must be declared. + DECLARATION = 0; + UNVERIFIED = 1; + } + + // The verification state of the range. + // TODO: flip the default to DECLARATION once all empty ranges + // are marked as UNVERIFIED. + optional VerificationState verification = 3 + [default = UNVERIFIED, retention = RETENTION_SOURCE]; // Clients can define custom options in extensions of this message. See above. extensions 1000 to max; @@ -153,9 +251,10 @@ message FieldDescriptorProto { TYPE_BOOL = 8; TYPE_STRING = 9; // Tag-delimited aggregate. - // Group type is deprecated and not supported in proto3. However, Proto3 + // Group type is deprecated and not supported after google.protobuf. However, Proto3 // implementations should still be able to parse the group wire format and - // treat group fields as unknown fields. + // treat group fields as unknown fields. In Editions, the group wire format + // can be enabled via the `message_encoding` feature. TYPE_GROUP = 10; TYPE_MESSAGE = 11; // Length-delimited aggregate. @@ -172,8 +271,11 @@ message FieldDescriptorProto { enum Label { // 0 is reserved for errors LABEL_OPTIONAL = 1; - LABEL_REQUIRED = 2; LABEL_REPEATED = 3; + // The required label is only allowed in google.protobuf. In proto3 and Editions + // it's explicitly prohibited. In Editions, the `field_presence` feature + // can be used to get this behavior. + LABEL_REQUIRED = 2; } optional string name = 1; @@ -199,7 +301,6 @@ message FieldDescriptorProto { // 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 @@ -217,12 +318,12 @@ message FieldDescriptorProto { // 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. + // When proto3_optional is true, this field must 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 @@ -306,7 +407,6 @@ message MethodDescriptorProto { optional bool server_streaming = 6 [default = false]; } - // =================================================================== // Options @@ -347,18 +447,17 @@ message FileOptions { // 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). + // Controls the name of the wrapper Java class generated for the .proto file. + // That class will always contain the .proto file's getDescriptor() method as + // well as any top-level extensions defined in the .proto file. + // If java_multiple_files is disabled, then all the other classes from the + // .proto file will be nested inside the single wrapper outer class. optional string java_outer_classname = 8; - // If set true, then the Java code generator will generate a separate .java + // If enabled, 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 + // file. Thus, these types will *not* be nested inside the wrapper class + // named by java_outer_classname. However, the wrapper 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]; @@ -366,15 +465,18 @@ message FileOptions { // 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. + // A proto2 file can set this to true to opt in to UTF-8 checking for Java, + // which will throw an exception if invalid UTF-8 is parsed from the wire or + // assigned to a string field. + // + // TODO: clarify exactly what kinds of field types this option + // applies to, and update these docs accordingly. + // + // Proto3 files already perform these checks. Setting the option explicitly to + // false has no effect: it cannot be used to opt proto3 files out of UTF-8 + // checks. 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, @@ -391,9 +493,6 @@ message FileOptions { // - 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). @@ -407,7 +506,8 @@ message FileOptions { 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]; + reserved 42; // removed php_generic_services + reserved "php_generic_services"; // Is this file deprecated? // Depending on the target platform, this can emit Deprecated annotations @@ -419,7 +519,6 @@ message FileOptions { // 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; @@ -452,6 +551,11 @@ message FileOptions { // determining the ruby package. optional string ruby_package = 45; + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 50; // The parser stores options it doesn't recognize here. // See the documentation for the "Options" section above. @@ -496,6 +600,8 @@ message MessageOptions { // this is a formalization for deprecating messages. optional bool deprecated = 3 [default = false]; + reserved 4, 5, 6; + // Whether the message is an automatically generated map entry type for the // maps field. // @@ -522,6 +628,23 @@ message MessageOptions { reserved 8; // javalite_serializable reserved 9; // javanano_as_lite + // Enable the legacy handling of JSON field name conflicts. This lowercases + // and strips underscored from the fields before comparison in proto3 only. + // The new behavior takes `json_name` into account and applies to proto2 as + // well. + // + // This should only be used as a temporary measure against broken builds due + // to the change in behavior for JSON field name conflicts. + // + // TODO This is legacy behavior we plan to remove once downstream + // teams have had time to migrate. + optional bool deprecated_legacy_json_field_conflicts = 11 [deprecated = true]; + + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 12; // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; @@ -531,15 +654,24 @@ message MessageOptions { } message FieldOptions { + // NOTE: ctype is deprecated. Use `features.(pb.cpp).string_type` instead. // 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]; + // options below. This option is only implemented to support use of + // [ctype=CORD] and [ctype=STRING] (the default) on non-repeated fields of + // type "bytes" in the open source release. + // TODO: make ctype actually deprecated. + optional CType ctype = 1 [/*deprecated = true,*/ default = STRING]; enum CType { // Default mode. STRING = 0; + // The option [ctype=CORD] may be applied to a non-repeated field of type + // "bytes". It indicates that in C++, the data should be stored in a Cord + // instead of a string. For very large strings, this may reduce memory + // fragmentation. It may also allow better performance when parsing from a + // Cord, or when parsing with aliasing enabled, as the parsed Cord may then + // alias the original buffer. CORD = 1; STRING_PIECE = 2; @@ -548,7 +680,9 @@ message FieldOptions { // 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. + // false will avoid using packed encoding. This option is prohibited in + // Editions, but the `repeated_field_encoding` feature can be used to control + // the behavior. optional bool packed = 2; // The jstype option determines the JavaScript type used for values of the @@ -591,19 +725,18 @@ message FieldOptions { // 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. + // Note that lazy message fields are still eagerly verified to check + // ill-formed wireformat or missing required fields. Calling IsInitialized() + // on the outer message would fail if the inner message has missing required + // fields. Failed verification would result in parsing failure (except when + // uninitialized messages are acceptable). optional bool lazy = 5 [default = false]; + // unverified_lazy does no correctness checks on the byte stream. This should + // only be used where lazy with verification is prohibitive for performance + // reasons. + optional bool unverified_lazy = 15 [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 @@ -613,6 +746,70 @@ message FieldOptions { // For Google-internal migration only. Do not use. optional bool weak = 10 [default = false]; + // Indicate that the field value should not be printed out when using debug + // formats, e.g. when the field contains sensitive credentials. + optional bool debug_redact = 16 [default = false]; + + // If set to RETENTION_SOURCE, the option will be omitted from the binary. + enum OptionRetention { + RETENTION_UNKNOWN = 0; + RETENTION_RUNTIME = 1; + RETENTION_SOURCE = 2; + } + + optional OptionRetention retention = 17; + + // This indicates the types of entities that the field may apply to when used + // as an option. If it is unset, then the field may be freely used as an + // option on any kind of entity. + enum OptionTargetType { + TARGET_TYPE_UNKNOWN = 0; + TARGET_TYPE_FILE = 1; + TARGET_TYPE_EXTENSION_RANGE = 2; + TARGET_TYPE_MESSAGE = 3; + TARGET_TYPE_FIELD = 4; + TARGET_TYPE_ONEOF = 5; + TARGET_TYPE_ENUM = 6; + TARGET_TYPE_ENUM_ENTRY = 7; + TARGET_TYPE_SERVICE = 8; + TARGET_TYPE_METHOD = 9; + } + + repeated OptionTargetType targets = 19; + + message EditionDefault { + optional Edition edition = 3; + optional string value = 2; // Textproto value. + } + repeated EditionDefault edition_defaults = 20; + + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 21; + + // Information about the support window of a feature. + message FeatureSupport { + // The edition that this feature was first available in. In editions + // earlier than this one, the default assigned to EDITION_LEGACY will be + // used, and proto files will not be able to override it. + optional Edition edition_introduced = 1; + + // The edition this feature becomes deprecated in. Using this after this + // edition may trigger warnings. + optional Edition edition_deprecated = 2; + + // The deprecation warning text if this feature is used after the edition it + // was marked deprecated in. + optional string deprecation_warning = 3; + + // The edition this feature is no longer available in. In editions after + // this one, the last default assigned will be used, and proto files will + // not be able to override it. + optional Edition edition_removed = 4; + } + optional FeatureSupport feature_support = 22; // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; @@ -620,10 +817,17 @@ message FieldOptions { // Clients can define custom options in extensions of this message. See above. extensions 1000 to max; - reserved 4; // removed jtype + reserved 4; // removed jtype + reserved 18; // reserve target, target_obsolete_do_not_use } message OneofOptions { + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 1; + // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; @@ -645,6 +849,20 @@ message EnumOptions { reserved 5; // javanano_as_lite + // Enable the legacy handling of JSON field name conflicts. This lowercases + // and strips underscored from the fields before comparison in proto3 only. + // The new behavior takes `json_name` into account and applies to proto2 as + // well. + // TODO Remove this legacy behavior once downstream teams have + // had time to migrate. + optional bool deprecated_legacy_json_field_conflicts = 6 [deprecated = true]; + + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 7; + // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; @@ -659,6 +877,20 @@ message EnumValueOptions { // this is a formalization for deprecating enum values. optional bool deprecated = 1 [default = false]; + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 2; + + // Indicate that fields annotated with this enum value should not be printed + // out when using debug formats, e.g. when the field contains sensitive + // credentials. + optional bool debug_redact = 3 [default = false]; + + // Information about the support window of a feature value. + optional FieldOptions.FeatureSupport feature_support = 4; + // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; @@ -668,6 +900,12 @@ message EnumValueOptions { message ServiceOptions { + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 34; + // 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 @@ -710,6 +948,12 @@ message MethodOptions { optional IdempotencyLevel idempotency_level = 34 [default = IDEMPOTENCY_UNKNOWN]; + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 35; + // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; @@ -717,7 +961,6 @@ message MethodOptions { 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, @@ -728,8 +971,8 @@ 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". + // E.g.,{ ["foo", false], ["bar.baz", true], ["moo", false] } represents + // "foo.(bar.baz).moo". message NamePart { required string name_part = 1; required bool is_extension = 2; @@ -746,6 +989,195 @@ message UninterpretedOption { optional string aggregate_value = 8; } +// =================================================================== +// Features + +// TODO Enums in C++ gencode (and potentially other languages) are +// not well scoped. This means that each of the feature enums below can clash +// with each other. The short names we've chosen maximize call-site +// readability, but leave us very open to this scenario. A future feature will +// be designed and implemented to handle this, hopefully before we ever hit a +// conflict here. +message FeatureSet { + enum FieldPresence { + FIELD_PRESENCE_UNKNOWN = 0; + EXPLICIT = 1; + IMPLICIT = 2; + LEGACY_REQUIRED = 3; + } + optional FieldPresence field_presence = 1 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "EXPLICIT" }, + edition_defaults = { edition: EDITION_PROTO3, value: "IMPLICIT" }, + edition_defaults = { edition: EDITION_2023, value: "EXPLICIT" } + ]; + + enum EnumType { + ENUM_TYPE_UNKNOWN = 0; + OPEN = 1; + CLOSED = 2; + } + optional EnumType enum_type = 2 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_ENUM, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "CLOSED" }, + edition_defaults = { edition: EDITION_PROTO3, value: "OPEN" } + ]; + + enum RepeatedFieldEncoding { + REPEATED_FIELD_ENCODING_UNKNOWN = 0; + PACKED = 1; + EXPANDED = 2; + } + optional RepeatedFieldEncoding repeated_field_encoding = 3 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "EXPANDED" }, + edition_defaults = { edition: EDITION_PROTO3, value: "PACKED" } + ]; + + enum Utf8Validation { + UTF8_VALIDATION_UNKNOWN = 0; + VERIFY = 2; + NONE = 3; + reserved 1; + } + optional Utf8Validation utf8_validation = 4 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "NONE" }, + edition_defaults = { edition: EDITION_PROTO3, value: "VERIFY" } + ]; + + enum MessageEncoding { + MESSAGE_ENCODING_UNKNOWN = 0; + LENGTH_PREFIXED = 1; + DELIMITED = 2; + } + optional MessageEncoding message_encoding = 5 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "LENGTH_PREFIXED" } + ]; + + enum JsonFormat { + JSON_FORMAT_UNKNOWN = 0; + ALLOW = 1; + LEGACY_BEST_EFFORT = 2; + } + optional JsonFormat json_format = 6 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_MESSAGE, + targets = TARGET_TYPE_ENUM, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "LEGACY_BEST_EFFORT" }, + edition_defaults = { edition: EDITION_PROTO3, value: "ALLOW" } + ]; + + enum EnforceNamingStyle { + ENFORCE_NAMING_STYLE_UNKNOWN = 0; + STYLE2024 = 1; + STYLE_LEGACY = 2; + } + optional EnforceNamingStyle enforce_naming_style = 7 [ + retention = RETENTION_SOURCE, + targets = TARGET_TYPE_FILE, + targets = TARGET_TYPE_EXTENSION_RANGE, + targets = TARGET_TYPE_MESSAGE, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_ONEOF, + targets = TARGET_TYPE_ENUM, + targets = TARGET_TYPE_ENUM_ENTRY, + targets = TARGET_TYPE_SERVICE, + targets = TARGET_TYPE_METHOD, + feature_support = { + edition_introduced: EDITION_2024, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "STYLE_LEGACY" }, + edition_defaults = { edition: EDITION_2024, value: "STYLE2024" } + ]; + + reserved 999; + + extensions 1000 to 9994 [ + declaration = { + number: 1000, + full_name: ".pb.cpp", + type: ".pb.CppFeatures" + }, + declaration = { + number: 1001, + full_name: ".pb.java", + type: ".pb.JavaFeatures" + }, + declaration = { number: 1002, full_name: ".pb.go", type: ".pb.GoFeatures" }, + declaration = { + number: 9990, + full_name: ".pb.proto1", + type: ".pb.Proto1Features" + } + ]; + + extensions 9995 to 9999; // For internal testing + extensions 10000; // for https://github.com/bufbuild/protobuf-es +} + +// A compiled specification for the defaults of a set of features. These +// messages are generated from FeatureSet extensions and can be used to seed +// feature resolution. The resolution with this object becomes a simple search +// for the closest matching edition, followed by proto merges. +message FeatureSetDefaults { + // A map from every known edition with a unique set of defaults to its + // defaults. Not all editions may be contained here. For a given edition, + // the defaults at the closest matching edition ordered at or before it should + // be used. This field must be in strict ascending order by edition. + message FeatureSetEditionDefault { + optional Edition edition = 3; + + // Defaults of features that can be overridden in this edition. + optional FeatureSet overridable_features = 4; + + // Defaults of features that can't be overridden in this edition. + optional FeatureSet fixed_features = 5; + + reserved 1, 2; + reserved "features"; + } + repeated FeatureSetEditionDefault defaults = 1; + + // The minimum supported edition (inclusive) when this was constructed. + // Editions before this will not have defaults. + optional Edition minimum_edition = 4; + + // The maximum known edition (inclusive) when this was constructed. Editions + // after this will not have reliable defaults. + optional Edition maximum_edition = 5; +} + // =================================================================== // Optional source code info @@ -801,8 +1233,8 @@ message SourceCodeInfo { // 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: + // the root FileDescriptorProto to the place where the definition appears. + // For example, this path: // [ 4, 3, 2, 7, 1 ] // refers to: // file.message_type(3) // 4, 3 @@ -856,13 +1288,13 @@ message SourceCodeInfo { // // Comment attached to baz. // // Another line attached to baz. // - // // Comment attached to qux. + // // Comment attached to moo. // // - // // Another line attached to qux. - // optional double qux = 4; + // // Another line attached to moo. + // optional double moo = 4; // // // Detached comment for corge. This is not leading or trailing comments - // // to qux or corge because there are blank lines separating it from + // // to moo or corge because there are blank lines separating it from // // both. // // // Detached comment for corge paragraph 2. @@ -880,6 +1312,13 @@ message SourceCodeInfo { optional string trailing_comments = 4; repeated string leading_detached_comments = 6; } + + // Extensions for tooling. + extensions 536000000 [declaration = { + number: 536000000 + type: ".buf.descriptor.v1.SourceCodeInfoExtension" + full_name: ".buf.descriptor.v1.buf_source_code_info_extension" + }]; } // Describes the relationship between generated code and its original source @@ -902,8 +1341,20 @@ message GeneratedCodeInfo { 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 + // relates to the identified object. The end offset should be one past // the last relevant byte (so the length of the text = end - begin). optional int32 end = 4; + + // Represents the identified object's effect on the element in the original + // .proto file. + enum Semantic { + // There is no effect or the effect is indescribable. + NONE = 0; + // The element is set or otherwise mutated. + SET = 1; + // An alias to the element is returned. + ALIAS = 2; + } + optional Semantic semantic = 5; } } diff --git a/lamda/google/protobuf/duration.proto b/lamda/google/protobuf/duration.proto index 81c3e36..41f40c2 100644 --- a/lamda/google/protobuf/duration.proto +++ b/lamda/google/protobuf/duration.proto @@ -32,13 +32,13 @@ 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"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; // A Duration represents a signed, fixed-length span of time represented // as a count of seconds and fractions of seconds at nanosecond @@ -99,7 +99,6 @@ option objc_class_prefix = "GPB"; // 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: diff --git a/lamda/google/protobuf/empty.proto b/lamda/google/protobuf/empty.proto index 5f992de..b87c89d 100644 --- a/lamda/google/protobuf/empty.proto +++ b/lamda/google/protobuf/empty.proto @@ -32,12 +32,12 @@ 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 csharp_namespace = "Google.Protobuf.WellKnownTypes"; option cc_enable_arenas = true; // A generic empty message that you can re-use to avoid defining duplicated @@ -48,5 +48,4 @@ option cc_enable_arenas = true; // rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); // } // -// The JSON representation for `Empty` is empty JSON object `{}`. message Empty {} diff --git a/lamda/google/protobuf/field_mask.proto b/lamda/google/protobuf/field_mask.proto index 6b5104f..b28334b 100644 --- a/lamda/google/protobuf/field_mask.proto +++ b/lamda/google/protobuf/field_mask.proto @@ -32,11 +32,11 @@ 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 csharp_namespace = "Google.Protobuf.WellKnownTypes"; option go_package = "google.golang.org/protobuf/types/known/fieldmaskpb"; option cc_enable_arenas = true; diff --git a/lamda/google/protobuf/source_context.proto b/lamda/google/protobuf/source_context.proto index 06bfc43..135f50f 100644 --- a/lamda/google/protobuf/source_context.proto +++ b/lamda/google/protobuf/source_context.proto @@ -32,11 +32,11 @@ 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 csharp_namespace = "Google.Protobuf.WellKnownTypes"; option go_package = "google.golang.org/protobuf/types/known/sourcecontextpb"; // `SourceContext` represents information about the source of a diff --git a/lamda/google/protobuf/struct.proto b/lamda/google/protobuf/struct.proto index 545215c..1bf0c1a 100644 --- a/lamda/google/protobuf/struct.proto +++ b/lamda/google/protobuf/struct.proto @@ -32,13 +32,13 @@ 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"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; // `Struct` represents a structured data value, consisting of fields // which map to dynamically typed values. In some languages, `Struct` @@ -55,8 +55,8 @@ message Struct { // `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. +// list of values. A producer of value is expected to set one of these +// variants. Absence of any variant indicates an error. // // The JSON representation for `Value` is JSON value. message Value { @@ -80,7 +80,7 @@ message Value { // `NullValue` is a singleton enumeration to represent the null value for the // `Value` type union. // -// The JSON representation for `NullValue` is JSON `null`. +// The JSON representation for `NullValue` is JSON `null`. enum NullValue { // Null value. NULL_VALUE = 0; diff --git a/lamda/google/protobuf/timestamp.proto b/lamda/google/protobuf/timestamp.proto index 3b2df6d..fd0bc07 100644 --- a/lamda/google/protobuf/timestamp.proto +++ b/lamda/google/protobuf/timestamp.proto @@ -32,13 +32,13 @@ 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"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; // 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 @@ -90,7 +90,6 @@ option objc_class_prefix = "GPB"; // 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(); @@ -99,7 +98,6 @@ option objc_class_prefix = "GPB"; // Timestamp.newBuilder().setSeconds(now.getEpochSecond()) // .setNanos(now.getNano()).build(); // -// // Example 6: Compute Timestamp from current time in Python. // // timestamp = Timestamp() @@ -129,10 +127,9 @@ option objc_class_prefix = "GPB"; // [`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 +// http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime() // ) 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 diff --git a/lamda/google/protobuf/type.proto b/lamda/google/protobuf/type.proto index d3f6a68..48cb11e 100644 --- a/lamda/google/protobuf/type.proto +++ b/lamda/google/protobuf/type.proto @@ -35,12 +35,12 @@ 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 csharp_namespace = "Google.Protobuf.WellKnownTypes"; option go_package = "google.golang.org/protobuf/types/known/typepb"; // A protocol buffer message type. @@ -57,6 +57,8 @@ message Type { SourceContext source_context = 5; // The source syntax. Syntax syntax = 6; + // The source edition string, only valid when syntax is SYNTAX_EDITIONS. + string edition = 7; } // A single field of a message type. @@ -151,6 +153,8 @@ message Enum { SourceContext source_context = 4; // The source syntax. Syntax syntax = 5; + // The source edition string, only valid when syntax is SYNTAX_EDITIONS. + string edition = 6; } // Enum value definition. @@ -184,4 +188,6 @@ enum Syntax { SYNTAX_PROTO2 = 0; // Syntax `proto3`. SYNTAX_PROTO3 = 1; + // Syntax `editions`. + SYNTAX_EDITIONS = 2; } diff --git a/lamda/google/protobuf/wrappers.proto b/lamda/google/protobuf/wrappers.proto index d49dd53..e583e7c 100644 --- a/lamda/google/protobuf/wrappers.proto +++ b/lamda/google/protobuf/wrappers.proto @@ -27,11 +27,18 @@ // 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. +// +// Wrappers for primitive (non-message) types. These types were needed +// for legacy reasons and are not recommended for use in new APIs. +// +// Historically these wrappers were useful to have presence on proto3 primitive +// fields, but proto3 syntax has been updated to support the `optional` keyword. +// Using that keyword is now the strongly preferred way to add presence to +// proto3 primitive fields. +// +// A secondary usecase was to embed primitives in the `google.protobuf.Any` +// type: it is now recommended that you embed your value in your own wrapper +// message which can be specifically documented. // // These wrappers have no meaningful use within repeated fields as they lack // the ability to detect presence on individual elements. @@ -42,17 +49,20 @@ 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"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; // Wrapper message for `double`. // // The JSON representation for `DoubleValue` is JSON number. +// +// Not recommended for use in new APIs, but still useful for legacy APIs and +// has no plan to be removed. message DoubleValue { // The double value. double value = 1; @@ -61,6 +71,9 @@ message DoubleValue { // Wrapper message for `float`. // // The JSON representation for `FloatValue` is JSON number. +// +// Not recommended for use in new APIs, but still useful for legacy APIs and +// has no plan to be removed. message FloatValue { // The float value. float value = 1; @@ -69,6 +82,9 @@ message FloatValue { // Wrapper message for `int64`. // // The JSON representation for `Int64Value` is JSON string. +// +// Not recommended for use in new APIs, but still useful for legacy APIs and +// has no plan to be removed. message Int64Value { // The int64 value. int64 value = 1; @@ -77,6 +93,9 @@ message Int64Value { // Wrapper message for `uint64`. // // The JSON representation for `UInt64Value` is JSON string. +// +// Not recommended for use in new APIs, but still useful for legacy APIs and +// has no plan to be removed. message UInt64Value { // The uint64 value. uint64 value = 1; @@ -85,6 +104,9 @@ message UInt64Value { // Wrapper message for `int32`. // // The JSON representation for `Int32Value` is JSON number. +// +// Not recommended for use in new APIs, but still useful for legacy APIs and +// has no plan to be removed. message Int32Value { // The int32 value. int32 value = 1; @@ -93,6 +115,9 @@ message Int32Value { // Wrapper message for `uint32`. // // The JSON representation for `UInt32Value` is JSON number. +// +// Not recommended for use in new APIs, but still useful for legacy APIs and +// has no plan to be removed. message UInt32Value { // The uint32 value. uint32 value = 1; @@ -101,6 +126,9 @@ message UInt32Value { // Wrapper message for `bool`. // // The JSON representation for `BoolValue` is JSON `true` and `false`. +// +// Not recommended for use in new APIs, but still useful for legacy APIs and +// has no plan to be removed. message BoolValue { // The bool value. bool value = 1; @@ -109,6 +137,9 @@ message BoolValue { // Wrapper message for `string`. // // The JSON representation for `StringValue` is JSON string. +// +// Not recommended for use in new APIs, but still useful for legacy APIs and +// has no plan to be removed. message StringValue { // The string value. string value = 1; @@ -117,6 +148,9 @@ message StringValue { // Wrapper message for `bytes`. // // The JSON representation for `BytesValue` is JSON string. +// +// Not recommended for use in new APIs, but still useful for legacy APIs and +// has no plan to be removed. message BytesValue { // The bytes value. bytes value = 1; diff --git a/lamda/rpc/application.proto b/lamda/rpc/application.proto index 4393b5b..bdbbf8b 100644 --- a/lamda/rpc/application.proto +++ b/lamda/rpc/application.proto @@ -1,4 +1,4 @@ -// Copyright 2022 rev1si0n (ihaven0emmail@gmail.com). All rights reserved. +// 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 @@ -8,76 +8,194 @@ package lamda.rpc; import "google/protobuf/struct.proto"; enum GrantType { - GRANT_ALLOW = 0; - GRANT_DENY = 1; - GRANT_IGNORE = 2; + 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; +} + +enum ActivityExtrasNullValue { + ACTIVITY_EXTRAS_NULL_VALUE = 0; +} + +message ActivityExtrasValue { + // Struct Compat + oneof kind { + ActivityExtrasNullValue null_value = 1; + double double_value = 2; + string string_value = 3; + bool bool_value = 4; + int64 int64_value = 7; + } +} + +message ActivityExtras { + map fields = 1; } message ApplicationRequest { - string name = 1; - string permission = 2; - GrantType mode = 3; - string path = 4; + string name = 1; + string permission = 2; + GrantType mode = 3; + string path = 4; + uint32 user = 5; + uint32 display = 16; } 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; + string package = 1; + string action = 2; + string category = 3; + string component = 4; + ActivityExtras extras = 5; + int64 flags = 7; + bool debug = 8; + string data = 9; + uint32 user = 10; + uint32 display = 16; } 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; -} - -message ApplicationActivityInfoList { - repeated ApplicationActivityInfo activities = 1; + string action = 2; + string component = 4; + repeated string categories = 6; } message ApplicationPermissions { - repeated string permissions = 1; + 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; + 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; + string label = 14; } message ApplicationProcess { - repeated string packages = 1; - string processName = 2; - int64 uid = 3; - int64 pid = 4; + repeated string packages = 1; + string processName = 2; + int64 uid = 3; + int64 pid = 4; + string label = 5; } message ApplicationProcesses { - repeated ApplicationProcess processes = 1; + repeated ApplicationProcess processes = 1; +} + +message InstalledApplicationInfo { + string packageName = 1; + string label = 2; + int64 uid = 3; + int64 user = 4; + bool enabled = 5; + bool system = 6; + string versionName = 7; +} + +message InstalledApplications { + repeated InstalledApplicationInfo applications = 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; + string process = 9; } -message ApplicationPkgNames { - repeated string names = 1; +message HookRpcRequest { + string package = 1; + string callinfo = 2; + uint32 user = 3; } + +message HookRpcResponse { + string package = 1; + string callresult = 2; +} + +message InstallSessionCreateRequest { + uint32 user = 1; + string installerPackageName = 2; + uint64 sizeBytes = 4; + string package = 5; + bool dontKillApp = 14; + bool replaceExisting = 15; + bool allowTest = 16; + bool requestDowngrade = 17; + bool grantRuntimePermissions=18; +} + +message InstallSessionWriteRequest { + int32 session = 1; + string path = 2; + string name = 3; + bool delete = 10; +} + +message InstallSessionAbandonRequest { + int32 session = 1; +} + +message InstallSessionQueryRequest { + int32 session = 1; +} + +message InstallSessionCommitRequest { + int32 session = 1; + bool wait = 2; + uint64 timeout = 3; +} + + +message InstallSessionCreateResponse { + uint32 user = 1; + int32 session = 2; +} + +message InstallSessionWriteResponse { + int32 session = 1; + string name = 2; + uint64 writtenBytes = 3; + uint64 totalBytes = 4; +} + +message InstallSessionStatusResponse { + uint32 user = 1; + int32 session = 2; + string package = 3; + int32 status = 4; + int32 legacyStatus = 5; + string statusMessage = 6; + bool finished = 7; + bool committed = 8; + bool abandoned = 9; + float progress = 10; + bool success = 11; +} \ No newline at end of file diff --git a/lamda/rpc/debug.proto b/lamda/rpc/debug.proto index be2c303..291519b 100644 --- a/lamda/rpc/debug.proto +++ b/lamda/rpc/debug.proto @@ -1,4 +1,4 @@ -// Copyright 2022 rev1si0n (ihaven0emmail@gmail.com). All rights reserved. +// 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 @@ -7,11 +7,6 @@ package lamda.rpc; import "google/protobuf/struct.proto"; -message IDAConfigRequest { - uint32 port = 1; - google.protobuf.Struct environment = 8; -} - message ADBDConfigRequest { string adb_pubkey = 1; } \ No newline at end of file diff --git a/lamda/rpc/file.proto b/lamda/rpc/file.proto index a056590..7c78a70 100644 --- a/lamda/rpc/file.proto +++ b/lamda/rpc/file.proto @@ -1,4 +1,4 @@ -// Copyright 2022 rev1si0n (ihaven0emmail@gmail.com). All rights reserved. +// 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 diff --git a/lamda/rpc/policy.proto b/lamda/rpc/policy.proto index 1dcd593..1627143 100644 --- a/lamda/rpc/policy.proto +++ b/lamda/rpc/policy.proto @@ -1,4 +1,4 @@ -// Copyright 2022 rev1si0n (ihaven0emmail@gmail.com). All rights reserved. +// 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 @@ -6,8 +6,8 @@ syntax = "proto3"; package lamda.rpc; message SelinuxPolicyRequest { - string source = 1; - string target = 2; - string tclass = 3; - string action = 4; + string source = 1; + string target = 2; + string tclass = 3; + string action = 4; } \ No newline at end of file diff --git a/lamda/rpc/proxy.proto b/lamda/rpc/proxy.proto index 538e01c..4d35fe0 100644 --- a/lamda/rpc/proxy.proto +++ b/lamda/rpc/proxy.proto @@ -1,4 +1,4 @@ -// Copyright 2022 rev1si0n (ihaven0emmail@gmail.com). All rights reserved. +// 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 @@ -7,23 +7,45 @@ 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; + 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; + string interface = 12; } enum OpenVPNProto { @@ -70,16 +92,18 @@ enum OpenVPNEncryption { } 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; + 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; } diff --git a/lamda/rpc/services.proto b/lamda/rpc/services.proto index a37ce09..f980939 100644 --- a/lamda/rpc/services.proto +++ b/lamda/rpc/services.proto @@ -1,4 +1,4 @@ -// Copyright 2022 rev1si0n (ihaven0emmail@gmail.com). All rights reserved. +// 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 @@ -18,6 +18,7 @@ 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"; @@ -25,20 +26,18 @@ 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 currentApplication(Integer) returns (ApplicationInfo) {} + rpc enumerateInstalledApps(Integer) returns (InstalledApplications) {} 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) {} @@ -46,26 +45,29 @@ service Application { 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) {} + + rpc installSessionCreate(InstallSessionCreateRequest) returns (InstallSessionCreateResponse) {} + rpc installSessionWrite(InstallSessionWriteRequest) returns (InstallSessionWriteResponse) {} + rpc installSessionCommit(InstallSessionCommitRequest) returns (InstallSessionStatusResponse) {} + rpc installSessionQuery(InstallSessionQueryRequest) returns (InstallSessionStatusResponse) {} + rpc installSessionAbandon(InstallSessionAbandonRequest) returns (Boolean) {} } service Debug { - rpc isIDARunning(google.protobuf.Empty) returns (Boolean) {} - rpc isIDA64Running(google.protobuf.Empty) returns (Boolean) {} rpc isAndroidDebugBridgeRunning(google.protobuf.Empty) returns (Boolean) {} rpc installADBPubKey(ADBDConfigRequest) returns (Boolean) {} rpc uninstallADBPubKey(ADBDConfigRequest) returns (Boolean) {} - rpc startIDA(IDAConfigRequest) returns (Boolean) {} - rpc startIDA64(IDAConfigRequest) returns (Boolean) {} rpc startAndroidDebugBridge(google.protobuf.Empty) returns (Boolean) {} - - rpc stopIDA(google.protobuf.Empty) returns (Boolean) {} - rpc stopIDA64(google.protobuf.Empty) returns (Boolean) {} rpc stopAndroidDebugBridge(google.protobuf.Empty) returns (Boolean) {} - rpc setDebuggable(google.protobuf.Empty) returns (Boolean) {} } service Proxy { @@ -126,7 +128,7 @@ service UiAutomator { 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 freezeRotation(FreezeRotationRequest) returns (Boolean) {} rpc setOrientation(OrientationRequest) returns (Boolean) {} rpc pressKey(PressKeyRequest) returns (Boolean) {} rpc pressKeyCode(PressKeyRequest) returns (Boolean) {} @@ -134,7 +136,7 @@ service UiAutomator { rpc deviceInfo(google.protobuf.Empty) returns (DeviceInfo) {} rpc takeScreenshot(TakeScreenshotRequest) returns (Bytes) {} - rpc dumpWindowHierarchy(google.protobuf.Empty) returns (Bytes) {} + rpc dumpWindowHierarchy(DumpWindowHierarchyRequest) returns (Bytes) {} rpc waitForIdle(Integer) returns (Boolean) {} rpc selectorTakeScreenshot(SelectorTakeScreenshotRequest) returns (Bytes) {} @@ -146,6 +148,7 @@ service UiAutomator { rpc selectorClick(SelectorClickRequest) returns (Boolean) {} rpc selectorClickExists(SelectorClickRequest) returns (Boolean) {} rpc selectorLongClick(SelectorClickRequest) returns (Boolean) {} + rpc pointLongClick(ClickPointRequest) returns (Boolean) {} rpc selectorExists(SelectorOnlyRequest) returns (Boolean) {} rpc getLastToast(google.protobuf.Empty) returns (ToastInfo) {} @@ -169,21 +172,34 @@ service UiAutomator { 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 setWatcherLoopEnabled(WatcherControlRequest) returns (Boolean) {} + rpc getWatcherLoopEnabled(WatcherControlRequest) returns (Boolean) {} + rpc setWatcherEnable(WatcherControlRequest) returns (Boolean) {} + rpc getWatcherEnable(WatcherControlRequest) returns (Boolean) {} + rpc removeWatcher(WatcherControlRequest) returns (Boolean) {} + rpc removeAllWatchers(WatcherControlRequest) returns (Boolean) {} + rpc getEnabledWatchers(WatcherControlRequest) returns (WatcherInfoList) {} + rpc getWatchers(WatcherControlRequest) returns (WatcherInfoList) {} + rpc getWatcherTriggeredCount(WatcherControlRequest) returns (Integer) {} + rpc resetWatcherTriggeredCount(WatcherControlRequest) returns (Boolean) {} + rpc registerClickUiObjectWatcher(WatcherControlRequest) returns (Boolean) {} + rpc registerPressKeysWatcher(WatcherControlRequest) returns (Boolean) {} + rpc registerNoneOpWatcher(WatcherControlRequest) returns (Boolean) {} + + rpc findSimilarImage(FindImageRequest) returns (FindImageResponse) {} + + rpc listAllDisplays(google.protobuf.Empty) returns (DisplayInfoList) {} + rpc createVirtualDisplay(CreateVirtualDisplayRequest) returns (Integer) {} + rpc getDisplayInfo(Integer) returns (DisplayInfo) {} + rpc releaseVirtualDisplay(Integer) returns (Boolean) {} + + rpc setDisplayImePolicy(ImePolicyRequest) returns (Boolean) {} + rpc getDisplayImePolicy(Integer) returns (Integer) {} } service Wifi { @@ -214,16 +230,21 @@ 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 reload(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 { @@ -236,6 +257,20 @@ service File { 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) {} } \ No newline at end of file diff --git a/lamda/rpc/settings.proto b/lamda/rpc/settings.proto index 19e553c..8d9f9e1 100644 --- a/lamda/rpc/settings.proto +++ b/lamda/rpc/settings.proto @@ -1,4 +1,4 @@ -// Copyright 2022 rev1si0n (ihaven0emmail@gmail.com). All rights reserved. +// 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 @@ -12,7 +12,7 @@ enum Group { } message SettingsRequest { - Group group = 1; - string name = 2; - string value = 3; + Group group = 1; + string name = 2; + string value = 3; } diff --git a/lamda/rpc/shell.proto b/lamda/rpc/shell.proto index e419ed7..978796d 100644 --- a/lamda/rpc/shell.proto +++ b/lamda/rpc/shell.proto @@ -1,4 +1,4 @@ -// Copyright 2022 rev1si0n (ihaven0emmail@gmail.com). All rights reserved. +// 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 @@ -6,19 +6,20 @@ syntax = "proto3"; package lamda.rpc; message ShellRequest { - string tid = 1; - string name = 2; - string script = 3; + string tid = 1; + string name = 2; + string script = 3; + int32 timeout = 4; } message ShellResult { - int32 exitstatus = 1; - string stdout = 2; - string stderr = 3; + int32 exitstatus = 1; + bytes stdout = 2; + bytes stderr = 3; } message ShellTask { - string tid = 1; - string name = 2; - int32 pid = 3; + string tid = 1; + string name = 2; + int32 pid = 3; } \ No newline at end of file diff --git a/lamda/rpc/status.proto b/lamda/rpc/status.proto index ee7705e..57678aa 100644 --- a/lamda/rpc/status.proto +++ b/lamda/rpc/status.proto @@ -1,4 +1,4 @@ -// Copyright 2022 rev1si0n (ihaven0emmail@gmail.com). All rights reserved. +// 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 @@ -6,60 +6,60 @@ syntax = "proto3"; package lamda.rpc; message BatteryInfo { - bool batt_charging = 1; - int32 batt_percent = 2; - float batt_temperature = 3; + 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; + 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; + 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; + 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; + 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; + 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; } diff --git a/lamda/rpc/storage.proto b/lamda/rpc/storage.proto new file mode 100644 index 0000000..c6e82a0 --- /dev/null +++ b/lamda/rpc/storage.proto @@ -0,0 +1,13 @@ +// 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; +} \ No newline at end of file diff --git a/lamda/rpc/types.proto b/lamda/rpc/types.proto index f9d3153..9c56392 100644 --- a/lamda/rpc/types.proto +++ b/lamda/rpc/types.proto @@ -1,4 +1,4 @@ -// Copyright 2022 rev1si0n (ihaven0emmail@gmail.com). All rights reserved. +// 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 @@ -6,19 +6,19 @@ syntax = "proto3"; package lamda.rpc; message Boolean { - bool value = 1; + bool value = 1; } message Integer { - int64 value = 1; + int64 value = 1; } message String { - string value = 1; + string value = 1; } message Bytes { - bytes value = 1; + bytes value = 1; } message Empty { diff --git a/lamda/rpc/uiautomator.proto b/lamda/rpc/uiautomator.proto index bcf8159..2c475ac 100644 --- a/lamda/rpc/uiautomator.proto +++ b/lamda/rpc/uiautomator.proto @@ -1,4 +1,4 @@ -// Copyright 2022 rev1si0n (ihaven0emmail@gmail.com). All rights reserved. +// 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 @@ -22,6 +22,7 @@ enum Orientation { ORIEN_LEFT = 1; ORIEN_UPSIDEDOWN = 2; ORIEN_RIGHT = 3; + ORIEN_UNFREEZE = 4; } enum Key { @@ -336,6 +337,31 @@ enum KeyCode { 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; @@ -349,6 +375,12 @@ enum Direction { DIR_RIGHT = 3; } +enum ImePolicy { + IME_LOCAL = 0; + IME_FALLBACK_DISPLAY=1; + IME_HIDE = 2; +} + message ObjInfo { Bound bounds = 1; bool checkable = 2; @@ -408,6 +440,8 @@ message Selector { uint32 instance = 26; repeated string childOrSibling = 27; repeated Selector childOrSiblingSelector = 28; + uint32 resultIndex = 49; + repeated string fields = 50; } message DeviceInfo { @@ -425,130 +459,113 @@ message DeviceInfo { } message SelectorTakeScreenshotRequest { - oneof _selector { - Selector selector = 1; - string object = 2; - } + Selector selector = 1; uint32 quality = 3; + uint32 display = 16; +} +message DumpWindowHierarchyRequest { + bool compressed = 1; + uint32 display = 16; } message SelectorSetTextRequest { - oneof _selector { - Selector selector = 1; - string object = 2; - } + Selector selector = 1; string text = 3; + uint32 display = 16; } message SelectorClickRequest { - oneof _selector { - Selector selector = 1; - string object = 2; - } + Selector selector = 1; Corner corner = 3; + uint32 timeout = 4; + uint32 display = 16; } message SelectorOnlyRequest { - oneof _selector { - Selector selector = 1; - string object = 2; - } + Selector selector = 1; + uint32 display = 16; } message SelectorDragToRequest { - oneof _selector { - Selector selector = 1; - string object = 2; - } + Selector selector = 1; oneof _target { Selector target = 3; Point point = 4; } uint32 step = 5; -} -message SelectorScrollToRequest { - oneof _selector { - Selector selector = 1; - string object = 2; - } - oneof _target { - Selector target = 3; - Point point = 4; - } - bool vertical = 5; + uint32 display = 16; } message SelectorWaitRequest { - oneof _selector { - Selector selector = 1; - string object = 2; - } + Selector selector = 1; uint32 timeout = 3; + uint32 display = 16; } message SelectorSwipeRequest { - oneof _selector { - Selector selector = 1; - string object = 2; - } + Selector selector = 1; Direction direction = 3; uint32 step = 4; + uint32 display = 16; } message SelectorFlingRequest { - oneof _selector { - Selector selector = 1; - string object = 2; - } + Selector selector = 1; bool vertical = 3; uint32 maxSwipes = 4; + uint32 display = 16; } message SelectorScrollRequest { - oneof _selector { - Selector selector = 1; - string object = 2; - } + Selector selector = 1; + Selector target = 2; bool vertical = 3; uint32 maxSwipes = 4; uint32 step = 5; + uint32 display = 16; } message SelectorPinchRequest { - oneof _selector { - Selector selector = 1; - string object = 2; - } + Selector selector = 1; uint32 percent = 3; uint32 step = 4; + uint32 display = 16; } message ClickPointRequest { Point point = 1; + uint32 timeout = 2; + uint32 display = 16; } message DragPointRequest { Point A = 1; Point B = 2; uint32 step = 3; + uint32 display = 16; } message SwipePointRequest { Point A = 1; Point B = 2; uint32 step = 3; + uint32 display = 16; } message SwipePointsRequest { repeated Point points = 1; uint32 step = 2; + uint32 display = 16; } message OrientationRequest { Orientation orientation = 1; + uint32 display = 16; } message PressKeyRequest { Key key = 1; uint32 code = 2; uint32 meta = 3; + uint32 display = 16; } message TakeScreenshotRequest { Bound bound = 1; uint32 quality = 2; + uint32 display = 16; } message ClipboardRequest { @@ -556,15 +573,88 @@ message ClipboardRequest { string value = 2; } -message WatcherNameList { - repeated string watchers = 1; +message WatcherInfo { + string name = 1; + bool enabled = 3; } -message WatcherRegistRequest { +message WatcherInfoList { + repeated WatcherInfo watchers = 1; +} + +message WatcherControlRequest { string name = 1; repeated Selector selectors = 2; oneof _target { Selector target = 3; Key key = 4; } + bool enable = 5; + uint32 display = 16; +} + +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; + uint32 display = 16; +} + +message FindImageResponse { + repeated Bound bounds = 1; +} + +message CreateVirtualDisplayRequest { + string name = 1; + uint32 width = 2; + uint32 height = 3; + uint32 densityDpi = 4; +} + +message DisplayInfo { + string name = 1; + uint32 displayId = 2; + uint32 height = 4; + uint32 width = 5; + uint32 densityDpi = 8; + uint32 rotation = 9; + uint32 installOrientation=10; + bool virtual = 16; } + +message DisplayInfoList { + repeated DisplayInfo displays = 1; +} + +message ImePolicyRequest { + ImePolicy policy = 1; + uint32 display = 16; +} + +message FreezeRotationRequest { + bool freeze = 1; + uint32 display = 16; +} \ No newline at end of file diff --git a/lamda/rpc/util.proto b/lamda/rpc/util.proto index 22214bf..44a007d 100644 --- a/lamda/rpc/util.proto +++ b/lamda/rpc/util.proto @@ -1,10 +1,24 @@ -// Copyright 2022 rev1si0n (ihaven0emmail@gmail.com). All rights reserved. +// 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; @@ -46,10 +60,50 @@ message PerformTouchRequest { } message SetPropRequest { - string name = 1; - string value = 2; + string name = 1; + string value = 2; } message CertifiRequest { - bytes cert = 1; + 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; + bool privileged = 6; } + +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; +} \ No newline at end of file diff --git a/lamda/rpc/wifi.proto b/lamda/rpc/wifi.proto index ef3f856..422ae17 100644 --- a/lamda/rpc/wifi.proto +++ b/lamda/rpc/wifi.proto @@ -1,4 +1,4 @@ -// Copyright 2022 rev1si0n (ihaven0emmail@gmail.com). All rights reserved. +// 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 @@ -6,68 +6,68 @@ 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; + 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; + 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; + 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; + repeated WifiInfo stations = 1; } message Network { - int32 nid = 1; - string bssid = 2; - string ssid = 3; - string flags = 4; + int32 nid = 1; + string bssid = 2; + string ssid = 3; + string flags = 4; } message NetworkList { - repeated Network networks = 1; + repeated Network networks = 1; } message NetworkConfig { - Network network = 1; - string name = 2; - string value = 3; + Network network = 1; + string name = 2; + string value = 3; } message WifiConfig { - string name = 1; - string value = 2; + string name = 1; + string value = 2; } message WifiBlacklist { - repeated string bssids = 1; + repeated string bssids = 1; } \ No newline at end of file diff --git a/lamda/types.py b/lamda/types.py index af86685..21b68df 100644 --- a/lamda/types.py +++ b/lamda/types.py @@ -1,4 +1,4 @@ -# Copyright 2022 rev1si0n (ihaven0emmail@gmail.com). All rights reserved. +# 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 diff --git a/properties.example b/properties.example new file mode 100644 index 0000000..b757eaf --- /dev/null +++ b/properties.example @@ -0,0 +1,148 @@ +; properties configuration file (ini format) +; +; DO NOT DIRECTLY COPY OR EDIT THIS FILE. PLEASE ADD OR REMOVE CONFIGURATION FROM THE DEVICE'S REMOTE DESKTOP +; +; This file is the startup configuration file for the lamda server. +; You can use this file to configure the server's startup behavior, such as port, certificate, proxy, +; and other settings. You can configure it within the 65000 remote desktop. + +[DEFAULT] +port=65000 ; Set listening port for LAMDA. + +; webui.* keys are only allowed in DEFAULT section +; You can preset various parameters of the remote desktop. +webui.darkmode = off ; Whether to enable remote desktop darkmode theme. +webui.audio = on ; Whether to enable remote desktop audio. (need android 10+) +webui.upload-path = /data/local/tmp/uploads ; Set the default directory for drag-and-drop upload in remote desktop. +webui.share-clipboard = off ; Whether to enable remote desktop bidirectional clipboard sharing. +webui.touch.use-screen-size = off ; Use the original screen size to calculate touch coordinates (enable in special cases). +webui.webrtc = on ; Whether to enable remote desktop WebRTC. +webui.webrtc.ice-server = stun:stun.device-farm.com ; Set the WebRTC STUN/TURN server addresses (separate multiple entries with commas). + ; example stun server: stun:stun.device-farm.com + ; example turn server: turn://user:pass@example.com:3478?transport=tcp +webui.video.h264 = on ; Whether to enable H264 encoding (improves smoothness). +webui.video.backend = 0 ; Set the video backend implementation (0 = default or 1 = hardware). +webui.video.scale = 0.5 ; Set the video scaling ratio (0.1-1.0, where 1.0 represents full resolution). +webui.video.quality = 50 ; Set video quality (10-100). +webui.video.fps = 35 ; Set the expected video frame rate. + +; Set service 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'. +cert=TEFNREEgU1NMIENFUlRJRklDQVRFIChDTj10ZXN0LFBBU1NXRD1hMWMwZTNlYTcwN2E1NGRlN2EwZjk1KQotLS0tLUJFR0lOIFBSSVZBVEUgS0VZLS0tLS0KTUlJRXZnSUJBREFOQmdrcWhraUc5dzBCQVFFRkFBU0NCS2d3Z2dTa0FnRUFBb0lCQVFEVmNMWlA5b0xRWkRIRgp5V0pTa2U0Z0crSUpmSCtMWlk0cXUzdS9OckRwSHZCN2k5V01rMWxRL2FMSGI0V3ZqelBLK1RITm9rRzc2MENRClhBTUpWS0dmYXRwcmNLUXdvMGhvWDZ2NlhsTVlZUlNRbW9wN3pSaUtnN3ZxKzV2S09DQ2RKcDFlSVNiZXcyTEgKTmYxL3JZelpwa1Q1bHoxTGZkem00eXJBS3VNa0tyZ3pnTzJRcE9CQVdYdmdiWG9BMDdidDdOODZZOGdNZFUwdAp6Ui9EcmhLTi9JMVdYSk11MU4wQW5UbDJRdEhEb0dCN0UyS0xpdmwybDZJdnRrYWJ4RE55Y2lHbUxOUGlMRklrCllPSHlnMUg3MUJ2NU04NE5TWDc1c2xuOXVNUGUzOVNFVlJoU0ptNHcvT2tXZnA5dGpZRUx3dHphdFhoSWJoS3MKaC9OZU1lOWRBZ01CQUFFQ2dnRUFCYlFuWUdlcFdKYjAydURtSnhLNGx2OFNhL0o1dzJpSldMYjk0dW1SUExRKwpTa1E5c1Zpc0JiQU1JNHc2dWFyNEFBVTh3WGxaTndPekMvM2V6dWNHRXFreEFReXM0VDB4SXRPUkF2WExxVlowCkl1WnpxNW53Si9OeFFzeEtmaWhBZkRLYlRmZjdmcG5MWlV0dlpNbG5LWUhQVExtRlFua3drckwyRE5YdDVVOTYKYXJUUDVOY0x1aDQ2dU93alJUOWhaNytYQi9ubU9LeTV4V3hoNWVMQmJJUTJrS0UvdWViUlVYRGZNdG5tbTh2aApRSG9VM3N5dzlZcE1xRDlWQWppcGlqQXRwbUlwa2w1emRWSE52Q1dCSGk2NjZxKzF6cUpHeFVUODBseHo1N2R1ClRvRFFQc1l0OFFEL3ZjNGkxajd0bDZyRzNQWkJNM05LNVR5ZFYyRnlnUUtCZ1FEYUcycnV3aWxyYUdZRzZNQWwKNEF2WW1BY0hHQWUwQjR0ZmtkdS9QandlRWQyWUF2TjJIQWV1Z1ZUSWg1eFplUlIwNFE4ZVNGenBTaEpwREpkNAp1TEhHcXJ2cmpXL1greEVIc081NnNjNjRiem1weWJWQXBlQnA4NlFGSTk4V3FmZkFyN3FzbzhweFJjNmdTRU1uCk5TcXV1Z2psYU05TlJmcUo4ci82RklmQkxRS0JnUUQ2aGJ0Z2dIdVJoTHhHYWNNaERvOVppcXJlNi9HN1dvZnQKR2FGZmFQM2xZNTNmM0hPdSsyNTZMWDY1ZWFYRTdrbXlQOGRDM2VWL24xT3dvTHdBYm1BZ2pEWVd1N09KdGk4QQpPbG9VNmtnTkVwNWcwOHpVWlBaR3NSMkZMd2VpUkgrQ3ZOdFBZakJydmFIQUtVU2lLa3BKdEpWeFpIdUl6SlpGCjVUZkM2VjNrOFFLQmdEeWp0TlpPKzA4V2hvOVROT0VTNnBnOHBHK1BlY3pPOEN3UkZJU1dYQWFNTnd6bGZTVVEKWS81YmpPUDMrRHRVRTZEdlZkRzRrc1IxeUtxV1NxTFF6dlNLVVpjTEN0YUV3bFplRmQvZEFibDdpdyt1dWdzUQpVMVdCM005bENzaDFWeUdtZWdNM3dyZzlqVlk0NFJyTWlHSnQ3TDFEcDZjM1ZwSDJBUFFac3lpOUFvR0JBTk9pCmpmeWtEYitNNXBDRllEWlkybmpHVURzcUQzZzZyb0Y2R1gxRWNOaU1JeDZ1V1h3RkkvdEsyN2RNTU9JQWUzbDkKcjVPcGFPczdhYlBZMVhsM3hQVTUvYWVPd2NrZ2d1d3FYMWN6NDlKSFhFeG9JSzE4N1NBakY5RWZQYyt6RmhVWAovaDA5MGJIeTdPWXM5cklZRDlIY0lETStzNjJKUjVtY1hsTG1Xay9CQW9HQkFKRVhQV05IWEwra1l6My91R1c3CnRKd0hUQzFlbEJjclcvaHpJMWt4ZEhXem5VaXNTWlcyVnA5b0wwSWNrQXVWQkx6eGUvR1h6OGJRTjZkOWwyZDAKdGtmUmo1TmpDOTUzS2N1cTNSekRmVU40cTcyUlVWTWlFOHVvSTBkVVZpalczN0tVMEhLcm1pbDBocU01eW9iNQpVZlhPQ2Q5SlRRSWx5Y2dNWER6Tm00S3oKLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQotLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ3FEQ0NBWkNnQXdJQkFnSVJBUHNjMVBRNXBuSDNhNk1GZkdVTXA2WXdEUVlKS29aSWh2Y05BUUVMQlFBdwpFREVPTUF3R0ExVUVDZ3dGVEVGTlJFRXdIaGNOTWpBd01UQXhNREF3TURBeFdoY05Namt4TWpJNU1EQXdNREF4CldqQVBNUTB3Q3dZRFZRUUREQVIwWlhOME1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0MKQVFFQTFYQzJUL2FDMEdReHhjbGlVcEh1SUJ2aUNYeC9pMldPS3J0N3Z6YXc2Ujd3ZTR2VmpKTlpVUDJpeDIrRgpyNDh6eXZreHphSkJ1K3RBa0Z3RENWU2huMnJhYTNDa01LTklhRityK2w1VEdHRVVrSnFLZTgwWWlvTzc2dnViCnlqZ2duU2FkWGlFbTNzTml4elg5ZjYyTTJhWkUrWmM5UzMzYzV1TXF3Q3JqSkNxNE00RHRrS1RnUUZsNzRHMTYKQU5PMjdlemZPbVBJREhWTkxjMGZ3NjRTamZ5TlZseVRMdFRkQUowNWRrTFJ3NkJnZXhOaWk0cjVkcGVpTDdaRwptOFF6Y25JaHBpelQ0aXhTSkdEaDhvTlIrOVFiK1RQT0RVbCsrYkpaL2JqRDN0L1VoRlVZVWladU1QenBGbjZmCmJZMkJDOExjMnJWNFNHNFNySWZ6WGpIdlhRSURBUUFCTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFBWEQ0L1cKQjBhSW1aWGpQbTRxUnBOazJmUnpjU1g4MGw2TlZaWWxJV3ZYalFxUXdXZnMvSGczZDVzYUpickFmcWVPa1lQdQpjeXJEWFZPdC9RTEVDOTFBSGtjRWJ1R0dPMGNFU2YyOHdUM1UzRnJJb2cxS1VyTURqWFFIb09vZEJpOGdNaVBmCmROcWhMSTdkNDJBTXJKU3dZUTlSUG9vWG9UZ2xDa0d3R291RDhuS0V5MmNHeVMxM3lQcDRseC9TWTR1QkRFU0sKRlErR0ZRTExGQktQZHZNc2x0cHYyQWFMWmR3clF4aFQ2aTU1U1puNStLb3c1TGxYL0RHdUw5UnRPdmZ2T0tzZQpRZ3pOQUg3QkYzbGdvQmJjYk9yZkVQazY1ZEZRN0NXYi91aDZjVmlmSjdxQzkvL0xhdElmb1VQVnJiRXdZL2dRCk5BRXFYclduMGZuYUc0cUEKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQotLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ2xEQ0NBWHdDQVFBd0RRWUpLb1pJaHZjTkFRRUxCUUF3RURFT01Bd0dBMVVFQ2d3RlRFRk5SRUV3SGhjTgpNakF3TVRBeE1EQXdNREF4V2hjTk1qa3hNakk1TURBd01EQXhXakFRTVE0d0RBWURWUVFLREFWTVFVMUVRVENDCkFTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTG5xZkJadnJHWmFxZ2s1bXNDUlJwUHoKcC8rNDY0akJrbmxtVEtldE9ja0RUVXE4VjZmSC8yR2ZiNkhqam9ENXBrQ3RENW1TS2thSE5odXhMWHNGZkVmYwpLbG1ubjNacGp5Tk9IRUEvaUFPMkR5RVlhMDh4U2V2TTdXb2piRjdjTmo1L0RZZzdlYjBpMCsvL2JCbGg4bmxPCmdoU1VoQ1RNNVBDb2ZMRFU4c1ZYdVlBaUdVNlV6QnJJQzB2SEVsdERraUpWTHBjQ3RzS2pFWk9za1BkQWM3dTYKL2FBMFA1R29uWjVVa1JEWXBhK2plSlVhYnFXWlFRRWd0bXZqbG1VVWlYd3UwalJuajFuMFQzZlBRRDNnQStMSQp2QUU5dmd2cFk1WFFqNm90cEJ2c1ozTUpKTktjVU1RdTF6T0FOVHpPMThUbEE4S29CTnNCeThaOURRWktYRjhDCkF3RUFBVEFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBZUduL055cUlSSS8wQUdxdkhQOTdLdFE3NlRqNmFjaGIKMzBMSVhXcCtZSFVhTWVBVmpkMlo3alNRcDVtWlpGbCtrMWZiMzM3SWVhR1hvZlZJMjFlSzUyUVgydGVOb0JrQQovVi9PMUh1MzUvK2FpejB4c2RENndXdndvNEZ5MWpsbWFlSmh3ZFFhY0JsREdGQTJqRkp4dUVwYWhmeFp2VXNiCjNqNXpVMFdLVFVDZkVEZ1hGd0J3MTJ4a3UvN1RNZENFYlJzWWFaM3pGVEMyMjZsUWJVRE43d2VxRndTRCt0QjYKUnVoSXhlOCtjRndBc0FXSENsZXJLZ1pucjN0NVFGMDc4cFcyR0h5OENzSjdWM01aVDVsWjQzbFM1TklCOUp6WgpTWXhaL2l6aFJ5aDVxUjczdUFnc0phTDU2QmorY1Fxbm9UcWhMWlZsN0orTTZXaFdLem9qc0E9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + +; Set remote desktop login password, 6-32 characters. +; This password will only take effect if a service 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 to your web application +; see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin +allow_origin=https://example.com + +; ----- Remote Desktop Touch Fix ----- +; If there is an offset or no response when you touch the screen on +; the remote desktop, please set the touch.backend configuration to system +; available options are: system | native (default) +touch.backend=system + +; Set service log file (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] +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. +openvpn.global=false ; Whether to enable global VPN. +openvpn.enable=true ; Whether to enable the service true | false + +; ----- Proxy Configuration ----- +; This configuration is used to make the device automatically use a proxy server at startup. +[gproxy] +gproxy.enable=true ; Whether to enable the service true | false +gproxy.type=http-connect ; The proxy type can be either http-connect, https-connect, socks5, shadowsocks +gproxy.host=172.1.1.1 ; Proxy server address +gproxy.port=8080 ; Proxy server port +gproxy.password= ; Proxy server login password (leave empty for no authentication) +gproxy.login= ; Proxy server login username (leave empty for no authentication, crypto method if type is shadowsocks) +gproxy.nameserver= ; Custom DNS server +gproxy.drop_udp=false ; Whether to discard UDP packets. +gproxy.udp_proxy=false ; Whether to proxy UDP packets (requires proxy support, such as socks5, shadowsocks). +gproxy.bypass_local_subnet=true ; Do not proxy local area network traffic. +gproxy.dns_proxy=false ; Make DNS queries go through the proxy (requires a DNS server that supports UDP and TCP). +gproxy.uid= ; Only proxy for specified UIDs. (default global) + +# ----- CRONTAB Scheduled Task Service ----- +# If you do not need scheduled tasks, you can disable this service. +[cron] +cron.enable=true ; Whether to enable the service true | false + +# ----- SSHD Service ----- +# If you do not need to connect to the device via SSH, you can disable this service. +[sshd] +sshd.enable=true ; Whether to enable the service true | false + +# ----- Port Forwarding (frp) Service ----- +[fwd] +fwd.enable=true ; Whether to enable the service true | false +fwd.rport=0 ; Port to forward to the remote (0 means randomly allocated) +fwd.host=123.123.123.123 ; FRP server address +fwd.port=9911 ; FRP server port +fwd.protocol=tcp ; FRP protocol +fwd.token=abc123 ; FRP login authentication (token) + +# ----- ADB Service ----- +[adb] + +adb.enable=true ; Whether to enable the service true | false +adb.directory=/data/local/tmp ; Default working directory for built-in ADB (adb shell working directory) +; WARNING WARNING WARNING +; 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 ----- +[tunnel2] +tunnel2.enable=true ; Whether to enable the service true | false +; 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 +tunnel2.password=1234 ; Login password for the bridge proxy +; 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 +; You can test the proxy by using curl -x http://192.168.x.x:65000 https://httpbin.org/ip +; (no username or password configured by default). If you have enabled certificate, +; you need to use curl -x https://lamda:cert-password@192.168.x.x:65000 https://httpbin.org/ip--proxy-insecure -v +; (or reconfigure the login name and password). + +# ----- mDNS Broadcast Service ----- +[mdns] +mdns.enable=false ; Enable or disable true | false +mdns.meta=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.name=DEVICEID-UNIQUE.lamda ; 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.service=lamda ; Set the broadcast service name, default is lamda, i.e., _lamda._tcp.local. \ No newline at end of file diff --git a/setup.py b/setup.py index f019342..f9e9f80 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ #!/usr/bin/python3 -# Copyright 2022 rev1si0n (ihaven0emmail@gmail.com). All rights reserved. +# Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved. import setuptools exec(open("lamda/__init__.py", "rt").read()) @@ -7,34 +7,54 @@ setuptools.setup( name = "lamda", version = "{}".format(__version__), - description = "Android reverse engineering & automation framework", - url = "https://github.com/rev1si0n/lamda", + description = "Android reverse engineering & automation framework (Client API)", + url = "https://github.com/firerpa/lamda", author = "rev1si0n", - python_requires = ">=3.6,<3.11", + python_requires = ">=3.6,<=3.14", zip_safe = False, extras_require = { - "next": ["frida>=16.0.0,<17.0.0"], - "full": ["frida>=15.0.0,<16.0.0,!=15.1.15,!=15.1.16,!=15.1.17"], + "full": ["frida>=17.0.0,<18.0.0,!=17.16.*"], ":sys_platform == \"win32\"": [ "pyreadline==2.1", ], }, install_requires= [ - "grpcio-tools>=1.35.0,<1.49.0", - "grpc-interceptor>=0.13.0,<0.15.0", - "grpcio>=1.35.0,<1.49.0", + "grpcio-tools>=1.35.0,<=1.82.0", + "grpc-interceptor>=0.13.0,<=0.15.4", + "grpcio>=1.35.0,<=1.82.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 :: Developers", "Intended Audience :: Information Technology", - "Intended Audience :: Science/Research", + "Intended Audience :: System Administrators", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Operating System :: Android", + "Operating System :: POSIX", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows", "Topic :: Security", + "Topic :: System :: Systems Administration", + "Topic :: System :: Monitoring", ], + project_urls={ + "Homepage": "https://device-farm.com", + "Documentation": "https://device-farm.com/docs/", + "Changelog": "https://device-farm.com/docs/en/changelog", + "GitHub: issues": "https://github.com/firerpa/lamda/issues", + "GitHub: repo": "https://github.com/firerpa/lamda", + }, package_data = { "lamda": ["*.py", "*.proto"], "lamda.google.protobuf.compiler": ["*.proto"], diff --git a/tools/README.en.md b/tools/README.en.md deleted file mode 100644 index f150a80..0000000 --- a/tools/README.en.md +++ /dev/null @@ -1,376 +0,0 @@ -These tools normally only work on linux/mac systems and are a package of common personal functions, not specifically designed to be Windows compatible but that doesn't mean lamda doesn't support them. If you are using Windows, scripts ending in `.sh` should not work properly. - -## Pre-requisites - -Before you start, make sure that you have started the lamda server on your device. - -**Note**: Some of the command documentation may be updated from time to time, so to avoid versioning issues, make sure you have the latest version of lamda and its Python libraries and dependencies installed on your device before continuing. Some functions require adb, so please ensure that you have the latest version of adb installed. - -```bash -# If you don't have it installed, please do your own search for how to install it, this is only basic advice -# Windows -https://developer.android.com/studio/releases/platform-tools -# Mac -brew install android-platform-tools -# brew cask install android-platform-tools -# Debian/Ubuntu -apt install adb -``` - -Clone the code locally - -```bash -git clone https://github.com/rev1si0n/lamda.git -# If you don't know how to use git or don't have it installed, download this file locally and unpack it -# https://github.com/rev1si0n/lamda/archive/refs/heads/master.zip -``` - -Go to the tools directory and install the required dependencies -```bash -# Switch to the tools/ directory and execute -pip3 install -r requirements.txt -``` - -If you have enabled communication encryption (certificate) on the server side, you will need to set the environment variable ``CERTIFICATE`` before you can use it -```bash -# For linux / Mac -export CERTIFICATE=/path/to/lamda.pem -# For Windows (paths must not contain spaces) -set CERTIFICATE=C:\Users\path\to\lamda.pem -``` - -If you have changed lamda's default port 65000, you will also need to set the environment variable before using it - -```bash -# For linux / Mac -export PORT=8123 -# For Windows (path must not contain spaces) -set PORT=8123 -``` - -192.168.1.2 is the example IP, please get the actual IP address of the device yourself. - -## cert.sh - -Used to generate a certificate for transmission encryption -```bash -bash cert.sh -``` - -If you need to generate a certificate for a specific CN -```bash -bash cert.sh device1.example.com -``` - -The lamda.pem or device1.example.com.pem in the current directory will be the required certificate. -After the certificate has been applied on the server side, the client must also use the certificate for remote calls and ssh connections. -Once the certificate is applied, any operations performed through the client library will be encrypted and ssh will no longer use the default secret key. -The web control page will also require a login (you can find this login password in the last line of the certificate). - -If the operating system is not convenient for using this tool, a default SSL certificate is already available for you in the current directory - -```bash -test.pem # The certificate used to encrypt client-server communication -``` - -## cert.py - -Used to generate a certificate for transport encryption, equivalent to ``cert.sh`` except that this script can also be run on Windows and is used in the same way as cert.sh. - -> Note: you may need to install the pyOpenSSL dependencies manually `pip install pyOpenSSL` - - -## id_rsa - -The default ssh private key used by lamda's built-in ssh, which is the same as the hard-coded private key in `ssh.sh`, `scp.sh`. - -## startmitm.py - -Start the man-in-the-middle, this will fully automate the global man-in-the-middle on the device and you will be able to intercept http/s traffic for your application, including of course DNS requests (globally). -It is possible to apply and withdraw the man-in-the-middle automatically, and the device and network will be restored to its original state when you exit the script. - -> Tip: Sometimes it is annoying to have all this stuff on Windows. -> So specifically for Windows there is **startmitm.exe**, a **Windows command line program** that can be downloaded and placed in the system PATH or current directory and executed from the command line. -> It has the same arguments as startmitm.py. You can do this without installing Python and any associated environment, but you will only be able to do basic man-in-the-middle operations. You can find it in every release. If not, you can look at older releases. -> For Linux/Mac users, if you want to generate/use this standalone executable, package it yourself using the `pyinstaller startmitm.spec` command -> (requires pyinstaller to be installed). - -> If you need to intermediate the IAPP, go to **globalmitm** - -First make sure that the current computer is on the same network segment as the device, 192.168.1.2 for the mobile device with lamda running. -Secondly, make sure you have verified on the command line that mitmproxy has been installed successfully (type `mitmdump` on the command line to verify this). - -Of course, you can also easily mitm if the computer is not on the same network segment as the device or if it is a remote device, please read on. - -> Note: mitmweb is not suitable for long periods of multiple requests, the intercepted requests are stored in memory. So your memory will be eaten up if you use it for a long time (you can free it up from time to time by clicking on File->Clear All in the top left corner of the mitmweb interface). -> For long man-in-the-middle operations, please use mitmdump instead, please find out how to use it yourself. - -Execute -```bash -# Note that the device IP must be first in the argument -python3 -u startmitm.py 192.168.1.2 -``` -That's all. - -If you want to analyse with a LAN buddy - -```bash -python3 -u startmitm.py 192.168.1.2 --web-port 7890 --web-host 0.0.0.0 -# Then, have your little one open http://你的IP地址:7890 in their browser and voila! -``` - -If you need to intercept application-specific traffic rather than global -```bash -# where com.some.package is the application ID -python3 -u startmitm.py 192.168.1.2:com.some.package -``` -That will work. - - -If you want to use a specific DNS, or in some cases you may have DNS resolution errors/unresolvable (which may occur on some native systems), you can do this - -```bash -# Use mainland China DNS -python3 -u startmitm.py 192.168.1.2 --nameserver 114.114.114.114 -``` - -If additional arguments need to be passed to mitmproxy, such as -s, then execute - -> For information on how to write the `http_flow_hook.py` script, please refer to [docs.mitmproxy.org/stable/addons-examples](https://docs.mitmproxy.org/stable/addons-examples/) and [ mitmproxy/examples/contrib](https://github.com/mitmproxy/mitmproxy/tree/9.0.0/examples/contrib) - -```bash -# So you can modify the request or response in real time by writing http_flow_hook.py -python3 -u startmitm.py 192.168.1.2 -s http_flow_hook.py -``` -That's all. - -The phone is not on the same network as the current computer, but you can physically touch the device, you can still do man-in-the-middle, but **you need to make sure the current device is connected to the **computer** via USB or ``adb connect`` and is ADB authorized. - -```bash -# localhost means using an adb device -# Only one adb device is currently connected -python3 -u startmitm.py localhost -# The computer is connected to multiple adb devices, you need to specify adb serial -# This serial can be found in the output of the command adb devices -python3 -u startmitm.py localhost --serial bfde362 -``` -This will work. - -The phone is not under the same network as the current computer and does not have physical access to the device, but as long as you have access to the lamda port you can also do man-in-the-middle. -**This is usually the case** when you have forwarded lamda to a remote server using the built-in frp service, or when you have forwarded lamda's port 65000 somewhere on your own in some way (e.g. SSH, router port forwarding, etc. - **be aware of security issues**), in which case you and lamda -**Only this port** is available for direct communication, the other ports are not accessible to each other. In this case, the phone cannot access any of the local ports, and the local phone can only access the lamda port of the phone (or the phone has a public IP, but the local phone is on a non-interoperable intranet), so that you need to go through the following. (Note that OpenVPN network interworking is not a case of this) - -In this case, it needs to be done in a slightly more cumbersome combination, and here's how to do it. - -First, install your own adb public key onto the device using `adb_pubkey.py` or by calling the interface yourself (search for it in this document). - -Now execute the following command - -```bash -adb kill-server -# If you are using the built-in frp or using ssh forwarding yourself -# where x.x.x.x is usually 127.0.0.1, please change this to suit the facts -# and 65000 here is not fixed, change it according to your actual destination forwarding port -adb connect x.x.x.x:65000 -``` - -Finally, do the same as above via USB -```bash -# localhost means the adb device is being used -# Only one adb device is currently connected -python3 -u startmitm.py localhost -# The computer is connected to multiple adb devices, you need to specify the adb serial -python3 -u startmitm.py localhost --serial x.x.x.x:65000 -``` - -and that will do it. - -Note: You may need to end the app completely and reopen it before the traffic data will be displayed. - -Press `CONTROL` + `C` once to exit the script. - -### Forwarding to upstream agents - -startmitm itself will also start mitmproxy as a proxy service, by default traffic is sent from the local NIC by mitmproxy, if you need traffic to go through an upstream proxy instead of the local machine, you can specify an upstream proxy using the following, **only** **HTTP** is supported as an upstream proxy. - -> DNS traffic will not go through the upstream proxy - -```bash -python3 -u startmitm.py 192.168.1.2 --mode upstream:http://example.com:8080 -# Omitting the http:// start is fine -python3 -u startmitm.py 192.168.1.2 --mode upstream:example.com:8080 -``` - -If the upstream proxy requires login authentication - -```bash -python3 -u startmitm.py 192.168.1.2 --mode upstream:example.com:8080 --upstream-auth USER:PASSWORD -``` - -These options are compatible with mitmproxy. - -### DNS man-in-the-middle (DNS+HTTP/S) - -Intercepting DNS requests requires that mitmproxy version >= 9.0.0 (and Python >= 3.9) and that the script is run as **administrator** or **root**. -Some systems may have their own DNS service, so make sure that no other service is using port 53 before using this feature. - -This option is different from `-nameserver` above, the `-dns` option refers to the dns intermediary - -> DNS broker, default upstream DNS server is 1.1.1.1 -```bash -python3 -u startmitm.py 192.168.1.2 --dns -``` - -> Specify the upstream DNS as 114.114.114.114 (if in mainland China, we recommend using the following command to specify DNS) - -```bash -python3 -u startmitm.py 192.168.1.2 --dns 114.114.114.114 -``` - -> If the upstream DNS uses a non-standard port (e.g. 5353) - -```bash -python3 -u startmitm.py 192.168.1.2 --dns 192.168.0.100:5353 -``` - -There are some changes to the method name definitions in the hook script, `response()` for normal http requests, and `dns_response()` for intercepting DNS. - -```python -def response(flow): - print (flow, type(flow)) - -def dns_response(flow): - print (flow, type(flow)) -``` - -See the documentation for mitmproxy for details. - -## adb_pubkey.py - -A script to install the local adb pubkey to lamda, otherwise the adb connection will show unauthorized. - -```bash -## install adb pubkey -python3 -u adb_pubkey.py install 192.168.1.2 -# Uninstall adb pubkey -python3 -u adb_pubkey.py uninstall 192.168.1.2 -``` - -After installation, execute -```bash -adb kill-server -adb connect 192.168.1.2:65000 -adb -s 192.168.1.2:65000 shell -``` -to connect to the lamda adb. - -## ssh.sh - -Connect to the shell terminal on the phone. - -Execute -```bash -bash ssh.sh 192.168.1.2 -``` - -and you're done. - -## scp.sh - -Use ``scp`` to copy the files on the device locally. - -Copy the `/sdcard/DCIM` directory of 192.168.1.2 to the current directory - -```bash -bash scp.sh 192.168.1.2:/sdcard/DCIM . -``` - -Copy the local directory/file `test/` to `/sdcard/` on device 192.168.1.2 - -```bash -bash scp.sh test/ 192.168.1.2:/sdcard -``` - -## discover.py - -List all devices online in the **local network** (some devices may not be listed in some network situations, please try several times) - -```bash -python3 discover.py -``` - -## fridarpc.py - -A simple wrapper for the fridarpc function. - -This function requires you to be proficient in writing frida scripts. Please refer to the test-fridarpc.js file for the script used in the example, and note in particular that the return value of the function defined by rpc.exports in the frida script can only be string/list/json or any js that can be serialised by json. Assume that the device IP is 192.168.0.2. - -> Execute the following command to inject RPC into com.android.settings (watch for errors) - -```bash -python3 fridarpc.py -f test-fridarpc.js -a com.android.settings -d 192.168.0.2 -``` - -Now that you have the interface out, you can simply request `http://192.168.0.2:65000/fridarpc/myRpcName/getMyString?args=["A", "B"]` to get the results of the methods within the script, and the link can be opened with a browser. GET, and the parameter list can also use multiple parameters at the same time. - -Note that the arguments are provided in **double quotes** and we recommend using json.dumps(["A", "B"]) - -> call with requests - -```python -import json -import requests -url = "http://192.168.0.2:65000/fridarpc/myRpcName/getMyString" -data = requests.post(url, data={"args": json.dumps(["A", "B"])}).json() -print (data["result"]) - -#* Status code 200 Everything is fine -#* Status code 410 Script needs to be re-injected or script is not injected (automatic re-injection is not currently supported) -#* Status code 500 Script or parameter exception -#* Status code 400 Parameter error -``` - -The format of the response result is fixed and can be opened in a browser for viewing. - -## emu-install.sh - -Server-side installation script for mainstream emulators only, requires device to be WIFI adb enabled, needs to be downloaded in advance -The corresponding architecture server-side installation package to the current running directory. - -```bash -bash emu-install 192.168.1.2 -``` - -## statistics.sh - -If you are unable to run LAMDA properly, you can use this script to generate an issue report by rebooting the device and ensuring that LAMDA has exited, pushing ``statistics.sh`` to /data/local/tmp using adb, and then executing it as root. - -```bash -# launch.sh is the path to the script that starts LAMDA -sh /data/local/tmp/statistics.sh /data/local/tmp/arm64-v8a/bin/launch.sh -# After execution, the file /sdcard/statistics.txt will be generated, just report this file -``` - -## magisk - -lamda's magisk module architecture - -## Various service scripts (Docker) - -> All images are for x86 platforms, you may have to make your own modifications and regenerate them for use on ARM processor Linux/Mac. - -### openvpn - -Out-of-the-box OpenVPN service - -### globalmitm - -Used to analyse traffic from foreign apps that require a proxy to connect - -### frps - -Out-of-the-box frp port forwarding - -### socks5 - -Out-of-the-box socks5 proxy service \ No newline at end of file diff --git a/tools/README.md b/tools/README.md index 7af086e..58a7740 100644 --- a/tools/README.md +++ b/tools/README.md @@ -1,374 +1 @@ -这些工具正常情况下只能工作于 linux/mac 系统之上,为个人常用功能的封装,并未特意考虑兼容 Windows 但是这不代表 lamda 不支持。如果你使用的是 Windows,以`.sh`结尾的脚本应该无法正常工作。 - -## 前置条件 - -开始之前,请确保已经启动设备上的 lamda 服务端。 - -**注意**:部分命令文档可能具有时效性随时更新,为了避免版本问题,继续前请务必先在手机上安装最新版本的 lamda 以及其 Python 库和依赖库。部分功能需要使用 adb,请务必确保安装的 adb 是最新版本。 - -```bash -# 如果没有安装,请 自行搜索 如何安装,这里提供的只是基础建议 -# Windows -https://developer.android.com/studio/releases/platform-tools -# Mac -brew install android-platform-tools -#brew cask install android-platform-tools -# Debian/Ubuntu -apt install adb -``` - -克隆代码到本地 - -```bash -git clone https://github.com/rev1si0n/lamda.git -# 如果不会使用或未安装 git,下载此文件到本地并解压 -# https://github.com/rev1si0n/lamda/archive/refs/heads/master.zip -``` - -进入 tools 目录并安装所需的依赖包 -```bash -# 切换到 tools/ 目录并执行 -pip3 install -r requirements.txt -``` - -如果你在服务端启用了通信加密(certificate),你需要在使用前设置环境变量 `CERTIFICATE` -```bash -# 对于 linux / Mac -export CERTIFICATE=/path/to/lamda.pem -# 对于 Windows(路径不能包含空格) -set CERTIFICATE=C:\Users\path\to\lamda.pem -``` - -如果你修改了 lamda 的默认端口 65000,也需要在使用前设置环境变量 - -```bash -# 对于 linux / Mac -export PORT=8123 -# 对于 Windows(路径不能包含空格) -set PORT=8123 -``` - -192.168.1.2 为示例IP,请自行获取设备的实际IP地址。 - -## cert.sh - -用来生成用于传输加密的证书 -```bash -bash cert.sh -``` - -如果你需要生成特定 CN 的证书 -```bash -bash cert.sh device1.example.com -``` - -即可,当前目录下的 lamda.pem 或者 device1.example.com.pem 即为所需的证书。 -在服务端应用该证书后,客户端也必须使用此证书来进行远程调用以及 ssh 连接。 -应用此证书后,将会加密任何通过 client 库进行的操作,ssh 将不再使用默认的秘钥。 -同时,web 控制页面将需要登录(你可以在证书最后一行找到此登录密码), - -如果操作系统不方便使用此工具,当前目录下已经为你准备好了一个默认的 SSL 证书 - -```bash -test.pem # 用于加密客户端与服务端通信的证书 -``` - -## cert.py - -用来生成用于传输加密的证书,等效于 `cert.sh` 只不过这个脚本同时可以运行于 Windows,用法等同于 cert.sh。 - -> 注:你有可能需要手动安装 pyOpenSSL 依赖库 `pip install pyOpenSSL` - - -## id_rsa - -lamda 内置 ssh 使用的默认 ssh 私钥,此与 `ssh.sh`、`scp.sh` 中硬编码的私钥相同。 - -## startmitm.py - -启动中间人,这将会全自动的在设备上开启全局的中间人,你就可以截获应用的 http/s 流量,当然,也可以包括 DNS 请求(全局)。 -它可以自动应用及撤销中间人,退出脚本后设备及网络也将恢复它原来的样子。 - -> 提示:有时在 Windows 上装这些东西是很烦的事情, -> 所以专门为 Windows 提供了 **startmitm.exe**,这是一个**Windows 命令行程序**,将其下载并放入系统 PATH 或者当前目录,在命令行执行即可。 -> 它的参数与 startmitm.py 相同。这样你可以不用安装 Python 及任何相关环境,但是你也只能进行基本的中间人操作。你可以在每个版本的 release 中找到它。如果没有,可以翻看稍旧的 release。 -> 对于 Linux/Mac 用户,如果你想生成/使用这种独立可执行程序,请自行使用 `pyinstaller startmitm.spec` 命令打包 -> (需要安装 pyinstaller)。 - -> 如果你需要对国际APP进行中间人,请转到 **globalmitm** - -首先确保当前电脑与设备在同一个网段,192.168.1.2 为运行了 lamda 的手机设备。 -其次,确保你已在命令行验证 mitmproxy 已安装成功(在命令行输入 `mitmdump` 进行验证)。 - -当然,电脑与设备不在同一网段或者是远程设备,你也可以轻松 mitm,请继续看下去。 - -> 注意:mitmweb 并不适合长时间多请求,其截获的请求均存储于内存之中。所以在长时间使用的情况下你的内存会被吃光(你可以不定期点击 mitmweb 界面的左上角 File->Clear All 来释放)。 -> 对于长时间的中间人操作,请改用 mitmdump,请自行了解使用方法。 - -执行 -```bash -# 注意,设备IP 必须在参数第一位 -python3 -u startmitm.py 192.168.1.2 -``` -即可。 - -如果你想和局域网的小伙伴一起分析 - -```bash -python3 -u startmitm.py 192.168.1.2 --web-port 7890 --web-host 0.0.0.0 -# 然后,让你的小伙伴用浏览器打开 http://你的IP地址:7890 即可 -``` - -如果需要截获特定应用的流量而不是全局 -```bash -# 这里的 com.some.package 是应用的 ID -python3 -u startmitm.py 192.168.1.2:com.some.package -``` -即可。 - - -如果你想使用特定的DNS,或者一些情况下,你可能出现DNS解析错误/无法解析的情况(可能出现于一些原生的系统),可以这样做 - -```bash -# 使用中国大陆 DNS -python3 -u startmitm.py 192.168.1.2 --nameserver 114.114.114.114 -``` - -如果需要传递其他参数到 mitmproxy,例如 -s,则执行 - -> 关于如何编写 `http_flow_hook.py` 脚本,请参考 [docs.mitmproxy.org/stable/addons-examples](https://docs.mitmproxy.org/stable/addons-examples/) 以及 [mitmproxy/examples/contrib](https://github.com/mitmproxy/mitmproxy/tree/9.0.0/examples/contrib) - -```bash -# 这样你就可以通过编写 http_flow_hook.py 实时修改请求或者响应 -python3 -u startmitm.py 192.168.1.2 -s http_flow_hook.py -``` -即可。 - -手机与当前电脑不在同一网络下,但是你可以物理接触设备,你仍然可以进行中间人,但是**需要确保当前设备已通过USB 或者 `adb connect` 接入**电脑且已ADB授权。 - -```bash -# localhost 代表使用 adb 设备 -# 当前仅连接了一台 adb 设备 -python3 -u startmitm.py localhost -# 电脑连接了多台 adb 设备,你需要指定 adb serial -# 这个 serial 请从命令 adb devices 的输出中寻找 -python3 -u startmitm.py localhost --serial bfde362 -``` -即可。 - -手机与当前电脑不在同一网络下,也无法物理接触设备,但是只要你可以访问 lamda 的端口,你也可以进行中间人。 -**这种情况通常为**:你使用了内置 frp 服务转发了 lamda 到远程服务器,或者你自行通过某种方式转发了 lamda 的 65000 端口到某个地方(例如 SSH、路由器端口转发等-**注意安全性问题**),这种情况下你和 lamda 之间 -**仅有这一个端口**可以直接交流,其他端口是无法互相访问的。这种情况下,手机无法访问到本机的任何端口,本机也只能访问到手机的 lamda 端口(或者手机有公网IP,但本机在不互通的内网),这样需要通过以下方式来进行。(注意 OpenVPN 网络互通,并不属于这个情况) - -这时,需要通过稍微繁琐的组合方式来进行,下面介绍如何操作。 - -首先,使用 `adb_pubkey.py` 或者自行调用接口将自身的 adb 公钥安装到设备上(请在本文档搜索)。 - -现在执行以下命令 - -```bash -adb kill-server -# 如果你使用了内置 frp 或者自行使用了 ssh 转发, -# 这里的 x.x.x.x 通常为 127.0.0.1,请依据事实修改 -# 而这里的 65000 也非固定,依据你实际设置的目的转发端口做修改 -adb connect x.x.x.x:65000 -``` - -最后,按照和上文 通过USB 一样的方法操作 -```bash -# localhost 代表使用 adb 设备 -# 当前仅连接了一台 adb 设备 -python3 -u startmitm.py localhost -# 电脑连接了多台 adb 设备,你需要指定 adb serial -python3 -u startmitm.py localhost --serial x.x.x.x:65000 -``` - -即可。 - -注意:你可能需要完全结束APP并重新打开才会显示流量数据。 - -按下一次 `CONTROL` + `C` 退出脚本。 - -### 转发到上游代理 - -startmitm 本身也会启动 mitmproxy 作为代理服务,默认情况下流量都是 mitmproxy 从本机网卡发出的,如果你需要流量通过一个上游代理发出而不是本机,可以使用如下方式指定上游代理,**仅支持** **HTTP** 作为上游代理。 - -> DNS 流量不会经过上游代理 - -```bash -python3 -u startmitm.py 192.168.1.2 --mode upstream:http://example.com:8080 -# 省略掉 http:// 开头也是可以的 -python3 -u startmitm.py 192.168.1.2 --mode upstream:example.com:8080 -``` - -如果上游代理需要登录认证 - -```bash -python3 -u startmitm.py 192.168.1.2 --mode upstream:example.com:8080 --upstream-auth USER:PASSWORD -``` - -这些选项是与 mitmproxy 兼容的。 - -### DNS 中间人 (DNS+HTTP/S) - -截获 DNS 请求需要确保 mitmproxy 的版本 >= 9.0.0(且 Python>=3.9),且需要以**管理员**或者**root**身份运行脚本。 -部分系统上会存在自带的 DNS 服务,在使用该功能前请务必确保没有其他服务使用了 53 端口。 - -此选项与上方 `--nameserver` 意义不同,`--dns` 选项专指 dns 中间人 - -> DNS 中间人,默认上游 DNS 服务器为 1.1.1.1 -```bash -python3 -u startmitm.py 192.168.1.2 --dns -``` - -> 指定上游 DNS 为 114.114.114.114(如果在中国大陆,建议使用如下命令指定DNS) - -```bash -python3 -u startmitm.py 192.168.1.2 --dns 114.114.114.114 -``` - -> 如果上游 DNS 使用了非标准端口(例如 5353) - -```bash -python3 -u startmitm.py 192.168.1.2 --dns 192.168.0.100:5353 -``` - -hook 脚本的方法名称定义有一些变化,正常 http 请求为 `response()`,截获 DNS 时需要使用 `dns_response()`。 - -```python -def response(flow): - print (flow, type(flow)) - -def dns_response(flow): - print (flow, type(flow)) -``` - -具体请查看 mitmproxy 的文档。 - -## adb_pubkey.py - -用于安装本机的 adb pubkey 到 lamda 的脚本,否则 adb 连接将会显示未授权。 - -```bash -# 安装 adb pubkey -python3 -u adb_pubkey.py install 192.168.1.2 -# 卸载 adb pubkey -python3 -u adb_pubkey.py uninstall 192.168.1.2 -``` - -安装后,执行 -```bash -adb kill-server -adb connect 192.168.1.2:65000 -adb -s 192.168.1.2:65000 shell -``` -来连接 lamda adb。 - -## ssh.sh - -连接入手机上的 shell 终端。 - -执行 -```bash -bash ssh.sh 192.168.1.2 -``` -即可。 - -## scp.sh - -使用 `scp` 复制设备上的文件到本地。 - -将 192.168.1.2 的 `/sdcard/DCIM` 目录复制到当前目录 - -```bash -bash scp.sh 192.168.1.2:/sdcard/DCIM . -``` - -将本地目录/文件 `test/` 复制到设备 192.168.1.2 的 `/sdcard/` - -```bash -bash scp.sh test/ 192.168.1.2:/sdcard -``` - -## discover.py - -列出**本地网络**中所有在线的设备(部分网络情况下可能一些设备没有列出,请多次尝试) - -```bash -python3 discover.py -``` - -## fridarpc.py - -一个 fridarpc 功能的简单封装。 - -此功能需要你能熟练编写 frida 脚本。示例中使用的脚本请参照 test-fridarpc.js 文件,特别注意: frida 脚本中 rpc.exports 定义的函数返回值只能为 string/list/json 或者任意 js 中可以被 json 序列化的值。假设设备IP为 192.168.0.2。 - -> 执行以下命令注入 RPC 到 com.android.settings(注意查看是否有报错) - -```bash -python3 fridarpc.py -f test-fridarpc.js -a com.android.settings -d 192.168.0.2 -``` - -现在已经将接口拿出来了,只需要请求 `http://192.168.0.2:65000/fridarpc/myRpcName/getMyString?args=["A","B"]` 即可得到脚本内方法的返回结果,链接也可以用浏览器打开,接口同时支持 POST 以及 GET,参数列表也可以同时使用多个参数。 - -注意参数的提供形式,是**双引号**,建议使用 json.dumps(["A", "B"]) - -> 用 requests 调用 -```python -import json -import requests -url = "http://192.168.0.2:65000/fridarpc/myRpcName/getMyString" -data = requests.post(url, data={"args": json.dumps(["A", "B"])}).json() -print (data["result"]) - -#* 状态码 200 一切正常 -#* 状态码 410 需要重新注入脚本或者脚本未注入(目前不支持自动重新注入) -#* 状态码 500 脚本或参数异常 -#* 状态码 400 参数错误 -``` - -响应结果的格式是固定的,可在浏览器打开查看。 - -## emu-install.sh - -仅适用于主流模拟器的服务端安装脚本,需要设备开启 WIFI adb,需要提前下载 -对应架构服务端安装包到当前运行目录。 - -```bash -bash emu-install 192.168.1.2 -``` - -## statistics.sh - -如果你无法正常运行 LAMDA,可以使用此脚本来生成 issue 报告,重启设备并且确保 LAMDA 已经退出,将 `statistics.sh` 使用 adb push 到 /data/local/tmp,随后使用 root 身份执行。 - -```bash -# launch.sh 为启动 LAMDA 的脚本路径 -sh /data/local/tmp/statistics.sh /data/local/tmp/arm64-v8a/bin/launch.sh -# 执行完毕后,会生成文件 /sdcard/statistics.txt,报告此文件即可 -``` - -## magisk - -lamda 的 magisk 模块构架 - -## 各种服务脚本 (Docker) - -> 所有镜像均为 x86 平台,在 ARM 处理器的 Linux/Mac 上使用你可能要自行做出修改并重新生成。 - -### openvpn - -开箱即用的 OpenVPN 服务 - -### globalmitm - -用于分析需要透过代理才能连接的国外APP的流量 - -### frps - -开箱即用的 frp 端口转发 - -### socks5 - -开箱即用的 socks5 代理服务 +Document: https://device-farm.com/doc/ \ No newline at end of file diff --git a/tools/cert.py b/tools/cert.py index ad85194..620973e 100755 --- a/tools/cert.py +++ b/tools/cert.py @@ -1,42 +1,112 @@ #!/usr/bin/env python3 -#encoding=utf-8 +import os import sys -from hashlib import md5 -from OpenSSL import crypto +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] -pk = crypto.PKey() -pk.generate_key(crypto.TYPE_RSA, 2048) -cert = crypto.X509() +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(), + ) + ) -cert.get_subject().O = "lamda" -cert.get_subject().CN = CN +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)) -cert.gmtime_adj_notBefore(0) -cert.gmtime_adj_notAfter(315360000) -cert.set_issuer(cert.get_subject()) -cert.set_pubkey(pk) -cert.sign(pk, "sha256") -pkey = crypto.dump_privatekey(crypto.FILETYPE_PEM, pk) -cert = crypto.dump_certificate(crypto.FILETYPE_PEM, cert) +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()) + ) -asn1_pk = crypto.dump_privatekey(crypto.FILETYPE_ASN1, pk) -passwd = md5(asn1_pk).hexdigest().encode() + 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()) + ) -f = open("{}.pem".format(CN), "wb") -f.write(b"# Certificate Used BY LAMDA") -f.write(", CN={}".format(CN).encode()) + 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) -f.write(b"\n") -f.write(cert.strip()) -f.write(b"\n") -f.write(pkey.strip()) -f.write(b"\n") + d = pk.private_numbers().d + pd = d.to_bytes((d.bit_length() + 7) // 8, "little") + cred = sha256(pd).hexdigest()[::3] -f.write(passwd.strip()) -f.write(b"\n") -f.close() + 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") diff --git a/tools/cert.sh b/tools/cert.sh deleted file mode 100755 index 7778aff..0000000 --- a/tools/cert.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -# generate a certificate for lamda -# default expire days is 3650 = 10 years -set -e -CN=${1:-lamda} -PSW="secret" -DAYS=3650 -BITS=2048 - -openssl genrsa -passout pass:$PSW -des3 -out ${CN}.key ${BITS} -# the subject O must be lamda -openssl req -passin pass:$PSW -new -key ${CN}.key -out ${CN}.csr -subj "/O=lamda/CN=${CN}" -openssl x509 -passin pass:$PSW -req -days ${DAYS} -in ${CN}.csr -signkey ${CN}.key -out ${CN}.crt -openssl rsa -passin pass:$PSW -in ${CN}.key -out ${CN}.key - -openssl x509 -in ${CN}.crt -text -noout >${CN}.pem -cat ${CN}.{crt,key} >>${CN}.pem -openssl rsa -in ${CN}.key -outform der 2>/dev/null | openssl md5 | awk '{print $NF}' >>${CN}.pem - -echo certificate: $(pwd)/${CN}.pem -rm ${CN}.{csr,crt,key} diff --git a/tools/debugimage.py b/tools/debugimage.py new file mode 100644 index 0000000..cd28a5d --- /dev/null +++ b/tools/debugimage.py @@ -0,0 +1,48 @@ +#!/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() \ No newline at end of file diff --git a/tools/discover.py b/tools/discover.py deleted file mode 100755 index 31ffde3..0000000 --- a/tools/discover.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/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 = "/dev/null 2>&1 -adb -s $DEVICE wait-for-device -ABI=$(adb -s $DEVICE shell getprop ro.product.cpu.abi|tr -d '\r') -adb -s $DEVICE root >/dev/null 2>&1 -adb -s $DEVICE wait-for-device - -if [ ! -f "${ABI}.tar.gz" ]; then -echo -e "[!]$RED 下载/复制 ${ABI}.tar.gz 到目录 $(pwd) 并重新运行此脚本 $RST" -exit 1 -fi -if ! adb -s $DEVICE shell id|grep 'uid=0' >/dev/null; then -echo -e "[!]$RED ADB root 失败 $RST" -exit 1 -fi -adb -s $DEVICE remount >/dev/null 2>&1 -echo -e "[*] 正在上传 ${ABI}.tar.gz" -adb -s $DEVICE push ${ABI}.tar.gz ${TARGET} - -adb -s $DEVICE shell rm -rf ${TARGET}/${ABI}/bin >/dev/null 2>&1 -adb -s $DEVICE shell rm -rf ${TARGET}/${ABI}/lib >/dev/null 2>&1 -adb -s $DEVICE shell rm -rf ${TARGET}/${ABI}/etc >/dev/null 2>&1 - -echo -e "[*] 正在解压 ${ABI}.tar.gz" -adb -s $DEVICE shell tar -C ${TARGET} -xzf ${TARGET}/${ABI}.tar.gz - -echo -e "[*] 尝试安装为开机启动(仅雷电安卓9)" -adb -s $DEVICE shell mount -o rw,remount /system -adb -s $DEVICE shell mount -o rw,remount / - -adb -s $DEVICE shell "sed -i 's,\(do_bootcomplete$\)\(.*\),\1; sh /data/launch.sh,' /system/etc/init.sh" >/dev/null 2>&1 -adb -s $DEVICE shell "echo \"exec sh ${TARGET}/${ABI}/bin/launch.sh\" > /data/launch.sh" - -if adb -s $DEVICE shell cat /system/etc/init.sh 2>/dev/null | grep launch.sh >/dev/null; then -echo -e "[*]$GRE 成功安装为开机启动 $RST" -echo -e "[#]$GRE 设备重启/开机后,服务将会自动启动 $RST" -echo -e "[#]$GRE 你将无需手动启动 $RST" -else -echo -e "[!]$RED 未能安装为开机启动 $RST" -echo -e "[#]$RED 每次设备重启/开机后,请进入ADB并以ROOT身份执行 $RST" -echo -e "[#]$RED sh /data/launch.sh $RST" -echo -e "[#]$RED 来启动服务 $RST" -fi -echo -e "[*] 正在首次启动" -adb -s $DEVICE shell sh /data/launch.sh -adb disconnect $DEVICE >/dev/null 2>&1 -echo -e "[*] 已成功完成" -exit 0 \ No newline at end of file diff --git a/tools/fridarpc.py b/tools/fridarpc.py index 9d19637..97791a4 100755 --- a/tools/fridarpc.py +++ b/tools/fridarpc.py @@ -17,7 +17,7 @@ help="service port") parser.add_argument("-f", type=argparse.FileType("r"), dest="script", help="frida script", required=True) - parser.add_argument("-delay", type=int, dest="delay", default=0, + parser.add_argument("-delay", type=int, dest="delay", default=5, help="attach after delay") parser.add_argument("-cert", type=str, default=cert, help="ssl cert") @@ -25,17 +25,9 @@ d = Device(args.device, port=args.port, certificate=args.cert) - pid = d.frida.spawn(args.package) - d.frida.resume(pid) - time.sleep(args.delay) - session = d.frida.attach(pid) - session.on("detached", print) - - sc = session.create_script(args.script.read()) - sc.on("destroyed", print) - sc.on("message", print) - - sc.load() - sc.eternalize() - exit (0) + app = d.application(args.package) + app.detach_script() + app.attach_script(args.script.read(), + standup=args.delay) + exit (0) \ No newline at end of file diff --git a/tools/frps/Dockerfile b/tools/frps/Dockerfile deleted file mode 100644 index 0ce1003..0000000 --- a/tools/frps/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -FROM alpine:3.15 -LABEL maintainer="rev1si0n " - -COPY entry /usr/bin -RUN chmod 755 /usr/bin/entry - -ENV VERSION=0.44.0 -ENV PLAT=linux_386 - -ENV SOURCESMIRROR=mirrors.ustc.edu.cn -RUN sed -i "s/dl-cdn.alpinelinux.org/${SOURCESMIRROR}/g" /etc/apk/repositories -RUN apk add bash curl jq - -WORKDIR /tmp - -ENV DOWNLOADURL=https://github.com/fatedier/frp/releases/download/v${VERSION}/frp_${VERSION}_${PLAT}.tar.gz -RUN wget ${DOWNLOADURL} -O - | tar -xz - -RUN cp frp_${VERSION}_${PLAT}/frps /usr/bin -WORKDIR / - -EXPOSE 6009/tcp 6119/tcp -ENTRYPOINT ["entry"] diff --git a/tools/frps/README.en.md b/tools/frps/README.en.md deleted file mode 100644 index 7295aa9..0000000 --- a/tools/frps/README.en.md +++ /dev/null @@ -1,22 +0,0 @@ -FRPS service docker, save yourself the trouble of building it and you'll be up and running in a single command! - -Note that these port forwarders are only recommended for Linux systems, so if your docker host is not Linux, please do not use them. - -> If you need to log in to the backend of frps from outside, you will also need to unblock 6119/tcp. - -Execute the command -```bash -docker run -it --rm --net host -e TOKEN=mypasswod -e BIND=127.0.0.1 -e PORTS=1000-5000 rev1si0n/frps -# TOKEN is the password, also the login password for the frps backend -# BIND is the bind interface The default is 127.0.0.1, do not set it to 0.0.0.0 unless you know what you are doing -# PORTS is the range of ports allowed to bind Default is 3000-5000 -``` - -Note that some configuration and connection information will be output in the terminal after startup, find the ``----- LAMDA CONFIG -----`` segment -Copy the line `fwd.xxxxxx=xxxx` and write it to the `/data/local/tmp/properties.local` file on the device. -You can change the `rport` field there to the port you want, this port value must be in the `PORTS` range specified at boot time, the default 0 is randomly assigned. -Reboot the device and start lamda and lamda will automatically forward its own port to the server. - - -You can also access the frps backend page via 127.0.0.1:6119 (if the firewall 6119 is put through, you can also access it via http://服务器IP:6119) -The backend login user is `lamda` and the password is the TOKEN you provided when you started. \ No newline at end of file diff --git a/tools/frps/README.md b/tools/frps/README.md deleted file mode 100644 index 02135b1..0000000 --- a/tools/frps/README.md +++ /dev/null @@ -1,22 +0,0 @@ -FRPS 服务 docker,省得你自己搭建,一条命令就可以跑起来 - -注意此类端口转发程序只建议在 Linux 类系统上使用,如果你的 docker 宿主机不是 Linux 类,请不要使用。 - -> 开始前请先放通防火墙 6009/tcp,如果需要从外网登录 frps 的后台,你还需要放通 6119/tcp。 - -执行命令 -```bash -docker run -it --rm --net host -e TOKEN=mypasswod -e BIND=127.0.0.1 -e PORTS=1000-5000 rev1si0n/frps -# TOKEN 为密码,同样为 frps 后台的登录密码 -# BIND 为绑定接口 默认为 127.0.0.1,请不要设为 0.0.0.0 除非你知道自己在做什么 -# PORTS 为允许绑定端口的范围 默认为 3000-5000 -``` - -注意,启动后会在终端输出一些配置及连接信息,找到 `----- LAMDA CONFIG -----` 段 -复制其中 `fwd.xxxxx=xxxx` 的行,并将其写入到设备上的 `/data/local/tmp/properties.local` 文件中, -你可以修改其中的 `rport` 字段为你想要的端口,这个端口值必须在启动时指定的 `PORTS` 范围中,默认 0 则为随机分配。 -重启设备并启动 lamda,lamda 将会自动将自身端口转发到服务器上。 - - -你还可以通过 127.0.0.1:6119 访问 frps 的后台页面(如果放通了防火墙 6119,你也可以通过 http://服务器IP:6119 访问) -后台登录用户名为 `lamda`,登录密码为你启动时提供的 TOKEN。 diff --git a/tools/frps/entry b/tools/frps/entry deleted file mode 100755 index b7d144a..0000000 --- a/tools/frps/entry +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash -TOKEN=${TOKEN:-lamda0fwd} -cat </etc/frps.ini -[common] -bind_addr = 0.0.0.0 -bind_port = 6009 - -token = ${TOKEN} -use_encryption = true -allow_ports = ${PORTS:-2000-5000} -authentication_timeout = 60 - -dashboard_port = 6119 -dashboard_user = lamda -dashboard_pwd = ${TOKEN} - -proxy_bind_addr = ${BIND:-127.0.0.1} -max_pool_count = 15 -EOL - -WANIP=$(curl -s https://httpbin.org/ip | jq -r .origin) - -echo "----- FRPS CONFIG -----" -cat /etc/frps.ini - -echo -echo "----- LAMDA CONFIG -----" - -cat < foreign app mode - -```bash -# Use the HTTP proxy -docker run --rm -it --net host -e HTTP=192.168.0.2:7890 rev1si0n/mitm 192.168.x.x -# Use the SOCKS5 proxy -docker run --rm -it --net host -e SOCKS5=192.168.0.2:1080 rev1si0n/mitm 192.168.x.x -``` - -> normal mode - -```bash -docker run --rm -it --net host rev1si0n/mitm 192.168.x.x -``` - -Now use your browser to open ``http://127.0.0.1:1234`` or ``http://本机IP:1234`` for man-in-the-middle. - -### Mac (Intel)/Windows system commands - -For such systems, docker does not support native host mode, and the program will not know the IP address of the host. -So you need to prepare the local **LAN IP** in advance, i.e. the LANIP in the following parameters, assuming your LANIP is 192.168.0.2 - -> Foreign APP mode - -```bash -# Use the SOCKS5 proxy -docker run -it --rm -p 53:53/udp -p 8118:8118 -p 1234:1234 -e LANIP=192.168.0.2 -e SOCKS5=192.168.0.2:1080 rev1si0n/mitm 192.168.x.x -# Use HTTP proxy -docker run -it --rm -p 53:53/udp -p 8118:8118 -p 1234:1234 -e LANIP=192.168.0.2 -e HTTP=192.168.0.2:7890 rev1si0n/mitm 192.168.x.x -``` - -> normal mode - -```bash -docker run -it --rm -p 8118:8118 -p 1234:1234 -e LANIP=192.168.0.2 rev1si0n/mitm 192.168.x.x -``` - -Now use your browser to open `http://127.0.0.1:1234` or `http://MY_IP:1234` for the intermediary. - -### How to build the image - -```bash -### cd to the tools directory -docker build -t rev1si0n/mitm -f globalmitm/Dockerfile . -``` - -### DNS2SOCKS - -https://sourceforge.net/projects/dns2socks \ No newline at end of file diff --git a/tools/globalmitm/README.md b/tools/globalmitm/README.md deleted file mode 100644 index 0fed4ac..0000000 --- a/tools/globalmitm/README.md +++ /dev/null @@ -1,75 +0,0 @@ -用于分析需要使用国际代理才能访问的应用的流量,是一个整合的 docker 镜像, -因为 Windows/Mac 上的 docker 实现问题,此镜像对于 Windows/Mac,Linux 有着不同的使用方法,请分别看不同章节。 -如果你使用的是 Mac M1,下列任何方式可能都不适用于你,请使用 x86 架构的 Windows 或者 linux。 - -你可能会说,为什么我不直接使用上游代理:由于某些原因影响DNS,你可能无法访问到真正的目标站点。 - -### 准备代理服务 - -此镜像同时支持普通模式(即 startmitm.py 的再次封装)以及国外APP模式,对于国外APP,你需要先准备下面任一类型代理。 -目前**不支持需要登录的代理**。通常,你用的客户端软件(例如 clash 等)会在你的本机创建这样的代理端口(SOCKS5 或者 HTTP)。 -但是有可能它只绑定到了 `127.0.0.1`,将其设置为绑定**任意接口**并获知它的绑定**端口**,随后获取你的**本机局域网地址**。 - -拼接代理为以下链接格式 `IP:端口`,并牢记其代理类型(SOCKS5/HTTP)。 - -```bash -# 这是一个示例 HTTP 代理地址 -192.168.0.2:7890 -# 这是一个示例 SOCKS5 代理地址 -192.168.0.2:1080 -# 是的,它们长得一样,请继续往下看 -``` - -现在请选择性查看对应你当前系统的命令。 - -### Linux 系命令 - -> 国外APP模式 - -```bash -# 使用 HTTP 代理 -docker run --rm -it --net host -e HTTP=192.168.0.2:7890 rev1si0n/mitm 192.168.x.x -# 使用 SOCKS5 代理 -docker run --rm -it --net host -e SOCKS5=192.168.0.2:1080 rev1si0n/mitm 192.168.x.x -``` - -> 普通模式 - -```bash -docker run --rm -it --net host rev1si0n/mitm 192.168.x.x -``` - -现在使用浏览器打开 `http://127.0.0.1:1234` 或者 `http://本机IP:1234` 即可进行中间人。 - -### Mac (Intel)/Windows 系命令 - -对于此类系统,docker 并不支持原生 host 模式,程序也就无法获知宿主机的IP地址, -所以你需要提前准备本机**局域网IP**,即以下参数中的 LANIP,假设你的 LANIP 为 192.168.0.2 - -> 国外APP模式 - -```bash -# 使用 SOCKS5 代理 -docker run -it --rm -p 53:53/udp -p 8118:8118 -p 1234:1234 -e LANIP=192.168.0.2 -e SOCKS5=192.168.0.2:1080 rev1si0n/mitm 192.168.x.x -# 使用 HTTP 代理 -docker run -it --rm -p 53:53/udp -p 8118:8118 -p 1234:1234 -e LANIP=192.168.0.2 -e HTTP=192.168.0.2:7890 rev1si0n/mitm 192.168.x.x -``` - -> 普通模式 - -```bash -docker run -it --rm -p 8118:8118 -p 1234:1234 -e LANIP=192.168.0.2 rev1si0n/mitm 192.168.x.x -``` - -现在使用浏览器打开 `http://127.0.0.1:1234` 或者 `http://本机IP:1234` 即可进行中间人。 - -### 如何构建镜像 - -```bash -# cd 到 tools 目录 -docker build -t rev1si0n/mitm -f globalmitm/Dockerfile . -``` - -### DNS2SOCKS - -https://sourceforge.net/projects/dns2socks diff --git a/tools/ida.py b/tools/ida.py new file mode 100644 index 0000000..fc15cfd --- /dev/null +++ b/tools/ida.py @@ -0,0 +1,60 @@ +#!/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() \ No newline at end of file diff --git a/tools/magisk/common/adb_keys b/tools/magisk/common/adb_keys new file mode 100644 index 0000000..e69de29 diff --git a/tools/magisk/common/properties b/tools/magisk/common/properties new file mode 100644 index 0000000..5c82a49 --- /dev/null +++ b/tools/magisk/common/properties @@ -0,0 +1,3 @@ +[DEFAULT] + +port=65000 \ No newline at end of file diff --git a/tools/magisk/common/service.sh b/tools/magisk/common/service.sh index c1c3612..cfd1458 100755 --- a/tools/magisk/common/service.sh +++ b/tools/magisk/common/service.sh @@ -1,13 +1,10 @@ #!/system/bin/sh base=${0%/*} -ABI=$(getprop ro.product.cpu.abi) -launch="sh ${base}/${ABI}/bin/launch.sh" -cert=${base}/config/lamda.pem +cert=/data/usr/lamda.pem +launch="sh ${base}/server/bin/launch.sh" port=65000 -sleep 30 -# where to locate properties.local -export CFGDIR=${base}/etc +sleep 25 export ca_store_remount=true if [ -f "${cert}" ]; then $launch --port=${port} --certificate=${cert} diff --git a/tools/magisk/install.sh b/tools/magisk/install.sh index 5167d21..0965237 100755 --- a/tools/magisk/install.sh +++ b/tools/magisk/install.sh @@ -1,8 +1,16 @@ #!/system/bin/sh ABI=$(getprop ro.product.cpu.abi) -SERVER=$TMPDIR/$ABI.tar.gz +SERVER=$TMPDIR/lamda-server-$ABI.tar.gz BB="/data/adb/magisk/busybox" -CONFDIR="$MODPATH/config" +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 @@ -17,20 +25,20 @@ ui_print " installer " pushd $(pwd) cd $MODPATH if [ ! -f $SERVER ]; then -abort "${ABI}.tar.gz not found in archive" +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 /data/usr -cp -af $TMPDIR/adb_keys /data/usr/.adb_keys +mkdir -p ${USRDIR} +mkdir -p ${USRDIR}/.local -mkdir -p $CONFDIR +cp -af $TMPDIR/adb_keys ${USRDIR}/.adb_keys -cp -af $TMPDIR/properties.local $CONFDIR -cp -af $TMPDIR/lamda.pem $CONFDIR +cp -af $TMPDIR/properties ${USRDIR}/.local/properties +cp -af $TMPDIR/lamda.pem ${USRDIR} ui_print "- Please reboot your device" popd \ No newline at end of file diff --git a/tools/magisk/module.prop b/tools/magisk/module.prop index 313f7a9..f765d89 100644 --- a/tools/magisk/module.prop +++ b/tools/magisk/module.prop @@ -1,6 +1,6 @@ id=lamda name=LAMDA -version=3.0 -versionCode=3000 +version=VERSION +versionCode=VERSIONCODE author=rev1si0n description=Android reverse engineering & automation framework \ No newline at end of file diff --git a/tools/magisk/uninstall.sh b/tools/magisk/uninstall.sh index 49c96a6..0cf568d 100755 --- a/tools/magisk/uninstall.sh +++ b/tools/magisk/uninstall.sh @@ -1,4 +1,4 @@ #!/system/bin/sh MODPATH=${0%/*} -rm -rf /data/usr \ No newline at end of file +echo "/data/usr will not be removed, please remove it manually" \ No newline at end of file diff --git a/tools/objection-1.11.0-command-patch.diff b/tools/objection-1.11.0-command-patch.diff new file mode 100644 index 0000000..03746ab --- /dev/null +++ b/tools/objection-1.11.0-command-patch.diff @@ -0,0 +1,44 @@ +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 diff --git a/tools/openvpn/Dockerfile b/tools/openvpn/Dockerfile index 8f52c05..2309084 100644 --- a/tools/openvpn/Dockerfile +++ b/tools/openvpn/Dockerfile @@ -1,6 +1,6 @@ FROM alpine:3.15 -LABEL maintainer="rev1si0n " +LABEL maintainer="rev1si0n " ENV VARS=/etc/openvpn/easy-rsa/vars ENV OVPNCONFIG=/etc/openvpn/config.ovpn diff --git a/tools/openvpn/README.en.md b/tools/openvpn/README.en.md deleted file mode 100644 index 7be644e..0000000 --- a/tools/openvpn/README.en.md +++ /dev/null @@ -1,142 +0,0 @@ -## Out-of-the-box OpenVPN service image - -This image only ensures that the basic functionality is correct. If you are able to configure it yourself, it is recommended that you build it yourself or you can refer to the implementation of this image to do so. You will need to have a basic knowledge of Linux and Docker before using it. This image has been tested on Debian 9. - -> The default port for this service is 1190/UDP, so make sure you have allowed this rule in your firewall. - - -If your server is newly created, you may need to install Docker first, using the following command to install Docker -```bash -apt update; apt upgrade -y; apt install -y curl -curl https://get.docker.com | sh -# In the test environment, the installation of docker failed with the following error -#E: Unable to locate package docker-compose-plugin -#E: Unable to locate package docker-scan-plugin -#E: Unable to locate package docker-scan-plugin -apt-get install -y --no-install-recommends docker-ce docker-ce-cli containerd.io -``` - -## Preparation - -You will also need to make the following changes on the server by executing the command - -```bash -echo net.ipv4.ip_forward=1 >>/etc/sysctl.conf -sysctl -p -``` - -If you have `ufw`** installed on your server, you also need to write the following in the first few lines of `/etc/ufw/before.rules` - -> where eth0 and the network segment are modified according to your actual server interface and configuration, note that they are added before *filter (if any) - -```bash -*nat -:POSTROUTING ACCEPT [0:0] --A POSTROUTING -s 172.27.27.0/24 -o eth0 -j MASQUERADE -COMMIT -``` - -Then modify `/etc/default/ufw` -```bash -DEFAULT_FORWARD_POLICY="ACCEPT" -``` - -Then execute the command -```bash -ufw reload -``` - -If `ufw is not installed on your server** -Make sure that iptables' FORWARD defaults to ACCEPT, you can execute the following command - -> Note that you may need to reapply this rule after a server reboot, it is recommended that you install ufw - -```bash -iptables -P FORWARD ACCEPT -``` - -## Initialising the configuration - -Now, create a directory to store the service's configuration - -```bash -mkdir -p ~/lamda-openvpn-server -``` - -Immediately afterwards, start the initialization of the OpenVPN service - -```bash -docker run -it --rm --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn ovpn-server-new -``` - -Wait for the command to finish and you can now see the configuration of the service in the `~/lamda-openvpn-server` directory -The configuration file is `config.ovpn` - - -You can open this file with an editor and it is recommended to change only the following fields - -```ini -# VPN network segment and mask -server 172.27.27.0 255.255.255.0 -# VPN service port -port 1190 - -# or if you need a network interface on the server to be accessible by the VPN client -# You can also add a route, but note that you will only be able to access the IP of the current host in this segment -# If you need full client access to this network segment, you will need to make additional settings -push "route 192.168.68.0 255.255.255.0" - -# Change 114 to the DNS server you need -push "dhcp-option DNS 114.114.114.114" -``` - -## Create client connection credentials - -Now that you've finished editing, start creating a client, `myname` can be changed to whatever you want, but don't duplicate it - -```bash -docker run --it --rm --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn ovpn-client-new myname -``` - -Once created, use the following command to get the login credentials for this client - -```bash -# Note: The IP in the configuration is the current public IP that is automatically obtained, if it is not correct you will need to change it yourself -# -# Generate the ovpn configuration and redirect it to the file myname.ovpn, which can be used for APPs such as OpenVPN-Connect -docker run -it --rm --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn ovpn-client-profile ovpn myname >myname. ovpn -# Generate the OpenVPNProfile used by lamda, which can be used directly in lamda -# It contains a properties.local comment section where you can configure openvpn.* -# Copy it to /data/local/tmp/properties.local for automatic VPN connection -docker run -it --rm --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn ovpn-client-profile lamda myname -``` - -## Revoke client credentials - -If you need to revoke a client credential please execute the following command, you may need to restart the OpenVPN service after revocation - -```bash -docker run -it --rm --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn ovpn-client-revoke myname -``` - -## Start the OpenVPN service - -Now, start the OpenVPN service - -> Run in the foreground to see client connection logs directly for troubleshooting errors -```bash -docker run --it --rm --name openvpn-server --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn run -``` - -> Run in the background, it is recommended to start with this method after confirming -```bash -docker run -d --rm --name openvpn-server --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn run -``` - -If that's correct, the individual functions are just right and you're ready to go. - -## Other references - -Basic documentation: [https://openvpn.net/community-resources/reference-manual-for-openvpn-2-4/](https://openvpn.net/community-resources/reference -manual-for-openvpn-2-4/) - -Routing setup: [https://community.openvpn.net/openvpn/wiki/BridgingAndRouting](https://community.openvpn.net/openvpn/wiki/BridgingAndRouting ) \ No newline at end of file diff --git a/tools/openvpn/README.md b/tools/openvpn/README.md deleted file mode 100644 index 70bd94f..0000000 --- a/tools/openvpn/README.md +++ /dev/null @@ -1,142 +0,0 @@ -## 开箱即用的 OpenVPN 服务镜像 - -此镜像仅确保了基本功能无误,如果你自己有能力配置,建议自行搭建或者可以参考这个镜像的实现方法来完成。使用前,你需要有 Linux 以及 Docker 相关基础。此镜像在 Debian 9 上测试通过。 - -> 这里讲述的是如何在 Debian 9 上使用,通常情况下,这可以适用于其他系统例如 Ubuntu 以及 CentOS。该服务默认端口为 1190/UDP,请确保已在防火墙允许该规则。 - - -如果你的服务器是全新创建的,你可能需要先安装 Docker,使用如下命令安装 Docker -```bash -apt update; apt upgrade -y; apt install -y curl -curl https://get.docker.com | sh -# 在测试环境下,安装 docker 出现了失败的问题,报错为 -#E: Unable to locate package docker-compose-plugin -#E: Unable to locate package docker-scan-plugin -# 此时执行如下命令即可 -apt-get install -y --no-install-recommends docker-ce docker-ce-cli containerd.io -``` - -## 准备工作 - -你还需要在服务器上做出如下修改,执行命令 - -```bash -echo net.ipv4.ip_forward=1 >>/etc/sysctl.conf -sysctl -p -``` - -如果你的服务器上**安装了 `ufw`**,还需在 `/etc/ufw/before.rules` 的前几行写入如下内容 - -> 这里的 eth0 和网段根据你的实际服务器接口和配置做修改,注意是加在 *filter 之前(如果有的话) - -```bash -*nat -:POSTROUTING ACCEPT [0:0] --A POSTROUTING -s 172.27.27.0/24 -o eth0 -j MASQUERADE -COMMIT -``` - -然后修改 `/etc/default/ufw` -```bash -DEFAULT_FORWARD_POLICY="ACCEPT" -``` - -随后执行命令 -```bash -ufw reload -``` - -如果你的服务器上**没有安装 `ufw`** -请确保 iptables 的 FORWARD 默认为 ACCEPT,你可以执行如下命令 - -> 注意服务器重启后你可能需要重新应用此规则,建议安装 ufw - -```bash -iptables -P FORWARD ACCEPT -``` - -## 初始化配置 - -现在,创建一个目录用来保存服务的配置 - -```bash -mkdir -p ~/lamda-openvpn-server -``` - -紧接着,开始 OpenVPN 服务的初始化操作 - -```bash -docker run -it --rm --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn ovpn-server-new -``` - -等待命令结束,现在,你可以在 `~/lamda-openvpn-server` 目录下看到服务的配置 -配置文件为 `config.ovpn` - - -你可以使用编辑器打开此文件,建议只修改下面几个字段 - -```ini -# VPN 的网段以及掩码 -server 172.27.27.0 255.255.255.0 -# VPN 服务端口 -port 1190 - -# 或者如果你需要服务器上某个网络接口可以被VPN客户端访问 -# 你也可以增加一条路由,但是注意此时你也只能访问到当前主机在此网段的IP -# 如果需要客户端对此网段的完全访问,你还需进行额外设置 -push "route 192.168.68.0 255.255.255.0" - -# 修改 114 为你需要的 DNS 服务器 -push "dhcp-option DNS 114.114.114.114" -``` - -## 创建客户端连接凭证 - -编辑完毕,现在开始创建一个客户端,`myname` 可以改为其他你想要的,但是不可重复 - -```bash -docker run -it --rm --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn ovpn-client-new myname -``` - -创建完成后,使用如下命令来获取此客户端的登录凭证 - -```bash -# 注意:配置中的 IP 是自动获取的当前公网IP,如果不对还需自行修改 -# -# 生成 ovpn 配置,并重定向保存到文件 myname.ovpn,这个文件可以用于 OpenVPN-Connect 等 APP -docker run -it --rm --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn ovpn-client-profile ovpn myname >myname.ovpn -# 生成 lamda 使用的 OpenVPNProfile,可以直接在 lamda 中使用 -# 其中包含一段 properties.local 注释部分,你可以将其中的 openvpn.* 配置 -# 复制到 /data/local/tmp/properties.local 中以实现自动连接 VPN -docker run -it --rm --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn ovpn-client-profile lamda myname -``` - -## 撤销客户端凭证 - -如果需要撤销某个客户端凭证请执行以下命令,撤销后可能需重新启动 OpenVPN 服务 - -```bash -docker run -it --rm --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn ovpn-client-revoke myname -``` - -## 启动 OpenVPN 服务 - -现在,启动 OpenVPN 服务 - -> 在前台运行,可以直接看到客户端连接日志,用于排查错误 -```bash -docker run -it --rm --name openvpn-server --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn run -``` - -> 在后台运行,确认无误后建议使用此方法启动 -```bash -docker run -d --rm --name openvpn-server --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn run -``` - -如果没错的话,各个功能都是刚刚好,你可以开始使用了。 - -## 其他参考 - -基础文档:[https://openvpn.net/community-resources/reference-manual-for-openvpn-2-4/](https://openvpn.net/community-resources/reference-manual-for-openvpn-2-4/) - -路由设置:[https://community.openvpn.net/openvpn/wiki/BridgingAndRouting](https://community.openvpn.net/openvpn/wiki/BridgingAndRouting) diff --git a/tools/pack_frida_script.py b/tools/pack_frida_script.py new file mode 100644 index 0000000..2807f8b --- /dev/null +++ b/tools/pack_frida_script.py @@ -0,0 +1,38 @@ +#!/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 sys +import base64 +import importlib.util + + +def embed_bridge(source, lang): + # https://github.com/frida/frida/issues/3460#issuecomment-3066016776 + tools_dir = os.path.dirname(importlib.util.find_spec("frida_tools").origin) + fmt = '(function() { %s; Object.defineProperty(globalThis, "%s", { value: bridge }); })(); %s' + bridge = os.path.join(tools_dir, "bridges", f"{lang.lower()}.js") + with open(bridge, "r", encoding="utf-8") as f: + return fmt % (f.read(), lang, source) + + +if __name__ == "__main__": + script = sys.argv[1] + source = embed_bridge(open(script, "rt", encoding="utf-8").read(), + "Java") + open("%s-packed" % script, "w").write(source) + encoded = base64.b64encode(source.encode("utf-8")).decode() + open("%s-packed-modules-script.yaml" % script, "w").write( +"""enable: true +application: "YOUR_APPLICATION" +version: "N/A" +user: 0 +runtime: "qjs" +script: !!binary "%s" +emit: "http://myserver/reportData" +encode: "none" +standup: 10 +spawn: false""" % encoded) \ No newline at end of file diff --git a/tools/paddle_ocr_http_api.py b/tools/paddle_ocr_http_api.py new file mode 100644 index 0000000..59fc4be --- /dev/null +++ b/tools/paddle_ocr_http_api.py @@ -0,0 +1,26 @@ +#!/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) \ No newline at end of file diff --git a/tools/requirements.txt b/tools/requirements.txt index faa8caf..0e46a05 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -1,3 +1,5 @@ -mitmproxy>=7.0.0,<10.0.0 -lamda>=3.0.0,<4.0.0 -packaging \ No newline at end of file +mitmproxy>9.0.0 +dnspython +httpx[socks]<0.28.0 +packaging +Pillow \ No newline at end of file diff --git a/tools/root.crt b/tools/root.crt new file mode 100644 index 0000000..a7a7a91 --- /dev/null +++ b/tools/root.crt @@ -0,0 +1,16 @@ +-----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----- diff --git a/tools/root.key b/tools/root.key new file mode 100644 index 0000000..d7cca99 --- /dev/null +++ b/tools/root.key @@ -0,0 +1,28 @@ +-----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----- diff --git a/tools/rsync.sh b/tools/rsync.sh new file mode 100755 index 0000000..58a92c9 --- /dev/null +++ b/tools/rsync.sh @@ -0,0 +1,50 @@ +#!/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 diff --git a/tools/scp.sh b/tools/scp.sh index 9beb4d6..9992037 100755 --- a/tools/scp.sh +++ b/tools/scp.sh @@ -1,16 +1,13 @@ #!/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 + +HOST="${1%%:*}" +USER="${HOST%%@*}" TARGET="${HOST##*@}" && [ "$TARGET" = "$HOST" ] && USER="root" +case "$1" in *':'*) p1="$1"; [[ "$1" != *@* ]] && p1="root@$1" ;; *) p1="$1" ;; esac +case "$2" in *':'*) p2="$2"; [[ "$2" != *@* ]] && p2="root@$2" ;; *) p2="$2" ;; esac + umask 077 if [ ! -f "${CERTIFICATE}" ]; then # this is the default id_rsa for ssh service @@ -46,5 +43,6 @@ 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 diff --git a/tools/socks5/Dockerfile b/tools/socks5/Dockerfile index 1c3d40f..127814c 100644 --- a/tools/socks5/Dockerfile +++ b/tools/socks5/Dockerfile @@ -1,6 +1,6 @@ FROM alpine:3.15 # this will produce large image but IDC -LABEL maintainer="rev1si0n " +LABEL maintainer="rev1si0n " ENV SOURCESMIRROR=mirrors.ustc.edu.cn RUN sed -i "s/dl-cdn.alpinelinux.org/${SOURCESMIRROR}/g" /etc/apk/repositories diff --git a/tools/socks5/README.en.md b/tools/socks5/README.en.md deleted file mode 100644 index 72e7814..0000000 --- a/tools/socks5/README.en.md +++ /dev/null @@ -1,67 +0,0 @@ -## Out-of-the-box socks5 proxy service images - -Here lamda provides two methods of installing socks5, to avoid problems please read the following rules first - -> If you need to proxy the UDP protocol - -Due to the nature of the socks5 udp proxy, this can make potholes as well as things more and more common, if you are sure you need to use UDP -One: your host system must be Linux and your firewall needs to be free of UDP ports `50000-55000 -Two: your server network must not be NAT (FULL NAT is not an option, existing cloud servers are basically in NAT mode) - -Why host Linux? Because on other systems docker may not be able to map such a large number of ports properly. -Secondly, the docker network mode on other systems will affect it. - -If you don't know whether your server network is NAT or not, get the IP address of the default network interface by using a command such as ifconfig. -Then ping the address where you want to use the proxy. If the ping doesn't work, then your server is probably in NAT. - -Of course, if the conditions are not met it doesn't mean that you can't use UDP, you can still set up gost yourself, the end of this article describes how to use gost to set up. - -> If you don't need to use UDP, then everything is very simple - -Just use the following command -```bash -docker run --it --rm -p 1080:1080 --name socks -e LOGIN=username -e PASSWORD=passwd rev1si0n/socks5 -``` - -> you can specify the outgoing network card if you need to - -If you need to be able to specify an outgoing network card, that is, if your server or computer has more than one interface to the internet, take a home computer, you have two networks connected via cable and WIFI respectively -Then your computer may have two network interfaces wlan0 , eth0 -and you need to specify which one is the proxy outgoing network. - -Your computer or server must be a Linux system, if you want to use eth0 to get out of the network, you can start it with the following command -```bash -docker run --it --rm --net host --name socks -e LOGIN=username -e PASSWORD=passwd -e DEV=eth0 rev1si0n/socks5 -``` - -If you have met all the conditions for using UDP, use the following command - -```bash -docker run -it --rm --net host --name socks -e LOGIN=username -e PASSWORD=passwd rev1si0n/socks5 -``` - -## Using gost as a proxy - -If you want to use UDP but can't meet the conditions for using UDP above, or can't use the specified outgoing interface, or don't want to install docker you can try gost. -Download the executable zip for your system from [github.com/ginuerzh/gost/releases/](https://github.com/ginuerzh/gost/releases). - -```bash -gost -L=socks5://username:passwd@:1080 -# Note that all UDP port firewalls on the system are turned off. -``` - -that works. - - -Finally, just connect with the following code in lamda - -```python -profile = GproxyProfile() -profile.type = GproxyType.SOCKS5 -#profile.udp_proxy = True or False -profile.host = "192.168.server IP" -profile.port = 1080 -profile.login = "username" -profile.password = "passwd" -d.start_gproxy(profile) -``` \ No newline at end of file diff --git a/tools/socks5/README.md b/tools/socks5/README.md deleted file mode 100644 index b9e04ed..0000000 --- a/tools/socks5/README.md +++ /dev/null @@ -1,67 +0,0 @@ -## 开箱即用的 socks5 代理服务镜像 - -在这里 lamda 提供了两种 socks5 的安装方法,为了避免问题,请先阅读以下规则 - -> 如果你需要代理UDP协议 - -由于socks5 udp代理的特性,这会让坑以及事情越来越多,如果你确定需要使用UDP -你必须遵循这些规则,其一:你的宿主机系统必须为 Linux,且防火墙需要放开 `50000-55000` 的UDP端口 -其二:你的服务器网络必不能为 NAT(FULL NAT 也不行,现有的云服务器基本都是 NAT 模式) - -为什么宿主机要是 Linux,因为在其他系统上 docker 可能无法正常映射如此大段的端口, -其次,其他系统上 docker 网络模式会影响。 - -如果你不知道你的服务器网络是不是 NAT,请通过 ifconfig 等命令获取默认网络接口的IP地址, -随后在你想要使用代理的地方 ping 这个地址,如果PING不通,那么你的服务器可能在 NAT 里。 - -当然,如果有条件不符合并不代表就不能用UDP,你仍然可以自行搭建 gost,文末介绍如何使用 gost 搭建。 - -> 如果不需要使用 UDP,那么一切都很简单 - -使用如下命令即可 -```bash -docker run -it --rm -p 1080:1080 --name socks -e LOGIN=username -e PASSWORD=passwd rev1si0n/socks5 -``` - -> 如果你需要可以指定出网网卡 - -就是你的服务器或者电脑上有多个可以上网的接口,拿家用电脑来说,你通过网线和WIFI分别连接了两个网络 -那么你的电脑可能就有两个网络接口 wlan0 , eth0 -而你需要指定代理出网的那个网络。 - -你的电脑或者服务器必须为 Linux 系统,如果你想使用 eth0 出网,使用以下命令启动即可 -```bash -docker run -it --rm --net host --name socks -e LOGIN=username -e PASSWORD=passwd -e DEV=eth0 rev1si0n/socks5 -``` - -如果你已满足使用UDP的各项条件,使用如下命令即可 - -```bash -docker run -it --rm --net host --name socks -e LOGIN=username -e PASSWORD=passwd rev1si0n/socks5 -``` - -## 使用 gost 来做代理 - -如果你想使用 UDP 但是无法满足上面使用UDP的条件,或者用不到指定出网接口,或者不想安装 docker 你可以尝试 gost。 -从 [github.com/ginuerzh/gost/releases/](https://github.com/ginuerzh/gost/releases) 下载对应系统的可执行文件压缩包。 - -```bash -gost -L=socks5://username:passwd@:1080 -# 注意关闭系统上的所有UDP端口防火墙。 -``` - -即可。 - - -最后,在 lamda 中使用如下代码连接即可 - -```python -profile = GproxyProfile() -profile.type = GproxyType.SOCKS5 -#profile.udp_proxy = True 或者 False -profile.host = "192.168.服务器IP" -profile.port = 1080 -profile.login = "username" -profile.password = "passwd" -d.start_gproxy(profile) -``` \ No newline at end of file diff --git a/tools/ssh.sh b/tools/ssh.sh index 9490096..170f830 100755 --- a/tools/ssh.sh +++ b/tools/ssh.sh @@ -1,7 +1,10 @@ #!/bin/bash -TARGET=${1:-localhost} PORT=${PORT:-65000} DEFAULT_ID_RSA=$(mktemp) +if [[ "$1" == *@* ]]; then + USER="${1%@*}" TARGET="${1#*@}" +else USER="root" TARGET="$1" +fi umask 077 if [ ! -f "${CERTIFICATE}" ]; then @@ -38,5 +41,6 @@ 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} + -o LogLevel=ERROR -i $DEFAULT_ID_RSA -p $PORT $USER@$TARGET ${@:2} diff --git a/tools/startmitm.py b/tools/startmitm.py index 26ba0ba..b73794d 100755 --- a/tools/startmitm.py +++ b/tools/startmitm.py @@ -1,15 +1,25 @@ #!/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 subprocess -import argparse 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 @@ -41,10 +51,26 @@ def add_server(command, spec): 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: @@ -88,58 +114,57 @@ def get_default_interface_ip(target): print (r" /____ > |__| (____ /__| |__| |__|_| /__||__| |__|_| / ") print (r" \/ \/ \/ \/ ") print (r" Android HTTP Traffic Capture ") -print (r"%60s" % ("lamda#v%s BY rev1si0n" % (__version__))) +print (r"%60s" % ("lamda#v%s BY firerpa" % (__version__))) pkgName = None argp = argparse.ArgumentParser() -def dnsopt(dns): - return "reverse:dns://{}@53".format(dns) +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) -argp.add_argument("-m", "--mode", default="regular") -argp.add_argument("--serial", type=str, default=None) -dns = argp.add_mutually_exclusive_group(required=False) -dns.add_argument("--dns", type=dnsopt, nargs="?", - const="1.1.1.1") -dns.add_argument("--nameserver", type=str, default="") +mod = argp.add_mutually_exclusive_group(required=False) +mod.add_argument("-m", "--mode", default="socks5") +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("--device-side-out-interface", type=str, default="auto", + help="Specify the outgoing network interface on the device") +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(":") -if args.dns and ver(VERSION) < ver("9.0.0"): - log ("dns mitm needs mitmproxy>=9.0.0") - sys.exit (1) - -login = "mitm" -psw = uuid.uuid4().hex[::4] -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)) server = get_default_interface_ip(host) usb = server in ("127.0.0.1", "::1") if cert: log ("ssl:", cert) -if usb and args.dns: - log ("dns mitm not available over usb") - sys.exit (1) -if usb and (forward(lamda, lamda).wait() != 0 or \ - reverse(proxy, proxy).wait() != 0): - log ("forward failed") - sys.exit (1) - -# 创建设备实例 +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") @@ -147,13 +172,27 @@ def dnsopt(dns): log ("install cacert: %s" % ca) d.install_ca_certificate(ca) -# 初始化 proxy 配置 +# disable ipv6 +# If the local device does not have a valid public IPv6 address but the mobile device does, +# it may cause the device to show "no network". so IPv6 is disabled here for the phone. +d.execute_script("echo 1 | tee /proc/sys/net/ipv6/conf/all/disable_ipv6") + +# Initialize proxy profile profile = GproxyProfile() -profile.type = GproxyType.HTTP_CONNECT -profile.nameserver = args.nameserver -if not usb and args.dns: - profile.nameserver = server -profile.drop_udp = True +profile.type = GproxyType.SOCKS5 +profile.bypass_local_subnet = True +profile.interface = args.device_side_out_interface + +# SOCKS5 is not supported in upstream mode +# https://github.com/mitmproxy/mitmproxy/issues/2813 +if args.upstream: profile.type = GproxyType.HTTP_CONNECT + +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]) + +profile.udp_proxy = True profile.host = server profile.port = proxy @@ -169,20 +208,18 @@ def dnsopt(dns): d.start_gproxy(profile) command = [] -# 设置 MITMPROXY 代理模式 add_server(command, args.mode) -add_server(command, args.dns) command.append("--ssl-insecure") -# 代理认证,防止误绑定到公网被扫描 +# Simple random auth command.append("--proxyauth") command.append("{}:{}".format(login, psw)) -# 随机 web-port +# 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)) diff --git a/tools/statistics.sh b/tools/statistics.sh deleted file mode 100755 index fa09456..0000000 --- a/tools/statistics.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/system/bin/sh -if [ ! -f "$1" ]; then - echo launch.sh path not provided. -exit 1 -fi -OUTFILE=/sdcard/statistics.txt -echo "collecting lamda statistics" -rm $OUTFILE >/dev/null 2>&1 -{ -echo "BEGIN LAMDA REPORT $(date +%Y-%m-%d.%H:%M:%S)" -echo "---------------------------------------------" -echo "enforced: $(getenforce)" -echo "ashmem: $(! grep ashmem /proc/misc >/dev/null 2>&1; echo $?)" -echo "binder: $(! grep binder proc/filesystems >/dev/null 2>&1; echo $?)" -echo "uname: $(uname -a)" -echo "" -echo "ro.boot.hardware: $(getprop ro.boot.hardware)" -echo "ro.boot.selinux: $(getprop ro.boot.selinux)" -echo "" -echo "ro.build.version.sdk: $(getprop ro.build.version.sdk)" -echo "ro.build.fingerprint: $(getprop ro.build.fingerprint)" -echo "ro.build.type: $(getprop ro.build.type)" -echo "" -echo "ro.product.brand: $(getprop ro.product.brand)" -echo "ro.product.board: $(getprop ro.product.board)" -echo "ro.product.cpu.abi: $(getprop ro.product.cpu.abi)" -echo "ro.product.cpu.abilist: $(getprop ro.product.cpu.abilist)" -echo "ro.product.device: $(getprop ro.product.device)" -echo "ro.product.model: $(getprop ro.product.model)" -echo "ro.debuggable: $(getprop ro.debuggable)" -echo "" -echo "persist.sys.timezone: $(getprop persist.sys.timezone)" -echo "persist.sys.locale: $(getprop persist.sys.locale)" -echo "" - -echo "IP-tables check:" -echo "IP-tables nat: $(! iptables -L -n -t nat >/dev/null; echo $?)" -echo "" - -echo "IP-route check:" -echo "ip link: $(! ip link >/dev/null; echo $?)" -echo "ip tunnel: $(! ip tunnel >/dev/null; echo $?)" -echo "ip tuntap: $(! ip tuntap >/dev/null; echo $?)" -echo "ip route: $(! ip route >/dev/null; echo $?)" -echo "ip rule: $(! ip rule >/dev/null; echo $?)" -echo "" - -echo "lsmod:" -lsmod -echo "" - -} >>$OUTFILE 2>&1 - -DMESG=$(mktemp) -LOGCAT=$(mktemp) -PIDS= - -dmesg -C -logcat --clear - -dmesg -w >$DMESG 2>&1 & -PIDS+="$PIDS $!" -logcat >$LOGCAT 2>&1 & -PIDS+="$PIDS $!" - -echo "please wait..." -sh $@ >> $OUTFILE 2>&1 - -sleep 60 -kill -TERM $PIDS - -{ -echo "-------- BEGIN dmesg" -cat $DMESG -echo "-------- BEGIN logcat" -cat $LOGCAT -} >>$OUTFILE 2>&1 -echo "report: $OUTFILE" -echo "finished" -exit 0 \ No newline at end of file diff --git a/tools/test-fridarpc.js b/tools/test-fridarpc.js index d7706b2..8b661a1 100644 --- a/tools/test-fridarpc.js +++ b/tools/test-fridarpc.js @@ -3,11 +3,24 @@ Java.perform(function() { 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 + }) + }, } -createFridaRpc("myRpcName", rpc.exports) -console.log("fridarpc test loaded") }); diff --git a/tools/test.pem b/tools/test.pem index ce946c4..a83497a 100644 --- a/tools/test.pem +++ b/tools/test.pem @@ -1,95 +1,62 @@ -Certificate: - Data: - Version: 1 (0x0) - Serial Number: - 68:20:bd:74:37:c8:00:25:6b:dd:2b:36:c4:32:c2:6d:2c:eb:7a:a6 - Signature Algorithm: sha256WithRSAEncryption - Issuer: O = lamda, CN = test - Validity - Not Before: Jan 1 00:00:01 2020 GMT - Not After : Dec 29 00:00:01 2029 GMT - Subject: O = lamda, CN = test - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public-Key: (2048 bit) - Modulus: - 00:e8:f4:8c:71:44:62:a6:11:19:7e:41:95:7f:cf: - 29:6c:43:80:bb:93:ee:36:fb:24:d1:fa:36:25:9c: - c6:17:00:1e:82:91:7e:cd:56:21:0f:e9:f0:ff:06: - d0:19:22:07:6a:dd:5a:52:53:ca:80:70:8d:bc:bd: - db:dc:df:e2:77:28:03:bc:5f:e7:e1:b2:38:e0:0f: - fa:f5:0a:80:0c:db:e7:65:63:b1:4c:d5:fa:41:2b: - e5:cc:c2:bd:dd:0a:80:38:b2:00:53:fa:bf:5e:f9: - 34:46:d7:3e:e0:64:65:8d:8c:f1:66:0c:3b:dd:39: - d6:70:ff:88:4a:29:2a:82:10:ce:eb:7f:59:d3:f7: - ab:eb:d9:7e:8b:34:33:8b:e0:38:58:5c:de:0b:95: - 9f:42:8e:fb:95:bd:7e:a4:63:58:3b:5a:d1:2c:56: - 3f:42:91:79:fa:17:75:bb:4d:79:87:7c:97:4d:95: - 23:50:6f:fe:17:9f:fa:81:ba:48:cb:46:7d:32:29: - 67:a8:d9:2b:75:f3:c4:64:3b:76:24:1e:41:13:81: - cb:64:da:2e:57:04:d1:99:de:9c:f0:18:72:03:52: - 24:6f:9b:c8:d2:fd:ef:b3:67:9e:d9:a3:e2:e0:a3: - 84:b2:7f:5e:8d:18:ae:d0:d6:21:d7:f4:ee:79:a3: - 85:79 - Exponent: 65537 (0x10001) - Signature Algorithm: sha256WithRSAEncryption - ca:71:90:4d:bd:3d:5d:ea:4b:53:cb:a9:1b:d7:bf:c7:96:88: - c8:2c:ea:26:29:c7:6f:a5:f3:45:dc:86:bd:7d:e9:20:a9:d8: - ef:31:12:37:03:50:5e:73:58:02:17:fd:f8:09:46:04:ee:3c: - 5b:ab:9c:01:58:a9:1c:09:67:85:5a:be:c7:07:5d:4b:5d:2e: - 40:5f:2b:f0:1f:aa:17:f4:aa:1e:0d:d9:bf:4c:e8:46:f7:56: - e5:a8:72:e6:de:73:cd:c3:c2:02:c4:1f:aa:93:69:1b:f1:16: - 9d:0a:7a:22:47:12:03:b5:21:28:4d:82:12:cf:ee:82:77:09: - 99:9c:ae:ff:55:cb:3d:66:82:8f:ae:f7:64:19:7f:81:8b:b3: - b6:fb:70:20:e3:52:69:ea:ea:9b:5a:3f:5d:54:88:05:a4:98: - 43:ef:fa:bf:21:df:6b:85:44:98:49:5b:21:0f:4a:af:1c:16: - ec:95:b1:18:ba:fc:08:9c:17:79:a4:d9:09:9b:e8:47:47:37: - 0a:05:be:9f:96:a3:2c:f6:21:19:25:0b:65:34:15:77:f8:09: - 4d:df:46:6c:0b:2d:d4:27:fa:d7:dc:49:a1:b8:68:d3:4b:68: - 45:f4:60:f5:44:92:1a:01:4b:6f:bd:e0:5b:31:54:98:e7:96: - 63:3d:08:ee +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----- -MIICxTCCAa0CFGggvXQ3yAAla90rNsQywm0s63qmMA0GCSqGSIb3DQEBCwUAMB8x -DjAMBgNVBAoMBWxhbWRhMQ0wCwYDVQQDDAR0ZXN0MB4XDTIwMDEwMTAwMDAwMVoX -DTI5MTIyOTAwMDAwMVowHzEOMAwGA1UECgwFbGFtZGExDTALBgNVBAMMBHRlc3Qw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDo9IxxRGKmERl+QZV/zyls -Q4C7k+42+yTR+jYlnMYXAB6CkX7NViEP6fD/BtAZIgdq3VpSU8qAcI28vdvc3+J3 -KAO8X+fhsjjgD/r1CoAM2+dlY7FM1fpBK+XMwr3dCoA4sgBT+r9e+TRG1z7gZGWN -jPFmDDvdOdZw/4hKKSqCEM7rf1nT96vr2X6LNDOL4DhYXN4LlZ9CjvuVvX6kY1g7 -WtEsVj9CkXn6F3W7TXmHfJdNlSNQb/4Xn/qBukjLRn0yKWeo2St188RkO3YkHkET -gctk2i5XBNGZ3pzwGHIDUiRvm8jS/e+zZ57Zo+Lgo4Syf16NGK7Q1iHX9O55o4V5 -AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAMpxkE29PV3qS1PLqRvXv8eWiMgs6iYp -x2+l80Xchr196SCp2O8xEjcDUF5zWAIX/fgJRgTuPFurnAFYqRwJZ4VavscHXUtd -LkBfK/Afqhf0qh4N2b9M6Eb3VuWocubec83DwgLEH6qTaRvxFp0KeiJHEgO1IShN -ghLP7oJ3CZmcrv9Vyz1mgo+u92QZf4GLs7b7cCDjUmnq6ptaP11UiAWkmEPv+r8h -32uFRJhJWyEPSq8cFuyVsRi6/AicF3mk2Qmb6EdHNwoFvp+Woyz2IRklC2U0FXf4 -CU3fRmwLLdQn+tfcSaG4aNNLaEX0YPVEkhoBS2+94FsxVJjnlmM9CO4= +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----- ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEA6PSMcURiphEZfkGVf88pbEOAu5PuNvsk0fo2JZzGFwAegpF+ -zVYhD+nw/wbQGSIHat1aUlPKgHCNvL3b3N/idygDvF/n4bI44A/69QqADNvnZWOx -TNX6QSvlzMK93QqAOLIAU/q/Xvk0Rtc+4GRljYzxZgw73TnWcP+ISikqghDO639Z -0/er69l+izQzi+A4WFzeC5WfQo77lb1+pGNYO1rRLFY/QpF5+hd1u015h3yXTZUj -UG/+F5/6gbpIy0Z9MilnqNkrdfPEZDt2JB5BE4HLZNouVwTRmd6c8BhyA1Ikb5vI -0v3vs2ee2aPi4KOEsn9ejRiu0NYh1/TueaOFeQIDAQABAoIBAQDLare0NF3b2zSi -ngw4daIbKnqf0RYDJpxJSq67e0EyVmVN6FSMYvgt2D8bwkw2lhNaCn3TQ92g+Cec -N0Fkho9PMz7H4AswdprZdLrT/F5CXEebrkjk6jhDblyUxzxZZtD+MNi+fEddXEh8 -z3nVIXXX+8nTLT2c+OM6+rd84qN+vBn3Z37o9Y50bL7dJZr5dANi94lHG1ymK4M9 -7w+bdiAmj4jun+KIuZNpjSsfPSS7JMzXGZotumb8rzhEARcjFEyczj2s6pGj76mc -A7cWy3na2DN1laxghbArbV39BDyGeJSq57O1CTsRm1IxNNBAqKJbusgoBUJdt4zz -DAQ4tTBxAoGBAPt48M67eisvahRZ3PGj9QpU3XAe3hDRfQYb7CaBG9WV11hG7DSt -km6u+gbNaWUr3TK1sE3sY6dJdcabkHbKmQlLxcNHO8+CIR6qVFNvU5A5s8bXjYon -6KJaBIn82hqtydlHL9wF32woDwBau9B+/I2sUJBdHX5ke70ZVnZMKWJ9AoGBAO0m -QrxIhOnZErcohYsWE01wgZkQOoRESmJ6Pcpy9gGpW43EtJgy1xP5ykq7qYpjOsJV -itBWvwNQK5XSMmnCCM5A5DlHNQA8e66JKLu1RJRYQ28/j1Ax39bwc2qRtpIVskIP -r7nNENe2WhCfoSg+0Xiim8c8k5QmLvsFkm0CPoOtAoGAettBOp7ZNtP5TWwBx4RE -qQGBFc/eyfSu0hc+LimRga2tAZHUwH+0NKMDv+MHBMiDTsz5D3jXztjS028ZCxA7 -6BC+04Kvs9dtet/YRtLyzlmCfTi+C6snunmkEg4YOESDgsWGmkW8O8juW0v4ygng -NQ7kZUM4r5FD6sTAOxdGeLkCgYBQYQh7ksTiEf5C8EwbMVdHhxuWCqcaz566tB4f -cUmqlIGAkqx3bbvyfFaxb9vuGOYAmkfbh5tkfxbWyBDoQakEZ4AzXcw8EUQGi1yw -Cqd89FGe8lcb4luxGXy1IUBVUV9C/tgbzMKpqpfqTICuCGStU8gjBogqzapc0K2p -nvobXQKBgQCxT2NDwlZaV/r3DWbCifHn7YIlW6uSYF+OKE0cZQHnxC5vVQeDi4XO -+LIPaxQb2yXSVXD4hojeMpcQ98eMUbQJp8HZYErny+003XWnrZxbvO2ZsCeP4vgL -HTtG7CVRlw/kgfqtSGimgH0+/IeqKqG+Zk2M+RnwbNiyvnKyo8X0fw== ------END RSA PRIVATE KEY----- -a1fd89e85343f347b70c4195292791ea