InCTF Jr 2022
August - November, 2022

Bad Race

Medium Pwn

Author: d1g174l_f0r7r355

This was one of the medium challenges I made for inctfj qualifiers. It is based on ret2shellcode.

Preliminary checks:

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 race_car.


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 0xdeadbeef.
  • If car choice is '2' and race choice is '1', our access code is 0xd0d0face.
  • If car choice is '1' and race choice is '2', our access code is 0xc0cac0de.
  • If car choice is '2' and race choice is also '2', our access code is 0xcafebabe.

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:

  • 3449454129 if our access code = 0xdeadbeef. (Since 0xdeadbeef ^ 0x1337c0de = 3449454129)
  • 3286710800 if our access code = 0xd0d0face. (Since 0xd0d0face ^ 0x1337c0de = 3286710800)
  • 3556573184 if our access code = 0xc0cac0de. (Since 0xc0cac0de ^ 0x1337c0de = 3556573184)
  • 3653859936 if our 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 0xb for the execve shellcode 32 bit.
  • ebx should contain a pointer to the string /bin/sh.
  • ecx and edx must 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 0x30 bytes. 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: