Android SSH反向连接实践

2,056 阅读4分钟

微信公众号:Android部落格

个人网站:chengang.plus/

方案实现参考以下几篇文章:

blog.163.com/leekwen@126…

www.cnblogs.com/whltingyu/p…

segmentfault.com/a/119000000…

1、背景

在Android系统中移植ssh服务是基于实时维护移动端设备而提出来的需求,在客户的设备出现问题而研发人员无法处于现场时提出的解决方案。 在实现这个方案之后可以通过终端执行adb指令,可以进入终端的文件系统删除或修改一些文件,以保证系统能够流畅运行。

2、思路

该方案实现思路如下:

解释一下,C端是PC端的一些ssh客户端,比如bitvise,SecureCRT等;B端是指后台服务器;A是指客户手上的机器,现在要实现的是C通过B连接A,处理A上面出现的问题。

C端很好解决,下载一个SSH客户端就可以解决问题。B端是后台开发人员需要解决的问题,基本上安装一个ssh服务就可以解决了。A端是要重点解决的地方,涉及到了ssh服务的移植,包含移动端和服务端,另外还要移植ftp,开启服务之后保证服务的稳定性。

3、移植ssh

3.1 编译ssh

第一步,移植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);
}

方法中需要说明几个的地方是:

  1. dss_host_key是PC端ssh客户端生成的公钥文件,因为ssh是非对称加密,需要将公钥放到远端,私钥放到本地
  2. 需要从system cp到目标目录的几个文件都是需要提前放到升级包(update.zip)中,然后OTA升级。
  3. sftp-server文件在此处是为了PC客户端能够通过ftp打开A端的文件系统 至此差不多将ssh服务移植到了Android系统了。

3.1 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;

3.2 执行dropbear指令

然后执行指令,在执行这个指令的过程中需要你输入一系列的确认信息,此处需要借助Java的IO接口做动态数据的写入和读取,如下:

private void executeReverseConnectServerCommand(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();
			}
		}
	}
}

总共确认了三个信息,分别是密码,是否连接这个地址,是否连接。

4、开始连接

在确认完成之后接连接成功了,此时点击PC端的ftp按钮应该是可以弹出远端A的文件系统了,ssh在PC端连接成功之后就相当于是一个终端了,可以执行各种指令操作了。 相关连接思路的实现参考文章开头的第三篇文章。 需要注意的是整个过程的实现需要该app有root权限或是系统签名加android:sharedUserId="android.uid.system",不然权限不够执行ssh和dropbear相关指令的时候会失败。

5、总结

实现整个方案的过程中比较艰难的是:

  1. ssh服务和客户端的移植,研究在A端支持ftp服务
  2. 移植成功之后,将ssh服务跑起来并将来来回回的流程跑通

微信公众号:Android部落格