Forum: Compiler & IDEs IDA Pro wieder zurück assemblieren


von Steffen Hausinger (Gast)


Lesenswert?

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

von Markus F. (mfro)


Lesenswert?

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.

: Bearbeitet durch User
von Steffen Hausinger (Gast)


Lesenswert?

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.

von 2⁵ (Gast)


Lesenswert?

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.

von Markus F. (mfro)


Lesenswert?

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.

von Steffen Hausinger (Gast)


Lesenswert?

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...

von ich (Gast)


Lesenswert?

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.

von c-hater (Gast)


Lesenswert?

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.

von c-hater (Gast)


Lesenswert?

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.

von Markus F. (mfro)


Lesenswert?

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.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

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.

von Steffen Hausinger (Gast)


Lesenswert?

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!!

von c-hater (Gast)


Lesenswert?

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...

von Markus F. (mfro)


Lesenswert?

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.

von Steffen Hausinger (Gast)


Lesenswert?

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.

von Markus F. (mfro)


Lesenswert?

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.

von Steffen Hausinger (Gast)


Lesenswert?

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?

von Markus F. (mfro)


Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

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

von Steffen Hausinger (Gast)


Lesenswert?

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

von Markus F. (mfro)


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.