2012-05-04 5 views
7

Ho lavorato a un progetto (C++), che richiede funzioni allocate dinamicamente, che significa malloc/new e mprotect e quindi modificare manualmente il buffer in codice assembly. Per questo motivo mi sono chiesto esattamente cosa è richiesto in questo "buffer", perché sia ​​una replica di qualsiasi altra funzione _cdecl. Per esempio:Utilizzo di C++ con assembly per allocare e creare nuove funzioni in fase di esecuzione

int ImAcDeclFunc(int a, int b) 
{ 
    return a + b; 
} 

Se mi piacerebbe creare letteralmente un duplicato di questa funzione, ma completamente in modo dinamico, che cosa che richiedono (e ricordatevi che è C++ con linea montaggio)? Per cominciare, penso che avrei dovuto fare qualcosa di simile (o una soluzione simile):

// My main.... 
byte * ImAcDeclFunc = new byte[memory]; 
mprotect(Align(ImAcDeclFunc), pageSize, PROT_EXEC | PROT_READ | PROT_WRITE); 

Dopo questo avrei dovuto scoprire il codice assembly per il ImAcDeclFunc(int a, int b);. Ora sono ancora pessimo al montaggio, quindi come sarebbe questa funzione nella sintassi AT &? Ecco il mio tentativo audace:

push %ebp 
movl %%ebp, %%esp 
movl 8(%ebp), %%eax 
movl 12(%ebp), %%edx 
addl edx, eax 
pop ebp 
ret 

Ora, se questo codice è corretto (che dubito fortemente, per favore correggetemi) farebbe Ho solo bisogno di trovare il valore di questo codice in esadecimale (ad esempio, 'JMP' è 0xE9 e ' inc 'è 0xFE) e usa questi valori direttamente in C++? Se continuo il mio precedente codice C++:

*ImAcDeclFunc = 'hex value for push'; // This is 'push' from the first line 
*(uint)(ImAcDeclFunc + 1) = 'address to push'; // This is %ebp from the first line 
*(ImAcDeclFunc + 5) = 'hex value for movl' // This is movl from the second line 
// and so on... 

Dopo che ho fatto questo per tutto il codice/tampone, quello sarebbe sufficiente per una funzione _cdecl completamente dinamico (cioè poteva ho appena gettato a un puntatore a funzione e fare int result = ((int (*)(int, int))ImAcDeclFunc)(firstArg, secondArg)?). E io non sono interessato a usare il boost :: funzione o qualcosa di simile, ho bisogno la funzione di essere completamente dinamico, quindi il mio interesse :)

NOTA: Questa domanda è una continuazione sul mio previous one, ma con molto più specifico.

+0

Perché dovresti copiare una funzione? L'originale è altrettanto buono. Vuoi generare una funzione completamente nuova da una rappresentazione di livello superiore? –

+0

@ n.m. Sì, questo è stato solo un esempio per me per capire e per presentare facilmente tutto per te. Avrò facilmente bisogno di una ventina di questi. Se leggi il mio link (alla mia altra domanda) capiresti esattamente perché :) –

+0

Ho provato a capire quella domanda la prima volta, senza alcun successo. –

risposta

5

Se si prende questo lala.c:

int ImAcDeclFunc(int a, int b) 
{ 
    return a + b; 
} 

int main(void) 
{ 
    return 0; 
} 

È possibile compilare con gcc -Wall lala.c -o lala. È quindi possibile disassemblare l'eseguibile con objdump -Dslx lala >> lala.txt. Troverete ImAcDeclFunc è assemblato a:

00000000004004c4 <ImAcDeclFunc>: 
ImAcDeclFunc(): 
    4004c4: 55      push %rbp 
    4004c5: 48 89 e5    mov %rsp,%rbp 
    4004c8: 89 7d fc    mov %edi,-0x4(%rbp) 
    4004cb: 89 75 f8    mov %esi,-0x8(%rbp) 
    4004ce: 8b 45 f8    mov -0x8(%rbp),%eax 
    4004d1: 8b 55 fc    mov -0x4(%rbp),%edx 
    4004d4: 8d 04 02    lea (%rdx,%rax,1),%eax 
    4004d7: c9      leaveq 
    4004d8: c3      retq 

In realtà questa funzione è relativamente facile da copiare altrove. In questo caso, hai perfettamente ragione nel dire che puoi copiare i byte e funzionerebbe.

I problemi si verificano quando si inizia a utilizzare le istruzioni che utilizzano offset relativi come parte dell'opcode. Ad esempio, un salto relativo o una chiamata relativa. In questi casi, è necessario riposizionare l'istruzione correttamente, a meno che non si riesca a copiarlo nello stesso indirizzo in cui era originariamente.

In breve, per riposizionare, è necessario trovare dove era originariamente basato, e calcolare la differenza a dove si andrà a basare e riposizionare ciascuna istruzione relativa a questo offset. Questo di per sé è fattibile.La tua vera difficoltà è gestire le chiamate ad altre funzioni, in particolare le chiamate alle librerie. In questo caso, dovrai garantire che la libreria sia collegata e quindi chiamarla nel modo definito dal formato eseguibile che stai prendendo di mira. Questo è altamente non banale. Se sei ancora interessato, posso indicarti dove dovresti leggere per questo.


Nel tuo caso semplice di cui sopra, si può fare questo:

#include <stdlib.h> 
#include <stdio.h> 
#include <string.h> 
#include <malloc.h> 
#include <sys/mman.h> 
#include <unistd.h> 

int main(void) 
{ 
    char func[] = {0x55, 0x48, 0x89, 0xe5, 0x89, 0x7d, 0xfc, 
    0x89, 0x75, 0xf8, 0x8b, 0x45, 0xf8, 
    0x8b, 0x55, 0xfc, 0x8d, 0x04, 0x02, 
    0xc9, 0xc3}; 

    int (* func_copy)(int,int) = mmap(NULL, sizeof(func), 
     PROT_WRITE | PROT_READ | PROT_EXEC, 
     MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); 

    memcpy(func_copy, func, sizeof(func)); 
    printf("1 + 2 = %d\n", func_copy(1,2)); 

    munmap(func_copy, sizeof(func)); 
    return EXIT_SUCCESS; 
} 

Questo funziona bene su x86-64. Esso stampa:

1 + 2 = 3 
+1

Lo apprezzerei molto ... se potessi fornirmi un esempio funzionante. Sarebbe oro solido per me! Riguardo alle chiamate relative, da quello che so è proprio così; 'targetAddress - currentAddress -/+ eventuali offset '? Per quanto riguarda la 'gestione delle chiamate alle librerie', sarebbe un problema se chiamassi solo funzioni membro? Dal momento che utilizzo GCC è _exactly_ come una chiamata cdecl ma con un puntatore aggiuntivo (il puntatore "this"). O creerebbe problemi se poi chiamo funzioni di libreria con forse _stdcall dalla funzione membro; i.e dynamic_func-> member_func-> library_func? –

+0

Oh, a proposito, non mprotect fallire perché non allineare la memoria? Ci proverò :) –

+0

@ElliottDarfink: Sì, ho appena notato anche l'allineamento. È ancora segfaults dopo averlo modificato, quindi sarà necessario riprodurne altri. Sì, le compensazioni relative funzionano principalmente dall'assunzione del delta di targetAddress e currentAddress. –

1

È possibile controllare il fulmine GNU: http://www.gnu.org/software/lightning/. Potrebbe aiutarti con quello che stai cercando di fare.

+1

Sì, ne ho letto ma non ho capito bene come funziona. Per non parlare di quanto sia sottile la documentazione. Non sai se ci sono risorse di documentazione che potrebbero offrire una mano? Sembra essere quello che voglio, ma non so _how_. –

1

Penso che sarà meglio l'idea di incorporare qualche linguaggio di scripting nel progetto invece di scrivere programma di auto-modifica. Ci vorrà meno tempo e otterrai una maggiore flessibilità.

Se volessi creare un duplicato di questa funzione, ma in modo completamente dinamico, cosa richiedere (e ricorda che è C++ con assemblaggio in linea)?

Richiederebbe umano con disassemblatore. Tecnicamente, la funzione dovrebbe iniziare da un indirizzo e terminare con l'istruzione return. Tuttavia, non si sa cosa ha fatto esattamente il compilatore con la funzione durante la fase di ottimizzazione. Non sarei sorpreso se il punto di ingresso della funzione si trovasse in una sorta di luogo strano (come nella fine della funzione, dopo l'istruzione return), o se la funzione fosse suddivisa in più parti condivise con altre funzioni.

+0

'Richiederebbe umano con disassemblatore' - Questo non è corretto. Esistono strumenti automatici che eseguono analisi statiche che lo contraddicono (come Dyninst). –

+0

@ MikeKwan: ​​Non c'è alcuna contraddizione e io ho ragione. Sebbene esistano strumenti automatizzati, non sono affidabili al 100%, potrebbero richiedere assistenza umana e spesso estraggono i dati di supporto dalle informazioni di debug. In qualche modo, come IDA pro, occorrono pochi minuti per suddividere i file in routine, e possono ancora mancarne alcuni. Diventerà ancora più divertente se proverai ad analizzare il software che è stato offuscato per confondere il disassemblatore. – SigTerm

+0

E credi che un umano con un disassemblatore possa fare di meglio in questi casi? L'analisi prevalentemente statica cade con la ramificazione indiretta. In questi casi l'analisi umana non è molto meglio. Ci sono anche più imprecisioni nella tua risposta.La dimensione di una funzione può essere effettivamente determinata (almeno su ELF) con l'aiuto delle informazioni sui simboli. –

Problemi correlati