Hallo zusammen,
ich habe mit IDA Pro ein Binary von einem Motorola MC68336
disassembliert und in eine Datei gespeichert (File -> Produce file ->
Create ASM file). Wie kann ich aus diesem Disassembly nun wieder ein
Binary generieren?
Im Header-Kommentar des Disassembly gibt IDA Folgendes an:
1
; Target Assembler: 680x0 Assembler in MRI compatible mode
2
; This file should be compiled with "as -M"
Ist mit "as" der GNU Assembler gemeint? Den habe ich nämlich erfolglos
ausprobiert (inkl. Parameter "-M"). Er spuckt mir diverse
Fehlermeldungen aus ("junk at end of line", "symbol <xyz> already
defined", "zero assumed for missing expression" usw.).
Was mache ich falsch? Muss ich einen anderen Assembler verwenden und
falls ja, welchen?
Grüße
Steffen
Steffen Hausinger schrieb:> Was mache ich falsch? Muss ich einen anderen Assembler verwenden und> falls ja, welchen?
Einen "MRI kompatiblen" Assembler wirst Du heutzutage schwerlich finden.
Microtec Research ist schon vor Jahren in Mentor "verschwunden".
GNU as kann (mit -M oder --mri) ein bißchen was von den
MRI-Spezialitäten.
Du scheinst aber an den Kommentarzeichen zu scheitern: GNU as will bei
m68k unbedingt '|' als Kommentarzeichen sehen, während die meisten
anderen Assembler '*' oder ';' akzeptieren. Besser ist allerdings (weil
man dann auch den "OR"-Operator verwenden kann), die Files von '.s' in
'.S' umzubenennen und gleich mit gcc zu verwursten. Dann kann man
C++-Kommentarzeichen ('//') verwenden.
Ob das neu generierte Binary funktioniert, hängt davon ab, welche Tricks
in den originalen Assemblerquellen verwendet wurden. m68k ist wohl der
am logischsten aufgebaute (und am einfachsten zu verstehende)
Assemblerdialekt überhaupt, aber wenn der originale Autor beispielsweise
Code und Daten gemischt hat, kommt auch IDA Pro damit nicht zurecht. Da
ist u.U. Handarbeit gefragt.
Markus F. schrieb:> Du scheinst aber an den Kommentarzeichen zu scheitern
Ich habe probeweise alle Kommentare gelöscht. Leider erhalte ich
weiterhin Fehlermeldungen.
Beispiel 1: Variablendeklaration
1
sub_1234:
2
var_4 = -4
3
link a6,#-4
4
...
=> Error: junk at end of line, first unrecognized character is '='
(Anmerkung: gemeint ist Zeile 2)
Beispiel 2: Definition von Konstanten
1
...
2
move.w dword_5678(pc,d1.w*2),d0
3
jmp dword_5678(pc,d0.w)
4
dword_5678: dc.l $F1F2F3F4,$F5F6F7F8
5
6
loc_9ABC: movem.l (sp)+,d2-d7/a1-a3
7
...
=> Warning: zero assumed for missing expression (Anmerkung: gemeint ist
Zeile 3)
Markus F. schrieb:> Einen "MRI kompatiblen" Assembler wirst Du heutzutage schwerlich finden.
Ach so, MRI steht für Microtec Research? Ich habe den ASM68k Assembler
gefunden, der angeblich von Microtec stammt. Aber damit schaut es noch
viel schlimmer aus:
1
move.w $123(a5),$456(a5,d0.w*2)
=> Error : Missing or misplaced ')' in operand
1
mulu.l d1,d7
=> Error: Bad size on opcode
Markus F. schrieb:> Ob das neu generierte Binary funktioniert, hängt davon ab, welche Tricks> in den originalen Assemblerquellen verwendet wurden.
Und genau das verstehe ich nicht. Das Disassembly ist doch eine direkte
Übersetzung der binären Prozessorbefehle. Es ändert sich nur die
Darstellungsart (Binärcode vs. Mnemonics) und ist damit voll reversibel!
Ich könnte es doch auch von Hand hin und her übersetzen.
Steffen Hausinger schrieb:> mulu.l d1,d7> => Error: Bad size on opcode
Der MC68336 µC hat eine CPU32, die kann etwas mehr als die klassische
MC68000 CPU. Der Asm68k kann (IMHO) keine CPU32 Befehle.
Steffen Hausinger schrieb:> Markus F. schrieb:>> Du scheinst aber an den Kommentarzeichen zu scheitern>> Ich habe probeweise alle Kommentare gelöscht. Leider erhalte ich> weiterhin Fehlermeldungen.>> Beispiel 1: Variablendeklaration>
1
> sub_1234:
2
> var_4 = -4
3
> link a6,#-4
4
> ...
5
>
> => Error: junk at end of line, first unrecognized character is '='> (Anmerkung: gemeint ist Zeile 2)
Das wirst Du anpassen müssen:
1
.equ var_4, -4
>> Beispiel 2: Definition von Konstanten>
1
> dword_5678: dc.l $F1F2F3F4,$F5F6F7F8
2
>
> => Warning: zero assumed for missing expression (Anmerkung: gemeint ist> Zeile 3)
dito:
1
.long 0xF1F2F3F4,0xF5F6F7F8
gas kennt weder dc.l noch das '$'-Zeichen für Hexadezimalkonstanten.
>>>> Markus F. schrieb:>> Einen "MRI kompatiblen" Assembler wirst Du heutzutage schwerlich finden.>> Ach so, MRI steht für Microtec Research? Ich habe den ASM68k Assembler> gefunden, der angeblich von Microtec stammt. Aber damit schaut es noch> viel schlimmer aus:>>>
1
> move.w $123(a5),$456(a5,d0.w*2)
2
>
> => Error : Missing or misplaced ')' in operand>>>
1
> mulu.l d1,d7
2
>
> => Error: Bad size on opcode>>
Das sieht m.E. nicht schlechter, sondern besser aus. Daß es trotzdem
nicht funktioniert, liegt wahrscheinlich daran, daß CPU32 ein paar
Befehle und Adressierungsarten kennt, die ein simpler 68000er nicht kann
und dein Assembler nicht weiß, daß Du für CPU32 assemblieren willst.
Adressregister indirekt mit Offset und Displacement (oben) und mulu.l
(unten) gehören zu den CPU32-Erweiterungen. Dein MRI-Assembler hat
möglicherweise eine Option, um CPU32-Befehle zu aktivieren. Wenn er das
nicht kann, dann klappt's vielleicht im 68020-Mode (von dort stammt der
erweiterte Befehlssatz ursprünglich). Wie das allerdings bei dem
Assembler geht, mußt Du selbst rausfinden.
>> Markus F. schrieb:>> Ob das neu generierte Binary funktioniert, hängt davon ab, welche Tricks>> in den originalen Assemblerquellen verwendet wurden.>> Und genau das verstehe ich nicht. Das Disassembly ist doch eine direkte> Übersetzung der binären Prozessorbefehle. Es ändert sich nur die> Darstellungsart (Binärcode vs. Mnemonics) und ist damit voll reversibel!> Ich könnte es doch auch von Hand hin und her übersetzen.
Jein. Wenn Code und Daten "gemischt" wurden (z.B. eine Sprungtabelle im
Codesegment), erkennt der Disassembler das u.U. nicht und übersetzt das
als Code (wo's einen gültigen Befehl dazu gibt) oder eben als Daten.
Wenn Du das wieder zurückübersetzt, kommt tatsächlich (mit etwas Glück,
manche Assembler übersetzen nicht unbedingt das, was dasteht, sondern
optimieren z.B. bra in bra.s) wieder dasselbe raus.
Aber Du willst ja sicher nicht denselben Code haben, Du willst was
ändern. Wenn Du Code einfügst oder löschst, verschieben sich die
Adressen in deiner Sprungtabelle, aber weil die nicht als solche erkannt
wurde, werden die Adressen beim Neuassemblieren nicht angepaßt -> Crash.
Danke für Eure Hinweise. Ich habe bis eben probiert, die CPU-Architektur
vorzugeben. Beim ASM68k war ich leider erfolglos, weil ich die Syntax
nicht verstehe. Ich habe es dann abgebrochen und es beim GAS probiert.
Dort hat es geklappt - aber nichts am Ergebnis geändert.
Ich habe das Gefühl, dass meine Aufgabe doch nicht so trivial ist, wie
sie sich anhört: Binary in IDA einlesen -> analysieren -> anpassen ->
wieder ein Binary erzeugen.
Na gut, dann vereinfache ich meine Aufgabe: Binary disassemblieren ->
anpassen -> wieder ein Binary erzeugen. Wie kann ich das machen?
Ich habs gerade mal mit Objdump und GAS probiert. Objdump erzeugt mir
ein schönes Disassembly. Aber leider wird es nicht von GAS als Input
akzeptiert.
Kann mich bitte jemand erlösen? Wie patcht man einen Code? Das macht man
doch nicht, indem man das Binary direkt editiert...
Steffen Hausinger schrieb:> ie patcht man einen Code? Das macht man> doch nicht, indem man das Binary direkt editiert...
doch genau so... disassemblieren um zu sehen was man machen muss und
dann im Hex Editor anpassen.
Steffen Hausinger schrieb:> Und genau das verstehe ich nicht. Das Disassembly ist doch eine direkte> Übersetzung der binären Prozessorbefehle.
Nur im Idealfall ist das so. Nämlich dann, wenn der Disassembler
entweder ein glückliches Händchen bei der Identifizierung von
Datenbereichen hat oder, wenn in den Datenbereichen zufällig nur Daten
stehen, die auch als Code interpretiert immer etwas Gültiges ergeben.
Das ist bei komplexen und vor allem bei hochgradig optimierten
Assemblerprogrammen aber eher selten der Fall, deswegen brauchen auch
sehr gute Disassembler i.d.R. manuelle Nachhilfe zur korrekten
Identifizierung der Datenbereiche.
Naja, dazu kommt dann natürlich noch das triviale Problem, dass der
Output des Disassemblers natürlich in einem Dialekt sein muss, denn auch
der Assembler versteht, mit dem du das wieder in ein ausführbares
Programm übersetzen willst. Ansonsten ist auch hier nochmal Handarbeit
angesagt.
ich schrieb:> Steffen Hausinger schrieb:>> ie patcht man einen Code? Das macht man>> doch nicht, indem man das Binary direkt editiert...>> doch genau so... disassemblieren um zu sehen was man machen muss und> dann im Hex Editor anpassen.
Naja, das hängt sicher mindestens vom Umfang der gewünschten Änderung
ab, ob das das geeignete Vorgehen ist. Um nur mal einen einzelnen Branch
oder Subroutinenaufruf umzubiegen, würde ich mir auch nicht die Mühe
machen, den Kram komplett zu disassemblieren. Aber bei umfangreicheren
Änderungen oder gar Funktionserweiterungen wird oft doch der lange Weg
fällig.
Und natürlich gilt das insbesondere auch dann, wenn der Code
"Kopierschutzmechanismen" enthält. Also z.B. Teile des eigenen Codes an
anderer Stelle auf ihre Integrität prüft. Und das ist sogar noch
ziemlich trivial, das immerwährende Wettrüsten zwischen
"Kopierschutz"herstellern und Crackern hat zu wesentlich komplexeren
Codegebilden geführt.
Steffen Hausinger schrieb:> Na gut, dann vereinfache ich meine Aufgabe: Binary disassemblieren ->> anpassen -> wieder ein Binary erzeugen. Wie kann ich das machen?>> Ich habs gerade mal mit Objdump und GAS probiert. Objdump erzeugt mir> ein schönes Disassembly. Aber leider wird es nicht von GAS als Input> akzeptiert.
Das ist auch nicht dazu gedacht. Ein bißchen was mußt Du schon dran
machen:
1
# m68k-elf-objdump --no-show-raw-insn -d <obj>
2
3
e0000008 <_rom_entry>:
4
e0000008: movew #9984,%sr
5
e000000c: movel #-16777216,%d0
6
e0000012: movec %d0,%mbar1
7
e0000016: movel %d0,ff100844 <_rt_mbar>
8
e000001c: movel #-16515071,%d0
9
e0000022: movec %d0,%mmubar
10
e0000026: clrl %d0
11
e0000028: movel %d0,ff040000 <__MMUBAR>
12
e000002e: nop
13
e0000030: movel #-15728633,%d0
14
e0000036: movec %d0,%rambar0
15
...
Dann mußt Du nur noch die Adressen vorne abschneiden und evt. (in
spitzen Klammern gesetzte) gefundene Label löschen. Das Ergebnis sollte
gas dann auch wieder fressen.
Markus F. schrieb:> Dann mußt Du nur noch die Adressen vorne abschneiden und evt. (in> spitzen Klammern gesetzte) gefundene Label löschen.
Dann ist aber sichergestellt, daß Änderungen annähernd unmöglich sind,
denn mit absoluten Adressen im Code kann keine Instruktion eingefügt
oder entfernt werden.
Die damit noch übrigbleibenden Änderungsmöglichkeiten sind dem
unmittelbaren Patchen des Binärcodes kaum noch überlegen.
Langsam verstehe ich die Schwierigkeiten. Es gibt keinen Lauf
Disassembler -> Assembler (vor und zurück), der einen identischen
Binärcode ergibt, weil Disassembler und Assembler den Code
interpretieren (d.h. keine sturen Übersetzer sind). Mit Aufwand ließe
sich vielleicht schon ein Assembly erstellen, aber sicher ist das nicht.
Na schön, dann muss ich wohl tatsächlich Sprungbefehle einbauen, die zu
meinem Patch führen. Vielen Dank für Eure Hinweise!!
Steffen Hausinger schrieb:> Langsam verstehe ich die Schwierigkeiten. Es gibt keinen Lauf> Disassembler -> Assembler (vor und zurück), der einen identischen> Binärcode ergibt, weil Disassembler und Assembler den Code> interpretieren
Nein, das ist nicht der Grund. Der Grund ist vielmehr, das der "Code"
nicht nur Code ist, sondern auch Datenbereiche enthalten kann (und
üblicherweise auch tatsächlich welche enthält), es aber in der binären
Inkarnation des Programms wenig bis keine Informationen darüber gibt,
was nun Code und was Daten sind.
Einiges kann der Disassembler zwar ggf. aus dem Kontext ermitteln (also
aus der Struktur des Executables und/oder spezifischen Eigenheiten des
Zielsystems, aber das genügt längst nicht immer.
Übrigens kann man tatsächlich das erreichen, was dir vorschwebt. Man
muss dem Disassembler nur sagen, dass er einfach alles als Daten mit
Bytegröße interpretieren soll. Da kommt dann eine Wüste von
".db"-Direktiven heraus, die sich problemlos tatsächlich 1:1 wieder in
den ursprünglichen Code zurückübersetzen lassen.
Bloss zum Editieren ist das natürlich Mist. Das ist dann genau so, wie
mit dem Hexeditor...
Letztendlich wäre es wahrscheinlich zielführender, wenn Du uns erzählen
würdest, was Du eigentlich genau machen willst.
Für die am meisten empfehlenswerte Vorgehensweise ist es durchaus
entscheidend, was dein Patch denn nun eigentlich genau machen und wie
umfangreich/komplex das werden soll.
Außerdem würde das meine durchaus vorhandene Neugierde befriedigen.
c-hater schrieb:> Der Grund ist vielmehr, das der "Code"> nicht nur Code ist, sondern auch Datenbereiche enthalten kann (und> üblicherweise auch tatsächlich welche enthält), es aber in der binären> Inkarnation des Programms wenig bis keine Informationen darüber gibt,> was nun Code und was Daten sind.
Das verstehe ich nicht. Wenn der Disassembler diese Daten
fälschlicherweise als Programmcode behandelt, dann macht der Assembler
diesen Fehler doch auch und zwar in umgekehrter Weise. Das heißt: er
übersetzt alles wieder zurück.
Angenommen ich habe ein Datenfeld
1
.db $4E
2
.db $75
3
.db $4E
4
.db $71
5
.db $23
dann macht mein Disassembler daraus
1
rts
2
nop
3
.db $23
Wenn ich anschließend den Assembler damit aufrufe, erzeugt er wieder
mein ursprüngliches Datenfeld
1
.db $4E
2
.db $75
3
.db $4E
4
.db $71
5
.db $23
Natürlich ist das Disassembly mit dem "rts" und "nop" Nonsense. Aber vom
Prinzip her müsste ein Lauf Disassembler -> Assembler (vor und zurück)
doch schon einen identischen Binärcode ergeben.
Markus F. schrieb:> Für die am meisten empfehlenswerte Vorgehensweise ist es durchaus> entscheidend, was dein Patch denn nun eigentlich genau machen und wie> umfangreich/komplex das werden soll.
Ich habe einen Code, den ich auf eine andere Hardware-Generation
anpassen möchte. Dazu habe ich die Lowlevel-Treiber ausgemacht und
möchte die nun umbiegen. Bei einigen müssen nur die Ports angepasst
werden, bei anderen etwas mehr (bspw. die Diagnosen).
Mein Programmspeicher ist zu ~90% ausgenutzt. Es kommt daher durchaus
auch darauf an, eine schlanke Lösung zu finden. Aber ich denke, dass
meine neuen Treiber nicht zu groß werden und ich die alten Treiber
einfach als toten Code liegen lassen kann.
Steffen Hausinger schrieb:> Ich habe einen Code, den ich auf eine andere Hardware-Generation> anpassen möchte. Dazu habe ich die Lowlevel-Treiber ausgemacht und> möchte die nun umbiegen. Bei einigen müssen nur die Ports angepasst> werden, bei anderen etwas mehr (bspw. die Diagnosen).> Mein Programmspeicher ist zu ~90% ausgenutzt. Es kommt daher durchaus> auch darauf an, eine schlanke Lösung zu finden. Aber ich denke, dass> meine neuen Treiber nicht zu groß werden und ich die alten Treiber> einfach als toten Code liegen lassen kann.
Wenn Du dir die Verzögerung leisten kannst (so ungefähr 40 Takte, wenn
ich's richtig im Kopf habe), ist ein eigener Traphandler für die
Patcherei die "minimalinvasive Methode". Dazu mußt Du nur einen
unbenutzten Exception-Vektor patchen. Ein Trap-Befehl paßt überallhin,
wo ein beliebig kurzer Befehl steht und den Handler kannst Du
"irgendwohin" tun.
Ich hatte Deinen Ansatz früher schon einmal zum Setzen von
selbstgemachten Software-Breakpoints benutzt. Die Lösung ist schön, weil
der Trap-Befehl nur ein Wort breit ist. In diesem Fall habe ich aber
leider mehr Treiber als Trap-Vektoren. Das stört mich allerdings nicht,
da ich auch einfach einen "jmp" auf die neue Treiber-Routine setzen
kann.
Viel mehr Kopfzerbrechen bereitet mir dagegen die TPU. Das Programm lädt
sie mit einem eigenen Code. Ich habe mir die Literatur zur TPU
durchgelesen und versucht, den Code zu verstehen. Leider ist die Einheit
recht kompliziert und ich habe den Code (noch) nicht verstanden. Damit
kennst Du Dich nicht zufälligerweise auch aus?
Steffen Hausinger schrieb:> Ich hatte Deinen Ansatz früher schon einmal zum Setzen von> selbstgemachten Software-Breakpoints benutzt. Die Lösung ist schön, weil> der Trap-Befehl nur ein Wort breit ist. In diesem Fall habe ich aber> leider mehr Treiber als Trap-Vektoren.
Du brauchst nur einen unbenutzten Trap-Vektor. Im Handler kannst Du ja
leicht feststellen, wo der Trap ausgelöst wurde (steht auf dem Stack)
und entsprechend verzweigen. Falls Du den Befehl noch ausführen willst,
der dort ursprünglich stand, kannst Du den auch leicht aus einer Tabelle
holen und abarbeiten. Auf die Art kannst Du leicht patchen und trotzdem
das ursprüngliche Programm weitgehend unverändert lassen.
> Leider ist die Einheit> recht kompliziert und ich habe den Code (noch) nicht verstanden. Damit> kennst Du Dich nicht zufälligerweise auch aus?
Tut mir leid, damit kenne ich mich nicht aus.
Steffen Hausinger schrieb:> Natürlich ist das Disassembly mit dem "rts" und "nop" Nonsense. Aber vom> Prinzip her müsste ein Lauf Disassembler -> Assembler (vor und zurück)> doch schon einen identischen Binärcode ergeben.
Das funktioniert, wenn alle Befehle deines Prozessors gleich viel
Speicher belegen. Wenn nicht, bist du halt irgendwann asynchron zum
Originalprogramm. Obs beim 68xxx so ist, keine Ahnung, ist zu lange her.
Oliver
Vielen Dank für Eure Hilfe! So ungefähr habe ich die Schwierigkeit nun
verstanden.
Danke auch für die Idee mit den Trap-Vektoren fürs Patchen. Es
funktioniert damit ganz prima!
Schöne Grüße
Steffen
Oliver S. schrieb:> Obs beim 68xxx so ist, keine Ahnung, ist zu lange her.
m68k hat "variable length" instructions, d.h. die Befehle können ein
(16-bit) Maschinenwort, aber auch bis zu 11 Worte umfassen.
Das bedeutet natürlich, daß es darauf ankommt wo Du mit
Disassemblieren einsteigst. Abhängig davon kommt völlig anderer Code
raus.