Guida a sl scopo illustrativo,se lo volete fare,fate pure,io non tengo nessuna reponsabilità di che fate!
Introduction
In questo tutorial, andremo a creare una finestra avente forma e sfondo da noi deciso (cioè il cosiddetto "skinning" della finestra)
Prima di tutto creeremo una generifa finestra in Win32, poi ci staccheremo un attimo dalla programmazione dedicandoci alla grafica
ed infine metteremo tutto insieme, aggiungendo anche qualche pulsantino per rendere la nostra finestra simile a quelle dei crackme/keygen delle varie crew in circolazione
Tools
Notepad++ o un qualcunque altro editor di testo
Ci servirà per scrivere il codice
FASM
L'assembler che uso e che consiglio. Naturalmente potete usarne altri, ma a quel punto la traduzione delle varie direttive spetta a voi.
Un programma per disegnare
QUi la scelta è ampia, dal paint di windows a Photoshop e derivati. Io uso di solito la suite di CorelDraw, ma come ho detto la scelta è soggettiva, ne va bene qualunque possa salvare in bitmap puro (.BMP)
Creatore Regioni
Un resource compiler
Ovvero il programmino che da resource script (.RC) ci dà la versione compilata e pronta al linking (.RES).
Potete scegliere quello che vi aggrada di più, se non sapete quale prendere, usate questo: GoRC
Guida API
Conoscienza dell'assembly (e del FASM magari)
Conoscienza della programmazione Win32
Essay
PARTE [1] -> Creazione della finestra generica
Bene Iniziamo:
Allora, apriamo il nostro editor di testo e iniziamo col creare una generica finestra in win32 (Nel sito ci sono tutorial a riguardo, altrimenti potete vedere anche il sito di Iczelion)
Quindi:
Format PE GUI 4.0
entry start
Con questo, indicheremo al linker interno del fasm il formato di file in output (Win32) e che l'EntryPoiny del programma sarà all'etichetta start
include "C:\FASM\INCLUDE\WIN32A.INC"
Includiamo il file win32a.inc che contiene alcune macro utili per velocizzare la scrittura del codice e per evitare di scriversi i descrittori dell'IT a mano (cosa che il fasm permette di fare
)
section "import" import data readable
library kernel32,"kernel32.dll",\
user32,"user32.dll",\
gdi32,"gdi32.dll"
include "C:\FASM\INCLUDE\API\kernel32.inc"
include "C:\FASM\INCLUDE\API\user32.inc"
include "C:\FASM\INCLUDE\API\gdi32.inc"
Ecco a voi la prima sezione che scriveremo: la cosiddetta sezione .idata (che per comodità ho chiamato "import"), alla quale ho attivato il flag di lettura
Per informazioni su l'uso di library e import vi rimando al sito del FASM.
Scriviamo ora una sezione per i dati:
section "dativari" data readable writeable
Classname db "SkinClass",0
wc WNDCLASSEX sizeof.WNDCLASSEX,\
CS_BYTEALIGNWINDOW + CS_BYTEALIGNCLIENT,\
WndProc,0,0,\
0,\
0,0,\
0,\
0,Classname,0
msg MSG
hInst dd 0
hwnd dd 0
himg dd 0
Come vedete, la sezione si chiama dativari, contiene dati generici e è leggibile e scrivibile.
Naturalmente c'è la struttura WNDCLASSEX inizializzata con i membri di cui abbiamo già il valore, una struttura MSG per la gestione dei messaggi e un pò di dword per handle vari.
Ora cominciamo a esaminare il codice della prima parte:
section "codice" code readable executable
start:
push 0
call [GetModuleHandle]
mov dword [hInst],eax
mov dword [wc.hInstance],eax
push IDC_CROSS
push 0
call [LoadCursor]
mov dword [wc.hCursor],eax
push IDI_APPLICATION
push 0
call [LoadIcon]
mov dword [wc.hIcon],eax
mov dword [wc.hIconSm],eax
mov dword [wc.hbrBackground],COLOR_WINDOW
push wc
call [RegisterClassEx]
or eax,eax
jz endofall
Dunque, con questo primo frammento, creiamo la sezione per il codice (chiamata appunto "codice") con permessi per la lettura ed esecuzione.
Nel codice vero e proprio, chiamiamo GetModuleHandle per ottenere l'instance handle del nostro programma e depositiamo tale valore sia nella variabile hInst che nella struttura WNDCLASSEX
Quindi, Riempiamo gli altri membri di WNDCLASSEX con LoadIcon, LoadCursor e sistemando temporaneamente (visto che successivamente lo cambieremo) il valore COLOR_WINDOW al membro hbrBackground
Riempita così la struttura WNDCLASSEX, la passiamo a RegisterClassEx controllando poi che sia tutto ok (risultato diverso da 0)
PS. Ricordatevi che il FASM, quando incontra il nome di un dato senza parentesi quadre o altre forme esplicite, intende l'indirizzo di quella variabile
Proseguiamo col codice:
push 0
push dword [hInst]
push 0
push 0
push 250
push 250
push 300
push 300
push WS_VISIBLE or WS_SYSMENU or WS_CAPTION or WS_BORDER
push Classname
push Classname
push 0
call [CreateWindowEx]
or eax,eax
jz endofall
mov dword [hwnd],eax
msgloop:
push 0
push 0
push 0
push msg
call [GetMessage]
or eax,eax
jz endofall
push msg
call [TranslateMessage]
push msg
call [DispatchMessage]
jmp msgloop
endofall:
push 0
call [ExitProcess]
nop
Avendo registrato la classe, adesso possiamo creare la finestra, e lo facciamo con CreateWindowEx e memorizzando l'handle dentro la variabile hwnd
Notate che come stile di finestra, per ora, abbiamo importato WS_VISIBLE or WS_SYSMENU or WS_CAPTION or WS_BORDER. Anche questo successivamente andrà cambiato
Dopo la creazione della finestra e la verifica di successo (or eax,eax è uno dei mille metodi per verificare se eax==0) scriviamo il generico ciclo di gestione di messaggi. (Se siete estranei a ciò, vi rimando ad altri tutorial
)
Ora è tutto pronto per la WndProc. Eccola qui di seguito:
WndProc:
label .hwnd at ebp+8
label .msg at ebp+0Ch
label .wParam at ebp+10h
label .lParam at ebp+14h
enter 0,0
mov eax,dword [.msg]
cmp eax,WM_DESTROY
jnz @f
push 0
call [PostQuitMessage]
jmp .end
@@:
push dword [.lParam]
push dword [.wParam]
push dword [.msg]
push dword [.hwnd]
call [DefWindowProc]
leave
ret 10h
.end:
xor eax,eax
leave
ret 10h
A parte la dichiarazione delle label che è un mio tipico modo di fare (potete anche usare proc....endp per dichiarare funzioni e parametri), qui gestiamo il solo messaggio WM_DESTROY.
A questo punto la prima parte è finita. Assemblate il tutto, eseguite e avrete una piccola finestra semplicissima.
PARTE [2] -> Grafica!
Accantonate temporaneamente l'editor e prendete il vostro programma di grafica.
Quello che dobbiamo fare è creare un immagine bitmap delle dimenzioni della nostra finestra. La cosa importante è che L'immagine deve avere come sfondo un colore che differisca il più possibile dall'immagine stessa. Cioè se volessimo fare qualcosa sul verde-nero, consideriamo l'ipotesi di mettere come sfondo un colore quale il rosso o il magenta come sfondo.
In ogni caso, se volete un consiglio, per evitare di modificare l'immagine e ricompilare 500 volte, lo sfondo consiglio di farlo di colori come il bianco o il nero, cosicchè i bordi dell'imamgine, se sfumati, non presenteranno inesteticità evidenti....vi invito comunque a fare prove e vedere il colore più adatto per ogni situazione
Altra cosa che come detto, dobbiamo decidere, è la dimenzione. Io in questo tutorial utilizzerò le dimenzioni 600x600, ma potete scegliere qualunque dimenzione, l'importante è che utilizziate la stessa sia per la grandezza della finestra in CreateWindowEx sia per quanto riguarda l'immagine.
QUindi, ecco di seguito quello che ho fatto io, le dimenzioni sono come ho detto, 600 pixel per 600 pixel
Image:Skin k0h.jpg
Come vedere, lo sfondo è nero, mentre i colori usati nell'immagine spaziano dal bianco al blu.
Assicuratevi di aver salvato in bitmap, quidni prendete il Region Creator, caricate l'immagine e, scrivete nei campi Red, Green e Blue i valori RGB del colore usato come sfondo (in decimale). (Se non sapete dove prenderli, buttate questo tutorial e andate a funghi
...oppure studiatevi il vostro programma di grafica )
Se tutto vi è filato liscio a questo punto avremo il file .rgn (al quale farò riferimento col nome region).
Parte [3] -> File delle Risorse
Ebbene si, dobbiamo inserire nell'eseguibile sia il file bitmap che la region, e per farlo ci serviremo di un classico resource script (.rc)
Volendo potremo prendere qualche editor specifico per il lavoro che faremo tra poco, ma per 2 righe contante non vedo vantaggi
Dunque, create un nuovo file, dategli l'estenzione .rc e apritelo col vostro editor e scriviamo le seguenti righe: (sostituite i nomi dei file bitmap e region con i vostri)
401 BITMAP "skin.bmp"
REGION RGN "skin.rgn"
Se conoscete un minimo di script RC, come dovreste, potete modificarvelo a piacimento
Salvate il tutto e passatelo al resource compiler e otterrete così il file di risorse compilato e pronto all'uso. (.res)
Parte [4] -> Cambiamo Sfondo
Ed è arrivato il momento di far prendere forma al nostro progetto: cominciamo col sostituire allo sfondo della finestra, il file bitmap che abbiamo inserito nelle risorse
Riaprite il file .asm e aggiungiamo a fine file una sezione per le risorse
section "res" resource from "rsrc.res" data readable
Questa riga dice al FASM di utilizzare il file rsrc.res per le risorse. (Cambiate il nome del file col vostro se diverso)
Ora per sicurezza potete assemblare e verificare con ResHack o CFF se le risorse che abbiamo aggiunto ci sono effettivamente.
Prima di continuare, ecco praticamente quello che faremo:
Per impostare come sfondo la nostra bitmap, per prima cosa dichiariamo una nuova variabile per l'handle della bitmap:
hbmp dd 0
Dopodichè, carichiamo la bitmap in memoria con LoadBitmap:
push 401 ; lo stesso ID usato nel file rc
push dword [hInst]
call [LoadBitmap]
mov dword [hbmp],eax
Così facendo avremo in eax l'handle della bitmap.
Adesso dobbiamo far vedere la bitmap al programma come fosse il brush da usare per disegnare lo sfondo della finestra. A questo scopo usiamo CreatePatternBrush:
push eax
call [CreatePatternBrush]
Come risultato, in eax avremo l'handle del nuovo brush.
Quello che ci rimane da fare è quindi inserire l'handle ottenuto dentro il membro hbrBackground della struttura WNDCLASSEX, al posto di COLOR_WINDOW.
Ricapitolando, risaliamo nel codice scritto fino a questo punto:
push IDI_APPLICATION
push 0
call [LoadIcon]
mov dword [wc.hIcon],eax
mov dword [wc.hIconSm],eax
mov dword [wc.hbrBackground],COLOR_WINDOW
push wc
call [RegisterClassEx]
or eax,eax
jz endofall
Applicando quello che ho detto sopra, diventerà:
push IDI_APPLICATION
push 0
call [LoadIcon]
mov dword [wc.hIcon],eax
mov dword [wc.hIconSm],eax
;------------da qui
push 401
push dword [hInst]
call [LoadBitmap]
mov dword [hbmp],eax
push eax
call [CreatePatternBrush]
mov dword [wc.hbrBackground],eax
;-------------a qui
push wc
call [RegisterClassEx]
or eax,eax
jz endofall
Altra cosa da aggiungere è la distruzione della bitmap quando chiudiamo il programma. La cosa più normale da fare risulta chiamare DeleteObject' in risposta a WM_DESTROY, poco prima della chiamata a PostQuitMessage. Il codice quindi prenderà questo aspetto:
cmp eax,WM_DESTROY
jnz @f
push dword [hbmp]
call [DeleteObject]
push 0
call [PostQuitMessage]
jmp .end
Salvate, compilare e avrete la vostra finestra che come sfondo ha la vostra bitmap
Parte [5] -> Skinniamo!
Ora il succo del discorso: lo skinning vero e proprio.
Quello che faremo è scrivere è scrivere una funzione chiamata SkinWindow che caricherà la region, contenuta nelle risorse, in memoria e l'adatterà e applicherà alla finestra.
Per iniziare, aggiungiamo le seguenti variabili:
restype db "RGN",0
resname db "REGION",0
hrgn dd 0
La prima non è altro che il tipo di risorsa (vedere il file rc), la seconda è il nome che abbiamo dato alla region (ancora, vedere il file rc), mentre la terza è una varibabile (globale) che useremo per memorizzare l'handle della regione una volta che sarà pronta all'uso.
Poi, andiamo alla chiamata di CreateWindowEx e cambiamo lo stile in WM_POPUP, ovvero:
push 0
push dword [hInst]
push 0
push 0
push 600
push 600
push 0
push 0
push WS_VISIBLE or WS_POPUP ;cioè una finestra visibile e senza bordature varie
push Classname
push Classname
push 0
call [CreateWindowEx]
Ricordiamoci che avendo applicato lo stile WM_POPUP non avremo pulsanti di chiusura: dovremo chiudere la finestra con ALT+F4 !!
Ora, andiamo nel codice appena dopo la WndProc e cominciamo a scrivere:
SkinWindow:
label hres1 at ebp-4
label hres2 at ebp-8
label ressize at ebp-0Ch
enter 0Ch,0
push restype
push resname
push 0
call [FindResource]
mov dword [hres1],eax
Iniziamo a commentare: prima di tutto, ci sistemiamo le label per 3 dword che ci serviranno come variabili locali.
l'istruzione enter ci eviterà di scrivere quello che normalmente andrebbe scritto come:
push ebp
mov ebp,esp
sub esp, 0Ch
La prima chiamata a funzione che facciamo è FindResource, che ci permette a partire da un tipo di risorsa e un nome, di ottenere un identificatore per la risorsa cercata. (Lo 0 indica che la risorsa è nel modulo usato per creare il processo, cioè alla fine dei conti, il nostro file .exe)
Memorizziamo il valore che ci viene restituito nella variabile locale hres1
Poi,
;in eax c'è il risultato di FindResource
push eax ;attenzione, questo è il secondo parametro di LoadResource!
push eax
push 0
call [SizeofResource]
mov dword [ressize],eax
push 0
call [LoadResource]
mov dword [hres2],eax
push eax
call [LockResource]
push eax
push dword [ressize]
push 0
call [ExtCreateRegion]
mov dword [hrgn],eax
Chiamiamo in seguenza SizeofResource per ottenere la grandezza in bytes del file region, LoadResource per caricare tutto in memoria, LockResource per ottenere il puntatore alla region in memoria e ExtCreateRegion per adattare i dati della region ai nostri usi (Date uno sguardo alla guida API per maggiori informazioni)
Ecco il finale della funzione:
push 1 ;ridisegnare tutto? yes
push eax ;risultato di ExtCreateRegion
push dword [hwnd]
call [SetWindowRgn]
push dword [hres1] ;chiudiamo tutti gli handle rimasti aperti
call [CloseHandle]
push dword [hres2]
call [CloseHandle]
push dword [hrgn]
call [CloseHandle]
leave
ret
Tutto viene infine applicato con una chiamata a SetWindowRgn
Quello che ci rimane da fare per completare questo passo è di far fare al programma una chiamata a questa funzione. Una buona idea è quella di metterla appena dopo creata la finestra cioè:
or eax,eax
jz endofall
mov dword [hwnd],eax
call SkinWindow ;->>>>>>>>>>> QUI !
msgloop:
push 0
push 0
push 0
push msg
call [GetMessage]
Ora salvate assemblate e godetevi il risultato....ma sappiate che ancora non è finita
PARTE [6] -> Muovimi!
...Non è finita perchè infatti abbiamo una finestra, ma non ha pulsanti di chiusura, ne possibilità di muoverla.
In questa parte ci occuperemo del movimento della finestra.
Il concetto che useremo sarà il seguente: faremo in modo che, ogni volta che "trascineremo" dentro la finestra, essa pensi che il puntatore del muose sia in realtà sulla caption della finestra (cioè sulla barra del titolo), cosicchè windows la muova a nostro piacimento
Per fare questo, dovremo tornare nella WndProc e far gestire 'WM_LBUTTONDOWN:
(per semplificare, eccovi tutta la WndProc)
WndProc:
label .hwnd at ebp+8
label .msg at ebp+0Ch
label .wParam at ebp+10h
label .lParam at ebp+14h
enter 0,0
mov eax,dword [.msg]
cmp eax,WM_DESTROY
jnz @f
push dword [hbmp]
call [DeleteObject]
push 0
call [PostQuitMessage]
jmp .end
@@:
cmp eax,WM_LBUTTONDOWN
jnz @f
push dword [.lParam]
push 2
push WM_NCLBUTTONDOWN
push dword [.hwnd]
call [SendMessage]
jmp .end
@@:
push dword [.lParam]
push dword [.wParam]
push dword [.msg]
push dword [.hwnd]
call [DefWindowProc]
leave
ret 10h
Come potete vedere, appena ci arriva un messaggio di tipo WM_LBUTTONDOWN, noi ne rispediamo un altro con SendMessage, più precisamente, mandiamo un WM_NCLBUTTONDOWN (per esteso, Non Client Left Button Down). A questo messaggio mandiamo ome wParam il valore HTCAPTION (che è uguale a 2) e come lParam la struttura POINT che indica la posizione del mouse e che già abbiamo dentro lParam stesso.
Salvate, assemblare e provate a trascinare
Parte [7] -> E chiudiamola!
Certo, chiudere la finestra con ALT+F4 non è che sia proprio eccezionale come cosa...per questo aggiungeremo un piccolo pulsantino che ci permetterà di chiudere tutto
Dobbiamo prima aggiungere 3 variabili:
btnclass db "Button",0
btntitle db "[X]",0
hXbtn dd 0
La prima è la il nome della classe standard per i pulsanti, la seconda è il testo che metteremo nel pulsante (originale eh?) e la terza è dove andremo a depositare l'handle del pulsante
Per comodità, aggiungiamo una nuova funzione, che chiamerò CreaControlli, nella quale creeremo tutti i controlli di cui abbiamo bisogno (per ora è solo un pulsante ma lascio a voi la fantasia
)
La funzione non contiene altro che chiamate a CreateWindowEx, eccovela:
CreaControlli:
push 0
push dword [hInst]
push 1 ; essendo un controllo, al posto di hmenù va messo l'ID che vogliamo per il controllo
push dword [hwnd] ;qui va l'handle del parent, cioè la finestra principale in questo caso
push 18 ;altezza
push 22 ;larghezza
push 10 ;coordinata y
push 500 ;coordinata x
push WS_VISIBLE or BS_FLAT or WS_CHILD ;attenzione! deve avere lo stile WS_CHILD!!
push btntitle ;testo
push btnclass ;classe
push 0
call [CreateWindowEx]
mov dword [hXbtn],eax ;memorizziamo dentro la variabile hXbtn
ret
Dopo creato, il pulsante va fatto funzionare (comunque prima di ciò, vi invito a assemblare e eseguire per vedere se il pulsante viene creato). Per far ciò è inutile dire che aggiungeremo la gestione del messaggio WM_COMMAND nella WndProc:
(la rimetto tutta di nuovo)
WndProc:
label .hwnd at ebp+8
label .msg at ebp+0Ch
label .wParam at ebp+10h
label .lParam at ebp+14h
enter 0,0
mov eax,dword [.msg]
cmp eax,WM_DESTROY
jnz @f
push dword [hbmp]
call [DeleteObject]
push 0
call [PostQuitMessage]
jmp .end
@@:
cmp eax,WM_LBUTTONDOWN
jnz @f
push dword [.lParam]
push 2
push WM_NCLBUTTONDOWN
push dword [.hwnd]
call [SendMessage]
jmp .end
@@:
cmp eax, WM_COMMAND
jnz @f
mov eax,dword [.lParam]
cmp eax,dword [hXbtn]
jnz .end
push 0
push 0
push WM_DESTROY
push dword [.hwnd]
call [SendMessage]
jmp .end
@@:
push dword [.lParam]
push dword [.wParam]
push dword [.msg]
push dword [.hwnd]
call [DefWindowProc]
leave
ret 10h
.end:
xor eax,eax
leave
ret 10h
Appena riceviamo il WM_COMMAND, verifichiamo che l'handle del controllo (che ci arriva da lParam) sia quello del nostro pulsante. Se è il nostro pulsantino, mandiamo con SendMessage un bel WM_DESTROY alla finestra
Attenzione: se volete fare le cose bene, verificate anche che la word alta di wParam sia uguale a BN_CLICKED prima di chiamare SendMessage
Salvate, assemblate e godetevi la vostra finestra....per ora
Parte [8] -> Alla moda!
Ebbene si, la trasparenza delle finestre è alla moda...o così dicono
Mettiamola anche noi nella nostra finesta, è semplicissimo.
Per prima cosa, cambiate gli Exstyle della finestra (primo parametro) in WS_EX_LAYERED, poi chiamiamo SetLayeredWindowAttributes per impostare la trasparenza:
push 0
push dword [hInst]
push 0
push 0
push 600
push 600
push 0
push 0
push WS_VISIBLE or WS_POPUP
push Classname
push Classname
push WS_EX_LAYERED
call [CreateWindowEx]
or eax,eax
jz endofall
mov dword [hwnd],eax
push LWA_ALPHA ; ci basiamo sul canale alpha (da 0 a 255)
push 7Fh ; indovinate qual'è la metà di 255 ;P
push 0 ; il nero come base va sempre benissimo
push eax ; handle della finestra
call [SetLayeredWindowAttributes]
Fatto, salvate, compilate et voilà!
Ora potete anche cambiare il valore 7Fh in qualcos'altro per regolare la trasparenza, o magari aggiungere una scrollbar o un paio di pulsanti per farla cambiare a runtime!
Guida by nick40