2013-02-14 16 views
16

Nel mio tentativo di ottenere "Steam per Linux" lavorando su Debian, ho riscontrato un problema. libcef (cromo incorporato quadro) funziona bene con GLIBC_2.13 (che EGLIBC su Debian testing in grado di fornire), ma richiede un fastidioso piccola funzione in più da GLIBC_2.15 (che EGLIBC non può fornire):Posso ingannare libc (GLIBC_2.13) nel caricare un simbolo che non ha (da GLIBC_2.15)?

$ readelf -s libcef.so | grep -E "@GLIBC_2\.1[4567]" 
1037: 00000000  0 FUNC GLOBAL DEFAULT UND [email protected]_2.15 (49) 
2733: 00000000  0 FUNC GLOBAL DEFAULT UND [email protected]@GLIBC_2.15 

Il mio piano di attacco qui era a LD_PRELOAD una libreria shim che fornisce solo queste funzioni. Questo non sembra funzionare. Voglio davvero evitare di installare GLIBC_2.17 (dato che è in Debian sperimentale, anche Debian sid ha ancora GLIBC_2.13).


Questo è quello che ho provato.

fdelt_chk.c è fondamentalmente rubato from the GNU C library:

#include <sys/select.h> 

# define strong_alias(name, aliasname) _strong_alias(name, aliasname) 
# define _strong_alias(name, aliasname) \ 
    extern __typeof (name) aliasname __attribute__ ((alias (#name))); 

unsigned long int 
__fdelt_chk (unsigned long int d) 
{ 
    if (d >= FD_SETSIZE) 
    __chk_fail(); 

    return d/__NFDBITS; 
} 
strong_alias (__fdelt_chk, __fdelt_warn) 

Il mio script Versions appare come segue:

GLIBC_2.15 { 
    __fdelt_chk; __fdelt_warn; 
}; 

Ho poi costruire la biblioteca come segue:

$ gcc -m32 -c -fPIC fdelt_chk.c -o fdelt_chk.o 
$ gcc -m32 -shared -nostartfiles -Wl,-s -Wl,--version-script Versions -o fdelt_chk.so fdelt_chk.o 

Tuttavia, se quindi esegui Steam (con un po 'di roba extra per averlo lavorando in primo luogo), il caricatore rifiuta ancora di trovare il simbolo:

% LD_LIBRARY_PATH="/home/tinctorius/.local/share/Steam/ubuntu12_32" LD_PRELOAD=./fdelt_chk.so:./steamui.so ./steam 
./steam: /lib/i386-linux-gnu/i686/cmov/libc.so.6: version `GLIBC_2.15' not found (required by /home/tinctorius/.local/share/Steam/ubuntu12_32/libcef.so)  

Tuttavia, il simbolo versione è fornita anche dal .so Ho appena costruito:

% readelf -s fdelt_chk.so 

Symbol table '.dynsym' contains 8 entries: 
    Num: Value Size Type Bind Vis  Ndx Name 
    0: 00000000  0 NOTYPE LOCAL DEFAULT UND 
    1: 00000000  0 FUNC GLOBAL DEFAULT UND [email protected]_2.3.4 (3) 
    2: 0000146c  0 NOTYPE GLOBAL DEFAULT ABS _edata 
    3: 0000146c  0 NOTYPE GLOBAL DEFAULT ABS _end 
    4: 00000310 44 FUNC GLOBAL DEFAULT 11 [email protected]@GLIBC_2.15 
    5: 00000310 44 FUNC GLOBAL DEFAULT 11 [email protected]@GLIBC_2.15 
    6: 00000000  0 OBJECT GLOBAL DEFAULT ABS GLIBC_2.15 
    7: 0000146c  0 NOTYPE GLOBAL DEFAULT ABS __bss_start 

A questo punto, mi non so cosa posso fare per ingannare il caricatore (chi?) nella scelta dei miei simboli. Sto andando nella giusta direzione?

+1

tenta di attivare [LD_DEBUG] (http://www.bnikolic.co .uk/blog/linux-ld-debug.html) '= all' e controlla, come rtld (ld-linux.so) cerca' __fdelt' e '@@ GLIBC_2.15' – osgx

+0

Tanto logspam. Sembra che stia guardando solo a/lib/i386-linux-gnu/i686/cmov/libc.so.6' e rinunciando immediatamente. Forse ho bisogno di scegliere un soname adatto per fdelt_chk.so ... –

+0

Impostando '-Wl, -soname, libc.so.6' uccide tutto, perché ora non guarderà oltre la mia stessa libreria. Sarebbe possibile importare il reale 'libc' con soname assoluto e riesportare tutti gli altri simboli? –

risposta

9

Mi sono imbattuto in questo stesso problema, anche se non con Steam. Quello che stavo cercando di eseguire voleva 2.15 per fdelt_chk mentre il mio sistema aveva 2.14. Ho trovato una soluzione per casi semplici come il nostro in cui possiamo facilmente fornire la nostra implementazione per la funzionalità mancante.

Ho iniziato la tua tentata soluzione di implementazione della funzionalità e LD_PRELOAD. Utilizzando LD_DEBUG=all (come suggerito da osgx) ha mostrato che il linker stava ancora cercando 2.15, quindi il solo simbolo giusto non era sufficiente e c'era qualche altro meccanismo di versioning da qualche parte. Ho notato che objdump -p e readelf -V entrambi hanno mostrato riferimenti a 2.15, quindi ho cercato la documentazione su ELF e ho trovato information on version requirements.

Quindi il mio nuovo obiettivo era trasformare i riferimenti in 2.15 in riferimenti a qualcos'altro. Sembrava ragionevole che potessi semplicemente sovrascrivere le strutture che si riferivano a 2.15 con le strutture che si riferivano ad una versione inferiore, come 2.1. Alla fine, dopo alcune prove ed errori, ho trovato che modificare il giusto Elfxx_Vernaux (es?) In .gnu.version_r era sufficiente, ma credo che l'hacker, immagino.

La sezione .gnu.version_r è una lista di 16-byte Elfxx_Verneed se 16-byte Elfxx_Vernaux es.Ogni voce Elfxx_Verneed è seguita dallo stesso Elfxx_Vernaux es. Per quanto posso dire, vn_file è in realtà quanti sono associati allo Elfxx_Vernaux es, anche se i documenti dicono number of associated verneed array entries. Potrebbe anche essere un malinteso da parte mia, comunque.

Quindi, per iniziare a fare le modifiche, diamo un'occhiata ad alcune delle informazioni da readelf -V. Ho ritagliato parti che non ci interessano.

$ readelf -V mybinary 
<snip stuff before .gnu.version_r> 
Version needs section '.gnu.version_r' contains 5 entries: 
Addr: 0x00000000000021ac Offset: 0x0021ac Link: 4 (.dynstr) 
<snip libraries that don't refer to GLIBC_2.15> 
    0x00c0: Version: 1 File: libc.so.6 Cnt: 10 
    0x00d0: Name: GLIBC_2.3 Flags: none Version: 19 
    0x00e0: Name: GLIBC_2.7 Flags: none Version: 16 
    0x00f0: Name: GLIBC_2.2 Flags: none Version: 15 
    0x0100: Name: GLIBC_2.2.4 Flags: none Version: 14 
    0x0110: Name: GLIBC_2.1.3 Flags: none Version: 13 
    0x0120: Name: GLIBC_2.15 Flags: none Version: 12 
    0x0130: Name: GLIBC_2.4 Flags: none Version: 10 
    0x0140: Name: GLIBC_2.1 Flags: none Version: 9 
    0x0150: Name: GLIBC_2.3.4 Flags: none Version: 4 
    0x0160: Name: GLIBC_2.0 Flags: none Version: 2 

Da questo vediamo che la sezione inizia alle 0x21ac. Ogni file elencato avrà un Elfxx_Verneed seguito da uno Elfxx_Vernaux per ciascuna delle voci secondarie (come GLIBC_2.3). Presumo che l'ordine delle informazioni nell'output corrisponderà sempre all'ordine nel file poiché lo readelf sta scaricando le strutture. Ecco la mia intera sezione.gnu.version_r.

000021A0           01 00 02 00 
000021B0 A3 0C 00 00 10 00 00 00 30 00 00 00 11 69 69 0D 
000021C0 00 00 11 00 32 0D 00 00 10 00 00 00 10 69 69 0D 
000021D0 00 00 0B 00 3C 0D 00 00 00 00 00 00 01 00 02 00 
000021E0 BE 0C 00 00 10 00 00 00 30 00 00 00 13 69 69 0D 
000021F0 00 00 08 00 46 0D 00 00 10 00 00 00 10 69 69 0D 
00002200 00 00 07 00 3C 0D 00 00 00 00 00 00 01 00 02 00 
00002210 99 0C 00 00 10 00 00 00 30 00 00 00 11 69 69 0D 
00002220 00 00 06 00 32 0D 00 00 10 00 00 00 10 69 69 0D 
00002230 00 00 05 00 3C 0D 00 00 00 00 00 00 01 00 02 00 
00002240 AE 0C 00 00 10 00 00 00 30 00 00 00 11 69 69 0D 
00002250 00 00 12 00 32 0D 00 00 10 00 00 00 10 69 69 0D 
00002260 00 00 03 00 3C 0D 00 00 00 00 00 00 01 00 0A 00 
00002270 FF 0C 00 00 10 00 00 00 00 00 00 00 13 69 69 0D 
00002280 00 00 13 00 46 0D 00 00 10 00 00 00 17 69 69 0D 
00002290 00 00 10 00 50 0D 00 00 10 00 00 00 12 69 69 0D 
000022A0 00 00 0F 00 5A 0D 00 00 10 00 00 00 74 1A 69 09 
000022B0 00 00 0E 00 64 0D 00 00 10 00 00 00 73 1F 69 09 
000022C0 00 00 0D 00 70 0D 00 00 10 00 00 00 95 91 96 06 
000022D0 00 00 0C 00 7C 0D 00 00 10 00 00 00 14 69 69 0D 
000022E0 00 00 0A 00 87 0D 00 00 10 00 00 00 11 69 69 0D 
000022F0 00 00 09 00 32 0D 00 00 10 00 00 00 74 19 69 09 
00002300 00 00 04 00 91 0D 00 00 10 00 00 00 10 69 69 0D 
00002310 00 00 02 00 3C 0D 00 00 00 00 00 00 

a parlare brevemente la struttura qui, inizia con un Elfxx_Verneed. Come per i documenti, possiamo vedere che ci saranno 2 Elfxx_Vernaux es, un offset di 16 byte e il successivo Elfxx_Verneed è offset di 48 byte. Questi offset sono dall'inizio della struttura corrente. Sembra che tecnicamente gli associati Elfxx_Vernaux es potrebbero non essere adiacenti dopo l'attuale Elfxx_Verneed ma in realtà era così in tutti i file che ho cercato.

Da questo possiamo trovare il file che vogliamo (libc.so.6) in alcuni modi diversi. Fare riferimento alla stringa (che non entrerò), trovare Elfxx_Verneed con un conteggio di 0A 00 (10, corrispondente all'uscita readelf sopra) o trovare l'ultimo Elfxx_Verneed poiché è l'ultimo output readelf. In ogni caso, quello giusto per il mio file è 0x226C. Il suo primo Elfxx_Vernaux inizia a 0x227C.

Vogliamo trovare lo Elfxx_Vernaux con una versione di 0C 00 (12, di nuovo corrispondente al nostro output readelf in alto). Vediamo lo Elfxx_Vernaux che corrisponde a 0x22CC e l'intera struttura è 95 91 96 06 00 00 0C 00 7C 0D 00 00 10 00 00 00. Sovrascriviamo i primi 12 byte in modo da lasciare solo l'offset. Stiamo solo modificando i dati, non spostandoci attorno alle strutture, dopotutto.

Per scegliere i dati da sovrascrivere, basta copiarli da un diverso Elfxx_Vernaux per una versione di glibc che possiamo soddisfare. Ne ho preso uno per il 2.1, che è al 0x22EC nel mio file, con i dati 11 69 69 0D 00 00 09 00 32 0D 00 00 10 00 00 00. Quindi prendi i primi 12 byte da questo e sovrascrivi i primi 12 byte sopra, e questo è tutto per la modifica esadecimale.

Naturalmente, potresti avere più riferimenti da trattare. Il tuo programma potrebbe avere più file binari da modificare.

A questo punto, il nostro programma continua a non funzionare. Ma invece di essere detto qualcosa come GLIBC_2.15 not found dovrebbe lamentarsi della mancanza di __fdelt_chk. Ora facciamo lo shim e lo LD_PRELOAD descritto nella domanda, tranne che per la versione della nostra implementazione come 2.15, usiamo la versione che abbiamo scelto durante la modifica esadecimale. A questo punto il programma dovrebbe essere eseguito.

Questo metodo dipende dall'essere in grado di fornire un'implementazione per ciò che manca. Il nostro __fdelt_chk è estremamente semplice ma non dubito che in alcuni casi fornire un'implementazione potrebbe essere più difficile rispetto al semplice aggiornamento della libc del sistema.

+0

Grazie per il collegamento! Le tue soluzioni sembrano un hack. Puoi mostrare i dati esadecimali prima e dopo e mostrarci in dettaglio, come trovare gli offset? – osgx

+2

Espansione della mia risposta. È decisamente un hack, heh. – Fyren

+0

Ho accettato questa risposta, non perché è perfetta, ma perché descrive la soluzione più semplice in dettaglio, e penso che non ci sia un buon altro modo per farlo. Grazie :) –

6

Per quello che vale, la funzione __fdelt_chk è correlata alla funzione FORTIFY_SOURCE che è stata aggiunta in glibc 2.15. Abilita il controllo in fase di compilazione e in fase di esecuzione per i buffer overflow.

se foste in grado di ricompilare con il seguente CFLAGS aggiunto, sarebbe costruire un binario compatibile all'indietro, senza il controllo in più:

-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 
+1

Sfortunatamente quel suggerimento -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE = 0 non ha rimosso il simbolo __fdelt_chk. Sto anche collegando a pthreads che forse potrebbero usarlo. – Matt

+0

FWIW, ha risolto il problema per me dopo aver impostato i flag, quindi sostituito readline e quindi questo: https://forums.gentoo.org/viewtopic-p-7807512.html?sid=72aae1be7e1c8994989b783fe1462ee8 –

+0

funziona per me, rimosso __fdelt_chk @ GLIBC_2.15 e [email protected]_2.11 – zhaorufei