Skip to content Skip to navigation

PIN (reverse 400)

Category: 

The task was to reverse file main. This is an executable for MS DOS.

Fortunately, this binary isn't packed and it's logic can be easily understand without dynamic analysis. After few minutes of analysis is becames obvius that this executable set hook for interupt int9 (keyboard handler) and for every input character makes some changes with global variable byte_178. If this variables equals 0x14 then we get success message.

First of all let's take a look at the begging of main function:

There you can see that new int9 handler is function at address loc_103. New int9 handler has nothing intrestin: it takes input key code, increment pointer to input key code and set key code there. It's worth noting that new int9 handler uses it's own local buffer which I called keyboard_buffer and pointer to recently added key code in that buffer (I called it recived_cur_elem_offset).

After setting new int9 handler main function goes to loop at address loc_38E and leave it only when new key code has been added. Let's see what happens when new key is pressed (code of pressed key is in al registger):

So this binary exits when key PgDn pressed and does nothing if pressed any key except keys belong to numbers (key codes can be found there: http://msdn.microsoft.com/en-us/library/aa299374(v=vs.60).aspx). So PIN checking algorithm is the next:

arr_byte_179 = [28, 15, 44, 16, 10, 25, 48, 46, 23, 14, 3, 27, 31, 33, 40, 39, 18, 45, 34, 21, 31, 22, 39, 49, 17, 32, 45, 33, 41, 21, 4, 22, 35, 32, 21, 19, 29, 41, 49, 40, 22, 39, 18, 47, 34, 27, 19, 1, 32, 29, 30, 44, 24, 0, 38, 26, 25, 14, 37, 9, 46, 26, 14, 13, 11, 5, 37, 10, 24, 44, 44, 14, 23, 38, 16, 20, 6, 0, 8, 9, 0, 37, 48, 44, 23, 6, 46, 9, 10, 16, 14, 30, 24, 10, 13, 28, 5, 15, 48, 12, 14, 28, 0, 25, 15, 16, 48, 9, 12, 38, 23, 24, 7, 15, 26, 10, 30, 13, 12, 9, 37, 10, 23, 38, 44, 28, 13, 0, 26, 15, 9, 5, 38, 44, 24, 15, 48, 23, 37, 8, 25, 16, 30, 24, 28, 37, 10, 8, 38, 12, 46, 10, 24, 28, 48, 37, 0, 23, 13, 8, 12, 9, 48, 44, 38, 24, 8, 26, 28, 15, 39, 36, 29, 3, 34, 19, 27, 40, 47, 22, 31, 47, 40, 2, 22, 27, 21, 3, 32, 1, 21, 39, 41, 4, 3, 40, 47, 22, 31, 18, 9, 6, 48, 7, 26, 5, 13, 12, 10, 8, 27, 19, 29, 41, 49, 3, 31, 47, 40, 39, 21, 1, 32, 18, 19, 3, 27, 4, 35, 39, 16, 38, 0, 9, 13, 30, 48, 26, 44, 5, 16, 9, 37, 44, 15, 23, 14, 28, 48, 0, 6, 13, 26, 0, 12, 23, 15, 5, 14, 48, 5, 15, 16, 13, 14, 23, 46, 24, 48, 10, 31, 29, 40, 39, 35, 21, 47, 32, 22, 33, 37, 16, 8, 48, 30, 46, 23, 38, 9, 13, 47, 34, 49, 17, 32, 31, 41, 1, 18, 19, 14, 37, 10, 12, 38, 15, 48, 5, 9, 13, 4, 27, 22, 45, 2, 33, 17, 47, 35, 32, 18, 1, 49, 34, 2, 29, 27, 3, 31, 4, 22, 32, 29, 45, 34, 3, 39, 27, 21, 47, 3, 41, 35, 31, 19, 18, 40, 1, 22, 27, 22, 31, 29, 49, 21, 19, 47, 18, 40, 1, 41, 19, 29, 40, 35, 18, 22, 42, 45, 39, 44, 24, 25, 0, 46, 26, 28, 16, 9, 8, 9, 15, 13, 26, 25, 16, 6, 23, 10, 5, 3, 1, 35, 4, 17, 34, 22, 47, 45, 19, 39, 22, 29, 27, 32, 35, 41, 1, 2, 17, 21, 3, 27, 31, 33, 40, 22, 39, 34, 17, 43, 33, 32, 18, 31, 4, 41, 45, 22, 3, 4, 42, 40, 27, 47, 21, 9, 45, 1, 3, 13, 30, 23, 37, 14, 10, 12, 6, 9, 28, 3, 4, 34, 32, 31, 49, 22, 2, 19, 1, 23, 44, 13, 10, 30, 9, 0, 8, 14, 5, 33, 19, 39, 3, 35, 4, 1, 27, 31, 17, 38, 6, 46, 37, 28, 5, 25, 26, 8, 9, 41, 33, 32, 18, 21, 45, 34, 27, 19, 4]

key_format = '1234567890'

def to_keycodes(key_str):
	if key_str not in key_format:
		print("Invalid key format (only numbers)!")
		return None
	return key_format.index(key_str)+2

def check_key(key):
	glob_byte_178 = 0x16
	for el in key:
		glob_byte_178 = arr_byte_179[glob_byte_178*10 + to_keycodes(el) - 2]
		if glob_byte_178==0x14:
			print("Success! Your key is '"+key+"'")
			return
	print("No...")

The algorithm is quite easy and the only bad news is that array at address 179 isn't a substitution and can't be easily reversed. So we have graph and should find there the way from 0x16 to 0x14. Before searching for some fast algorithm I started simples brute via next algorithm:

import sys

arr_byte_179 = [28, 15, 44, 16, 10, 25, 48, 46, 23, 14, 3, 27, 31, 33, 40, 39, 18, 45, 34, 21, 31, 22, 39, 49, 17, 32, 45, 33, 41, 21, 4, 22, 35, 32, 21, 19, 29, 41, 49, 40, 22, 39, 18, 47, 34, 27, 19, 1, 32, 29, 30, 44, 24, 0, 38, 26, 25, 14, 37, 9, 46, 26, 14, 13, 11, 5, 37, 10, 24, 44, 44, 14, 23, 38, 16, 20, 6, 0, 8, 9, 0, 37, 48, 44, 23, 6, 46, 9, 10, 16, 14, 30, 24, 10, 13, 28, 5, 15, 48, 12, 14, 28, 0, 25, 15, 16, 48, 9, 12, 38, 23, 24, 7, 15, 26, 10, 30, 13, 12, 9, 37, 10, 23, 38, 44, 28, 13, 0, 26, 15, 9, 5, 38, 44, 24, 15, 48, 23, 37, 8, 25, 16, 30, 24, 28, 37, 10, 8, 38, 12, 46, 10, 24, 28, 48, 37, 0, 23, 13, 8, 12, 9, 48, 44, 38, 24, 8, 26, 28, 15, 39, 36, 29, 3, 34, 19, 27, 40, 47, 22, 31, 47, 40, 2, 22, 27, 21, 3, 32, 1, 21, 39, 41, 4, 3, 40, 47, 22, 31, 18, 9, 6, 48, 7, 26, 5, 13, 12, 10, 8, 27, 19, 29, 41, 49, 3, 31, 47, 40, 39, 21, 1, 32, 18, 19, 3, 27, 4, 35, 39, 16, 38, 0, 9, 13, 30, 48, 26, 44, 5, 16, 9, 37, 44, 15, 23, 14, 28, 48, 0, 6, 13, 26, 0, 12, 23, 15, 5, 14, 48, 5, 15, 16, 13, 14, 23, 46, 24, 48, 10, 31, 29, 40, 39, 35, 21, 47, 32, 22, 33, 37, 16, 8, 48, 30, 46, 23, 38, 9, 13, 47, 34, 49, 17, 32, 31, 41, 1, 18, 19, 14, 37, 10, 12, 38, 15, 48, 5, 9, 13, 4, 27, 22, 45, 2, 33, 17, 47, 35, 32, 18, 1, 49, 34, 2, 29, 27, 3, 31, 4, 22, 32, 29, 45, 34, 3, 39, 27, 21, 47, 3, 41, 35, 31, 19, 18, 40, 1, 22, 27, 22, 31, 29, 49, 21, 19, 47, 18, 40, 1, 41, 19, 29, 40, 35, 18, 22, 42, 45, 39, 44, 24, 25, 0, 46, 26, 28, 16, 9, 8, 9, 15, 13, 26, 25, 16, 6, 23, 10, 5, 3, 1, 35, 4, 17, 34, 22, 47, 45, 19, 39, 22, 29, 27, 32, 35, 41, 1, 2, 17, 21, 3, 27, 31, 33, 40, 22, 39, 34, 17, 43, 33, 32, 18, 31, 4, 41, 45, 22, 3, 4, 42, 40, 27, 47, 21, 9, 45, 1, 3, 13, 30, 23, 37, 14, 10, 12, 6, 9, 28, 3, 4, 34, 32, 31, 49, 22, 2, 19, 1, 23, 44, 13, 10, 30, 9, 0, 8, 14, 5, 33, 19, 39, 3, 35, 4, 1, 27, 31, 17, 38, 6, 46, 37, 28, 5, 25, 26, 8, 9, 41, 33, 32, 18, 21, 45, 34, 27, 19, 4]
key_format = '1234567890'

def from_keycodes(keycode):
	if keycode>=2 and keycode<=0xB:
		return key_format[keycode-2]
	else:
		print("Invalid key format (only numbers)!")
		return None

def all_ind_of_el(arr,el):
	ind_arr =[]
	for i in range(len(arr)):
		if arr[i]==el:
			ind_arr.append(i)
	return ind_arr

def excract_prev_states(dw_val):
	res_arr = []
	ells_id = all_ind_of_el(arr_byte_179,dw_val)
	for cur_id  in ells_id:
		loc_val = cur_id + 2
		cur_keycode = (loc_val % 10)
		if cur_keycode==0 or cur_keycode==1:
			cur_keycode = cur_keycode + 10
		prev_dwVal = int((loc_val - cur_keycode)/10)
		res_arr.append([cur_keycode,prev_dwVal])
	return res_arr

MAX_DEPTH = 15
pin = ''
def looper(start_elem,depth):
	global pin
	depth = depth+1
	if depth>MAX_DEPTH:
		return False
	for tt in excract_prev_states(start_elem):
		if tt[1]==0x16 or looper(tt[1], depth)==True:
			print(str(depth)+"\t:\t"+str(tt[1])+"\t:\t"+from_keycodes(tt[0]))
			pin = pin + from_keycodes(tt[0])
			return True
	return False

looper(0x14,0)
print("You pin is "+ pin)

And it has suddenly found a pin "052817506537536". I've entered it in form as flag and recived "Wrong!". So I've started to search an error in my code.. and found more 'valid' pins: "887452817506536", "27452817506536". But there was no flag among them;(

Few minutes later organizers have published hint that the length of pin is 11 symbols. So I've changed MAX_DEPTH to 11 and run next code:

MAX_DEPTH = 11
pin = ''
def looper(start_elem,depth,curpath):
	global pin
	depth = depth+1
	if depth>MAX_DEPTH:
		return False
	curpath = curpath + [start_elem]
	for tt in excract_prev_states(start_elem):
		if tt[1] in curpath:
			continue
		if tt[1]==0x16 or looper(tt[1], depth,curpath)==True:
			print(str(depth)+"\t: "+str(tt[1])+"\t: "+from_keycodes(tt[0]))
			pin = pin + from_keycodes(tt[0])
			return True
	return False

mypath = []
looper(0x14,0,mypath)
print("You pin is "+ pin)

Just in minute I've got the flag: 05281792536.