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系統結構>