Sharif CTF 2016 - Snake rev400 Writeup

Hi,

Intro

We finished #21 in this CTF, we didn't manage to solve many easy tasks. We are always looking for people to join the team and help us doing better results.


Only 8 teams solved this challenge that's why I decided to publish a write-up to explain how I solved it. I earned the second most points in the reverse engineering catgory by solving all the tasks and wining bonus points for 2 tasks. 

The challenge:

Given a snake game the goal is to eat all the provided foods in order to win and get the flag.

The number of foods is computed based on total used allocation units of the disk.

The first appraoch would be  take the binary and  reverse engineer the game logic, patch either the memory variable containing the score or the remaining foods.

I decided from the beginning not to go through this approach since I estimated that it will be time consuming because I need to unpack the binary and evade the anti-debug tricks present.

My idea was just simple, make the number of remaining foods 0 or negative.

How this number is calculated ? To go with more details let's first of all trace the execution of the program using procmon.



Just before typing enter to start the game we can see that there is an invoked operation (highlighted) QueryFullSizeInformationVolume 


We can conclude taht the number of remaining food us computed as follows:


Total Foods = UsedAllocationUnits = (TotalAllocationUnits - ActualAvailableAllocationUnits) * SectorsPerAllocationUnit * BytesPerSector




TotalAllocationUnits: 26214399
CallerAvailableAllocationUnits: 1599263
ActualAvailableAllocationUnits: 1599263
SectorsPerAllocationUnit: 8
BytesPerSector: 512

The number is ~100823597056 as shown in the following screenshot


Imagine if we hook or intercept this call and set the value of TotalAllocationUnits to 0 what would be the total number of food ? negative ? Win ?

Lets visualize the stack call trace around this operation.




GetDiskFreeSpaceExW function sounds a good interception point.

Referring to MSDN documentation, it retrieves information about the amount of space that is available on a disk volume, which is the total amount of space, the total amount of free space, and the total amount of free space available to the user that is associated with the calling thread.

BOOL WINAPI GetDiskFreeSpaceEx(
  _In_opt_  LPCTSTR         lpDirectoryName,
  _Out_opt_ PULARGE_INTEGER lpFreeBytesAvailable,
  _Out_opt_ PULARGE_INTEGER lpTotalNumberOfBytes,
  _Out_opt_ PULARGE_INTEGER lpTotalNumberOfFreeBytes
);


By setting lpTotalNumberOfBytes to 0 we achieve our goal. 

To do this we need to write a small C code which before GetDiskFreeSpaceEx call get pointer to lpTotalNumberOfBytes  and after the call set the value of lpTotalNumberOfBytes  to 0 using the saved pointer.


I downloaded Visual Studio, I already have pin and I wrote the following code:

#include "pin.H"

#include <fstream>
#include <iomanip>
#include <iostream>
#include <list>

char * lpDirectoryName;
ADDRINT lpFreeBytesAvailable;
ADDRINT lpTotalNumberOfBytes;
ADDRINT lpTotalNumberOfFreeBytes;

INT32 Usage()
{
 return -1;
}

VOID GetDiskFreeSpaceExWArg(CHAR * name, char *  arg1, ADDRINT arg2, ADDRINT arg3, ADDRINT arg4)
{
 lpDirectoryName = arg1;
 lpFreeBytesAvailable=  arg2;
 lpTotalNumberOfBytes = arg3;
 lpTotalNumberOfFreeBytes = arg4;
 cout << name << "(" << arg1 << ")" << arg2 << " " << arg3 << " "<< arg4 << endl;
}

VOID GetDiskFreeSpaceExWAfter(ADDRINT ret)
{
 cout << "\tReturned handle: " << ret << endl;

 *((unsigned __int64 *)lpTotalNumberOfBytes) = 0;

 cout << "\lpDirectoryName: " << lpDirectoryName << endl;
 cout << "\tlpFreeBytesAvailable: " << *((unsigned __int64 *)lpFreeBytesAvailable) << endl;
 cout << "\lpTotalNumberOfBytes: " << *((unsigned __int64 *)lpTotalNumberOfBytes) << endl;
 cout << "\lpTotalNumberOfFreeBytes: " << *((unsigned __int64 *)lpTotalNumberOfFreeBytes) << endl;


}


VOID Image(IMG img, VOID *v)
{
 RTN cfwRtn = RTN_FindByName(img, "GetDiskFreeSpaceExW");
 if (RTN_Valid(cfwRtn))
 {
  RTN_Open(cfwRtn);

  RTN_InsertCall(cfwRtn, IPOINT_BEFORE, (AFUNPTR)GetDiskFreeSpaceExWArg,
   IARG_ADDRINT, "GetDiskFreeSpaceExW",
   IARG_FUNCARG_ENTRYPOINT_VALUE, 0,
   IARG_FUNCARG_ENTRYPOINT_VALUE, 1,
   IARG_FUNCARG_ENTRYPOINT_VALUE, 2,
   IARG_FUNCARG_ENTRYPOINT_VALUE, 3,
   IARG_END);
      RTN_InsertCall(cfwRtn, IPOINT_AFTER, (AFUNPTR)GetDiskFreeSpaceExWAfter,
   IARG_FUNCRET_EXITPOINT_VALUE, IARG_END);

  RTN_Close(cfwRtn);
 }
}


int main(int argc, char *argv[])
{
 PIN_InitSymbols();
 if (PIN_Init(argc, argv)) {
  return Usage();
 }
 IMG_AddInstrumentFunction(Image, 0);
 PIN_StartProgram();
 return 0;
}

Build and run

C:\CTF\inst>pin -t snake.dll -- snake.exe


Bingo !

We won the game with 0 points and the flag was SharifCTF{c98128632a4d6741d0f2b9d616f4af09}

Follow me on Twitter if you enjoyed this !

Thanks for reading !