Termux(一个免root的android上shell环境与包管理器)

官网:https://termux.com/

本文写于2020.02.25,起因是因为最近学习二进制,想把一些题目编译到android看一看,并且用gdb和一些插件调一调。编译android的二进制正常是用NDK,调试采用gdbserver或者idaserver这种。但是感觉不够优雅,还是想直接adb shell进去就能gcc以及gdb,于是搜索了”android 包管理工具 gcc”相关字样,找到一篇文章:令人惊艳的Termux,安卓上的shell环境与包管理器,便发现了这个工具。以下是分别在我的两个手机上的折腾过程。

一加1 Kali Nethunter Android 6.0.1

这个手机是当时买的二手的,就是为了刷kali玩的,当时的笔记:Android刷机

安装应用

研究原理

安装成功之后打开应用可以看到使用帮助,可以用pkg命令安装东西,这玩意就是apt的封装,但是当我满心欢喜的连上adb shell然后输入pkg但是并没有这个可执行程序。应该是环境变量的问题,在手机的app终端里用env命令查看环境变量:

$ env
SHELL=/data/data/com.termux/files/usr/bin/bash
PREFIX=/data/data/com.termux/files/usr
PWD=/data/data/com.termux/files/home
EXTERNAL_STORAGE=/sdcard
LD_PRELOAD=/data/data/com.termux/files/usr/lib/libtermux-exec.so
HOME=/data/data/com.termux/files/home
LANG=en_US.UTF-8
TMPDIR=/data/data/com.termux/files/usr/tmp
ANDROID_DATA=/data
TERM=xterm-256color
SHLVL=1
ANDROID_ROOT=/system
LD_LIBRARY_PATH=/data/data/com.termux/files/usr/lib
PATH=/data/data/com.termux/files/usr/bin:/data/data/com.termux/files/usr/bin/applets
_=/data/data/com.termux/files/usr/bin/env

可以看到这个软件的工作原理是使用了这个app自己的私有文件目录下的可执行程序和动态库,不过我们可以看到这里有保留了ANDROID_ROOT的环境变量,很可能说明这个app并没有独立于整个android系统,当我们进入到这个应用的存放动态链接库的文件夹:/data/data/com.termux/files/usr/lib,我们查看一下这里的文件:

root@bacon:/data/data/com.termux/files/usr/lib # ls
apt                   libcrypto.so      libiconv.so        libreadline.so.8.0 
bash                  libcrypto.so.1.1  liblzma.so         libssl.so          
engines-1.1           libcurl.so        libncurses.so      libssl.so.1.1      
libandroid-support.so libcurses.so      libncurses.so.6    libtermux-exec.so  
libapt-pkg.so         libcurses.so.6    libncurses.so.6.1  libz.so            
libapt-private.so     libcurses.so.6.1  libncursesw.so     libz.so.1          
libbz2.so             libgcrypt.so      libncursesw.so.6   libz.so.1.2.11     
libbz2.so.1.0         libgpg-error.so   libncursesw.so.6.1 terminfo           
libbz2.so.1.0.6       libhistory.so     libnghttp2.so      
libc++_shared.so      libhistory.so.8   libreadline.so     
libcharset.so         libhistory.so.8.0 libreadline.so.8  

可以看到这里并没有libc.so,大部分程序的运行怎么可能离开libc呢?这里我猜应该是LD_PRELOAD这个环境变量实现的这个技巧:动态连接的诀窍:使用 LD_PRELOAD 去欺骗、注入特性和研究程序,应该是用了libtermux-exec.so去加载了android自带的libc.so,程序进而得以顺利运行。

所以我们可以完全替换adbshell进来之后的环境变量,来达到使用termux在adbshell中这个目标。环境变量和普通的变量可以看做shell的全局变量和局部变量,利用export命令声明环境变量,环境变量在当前shell启动的子进程中仍然有效,做如下实验:


# 普通变量
root@bacon:/ # val=xuanxuan
root@bacon:/ # echo $val
xuanxuan
root@bacon:/ # bash
bacon / # echo $val

bacon / # exit
exit
root@bacon:/ # echo $val
xuanxuan

# 环境变量
root@bacon:/ # export val=xuan
root@bacon:/ # echo $val
xuan
root@bacon:/ # bash
bacon / # echo $val
xuan
bacon / # exit
exit
root@bacon:/ # echo $val
xuan

可见普通变量是无法跨进程的,而环境变量是在子进程仍然存在的。所以我们新建一个shell脚本,内容如下:

export SHELL="/data/data/com.termux/files/usr/bin/bash"
export PREFIX="/data/data/com.termux/files/usr"
export PWD="/data/data/com.termux/files/home"
export EXTERNAL_STORAGE="/sdcard"
export LD_PRELOAD="/data/data/com.termux/files/usr/lib/libtermux-exec.so"
export HOME="/data/data/com.termux/files/home"
export LANG="en_US.UTF-8"
export TMPDIR="/data/data/com.termux/files/usr/tmp"
export ANDROID_DATA="/data"
export TERM="xterm-256color"
export SHLVL="1"
export ANDROID_ROOT="/system"
export LD_LIBRARY_PATH="/data/data/com.termux/files/usr/lib"
export PATH="/data/data/com.termux/files/usr/bin:/data/data/com.termux/files/usr/bin/applets"
export _="/data/data/com.termux/files/usr/bin/env"
bash

脚本导出导出termux自带的所有环境变量,最后最后启动一个bash,因为shell脚本实际上是父shell启动一个子进程执行,如果执行完就退出了那么导出的环境变量也就一起消失了。然后把这个脚本扔到手机里的去执行,我这里为了方便直接扔到一个存在于环境变量中并且可写的目录下了:/su/bin,一般手机的这种目录都是不可写的,一般放在/data/local/tmp中是肯定可以的。然后我重新进入adb shell执行termux这个脚本,已经可以正常使用相关功能啦:

➜   adb push ./termux.sh /su/bin/termux
./termux.sh: 1 file pushed. 0.1 MB/s (697 bytes in 0.006s)
➜   adb shell
croot@bacon:/ # cd /su/bin
root@bacon:/su/bin # chmod +x termux                                           
root@bacon:/su/bin # termux                                                    
# exit
exit
root@bacon:/su/bin # exit
➜   adb shell
root@bacon:/ # termux
# pkg
Usage: pkg command [arguments]

A tool for managing packages. Commands:

  files <packages>
  install <packages>
  list-all
  list-installed
  reinstall <packages>
  search <query>
  show <packages>
  uninstall <packages>
  upgrade
# 

网上大部分的办法是在手机app里开sshd,然后电脑连上,显然不够优雅。

不要换源

apt软件源文件的路径是这个:/data/data/com.termux/files/usr/etc/apt/sources.list,内容如下:

# cat sources.list
# The main termux repository:
deb https://termux.net stable main

不过我这android6.0上的termux0.73千万不要换源!!!清华的也不行,换完然后upgrade一波就都不好使了,因为清华的那个源是支持新版的termux的,android7.0以上的。

安装软件

因为这个软件是运行在普通用户上的,我这个kali adb进来是rootshell,这个软件的apt是禁止在root下安装程序的,所以还是在手机上安装软件,在电脑的rootshell中使用,在手机上执行:

$ apt update && apt -y upgrade
$ apt install -y python python2 clang gdb vim file

中间会提示一些是否覆盖老版本,一路确定就行了。然后就可以在adb shell里开心的使用gcc和gdb啦:

# gcc test.c -o test
# file ./test
./test: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /system/bin/linker, not stripped
# ./test
xuanxuan
# gdb -q test
Reading symbols from test...
(No debugging symbols found in test)
(gdb) b main
Breakpoint 1 at 0x40c
(gdb) r
Starting program: /data/local/tmp/gcctest/test 

Breakpoint 1, 0x7f55540c in main ()
(gdb) disassemble main
Dump of assembler code for function main:
   0x7f555400 <+0>:	push	{r11, lr}
   0x7f555404 <+4>:	mov	r11, sp
   0x7f555408 <+8>:	sub	sp, sp, #8
=> 0x7f55540c <+12>:	ldr	r0, [pc, #28]	; 0x7f555430 <main+48>
   0x7f555410 <+16>:	add	r0, pc, r0
   0x7f555414 <+20>:	bl	0x7f555354 <printf@plt>
   0x7f555418 <+24>:	mov	r1, #0
   0x7f55541c <+28>:	str	r0, [sp, #4]
   0x7f555420 <+32>:	mov	r0, r1
   0x7f555424 <+36>:	mov	sp, r11
   0x7f555428 <+40>:	pop	{r11, lr}
   0x7f55542c <+44>:	bx	lr
   0x7f555430 <+48>:	andeq	r0, r0, r12, lsl r0
End of assembler dump.
(gdb) 

但是静态编译不成功,报错是找不到libc.so和libdl.so:

# gcc -static test.c -o tests
/data/data/com.termux/files/usr/bin/arm-linux-androideabi-ld: cannot find -ldl
/data/data/com.termux/files/usr/bin/arm-linux-androideabi-ld: cannot find -lc
clang-9: error: linker command failed with exit code 1 (use -v to see invocation)

估计原因就是因为那个LD_PRELOAD

gdb不支持python

在Android6.0上的版本0.73的termux,从官方源中安装的gdb的版本是8.3.1,编译时没有添加python支持,利用ldd工具可以看到并没有依赖相应的python的库:

# ldd `which gdb`
libreadline.so.8
libz.so.1
libncursesw.so.6
libm.so
libexpat.so
liblzma.so
libmpfr.so
libgmp.so
libdl.so
libc++_shared.so
libc.so

就导致我们没有办法给gdb扩展gef,peda,pwndbg等插件,相关问题已经又人在问了,而且也是个想在手机上安gef的人:

回答是termux从gdb8.3.1版本就会开始支持,我们可以在Android7.0以上的官方源中发现gdb版本是9.1,是否已经支持了呢?于是我用我的小米重新尝试了一下。

小米6 MIUI 10.3稳定版 Android 8.0.0

因为这个手机是稳定版的,也没有root,所以我在adb shell进去之后也无法访问应用的私有文件,所以除了adb install剩下的都是在手机上完成的。这里安装包用的是:https://f-droid.org/en/packages/com.termux/,还有在其他地方找的好多安装包安完都闪退。

尝试gef成功

安装完后按照刚才的步骤一顿折腾,看一下gdb的动态库依赖:

$ ldd `which gdb`
libreadline.so.8
libz.so.1
libncursesw.so.6
libm.so
libpython3.8.so.1.0
libdl.so
libexpat.so
liblzma.so.5
libiconv.so
libmpfr.so
libc++_shared.so
libc.so

可以看到支持python啦,然后我在手机操作编译了个可执行程序,静态编译仍然失败:

$ file test
test: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /system/bin/linker64, not stripped

$ gcc -static test.c -o tests
/data/data/com.termux/files/usr/bin/aarch64-linux-android-ld: cannot find -lc
clang-9: error: linker command failed with exit code 1 (use -v to see invocation)

然后安好gef,调试后如下:

image

发现好像gef分析堆的功能在android上并不管用,然后看了看vmmap也一头雾水,最后问了师兄才知道,android上的libc并不是glibc,而是bionic libc。啊,恍然大悟,android是采用的linux kernel,但是libc是用户态的动态链接库啊,所以linux和glibc不一定是绑定的呦。

参考