VB Reversing

Data

by "Winebag"

 

18/03/2003

UIC's Home Page

Published by Quequero

Sò diabbolico nell' amplesso, sprupurziunat' per quanto riguarda le dimensioni di sesso

Grazie Wine

...e se trovo la donna ggiusta me la ciuccio com' n'aracosta

....


E-mail: Winebag@usa.com

....

Difficoltà

(X)NewBies ( )Intermedio ( )Avanzato ( )Master

 

Oggi AndreaGeddon ci ha preparato un corso in cui ci divertiremo come bambini a reversare il Visual Basic!


VB Reversing

Written by Winebag

Introduzione

Spesso quando ci troviamo di fronte a crackme in VB, tendiamo a dribblarli, tanto abbiamo sempre qualcosa di più utile da fare, però questo linguaggio, pur con tutti i suoi problemi, non è poi così difficile da reversare. Questo corso ci dà la possibilità di cominciare con un CM facile ad addentrarci in questi simpatici eseguibili. 

Tools usati

SoftIce 
IDA Pro

URL o FTP del programma

Allegato

Notizie sul programma

Questo programma è un semplice crackme in VB, scritto per noi da Ntoskrnl, ci chiede un serial a partire dal nostro nome.

Essay

Io personalmente ho agito così: prima ho dato una rapida occhiata con SICE,ho individuato le routine che mi sono poi guardato con comodo con IDA. La prima cosa da fare quando usiamo SIce sul VB, se non l'abbiamo già fatta è caricare col SymbolLoader la Virtual Machine, che in questo caso è MSVBVM60.dll. 
Quindi possiamo cominciare a guardare il CM. Probabilmente un espertone di VB vi sconsiglierebbe di mettere un bpx su hmemcpy,quando ci sono modi più eleganti e meno aleatori per entrare nel codice del programma. Io tuttavia non sono un espertone quindi me ne fotto e metto il mio bpx hmemcpy e premo il pulsante check; dopo 14 F12 e un F8, mi ritrovo nel codice del programma. La prima cosa che vedo è un confronto,ottenuto con __vbaStrCmp, tra il nostro username e una stringa vuota, quindi se non abbiamo inserito il nome arriva una messagebox, altrimenti continua; poi prende il nostro serial, arriva hmemcpy, solita trafila di F12 e ritorniamo nel codice; un altro confronto; se c'è anche il serial continua. A questo punto arriva la parte interessante dove si confrontano i valori: il primo check che mi trovo è questo:
push eax<--si riferisce al serial
call ds:__vbaLenBstr
mov [ebp-28h], eax
mov dword ptr [ebp-4], 0Eh
cmp dword ptr [ebp-28h], 8
jz short loc_0_404219 
in cui viene controllata la lunghezza del serial, che deve essere 8. Andiamo avanti con SIce, troviamo un loop che controlla che il seral sia numerico, altri due loop che vediamo dopo e un cmp che mi sembra importante:
mov ecx, [ebp-38h]
cmp ecx, [ebp-3Ch]
jz short ..
Allo stato attuale [ebp-38h]e [ebp-3Ch] non sono uguali, quindi dobbiamo modificare il flag Z(in Sice:r fl z) per ottenere il salto. Dopo troviamo altri due loop e lo stesso check di prima,che sappiamo già come superare. Quindi compare la MBox di congratulazioni. Siamo stati fortunati, il che vuol dire che dovremo passare solo due check; quindi lo disassembliamo con IDA, cerchiamo l'indirizzo di questa funzione e ci reversiamo i 5 loop che abbiamo trovato. Una nota per i newbies: ho cambiato il nome di alcune label e variabili locali per maggiore chiarezza, quindi non spaventatevi se trovate ad esempio Var1 al posto di [esp+38h]. Iniziamo dal primo:

    mov dword ptr [ebp-4], 11h
    mov ecx, length
    call ds:__vbaI2I4
<--trasforma una dword in word
    mov lengthserial, ax
    mov word ptr [ebp-0DCh], 1
    mov word ptr counter, 1
    jmp short entry1



Loop1: 
    mov cx, counter
    add cx, [ebp-0DCh]
<--questo valore è 1
    jo Error
    mov counter, cx
entry1: 
    mov dx, counter
    cmp dx, lengthserial
    jg exit1
    mov dword ptr [ebp-4], 12h
    mov dword ptr [ebp-58h], 1
    mov dword ptr [ebp-60h], 2
    lea eax, [ebp-2Ch]
    mov [ebp-98h], eax
    mov dword ptr [ebp-0A0h], 4008h
    lea ecx, [ebp-60h]
    push ecx
    movsx edx, word ptr counter
    push edx
<---indice nella stringa
    lea eax, [ebp-0A0h]
    push eax
    lea ecx, [ebp-70h]
    push ecx
    call ds:rtcMidCharVar
    lea edx, [ebp-70h]
    push edx
    lea eax, [ebp-40h]
    push eax
    call ds:__vbaStrVarVal
    push eax
    call ds:rtcAnsiValueBstr
    mov ecx, eax
<--qui c'è il byte individuato dall'indice, quindi dal contatore
    call ds:__vbaUI1I2
<--converte una word in un unsigned char
    mov char, al
<--qui salva questo valore in una variabile locale
    lea ecx, [ebp-40h]
    call ds:__vbaFreeStr
    lea ecx, [ebp-70h]
    push ecx
    lea edx, [ebp-60h]
    push edx
    push 2
    call ds:__vbaFreeVarList
    add esp, 0Ch
    mov dword ptr [ebp-4], 13h
    movzx ax, char
    cmp ax, 30h
<---
    jge short OK3
    jmp No
OK3: 
    mov dword ptr [ebp-4], 16h
    movzx cx, char
    cmp cx, 39h
<---questi due cmp controllano che il carattere sia numerico
    jle short OK4
    jmp No
OK4: 
    mov dword ptr [ebp-4], 19h
    jmp Loop1

Starete pensando minchia che ciclo lungo, chissà quante cose riuscirà a fare questo ottimo VB! Invece no, non so se sia l'autore che volutamente per complicarci la vita abbia aggiunto codice inutile o se il compilatore si sia preso la libertà di farlo lui anche se mi sembra più plausibile la seconda ipotesi. Dunque cercherò di dare un'interpretazione alle sei funzioni chiamate dal programma,  andando un po' a senso perché non credo siano documentate e ovviamente non me le sono reversate! La prima, rtcMidCharVar,  salva in una qualche struttura l'indice inserito come parametro;  __vbaStrVarVal restituisce un puntatore alla stringa considerata(in questo caso quella contenente il serial); rtcAnsiValueBstr restituisce il byte della stringa individuato dall'indice( ricordando che i byte sono numerati a partire da 1, quindi con indice 1 restituisce string[0], e che le stringhe sono in formato unicode); __vbaUI1I2 ha il compito di convertire il valore passatole in cx, in un byte; in questo caso mi sembra però sovrabbondante dato che la funzione precedente restituisce già un byte.__vbaFreeStr e __vbaFreeVarList dovrebbero servire a liberare la memoria precedentemente allocata,ma in ogni caso non ci interessa.Tutto questo loop come avevamo detto all'inizio è un banale controllo della "numericità" del serial,che in assembly ben scritto starebbe comodamente in una ventina di byte. Passiamo al secondo loop dove viene generata la dword[ebp-38h],rinominata Var1; ho tagliato qualche pezzo che si ripeteva uguale a sopra.

    mov length, ax
    mov word ptr [ebp-0E4h], 1
    mov word ptr counter, 1
    jmp short entry2
Loop2: 
    mov ax, counter
    add ax, [ebp-0E4h]
<--+1
    jo error
    mov counter, ax
entry2: 
    mov cx, counter
    cmp cx, length
    jg exit2
    ...
<--qui in un modo non  molto ortodosso viene messo un carattere dell'UserName in al
    movsx eax, ax
    mov ecx, Var1
<---alla Var1 del loop precedente 
    add ecx, eax
<--viene aggiunto il carattere di questo ciclo
    jo error
    mov Var1, ecx
    ...
    jmp Loop2

exit2: 
    mov dword ptr [ebp-4], 1Fh
    mov ecx, Var1
    xor ecx, length
<--xora la somma dei caratteri con la lunghezza
    mov Var1, ecx
<-- e salva il risultato in Var1

Se togliamo il superfluo diventa ovvio capire come Var1,che all'inizio valeva 0, adesso contenga la sommatoria di tutti i caratteri xorata con la lunghezza. Passiamo ora ad analizzare il terzo loop:

    mov word ptr [ebp-0F0h], 4
    mov word ptr [ebp-0ECh], 1
    mov word ptr counter, 1
    jmp short entry3

Loop3: 
    mov dx, counter
    add dx, [ebp-0ECh]
<---+1
    jo error
    mov counter, dx
entry3: 
    mov ax, counter
    cmp ax, [ebp-0F0h]
<--Primi 4 char
    jg exit3
    mov dword ptr [ebp-4], 22h
    mov dword ptr [ebp-58h], 1
    mov dword ptr [ebp-60h], 2
    lea ecx, [ebp-2Ch]
    mov [ebp-98h], ecx
    mov dword ptr [ebp-0A0h], 4008h
    lea edx, [ebp-60h]
    push edx
    movsx eax, word ptr counter
    push eax
    lea ecx, [ebp-0A0h]
    push ecx
    lea edx, [ebp-70h]
    push edx
    call ds:rtcMidCharVar
<-- di nuovo salva l'indice rappresentato dal contatore
    mov eax, Var2
<-- prende il vecchio valore di Var2
    push eax
    call ds:__vbaStrI4
<--lo trasforma in una stringa (tipo itoa)
    mov edx, eax
    lea ecx, [ebp-44h]
    call ds:__vbaStrMove
<--copia la stringa da un'altra parte 
    push eax
    lea ecx, [ebp-70h]
    push ecx
    lea edx, [ebp-40h]
    push edx
    call ds:__vbaStrVarVal
<--restituisce il puntatore alla stringa
    push eax
    call ds:rtcAnsiValueBstr
<--legge il valore del byte
    sub ax, 30h
<--legge il valore decimale rappresentato
    jo error
    push eax
    call ds:__vbaStrI2
<--lo riconverte in una stringa!
    mov edx, eax
    lea ecx, [ebp-48h]
    call ds:__vbaStrMove
<--copia la stringa tanto per sprecare un po' di cicli
    push eax
    call ds:__vbaStrCat
<--concatena le due stringhe
    mov edx, eax
    lea ecx, [ebp-4Ch]
    call ds:__vbaStrMove
<--sposta la stringa ottenuta,tanto per cambiare
    push eax
    call ds:__vbaI4Str
<--ritrasforma la stringa in intero decimale(vedi atoi)
    mov Var2, eax
<--lo salva in Var2
    ...
<--libera la memoria
    jmp Loop3

exit3: 
    mov dword ptr [ebp-4], 24h
    mov eax, Var2
    xor eax, 29Ah
<--xora il valore ottenuto con 29Ah
    mov Var2, eax
    mov dword ptr [ebp-4], 25h
    mov ecx, Var2
    cmp ecx, Var1
<--confronta i due valori
    jz short OK5
<--continua
    jmp No
<--esce

In sintesi, dimenticando tutto il commercio di stringhe che viene fatto, questo loop prende ciascuno dei primi 4 caratteri del serial e lo concatena ai precedenti in una stringa, che poi converte in un intero e salva in Var2; tutto ciò corrisponde ad un unico atoi, in Var2 ci sarà quindi il valore decimale dei primi 4 caratteri; questo valore viene xorato con 29Ah, e deve essere uguale al valore trovato sopra. Possiamo quindi avere un idea del serial: è di 8 caratteri, per ottenere i primi 4 sommo tutti i valori dell'UN, xoro prima con la sua lunghezza e poi con 29Ah. Andiamo al 4°loop.

    mov dword ptr [ebp-4], 28h
    mov dword ptr Var2, 0
<---
    mov dword ptr [ebp-4], 29h
    mov dword ptr Var1, 0
<--azzera le due variabili
    mov dword ptr [ebp-4], 2Ah
    mov ecx, length
<--si riferisce all'UN
    call ds:__vbaI2I4
    mov UNlength, ax
    mov word ptr [ebp-0F4h], 1
    mov word ptr counter, 1
    jmp short entry4

loop4: 
    mov dx, counter
    add dx, [ebp-0F4h]
<--che brutto modo di scrivere inc dx :-((
    jo error
    mov counter, dx
entry4: 
    mov ax, counter
    cmp ax, UNlength
    jg exit4
    mov dword ptr [ebp-4], 2Bh
    mov dword ptr [ebp-58h], 1
    mov dword ptr [ebp-60h], 2
    lea ecx, [ebp-30h]
    mov [ebp-98h], ecx
    mov dword ptr [ebp-0A0h], 4008h
    lea edx, [ebp-60h]
    push edx
    movsx eax, word ptr counter
    push eax
    lea ecx, [ebp-0A0h]
    push ecx
    lea edx, [ebp-70h]
<--fate attenzione a questo indirizzo
    push edx
    call ds:rtcMidCharVar
<--salva l'indice corrispondente al ciclo
    mov dword ptr [ebp-78h], 1
    mov dword ptr [ebp-80h], 2
    lea eax, [ebp-30h]
    mov [ebp-0B8h], eax
    mov dword ptr [ebp-0C0h], 4008h
    lea ecx, [ebp-80h]
    push ecx
    movsx edx, word ptr counter
    mov eax, length
    sub eax, edx
<--qui eax=L-C
    jo error
    push eax
<--fissa eax come indice per un secondo valore
    lea ecx, [ebp-0C0h]
    push ecx
    lea edx, [ebp-90h]
<--che mettera in una,credo, struttura a quest'indirizzo
    push edx
    call ds:rtcMidCharVar
    lea eax, [ebp-70h]
<--questo si riferisce alla prima chiamata a rtcMidCharVar
    push eax
    lea ecx, [ebp-40h]
    push ecx
    call ds:__vbaStrVarVal
    push eax
    call ds:rtcAnsiValueBstr
<--questo sarà il carattere di posizione C nella stringa col nostro UN
    movsx edx, ax
    mov esi, Var1
    add esi, edx
<--aggiunge a Var1 il carattere C
    jo error
    lea eax, [ebp-90h]
<--questa variabile è stata usata dalla seconda rtcMidCharVar
    push eax
    lea ecx, [ebp-44h]
    push ecx
    call ds:__vbaStrVarVal
    push eax
    call ds:rtcAnsiValueBstr
<--quindi questo sarà il char di posizione L-C
    movsx edx, ax
    xor esi, edx
<--con cui xoriamo il risultato della somma precedente
    mov Var1, esi
<--che rimettiamo in Var1 per il prossimo ciclo
    ...
    jmp loop4

Anche qui siamo alla fiera dell'overbloating, e una banalissima routine viene espansa in righe e righe di codice Si ma e' funzionale per annoiarci ;p NdQue.Vi ho messo quasi tutta la routine, perché poteste vedere la differenza tra i due caratteri che vengono usati per ogni ciclo : di posizione C e (L-C). Se però siete stati attenti noterete che c'è un piccolo bug: infatti poiché i caratteri vengono contati a partire da uno, la variabile C assumerà tutti i valori compresi tra 1 e L; l'indice dell'altro carattere andrà quindi da (C-1) a (L-L)=0, quindi dal penultimo al "numero zero"(come l'osteria:-))), che quindi non esiste. Strano a dirsi questo fatto non fa crashare il programma,che anzi riprende da dove aveva lasciato come se nulla fosse successo! Questo se vogliamo è una delle poche cose utili del VB: anche se in questo caso c'è un errore, non si blocca,ma riprende da dove aveva lasciato, anche se probabilmente in un linguaggio umano non sarebbe neanche avvenuto l'errore, ma questo è un altro discorso. Ora a noi non interessa se questo bug sia stato messo di proposito, a noi interessa sapere cosa succede a Var1: ovviamente non viene modificata, quindi di fatto è come se il programma facesse un ciclo in meno. Ricapitolando in pseudo-C:
for(i=0;i<strlen(un)-1;i++){Var1+=un[i]; Var1^=un[strlen(un)-i-2]
Adesso dovremmo passare al quinto loop, che vi risparmio, tanto è uguale al terzo, quindi un atoi, solo che per gli ultimi 4 char del serial; diamo uno sguardo all' ultimo check, per vedere se ci sono altre operazioni:

    mov word ptr [ebp-100h], 8<--
    mov word ptr [ebp-0FCh], 1
    mov word ptr counter, 5
<---Notate solo che usa i caratteri da 5 a 8

    ...
<---Var2=atol(&serial[4])

    mov ecx, Var1
    cmp ecx, Var2
<--Niente, un semplice controllo
    jz short OK6
<--abbiamo finito
    jmp No

Come avete potuto vedere la difficoltà più grossa è stata orientarsi nel codice mostruosamente lungo prodotto dal compilatore VB; l'algoritmo era piuttosto semplice, quindi possiamo scrivere un piccolo crackme, per dare un esempio in prima persona non ho usato il VB! Ho optato per il C, solo per la comodità delle funzioni di I-O, avrei anche potuto scriverlo in asm, per far risaltare chiaramente la differenza di lunghezza del compilato, ma non avevo voglia.

//----------------------------- Keygen.cpp-------------------------------------------

#include <stdio.h>
#include <string.h>
void main(){
char *name;
char s[9];
int a,l,h,b=0;
strcpy(s,"00000000");
gets(name);
l=strlen(name);
h=l^0x29A;
l--;
a=name[l];
for (int i=0;i<l;i++){
b+=name[i];
b^=name[l-i-1];
a+=name[i];}
a^=h;
b+=a*10000;
for (int i=7;i>=0;i--){
s[i]+=b%10;
b/=10;}
printf("%s",s);}
//---------------------------------------------------------------

Il codice ovviamente non è commentato, ma se avete capito come funziona l'algo non dovreste avere problemi a capirlo; l'unica cosa che ho aggiunto è l'ultimo ciclo, per formattare l'output, dato che in printf non sapevo come ottenere l'equivalente di %08d.
Per oggi abbiamo finito,ciao a tutti

Winebag

Note finali

Allora saluterei gli autori di questo corso, Ntoskrnl e AndreaGeddon (con la gentile partecipazione dei suoi capelli), che mi hanno spinto a dare uno sguardo al VB, cosa che ho sempre tentato di evitare. Un ringraziamento va a tutta la UIC e a tutti quelli che si sbattono per mandarla avanti.

Disclaimer

Vorrei ricordare che il software va comprato e  non rubato, dovete registrare il vostro prodotto dopo il periodo di valutazione. Non mi ritengo responsabile per eventuali danni causati al vostro computer determinati dall'uso improprio di questo tutorial. Questo documento è stato scritto per invogliare il consumatore a registrare legalmente i propri programmi, e non a fargli fare uso dei tantissimi file crack presenti in rete, infatti tale documento aiuta a comprendere lo sforzo immane che ogni singolo programmatore ha dovuto portare avanti per fornire ai rispettivi consumatori i migliori prodotti possibili.

Noi reversiamo al solo scopo informativo e di miglioramento del linguaggio Assembly.