Random IRC quote :      <chupameunpie> las pollas son deformes

Cobertura de código, herramientas y ejemplos

Hola a todos,

en este post os paso algunas herramientas para realizar cobertura de código que me han sido bastante útiles, así como su documentación y unos ejemplos que intentan ser bastante claros sobre como usarlas.

A la hora de buscar vulnerabilidades o hacer reversing de un ejecutable, sobretodo si es grande, es muy útil realizarle un análisis previo de cobertura de código que nos de una primera idea de por donde anda cada cosa. También nos será útil para probar nuestros fuzzers y ver si realmente nuestros datos están haciendo que se ejecute una buena parte del código.

Hay muchas otras herramientas de cobertura de código, algunas se apoyan en IDA u otros desensambladores/depuradores, otras en frameworks como PaiMei, otras funcionan por si mismas sin apoyarse en otras, etc… Así que seguramente no encontraréis conceptos nuevos por aquí que no hayáis visto por otros sitios.

Las herramientas que os paso son scripts en python principalmente para IDA (aunque también va un script para realizar la cobertura con pydbg).  Son fruto de varios cambios y mejoras que he ido metiendo según he ido necesitando. También he quitado cosas que pensé que iban a ser útiles y que finalmente no me ayudaban. Así que espero que aunque no aporte ideas nuevas, estas herramientas sean lo suficientemente prácticas para ser usadas en casos reales, ya que son fruto de eso mismo, de su uso en casos reales.

Aunque he usado bastante estos scripts no es seguro que no vayan a tener bugs o cosas que se puedan mejorar, asi que si alguien se anima a retocarlos o mejorarlos, adelante, y si luego nos pasa las mejoras, mucho mejor 😉

Respecto a los ejemplos he intentado que sean bastante claros, con varias imágenes, etc… para que se puedan ir siguiente bien y se vea el potencial que tiene realizar cobertura de código a la hora de hacer reversing.

Espero que lo disfrutéis y a alguien le sean útiles estas herramientas.

Documentación de las herramientas para cobertura de código

Este paquete de herramientas para cobertura de código comprende los siguientes scripts en python:

Getfuncs.py:

script para ida python que extrae a un fichero todas las direcciones de las funciones del ejecutable analizado (que después podrá ser usado con los demás scripts).

Getbblocks.py:

script para ida python que extrae a un fichero todas las direcciones de los basic blocks del ejecutable analizado (que después podrá ser usado con los demás scripts).

Funcs2bblocks.py:

script para ida python al que se le especifica un fichero de entrada con direcciones de funciones y extrae a otro fichero especificado todas las direcciones de los basic blocks.

Hits2ida.py:

script para ida python al que se le especifica un fichero de entrada con direcciones de hits de basic blocks. El script colorea y comenta el código en ida. Por defecto el color es 0xAAAAAA y el comentario es el nombre del fichero de hits. Es posible nombrar el fichero de esta forma:

Color.0xABCDEF.comment.hola.file.txt

El script tomará el dato después de “color.” para colorear con dicho valor, y el dato después de “comment.” para usarlo como comentario.

Cuando se ejecuta el script también preguntará si se desea abrir también todos los ficheros en el mismo directorio de manera que se pueda hacer un coloreado/comentado múltiple.

Cover.py:

usage: cover.py covermodname path2exe/attachid path2bblocks path2res

Script en python que utiliza el framework paimei (pydbg) para realizar la cobertura de código de un ejecutable dado.

En la línea de comandos debe especificarse el nombre del ejecutable (puede ser dll), el path al exe para ejecutar o id del proceso al que engancharse, el path al fichero de direcciones donde monitorizar los hits, y el path del fichero donde almacenar los resultados (los hits ocurridos).

Después de varias pruebas haciendo la cobertura de código con pydbg he podido ver que ocurren bastantes errores que no ocurren si se realiza con el depurador de ida (y un plugin de monitizaración de los hits).

Idacover.py:

script para ida python que recibe un fichero de direcciones, setea los breakpoints para estas direcciones, y se instala como hook de debug. Cuando se depure el ejecutable analizado, el script monitoriza los hits (eliminando los breakpoints según pasa por ellos).

Al finalizar la depuración el script pregunta si se desean realizar varias acciones:

  1. Coloreado de los Basic blocks de los hits.
  2. Coloreado de las funciones enteras de los hits.
  3. En ambos casos el valor numérico del color (0xAABBCC).
  4. Si se desea guardar los hits a un fichero.

Ccadd.py:

usage: ccadd.py file/dir1 file/dir2 …file/dirN fileresults

script para crear un fichero de direcciones nuevo con todas los direcciones en los ficheros especificados previamente en la línea de comandos.

Ccsub.py:

usage: ccsub.py fileorig subfile/dir1 … subfile/dirN fileresults

script para crear un fichero de direcciones nuevo quitando a fileorig todas las direcciones en los demás ficheros especificados en la línea de comandos.

Ccand.py:

usage: ccand.py file/dir1 file/dir2 … file/dirN fileresults

script para crear un fichero de direcciones nuevo solamente con las direcciones que estén en todos los ficheros especificados en la línea de comandos.

Ccxor.py:

usage: ccxor.py file/dir1 file/dir2 … file/dirN fileresults

script para crear un fichero de direcciones nuevo, para el cual primero se realiza un “and” (como con ccand.py) y luego se meten al fichero nuevo todas las direcciones que estén en algún fichero pero no en todos.

Ejemplos:

Caso 1. Cobertura de código del parser de HTML en mshtml.dll.

Introducción:

Para realizar esta prueba podríamos usar cualquier programa que tirara de mshtml.dll, por ejemplo, Internet Explorer.

image001

Ya que vamos a tener más versatilidad para tocar cosas, vamos a tirar de un script en python que use la dll para crear un diálogo que muestre una web:

import tempfile
import sys

# From urlmon.h
URL_MK_UNIFORM = 1

# Dialog box properties
DLG_OPTIONS = "dialogWidth:350px;dialogHeight:140px;center:yes;help:no"

# HTML content
HTML_DLG = """\
<html>
<head>
<title>ttttttttttttttttt</title>
</head>
<body>
bbbbbbbbbbbbbbbbbbbbbbbbb
</body>
</html>"
""

# Create temp HTML file
def create_html_file(html):
  """Create temporary HTML file from HTML template."""
  tmp_fd, url = tempfile.mkstemp(suffix=‘.html’)
  tmp_file = os.fdopen(tmp_fd, ‘w’)
  tmp_file.write(html)
  tmp_file.close()
  return url

def show_html_dialog(url, dlg_options):
  """Prepare moniker. Invoke ShowHTMLDialog."""

  # Helper for Python 2.6 and 3.0 compatibility
  if sys.version_info > (3, 0):
    wstr = str
  else:
    wstr = unicode

  moniker = POINTER(IUnknown)()
  windll.urlmon.CreateURLMonikerEx(0, wstr(url),
                                   byref(moniker), URL_MK_UNIFORM)

  windll.mshtml.ShowHTMLDialog(None, moniker, None, wstr(dlg_options), None)
if __name__ == ‘__main__’:
  show_html_dialog(create_html_file(HTML_DLG), DLG_OPTIONS)

Este script creará un diálogo en el que mostrará el código html que va en el propio script.

Estrategia a seguir:

  1. Primero vamos a hacer la cobertura a nivel de funciones. Extraemos las funciones con getfuncs.py a un fichero funcs.txt.
  2. Vamos a quitarnos bloques de código que se ejecutan siempre, inicializaciones, gestor de mensajes de la ventana, etc… Para ello primero realizamos la cobertura de código con un fichero de direcciones con todas las funciones del ejecutable y en el script metemos un trozo de código html muy básico:
    # HTML content
    HTML_DLG = """\
    <html>
    <head>
    <title>ttttttttttttttttt</title>
    </head>
    <body>
    bbbbbbbbbbbbbbbbbbbbbbbbb
    </body>
    </html>"
    ""
  3. Guardamos todos los hits sin colorear nada a un fichero, lo llamamos init.txt.
  4. Restamos al fichero con todas las funciones func.txt lo que se ha ejecutado con el html más básico usando ccsub.py y lo guardamos en noinit.txt
  5. Ahora podemos ir probando otros tags html haciendo la cobertura con noinit.txt, de manera que lo nuevo que se ejecute estará relacionado con los tags introducidos.

Desarrollo de la prueba:

Tras analizar mshtml.dll con IDA lanzamos el script getfuncs.py.

image002

Y nos guardamos todas las funciones a un fichero, funcs.txt:

image003

A continuación realizamos una primera cobertura de código para quitarnos del medio todas las funciones de inicialización, etc… y otras funciones al margén de las que van a gestionar los tags, que es lo que queremos localizar.

Para lanzar el script de python que hemos dicho antes, configuramos IDA:

image004

Posteriormente ejecutamos idacover.py:

image005

Nos va a pedir el fichero de direcciones sobre las que monitorizar. Usaremos todas las funciones, que previamente habíamos guardado en funcs.txt:

image006

Idacover.py pondrá breakpoints en todas las direcciones dadas y se instalará para monitorizar los hits. Cada vez que ocurra un breakpoint, lo quitará y se guardará la dirección.

image007

Empezamos la depuración y le dejamos que saque la web en el diálogo, movemos la ventana, pasamos el ratón, toquiteamos, etc… veremos como van saltando breakpoints a medida que se pasa por distintos sitios. La idea es quitarnos todo el código posible de en medio (por eso es interesante hacer cosas con la ventana, pasarla al fondo, traerla, moverla, sacar un trozo de la pantalla, etc…).

image008

Al finalizar el proceso se nos preguntarán varias cosas:

image009

En este caso no queremos colorear nada en IDA, no nos interesan esas zonas de código. Pero sí que salvaremos los hits para hacernos un subconjunto de funciones para continuar con la cobertura en las que no estén todas las que nos hemos quitado en el paso anterior.

Salvamos todas estas direcciones en un fichero init.txt:

image010

Restamos init.txt a funcs.txt y lo guardamos en noinit.txt:

image011

Para continuar con la cobertura de código usamos noinit.txt.

Quitamos todos los breakpoints de IDA y volvemos a ejecutar idacover.py, esta vez seleccionando noinit.txt.

Además vamos a modificar el trozo html:

# HTML content
HTML_DLG = """\
<html>
<head>
<title>ttttttttttttttttt</title>
</head>
<body>
bbbbbbbbbbbbbbbbbbbbbbbbb<font size=3>sksksksk</font>
</body>
</html>"
""

Como vemos es como el de antes, pero hemos añadido texto entre el tag <font>.

En la cobertura que vamos a realizar ahora no van a saltar ninguno de los hits de antes porque nos los hemos quitado ya (por eso usamos noinit.txt).

Repetimos los pasos de antes, ejecutamos el programa y esperamos a ver que nuevos hits ocurren. Cuando acaba la ejecución nos pregunta si queremos colorear el código:

image012

Le decímos que sí queremos aplicar los colores, le ponemos el valor del color, y le decímos que coloree toda la función donde esté la dirección del hit (ya que hemos ido haciendo la cobertura por funciones).

Si queremos podemos salvar los hits a un fichero también (hits.font.txt por ejemplo).

Vemos algunas de las zonas coloreadas:

CFontElement::CreateElement:

image013

CFontElement:: CFontElement:

image014

image015

CFontElement::ApplyDefaultFormat:

image016

ApplyFontSize:

image017

Vemos como el tag que hemos añadido: <font size=3>sksksksk</font>

Ha provocado que se ejecuten nuevas funciones:

CFontElement::CreateElement

CFontElement:: CFontElement

CFontElement::ApplyDefaultFormat

ApplyFontSize

Etc…

En este caso concreto tenemos los símbolos de microsoft y no nos es tán útil los resultados de la cobertura de código, pero en otros casos puede darnos una cantidad de información muy útil.

Repitiendo este proceso con otros tags podemos ir cubriendo más zonas de código y teniendo una idea aproximada de donde buscar cuando queramos encontrar algo. Si tenemos un casque con un documento html generado aleatoriamente por un fuzzer, analizando el casque, y con la información que saquemos de la cobertura de código, también podremos tener una idea más concreta de que parte del documento ocasionó el casque.

Opcionalmente, si queremos información más detallada, podemos convertir las funciones en las que ocurrieron hits para <font> a basic blocks con funcs2bblocks.py. Una vez convertidas a basic blocks, usamos el resultado para repetir el code coverage de manera que obtendremos lo mismo pero sabremos exactamente los basic blocks que se han ejecutado en la función y cuales no.

Caso 2. Localización del checkeo de password de Filezilla Server.

Introducción:

En esta prueba vamos a localizar la zona en la que se checkea el password en FileZilla Server mediante cobertura de código.

Estrategia a seguir:

  1. Lanzamos el servidor y nos attacheamos, conectamos a él pero no introducimos password, etc… es decir intentamos quitarnos todas las funciones posibles del medio que no tengan que ver con el checkeo de password.
  2. Lanzamos el servidor, nos attacheamos, y esta vez nos logeamos correctamente, coloreando las partes por las que se pasa y guardándonos los hits.
  3. Lanzamos de nuevo el servidor y nos attacheamos, pero esta vez nos logeamos con password no válido y vemos los nuevos hits, los coloreamos también.

Desarrollo de la prueba:

En primer lugar vamos a obtener directamente todos los basic blocks con getbblocks.py.

image018

Guardamos las direcciones de los basic blocks y lanzamos idacover.py con ellos.

image019

Una vez seteados todos los breakpoints:

image020

Lanzamos el servidor de ftp y vamos “trasteando” sin llegar a hacer nada que tenga que ver con el checkeo de password, para quitarnos de en medio todas las funciones que podamos que no nos interesen. Introducimos el usuario, pero no llegamos a introducir el password ni bueno ni malo. Y paramos el proceso quitando todos esos hits de en medio.

image021

Con esto practicamente nos hemos quedado con la zona de checkeo de password. Ahora lanzamos el server e introducimos la clave buena.

image022

E inmediatamente paramos, esta vez cuando nos pregunte si deseamos colorear, coloreamos de verde por ejemplo (esta vez no le decimos que coloree la función entera, solo el basic block del hit). Repetimos el mismo proceso para el password inválido y coloreamos de otro color los basic blocks.

Vamos comparando y revisando las funciones coloreadas y vemos los sitios por los que ha pasado para el caso válido y caso inválido:

image023

La zona verde es el recorrido para password válido y la zona azul para inválido.

image024

Si vamos exactamente al punto donde se bifurca la ejecución para password bueno y malo:

image025

Ahora tenemos los símbolos y vemos claramente que esa es la función que comprueba el pass y ese jnz es el salto que bifurca si es pass bueno o malo (de hecho si metemos pass malo y forzamos a saltar para el otro lado nos logearemos bien). Pero en un caso en que no tuvieramos los símbolos más o menos podríamos intuir por donde anda toda la historia del checkeo.

Código fuente:

Getfuncs.py:

from idaapi import *
from idc import *
from idautils import *

fs = AskFile(1, "*.txt", "Select file to save basic block start addresses…")
ffs=""+fs
print "saving basic blocks to "+ffs
f = open(ffs,"w+b")

ea=0
for ea in range(MinEA(),MaxEA()):
  if get_func(ea):
    break

f.write(("0x%08x"%get_imagebase())+",modbase\n")

for ea in Functions(ea, MaxEA()):
    f.write(("0x%08x"%ea)+"\n")

f.close()

Getbblocks.py:

from idaapi import *
from idc import *
from idautils import *

###############################################
def _branches_from (ea):
    »
    Enumerate and return the list of branches from the supplied address, *including* the next logical instruction.
    Part of the reason why we even need this function is that the "flow" argument to CodeRefsFrom does not appear
    to be functional.

    @type  ea: DWORD
    @param ea: Effective address of instruction to enumerate jumps from.

    @rtype:  List
    @return: List of branches from the specified address.
    ‘»

    if is_call_insn(ea):
        return []

    xrefsgen = CodeRefsFrom(ea, 1)
    xrefs=[]
    for xref in xrefsgen:
      xrefs.append(xref)

    # if the only xref from ea is next ea, then return nothing.
    if len(xrefs) == 1 and xrefs[0] == NextNotTail(ea):
        xrefs = []

    return xrefs

###############################################
def _branches_to (ea):
    »
    Enumerate and return the list of branches to the supplied address, *excluding* the previous logical instruction.
    Part of the reason why we even need this function is that the "flow" argument to CodeRefsTo does not appear to
    be functional.

    @type  ea: DWORD
    @param ea: Effective address of instruction to enumerate jumps to.

    @rtype:  List
    @return: List of branches to the specified address.
    ‘»

    xrefs        = []
    prev_ea      = PrevNotTail(ea)
    prev_code_ea = prev_ea

    if prev_ea!=0xffffffff:
        while not isCode(GetFlags(prev_code_ea)):
            prev_code_ea = PrevNotTail(prev_code_ea)

    for xref in CodeRefsTo(ea, 1):
        if not is_call_insn(xref) and xref not in [prev_ea, prev_code_ea]:
            xrefs.append(xref)

    return xrefs
###############################################

fs = AskFile(1, "*.txt", "Select file to save basic block start addresses…")
ffs=""+fs
print "saving basic blocks to "+ffs
f = open(ffs,"w+b")

ea=0
for ea in range(MinEA(),MaxEA()):
  if get_func(ea):
    break

all_basic_blocks = []

for ea in Functions(ea, MaxEA()):

  print "Function:" + hex(ea)

  function_instructions = list()
  func_iter = func_tail_iterator_t(get_func(ea))
  status = func_iter.main()

  while status:
    chunk = func_iter.chunk()
    for head in Heads(chunk.startEA, chunk.endEA):
        if isCode(getFlags(head)):
            function_instructions.append(head)
    status = func_iter.next()

  basic_blocks = set()

  l=[ea]
  basic_blocks.update(l)

  lastwasret=0

  for address in function_instructions:
    if lastwasret:
      l=[address]
      basic_blocks.update(l)
    else:
      refs_from=_branches_from(address)
      refs_to=_branches_to(address)
      if len(refs_from):
        basic_blocks.update(refs_from)
      if len(refs_to):
        l=[address]
      basic_blocks.update(l)

    if is_ret_insn(ea):
      lastwasret=1

  basic_blocks = filter(lambda i: i in function_instructions, basic_blocks)

  for bb in basic_blocks:
    #set_item_color(bb, 0xffff00)
    all_basic_blocks.append(bb)

f.write(("0x%08x"%get_imagebase())+",modbase\n")

all_basic_blocks.sort()
lastone=-1
for bb in all_basic_blocks:
  if bb!=lastone:
    f.write(("0x%08x"%bb)+"\n")
    lastone=bb
  else:
    print "*"

f.close()

Funcs2bblocks.py:

from idaapi import *
from idc import *
from idautils import *

###############################################
def _branches_from (ea):
    »
    Enumerate and return the list of branches from the supplied address, *including* the next logical instruction.
    Part of the reason why we even need this function is that the "flow" argument to CodeRefsFrom does not appear
    to be functional.

    @type  ea: DWORD
    @param ea: Effective address of instruction to enumerate jumps from.

    @rtype:  List
    @return: List of branches from the specified address.
    ‘»

    if is_call_insn(ea):
        return []

    xrefsgen = CodeRefsFrom(ea, 1)
    xrefs=[]
    for xref in xrefsgen:
      xrefs.append(xref)

    # if the only xref from ea is next ea, then return nothing.
    if len(xrefs) == 1 and xrefs[0] == NextNotTail(ea):
        xrefs = []

    return xrefs

###############################################
def _branches_to (ea):
    »
    Enumerate and return the list of branches to the supplied address, *excluding* the previous logical instruction.
    Part of the reason why we even need this function is that the "flow" argument to CodeRefsTo does not appear to
    be functional.

    @type  ea: DWORD
    @param ea: Effective address of instruction to enumerate jumps to.

    @rtype:  List
    @return: List of branches to the specified address.
    ‘»

    xrefs        = []
    prev_ea      = PrevNotTail(ea)
    prev_code_ea = prev_ea

    if prev_ea!=0xffffffff:
        while not isCode(GetFlags(prev_code_ea)):
            prev_code_ea = PrevNotTail(prev_code_ea)

    for xref in CodeRefsTo(ea, 1):
        if not is_call_insn(xref) and xref not in [prev_ea, prev_code_ea]:
            xrefs.append(xref)

    return xrefs
###############################################

funcsfname = AskFile(0, "*.*", "Select functions file.")
fs = AskFile(1, "*.txt", "Select file to save basic block start addresses…")

all_basic_blocks = []
all_funcs = []

f=open(funcsfname)
s=f.readline()
modbase=int(s[0:10],16)
while 1:
  s=f.readline()
  if s=="":
    break
  print s[0:10]
  if isCode(GetFlags(int(s[0:10],16))):
    all_funcs.append(int(s[0:10],16))
f.close()

ffs=""+fs
print "saving basic blocks to "+ffs
f = open(ffs,"w+b")

for ea in all_funcs:

  print "Function:" + hex(ea)

  function_instructions = list()
  func_iter = func_tail_iterator_t(get_func(ea))
  status = func_iter.main()

  while status:
    chunk = func_iter.chunk()
    for head in Heads(chunk.startEA, chunk.endEA):
        if isCode(getFlags(head)):
            function_instructions.append(head)
    status = func_iter.next()

  basic_blocks = set()

  l=[ea]
  basic_blocks.update(l)

  lastwasret=0

  for address in function_instructions:
    if lastwasret:
      l=[address]
      basic_blocks.update(l)
    else:
      refs_from=_branches_from(address)
      refs_to=_branches_to(address)
      if len(refs_from):
        basic_blocks.update(refs_from)
      if len(refs_to):
        l=[address]
      basic_blocks.update(l)

    if is_ret_insn(ea):
      lastwasret=1

  basic_blocks = filter(lambda i: i in function_instructions, basic_blocks)

  for bb in basic_blocks:
    #set_item_color(bb, 0xffff00)
    all_basic_blocks.append(bb)

f.write(("0x%08x"%get_imagebase())+",modbase\n")

all_basic_blocks.sort()
lastone=-1
for bb in all_basic_blocks:
  if bb!=lastone:
    f.write(("0x%08x"%bb)+"\n")
    lastone=bb
  else:
    print "*"

f.close()

Hits2ida.py:

from idaapi   import *
from idautils import *
from idc      import *

import os

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

def _branches_from (ea):
    »
    Enumerate and return the list of branches from the supplied address, *including* the next logical instruction.
    Part of the reason why we even need this function is that the "flow" argument to CodeRefsFrom does not appear
    to be functional.

    @type  ea: DWORD
    @param ea: Effective address of instruction to enumerate jumps from.

    @rtype:  List
    @return: List of branches from the specified address.
    ‘»

    if is_call_insn(ea):
        return []

    xrefsgen = CodeRefsFrom(ea, 1)
    xrefs=[]
    for xref in xrefsgen:
      xrefs.append(xref)

    # if the only xref from ea is next ea, then return nothing.
    if len(xrefs) == 1 and xrefs[0] == NextNotTail(ea):
        xrefs = []

    return xrefs

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

def _branches_to (ea):
    »
    Enumerate and return the list of branches to the supplied address, *excluding* the previous logical instruction.
    Part of the reason why we even need this function is that the "flow" argument to CodeRefsTo does not appear to
    be functional.

    @type  ea: DWORD
    @param ea: Effective address of instruction to enumerate jumps to.

    @rtype:  List
    @return: List of branches to the specified address.
    ‘»

    xrefs        = []
    prev_ea      = PrevNotTail(ea)
    prev_code_ea = prev_ea

    while not isCode(GetFlags(prev_code_ea)):
        prev_code_ea = PrevNotTail(prev_code_ea)

    for xref in CodeRefsTo(ea, 1):
        if not is_call_insn(xref) and xref not in [prev_ea, prev_code_ea]:
            xrefs.append(xref)

    return xrefs
###############################################

def dofile(dir,fname):
  color=0xAAAAAA
  comment=fname
  lfname = fname.split(".")

  if len(lfname)>=2:
    if lfname[0].lower()=="color":
      color=int(lfname[1],16)
    if lfname[0].lower()=="comment":
      comment=lfname[1]
  if len(lfname)>=4:
    if lfname[2].lower()=="color":
      color=int(lfname[3],16)
    if lfname[2].lower()=="comment":
      comment=lfname[3]

  f=open(dir+"\\"+fname)
  while 1:
    s=f.readline()
    if s=="":
      break
    eip=int(s,16)
    print "0x%08x"%eip
    while 1:
      SetColor(eip, CIC_ITEM, color)
      MakeComm(eip, comment)
      eip=NextHead(eip,MaxEA())
      refs_from=_branches_from(eip)
      refs_to=_branches_to(eip)
      if len(refs_from) or len(refs_to) or is_ret_insn(eip):
        SetColor(eip, CIC_ITEM, color)
        MakeComm(eip, comment)
        break
      if eipMaxEA():
        break
  f.close()

  return

fname = AskFile(0, "*.*", "Select hits file.")

allf = AskYN(1,"Do you want to load all files in the same directory?")

if allf:
  for fnamex in os.listdir(os.path.dirname(fname)):
    dofile(os.path.dirname(fname),fnamex)
else:
  dofile(os.path.dirname(fname),os.path.basename(fname))

cover.py:

import sys
import utils
import pida
import pydbg

###############################################
def handler_access_violation (dbg):
  #log crash
  dbg.terminate_process()
###############################################
def handler_breakpoint (dbg):
  global hits
  print hex(dbg.context.Eip)
  hits.append(dbg.context.Eip)
  return pydbg.defines.DBG_CONTINUE
###############################################
def handler_load_dll (dbg):
  global covermodname
  global bpsset
  global basicblocks
  global modbase
  global reloc
  #last_dll = dbg.get_system_dll(-1)
  #print last_dll.name
  #print last_dll.path
  #print last_dll.base
  if not bpsset:
    for mod32 in dbg.iterate_modules():
      if mod32.szModule.lower()==covermodname.lower():
        bpsset=1
        reloc=mod32.modBaseAddr-modbase
        if reloc:
          for i in range(0,len(basicblocks)):
            basicblocks[i]+=reloc
        #print map(hex,basicblocks)
        dbg.bp_set(basicblocks, restore=False)
  return pydbg.defines.DBG_CONTINUE
###############################################
def handler_create_process (dbg):
  return pydbg.defines.DBG_CONTINUE
###############################################
def handler_create_thread (dbg):
  return pydbg.defines.DBG_CONTINUE
###############################################

if len(sys.argv)<5:
  print "usage: cover.py covermodname path2exe/attachid path2bblocks path2res"
  sys.exit(0)

reloc=0
hits=[]
bpsset=0
basicblocks=[]
modbase=0
covermodname = sys.argv[1]
path2exe = sys.argv[2]
path2bblocks = sys.argv[3]
path2res = sys.argv[4]

f=open(path2bblocks)
s=f.readline()
modbase=int(s[0:10],16)
while 1:
  s=f.readline()
  if s=="":
    break
  basicblocks.append(int(s[0:10],16))
f.close()

attachid=0
try:
  attachid = int(path2exe)
  path2exe = None
except:
  attachid=0

print "attachid="+str(attachid)

dbg = pydbg.pydbg()
dbg.set_callback(pydbg.defines.EXCEPTION_BREAKPOINT,       handler_breakpoint)
dbg.set_callback(pydbg.defines.LOAD_DLL_DEBUG_EVENT,       handler_load_dll)
dbg.set_callback(pydbg.defines.EXCEPTION_ACCESS_VIOLATION, handler_access_violation)
dbg.set_callback(pydbg.defines.CREATE_PROCESS_DEBUG_EVENT, handler_create_process)
dbg.set_callback(pydbg.defines.CREATE_THREAD_DEBUG_EVENT,  handler_create_thread)

if not attachid:
    dbg.load(path2exe, "")
else:
    dbg.attach(attachid)

dbg.run()

f=open(path2res,"w+b")
f.write(("0x%08x"%modbase)+",modbase\n")
hits.sort()
for hit in hits:
  hit=hit-reloc
  s="0x%08x"%hit
  f.write(s+"\n")
f.close()

Idacover.py:

from idaapi import *

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

def _branches_from (ea):
    if is_call_insn(ea):
        return []
    xrefsgen = CodeRefsFrom(ea, 1)
    xrefs=[]
    for xref in xrefsgen:
      xrefs.append(xref)
    if len(xrefs) == 1 and xrefs[0] == NextNotTail(ea):
        xrefs = []
    return xrefs

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

def _branches_to (ea):
    xrefs        = []
    prev_ea      = PrevNotTail(ea)
    prev_code_ea = prev_ea
    while not isCode(GetFlags(prev_code_ea)):
        prev_code_ea = PrevNotTail(prev_code_ea)
    for xref in CodeRefsTo(ea, 1):
        if not is_call_insn(xref) and xref not in [prev_ea, prev_code_ea]:
            xrefs.append(xref)
    return xrefs

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

class MyDbgHook(DBG_Hooks):

    def dbg_process_start(self, pid, tid, ea, name, base, size):
        global path2bblocks
        global hits
        hits=[]
        print "process:"+name
        if AskYN(1,"Do you want to restore breakpoints?"):
          f=open(path2bblocks)
          s=f.readline()
          modbase=int(s[0:10],16)
          while 1:
            s=f.readline()
            if s=="":
              break
            print s[0:10]
            add_bpt(int(s[0:10],16),0,BPT_SOFT)
            enable_bpt(int(s[0:10],16),True)
          f.close()
        return 0

    def dbg_process_attach(self, pid, tid, ea, name, base, size):
        print "process attach"
        return self.dbg_process_start(pid, tid, ea, name, base, size)

    def dbg_process_exit(self, pid, tid, ea, code):
        global hits
        global docolor
        global color
        global colorfuncs
        docolor=False
        color=0xff00ff

        if AskYN(1,"Do you want to apply colors?"):

          docolor=True
          color=AskLong(0xff00ff, "Please, introduce color hexadecimal value")

          if AskYN(1,"Do you want to color entire functions?"):
            colorfuncs=True
          else:
            colorfuncs=False

          if colorfuncs:
            for ea in hits:
              prevfunc=PrevFunction(ea)
              if prevfunc!=0xffffffff:
                funcend=FindFuncEnd(ea)
                if funcend!=0xffffffff and docolor:
                  eip=prevfunc
                  while eip=prevfunc and eip!=0xffffffff:
                    SetColor(eip, CIC_ITEM, color)
                    eip=NextHead(eip,MaxEA())
                    if eip==0xffffffff:
                      break
                    if eipMaxEA():
                      break
          else:
            for eip in hits:
              while 1:
                SetColor(eip, CIC_ITEM, color)
                eip=NextHead(eip,MaxEA())
                if eip==0xffffffff:
                  break
                refs_from=_branches_from(eip)
                refs_to=_branches_to(eip)
                if len(refs_from) or len(refs_to) or is_ret_insn(eip):
                  SetColor(eip, CIC_ITEM, color)
                  break
                if eipMaxEA():
                  break

        if AskYN(1,"Do you want to save hits?"):
          path2res = AskFile(1, "*.*", "Select results file.")
          f=open(path2res,"w+b")
          hits.sort()
          f.write(("0x%08x"%get_imagebase())+",modbase\n")
          for hit in hits:
            s="0x%08x"%hit
            f.write(s+"\n")
          f.close()
        return 0

    def dbg_process_detach(self, pid, tid, ea):
      return self.dbg_process_exit(pid, tid, ea, 0)

    def dbg_library_load(self, pid, tid, ea, name, base, size):
        print "loaded:"+name
        return 0

    def dbg_bpt(self, tid, ea):
        global hits
        global docolor
        global color
        print hex(ea)
        hits.append(ea)
        del_bpt(ea)
        continue_process()
        return 0

    def dbg_trace(self, tid, ea):
        return 0

    def dbg_step_into(self):
        return 0

    def dbg_step_over(self):
        return 0
try:
    if debughook:
        print "Removing previous hook …"
        debughook.unhook()
except:
    pass

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

# Install the debug hook
debughook = MyDbgHook()
debughook.hook()
debughook.steps = 0

hits=[]
colorfuncs=False
docolor=False
color=0xff00ff

path2bblocks = AskFile(0, "*.*", "Select basic blocks file.")

f=open(path2bblocks)
s=f.readline()
modbase=int(s[0:10],16)
while 1:
  s=f.readline()
  if s=="":
    break
  print s[0:10]
  if isCode(GetFlags(int(s[0:10],16))):
    add_bpt(int(s[0:10],16),0,BPT_SOFT)
    enable_bpt(int(s[0:10],16),True)
f.close()

# Start debugging
run_requests()

Ccadd.py:

import sys
import os

def readfilehits(fname,hits):
  f=open(fname)
  while 1:
    s=f.readline()
    if s=="":
      break
    #if int(s[0:10],16) not in hits:
    hits.append(int(s[0:10],16))
  f.close()
  return hits

def readhits(path,hits):
  if os.path.isfile(path):
    hits=readfilehits(path,hits)
  if os.path.isdir(path):
    l=os.listdir(path)
    for fname in l:
      hits=readfilehits(path+"\\"+fname,hits)
  return hits

def savehits(fname,hits):
  hits.sort()
  f=open(fname,"w+b")
  for hit in hits:
    f.write(("0x%08x"%hit)+"\n")
  f.close()

sum=[]

if len(sys.argv)<4:
  print "usage: ccadd.py file/dir1 file/dir2 … file/dirN fileresults"
  sys.exit(0)

for i in range(1,len(sys.argv)-1):
  sum=readhits(sys.argv[i],sum)

savehits(sys.argv[len(sys.argv)-1],sum)

print "Done."

Ccsub.py:

import sys
import os

def readfilehits(fname,hits):
  f=open(fname)
  while 1:
    s=f.readline()
    if s=="":
      break
    #if int(s[0:10],16) not in hits:
    hits.append(int(s[0:10],16))
  f.close()
  return hits

def readhits(path,hits):
  if os.path.isfile(path):
    hits=readfilehits(path,hits)
  if os.path.isdir(path):
    l=os.listdir(path)
    for fname in l:
      hits=readfilehits(path+"\\"+fname,hits)
  return hits

def savehits(fname,hits):
  hits.sort()
  f=open(fname,"w+b")
  for hit in hits:
    f.write(("0x%08x"%hit)+"\n")
  f.close()

orig=[]

if len(sys.argv)<4:
  print "usage: ccsub.py fileorig subfile/dir1 … subfile/dirN fileresults"
  sys.exit(0)

print "1"

orig=readhits(sys.argv[1],orig)

print "2"

for i in range(2,len(sys.argv)-1):
  sub=[]
  sub=readhits(sys.argv[i],sub)
  for e in sub:
    while e in orig:
      orig.remove(e)

print "3"

savehits(sys.argv[len(sys.argv)-1],orig)

print "Done."

Ccand.py:

import sys
import os

def readfilehits(fname,hits):
  f=open(fname)
  while 1:
    s=f.readline()
    if s=="":
      break
    #if int(s[0:10],16) not in hits:
    hits.append(int(s[0:10],16))
  f.close()
  return hits

def readhits(path,hits):
  if os.path.isfile(path):
    hits=readfilehits(path,hits)
  if os.path.isdir(path):
    l=os.listdir(path)
    for fname in l:
      hits=readfilehits(path+"\\"+fname,hits)
  return hits

def savehits(fname,hits):
  hits.sort()
  f=open(fname,"w+b")
  for hit in hits:
    f.write(("0x%08x"%hit)+"\n")
  f.close()

first=[]
all=[]
res=[]

if len(sys.argv)<4:
  print "usage: ccand.py file/dir1 file/dir2 … file/dirN fileresults"
  sys.exit(0)

first=readhits(sys.argv[1],first)

for i in range(1,len(sys.argv)-1):
  if os.path.isfile(sys.argv[i]):
    cur=[]
    cur=readhits(sys.argv[i],cur)
    all.append(cur)
  if os.path.isdir(sys.argv[i]):
    for fname in os.listdir(sys.argv[i]):
      cur=[]
      cur=readhits(sys.argv[i]+"\\"+fname,cur)
      all.append(cur)

for e in first:
  valid=1
  for l in all:
    if e not in l:
      valid=0
      break
  if valid:
    res.append(e)

savehits(sys.argv[len(sys.argv)-1],res)

print "Done."

Ccxor.py:

import sys
import os

def readfilehits(fname,hits):
  f=open(fname)
  while 1:
    s=f.readline()
    if s=="":
      break
    #if int(s[0:10],16) not in hits:
    hits.append(int(s[0:10],16))
  f.close()
  return hits

def readhits(path,hits):
  if os.path.isfile(path):
    hits=readfilehits(path,hits)
  if os.path.isdir(path):
    l=os.listdir(path)
    for fname in l:
      hits=readfilehits(path+"\\"+fname,hits)
  return hits

def savehits(fname,hits):
  hits.sort()
  f=open(fname,"w+b")
  for hit in hits:
    f.write(("0x%08x"%hit)+"\n")
  f.close()

first=[]
all=[]
resand=[]
res=[]

if len(sys.argv)<4:
  print "usage: ccxor.py file/dir1 file/dir2 … file/dirN fileresults"
  sys.exit(0)

first=readhits(sys.argv[1],first)

for i in range(1,len(sys.argv)-1):
  if os.path.isfile(sys.argv[i]):
    cur=[]
    cur=readhits(sys.argv[i],cur)
    all.append(cur)
  if os.path.isdir(sys.argv[i]):
    for fname in os.listdir(sys.argv[i]):
      cur=[]
      cur=readhits(sys.argv[i]+"\\"+fname,cur)
      all.append(cur)

for e in first:
  valid=1
  for l in all:
    if e not in l:
      valid=0
      break
  if valid:
    resand.append(e)

for l in all:
  for e in l:
    if e not in res and e not in resand:
      res.append(e)

savehits(sys.argv[len(sys.argv)-1],res)

print "Done."

12 Comentarios para “Cobertura de código, herramientas y ejemplos”

  1. Comment por matalaz | 09/15/09 at 8:39 am

    Esta de puta madre el artículo. Muy guapo!

  2. Comment por Mario Ballano | 09/15/09 at 10:42 am

    Muy bueno el articulo si señor!! :)))

  3. Comment por erg0t | 09/15/09 at 1:37 pm

    Muy bueno y sencillo de llevar. No es facil toparse con este tipo de articulos hoy en dia y menos en castellano 🙂

  4. Comment por ruben | 09/15/09 at 1:57 pm

    Muy muy bueno y útil Javi!

    Me ha venido a la cabeza un .idc que en ocasiones es útil y que se aprovecha de la potencia del windbg, by Cody Pierce.

    http://dvlabs.tippingpoint.com/blog/2008/07/17/mindshare-hit-tracing-in-windbg

  5. Comment por javi | 09/16/09 at 4:26 am

    hola Rubén,

    ese idc lo he usado algunas veces y sí que es bastante útil, lo que pasa que windbg hace tracing paso a paso, y es muy lento. No se si windbg cuando va haciendo este tipo de tracing se le puede configurar para que cuando pase por una instrucción no vuelva a parar en ella. Va todo el rato haciendo steps, aunque ya sepas que en tal dirección hay un hit, y al final no hay manera. InmmunityDbg y Olly también tienen hittracing, y además una vez pasan por una zona no vuelven a parar y va más rápido (y creo que ponen los breakpoints por basic blocks además), pero luego no se como sacar un log de los hits.

    Ese idc combinado con windbg a mi me parece muy útil al analizar un virus, que tampoco se van a ejecutar una burrada de instrucciones, y luego el log lo puedes comparar con un log de lo que haga un emulador por ejemplo, para ver si se emula todo como debería.

  6. Comment por erg0t | 09/16/09 at 1:24 pm

    Esto que pregunto esta fuera del tema pero quiero aprovechar para sacarme la duda. Que tal va el windbg haciendo tracing step a step cuando se encuentra algun trick anti-debug como un popf que active la trap flag? Con el olly haciendo tracing no sabe que hacer cuando se encuentra con ese caso, pero haciendo un run no parece afectarle.

  7. Comment por javi | 09/17/09 at 4:35 am

    hola erg0t,

    pues con este código:

    01012475 669c pushf
    01012477 6658 pop ax
    01012479 660d0001 or ax,100h
    0101247d 6650 push ax
    0101247f 669d popf
    01012481 90 nop
    01012482 90 nop
    01012483 90 nop

    Si vas paso a paso parece que van todos bien. Si pones un bp en 0x01012483 y ejecutas el olly se queda flipado como tú decías, el IDA te pregunta si le pasas el control a la aplicación, si le dices que no, tira bien, y el windbg y el inmmunitydbg se paran en 0x01012482 por el trap flag.

  8. Comment por erg0t | 09/17/09 at 2:32 pm

    A mi el olly me funcionaba justamente en el caso contrario, cuando usaba bp y run, cuando hacia tracing step a step era cuando no sabia como seguir (puede que tenga alguna relación el uso de plugins como phantom). Otra cosa, creo que el comportamiento correcto seria pasar la excepción a la aplicación, es decir el trick consiste en instalar un handler para manejar el trap y si el debugger se equivoca va a seguir de largo si ejecutar el handler. Por lo que dices el que mejor se comporta es el debugger del IDA, que te permite pasar el control a la aplicación.

  9. Comment por javi | 09/18/09 at 5:05 am

    Ah claro.. el truco es al revés. El olly lo he probado otra vez, ayer debí probar mal, sí que salta la excepción tanto si vas por steps como si haces run. Lo que pasa que he probado teniendo instalado el Phantom y el IDAStealth.

    Para Windbg he visto esta extensión de metasploit:

    http://blog.metasploit.com/2008/08/byakugan-windbg-plugin-released.html

    No la he probado, entre otras cosas pone que tiene:

    «mushishi – a framework (with examples) for the detection and defeat of anti-debugging methods.»

  10. Comment por Tarakito | 09/23/09 at 8:13 am

    Bueno, solo decir que al copiar pegar los scripts algunos caracteres de los mismos estan cambiados y dan error al correrlos y hay que modificarlos (la proxima vez un zip no vendria mal :p)

    Y que con el ida 5.2 + idapython 0.9.0 + python 2.5 no funcionan.

    BTW: muy interesante 😉 algo del tema habia leido en el «Reverse Engineering Code with IDA Pro (Paperback)» pero aqui esta muy/mas claro.

    Un saludo

  11. Comment por nexus148 | 09/27/09 at 6:47 am

    Hola Tarakito

    Yo tengo tu misma configuracion. Para resolver el problema de copiar y pegar, simplemente he grabado el fichero en formato «UTF-8» y lo reconoce.
    Al executar el script «Getfuncs.py» me sale el siguiente error:

    *************************
    File «C:/Downloads/48Bits/Scripts/Getfuncs.py», line 1, in
    Code (python)
    NameError: name ‘Code’ is not defined
    **************************

    ¿Es debido a la version del ida?

    Un salduo.

  12. Comment por raimon | 11/06/09 at 7:16 pm

    menuda chapa amigo, menuda chapa.

Se han cerrado los comentarios