前言

继上一篇 Android 调用 Termux 执行命令,执行命令的问题基本解决,但是 bashawkclangd 这类命令可以从标准输入读取信息并维持运行,Termux 第三方调用缺乏有效支持。而 RunCommandService 可以允许命令后台运行,然后我们以某种方式取得该后台程序的标准输入/输出,便可以实现前后端的持续通信。


一、Android 进程间通信(IPC)

进程间通信(In-Process Communication, IPC)主要实现多进程间的数据通信问题。比如一个 UI 程序后台调用一个 CLI 程序执行某功能,每个程序会启动一个进程,UI 进程给 CLI 进程发送输入数据,CLI 进程接收后返回处理结果给 UI 进程。

Android 实现跨进程通信有多种方式,其各有优缺点:

文件:直接在设备上创建文件实现数据通信。原理简单,操作方便,但是效率低。

Binder:结构上 Binder 是一个虚拟的设备驱动(/dev/binder),连接 Service 进程、Client 进程和 Service Manager 进程。其数据只在内核空间与用户空间复制一次,效率较高,但是限制 1M 数据。另外,基于 Binder 的方法有 AIDL、ContentProvider、Messenger 等。

SharedMemory:共享内存在 Android SDK 27 引入,允许开辟一块共享内存空间用于进程间的数据交互。SharedMemory 配合 AIDL/Binder 使用,可以破除 1M 的限制,传输大文件。但是注意直接对内存进行操作,使用完毕需要手动销毁。

Unix Domain Socket:又叫 Local Domain Socket,本地套字节是 Linux 内核提供的功能,数据经过内核,实现本地进程间通信。其效率高,但是 Android 9+ 限制用户 App 间使用 UDS 通信。

Socket:套字节本质上是网络通信,采用 TCP 或 UDP 协议,主要用于网络通信。其中 TCP 协议较复杂,用于建立稳定的通信;UDP 则速度快而不安全。

受安卓不同应用之间的权限限制,支持进程间字节数据 IO 通信的方案较少,本次采用 Socket 实现。

二、Netcat 网络瑞士军刀

Netcat 是一个小巧强大的网络工具,用于网络监听测试等。Netcat 可建立网络通信,支持 TCP/UDP/Unix 协议。在 Termux 端使用 Netcat 建立套字节通信,并将 stdin/stdout 重定向到一个子进程,如此实现通信。

通过以下方式建立 TCP 通信。服务器端:

nc -l -s 127.0.0.1 -p 1234

客户端:

nc 127.0.0.1 1234

此时在客户端输入的内容可传至服务器端,而服务器端输入的内容可传回客户端,二者间实现通信。

另外,使用 Netcat 可以方便反弹 shell 程序。下面是服务器端命令:

nc -l -s 127.0.0.1 -p 1234 bash

用客户端登录到 127.0.0.1:1234,建立连接后,服务端会启动 bash 程序,并接收来自客户端的标准输入,将标准输出发送给客户端。

三、第三方 App 与 Termux 建立 TCP/Socket 通信

通过 RunCommandService 调用 Termux 执行 nc 命令反弹某个程序,然后通过 java.net.Socket 建立 Socket 连接,取得 Socket 的 IO 流,即可实现进程间通信。

调用 Termux。注意,Termux 可使用两个版本的 Netcat:安卓自带的 /system/bin/nc 和 Termux 仓库的 netcat-openbsd。前者随 ToyBox 在 Android Marshmallow 被引入,支持反弹 shell,而后者不支持;后者支持抽象命名空间 UDS。所以我们使用 /system/bin/nc

intent.setClassName("com.termux", "com.termux.app.RunCommandService");
intent.setAction("com.termux.RUN_COMMAND");
intent.putExtra("com.termux.RUN_COMMAND_PATH", "/system/bin/nc");
intent.putExtra("com.termux.RUN_COMMAND_ARGUMENTS", new String[]{"-l", "-s", "127.0.0.1", "-p", "1234", "bash"});
intent.putExtra("com.termux.RUN_COMMAND_WORKDIR", "/data/data/com.termux/files/home");
intent.putExtra("com.termux.RUN_COMMAND_BACKGROUND", true);
intent.putExtra("com.termux.RUN_COMMAND_SESSION_ACTION", "0");
startService(intent);

建立 Socket 连接:

Socket mSocket;
InputStream mInput;
OutputStream mOutput;
public void connect() {
mSocket = new Socket();
new Thread(){
public run() {
try {
sk.connect(new InetSocketAddress("127.0.0.1", 1234));
mInput = sk.getInputStream();
// 写入命令/发出数据
mOutput = sk.getOutputStream();
mOutput.write("ls\n");
mOutput.flush();
// 读取结果
Thread.sleep(200L);
int l = mInput.avaliable();
byte[] bs = new byte[l];
mInput.read(bs);
System.out.println(new String(bs));
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}

四、应用:调用 LSP 语言服务器

语言服务器协议(Language Server Protocol, LSP)是微软推出的一个基于 JSONRPC 的数据协议,用于

Termux 的软件仓库里正好有 clangdccls 两个 C/C++ 的 LSP 服务器端。笔者测试 ccls 时遇到 BUG,故选用 clangd 测试。

安装 clangd

apt install clangd

nc 反弹 clangd

/system/bin/nc -l -s 127.0.0.1 -p 48455 clangd

Android 客户端建立 Socket IO 通信:

Socket sk = new Socket(new InetAddress("127.0.0.1", 48455));

注意,安卓中 Socket 的 IO 流不允许在 UI 主线程进行操作,需要另起线程,以免阻塞主线程引起卡顿。

读取线程:

new Thread() {
public void run() {
InputStream mIn = sk.getInputStream();
final int L = 1024;
byte[] buf = new byte[L];
while (mIn.read(buf, 0, 16)!=-1) {
if (new String(buf, 0, 16).equals("Content-Length: ")) {
// read int c
// skip \r\n\r\n
// read c bytes
}
}
}
}.start();

写入线程:

Thread td = new Thread() {
public void run() {
try {
OutputStream mOut = sk.getOutputStream();
byte[] s="{\"jsonrpc\":\"2.0\",\"id\":0,\"method\":\"initialize\",\"params\":{}}".getBytes(StandardCharsets.UTF_8);
mOut.write(("Content-Length: "+s.length+"\r\n\r\n").getBytes());
mOut.write(s);
mOut.flush();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
};
td.start();
td.join(); // 阻塞写入线程,避免同时写入

笔者的开源项目:TermuC - github.com/RainbowC0


参见

  1. android共享内存(ShareMemory)的实现 - 简书
  2. 彻底弄懂netcat命令的使用 - CSDN
  3. What is toybox? - Landley
  4. 语言服务器协议概述 - Microsoft Learn

第三方App与Termux命令建立IO通道的更多相关文章

  1. iOS逆向工程之Hopper+LLDB调试第三方App

    LLDB是Low Level Debugger的简称,在iOS开发的调试中LLDB是经常使用的,LLDB是Xcode内置的动态调试工具.使用LLDB可以动态的调试你的应用程序,如果你不做其他的额外处理 ...

  2. android 关于提高第三方app的service优先级

    本博客仅仅要没有注明"转".那么均为原创,转贴请注明本博客链接链接 基本上大家都知道提高service优先级能够在非常大程度上让你的service免于由于内存不足而被kill,当然 ...

  3. TPYBoard实例之利用WHID为隔离主机建立隐秘通道

    本文作者:xiaowuyi,来自FreeBuf.COM(MicroPythonQQ交流群:157816561,公众号:MicroPython玩家汇) 0引言 从2014年BADUSB出现以后,USB- ...

  4. Linux ln命令 - 建立文件/目录链接

    转自Linux ln命令 - 建立文件/目录链接 1. 使用方式:ln [option] source_file dist_file                     -f 建立时,将同档案名删 ...

  5. 微信授权登陆接入第三方App(步骤总结)Android

    微信授权登陆接入第三方App(步骤总结)Android Android App实现第三方微信登录

  6. Android系统--输入系统(十六)APP跟输入系统建立联系_InputChannel和Connection

    Android系统--输入系统(十六)APP跟输入系统建立联系_InputChannel和Connection 0. 核心:socketpair机制 1. 回顾Dispatch处理过程: 1.1 放入 ...

  7. 接受第三方app分享的数据

    前段时间公司项目需要一个需求: 把第三方的app分享的数据接受到自己的apk中, 涉及到的第三方app是: Youtube/Amazon/NetFlix, 这些app通过分享功能把当前的信息分享出去. ...

  8. ionicAPP打开第三方APP

    近来,碰到一个问题,需要在ionicAPP中打开第三方APP 然后,就找资料,发现了个比较好的解决方案 可以参考:https://blog.csdn.net/a727911438/article/de ...

  9. 酷开 5.5 版本安装第三方app

    https://www.znds.com/jc/article/2952-1.html .开始安装(以安装当贝桌面为例): adb connect 192.168.XXX.XXX(电视IP) adb ...

  10. android -------- 打开本地浏览器或指定浏览器加载,打电话,打开第三方app

    开发中常常有打开本地浏览器加载url或者指定浏览器加载, 还有打开第三方app, 如 打开高德地图 百度地图等 在Android程序中我们可以通过发送隐式Intent来启动系统默认的浏览器. 如果手机 ...

随机推荐

  1. Modelsim使用指南

    Modelsim使用指南 本文讲述Modelsim的使用步骤. 添加一个测试文件,比如modulename.v. 编辑这个Verilog模块. 为了方便讲述,顶层模块名命名为"top&quo ...

  2. Servlet注解的使用,简化配置 以及,使用模板方法设计模式优化oa项目

    Servlet注解的使用,简化配置 以及,使用模板方法设计模式优化oa项目 每博一文案 有句谚语说:"一怒之下踢石头,只有痛着脚趾头." 比一件糟糕的事情更可拍的,是你用糟糕的态度 ...

  3. 您可知道如何通过`HTTP2`实现TCP的内网穿透???

    可能有人很疑惑应用层 转发传输层?,为什么会有这样的需求啊???哈哈技术无所不用其极,由于一些场景下,对于一个服务器存在某一个内部网站中,但是对于这个服务器它没有访问外网的权限,虽然也可以申请端口访问 ...

  4. 如何使用go module导入本地包

    go module是Go1.11版本之后官方推出的版本管理工具,并且从Go1.13版本开始,go module将是Go语言默认的依赖管理工具. 到今天Go1.14版本推出之后Go modules 功能 ...

  5. 批量删除WordPress文章和页面的数据库命令和从后台直接删除

    批量删除wordpress的方法有两种:1.从wp后台可以调整展示:最多999条 2.选择"Bulk"--"Apply" 通过批量删除wordpress文章和页 ...

  6. JS基础--JavaScript实例集锦(初学)

    1.子节点childNodes: <!DOCTYPE html> <html> <head> <title>childNodes</title&g ...

  7. Chrome 浏览器插件 Manifest.json V3 中权限(Permissions)字段解析

    一.权限(Permissions) 再使用拓展程序的 API 时,大多数的时候,需要在 manifest.json 文件中声明 permissions 字段. 一.权限类型 在 V3 版本中可以声明以 ...

  8. Linux上执行内存中的脚本和程序

    在Linux中可以不需要有脚本或者二进制程序的文件在文件系统上实际存在,只需要有对应的数据在内存中,就有办法执行这些脚本和程序. 原理其实很简单,Linux里有办法把某块内存映射成文件描述符,对于每一 ...

  9. 【U8】bom全阶 点击展开按钮,无法获得叶子子阶

    经查看 是因为 子件的失效日期与生效日期是相同一天,延后子件的失效日期就可以在点击展开按钮后,看到子阶bom了.

  10. 什么是Token?为什么大模型要计算Token数

    本文分享自华为云社区<[技术分享]什么是Token?为什么GPT基于Token定价>,作者:开天aPaaS小助手Tracy. 在使用LLM大模型时,我们经常会遇到一个关键词,叫:Token ...