Android SSH反向连接实践
方案实现依靠以下几篇文章:
http://blog.163.com/leekwen@126/blog/static/3316622920118144927681/
http://www.cnblogs.com/whltingyu/p/4083448.html
https://segmentfault.com/a/1190000002718360
在android系统中移植ssh服务是基于实时维护移动端设备而提出来的需求,在客户的设备出现问题而研发人员无法处于现场时提出的解决方案。
在实现这个方案之后可以通过终端执行adb指令,可以进入终端的文件系统删除或修改一些文件,以保证系统能够流畅运行。
该方案实现思路如下:
image
解释一下,C端是PC端的一些ssh客户端,比如bitvise,SecureCRT等;B端是指后台服务器;A是指客户手上的机器,现在要实现的是C通过B连接A,处理A上面出现的问题。
C端很好解决,下载一个SSH客户端就可以解决问题。B端是后台开发人员需要解决的问题,基本上安装一个ssh服务就可以解决了。A端是要重点解决的地方,涉及到了ssh服务的移植,包含移动端和服务端,另外还要移植ftp,开启服务之后保证服务的稳定性。
- 第一步,移植ssh服务到Android系统,主要参考的文章是文章开头的第一篇文章,将其编译之后生成的目标文件分别是:dropbear,dropbearkey,scp,sftp-server,ssh,我们实际需要的文件是dropbear和sftp-server,ssh文件。获取到这两个文件之后,通过分析提取文章开头的第二篇文章中的步骤,不断摸索失败总结之后,最终只需要执行以下几行代码:
private void initSSHFile(){
MyLog.e(TAG, "init ssh file");
String[]commands = {
"rm-rf /data/dropbear/","mkdir /data/dropbear", "mkdir/data/dropbear/.ssh",
"cp-rf /system/dss_host_key /data/dropbear/",//作为服务端生成的密钥
"cp-rf /system/authorized_keys /data/dropbear/.ssh",//客户端生成的密钥pub文件
"chmod-R 0744 /data/dropbear","mount -o rw,remount /",
"echo\"root:x:0:0::/root:/system/bin/sh\" > /etc/passwd",
"echo\"root::14531:0:99999:7:::\" > /etc/shadow",
"echo\"root:x:0:\" > /etc/group",
"echo\"root:!::\" > /etc/gshadow",
"echo\"/system/bin/sh\" > /etc/shells",
"echo\"PATH=\\\"/usr/bin:/usr/sbin:/bin:/sbin:/system/sbin:/system/bin:/system/xbin:/data/local/bin\\\"\"> /etc/profile",
"echo\"export PATH\" >> /etc/profile",
"mkdir/usr", "mkdir /usr/libexec",
"cp-rf /system/bin/sftp-server /usr/libexec/",
"rm-rf /system/priv-app/DLNARemoteService.apk",
"mount-o ro,remount /"
};
ShellUtils.CommandResultresult = ShellUtils.execCommand(commands, true, true);
MyLog.e(TAG,"error is "+ result.errorMsg);
MyLog.e(TAG,"success is "+ result.successMsg);
}
方法中需要说明几个的地方是:
- dss_host_key是PC端ssh客户端生成的公钥文件,因为ssh是非对称加密,需要将公钥放到远端,私钥放到本地
- 需要从system cp到目标目录的几个文件都是需要提前放到升级包(update.zip)中,然后OTA升级。
- sftp-server文件在此处是为了PC客户端能够通过ftp打开A端的文件系统
至此差不多将ssh服务移植到了Android系统了。
-
接下来执行dropbear指令:
dropbear-d /data/dropbear/dss_host_key -E -s –v
在此过程中,B的端口对于A来说是未知的,需要A进程起来的时候主动向B请求ssh连接的地址和端口号,同时将本地开辟的端口通知B端。获取到B端的地址和端口之后就可以连接了:
String remoteServerPort = "ssh -f -N -R" + portForA + ":localhost:localPort " + "-i /system/yourname.key"+ " remoteName@" + remoteIp;
-
然后执行指令,在执行这个指令的过程中需要你输入一系列的确认信息,此处需要借助Java的IO接口做动态数据的写入和读取,如下:
private voidexecuteReverseConnectServerCommand(String command){
Log.e(TAG , "executeReverseConnectServerCommand:" + command);
//ShellUtils.CommandResult reverseResult = ShellUtils.execCommand(commands, true , true);
//Log.e(TAG , "fail result = " + reverseResult.errorMsg);
//Log.e(TAG , "success result = " + reverseResult.successMsg);
DataOutputStream os = null;
Process process = null;
BufferedReader successResult = null;
BufferedReader errorResult = null;
try {
process = Runtime.getRuntime().exec("su");
os = new DataOutputStream(process.getOutputStream());
successResult = new BufferedReader(newInputStreamReader(process.getInputStream()));
errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
os.write((command + "\n").getBytes());
os.flush();
String s = null;
StringBuilder successMsg = new StringBuilder();
StringBuilder errorMsg = new StringBuilder();
while ((s = errorResult.readLine())!= null) {
MyLog.e(TAG , "error msg =" + s);
if(!StringUtil.isEmpty(s)&& s.contains("password")){
MyLog.e(TAG ,"password msg = " + s);
os.write("yourpassward\n".getBytes());
os.flush();
}else if(!StringUtil.isEmpty(s)&& s.contains("fingerprint")){
MyLog.e(TAG ,"fingerprint msg = " + s);
os.write("y\n".getBytes());
os.flush();
}else if(!StringUtil.isEmpty(s)&& s.contains("connecting")){
MyLog.e(TAG ,"connecting msg = " + s);
os.write("y\n".getBytes());
os.flush();
}
errorMsg.append(s);
s = null;
}
MyLog.e(TAG , "start exit");
os.write("exit\n".getBytes());
os.flush();
int result = process.waitFor();
MyLog.e(TAG , "result = " + result);
MyLog.e(TAG , "success msg = " + successMsg.toString());
MyLog.e(TAG , "error msg1 = " + errorMsg.toString());
} catch (IOException e) {
e.printStackTrace();
}catch(Exception e){
e.printStackTrace();
} finally {
try {
if (os != null) {
os.close();
}
if (successResult != null) {
successResult.close();
}
if (errorResult != null) {
errorResult.close();
}
} catch (IOException e) {
e.printStackTrace();
}
if (process != null) {
process.destroy();
}
}
}
}
总共确认了三个信息,分别是密码,是否连接这个地址,是否连接。
在确认完成之后接连接成功了,此时点击PC端的ftp按钮应该是可以弹出远端A的文件系统了,ssh在PC端连接成功之后就相当于是一个终端了,可以执行各种指令操作了。
相关连接思路的实现参考文章开头的第三篇文章。
需要注意的是整个过程的实现需要该app有root权限或是系统签名加android:sharedUserId="android.uid.system"
,不然权限不够执行ssh和dropbear相关指令的时候会失败。
实现整个方案的过程中比较艰难的是:
- ssh服务和客户端的移植,研究在A端支持ftp服务
- 移植成功之后,将ssh服务跑起来并将来来回回的流程跑通
- 网上没有在android上做ssh反向连接的先例,但是还是一往无前的做了,并做出来了。
微信公众号:Android部落格