H4CK.NL
Home > Tutorials > Hacking > Kernel hacken

Gebruikersnaam
Wachtwoord vergeten
Wachtwoord
Onthoud Gebruiker  

Kernel hacken

-------------------------------------------------------
Kernel hacking
-------------------------------------------------------
* Disclaimer
* Inleiding
* System calls
* Loadable kernel modules
* Verbergen van modules
* Afsluiting
* Bronnen

- Disclaimer

Het kan zijn dat als je informatie uit de artikel gaat uitvoeren op je OS en
je gaat zelf dingen uit proberen met betrekking tot kernel modules en dergelijke
dan is het mogelijk dat je dingen verkeerd doet, zodat je kernel (tijdelijk?)
niet meer goed werkt. Alles is dus op eigen risico en ik ben nergens voor
aansprakelijk.


- Inleiding

Zoals velen van jullie wel weten bestaan er mogelijkheden om de kernel van een
OS te hacken. Dit is natuurlijk een zeer gevaarlijke attack, want wat moet je
als sysop als je je eigen kernel niet eens meer kan vertrouwen? Een kernel kan
je echter pas hacken als je in een systeem bent binnengedrongen en root hebt
verkregen. Maar hoe goed je je systeem ook beveiligd, er kan altijd iemand
binnenkomen. En hoe slimmer de hacker is die jouw geweldige beveiliging omzeild,
hoe groter de kans dat hij je kernel hackt om later nog eens terug op bezoek
te kunnen komen. Deze worden vaak als een kernelmodule gecompileerd en geinstal-
leerd, terwijl ze op een vernuftige manier in elkaar gezet zijn, zodat je ook
met lsmod de desbetreffende backdoor niet kan zien. In dit artikel zal ik eens
wat dieper ingaan op het hacken van de kernel. Alles is van toepassing op de
Linux-kernel, maar de principes zijn hetzelfde voor alle UNIX-based systemen. Je
zult dan wel even uit moeten vogelen welke functies de kernel gebruikt om
systemcalls uit te voeren. Dit gaat natuurlijk het makkelijkst als je de sources
van de kernel van je OS hebt. In principe zijn er ook wel slimme backdoors te
schrijven voor windows, maar dit kost heel veel tijd en je zult je door alle
assembly-sources van Windows 9x/2K/ME heen moeten worstelen. :)


- System calls

Voordat we kunnen bekijken wat system calls eigenlijk zijn moet er eerst wat
uitgelegd worden over hoe het geheugen in Linux is ingedeeld. Het geheugen is
in twee gedeeltes verdeeld; kernelspace en userspace. Een programma kan niet
naar de kernelspace schrijven, omdat die voor de kernel is gereserveerd *doh*.
Ook kun je niet zonder meer de kernel access tot het usergeheugen laten geven.
Als je een system call uitvoert doet de kernel enkele dingen. Hij zet DS en ES
(geheugenpointers, voor meer info lees wat over Assembler) zodat het geheugen
wijst naar het kernelsegment. Hierna zet het FS en die wijst dan naar het
usersegment in het geheugen. Vanuit de kernelspace kun je dus toch het stukje
geheugen dat gereserveerd is voor de user aanroepen.

Elk programma dat je in C hebt geschreven gebruikt libc. Dit is een library die
alle basisfuncties, die je nodig hebt om te programmeren, vastlegd. In feite
zijn bijna alle functies in libc gebaseerd op de onderliggende system calls.
Dit zijn de simpelste kernelfuncties die een userprogramma kan aanroepen.
Alle system calls van een OS zijn geimplementeerd in de kernel zelf of in kernel
modules die je pas in het geheugen laat als je ze nodig hebt (insmod, rmmod,
lsmod...)

In Linux zijn de system calls geimplementeerd door door een interrupt. Deze is
'int 0x80'. Als je deze instructie uitvoert wordt de controle aan de kernel
overgedragen met behulp van de functie _system_call(). Deze functie saved alle
registers en daarna wordt de inhoud van het %eax register gecheckt en vergeleken
met de zogeheten 'global system calls'-table. In deze tabel staan alle system
calls en hun geheugenadressen. Als de system call in %eax niet in de tabel staat
retouneert _system_call een error. In alle andere gevallen zal de kernel het
geheugenadres dat in de tabel stond achter de system call uitvoeren. Hier volgt
even een klein voorbeeldje dat de system call aanroept met een system call
die niet bestaat, zodat de kernel een foutmelding zal geven! Let er wel op dat
je bij jouw systeem wel een waarde voor 'movl $165,%eax' geeft die niet bestaat,
want anders kan je kernel hele rare dingen gaan doen. Voor een overzicht van
alle system calls kijk in: "/usr/include/asm/unistd.h". Daar staan ze tenminste
bij mij. Anders kan je OS ze ook in: "/usr/include/sys/syscall.h" hebben staan.
Daar staan alle gedefineerde system calls van je OS in. Laten we eens gaan
kijken wat er gebeurd als we een systemcall uit proberen die dus NIET bestaat!

[gorny@localhost artikel]$ vi ./syscall1.c
/* Voorbeeld van een systemcall */
#include
#include
#include

extern void *sys_call_table[];

functie() {
__asm__(
/* wel gecheckt of systemcall 165 NIET bestaat? */
"movl $165,%eax
int $0x80");
}

main() {
errno = functie();
perror("Invalid syscall");
}
[gorny@localhost artikel]$ gcc -O2 -o syscall1 ./syscall1.c
[gorny@localhost artikel]$ ./syscall1
Invalid syscall: Unknown error 4294967282
[gorny@localhost artikel]$ su
Password:
[root@localhost artikel]# ./syscall1
Invalid syscall: Unknown error 4294967282
[root@localhost artikel]# exit
[gorny@localhost artikel]$

En zoals we al verwacht hadden geeft de kernel een keurig nette foutmelding.
Normaal zal de controle terug gegeven worden gegeven aan de gespecificeerde
systemcall. Hierna roept _system_call() de functie _ret_from_sys_call() aan,
om verschillende informatie te checken en uiteindelijk weer terug te keren
naar het usersegment in het geheugen. Zoals je ziet, maakt het ook geen ver-
schil of je het programmaatje uitvoert als gewone user of als root.
NOTE: Om de een of andere vage reden geeft het voorbeeldproggie een core-dump
als ik hem gecompiled heb met de optimatie-optie '-O3'. Vraag me niet waarom,
want '-O1' en '-O2' werken wel gewoon :(


- Loadable kernel modules

Als je nou zelf je eigen system calls wilt maken dan kun je dit doen door
de source van je kernel handmatig aan te passen en een nieuwe kernel te
compilen. Het is echter heel wat makkelijker als je een loadable kernel
module gebruikt. Dit betekent domweg dat het een objectfile is die gelinked
zal worden als de kernel de module nodig heeft. Hierdoor kun je ook een kleinere
kernel krijgen, door alles wat je niet vaak nodig hebt, als aparte module te
compileren. In feite is het enige wat je moet doen in je module om system calls
toe te voegen is het veranderen van de sys_call_table array die al eerder is
beschreven. Elke loadable kernel module (lkm) heeft in ieder geval de functies;

int init_module(void)
void cleanup_module(void)

De eerste functie wordt aangeroepen bij het laden van de module en de tweede bij
(je raadt het al :) het unloaden van de module. Nog even een waarschuwing voor
mensen die nu hun eigen loadable kernel modules willen gaan schrijven. Begrijp
goed dat je met kernel-code bezig bent en dat je hele systeem in de war kan
lopen als je echt rare system calls gaat uitvoeren. Let dus goed op en zorg
ervoor dat de code goed is voordat je de gecompilede code gaat uitvoeren. Een
ander nadeel van kernel modules is dat ze niet libc kunnen gebruiken. Ze moeten
direct via system calls hun opdrachten uitvoeren. Hier bestaat ook een handige
macro voor genaamd _syscallX(), maar het kan ook gewoon met de sys_call_table
array. Ook is het een goed idee om je module te compileren met een regel als:

[gorny@localhost artikel]$ gcc -c -O3 blaat.c

Met de optie O3 wordt namelijk alles geoptimaliseerd en duurt het compilen
wat langer, maar uiteindelijk levert het wel wat snelheidswinst op :) Je krijgt
dan een module genaamd blaat.o. Deze kun je laden met 'insmod blaat.o' en dan
is je lkm actief. Wil je hem weer uit het geheugen laden dan doe je 'rmmod' en
voor een overzicht van alle geladen kernel modules typ je 'lsmod'. Laten we
eens een voorbeeld module gaan bekijken. Deze heb ik gehaald uit het artikel
van plaguez.


#define MODULE
#define __KERNEL__
#include
#ifdef MODULE
#include
#include
#else
#define MOD_INC_USE_COUNT
#define MOD_DEC_USE_COUNT
#endif
#include
#include
#include
#include
#include
#include
#include
#include

int errno;

/* Hier wordt een pointer gedefineerd die naar de oude setreuid */
int (*o_setreuid) (uid_t, uid_t);

/* In de onderstaande array staan de systemcalls en pointers */
extern void *sys_call_table[];


int new_setreuid(uid_t ruid, uid_t euid)
{
printk("uid %i trying to seteuid to euid=%i", current->uid, euid);
return (*o_setreuid) (ruid, euid);
}


int init_module(void)
{
o_setreuid = sys_call_table[SYS_setreuid];
sys_call_table[SYS_setreuid] = (void *) new_setreuid;
printk("swatch loaded.\n");
return 0;
}

void cleanup_module(void)
{
sys_call_table[SYS_setreuid] = o_setreuid;
printk("swatch unloaded.\n");
}


Je compileert deze module met 'gcc -c -O3 plaguez.c'.
Wat doet deze module eigenlijk precies? Als hij wordt aangeroepen en in het
geheugen wordt geladen zal de pointer naar de oude setreuid functie opgeslagen
worden in o_setreuid. Hierna wordt de sys_call_table gewijzigd zodat de
systemcall 'setreuid' nu wijst naar de functie 'new_setreuid'. De informatie
wordt dus onderschept en daarna wordt pas de goede functie aangeroepen. Bij het
unloaden van de module wordt alle informatie weer terug gezet zoals het was en
zal de system call 'setreuid' weer gewoon wijzen naar de goede functie. Zoals
je hier ook duidelijk ziet kun je niet de gewone printf-functie gebruiken van
libc. Daarom wordt hier de functie printk gebruikt die wel aangeroepen kan
worden door loadable kernel modules.


- Verbergen van modules

Maar natuurlijk zal de module ook verborgen moeten worden. Anders is de module
veel te gauw gespot en daar heb je hem niet voor geinstalleerd :). Dit kan ge-
daan worden op verschillende manieren. Een mogelijkheid is om '/proc/modules'
te wijzigen zodra een programma die file probeert te lezen, maar dit is erg
moeilijk om te programmeren en het schijnt dat je dan met:
'dd if=/proc/modules bs=1' wel gewoon de file kan lezen inclusief de naam van de
module die je wilt verbergen. Er is wel een oplossing. De module lijst wordt
niet direct geëxporteerd vanuit de kernel zodat er geen manier is om deze lijst
direct aan te spreken. Dit laatste kan echter alleen als sys_init_module() wordt
aangeroepen, want dat is namelijk de module die onze eigen lkm moet gaan aan-
roepen, dus die moet toegang hebben tot de moduleinfolijst. Hierbij moeten we
er wel van uitgaan dat GCC de registers niet verneukt tussen het aanroepen van
'sys_init_module' en het aanroepen van 'init_module()'. Dat maakt het mogelijk
om na te gaan wat het register was waarin de moduleinfolijst begon. De module-
lijsten zijn opgeslagen in een register struct van het gedefineerd type module.
De naam van deze struct is mp. Je kan dan nagaan wat het adres is van 1 van de
items van de struct. Als je dan een stukje code als het onderstaande schrijft
in je lkm dan zal de module niet getoond worden in /proc/modules, omdat de
kernel denkt dat het een kernel module is, aangezien die ook geen referenties
en een naam hebben. Bij het onderstaande stukje wordt er wel vanuit gegaan
dat het adres van die struct is opgeslagen in %ebx. Dit zul je dus wel even
moeten checken om daar het goede register in te vullen.

int init_module()
{
register struct module *mp asm("%ebx");
*(char*)mp->name=0;
mp->size=0;
mp->ref=0;
/* De rest van je code */
}

Naast het verbergen van de module zelf zijn er trojans geschreven met allerlei
opties. Zoals: PID-hiders, execve redirection, file hiders, socket recvfrom()
en gemodificeerde setuid()-calls. Vooral itf.c van plaguez heeft veel opties.
Maar het is natuurlijk wel lame om niet je eigen trojans te gebruiken, maar die
van anderen (zie artikel van Nynex in H4H-0C over lameheid :). Bovendien
leer je gigantisch veel van het zelf schrijven van je trojans en tools.
Als je van het zelf maken van je programma's niets leert dan leer je nergens wat
van. Kun je beter je computer verkopen ofzo en postzegels gaan verzamelen :)

h4ck.nl
Rated 9/10 based on 714 reviews

Je kan je eigen review plaatsen als je lid bent.

Â