- Messaggi
- 2,332
- Reazioni
- 1,928
- Punteggio
- 134
Calcolare la lunghezza di una funzione
1. Introduzione
Non è una domanda comune, ma vuoi per curiosità o per necessità, a qualcuno capita di chiedersi "ma, come posso sapere quanta è lunga una funzione in bytes?". Queti sono alcuni esempi:Find size of a function in C
Is it possible to calculate function length at compile time in C?
E per necessità, qualcuno si inventa soluzioni interessanti:
Calculate C function size x86 and x64
Il metodo presentato in quell'articolo è piuttosto semplice: consiste nell'inserire nel programma una porzione di codice inutile, inserita spesso quando un file viene compilato con il flag DEBUG, che prende forma nell'istruzione
INT 3
(0xCC). In questo caso vengono usate più ripetizioni della medesima istruzioni, inserendo un totale di 8bytes.Poi, considerando che il compilatore va a rimuovere il codice inutile e che del codice dopo il "return" ricade proprio in questo caso, l'autore dell'articolo utilizza la parola chiave
volatile
, così nulla viene rimosso. Nel suo screenshot infatti vedrete presenti quei bytes. Di conseguenza, passando l'indirizzo della funzione, si capisce qual è la fine grazie a quella sequenza di bytes inserita in fase di compilazione.2. Perchè è complicato individuare la fine di una funzione?
E' complicato per una serie di fattori. Diciamo che una funzione termina con il classico "return", che in assembly lo si può rappresentare dall'istruzioneRET
. Se fosse così semplice, basterebbe cercare nel codice macchina l'opcode di RET
(che è 0xC3); un gioco da ragazzi insomma.Ed ecco le complicazioni: se quel 0xC3 in realtà fosse un byte che fa parte di un'altra istruzione? Magari un valore costante come il numero 195 (0xC3 in esadecimale) oppure il cosiddetto "spiazzamento" (displacement) piuttosto che un'altra parte dell'istruzione.
Per capire se il byte che stiamo cercando è l'opcode cercato, è necessario stabilire di quanti byte è composta un'istruzione. Avevo già trattato l'argomento, in realtà senza un vero e proprio articolo, ma proponendo un mio progetto (MCA). Potete leggerne di più al seguente topic Decodificare la lunghezza delle istruzioni di x86 e x64.
Stabilire quanti byte compongono un'istruzione non è semplice, in quanto questa architettura (x86 e x64) ha istruzioni con lunghezza variabile.
Supponiamo però che questo problema non esista, e torniamo alla prima semplificazione: cercare il byte 0xC3. Bene, ora l'abbiamo trovato ma... se fossero presenti "ret" multipli? Anche questo può verificarsi. Non solo, ci sono anche casi nei quali il "ret" non è l'ultima istruzione della funzione, in quanto un qualche tipo di salto (JMP o uno dei salti condizionali,diciamo un IF) lo raggiunge.
In un caso come questo, non si riuscirebbe a stabilire l'effettiva lunghezza, in quanto il primo
RET
, verrebbe rilevato come termine della funzione.3. Disassembly: identificare una funzione
Ciò che presento io è un altro metodo, alternativo, rispetto a quello linkato in apertura. Non è ancora del tutto completo, sono certo ci siano situazioni e casi non "coperti" vista la complessità dell'argomento (e dei test limitati che ho condotto). Ciò che so di non aver gestito sono le jump table, e purtroppo in questi casi la lunghezza viene identificata in modo errato, quindi non sto presentando una soluzione definitiva e priva di errori. Un caso di esempio verrà presentato in seguito.Proseguendo però con il disassembly, consideriamo questo codice in C:
C:
void bubble_sort(int *array) {
printf("Bubble sort");
for(int i=0; i<10; i++) {
for(int j=i+1; j<10; j++) {
if(array[i] > array[j]) {
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
}
}
}
Utilizzando GCC (MinGw, in ambiente Windows) il codice generato è quello visualizzato nello screenshot sottostante:
Nell'immagine si vedono anche distintamente i due loop annidati (i
JMP
in cima con i due JLE
e l'if, l'altro JLE
). Questa funzione non ha nulla di complesso, ha solo un'istruzione RET
. Ora, come funziona l'algoritmo?Ho deciso di utilizzare un'approccio "misto" che mi consentisse di seguire il flusso del codice, ma procedendo anche linearmente, istruzione per istruzione. Ciò che faccio è seguire subito i
JMP
e aggiungere l'istruzione successiva a una "coda futura" (future paths). I Jcc (sallti condizionati, JE, JNE, JG,...) invece non li seguo subito, ma aggiungo aggiungo l'indirizzo target alla coda e poi procedo all'istruzione successiva. Non vado in profondità attualmente, quindi le CALL
le ignoro, le tratto come qualsiasi altra istruzione.Gli opcode
RET
(0xC3) e 0xCC provocano il termine di quello che è un loop. Il loop prosegue sino a che la coda è vuota. La fine di un "path" coincide con l'elaborazione di RET
, 0xCC oppure quando viene rilevato che l'indirizzo attuale è già stato visitato. Ad ogni iterazione infatti ciascun indirizzo viene aggiunto a un array (dinamico).Il raggiungimendo di questo "stato terminale" provoca sempre la lettura dalla coda (future paths): se un indirizzo è presente, viene "caricato" e il disassembly prosegue da questo indirizzo; se la coda è vuota, abbiamo analizzato l'intera funzione.
Riporto il codice completo, in C:
C:
#include "function_length.h"
vector* instrFlowLength(char *pMemory, enum supported_architecture arch)
{
int bytes_len = 0;
uint32_t addr = ((int)pMemory);
queue *future_paths = queue_init();
vector *visited = vector_init();
char *tmp_addr = pMemory;
while(true)
{
struct instruction instr;
mca_decode(&instr, arch,tmp_addr,0);
if(instr.op == 0xc3 || instr.op == 0xCC)
{
if(queue_empty(future_paths))
{
if(instr.op == 0xC3)
vector_push_back(visited,addr+instr.length);
queue_free(future_paths);
return visited;
}
vector_push_back(visited,addr);
tmp_addr = 0;
tmp_addr += queue_dequeue(future_paths);
addr = (uint32_t) tmp_addr;
}
else
{
if(vector_find(visited, addr))
{
if(queue_empty(future_paths)) {
queue_free(future_paths);
return visited;
}
tmp_addr = 0;
tmp_addr += queue_dequeue(future_paths);
addr = (uint32_t) tmp_addr;
}
else
{
vector_push_back(visited, addr);
if (instr.jcc_type == JCC_FAR || instr.jcc_type == JCC_SHORT)
{
if (queue_find(future_paths,instr.label) == 0)
queue_enqueue(future_paths,instr.label);
}
bytes_len += instr.length;
addr += instr.length;
tmp_addr += instr.length;
if (instr.jcc_type == JMP_FAR || instr.jcc_type == JMP_SHORT)
{
if (queue_find(future_paths,addr))
queue_enqueue(future_paths,addr);
tmp_addr = 0;
tmp_addr += instr.label;
addr = instr.label;
}
}
}
}
}
int compare(const void * n1, const void * n2)
{
return (int)( *(uint32_t *)n1 - *(uint32_t*)n2 );
}
pFunctionInfo getFunctionLength(char *buffer, enum supported_architecture arch)
{
pFunctionInfo f_info = calloc(1,sizeof(functionInfo));
vector *visited = instrFlowLength(buffer, arch);
qsort(visited->vect, visited->tos, sizeof(uint32_t), compare);
uint32_t min = visited->vect[0];
uint32_t max = visited->vect[visited->tos-1];
f_info->pVisited = visited;
f_info->length = (int)(max-min);
return f_info;
}
Tutto il codice è stato integrato in MCA e può essere compilato/utilizzato anche da un codice in C++, non ci sono problemi. Ho creato un main che consente la lettura del codice di una funzione e ne restituisce la lunghezza, oltre che tutte le istruzioni che la compongono. Di seguito riporterò alcune parti, spiegandole, e per chiarezza lascerò anche sotto spoiler l'intero output (è un pò lungo...).
L'output che riporto e andrò a spiegare è stato eseguito sulla funzione bubble sort dello screenshot riportato sopra.
Codice:
Reading function machine code at address 0x402620... Done!
Function Length: 179-bytes, decoded 54 instructions.
Addresses of instructions that has been decoded:
402620, 402621, 402623, 402626, 40262D, 402632, 402639, 40263E,
402641, 402644, 402647, 402649, 40264C, 402653, 402656, 402658,
40265A, 40265D, 402664, 402667, 402669, 40266B, 40266D, 40266F,
402672, 402679, 40267C, 40267E, 402680, 402683, 402686, 40268D,
402690, 402692, 402695, 40269C, 40269F, 4026A1, 4026A3, 4026A5,
4026A8, 4026AF, 4026B2, 4026B4, 4026B7, 4026B9, 4026BD, 4026C1,
4026C3, 4026C7, 4026CB, 4026D1, 4026D2, 4026D3,
La prima info interessante è l'entry point della funzione, che se andate a guardare lo screen, coincide con quello riportato qui. L'altra info interessante è proprio la sua lunghezza in bytes. Vengono anche riportati tutti gli indirizzi delle istruzioni che sono state elaborate; potrebbe tornare utile per qualche motivo avere l'elenco degli indirizzi e da lì risalire facilmente all'istruzione e analizzarla singolarmente con MCA, o chissà, anche modificarla.
Proseguo con la parte successiva dell'output:
Codice:
Start disassembly the addresses range: [0x402620, 0x4026D3]
Instr. VA: 0x402620
RAW bytes (hex): 55
Instr. length: 1
Print instruction fields:
Located Prefixes 0:
OP: 0x55
------------------------------------------------
Instr. VA: 0x402621
RAW bytes (hex): 89 E5
Instr. length: 2
Print instruction fields:
Located Prefixes 0:
OP: 0x89
mod_reg_rm: 0xE5
------------------------------------------------
Instr. VA: 0x402623
RAW bytes (hex): 83 EC 28
Instr. length: 3
Print instruction fields:
Located Prefixes 0:
OP: 0x83
mod_reg_rm: 0xEC
Iimm: 0x28
------------------------------------------------
Instr. VA: 0x402626
RAW bytes (hex): C7 04 24 78 D1 40 00
Instr. length: 7
Print instruction fields:
Located Prefixes 0:
OP: 0xC7
mod_reg_rm: 0x4
SIB byte: 0x24
Iimm: 0x40D178
------------------------------------------------
Instr. VA: 0x40262D
RAW bytes (hex): E8 82 1A 00 00
Instr. length: 5
Print instruction fields:
Located Prefixes 0:
OP: 0xE8
Iimm: 0x1A82
------------------------------------------------
Instr. VA: 0x402632
RAW bytes (hex): C7 45 F4 00 00 00 00
Instr. length: 7
Print instruction fields:
Located Prefixes 0:
OP: 0xC7
mod_reg_rm: 0x45
disp (1): 0xF4
Iimm: 0x0
------------------------------------------------
Instr. VA: 0x402639
RAW bytes (hex): E9 89 00 00 00
Instr. length: 5
Instr. is a jump with target address: 0x4026C7
Print instruction fields:
Located Prefixes 0:
OP: 0xE9
Iimm: 0x89
------------------------------------------------
Penso ci sia poco da aggiungere: si tratta di tutte le info riguardanti ciascuna istruzione che MCA ha disassemblato (notate anche l'identificazione di un salto JMP/JCC; è il modo in cui il codice che effettua il conteggio dei bytes che compongono la funzione, verifica se si tratta di un JMP/JCC).
Riporto anche il medesimo codice compilato però con MSVC:
Riporto solo alcune istruzioni:
Codice:
Reading function machine code at address 0x812880... Done!
Function Length: 140-bytes, decoded 50 instructions.
Addresses of instructions that has been decoded:
812880, 812881, 812883, 812886, 812887, 81288C, 812891, 812894,
81289B, 81289D, 8128A0, 8128A3, 8128A6, 8128AA, 8128AC, 8128AF,
8128B2, 8128B5, 8128B7, 8128BA, 8128BD, 8128C0, 8128C4, 8128C6,
8128C9, 8128CC, 8128CF, 8128D2, 8128D5, 8128D8, 8128DA, 8128DD,
8128E0, 8128E3, 8128E6, 8128E9, 8128EC, 8128EF, 8128F2, 8128F5,
8128F8, 8128FB, 8128FE, 812901, 812904, 812906, 812908, 812909,
81290B, 81290C,
Start disassembly the addresses range: [0x812880, 0x81290C]
Instr. VA: 0x812880
RAW bytes (hex): 55
Instr. length: 1
Print instruction fields:
Located Prefixes 0:
OP: 0x55
------------------------------------------------
Instr. VA: 0x812881
RAW bytes (hex): 8B EC
Instr. length: 2
Print instruction fields:
Located Prefixes 0:
OP: 0x8B
mod_reg_rm: 0xEC
------------------------------------------------
Gli indirizzi differiscono nel byte più alto con MSVC in quanto è presente la randomizzazione (ASLR). L'altra parte è infatti uguale, come 0x812880 e il primo indirizzo visibile nell'immagine.
Senza l'utilizzo di flags, il codice prodotto da MSVC è di 39-bytes più corto. Riporto anche il medesimo codice, ma compilato utilizzando -O2 (MinGw) e /O2 (MSVC).
MinGW, bubble sort:
L'output parziale è il seguente:
Codice:
Reading function machine code at address 0x4022F0... Done!
Function Length: 104-bytes, decoded 36 instructions.
Addresses of instructions that has been decoded:
4022F0, 4022F1, 4022F3, 4022F4, 4022F5, 4022F6, 4022F9, 402300,
402304, 402309, 402311, 402316, 402319, 402320, 402323, 402326,
402328, 40232A, 40232C, 40232E, 402330, 402332, 402335, 402338,
40233A, 40233E, 402341, 402344, 402346, 40234A, 402350, 402353,
402354, 402355, 402356, 402358,
Start disassembly the addresses range: [0x4022F0, 0x402358]
Instr. VA: 0x4022F0
RAW bytes (hex): 55
Instr. length: 1
Print instruction fields:
Located Prefixes 0:
OP: 0x55
------------------------------------------------
Instr. VA: 0x4022F1
RAW bytes (hex): 31 ED
Instr. length: 2
Print instruction fields:
Located Prefixes 0:
OP: 0x31
mod_reg_rm: 0xED
------------------------------------------------
E MSVC:
Codice:
Reading function machine code at address 0x8D1E90... Done!
Function Length: 72-bytes, decoded 33 instructions.
Addresses of instructions that has been decoded:
8D1E90, 8D1E91, 8D1E92, 8D1E93, 8D1E94, 8D1E99, 8D1E9E, 8D1EA2,
8D1EA7, 8D1EAA, 8D1EAC, 8D1EAF, 8D1EB0, 8D1EB2, 8D1EB5, 8D1EB7,
8D1EBA, 8D1EBC, 8D1EBE, 8D1EC0, 8D1EC2, 8D1EC5, 8D1EC6, 8D1EC9,
8D1ECB, 8D1ECC, 8D1ECF, 8D1ED2, 8D1ED4, 8D1ED5, 8D1ED6, 8D1ED7,
8D1ED8,
Start disassembly the addresses range: [0x8D1E90, 0x8D1ED8]
Instr. VA: 0x8D1E90
RAW bytes (hex): 53
Instr. length: 1
Print instruction fields:
Located Prefixes 0:
OP: 0x53
------------------------------------------------
Instr. VA: 0x8D1E91
RAW bytes (hex): 55
Instr. length: 1
Print instruction fields:
Located Prefixes 0:
OP: 0x55
------------------------------------------------
4. Problemi nel disassembly e "magie" dei compilatori
Il problema principale è sicuramente dovuto alle jump table nel caso di switch e magari di if/elseif. Potrebbero esserci altri problemi in caso di funzioni che non hanno un ritorno esplicito con RET, ma sono casi che andrebbero creati adhoc per essere poi testati appositamente. Non ho testato con eseguibili prodotti da altri compilatori, ma è probabile che in futuro farò anche questo (CLang ad esempio).Ma veniamo al problema, che ahimè, non è facilmente risolvibile. Ho creato una funzione senza un significato preciso, il cui corpo è il seguente:
C:
int example1(int n) {
printf("example1");
int c = 0;
for(int i=0; i<10; i++) {
for(int j=i; j<n; j++) {
switch (n%i) {
case 2:
return 3;
case 10:
return 23;
case 3:
return 412;
case 98:
return 32;
default:
c++;
}
}
}
return c;
}
Iniziamo da MinGw senza flag:
Nulla di particolare, cicli e "if". Questo è il codice prodotto invece da MSVC:
NOTA: chiedo venia, non mi ero reso conto di aver compilato questa versione: la variabile i del ciclo for deve essere inizializzata a 1; per il resto è tutto corretto, quindi l'istruzione dell'assegnamento del valore a LOCAL.1 verrà fatta con 1 e non con 0 come riportato negli screen.
In particolare guardate questo codice:
Codice:
CPU Disasm
Address Hex dump Command Comments
00C229B7 |. 0FB682 0C2AC2 ||MOVZX EAX,BYTE PTR DS:[EDX+0C22A0C] ; Switch (cases 0..4, 5 exits)
00C229BE |. FF2485 F829C2 ||JMP DWORD PTR DS:[EAX*4+0C229F8]
all'indirizzo 0x00C22A0C si trovano dei valori, dei bytes, che riporto sotto spoiler:
Codice:
CPU Disasm
Address Hex dump Command Comments
00C22A0C . 00 DB 00
00C22A0D . 01 DB 01
00C22A0E . 04 DB 04
00C22A0F . 04 DB 04
00C22A10 . 04 DB 04
00C22A11 . 04 DB 04
00C22A12 . 04 DB 04
00C22A13 . 04 DB 04
00C22A14 . 02 DB 02
00C22A15 . 04 DB 04
00C22A16 . 04 DB 04
00C22A17 . 04 DB 04
00C22A18 . 04 DB 04
00C22A19 . 04 DB 04
00C22A1A . 04 DB 04
00C22A1B . 04 DB 04
00C22A1C . 04 DB 04
00C22A1D . 04 DB 04
00C22A1E . 04 DB 04
00C22A1F . 04 DB 04
00C22A20 . 04 DB 04
00C22A21 . 04 DB 04
00C22A22 . 04 DB 04
00C22A23 . 04 DB 04
00C22A24 . 04 DB 04
00C22A25 . 04 DB 04
00C22A26 . 04 DB 04
00C22A27 . 04 DB 04
00C22A28 . 04 DB 04
00C22A29 . 04 DB 04
00C22A2A . 04 DB 04
00C22A2B . 04 DB 04
00C22A2C . 04 DB 04
00C22A2D . 04 DB 04
00C22A2E . 04 DB 04
00C22A2F . 04 DB 04
00C22A30 . 04 DB 04
00C22A31 . 04 DB 04
00C22A32 . 04 DB 04
00C22A33 . 04 DB 04
00C22A34 . 04 DB 04
00C22A35 . 04 DB 04
00C22A36 . 04 DB 04
00C22A37 . 04 DB 04
00C22A38 . 04 DB 04
00C22A39 . 04 DB 04
00C22A3A . 04 DB 04
00C22A3B . 04 DB 04
00C22A3C . 04 DB 04
00C22A3D . 04 DB 04
00C22A3E . 04 DB 04
00C22A3F . 04 DB 04
00C22A40 . 04 DB 04
00C22A41 . 04 DB 04
00C22A42 . 04 DB 04
00C22A43 . 04 DB 04
00C22A44 . 04 DB 04
00C22A45 . 04 DB 04
00C22A46 . 04 DB 04
00C22A47 . 04 DB 04
00C22A48 . 04 DB 04
00C22A49 . 04 DB 04
00C22A4A . 04 DB 04
00C22A4B . 04 DB 04
00C22A4C . 04 DB 04
00C22A4D . 04 DB 04
00C22A4E . 04 DB 04
00C22A4F . 04 DB 04
00C22A50 . 04 DB 04
00C22A51 . 04 DB 04
00C22A52 . 04 DB 04
00C22A53 . 04 DB 04
00C22A54 . 04 DB 04
00C22A55 . 04 DB 04
00C22A56 . 04 DB 04
00C22A57 . 04 DB 04
00C22A58 . 04 DB 04
00C22A59 . 04 DB 04
00C22A5A . 04 DB 04
00C22A5B . 04 DB 04
00C22A5C . 04 DB 04
00C22A5D . 04 DB 04
00C22A5E . 04 DB 04
00C22A5F . 04 DB 04
00C22A60 . 04 DB 04
00C22A61 . 04 DB 04
00C22A62 . 04 DB 04
00C22A63 . 04 DB 04
00C22A64 . 04 DB 04
00C22A65 . 04 DB 04
00C22A66 . 04 DB 04
00C22A67 . 04 DB 04
00C22A68 . 04 DB 04
00C22A69 . 04 DB 04
00C22A6A . 04 DB 04
00C22A6B . 04 DB 04
00C22A6C . 03 DB 03
sono in totale 96-bytes.
Riporto una parte di codice dello screen, quella davvero interessante:
Codice:
CPU Disasm
Address Hex dump Command Comments
00C2299B |. 8B45 08 ||MOV EAX,DWORD PTR SS:[ARG.1]
00C2299E |. 99 ||CDQ
00C2299F |. F77D FC ||IDIV DWORD PTR SS:[LOCAL.1]
00C229A2 |. 8955 F8 ||MOV DWORD PTR SS:[LOCAL.2],EDX
00C229A5 |. 8B4D F8 ||MOV ECX,DWORD PTR SS:[LOCAL.2]
00C229A8 |. 83E9 02 ||SUB ECX,2
00C229AB |. 894D F8 ||MOV DWORD PTR SS:[LOCAL.2],ECX
00C229AE |. 837D F8 60 ||CMP DWORD PTR SS:[LOCAL.2],60
00C229B2 |. 77 2D ||JA SHORT 00C229E1
00C229B4 |. 8B55 F8 ||MOV EDX,DWORD PTR SS:[LOCAL.2]
00C229B7 |. 0FB682 0C2AC2 ||MOVZX EAX,BYTE PTR DS:[EDX+0C22A0C] ; Switch (cases 0..4, 5 exits)
00C229BE |. FF2485 F829C2 ||JMP DWORD PTR DS:[EAX*4+0C229F8]
ARG.1 è il parametro passato alla funzione, la variabile n. LOCAL.1 è la variabile "i". Viene esteso il valore di EAX da double word a quad word (azzerando il registro EDX), e successivamente avviene la divisione; il registro EDX conterrà il resto della divisione. Questo valore viene assegnato a ECX che viene sottratto di 2 unità. Questo valore viene poi confrontato con il numero 0x60, ovvero 96. Avete già visto questo valore? Già, i bytes della lookup table sono 96. In sostanza, visto che i "case" dello switch non coprono i primi 2 numeri (0 e 1) e che il case con il valore più alto è 98, il compilatore ha prodotto una tabella di 96-bytes, ai quali accede usando il resto della divisione - 2 unità.
Un esempio numerico: diciamo che EAX (il valore di n) sia 10 e che LOCAL.1 sia alla terza iterazione (i=3). Ciò che si verifica è che la divisione 10/3=3 con resto 1 (quindi EDX = 1). ECX = EDX, quindi avverrà la sottrazione ECX-2 = -1.
Questo valore viene confrontato con 96 (0x60), ma il confronto avviene tenendo conto del valore unsigned del primo operando (che nella CMP è il resto, LOCAL.2): questo significa che il confronto avverrà tra 0xFFFFFFFF che è molto più grande di 0x60. Trattandosi di un valore molto più grande, il salto JA avviene e quindi viene eseguito il case di default all'indirizzo 0x00C229E1:
Codice:
CPU Disasm
Address Hex dump Command Comments
00C229B2 |. /77 2D ||JA SHORT 00C229E1
00C229B4 |. |8B55 F8 ||MOV EDX,DWORD PTR SS:[LOCAL.2]
00C229B7 |. |0FB682 0C2AC2 ||MOVZX EAX,BYTE PTR DS:[EDX+0C22A0C] ; Switch (cases 0..4, 5 exits)
00C229BE |. |FF2485 F829C2 ||JMP DWORD PTR DS:[EAX*4+0C229F8]
00C229C5 |> |B8 03000000 ||MOV EAX,3 ; Case 0 of switch main.0C229B7
00C229CA |. |EB 25 ||JMP SHORT 00C229F1
00C229CC |> |B8 17000000 ||MOV EAX,17 ; Case 2 of switch main.0C229B7
00C229D1 |. |EB 1E ||JMP SHORT 00C229F1
00C229D3 |> |B8 9C010000 ||MOV EAX,19C ; Case 1 of switch main.0C229B7
00C229D8 |. |EB 17 ||JMP SHORT 00C229F1
00C229DA |> |B8 20000000 ||MOV EAX,20 ; Case 3 of switch main.0C229B7
00C229DF |. |EB 10 ||JMP SHORT 00C229F1
00C229E1 |> \8B4D F0 ||MOV ECX,DWORD PTR SS:[LOCAL.4] ; Case 4 of switch main.0C229B7
00C229E4 |. 83C1 01 ||ADD ECX,1
00C229E7 |. 894D F0 ||MOV DWORD PTR SS:[LOCAL.4],ECX
Prendiamo un'iterazione successiva, quando avremo EDX = 3. In questo caso la sottrazione di 1 unità porterà ECX a 1. Questo valore è più piccolo di 0x60 (96d), quindi il JA non avviene.
Quando il
JA
non avviene viene eseguito il codice riguardante le tabelle di lookup: prima avviene il lookup del case, quindi si usa il resto che è 1 come indice nell'insieme di byte mostrato sopra: 0x0C22A0C = 0x0C22A0D. Se guardate i bytes mostrati sopra, vedrete che il valore contenuto a questa locazione è il numero 1.Questo numero viene moltiplicato per 4 (perchè stiamo indirizzando una DWORD, quindi 4-byt) e sommato a 0x0C229F8 dando come risultato 0x00C229FC, indirizzo al quale otteniamo 0x00C229D3; a partire dalla locazione 0x0C229F8 infatti la tabella contiene questi dati:
Codice:
CPU Disasm
Address Hex dump Command Comments
00C229F8 . \C529C200 DD 00C229C5
00C229FC . D329C200 DD 00C229D3
00C22A00 . CC29C200 DD 00C229CC
00C22A04 . DA29C200 DD 00C229DA
00C22A08 . E129C200 DD 00C229E1
come potete vedere viene eseguito questo codice:
Codice:
00C229D3 |> |B8 9C010000 ||MOV EAX,19C ; Case 1 of switch main.0C229B7
00C229D8 |. |EB 17 ||JMP SHORT 00C229F1
il vlore 0x19C in decimale è il numero 412 e guardando il codice... vedrete che si tratta del case (con resto) 3.
Questa è la parte "magica", se vogliamo, del lavorone fatto dietro dal compilatore. Implementare un algoritmo che riesca a "capire" che si tratta di una jump table e risalire ai vari case, è oltre a ciò che posso fare ora (ammesso che poi riesca a implementare una cosa come questa) anche per motivi di tempo. In futuro magari proseguirò, chissà...
Ma tornando ai codici, guardate usando O2 con GCC (MinGw) cosa si ottiene:
il codice viene già di molto semplificato. Anche in questo caso l'algoritmo che ottiene la lunghezza funziona correttamente:
Codice:
Reading function machine code at address 0x402340... Done!
Function Length: 108-bytes, decoded 44 instructions.
Addresses of instructions that has been decoded:
402340, 402341, 402343, 402344, 402347, 40234E, 402352, 402357,
40235C, 40235E, 402360, 402362, 402363, 402365, 402368, 40236A,
40236D, 40236F, 402372, 402374, 402377, 402379, 40237B, 40237C,
402380, 402383, 402386, 402388, 40238B, 40238D, 40238E, 40238F,
402390, 402395, 402398, 40239A, 40239B, 40239C, 4023A0, 4023A5,
4023A8, 4023AA, 4023AB, 4023AC,
Start disassembly the addresses range: [0x402340, 0x4023AC]
Instr. VA: 0x402340
RAW bytes (hex): 56
Instr. length: 1
Print instruction fields:
Located Prefixes 0:
OP: 0x56
------------------------------------------------
Instr. VA: 0x402341
RAW bytes (hex): 31 F6
Instr. length: 2
Print instruction fields:
Located Prefixes 0:
OP: 0x31
mod_reg_rm: 0xF6
------------------------------------------------
Instr. VA: 0x402343
RAW bytes (hex): 53
Instr. length: 1
Print instruction fields:
Located Prefixes 0:
OP: 0x53
------------------------------------------------
Instr. VA: 0x402344
RAW bytes (hex): 83 EC 14
Instr. length: 3
Print instruction fields:
Located Prefixes 0:
OP: 0x83
mod_reg_rm: 0xEC
Iimm: 0x14
------------------------------------------------
Instr. VA: 0x402347
RAW bytes (hex): C7 04 24 D4 75 40 00
Instr. length: 7
Print instruction fields:
Located Prefixes 0:
OP: 0xC7
mod_reg_rm: 0x4
SIB byte: 0x24
Iimm: 0x4075D4
------------------------------------------------
Instr. VA: 0x40234E
RAW bytes (hex): 8B 5C 24 20
Instr. length: 4
Print instruction fields:
Located Prefixes 0:
OP: 0x8B
mod_reg_rm: 0x5C
SIB byte: 0x24
disp (1): 0x20
------------------------------------------------
Instr. VA: 0x402352
RAW bytes (hex): E8 0D 18 00 00
Instr. length: 5
Print instruction fields:
Located Prefixes 0:
OP: 0xE8
Iimm: 0x180D
------------------------------------------------
Instr. VA: 0x402357
RAW bytes (hex): B9 01 00 00 00
Instr. length: 5
Print instruction fields:
Located Prefixes 0:
OP: 0xB9
Iimm: 0x1
------------------------------------------------
Instr. VA: 0x40235C
RAW bytes (hex): 39 CB
Instr. length: 2
Print instruction fields:
Located Prefixes 0:
OP: 0x39
mod_reg_rm: 0xCB
------------------------------------------------
Instr. VA: 0x40235E
RAW bytes (hex): 7E 20
Instr. length: 2
Instr. is a jump with target address: 0x402380
Print instruction fields:
Located Prefixes 0:
OP: 0x7E
Iimm: 0x20
------------------------------------------------
Instr. VA: 0x402360
RAW bytes (hex): 89 D8
Instr. length: 2
Print instruction fields:
Located Prefixes 0:
OP: 0x89
mod_reg_rm: 0xD8
------------------------------------------------
Instr. VA: 0x402362
RAW bytes (hex): 99
Instr. length: 1
Print instruction fields:
Located Prefixes 0:
OP: 0x99
------------------------------------------------
Instr. VA: 0x402363
RAW bytes (hex): F7 F9
Instr. length: 2
Print instruction fields:
Located Prefixes 0:
OP: 0xF7
mod_reg_rm: 0xF9
------------------------------------------------
Instr. VA: 0x402365
RAW bytes (hex): 8D 04 33
Instr. length: 3
Print instruction fields:
Located Prefixes 0:
OP: 0x8D
mod_reg_rm: 0x4
SIB byte: 0x33
------------------------------------------------
Instr. VA: 0x402368
RAW bytes (hex): 29 C8
Instr. length: 2
Print instruction fields:
Located Prefixes 0:
OP: 0x29
mod_reg_rm: 0xC8
------------------------------------------------
Instr. VA: 0x40236A
RAW bytes (hex): 83 FA 02
Instr. length: 3
Print instruction fields:
Located Prefixes 0:
OP: 0x83
mod_reg_rm: 0xFA
Iimm: 0x2
------------------------------------------------
Instr. VA: 0x40236D
RAW bytes (hex): 74 31
Instr. length: 2
Instr. is a jump with target address: 0x4023A0
Print instruction fields:
Located Prefixes 0:
OP: 0x74
Iimm: 0x31
------------------------------------------------
Instr. VA: 0x40236F
RAW bytes (hex): 83 FA 03
Instr. length: 3
Print instruction fields:
Located Prefixes 0:
OP: 0x83
mod_reg_rm: 0xFA
Iimm: 0x3
------------------------------------------------
Instr. VA: 0x402372
RAW bytes (hex): 74 1C
Instr. length: 2
Instr. is a jump with target address: 0x402390
Print instruction fields:
Located Prefixes 0:
OP: 0x74
Iimm: 0x1C
------------------------------------------------
Instr. VA: 0x402374
RAW bytes (hex): 83 C6 01
Instr. length: 3
Print instruction fields:
Located Prefixes 0:
OP: 0x83
mod_reg_rm: 0xC6
Iimm: 0x1
------------------------------------------------
Instr. VA: 0x402377
RAW bytes (hex): 39 C6
Instr. length: 2
Print instruction fields:
Located Prefixes 0:
OP: 0x39
mod_reg_rm: 0xC6
------------------------------------------------
Instr. VA: 0x402379
RAW bytes (hex): 75 EF
Instr. length: 2
Instr. is a jump with target address: 0x40236A
Print instruction fields:
Located Prefixes 0:
OP: 0x75
Iimm: 0xEF
------------------------------------------------
Instr. VA: 0x40237B
RAW bytes (hex): 90
Instr. length: 1
Print instruction fields:
Located Prefixes 0:
OP: 0x90
------------------------------------------------
Instr. VA: 0x40237C
RAW bytes (hex): 8D 74 26 00
Instr. length: 4
Print instruction fields:
Located Prefixes 0:
OP: 0x8D
mod_reg_rm: 0x74
SIB byte: 0x26
disp (1): 0x0
------------------------------------------------
Instr. VA: 0x402380
RAW bytes (hex): 83 C1 01
Instr. length: 3
Print instruction fields:
Located Prefixes 0:
OP: 0x83
mod_reg_rm: 0xC1
Iimm: 0x1
------------------------------------------------
Instr. VA: 0x402383
RAW bytes (hex): 83 F9 0A
Instr. length: 3
Print instruction fields:
Located Prefixes 0:
OP: 0x83
mod_reg_rm: 0xF9
Iimm: 0xA
------------------------------------------------
Instr. VA: 0x402386
RAW bytes (hex): 75 D4
Instr. length: 2
Instr. is a jump with target address: 0x40235C
Print instruction fields:
Located Prefixes 0:
OP: 0x75
Iimm: 0xD4
------------------------------------------------
Instr. VA: 0x402388
RAW bytes (hex): 83 C4 14
Instr. length: 3
Print instruction fields:
Located Prefixes 0:
OP: 0x83
mod_reg_rm: 0xC4
Iimm: 0x14
------------------------------------------------
Instr. VA: 0x40238B
RAW bytes (hex): 89 F0
Instr. length: 2
Print instruction fields:
Located Prefixes 0:
OP: 0x89
mod_reg_rm: 0xF0
------------------------------------------------
Instr. VA: 0x40238D
RAW bytes (hex): 5B
Instr. length: 1
Print instruction fields:
Located Prefixes 0:
OP: 0x5B
------------------------------------------------
Instr. VA: 0x40238E
RAW bytes (hex): 5E
Instr. length: 1
Print instruction fields:
Located Prefixes 0:
OP: 0x5E
------------------------------------------------
Instr. VA: 0x40238F
RAW bytes (hex): C3
Instr. length: 1
Print instruction fields:
Located Prefixes 0:
OP: 0xC3
------------------------------------------------
Instr. VA: 0x402390
RAW bytes (hex): BE 9C 01 00 00
Instr. length: 5
Print instruction fields:
Located Prefixes 0:
OP: 0xBE
Iimm: 0x19C
------------------------------------------------
Instr. VA: 0x402395
RAW bytes (hex): 83 C4 14
Instr. length: 3
Print instruction fields:
Located Prefixes 0:
OP: 0x83
mod_reg_rm: 0xC4
Iimm: 0x14
------------------------------------------------
Instr. VA: 0x402398
RAW bytes (hex): 89 F0
Instr. length: 2
Print instruction fields:
Located Prefixes 0:
OP: 0x89
mod_reg_rm: 0xF0
------------------------------------------------
Instr. VA: 0x40239A
RAW bytes (hex): 5B
Instr. length: 1
Print instruction fields:
Located Prefixes 0:
OP: 0x5B
------------------------------------------------
Instr. VA: 0x40239B
RAW bytes (hex): 5E
Instr. length: 1
Print instruction fields:
Located Prefixes 0:
OP: 0x5E
------------------------------------------------
Instr. VA: 0x40239C
RAW bytes (hex): C3
Instr. length: 1
Print instruction fields:
Located Prefixes 0:
OP: 0xC3
------------------------------------------------
Instr. VA: 0x40239D
RAW bytes (hex): 8D 76 00
Instr. length: 3
Print instruction fields:
Located Prefixes 0:
OP: 0x8D
mod_reg_rm: 0x76
disp (1): 0x0
------------------------------------------------
Instr. VA: 0x4023A0
RAW bytes (hex): BE 03 00 00 00
Instr. length: 5
Print instruction fields:
Located Prefixes 0:
OP: 0xBE
Iimm: 0x3
------------------------------------------------
Instr. VA: 0x4023A5
RAW bytes (hex): 83 C4 14
Instr. length: 3
Print instruction fields:
Located Prefixes 0:
OP: 0x83
mod_reg_rm: 0xC4
Iimm: 0x14
------------------------------------------------
Instr. VA: 0x4023A8
RAW bytes (hex): 89 F0
Instr. length: 2
Print instruction fields:
Located Prefixes 0:
OP: 0x89
mod_reg_rm: 0xF0
------------------------------------------------
Instr. VA: 0x4023AA
RAW bytes (hex): 5B
Instr. length: 1
Print instruction fields:
Located Prefixes 0:
OP: 0x5B
------------------------------------------------
Instr. VA: 0x4023AB
RAW bytes (hex): 5E
Instr. length: 1
Print instruction fields:
Located Prefixes 0:
OP: 0x5E
------------------------------------------------
Instr. VA: 0x4023AC
RAW bytes (hex): C3
Instr. length: 1
Print instruction fields:
Located Prefixes 0:
OP: 0xC3
------------------------------------------------
Process finished with exit code 0
Riporto anche il codice prodotto da MSVC utilizzando O2:
Snellito rispettoal precedente, e sono spariti tutti i JMP verso l'uscita dalla funzione. Da notare che la jump table è rimasta e la SUB è stata sostituita con una ADD EDX,-2.
Per mostre che accade a chi non ha mai "smanettato" molto con un debugger, riporto anche l'output di x32dbg, così potete confrontare l'output errato con quello mostrato sopra:
Dopo alla RET (fine procedura), si vede:
Codice:
00C01EE9 | 0F1F00 | nop dword ptr ds:[eax],eax |
00C01EEC | C51E | lds ebx,fword ptr ds:[esi] | esi:EntryPoint
00C01EEE | C000 D7 | rol byte ptr ds:[eax],D7 |
00C01EF1 | 1E | push ds |
00C01EF2 | C000 CE | rol byte ptr ds:[eax],CE |
00C01EF5 | 1E | push ds |
00C01EF6 | C000 E0 | rol byte ptr ds:[eax],E0 |
00C01EF9 | 1E | push ds |
00C01EFA | C000 B3 | rol byte ptr ds:[eax],B3 |
00C01EFD | 1E | push ds |
00C01EFE | C000 00 | rol byte ptr ds:[eax],0 |
00C01F01 | 010404 | add dword ptr ss:[esp+eax],eax |
00C01F04 | 04 04 | add al,4 |
00C01F06 | 04 04 | add al,4 |
00C01F08 | 020404 | add al,byte ptr ss:[esp+eax] |
00C01F0B | 04 04 | add al,4 |
00C01F0D | 04 04 | add al,4 |
00C01F0F | 04 04 | add al,4 |
00C01F11 | 04 04 | add al,4 |
00C01F13 | 04 04 | add al,4 |
00C01F15 | 04 04 | add al,4 |
00C01F17 | 04 04 | add al,4 |
00C01F19 | 04 04 | add al,4 |
00C01F1B | 04 04 | add al,4 |
00C01F1D | 04 04 | add al,4 |
00C01F1F | 04 04 | add al,4 |
00C01F21 | 04 04 | add al,4 |
00C01F23 | 04 04 | add al,4 |
00C01F25 | 04 04 | add al,4 |
00C01F27 | 04 04 | add al,4 |
00C01F29 | 04 04 | add al,4 |
00C01F2B | 04 04 | add al,4 |
In questo caso il debugger rileva questo blocco come istruzioni e non come dati; gli si dovrebbe quindi dire di trattarli come DWORD e sotto come BYTE (ADD AL, 4 sono i valori visti in precedenza).
Per completezza sotto spoiler lascio anche il disassemblato di IDA Free:
Codice:
.text:00401E70 sub_401E70 proc near ; DATA XREF: sub_401010+17↑o
.text:00401E70 ; sub_401010+28↑o ...
.text:00401E70
.text:00401E70 arg_0 = dword ptr 4
.text:00401E70
.text:00401E70 push ebx
.text:00401E71 push esi
.text:00401E72 push edi
.text:00401E73 push offset aExample1 ; "example1"
.text:00401E78 call sub_4015E0
.text:00401E7D mov ebx, [esp+10h+arg_0]
.text:00401E81 add esp, 4
.text:00401E84 xor esi, esi
.text:00401E86 mov edi, 1
.text:00401E8B nop dword ptr [eax+eax+00h]
.text:00401E90
.text:00401E90 loc_401E90: ; CODE XREF: sub_401E70+4D↓j
.text:00401E90 mov ecx, edi
.text:00401E92 cmp edi, ebx
.text:00401E94 jge short loc_401EB9
.text:00401E96 mov eax, ebx
.text:00401E98 cdq
.text:00401E99 idiv edi
.text:00401E9B add edx, 0FFFFFFFEh
.text:00401E9E xchg ax, ax
.text:00401EA0
.text:00401EA0 loc_401EA0: ; CODE XREF: sub_401E70+47↓j
.text:00401EA0 cmp edx, 60h ; switch 97 cases
.text:00401EA3 ja short def_401EAC ; jumptable 00401EAC default case, cases 2-7,9-95
.text:00401EA5 movzx eax, ds:byte_401F00[edx]
.text:00401EAC jmp ds:jpt_401EAC[eax*4] ; switch jump
.text:00401EB3 ; ---------------------------------------------------------------------------
.text:00401EB3
.text:00401EB3 def_401EAC: ; CODE XREF: sub_401E70+33↑j
.text:00401EB3 ; sub_401E70+3C↑j
.text:00401EB3 ; DATA XREF: ...
.text:00401EB3 inc ecx ; jumptable 00401EAC default case, cases 2-7,9-95
.text:00401EB4 inc esi
.text:00401EB5 cmp ecx, ebx
.text:00401EB7 jl short loc_401EA0
.text:00401EB9
.text:00401EB9 loc_401EB9: ; CODE XREF: sub_401E70+24↑j
.text:00401EB9 inc edi
.text:00401EBA cmp edi, 0Ah
.text:00401EBD jl short loc_401E90
.text:00401EBF pop edi
.text:00401EC0 mov eax, esi
.text:00401EC2 pop esi
.text:00401EC3 pop ebx
.text:00401EC4 retn
.text:00401EC5 ; ---------------------------------------------------------------------------
.text:00401EC5
.text:00401EC5 loc_401EC5: ; CODE XREF: sub_401E70+3C↑j
.text:00401EC5 ; DATA XREF: .text:jpt_401EAC↓o
.text:00401EC5 pop edi ; jumptable 00401EAC case 0
.text:00401EC6 pop esi
.text:00401EC7 mov eax, 3
.text:00401ECC pop ebx
.text:00401ECD retn
.text:00401ECE ; ---------------------------------------------------------------------------
.text:00401ECE
.text:00401ECE loc_401ECE: ; CODE XREF: sub_401E70+3C↑j
.text:00401ECE ; DATA XREF: .text:jpt_401EAC↓o
.text:00401ECE pop edi ; jumptable 00401EAC case 8
.text:00401ECF pop esi
.text:00401ED0 mov eax, 17h
.text:00401ED5 pop ebx
.text:00401ED6 retn
.text:00401ED7 ; ---------------------------------------------------------------------------
.text:00401ED7
.text:00401ED7 loc_401ED7: ; CODE XREF: sub_401E70+3C↑j
.text:00401ED7 ; DATA XREF: .text:jpt_401EAC↓o
.text:00401ED7 pop edi ; jumptable 00401EAC case 1
.text:00401ED8 pop esi
.text:00401ED9 mov eax, 19Ch
.text:00401EDE pop ebx
.text:00401EDF retn
.text:00401EE0 ; ---------------------------------------------------------------------------
.text:00401EE0
.text:00401EE0 loc_401EE0: ; CODE XREF: sub_401E70+3C↑j
.text:00401EE0 ; DATA XREF: .text:jpt_401EAC↓o
.text:00401EE0 pop edi ; jumptable 00401EAC case 96
.text:00401EE1 pop esi
.text:00401EE2 mov eax, 20h ; ' '
.text:00401EE7 pop ebx
.text:00401EE8 retn
.text:00401EE8 sub_401E70 endp
.text:00401EE8
.text:00401EE8 ; ---------------------------------------------------------------------------
.text:00401EE9 align 4
.text:00401EEC jpt_401EAC dd offset loc_401EC5 ; DATA XREF: sub_401E70+3C↑r
.text:00401EEC dd offset loc_401ED7 ; jump table for switch statement
.text:00401EEC dd offset loc_401ECE
.text:00401EEC dd offset loc_401EE0
.text:00401EEC dd offset def_401EAC
.text:00401F00 byte_401F00 db 0, 1, 4, 4
.text:00401F00 ; DATA XREF: sub_401E70+35↑r
.text:00401F00 db 4, 4, 4, 4 ; indirect table for switch statement
.text:00401F00 db 2, 4, 4, 4
.text:00401F00 db 4, 4, 4, 4
.text:00401F00 db 4, 4, 4, 4
.text:00401F00 db 4, 4, 4, 4
.text:00401F00 db 4, 4, 4, 4
.text:00401F00 db 4, 4, 4, 4
.text:00401F00 db 4, 4, 4, 4
.text:00401F00 db 4, 4, 4, 4
.text:00401F00 db 4, 4, 4, 4
.text:00401F00 db 4, 4, 4, 4
.text:00401F00 db 4, 4, 4, 4
.text:00401F00 db 4, 4, 4, 4
.text:00401F00 db 4, 4, 4, 4
.text:00401F00 db 4, 4, 4, 4
.text:00401F00 db 4, 4, 4, 4
.text:00401F00 db 4, 4, 4, 4
.text:00401F00 db 4, 4, 4, 4
.text:00401F00 db 4, 4, 4, 4
.text:00401F00 db 4, 4, 4, 4
.text:00401F00 db 4, 4, 4, 4
.text:00401F00 db 4, 4, 4, 4
.text:00401F00 db 4, 4, 4, 4
.text:00401F00 db 3
5. Conclusione
Ciò detto, spero sia stato di vostro interesse, non solo la parte dedicata alla mia libreria e alla funzionalità implementata, ma l'intero paragrafo 4 in particolare.Grazie per la lettura, e se vedete qualcosa di non chiaro (o volete chiarimenti/info o correggermi in qualche parte), scrivete pure qui sotto.