CH00 – RootersCTF Writeup

Hello everyone,

A new writeup for RootersCTF’s CH00 challenge, this time the challenge is a Windows binary.

So, let us start by loading our binary into IDA, and we can quickly see the main logic of the program.

As we can see, the WinAPI function GetWindowsText is used to get user input from a GUI Window handle, provides it to sub_4018CF, which does nothing more than allocating heap area and copies the user input into that allocated area.

The return value is the address of the allocated area in the heap memory, which is then provided to another routine sub_40158D that in turn returns a bool value based on which the execution flow pops up either a Correct or Wrong message box. It seems that the sub_40158D is where the input validation checks are happening. Let’s dig in.

First, it checks if the provided input is exactly 8 characters long, then a call to sub_401468 is executed with the user input as parameter.

We can follow the logic that’s happening in here as follows:

  • The function is iterating over the user input string, 2 characters at each iteration.
  • Every character is provided to the function sub_401410 (more about it below).
  • Returned value from the latter routine is left shifted by 4 bits.
  • The second processed character is added to the previous shifted character.
  • The resulting byte is stored at some memory location.

Actually, upon investigating the function sub_401410, it simply converts a character into an integer value.

The above function could be decompiled to the following C++ snippet.

unsigned char defByte = 0x00;
unsigned char sub_401410(unsigned char c) {
	if(c > 0x60 && c <= 0x7A)
		return (defByte = c-0x57);
	if(c > 0x40 && c <= 0x5A)
		return (defByte = c-0x37);
	if(c > 0x2F && c <= 0x39)
		return (defByte = c-0x30);
	return defByte;
}

Now, let’s get 2 calls back and check what values the 4 calculated resulting bytes are compared against.

We can do so by simply placing a breakpoint at the call to sub_4014DE routine, the reason of that specific location is that immediately afterward, we can notice 4 double words in memory being compared against our calculated bytes.

As we can see, our bytes are compared against the following values 0x66,0x75,0x63,0x6B.

And that’s simply it, so now all we have to do is code our own bruteforcer that would reveal what input we should provide the algorithm with in order to get us those specific 4 bytes. I’ve written mine in C++, and here is the code along with the output.

#include <iostream>
#include <map>
using namespace std;

map<int, char> charMap = {
	pair<int, char>(0, '0'),
	pair<int, char>(1, '1'),
	pair<int, char>(2, '2'),
	pair<int, char>(3, '3'),
	pair<int, char>(4, '4'),
	pair<int, char>(5, '5'),
	pair<int, char>(6, '6'),
	pair<int, char>(7, '7'),
	pair<int, char>(8, '8'),
	pair<int, char>(9, '9'),
	pair<int, char>(10, 'A'),
	pair<int, char>(11, 'B'),
	pair<int, char>(12, 'C'),
	pair<int, char>(13, 'D'),
	pair<int, char>(14, 'E'),
	pair<int, char>(15, 'F'),
	pair<int, char>(16, 'G'),
	pair<int, char>(17, 'H'),
	pair<int, char>(18, 'I'),
	pair<int, char>(19, 'J'),
	pair<int, char>(20, 'K'),
	pair<int, char>(21, 'L'),
	pair<int, char>(22, 'M'),
	pair<int, char>(23, 'N'),
	pair<int, char>(24, 'O'),
	pair<int, char>(25, 'P'),
	pair<int, char>(26, 'Q'),
	pair<int, char>(27, 'R'),
	pair<int, char>(28, 'S'),
	pair<int, char>(29, 'T'),
	pair<int, char>(30, 'U'),
	pair<int, char>(31, 'V'),
	pair<int, char>(32, 'W'),
	pair<int, char>(33, 'X'),
	pair<int, char>(34, 'Y'),
	pair<int, char>(35, 'Z')
};

unsigned char defByte = 0x00;
unsigned char* processFun1(const string);
void bruteForcePassword(unsigned char);
unsigned char mFun(unsigned char);

int main(){
	bruteForcePassword(0x66);
	cout << "####" << endl;
	bruteForcePassword(0x75);
	cout << "####" << endl;
	bruteForcePassword(0x63);
	cout << "####" << endl;
	bruteForcePassword(0x6B);
	return 0;
}

void bruteForcePassword(unsigned char tgtByte) {
	unsigned char tempInChar, uChar1, uChar2;

	for(uChar1 = 0; uChar1 < 36; uChar1++){
		for(uChar2 = 0; uChar2 < 36; uChar2++) {
			tempInChar   = uChar1;
			tempInChar <<= 0x4;
			tempInChar  += uChar2;
			if ((tempInChar & 0xFF) == tgtByte) {
				cout << charMap[(int)uChar1] << charMap[(int)uChar2];
				cout << endl;
				break;
			}
		}
	}
}

unsigned char* processFun1(const string mystr){
	unsigned char tempInChar;
	short int counter, outc;
	unsigned char* out;
	
	out = reinterpret_cast<unsigned char*>(malloc(sizeof(unsigned char)*8));
	counter = 8;
	outc = 0;

	while(counter >= 0) {
		tempInChar = mFun(mystr[counter]);
		tempInChar <<= 0x4;
		tempInChar += mFun(mystr[counter+1]);
		out[outc] = tempInChar & 0xFF;
		outc++;
		counter -= 2;
	}
	return out;
}

unsigned char mFun(unsigned char c) {
	if(c > 0x60 && c <= 0x7A)
		return (defByte = c-0x57);
	if(c > 0x40 && c <= 0x5A)
		return (defByte = c-0x37);
	if(c > 0x2F && c <= 0x39)
		return (defByte = c-0x30);
	return defByte;
}

As we can see, I’ve decided to print all possible combinations of every 2 characters that -when all the 8 characters are combined- would give us a correct password, we ended up having 384 correct passwords for this challenge!

Leave a comment