This program is statically linked and its main function is relatively small:
void dev_null(void)
{
char local_10 [8];
puts("[/dev/null as a service] Send us anything, we won\'t do anything with it.");
enable_seccomp();
gets(local_10);
return;
}
Looking at the security of the binary we have:
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
Although checksec claims there is a canary, for the dev_null function there isn't.
Looking at the seccomp rules:
> seccomp-tools dump ./dev_null
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x1c 0xc000003e if (A != ARCH_X86_64) goto 0030
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x19 0xffffffff if (A != 0xffffffff) goto 0030
0005: 0x15 0x18 0x00 0x00000002 if (A == open) goto 0030
0006: 0x15 0x17 0x00 0x00000003 if (A == close) goto 0030
0007: 0x15 0x16 0x00 0x00000012 if (A == pwrite64) goto 0030
0008: 0x15 0x15 0x00 0x00000014 if (A == writev) goto 0030
0009: 0x15 0x14 0x00 0x00000016 if (A == pipe) goto 0030
0010: 0x15 0x13 0x00 0x00000020 if (A == dup) goto 0030
0011: 0x15 0x12 0x00 0x00000021 if (A == dup2) goto 0030
0012: 0x15 0x11 0x00 0x00000028 if (A == sendfile) goto 0030
0013: 0x15 0x10 0x00 0x00000029 if (A == socket) goto 0030
0014: 0x15 0x0f 0x00 0x0000002c if (A == sendto) goto 0030
0015: 0x15 0x0e 0x00 0x0000002e if (A == sendmsg) goto 0030
0016: 0x15 0x0d 0x00 0x00000031 if (A == bind) goto 0030
0017: 0x15 0x0c 0x00 0x00000038 if (A == clone) goto 0030
0018: 0x15 0x0b 0x00 0x00000039 if (A == fork) goto 0030
0019: 0x15 0x0a 0x00 0x0000003a if (A == vfork) goto 0030
0020: 0x15 0x09 0x00 0x0000003b if (A == execve) goto 0030
0021: 0x15 0x08 0x00 0x00000065 if (A == ptrace) goto 0030
0022: 0x15 0x07 0x00 0x00000113 if (A == splice) goto 0030
0023: 0x15 0x06 0x00 0x00000114 if (A == tee) goto 0030
0024: 0x15 0x05 0x00 0x00000124 if (A == dup3) goto 0030
0025: 0x15 0x04 0x00 0x00000125 if (A == pipe2) goto 0030
0026: 0x15 0x03 0x00 0x00000128 if (A == pwritev) goto 0030
0027: 0x15 0x02 0x00 0x00000137 if (A == process_vm_writev) goto 0030
0028: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0030
0029: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0030: 0x06 0x00 0x00 0x00000000 return KILL
Notice that the openat, read and write syscalls are not blacklisted. Therefore, we can write an ROP chain to do just that. Since there was no pop rdx gadget, I had to settle for a xchg rdx, rax gadget. Also, I wanted to use flag.txt for the second parameter to openat, which is a relative pathname. Therefore I had to pass the special value of AT_FDCWD (which is -100) as the first argument. Using pwntools' constants.linux.amd64.varname saved time in finding the values of constants.