01 Feb 2016 @ 7:53 PM 

Yet another x64 ELF, this time also obfuscated.
RE4-obfMoar while loops!

We have option to select one of 3 cells. Then we have to provide paths. Each step is accepted as a character. By following scanf() and jumping though the decompiled code, it was evident that there are 7 possible letters = “asdqwez”. When we provide the path, it is verified by a function(sub_400960()) and decided whether program will continue or not. If the path is accepted, it is fed to MD5. At the final state (‘e’), the hash is calculated and compared to these values “6dff819e14ce1bfc112d1817e69cff1f”, “6a9e23f57e9f5590b0c168a781bf07c7”, “05aeebcadfe7f05a7e778d904d6d297e”. We can guess that each one of the hash is for one of the cell.

Now the check function (sub_400960()) is small and can be decompiled to pretty easily. Rest of the code also can be reconstructed by looking around decompiled code and eliminating loops. The final code should look something like this.

#include <stdio.h>
#include <stdlib.h>

int CheckFunc(unsigned int *a1, unsigned int *a2)
{
	if (a1[0] < a1[1])
	{
		if (a2[0] == a2[1])
		{
			a2[(a2[0] - 1) + 2] = a1[a1[0] + 2];
			a1[0] = a1[0] + 1;
			a2[0] = a2[0] - 1;
			return 0;
		}
		else
		{
			if (a1[a1[0] + 2] < a2[a2[0] + 2])
			{
				a2[(a2[0] - 1) + 2] = a1[a1[0] + 2]; 
				a1[0] = a1[0] + 1;
				a2[0] = a2[0] - 1;
				return 0;
			}
			else
			{
				return 1;
			}
		}
	}
	return 1;
}
int main()
{
	unsigned int DF1[14] = { 0 }, DF2[14] = { 0 }, DF3[14] = { 0 };
	int cell, count, ret;
	char c = 0;
	//MD5_CTX ctx;
	printf("Select your cell (1-3): ");
	scanf("%d", &cell);
	switch (cell)
	{
	case 1:
		count = 12;
		break;
	case 2:
		count = 10;
		break;
	case 3:
		count = 11;
		break;
	}
	DF1[0] = count;
	DF1[1] = count;

	DF2[0] = count;
	DF2[1] = count;

	DF3[0] = count;
	DF3[1] = count;

	while (DF3[0])
	{
		DF3[(DF3[0] - 1) + 2] = DF3[0];
		DF3[0] = DF3[0] - 1;
	}
	//MD5_Init(&ctx);
	while (c != 'e')
	{
		printf("Enter your path: ");
		scanf("%c", &c);
		switch (c)
		{
		case 'a':
			ret = CheckFunc(DF3, DF1);			
			break;
		case 's':
			ret = CheckFunc(DF1, DF3);
			break;
		case 'd':
			ret = CheckFunc(DF3, DF2);
			break;
		case 'q':
			ret = CheckFunc(DF2, DF1);
			break;
		case 'w':
			ret = CheckFunc(DF1, DF2);
			break;
		case 'z':
			ret = CheckFunc(DF2, DF3);
			break;
		}
		
		if (ret == 1)
		{
			printf("Bad boy!");
			exit(0);
		}
		//MD5_Update(&ctx, &c, 1);
	}
	if (DF2[0] == 0 && DF1[0] == DF1[1] && DF3[0] == DF3[1])
	{
		//MD5_Final(&ctx);
		//get md5 string

		//6dff819e14ce1bfc112d1817e69cff1f
		//6a9e23f57e9f5590b0c168a781bf07c7
		//05aeebcadfe7f05a7e778d904d6d297e

		//see if string is of any of this
	}
	
	return 0;
}

Now we have 3 arrays and it is doing some operation on that. First thing in mind is the hash cracking – key space is small (asdqwz) and we may get lucky. I ran Hashcat till 16 chars without any luck. Mean while I was trying to figure out what exactly was the operation that is happening here. By the prison and path, it was clear that it is some kind of puzzle, but which puzzle will have 6 types of moves and these weird conditions. It didn’t make any sense. Finally drawn the array in a notebook to figure out if that makes any sense. Suddenly it all became clear – this is Tower of Hanoi!

The cells are number of disks (10, 11, 12 – not in order) and 3 arrays are poles. The CheckFunction makes sure that the move is valid.

Solution for this is to solve Tower of Hanoi and provide the solution in this format (asdqwz). A recursive solver was written and final solution was provided to challenge.

import sys
import subprocess
import time

solution = 'adwazqadwszwadwazqazwszqadwazqadwszwadwszqazwszwadwazqadwszwadwazqazwszqadwazqazwszwadwszqazwszqadwazqadwszwadwazqazwszqadwazqadwszwadwszqazwszwadwazqadwszwadwszqazwszqadwazqazwszwadwszqazwszwadwazqadwszwadwazqazwszqadwazqadwszwadwszqazwszwadwazqadwszwadwazqazwszqadwazqazwszwadwszqazwszqadwazqadwszwadwazqazwszqadwazqazwszwadwszqazwszwadwazqadwszwadwszqazwszqadwazqazwszwadwszqazwszqadwazqadwszwadwazqazwszqadwazqadwszwadwszqazwszwadwazqadwszwadwazqazwszqadwazqazwszwadwszqazwszqadwazqadwszwadwazqazwszqadwazqadwszwadwszqazwszwadwazqadwszwadwszqazwszqadwazqazwszwadwszqazwszwadwazqadwszwadwazqazwszqadwazqadwszwadwszqazwszwadwazqadwszwadwszqazwszqadwazqazwszwadwszqazwszqadwazqadwszwadwazqazwszqadwazqazwszwadwszqazwszwadwazqadwszwadwszqazwszqadwazqazwszwadwszqazwszwadwazqadwszwadwazqazwszqadwazqadwszwadwszqazwszwadwazqadwszwadwazqazwszqadwazqazwszwadwszqazwszqadwazqadwszwadwazqazwszqadwazqadwszwadwszqazwszwadwazqadwszwadwszqazwszqadwazqazwszwadwszqazwszwadwazqadwszwadwazqazwszqadwazqadwszwadwszqazwszwadwazqadwszwadwe'


p = subprocess.Popen("./jail_break_bin", stdin=subprocess.PIPE)
p.stdin.write(str(2) + '\n')
p.stdin.write(str(2) + '\n')
for c in solution:
	p.stdin.write(c + '\n')
p.stdin.close()
ret = p.wait()


dan@ubuntu:~/nullc$ python jail.py
Welcome to the Mini-Prison (not secure, but hard to escape)!!
You should select a cell and find a path to escape to the flag.
You will not be disappointed!!
Select your cell (1-3):
Go on enter your path:You Found The Path.
The flag is nullcon{t00_34sy_t0_br34k_th15_pr1510n_by_d0nf05}

Posted By: Dan
Last Edit: 01 Feb 2016 @ 07:57 PM

EmailPermalinkComments (0)
Tags
Categories: CTF, HackIM2016
 01 Feb 2016 @ 7:46 PM 

x64 ELF, probably obfuscated with LLVM Obfuscator. Hexrays will decompile it to a nested while-loop.

RE3-obfI spent sometime understanding how exactly it works. Basic idea about this obfuscation is to transform pieces of code into different loops, set a variable to identify which loop and condition to execute. Even though it looked confusing initially, with a bit of splicing decompiled code, it made better sense.

Essentially code reads 16 numbers, and calls a function (sub_400780())to see if that value is correct or not. If it is correct, the next number is checked. If the numbers are correct, it is used in some operation to finally calculate the flag string.

To solve this I decompiled sub_400780() and bruteforced it. Initially I thought there will be only 1 correct solution and I have figured all the checks, only to see that there are few other checks (like total length should be 26).

unsigned int cpow(int val, int pow)
{
	unsigned int pp = 1;
	while (pow)
	{
		pp *= val;
		pow--;
	}
	return pp;
}

int Brute(unsigned int loc)
{
	unsigned int HCArr[] = { 0xE2246054, 0x1DDB9FAF, 0x0E224604C, 0x1DDB9FA9, 0x0EB48CD5A, 0x0EB48CD66, 0x0EB48CD68, 0x14B732AC, 0x0E9682E7A, 0x0E9682E3A, 0x0E9682E37, 0x1697D1DA, 0x9E32D9, 0x0FF61CD00, 0x0FF61CD1F, 0x9E32FE, 0x3FDD8C9D, 0x0C0227348, 0x0C0227355, 0x3FDD8CAB, 0x108AE52C, 0x0EF751AFB, 0x0EF751AFA, 0x108AE52C, 0x0FED8F192, 0x1270E7E, 0x0FED8F1BE, 0x1270E73, 0x3D51216D, 0x3D51214B, 0x0C2AEDEBA, 0x3D512142, 0x0D63C4BCE, 0x29C3B40C, 0x29C3B42E, 0x29C3B42F, 0x0C93E69E2, 0x0C93E69AD, 0x36C19603, 0x36C19641, 0x1149F143, 0x0EEB60ED5, 0x0EEB60E9E, 0x1149F173, 0x0CB68709A, 0x0CB6870DE, 0x0CB6870B5, 0x34978F66, 0x0C163D0A7, 0x3E9C2F09, 0x3E9C2F02, 0x3E9C2F5F, 0x0D2CB22ED, 0x2D34DD3E, 0x0D2CB22F3, 0x2D34DD1B, 0x0F6BA6729, 0x0F6BA6721, 0x0F6BA6769, 0x94598CD, 0x0D302FAE3, 0x0D302FA82, 0x2CFD057C, 0x2CFD0554 };

	unsigned int val = 0;
	unsigned int res = 0;
	unsigned int eax, edx, ebx, ecx, esi, edi, r8d, r9d, r10d;

	while (val < 0xFFFFFFFF)
	{
		res = cpow(val, 3);
		eax = cpow(val, 2);
		edx = HCArr[loc * 4];
		ecx = HCArr[(loc * 4) + 3];

		esi = (~edx) & 0x6EFBE94D;
		edx = edx & 0x910416B2;
		edi = (~ecx) & 0x6EFBE94D;
		ecx = ecx & 0x910416B2;

		edx = edx | esi;
		ecx = ecx | edi;
		ecx = ecx ^ edx;
		ecx = ecx * eax;
		ecx = -ecx;
		ecx -= res;
		ecx = -ecx;
		res = ecx;

		eax = cpow(val, 2);
		edx = HCArr[(loc * 4) + 1];
		ecx = HCArr[(loc * 4) + 3];
		esi = (~edx) & ecx;
		ecx = (~ecx) & edx;
		ecx |= esi;
		ecx *= eax;
		res += ecx;

		eax = cpow(val, 2);
		edx = HCArr[(loc * 4) + 2];
		ecx = HCArr[(loc * 4) + 3];
		esi = (~edx) & 0x43B37B1;
		edx &= 0x0FBC4C84E;
		edi = (~ecx) & 0x43B37B1;
		ecx &= 0x0FBC4C84E;
		edx |= esi;
		ecx |= edi;
		ecx ^= edx;
		ecx *= eax;
		ecx = -ecx;
		res -= ecx;

		esi = HCArr[(loc * 4)];
		edi = (~esi) & 0x59C0DD4B;
		esi &= 0x0A63F22B4;

		ebx = HCArr[(loc * 4) + 3];
		ecx = (~ebx) & 0x59C0DD4B;
		eax = ebx & 0x0A63F22B4;
		esi |= edi;
		eax |= ecx;
		eax ^= esi;

		ecx = HCArr[(loc * 4) + 1];
		edx = (~ecx) & 0x0BA0892CF;
		ecx &= 0x45F76D30;
		ebx = (~HCArr[(loc * 4) + 3]) & 0x0BA0892CF;
		r8d = HCArr[(loc * 4) + 3] & 0x45F76D30;
		ecx |= edx;
		r8d |= ebx;
		r8d ^= ecx;
		r8d *= eax;
		r8d *= val;
		res += r8d;

		ecx = HCArr[(loc * 4) + 2];
		esi = (~ecx) & 0x0EFD60EC8;
		ecx &= 0x1029F137;
		eax = HCArr[(loc * 4) + 3];
		edi = ~eax;
		ebx = (edi)& 0x0EFD60EC8;
		edx = eax & 0x1029F137;
		ecx |= esi;
		edx |= ebx;
		edx ^= ecx;
		ecx = HCArr[(loc * 4) + 1];
		esi = (~ecx) & 0x6F5BEF81;
		ecx &= 0x90A4107E;
		edi &= 0x6F5BEF81;
		eax &= 0x90A4107E;
		ecx |= esi;
		eax |= edi;
		eax ^= ecx;
		eax *= edx;
		eax *= val;
		res += eax;

		ecx = HCArr[(loc * 4)];
		edx = (~ecx) & 0x50C0D70D;
		ecx &= 0x0AF3F28F2;
		eax = HCArr[(loc * 4) + 3];
		edi = ~eax;
		ebx = edi & 0x50C0D70D;
		esi = eax & 0x0AF3F28F2;
		ecx |= edx;
		esi |= ebx;
		esi ^= ecx;

		ecx = HCArr[(loc * 4) + 2];
		edx = ecx;
		edx = (~edx) & eax;
		edi &= ecx;
		edi |= edx;
		edi *= esi;
		edi *= val;
		res += edi;

		r10d = HCArr[(loc * 4)];
		ecx = ~r10d;
		eax = HCArr[(loc * 4) + 3];
		ecx &= eax;
		esi = ~eax;
		r10d &= esi;
		r10d |= ecx;

		ecx = HCArr[(loc * 4) + 1];
		ebx = (~ecx) & 0x73E6034A;
		ecx &= 0x8C19FCB5;
		edx = esi & 0x73E6034A;
		edi = eax & 0x8C19FCB5;
		ecx |= ebx;
		edi |= edx;
		edi ^= ecx;
		edi *= r10d;

		ecx = HCArr[(loc * 4) + 2];
		edx = (~ecx) & 0x0AC23EFC0;
		ecx &= 0x53DC103F;
		esi &= 0x0AC23EFC0;
		eax &= 0x53DC103F;
		ecx |= edx;
		eax |= esi;
		eax ^= ecx;
		eax *= edi;
		res += eax;

		if (res == 0x0)
		{
			printf("%x-", val);
			//return val;
		}
		val++;
	}
	return 0;
}

int main()
{
	printf("%d\n", Brute(11));
	printf("%d\n", Brute(12));
	printf("%d\n", Brute(6));
	printf("%d\n", Brute(7));

	printf("%d\n", Brute(4));
	printf("%d\n", Brute(0xd));
	printf("%d\n", Brute(5));
	printf("%d\n", Brute(0));

	printf("%d\n", Brute(1));
	printf("%d\n", Brute(0xf));
	printf("%d\n", Brute(8));
	printf("%d\n", Brute(0xe));

	printf("%d\n", Brute(0xa));
	printf("%d\n", Brute(2));
	printf("%d\n", Brute(9));
	printf("%d\n", Brute(3));
	return 0;
}

Now I had a list of solutions for each of 16 numbers, and I needed to make a combination of them andΒ  bruteforce the binary directly. I removed large numbers as it won’t fit the 26 length criteria.
import sys
import subprocess
import itertools

a = [[4, 45, 72], [8], [31, 51], [8], [2, 29], [10, 24], [0, 41, 42], [3, 27], [10, 53, 60], [42, 73], [31], [20, 28, 92], [19, 90], [19, 32, 96], [20, 93], [2, 31]]

vals = list(itertools.product(*a))
count = 0;
while count < len(vals):
	p = subprocess.Popen("./donfos_reversing", stdin=subprocess.PIPE)
	p.stdin.write(str(vals[count][0]) + '\n')
	p.stdin.write(str(vals[count][1]) + '\n')
	p.stdin.write(str(vals[count][2]) + '\n')
	p.stdin.write(str(vals[count][3]) + '\n')
	p.stdin.write(str(vals[count][4]) + '\n')
	p.stdin.write(str(vals[count][5]) + '\n')
	p.stdin.write(str(vals[count][6]) + '\n')
	p.stdin.write(str(vals[count][7]) + '\n')
	p.stdin.write(str(vals[count][8]) + '\n')
	p.stdin.write(str(vals[count][9]) + '\n')
	p.stdin.write(str(vals[count][10]) + '\n')
	p.stdin.write(str(vals[count][11]) + '\n')
	p.stdin.write(str(vals[count][12]) + '\n')
	p.stdin.write(str(vals[count][13]) + '\n')	
	p.stdin.write(str(vals[count][14]) + '\n')
	p.stdin.write(str(vals[count][15]) + '\n')
	p.stdin.close()
	ret = p.wait()
	count = count+1

Runnin it for sometime gave possible flags:

Good Job!!
flag=nullcon{d0n_f05ijg[65\5rt_t0_n8?qo1}
Good Job!!
flag=nullcon{d0n_f05ing_15_4rt_t0_l34rn}
Good Job!!
flag=nullcon{d0h_f0=kna]22Z6uw^u3Vd:?tn02}
Good Job!!
flag=nullcon{g:e]o:6hje^74[1rv\v1_m;?{o31}
Good Job!!
flag=nullcon{g:e]o:6hja^33[2sv\v1_m94pl2}

nullcon{d0n_f05ing_15_4rt_t0_l34rn} it is!

Posted By: Dan
Last Edit: 01 Feb 2016 @ 07:46 PM

EmailPermalinkComments (0)
Tags
Categories: CTF, HackIM2016
 01 Feb 2016 @ 7:37 PM 

Again x64 ELF. This one was the last RE challenge I solved in HackIM, because I didn’t see the srand in .ctor πŸ™ initially.
The code is pretty clear, there were few obfuscation though (v18 = v19 + v18 – 409176519 + 409176519;)

decSeed is fixed, which will produce consistent random numbers.
There are 3 interesting functions in the code, two of them generates a number as output and the other one is a check. Once the check is successful, the user entered number is MD5ed and compared to a hardcoded hash value “15b74b4db57d0afdfe98eb5dbc3b542b”

Again we are back to bruteforcing. (Actually we don’t need to bruteforce, the Gen1()/Func() will spit out the correct code)

int Func(int a)
{
	int temp = 1;
	int result = 0;
	while (temp)
	{
		if (a & (~temp ^ a))
		{
			result = ~(~temp | ~a);
		}
		temp *= 2;
		if (temp > a)
			break;
	}
	return result;
}

int Func2(int a)
{
	int result = 0;
	while (a)
	{
		a = ~(~(a - 1) | ~a);
		result++;
	}
	return result;
}
int Func3(int a1, int a2)
{
	if (a1&&a2)
	{
		int v4 = a1 & (Func(a1) ^ a1);
		int v5 = Func(a1);
		int v8 = (2 * v5 ^ v4 | 2 * v5 & v4) == a2 + a1;
		return v8;
	}
	return 0;
}
int Brute()
{
	int rnum;
	int inp, size, temp, count;
	char str[128] = { 0 };
	MD5 md;
	rnum = 0x327B23C6;
	int end = 0x0;
	count = 0;
	temp = Func(rnum);
	size = (1 << Func2(rnum)) - 1;
	md.Init();

	inp = 1;
	while (inp < 0x7fffffff && count != rnum)
	{
		if (Func3(size, inp))		
		{
			sprintf(str, "%d", inp);
			printf("%s\n", str);
			//printf("==>%d\n", Func(size) == inp);
			size += inp;
			md.Update((unsigned char*)str, strlen(str));
			while (~(~temp | ~size))
			{
				count = temp ^ count | temp & count;
				size &= temp ^ size;
				temp = Func(rnum & (count ^ rnum));
			}
			inp = 0;
		}
		inp++;
	}
	md.Final();
	if (strcmp(md.digestChars, "15b74b4db57d0afdfe98eb5dbc3b542b") == 0)
	{
		printf("\nSuccess: %d\n\n", rnum);
	}

	return 0;
}

Code will spit up valid inputs, which will give us the flag. Input is too long to type into console, so wrote a bit of python to automate. You should have also seen that there are two sleep() calls which we need to patch/LD_PRELOAD. I patched out those sleeps.
import sys
import subprocess

arr = [32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608, 16777216, 33554432, 67108864, 134217728, 268435456, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608, 16777216, 33554432, 67108864, 134217728, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608, 16777216, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 64, 128, 256, 512, 1024, 2048, 4096, 32, 64, 128, 256, 16, 32, 64, 128, 8, 16, 32, 64, 4, 8, 16, 32, 2, 1]


p = subprocess.Popen("./patched", stdin=subprocess.PIPE)
for i in arr:
	p.stdin.write(str(i) + '\n')
p.stdin.close()
ret = p.wait()


dan@ubuntu:~/nullc$ python ./pssolve.py
I will generate some random numbers.
If you can give me those numbers, you will be $$rewarded$$
hmm..thinking...OK. I am Ready. Enter Numbers.
Good Job!!
Wait till I fetch your reward...OK. Here it is
The flag is:nullcon{50_5tup1d_ch4113ng3_f0r_e1i73er_71k3-y0u}

Posted By: Dan
Last Edit: 01 Feb 2016 @ 07:37 PM

EmailPermalinkComments (0)
Tags
Categories: CTF, HackIM2016
 01 Feb 2016 @ 7:29 PM 

Given file is x64 ELF binary. Opened it up in IDA and code looks pretty straight forward.
If you have HexRays 64, things are pretty easy.
decompiledCode accepts couple of numbers as input, XORs them together, initiates srand() with the final value and creates 30 rand() values. Rand values are converted into strings and calculates MD5 of them and compares with a hardcoded hash value “5eba99aff105c9ff6a1a913e343fec67”.

Now this is a pretty straight forward bruteforce.

#include <openssl/md5.h>
#include <stdio.h>

int main()
{
	MD5_CTX ctx;
	unsigned char out[MD5_DIGEST_LENGTH];
	int n=0;
	char hash[128] = {0};
	int i = 1;
	int sum = 0;
	int temp;
	int seed;
	int count;
	int rnd;
	char ssra[128] = {0};
	while (1)
	{		
		if (i > 0xffff)
			break;
		i++;
		temp = i;
		sum = 0;
		while (temp)
		{
			sum++;
			temp &= (temp - 1);
		}

		if (sum == 10)
		{
			seed = i;
			srand(seed);
			MD5_Init(&ctx);
			for(count=0;count<30;count++)
			{
				rnd = rand() % 1000;
				sprintf(ssra, "%d", (unsigned int)rnd);
				rnd = strlen(ssra);
				MD5_Update(&ctx, ssra, rnd);
			}

			MD5_Final(out, &ctx);
			for(n=0; n<MD5_DIGEST_LENGTH; n++)
			{
				sprintf(&(hash[2*n]), "%02x", out[n]);
			}
			if ( strcmp(hash, "5eba99aff105c9ff6a1a913e343fec67") == 0 )
			{
				printf("Got: %d\n", i);
			}
		}
	}
	
	return 0;
}


dan@ubuntu:~/nullc$ ./zorro_bin
Welcome to Pub Zorro!!
Straight to the point. How many drinks you want?1
OK. I need details of all the drinks. Give me 1 drink ids:59306

You choose right mix and here is your reward: The flag is nullcon{nu11c0n_s4yz_x0r1n6_1s_4m4z1ng}

Posted By: Dan
Last Edit: 01 Feb 2016 @ 07:29 PM

EmailPermalinkComments (0)
Tags
Categories: CTF, HackIM2016
 31 Dec 2014 @ 12:17 AM 

OTP (one time password) challenge gave us a website with username/password/otp as login scheme. A sample login of admin was provided. Since OTP is OTP :P, it won’t work for the second time. We need to understand how exactly OTPs are generated.

Once we create a login in the website, we are provided with an option to download and use OTP generator. Each user gets an option to create a config (token) file, which should be used to feed the OTP generator binary. OTP values contain counters, which will make sure that different OTP gets generated every time.

Reversing OTP binary

Provided binary is ELF x64 C++ binary. Useful symbols were there to help reverse engineer the binary. The config/token files should look like this.

tokenFirst 8 bytes is key-id/random number, second 8 bytes is counter and rest is username. After spending some time with IDA and debugging, we figured the token generating algorithm. It more or less looked like this
1. Username + 7 bytes of key-id is hashed(SHA2) and XORed byte by byte – First 8 bytes of OTP
2. Counter – Next 8 bytes of OTP
3. Counter + 8 bytes of key-id is hashed(SHA2) – last 16 bytes of OTP

Now we know the algorithm, Admin’s OTP and to create the next OTP, we need to know his key-id/token file (8bytes).

Recovering OTP key-id

To attack this scheme, we have to figure out a way to predict the 7 bytes from first part of OTP. An all out brute-force may take ages to complete (even if we precompute values). At this point I consulted dcoder, who pointed me to do meet-in-the-middle.

We are attacking a 7 byte combination. So the idea is to precompute hashes of 3 byte combination, and try to find a match when creating 4 byte hashes. Code should look like this:

uint64 MakeBaseSum()
{
	uint64 base = 0; 
	uint8 data[] ="\x61\x64\x6d\x69\x6e\x00";		//admin\0

	base = SingleHash(0, 0);
	for(int i=0;i<6;i++)
	{
		base ^= SingleHash(data[i], i);
	} 
	return base;
}
void MiTM()
{
	uint64 basesum;
	uint8 target_a[] = "\x9a\xe6\x84\xca\x58\x32\x14\xd3";	//hash
	uint64 p0sum[256];
	uint64 p1sum[256];
	uint64 p2sum[256];
	uint64 p3sum[256];
	uint64 p4sum[256];
	uint64 p5sum[256];
	uint64 p6sum[256];
	uint64 target; 
	memcpy(&target, target_a, sizeof target);

	basesum = MakeBaseSum();
	size_t userLen = 6;		//with '\0'
	for(int i=0;i<256;i++)
	{
		p0sum[i] = SingleHash(i, userLen + 0);
		p1sum[i] = SingleHash(i, userLen + 1);
		p2sum[i] = SingleHash(i, userLen + 2);
		p3sum[i] = SingleHash(i, userLen + 3);
		p4sum[i] = SingleHash(i, userLen + 4);
		p5sum[i] = SingleHash(i, userLen + 5);
		p6sum[i] = SingleHash(i, userLen + 6);
	}
	printf("Tables created.\n");
	unordered_map<uint64, uint32> map;
	map.reserve(1<<24);
	//all 3 byte combination
	for(uint32 i=0; i < (1<<24); i++)
	{
		uint64 temp = basesum ^ p0sum[i&0xFF] ^ p1sum[(i>>8)&0xFF] ^ p2sum[(i>>16)&0xFF];
		map.insert(std::pair<uint64, uint32>(temp, i));
	}
	printf("3 byte done.\n");
	//all 4 byte combination
	for (uint64 i= 0; i < (1LL<<32); i++)
	{
		uint64 temp = p3sum[i&0xFF] ^ p4sum[(i>>8)&0xFF] ^ p5sum[(i>>16)&0xFF] ^ p6sum[(i>>24)&0xFF];
		if(map.count(temp ^ target))
		{
			uint64 full = ((i << 24) + map.at(temp ^ target));
			uint8 bytes[8]; 
			memcpy(bytes, &full, sizeof bytes);
			printf("Found: \n");
			for(size_t j = 0; j < 8; ++j)
				printf("0x%02X,", bytes[j]);
			printf("\n");
			break;
		}
	}					
}

After running this for couple of minutes, we got the 7 bytes of key-id. Missing byte can be brute-forced by comparing with the known OTP value.
Final source code for OTP gen should look like this:
int main( int argc, char *argv[] )
{
	int i;
	sha256_context ctx;
	unsigned char sha256sum[32], basesum[32];
	uint8 data[] ="\x61\x64\x6d\x69\x6e\x00\xC1\x0E\x43\x8C\x99\xBB\x49"; //name+7bytes of key-id
	uint8 base[] ="\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; //null byte string to hash first
	sha256_starts( &ctx );
	sha256_update( &ctx, base, 0xd );
	sha256_finish( &ctx, basesum );

	//data is hashed byte by byte and xored
	for(i=0;i<0xd;i++)
	{
		uint8 cont[] ="\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
		cont[i] = data[i];
		sha256_starts( &ctx );
		sha256_update( &ctx, cont, 0xd );
		sha256_finish( &ctx, sha256sum );
		XOR(basesum, sha256sum);			//byte array XOR
		//PrintHash(sha256sum);
	} 
	//first 8 bytes of OTP
	for(i=0;i<8;i++)
	{
		printf("%02x", basesum[i] );
	}
	//PrintHash(basesum);
	uint8 count[] = "\x3a\x05\x00\x00\x00\x00\x00\x00\x93\xC1\x0E\x43\x8C\x99\xBB\x49"; //computed key-id
	sha256_starts( &ctx );
	sha256_update( &ctx, count, 0x10 );
	sha256_finish( &ctx, sha256sum );

	//second 8bytes - the counter
	for(i=0;i<8;i++)
	{
		printf("%02x", count[i] );
	}
	//signature
	for(i=0;i<16;i++)
	{
		printf("%02x", sha256sum[i] );
	}
	return 0;
}

Now we can login to the webpage and get our flag πŸ™‚

Posted By: Dan
Last Edit: 02 Jan 2015 @ 07:02 PM

EmailPermalinkComments (0)
Tags
Tags: , , , ,
Categories: CTF
 30 Dec 2014 @ 6:15 PM 

In this challenge we get AES implemented in a hardware and some kind of SPI interface to access the hardware.

Looking at the code, the FLAG is encrypted by random password every time we access it. Now we need to find a way to get the password or some info about the password which can be useful.
This is how a normal operation looks like.

hwaesThe initial getkey command returns a 16*’0′, which says that the key/password initially set was NULL. We can verify that by encrypting something offline and checking the encrypted value from the service. We can also see that once encryption is done, they getkey command returns a different value – b4ef5bcb3e92e21123e951cf6f8f188e

Searching that value in the net promptly puts us in the AES key expansion, and we know that we have a key/info leak.
Now the question is, can we derive the actual key/password or all roundkeys from the final roundkey?

Well we can!. Before searching much, I just wrote some “ghetto” code to reverse the key expansion.

//final round key
uint8_t s[] = "\xb4\xef\x5b\xcb\x3e\x92\xe2\x11\x23\xe9\x51\xcf\x6f\x8f\x18\x8e";
int round = 40;

Print(s);
for(int i=0; i<10;i++)
{
	Reverse(s, op, round);
	round -= 4;
	for(int j = 0;j<16; j++)
	{
		s[j] = op[j];
	}
} 
void Reverse(uint8_t* inp, uint8_t* op, int round)
{
	uint8_t tempa[4];
	uint8_t k;
	tempa[0] = inp[0];
	tempa[1] = inp[1];
	tempa[2] = inp[2];
	tempa[3] = inp[3];

	for(int i=4; i<16;i++)
	{
		op[i] = inp[i] ^ tempa[i%4];
		tempa[i%4] = inp[i];
	}
	tempa[0] = op[12];
	tempa[1] = op[13];
	tempa[2] = op[14];
	tempa[3] = op[15];

	//rotate
	k = tempa[0];
	tempa[0] = tempa[1];
	tempa[1] = tempa[2];
	tempa[2] = tempa[3];
	tempa[3] = k;

	//substitute
	tempa[0] = getSBoxValue(tempa[0]);
	tempa[1] = getSBoxValue(tempa[1]);
	tempa[2] = getSBoxValue(tempa[2]);
	tempa[3] = getSBoxValue(tempa[3]);

	int i = round;
	tempa[0] =  tempa[0] ^ Rcon[i/Nk];

	op[0] = inp[0] ^ tempa[0];
	op[1] = inp[1] ^ tempa[1];
	op[2] = inp[2] ^ tempa[2];
	op[3] = inp[3] ^ tempa[3];
	Print(op);
}

reverse_keReplace with the getkey value after encrypted flag is obtained, and we will have the key used for encrypting that flag. Decrypt with obtained key and we have our flag.

Posted By: Dan
Last Edit: 30 Dec 2014 @ 06:27 PM

EmailPermalinkComments (0)
Tags
Tags: , , ,
Categories: CTF
 04 Mar 2014 @ 12:56 AM 

Rarverseme is a rar file, and rar has an inbuilt VM which can be programmed(http://blog.cmpxchg8b.com/2012/09/fun-with-constrained-programming.html). Given challenge contains a custom bytecode which will print πŸ™ every time upon unraring. Our target is to understand the bytecode, reverse it to find the flag.

Idea is to disassemble/debug the rarVM to figure out what exactly is getting executed. After getting unrar source compiled, rest was pretty straight forward.

rarvm
Running a trace resulted in this.

MOVD [010DC294], 0x00002000
MOVD [010FFC30], 0x796f7572 ;your
MOVD [010FFC34], 0x666c6167 ;flag
MOVD [010FFC38], 0x676f6573 ;goes
MOVD [010FFC3C], 0x68657275 ;heru
MOVD [010DC298], 0x00005000
MOVD [01102C30], 0x31337357 ;13sW
MOVD [01102C34], 0x63561227 ;cV.'
MOVD [01102C38], 0x6666654a ;ffeJ
MOVD [01102C3C], 0x78584148 ;xXAH
JMP 0x000000ae
MOVD [010DC290], 0x796f7572 ; flag[i]
AND 0x796f7572, 0x000000ff
MOVD [010DC29C], 0x31337357 ; arr[i]
AND 0x31337357, 0x000000ff
MUL 0x00000072, 0x00000057 ; flag[i] * arr[i]

MOVD [010DC28C], 0x000026be
MOVD [010DC290], 0x796f7572
SHR 0x796f7572, 0x00000008 
AND 0x00796f75, 0x000000ff
MOVD [010DC29C], 0x63561227
AND 0x63561227, 0x000000ff
MUL 0x00000075, 0x00000027
ADDD 0x00003891, 0x000011d3 ; sum the results

MOVD [010DC290], 0x796f7572
SHR 0x796f7572, 0x00000010
AND 0x0000796f, 0x000000ff
MOVD [010DC29C], 0x6666654a
AND 0x6666654a, 0x000000ff
MUL 0x0000006f, 0x0000004a
ADDD 0x000058a7, 0x00002016

MOVD [010DC290], 0x796f7572
SHR 0x796f7572, 0x00000018
AND 0x00000079, 0x000000ff
MOVD [010DC29C], 0x78584148
AND 0x78584148, 0x000000ff
MUL 0x00000079, 0x00000048
ADDD 0x00007aaf, 0x00002208

CMPD 0x00007aaf, 0x0000706f

its doing a multiplication of flag values and a fixed array and comparing to a hardcoded value. If the comparison is wrong, it is jumping to bad_boy. The whole checking code can be dumped by setting CMPD as success. As we have the code figured, rest was simple bruteforcing. Before I started bruteforcing, my friend wrote a z3 code which runs much faster and yielded the flag as: evenmoarvms?rly?

V = [0x706f, 0x7972, 0x89d1, 0x9cc5]
#add rest of hardcoded values

w0, w1, w2, w3 = BitVecs("w0 w1 w2 w3", 16)

solve( w0 * 0x57 + w1 * 0x27 + w2 * 0x4a + w3 * 0x48 == V[0],  
	   w0 * 0x73 + w1 * 0x12 + w2 * 0x65 + w3 * 0x41 == V[1],  
	   w0 * 0x33 + w1 * 0x56 + w2 * 0x66 + w3 * 0x58 == V[2], 
	   w0 * 0x31 + w1 * 0x63 + w2 * 0x66 + w3 * 0x78 == V[3],
	   ULT(w0, 128), ULT(w1, 128))

Posted By: Dan
Last Edit: 04 Mar 2014 @ 12:56 AM

EmailPermalinkComments (2)
Tags
Categories: CTF, Reverse Engineering
 03 Mar 2014 @ 11:09 PM 

Hypercube is a DOL file which points us to gamecube and Dolphin emulator. Once we have the emulator, we can see the output. It clearly states that we are in a gamecube and only hypercube is fast enough to get the flag. This pretty much sounds like Hypercomputer challenge from PlaidCTF. Now its time to do some RE then.

To get disassembly we will need specific loader for DOL files. Hopefully some of those are available and we can directly start our disassembly. We can easily reach interesting function by looking up for strings. Reading Power PC assembly is not in my usual work description, but I can do that too while doing a CTF πŸ˜‰
init_ida
Next thing I did was to try to debug it in the emulator and see what exactly happens. Once the call at 0x80005c78 was invoked, it just went into full on computation which eventually crashed the emulator. Now the Hyper part was pretty much making sense πŸ™‚
init_dbg
Upon analyzing there are only 3 functions we have to worry about – 80005B18, 80005A4C and 800059E4.

After converting the PPC to higher language, it was evident that these operations are simple and written in a way that will consume lot of CPU cycles.

We can rename those functions as 80005B18 -Multiply, 80005A4C – Add and 800059E4 – Increment

That reduces our code to minimal and running it yields our flag. (Don’t mind the variable names – happens when you haven’t slept for more than 24 hours πŸ˜€ )

UInt32 s_8 = 0xadd;
UInt32 s_c = 0x5dd11;
UInt32 s_10 = 0x352463;
UInt32 s_14 = 0x8008135;
UInt32 r0, r11, r3, r9, r4;
for (int i = 0; i <= 31390; i++)
{
	for (int j = 0; j <= 21; j++)
	{
		r9 = s_c * s_10;


		r0 = (UInt32)((((UInt64)r9 * (UInt64)3) & 0xFFFFFFFF00000000) >> 32);
		r11 = r9 - r0;
		r11 = r11 >> 1;
		r0 += r11;
		r11 = r0 >> 30;
		r0 = r11 << 31;
		r0 = r0 - r11;
		r0 = r9 - r0;
		s_8 = r0;
		 

		r3 = s_c;
		r4 = 0x6ddb;
		r9 = r3 * r4;
		
		r0 = (UInt32)((((UInt64)r9 * (UInt64)3)&0xFFFFFFFF00000000)>>32);
		r11 = r9 - r0;
		r11 = r11 >> 1;
		r0 += r11;
		r11 = r0 >> 30;
		r0 = r11 << 31;
		r0 = r0 - r11;
		r0 = r9 - r0;
		s_c = r0;
		

		s_10 = s_c ^ 0x1BA40000 ^ 0x1c3c;
		s_14++;
	}
}
richText.Text = "key{1337" + s_8.ToString() + s_c.ToString() + s_10.ToString() + "h4x" + s_14.ToString() + "}";

Posted By: Dan
Last Edit: 03 Mar 2014 @ 11:22 PM

EmailPermalinkComments (0)
Tags
Categories: CTF, Reverse Engineering
 21 Aug 2013 @ 10:11 PM 

Given challenge is an implementation of ECDSA, where we are given an implementation in python and a list of signed messages for level 0 access. Our aim is to create/forge a signed message with level 1 access. We can see from the source code that, there are 2 users with level 1 access. Cracking those MD5 hashes gives us the username as

Butler : 98c131f9fb31f732b136f87e64ff686a
Kevin : f1cd318e412b5f7226e5f377a9544ff7

Now our main protection comes into picture – ECDSA. As far as the algorithm parameters are concerned, we don’t have any parameters available (not even public params!!). Our first challenge will be to figure out the params.

Looking at the ecdsa.py we can verify that the curve we are dealing with is y^2 = x^3 + ax + b \pmod p. From service.py we can see that 3 points (G, Q and R) are given, which should be points on the curve. From these 3 point values, we can calculate p,a and b of the equation. (a = p - 3)

y^2 = x^3 + ax + b \pmod p

y^2 = x^3 + (p-3)*x + b \pmod p

y^2 = x^3 -3x + b \pmod p

substituting 3 points in the equation and solving:
m = (Qy^2 - (Qx^3 - 3*Qx)) - (Gy^2 - (Gx^3 - 3*Gx))

n = (Qy^2 - (Qx^3 - 3*Qx)) - (Ry^2 - (Rx^3 - 3*Rx))

p = GCD(m, n)

b = Gy^2 - (Gx^3 - 3*Gx)

calculating these equations with actual values will render these results

p = 89953523493328636138979614835438769105803101293517644103178299545319142490503
a = 89953523493328636138979614835438769105803101293517644103178299545319142490500
b = 28285296545714903834902884467158189217354728250629470479032309603102942404639

Now we have to calculate order(cardinality) of the curve, which can be done efficiently by Schoof-Elkies-Atkin point counting algorithm. Calculating q results in

q = 89953523493328636138979614835438769106005948670998555217484157791369906305783

Now we have all the public parameters.

From checking the implementation and some guess, we can figure out the vulnerability – that is reusing k while signing(PS3 hack πŸ˜‰ ). ECDSA signature has two parts r and s. r is calculated as r = k * g (x coordinate of point multiplication). So if k is same in different signatures, r will be also same. Now we have to find two instances in given signed messages, where r is same. We can get one pair as

Bushing wNpp5moxL1+Z0I40PJQCC/LegcOQocP+8Y9GV7Mv+5xiKYZxm648bajMuJ9Dy1SyHEpu/MBHkViuCM4P9Fd78Q==
Hotz wNpp5moxL1+Z0I40PJQCC/LegcOQocP+8Y9GV7Mv+5xCxqlIuZkMQDSpRWffZGG4NdkaOvAfttTtQuCaJZonxA==

Now we can calculate sk (secret multiplier) by following the attack specified in wiki (same attack is used in PS3 hack).

s1 - s2 = modinv(k, order) * (hash1 - hash2) % order

k = (hash1 - hash2) * modinv(s1 - s2, order)

sk = (s1*k - hash1) * modinv(r, order)

sk is calculated as

sk = 68503307448214310387573639006216872681840007669594105206515313184282784925849

Now its just the matter of signing a hash. We will just reuse the code from CTF.

import string
import random
import hashlib
import ecdsa
import base64
import os

access = {"f56334fbe02eaa05218c31b01a80f2f6":0, "00b37cb56bb57705348610253b1b82e4":0, 
          "f2131629ea6c08f7f5f326d8bb6eb5fd":0, "6fa95b1427af77b3d769ae9cb853382f":0, 
          "58cd57027cf126fcc9bd93dea9d74c1a":0, "f1cd318e412b5f7226e5f377a9544ff7":1, 
          "98c131f9fb31f732b136f87e64ff686a":1, "6f3249aa304055d63828af3bfab778f6":2}

#our values		  
_p = 89953523493328636138979614835438769105803101293517644103178299545319142490503L
_b = 28285296545714903834902884467158189217354728250629470479032309603102942404639L
_q = 89953523493328636138979614835438769106005948670998555217484157791369906305783L
_sk = 68503307448214310387573639006216872681840007669594105206515313184282784925849L
ec = ecdsa.CurveFp( _p, _p-3, _b )

#some points that really should be on the curve we're going to use
_Gx = 0x337ef2115b4595fbd60e2ffb5ee6409463609e0e5a6611b105443e02cb82edd8L
_Gy = 0x1879b8d7a68a550f58166f0d6b4e86a0873d7b709e28ee318ddadd4ccf505e1aL

_Qx = 0x2a40fd522f73dc9f7c40b2420e39e62c5742ff2f11805a1577ed7f60153a0be1L
_Qy = 0x3085e99246006b71b4211eff47ff3efc0f93103ee7379dc3bcc6decdc46073a3L

_Rx = 0xbd0a442367bdc24cb09c49404e3d307ba99122e7b78e14f0d84870d0df97aa59L
_Ry = 0x22c88612db6b6af6f196cd815fc5f57fe871d3b6588b0c7a59e06cc759d736b2L

#check the curve is loaded ok
if not ec.contains_point(_Gx,_Gy):
        exit()
if not ec.contains_point(_Qx,_Qy):
        exit()
if not ec.contains_point(_Rx,_Ry):
        exit()

g = ecdsa.Point( ec, _Gx, _Gy, _q )
seed = os.urandom(32)

#construct the server key material
server_public_key = ecdsa.Public_key( g, g * _sk)
server_secret_key = ecdsa.Private_key(server_public_key, _sk )

#some extended interfaces to Lis's ecdsa script
def sign(hashed_data, privkey):
        h = int(hashed_data,16)
        k = get_ephemeral_key()
        signature  = privkey.sign( h, k )
        return encode_signature(signature)

def encode_signature(signature):
        rhex = "%064x" % signature.r
        shex = "%064x" % signature.s
        return base64.b64encode(rhex.decode('hex') + shex.decode('hex')) 

def verify(signature, hashed_input, pubkey):
        tmp = signature.replace("\n", "")       
        try:
                tmp = base64.b64decode(tmp).encode('hex')
        except:
                return False
        if len(tmp) != 128:
                return False
        sig = ecdsa.Signature(int(tmp[:64],16),int(tmp[64:],16))
        h = int(hashed_input,16)
        return pubkey.verifies(h, sig)
                
def get_ephemeral_key():
        k = ""
        while len(k) < 32:
                k += hashlib.md5(seed + str(random.randint(0,255))).digest()
        return int(k.encode('hex'),16)

#existing signature	for user level 0
token = 'f56334fbe02eaa05218c31b01a80f2f6'
sec_token = 'wNpp5moxL1+Z0I40PJQCC/LegcOQocP+8Y9GV7Mv+5xCxqlIuZkMQDSpRWffZGG4NdkaOvAfttTtQuCaJZonxA=='

if verify(sec_token, token, server_public_key):
	print "[*]verify good!"
print 
#creating our signature for user level 1
name = 'Kevin'
token = 'f1cd318e412b5f7226e5f377a9544ff7'
sec_token = sign(token, server_secret_key) 
print sec_token
print 
#verifying
if verify(sec_token, token, server_public_key):
	print "[*]verify good!"

Finally we have to bruteforce md5 with a given prefix to get a hash which begins with 0000. Straight forward bruteforce πŸ™‚

Posted By: Dan
Last Edit: 05 Jan 2016 @ 01:17 AM

EmailPermalinkComments (0)
Tags
Categories: CTF
 08 Aug 2013 @ 8:52 PM 

Cross posted from my team site: http://seg.fault.in

I started looking at cone when I got frustrated with cnot. Initial analysis confirmed that it is a x64 binary, asks for a password when run, and prints whether the password was right or not.

Running through ltrace went fine and there was no trace for anti-dbg (like ptrace call from cnot).

1

Next logical step is to load into IDA and take a look at the disassembly.

No, not this madness again :/ (Both CNOT and CON are obfuscated x64 binaries).

Looking at the disassembly it looked similar to cnot, but expectedly lesser protection/obfuscation. From previous experience from cnot, I started to rename fgetc/fputc/calloc wrapper functions to get a better understanding. In the mean time, lets look at the ltrace logs.

2

There is one calloc just after the password prompt is printed and lot of callocs after the password is read. We can easily assume that lot of allocated memory will be used for password verification.

Now looking back at the disassembly and looking only at function calls we can get the flow ofΒ  fputc->calloc->fgetc function wrappers. Its time to start debugging as we have figured out where exactly our input is being read. I started debugging by putting breakpoint at the place where it checks for ‘\n’.

.text:0000000000404B0C         mov     qword ptr [rbp-58h], 0
.text:0000000000404B14         mov     dword ptr [rbp-58h], 0Ah
.text:0000000000404B1B         mov     r15d, [rbp-50h]
.text:0000000000404B1F         cmp     r15d, [rbp-58h]
.text:0000000000404B23         jz      short loc_404B36

Then traced down ignoring everything other than function calls (experience from cnot). I stepped into first function call and spent sometime trying to understand what it does, got frustrated and went back into disassembly to see whether the function calls return values is being used/checked at some place. As the check was there, I put BP on the next statement to see the return value. It turned to be 1, and the execution continues. The next function call is interesting one.

3Yes, we have our input in stack and its being referenced in couple of registers(I entered password as “abcd”). Now we can safely assume that call_404f89 does some operation on our password. Stepping into the function and tracing manually was a frustrating experience and decided to do what I previously did –Β  lets check the return value of the function. Return value was 0xa46c74bb and it is saved to stack. Tracing down the function, it ended up in doing a comparison with a hardcoded value.

4

Doing couple of more testing with different passwords verified that the password is split into 4bytes and passed to the function call_404f89, and the return values are compared with different hardcoded values. A total of 9 values are being compared – making our password 36 characters (including ‘\0’ – we will see how we got that later).

So essentially the algo should look like this:

char* szPassword = ReadPassword();
unsigned int hardcoded[] = {0x247BFB03, 0x8C33E4BB, 0xD49D047B,
                            0x7303FC73, 0x8C950303, 0x2304743B,
                            0x64647303, 0xA454949B, 0xD48CB5DB};
for(int i=0; i < 9; i++)
{
 retVals[i] = call_404f89(szPassword + (i * 4));
}
for(int i=0; i < 9; i++)
{
 if(retVals[i] != hardcoded[i])
 {
  //puts("Wrong!");
  //return -1;
 }
}
puts("Correct!");

As we have partially figured out the algo, the next big thing is to understand call_404f89. I tried to single step through the code but resulted in spending more time staring at the disassembly that doesn’t make much sense. Meantime I was also trying out the other challenges and partially solved compression (CRIME attack) and three_eyed_fish (didn’t complete both due to lack of time/resource :/). Revisiting the challenge again, I took a look at the ltrace log – well we havn’t looked at the calloc function calls yet. I put a BP on calloc wrapper function (0x400bc4). We should be worried about breaks only after call_404f89 starts. Once the call_404f89 started, I looked into the allocated memory to see if something interesting gets stored in those places. As the memory got allocated, I looked into the previous allocated space (when 3rd allocation happened, I looked into second allocated heap memory and so on). By the time the processing of call_404f89 got over, I had the full memory dump of all allocated memories. I ran couple of sample inputs and compared the output to make sure that I am not looking at heap headers rather than actual values. Clearly a pattern emerged as shown in the below pic.
5
While debugging at calloc return statement (jmp rdi), we can see that our password (4bytes) are used in somekind of operation and temporary results are stored in heap memory and eventually our calculated value (0xa46c74bb see above) is generated. While debugging after calloc and copy of 4bytes, I stumbled upon a piece of code which actually does an operation on our password values.
6It is a shift-arithmetic-right (sar val,0x15) which produced 0x323, which is present in the memory dump. Yiyeks! we have one operation figured. Looking at the disassembly near by, I figured some kind of pattern and found another operation by address 0x004019bf -> shl val, 0x0b. Now I restarted the debugging session with BP set on to 0x004019bf and 0x00401aea. BP on 0x004019bf hit first, and I can see the value (0x64636261 – “abcd”) is being left shifted with 0x0b resulting 0x1b130800 – which is again present in the memory dump. 2nd BP hit and we got 0x323. Now I looked at the memory dump to see if some info can be retrieved out of it. From the values in memory, I deduced the rest of the operation. Pure intuition and luck works sometime ;). Till now our algo is
value = 0x64636261;

t1 = value << 0x0b; //0x1b130800
t2 = value >> 0x15; //0x323

//next values in memory
//[0x1b130b23] - which is t1 ^ t2
//[0xdeadbeef] 
//[0xc5beb5cc] = 0x1b130b23 ^ 0xdeadbeef
//[0xdeadbeef] 
//[0xa46c74bb] = 0xc5beb5cc + 0xdeadbeef

We can rewrite the function as:
unsigned int call_404f89(char* password)
{
 unsigned int value = (unsigned int *)password;
 return ((((value << 0x0b)^(value >> 0x15))^ 0xdeadbeef)+ 0xdeadbeef);
}

Now we have the checking algo inΒ  hand. Next is to write small script/program to do brute-forcing.
for (int i = rangeStart; i < rangeEnd; i++)
{
 for (int j = rangeStart; j < rangeEnd; j++)
 {
  for (int k = rangeStart; k < rangeEnd; k++)
  {
   for (int l = rangeStart; l < rangeEnd; l++)
   {
    val = (uint)(i << 0 | j << 8 | k << 16 | l << 24);
    t1 = val << 0x0b;
    t2 = val >> 0x15;
    t2 ^= t1;
    t2 ^= 0xdeadbeef;
    t2 += 0xdeadbeef;
    if (t2 == 0x247BFB03)  //change the hardcoded values to get next 4 chars.
    {
     MessageBox.Show("Got it: " + Convert.ToChar(i) +
                                  Convert.ToChar(j) + 
                                  Convert.ToChar(k) + 
                                  Convert.ToChar(l));
     goto done;
    }
   }
  }
 }
}

The last hadcoded value won’t be found through bruteforcing until you start with rangeStart=0; – the C string delimiter ‘\0’. Anyway last part can be easily guessed πŸ™‚

Finally the password/flag is: pls_send_help_im_in_a_stack_machine

Posted By: Dan
Last Edit: 08 Aug 2013 @ 08:52 PM

EmailPermalinkComments (0)
Tags
Categories: CTF, Reverse Engineering

 Last 50 Posts
Change Theme...
  • Users » 1
  • Posts/Pages » 19
  • Comments » 41
Change Theme...
  • VoidVoid « Default
  • LifeLife
  • EarthEarth
  • WindWind
  • WaterWater
  • FireFire
  • LightLight

About



    No Child Pages.