Aggiungiamo delle label ad un file ELF - Linux executable
Pubblicato da Pusillus il 14/02/2000
Livello avanzato

Introduzione

Da qualche tempo questo SO sta riscuotendo sempre piu' popolarita' alla faccia di chi non aveva mai visto come un concorrente un progetto open source! (vedi Micro$oft).
Fino a poco tempo fa dal punto di vista del reversing non si era guardato molto a Linux perche' tutto il software era distribuito in forma di sorgente e quindi ritenuto poco utile. Ma ultimamente le cose stanno cambiando e molte software house stanno implementando programmi e giochi per Linux.
Certo siamo ancora agli albori, non esistono dei debugger di sistema come SoftIce per Linux (a parte kdb.....), i disassembler sono home made ecc. ecc., ma questo rende tutto ancora piu' interessante no!?
Da questo punto di vista Linux e' una fonte inesauribile visto che sono disponibili in in rete documentazioni di ogni tipo e i sorgenti di qualsiasi cosa si voglia.

Programmi usati

Iniziamo

Ulteriori informazioni su questi tools e alcuni interessanti tutorial sono disponibili su http://hculinux.cjb.net
Dopo questo breve preambolo, inizierei subito a spiegarvi gli intenti di questo tute: quello che vi propongo e' un breve studio del formato ELF comune a Linux a e a molti altri Unix in circolazione.
In pratica si tratta dell'equivalente del formato PE per il Windows, anzi si puo dire che il PE non e' altro che una scopiazzatura del formato ELF molto semplificata, infatti il formato ELF e' molto piu' complicato ma per fortuna ben documentato.
Lo scopo di questo tute e' quello di aggiungere alcune informazioni all'interno dell'ELF che ci permetteranno di definire delle label in modo da facilitarne il debugging, sara' possibile quindi settare un breakpoint su una label con un nome da noi impostato corrispondente ad un determinato indirizzo.
Quando viene compilato un programma con il GCC con le opzioni di debugging (-g) all'interno del programma eseguibile saranno presenti tutte le infomazioni che permettono al debugger di raggiungere variabili funzioni etichette ecc.. Ma attraverso un semplice comando: "strip" e' possibile eliminare tutte queste utili info in un colpo solo e far diventare il programma abbastanza criptico considerando pure il fatto che il formato AT&T dell'assembler mostrato dal debugger non ci molto familiare, anche se si dice che sia meno contraddittorio di quello Intel.
La struttura di un file eseguibile ELF questa:


Execution View
==============
ELF header
Program header table
Segment 1
Segment 2
...
Section header table (optional)
ELF header: risiede all'inzio del file e descrive l'organizzazione del file.
Program header table: informa il loader come creare un immagine in memoria ed eseguirla Segment: i vari segmenti del programma.
Section header table: contiene le descrizioni delle sezioni del file ELF.
Soltanto 'ELF header' ha una posizione fissa all'inizio del file, le sezioni e i segmenti non hanno un ordine prestabilito.
Vediamo ora in dettaglio la struttura di queste sezioni (alleghero' anche il documento sul formato ELF di cui mi servito, molto piu' dettagliato dei miei commenti):

ELF header 

#define EI_NIDENT 16 

typedef struct {
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} Elf32_Ehdr;
e_ident: marcaggio del file come ELF uguale a: '0x7f E L F';
e_type: tipo di object file;
e_machine: tipo di processore: Intel386, MIPSrs3000, SPARC ecc.;
e_version: object file version;
e_entry: virtual address entry point;
e_phoff: program header table offset;
e_shoff: Section header table offset;
e_flags: flag specifici dipendenti dal tipo di processore;
e_ehsize: dimensioni dell' ELF header;
e_phentsize: dimensioni in bytes di una entry della program header table;
e_phnum: numero entries della program header table;
e_shentsize: dimensione in bytes di una entry della section header table;
e_shnum: numero entries della section header table;
e_shstrndx: indice alla section header table contenente la tabella dei nomi delle sezioni (.shstrtab).
Section header
typedef struct {
Elf32_Word sh_name;
Elf32_Word sh_type;
Elf32_Word sh_flags;
Elf32_Addr sh_addr;
Elf32_Off sh_offset;
Elf32_Word sh_size;
Elf32_Word sh_link;
Elf32_Word sh_info;
Elf32_Word sh_addralign;
Elf32_Word sh_entsize;
} Elf32_Shdr;
sh_name : in questa variabile e' contenuto un indice alla sezione 'section header string table (.shstrtab)' nella quale risiedono i nomi di tutte le sezioni del file ELF (1);
sh_type : tipo di sezione;
sh_flags : attributi della sezione;
sh_addr : virtual address, cioe come apparira' l'indirizzo in memoria di questa sezione, se non usato = 0;
sh_offset : file offset della sezione, cioe' la posizione fisica all'interno dell'eseguibile;
sh_size : dimensioni in bytes della sezione;
sh_link : section header table index link;
sh_info : extra informations;
sh_addralign : allineamento di indirizzo;
sh_entsize : dimensioni in bytes di una entry se il tipo di sezione prevede delle dimensioni fisse per le entries;
(1): Per ottenere il nome di una sezione basta sommare questo indice all'offset della .shstrtab; a tal scopo possiamo utilizzare BIEW:
editando un file ELF possiamo, tramite ALT+F9, navigare tra le varie sezioni e visualizzare l'offset nel file ELF della .shstrtab.
Sezioni di partcolare interesse:
.shstrtab : tabella con i nomi delle sezioni;
.strtab : string table in cui sono contenuti i nomi associati alla symbol table (.symtab);
.symtab : (symbol table) in questa sezione sono contenute tutte le informazioni per localizzare i riferimenti e le definizioni riguardanti l'eseguibile stesso.
Ogni entry della symtab ha una struttura definita:
typedef Struct { 
Elf32_Word st_name; 
Elf32_Addr st_value; 
Elf32_Word st_size; 
unsigned char st_info; 
unsigned char st_other; 
Elf32_Half st_shndx; 
} Elf32_Sym;
st_name : indice alla .strtab contenente i nomi dei simboli;
st_value : valore associato al simbolo (un valore assoluto, un indirizzo ecc.);
st_size : dimensioni associate al simbolo;
st_info : tipo di simbolo;
st_other : non utilizzato = 0;
st_shndx : ogni simbolo e' definito in relazione ad una sezione e in questo campo c'e' il riferimento alla relativa section header table index.
NB: le string tables sono composte da sequenze di caratteri terminati da un carattere null.
NBB: tutte le definizioni di queste strutture sono incluse nel file elf.h presente nella directory di include di Linux.
In un programma 'ripulito' tramite il comando strip verranno eliminate la .strtab e la .symtab assieme ad altre cose. Quello che cercheremo di fare e' di modificare il file ELF in modo da costruire una nuova .strtab e .symtab nelle quali inseriremo delle definizioni di labels.
Modifiche apportate al file ELF compilato con il GCC dal comando strip:
Elf32_Ehdr.e_shnum : viene modificato perche' il numero delle sezioni verra decrementato.
.stab : viene eliminata.
.stabstr : " "
.note : " "
.strtab : " "
.symtab : " "
NOTA: .stab .stabstr .note contengono altre informazioni di debug che non ho considerato in questo tute.
Per avere un file di esempio ho compilato, e 'strippato', il classico "hello world" che allego al tute e che useremo per i nostri test.
Come prima cosa bisogna modificare l'elf header e incrementare Elf32_Ehdr.e_shnum di due, in modo che il loader riconosca le due nuove sezioni che aggiungeremo all'eseguibile.
L'offset nel file mainstrip e' 30h e il valore 19h va incrementato di 2
File : mainstrip Size : 2980 bytes 12% 
00000000: 7F 45 4C 46 01 01 01 00 00 00 00 00 00 00 00 00 ?ELF A A A 
00000010: 02 00 03 00 01 00 00 00 30 83 04 08 34 00 00 00 B C A 0â D 
00000020: BC 07 00 00 00 00 00 00 34 00 20 00 06 00 28 00 +^G 4 ^F 
-->00000030: 19 00 18 00 06 00 00 00 34 00 00 00 34 80 04 08 ^Y ^X ^F 4 
00000040: 34 80 04 08 C0 00 00 00 C0 00 00 00 05 00 00 00 4Ç^D^H+ + E 
00000050: 04 00 00 00 03 00 00 00 F4 00 00 00 F4 80 04 08 ^D ^C 
00000060: F4 80 04 08 13 00 00 00 13 00 00 00 04 00 00 00 Ç^D^H^S ^S
Adesso dobbiamo ispezionare la .shstrtab (la tabella contenete i nomi delle sezioni), trovare i nomi corrispondenti alla .strtab e alla .symtab e prender nota dell'index all'interno della sezione. Fortunatamente il comando strip non altera il contenuto della .shstrtab e i nomi delle sezioni rimangono invariati anche se i realativi section headers vengono rimossi.
000006E5: 00 2E 73 79 6D 74 61 62 00 2E 73 74 72 74 61 62 .symtab .strtab 
000006F5: 00 2E 73 68 73 74 72 74 61 62 00 2E 69 6E 74 65 .shstrtab .inte 
00000705: 72 70 00 2E 6E 6F 74 65 2E 41 42 49 2D 74 61 67 rp .note.ABI-tag 
00000715: 00 2E 68 61 73 68 00 2E 64 79 6E 73 79 6D 00 2E .hash .dynsym . 
00000725: 64 79 6E 73 74 72 00 2E 67 6E 75 2E 76 65 72 73 dynstr .gnu.vers 
00000735: 69 6F 6E 00 2E 67 6E 75 2E 76 65 72 73 69 6F 6E ion .gnu.version 
00000745: 5F 72 00 2E 72 65 6C 2E 67 6F 74 00 2E 72 65 6C _r .rel.got .rel 
00000755: 2E 70 6C 74 00 2E 69 6E 69 74 00 2E 70 6C 74 00 .plt .init .plt 
00000765: 2E 74 65 78 74 00 2E 66 69 6E 69 00 2E 72 6F 64 .text .fini .rod 
00000775: 61 74 61 00 2E 64 61 74 61 00 2E 65 68 5F 66 72 ata .data .eh_fr 
00000785: 61 6D 65 00 2E 63 74 6F 72 73 00 2E 64 74 6F 72 ame .ctors .dtor 
00000795: 73 00 2E 67 6F 74 00 2E 64 79 6E 61 6D 69 63 00 s .got .dynamic 
000007A5: 2E 62 73 73 00 2E 63 6F 6D 6D 65 6E 74 00 2E 6E .bss .comment .n 
000007B5: 6F 74 65 00 00 00 00 00 00 00 00 00 00 00 00 00 ote
come potrete notare i nomi delle sezioni sono in testa alla .shstrtab e gli indici corrispondenti sono .symtab=1 .strtab=9 . Questi due valori ci saranno utili per la costituzione dei nuovi section headers.
Spostiamoci al section header table offset ricavabile ispezionando Elf32_Ehdr.e_shoff. Nel nostro caso corrispondente ai 4 bytes che partono dalla locazione 020h e contenete il valore 000007BCh.
A questo indirizzo ci sara' un array di Elf32_Shdr di 19h elementi composti a loro volta da 10 variabili di 4 bytes quindi (19h*10*4) 1000bytes che ci portano all'indirizzo BA3h che corrisponde alla fine del file e proprio qui aggiungeremo i nostri due nuovi headers:

section header .symtab :
hsymtab.sh_name = 00000001; l'indice alla .shstrtab di cui avevamo preso nota
hsymtab.sh_type = 00000002; SHT_SYMTAB
hsymtab.sh_flags = 00000000;
hsymtab.sh_addr = 00000000;
hsymtab.sh_offset = 00000BF4; offset dove risiedera nel file la .symtab (2)
hsymtab.sh_size = 00000040; dimensioni in bytes della .symtab (3)
hsymtab.sh_link = 0000001A; section header index della string table associata, nel nostro caso il riferimento e' la .strtab che inseriremo subito dopo la .symtab il suo indice nella tabella risultera' 1A.
hsymtab.sh_info = 00000003; di 1 piu grande del symtab index dell'ultimo simbolo locale (4)
hsymtab.sh_addralign = 00000004; tipo di allineamento
hsymtab.sh_entsize = 00000010; dimensioni di una entry. Nel nostro caso sizeof(Elf32_Sym).
(2) la .symtab risiedera' subito dopo le due nuove entries x il section header dato che ogni sezione e' di 40 bytes le due sezioni occupano 50h bytes BA4+50 = BF4 .
(3) nel nostro esempio ho deciso di inserire 3 nuove labels + una entry vuota, symtab.sh_entsize * 4 = 40h
(4) questi valori sono ricavabili dalla tabella e dipendono dal valore di symtab.sh_type:
sh_type sh_link sh_info 
======= ======= ======= 
SHT_DYNAMIC The section header index of 0 the string table used by entries in the section. 
SHT_HASH The section header index of 0 the symbol table to which the hash table applies. 
SHT_REL, The section header index of The section header index of 
SHT_RELA the associated symbol table. the section to which the relocation applies. 
SHT_SYMTAB, The section header index of One greater than the symbol 
SHT_DYNSYM the associated string table. table index of the last local symbol (binding STB_LOCAL). 
other SHN_UNDEF 0 

section header .strtab: 
hstrtab.sh_name = 00000009; indice alla .shstrtab 
hstrtab.sh_type = 00000003; SHT_STRTAB 
hstrtab.sh_flags = 00000000; 
hstrtab.sh_addr = 00000000; 
hstrtab.sh_offset = 00000C34; offset dove risiedera' nel file la .strtab (5) 
hstrtab.sh_size = 00000028; dimensione della sezione in bytes 
hstrtab.sh_link = 00000000; 
hstrtab.sh_info = 00000000; 
hstrtab.sh_addralign = 00000001; tipo di allineamento 
hstrtab.sh_entsize = 00000000; non specificato, si tratta di null-terminated strings
(5) la .strtab sara' inserita dopo la .symtab quindi considerando i 40h bytes che compongono la .symtab ci troveremo prorio a 0C34h.
A questo punto non ci resta che inserire le 4 entries della .symtab. analizziamo la seconda, considerato che la prima e' vuota:
symtab.st_name =00000002; indice alla .strtab (nome della label)
symtab.st_value =080483f5; indirizzo a cui e' riferita label
symtab.st_size =00000000;
symtab.st_info = 00; ELF32_ST_INFO(STB_LOCAL, STT_NOTYPE)
symtab.st_other = 00;
symtab.st_shndx = 000C; indice del section heder a cui ci riferiamo: nel nostro caso ".text" la numero 000C entries nella section header table
I valori di st_info sono stati ottenuti con il programma 'elfls' del pacchetto ELFkickers in quanto non sono riuscito a trovare info sulle opzioni di debugging. Ma sicuramente ci saranno dei doc sulla rete.
La .strtab e' costituita da null-terminated strings quindi non credo abbia bisogno di spiegazioni particolari.
Le modifiche apportate dovrebbero far apparire il nostro programma in questo modo:
00000BA4: 01 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 ^A ^B 
00000BB4: F4 0B 00 00 40 00 00 00 1A 00 00 00 03 00 00 00 ^K @ ^Z ^C 
00000BC4: 04 00 00 00 10 00 00 00 09 00 00 00 03 00 00 00 ^D ^P ^C 
00000BD4: 00 00 00 00 00 00 00 00 34 0C 00 00 28 00 00 00 4^L ( 
00000BE4: 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 ^A 
00000BF4: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 0C 00 ^A ^L 
00000C04: 02 00 00 00 F5 83 04 08 00 00 00 00 00 00 0C 00 ^B §â^D^H ^L 
00000C14: 08 00 00 00 E8 83 04 08 00 00 00 00 00 00 0C 00 ^H Þâ^D^H ^L 
00000C24: 0E 00 00 00 DB 83 04 08 00 00 00 00 00 00 0C 00 ^N _â^D^H ^L 
00000C34: 00 00 72 69 67 61 33 00 72 69 67 61 32 00 72 69 riga3 riga2 ri 
00000C44: 67 61 31 00 00 00 00 00 00 00 00 00 00 00 00 00 ga1 
00000C54: 00 00 00 00 00 00 00 00 
Finalmente abbiamo concluso la manipolazione del file, e devo dire che smanettare con gli editor hex e' una bella scocciatura!
Possiamo ora testare il nostro file modificato con il gdb:
>gdb mainstrip 
GNU gdb 4.18 
Copyright 1998 Free Software Foundation, Inc. 
GDB is free software, covered by the GNU General Public License, and you are 
welcome to change it and/or distribute copies of it under certain conditions. 
Type "show copying" to see the conditions. 
There is absolutely no warranty for GDB. Type "show warranty" for details. 
This GDB was configured as "i686-pc-linux-gnu"... 
(no debugging symbols found)... 
(gdb) br riga1 
Breakpoint 1 at 0x80483db 
(gdb) br riga2 
Breakpoint 2 at 0x80483e8 
(gdb) br riga3 
Breakpoint 3 at 0x80483f5 
(gdb) run 
Starting program: /mainstrip 
Breakpoint 1, 0x80483db in riga1 () 
(gdb) disassemble riga1 
Dump of assembler code for function riga1: 
0x80483db <riga1>: mov %ebp,%esp 
0x80483dd <riga1+2>: pop %ebp 
0x80483de <riga1+3>: ret 
0x80483df <riga1+4>: nop 
0x80483e0 <riga1+5>: push %ebp 
0x80483e1 <riga1+6>: mov %esp,%ebp 
0x80483e3 <riga1+8>: mov %ebp,%esp 
0x80483e5 <riga1+10>: pop %ebp 
0x80483e6 <riga1+11>: ret 
0x80483e7 <riga1+12>: nop 
End of assembler dump. 
(gdb) cont 
Continuing. 
Breakpoint 2, 0x80483e8 in riga2 () 
(gdb) disassemble riga2 
Dump of assembler code for function riga2: 
0x80483e8 <riga2>: push $0x8048464 
0x80483ed <riga2+5>: call 0x8048314 <printf> 
0x80483f2 <riga2+10>: add $0x4,%esp 
End of assembler dump. 
(gdb) cont 
Continuing. 
ciao bello 
Breakpoint 3, 0x80483f5 in riga3 () 
(gdb) cont 
Continuing. 
Program exited normally. 
(gdb) q 
>
Quello che abbiamo fatto non e' molto, solo un piccolo aiuto per il debugging. Sicuramente inserire a mano queste informazioni e' una grossa perdita di tempo e non ne varrebbe la pena, ma ho fatto un programmino che dovrebbe fare tutto questo per noi. Purtroppo credo sia pieno di bachi perche' l'ho dovuto fare in piu' riprese, spero presto di dedicargli un po del mio tempo per sistemare il sorgente C, renderlo pubblico e magari aggiungere altre funzioni oltre all'inserimento di labels, ammesso che sia possibile, visto che non ho potuto ancora approfondire la faccenda. Magari qualche volenteroso potrebbe darmi una mano ;))
Per usare il programma, che ho deciso di chiamare LABELZ bisogna prima creare un file di testo dove inseriremo indirizzo e nome delle labels in questo modo:
file main.dat
----------------------------
80483db riga1
80483e8 riga2
80483f5 riga3
----------------------------
per inserire le labels nel nostro eseguibile ELF bisogna digitare:
>labelz exefile labelfile
Il programma labelz precompilato necessita delle libc6.
Labelz v0.01

Conclusioni

Spero di non essere stato troppo noiso, e di avervi proposto qualcosa di diverso del solito Win reversing.
Pusillus.