Pourquoi le libre c'est nul sauf le python
Par Baboon le jeudi, octobre 14 2010, 16:55 - Lien permanent
Un petit post rapide pour partager avec vous un petit script fait à la va vite en python mais dont le résultat est, en toute modestie, pas dégueu.
J'ai récemment résolu une petite série de crackme linux pas bien méchants mais dont l'un comprenait une quantité non négligeable de code mort. Pour le résoudre sous IDA cela prenait 2 secondes, il suffisait de regarder les blocks sautant sur le printf "bad boy" pour retrouver les 2 uniques tests faits sur le sérial. Néanmoins les conditions "normales" du challenges exigeaient de ne se servir que des outils standards et gratuits des barbus qui puent.
J'ai donc décider de coder rapidement un petit outil personnalisable permettant de transformer la sortie de objdump en quelque chose d'humainement lisible, dans le même esprit que les graphs fournit par IDA.
Pour ce faire j'ai codé un petit script python à base de regexp qui prend en paramètre la sortie de objdump et vous donne en retour un fichier .dot compilable à l'aide de graphviz.
et voila le résultat :
Le script python n'est pas très long, je vous le paste donc ici :
''' objdump --no-show-raw-insn -d -M intel > log idagraph log rules dot -Tsvg -Ln20 -LC2 -omain.svg *.dot ''' import sys import re def decorate(line): color = "" font = "" for display_rule in display_rules : if display_rule[0].match(line) : color += " "+display_rule[1] for font_rule in font_rules : if font_rule[0].match(line) : font += " "+font_rule[1] return "<TR><TD "+color+">"+(font and ("<FONT %s >"%font) or "" )+line.replace("<","").replace(">","")+(font and "</FONT>" or "" )+"</TD></TR>" rules = {"functions":[],"graphparam":[],"table_param":[],"font_rule":[],"display_rule":[],"label_font":[],"label_display":[]} nodes = {} labels = {} currnode = 0 funregex = re.compile("(?P<address>[0-9a-f]+)[\t ]+<(?P<funname>[^>]+)>:") jumpregex = re.compile(" (?P<address>[0-9a-f]+):[\t ]+(?P<jmptype>j[^ ]+)[\t ]+(?P<destadd>[0-9a-f]*)([\t ]+<(?P<destid>[^>]+)>)?") disasregex = re.compile(" ?(?P<address>[0-9a-f]+):?[\t ]+(?P<disasm>.*)") parseit = False if len(sys.argv) < 2 : print "pyda.py objdumplog [rules]" sys.exit(0) log = file(sys.argv[1], "rb") print "parsing rules ..." if len(sys.argv) > 2 : frules = file(sys.argv[2],"r") raw_rules = frules.readlines() for raw_rule in raw_rules : print [rule.strip() for rule in raw_rule.strip().split("\t",1)] type, rule = [rule.strip() for rule in raw_rule.strip().split("\t",1)] if not type in rules : raise Exception("invalid rule : %s"%raw_rule) else : rules[type].append(rule) print "parsing rules : OK" display_rules = [] print "parsing display rules ..." for raw_display_rule in rules["display_rule"] : regex , display_rule = [rule.strip() for rule in raw_display_rule.split("\t",1)] try : display_rules.append((re.compile(regex), display_rule)) except : raise Exception("invalid color rule : %s"%raw_display_rule) print "parsing display rules : OK" font_rules = [] print "parsing font rules ..." for raw_font_rule in rules["font_rule"] : regex , font_rule = [rule.strip() for rule in raw_font_rule.split("\t",1)] try : font_rules.append((re.compile(regex), font_rule)) except : raise Exception("invalid color rule : %s"%raw_font_rule) print "parsing font rules : OK" print "parsing log ..." while (True): d = log.readline() if not d : break disas = disasregex.match(d) if not disas : continue f = funregex.match(d) if f : if not rules["functions"] : yn = None while not (yn in ["Y","N"]): print "include %s:%s in graph ? (y|n)"%(f.group("address"),f.group("funname")) yn = sys.stdin.readline().strip().upper() if yn == "Y" : parseit = True elif yn == "N" : parseit = False else : parseit = f.group("funname") in rules["functions"] print "parse %s:%s :"%(f.group("address"),f.group("funname")),f.group("funname") in rules["functions"] if parseit : currnode = f.group("address") nodes[currnode] = (f.group("funname"), [], [], {}, {}) labels[f.group("address")] = "fun_"+f.group("funname").replace("<","").replace("<","").replace(" ","_") continue if not parseit : continue j = jumpregex.match(d) if j : nodes[currnode][3][j.group("address")] = (j.group("jmptype"),j.group("destadd")) nodes[currnode][4][(j.group("destadd"))] = None if j.group("destid") : labels[j.group("destadd")] = "loc_"+j.group("destid").replace("<","").replace("<","").replace(" ","_") nodes[currnode][1].append(disas.group("disasm")) nodes[currnode][2].append(disas.group("address")) print "parsing log : OK" for fun in nodes : filename = (nodes[fun][0] and nodes[fun][0] or fun) +".dot" print "generating %s ..."%filename try : fdot = file(filename, "w") fdot.write("digraph G {\n") for i in rules["graphparam"] : fdot.write(i+"\n") name, lines, addresses, jmps, destinations = nodes[fun] currname = "_"+fun blocks = {currname:[]} relations = [] for i in xrange(len(lines)) : lines[i] = decorate(lines[i]) i = 0 while i < len(addresses) : if addresses[i] in jmps : blocks[currname].append(lines[i]) jmptype, destaddr = jmps[addresses[i]] if i+1 < len(addresses) : nextname = "_"+addresses[i+1] else : nextname = None destname = "_"+destaddr relations.append((currname, destname, (jmptype == "jmp") and "blue" or "green", (jmptype == "jmp") and "s" or "se")) if nextname and jmptype != "jmp" : relations.append((currname, nextname, "red", "sw")) currname = nextname if currname : blocks[currname] = [lines[i+1]] i+=1 elif addresses[i] in destinations : if i != 0 and not addresses[i-1] in jmps : relations.append((currname, "_"+addresses[i],"blue", "s")) currname = "_"+addresses[i] blocks[currname] = [lines[i]] else : blocks[currname].append(lines[i]) i+=1 for relation in relations : fdot.write(relation[0]+" -> "+relation[1]+" [color=%s, tailport=%s];\n"%(relation[2],relation[3])) for block in blocks : fdot.write(block+" [label=<<TABLE %s>%s</TABLE>>];\n"%(rules["table_param"] and rules["table_param"][0] or "", decorate(labels.get(block[1:],"loc_"+block[1:]).replace("<","").replace(">","")) + "".join(blocks[block]))) fdot.write("}\n") fdot.close() print "generating %s : OK"%filename except : print "generating %s : FAIL"%filename
le script prend en paramètre la sortie de objdump ainsi qu'un fichier de règle optionnel (mais pas vraiment, la sortie sans ce fichier est dégueulasse)
Le fichier de règle est très simple et pour les différentes options qui sont utilisables, je vous conseille de vous référer au manuel graphviz, Grâce à cet aide ainsi qu'au fichier de règle d'exemple suivant (utilisé pour produire l'image plus bas) vous ne devriez pas avoir de mal à faire votre propre rendu. (atention les séparateurs doivent être des tabulations)
functions main graphparam graph [splines=true]; graphparam edge [color=blue, arrowsize=2]; graphparam node [color=lightblue, style=filled, shape=box, fontname="Verdana"]; table_param ALIGN="LEFT" BGCOLOR="lightblue" BORDER="0" CELLBORDER="0" display_rule call.* BGCOLOR="blue" display_rule jmp BGCOLOR="yellow" display_rule j[^m] BGCOLOR="yellow" display_rule .* ALIGN="LEFT" font_rule j[^m] COLOR="red" font_rule loc_ COLOR="darkblue" FACE="Georgia" POINT-SIZE="20" font_rule fun_ COLOR="darkblue" FACE="Georgia" POINT-SIZE="20"
Un exemple un peu plus complexe (le crackme en question) pour vous montrer ce qu'est capable de produire pyda (un peu prétentieux comme nom mais j'assume complètement) et graphviz.
Attention opera chie dans la colle avec ce svg, préférez firefox ...).
Bien sur ca n'atteint pas le niveau de IDA mais ca permet de dépanner les pauvres barbus sans le sous
Commentaires
C'est vrai que c'est bien classe le résultat ! En plus tu m'as fait réaliser qu'export .svg = texte = on peut même faire ctrl+f. Bien joué
Sinon, ya radare (radare.org) qui marche pas mal.
très sympa, ca peut toujours servir.
Ah tient, je ne connaissais pas radare, merci.
Sinon mon but n'était pas de fournir un desassembleur high level ni même un truc super poussé (ni même utile en fait), l'idée était vraiment de faire un graph rapidement à l'aide des outils disponibles (bon après graphviz n'est peut être pas installé partout, je vous l'accorde).
Avec un minimum de choses paramétrables et avec du code potentiellement réutilisable.
Nice Baboon. Sinon y'a aussi dissy 1
1 https://code.google.com/p/dissy
Mais non le libre c'est pas nul Baboon: on t'attend justement pour que tu codes un IDA-like!
Ahah bien sur !
Tu as vu la gueule de mon code pour un mini script ?
Tu imagine ce que ca donnerait pour un projet conséquent ? :p
Je doute que la communauté des barbus qui puent accueillent ce genre de code à bras ouverts :p
Bah c'est python qui pue. :=) Tu exageres Baboon, les fonctions c'est pas pour les chiens. :=)
en fait c'est peut-être Baboon qui pue ?