Syscalls (pwn)
You can't escape this fortress of security.
The pseudocode Ghidra gives us is as follows (with functions renamed):
void main(void)
{
long in_FS_OFFSET;
undefined local_c8 [184];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
setvbuf(stdout,(char *)0x0,2,0);
setvbuf(stderr,(char *)0x0,2,0);
setvbuf(stdin,(char *)0x0,2,0);
intro(local_c8);
set_seccomp();
execshellcode(local_c8);
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
void intro(char *param_1)
{
puts(
"The flag is in a file named flag.txt located in the same directory as this binary. That\'s al l the information I can give you."
);
fgets(param_1,176,stdin);
return;
}
int set_seccomp(void)
{
int iVar1;
long in_FS_OFFSET;
undefined2 local_e8 [4];
undefined8 *local_e0;
undefined8 local_d8;
undefined8 local_d0;
undefined8 local_c8;
undefined8 local_c0;
undefined8 local_b8;
undefined8 local_b0;
undefined8 local_a8;
undefined8 local_a0;
undefined8 local_98;
undefined8 local_90;
undefined8 local_88;
undefined8 local_80;
undefined8 local_78;
undefined8 local_70;
undefined8 local_68;
undefined8 local_60;
undefined8 local_58;
undefined8 local_50;
undefined8 local_48;
undefined8 local_40;
undefined8 local_38;
undefined8 local_30;
undefined8 local_28;
undefined8 local_20;
undefined8 local_18;
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
local_d8 = 0x400000020;
local_d0 = 0xc000003e16000015;
local_c8 = 0x20;
local_c0 = 0x4000000001000035;
local_b8 = 0xffffffff13000015;
local_b0 = 0x120015;
local_a8 = 0x100110015;
local_a0 = 0x200100015;
local_98 = 0x11000f0015;
local_90 = 0x13000e0015;
local_88 = 0x28000d0015;
local_80 = 0x39000c0015;
local_78 = 0x3b000b0015;
local_70 = 0x113000a0015;
local_68 = 0x12700090015;
local_60 = 0x12800080015;
local_58 = 0x14200070015;
local_50 = 0x1405000015;
local_48 = 0x1400000020;
local_40 = 0x30025;
local_38 = 0x3000015;
local_30 = 0x1000000020;
local_28 = 0x3e801000025;
local_20 = 0x7fff000000000006;
local_18 = 6;
local_e0 = &local_d8;
local_e8[0] = 0x19;
/* PR_SET_NO_NEW_PRIVS */
prctl(38,1,0,0,0);
iVar1 = prctl(22,2,local_e8);
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return iVar1;
}
void execshellcode(code *param_1)
{
(*param_1)();
return;
}Firstly, main() calls the intro() function, which obtains input from stdin and puts it into local_c8.
Then, in set_seccomp(), we see a few prctl calls. On investigating the documentation, what prctl does depends on its first argument. The first prctl call has 38 as its first argument which represents PR_SET_NO_NEW_PRIVS (with reference to prctl.h). This means that execve promises not to grant privileges to do anything that could not have been done without the execve call (with reference to the manpage for pr_set_no_new_privs). The second prctl call has a first argument of 22, which is PR_SET_SECCOMP. Referring to seccomp.h, the second argument is 2 which refers to SECCOMP_MODE_FILTER. This means that the third argument, which is local_e8, is a pointer to the filter to be applied.
To check the seccomp rules which have been applied, we can use the seccomp-tools. On running seccomp-tools dump ./syscalls, we can see the following seccomp rules:
The rules prevent us from using most file i/o related syscalls, but allows us to use writev as long as either:
The first 32 bits of the file descriptor are greater than 0, or
The first 32 bits of the file descriptor = 0, and the file descriptor >
0x3e8.
After the call to set_seccomp, the execshellcode() function is invoked. It treats our input, which was stored in local_c8, as code and runs it.
This challenge requires us to provide shellcode to read flag.txt, given the restrictive seccomp rules. Usually, in order to print a file's contents to stdout, we would call open with the filename to obtain the file handle, call read with its handle to read the contents into a buffer, then call write to output the buffer content to the stdout file descriptor (which is 1). After a bit of research on the remaining syscall options, I was able to find alternatives to these syscalls. Instead of using open, we can use openat. Instead of using read, pread64, readvor preadv, we can use mmap to map the file handle's contents into memory. To output to stdout, we can use writev, but we cannot use 2 as the file descriptor. Hence we have to first use dup2 to duplicate the stdout file descriptor to one that seccomp won't kill, then call writev on our new file descriptor.
Here's the annotated shellcode I used (written in x64 assembly using intel syntax):
With this payload, we can construct the payload using pwntools and send it to the server to retrieve the flag. My solve script is as follows:
Last updated