I am trying to solve The Protostar Net One task.
When I open the net1 binary with strace -f ./net1
I get this result. In which the program clones itself, kills the parent, and dup
s all standard file descriptors to /dev/null
.
execve("./net1", ["./net1"], [/* 15 vars */]) = 0
brk(0) = 0x804b000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fe0000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=13796, ...}) = 0
mmap2(NULL, 13796, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7fdc000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("/lib/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\320m\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1319176, ...}) = 0
mmap2(NULL, 1329480, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7e97000
mprotect(0xb7fd5000, 4096, PROT_NONE) = 0
mmap2(0xb7fd6000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x13e) = 0xb7fd6000
mmap2(0xb7fd9000, 10568, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7fd9000
close(3) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7e96000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb7e966c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb7fd6000, 8192, PROT_READ) = 0
mprotect(0xb7ffe000, 4096, PROT_READ) = 0
munmap(0xb7fdc000, 13796) = 0
rt_sigaction(SIGCHLD, {0x8048e24, [CHLD], SA_RESTART}, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGPIPE, {SIG_IGN, [PIPE], SA_RESTART}, {SIG_DFL, [], 0}, 8) = 0
open("/opt/protostar/run/net1.pid", O_RDWR|O_CREAT|O_TRUNC, 0700) = 3
setgroups32(1, [998]) = 0
setresgid32(998, 998, 998) = 0
setresuid32(998, 998, 998) = 0
clone(Process 2161 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0xb7e96728) = 2161
[pid 2160] exit_group(0) = ?
setsid() = 2161
chdir("/") = 0
open("/dev/null", O_RDWR) = 4
fstat64(4, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 3), ...}) = 0
dup2(4, 0) = 0
dup2(4, 1) = 1
dup2(4, 2) = 2
close(4) = 0
write(3, "2161\n", 5) = 5
close(3) = 0
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
bind(3, {sa_family=AF_INET, sin_port=htons(2998), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 10) = 0
accept(3,
The main part of interest to me are the lines which contains the function dup2. As seen, this function makes the standard input, output and error a copy of /dev/null. Which means, the program is supposed to throw everything comes in and out to the black hole (I am very suspicious I am wrong somewhere in this idea).
But when the program prints out an integer with the write
syscall as below
write(0, &wanted, sizeof(wanted))
I can see the printed out text in my terminal. Which means the program did not throw what comes in to its standard input (file descriptor 0) to the black hole but printed it out.
You have a clone
call without CLONE_FILES
set in the flags. This creates a child process which has its own file descriptors independent of the parent. So if the child is using dup2 to change its stdin/stdout/stderr to /dev/null, that has no effect on the parent. Alternately, if it is the parent calling dup2, it will have no effect on the child.
Normally when you run strace -f on a program that calls clone (or fork), each syscall will be prefixed with a pid, so you can know which process (parent or child) is calling which system call. There is some of that (the "Process 2061 attached" and "[pid 2160]" strings), but for some reason, the trace you've posted is mostly free of them.
from your step 2 screenshot, the parent process [1760] accepts a new connection on fd 3 as fd 4. It then calls clone to make a child [1763], which calls dup2 to make that new connection be stdin/stdout/stderr in the child. Meanwhile, the parent closes fd 4 and then calls accept again (which blocks; there's not a new incoming connection yet)