2012-09-27 15 views
7

Przyjrzałem się podstawom luk w przepełnieniu bufora i próbowałem zrozumieć, w jaki sposób działa stos. W tym celu chciałem napisać prosty program, który zmienia adres adresu zwrotnego na pewną wartość. Czy ktokolwiek może mi pomóc w określeniu wielkości wskaźnika bazowego, aby uzyskać offset od pierwszego argumentu?Zmodyfikuj adres zwrotny na stosie

void foo(void) 
{ 
    char ret; 
    char *ptr; 

    ptr = &ret; //add some offset value here 
    *ptr = 0x00; 
} 

int main(int argc, char **argv) 
{ 
    foo(); 

    return 1; 
} 

Wygenerowany kod asemblera wygląda następująco:

.file "test.c" 
    .text 
    .globl foo 
    .type foo, @function 
foo: 
.LFB0: 
    .cfi_startproc 
    pushq %rbp 
    .cfi_def_cfa_offset 16 
    .cfi_offset 6, -16 
    movq %rsp, %rbp 
    .cfi_def_cfa_register 6 
    leaq -9(%rbp), %rax 
    movq %rax, -8(%rbp) 
    movq -8(%rbp), %rax 
    movb $0, (%rax) 
    popq %rbp 
    .cfi_def_cfa 7, 8 
    ret 
    .cfi_endproc 
.LFE0: 
    .size foo, .-foo 
    .globl main 
    .type main, @function 
main: 
.LFB1: 
    .cfi_startproc 
    pushq %rbp 
    .cfi_def_cfa_offset 16 
    .cfi_offset 6, -16 
    movq %rsp, %rbp 
    .cfi_def_cfa_register 6 
    subq $16, %rsp 
    movl %edi, -4(%rbp) 
    movq %rsi, -16(%rbp) 
    call foo 
    movl $1, %eax 
    leave 
    .cfi_def_cfa 7, 8 
    ret 
    .cfi_endproc 
.LFE1: 
    .size main, .-main 
    .ident "GCC: (GNU) 4.7.1 20120721 (prerelease)" 
    .section .note.GNU-stack,"",@progbits 

Odpowiednia część segmentu ramy bla powinien wyglądać tak jak poniżej:

[znak retencji] [wskaźnik zasada] [adres zwrotny ]

Mam pozycję pierwszego, który ma tylko 1 bajt wielkości. Czy jest to tylko 1 bajt od wskaźnika bazowego lub rozmiar słowa, o którym mowa w http://insecure.org/stf/smashstack.html? A jak mogę poznać rozmiar wskaźnika bazowego?

Odpowiedz

0

Wygląda na to, że używasz architektury 64-bitowej, ponieważ rejestry RBP i RSP mają długość 64-bitów. Jeśli zadeklarujesz ptr jako char* będziesz musiał go zwiększyć 8 razy, aby przejść przez stos. Zamiast tego możesz oznaczyć go jako uint64_t *. Ten typ danych jest zwykle dostępny pod numerem <stdint.h>.

Jednak definicja ramki stosu zależy od architektury docelowej, a nawet od zachowania i optymalizacji kompilatora. W porządku, jeśli eksperymentujesz.

1

Nie będziesz mógł tego zrobić w wanilii C, nie masz kontroli nad tym, jak kompilator układa ramkę stosu.

W x86-64 adres zwrotny powinien znajdować się pod adresem %rbp + 8. Można to zrobić za pomocą wbudowanego zestawu (składnia gcc):

uint64_t returnaddr; 
asm("mov 8(%%rbp),%0" : "=r"(returnaddr) : :); 

Podobnie do ustawienia.

Nawet to jest nieco pobieżne, ponieważ nie wiesz, czy kompilator skonfiguruje się jako %rbp, czy też nie. YMMV.

+1

+1 Rekompilacja zawsze może przenosić zmienne. Tylko dla testów OP, w tym konkretnym przypadku adres zwrotny znajduje się na '& ret + 17', co jest mało prawdopodobne, dopóki zmienne lokalne w tej funkcji nie ulegną zmianie. – ughoavgfhw

+0

offset 17 prac. czy możesz wyjaśnić, jak to określiłeś? – fliX

+0

@fliX W złożeniu instrukcja 'leaq -9 (% rbp),% rax' otrzymuje adres na stosie. Ponieważ jest to bajt wyrównany, a jedynym obliczeniem adresu musi być miejsce, w którym znajduje się 'ret'. Biorąc to 9 i dodając 8 dla poprzedniego 64-bitowego wskaźnika bazowego, otrzymujesz 17. – ughoavgfhw

1

Twój wskaźnik bazowy najprawdopodobniej jest tylko wskaźnikiem, więc ma rozmiar sizeof (int *). Ale istnieje również inna wartość pomiędzy zmienną ret a podstawowym wskaźnikiem. Założę się, że wartość rejestru (eax?). Prowadziłoby to do czegoś poniżej, jeśli chcesz nieskończoną pętlę:

void foo(void) 
{ 
    char ret; 
    char *ptr; 

    ptr = (char*)(&ret) + (sizeof(ret) + 2*sizeof(int*)) ; 
    *(int*)ptr -= 0x0c; 
} 

Docelowy zwrot, który jest modyfikowany przy założeniu, że ma rozmiar wskaźnika (może być inny dla innych zestawów instrukcji). Po zmniejszeniu go, cel powrotu jest ustawiony na punkt przed punktem wywołania foo.