Android Sshd使用

1. 介绍

因为某些原因, 笔者需要在android上开发,  使用adb比较麻烦, 于是想使用sshd.

推荐的软件是openssh, 其他选择有dropbear, mosh.
当然还有其他选择, 如termux, 这里不予讨论

2. 编译

在Android中已经有openssh包, 位于external/openssh;默认openssh没有编译进Android系统, 需要进行配置

2.1 openssh模块

首先我们要了解Android中程序都是以模块(PACKAGES)的形式唯一存在的
我们按照既定的规则在模块中添加Android.mk的文件, 通过LOCAL_MODULE来定义
对于openssh, 它包含了如下模块

scp, sftp, ssh, sshd, sshd_config, ssh-keygen, start-ssh

2.2 Android编译系统

其次就是需要我们将openssh模块添加到Android的编译系统中去
而所有需要编译进Android中的模块则通过PRODUCT_PACKAGES变量来定义

Android在编译时候通常通过lunch在制定target
以bpi为例, 使用的命令是lunch mars_a31s-eng
而mars_a31s-eng则在device/softwinner/mars-a31s/vendorsetup.sh中定义

add_lunch_combo mars_a31s-eng

而该target又制定了总Makefile, 位于device/softwinner/mars-a31s/AndroidProducts.mk

PRODUCT_MAKEFILES := \
    $(LOCAL_DIR)/mars_a31s.mk

mars_a31s.mk又包含了其他林林总总的Makefile, 其中就包含了device/softwinner/fiber-common/fiber-common.mk
我们就把openssh模块添加到fiber-common.mk文件中

在fiber-common.mk中新增如下内容

# Openssh
PRODUCT_PACKAGES += \
    scp \
    sftp \
    ssh \
    sshd \
    sshd_config \
    ssh-keygen \
    start-ssh

然后重新编译Android系统

2.3 openssh文件

编译完成后烧录或者刷机后, 可以看到文件系统中openssh的文件分别在如下位置(CM中有所不同)

/system/bin/ssh
/system/bin/ssh-keygen
/system/bin/sshd
/system/bin/start-ssh
/system/bin/scp
/system/bin/sftp
/system/etc/ssh/sshd_config

3. 配置

在Linux中使用ssh我们一般都是采用username/password的方式
但是在Android中是没有这一概念的, 当然可以通过修改源码或者添加伪用户的方式
笔者这里采用的是ssh的另一个使用方法即使用密钥登录登录

注意: 以下命令均在root下执行

3.1 创建目录结构

mkdir -p /data/ssh/empty
chmod 700 /data/ssh
chmod 700 /data/ssh/empty

其中, /data/ssh用来存放密钥文件和sshd配置文件

3.2 生成配置文件

cat /system/etc/ssh/sshd_config | \
sed 's/#PermitRootLogin yes$/PermitRootLogin without-password/' | \
sed 's/#RSAAuthentication yes/RSAAuthentication yes/' | \
sed 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' | \
sed 's/PasswordAuthentication no/#PasswordAuthentication no/' | \
sed 's/#PermitEmptyPasswords no/PermitEmptyPasswords yes/' | \
sed 's/#ChallengeResponseAuthentication yes/ChallengeResponseAuthentication yes/' | \
sed 's/#UsePrivilegeSeparation yes/UsePrivilegeSeparation no/' | \
sed 's;/usr/libexec/sftp-server;internal-sftp;' > \
/data/ssh/sshd_config
chmod 600 /data/ssh/sshd_config

这里需要说明的是我们需要配置为root使用, 同时又不需要密码.

另外, 需要注意配置文件中指定了AuthorizedKeysFile为/data/ssh/authorized_keys

3.3 生成密钥

在Windows/Linux上通过下面的命令来生成密钥

ssh-keygen -t rsa -C "your_email_address"

上面的命令会在主目录下生成.ssh目录, 目录包含id_rsa(私钥)和id_rsa.pub(公钥)两个文件

然后通过adb等命令将id_rsa.pub上传至Android中(!!!文件要对应于AuthorizedKeysFile!!!)

adb push id_rsa.pub /data/ssh/authorized_keys
chmod 600 /data/ssh/authorized_keys
chown root:root /data/ssh/authorized_keys

3.4 生成启动脚本

mkdir -p /data/local/userinit.d
cat /system/bin/start-ssh | \
    sed 's;/system/etc/ssh/sshd_config;/data/ssh/sshd_config;' > \
    /data/local/userinit.d/99sshd
chmod 755 /data/local/userinit.d/99sshd

通过上面的命令单独生成一个启动脚本
然后就可以通过执行下面的脚本来启动sshd

/data/local/userinit.d/99sshd

实际操作过程中如果出现问题也可以通过下面的命令以调试的方式来启动sshd

/system/bin/sshd -f /data/ssh/sshd_config -D -ddd

3.5 连接sshd

使用命令即可连接sshd

ssh root@ip

需要注意的是在Windows下使用ssh客户端时需要配置使用密钥登录选项, 并指定密钥文件
具体选项的位置则依据工具的不同而不同

同理SFTP和SCP登录也如此, 需要制定密钥文件

4. 自启动

现在sshd已经可以成功运行并登录, 接下来需要做的则是让sshd可以自启动

4.1 修改init.rc

这是最直接的办法, 问题在于init.rc是由boot.img动态生成的initramfs而产生
故而即使在已经运行的文件系统中修改了, 重启后还是会恢复原来的内容
那么就只能在源代码中修改后再行编译

找到system/core/rootdir/init.rc, 发现已经包含了sshd的内容, 只是默认被禁用了, 而且启动方式也不是我们期望的

service sshd /system/bin/start-ssh
    class main
    disable

将init.rc修改为

service sshd /system/bin/start-ssh
    class main
    user  root
    group root

NOTE:
测试发现, 修改后没有效果, 因为编译时Android使用设备自己提供的init.rc将其覆盖
实际需要修改的文件是device/softwinner/fiber-common/init.rc
而对于CM, 只要修改system/core/rootdir/init.rc即可

除了修改init.rc外, 为了让sshd能够正常自启动
另外需要修改的文件包括如下external/openssh/start-ssh和external/openssh/sshd_config.android

具体的修改内容可以参考前文描述

TIP: 上面的做法对于没有selinux的Android版本正常工作, 但是一旦有了selinux则发现如下错误

[  155.996453] c0 init: Warning!  Service sshd needs a SELinux domain defined; please fix!
[  156.004202] c0 init: Starting service 'sshd'...
[  156.008591] c0 init: cannot execve('/system/bin/start-ssh'): Permission denied
[  156.014985] c0 type=1400 audit(1480673531.748:53): avc: denied { execute_no_trans } for pid=4073 comm="init" path="/system/bin/star
t-ssh" dev=mmcblk0p9 ino=402 scontext=u:r:init:s0 tcontext=u:object_r:system_file:s0 tclass=file permissive=0

需要添加为start-ssh添加sepolicy
方法为start-ssh声明一个独立的domain, 同时为该domain声明start-ssh拥有的权限
具体的做法可以参考sysinit
sysinit在vendor/cm/sepolicy/file_contexts文件中增加了如下行

/system/bin/sysinit       u:object_r:sysinit_exec:s0

然后创建vendor/cm/sepolicy/sysinit.te文件, 在该文件中声明相关权限

4.2 其他方式(不成功)

在笔者使用的CM系统中, 就出现了由于selinux导致的权限问题
这里介绍另一种方式让sshd正常自启动, 对CM是有效的, 其他Android版本是否有效则需要验证

查看启动log后发现CM在启动的时候会执行/system/bin/sysinit脚本
sysinit则会执行/system/etc/init.d/目录下所有脚本, 其中包含了90userinit
该脚本又执行另一个脚本/data/local/userinit.sh
关键在于userinit.sh位于/data下, 即我们有权限修改的地方, 那么我们就可以在这里做文章

创建/data/local/userinit.sh文件, 内容如下

#!/system/bin/sh
export PATH=/sbin:/system/sbin:/system/bin:/system/xbin
for i in /data/local/userinit.d/*; do
if [ -x $i ]; then
/system/bin/log -t userinit Running $i
$i
fi
done

修改userinit.sh的权限

chmod 755 /data/local/userinit.sh

然后按照3.4章节生成/data/local/userinit.d/99sshd作为sshd的启动脚本

因为userinit.sh需要读取目录和文件执行, 需要修改它的domain, 改为和sysinit一样
修改vendor/cm/sepolicy/file_contexts文件

/data/local/userinit.sh                 u:object_r:userinit_data_exec:s0
    --->  
/data/local/userinit.sh                 u:object_r:userinit_exec:s0

同时需要为/data/local/userinit.d目录下所有文件增加权限

/data/local/userinit.d(/.*)?            u:object_r:userinit_exec:s0

注: 上面的方法不成功, 一直提示如下错误

12-02 22:42:39.370     1     1 W init    : type=1400 audit(0.0:4): avc: denied { relabelto } for name="userinit.sh" dev=mmcblk0p10 ino=81928 scontext=u:r:init:s0 tcontext=u:object_r:userinit_exec:s0 tclass=file permissive=0

似乎sepolicy比较麻烦, 必须单独配置, 这里不再深究, 还是采用init.rc的方法简单明了

配置sepolicy的方法可参考<Android下添加自启动应用Android下添加自启动应用>

5. 其他配置

5.1 shell

通过ssh登录后, 发现当前shell与系统的shell是有一些不同的, 同时如果我们需要配置一些环境变量的话又不知从何入手
笔者当前使用的shell是Android的默认mksh, 查阅后找到shell的配置方法(只针对拥有bash的Android起作用, 如CM)

创建HOME目录

mkdir /data/home
chmod 755 /data/home
chown root:root /data/home

然后在HOME目录下创建一个脚本/data/home/login, 内容如下

#!/system/xbin/bash

HOME='/data/home'
cd
exec bash --login

修改该脚本的权限

chmod 755 /data/home/login

然后每次登陆后执行执行下面这条命令

exec /data/home/login

同时在/data/home/目录下创建.bash_profile, 内容如下

if [ -f /etc/bash/bashrc ]; then
        . /etc/bash/bashrc
fi
unset HOME
HOME=/data/home
LD_LIBRARY_PATH=.:/vendor/lib:/system/lib
PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin
ANDROID_DATA=/data
ANDROID_ROOT=/system
export PS1 HOME HOSTNAME LD_LIBRARY_PATH PATH ANDROID_DATA ANDROID_ROOT

其中, bash默认会导入~/.bash_profile作为环境变量, 这里另外导入了/etc/bash/bashrc
是为了一些通用环境变量, 如PS1, 主要是为了避免登录后出现-bash-3.1#这样的提示符

-------------------------------------------------------------------------------------------
笔者使用的另一款Android系统中, 由于没有bash, 只有Android默认的mksh

了解发现, mksh会导入全局文件/profile作为环境变量
然后对于非root用户还会导入$HOME/.profile个文件作为环境变量
另外, 对于非root用户还可通过-i来导入$HOME/.mkshrc这个文件

显然这些我们都无法使用, 笔者的方案是自行导入一个文件作为环境变量
通过在login脚本的最后加上一句source .shrc
.shrc内容如下
HOME=/data/home
LD_LIBRARY_PATH=/vendor/lib:/system/lib
PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin
ANDROID_DATA=/data
ANDROID_ROOT=/system
export HOME HOSTNAME LD_LIBRARY_PATH PATH ANDROID_DATA ANDROID_ROOT

NOTE:
笔者最后也没有成功, 只能每次在使用前手动执行source /data/home/.shrc

5.2 busybox

笔者使用的Android中, 发现很多命令在执行时需要加上busybox前缀, 查阅后了解到时这些命令到链接到toolbox的缘故

比较常见的修改方式使用busybox --install命令安装到PATH中
而这里的修改方式就是做一个巧妙的映射, 然后将这个映射导入到当前的环境变量中

在上面提到的.shrc加上如下内容

# for busybox
for n in $(busybox --list)
do 
    eval alias $n=\'busybox $n\'
done

参考:
<Sshd howto for CM>
<理解Android Build系统>
<Android启动过程深入解析>
<从CM刷机过程和原理分析Android系统结构>

posted @ 2016-12-02 01:58  北落不吉  阅读(8408)  评论(0编辑  收藏  举报