I had the possiblity to play a few hours on TUM CTF Teaser. It was
nicely organized and the challenges were fun to solve - even for the easy ones.
Here is the first write-up I am going to publish for that CTF.
From the organizers:
We are facing an ELF 64-bit binary, stripped and with only NX enabled:
By looking at the relocation table, we can see that the binary must have
somewhere a call to execl and another to fscanf:
When interacting with the service, no welcome message or instruction is
provided, it only seems to echo our input. The call to fscanf is made at 0x40074f:
In GDB with peda, we can see the arguments given to fscanf:
The first argument is the address of stdin in the glibc, the second argument
is the format used to interpret our input and the third argument is the address
of the destination buffer where our input is stored.
Between 0x400794 and 0x4007ae we can see that the code loops over the buffer, count the occurence of each character of our input and save that information on the stack:
This can be interpreted as the following C code:
As a byte can contain a value between 0x00 and 0xff, we can manipulate the
stack from its top to 256 bytes further. If the saved rip value is contained
within that range, we can hijack the flow of the program.
Here is an example where I’ve fed the binary with the string AAAABBBBCCCCDDDD. To do that I usually create a fifo:
In gdb, I use it to feed the binary through its standard input:
And in another shell, I can use any commands to feed the fifo:
Here is the stack before executing the vulnerable function:
And here it is after the execution of the vulnerable function:
You can see that the bytes between addresses 0x7fffffffdc41 and
0x7fffffffdc44 have been incremented by 4. These addresses correspond to the
address of rsp, 0x7fffffffdc00, plus the value in bytes of A, B, C
and D: 0x41, 0x42, 0x43 and 0x44 respectively. You may find more
information about ASCII characters value in hexadecimal here.
Here is the information about the current stack frame, when the vulnerable code is executed:
The distance from the top of the stack (rsp) and the location of the saved rip is 152 bytes:
If we send to the binary bytes having values between 152 (0x98) and 160 (0xa0),
we can change the value of the saved rip and control the flow of the program
when the current function return.
As mentionned earlier, there is at least a call to execl somewhere is the code:
This shows to complete call to execl:
the first argument is a string located at 0x4008d4
the second argument is a string located at 0x4008d9
and the third argument is 0 (xor edx,edx), which is used to indicate
the end of the arguments list. Here are the strings:
This gives us the following call: execl("/bin/sh", "sh");
Let’s replace the saved rip with 0x4006e0 so that we can spawn a shell when
the function returns. Saved rip minus the address of the call to execl will
give us the number of characters we have to use:
0x4006e0 - 0x4005d9 = 0x0107 or 0xe0 - 0xd9 = 0x07 and 0x06 - 0x05 = 0x01
This means that the lowest byte of saved rip must be incremented 7 times and
the next one, once: