Inline Assembly per Linux x86 e get_current(void)

Data

by "Quequero"

 

16/Oct/2002

UIC's Home Page

Published by Quequero


Quando qualcosa puo' esser concepita in maniera criptica...


Non posso che dedicare questo tutorial alla persona che piu` amo e stimo su questa terra, vale a dire: la mia dolce e tenerissima meta' :*, senza la quale la mia vita prenderebbe di certo un aspetto peggiore, ti voglio bene tesoro sei quanto di piu' puro e bello mi sia mai stato donato :*

...Sara' estremamente piu' criptica di quanto l'avrebbe immaginata il piu' pessimista dei pensatori.

2^ Legge di Que

....

Home page: http://www.quequero.tk
E-mail: quequero (at) bitchx (dot) it
on #[email protected]

....

Difficoltà

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

 

L'ibrido asm/C e' qualcosa di estremamente potente, i programmatori l'hanno capito e hanno creato l'inline assembly... Sebbene sia estremamente ostico c'e' da dire che e' anche molto molto potente, percio' vale la pena di studiarlo


Inline Assembly per Linux x86 e get_current(void)

Written by Quequero

Introduzione

Impariamo ad utilizzare nei nostri listati questo interessante connubbio

Tools usati

gcc vim e una shell

Essay

Guardate qui:

__asm__ __volatile__ ("cld; rep; movsl;" : : "S" (src), "D" (dest), "c" (count));

Cos'e'? Lo capirete a breve...

Sintassi AT&T vs INTEL

 

AT&T
INTEL
movl %eax, %ebx
mov ebx, eax
movl $50, %ecx
mov ecx, 50
movl $0x100, %eax
mov eax, 100h
movb (%eax), bl
mov bl, [eax]
movswl %bx, %edx
movsx edx, bx
movl 4(%esp), %eax
mov eax, [esp+4]
movl (%ebx,%eax,$4), %edx
mov edx, [ebx+eax*4]
movb $4, %fs:(%eax)
mov fs:eax, 4
movl _array(,%eax,$4), %eax
mov eax, [array+eax*4]
movw _array(%ebx,%eax,$4), %cx
mov cx, [array+ebx+eax*4]
cbtw
cbw
cwtl
cwde
cwtd
cwd
cltd
cdq

Mi rendo conto che non sia la piu' splendida delle sintassi, specie per chi e' abituato a quella intel, ma bisogna riconoscere che reca alcuni grossi vantaggi (si lo so che potevano risparmiarsi tutti i % e tant'altro, ma non siamo noi a inventare le sintassi purtroppo :) ad ogni modo vi stilo le golden rules della sintassi AT&T in questo modo vi sara' estremamente facile comprenderla:

1. Le istruzioni seguono quest'ordine: "istruzione sorgente, destinazione" al contrario della intel "istruzione destinazione, sorgente", 1 punto a favore della sintassi AT&T.
2. I registri sono sempre preceduti dal segno % le costanti dal segno $ e per indicare un numero esadecimale si usa la notazione $0x1234, nella sintassi intel i registri si chiamano per nome, le costanti sono numeri e basta e possiamo avvalerci sia della notazione 0x1234 che di quella 1234h, 1 punto a favore della sintassi Intel.
3. Ad ogni istruzione va accodata:
una l se parliamo di dword (32-bit) (movl, addl, subl),
una w se parliamo di word (16-bit) (movw, addw, subw),
una b se parliamo di byte (8-bit) (movb, addb, subb),
quindi se dobbiamo muovere una dword da eax in ebx scriveremo: "movl %eax, %ebx", se dobbiamo muovere un byte semplicemente:
"movb %al, %bl,
questo logicamente vale anche per le referenze alla memoria, quindi se dobbiamo muovere un byte puntato da eax in ebx faremo cosi:
"movb (%eax), %ebx"
come vedete per indicare i valori che si trovano in memoria utilizziamo le parentesi tonde invece che le quadre. In questo caso e' difficile dire quale delle due notazioni sia migliore, da una parte ci evitiamo di scrivere "dword ptr, word ptr, o semplicemente: word, dword" quando serve, ma dall'altra ci tocca assegnare una letterina a quasi tutti gli operatori, questo sarebbe poco problematico fino a che non vengono usate istruzioni come movsx movzx, oppure con tutte le istruzioni come loop, loopnz, loopne, loopz etc... Perche' in questo caso non va accodato nulla all'istruzione, il connubbio migliore secondo me e' quello che usa il nasm (vero albe? :p) dal momento che vuole solo sapere se dobbiamo muovere byte, word o dword senza aggiungere ptr e cavoli vari (come nel Tasm) percio' direi che e' un punto a favore della sintassi Intel.
4. Se utilizziamo istruzioni come movsx, movzx... Non possiamo farlo in maniera _nativa_ dobbiamo utilizzare una notazione differente, la prima parte dell'istruzione resta invariata, percio' movs resta movs e movz resta movz, la seconda parte cambia perche' al compilatore serve sapere la grandezza del Sorgente e quella della Destinazione (movsSD, movzSD) quindi:
"movsx edx, bx" diventa
"movswl %bx, %edx"
indubbiamente un punto a favore della sintassi Intel che almeno ci consente di utilizzare le istruzione per quello che realmente sono.
5. Se per spostare un valore puntato da un registro da qualche parte basta utilizzare le parentesi tonde, non e' altrettanto banale muovere un valore puntato + una costante, se nella sintassi Intel si usa semplicemente:
"mov eax, dword [ebx+4]"
nella sintassi AT&T si fa cosi:
"movl 4(%ebx), %eax"

E se dovessimo fare una cosa del tipo:
"
mov eax, dword [ebx*4]"
Dovremmo fare niente di meno intuitivo di:

"movl (%ebx, $4), %eax"

proviamo addirittura a combinare le due cose e traduciamo:
"mov ecx, dword [ebp*4+eax]"
diventa:
"movl (%eax, %ebp, $4), %ecx"

Ma l'apice del masochismo lo raggiungeremmo se dovessimo fare:
"mov cx, [array+ebx+eax*4]"
che diventerebbe:
"movw _array(%ebx,%eax,$4), %cx"

Gia', non sono un grande fan della sintassi AT&T, anzi la reputo davvero qualcosa di tremendamente criptico e sinceramente non capisco perche' sia stata concepita in questa maniera (forse solo per attenersi alla seconda legge di Que), ma visto che c'e' ed e' piuttosto utilizzata sotto ogni *nix, ci tocca conoscerla :)

Un po` di Inline Assembly


Adesso che siete dei guru della splendida sintassi AT&T (erm...) possiamo spiegare come si utilizza l'inline assembly nei nostri sorgenti: prendiamo un listato di questo tipo:

inline.c:
main(){

   int a=10, b;
   a += 5;
   b = a;

   printf("b vale %d\n", b);
}


compiliamolo e vediamo cosa succede:

quequero@panther:~$ gcc inline.c -o inline && ./inline
b vale: 15


bene, sembra funzionare! ;p immaginiamo adesso di voler fare quel calcolo in asm, la prima cosa che ci interessa sapere e' la sintassi dell'inline assembly, che si presenta cosi:

__asm__ ( assembler template
: output operands (optional)
: input operands (optional)
: list of clobbered registers (optional)
);


Ok, il motivo per cui si usa l'inline asm e' che altrimenti sarebbe impossibile far interagire le variabili C con il nostro codice assembler, e se alle volte ci servisse di utilizzare l'asm in routine critiche, ci sarebbe impossibile farlo, percio' traduciamo in pseudo-asm le operazioni di sopra e mettiamole nel sorgente:

addl a, 5             ; a = 15
movl a, %ebx          ; ebx = a
movl %ebx, b          ; il risultato in b


il problema principale e' che non ci e' consentito di fare il primo, secondo e terzo passaggio per ovvi motivi ;) cosi ricorriamo all'inline, il primo parametro "assembler template" non e' altro che il nostro set di istruzioni, "output operands" sono gli operandi nei quali finiranno i risultati delle operazioni fatte nell'assembler template (e' opzionale perche' non e' detto che dobbiamo per forza andare a mettere il risultato in qualche posto, un nop ad esempio non ha output) l'altro parametro sono gli "input operands" (opzionale per lo stesso motivo) e il terzo sono i "clobbered registers" ovvero i registri modificati, anche questo e' opzionale dal momento che non e' detto che andremo a modificare qualcosa, oppure possiamo salvare lo stato dei registri con push e pop e quindi di fatto nulla verrebbe modificato. Adesso quindi possiamo tradurre in inline asm le nostre operazioni:

__asm__ ("
movl %1, %%eax;
addl $5, %%eax;
movl %%eax, %0;
"

: "=r" (b)
: "r" (a)
: "%eax"
);

Non vi spaventate e' tutto molto semplice, facciamo un paio di precisazioni, all'interno dell'inline i registri vanno chiamati con due %% invece che con uno, questo perche con un solo % vengono indicate le nostre variabili. Vi ho colorato il listato per rendervene piu' semplice la comprensione, dunque, in rosso troviamo il nostro "assembler template", in blu "output register" in verde "input register" in giallo i "clobbered registers". La prima perplessita' dovrebbe esser rappresentata da quegli %1 e %0, per capire cosa sono dovete andare nell'input register, e vedete che c'e' (b), quella e' la variabile %0, una riga sotto troviamo (a) e quella e' la variabile %1, logicamente (b) e' l'int `b` e (a) l'int `a`. Eax e' invece il registro che alla fine della routine sara' stato modificato, questo serve al gcc in fase di compilazione per consentirgli o di non usare eax se possibile, oppure di salvare il risultato di eax prima di entrare nella routine e recuperarlo dopo, se non specificassimo che eax e' un registro modificato potremmo avere seri problemi, infatti eax potrebbe contenere un valore importante che di conseguenza andrebbe perso.
Esaminiamo ora riga per riga quello che e' successo:

movl %1, %%eax; <- la variabile %1 ovvero `a` viene messa in eax
addl $5, %%eax;
<- quindi le viene sommato 5 (a+5)
movl %%eax, %0;
<- e viene spostata dentro la variabile %0 ovvero `b`

Questo e' abbastanza semplice, passiamo agli output registers:

"=r" (b)

tra le virgolette il parametro `=` significa che il registro che stiamo usando e' un registro di OUTPUT, e' fondamentale mettere il simboletto `=` li, `r` sta ad indicare che alla variabile `b` possiamo associare un qualunque registro (eax, ebx, ecx, edx), `(b)` e' ovviamente la variabile alla quale assegneremo il registro ed essendo la prima specificata sara' anche la variabile %0. Riassumento quella riga vuol dire: assegna all'intero `b` un registro qualunque e ricordati di contrassegnare quel registro come registro di uscita.

"r" (a)

non essendo un output register, non abbiamo bisogno del simbolo `=`, questa riga vuol dire: assegna all'intero `a` un registro a piacere, logicamente essendo `a` la seconda variabile definita dentro il nostro inline, sara' anche %1.

"%eax"

questa vuol dire: gcc mentre compili ricordati che in eax non sai cosa ci troverai dal momento che viene usato dall'utente, percio' attento (nella lista dei clobbered register non sono necessari due %% per indicare i registri ma ne basta uno).
Trasferiamo quindi il nostro codice all'interno del sorgente e compiliamo:

inline.c
main(){
int a=10, b;
__asm__("movl %1, %%eax; addl $5, %%eax; movl %%eax, %0;" : "=r" (b) : "r" (a) : "%eax");
printf("b vale: %d\n", b);
}


vediamo cosa succede:

quequero@panther:~$ gcc inline.c -o inline && ./inline
b vale: 15

funziona, quindi vediamo come l'ha compilato il gcc (gcc inline.c -o inline -S):

main:
 pushl %ebp
 movl %esp, %ebp
 pushl %ebx
 subl $20, %esp
 andl $-16, %esp
 movl $0, %eax
 subl %eax, %esp
 movl $10, -8(%ebp)
 movl -8(%ebp), %edx
  #APP
  movl %edx, %eax; addl $5, %eax; movl %eax, %edx;
  #NO_APP

 movl %edx, %eax        ; un pochino ridondante ;p
 movl %eax, -12(%ebp)   ; gia :)
 subl $8, %esp
 pushl -12(%ebp)
 pushl $.LC0
 call printf
 addl $16, %esp
 movl -4(%ebp), %ebx
 leave
ret


come potete vedere, il gcc non ha utilizzato eax perche' l'ha assegnato alla nostra variabile, ho contrassegnato un paio di righe come ridondanti ma quelle spariscono se compiliamo come -O2/-O3, ok prima di complicarci la vita ulteriormente vorrei dirvi che all'interno dell'assembler template potete separare ogni istruzione sia con un ; che con uno \n e se volete studiare il listato asm potete accodare allo \n anche uno \t quindi fare questo "movl %eax, %ebx; nop;" e' identico a: "movl %eax, %ebx\n\t nop\n\t" a questo punto complichiamoci un po' la vita :)
Alle volte puo' non bastarci utilizzare il parametro "r" o "=r" perche' dobbiamo utilizzare un registro specifico (prendete come esempio le istruzioni stosw, stosb, loop...) possiamo quindi avvalerci di ulteriori parametri che saranno spesso fondamentali, un breve elenco e' questo (non sono tutti elencati, `man gcc` se vi interessa conoscerli uno per uno):

Parametro
Registro
a
eax
b
ebx
c
ecx
d
edx
S
esi
D
edi
q
eax, ebx, ecx, edx
r
eax, ebx, ecx, edx, esi, edi
g
eax, ebx, ecx, edx o una variabile in memoria
A
eax viene esteso a edx diventando un registro a 64-bit
m
una variabile in memoria
&x (&r...)
un registro diverso da ... (guardate gli esempi sotto)
0...n
non importa quale registro purche' sia lo stesso della variabile %0, %1....%n

facciamo qualche esempio, traduciamo questo: `for (int i=0; i<50; i++)` in inline asm:

main(){
int loop=50, i=0;
__asm__("label: inc %0; loop label" : "=a" (i) : "c" (loop));
printf("i vale: %d\n", i);
}


con allegria abbiamo dato in pasto al gcc il nostro assembler template, riservandoci di dirgli che volevamo `i` dentro eax e `loop` dentro ecx, questo era obbligatorio dal momento che l'istruzione loop funziona su ecx, compiliamo e vediamo se effettivamente ha fatto cosi:

movl $50, %ecx
#APP
label: inc %eax; loop label
#NO_APP
pushl %eax


come potete vedere ha assegnato ecx a `loop` eax a `i`, ma non manca nulla? Gia i clobbered registers, non ce n'e' bisogno dal momento che abbiamo chiesto esplicitamente eax ed ecx nell'input/output, se avessimo invece chiesto un registro generico, allora avremmo dovuto specificare quale avevamo utilizzato nella nostra clobber list. Facciamo un altro esperimento:

main(){
int loop=50, i=0;
__asm__("label: movl %0, %%eax; inc %%eax; movl %%eax, %0; loop label" : "=m" (i) : "c" (loop): "%eax");
printf("i vale: %d\n", i);
}

Come vedete qui ho scelto di infilare `i` dentro una variabile in memoria, quindi non posso fare `inc variabile` ma la devo estrarre, incrementare e quindi rimettere in memoria. Vediamo il listato asm:

movl $50, %ecx
#APP
label: movl -4(%ebp), %eax; inc %eax; movl %eax, -4(%ebp); loop label
#NO_APP
pushl -4(%ebp)


come possiamo felicemente constatare, `i` e' stata messa dentro [ebp-4], quindi funziona :), ovviamente ho dovuto aggiungere eax alla lista dei registri utilizzati visto che lo modifichiamo all'interno dell'asm template. Altra prova:

main(){
int i=0;
asm ("incl %0" :"=a"(i):"0"(i));
printf("i vale: %d\n", i);
}

abbiamo chiesto al gcc di assegnare eax a `i`, ma quello "0" cos'e'? Semplicissimo: `i` e' la variabile %0, quindi passando al gcc il parametro "0" nell'input register gli diciamo: gcc dammi come input register lo stesso che utilizzi per la variabile %0, nel nostro caso eax, ma se avessimo usato "=r" come output e come input "0" cmq il registro di input sarebbe stato lo stesso di quello dell'output, semplice vero? :) Verifichiamo:

xorl %eax, %eax
#APP
incl %eax
#NO_APP
pushl %eax


Logicamente funziona ma se per esempio ci servisse di avere in un registro qualunque una variabile e in un'altro la stessa?

main(){
int x=10, y;
asm ("movl %1, %%ecx; movl %%ecx, %0" :"=&r"(y):"r"(x): "%ecx");
printf("y vale: %d\n", y);
}

in questo caso diciamo al gcc che per la variabile `y` vogliamo un registro qualunque purche' sia differente da quello utilizzato per `x`, percio' utilizzando l'operatore & costringiamo il gcc ad utilizzare un altro registro, verifichiamo anche stavolta se e' stato cosi:

movl $10, %eax
subl $16, %esp
#APP
movl %eax, %ecx; movl %ecx, %edx
#NO_APP
pushl %edx


e infatti come volevasi dimostrare, e' stato assegnato eax a `x` ed edx a `y` :)

get_current(void)?


[18:45:17] <buffer> Quequero: io l'ho conosciuto a padova mentre interpetavo la get_current() del kernel....però manco lui ci capiva nulla! ;))))
[18:45:28] <Quequero> rotfl
[18:45:52] <buffer> è una sola riga di asm inline alquanto criptica.....
[18:46:57] <buffer> no poi l'ho capita ma al momento mi lasciò perplesso
[18:47:12] <Quequero> ;pp
[18:47:55] <buffer> static inline struct task_struct * get_current(void)
[18:47:56] <buffer> {
[18:47:56] <buffer> struct task_struct *current;
[18:47:56] <buffer> __asm__("andl %esp,%0; ":"=r" (current) : "0" (~8191UL));
[18:47:56] <buffer> return current;
[18:47:56] <buffer> }
[18:48:01] <buffer> per la cronaca è questa


apriamo: /usr/src/linux/include/asm-i386/current.h

static inline struct task_struct * get_current(void)
{
   struct task_struct *current;
   __asm__("andl %esp,%0; ":"=r" (current) : "0" (~8191UL));
   return current;
}


NotaBene: (~8191UL) NON vuol dire che si cerca di fare il NOT bit a bit, bensi che si fa il NOT del valore in modo da renderlo NEGATIVO!

Ebbene si, buffer mi ha parecchio invogliato a studiare l'inline, senza dire che il get_current mi lascio' un attimino perplesso anche dopo aver capito a fondo l'inline (ovvero ieri ;p). Cosa fa quella piccola funzione? (Logicamente oltre a restituire il descrittore del processo corrente...) Si sa che le funzioni piu' piccole sono anche le piu' infime, per prima cosa creiamo un programmino che utilizzi la get_current:

#include <asm/current.h>
main(){
get_current();
}


bene, a questo punto compiliamolo con -S e vediamo cosa otteniamo (ovviamente dovrebbe girare a livello kernel per leggere lo stack kernel mode):

get_current:
  pushl %ebp          ; ..
  movl %esp, %ebp     ; ..
  subl $4, %esp       ; sistema lo stack
  movl $-8192, %eax   ; tenete d'occhio -8192 (e' 8192 invece che 8191 per via del complemento a due, grazie buff)
 #APP
   andl %esp,%eax;    ; fa l'and dell'esp con -8192
 #NO_APP
  movl %eax, -4(%ebp) ; un po ridondante anche con -O2
  movl -4(%ebp), %eax ;
  leave
ret


Cosa fa di particolare questa get_current() in una sola riga di asm? Fa l'AND dell'esp corrente con un numero, -8192 ma perche'? Per capirlo dobbiamo andare un pochino piu' a fondo nei meandri del kernel... Noi sappiamo che ogni processo ha due stack, uno usermode e uno logicamente kernelmode che e' grande 8kb (due pagine contigue da 4kb), attenzione... Alla cima di questo stack (per come cresce lo stack sugli intel per cima intendo l'inizio dello stack+8kb) sta il descrittore del nostro processo (la task_struct) e sotto c'e' lo stack kernel mode, in pratica:

0xFFFFFFFF +------------------------------+
           +     ...................      +
    |      +     Stack kernel mode        + <- Inizio stack Kernel Mode
    V      +            ...               +  |
           +           .....              +  | Questi sono 8kb
    |      +          .......             +  |
    V      +     struct task_struct       + <- Alla fine dello stack Kernel Mode c'e' la nostra task_struct
           +          .......             +
    |      +           .....              +
    V      +            ...               +
           +        Fine memoria          +
0x00000000 +------------------------------+


bisogna capire cosa sia ora il numero -8192, in realta' e' semplice, fate 1024*8 = 8192, percio prendete il programma, stampate lo stack a skermo e fate due conti:


#include <asm/current.h>
main(){
int esp;
__asm__("mov %%esp, %0": "=r" (esp));
printf("esp vale 0x%x\n", esp);
get_current();
}

vediamo cosa stampa:

quequero@panther:~$ ./inline
esp vale 0xbffff870

quindi 0xbffff870 AND -8192 = 0xBFFFE000, come e' chiaro succede una cosa, gli ultimi 13 bit di esp vengono azzerati (infatti gli ultimi 13 bit di -8192 in binario valgono: 0000000000000, quindi con l'and azzeriamo gli ultimi 13bit) facendo questo non facciamo altro che arrotondare il valore dello stack agli 8kb inferiori, e guarda caso proprio li si trova il primo byte della nostra task_struct, una maniera quindi estremamente efficiente per ottenere l'indirizzo della struttura. Io ho impiegato un pochino a capire di preciso come funzionasse ma vi assicuro che diventera' piuttosto semplice dopo che l'avrete riflettuta :)) buon divertimento.

-=Quequero=-

Note finali

Al mio amore :*.

Ringrazio poi: awgn, twiz e buffer perche' se ho capito questa roba e' soltanto grazie loro, saluto poi tutta #crack-it, #phrack.it, #spippolatori, #ahccc, #fogna, #cisco, sgrakkyu, il mio spasimante maranik :*, alor che mi ha aiutato con le prime ebuild ;p (trema, io e awgn stiamo preparando uno shellcode talmente elite che ti verra' un infarto quando lo vedrai :), marella (la piu` bella :*), artemis :*, Yado, andregay, CAMILLOOOOOOO, il mio fratellone N0bodY88, l'altro mio fratellone Vinciccio, tutta la STRAMITICA crew degli Spippolatori... Beh, se ho salutato i canali era per non menzionarvi uno a uno o sbaglio? ;ppp ciauz gente!

Disclaimer

Non garantisco nulla, inclusa la veridicita' delle informazioni, il tutorial viene pubblicato sotto licenza GPL versione 2, potete fare quello che volete a patto di menzionare sempre l'autore (IO), il sito dal quale e' stato prelevato (www.quequero.tk, www.scared.tk, www.quequero.cjb.net ...) ed eventuali modifiche apportate da terzi se ne dovessero venir fatte.