2012-01-16 7 views
9

Per vari scopi, sto cercando di ottenere l'indirizzo dell'intestazione ELF dell'eseguibile principale senza analizzare /proc/self/maps. Ho provato ad analizzare la catena link_list fornita dalle funzioni dlopen/dlinfo ma non contiene una voce in cui l_addr punta all'indirizzo di base dell'eseguibile principale. C'è un modo per farlo (Standard o no) senza analizzare /proc/self/maps?Ottenere l'intestazione ELF dell'eseguibile principale

Un esempio di quello che sto cercando di fare:

#include <stdio.h> 
#include <elf.h> 
int main() 
{ 
    Elf32_Ehdr* header = /* Somehow obtain the address of the ELF header of this program */; 
    printf("%p\n", header); 
    /* Read the header and do stuff, etc */ 
    return 0; 
} 
+1

'read()' da '/ proc/self/exe' – Dave

+0

Ah, ho dimenticato di menzionare, ho anche bisogno dell'indirizzo di base insieme all'intestazione ELF, e l'intestazione ELF dovrebbe essere dove l'indirizzo di base è –

risposta

16

Il puntatore void * restituito da dlopen(0, RTLD_LAZY) ti dà un struct link_map *, che corrisponde al file eseguibile principale.

La chiamata dl_iterate_phdr restituisce inoltre la voce per l'eseguibile principale sulla prima esecuzione di callback.

Probabilmente siete confusi dal fatto che .l_addr == 0 nella mappa dei collegamenti e da quello dlpi_addr == 0 quando si utilizza dl_iterate_phdr.

Questo sta accadendo, perché l_addr (e dlpi_addr) non registrano effettivamente l'indirizzo di caricamento di un'immagine ELF. Piuttosto, registrano il riposizionamento che è stato applicato a quell'immagine.

Solitamente l'eseguibile principale è costruito per essere caricato 0x400000 (per Linux x86_64) oa 0x08048000 (per ix86 Linux), e vengono caricati in quello stesso indirizzo (cioè esse non vengono spostati).

Ma se si collega il vostro eseguibile con -pie bandiera, allora saranno collegati al-0x0, e saranno trasferiti a qualche altro indirizzo.

Quindi, come si arriva all'intestazione ELF? Facile:

#ifndef _GNU_SOURCE 
#define _GNU_SOURCE 
#endif 

#include <link.h> 
#include <stdio.h> 
#include <stdlib.h> 

static int 
callback(struct dl_phdr_info *info, size_t size, void *data) 
{ 
    int j; 
    static int once = 0; 

    if (once) return 0; 
    once = 1; 

    printf("relocation: 0x%lx\n", (long)info->dlpi_addr); 

    for (j = 0; j < info->dlpi_phnum; j++) { 
    if (info->dlpi_phdr[j].p_type == PT_LOAD) { 
     printf("a.out loaded at %p\n", 
      (void *) (info->dlpi_addr + info->dlpi_phdr[j].p_vaddr)); 
     break; 
    } 
    } 
    return 0; 
} 

int 
main(int argc, char *argv[]) 
{ 
    dl_iterate_phdr(callback, NULL); 
    exit(EXIT_SUCCESS); 
} 


$ gcc -m32 t.c && ./a.out 
relocation: 0x0 
a.out loaded at 0x8048000 

$ gcc -m64 t.c && ./a.out 
relocation: 0x0 
a.out loaded at 0x400000 

$ gcc -m32 -pie -fPIC t.c && ./a.out 
relocation: 0xf7789000 
a.out loaded at 0xf7789000 

$ gcc -m64 -pie -fPIC t.c && ./a.out 
relocation: 0x7f3824964000 
a.out loaded at 0x7f3824964000 

Aggiornamento:

Perché la pagina man dire "l'indirizzo di base" e non delocalizzazione?

Si tratta di un bug ;-)

sto indovinando che la pagina è stata scritta molto tempo prima che prelink e pie e ASLR esistito. Senza prelink, le librerie condivise sono sempre collegate per caricare all'indirizzo 0x0, e quindi relocation e base address diventano una e la stessa cosa.

come mai dlpi_name punta a una stringa vuota quando le informazioni si riferiscono all'eseguibile principale?

È un incidente di implementazione.

Il modo in cui funziona, è che il kernel open(2) s l'eseguibile e passa il descrittore di file aperto al caricatore (nel auxv[] vettore, come AT_EXECFD). Tutto il il caricatore sa dell'eseguibile ottenuto leggendo il descrittore di file.

Non esiste un modo semplice in UNIX per eseguire il mapping di un descrittore di file al nome con cui è stato aperto. Per prima cosa, UNIX supporta i collegamenti fisici e potrebbero esserci più nomi di file che fanno riferimento allo stesso file.

I nuovi kernel di Linux passano anche il nome che era stato utilizzato per execve(2) l'eseguibile (anche in auxv[], come AT_EXECFN). Ma questo è opzionale, e anche quando viene passato, glibc non lo inserisce in .l_name/dlpi_name per non interrompere i programmi esistenti che sono diventati dipendenti dal fatto che il nome sia vuoto.

Invece, glibc salva quel nome in __progname e __progname_full.

Il caricatore COUDreadlink(2) il nome da /proc/self/exe sui sistemi che non hanno utilizzato AT_EXECFN, ma il /proc file system non è garantito per essere montato, in modo che sarebbe ancora lasciare con un nome vuoto volte.

+0

Sì, credo di essere stato confuso allora. Ma mi chiedo, dove nella pagina di manuale si dice che 'l_addr' o' dlpi_addr' è l'indirizzo trasferito? Tutte le manpage che ho letto dicono solo "indirizzo di base" –

+0

Inoltre, come mai 'dlpi_name' punta a una stringa vuota quando' info' si riferisce all'eseguibile principale? Non dovrebbe contenere il nome dell'eseguibile principale? –

+0

Ho aggiornato la risposta. Ottieni 3 risposte al prezzo di 1 ;-) –

0

C'è la dl_iterate_phdr glibc funzione di(). Non sono sicuro che ti dia esattamente quello che vuoi, ma questo è il più vicino che conosco:

"La funzione dl_iterate_phdr() consente a un'applicazione di informarsi in fase di esecuzione per scoprire quali oggetti condivisi ha caricato. " http://linux.die.net/man/3/dl_iterate_phdr

+0

ottiene tutti gli oggetti condivisi che il programma ha caricato, cosa che posso già fare passando attraverso la catena link_list, e probabilmente è ciò che fa nella funzione. Ma voglio l'indirizzo di base dell'applicazione stessa, non gli oggetti condivisi che ha caricato. –

+0

Hai provato che non restituisce l'applicazione stessa? –

+0

Scusate, stavo semplicemente andando fuori da ciò che ho letto nella manpage –

Problemi correlati