Scrittura di un eseguibile ELF e
status di un i386 dopo la sua esecuzione


20/05/2000

by Ritz

 

 

UIC's Home Page

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
E-mail: [email protected] 
IRC chan: #crack-it
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.


Scrittura di un eseguibile ELF e
status di un i386 dopo la sua esecuzione
Written by Ritz

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

Queste informazioni sono solo a scopo puramente didattico. Ogni riferimento a persone o fatti realmente accaduti � da considerarsi puramente casuale, quindi non mi ritengo responsabile se Guglielmo Cancelli si offende per la barza soprastante.

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