Monday, 19 November 2018

Gimme sum fud RITSEC CTF 2018

In this challenge, we are given a 64-bit ELF binary which is compiled from Go code.

file command output:

$ file pwn3
pwn3: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 3.2.0, BuildID[sha1]=7bf36f31cd9a2014bad5fb119d30119e3ea0cad9, not stripped

We can confirm that the binary was compiled using Go language with the following command:

$ readelf -e pwn3 | grep -i .gopclntab
  [19] .gopclntab        PROGBITS         00000000004d9740  000d9740
   04     .rodata .typelink .itablink .gopclntab .eh_frame_hdr .eh_frame

The binary has a section called ".gopclntab" which is corresponding to Go compiled binaries.

Let's execute the binary:


Gimme some bytes, I'm hangry...
mmmmm...., your 1234
 is so good. Thanks. Bye.

So, the binary expects some input and the input is printed back to stdout along with some other text.

We can get more details using ltrace as well:

malloc(16)                                                                                    = 0x5631a0
malloc(100)                                                                                   = 0x563420
malloc(10)                                                                                    = 0x563050
Gimme some bytes, I'm hangry...
, "1234\n", 4919)                                                                       = 5
mmmmm...., your printf("%s", "1234\n"1234
)                                                                        = 5
 is so good. Thanks. Bye.+++ exited (status 0) +++

Also in the above output, we can see 3 buffers. These will be useful later.

In the main subroutine, we can see that the binary reads the flag from flag.txt file.

Inside, main_main_func1 subroutine, it allocates new memory and copies the flag to this memory location using memmove() as shown below:

It then reads the input from the user and prints it back as shown below:

We can create a new file called flag.txt in the present directory so that it's contents are read while debugging.

Now, to understand how to read the flag in this case, we need to understand the memory layout. Below is the memory layout which shows the location of our input in memory as well as the flag location.

Location of input in memory:

Location of flag in memory:

So, in our case, the input is located at address: 0x563050 and the flag is located at address: 0x563490

The difference between the two memory locations is: 0x440 bytes

It is important to note that the location of flag in memory will change because this memory is allocated using malloc()

The memory layout looks like this: [input][data][flag]

The data in between input and flag contains null bytes as shown above.

In the, main__Cfunc_myGets() function, we can see that the binary reads a maximum of 0x1337 bytes as shown below:

Binary prints the input back to stdout as shown below:

The output is printed using printf() function with a format string, "%s"

This means if the data in between the input and the flag location in memory contains any null bytes, then the flag will not be printed.

So, we have to make sure that all the data between the location of input and the flag does not contain null bytes.

Since we don't know the exact location of flag however we know the format of the flag, we can bruteforce the number of bytes to be sent so that all the space between the flag and the input is filled with data that does not contain null bytes.

I wrote the following code to bruteforce it:

When the string, "RITSEC" is found in the output, it will print the flag.

Flag: RITSEC{Muff1n_G0verFl0w_mmmm}


No comments:

Post a Comment