A:link { TEXT-DECORATION: underline } A:visited { TEXT-DECORATION: underline } A:active { COLOR: red; TEXT-DECORATION: underline } A:hover { COLOR: #8080ff; TEXT-DECORATION: underline }

Extending Visual Basic 5.0

Data

by Spider

 

08/2001

UIC's Home Page

Published by Quequero

La m$ non documenta alcune interessanti cosette...

Qualche mio eventuale commento sul tutorial :)))

...secondo voi ci credono così lamah da non capire a cosa servono???

Secondo me i lamah sono loro, che, come vedremo,...

E-mail: spider_xx87@hotmail.com

Nick, UIN, canale IRC/EFnet frequentato
irc.azzurra.it #crack-it  #asm

...non sono nemmeno capaci di fare un po' di ottimizzazione!

Difficoltà

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

 

Il Visual Basic ha molti limiti. Non si possono usare le callback, non si può includere codice assembly... A noi i limiti non piacciono giusto? Quindi vedremo come abbattere questi problemi prendendo un po' per il culo il VB.


Extending Visual Basic 5.0
Written by Spider

Introduzione


Anche la guida di VB dice che in Visual Basic non si possono utilizzare le funzioni callback. E l'assembler non è nemmeno nominato. Tuttavia, ricorrendo a qualche API, è possibile abbattere questi limiti di VB5.

Tools usati


Ovviamente Visual Basic 5.0 o superiore.

Essay


Funzioni callback

Per cominciare vedremo  come utilizzare le funzioni callback in Visual Basic.
Innanzitutto, che cos'è una funzione callback? Una funzione callback è una funzione che viene richiamata da un'altra funzione che dispone del suo indirizzo in memoria. Ad esempio, quando subclassiamo una finestra passiamo alla funzione SetWindowLong l'indirizzo della nostra Window Procedure, che abbiamo ottenuto con l'operatore AddressOf. Windows potrà così richiamare la nostra funzione ad ogni messaggio. In VB, normalmente, è questo tutto ciò che possiamo fare con le funzioni callback. Supponiamo però di voler richiamare una funzione callback da VB. Come fare? Hehehe! Per tanto tempo anch'io me lo sono chiesto. La risposta l'ho trovata riflettendo sul funzionamento del subclassing: quando richiamiamo la SetWindowLong, essa ci restituisce l'indirizzo della vecchia Window Procedure. Nella nostra Window Procedure, mettiamo una chiamata alla vecchia Window Procedure utilizzando la funzione API CallWindowProcA. Questa funzione non fa altro che richiamare la Window Procedure che c'era prima che noi installassimo il subclassing. Se la CallWindowProcA può fare ciò con l'indirizzo di una Window Procedure, perchè non dovrebbe poterlo fare con una procedura di altro genere? Allora mi sono messo a fare alcuni esperimenti e, visti i buoni risultati iniziali, ho approfondito l'argomento. Ho constatato che la CallWindowProcA, sebbene normalmente funzioni con 5 argomenti, funziona anche con un numero diverso di argomenti. Il minimo è ovviamente 1, perchè è sempre necessario l'indirizzo della procedura da richiamare. Facciamo un esempio pratico:

'###########################################################
'# Questo codice va inserito in un modulo standard         #
'###########################################################

Declare Function CallProc0 Lib "user32" Alias "CallWindowProcA" (ByVal _
FunctionAddress As Long) As Long

Public Function Prova() As Long
    Form1.Print "Ciauz!"
    Prova = 2001
End Function

'<-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><->
'################################################################
'# Questo nella sezione dichiarazioni di un Form chiamato Form1 #
'# contenente un bottone chiamato Command1                      #
'################################################################

Private Sub Command1_Click()
    Print CallProc0(AddressOf Prova)
End Sub


Eseguitelo e cliccate su Command1. Sul form la funzione scriverà "Ciauz!", e poi verrà scritto 2001, che è il valore di ritorno della funzione. Adesso un'altra versione con 1 argomento:


'###########################################################
'# Questo codice va inserito in un modulo standard         #
'###########################################################

Declare Function CallProc1 Lib "user32" Alias "CallWindowProcA" (ByVal _
FunctionAddress As Long, ByVal Argomento1 As Long) As Long

Public Function Prova(ByVal Argomento As Long) As Long
    Form1.Print "Ciauz!"
    Form1.Print "L'argomento che mi hai passato è: " & Argomento
    Prova = Argomento
End Function

'<-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><->
'################################################################
'# Questo nella sezione dichiarazioni di un Form chiamato Form1 #
'# contenente un bottone chiamato Command1                      #
'################################################################

Private Sub Command1_Click()
    Print CallProc1(AddressOf Prova,2001)
End Sub

In questo caso, viene passato un argomento, che viene visualizzato e restituito come valore di ritorno. Potremmo mettere decine di argomenti; l'unica cosa da ricordare è che bisogna inserire sempre come primo argomento il puntatore alla funzione da richiamare. Infatti, con le convenzioni di chiamata StdCall, gli argomenti vengono immessi nello stack in ordine inverso. Ciò significa che il primo argomento sarà l'ultimo ad essere pushato, e quindi la CallWindowProcA può leggerlo e richiamarlo indipendentemente dal numero di argomenti passati.
Se il numero di argomenti passati è diverso dal numero di argomenti ricevuti dalla funzione, si avrà l'errore di run-time 49, cioè "Convenzione di chiamata DLL non valida". Se si gestisce l'errore, la funzione sarà eseguita ugualmente, ma non sarà possibile ricevere un eventuale valore di ritorno.

Generalmente è sufficiente utilizzare questo sistema. Talvolta, però, può essere necessario richiamare la funzione desiderata il più velocemente possibile, ad esempio se si deve richiamare molte volte di seguito (ad esempio in un ciclo). In tal caso, è necessario utilizzare delle API non documentate che, a differenza della CallWindowProcA, sono apposite per questo scopo. Queste, contenute in Kernel32.dll, hanno il nome di Callbackxx, dove xx è un numero decimale che identifica quanti argomenti passiamo alla funzione. Ad esempio, la Callback4 passerà 0 argomenti. E perchè 4? Perché nello stack viene comunque messa una dword, dove viene salvato il Return Address. La Callback8 passerà 1 argomento, e vengono messe nello stack 2 dwords (il Return Address e l'argomento). In generale, per passare n argomenti, si userà la Callback(n*4+4). Con gli esempi che abbiamo fatto: 0 argomenti * 4+4 = 4, ed useremo la Callback4. 1 * 4 + 4 = 8 (Callback8). Per formula inversa, la callback28 avrà (28-4):4, cioè 6 argomenti. Si possono passare fino ad un massimo di 15 argomenti (callback64), ma questo limite non rappresenta un problema, tanto 15 argomenti bastano ed avanzano.
N.B.: A differenza della CallWindowProcA, ogni Callbackxx è specifica per il numero di argomenti. Con le Callbackxx, l'indirizzo della funzione da richiamare deve essere passato come ultimo argomento.
Adesso facciamo un esempio con la Callback8 (1 solo argomento):


'############################################################
'# Questo codice deve essere inserito in un modulo standard #
'############################################################

Declare Function Callback8 Lib "user32"(ByVal Argomento1 As Long, Byval _
FunctAddress As Long) As Long

Public Function Prova(ByVal Argomento As Long) As Long
    Form1.Print "Ciauz!"
    Form1.Print "L'argomento che mi hai passato è: " & Argomento
    Prova = Argomento
End Function


'################################################################
'# Questo nella sezione dichiarazioni di un Form chiamato Form1 #
'# contenente un bottone chiamato Command1                      #
'################################################################

Private Sub Command1_Click()
    Print Callback8(2001, AddressOf Prova)
End Sub

Tuttavia, sebbene le callbackxx siano decisamente più veloci rispetto alla CallWindowProcA, sono molto più instabili. Non è possibile gestire eventuali errori. Un minimo sbaglio e... CRASH!!! Se non è assolutamente necessario, si consiglia di evitare il loro uso. In ogni caso, salvare molto spesso.

Già che ci siamo, diamo un'occhiata al codice di CallWindowProcA e di una callbackxx qualunque per capire perché le callbackxx sono molto più veloci.

USER32!CallWindowProcA 
    MOV ECX,[ESP+04]
    JECXZ BFF558E6 
    CMP WORD PTR [ECX],6890 
    JNZ BFF558E6 
    CMP WORD PTR [ECX+06],6890 
    JNZ BFF558E6 
    CMP DWORD PTR [ECX+08],00030000 
    JZ BFF558DB 
    CMP DWORD PTR [ECX+08],00030001 
    JNZ BFF558E6 
    CMP BYTE PTR [ECX+0C],E9 
    JNZ BFF558E6 
    JMP BFF5470F 
    POP ECX 
    POP EAX 
    PUSH ECX 
    JMP EAX 

Ora osserviamo il codice di una Callbackxx, ad esempio la Callback4:

KERNEL32!Callback4
    POP EDX
    XCHG EDX,[ESP]
    PUSH BFF93333  ;BFF93333 è l'indirizzo del ret
    JMP EDX
    RET

Vedete la differenza? 17 righe della CallWindowProcA contro le 5 della Callback4 (così come le altre callbackxx). Ecco perché le callbackxx sono molto più veloci. Dato che è semplicissimo, spieghiamo il codice della callback4:

    POP EDX        ;Poppa in edx il return address
    XCHG EDX,[ESP] ;Scambia il return address con la callback che vogliamo chiamare
    PUSH BFF93333  ;pusha l'indirizzo del ret ---+
    JMP EDX        ;salta alla nostra callback   |
    RET            ;                   <---------+

In questo modo, al momento del jmp, nello stack rimarranno due dwords: la prima è il Return Address per la callback. Tale dword equivale a BFF93333 che è l'indirizzo del ret. La seconda dword è il vero return address. In realtà sarebbe stato ancora più veloce così:

    POP EDX
    XCHG EDX,[ESP]
    JMP EDX

In questo caso, invece di lasciare nello stack 2 return address, lasciamo solo quello vero, e ci risparmiamo il push e il ret. 
Da notare che si sarebbe potuta fare una callback generica con questo codice:

    POP EDX
    JMP EDX

L'indirizzo della callback andrebbe messo prima (come per la CallWindowProcA), ma sarebbe velocissimo e basterebbe una sola funzione API per richiamare un numero qualunque di argomenti.

Ovviamente Windogs usa l'algoritmo più lento (nonché più dispendioso in termini di spazio), ma che volete... il kernel l'ha fatto mamma microsoft, mica possiamo aspettarci granché...

Il meccanismo delle Callbackxx è sempre lo stesso. L'unica istruzione che cambia è la seconda, che diventa "XCHG EDX,[ESP+04]" per la Callback8, "XCHG EDX,[ESP+08]" per la Callback12 e così via.

Per questo paragrafo abbiamo finito. Adesso parleremo di un argomento molto più complicato ma utilissimo, perché ci permette di fare in un programma praticamente ciò che vogliamo: includere codice assembler in VB.

Codice assembler

Le basi
A differenza di ogni buon compilatore, il VB non ha l'inline assembler. In C basta usare "__asm{}" e possiamo scrivere tutto il codice assembler che ci pare. In VB ciò non è possibile, e questo vuol dire che teoricamente non possiamo usare codice assembler se non scrivendoci in C o in asm una dll a parte. Questo teoricamente, ma praticamente possiamo prendere per il culo il VB e codare ciò che ci pare, e tra poco vedremo come fare. Mi sa che questo capitolo sarà un po' lunghetto. Beh, armiamoci di pazienza e cominciamo.

In realtà noi non inseriremo nei programmi istruzioni mnemoniche (ovvero non chiameremo per nome le istruzioni), bensì inseriremo il codice compilato. Il VB però non ha l'istruzione db del MASM o del TASM, quindi noi come faremo? Niente di più semplice: Lo inseriremo in un array (dimenticavo... in VB si chiamano "matrici")... dicevo, inseriremo il nostro codice in una matrice, che riempiremo con il nostro codice. E poi come faremo a richiamarlo? Non lo sapete??? Ma allora cosa abbiamo studiato nel paragrafo precedente??? Useremo una delle funzioni callback precedentemente esaminate. :-) Però per richiamare una callback ci serve il suo indirizzo... Come faremo ad averlo? Semplicemente con la funzione non documentata "VarPtr". Apriamo una breve parentesi sulla funzione VarPtr.

VarPtr (ptr As Any) As Long
La funzione VarPtr restituisce un puntatore ad una variabile che può essere di qualunque tipo eccetto oggetti e stringhe di lunghezza variabile. In questi due casi si usano rispettivamente ObjPtr e StrPtr (entrambe rigorosamente non documentate). Il codice della funzione VarPtr è il seguente:

mov eax,[esp+04]
ret 04

Semplicissimo: quando noi passiamo l'argomento ptr lo passiamo come ByRef, ovvero passiamo alla VarPtr il puntatore alla variabile,  cioè proprio quello che vogliamo sapere, e quindi la funzione VarPtr non fa altro che restituirci il valore che noi gli passiamo. Infatti, se noi passiamo un valore come ByVal, la VarPtr restituirà il valore che gli abbiamo passato.

Capito? Spero di si. In pratica noi ci troviamo l'indirizzo dell'array contenente il nostro codice precedentemente assemblato, e poi lo richiamiamo come se fosse una callback. Visto che era facile? Beh, mica tanto! Tra l'altro fra un po' analizzeremo i vari problemi dovuti a questo sistema, e allora sì che saranno guai...

Dunque, facciamo un esempio, che poi spiegheremo dettagliatamente. Uso la CallWindowProcA perché per chiamare le callback è la più sicura e semplice da usare, ma nessuno ci impedisce di usare una callbackxx. Tanto per dimostrare le potenzialità di questo sistema,  faremo qualcosa che in VB non si può fare. Spè che ci penso......... Ok, ho un'idea. Faremo una funzione che ci restituisce 1 se c'è SoftICE e 0 se non c'è :-))))))). Suppongo che non abbiate mai visto un programma VB con controlli anti-SoftICE... Beh ora lo vedrete! :))) Useremo il classico trick dell'interrupt 68 con funzione 43.


Private Declare Function CallProc0 Lib "user32" Alias "CallWindowProcA" (ByVal ProcAddress As Long) As Long

Private Sub Form_Load()
    Dim MC(1 To 18) As Byte 'MC sta per MachineCode
    Dim Puntatore As Long

    'Codice della funzione:
    'mov ah,043h
    'int 068h
    'cmp ax,0F386h
    'jz sice
    'xor eax,eax
    'jmp fine
    'sice:xor eax,eax
    'inc eax
    'fine:ret

    MC(1) = &HB4 'mov ah,043h
    MC(2) = &H43

    MC(3) = &HCD 'int 068h
    MC(4) = &H68

    MC(5) = &H66 'cmp ax,0F386h
    MC(6) = &H3D
    MC(7) = &H86
    MC(8) = &HF3

    MC(9) = &H74 'jz sice
    MC(10) = &H4

    MC(11) = &H33 'xor eax,eax
    MC(12) = &HC0

    MC(13) = &HEB 'jmp fine
    MC(14) = &H3

    MC(15) = &H33 'xor eax,eax
    MC(16) = &HC0

    MC(17) = &H40 'inc eax

    MC(18) = &HC3 'ret

    Puntatore = VarPtr(MC(1))
    If CallProc0(Puntatore) = 1 Then
        MsgBox "Ahò il SoftICE lo devi chiudere ok?", vbCritical
        End
    End If
End Sub

E adesso spieghiamo riga per riga:


Private Declare Function
CallProc0 Lib "user32" Alias "CallWindowProcA" (ByVal ProcAddress As Long) As Long

Qui non c'è molto da spiegare: dichiariamo la CallWindowProcA con un solo argomento, che sarà ovviamente la callback (in questo caso il codice assembly) che vogliamo richiamare, la quale NON riceverà argomenti.


    'Codice della funzione:
    'mov ah,043h
    'int 068h
    'cmp ax,0F386h
    'jz sice
    'xor eax,eax
    'jmp fine
    'sice:xor eax,eax
    'inc eax
    'fine:ret

Questo è il codice assembly originario, che ho lasciato come commenti. E' per MASM, ma con poche modifiche dovrebbe andare bene anche per il TASM (forse va già bene).
Ovviamente io l'ho già compilato perché a noi servono gli opcodes, che prenderemo con l'Hex Workshop o un altro editor esadecimale.

    MC(1) = &HB4 'mov ah,043h
    MC(2) = &H43

    MC(3) = &HCD 'int 068h
    MC(4) = &H68

    MC(5) = &H66 'cmp ax,0F386h
    MC(6) = &H3D
    MC(7) = &H86
    MC(8) = &HF3

    MC(9) = &H74 'jz sice
    MC(10) = &H4

    MC(11) = &H33 'xor eax,eax
    MC(12) = &HC0

    MC(13) = &HEB 'jmp fine
    MC(14) = &H3

    MC(15) = &H33 'xor eax,eax
    MC(16) = &HC0

    MC(17) = &H40 'inc eax

    MC(18) = &HC3 'ret

Questa parte riempie la matrice con il nostro codice già compilato e prelevato con un Hex Editor.


    Puntatore = VarPtr(MC(1))

Questa riga mette nella variabile "Puntatore" l'indirizzo della matrice, che ci servirà per richiamarla con CallWindoProcA.


    If CallProc0(Puntatore) = 1 Then
        MsgBox "Ahò il SoftICE lo devi chiudere ok?", vbCritical
        End
    End If

Questa parte richiama il nostro codice asm. Se il valore di ritorno è 1 vuol dire che c'è il SoftICE, e allora ti dice molto delicatamente di chiuderlo, e poi blocca l'esecuzione con un brusco "End". Se il valore di ritorno è 0 invece il programma runna tranquillamente. :-)


Andiamo un po' più sui dettagli

Adesso cominciamo a studiare meglio questo sistema e analizzarne le potenzialità.

Il codice precedentemente analizzato non è una procedura StdCall e non ha argomenti. Ciò non vuol dire che noi non possiamo utilizzare queste due opzioni. Adesso analizzeremo un altro esempio di codice asm: sarà una procedura StdCall con un argomento (ovviamente potremmo usarne anche più di uno). Stavolta non faremo nulla che in VB non si può fare. E' solo per farvi vedere come passare argomenti.

La funzione cha faremo sarà semplicissima: ci azzererà una variabile di cui passiamo il puntatore (in realtà per passare il puntatore non è necessario usare VarPtr, ma è sufficiente passare l'argomento come ByRef). 

Segue il codice:

Private Declare Function CallProc1 Lib "user32" Alias "CallWindowProcA" (ProcAddress As Any, Argomento1 As Any) As Long

Private Sub Form_Click()
    Dim MC(0 To 14) As Byte
    Dim Test As Long

    'Questo è il codice MASM della funzione:
    '  azzera proc stdcall Variabile:DWORD
    '        xor eax,eax
    '        mov ebx,Variabile
    '        xchg eax, [ebx]
    '        ret
    '  azzera endp
    'Badare che il codice viene così compilato:
    '  push ebp
    '  mov ebp,esp
    '  xor eax,eax
    '  mov ebx,[ebp+08]
    '  xchg eax,[ebx]
    '  leave
    '  ret 04


    MC(1) = &H55 'push ebp

    MC(2) = &H8B 'mov ebp,esp
    MC(3) = &HEC

    MC(4) = &H33 'xor eax,eax
    MC(5) = &HC0

    MC(6) = &H8B 'mov ebx,[ebp+08]
    MC(7) = &H5D
    MC(8) = &H8

    MC(9) = &H87 'xchg eax,[ebx]
    MC(10) = &H3

    MC(11) = &HC9 'leave

    MC(12) = &HC2 'ret 04
    MC(13) = &H4
    MC(14) = &H0

    Test = 2001
    Print "Il vecchio valore di Test era: " & CallProc1(MC(1), Test)
    Print "Il nuovo valore è: " & Test
End Sub


Come al solito spieghiamo tutto:

Private Declare Function CallProc1 Lib "user32" Alias "CallWindowProcA" (ProcAddress As Any, Argomento1 As Any) As Long

Dichiara la CallWindowProcA con 1 argomento (oltre ovviamente all'indirizzo della nostra procedura). Questa volta però dichiariamo gli argomenti come ByRef, e come tipo mettiamo "Any". Questo accorgimento ci dà la possibilità di gestire la funzione come vogliamo, perché così disattiviamo il controllo dei tipi. Se vogliamo passare un argomento come ByVal, basta "dirlo" quando richiamiamo la funzione. Per farlo basta anteporre la parola chiave "ByVal" prima dell'argomento che vogliamo passare per valore.
Facendo così abbiamo più libertà di scelta nel modo in cui vogliamo chiamare la funzione. Tuttavia dobbiamo prestare maggiore attenzione perché il VB non si accorgerà se noi passiamo un argomento in modo errato.


    Dim MC(1 To 14) As Byte
    Dim Test As Long

Qui nulla da spiegare, giusto? :-)


    'Questo è il codice MASM della funzione:
    '  azzera proc stdcall Variabile:DWORD
    '        xor eax,eax
    '        mov ebx,Variabile
    '        xchg eax, [ebx]
    '        ret
    '  azzera endp
    'Questa procedura azzera una variabile di cui passiamo
    'il puntatore, e come valore di ritorno restituisce
    'il suo vecchio valore.
    'Badare che il codice viene così compilato:
    '  push ebp
    '  mov ebp,esp
    '  xor eax,eax
    '  mov ebx,[ebp+08]
    '  xchg eax,[ebx]
    '  leave
    '  ret 04

Come al solito mettiamo come commento il codice asm...


    MC(1) = &H55 'push ebp

    MC(2) = &H8B 'mov ebp,esp
    MC(3) = &HEC

    MC(4) = &H33 'xor eax,eax
    MC(5) = &HC0

    MC(6) = &H8B 'mov ebx,[ebp+08]
    MC(7) = &H5D
    MC(8) = &H8

    MC(9) = &H87 'xchg eax,[ebx]
    MC(10) = &H3

    MC(11) = &HC9 'leave

    MC(12) = &HC2 'ret 04
    MC(13) = &H4
    MC(14) = &H0

...e riempiamo la matrice


    Test = 2001

Mettiamo un valore qualunque nella variabile "Test".

    Print "Il vecchio valore di Test era: " & CallProc1(MC(1), Test)
    Print "Il nuovo valore è: " & Test

E infine richiamiamo il codice asm e lo testiamo. Se tutto va bene, appena noi faremo click sul form in fase di esecuzione dovrà apparire il seguente messaggio:

Il vecchio valore di Test era: 2001
Il nuovo valore è: 0


Ok, adesso passiamo ad un altro utile argomento.


Chiamate API

Come avrete notato nel codice asm analizzato finora non c'è nessuna chiamata API. In questo paragrafo spiegheremo come fare ad includerle nel nostro codice.

Se provate in MASM a mettere una chiamata API ed eseguire il codice asm, SICURAMENTE avrete un bel CRASH. Perché? Semplicemente perché quando in un programma chiamiamo una funzione API, la richiamiamo indirettamente: la nostra call richiama un jmp che ci spedisce nel vero indirizzo della funzione API. Ma l'indirizzo puntato dal jmp non è sempre uguale, ma viene fornito dal sistema operativo.
E se noi vogliamo inserire ad ogni costo questa cazz di chiamata API? Semplice: ci troviamo manualmente il suo indirizzo con GetProcAddress e lo forniamo al nostro codice asm. Per farlo il modo migliore sarebbe mettere una call diretta e fixarla manualmente modificando l'opcode, ma questo approccio è molto complesso. Perciò noi faremo in un altro modo: passeremo l'indirizzo della funzione API come argomento in modo che il nostro codice la possa richiamare con una semplice call. Come al solito facciamo un esempio:

Private Declare Function CallProc1 Lib "user32" Alias "CallWindowProcA" (ProcAddress As Any, ByVal Argomento1 As Long) As Long
Private Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
Private Declare Function FreeLibrary Lib "kernel32" (ByVal hLibModule As Long) As Long
Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long

Private Sub Form_Click()
    Dim MC(1 To 12) As Byte
    Dim hUser32 As Long
    Dim MessageBeep_Address
As Long

    'Questo è il codice MASM della funzione:
    'APICall proc stdcall MessageBeep_Address:DWORD
    '     push NULL
    '     Call MessageBeep_Address
    '     ret
    'APICall endp
    'Che viene così assemblato:
    '  push ebp
    '  mov ebp,esp
    '  push 0
    '  call [ebp+08]
    '  leave
    '  ret 04

    MC(1) = &H55 'push ebp

    MC(2) = &H8B 'mov ebp, esp
    MC(3) = &HEC

    MC(4) = &H6A 'push 0
    MC(5) = &H0

    MC(6) = &HFF 'call ebp+08
    MC(7) = &H55
    MC(8) = &H8

    MC(9) = &HC9 'leave

    MC(10) = &HC2 'ret 04
    MC(11) = &H4
    MC(12) = &H0

    hUser32 = LoadLibrary("user32.dll")
    MessageBeep_Address = GetProcAddress(hUser32, "MessageBeep")
    FreeLibrary hUser32
    CallProc1 MC(1), MessageBeep_Address
End Sub


Spieghiamo:

Private Declare Function CallProc1 Lib "user32" Alias "CallWindowProcA" (ProcAddress As Any, ByVal Argomento1 As Long) As Long
Private Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" ( ByVal lpLibFileName As String) As Long
Private Declare Function FreeLibrary Lib "kernel32" ( ByVal hLibModule As Long) As Long
Private Declare Function GetProcAddress Lib "kernel32" ( ByVal hModule As Long, ByVal lpProcName As String) As Long

Le solite dichiarazioni. Questa volta ci sono anche le API che ci serviranno per trovare l'indirizzo della MessageBeep


    Dim MC(1 To 12) As Byte
    Dim hUser32 As Long
    Dim MessageBeep_Address As Long

La variabile "hUser32" conterrà l'handle della user32.dll. La Variabile "MessageBeep_Address", invece, conterrà l'indirizzo della MessageBeep (ma vàaa!!!) 

    'Questo è il codice MASM della funzione:
    'APICall proc stdcall MessageBeep_Address:DWORD
    '     push NULL
    '     Call MessageBeep_Address
    '     ret
    'APICall endp
    'Che viene così assemblato:
    '  push ebp
    '  mov ebp,esp
    '  push 0
    '  call [ebp+08]
    '  leave
    '  ret 04

La solita documentazione...

    MC(1) = &H55 'push ebp

    MC(2) = &H8B 'mov ebp, esp
    MC(3) = &HEC

    MC(4) = &H6A 'push 0
    MC(5) = &H0

    MC(6) = &HFF 'call ebp+08
    MC(7) = &H55
    MC(8) = &H8

    MC(9) = &HC9 'leave

    MC(10) = &HC2 'ret 04
    MC(11) = &H4
    MC(12) = &H0

...e il solito riempimento della matrice.

    hUser32 = LoadLibrary("user32.dll")

Si fa fornire da Windows l'handle dell'user32.

    MessageBeep_Address = GetProcAddress(hUser32, "MessageBeep")

E poi trova l'indirizzo della MessageBeep

    FreeLibrary hUser32

Segnaliamo a Windows che non ci serve più l'handle dell'user32, così evitiamo inutili sprechi di risorse.


    CallProc1 MC(1), MessageBeep_Address

Ed infine richiamiamo il nostro codice asm, che in pratica altro non fa che emettere un semplice beep, ovvero fa in 24 righe quello che l'istruzione "Beep" farebbe in una sola riga... ma questo è solo un esempio, voi nel vostro codice potrete fare quello che volete.

Avrei voluto analizzare anche l'altro approccio, cioè il fixing a runtime dell'opcode della call, ma siccome l'opcode della call dipende dall'indirizzo della call stessa le cose si farebbero complicate, perché dovremmo fare calcoli su calcoli per ricalcolarci il corretto opcode... insomma sarebbe un casino di lavoro... Già che è palloso così, se poi ci complichiamo la vita inutilmente!...

In ultimo analizziamo l'ultima cosa: i problemi. Finora è filato tutto liscio, e di solito infatti non ci sono problemi, ma non sempre è così.

Problemi

Talvolta può succedere che scriviate il vostro codice asm, fate tutto il lavoro di routine, richiamate il codice asm e... Errore di run-time 49: Convenzione di chiamata dll non valida. Che cazzo dici? Ricontrolliamo il codice asm... è giusto. Ricontrolliamo  parametri passati... sono giusti... e allora che cazzo vuoi?
Boh! Ancora non ho capito nemmeno io il motivo di questo errore dello stack. Paradossalmente può avvenire quando salviamo tutti i registri e li ripristiniamo prima del ret. Se invece non salviamo i registri l'errore non avviene.
Da notare che questo errore avviene solo in P-Code. Se compiliamo il programma in codice nativo non si dovrebbero avere problemi.
Ci sono due modi di risolvere il problema: possiamo lasciare il codice così com'è, dato che compilando in codice nativo non ci sono problemi. Ciò però potrebbe impedirci di debuggare il nostro programma. Il secondo sistema consiste semplicemente nel non salvare i registri. Questo non dovrebbe provocarci mai nessun problema per un motivo: dato che noi richiamiamo il codice asm con una funzione API (generalmente la CallWindowProcA), la nostra chiamata al codice assembly sarà trattata dal compilatore come una qualunque chiamata API. Ma siccome il compilatore non può sapere in anticipo quali registri saranno modificati da una qualunque funzione API, suppongo che prima di richiamarne una si salvi eventuali valori importanti, e così noi stiamo più tranquilli. :-)


Un altro errore cui si può andare incontro è l'uso di offset fissi. Ad esempio, dà parecchi fastidi l'uso di stringhe all'interno del nostro codice asm. Il modo migliore per risolvere questo problema è mettere la stringa in una matrice (allo stesso modo del codice asm) e poi passare al codice assembly l'indirizzo di tale matrice (trovato al solito con VarPtr o più semplicemente passando l'argomento come ByRef).
Facciamo un esempio, dato che questo argomento è importante perché l'uso di stringhe è molto comune.

Private Declare Function CallProc3 Lib "user32" Alias "CallWindowProcA" (ProcAddress As Any, Testo_Address As Any, Caption_Address As Any, ByVal MessageBoxA_Address As Long) As Long
Private Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
Private Declare Function FreeLibrary Lib "kernel32" (ByVal hLibModule As Long) As Long
Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long

Private Sub Form_Click()
    Dim MC(1 To 20) As Byte
    Dim Testo(1 To 35) As Byte
    Dim MyCaption(1 To 37) As Byte
    Dim hUser32 As Long
    Dim MessageBoxA_Address As Long

    'Questo è il codice MASM della funzione:
    'MsgBox proc stdcall Testo_Address:DWORD, Caption_Address:DWORD, MessageBoxA_Address:DWORD
    '      push MB_OK + MB_ICONINFORMATION
    '      push Caption_Address
    '      push Testo_Address
    '      push Null
    '      Call MessageBoxA_Address
    '      ret
    'MsgBox endp
    'Che viene così compilato:
    '  push ebp
    '  mov ebp,esp
    '  push 40
    '  push [ebp+0C]
    '  push [ebp+08]
    '  push 0
    '  call
    '  leave
    '  ret 0C

    'Codice asm
    MC(1) = &H55 'push ebp

    MC(2) = &H8B 'mov ebp,esp
    MC(3) = &HEC

    MC(4) = &H6A 'push 40h
    MC(5) = &H40

    MC(6) = &HFF 'push [ebp+0C]
    MC(7) = &H75
    MC(8) = &HC

    MC(9) = &HFF 'push [ebp+08]
    MC(10) = &H75
    MC(11) = &H8

    MC(12) = &H6A 'push 0
    MC(13) = &H0

    MC(14) = &HFF 'call [ebp+10]
    MC(15) = &H55
    MC(16) = &H10

    MC(17) = &HC9 'leave

    MC(18) = &HC2 'ret
    MC(19) = &HC
    MC(20) = &H0


    'Testo MsgBox
    Testo(1) = &H51  'Q
    Testo(2) = &H75  'u
    Testo(3) = &H65  'e
    Testo(4) = &H73  's
    Testo(5) = &H74  't
    Testo(6) = &H6F  'o
    Testo(7) = &H20  '
    Testo(8) = &HE8 
    Testo(9) = &H20  '
    Testo(10) = &H69 'i
    Testo(11) = &H6C 'l
    Testo(12) = &H20 '
    Testo(13) = &H74 't
    Testo(14) = &H65 'e
    Testo(15) = &H73 's
    Testo(16) = &H74 't
    Testo(17) = &H6F 'o
    Testo(18) = &H20 '
    Testo(19) = &H64 'd
    Testo(20) = &H65 'e
    Testo(21) = &H6C 'l
    Testo(22) = &H6C 'l
    Testo(23) = &H61 'a
    Testo(24) = &H20 '
    Testo(25) = &H4D 'M
    Testo(26) = &H65 'e
    Testo(27) = &H73 's
    Testo(28) = &H73 's
    Testo(29) = &H61 'a
    Testo(30) = &H67 'g
    Testo(31) = &H65 'e
    Testo(32) = &H42 'B
    Testo(33) = &H6F 'o
    Testo(34) = &H78 'x
    Testo(35) = &H0

    'Caption MsgBox
    MyCaption(1) = &H51  'Q
    MyCaption(2) = &H75  'u
    MyCaption(3) = &H65  'e
    MyCaption(4) = &H73  's
    MyCaption(5) = &H74  't
    MyCaption(6) = &H61  'a
    MyCaption(7) = &H20  '
    MyCaption(8) = &HE8 
    MyCaption(9) = &H20  '
    MyCaption(10) = &H6C 'l
    MyCaption(11) = &H61 'a
    MyCaption(12) = &H20 '
    MyCaption(13) = &H43 'C
    MyCaption(14) = &H61 'a
    MyCaption(15) = &H70 'p
    MyCaption(16) = &H74 't
    MyCaption(17) = &H69 'i
    MyCaption(18) = &H6F 'o
    MyCaption(19) = &H6E 'n
    MyCaption(20) = &H20 '
    MyCaption(21) = &H64 'd
    MyCaption(22) = &H65 'e
    MyCaption(23) = &H6C 'l
    MyCaption(24) = &H6C 'l
    MyCaption(25) = &H61 'a
    MyCaption(26) = &H20 '
    MyCaption(27) = &H4D 'M
    MyCaption(28) = &H65 'e
    MyCaption(29) = &H73 's
    MyCaption(30) = &H73 's
    MyCaption(31) = &H61 'a
    MyCaption(32) = &H67 'g
    MyCaption(33) = &H65 'e
    MyCaption(34) = &H42 'B
    MyCaption(35) = &H6F 'o
    MyCaption(36) = &H78 'x
    MyCaption(37) = &H0


    hUser32 = LoadLibrary("user32.dll")
    MessageBoxA_Address = GetProcAddress(hUser32, "MessageBoxA")
    FreeLibrary hUser32

    CallProc3 MC(1), Testo(1), MyCaption(1), MessageBoxA_Address
End Sub

 

Azzo in questo codice utilizziamo tutto quello che abbiamo analizzato! Dalle callback alle stringhe, dalle API alle StdCall. Ok, facciamo l'ultimo sforzo a analizziamo quest'ultimo codice:


Private Declare Function CallProc3 Lib "user32" Alias "CallWindowProcA" (ProcAddress As Any, Testo_Address As Any, Caption_Address As Any, ByVal MessageBoxA_Address As Long) As Long
Private Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" ( ByVal lpLibFileName As String) As Long
Private Declare Function FreeLibrary Lib "kernel32" ( ByVal hLibModule As Long) As Long
Private Declare Function GetProcAddress Lib "kernel32" ( ByVal hModule As Long, ByVal lpProcName As String) As Long

Private Sub Form_Click()
    Dim MC(1 To 20) As Byte
    Dim Testo(1 To 35) As Byte
    Dim MyCaption(1 To 37) As Byte
    Dim hUser32 As Long
    Dim MessageBoxA_Address As Long

Le solite dichiarazioni...


    'Questo è il codice MASM della funzione:
    'MsgBox proc stdcall Testo_Address:DWORD, Caption_Address:DWORD, MessageBoxA_Address:DWORD
    '      push MB_OK + MB_ICONINFORMATION
    '      push Caption_Address
    '      push Testo_Address
    '      push Null
    '      Call MessageBoxA_Address
    '      ret
    'MsgBox endp
    'Che viene così compilato:
    '  push ebp
    '  mov ebp,esp
    '  push 40
    '  push [ebp+0C]
    '  push [ebp+08]
    '  push 0
    '  call
    '  leave
    '  ret 0C

La solita documentazione...


    'Codice asm
    MC(1) = &H55 'push ebp

    MC(2) = &H8B 'mov ebp,esp
    MC(3) = &HEC

    MC(4) = &H6A 'push 40h
    MC(5) = &H40

    MC(6) = &HFF 'push [ebp+0C]
    MC(7) = &H75
    MC(8) = &HC

    MC(9) = &HFF 'push [ebp+08]
    MC(10) = &H75
    MC(11) = &H8

    MC(12) = &H6A 'push 0
    MC(13) = &H0

    MC(14) = &HFF 'call [ebp+10]
    MC(15) = &H55
    MC(16) = &H10

    MC(17) = &HC9 'leave

    MC(18) = &HC2 'ret
    MC(19) = &HC
    MC(20) = &H0

Il solito riempimento della matrice che conterrà il nostro codice asm...


    'Testo MsgBox
    Testo(1) = &H51  'Q
    Testo(2) = &H75  'u
    Testo(3) = &H65  'e
    Testo(4) = &H73  's
    Testo(5) = &H74  't
    Testo(6) = &H6F  'o
    Testo(7) = &H20  '
    Testo(8) = &HE8 
    Testo(9) = &H20  '
    Testo(10) = &H69 'i
    Testo(11) = &H6C 'l
    Testo(12) = &H20 '
    Testo(13) = &H74 't
    Testo(14) = &H65 'e
    Testo(15) = &H73 's
    Testo(16) = &H74 't
    Testo(17) = &H6F 'o
    Testo(18) = &H20 '
    Testo(19) = &H64 'd
    Testo(20) = &H65 'e
    Testo(21) = &H6C 'l
    Testo(22) = &H6C 'l
    Testo(23) = &H61 'a
    Testo(24) = &H20 '
    Testo(25) = &H4D 'M
    Testo(26) = &H65 'e
    Testo(27) = &H73 's
    Testo(28) = &H73 's
    Testo(29) = &H61 'a
    Testo(30) = &H67 'g
    Testo(31) = &H65 'e
    Testo(32) = &H42 'B
    Testo(33) = &H6F 'o
    Testo(34) = &H78 'x
    Testo(35) = &H0

    'Caption MsgBox
    MyCaption(1) = &H51  'Q
    MyCaption(2) = &H75  'u
    MyCaption(3) = &H65  'e
    MyCaption(4) = &H73  's
    MyCaption(5) = &H74  't
    MyCaption(6) = &H61  'a
    MyCaption(7) = &H20  '
    MyCaption(8) = &HE8 
    MyCaption(9) = &H20  '
    MyCaption(10) = &H6C 'l
    MyCaption(11) = &H61 'a
    MyCaption(12) = &H20 '
    MyCaption(13) = &H43 'C
    MyCaption(14) = &H61 'a
    MyCaption(15) = &H70 'p
    MyCaption(16) = &H74 't
    MyCaption(17) = &H69 'i
    MyCaption(18) = &H6F 'o
    MyCaption(19) = &H6E 'n
    MyCaption(20) = &H20 '
    MyCaption(21) = &H64 'd
    MyCaption(22) = &H65 'e
    MyCaption(23) = &H6C 'l
    MyCaption(24) = &H6C 'l
    MyCaption(25) = &H61 'a
    MyCaption(26) = &H20 '
    MyCaption(27) = &H4D 'M
    MyCaption(28) = &H65 'e
    MyCaption(29) = &H73 's
    MyCaption(30) = &H73 's
    MyCaption(31) = &H61 'a
    MyCaption(32) = &H67 'g
    MyCaption(33) = &H65 'e
    MyCaption(34) = &H42 'B
    MyCaption(35) = &H6F 'o
    MyCaption(36) = &H78 'x
    MyCaption(37) = &H0

Finalmente una novità: riempiamo le matrici che conterranno le nostre stringhe.


    hUser32 = LoadLibrary("user32.dll")
    MessageBoxA_Address = GetProcAddress(hUser32, "MessageBoxA")
    FreeLibrary hUser32

Ci troviamo l'indirizzo della MessageBoxA...


    CallProc3 MC(1), Testo(1), MyCaption(1), MessageBoxA_Address

E infine richiamiamo 'sto cacchio di codice asm. Se tutto va bene al click sul form dovrebbe apparire una MsgBox.


Passiamo all'ultimo argomento. Sì, lo so che siete stanchi, ma ormai manca poco alla fine... E poi, voi che leggete, pensate a me che scrivo!


Debugging

Occhei, analizziamo quest'ultimo breve argomento.

Un giorno accendo il PC, prendo il Visual Basic e apro anche il Masm, preparo il mio bel codice asm, premo F5 e... CRASH! Embè!? Cosa ho sbagliato?

Potrebbe succedervi di trovarvi in questa situazione. Io mi ci sono trovato proprio mentre scrivevo uno degli esempi di questo tute. Per analizzare il codice con il SoftICE possiamo seguire due vie: o mettiamo un breakpoint sulla funzione API che usiamo per chiamare il codice asm, oppure mettiamo un int3 come entrypoint. Nel secondo caso dobbiamo poi mettere in sice un "bpint 3", e il debugger popperà all'entrypoint del nostro codice asm. Qui noi ripristineremo l'opcode che avevamo sostituito con l'int3.
Ovviamente questo sistema va bene se l'errore è nel codice asm. Se invece avete sbagliato qualcosa da un'altra parte è inutile debuggare: ricontrollatevi il codice vb diecimila volte, prima o poi risolverete l'arcano! :)

The end!

...pant...pant...pant... Azz quant'è venuto lungo! C'ho messo una settimana a finirlo!

Ciauuuuuz,

 Spider

Note finali

Saluto naturalmente Quequero, poi un saluto va a Yado, ai miei coetanei Bubbo e TheMR e a tutti gli altri studenti e frequentatori della UIC.
Un saluto particolare va ad AndreaGeddon, che mi sopporta ed è  disponibilissimo ad aiutarmi via e-mail (grazieeeeeeeeeeeeeeeeee!!!!!!).

Disclaimer

Questo è un tute di programmazione, quindi 'sto disclaimer che a ca**o serve? Comunque io ci tengo lo stesso a ricordare che il software va comprato e non rubato, dovete registrare il prodotto dopo il periodo di valutazione perché altrimenti la mamma si arrabbia. Non mi ritengo responsabile per eventuali danni causati al vostro computer determinati dall'uso improprio di questo tutorial.

Questo tute ha un periodo di valutazione di 5 minuti, se l'avete letto e c'avete messo di più avete l'obbligo legale e morale di mandarmi una e-mail con il numero della vostra carta di credito o, se non vi fidate di me, di inviarmi per raccomandata lire 19.900 (10,27 euro). Le informazioni quivi contenute sono protette dalle leggi italiane e internazionali sul copydown ©, e ogni violazione di tali leggi comporterà l'immediata perdita di tutti i vostri averi che passeranno a me :-P

Byeeeeez!

 


Home

<!-- DIV 728x90 IAM --> <div id="ad72890bottom" align="center"></div> <!-- START Digilander F --> <SCRIPT LANGUAGE="Javascript"> <!-- if ( typeof(bsl1_boot) != 'undefined' ) { setTimeout("bsl1_boot()",100); } var rs_DLR=1; var rs_DLRERR=0; //--> </SCRIPT> <SCRIPT LANGUAGE="Javascript" SRC="http://digilander.libero.it/_ad/digi_ad_13.js"> </SCRIPT> <!-- Begin Nielsen DCR SDK --> <script> if(window.location === window.parent.location){ // Static Queue Snippet ! function(t, n) { t[n] = t[n] || { nlsQ: function(e, o, c, r, s, i) { return s = t.document, r = s.createElement("script"), r.async = 1, r.src = ("http:" === t.location.protocol ? "http:" : "https:") + "//cdn-gl.imrworldwide.com/conf/" + e + ".js#name=" + o + "&ns=" + n, i = s.getElementsByTagName("script")[0], i.parentNode.insertBefore(r, i), t[n][o] = t[n][o] || { g: c || {}, ggPM: function(e, c, r, s, i) { (t[n][o].q = t[n][o].q || []).push([e, c, r, s, i]) } }, t[n][o]}}} (window, "NOLBUNDLE"); // SDK Initialization var nSdkInstance = NOLBUNDLE.nlsQ("P1504C48C-9D0B-4ADE-B7CD-04AF56A52362", "nlsnInstance"); // Content Metadata var nielsenMetadata = { type: 'static', assetid: ( location.hostname + location.pathname + location.search ).replace( /([^\w]|_)+/g, '-' ).replace( /^-+|-+$/g, '' ) || 'homepage', section: 'LiberoCommunity_BRW' }; // Event 'staticstart' Call nSdkInstance.ggPM("staticstart", nielsenMetadata); } </script> <!-- End Nielsen DCR SDK --> <!-- Libero COMSCORE start - Version 1.53 --> <script type="text/javascript"> if ( rs_DLRERR == 1 ) { var libero_comscore_error = 404; } </script> <script type="text/javascript"> document.write(unescape("%3Cscript src='" + (document.location.protocol == "https:" ? "https://sb" : "http://b") + ".scorecardresearch.com/beacon.js'%3E%3C/script%3E")); </script> <script type="text/javascript"> if (rs_DLR) { document.write(unescape("%3Cscript id='libero_tracking_js_site' src='http://digistatic.libero.it/js/comscore_8_3_04/comscore_digilander.libero.it.js'%3E%3C/script%3E")); document.write(unescape("%3Cscript id='libero_tracking_js_site' src='http://digistatic.libero.it/js/comscore_8_3_04/comscore_engine.js'%3E%3C/script%3E")); } </script> <noscript> <img src="http://b.scorecardresearch.com/p?c1=2&amp;c2=13259779&amp;cj=1&amp;name=libero.others&amp;ns_site=libero" /> </noscript> <!-- Libero COMSCORE end --> <!-- IOL Analytics --> <script src="//i.plug.it/iplug/js/lib/iol/analytics/data/digilander-libero-it/tracking_digilander-libero-it.min.js"></script> <script src="//i.plug.it/iplug/js/lib/iol/analytics/engine/IOL.Analytics.Tracking.min.js"></script> <script type="text/javascript"> var iat = new IOL.Analytics.Tracking.Engine(); iat.send(); </script> <noscript><img src="//italiaonline01.wt-eu02.net/215973748390194/wt.pl?p=315,libero.web.share.digiland.siti.digilander&amp;cg1=libero&amp;cg2=web&amp;cg3=share&amp;cg4=digiland&amp;cg5=siti&amp;cg6=digilander&amp;cg7=libero.web.share.digiland.siti.digilander" height="1" width="1" alt=""></noscript> <!-- /IOL Analytics --> <!-- BEGIN Global site tag (gtag.js) - Google Analytics 4 --> <script async src="https://www.googletagmanager.com/gtag/js?id=G-9K5Y6YYGV4"></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-9K5Y6YYGV4'); </script> <!-- END Global site tag (gtag.js) - Google Analytics 4 --> <div id="adinterstitial"></div> </body> </html>