Fuzzeando activex con Sulley

Hace poco escribí acerca de como hacer un script para fuzzear aplicaciones locales con Sulley. Aprovechando esta base, he creado, junto con mi amigo Roi, otro script orientado a fuzzear activex. Está sin probar bien por lo que posiblemente requiera de algunos retoques, pero puede servir como base.

Las diferentes opciones son:

  • -l -> lista todos los activex instalados en Windows.
  • -n -> tiene en cuenta los que hay en exclude.txt y muestra los que no están ahí. Esta opción es bueno para tener ahí metidos todos los que aparezcan en tu máquina virtual y luego, localizar rápidamente los que instales.
  • -s NAME -> busca por aproximación el nombre de un componente.
  • -d ID -> igual que antes pero con el CSLID, es decir, busca por aproximación el ID del activex.
  • -m FILE -> aquí pasamos la ruta del activex y nos mostrará todos los métodos y parámetros que acepta.
  • -f FILE -> de forma similar al comando anterior, abre el activex e intenta fuzzear cada parámetro, con ayuda del potencial de Sulley.

El funcionamiento para fuzzear sería:

  1. Abre el activex.
  2. Enumera cada clase.
  3. Dentro de cada clase coge todos los métodos.
  4. Por cada método mira el número de parámetros que tiene y realiza todas las combinaciones de mutación.
  5. Luego continúa con el siguiente parámetro.
  6. Luego el siguiente método.
  7. Y el mismo proceso con la siguiente clase.

Por cada prueba se genera un fichero diferente. Y cuando lleva 100 ficheros generados, lanza el Internet Explorer para comenzar las pruebas.

Los ficheros generados son del tipo:

// file: c:/sulley/test0.html

<META HTTP-EQUIV='Refresh' CONTENT='3; URL=file:///c:/sulley/test1.html'>
<html>
<body>
<object classid='clsid:GUID id='target' />
<script language='vbscript'>
target "XXXXXXX"
</script>
</body>
</html>

De manera que tras lanzar el primer test, se va recargando el sólo con el siguiente y así evitamos tener que abrir y cerrar el Internet Explorer en cada prueba. Por defecto he puesto 3 segundos pero se puede cambiar en el script.

El fichero de sesiones sería el siguiente:

# script to fuzz ActiveX
#
# by:
# Pepelux <pepeluxx@gmail.com> - http://www.pepelux.org/ && http://www.enye-sec.org/

import win32api,win32con,pythoncom,sys,os,win32com.client
from win32com.axscript import axscript
import optparse
from sulley import *
from requests import activex_generic    # library to fuzz
from pydbg import *
from pydbg.defines import *
import os
import threading
import sys
from time import sleep

tmpfolder=r"c:\sulley\tmp"
iexplorepath=r"c:\Archivos de programa\Internet Explorer\iexplore.exe"
refreshTime=3

################################
### ACCESS VIOLATION HANDLE ####
################################

def av_handler(dbg):
 print "[x] Access Violation Handler"

 print dbg.dump_context()

 try:
 mod = dbg.addr_to_dll(dbg.context.Eip)
 print 'Eip module path : ', mod.path
 print 'Eip module base : 0x%08x'%mod.base
 print 'Eip offset : 0x%08x '%(dbg.context.Eip - mod.base)
 except:
 print 'Unable resolve Eip module'

 global stop
 stop=1

 dbg.stack_unwind()

 for i in dbg.disasm_around(dbg.context.Eip, 12):
 print "%x %s"%i

 dbg.terminate_process()

 return DBG_CONTINUE

##################################
### START PROCESS USING PYDBG ####
##################################

class start_proc (threading.Thread):
 def __init__(self, t_proc_name, t_name):
 threading.Thread.__init__(self)
 self.t_proc_name = t_proc_name
 self.t_name = t_name

 def run(self):
 dbg = pydbg()
 dbg.load(self.t_proc_name, self.t_name)
 dbg.set_callback(EXCEPTION_ACCESS_VIOLATION, av_handler)
 dbg.run()

#############################################
### GET COMPONENTS FROM WINDOWS REGISTRY ####
#############################################

def getCLSID( type, name ):
 index=0
 count=0
 clsid_list=[]

 #getting registry keys
 try:
 cKey=win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE,"SOFTWARE\\Classes")
 except win32api.error:
 print"[x] Error opening registry keys."
 sys.exit(0)

 #getting subkeys
 while True:
 try:
 sKey=win32api.RegEnumKey(cKey,index)
 except win32api.error:
 break
 progid=sKey

 #getting CLSID registry keys
 try:
 sKey=win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE,"SOFTWARE\\Classes\\%s\\CLSID" % progid)
 except win32api.error:
 index+=1
 continue

 #getting CLSID values
 try:
 clsid=win32api.RegQueryValueEx(sKey,None)[0]
 except win32api.error:
 print"[x] Error getting CLSID value."
 index+=1
 continue

 clsid_list.append((progid,clsid))
 index+=1

 if name == "new":
 wlist=[]

 try:
 file = open("exclude.txt")

 while 1:
 line=file.readline()
 wlist.append(line)

 if not line:
 break
 pass

 file.close()
 except:
 print "[x] File 'exclude.txt' not found"
 sys.exit(0)

 lastComName=""
 lastComNameIni=""
 lastComId=""

 for clsid in clsid_list:
 comName=clsid[0]
 comNameIni=clsid[0][0:clsid[0].find(".")]
 comId=clsid[1]
 show=1

 if name == "new":
 for value in wlist:
 if comNameIni.replace("\n","") == value.replace("\n",""):
 show=0

 if show == 1:
 if comName != lastComName and comId != lastComId:
 if lastComNameIni != comNameIni:
 print "\n"

 try:
 if type == "name":
 if name == "all" or name == "new" or comName.lower() == name.lower() or comName.lower().index(name.lower())>-1:
 print"[+] "+comName + " - " + comId
 else:
 if name == "all" or name == "new" or comId.lower() == name.lower() or comId.lower().index(name.lower())>-1:
 print"[+] "+comName + " - " + comId

 count+=1
 except:
 continue

 lastComName=comName
 lastComNameIni=comNameIni
 lastComId=comId

 if count == 0:
 print "[x] No entries found\n"

 sys.exit(0)

####################################
### GET METHODS FROM A DLL FILE ####
####################################

def getTypes( activex ):
 VTS={}

 try:
 tlb=pythoncom.LoadTypeLib(activex)
 except:
 print"[x] Error loading library."
 sys.exit(0)

 name=tlb.GetDocumentation(-1)[0]
 print "[+] Name: "+name

 clsid=str(tlb.GetLibAttr()[0])
 print "[+] CLSID: "+clsid+"\n"

 for vt in [x for x in pythoncom.__dict__.keys() if x.count("VT_")]:
 VTS[eval("pythoncom.%s" % vt)]=vt

 for i in xrange(tlb.GetTypeInfoCount()):
 name=tlb.GetDocumentation(i)[0]
 print "  [-] Interface: "+name
 info=tlb.GetTypeInfo(i)
 attr=info.GetTypeAttr()
 print "  [-] GUID: "+str(attr.iid)

 print"     [%d] Properties:" % i

 try:
 for j in xrange(attr.cVars):
 id=info.GetVArDesc(j)[0]
 names=info.GetNames(id)
 print"[%d/%d]\t%s" % (i,j,names[0])
 except:
 continue

 lastMethodName=""

 print"\t+ Methods:"

 for k in xrange(attr.cFuncs):
 desc=info.GetFuncDesc(k)

 if desc.wFuncFlags:
 continue

 id=desc.memid
 names=info.GetNames(id)
 methodName = names[0]

 if methodName != lastMethodName:
 print"\t\t%s" % (methodName)
 k=0

 lastMethodName=methodName

 try:
 for name in names[1:]:
 print"\t\t\t%s,%s" % (name, VTS[desc.args[k][0]])
 k+=1
 except:
 continue

 print"\n"
 sys.exit(0)

###########################
### FUZZING A DLL FILE ####
###########################

def startFuzz( activex ):
 VTS={}

 try:
 tlb=pythoncom.LoadTypeLib(activex)
 except:
 print"[x] Error loading library."
 sys.exit(0)

 if not os.path.isdir(tmpfolder):
 os.mkdir(tmpfolder)

 for file in os.listdir(tmpfolder):
 file_path = os.path.join(tmpfolder, file)

 try:
 if os.path.isfile(file_path):
 os.unlink(file_path)
 except:
 break

 name=tlb.GetDocumentation(-1)[0]
 clsid=str(tlb.GetLibAttr()[0])
 counter=0

 for vt in [x for x in pythoncom.__dict__.keys() if x.count("VT_")]:
 VTS[eval("pythoncom.%s" % vt)]=vt

 print "[+] Total classes for fuzzing: " + str(tlb.GetTypeInfoCount()) + "\n"

 for i in xrange(tlb.GetTypeInfoCount()):
 name=tlb.GetDocumentation(i)[0]
 info=tlb.GetTypeInfo(i)
 attr=info.GetTypeAttr()
 guid=str(attr.iid).replace("{","").replace("}","")
 ac_class=name

 lastMethodName=""

 for k in xrange(attr.cFuncs):
 desc=info.GetFuncDesc(k)

 if desc.wFuncFlags:
 continue

 id=desc.memid
 names=info.GetNames(id)
 methodName = names[0]

 if methodName != lastMethodName:
 print "\n-> " + ac_class + "->" + methodName + " {" + guid + "}"
 k=0

 lastMethodName=methodName

 try:
 for name in names[1:]:
 k+=1
 except:
 continue

 # ----- Start Fuzz -----
 for j in range (0, k):
 type="string"

 try:
 if VTS[desc.args[j][0]] == "VT_BSTR" or VTS[desc.args[j][0]] == "VT_VARIANT" or VTS[desc.args[j][0]] == "VT_ARRAY":
 print "  [-] Creating fuzzing cases for arg " + str(j+1) + " of " + str(k) + " (hex)"
 type="hex"
 else:
 if VTS[desc.args[j][0]] == "VT_BSTR" or VTS[desc.args[j][0]] == "VT_BOOL" or VTS[desc.args[j][0]] == "VT_VARIANT" or VTS[desc.args[j][0]] == "VT_UII" \
 or VTS[desc.args[j][0]] == "VT_I2" or VTS[desc.args[j][0]] == "VT_I4" or VTS[desc.args[j][0]] == "VT_EMPTY" or VTS[desc.args[j][0]] == "VT_NULL" \
 or VTS[desc.args[j][0]] == "VT_CY" or VTS[desc.args[j][0]] == "VT_DECIMAL" or VTS[desc.args[j][0]] == "VT_R4" or VTS[desc.args[j][0]] == "VT_R8":
 print "  [-] Creating fuzzing cases for arg " + str(j+1) + " of " + str(k) + " (integer)"
 type="integer"
 else:
 print "  [-] Creating fuzzing cases for arg " + str(j+1) + " of " + str(k) + " (string)"
 type="string"
 req=s_get("string") # session
 except:
 print "  [-] Creating fuzzing cases for arg " + str(j+1) + " of " + str(k) + " (string)"
 type="string"

 req=s_get(type) # session
 req.reset()

 for i in range(req.names["ini"].num_mutations()):
 data=[]
 data.append("<META HTTP-EQUIV='Refresh' CONTENT='"+str(refreshTime)+"; URL=file:///"+tmpfolder.replace("\\", "/")+"/test"+str(counter+1)+".html'>\n")
 data.append("<html>\n<body>\n")
 data.append("<object classid='clsid:"+guid+"' id='target' />\n")
 data.append("<script language='vbscript'>\n")

 targetStr = "target." + methodName + " "

 for l in range (0, k):
 if j == l:
 if type == "string":
 targetStr += "\""

 try:
 targetStr += s_render()
 except:
 targetStr += s_render().encode("hex")

 if type == "string":
 targetStr += "\""
 else:
 if type == "string":
 targetStr += "\"XX\""
 else:
 targetStr += "00"

 if l<k-1:
 targetStr += ", "

 targetStr += "\n"

 data.append(targetStr)

 data.append("</script></body>\n</html>\n")

 file = open(tmpfolder+"\\test"+str(counter)+".html","w")
 file.writelines(data)
 file.close()
 counter+=1

 s_mutate()

 if counter == 100:
 print "Starting fuzzing"
 t = start_proc(iexplorepath, tmpfolder + "\\test0.html")
 t.start()
 # ----- End Fuzz -----

 print "\n[+] Creation files has ended\n"
 print "\n"
 sys.exit(0)

#############
### INIT ####
#############

def main():
 os.system("cls")

 parser = optparse.OptionParser()
 parser.add_option('-l', dest='lst', action='store_true', help='list all components')
 parser.add_option('-n', dest='new', action='store_true', help='hidden windows components')
 parser.add_option('-s', dest='name', help='search components by name (ex: -s Office)')
 parser.add_option('-i', dest='id', help='search components by clsid (ex: -s {xxx-xxx-xxx-xxx})')
 parser.add_option('-m', dest='file', help='list methods by file (ex: -m c:\\path\\activex.dll)')
 parser.add_option('-f', dest='fuzz', help='fuzz file (ex: -f c:\\path\\activex.dll)')

 (opts, args) = parser.parse_args()

 if not opts.lst and not opts.new and not opts.name and not opts.id and not opts.file and not opts.fuzz:
 print "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"
 print "COM Activex fuzzer (by Pepelux && Roi)\n"
 print "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"
 parser.print_help()
 else:
 print "[+] Starting analysis ...\n"

 if opts.lst is not None:
 getCLSID("", "all")

 if opts.new is not None:
 getCLSID("", "new")

 if opts.name is not None:
 getCLSID("name", opts.name)

 if opts.id is not None:
 getCLSID("id", opts.id)

 if opts.file is not None:
 getTypes(opts.file)

 if opts.fuzz is not None:
 startFuzz(opts.fuzz)

if __name__=="__main__":
 main()

Y el correspondiente request:

# script to fuzz ActiveX
#
# by:
# Pepelux <pepeluxx@gmail.com> - http://www.pepelux.org/ && http://www.enye-sec.org/

from sulley import *

##################################################

s_initialize("string")
if s_block_start("ini"):
 s_string("A")
s_block_end()

s_initialize("integer")
if s_block_start("ini"):
 s_int(0, format="ascii", signed=True)
s_block_end()

s_initialize("hex")
if s_block_start("ini"):
 s_random("\xff", min_length=1, max_length=500)
s_block_end()

Un saludo a todos, en especial a Roi (http://elotrolad0.blogspot.com/).

Un comentario

Deja un comentario