2012-04-11 11 views
5

Prima di tutto mi dispiace per la lunghezza di questo post, ma volevo spiegare chiaramente il problema.Codice auto-modificante, copia/salta nell'heap fallito

Provo a scrivere una specie di piccolo programma di modifica auto in C ma ho alcuni problemi e non so esattamente perché.

Plateform è: Ubuntu/Linux 2.6.32-40 x86_64, prog è costruita su architettura x86, gcc (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3, GNU ld (GNU Binutils per Ubuntu) 2.20 .1-system.20100303

lo scopo del programma è quello di creare un lettura/scrittura/esecuzione pezzo di memoria (con memalign (3) e mprotect (2)), copiare una piccola funzione chiamato p() (definito nel segmento .text) in questo blocco di memoria e quindi eseguire la funzione copiata tramite un puntatore. La funzione p() visualizza solo un messaggio utilizzando printf(puts).

Al fine di ottenere l'indirizzo iniziale e finale del codice di p() (per copiarlo), io uso l'indirizzo della funzione stessa e l'indirizzo di una funzione dummy() creare subito dopo p() in .text.

int p() { ... }  <- address where the copy starts 
int dummy() { ... } <- address where the copy stops 

creazione di memoria Chunk e copia sono fatte con successo ma quando il codice del pezzo viene eseguito un segfault verifica. Utilizzando gdb è chiaro che inseriamo il codice del blocco (il corpo della funzione copiata) ma la chiamata a printf non è riuscita. Quando si smonta la funzione p() e il codice nel blocco, vedo che l'indirizzo utilizzato nella "chiamata" non è lo stesso.

E non so perché l'indirizzo non è corretto, quando il codice viene copiato viene visualizzato ed è lo stesso che objdump (o gdb) mi ha dato quando ho smontato la funzione p().

Il file binario viene creato con -static per evitare potenziali problemi con got/plt o con il processo di riposizionamento di ld.so. Non sembra essere un problema eseguire il codice su heap perché viene eseguito l'inizio della funzione copiata (controllare sotto gdb).

Lo src semplificata del programma:

<... skip include/checks ...> 
#define newline() putchar('\n') 

/* - function copied in the chunk */ 
int p() 
{ 
    printf("hello world\n"); 
    return 0; 
} 

/* - dummy function to get last address of p() */ 
int dummy() { return 0; } 

int main() 
{ 
    char *s, *code, *chunk; 
    unsigned int pagesz, sz; 
    int (*ptr)(void); 

    pagesz = sysconf(_SC_PAGE_SIZE); 
    chunk = (char*)memalign(pagesz, 4 * pagesz); 
    mprotect(chunk, 4 * pagesz, PROT_WRITE|PROT_EXEC|PROT_READ); 

    /* - get size, display addr */ 
    sz = (char *)dummy - (char *)p; 
    printf("Copy between : %p - %p\n", (char *)p, (char *)dummy); 
    printf("Copy in chunk : %p - %p\n", chunk, chunk + sz, sz); 

    /* - copy code (1 byte in supp) */ 
    printf("Copied code : "); 
    for(s = (char *)p, code = chunk; \ 
      s <= (char *)dummy; s++, code++) { 

     *code = *s;  
     /* - write in console -- to check if code of p() after disas 
     * it with objdump(1) is the same, RESULT : ok */ 
     printf("%02x ", *(unsigned char *)code); 
    } 
    newline(); 

    /* - run once orginal function */ 
    ptr = p; 
    ptr(); 

    /* - run copied function (in chunk) */ 
    ptr = (int (*)(void))chunk; 
    ptr(); 

    newline(); 
    free(chunk); 
    return 0; 
} 

La funzione p() smonta objdump(1):

080483c3 <p>: 
80482c0:  55      push %ebp 
80482c1:  89 e5     mov %esp,%ebp 
80482c3:  83 ec 18    sub $0x18,%esp 
80482c6:  c7 04 24 a8 d9 0a 08 movl $0x80ad9a8,(%esp) 
80482cd:  e8 7e 0c 00 00   call 8048f50 <_IO_puts> 
80482d2:  b8 00 00 00 00   mov $0x0,%eax 
80482d7:  c9      leave 
80482d8:  c3      ret  

080483dc <dummy>: 
.... 

Quando il programma viene eseguito sotto gdb (1) il codice copiato è lo stesso (esagono valore) di objdump (1) fornire sopra:

# gcc -m32 -o selfmodif_light selfmodif_light.c -static -g -O0 

# gdb -q ./selfmodif_light 
Reading symbols from /path/.../selfmodif_light...done. 

(gdb) list 55 
50   /* - run once orginal function */ 
51   ptr = p; 
52   ptr(); 
53 
54   /* - run copied function (in chunk) */ 
55   ptr = (int (*)(void))chunk; 

<<< The problem is here >>> 

56   ptr(); 
57  
58   newline(); 
59   free(chunk); 

(gdb) br 56 
Breakpoint 1 at 0x8048413: file tmp.c, line 56. 

(gdb) run 
Starting program: /path/.../selfmodif_light 
Copy between : 0x80482c0 - 0x80482d9 
Copy in chunk : 0x80d2000 - 0x80d2019 
Copied code : 55 89 e5 83 ec 18 c7 04 24 a8 d9 0a 08 e8 7e 0c 00 00 b8 00 00 00 00 c9 c3 55 
hello world 

Breakpoint 1, main() at tmp.c:56 
56   ptr(); 

Se guardiamo nel principale torneremo la prossima volta nel pezzo:

(gdb) disas main 
Dump of assembler code for function main: 
    0x080482e3 <+0>: push %ebp 
    ... <skip> ... 

=> 0x08048413 <+304>: mov 0x18(%esp),%eax 
    0x08048417 <+308>: call *%eax 

    ... <skip> ... 
    0x08048437 <+340>: ret 
End of assembler dump. 

Ma quando p() e pezzo vengono smontati, abbiamo un call 0x80d2c90 nel pezzo di memoria invece di una call 0x8048f50 <puts> come nel p() funzione ?Per quale motivo l'indirizzo visualizzato non è lo stesso.

(gdb) disas p 
Dump of assembler code for function p: 
    0x080482c0 <+0>:  push %ebp 
    0x080482c1 <+1>:  mov %esp,%ebp 
    0x080482c3 <+3>:  sub $0x18,%esp 
    0x080482c6 <+6>:  movl $0x80ad9a8,(%esp) 
    0x080482cd <+13>: call 0x8048f50 <puts> <<= it is not the same address 
    0x080482d2 <+18>: mov $0x0,%eax 
    0x080482d7 <+23>: leave 
    0x080482d8 <+24>: ret 
End of assembler dump. 
(gdb) disas 0x80d2000,0x80d2019 
Dump of assembler code from 0x80d2000 to 0x80d2019: 
    0x080d2000: push %ebp 
    0x080d2001: mov %esp,%ebp 
    0x080d2003: sub $0x18,%esp 
    0x080d2006: movl $0x80ad9a8,(%esp) 
    0x080d200d: call 0x80d2c90    <<= than here (but it should be ??) 
    0x080d2012: mov $0x0,%eax 
    0x080d2017: leave 
    0x080d2018: ret 
End of assembler dump. 

Quando la memoria è selezionata, i codici sembrano essere identici. A questo punto non capisco cosa sta succedendo, qual è il problema? interpretazione di gdb fallita, copia del codice o cosa?

(gdb) x/25bx p // code of p in .text 
0x80482c0 <p>: 0x55 0x89 0xe5 0x83 0xec 0x18 0xc7 0x04 
0x80482c8 <p+8>: 0x24 0xa8 0xd9 0x0a 0x08 0xe8 0x7e 0x0c 
0x80482d0 <p+16>: 0x00 0x00 0xb8 0x00 0x00 0x00 0x00 0xc9 
0x80482d8 <p+24>: 0xc3 

(gdb) x/25bx 0x80d2000 // code of copy in the chunk 
0x80d2000: 0x55 0x89 0xe5 0x83 0xec 0x18 0xc7 0x04 
0x80d2008: 0x24 0xa8 0xd9 0x0a 0x08 0xe8 0x7e 0x0c 
0x80d2010: 0x00 0x00 0xb8 0x00 0x00 0x00 0x00 0xc9 
0x80d2018: 0xc3 

Se un punto di interruzione è impostato poi l'esecuzione proseguire nel blocco di memoria:

(gdb) br *0x080d200d 
Breakpoint 2 at 0x80d200d 
(gdb) cont 
Continuing. 

Breakpoint 2, 0x080d200d in ??() 
(gdb) disas 0x80d2000,0x80d2019 
Dump of assembler code from 0x80d2000 to 0x80d2019: 
    0x080d2000: push %ebp 
    0x080d2001: mov %esp,%ebp 
    0x080d2003: sub $0x18,%esp 
    0x080d2006: movl $0x80ad9a8,(%esp) 
=> 0x080d200d: call 0x80d2c90 
    0x080d2012: mov $0x0,%eax 
    0x080d2017: leave 
    0x080d2018: ret 
End of assembler dump. 
(gdb) info reg eip 
eip   0x80d200d 0x80d200d 
(gdb) nexti 
0x080d2c90 in ??() 
(gdb) info reg eip 
eip   0x80d2c90 0x80d2c90 
(gdb) bt 
#0 0x080d2c90 in ??() 
#1 0x08048419 in main() at selfmodif_light.c:56 

Quindi a questo punto sia il programma viene eseguito in quel modo e si verifica un segfault o $ EIP viene modificato e il programma finisce senza errori.

(gdb) set $eip = 0x8048f50 
(gdb) cont 
Continuing. 
hello world 

Program exited normally. 
(gdb) 

Non capisco cosa sta succedendo, cosa è fallito. La copia del codice sembra essere ok, il salto nel blocco della memoria, quindi perché l'indirizzo (della chiamata) non è il buono?

Grazie per le vostre risposte e il vostro tempo

+2

Che tipo di hacker sei comunque ??? Per favore! Se hai intenzione di scrivere codice auto-modificante devi farlo da solo :-) – ControlAltDel

+0

TL; DR ......... –

risposta

7
80482cd:  e8 7e 0c 00 00   call 8048f50 

Questo è un relative CALL (a + 0xC7E). Quando si sposta tale istruzione su un EIP diverso, è necessario modificare l'offset.

+2

Inoltre, molti sistemi operativi moderni vietano la memoria RWX. Devi renderlo RW- per la durata della modifica e R-X quando sei pronto per eseguirlo. –

+0

@DCoder: Grazie per la risposta, in effetti ho bisogno di calcolare l'offset corretto o utilizzare una chiamata assoluta – hodj