ASM Help sintassi assembly

TheWorm91

Helper
31 Marzo 2022
426
49
204
317
Domanda per esperti di ASM...

Mi sto ristudiando da zero il linguaggio assembly, per ora ho visto che esistono due "formati" per scrivere le istruzioni ovvero la sintassi Intel (che dovrebbe essere la più usata) e la sintassi AT&T.
Poi ogni architettura ha la sua sintassi specifica e vorrei approfondire questo discorso, per esempio le istruzioni sono leggermente diverse tra i compilatori TASM, MASM NASM... Su quale variante di assembly conviene imparare per capire tutte le varianti? qual è la più diffusa?
Per il momento sto facendo delle prove su windows tramite dosbox e TASM anche se sto guardando diversi pdf e ognuno scrive i programmi in maniera differente e questo mi manda un po' fuoristrada.
Ad esempio per scrivere un banale hello world a schermo ho trovato queste sintassi diverse:
Questa è quella che sto usando nelle prove con TASM
Codice:
DATA SEGMENT PUBLIC
    FRASE DB "Hello World!",10,13,'$' ;FRASE = ETICHETTA
DATA ENDS ; fine segmento dati

CODE SEGMENT ;codice vero e proprio del programma
    ASSUME CS:CODE,DS:DATA ;si associa l'etichetta al segmento
    
INIZIO: ;inizio del programma
    ;caricamento dei dati
    MOV AX,DATA
    MOV DS,AX
    MOV AH,09H ;richiesta  di stampa della frase
    MOV DX, OFFSET FRASE ;caricamento offset della frase da stamparare (passo l'indirizzo della frase a DX)
    INT 21H ;interrupt per la stampa
    ;chiusura
    MOV AH,4CH ;richiesta di chiusura del programma
    INT 21H ;interrupt per chiudere il programma
CODE ENDS ;fine del segmento codice
END INIZIO ; fine del programma
questi invece sono altri due esempi del solito programma:

Codice:
hello: DB "Hello World" ; store string
; actual entry point of the program, must be present
start:
MOV AH, 0x13            ; move BIOS interrupt number in AH
MOV CX, 11              ; move length of string in cx
MOV BX, 0               ; mov 0 to bx, so we can move it to es
MOV ES, BX              ; move segment start of string to es, 0
MOV BP, OFFSET hello    ; move start offset of string in bp
MOV DL, 0               ; start writing from col 0
int 0x10                ; BIOS interrupt

Codice:
segment .text    ;code segment
   global _start ;must be declared for linker
_start:          ;tell linker entry point
   mov edx,len   ;message length
   mov ecx,msg   ;message to write
   mov ebx,1     ;file descriptor (stdout)
   mov eax,4     ;system call number (sys_write)
   int 0x80      ;call kernel
   mov eax,1     ;system call number (sys_exit)
   int 0x80      ;call kernel
segment .data    ;data segment

msg    db 'Hello, world!',0xa   ;our dear string
len    equ    $ - msg           ;length of our dear string

A prima vista ho notato che cambiano i codici per eseguire il solito interrupt (forse per l'architettura hw diversa?)
mi sarebbe molto d'aiuto capire le differenze tra questi codici per chiarirmi meglio le idee.

Grazie per l'aiuto!
 
Non sono l'esperto che stai cercando, però provo a risponderti comunque.

Poi ogni architettura ha la sua sintassi specifica e vorrei approfondire questo discorso, per esempio le istruzioni sono leggermente diverse tra i compilatori TASM, MASM NASM... Su quale variante di assembly conviene imparare per capire tutte le varianti? qual è la più diffusa?
Scegli un'architettura e impara la sintassi che ti sembra più comprensibile, tanto sono tutte equivalenti. Da quanto ne so la gente tende a preferire la sintassi Intel (e.g., NASM), ma tieni conto che non sono in tanti a programmare direttamente in assembly. Generalmente è più importante saperlo leggere che saperlo scrivere, e il più delle volte il codice che andrai a leggere è comunque troppo lungo e intricato per essere studiato riga per riga e te la caverai usando un buon debugger e un po' di intuito. Il discorso è un po' diverso se ti addentri in settori particolari; per esempio, se vuoi diventare bravo con gli shellcodes.

A prima vista ho notato che cambiano i codici per eseguire il solito interrupt (forse per l'architettura hw diversa?)
Non dipende solo dall'architettura, ma anche dal sistema operativo. In Linux il system call interrupt è mappato su 0x80, ma se ti scrivi un sistema operativo tutto tuo puoi scegliere anche un altro numero. In Windows mi pare di aver capito che questa cosa di usare direttamente l'istruzione int sia sconsigliato in quanto è tutta roba non documentata (e, potenzialmente [anche se improbabile], soggetta a cambiamenti). In DOS il system call interrupt è mappato su 0x21, ed ecco spiegato perché il tuoi primo esempio è diverso dal tuo terzo esempio. In ogni caso, è meglio usare l'istruzione syscall.

Il secondo esempio usa 0x10 e il tuo commento dice che si tratta del BIOS interrupt. In realtà, al giorno d'oggi, i primi 32 interrupts sono riservati per le eccezioni (info) quindi non può essere il BIOS interrupt. Il codice non è sbagliato, ma è pensato essere eseguito in real mode. In parole semplici real mode vuol dire "prima del 1982" e protected mode vuol dire "dopo il 1982". Per questioni di retrocompatibilità, quando accendi il tuo computer la tua CPU x86_64 parte come se fosse nata nel 1978 e tu gli devi abilitare (tramite assembly) tutte le menate che gli servono per fargli capire che in realtà siamo nel 2023 e il mondo ormai è cambiato.

Se vuoi più info su come sono gestite le syscall, ti rimando a questo thread dove mi ero messo a cercare anche l'implementazione delle varie funzionalità all'interno del kernel Linux.
 
  • Grazie
Reazioni: TheWorm91
@St3ve grazie per la risposta precisa, è quello che mi interessava sapere.
Mi leggerò i thread che hai linkato, il mio obiettivo è di capire la struttura delle istruzioni per imparare a fare reverse engineering.
 
Mi aggiungo a St3ve nella risposta.

Domanda per esperti di ASM...

Mi sto ristudiando da zero il linguaggio assembly, per ora ho visto che esistono due "formati" per scrivere le istruzioni ovvero la sintassi Intel (che dovrebbe essere la più usata) e la sintassi AT&T.
Si, in genere si parla di "sintassi". Sotto Linux vedrai più spesso AT&T.

Poi ogni architettura ha la sua sintassi specifica e vorrei approfondire questo discorso, per esempio le istruzioni sono leggermente diverse tra i compilatori TASM, MASM NASM... Su quale variante di assembly conviene imparare per capire tutte le varianti? qual è la più diffusa?

Oddio, parlare di "diffuso" al giorno d'oggi è arduo :p Anche se in certi contesti lo si vede ancora, ed è anche necessario (o almeno aiuta conoscerlo).

Comunque dipende che target hai: sotto Linux ti direi NASM, sotto Windows MASM. E qui sotto Win ti si apre un altro topic: puoi usare l'assembler distribuito da Microsoft (ml64.exe, lo trovi in Visual Studio) oppure usare l'SDK di MASM64. Nel secondo caso hai a disposizione tantissime Macro. Per farti un esempio pratico, questo lo scrissi usando MASM32, usando un pò di quelle Macro https://github.com/DispatchCode/BMPInfo
print, invoke e .IF, .WHILE sono un esempio di alcune di quelle macro.

L'ultima volta che ci ho smanettato però ho usato l'assembler di Microsoft, ma non ho pubblicato il codice (era un'implementazione stupida di Mastermind xD). Ce l'ho ancora però, se vuoi posso anche postarla domani o stasera.

Per il momento sto facendo delle prove su windows tramite dosbox e TASM anche se sto guardando diversi pdf e ognuno scrive i programmi in maniera differente e questo mi manda un po' fuoristrada.
Ad esempio per scrivere un banale hello world a schermo ho trovato queste sintassi diverse:
Questa è quella che sto usando nelle prove con TASM
Codice:
DATA SEGMENT PUBLIC
    FRASE DB "Hello World!",10,13,'$' ;FRASE = ETICHETTA
DATA ENDS ; fine segmento dati

CODE SEGMENT ;codice vero e proprio del programma
    ASSUME CS:CODE,DS:DATA ;si associa l'etichetta al segmento
   
INIZIO: ;inizio del programma
    ;caricamento dei dati
    MOV AX,DATA
    MOV DS,AX
    MOV AH,09H ;richiesta  di stampa della frase
    MOV DX, OFFSET FRASE ;caricamento offset della frase da stamparare (passo l'indirizzo della frase a DX)
    INT 21H ;interrupt per la stampa
    ;chiusura
    MOV AH,4CH ;richiesta di chiusura del programma
    INT 21H ;interrupt per chiudere il programma
CODE ENDS ;fine del segmento codice
END INIZIO ; fine del programma
questi invece sono altri due esempi del solito programma:

Codice:
hello: DB "Hello World" ; store string
; actual entry point of the program, must be present
start:
MOV AH, 0x13            ; move BIOS interrupt number in AH
MOV CX, 11              ; move length of string in cx
MOV BX, 0               ; mov 0 to bx, so we can move it to es
MOV ES, BX              ; move segment start of string to es, 0
MOV BP, OFFSET hello    ; move start offset of string in bp
MOV DL, 0               ; start writing from col 0
int 0x10                ; BIOS interrupt

Codice:
segment .text    ;code segment
   global _start ;must be declared for linker
_start:          ;tell linker entry point
   mov edx,len   ;message length
   mov ecx,msg   ;message to write
   mov ebx,1     ;file descriptor (stdout)
   mov eax,4     ;system call number (sys_write)
   int 0x80      ;call kernel
   mov eax,1     ;system call number (sys_exit)
   int 0x80      ;call kernel
segment .data    ;data segment

msg    db 'Hello, world!',0xa   ;our dear string
len    equ    $ - msg           ;length of our dear string

A prima vista ho notato che cambiano i codici per eseguire il solito interrupt (forse per l'architettura hw diversa?)
mi sarebbe molto d'aiuto capire le differenze tra questi codici per chiarirmi meglio le idee.

Grazie per l'aiuto!
Qui ti ha già risposto anche St3ve, ma il primo è per DOS; l'altro magari anche sotto Linux è compilabile. Il primo al giorno d'oggi lo esegui su un Windows 32bit oppure su un emulatore come DOSBox.

Il terzo però fa uso di cpu 386 come minimo (registri a 32bit) ed è per Linux.

Al giorno d'oggi, se guardi l'asm nuovo (come quel mio esempio li sopra) non vedrai più un codice come il primo; ora come ora è impossibile eseguire una cosa come quella. Tutte le chiamate alle sys call passano attraverso l'API di Windows oppure usando come una libreria come msvrcrt, dove richiami per esempio la printf (come in C), che internamente si appoggia a qualche sys call di Windows (WriteFile, probabilmente).

Con DOS funzionava in quel modo poichè si usava la IVT (Interrupt Vector Table) dove a ogni indice corrispondeva una diversa interrupt service routine. Poi con il registro AH selezionavi quale funzionalità ti servivano; il vettore maggiormente usato era 21h.

Consiglio: parti da x86 e non da 8086. Scegli se stare su Linux o Windows e poi vai avanti con quello; io di solito ci smanetto per cazzeggio sotto Windows, sotto Linux però mi capita spesso di leggerlo.
Se parti da DOS ti riempi la testa di concetti ormai obsoleti (come la suddivisione in segmenti e l'uso di segmenti come avveniva prima), le chiamate alle interrupt e altre cose. Poi, se lo fai perchè lo userai a scuola/Uni, è un altro discorso allora.
 
  • Grazie
Reazioni: TheWorm91