loadmap.py : importer un fichier map depuis IDA vers Immunity Debugger

Wed 16 January 2008 by JB

J'ai souvent l'habitude de travailler sur IDA, puis de charger Immunity Debugger après que l'analysea été un peu dégrossie. L'analyse d'ImmDbg est bonne, mais ne vaut pas celle d'IDA.Les noms de variables et de fonctions détectées par IDA, ajoutés par l'utilisateur ou retrouvés depuisun fichier de symboles ou une signature peuvent être exportées dans un fichier MAP. MapConv, unplugin C pour OllyDbg, charge ces fichiers et modifie le nom des fonctions détectées dans OllyDbg. Ilest facilement recompilable pour Immunity Debugger, mais ne fonctionne qu'avec l'exécutable debuggé(pas les modules chargés) et ne charge que les noms trouvés dans la première section du programme(la section de code, en général).

J'ai donc développé un nouveau plugin ajoutant aussi les noms trouvés dans les autres sections,en exploitant l'interface en Python, plus souple, d'Immunity Debugger.

Un fichier MAP est composé de deux parties. Le début contient des donnéessur les sections du programme chargé :

Start         Length     Name                   Class
0001:00000000 000004709H .text                  CODE
0002:00000000 0000001D4H .data                  DATA
0003:00000000 0000003F0H .rsrc                  DATA
0004:00000000 00000028EH .reloc                 DATA

Il contient le nom de chaque segment, sa longueur une fois mappé (VirtualSize)et son type (CODE ou DATA).

La deuxième partie contient les noms des variables ou fonctions retrouvées, et leuradresse relative au début de la section.

Address         Publics by Value
0001:0000019B       byte_62DC119B
0001:000001A8       aMicrosoftSansS
0001:000001D4       aFiledescriptio
0001:000001F4       aDmr
0001:00000200       word_62DC1200
...
0002:000000E0       _g_FontIDCache
0002:000000E4       dword_62DC60E4
0002:000001D0       _g_pCurrentAvailablePos

Le plugin doit donc :

  • parser le fichier : récupérer le nom, l'adresse et le segment de chaque variable
  • ajouter ce nom dans le listing d'ImmDbg

Ces informations sont récupérées à partir d'une expression régulière. Il ne faut pasprendre en compte les informations de la première partie du fichier. Ainsi, 0001:00000000 000004709H ne doit pas être matché. Les noms de variablesne peuvent pas commencer par des nombres, on crée l'expression régulière en fonction :

'\s+([\dA-F]{4}):([\dA-F]{8})\s+([^0-9][\S]+)'

Les informations sur les sections du fichier ne peuvent pas être lues depuis ImmLib.Debugger, la classe Python contrôlant le debugger. On a besoin deconnaître l'adresse de début de chaque section. J'utilise pour cela pefile d'EroCarrera, récemment intégré à ImmDbg. Le fichier est alors lu depuis le disque, et nondans la mémoire du processus.

Le reste est assez simple, et certainement trivial à partir d'un exemple : Comment charger 0001:0000019B       byte_62DC119B ?

  • On récupère le numéro de segment : 0001 et on récupère l'adresse à laquelle il sera mappé à l'aide de pefile ;
  • On ajoute l'offset de la variable : 0000019B ;
  • On ajoute l'adresse à laquelle est mappé le module (ImageBase), récupérable avec ImmLib ;
  • L'adresse trouvée est renommée en byte_62DC119B avec la fonction setLabel.

Reste à ajouter un peu de code pour retrouver les paramètres d'entrée : le nom du fichier map et le nomdu module associé. Ce dernier champ n'est pas obligatoire, on prend le module principal (le fichier exécutabledebuggé) s'il n'est pas spécifié :

!loadmap -f <map_file> [-m <module_name>]

Un test avec kernel32.map

  • Avant de charger le fichier map :

k32.png

  • Le fichier map est chargé :
!loadmap -f c:\\kernel32.map -m kernel32.dll

k32-map.png

Code source final

__VERSION__ = "1.3"
DESC = """Load a map file generated by IDA"""

import immlib
import re
import pefile
import getopt

def usage(imm):
    imm.log("!loadmap -f <map_file> [-m <module_name>]")
    imm.log(" ex: !loadmap -f c:\myprog.map")
    imm.log(" ex: !loadmap -f c:\user32.map -m USER32.DLL")

def main(args):
    imm = immlib.Debugger()

    if not args:
        return usage(imm)
    try:
        opts, argo = getopt.getopt(args, "m:f:")
    except getopt.GetoptError:
        usage(imm)
        return "Incorrect number of arguments (No args)"

    modulename = imm.getDebuggedName()
    s = None
    filename = None

    for o, a in opts:
        if o == "-m":
            modulename = a
        elif o == "-f":
            filename = a

    if filename is None:
        usage(imm)
        return "No map file has been specified"

    # Read map file and extract valid names
    s = open(filename).read()
    re_labels = re.compile('\s+([\dA-F]{4}):([\dA-F]{8})\s+([^0-9][\S]+)')
    labels_list = [[int(t1, 16) - 1, int(t2, 16), t3] for (t1, t2, t3) in re_labels.findall(s)]

    # Parse executable using pefile
    mod = imm.getModule(modulename)
    pe = pefile.PE(mod.getPath(), fast_load=True)

    # Set labels and comments in disassembly
    for l in labels_list:
        address = mod.getBaseAddress() + pe.sections[l[0]].VirtualAddress + l[1]
        imm.setLabel(address, l[2])
    return "Map file loaded"