2015-08-02 48 views
13

Biorąc fragment kodu:W jaki sposób odniesienie do print-a (var-arg) współdziała z układem pamięci stosu?

int main() 
{ 
    printf("Val: %d", 5); 
    return 0; 
} 

jest jakaś gwarancja, że ​​kompilator będzie przechowywać "Val: %d" i '5' ciągły? Na przykład:

+-----+-----+-----+-----+-----+-----+-----+-----+-----+ 
| ... | %d | ' ' | ':' | 'l' | 'a' | 'V' | '5' | ... | 
+-----+-----+-----+-----+-----+-----+-----+-----+-----+ 
    ^        ^ ^
     |   Format String   | int | 

Dokładnie jak te parametry są przydzielane w pamięci?

Co więcej, czy funkcja printf uzyskuje dostęp do int w stosunku do łańcucha formatu lub do wartości bezwzględnej? Tak na przykład w danych

+-----+-----+-----+-----+-----+-----+-----+-----+-----+ 
| ... | %d | ' ' | ':' | 'l' | 'a' | 'V' | '5' | ... | 
+-----+-----+-----+-----+-----+-----+-----+-----+-----+ 
    ^        ^ ^
     |   Format String   | int | 

gdy funkcja napotka %d będzie tam już zapisany adres pamięci dla pierwszego parametru funkcji, które byłyby odwołania lub będzie wartość jest obliczana w stosunku do pierwszego elementu ciąg formatu?

Przepraszam, jeśli mam być mylące, moim głównym celem jest, aby zrozumieć ciąg wyczyny formatowania, gdzie użytkownik może dostarczyć ciąg formatu w sposób opisany w niniejszym dokumencie

http://www.cis.syr.edu/~wedu/Teaching/cis643/LectureNotes_New/Format_String.pdf

Moje obawy pojawiają się na atak opisany na stronie 3 i 4. Doszedłem do wniosku, że %x mają pominąć 16 bitów, które przechwytuje ciąg znaków, co wskazywałoby, że funkcja przydzielona jest w sposób ciągły i odwołuje się względnie, ale inne źródła wskazują, że nie ma gwarancji, że kompilator musi przydzielać w sposób ciągły i byłem zaniepokojony, że artykuł był uproszczeniem.

+4

Cóż, z jednej strony, łańcuch format nie jest stor ed na stosie. –

+0

Dzięki. Naprawiono pytanie. – user2985955

+3

Te notatki z wykładów są okropne. Cały świat nie jest i386. Jeśli chodzi o C, może nie być nawet stosu. – user464502

Odpowiedz

15

ma żadnej gwarancji, że kompilator będzie przechowywać "Val:% d" i '5' zwarty

jest praktycznie gwarantowana one nie być. Wartość 5 jest na tyle mała, że ​​może zostać osadzona bezpośrednio w strumieniu instrukcji, a nie załadowana przez adres pamięci (wskaźnik) - coś podobnego do movl #5, %eax i/lub po której następuje naciśnięcie na stos - podczas gdy obiekt łańcuchowy zostanie ułożony w obszarze danych tylko do odczytu obrazu wykonywalnego i będzie odwoływany przez wskaźnik. Mówimy o układzie kompilacji czasu układu obrazu wykonawczego .

Chyba masz na myśli wykonania układ stosie w którym tak, słowo wielkości wskaźnik do tego łańcucha, a słowo wielkości stały 5, pojawią się obok siebie. Ale kolejność jest prawdopodobnie odwrotnością tego, czego się spodziewasz - przestudiuj "konwencję wywoływania funkcji C".

[Późniejsza edycja: uruchamianie niektórych próbek kodu za pomocą -S (złożenia wyjściowego); Przypomina mi się, że przy użyciu rejestrów świetlnych w wywołującym (tj. Rejestry CPU mogą być nadpisywane bez szkody) i kilku argumentów wywoływanej funkcji, argumenty mogą być przekazywane w całości przez rejestry, aby zapisać instrukcje i pamięć. Tak więc układ stosu jest trudny do przewidzenia, nawet jeśli atakujący miał dostęp do kodu źródłowego.Zwłaszcza w przypadku gcc -O2, który zwinął moją główną funkcję -> moja_funkcja -> funkcja printf do głównej -> printf]

Większość exploitów wykorzystuje przekroczenia stosu, ponieważ złośliwy kod przechodzi w ścianę z cegieł, próbując zmodyfikować pamięć we wspomnianym wyżej czytaniu - tylko obszar danych - system operacyjny przerywa proces.

Zachowanie printf jest osobliwe, ponieważ ciąg formatów jest jak miniaturowy program komputerowy, który nakazuje printf przeglądanie argumentów na stosie dla każdego znalezionego formatu "%". Jeśli argumenty te nigdy nie były wypychane i/lub miały różne rozmiary, printf ślepo przemieściłby części stosu, którego nie powinien, i może ujawniłby dane dalej na stosie (w dół łańcucha połączeń), gdzie mogą znajdować się prywatne dane. Jeśli pierwszym argumentem dla printf jest co najmniej stała, kompilator może przynajmniej ostrzec cię, gdy kolejne argumenty będą niezgodne ze specyfikatorami '%', ale gdy jest to zmienna, wszystkie zakłady są wyłączone.

printf jest okropny z punktu widzenia bezpieczeństwa i jest intensywny obliczeniowo, ale bardzo potężny i ekspresyjny. Zapraszamy do C :-)

2. późniejszej edycji teraz Twoje pierwsze pytanie w komentarzach ... jak widać twoje myśli terminologia i być może były nieco zniekształcone. Przestudiuj poniższe informacje, aby dowiedzieć się, co się dzieje. Nie martw się jeszcze o wskaźniki do łańcuchów. Zostało to skompilowane z gcc 4.8.2 na 64-bitowym systemie Linux 3.13 bez flag. Zwróć uwagę, że nadmierne użycie specyfikatorów formatów zasadniczo cofa się przez stos, ujawniając argumenty, które zostały przekazane w poprzednim wywołaniu funkcji.

/* Do not compile this at home. */ 
#include <stdio.h> 

int second() { 
    printf("%08X %08X %08X %08X %08X %08X %08X %08X\n"); 
} 

int first(int a, int b, int c, int d, int e, int f, int g, int h) { 
    second(); 
} 

int main(int argc, char **argv) { 
    first(0xDEEDC0DE, 0x1EADBEEF, 0x11BEDEAD, 0xCAFAF000, 0xDAFEBABE, 0xAACEBACE, 0xE1ED1EAA, 0x10F00FAA); 
    return 0; 
} 

Dwa back-to-back działa, wyjście stdio:

1EADBEEF 11BEDEAD CAFAF000 DAFEBABE AACEBACE 75F83520 00400568 88B151C8

1EADBEEF 11BEDEAD CAFAF000 DAFEBABE AACEBACE 8B4CBDC0 00400568 7BB841C8

+0

Załóżmy, że istnieje instrukcja printf, która umożliwiła użytkownikowi dostarczenie ciągu formatowania bez żadnych innych argumentów. W jaki sposób możemy uzyskać znak formatu% s, aby odczytać wartość z dowolnego adresu pamięci, biorąc pod uwagę, że ciąg formatu jest ciągłym wskaźnikiem? Jeśli ten ciąg jest tylko sąsiadująco reprezentowany jako wskaźnik, wówczas będziemy mogli tylko odczytać sam ciąg formatu. Kiedy skompensowalibyśmy jakiekolwiek bufory i zmienne w łańcuchu formatu, czy byłby jakiś sposób, aby% s odczytać określoną serię bitów i zinterpretować je jako wskaźnik, aby je odczytał? – user2985955

+0

@ user2985955, których nie można wykonać w C – Jasen

+0

Jak myślisz, co rozumiesz przez "ciągłe przedstawianie jako wskaźnik"? – user464502

3

ciekawe pytanie. Oto montażowa z dwóch programów testowych: jeden 32-bitowy/MSVC, drugie 64-bitowe GCC:

Program testu:

/* 
* Sample output: 
* A 
* B: 49, 2, 5.000000 
*/ 
#include <stdio.h> 

int main(int argc, char *argv[]) { 
    printf ("A\n"); 
    printf ("B: %d, %c, %f\n", 0x31, 0x32, 5.0); 
    return 0; 
} 

MSVS zespół/32-bitowe (cl /Fa)

_DATA SEGMENT 
$SG2938 DB 'A', 0aH, 00H 
    ORG $+1 
$SG2939 DB 'B: %d, %c, %f', 0aH, 00H 
... 
CONST SEGMENT 
[email protected] DQ 04014000000000000r ; 5 
... 
    push OFFSET $SG2938 
    call _printf 
... 
    movsd xmm0, QWORD PTR [email protected] 
    movsd QWORD PTR [esp], xmm0 
    push 50     ; 00000032H 
    push 49     ; 00000031H 
    push OFFSET $SG2939 
    call _printf 
montaż

GCC/64-bit (gcc -S):

.LC0: 
     .string "A" 
.LC1: 
     .string "B: %d, %c, %f\n" 
... 
     movl %edi, -4(%rbp) // You'll notice that GCC substitutes "puts()" for "printf()" here 
     movq %rsi, -16(%rbp) 
     movl $.LC0, %edi 
     call puts 
... 
     movl $.LC1, %eax  // Also notice the absence of "push": we're passing arguments in registers, instead of on the stack 
     movsd .LC2(%rip), %xmm0 
     movl $50, %edx 
     movl $49, %esi 
     movq %rax, %rdi 
     movl $1, %eax 
     call printf