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

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