nc 49.234.137.149 12421
This is a pwn challenge. We are given a binary and a shared library; the binary does not work out of the box:
./secure: error while loading shared libraries: libsgx_urts.so: cannot open shared object file: No such file or directory
The server asks for a blob and closes the connection:
_____ ____ _____ _____ ____ ___ ____ ___
|_ _/ ___|_ _| ___| |___ \ / _ \___ \ / _ \
| || | | | | |_ __) | | | |__) | | | |
| || |___ | | | _| / __/| |_| / __/| |_| |
|_| \____| |_| |_| |_____|\___/_____|\___/
____
/ ___| ___ ___ _ _ _ __ ___
\___ \ / _ \/ __| | | | '__/ _ \
___) | __/ (__| |_| | | | __/
|____/ \___|\___|\__,_|_| \___|
_____ _ _
| ____|_ ____ _(_)_ __ ___ _ __ _ __ ___ ___ _ __ | |_
| _| | '_ \ \ / / | '__/ _ \| '_ \| '_ ` _ \ / _ \ '_ \| __|
| |___| | | \ V /| | | | (_) | | | | | | | | | __/ | | | |_
|_____|_| |_|\_/ |_|_| \___/|_| |_|_| |_| |_|\___|_| |_|\__|
Please input size: 1
Please input secret: A
Not much else to see here, let's dive right in.
- Exploit
- Intel SGX
- Install the driver and SDK in order to run the binary.
- Build gdb-sgx in order to debug the enclave.
- Step through library layers in order to reach the challenge logic.
- The blob is a shellcode that runs inside the enclave.
- The SGX memory protection is asymmetric, so overwrite
main()
return address with one-gadget. - The shell appears:
$ ./getflag
flag{Th3_SGX_memory_protection_1s_asymmetric}
The missing libsgx_urts.so
is not part of any Ubuntu or Fedora package,
it can be found only in intel/linux-sgx
repo. So what is Intel SGX? It's a processor
feature, that allows running code in an isolated enclave, so that the system
(this includes kernel and hypervisors) has very limited capabilities of
observing and interfering with its execution.
The workflow is as follows. First, the launcher process creates the enclave and
loads the code into it using sgx_create_enclave()
.
The enclave defines so-called ecalls, which allow triggering code execution
within it using sgx_ecall()
.
It's convenient to think of ecalls as syscalls, because they also define a
security boundary. Finally, sgx_destroy_enclave()
can be used to destroy the enclave.
The enclave is mapped into its launcher's address space. SGX's tamper protection works in a single direction: the launcher cannot mess with the enclave, but the enclave can mess with the launcher. In particular, it can dereference pointers passed to its ecalls.
In order to run the program, I installed all the prebuilt Fedora RPMs from the
SDK. There is also
a driver which one has to build and insmod
. Unfortunately SDK does not include the
gdb wrapper,
which I also had to build manually:
linux-sgx$ cd sdk/debugger_interface/linux
linux$ make
linux$ cd ../../../build/linux/gdb-sgx-plugin
gdb-sgx-plugin$ sed -i -e "s!@SDK_LIB_PATH@!$PWD/..!g" sgx-gdb
gdb-sgx-plugin$ sudo ./sgx-gdb
The binary secure
is a simple launcher: it creates the enclave, passes the
user's blob to its ecall, and destroys the enclave.
alarm(180);
setbuf(stdout, 0LL);
setbuf(stdin, 0LL);
if (sgx_create_enclave(
/* file_name */ "env.signed.so",
/* debug */ 1,
/* launch_token */ NULL,
/* launch_token_updated */ NULL,
/* enclave_id */ &g_enclave,
/* misc_attr */ NULL)) {
puts("SGX initialization failed!");
return -1;
}
memset(secret, 0xc3, sizeof(secret));
memset(size_buf, 0, sizeof(size_buf));
print_logo();
printf("Please input size: ");
readln(size_buf, sizeof(size_buf));
size = strtol(size_buf, 0, 10);
if (size > 0x1000) {
puts("Size too large!");
return 1;
}
printf("Please input secret: ");
readn(secret, size);
secret_ptr = secret;
sgx_ecall(
/* eid */ g_enclave,
/* index */ 0,
/* ocall_table */ &g_ocall_table,
/* ms */ &secret_ptr)
sgx_destroy_enclave(g_enclave);
return 0;
So the shared library - env.signed.so
- must be the enclave. It links
statically with SGX runtime, for which we have the sources, so pigaios should be great to recover function
names. Unfortunately, it did not work for me this time - it built the database,
but then could not find any matches. Therefore I had to spend some time
correlating asm with sources in order to find the ecall logic.
The call chain is as follows:
enclave_entry()
- this is a public symbol.enter_enclave()
@0x6720
do_ecall()
@0x1ca0
trts_ecall()
@0x18c0
. This one referencesg_ecall_table
@0x208dd0
, which has the addresses of ecall handlers (just one in this case).challenge_ecall()
@0x5a0
- jump to shellcode @
0x594
Note that &secret_ptr
is passed unchanged through all the layers - there is
no complicated marshalling. challenge_ecall
does the following:
if (!mr)
return SGX_ERROR_INVALID_PARAMETER;
if (!sgx_is_outside_enclave(mr, sizeof(void *)))
return SGX_ERROR_INVALID_PARAMETER;
sgx_lfence();
secret_ptr = *mr;
if (!secret_ptr) {
sgx_lfence();
memcpy(secret, NULL, sizeof(secret));
((void *(*)(void))secret)();
return 0;
}
if (!sgx_is_outside_enclave(secret_ptr, 0x1000))
return SGX_ERROR_INVALID_PARAMETER;
sgx_lfence();
p = malloc(0x1000);
if (!p)
return SGX_ERROR_OUT_OF_MEMORY;
if (!memcpy_s(p, 0x1000, secret_ptr, 0x1000)) {
memcpy(secret, p, sizeof(secret));
((void *(*)(void))secret)();
free(p);
return 0;
}
free(p);
return 1;
So we just jump to the shellcode. In the debugger we can see that on entry to
shellcode &secret_ptr
is stored in %r13
.
With all that knowledge writing shellcode is easy: figure out
the distance between &secret_ptr
and main()
return address, read the
latter, compute the libc base, compute one-gadget address, overwrite main()
return address with it, store a bunch of zeroes below it in order to satisfy
one-gadget constraints, done.
This is a nice SGX intro challenge - even though the exploitation part is trivial, the main difficulties are to understand the technology, configure the development setup and reverse engineer the enclave.