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: 21 Aug 2013 @ 10:31 PM

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
 25 Jan 2012 @ 12:54 AM 

HackIM scoreboard

 

Malcon 2011 CTM write up – Malcon-CTM-Writeup

NULLCON 2012 HackIM write up – HackIM – danny

Thanks to many people for constant help and support. I won both the CTFs and hope to participate and learn from the newer ones ๐Ÿ™‚

Posted By: Dan
Last Edit: 25 Jan 2012 @ 12:56 AM

EmailPermalinkComments (0)
Tags
Categories: CTF, Reverse Engineering

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

About



    No Child Pages.