2016-04-08 9 views
-2

Ich schrieb ein einfaches C-Programm an dem Stapelrahmen von printf suchen()C: printf() 's Stack-Schwachstelle?

#include <stdio.h> 
int main(void){ 
    printf("%s"); 
} 

Ich dachte, die Art und Weise des Stapel arbeiten würde, ist main() würde zunächst drücken „% s“ auf den Stapel, so printf wird entweder Fehler segeln oder Müll ausgeben. Aber nirgends in meiner Disassembly drückt es "% s" auf den Stapel. Ich habe alle Werte zwischen% fp und% sp ausgedruckt, aber keiner davon enthält "% s".

Die Montage Dump für Haupt:

0x00400950 <+0>: lui gp,0x2 
0x00400954 <+4>: addiu gp,gp,-32224 
0x00400958 <+8>: addu gp,gp,t9 
0x0040095c <+12>: addiu sp,sp,-32 
0x00400960 <+16>: sw ra,28(sp) 
0x00400964 <+20>: sw s8,24(sp) 
0x00400968 <+24>: move s8,sp 
0x0040096c <+28>: sw gp,16(sp) 
0x00400970 <+32>: lw v0,-32744(gp) 
0x00400974 <+36>: nop 
0x00400978 <+40>: addiu v0,v0,2864 
0x0040097c <+44>: move a0,v0 
0x00400980 <+48>: lw v0,-32688(gp) 
0x00400984 <+52>: nop 
0x00400988 <+56>: move t9,v0 
0x0040098c <+60>: jalr t9 
0x00400990 <+64>: nop 
0x00400994 <+68>: lw gp,16(s8) 
0x00400998 <+72>: move sp,s8 
0x0040099c <+76>: lw ra,28(sp) 
0x004009a0 <+80>: lw s8,24(sp) 
0x004009a4 <+84>: addiu sp,sp,32 
0x004009a8 <+88>: jr ra 
0x004009ac <+92>: nop 

Wenn "% s" nicht auf dem Stapel gespeichert wird, wo sie gespeichert werden? Wo bekommt es auch die entsprechende Zeichenkette zum Ausdrucken?

+3

Beginnen Sie mit dem Lesen des C-Standards. Es erzwingt nicht einmal die Verwendung eines spezifischen Speichermodells für automatische Variablen usw. Lesen Sie dann den ABI Ihrer Plattform, um zu sehen, wie Parameter zu/von Funktionen übergeben werden. Und Ihr Code ruft undefiniertes Verhalten auf. Der Compiler ist frei, Code zu generieren, der alle Ihre Nahrung isst. – Olaf

+0

Mögliches Duplikat von [C String-Literale: Wohin gehen sie?] (Http://stackoverflow.com/questions/2589949/c-string-literals-where-do-they-go) –

+0

Bitte geben Sie an, auf welche CPU Sie zielen und welchen Compiler Sie verwenden –

Antwort

0

Normalerweise wird auf der Implementierungsebene ausgeführt, dass das Zeichenfolgenliteral "%s" in einer Art statischem Speicher ist. Wenn printf aufgerufen wird, wird ein Zeiger auf diese Zeichenfolge als Parameter übergeben. Das bedeutet nicht notwendigerweise, dass dieser Zeiger auf den Stapel geschoben wird. Wie der Parameter übergeben wird, hängt von den Parameterübergabekonventionen ab. Es könnte in ein Register geladen werden.

In Ihrem speziellen Fall, hier ist, wo "%s" für den Durchgang vorbereitet wird:

0x00400970 <+32>: lw v0,-32744(gp) 
0x00400974 <+36>: nop 
0x00400978 <+40>: addiu v0,v0,2864 
0x0040097c <+44>: move a0,v0 

Zuerst wird eine Basisadresse wird aus einem Datenbereich in Bezug auf das globale Zeigerregister geladen. Dann wird diese Basisadresse um 2864 versetzt, um die Adresse des "%s" zu erhalten. Die Adresse wird dann nach a0 verschoben, und das Register v0 wird für die Berechnung der Adresse printf wiederverwendet (was durch die Tatsache erschwert wird, dass es sich um eine gemeinsam genutzte Bibliothek handelt).

Jetzt seit "%s" hat keine entsprechende char * Argument, natürlich ist das Verhalten formal undefiniert. Aber was ist das tatsächliche Verhalten?

Das tatsächliche Verhalten ist wahrscheinlich, dass printf versuchen wird, einen char * Zeiger irgendwie, vielleicht aus dem Stapel zu extrahieren. (Die nachlaufenden Argumente einer variadischen Funktion werden oft einfach auf den Stapel gelegt.)

Jetzt, da der Aufrufer kein Argument dort hingelegt hat, extrahiert printf etwas "Müll" -Wort und behandelt es als char *, Drucken des Speichers auf das dieses Wort als Null-terminierte Zeichenfolge verweist. Das heißt, wenn dieses Wort auf einen gültigen Speicher zeigt.

Wenn Ihr Ziel ist, einige Bytes des Stapelspeichers auszugeben, ist dies überhaupt nicht zuverlässig. Sie haben keine Ahnung, was für ein Wert der Interpreter als char * Pointer bekommt, oder worauf er hinweist, oder ob er überhaupt auf etwas zeigt, geschweige denn den Stack.

Dieser falsche char * selbst kann aus dem Stapel gezogen werden, aber Sie drucken diesen Zeiger nicht selbst.

Die folgende könnte Sie ein paar Bytes Stack erhalten:

printf("%p\n"); 

Auch in ähnlicher Weise, wie könnte jeder numerische Konvertierung ohne Argument. Grund ist, dass %p im Gegensatz zu %s tatsächlich den Zeiger selbst druckt. Wenn der Argumentwert für %p aus dem Stapel gezogen wird, verliert die gedruckte Darstellung dieses Werts einige Informationen über ein kleines Stück des Stapels.

-1
 .file "1.c" 
     .section  .rodata 
.LC0: 
     .string "%s" 
     .text 
     .globl main 
     .type main, @function 
main: 
.LFB0: 
     .cfi_startproc 
     pushq %rbp 
     .cfi_def_cfa_offset 16 
     .cfi_offset 6, -16 
     movq %rsp, %rbp 
     .cfi_def_cfa_register 6 
     movl $.LC0, %edi 
     movl $0, %eax 
     call printf 
     movl $0, %eax 
     popq %rbp 
     .cfi_def_cfa 7, 8 
     ret 
     .cfi_endproc 
.LFE0: 
     .size main, .-main 
     .ident "GCC: (Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010" 
     .section  .note.GNU-stack,"",@progbits 

Ich habe gcc verwendet, um Assembly zu generieren. Die Zeichenfolge wird nicht so gespeichert, wie Sie denken. Die Zeichenfolge wird statisch gespeichert.

+0

Das scheint eine andere Architektur zu sein. Sie vergleichen Äpfel und Orangen. – Olaf

+0

Danke für das Aufzeigen. Das habe ich nicht gesehen. – user902384

1

Soweit ich mich erinnere, verwendet Mips Arch a0 ~ a3 als die ersten vier Argumente, um Anrufe zu funktionieren.