Scrittura di un
eseguibile ELF e |
|
|
20/05/2000 |
by Ritz |
|
|
Published by Quequero |
|
Bravo ritz che vi presenta il secondo tute nella nostra nuova sezione dedicata al Linux Reversing e vi spiega in modo chiaro e conciso cosa sia il formato ELF...E vi sarete di certo accorti che il PE altro non � che una ignobile semplificazione dell'ELF :) |
||
UIC's form |
|
UIC's form |
Difficolt� |
( )NewBies (x)Intermedio ( )Avanzato ( )Master |
Ecco in questo tute un esempio di asm prog sotto Linux con conseguente spiegazione su cosa succede nel computer una volta che esso � avviato.
Introduzione |
Come da oggetto;).
Tool usati |
NASM
ld
vi / ed / emacs / quel cazzo che preferite (li trovate sul vostro hard disk...NdQue)
Essay |
E finalmente ecco che si inizia a programmare... ma prima degli appunti da tenere in considerazione.
Linux, come Win32, gira sui proc Intel x86 in modalit� protetta (ring rulezz), e utilizza il modello di memoria flat (per fortuna), quindi abbiamo 4Gb di memoria allocabili come preferiamo (beh vabb� sono meno di 4 Gb., tra SO ecc viene mangiato molto spazio). Per ora non serve saper altro sulla gestione della memoria.
Una nota particolare meritano invece le convenzioni di chiamata: infatti non so come cazzo sia cos�, per� in Linux i parametri di una call non devono essere pushati nell stack come d'altronde accade per gli stessi FreeBSD e BeOS, bens� devono essere messi sequenzialmente nei registri ebx, ecx, edx, esi ed edi, eax contiene il valore di ritorno, lo stack non � nemmeo sfiorato. Sinceramente non ho idea del perch� di questo fatto, forse per una questione di velocit�: l'accesso diretto ai registri � sicuramente + veloce di quello allo stack, ma � sono una supposizione... che ne dici Que? Stranezze della vita...
Le chiamate al kernel avvengono semplicemente tramite un int 0x80, il numero della chiamata va messo in eax.
La struttura specifica di un file elf sar� oggetto di un prossimo tute, cmq anch'essi sono formati da varie sezioni, la .text deve essere sempre presente e rappresenta quella eseguibile, la .data contiene i dati, .bss per i dati non inizializzati:).
Detto questo, ecco un sorgente Linux per NASM (sintassi Intel):
section .text ;tipo sezione
global _start ;questo serve per il linker
msg db 'Salve mondo',0xA ;messaggio +
carattere invio
len equ $ - msg ;lunghezza messaggio
_start: ;entrypoint
mov edx,len ;edx = lunghezza
mov ecx,msg ;ecx = offset mex
mov ebx,1 ;standard output
mov eax,4 ;numero chiamata
(sys_write)
int 0x80 ;chiamata al kernel
mov eax,1 ;numero chiamata
int 0x80 ;chiamata al kernel (sys_exit)
I sorgenti sono molto semplici da comprendere: vengono dichiarati mex e sua lunghezza, si dichiara l'entrypoint, ebx contiene il file descriptor, edx la lunghezza, ecx punta alla stringa (che deve terminare con 0x00), in eax viene messo il numero di chiamata del kernel che si desidera, in questo caso 4, ovvero sys_write e poi la chiamata stessa viene eseguita. Subito dopo in eax si mette il valore 1 e viene chiamato ancora il kernel, precisamente la funzione sys_exit, che esce dal prog. Fine.
Altre info per Linux su /sys/syscall.h (sys call numbers) o asm/unistd.h, kernel src su arch/i386/kernel/entry.S, include/asm-i386/unistd.h, include/linux/sys.h.
/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
Per quanto riguarda FreeBSD, le regole sono un po' diverse da Linux: ecco lo stesso prg.
section .text
global _start
msg db "Hello, world!",0xa
len equ $ - msg
_syscall:
int 0x80 ;la nostra chiamata al kernel
ret
_start:
push dword len ;
push dword msg ;
push dword 1 ;convenzione della chiamata
mov eax,0x4 ;
call _syscall ;
;si sarebbe potuto chiamare il kernel anche con
;push eax
;call 7:0
add esp,12 ;stack pulito
push dword 0
mov eax,0x1
call _syscall
La parte iniziae non cambia, ma la convenzione
delle chiamate s�: infatti i paramentri passati devono essere pushati nello stack, e le
chiamate al kernel non devono mai essere fatte direttamente, bens� attraverso un'altra
chiamata interna al prog che a sua volta chiama l'int 0x80. Dopo tale chiamata, il valore
di ritorno � sempre in eax e lo stack deve essere pulito (in questo caso tramite add esp,
12). C'� cmq un'alternativa a tale chiamata, infatti � possibile, al posto dell'int
0x80, chiamare il gate 7:0 pushando prima eax, e ci� comporta un aumento di dimensioni
dell'elf finale, anche se il risultato non cambia.
Altre info per FreeBSD su sys/syscall.h (contiene anche i vari call numbers), i386/i386/exception.s, i386/i386/trap.c.
/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
Per quanto riguarda BeOS, le chiamate sono *identiche* a FreeBSD (lo stesso sorgente non cambia), a parte il fatto che l'int chiamato � 0x25 e i system call number (quelli da mettere in eax) sono diversi (il sys_write � 3 e sys_exit � 3F). Piccola nota: da BeOS i src devono essere compilati col NASM inserendo la riga #include "nasm.h" in float.h e #include <stdio.h> in nasm.h.
Per ulteriori info sul BeOS, consultare il file os_beos.inc delle asmutils.
Per tutti e 3 i sistemi, compilate con $ nasm -f elf ciao.asm, che produrr� l'obj file, e quindi linkate con $ ld -s -o ciao ciao.asm per creare l'eseguibile. Lanciate (./ciao) dopo aver dato i giusti permessi eventualmente. Bene. Forse � superfluo dirlo... osservate la grandezza. Scrivete lo stesso prog in c. Osservate la lunghezza. Confrontate. Bello eh?
E adesso ecco un piccolo approfondimento su cosa accade a grandi linee in un sistema i386 che usi eseguibili ELF (Linux, FreeBSD, BeOS, ecc.) quando un prg viene lanciato (tali cose possono essre facilmente scoperte osservando i file binfmt_rlf.c e sched.h).
Una volta che dalla shell si preme Invio dopo aver scritto il nome di un prg, viene eseguita la call sys_execve(). Ogni prg in memoria ciuccia minimo 2 pagine: una per la sezione .data e una per lo stack, argomenti ed esecuzione. Ecco un classico layout del segmento creato.
code | sezione .text |
data | sezione .data |
bss | sezione .bss |
................... | spazio vuoto |
stack | stack |
argoments | argomenti del prg |
environment | environment |
program name | nome del file elf |
dw null | dword finale |
Ed ecco cosa accade durante l'esecuzione.
Funzione | Kernel File | Commento |
shell | ......... | il loggato ;) scrive il nome del prg e preme invio |
execve() | ......... | la shel chiama la funzione libc |
sys_execve() | ......... | la libc chiama il kernel |
sys_execve() | arch/i386/kernel/process.c | zona kernel |
do_execve() | fs/exec.c | apre il file e prepara un po' di robette |
search_binary_handler() | fs/exec.c | osserva il tipo di eseguibile |
load_alf_binary() | fs/binfmt.c | carica l'elf e le relative librerie e crea il segmento apposito |
start_thread() | include/asm-i386/processor.h | passa il controllo al codice del nostro prg |
Questo invece � lo stato dello stack quando l'elf viene lanciato.
argc | dw (argumenti counter, integer) |
argv(0) | dw (nome del prg, puntatore) |
argv(1) ............ argv(argc-1) |
dw (argomenti del programma, puntatori) |
NULL | dw (fine argomenti, integer) |
env(0) env(1) ............... env(n) |
dw (puntatori alle variabili) |
NULL | dw |
Ecco quindi che per prendere gli argomenti basta popparli uno per uno dallo stack.
Per finire, ecco lo status dei registri come viene organizzato una volta lanciato il prg, quali sono modificati e quali no (i valori, a parte 0x0, sono cmq solo un esempio, dipendono da caso a caso).
Registri | Kernel 2.0.x | Da kernel 2.2.10 in poi |
EAX | 0x0 | 0x0 |
EBX | non modificato | 0x0 |
ECX | non modificato | 0x0 |
EDX | 0x0 | 0x0 |
ESI | non modificato | 0x0 |
EDI | non modificato | 0x0 |
EBP | non modificato | 0x0 |
ESP | 0xBFFFFE14 | 0xBFFFFB40 |
EFLAGS | 0x282 | 0x292 |
CS | 0x23 | 0x23 |
DS | 0x2B | 0x2B |
ES | 0x2B | 0x2B |
FS | 0x2B | 0x0 |
GS | 0x2B | 0x0 |
SS | 0x2B | 0x2B |
Anche per oggi � tutto, buon divertimento con Linux!
Note finali |
Spero di esservi stato utile, per dubbi chiarimenti proposte ecc. scrivetemi pure all'indirizzo sopra. Voglio concludere con una piccola cazzata che tanto non far� ridere nessuno...
Guglielmo Cancelli sta' facendo l'amore quando la compagna gli dice: "Caro non godo per niente!" E lui: "Annullo, Riprovo, Ignoro o Tralascio?" (Tralascia tralasciaaaa n.d.Ritz)Ciauz a tutti,
@2000 by Ritz
Disclaimer |
UIC's page of reverse engineering, scegli dove andare: |
Home Anonimato Assembly CrackMe ContactMe Forum Iscrizione |
Lezioni Links Linux NewBies News Playstation |
Tools Tutorial Search UIC Faq |
UIC |