Linux / Nasm / Beispielprogramme
-
- Beiträge: 25
- Registriert: Do Okt 31, 2013 9:03 pm
Linux / Nasm / Beispielprogramme
Hallo zusammen,
ich bin derzeit dabei, meine kläglichen Assemblerkenntnisse (Linux) etwas auf zu polieren. Und zwar dergestalt, dass ich vor einigen Jahren ein paar Testprogramme unter Fasm erstellt habe, und diese soweit möglich jetzt nach Nasm "konvertieren" will.
Mein Testsystem ist dabei Debian Wheezy, 64bit, und Nasm 2.10.01
Die Progrämmchen sind alles andere als perfekt, ("quick and dirty" wäre wohl teilweise der richtige Ausdruck), aber wenn auch nur ein Mensch auf der Welt etwas davon lernen kann, hat es sich meines Erachtens schon gelohnt.
Also, mal sehn, was kommt...
ich bin derzeit dabei, meine kläglichen Assemblerkenntnisse (Linux) etwas auf zu polieren. Und zwar dergestalt, dass ich vor einigen Jahren ein paar Testprogramme unter Fasm erstellt habe, und diese soweit möglich jetzt nach Nasm "konvertieren" will.
Mein Testsystem ist dabei Debian Wheezy, 64bit, und Nasm 2.10.01
Die Progrämmchen sind alles andere als perfekt, ("quick and dirty" wäre wohl teilweise der richtige Ausdruck), aber wenn auch nur ein Mensch auf der Welt etwas davon lernen kann, hat es sich meines Erachtens schon gelohnt.
Also, mal sehn, was kommt...
-
- Beiträge: 25
- Registriert: Do Okt 31, 2013 9:03 pm
Re: Linux / Nasm / Beispielprogramme
Das obligatorische "Hello World"-Programm (wohlgemerkt ein Netzfund, also nicht von mir selbst)
Auffallend dabei ist der "syscall"-Aufruf, weshalb auch mit ax=1 geschrieben wird, anstatt mit ax=4, wie man es bei Benutzung von int 80h machen würde.
Code: Alles auswählen
; 64-bit "Hello World!" in Linux NASM
; nasm -f elf64 hello_world.asm
; ld -s -o hello-world hello-world.o
[bits 64]
global _start ; global entry point export for ld
section .text
_start:
mov rax, 1 ; sys_write
mov rdi, 1 ; stdout
mov rsi, message ; message address
mov rdx, length ; message string length
syscall
; sys_exit(return_code)
mov rax, 60 ; sys_exit
mov rdi, 0 ; return 0 (success)
syscall
section .data
message: db 'Hello, world!',0x0A ; message and newline
length: equ $-message ; NASM definition pseudo-instruction
-
- Beiträge: 25
- Registriert: Do Okt 31, 2013 9:03 pm
Re: Linux / Nasm / Beispielprogramme
Folgendes Programm findet Primzahlen beginnend bei 12000 und abwärts bis 1 zählend.
Edit by Xin: Parameter für nasm -f hinzugefügt auf Wunsch von osculumobscenum
Code: Alles auswählen
; 64-bit ... gibt Primzahlen aus
; by myself
; nasm -f elf64 primzahlen.asm
; ld -s -o primzahlen primzahlen.o
[bits 64]
global _start ; global entry point export for ld
section .data
_start:
jmp start2
check_prim:
push rax
push rbx
push rcx
push rdx
push rsi
push rdi
xor ax, ax
mov [primzahl], ax
mov si, [para_w]
mov di, si
nochmal:
mov ax,si
dec di
cmp di,1
je ende
xor dx,dx
div di
cmp dx,0
jnz nochmal
mov ax,1
mov [primzahl], ax
ende:
pop rdi
pop rsi
pop rdx
pop rcx
pop rbx
pop rax
ret
crlf:
push rax
push rbx
push rcx
push rdx
push rsi
push rdi
mov eax, 4
mov ebx, 1
mov ecx, abc
mov edx, 2
int 128
pop rdi
pop rsi
pop rdx
pop rcx
pop rbx
pop rax
ret
print_dez:
push rax
push rbx
push rcx
push rdx
push rsi
push rdi
mov ax, [para_w]
xor cx,cx
mov bx,10
schl1:
xor dx,dx
div bx
push dx
inc cx
cmp ax,0
jnz schl1
schl2:
pop dx
add dl,30h
mov [ziffer],dl
push rcx
mov eax, 4
mov ebx, 1
mov ecx, ziffer
mov edx, 1
int 128
pop rcx
loop schl2
pop rdi
pop rsi
pop rdx
pop rcx
pop rbx
pop rax
ret
start2: ; here starts the proggi
mov cx,12000
hano:
mov [para_w], cx
call check_prim
cmp word [primzahl], 1
je noehaa
mov [para_w], cx
call print_dez
call crlf
noehaa:
dec cx
cmp cx,1
jnz hano
mov eax,1
int 128
; here just variables ------ end of code -------------------------------
para_b db 0
para_w dw 0
primzahl dw 0
ziffer db 0
zahl dd 0
abc db 13,10
- Xin
- nur zu Besuch hier
- Beiträge: 8859
- Registriert: Fr Jul 04, 2008 11:10 pm
- Wohnort: /home/xin
- Kontaktdaten:
Re: Linux / Nasm / Beispielprogramme
Willkommen im Forum,osculumobscenum hat geschrieben:Hallo zusammen,
lass mich raten, Dein Pseudonym ist weltweit eindeutig, oder?
Immerhin hast Du welcheosculumobscenum hat geschrieben:ich bin derzeit dabei, meine kläglichen Assemblerkenntnisse (Linux) etwas auf zu polieren.
Unter Linux habe ich mich noch gar nicht probiert.
Ich werde 2014 oder 2015 Assembler können müssen, mich interessiert das also durchaus. Ich kann mir auch vorstellen, dass andere das interessiert, daher wäre es auch schön, wenn Du die Tools, die du benutzt (nasm, ld...?) vorstellst und vielleicht auch was zum Aufbau der Assembleralgorithmen schreiben kannst.osculumobscenum hat geschrieben:Die Progrämmchen sind alles andere als perfekt, ("quick and dirty" wäre wohl teilweise der richtige Ausdruck), aber wenn auch nur ein Mensch auf der Welt etwas davon lernen kann, hat es sich meines Erachtens schon gelohnt.
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.
Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.
Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.
-
- Beiträge: 25
- Registriert: Do Okt 31, 2013 9:03 pm
Re: Linux / Nasm / Beispielprogramme
"Armstrong-Zahlen" (mir war der Begriff bis heute mittag auch neu...) sind Zahlen, deren einzelne Ziffern hoch drei wieder die Gesamtsumme ergeben. Als Beispiel
153 = (1 hoch 3) + (5 hoch 3) + (3 hoch 3) = 1 + 125 +27 = 153
Keine Ahnung, wer sich sowas überlegt, aber es ist eine interessante Übung zum programmieren :
(wie man wohl erkennt ist das Grundgerüst meiner Programme oft ähnlich, aber weshalb sollte man das Rad auch mehrfach neu erfinden?)
Edit by Xin: Parameter für nasm -f hinzugefügt auf Wunsch von osculumobscenum
153 = (1 hoch 3) + (5 hoch 3) + (3 hoch 3) = 1 + 125 +27 = 153
Keine Ahnung, wer sich sowas überlegt, aber es ist eine interessante Übung zum programmieren :
(wie man wohl erkennt ist das Grundgerüst meiner Programme oft ähnlich, aber weshalb sollte man das Rad auch mehrfach neu erfinden?)
Code: Alles auswählen
; 64-bit ... gibt Armstrong-Zahlen aus
; => alle Einzelziffern einer Zahl sollen im Kubik wieder aufsummiert die Ausgangszahl werden.
; (1 hoch 3) + (5 hoch 3) + (3 hoch 3) = 153
; by myself
; nasm -f elf64 armstrong.asm
; ld -s -o armstrong armstrong.o
[bits 64]
global _start ; global entry point export for ld
section .data
_start:
jmp start2
check_arm:
push rax ; alle Register sichern
push rbx ; ging früher mit pusja/popa,aber ab 64-bit
push rcx ; leider nicht mehr
push rdx
push rsi
push rdi
xor rax,rax
mov [armzahl], rax ; "armzahl" ist ein Boolean, im Moment 0
mov rax, [para_q] ; die zu testende Zahl ist jetzt im rax-Register
xor rcx, rcx
mov rbx, 10
schleife_1a:
xor rdx,rdx ; wir teilen die Zahl immer wieder durch 10, so dass die "Restwerte"
div bx ; auf den Stack gepusht werden, um dort die einzelnen Ziffern der Zahl zu haben.
push dx
inc cx
cmp ax,0 ; alle Einzelziffern gepusht?
jnz schleife_1a ; wenn nicht, oben weitermachen
xor rdx, rdx
schleife_2a:
xor rax, rax
pop ax ; die einzelne Ziffer aus dem Stack holen
push ax ; ax zweimal pushen, damit 2 mal mit sich selber multipliziert werden kann.
push ax
pop bx ; als bl (wir vernachlässigen bh) wieder holen
mul bl ; und multiplizieren = > Quadrat in ax
pop bx ; nochmal die Ziffer nach bl poppen
mul bl ; und multiplizieren
; Kubikzahl der Ziffer ist jetzt in ax
add rdx, rax ; und wird in rdx aufsummiert
loop schleife_2a
cmp rdx, [para_q] ; ist die Summe der Kubikzahlen so groß wie die Ausgangszahl?
jne noarm ; falls nicht, springe zu "noarm"
mov rax,1
mov [armzahl], rax ; setze Boolean "armzahl" (Es handelt sich in para_q um eine Armstrong-Zahl)
noarm:
pop rdi ; Register wieder retten
pop rsi
pop rdx
pop rcx
pop rbx
pop rax
ret
crlf:
push rax
push rbx
push rcx
push rdx
push rsi
push rdi
mov eax, 4 ; "schreiben"
mov ebx, 1 ; und zwar ein Zeichen
mov ecx, abc ; abc ist ein Zeilenumbruch, siehe Variablen
mov edx, 2
int 80h
pop rdi
pop rsi
pop rdx
pop rcx
pop rbx
pop rax
ret
print_dez: ; Routine um Zahlen zu schreiben
push rax
push rbx
push rcx
push rdx
push rsi
push rdi
mov rax, [para_q] ; die zu schreibende Zahl wird hier als qword übergeben
xor cx,cx
mov bx,10 ; ...immer durch 10 geteilt
schleife1:
xor dx,dx
div bx
push dx ; ... der Rest auf den Stack gepusht
inc cx
cmp ax,0
jnz schleife1
schleife2:
pop dx ; ... dann wieder aus dem Stack gepoppt
add dl,30h ; 30h addiert, damit es eine Ascii-Ziffer wird
mov [ziffer],dl
push rcx
mov eax, 4 ; und dann geschrieben.
mov ebx, 1
mov ecx, ziffer
mov edx, 1
int 80h
pop rcx
loop schleife2
pop rdi
pop rsi
pop rdx
pop rcx
pop rbx
pop rax
ret
start2: ; here starts the proggi
mov rcx,1 ; rcx immer hochzählen
nochmal:
mov [para_q], rcx ; ... als Parameter übergeben
call check_arm ; auf "armstrong getestet
cmp qword [armzahl], 1
jne noehaa
mov [para_q], rcx ; ... und falls armstrong, dann auch
call print_dez ; auf der Console geschrieben
call crlf ; ...und den Zeilenumbruch nicht vergessen.
noehaa:
inc rcx
cmp rcx,1000 ; alle Zahlen zwischen 1 und 1000 auf "Armstrong" testen
jne nochmal
mov rax,1
int 80h
; here just variables ------ end of code -------------------------------
para_q dq 0
para_w dw 0
armzahl dq 0
ziffer db 0
abc db 13,10
-
- Beiträge: 25
- Registriert: Do Okt 31, 2013 9:03 pm
Re: Linux / Nasm / Beispielprogramme
Mein Name ist nicht wirklich eindeutig, es gibt mehrere davon, weil es sich schlicht und ergreifend um ein Lied von Hypocrisy handeltXin hat geschrieben:Willkommen im Forum,osculumobscenum hat geschrieben:Hallo zusammen,
lass mich raten, Dein Pseudonym ist weltweit eindeutig, oder?
Immerhin hast Du welcheosculumobscenum hat geschrieben:ich bin derzeit dabei, meine kläglichen Assemblerkenntnisse (Linux) etwas auf zu polieren.
Unter Linux habe ich mich noch gar nicht probiert.
Ich werde 2014 oder 2015 Assembler können müssen, mich interessiert das also durchaus. Ich kann mir auch vorstellen, dass andere das interessiert, daher wäre es auch schön, wenn Du die Tools, die du benutzt (nasm, ld...?) vorstellst und vielleicht auch was zum Aufbau der Assembleralgorithmen schreiben kannst.osculumobscenum hat geschrieben:Die Progrämmchen sind alles andere als perfekt, ("quick and dirty" wäre wohl teilweise der richtige Ausdruck), aber wenn auch nur ein Mensch auf der Welt etwas davon lernen kann, hat es sich meines Erachtens schon gelohnt.
"Die Tools vorstellen, die ich benutze" ist eine kurze Vorstellung: Wie gesagt, ich habe ein 64-bit-Linux, und verwende den Nasm 2.10.01 und zum linken halt das "ld"-Programm, das meinem Linux beigefügt ist.
Die Programme selbst könen in jedem beliebigen Editor geschrieben werden (in meinem Fall "Geany"), da sie ja ohnehin später unabhängig davon auf der Konsole assembliert und gestartet werden.
Die genauen Befehle zum assemblieren und linken sind jeweils in den Quellcode-Stücken im Kopfbereich mit angegeben.
Algorithmen, sofern überhaupt welche vorkommen, sind innerhalb des Quellcodes ein wenig beschrieben, sofern das möglich war.
-
- Beiträge: 25
- Registriert: Do Okt 31, 2013 9:03 pm
Re: Linux / Nasm / Beispielprogramme
Es wird an der Konsole aufgefordert, etwas ein zu geben, und danach wird der Text wieder daselbst ausgegeben.
Edit by Xin: Parameter für nasm -f hinzugefügt auf Wunsch von osculumobscenum
Code: Alles auswählen
; 64-bit input und output
; => Zeichen an der Tastatur eingeben, welche später wieder ausgegeben werden
; by myself
; nasm -f elf64 inp_outp.asm
; ld -s -o inp_outp inp_outp.o
[bits 64]
global _start ; global entry point export for ld
section .text
_start:
mov rax,4 ; int 80h / ax, 4 => schreiben
mov rbx,1 ; bx, 1 => an die Konsole
mov rcx,msg ; was wir schreiben
mov rdx,msg_size ; und wie lang der Text ist
int 80h
mov rax,3 ; einlesen
mov rbx,1 ; von der Tastatur
mov rcx,inp_buf ; wo wir das Ganze speichern
mov rdx,100h ; und wieviel davon
int 80h
mov [inp_buf_size],rax ; rax hat wohl mitgezählt, wieviel Zeichen wir eingegeben haben
mov rax,4 ; wieder Ausgabe
mov rbx,1 ; an die Konsole
mov rcx,msg2 ; aus Variable msg2 (siehe unten)
mov rdx,msg2_size ; und wie lange der Text ist
int 80h
mov rax,4 ; nochmal Ausgabe
mov rbx,1 ; an die Konsole
mov rcx,inp_buf ; unseren Eingabepuffer von vorhin
mov rdx,[inp_buf_size] ; und die Länge des Puffers
int 80h
mov rax,4 ; Ausgabe
mov rbx,1 ; an die Konsole
mov rcx,crlf ; Zeilenumbruch (unten definiert)
mov rdx,1 ; ist in Linux(Unix nur ein Zeichen, im Gegensatz zu Windows
int 80h
mov rax,1 ; Programmende
xor rbx,rbx
int 80h
section .data
crlf db 0ah
msg db 'Input some data: '
msg_size: equ $-msg
inp_buf: dq 100h
inp_buf_size dq 1
msg2 db 'You entered: '
msg2_size: equ $-msg2
-
- Beiträge: 25
- Registriert: Do Okt 31, 2013 9:03 pm
Re: Linux / Nasm / Beispielprogramme
Nochmals ein Netzfund, Autor unbekannt.
Es wird mittels Escape-Sequenzen die Console gelöscht ("ClearScreen"), danach der Cursor an eine bestimmte Position platziert, und dann farbig und unterstrichen geschrieben. Escape-Sequenzen sind ein Thema für sich, aber das Internet hat bestimmt Informationen dazu. Wie man sie in Nasm unter Linux einsetzen kann, sieht man hier:
Edit by Xin: Parameter für nasm -f hinzugefügt auf Wunsch von osculumobscenum
Es wird mittels Escape-Sequenzen die Console gelöscht ("ClearScreen"), danach der Cursor an eine bestimmte Position platziert, und dann farbig und unterstrichen geschrieben. Escape-Sequenzen sind ein Thema für sich, aber das Internet hat bestimmt Informationen dazu. Wie man sie in Nasm unter Linux einsetzen kann, sieht man hier:
Code: Alles auswählen
; 64-bit ... mit Escape-Sequenzen den Bildschrim säubern,
; den Cursor an eine bestimmte Position setzen und dort schreiben.
; by ??? (Netzfund)
; nasm -f elf64 consol.asm
; ld -s -o consol consol.o
[bits 64]
global _start ; global entry point export for ld
section .text
_start:
jmp start
ESC equ 0x1b ; escape character
start:
; first clear the screen
mov eax, 4 ; schreiben
mov ebx, 1 ; Konsole
mov ecx, clear_screen
mov edx, clear_screen_size
int 0x80
; move cursor to (x:25, y:12)
mov eax, 4 ; schreiben
mov ebx, 1 ; Konsole
mov ecx, move_cursor
mov edx, move_cursor_size
int 0x80
; write the message
mov eax, 4
mov ebx, 1
mov ecx, message
mov edx, message_size
int 0x80
; exit from the program
mov eax, 1 ; Programm beenden
xor ebx, ebx ; Fehlercode 0
int 0x80
clear_screen: db ESC, "[2J"
clear_screen_size: equ $ - clear_screen
move_cursor: db ESC, "[12;25H"
move_cursor_size: equ $ - move_cursor
message: db ESC, "[31m", ESC, "[5m", ESC, "[4m" ; red, blink on, underline on
db "Programming linux is easy", 0xa
db ESC, "[25m", ESC, "[24m" ; blink off, underline off
message_size: equ $ - message
-
- Beiträge: 25
- Registriert: Do Okt 31, 2013 9:03 pm
Re: Linux / Nasm / Beispielprogramme
Ebenfalls Autor unbekannt, Netzfund...
Wie man externe Programme innerhalb eines Assemblerprogramms in Linux aufruft.
Seltsamerweise wird der Programmname einmal mit und einmal ohne Parameter angegeben...
Edit by Xin: Parameter für nasm -f hinzugefügt
Wie man externe Programme innerhalb eines Assemblerprogramms in Linux aufruft.
Seltsamerweise wird der Programmname einmal mit und einmal ohne Parameter angegeben...
Code: Alles auswählen
; 64-bit ... andere Programme ausführen, hier "ls" mit Parameter
; by ???
; nasm -f elf64 lstest.asm
; ld -s -o lstest lstest.o
[bits 64]
global _start ; global entry point export for ld
section .data
_start:
mov rax,0xB ; Funktion "externes Programm ausführen" (vermute ich mal so...)
mov rcx,args ; Programmname mit Parameter
mov rdx,0
mov rbx,arg0 ; Programmname ohne Parameter
int 80h
mov rax,1 ; Programm beenden
int 80h
arg0: db '/bin/ls',0 ; Programmangaben, mit abschließender Null
arg1: db '-l',0
args: dd arg0,arg1,0
-
- Beiträge: 25
- Registriert: Do Okt 31, 2013 9:03 pm
Re: Linux / Nasm / Beispielprogramme
Hier eine kleine Demonstration, wie man mit Assembler innerhalb eines Programms nicht nur Variablen, sondern auch den Programmcode verschlüsseln kann, und somit Programme hat, die sich erst zur Laufzeit decodieren.
Schritt 1:
Wir erstellen zunächst ein handelsübliches Programm, zum Beispiel das allseits beliebte "Hello World".
Vor und nach dem eigentlichen Programm habe ich jeweils 4 mal den nop-Befehl verwendet, bzw. den zugehörigen opcode, um später beim debuggen, oder beim hexdump leichter die wichtigen Stellen dazwischen identifizieren zu können.
OK, dann wie im Quellcode beschrieben assemblieren und linken, fertig ist der erste Schritt.
Schritt 2:
Wir nehmen uns die ausführbare Version des obigen Programms und schauen es uns an.
Linux liefert normalerweise ein hexdump-Programm mit, ansonsten geht das auch mit jedem beliebigen Hexeditor.
Ich selbst habe mit "hexdump -C hello-cod01" folgende Ausgabe erhalten:
In der Zeile 00000070 finden wir nach kurzer Suche unsere opcodes mit "90" wieder, welche wir vorhin zur besseren Identifikation mit ein programmiert haben. Und in der Zeile 000000b0 finden wir die anderen "90"-er Opcodes, die wir am Ende unseres kleinen Programms mit eingebaut haben. Das heißt, alles was dazwischen ist, ist jeweils der opcode der Befehle des eigentlichen Programms, weches "Hello World!" schreibt, und sich dann selbt beendet.
Schritt 3:
Wir erstellen eine Kopie des ersten asm-Programms und ersetzen die Befehle für den Schreibbefehl und den Befehl für das Programmende durch ihre Opcodes. Opcods kann man wie Variablen ohne Namen eingeben.
Es geht also wie gesgat los nach den 4 mal 90h, die für die4 "nop"-Befehle stehen und beginnt somit mit "b8" "04" "00" "00" usw...
bis wir eben wieder bei den unteren 90h Opcodes angelangt sind.
Faktisch gehen wir also zu dem sss:-Label und beginnen dort mit
db b8h
db 04h
db 00h
db 00h
usw...
bis wir alle Befehle durch Opcodes ersetzt haben. Im Programm selbst habe lasse ich jene Opcodes, die mit Buchstaben beginnen noch mit einer Null anführen, sozusagen aus ästhetischen Gründen.
Nach getaner Arbeit haben wir dann einen Assembler-Quellcode, der so aussieht:
Wenn wir die Datei assemblieren und linken, sollte eine exakte Kopie des ersten Programms entstanden sein, die auch exakt die gleiche Ausgabe produziert.
Womit wir auch schon zum dritten Schritt kommen.
Schritt 3:
Wir erstellen uns vom zweiten Programm ebenfalls eine Kopie und bearbeiten diese im Editor wie folgt:
Zu allen vorhin eingegebenen Opcodes (die ja letztlich nur eine hexadezimale Zahl darstellen) addieren wir 18h, so dass wir beginnen mit
...und das ganze fortführen bis zu den altbekannten "90h"-Opcodes. Den Variablen-Namen "Zeugs habe ich ebenfalls eingeführt, weil wir für die Decodierroutine einen Offset brauchen, um fest zu legen, wo mit der Decodierung angefangen werden soll.
Praktischerweise startet das Programm ja bereits mit einem Sprung zu "decode:" (als ob wir's geahnt hätten...) so dass wir genau dort sofort mit der Decodierung anfangen können.
usw...
Wir sagen also dem Programm, dass es ab dem Offset "zeugs" bei jedem folgenden byte 18h abziehen soll, nämlich genau jene, die wir vorhin händisch hinzu addiert haben. Und das machen wir bei allen einzelnen Opcodes, die wir vorhin ersetzt haben und dann 18h addiert haben.
Danach springen wir zu dem eigentlich aus zu führenden Programmteil, den wir bis dahin decodiert ahben, und alles sollte wieder funktionieren wie beim allerersten "Hello World!"-Programm.
Das fertige asm-Programm sollte also zum Schluss so aussehen:
Man könnte an dieser Stelle natürlich noch einen 4. Schritt anhängen und die Decodierroutine als Schleife formulieren, aber das gehört nicht mehr zu der Sache, die ich erläutern wollte. Wichtig bei allem ist auf jeden Fall, dass die ganze section als "data" definiert wird, denn das standardmäßige "text" ließe eine Veränderung der Opcodes nicht zu, da schreibgeschützt.
Wenn wir die erstellten 3 Programme jetzt mit einem Hexeditor anschauen, können wir also sehen, dass die ersten beiden die "Hello-World"-Meldung im Klartext vorliegen haben, so dass sie von jedem Hinterhof-Hacker verändert werden können, während das im dritten Programm nicht mehr der Fall ist.
Schritt 1:
Wir erstellen zunächst ein handelsübliches Programm, zum Beispiel das allseits beliebte "Hello World".
Code: Alles auswählen
; 64-bit "Hello World!" uncodiert... 1 von 3
; by myself
; nasm -f elf64 hello-cod01.asm
; ld -s -o hello-cod01 hello-cod01.o
[bits 64]
global _start ; global entry point export for ld
section .data
_start:
jmp decode
db 90h ; opcode für den nop-Befehl
db 90h ; also "no operation"
db 90h
db 90h
sss:
mov rax,4 ; schreiben
mov rbx,1 ; an der Console
mov rcx,msg
mov rdx,msg_size
int 0x80
mov rax,1
xor rbx,rbx
int 0x80
msg: db 'Hello world!',0xA
msg_size: equ $-msg
decode:
jmp sss
db 90h ; wieder 4 mal opcodes für den nop-Befehl,
db 90h ; also "no operation"
db 90h
db 90h
OK, dann wie im Quellcode beschrieben assemblieren und linken, fertig ist der erste Schritt.
Schritt 2:
Wir nehmen uns die ausführbare Version des obigen Programms und schauen es uns an.
Linux liefert normalerweise ein hexdump-Programm mit, ansonsten geht das auch mit jedem beliebigen Hexeditor.
Ich selbst habe mit "hexdump -C hello-cod01" folgende Ausgabe erhalten:
Code: Alles auswählen
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 3e 00 01 00 00 00 78 00 60 00 00 00 00 00 |..>.....x.`.....|
00000020 40 00 00 00 00 00 00 00 c8 00 00 00 00 00 00 00 |@...............|
00000030 00 00 00 00 40 00 38 00 01 00 40 00 03 00 02 00 |....@.8...@.....|
00000040 01 00 00 00 06 00 00 00 00 00 00 00 00 00 00 00 |................|
00000050 00 00 60 00 00 00 00 00 00 00 60 00 00 00 00 00 |..`.......`.....|
00000060 b6 00 00 00 00 00 00 00 b6 00 00 00 00 00 00 00 |................|
00000070 00 00 20 00 00 00 00 00 eb 36 90 90 90 90 b8 04 |.. ......6......|
00000080 00 00 00 bb 01 00 00 00 48 b9 a3 00 60 00 00 00 |........H...`...|
00000090 00 00 ba 0d 00 00 00 cd 80 b8 01 00 00 00 48 31 |..............H1|
000000a0 db cd 80 48 65 6c 6c 6f 20 77 6f 72 6c 64 21 0a |...Hello world!.|
000000b0 eb cc 90 90 90 90 00 2e 73 68 73 74 72 74 61 62 |........shstrtab|
000000c0 00 2e 64 61 74 61 00 00 00 00 00 00 00 00 00 00 |..data..........|
000000d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000100 00 00 00 00 00 00 00 00 0b 00 00 00 01 00 00 00 |................|
00000110 03 00 00 00 00 00 00 00 78 00 60 00 00 00 00 00 |........x.`.....|
00000120 78 00 00 00 00 00 00 00 3e 00 00 00 00 00 00 00 |x.......>.......|
00000130 00 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 |................|
00000140 00 00 00 00 00 00 00 00 01 00 00 00 03 00 00 00 |................|
00000150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000160 b6 00 00 00 00 00 00 00 11 00 00 00 00 00 00 00 |................|
00000170 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................|
*
00000188
Schritt 3:
Wir erstellen eine Kopie des ersten asm-Programms und ersetzen die Befehle für den Schreibbefehl und den Befehl für das Programmende durch ihre Opcodes. Opcods kann man wie Variablen ohne Namen eingeben.
Es geht also wie gesgat los nach den 4 mal 90h, die für die4 "nop"-Befehle stehen und beginnt somit mit "b8" "04" "00" "00" usw...
bis wir eben wieder bei den unteren 90h Opcodes angelangt sind.
Faktisch gehen wir also zu dem sss:-Label und beginnen dort mit
db b8h
db 04h
db 00h
db 00h
usw...
bis wir alle Befehle durch Opcodes ersetzt haben. Im Programm selbst habe lasse ich jene Opcodes, die mit Buchstaben beginnen noch mit einer Null anführen, sozusagen aus ästhetischen Gründen.
Nach getaner Arbeit haben wir dann einen Assembler-Quellcode, der so aussieht:
Code: Alles auswählen
; 64-bit "Hello World!" uncodiert, aber als opcodes... 2 von 3
; by myself
; nasm -f elf64 hello-cod02.asm
; ld -s -o hello-cod02 hello-cod02.o
[bits 64]
global _start ; global entry point export for ld
section .data
_start:
jmp decode
db 90h
db 90h
db 90h
db 90h
sss:
db 0b8h
db 04h
db 00h
db 00h
db 00h
db 0bbh
db 01h
db 00h
db 00h
db 00h
db 48h
db 0b9h
db 0a3h
db 00h
db 60h
db 00h
db 00h
db 00h
db 00h
db 00h
db 0bah
db 0dh
db 00h
db 00h
db 00h
db 0cdh
db 80h
db 0b8h
db 01h
db 00h
db 00h
db 00h
db 48h
db 31h
db 0dbh
db 0cdh
db 80h
db 48h
db 65h
db 6ch
db 6ch
db 6fh
db 20h
db 77h
db 6fh
db 72h
db 6ch
db 64h
db 21h
db 0ah
decode:
jmp sss
db 90h
db 90h
db 90h
db 90h
Womit wir auch schon zum dritten Schritt kommen.
Schritt 3:
Wir erstellen uns vom zweiten Programm ebenfalls eine Kopie und bearbeiten diese im Editor wie folgt:
Zu allen vorhin eingegebenen Opcodes (die ja letztlich nur eine hexadezimale Zahl darstellen) addieren wir 18h, so dass wir beginnen mit
Code: Alles auswählen
sss:
zeugs db 0b8h + 18h
db 04h + 18h
db 00h + 18h
db 00h + 18h
db 00h + 18h
db 0bbh + 18h
Praktischerweise startet das Programm ja bereits mit einem Sprung zu "decode:" (als ob wir's geahnt hätten...) so dass wir genau dort sofort mit der Decodierung anfangen können.
Code: Alles auswählen
decode:
sub byte [zeugs],18h
sub byte [zeugs+1],18h
sub byte [zeugs+2],18h
sub byte [zeugs+3],18h
sub byte [zeugs+4],18h
sub byte [zeugs+5],18h
Wir sagen also dem Programm, dass es ab dem Offset "zeugs" bei jedem folgenden byte 18h abziehen soll, nämlich genau jene, die wir vorhin händisch hinzu addiert haben. Und das machen wir bei allen einzelnen Opcodes, die wir vorhin ersetzt haben und dann 18h addiert haben.
Danach springen wir zu dem eigentlich aus zu führenden Programmteil, den wir bis dahin decodiert ahben, und alles sollte wieder funktionieren wie beim allerersten "Hello World!"-Programm.
Das fertige asm-Programm sollte also zum Schluss so aussehen:
Code: Alles auswählen
; 64-bit "Hello World!" codiert, als original opcodes + 18h... 3 von 3
; by myself
; nasm -f elf64 hello-cod03.asm
; ld -s -o hello-cod03 hello-cod03.o
[bits 64]
global _start ; global entry point export for ld
section .data
_start:
jmp decode
db 90h
db 90h
db 90h
db 90h
sss:
zeugs db 0b8h + 18h
db 04h + 18h
db 00h + 18h
db 00h + 18h
db 00h + 18h
db 0bbh + 18h
db 01h + 18h
db 00h + 18h
db 00h + 18h
db 00h + 18h
db 48h + 18h
db 0b9h + 18h
db 0a3h + 18h
db 00h + 18h
db 60h + 18h
db 00h + 18h
db 00h + 18h
db 00h + 18h
db 00h + 18h
db 00h + 18h
db 0bah + 18h
db 0dh + 18h
db 00h + 18h
db 00h + 18h
db 00h + 18h
db 0cdh + 18h
db 80h + 18h
db 0b8h + 18h
db 01h + 18h
db 00h + 18h
db 00h + 18h
db 00h + 18h
db 48h + 18h
db 31h + 18h
db 0dbh + 18h
db 0cdh + 18h
db 80h + 18h
db 48h + 18h
db 65h + 18h
db 6ch + 18h
db 6ch + 18h
db 6fh + 18h
db 20h + 18h
db 77h + 18h
db 6fh + 18h
db 72h + 18h
db 6ch + 18h
db 64h + 18h
db 21h + 18h
db 0ah + 18h
decode:
sub byte [zeugs],18h
sub byte [zeugs+1],18h
sub byte [zeugs+2],18h
sub byte [zeugs+3],18h
sub byte [zeugs+4],18h
sub byte [zeugs+5],18h
sub byte [zeugs+6],18h
sub byte [zeugs+7],18h
sub byte [zeugs+8],18h
sub byte [zeugs+9],18h
sub byte [zeugs+10],18h
sub byte [zeugs+11],18h
sub byte [zeugs+12],18h
sub byte [zeugs+13],18h
sub byte [zeugs+14],18h
sub byte [zeugs+15],18h
sub byte [zeugs+16],18h
sub byte [zeugs+17],18h
sub byte [zeugs+18],18h
sub byte [zeugs+19],18h
sub byte [zeugs+20],18h
sub byte [zeugs+21],18h
sub byte [zeugs+22],18h
sub byte [zeugs+23],18h
sub byte [zeugs+24],18h
sub byte [zeugs+25],18h
sub byte [zeugs+26],18h
sub byte [zeugs+27],18h
sub byte [zeugs+28],18h
sub byte [zeugs+29],18h
sub byte [zeugs+30],18h
sub byte [zeugs+31],18h
sub byte [zeugs+32],18h
sub byte [zeugs+33],18h
sub byte [zeugs+34],18h
sub byte [zeugs+35],18h
sub byte [zeugs+36],18h
sub byte [zeugs+37],18h
sub byte [zeugs+38],18h
sub byte [zeugs+39],18h
sub byte [zeugs+40],18h
sub byte [zeugs+41],18h
sub byte [zeugs+42],18h
sub byte [zeugs+43],18h
sub byte [zeugs+44],18h
sub byte [zeugs+45],18h
sub byte [zeugs+46],18h
sub byte [zeugs+47],18h
sub byte [zeugs+48],18h
sub byte [zeugs+49],18h
sub byte [zeugs+50],18h
jmp sss
db 90h
db 90h
db 90h
Wenn wir die erstellten 3 Programme jetzt mit einem Hexeditor anschauen, können wir also sehen, dass die ersten beiden die "Hello-World"-Meldung im Klartext vorliegen haben, so dass sie von jedem Hinterhof-Hacker verändert werden können, während das im dritten Programm nicht mehr der Fall ist.