Copy Link
Add to Bookmark
Report

Assembly Programming Journal

DrWatson's profile picture
Published in 
guide hacker
 · 3 Jun 2018
1

Assembly Programming Journal, traduzione a cura di Little-John per la comunità italiana del reverse engeneering (RingZer0)*** http://ringzer0.cjb.net ***

 
::/ \::::::.
:__\:::::::.
/| \::::::::.
:|
\:::::::::.
:| _|\ \::::::::::. Ott/Nov 98
:::\_____\::::::::::. Issue 1
::::::::::::::::::::::..........................................................

A S S E M B L Y P R O G R A M M I N G J O U R N A L
http://asmjournal.freeservers.com
asmjournal@mailcity.com




T A B L E O F C O N T E N T S
----------------------------------------------------------------------
Introduzione....................................................mammon_

"VGA Programming in Mode 13h".............................Lord Lucifer

"SMC Techniques: The Basics"...................................mammon_

"Going Ring0 in Windows 9x".....................................Halvar

Column: Win32 Assembly Programming
"The Basics"..............................................Iczelion
"MessageBox"..............................................Iczelion

Column: The C standard library in Assembly
"_itoa, _ltoa and _ultoa"...................................Xbios2

Column: The Unix World
"x86 ASM Programming for Linux"............................mammon_

Column: Issue Solution
"11-byte Solution"..........................................Xbios2
----------------------------------------------------------------------
+++++++++++++++++++++++ Sfida ++++++++++++++++++++
Scrivi un programma che visualizzi la sua command line in 11 bytes
----------------------------------------------------------------------




::/ \::::::.
:__\:::::::.
/| \::::::::.
:|
\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::..............................................INTRODUZIONE
by mammon_


Benvenuti alla prima issue dell'Assembly Programming Journal. Il linguaggio Assembly è stato oggetto di rinnovato interesse per molti programmatori; la ragione di ciò dovrebbe essere la reazione al boom improvviso di programmi RAD-developed di bassa qualità (vedi Delphi, VB, ecc) rilasciati come free/shareware negli anni scorsi. Il linguaggio Assembly è solido, veloce, e spesso ben fatto – è un più difficile trovare programmatori inesperti che sviluppano in assembler che non, diciamo, in Visual Basic.

La selezione degli articoli è qualcosa di eclettico e dovrebbe dimostrare il focus di questo giornale: p.e., si rivolge alla comunità dei programmatori assembler, non un tipo particolare di coding, come Win32, virus, o programmazione di demo. Siccome il magazine è appena nato e molti dei suoi scopi possono sembrare poco chiari, dedicherò il resto di questa introduzione alle domande più comuni che ho ricevuto via email per i diversi chiarimenti.


Quanto spesso sarà pubblicato il giornale?
------------------------------------

Salvo fatalità, ogni issue sarà rilasciata a mesi alterni.


Che tipo di articoli saranno accettati?
----------------------------------------

Qualsiasi cosa che abbia a che fare con il linguaggio assembly. Ovviamente repliche di materiale già pubblicato in precedenza non sono necessarie se non quando migliorano o chiarificano il materiale precedente. Il più sarà incentrato sugli instruction sets della famiglia Intel x86; comunque il coding per gli altri processori è accettabile (però sarebbe davvero una gran cortesia indicare un emulatore x86 per il processore riguardo al quale tu scrivi).

Personalmente sono alla ricerca di articoli sull'assembly language che mi interessano: ottimizzazione del codice, demo/graphics programming, virus coding, asm coding per unix e altri OS, e OS-internals.

Le demo (con il sorgente) e 'ASCII art' di qualità (per le copertine della issue, logo per gli articoli, ecc) sono davvero benvenuti.


Per quale livello di esperienza nel coding è inteso il mag?
--------------------------------------------------------

Il giornale intende coinvolgere gli asm-coders di ogni livello. Ogni issue
conterrà per lo più tecniche e codice per beginners e intermediate, dato che saranno, per forza di cose, di maggiore richiesta; comunque uno degli obiettivi dell'APJ è di includere abbastanza materiale 'advanced' per poter interessare anche i "pro-coders".


Come sarà distribuito il mag?
--------------------------------

L'Assembly Programming Journal ha la sua propria web page
http://asmjournal.freeservers.com
che conterrà la issue rilasciata e un archivio delle issue precedenti. La pagina contiene anche un guestbook e una disucssion board per gli articolisti e i lettori.

Un abbonamento via email può esser ottenuto inviando una email a
asmjournal@mailcity.com
con il subject "SUBSCRIBE"; a partire dalla prossima issue, l'Assembly Programming Journal sarà inviato via email all'indirizzo da cui hai inviato la mail.


Wrap-up
-------

Questo è per lo più la "faq". Enjoy the mag!

 
::/ \::::::.
:__\:::::::.
/| \::::::::.
:|
\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE..ARTICLE
VGA Programming in Mode 13h
by Lord Lucifer


Questo articolo descriverà come programmare grafica VGA Mode 13h usando il linguaggio assembly. Mode 13h è la modalità grafica 320x200x256, ed è veloce e molto conveniente dal punto di vista del programmatore.

Il buffer video comincia all'indirizzo A000:0000 e finisce all'indirizzo A000:F9FF. Ciò significa che il buffer è di 64000 bytes e che ogni pixel in mode 13h è rappresentato da un byte.

E' facile settare il mode 13h e il buffer video con l'assembly language:

 
mov ax,0013h ; Int 10 - Video BIOS Services
int 10h ; ah = 00 – Setta il Video Mode
; al = 13 - Mode 13h (320x200x256)

mov ax,0A000h ; punta il segment register es a A000h
mov es,ax ; possiamo ora accedere al buffer video come
; offset dal registro es


Alla fine del tuo programma, vorrai probabilmente ripristinare il text mode.
Ecco come:

 
mov ax,0003h ; Int 10 - Video BIOS Services
int 10h ; ah = 00 – Setta il Video Mode
; al = 03 - Mode 03h (80x25x16 text)


Accedere ad un pixel specifico nel buffer è anche molto semplice:

 
; bx = coordinata x
; ax = coordinata y
mul 320 ; moltiplica y per 320 per ottenere la riga
add ax,bx ; aggiungi questo alla x per otten. l'offset

mov cx,es:[ax] ; ora si può accedere al pixel x,y da es:[ax]


Hmm... questo era facile, ma quella moltiplicazione è un po' lenta e dovremmo sbarazzarcene. Questo è pure facile da fare, semplicemente usando il 'bit shifting' invece della moltiplicazione. Shiftando un numero a sinistra è come moltiplicarlo per 2. Noi vogliamo moltiplicare per 320, che non è una potenza di 2, ma 320 = 256 + 64, e 256 e 64 sono tutti e due potenze di 2. Quindi una maniera più veloce di accedere ad un pixel è:

 
; bx = coordinata x
; ax = coordinata y
mov cx,bx ; copia bx in cx, per salvarlo temporaneam.
shl cx,8 ; shift a sinistra per 8, che è come
; moltiplicare per 2^8 = 256
shl bx,6 ; ora shift sin. per 6, che è come
; moltiplicare per 2^6 = 64
add bx,cx ; ora somma queste 2 insieme, che è come
; moltiplicare effettivamente per 320
add ax,bx ; infine aggiungi la coord x a questo valore
mov cx,es:[ax] ; ora si può accedere al pixel x,y da es:[ax]


Beh, il codice è un po' più lungo e sembra anche più complicato, ma posso garantire che è molto più veloce.

Per disegnare i colori, usiamo una 'color look-up table' (tavola di riferimento colori, NdT). Questa look-up table è un array di 768 campi (3x256). Ogni indice della table è proprio l'offset index*3. I 3 bytes ad ogni indice contengono i valori corrispondenti (0-63) al rosso, al verde, e al blu. Quindi il numero totale dei colori possibili è 262144.
Comunque, siccome la table è di soli 256 elementi, solo 256 colori differenti sono possibili in un dato momento.

Il cambiamento della palette dei colori è realizzato attraverso le porte I/O della scheda VGA:

 
La Porta 03C7h è la Palette Register Read port (Porta di lettura)
LA Porta 03C8h è la Palette Register Write port (Porta di scrittura)
La Porta 03C9h è la Palette Data port (Porta dati)


Ecco come cambiare la palette dei colori:

 
; ax = indice della palette
; bl = componente rossa (0-63)
; cl = componente verde (0-63)
; dl = componente blu (0-63)

mov dx,03C8h ; 03c8h = Palette Register Write port
out dx,ax ; scegli l'index

mov dx,03C9h ; 03c8h = Palette Data port
out dx,al
mov bl,al ; setta il valore rosso
out dx,al
mov cl,al ; setta il valore verde
out dx,al
mov dl,al ; setta il valore blu


Questo è tutto. Leggere la palette del colore è quasi lo stesso:

 
; ax = indice della palette
; bl = componente rossa (0-63)
; cl = componente verde (0-63)
; dl = componente blu (0-63)

mov dx,03C7h ; 03c7h = Palette Register Read port
out dx,ax ; scegli l'index

mov dx,03C9h ; 03c8h = Palette Data port
in al,dx
mov bl,al ; prendi il valore rosso
in al,dx
mov cl,al ; prendi il valore verde
in al,dx
mov dl,al ; prendi il valore blu


Cosa abbiamo ora bisogno di sapere è la procedura per disegnare un pixel di un certo colore in una certa locazione sul monitor. E' molto facile dato ciò che già sappiamo:

 
; bx = coordinata x
; ax = coordinata y
; dx = colore (0-255)
mov cx,bx ; copia bx in cx, per salvarlo temporaneam.
shl cx,8 ; shift a sinistra per 8, che è come
; moltiplicare per 2^8 = 256
shl bx,6 ; ora shift a sin. per 6, che è come
; moltiplicare per 2^6 = 64
add bx,cx ; ora somma queste 2, che è come
; moltiplicare effettivamente per 320
add ax,bx ; infine aggiungi la coord x a questo valore
mov es:[ax],dx ; copia il colore dx nella memory location
; questo è tutto


Ok, ora noi sappiamo come gestire il Mode 13h, gestire il video buffer, disegnare un pixel, e editare la color palette.

Il mio prossimo articolo tratterà il disegno di linee, l'utilizzo del vertical
retrace per rendering più uniformi, e ogni cosa che mi verrà in mente fino ad allora...

 
::/ \::::::.
:__\:::::::.
/| \::::::::.
:|
\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE..ARTICLE
SMC Techniques: The Basics
by mammon_

Uno dei vantaggi della programmazione in assembler è che hai piene facoltà di controllo sull'applicazione: la 'ginnastica binaria' del codice di un virus dimostra ciò più di ogni altra cosa. Uno dei "trucchi" utilizzati dai virus, che ha avuto poi seguito negli schemi di protezione dei programmi, è il codice automodificante (SMC = self-modifyng code).

In quest'articolo non tratterò di virus polimorfici o di motori di mutazione (mutation engines); non analizzerò nessuno schema di protezione in particolare, non considererò trucchi anti-debugger/anti-disassembler, e non affronterò l'argomento della PIQ (Prefetch Instruction Queue, ndt). Questo articolo è solamente una prima trattazione riguardante il codice automodificante, per coloro a cui il concetto risulta nuovo e da implementare.

Episodio 1: Cambiamento di opcode (opcode alteration)
-----------------------------------------------------

Una delle forme più pure del codice automodificante è il cambiare il valore di una istruzione prima che sia eseguita... a volte come il risultato di una comparazione, e a volte per nascondere il codice da occhi curiosi. Questa tecnica segue essenzialmente questo schema:

 
mov reg1, codice-da-sostituire
mov [indir-su-cui-scrivere], reg1


in cui 'reg1' può essere qualsiasi registro, e '[indir-su-cui-scrivere]' dovrebbe essere un puntatore all'indirizzo da cambiare. Nota che il 'codice-da-sostituire' dovrebbe essere una istruzione in formato esadecimale, ma posizionando il codice da qualche altra parte nel programma -- in una subroutine non chiamata, o in un segmento diverso -- è possibile semplicemente trasferire il codice compilato da una locazione ad un'altra attraverso l'indirizzamento indiretto, come segue:

 
call changer
mov dx, offset [string] ;questo sarà eseguito ma ignorato
label: mov ah, 09 ;questo non sarà mai eseguito
int 21h ;questo chiuderà il programma
....
changer: mov di, offset to_write ;carica l'indirizzo del codice da scrivere
;in DI
mov byte ptr [label], [di] ;scrivi il codice nella locazione 'label'
ret ;ritorna dalla chiamata
to_write: mov ah, 4Ch ;codice di fine programma


questa piccola routine farà chiudere il programma, anche se in un disassembler essa all'inizio sembra essere una semplice routine di scrittura di stringa. Nota che combinando l'indirizzamento con dei loops, intere subroutine -- anche programmi -- possono essere sovrascritti, e il codice da scrivere -- che può essere presente nel programma come dati -- può essere criptato con un semplice XOR per farlo sfuggire da un disassembler.

Il seguente è un programma in assembler per dimostrare il cambiamento dal "vivo" del codice; esso chiede all'utente una password, poi cambia la stringa da scrivere a seconda che la password sia corretta o meno.

 
; smc1.asm ==================================================================
.286
.model small
.stack 200h
.DATA
;buffer for Keyboard Input, formatted for easy reference:
MaxKbLength db 05h
KbLength db 00h
KbBuffer dd 00h

;strings: nota che la password non è criptata, ma potrebbe esserlo
szGuessIt db 'Care to guess the super-secret password?',0Dh,0Ah,'$'
szString1 db 'Congratulations! You solved it!',0Dh,0Ah, '$'
szString2 db 'Ah, damn, too bad eh?',0Dh,0Ah,'$'
secret_word db "this"

.CODE
;===========================================
start:
mov ax,@data ; setta il registro di segmento
mov ds, ax ; uguale alla direttiva "assume"
mov es, ax
call Query ; chiede all'utente la password
mov ah, 0Ah ; funzione DOS 'Ricevi input
; dall'utente'
mov dx, offset MaxKbLength ; inizio del buffer
int 21h
call Compare ; confronta la password e cambia il
; codice
exit:
mov ah,4ch ; funzione 'Chiudi al DOS'
int 21h
;===========================================
Query proc
mov dx, offset szGuessIt ; stringa di richiesta password
mov ah, 09h ; funzione 'visualizza stringa'
int 21h
ret
Query endp
;===========================================
Reply proc
PatchSpot:
mov dx, offset szString2 ; stringa 'hai fallito'
mov ah, 09h ; funzione 'visualizza stringa'
int 21h
ret
Reply endp
;===========================================
Compare proc
mov cx, 4 ; num. di bytes nella password
mov si, offset KbBuffer ; inizio del buffer di input della
; password
mov di, offset secret_word ; indirizzo della password corretta
rep cmpsb ; confronto password
or cx, cx ; sono uguali?
jnz bad_guess ; no, non applicare il patch
mov word ptr cs:PatchSpot[1], offset szString1 ;cambia in GoodString
bad_guess:
call Reply ; output della stringa per visualizzare
; il risultato
ret
Compare endp
end start
; EOF =======================================================================

Episodio 2: Encryption (la traduzione di questo termine è proprio brutta, encriptazione, quindi ho preferito lasciare l'originale inglese)
-------------------------------------------------

Senza dubbio l'encryption è la forma più comune di codice automodificante utilizzato oggigiorno. E' utilizzata dai packers e dagli exe-encriptors o per comprimere o per nascondere codice, dai virus per rendere oscuri i propri contenuti, dagli schemi di protezione per nascondere dati. La forma base dell'encription può essere:

 
mov reg1, indir-da-sovrascrivere
mov reg2, [reg1]
manipola reg2
mov [reg1], reg2


dove 'reg1' sarebbe il registro contenente l'indirizzo (l'offset) della locazione da sovrascrivere, e 'reg2' un registro temporaneo che carica i contenuti del primo e poi li modifica attraverso operazioni matematiche (ROL) oppure logiche (XOR). L'indirizzo da cambiare è caricato in reg1, il suo contenuto è poi modificato all'interno di reg2, ed infine riscritto nella locazione originale ancora contenuta in reg1.

Il programma della sezione precedente può essere modificato in modo tale che esso decripti la password sovrascrivendola (in tal modo questa rimane decriptata finchè il programma non è terminato) prima cambiando la 'parola segreta' come segue:
secret_word db 06Ch, 04Dh, 082h, 0D0h

e poi cambiando la routine di confronto, Compare, per cambiare la locazione della 'secret_word' nel segmento dati:

 
;===========================================
magic_key db 18h, 25h, 0EBh, 0A3h ;non molto sicura!

Compare proc ; Passo 1: decripta la password
mov al, [magic_key] ; metti il byte1 della maschera XOR in al
mov bl, [secret_word] ; metti il byte1 della password in bl
xor al, bl
mov byte ptr secret_word, al ; cambia il byte1 della password
mov al, [magic_key+1] ; metti il byte2 della maschera XOR in al
mov bl, [secret_word+1] ; metti il byte2 della password in bl
xor al, bl
mov byte ptr secret_word[1], al ; cambia il byte2 della password
mov al, [magic_key+2] ; metti il byte3 della maschera XOR in al
mov bl, [secret_word+2] ; metti il byte3 della password in bl
xor al, bl
mov byte ptr secret_word[2], al ; cambia il byte3 della password
mov al, [magic_key+3] ; metti il byte4 della maschera XOR in al
mov bl, [secret_word+3] ; metti il byte4 della password in bl
xor al, bl
mov byte ptr secret_word[3], al ; cambia il byte4 della password
mov cx, 4 ;Passo 2: Comfonta le passwords...nessun
;cambiamento da qui in poi
mov si,offset KbBuffer
mov di, offset secret_word
rep cmpsb
or cx, cx
jnz bad_guess
mov word ptr cs:PatchSpot[1], offset szString1
bad_guess:
call Reply
ret
Compare endp


Nota l'aggiunta della locazione della 'magic_key' (chiave magica) che contiene la maschera XOR per la password. Tutto ciò potrebbe essere stato eseguito in maniera più sofisticata con un loop, ma con soli 4 byte il codice sopra velocizza il tempo di debugging (e, inoltre, il tempo di scrittura della articolo ( e della traduzione, ndt !)). Nota come la password è caricata, XORata, e riscritta un byte la volta; utilizzando codice a 32-bit, l'intera password (dword) può esser scritta, XORata e riscritta in un sol colpo.

Episodio 3: Giocherellando con lo stack
---------------------------------------

Questo è un trucco che ho imparato mentre decompilavo del codice di SunTzu. Ciò che accade qui è abbastanza interessante: lo stack è spostato nel segmento codice del programma, cosicchè il top dello stack punta al primo indirizzo da essere modificato (che, inoltre, dovrebbe essere uno vicinissimo alla fine del programma per il modo in cui lo stack funziona); il byte a questo indirizzo è POPato in un registro, manipolato, e PUSHato indietro nella sua locazione originale. Lo stack pointer (SP) è poi decrementato in modo che l'indirizzo successivo da essere modificato (1 byte più basso in memoria) è ora nel top dello stack.

In più, i byte sono XORati con una porzione del codice stesso del programma, anche per camuffare il valore della maschera XOR. Nel codice seguente, ho scelto di utilizzare i byte dallo Start: (200h quando è compilato) fino a -- ma non includendolo -- Exit: (214h quando è compilato; Exit-1=213h).
Comunque, come nel codice originale di SunTzu ho mantenuto la sequenza "inversa" della maschera di XOR sicchè il byte 213h è il primo byte della maschera di XOR, e il byte 200h ne è l'ultimo. Dopo alcuni esperimenti ho capito che questo era il modo più facile per sincronizzare una patch -- o un editor esadecimale -- al codice che manipola lo stack; dal momento che lo stack si muove all'indietro (uno stack che si sposta in avanti è più un problema che una soluzione), utilizzando una maschera XOR "inversa" che permetta a tutti e due i puntatori di file in un patcher di essere INCati o DECati in sincronia.

Come mai questa è una issue? A differenza dei due esempi precedenti, la seguente non contiene una versione criptata del codice da modificare. Questa contiene giusto il code di origine che, quando compilato, risulta essere nei byte non criptati che sono elaborati attraverso la routine di XOR, criptati, ed infine eseguiti (che, se hai seguito il discorso, si dimostrerà subito di non esser buono... in ogni caso è un ottimo metodo per far crashare la VM di DOS (Virtual Machine, ndt)).

Una volta che il programma è compilato bisogna o cambiare i byte da decriptare a mano, o scrivere un patcher che faccia ciò per te. La prima è più conveniente, l'ultima è più sicura e diventa una necessità se hai intenzione di conservare il codice. Nell'esempio seguente ho incluso 2 CCh (int 3) nel codice prima e dopo la fine dei byte da decriptare; un patcher deve solo ricercare questi due valori, contare i byte nel mezzo, ed infine eseguire lo XOR con i byte tra 200h e 213h.

Ancora una volta, questo esempio è la continuazione di quello precedente. In questo ho scritto una routine per decriptare per intero la routine 'Compare' della sezione precedente XORandola con i byte compresi tra 'Start' e 'Exit'. Ciò è eseguito settando il segmento di stack come segmento di codice, poi settando il puntatore di stack uguale all'ultimo indirizzo di codice (il più alto) da modificare. Un byte è POPato dallo stack (es. la sua locazione originale), XORato, e PUSHato indietro nella sua posizione originale. Il byte successivo è caricato decrementando il puntatore di stack. Quando tutto il codice è decriptato, il controllo è restituito alla appena decriptata routine 'Compare' e l'esecuzione continua normalmente.

 
;===========================================
magic_key db 18h, 25h, 0EBh, 0A3h

Compare proc
mov cx, offset EndPatch[1] ;inizio dell'indiriz-da-sovrascriv + 1
sub cx, offset patch_pwd ;fine dell'indiriz-da-sovrascriv
mov ax, cs
mov dx, ss ;salva lo stack segment -- importante!
mov ss, ax ;setta lo stack segment come code segment
mov bx, sp ;salva lo stack pointer
mov sp, offset EndPatch ;inizio dell'indiriz-da-sovrascriv
mov si, offset Exit-1 ;inizio dell'indiriz della maschera XOR
XorLoop:
pop ax ;prendi il byte-da-patchare in AL
xor al, [si] ;XORa al con la XorMask
push ax ;scrivi il byte-to-patch in memoria
dec sp ;carica il successivo byte-da-patchare
dec si ;carica il successivo byte della maschera
;XOR
cmp si, offset Start ;fine della maschera XOR?
jae GoLoop ;Se No, continua
mov si, offset Exit-1 ;reinizializza la maschera XOR
GoLoop:
loop XorLoop ;XORa il byte successivo
mov sp, bx ;ripristina lo stack pointer
mov ss, dx ;ripristina lo stack segment
jmp patch_pwd
db 0CCh,0CCh ;Identifcation mark: START
patch_pwd: ;Nessun cambiamento da qui
mov al, [magic_key]
mov bl, [secret_word]
xor al, bl
mov byte ptr secret_word, al
mov al, [magic_key+1]
mov bl, [secret_word+1]
xor al, bl
mov byte ptr secret_word[1], al
mov al, [magic_key+2]
mov bl, [secret_word+2]
xor al, bl
mov byte ptr secret_word[2], al
mov al, [magic_key+3]
mov bl, [secret_word+3]
xor al, bl
mov byte ptr secret_word[3], al
;compare password
mov cx, 4
mov si, offset KbBuffer
mov di, offset secret_word
rep cmpsb
or cx, cx
jnz bad_guess
mov word ptr cs:PatchSpot[1], offset szString1
bad_guess:
call Reply
ret
Compare endp
EndPatch:
db 0CCh, 0CCh ;Identification Mark: END


Questo genere di programmi è davvero difficile da debuggare. Per testarlo, ho sostituito 'xor al, [si]' prima con 'xor al,00', che non encripta ed è utile per cercercare eventuali errori nel codice, e poi con 'xor al, EBh', che mi ha permesso di verificare che venivano criptati i byte corretti (non fa mai male dare una controllatina, dopotutto).

Episodio 4: Conclusione
------------------------

Tutto ciò dovrebbe dare le basi sul codice automodificante. Ogni programma che utilizza tali tecniche sarà pieno di insidie.

La cosa più importante è avere il programma completamente funzionante prima di iniziare a sovrascrivere parti del suo codice. Inoltre, crea sempre una applicazione che esegua l'inverso di ogni routine di decriptazione/criptazione -- non solo per velocizzare la compilazione e il test automatizzando l'encriptazione di codice che sarà decriptato durante l'esecuzione, ma anche per disporre di un buono strumento per controlli utilizzando un disassemblatore (es. encripta il codice, dissasemblalo, decripta il codice, disassemblalo, confrontalo). Infatti, è una buona idea mantenere la porzione di codice automodificante del tuo programma in un eseguibile diverso e testarlo sulla versione definitiva, finchè tutti i bug sono eliminati dalla routine di decriptazione, e solo allora aggiungi la routine di decriptazione al codice finale. I segni di riconoscimento CCh (codemarks) sono anch'essi estremamente utili.

Infine, esegui il debug con il debug.com per applicazioni DOS -- il debugger è veloce, piccolo, e se crasha hai solo perso una finestra di DOS.

Esempi più complessi di codice automodificante può esser trovato nel codice di Dark Angel, il motore Rhince, o in qualsiasi motore di mutazione utilizzato nei virus polimorfici. Si ringrazia Sun-Tzu per la tecnica dello stack usata nella su applicazione ghf-crackme.

 
::/ \::::::.
:__\:::::::.
/| \::::::::.
:|
\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE..ARTICLE
Going Ring0 in Windows 9x
by Halvar Flake


Questo articolo fornisce una breve visione generale su due modi per andare al livello Ring0 in Windows 9x in modo non documentato, sfruttando il fatto che in Win9x nessuna delle tavole di sistema più importanti sono sulle pagine che sono protette da un accesso a basso privilegio (low-privilege access).

Una conoscenza di base del Protected Mode e degli OS Internals è richiesta, fa' riferimento al tuo Assembly Book per questo:-) Le tecniche presentate qui non sono assolutamente una maniera buona/pulita per raggiungere un livello di privilegio più alto, ma siccome richiedono uno sforzo di coding davvero piccolo, a volte sono più more piacevoli da implementare che non un rigoroso VxD.

1. Introduzione
----------------

In tutti i Sistemi Operativi moderni, la CPU va in protected mode, sfruttando le caratteristiche particolari di questo mode per implementare la virtual memory, il multitasking ecc. Per gestire l'accesso alle risorse system-critical (e perciò per fornire stabilità) un OS ha bisogno di livelli di privilegio, in modo che un programma non possa repentinamente uscirsene dal protected mode ecc. Questi livelli di privilegio sono rappresentati sulle CPU x86 (mi riferisco all'x86 intendendo il 386 e i seguenti) con dei 'Rings', in cui il Ring0 è quello con i privilegi più alti e il Ring3 è quello con i privilegi più bassi.
In teoria, l'x86 può gestire 4 livelli di privilegio, ma Win32 ne usa solo 2, Ring0 come 'Kernel Mode' e Ring3 come 'User Mode'.

Dal momento che il Ring0 non è richiesto dal 99% delle applicazioni, in Win9x l'unico modo documentato per usare le routine del Ring0 è attraverso i VxDs. Ma i VxDs, pur rappresentando l'unica maniera stabile e raccomandata, sono faticosi da scrivere e grandi, quindi in un paio di situazioni particolari, le altre vie per raggiungere il Ring0 possono tornare utili.

La CPU stessa amministra le transizioni di livello di privilegio in due modi: attraverso le Exceptions/Interrupts e attraverso i Callgates. I Callgates possono essere inseriti nella LDT o nella GDT, Interrupt-Gates si trovano nella IDT.

Noi trarremo profitto dal fatto che queste tables possono essere scritte liberamente dal Ring3 in Win9x (NON IN NT !).


2. Il metodo della IDT
-----------------

Se si verifica una exception (or is triggered), la CPU guarda nella IDT al descriptor corrispondente. Questo descriptor dà alla CPU un Address e un Segment
a cui trasferire il controllo. Un Interrupt Gate descriptor assomiglia a questo:

 
--------------------------------- ---------------------------------
D D
1.Offset (16-31) P P P 0 1 1 1 0 0 0 0 R R R R R +4
L L
--------------------------------- ---------------------------------
2.Segment Selector 3.Offset (0-15) 0
--------------------------------- ---------------------------------
DPL == I 2 bits che contengono il Descriptor Privilege Level
P == Il bit Present
R == bits Reserved


La prima word (Nr.3) contiene la più bassa word dell'address a 32-bit dell'Exception Handler. La word a +6 contiene la word di ordine più alto. La word a +2 è il selector del segment in cui risiede l'handler.

La word a +4 identifica il descriptor come Interrupt Gate, contiene il suo privilegio e il bit present. Ora, per usare la IDT per andare a Ring0, creeremo un nuovo Interrupt Gate che punta alla nostra procedura Ring0, salveremo quello vecchio e lo sostituiremo con il nostro.

Poi causeremo quella exception. Invece di passare il controllo all'handler proprio di Windows, la CPU eseguirà il nostro codice di Ring0. Non appena abbiamo finito, ripristineremo il vecchio Interrupt Gate.

In Win9x, il selector 0028h punta sempre ad un Segmento Ring0-Code, che si estende per il completo intervallo dello spazio di indirizzamento di 4 GB. Useremo questo come nostro Segment selector.

Il DPL deve essere 3, dal momento che stiamo chiamando dal Ring3, e il bit present deve essere settato. Quindi la word a +4 sarà 1110111000000000b => EE00h. Questi valori possono essere hardcodati nel tuo programma, noi dobbiamo solo aggiungere l'offset della nostra Procedura Ring0 al descriptor. In quanto exception, ne dovresti usare preferibilmente una che si verifica raramente, quindi non usare l'int 14h ;-)

Io userò l'int 9h, dal momento che (per quanto ne so) non è usato sul 486+.

Il codice di esempio segue (da compilare con il TASM 5):

 
-------------------------------- taglia qui -----------------------------------

.386P
LOCALS
JUMPS
.MODEL FLAT, STDCALL

EXTRN ExitProcess : PROC

.data

IDTR df 0 ; Questo conterrà i contenuti del registro IDTR

SavedGate dq 0 ; Salviamo il gate che posizionamo qui

OurGate dw 0 ; Offset low-order word
dw 028h ; Segment selector
dw 0EE00h ;
dw 0 ; Offset high-order word



.code

Start:
mov eax, offset Ring0Proc
mov [OurGate], ax ; Mette l'offset words
shr eax, 16 ; nel nostro descriptor
mov [OurGate+6], ax

sidt fword ptr IDTR
mov ebx, dword ptr [IDTR+2] ; carica il Base Address della IDT
add ebx, 8*9 ; Indirizzo del descriptor int9 in ebx

mov edi, offset SavedGate
mov esi, ebx
movsd ; Salva il vecchio descriptor
movsd ; nel SavedGate

mov edi, ebx
mov esi, offset OurGate
movsd ; Sostituisce il vecchio handler
movsd ; con il nostro, nuovo

int 9h ; Genera l'exception, quindi
; passa il controllo alla nostra
; procedura Ring0

mov edi, ebx
mov esi, offset SavedGate
movsd ; Ripristina il vecchio handler
movsd

call ExitProcess, LARGE -1

Ring0Proc PROC
mov eax, CR0
iretd
Ring0Proc ENDP

end Start

-------------------------------- taglia qui -----------------------------------

3. The LDT Method
-----------------

Un'altra possibilità per eseguire del Ring0-Code è quella di installare un cosiddetto callgate o nella GDT o nella LDT. Sotto Win9x è un po' più facile usare la LDT, dato che i primi 16 descriptors in essa sono sempre vuoti, quindi qui fornirò il codice solo per questo metodo.

Un Callgate è simile ad un Interrupt Gate ed è usato per trasferire il controllo da un segmento low-privileged ad un segmento high-privileged usando una istruzione CALL.

Il formato di un callgate è:

 
--------------------------------- ---------------------------------
D D D D D D
1.Offset (16-31) P P P 0 1 1 0 0 0 0 0 0 W W W W +4
L L C C C C
--------------------------------- ---------------------------------
2.Segment Selector 3.Offset (0-15) 0
--------------------------------- ---------------------------------
P == Present bit
DPL == Descriptor Privilege Level
DWC == Dword Count, numero di argomenti copiati nello stack del ring0


Quindi tutto ciò che ci resta da fare è creare un callgate, scriverlo in uno dei primi 16 descriptors, poi effettuare una far call a quel descriptor per eseguire il nostro Ring0 code.

Codice di esempio:

 
-------------------------------- taglia qui -----------------------------------

.386P
LOCALS
JUMPS
.MODEL FLAT, STDCALL

EXTRN ExitProcess : PROC

.data

GDTR df 0 ; Questo conterrà i contenuti del registro IDTR

CallPtr dd 00h ; Siccome stiamo usando il primo descriptor (8) ed
dw 0Fh ; è posizionato nell'LDT e il livello di privilegio
; è 3, il nostro selector sarà 000Fh.
; Ciò perchè i due bits low-order del selector
; sono il livello di privilegio, e il 3° bit
; è settato se il selector è nella LDT.

OurGate dw 0 ; Offset low-order word
dw 028h ; Segment selector
dw 0EC00h ;
dw 0 ; Offset high-order word

.code

Start:
mov eax, offset Ring0Proc
mov [OurGate], ax ; Mette l'offset words
shr eax, 16 ; nel nostro descriptor
mov [OurGate+6], ax

xor eax, eax

sgdt fword ptr GDTR
mov ebx, dword ptr [GDTR+2] ; carica il Base Address della GDT
sldt ax
add ebx, eax ; L'indirizzo del descriptor LDT in
; ebx
mov al, [ebx+4] ; Carica il base address
mov ah, [ebx+7] ; della stessa LDT in
shl eax, 16 ; eax, fa riferimento al tuo manuale
mov ax, [ebx+2] ; del pmode per i dettagli

add eax, 8 ; Salta il NULL Descriptor

mov edi, eax
mov esi, offset OurGate
movsd ; Sposta il nostro callgate personale
movsd ; nella LDT

call fword ptr [CallPtr] ; Esegui la nostra procedura Ring0

xor eax, eax ; Pulisci la LDT
sub edi, 8
stosd
stosd

call ExitProcess, LARGE -1

Ring0Proc PROC
mov eax, CR0
retf
Ring0Proc ENDP

end Start

-------------------------------- taglia qui -----------------------------------


Bene, popolo questo è tutto per ora. Questo metodo può essere facilmente adattato per usarlo con la GDT, che ti permetterà di salvare un po' di bytes nel caso tu abbia da fare una forte ottimizzazione.

Ad ogni modo, usa questi metodi con cura, che NON funzioneranno su NT e non sono in generale una maniera pulita o stabile per effettuare queste operazioni.


Credits & Thanks
----------------

Il metodo della IDT preso dal virus CIH & dall'esempio di Stone alla url
http://www.cracking.net.
Il metodo della LDT è fatto da me, ma senza l'aiuto di IceMan & The_Owl sarei ancora confuso, quindi tutti i crediti vanno a loro.

 
::/ \::::::.
:__\:::::::.
/| \::::::::.
:|
\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::................................WIN32.ASSEMBLY.PROGRAMMING
Win32 ASM: The Basics
by Iczelion


Tools necessari:
-Microsoft Macro Assembler 6.1x : il supporto MASM per la programmazione
Win32 comincia dalla versione 6.1. L'ultima versione è la 6.13 che
è una patch alla versione pecedente alla 6.11. Il Win98 DDK include
MASM 6.11d che può essere scaricato da Microsoft da
http://www.microsoft.com/hwdev/ddk/download/win98ddk.exe
Ma ti avviso, questo è mostruosamente grande, 18.5 MB. La patch per
MASM 6.13 può anche essere scaricata da
ftp://ftp.microsoft.com/softlib/mslfiles/ml613.exe
-Microsoft import libraries : Puoi usare le import libraries del
Visual C++. Alcune sono incluse nel Win98 DDK.
-Win32 API Reference : Puoi scaricarla dal sito della Borland:
ftp://ftp.borland.com/pub/delphi/techpubs/delphi2/win32.zip


Ecco una breve descrizione del processo di assembly.

MASM 6.1x è fornito di due tools esseziali: ml.exe e link.exe. ml.exe è l'assembler. Esso crea dal sorgente in assembly (.asm) un file object (.obj). Un file object è un file intermedio tra il codice sorgente e il file eseguibile. Esso ha bisogno dei fixups per gli indirizzi, servizio fornito dal link.exe. Link.exe trasforma un file object in eseguibile agendo su più piani, come aggiungere codice da altri moduli ai file object oppure fornendo i fixups per gli indirizzi, aggiungendo le risorse, ecc.

Per esempio:

 
ml skeleton.asm ---> questo crea skeleton.obj
link skeleton.obj ---> questo crea skeleton.exe


Le righe sopra sono, naturalmente, delle esemplificazioni. Nel mondo reale, devi aggiungere diversi switches a ml.exe e link.exe per personalizzare la tua applicazione. Inoltre ci saranno diversi files che dovrai linkare con il file object per creare la tua applicazione.

I programmi per Win32 sono eseguiti in "protected mode" che e' disponibile sin dai tempi degli 80286. Ma gli 80286 ormai sono storia. Percio' dovremo relazionarci all' 80386 e i suoi discendenti. Windows esegue ogni singolo programma Win32 in uno spazio virtuale separato e unico. Cio' significa che ogni programma Win32 avra' i suoi propri 4 GB di address space. Ogni programma e' solo nel suo address space. Cio' e' in contrasto con la situazione presente in Win16. Tutti i programmi Win16 possono *vedersi* gli uni con gli altri. Questo non accade in Win32. Tale caratteristica aiuta a ridurre la probabilita' che un programma scriva sul codice/dati di un altro.

Il memory model (modello di memoria, NdT) e' parimenti drasticamente diverso dai vecchi giorni del mondo a 16-bit. In Win32, non e' piu' necessario preoccuparsi del modello di memoria o dei segmenti! C'e' solo UN modello di memoria: il Flat memory model. Non esistono piu' segmenti da 64K. La memoria e' un largo e continuo spazio di 4 GB. Questo significa anche che non bisognera' piu' giocare con i segment registers. Potrete usare qualsiasi segment register per indirizzare qualsiasi punto nello spazio di memoria. Questo e' un GRANDE aiuto ai programmatori, ed e' cio' che rende la programmazione in assembly per Win32 semplice quanto quella in C.

We will examine a miminal skeleton of a Win32 assembly program. We'll add more flesh to it later. Ecco lo scheletro del programma. Se non comprendete alcune parti del codice, niente panico. Spieghero' ciascuna di esse in seguito.

 
.386
.MODEL Flat, STDCALL
.DATA

......
.DATA?

......
.CONST

......
.CODE
:

.....
end


Ecco tutto! Analizziamo questo scheletro.

.386
Questa e' una direttiva per l'assembler, a cui diciamo di usare il set di istruzioni 80386. Potreste anche usare .486, .586 ma la scelta piu' sicura e' quella di utilizzare sempre .386.

.MODEL FLAT, STDCALL
.MODEL e' una direttiva per l'assembler che specifica il modello di memoria del nostro programma. Sotto Win32, esiste un solo modello di memoria, il modello FLAT. STDCALL comunica a MASM la convenzione nel passare i parametri. Questa convenzione specifica l'ordine con cui i parametri verranno passati, da sinistra-verso-destra o da destra-verso-sinistra, oltre a chi bilanciera' lo stack frame dopo la chiamata di una call (procedura, NdT).

In Win16, ci sono due tipi di convenzioni di chiamata, C e PASCAL
La convenzione di chiamata C passa i parametri da destra a sinistra, cioe', il parametro all'estrema destra e' PUSHato per primo. Il caller e' responsabile del bilanciamento dello stack frame dopo la call. Ad esempio, volendo chiamare una funzione denominata foo(int primo_param, int secondo_param, int terzo_param) con la convenzione C, il codice assomiglierebbe a questo:

 
push [terzo_param] ; Pusha il terzo parametro
push [secondo_param] ; Seguito dal secondo
push [primo_param] ; E dal primo
call foo
add sp, 12 ; Il caller bilancia lo stack frame


La convenzione PASCAL e' l'inverso di quella per il C. Essa passa i parametri da sinistra a destra, e il callee e' responsabile per il bilanciamento della stack dopo la call.

Win16 adotta la convenzione PASCAL poiche' produce codici piu' piccoli. la convenzione C e' utile quando non si conosce il numero dei parametri che verranno passati alla funzione, come nel caso di wsprintf(). Nel caso di wsprintf(), la funzione non ha modo di determinare aprioristicamente il numero di parametri che verrano pushati sulla stack, percio' non puo' bilanciare lo stack frame.

STDCALL e' un ibrido tra le convenzioni C e PASCAL. Essa passa i parametri da destra a sinistra ma il callee e' responsabile per il bilanciamento della stack dopo la call. La piattaforma Win32 usa esclusivamente STDCALL. Eccetto in un caso: wsprintf(). Dovete usare la convenzione C con wsprintf().

 
.DATA
.DATA?
.CONST
.CODE


Tutte e quattro le direttive sono cio' che viene definito SEZIONE. Non avete segmenti in Win32, ricordate ? Ma potete dividere il vostro intero address space in sezioni logiche. L'inizio di una sezione definisce la fine della sezione precedente. Ci sono due gruppi di sezioni: dati e codice. Le sezioni dei dati sono divise in 3 categorie:

.DATA Questa sezione contiene i dati inizializzati del vostro programma.
.DATA? Questa sezione contiene i dati NON inizializzati del vostro programma. A volte capita di voler impegnare una parte di memoria (variabli, NdT) senza inizializzarla. Questa sezione serve a tale scopo.
.CONST Questa sezione contiene le costanti usate dal vostro programma. Le costanti in questa sezione non potranno mai essere modificate dal vostro programma. Sono semplicemente *costanti*.

Non e' necessario utilizzare tutte e tre le sezioni nel vostro programma. Dichiarate solo la/e sezione/i che volete usare.

C'e' solo una sezione per il codice: .CODE. Qui e' dove le vostre istruzioni risiedono. Ad esempio:

 
:
end


...dove e' una qualsiasi etichetta arbitraria usata per specificare l'estensione del vostro programma. Entrambe le etichette devono essere identiche. Tutto il vostro codice deve risiedere tra ed end

Traduttore in lingua italiana : -NeuRaL_NoiSE

 
::/ \::::::.
:__\:::::::.
/| \::::::::.
:|
\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::................................WIN32.ASSEMBLY.PROGRAMMING
MessageBox Display
by Iczelion

In questo tutorial, creeremo un programma per Windows completamente funzionante che mostra un box con il messaggio "Win32 assembly is great!".

Windows prepara una grossa quantita' di risorse per i programmi Windows. Al centro di cio' c'e' la Windows API (Application Programming Interface, Interfaccia per la Programmazione di Applicazioni, NdT). La Windows API e' un'immensa collezione di utilissime funzioni contenute in Windows stesso, pronte ad essere usate da qualsiasi programma per Windows.

Queste funzioni risiedono in DLL (dynamic-linked libraries, librerie collegate dinamicamente al programma, NdT) come kernel32.dll, user32.dll e gdi32.dll. Kernel32.dll contiene funzioni API relative alla memoria e alla gestione dei processi. User32.dll controlla gli aspetti dell'interfaccia utente del vostro programma.Gdi32.dll e' responsabile per le operazioni grafiche. Oltre alle "principali tre", ci sono altre DLL che il vostro programma puo' usare, ammesso che voi possediate abbastanza informazioni riguardo alla funzione API desiderata.

I programmi per Windows si linkano (collegano, NdT) dinamicamente a queste DLL, in altre parole il codice per le funzioni API non e' incluso nell'eseguibile del programma per Windows. Per comunicare al vostro programma dove trovare le funzioni API desiderate al momento dell'esecuzione, dovrete accludere tale informazione nel file eseguibile. L'informazione risiede nelle import libraries (librerie importate, NdT). Dovrete linkare il vostro programma con le corrette import libraries o esso non sara' capace di localizzare le funzioni API.

Esistono due tipi di funzioni API: Uno per ANSI e uno per Unicode. Il nome delle funzioni API per ANSI e' postfissato con "A", ad esempio MessageBoxA. Quelle per Unicode sono postfissate con "W" (per Wide Char, credo).

Windows 95 supporta nativamente ANSI e l'Unicode Windows NT. Ma la maggior parte delle volte, utilizzerete un file di include che puo' determinare e selezionare le funzioni API appropriate per la vostra piattaforma. Semplicemente riferitevi alla funzione API senza il postfisso.

Presentero' semplicemente lo scheletro del programma qui sotto. Lo riempiremo successivamente.

 
.386
.model flat, stdcall
.data
.code
Main:
end Main


Ogni programma per Windows deve chiamare una funzione API, ExitProcess, quando vuole uscire a Windows. In quest'ottica, ExitProcess e' equivalente a int 21h, ah=4Ch in DOS.

Ecco il prototipo per la funzione ExitProcess da winbase.h:

 
void WINAPI ExitProcess(UINT uExitCode);


-void significa che la funzione non restituisce nessun valore al caller.
-WINAPI e' un alias della convenzione di chiamata STDCALL.
-UINT e un tipo di dati, "unsigned integer", che e' un valore a 32-bits sotto Win32 (e' un valore a 16-bits sotto Win16)
-uExitCode e' il codice a 32-bits di ritorno a Windows. Questo valore non e' usato da Windows al momento.

Per chiamare ExitProcess da un programma in assembly, dovrete prima dichiarare il function prototype (prototipo di funzione, NdT) per ExitProcess.

 
.386
.model flat, stdcall
ExitProcess PROTO ,:DWORD
.data
.code
Main:
INVOKE ExitProcess, 0
end Main


Ecco tutto. Il vostro primo programma funzionante per Win32. Salvatelo come msgbox.asm.
Presupponendo che ml.exe e' nella vostra path, assemblate msgbox.asm con:

 
ml /c /coff /Cp msgbox.asm


/c dice a MASM di assemblare soltanto. Non invoca Link.
/coff dice a MASM di creare un file .obj in formato COFF.
/Cp dice a MASM di conservare le caratteristiche di formattazione (maiuscole/minuscole) degli identificatori (variabili, NdT) dell'utente.

Quindi procedete con link:

 
link /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm611\lib msgbox.obj kernel32.lib


/SUBSYSTEM:WINDOWS dice a Link che tipo di eseguibile e' questo programma.
/LIBPATH: dice a Link dove sono le import libraries. Sul mio PC, sono sotto c:\masm\lib

Adesso avete ottenuto msgbox.exe. Andate avanti, fatelo partire. Scoprirete che non fa niente. Beh, non ci abbiamo ancora inserito niente di interessante. Ma e' senza ombra di dubbio un programma per Windows. E osservate le sue dimensioni!
Sul mio PC, il file e' lungo 1,536 bytes.

La linea:

 
ExitProcess PROTO ,:DWORD


e' un prototipo di funzione. Voi dichiarate il nome della funzione seguito dalla parola chiave "PROTO", una virgola, e la lista del tipo di dati dei parametri. MASM usa il prototipo di funzione per controllare il numero e il tipo di parametri della funzione.

Il miglior posto per i prototipi di funzione e' un file di include. Potete creare un file di include pieno di prototipi di funzioni e strutture di dati frequentemente usati e includerlo all'inizio del vostro programma asm.

Chiamate le funzioni API usando la parola chiave INVOKE:

 
INVOKE ExitProcess, 0


INVOKE e' in pratica una specie di call specializzata. Essa controlla il numero e il tipo di parametri e li pusha sulla stack seguendo la convenzione di chiamata predefinita (in questo caso, stdcall). Usando INVOKE invece del normale CALL, potete prevenire gli errori della stack derivanti da un passaggio di parametri incorretto. Molto utile. La sintassi e':

 
INVOKE espressione [,argomenti]


dove espressione e' un'etichetta o il nome di una funzione.

Successivamente, metteremo su una message box. La dichiarazione per questa funzione e':

 
int WINAPI MessageBoxA(HWND hwnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);


-hwnd e' l'handle della parent window (finestra-genitrice, NdT :)
-lpText e' un puntatore al testo che volete mostrare nella client area (l'area a disposizione della message box, NdT)
-lpCaption e' un puntatore al titolo della message box
-uType specifica l'icona e il numero e tipo dei bottoni della message box

Sotto la piattaforma Win32, HWND, LPCSTR, e UINT sono tutti valori della dimensione di 32 bits.

Modifichiamo msgbox.asm per includere la message box.

 
.386
.model flat, stdcall
ExitProcess PROTO ,:DWORD
MessageBoxA PROTO ,:DWORD, :DWORD, :DWORD, :DWORD
.data
MsgBoxCaption db "Iczelion Tutorial No.2",0
MsgBoxText db "Win32 Assembly is Great!",0
.const
NULL equ 0
MB_OK equ 0
.code
Main:
INVOKE MessageBoxA, NULL, ADDR MsgBoxText, ADDR MsgBoxCaption, MB_OK
INVOKE ExitProcess, NULL
end Main
Assemblatelo così:
Assemble it by:
ml /c /coff /Cp msgbox.asm
link /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm\lib msgbox kernl32.lib user32.lib


Dovrete includere user32.lib nel parametro di Link, poiche' le informazioni per linkare MessageBoxA risiedono in user32.lib

Vedrete una message box che mostra il testo "Win32 Assembly is Great!".
Diamo un'altra occhiata al codice.

Definiamo due stringhe terminate con zero (zero-terminated) nella sezione .data. Ricordate che tutte le stringhe in Windows devono essere terminate con zero (ASCIIZ).

Definiamo due costanti nella sezione .const. Utilizziamo le costanti per rendere piu' chiaro il codice.

Osservate i parametri della funzione MessageBoxA. Il primo parametro e' NULL.
Cio' significa che non c'e' nessuna finestra che *possiede* questa message box.

L'operatore "ADDR" e' usato per passare l'indirizzo dell'etichetta alla
funzione. Questo operatore è specifico di MASM. Non esiste un equivalente di TASM. Funziona come l'operatore "OFFSET" ma con alcune differenze:
1. Non accetta le forward reference. Se vuoi usare "ADDR foo", devi dichiarare "foo" prima di usare l'operatore ADDR.
2. Può essere usato con una variabile locale. Una variabile local è una variabile creata nello stack. L'operator OFFSET non piò essere usato in questa situazione perchè l'assembler non conosce il reale indirizzo della variabile locale quando lo assembla.

Traduttore : -NeuRaL_NoiSE

 
::/ \::::::.
:__\:::::::.
/| \::::::::.
:|
\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::........................THE.C.STANDARD.LIBRARY.IN.ASSEMBLY
The _itoa, _ltoa and _ultoa functions
by Xbios2


ATTENZIONE I:
Questo documento è basato sul Borland C++ 4.02. Quando mi è stato possibile l'ho controllato con altre librerie / programmi contenenti le funzioni specifiche, ci potrebbero comunque essere delle differenze tra questa e la tua versione di C. Inoltre questo è solo codice 32-bit, Windows compiler. Niente DOS o UNIX.]

ATTENZIONE II:
I confronti di grandezza sono davvero facili da compiere. I confronti di velocità un po' meno. Le differenze di velocità da me rilevate sono basate sui timings RDTSC, ma NON prendono in considerazione casi estremi. E' questo il motivo per cui non fornisco il numero di cicli di clock esatti. Naturalmente se hai bisogno dei cicli di clock esatti per il tuo Pentium II, puoi sempre comprarmene uno :)


Il linguaggio C offre 3 funzioni per convertire un integer in ASCII:

 
char *itoa(int value, char *string, int radix);
char *ltoa(long value, char *string, int radix);
char *ultoa(unsigned long value, char *string, int radix);


_itoa e _ltoa fanno _esattamente_ la stessa cosa. Questo perchè un integer _è_ un long codice 32-bit. Però sono diversi: _itoa ha del codice _completamente_ inutile in sè (nel 16bit questo codice il valore sign-extend se radix=10).
Comunque il risultato è sempre lo stesso, quindi _ltoa da qui in poi significa sia _ltoa che _itoa. _ultoa esattamente uguale a _ltoa e _itoa, tranne quando radix=10 e il valore < 0.

In ogni modo tutte queste funzioni fanno riferimento a questa:

 
___longtoa(value, *string, radix, signed, char10)


I primi tre parametri sono passati 'così come sono', signed è settato ad 1 da _ltoa se radix=10, altrimenti è settato a 0 e char10 è il carattere corrispondente a 10 se radix>10, ed è sempre settato 'a' (___longtoa è anche utilizzato da printf, che ha un'opzione per ottenere i caratteri maiuscoli in Hex).

 
___longtoa esegue le seguenti (e lo fa con codice scritto male):


1. Controlla che 2< =radix< =36, se non lo è , restituisce '0'
2. Se signed=1 e value< 0 aggiunge '-' alla stringa e fa 'neg' sul valore
3. Loop1: crea una pseudo-string nello stack, invertita
4. Loop2: converte e copia la pseudo-string nella string

Il controllo su radix è necessario perchè:
radix=0 genererebbe un INT0 (divisione per zero)
radix=1 metterebbe l'applicazione in un loop infinito, distruggendo lo stack
radix=37 per valore=36 restituirebbe '}', il carattere dopo 'z'

I due loops sono necessari in ragione della maniera in cui la conversione è svolta. (vedi il codice dopo). Per implementare una conversione a loop-unico, il numero di caratteri dovrebbe essere calcolato in anticipo, con il risultato di un codice meno efficiente (il numero dei caratteri nel valore è n=(int)(log(value)/log(radix))+1, ma usare un loop in più è molto più veloce).

Includendo il listato disassembly delle funzioni di C allungherebbe di molto l'articolo, e in ogni caso sono quelli solo esempi di codice davvero brutto. Quindi, dritti al risultato:

 
ltoa proc
cmp dword ptr [esp+0Ch], 10
sete ch
mov cl, 'a'-'0'-10
jmp short longtoa

ultoa:
mov cx, 'a'-'0'-10

longtoa:
push ebx
push edi
push esi
sub esp, 24h
mov ebx, [esp+3Ch] ; radix
mov eax, [esp+34h] ; valore
mov edi, [esp+38h] ; stringa
cmp ebx, 2
jl short _ret
cmp ebx, 36
jg short _ret
or eax, eax
jge short skip
cmp byte ptr ch, 0 ; _ltoa ?
jz short skip
mov byte ptr [edi], '-'
inc edi
neg eax
skip: mov esi, esp

loop1: xor edx, edx
div ebx
mov [esi], dl
inc esi
or eax, eax
jnz loop1

loop2: dec esi
mov al, [esi]
cmp al, 10
jl short nochar
add al, cl
nochar: add al, '0'
stosb
cmp esi, esp
jg short loop2

_ret: mov byte ptr [edi], 0
mov eax, [esp+38h]
add esp, 24h
pop esi
pop edi
pop ebx
ret
ltoa endp


C'è un 3 in 1 procedura. ltoa e ultoa prendono gli stessi parametri come le funzioni standard di C. longtoa era stato cambiato per prendere dallo stack gli stessi parametri di ltoa e ultoa, mentre signed e char10 sono passati attraverso CH e CL rispettivamente. In questo modo ltoa e ultoa 'vedono' longtoa come 'proprio' codice, e non come una procedura diversa (ciò per evitare un problema comune in C, le procedure che 'inoltrano' i loro parametri ad un'altra funzione).

Questo codice si compila in 102 bytes (e potrebbe essere ottimizzato per 'grattare' altri byte), quando invece il codice standard di C impiega 270 bytes. Precisamente:

 
function C size Asm size
------------------------------
itoa 60 0
ltoa 40 12
ultoa 27 4
longtoa 143 86
------ ------
total 270 102


Va pure 2 volte più veloce di ltoa. E inoltre, questa è una versione completamente C-compatibile di ltoa e ultoa. Naturalmente potrebbe essere adattata da C-compatibile in altro per venire incontro a necessità specifiche (p.e renderla stdcall invece di cdecl, oppure se la velocità e la dimensione sono cruciali si può rimuovere il controllo per radix, e così via...)

Ad ogni modo, è abbastanza anomalo che non userai mai valori di radix che differiscano da 2, 8, 10 o 16. Quindi se velocità e dimensione sono l'essenza, può essere scritta una routine migliore e più specifica. Per esempio, considera questa routine che deposita il valore di EAX come un numero binario all'indirizzo specificato da EDI:

 
ultob proc
mov ecx, 32
more1: shl eax, 1
dec ecx
jc more2
jnl more1
more2: setc dl
add dl, '0'
shl eax, 1
mov [edi], dl
inc edi
dec ecx
jnl more2
mov [edi], al
ret
ultob endp


Questa è 14 volte più veloce di ltoa in C, e 7 volte più veloce di ltoa in Asm, ed è di soli 29 bytes. Ma questo articolo è già abbastanza lungo, quindi aspetta per un altro articolo su funzioni 'ltoa' specifiche (chi lo sa, forse potrei decidere di scrivere una funzione 'printf' in Asm, che potrebbe usarle...).

 
::/ \::::::.
:__\:::::::.
/| \::::::::.
:|
\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::............................................THE.UNIX.WORLD
x86 ASM Programming for Linux
by mammon_


Essenzialmente questo articolo è una scusa per conciliare i miei due interessi favoriti di coding: il sistema operativo Linux e la programmazione in linguaggio assembly. Tutti e due gli argomenti non necessitano (meglio, non dovrebbero) di una introduzione; come l'assembly Win32, assembly per Linux è eseguito in protected mode 32-bit... comunque ha il netto vantaggio di permetterti di chiamare le funzioni delle librerie standard C come ogni altra funzione delle normali librerie Linux "condivise". Ho cominciato con una breve introduzione sulla compilazione dei programmi in assembly language per Linux; per una migliore leggibilità potresti bypassarla e andare direttamente alla sezione su "Le Basi".


Compiling e Linking
---------------------

I due assemblers principali per Linux sono Nasm, l'Assembler (gratis) di Netwide, e GAS, l'Assembler (pure gratis) di Gnu, integrato in GCC. Mi concentrerò su Nasm in questo articolo, lasciando GAS per un altro giorno dal momento che usa la sintassi AT&T e ciò richiederebbe una introduzione più prolissa.

Nasm dovrebbe essere azionato con l'opzione di formato ELF ("nasm -f elf hello.asm"); l'object che ne deriva è poi linkato con GCC ("gcc hello.o") per creare il binario ELF finale. Lo script seguente può essere usato per compilare moduli ASM; l'ho scritto in modo che sia molto semplice, quindi tutto ciò che fa è prendere il primo filename passatogli (io consiglio di chiamarlo con una estensione ".asm"), lo compila con nasm, e lo linka con gcc.

 
#!/bin/sh
# assemble.sh =========================================================
outfile=${1%%.*}
tempfile=asmtemp.o
nasm -o $tempfile -f elf $1
gcc $tempfile -o $outfile
rm $tempfile -f
#EOF ==================================================================


Le Basi
----------

La cosa migliore per partire, naturalmente, è un esempio, prima di immergerci nei dettagli dell'OS. Ecco qui un programma "hello-world" davvero semplice:

 
; asmhello.asm ========================================================
global main
extern printf

section .data
msg db "Helloooooo, nurse!",0Dh,0Ah,0
section .text
main:
push dword msg
call printf
pop eax
ret
; EOF =================================================================


Una spiegazione veloce: il "global main" deve essere dichiarato global—-e dal momento che stiamo usando il linker GCC, l'entrypoint deve essere chamato "main"--per il loader dell'OS.
L'"extern printf" è semplicemente una dichiarazione per la call successiva nel programma; nota che questo è tutto il necessario; non è necessario dichiarare le dimensioni dei parametri. Ho diviso questo esempio nelle sezioni standard .data e .text, sebbene ciò non sia strettamente necessario –-chiunque potrebbe svignarsela con il solo segmento .text, proprio come in DOS.

Nel corpo del codice, nota che devi pushare i parametri alla call, e in Nasm devi dichiarare la dimensione di tutti i dati ambigui (p.e. non-register): di qui il qualificatore "dword". Nota che come in altri assemblatori, Nasm assume che ogni reference memory/label è volta a significare l'indirizzo della locazione di memoria o della label, non il loro contenuto. Perciò, per specificare l'indirizzo della stringa 'msg' scriveresti 'push dword msg', mentre per specificare il contenuto della stringa 'msg' scriveresti 'push dword [msg]' (nota che questo conterrà solo i primi 4 bytes di 'msg'). Dal momento che printf richiede un pointer alla string, specificheremo l'indirizzo di 'msg'.

La call a printf è abbastanza lineare. Considera che pulire lo stack dopo ogni call che esegui (vedi sotto); quindi, avendo PUSHato una dword, POPpiamo una dword dallo stack in un registro "da cestinare". I programmi Linux si chiudono semplicemente con una RET all'OS, dato che ogni processo è aperto dalla shell (o PID 1 ;) e finisce restituendogli il controllo.

Nota che in Linux fai uso delle librerie standard condivise fornite con l'OS in luogo di una "API" di degli Interrupt Services. Tutte le reference esterne saranno risolte dal linker GCC, in modo da alleggerire buona parte del carico di lavoro del programmatore asm. Una volta che ti sei abituato alle stranezze di base, il coding in assembler in Linux è davvero più semplice di quello su una macchina DOS-based!


La sintassi di chiamata C
--------------------

Linux usa la convenzione di chiamata C -– ciò significa che gli argomenti sono pushati nello stack in ordine inverso (l'ultimo arg per primo), e che il caller deve pulire lo stack. Puoi far ciò o poppando i valori dallo stack:

 
push dword szText
call puts
pop ecx
o modificando direttamente ESP:
push dword szText
call puts
add esp, 4


I valori restituiti dalla call si trovano in eax o in edx:eax se il valore è più grande di 32-bit. EBP, ESI, EDI, e EBX sono tutti salvati e ripristinati dal caller.
Nota che devi conservare tutti i registri che usi come illustra il codice seguente:

 
; loop.asm =================================================================
global main
extern printf
section .text
msg db "HoodooVoodoo WeedooVoodoo",0Dh,0Ah,0
main:
mov ecx, 0Ah
push dword msg
looper:
call printf
loop looper
pop eax
ret
; EOF ======================================================================


A primo acchito questo sembra molto semplice: dal momento che stai per usare la stringa nelle call 10 printf(), non hai bisogno di ripulire lo stack. Tuttavia quando lo compili, il loop non si ferma mai. Perché? Perchè da qualche parte nella call printf()ECX è usato e non salvato. Quindi per far funzionare il tuo loop a dovere, devi salvare il valore del contatore in ECX prima della call e ripristinarlo dopo, così:

 
; loop.asm ================================================================
global main
extern printf

section .text
msg db "HoodooVoodoo WeedooVoodoo",0Dh,0Ah,0
main:
mov ecx, 0Ah
looper:
push ecx ;salva Count
push dword msg
call printf
pop eax ;pulisce lo stack
pop ecx ;ripristina Count
loop looper
ret
; EOF ======================================================================


Programmazione della Porta I/O
--------------------------------

E per avere un accesso diretto all'hardware? In Linux hai bisogno di un driver kernel-mode per fare ogni cosa che sia davvero ingegnosa... ciò significa che il tuo programma finirà per essere di due parti, una kernel-mode che fornisce le funzionalità direct-hardware, l'altra user-mode per una interface. La buona notizia è che puoi ancora accedere alle porta usando i comandi IN/OUT da un programma user-mode.

L'accesso alle porte I/O al tuo programma deve essere concesso da un permesso dell'OS; per far ciò, devi compiere una call ioperm(). Questa funzione può essere chiamata solo da un utente root, quindi devi o setuid() il programma come root oppure eseguire il programma da root. La ioperm() ha la sintassi seguente:

 
ioperm( long StartingPort#, long #Ports, BOOL ToggleOn-Off)


dove 'StartingPort#' specifica il numero della prima porta da accedere (0 is port 0h, 40h is port 40h, etc), '#Ports' specifica quante porte accedere (i.e., 'StartingPort# = 30h' e '#Ports = 10' concederebbero l'accesso alle porte 30h-39h), e 'ToggleOn-Off' consente l'accesso se TRUE (1) o lo disabilita se FALSE (0).

Una volta che la call a ioperm() è compiuta, si può accedere alle porte richieste come normal. Il programma può chiamare ioperm() un qualsivoglia numero di volte e non ha bisogno di fare un successiva call ioperm() (anche se l'esempio sotto lo fa) [siccome l'OS si curerà di ciò].

 
; io.asm ====================================================================
BITS 32
GLOBAL szHello
GLOBAL main
EXTERN printf
EXTERN ioperm

SECTION .data
szText1 db 'Enabling I/O Port Access',0Ah,0Dh,0
szText2 db 'Disabling I/O Port Acess',0Ah,0Dh,0
szDone db 'Done!',0Ah,0Dh,0
szError db 'Error in ioperm() call!',0Ah,0Dh,0
szEqual db 'Output/Input bytes are equal.',0Ah,0Dh,0
szChange db 'Output/Input bytes changed.',0Ah,0Dh,0

SECTION .text

main:
push dword szText1
call printf
pop ecx
enable_IO:
push word 1 ; enable mode
push dword 04h ; 4 porte
push dword 40h ; inizia dalla porta 40
call ioperm ; Deve essere SUID "root" per questa call!
add ESP, 10 ; pulisci lo stack (metodo 1)
cmp eax, 0 ; controlla i risultati di ioperm()
jne Error

;---------------------------------------Port Programming Part--------------
SetControl:
mov al, 96 ; R/W low byte di Counter2, mode 3
out 43h, al ; porta 43h = control register
WritePort:
mov bl, 0EEh ; valore da inviare allo speaker timer
mov al, bl
out 42h, al ; porta 42h = speaker timer
ReadPort:
in al, 42h
cmp al, bl ; il byte dovrebbe essere cambiato--questo E' un timer :)
jne ByteChanged
BytesEqual:
push dword szEqual
call printf
pop ecx
jmp disable_IO
ByteChanged:
push dword szChange
call printf
pop ecx
;---------------------------------------End Port Programming Part----------

disable_IO:
push dword szText2
call printf
pop ecx
push word 0 ; disable mode
push dword 04h ; 4 porte
push dword 40h ; parte dalla porta 40h
call ioperm
pop ecx ;pulisci lo stack (metodo 2)
pop ecx
pop cx
cmp eax, 0 ; controlla i risultati di ioperm()
jne Error
jmp Exit
Error:
push dword szError
call printf
pop ecx
Exit:
ret
; EOF ======================================================================


Usare gli Interrupts In Linux
-------------------------

Linux è un ambiente shared-library in protected mode, il che significa che non ci sono i servizi interrupt. Giusto?

Sbagliato. Ho notato una call a INT 80 sul codice di alcuni esempi GAS con il commento "sys_write(ebx, ecx, edx)". Questa funzione è parte della syscall dell'interfaccia di Linux, e cioè l'interrupt 80 deve essere un gate ai servizi di syscall. Girovagando nel codice sorgente di Linux (e ignorando gli avvisi di NON USARE MAI l'interface INT 80 siccome i numeri della funzione potrebbere essere cambiati all'improvviso), ho trovato i "system call numbers" –-che indicano la funzione da passare a INT 80 per ogni routine di syscall–- nel file UNISTD.H. Ce ne sono 189, quindi non li elencherò qui... ma se ti accingi a programmare in Linux assembly, fa' un favore a te stesso e stampa questo file.

Quando chiami INT 80h, eax deve contenere il numero della funzione desirata. Tutti i parametri alla routine syscall devono trovarsi nei seguenti registri in questo ordine:

 
ebx, ecx, edx, esi, edi


quindi il parametro uno si trova in ebx, il parametro 2 in ecx, ecc. Nota non si usa lo stack per passare i valori alla routine syscall. Il risultato della call sarà restituito in eax.

Inoltre, l'interfaccia INT 80 è uguale ad una normale call (solo un po' più divertente ;). Il programma seguente dimostra una semplice call a INT 80h in cui il programma controlla e visualizza la sua PID. Nota l'uso del formato della stringa di printf() –-è meglio psuedocodarlo come una call C prima, poi rendere il formato della stringa DB e pushare ogni variabile passata (%s, %d, ecc). La struttura C per questa call sarebbe

 
printf( "%d\n", curr_PID);


Nota anche che le sequenze di escape ("\n") non sono tutte davvero attendibili in assembly; ho dovuto usare i valori hex (0Ah,0Dh) per il CR\LF.

 
;pid.asm====================================================================
BITS 32
GLOBAL main
EXTERN printf

SECTION .data
szText1 db 'Getting Current Process ID...',0Ah,0Dh,0
szDone db 'Done!',0Ah,0Dh,0
szError db 'Error in int 80!',0Ah,0Dh,0
szOutput db '%d',0Ah,0Dh,0 ;la strana formattazione è per printf()

SECTION .text
main:
push dword szText1 ;messaggio di apertura
call printf
pop ecx
GetPID:
mov eax, dword 20 ; getpid() syscall
int 80h ; syscall INT
cmp eax, 0 ; non sarà mai PID 0 ! :)
jb Error
push eax ; passa il valore restituito a printf
push dword szOutput ; passa il formato della stringa a printf
call printf
pop ecx ; pulisci lo stack
pop ecx
push dword szDone ; messaggio di chiusura
call printf
pop ecx
jmp Exit
Error:
push dword szError
call printf
pop ecx
Exit:
ret
; EOF =====================================================================


Ultime considerazioni
-----------------------

Il più dei problemi deriverà dall'abituarsi a Nasm stesso. Mentre nasm non è fornito di una man page, non la installa per default, quindi devi spostarla (cp or mv) da

 
/usr/local/bin/nasm-0.97/nasm.man


in

 
/usr/local/man/man1/nasm.man


La formattazione è un po' incasinata, ma è facilmente risistemata usando le direttive nroff. Non ti dà ancora tutta la documentazione di Nasm, comunque; per questo, copia nasmdoc.txt da

 
/usr/local/bin/nasm-0.97/doc/nasmdoc.txt


in

 
/usr/local/man/man1/nasmdoc.man


Ora puoi chiamare man page di nasm con 'man nasm' e la documentazione di nasm con 'man nasmdoc'.

Per ulteriori informazioni, controlla i seguenti:
Linux Assembly Language HOWTO
Linux I/O Port Programming Mini-HOWTO
Jan's Linux & Assembler HomePage (bewoner.dma.be/JanW/eng.html)

Devo anche dei ringraziamenti a Jeff Weeks alla code^x software (gameprog.com/ codex)
per avermi inviato un paio di hello-world di GAS nelle giornate nere, prima che trovassi la pagina di Jan.

 
::/ \::::::.
:__\:::::::.
/| \::::::::.
:|
\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................ISSUE.CHALLENGE
11-byte Program Displays Its Command-Line
by Xbios2


La Sfida
-----------

Scrivi un programma di 11 byte che visualizzi la sua command line.


La Soluzione
--------------

Prima di dire che questi programmi non funzionano, provali. Alcuni di loro funzionano solo dopo averli avviati due volte. In ogni caso, sono stati testati sia sotto Windows che in DOS puro, e funzionano. Che tu ci creda o no, questi sono i primi programmi che ho scritto in DOS, quindi ho solo provato alcune idee finchè alcune hanno funzionato, anche se ho pensato che non potessero... :)

La command line in DOS si trova nel PSP (Program Segment Prefix, Prefisso di Segmento del Programma, NdT) che nei file .COM occupa i primi 100h bytes nel segmento. All'offset 80h, una stringa (il primo byte è la lunghezza della stringa, e n bytes seguono) contiene tutto ciò che è stato digitato dopo il nome del file. L'ultimo carattere nella stringa è CR (carriage return, invio NdT).

I programmi richiesti dovrebbero essere composti di tre parti:

1. settaggio dei pointers ai dati
2. visualizzazione dei dati
3. uscita

In effetti tutti i programmi seguenti NON includono la parte 3, ma continua a leggere. I dati (command line) possono essere scritti o come una singola stringa, o carattere per carattere.


APPROCCIO 1: Scrivi una singola stringa
------------------------------------------

Per il primo approccio ci sono 2 interrupts:

 
1. INT 21, 9 ; scrivi una stringa string '$ terminated'
2. INT 21, 40 ; scrivi sul file usando un handle


Nel primo caso, la parte 2 sarebbe:

 
mov ah, 9
mov dx, 81h
int 21h


che sono 7 bytes, lasciando solo 4 bytes per sostituire l'ultimo CR con un '$',
che sono troppo pochi. (Effettivamente, se l'utente digitasse un $ come ultimo carattere nella comand line, questo sarebbe il programma più piccolo possibile.) Il programma più piccolo che son riuscito a scrivere è:

 
shr si,1 ; D1 EE
lodsb ; AC
push si ; 56
add si,ax ; 03 F0
mov byte ptr [si],'$' ; C6 04 24
xcgh bp,ax ; 95
pop dx ; 5A
int 21 ; CD 21


Per il secondo caso, il più piccolo programma sarebbe questo:

 
; Solution I
mov dx, 81h ; BA 81 00
mov cl, ds:[80h] ; 8A 0E 80 00
mov ah, 40h ; B4 40
int 21h ; CD 21


Le prime due righe sono la parte 1 (settaggio dei pointers) e le altre due sono la parte 2 (visualizzazione della stringa). Se pensi che manca qualcosa, hai ragione: non settiamo BX (l'handle).


APPROCCIO 2: Scrivi char per char
------------------------------

Per il secondo approccio ci sono interrupts:

 
1. INT 21, 2 ; scrivi il char in dl
2. INT 29 ; scrivi il char in al


Naturalmente il secondo interrupt è meglio, dal momento che non c'è bisogno di caricare ah con il valore di una funzione. In più, INT 29 legge il char da AL, quindi può essere usato con LODSB.

Il primo modo per implementare questo approccio è ridurre la parte 2 (display loop).
Un programma che fa ciò è il seguente:

 
; Solution II
mov si, 80h ; BE 80 00
lodsb ; AC
mov cl, al ; 8A C8
more: lodsb ; AC
int 29h ; CD 29
loop more ; E2 FB


Questo programma ha scritto CX caratteri. Il secondo modo per scrivere la stringa è scrivere fino al CR. Ecco come:

 
; Solution III
mov si, 81h ; BE 81 00
more: lodsb ; AC
int 29h ; CD 29
cmp al, 13 ; 3C 0D
jne more ; 75 F9
nop ; 90


Si, l'ultima istruzione E' un NOP. Quindi abbiamo un programma di 11-byte che funziona, e ha anche un NOP in sè. Rimuovendo il NOP si crea un programma ancora più pazzo di 10 bytes, che visualizza la sua command line E aspetta la pressione di un tasto prima di terminare... In realtà la soluzione II, sostituendo MOV SI,80h con SHR SI,1, fa la stessa cosa (10 bytes che visualizza la command line e aspetta che l'utente prema un tasto).

BTW: Davvero non so perchè questi programmi funzionano, sebbene abbia una o due teorie...


La sfida per il prossimo numero
---------------------------------

Scrivi un programma PE (win32) il più piccolo possibile che visualizzi la sua command line.

 
::/ \::::::.
:__\:::::::.
/| \::::::::.
:|
\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::........................................................FIN

#############################################

Per questa traduzione (e per le altre) dell'APJ mi è stata lasciata l'autorizzazione personale di +mammon

Colgo l'occasione per salutare tutto il crew di RingZ3r0 e di #crack-it ,
particolari ringraziamenti a Neural_Noise per avermi concesso le sue traduzioni dei tutorials di Iczelion (disponibili nelle pagine di ringzer0)

Little-John

#############################################

← previous
next →

Comments

1
guest's profile picture
@guest

cbd hemp oil cbd oil cbd oil cbd oil cbd oil cbd gummies

kentucky farms cbd oil cbd todohemp [url=https://cbdoilnumber1.com]cbd oil [/url] cbd oil cbd oil

13 Jan 2020
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT