2012-05-05 15 views
19

Ich experimentiere mit dem Konzept der pure-static-linked PIE ausführbaren Dateien auf Linux, aber mit dem Problem, dass die GNU Binutils-Linker darauf besteht, einen PT_INTERP-Header zum Ausgang binär hinzufügen, wenn -pie verwendet wird, auch wenn auch -static gegeben . Gibt es eine Möglichkeit, dieses Verhalten zu verhindern? Das heißt, gibt es eine Möglichkeit, GNU ld spezifisch mitzuteilen, bestimmte Header nicht in die Ausgabedatei zu schreiben? Vielleicht mit einem Linker-Skript?Gibt es eine Option zu GNU ld, um -dynamic-linker (PT_INTERP) komplett wegzulassen?

(Bitte nicht mit Behauptungen beantworten, dass es nicht funktioniert; ich bin mir bewusst, dass das Programm noch Relocation-Verarbeitung - load-Adresse-relative Umzüge nur aufgrund meiner Verwendung von -Bsymbolic - und ich habe spezielle Startcode anstelle des Standards Scrt1.o, um dies zu handhaben, aber ich kann es nicht aufgerufen werden, ohne den dynamischen Linker bereits treten und die Arbeit zu tun, außer hexedit die PT_INTERP Header aus der Binärdatei.)

+0

lassen Sie mich sehen, ob ich diese gerade haben. Sie geben Ihren eigenen Einstiegspunkt an, der wiederum einen benutzerdefinierten Umzug durchführt, und möchten nicht, dass der Kernel im Standardinterpreter geladen wird? Was ist, wenn Sie mit Bibliotheken verlinken, die einen Initialisierungslauf über .init benötigen? Meiner Erfahrung nach, wenn Sie etwas mit Ihren ausführbaren Dateien machen wollen, aber es gibt keine Möglichkeit, es mit einer Permutation von LDFLAGS zu erzeugen, dann ist es keine gute Idee. –

+1

Wenn ich versuche, dies in das Build-System einer Anwendung zu integrieren, würde ich Ihnen 100% zustimmen. Für eine Anwendung, die mit Linker-Optionen wie diesem täuscht, handelt es sich um einen schrecklichen Hack, der sowieso nicht funktioniert, da alle '.a'-Bibliotheken als PIC erstellt werden müssen. Woran ich gerade arbeite, ist eine neue Toolchain-Option, die für den Einsatz in sicherheitsgerichteten Distributionen gedacht ist, bei denen der dynamische Linker für setuid-Binärdateien ausgeführt wird, ist ein inakzeptables Risiko. Es ist viel einfacher zu implementieren, wenn keine Änderungen auf der Ebene "ld" erforderlich sind, sondern nur auf der Ebene gcc specfile und 'crt'. –

+0

sieht es so aus, als müsstest du einen Patch für ld schreiben und dann mit ihnen darüber streiten, warum sie auf den trunk angewendet werden soll. das hört sich auch nach sehr interessanter Arbeit an. –

Antwort

10

Ich glaube, ich könnte eine Lösung gefunden haben: einfach -shared statt -pie mit zu pie Binärdateien zu machen. Sie benötigen ein paar zusätzliche Linker-Optionen, um das Verhalten zu verbessern, aber es scheint, die Notwendigkeit für ein benutzerdefiniertes Linker-Skript zu vermeiden. Oder mit anderen Worten, das -shared Linker-Skript ist bereits im Wesentlichen korrekt zum Verknüpfen von statischen Kreis-Binärdateien.

Wenn ich damit arbeite, werde ich die Antwort mit der genauen Befehlszeile aktualisieren, die ich verwende.

Update: Es funktioniert! Hier ist die Befehlszeile ein:

gcc -shared -static-libgcc -Wl,-static -Wl,-Bsymbolic \ 
    -nostartfiles -fPIE Zcrt1.s Zcrt2.c /usr/lib/crti.o hello.c /usr/lib/crtn.o 

wo Zcrt1.s ist eine modifizierte Version von Scrt1.s, die eine Funktion in Zcrt2.c ruft vor seiner normalen Arbeit zu tun, und der Code in Zcrt2.c verarbeitet die aux Vektor nur Nach dem Argv- und dem Environment-Array, um den DYNAMIC-Abschnitt zu finden, werden die Umlagerungstabellen durchlaufen und alle Relationen des relativen Typs (die einzigen, die existieren sollten) angewendet.

Nun all dies kann (mit ein wenig Arbeit) nach oben in ein Skript oder gcc Specfile gewickelt werden ...

+0

Ordentlicher Trick. Sie bauen im Wesentlichen auf, was Sie brauchen, anstatt wegzuziehen. +1 und viel Glück. –

4

Expanding on meine frühere Anmerkung, da dies nicht in diese mickrige Box passt (und das ist nur als eine Idee oder Diskussion, fühlen Sie sich nicht verpflichtet, Kopfgeld zu akzeptieren oder zu belohnen), vielleicht ist der einfachste und sauberste Weg, dies zu tun, zu addieren a Post-Build-Schritt zum Entfernen der PT_INTERP Header aus der resultierenden Binärdatei?

Noch einfacher als das manuelle Bearbeiten der Header und möglicherweise alles verschieben muss nur PT_INTERP durch PT_NULL ersetzen. Ich weiß nicht, ob Sie einen Weg finden können, die Datei einfach über existierende Werkzeuge zu patchen (eine Art skriptfähiger Hexadezimalwert zum Suchen und Ersetzen) oder ob Sie dafür ein kleines Programm schreiben müssen. Ich weiß, dass libbfd (die GNU Binary File Descriptor-Bibliothek) in letzterem Fall dein Freund sein könnte, da es das ganze Geschäft viel einfacher macht.

Ich denke, ich verstehe einfach nicht, warum es wichtig ist, dies über eine ld Option durchgeführt zu haben. Wenn verfügbar, kann ich sehen, warum es vorzuziehen wäre; aber da einige (zugegebenermaßen leichte) Googles darauf hindeuten, dass es kein solches Feature gibt, ist es vielleicht weniger mühsam, es separat und nach der Tat zu tun. (Vielleicht die Fahne ld Zugabe ist einfacher als Skripts für den Ersatz von PT_INTERP mit PT_NULL, aber überzeugend die Devs es stromaufwärts zu ziehen ist eine andere Sache.)


Anscheinend (und bitte korrigieren Sie mich, wenn dies etwas, das Sie habe schon gesehen) Sie können das Verhalten von ld in Bezug auf einen der ELF-Header in Ihrem Linker-Skript with the PHDRS command überschreiben und :none verwenden, um anzugeben, dass ein bestimmter Header-Typ in keinem Segment enthalten sein sollte. Ich bin nicht sicher, der Syntax, aber ich vermute, es wie folgt aussehen würde:

PHDRS 
{ 
    headers PT_PHDR PHDRS ; 
    interp PT_INTERP ; 
    text PT_LOAD FILEHDR PHDRS ; 
    data PT_LOAD ; 
    dynamic PT_DYNAMIC ; 
} 

SECTIONS 
{ 
    . = SIZEOF_HEADERS; 
    .interp : { } :none 
    ... 
} 

Vom ld docs Sie die Linker-Skript mit --library-path außer Kraft setzen kann:

--library-path=searchdir 

hinzufügen Weg searchdir zu der Liste der Pfade, die nach Archivbibliotheken und ld Kontrollskripts suchen werden. Sie können diese Option beliebig oft unter verwenden. Die Verzeichnisse werden in der Reihenfolge gesucht, in der sie in der Befehlszeile angegeben sind. Verzeichnisse, die in der Befehlszeile angegeben sind, werden vor den Standardverzeichnissen gesucht. Alle Optionen -L gelten für alle -l Optionen, unabhängig von der Reihenfolge, in der die Optionen angezeigt werden.Der Standardsatz der durchsuchten Pfade (ohne mit `-L 'angegeben zu werden) hängt davon ab, welcher Emulationsmodus ld verwendet, und in einigen Fällen auch von der Konfiguration. Siehe Abschnitt Umgebung Variablen. Die Pfade können auch in einem Link-Skript mit dem Befehl SEARCH_DIR angegeben werden. Auf diese Weise angegebene Verzeichnisse werden unter dem Punkt gesucht, in dem das Linker-Skript in der Befehlszeile angezeigt wird. Auch

, von the section on Implicit Linker Scripts:

Wenn Sie eine Linker Eingabedatei angeben, die der Linker nicht als Objekt-Datei oder einer Archivdatei erkennen kann, wird es versuchen, die Datei als einer lesen Linker-Skript. Wenn die Datei nicht als Linker-Skript analysiert werden kann, meldet der Linker einen Fehler.

Welche implizieren Werte in benutzerdefinierter Linker-Skripte, im Gegensatz zu implizit definierten Linker-Skripte scheinen würde, wird ersetzen Werte in den Standard-Skripten.

+1

Das ist einfach zum Experimentieren, und genau das habe ich zum Experimentieren getan. Aber es hilft überhaupt nicht, wenn das Ziel darin besteht, eine Toolchain zu erstellen, mit der man einige zusätzliche Optionen in 'CFLAGS' und' LDFLAGS' löschen kann und jedes Programm als statischen Kuchen erstellen kann. –

+0

Abgesehen von einem Upstream-Patch von ld, um Unterstützung für das Weglassen von 'PT_INTERP' zu bieten (vorausgesetzt, es existiert noch keins), glaube ich nicht, dass es eine Alternative gibt, die eine generische Toolchain erzeugen kann. –

+0

Ich habe welche gefunden Informationen zum Überschreiben des Standardverhaltens für jeden ELF-Header und zum Aktualisieren meiner Antwort - gilt dies für Sie? –

12

Vielleicht bin ich naiv, aber ... wäre es nicht ausreichend, nach dem Standard-Linkerskript zu suchen, es zu bearbeiten und die Zeile zu entfernen, die im Abschnitt .interp verlinkt?

Zum Beispiel sind in meinem Computer die Skripte in /usr/lib/ldscripts und die fragliche Zeile ist interp : { *(.interp) } in der SECTIONS Abschnitt.

können Sie das Standardskript dumpp den folgenden Befehl verwendet, ausgeführt wird:.

$ ld --verbose ${YOUR_LD_FLAGS} | \ 
    gawk 'BEGIN { s = 0 } { if ($0 ~ /^=/) s = !s; else if (s == 1) print; }' 

Sie können die gawk Skript ändern leicht die interp Linie (oder nur grep -v verwenden zu entfernen und das Skript verwenden, das Programm zu verknüpfen

+0

Bis jetzt ist dies wahrscheinlich der beste Ansatz. Leider müssen für jedes System neue Versionen der Linker-Skripts erstellt werden, und es kann nicht einfach ein bereits funktionierendes Linker-Skript verwendet werden. Aber wenn ich den Proof of Concept gut funktionieren lassen kann, kann ich ihn vielleicht in Upstream-Binutils bekommen. –

+0

@R .. - Nun, mein Trick mit "strace" funktioniert nicht, weil das Skript tatsächlich in 'ld' kompiliert und nicht von der Festplatte gelesen wird. Aber Sie können es mit 'ld --verbose 'und ein wenig Magie in der Ausgabe bekommen (siehe die aktualisierte Antwort. – rodrigo

+0

Von diesem Code können Sie die ld-Wrapper machen, genau wie einige für [gcc] (http://users.sdsc.edu/~kst/gcc-wrapper/) – alexander

2

ich bin kein Experte in GNU ld, aber ich habe die folgenden Informationen in der documentation gefunden:

Der spezielle Sek-Name `/ DISCARD/'kann verwendet werden, um Eingabeabschnitte zu verwerfen. Alle Abschnitte, die einem Ausgabeabschnitt mit dem Namen `/ DISCARD/' zugewiesen sind, sind nicht in der endgültigen Linkausgabe enthalten.

Ich hoffe, das wird Ihnen helfen.

UPDATE:

(Dies ist die erste Version der Lösung, die nicht funktionieren, weil INTERP Abschnitt zusammen mit dem Kopf PT_INTERP fällt gelassen wird.)

main.c:

int main(int argc, char **argv)                                
{                                        
    return 0;                                     
} 

main.x:

SECTIONS {                                      
    /DISCARD/ : { *(.interp) }                                 
} 

build-Befehl:

$ gcc -nostdlib -pie -static -Wl,-T,main.x main.c 
$ readelf -S a.out | grep .interp 

Build-Befehl ohne Option -Wl, -T, Haupt.x:

$ gcc -nostdlib -pie -static main.c 
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000000218 
$ readelf -S a.out | grep .interp 
    [ 1] .interp   PROGBITS  00000134 000134 000013 00 A 0 0 1 

UPDATE 2:

Die Idee dieser Lösung ist, dass der ursprüngliche Abschnitt 'INTERP' .interp1 umbenannt wird (interp in der Linker-Skript-Datei.). Mit anderen Worten, der gesamte Inhalt des Abschnitts wird in den Abschnitt .interp1 gestellt. Daher können wir den INTERP-Abschnitt (jetzt leer) sicher entfernen, ohne befürchten zu müssen, Standard-Linker-Skripteinstellungen zu verlieren, und daher wird auch der Header INTERP_PT entfernt.

SECTIONS { 
    .interp1 : { *(.interp); } : NONE 
    /DISCARD/ : { *(.interp) } 
} 

Um zu zeigen, dass der Inhalt des Abschnitts INTERP in der Datei (wie .interp1), aber INTERP_PT Header entfernt, habe ich eine Kombination aus readelf + grep verwenden.

$ gcc -nostdlib -pie -Wl,-T,main.x main.c 
$ readelf -l a.out | grep interp 
    00  .note.gnu.build-id .text .interp1 .dynstr .hash .gnu.hash .dynamic .got.plt 
$ readelf -S a.out | grep interp 
    [ 3] .interp1   PROGBITS  0000002e 00102e 000013 00 A 0 0 1 
+0

Ich habe versucht, dies mit '.interp' zu verwenden, aber ich konnte es nicht zum Laufen bringen, vielleicht weil es vom Standard-Linker-Skript und nicht von den Quelldateien erstellt wurde Haben Sie eine Idee, wie es geschrieben werden sollte? –

+0

Ich habe die Antwort mit Beispiel aktualisiert. – alexander

+0

Nun, es ist '-l' nicht' -S' müssen Sie sich ansehen (Programm-Header). Sie haben Recht, dass dies hemmt den 'INTERP'-Header, aber es scheint auch zu verursachen, dass ein einzelner falscher' LOAD'-Header anstelle der richtigen erzeugt wird (das ganze Programm wird read-write geladen), so scheint es nur das zugrundeliegende System-ld-Skript zu überspringen. .. –