This was one of the medium challenges I made for inctfj qualifiers. It is based on ret2shellcode.
It is a 32 bit, dynamically linked, non-stripped binary.
For this challenge all protections are disabled and making it easier for us to pwn. However one thing that piques our interest is
NX bit being disabled. As a result, we can inject a shellcode and obtain a shell. No source code was given for this challenge, therefore we will make use of ida/ ghidra to decompile.
Ghidra decompilation and analysis:
In main, we see that it provides us with two choices, to
Buy car or to
Race car. Once
buy_car is called, it stores the value returned into variable
car which is passed as an argument to
In this function, we are again provided with two choices as to which car we need to select! Accordingly a leak is provided. Since all protections are disabled and in order to inject shellcode, we may find the leak useful.
In this function, firstly we are again given a choice as to what race we want to select. However if no car has been selected, it will simply ask you to select a car first and then exits.
- If car choice is '1' and race choice is also '1', our access code is
- If car choice is '2' and race choice is '1', our access code is
- If car choice is '1' and race choice is '2', our access code is
- If car choice is '2' and race choice is also '2', our access code is
Before returnig, it calls the function
begin_race() with the
access_code as an argument.
We have seen how
race_car() calls the function
begin_race() with the
access_code passed as an argument! In the function
begin_race(), you are asked to enter your token key. Thereafter, your token key is xored with param_1 (i.e your access code), and if the result is equal to
0x1337c0de, it simply asks you to enter the navogation commands. We can also see a buffer overflow while reading navigation commands as it reads
0x50 bytes of input, whereas the size of navigation is only 32 bytes.
Thus in order to satisfy the check, our token key should be:
access code = 0xdeadbeef. (Since
0xdeadbeef ^ 0x1337c0de = 3449454129)
access code = 0xd0d0face. (Since
0xd0d0face ^ 0x1337c0de = 3286710800)
access code = 0xc0cac0de. (Since
0xc0cac0de ^ 0x1337c0de = 3556573184)
access code = 0xcafebabe. (Since
0xcafebabe ^ 0x1337c0de = 3653859936)
Once we pass the check, we can simply inject shellcode with the help of the given leak.
Now that we have understood the binary, let's begin with the exploitation part! In my exploit I have chosen car '1' and race '2'. Thus my access code is
0xc0cac0de. You can choose any other car or race, however your token key will be different for each case, depending on your access key.
The shellcode we wish to inject can either be found online here or one may choose to write the shellcode! For the sake of writeup, below I have explained how you can write your own shellcode!
While writing the shellcode, it is necessary to note which syscall we are going to call. In this case I will be making use of an
execve shellcode. The arguments to the
execve shellcode are (shown in terms of registers eax, ebx, ecx and edx):
eax= syscall number i.e
0xbfor the execve shellcode 32 bit.
ebxshould contain a pointer to the string
edxmust be nulled out.
Thus our shellcode may look something like this:
Another factor we haven't looked into is the leak. Once we place our shellcode on the stack, while overflowing, we might have to give the location where our shellcode is present, so that the program can jump to that location and begin executing the shellcode. The leak provided is a stack leak. And the stack size is
We can find our shellcode in memory like this.
Thus we note that our shellcode is present at location $ebp-0x2c. With the given leak we can find that eip is located 0x13 bytes below the leaked address.
Since eip is 4 bytes above ebp, while giving the buffer we will have subtract the length of our shellcode from
0x30 in order to find the size.
Thus our payload will look something like this: