Autor: Sefer | Publicado: 2004-12-03T09:55:00-05:00

Netfilter + LKMs Infection                       Parte 1                 por:Sefer_Zohar* Todos os códigos foram criados e testados numa box Slackware 9.1 com o kernel 2.4.22.   Amigos fiz algumas experiencias utilizando duas técnicas que sairam na phrack que gostei muito.Este meu paper, é praticamente uma versão light destes textos que sairam na phrack, e principalmente por ser o primeiro, provavelmente não vai ter muita qualidade mas espero que no final alguém tire algum conhecimento ou proveito das técnicas que mostrei .A primeira técnica é baseada na inclusão de hooks dentro do netfilter, isto pode ser uma ferramenta muito interessante para os nossos objetivos de hacking.A segunda é a de injeção estática de código em módulos para o kernel do Linux, na minha opnião é algo muito show que pode( se bem utilizado), ajudar bastante existindo a necessidade de plantarmos alguns módulos dentro do kernel, sem sermos tão barulhentos para esconder o nosso fazendo sys_call hooking ou coisas deste tipo, hoje tão facilmente identificadas por um monte de ferramentas públicas.No final construiremos um grabber de tráfego pop, que gerará uma lista de usuários e senhas, e utilizando a segunda técnica injetaremos seu código em um driver residente da nossa suposta máquina alvo. Vamos lá!Compreendendo o netfilter:Quando comecei a ler do documento do bioforge, tinha uma expectativa, que foi muito mais do que suprida!Trabalhar com o netfilter abre todo um leque de opções em relação ao hacking, e agiliza muito o desenvolvimento da camada de networking dentro de um rootkit.Basicamente ele trabalha muito parecido com o iptables, então se vc conhecer um pouco do iptables, de linguagem C, e de desenvolvimento de módulos, vc ja esta bem encaminhado e não terá muitos problemas para compreender o que vou mostrar aqui.Bom algo interessante de se saber é que quando vc esta trabalhando com o iptables, vc ja esta mexendo com o netfilter mas nem sabe hehehehehe, entre outras coisas o iptables constrói e insere hooks dentro do netfilter da mesma forma que faremos aqui. :)Primeiro pra quem não conhece o iptables, vamos responder uma pergunta que talvez estejam se fazendo:O que é uma hook?Uma hook(neste caso), é um momento em que o pacote pode ser analisado e uma função que é disparada para a analise ou alteração do pacote. Um exemplo é o seguinte (válido para ipv4):               Um pacote atravessando o Netfilter:               --->[1]--->[ROUTE]--->[3]--->[4]--->                            |            ^                            |            |                            |         [ROUTE]                            v            |                           [2]          [5]                            |            ^                            |            |                            v       [ROUTE]                                           |                Fig.1 Arquitetura do netfilter.             Peguei este desenho "emprestado" do site do netfilter.org hehehehe :D.Bom podemos considerar 5 hooks neste desenho:1. NF_IP_PREROUTING = Esta primeira "hook" é conhecida como NF_IP_PRE_ROUTING, como vc ja deve ter notado pelo próprio nome, ela é disparada logo após a checagem de validade do pacote(checksums, um pacote não-promiscuo, etc), e antes do roteamento do mesmo.Acontece neste momento o roteamento do pacote, a decisão se ele é um pacote destinado a esta máquina ou se este pacote irá apenas atravessar a máquina passando para outra interface.2. NF_IP_LOCAL_IN = Caso o pacote sejam destinados a esta máquina, este pacote é considerado como um pacote entrante, e é tratado pela hook de NF_IP_LOCAL_IN.3. NF_IP_FORWARD = Caso o pacote não seja endereçado à máquina mas sim routeado pela mesma repassado para outra interface(haverá repasse do pacote), ele será tratado por esta hook NF_IP_FORWARD.4. NF_IP_POST_ROUTING = Caso o pacote esteja sendo repassado, esta hook será disparada antes dele ser enviado novamente a rede.5. NF_IP_LOCAL_OUT = Caso o pacote seja gerado pela máquina, esta hook será disparada antes que ele seja enviado para a rede, fiz uma pequena alteração no desenho, como no próprio site do netfilter diz (http://www.netfilter.org/documentation/HOWTO//netfilter-hacking-HOWTO-3.html ) o roteamento acontece antes desta hook, para obter o endereço de origem do pacote, e fazer outras operações.Podemos conhecer as hooks do netfilter olhando em linux/netfilter_ipv4.h, vamos dar uma olhadinha num pedaço deste header:/* IP Hooks *//* After promisc drops, checksum checks. */#define NF_IP_PRE_ROUTING    0/* If the packet is destined for this box. */#define NF_IP_LOCAL_IN        1/* If the packet is destined for another interface. */#define NF_IP_FORWARD        2/* Packets coming from a local process. */#define NF_IP_LOCAL_OUT        3/* Packets about to hit the wire. */#define NF_IP_POST_ROUTING    4A função responsável pela nossa hook:Vcs viram que podemos manipular um determinado pacote em certos momentos dentro do netfilter, mas pense bem como vamos manipula-lo?Bem quando chegar neste momento o netfilter "chamará" automaticamente uma função que registraremos dentro de nossa hook.Vamos ver a declaração desta nossa função, conforme ~/linux/netfilter.h:typedef unsigned int nf_hookfn(unsigned int hooknum,                  struct sk_buff **skb,                  const struct net_device *in,                  const struct net_device *out,                  int (*okfn)(struct sk_buff *));Vamos por partes como diria Jack estripador, agora acredito que vc tenha pensado o que é este tal de skb??Ele é um ponteiro para uma estrutura que representa nada mais nada menos do que um pacote no nível do kernel, o driver da nossa placa de rede nos entrega estes pacotes neste formato podemos encontrar sua declaração e seus membros em ~/linux/sk_buff.h, ele é um nó dentro de uma lista encadeada, digo para os programadores ruins como eu: "muita calma nesta hora", não tenham medo, não vamos manipular esta lista encadeada, pelo menos não neste paper hehehehe.Vamos dar uma olhada num pedaço deste bicho de 7 cabeças:struct sk_buff {   /* These two members must be first. */   struct sk_buff    * next;            /* Next buffer in list                 */   struct sk_buff    * prev;            /* Previous buffer in list             */   struct sk_buff_head * list;        /* List we are on                */   struct sock    *sk;            /* Socket we are owned by             */   struct timeval    stamp;            /* Time we arrived                */   struct net_device    *dev;        /* Device we arrived on/are leaving by        */   struct net_device    *real_dev;    /* For support of point to point protocols                          (e.g. 802.3ad) over bonding, we must save the                          physical device that got the packet before                          replacing skb->dev with the virtual device.  */   /* Transport layer header */   union   {       struct tcphdr    *th;       struct udphdr    *uh;       struct icmphdr    *icmph;       struct igmphdr    *igmph;       struct iphdr    *ipiph;       struct spxhdr    *spxh;       unsigned char    *raw;   } h;   /* Network layer header */   union   {       struct iphdr    *iph;       struct ipv6hdr    *ipv6h;       struct arphdr    *arph;       struct ipxhdr    *ipxh;       unsigned char    *raw;   } nh;   /* Link layer header */   union   {           struct ethhdr    *ethernet;         unsigned char     *raw;   } mac;...Bom esta estrutura esta bem comentada neste pedaço o que nos ajuda bastante a compreendê-la, mas vamos em frente que quem anda pra trás é caranguejo e traveco!Vamos ver alguns pedaços interessantes para nossas experiencias com esta estrutura:Estes ponteiros são utilizados para nos referenciar dentro da lista encadeada, e apontam para a lista encadeada a qual pertencemos ( *list), não vamos mexer com eles nesta nossa primeira jornada :).struct sk_buff    * next;            /* Next buffer in list                 */   struct sk_buff    * prev;        /* Previous buffer in list             */   struct sk_buff_head * list;    /* List we are on  No nível do kernel, os sockets são estruturas sock, aqui temos um ponteiro para o socket ao qual pertencemos, ou neste caso a struct sock que nos recebeu.   struct sock    *sk;            /* Socket we are owned by             */Bem legal para quem quiser trabalhar com controles de tempo, temos aqui o timestamp em que fomos recebidos   struct timeval    stamp;            /* Time we arrived  Aqui temos outra estrutura muito importante e que é muito útil, abrindo várias opções para podermos explorar nosso maravilhoso mundo do networking dentro do kernel, a struct *dev, que será observada mais pra frente, por enquanto é interessante saber que ela representa um dispositivo de comunicação ao qual estamos sendo entregues, ou deixando.   struct net_device    *dev;        /* Device we arrived on/are leaving by        */   struct net_device    *real_dev;    /* For support of point to point protocols                          (e.g. 802.3ad) over bonding, we must save the                          physical device that got the packet before                          replacing skb->dev with the virtual device.  */                        Finalmente chegamos numa parte conhecida, ai esta o transport layer header :D, muito importante dentro do nosso código./* Transport layer header */union   {       struct tcphdr    *th;       struct udphdr    *uh;       struct icmphdr    *icmph;       struct igmphdr    *igmph;       struct iphdr    *ipiph;       struct spxhdr    *spxh;       unsigned char    *raw;   } h;Bom como vc já deve ter imaginado(estudar networking no passado vai ser útil pra vc agora), abaixo vemos o network layer header   /* Network layer header */   union   {       struct iphdr    *iph;       struct ipv6hdr    *ipv6h;       struct arphdr    *arph;       struct ipxhdr    *ipxh;       unsigned char    *raw;   } nh;Por ultimo, mas não menos importante(a verdade é que neste documento pelo menos será menos importante), vemos o link layer header:/* Link layer header */   union   {           struct ethhdr    *ethernet;         unsigned char     *raw;   } mac;Blz, agora conhecemos o segundo argumento de nossa função de hooking, vamos ver o que mais vem da toca do coelho ;)O terceiro argumento const struct net_device *in é o dispositivo do qual nós chegamos, enquanto const struct net_device *out, é o dispositivo de saída(no caso de NF_IP_LOCAL_OUT ou NF_IP_POST_ROUTING).Esta struct esta declarada em ~/linux/netdevice.h, temos um campo muito interessante que nos pode ser útil que indica o nome da interface sua declaração é:   char            name[IFNAMSIZ];O último argumento da função de hooking é o:int (*okfn)(struct sk_buff *)Não mexeremos com esta função, mas para não deixar algo nebuloso vou tentar explicar para que serve esta função, dei uma pesquisada na net e descobri que as hooks do netfilter pode descançar(sleep), em momentos como por exemplo o pacote é passado para o espaço do usuário, neste caso, não seria muito inteligente manter o processo trancado, ou rodando esperando a hook retornar, então as funções de tratamento do netfilter foram divididas em duas, a segunda parte do tratamento, ocorre quando o pacote retorna do espaço do usuário, esta parte é a okfn :), teve uma thread lista do netfilter explicando isto, muito melhor do que eu posso explicar(o próprio rusty que explica hehehehe):http://lists.netfilter.org/pipermail/netfilter/2000-January/003173.htmlDe qualquer forma isto não é importante dentro deste documento, ja que não utilizaremos nada relativo a esta função :).Como vc pode ver esta função retorna um inteiro, este retorno decide o que vai ser feito com o pacote.Seria no iptables o -j, ou o que a hook fará com o pacote.Vamos dar uma olhada no que nossa função pode retornar:Estas definições estão em ~/linux/netfilter.h :#define NF_DROP 0#define NF_ACCEPT 1#define NF_STOLEN 2#define NF_QUEUE 3#define NF_REPEAT 4NF_DROP: óbvio faz com que o pacote seja descartado.NF_ACCEPT: Nem um pouco menos óbvio retornando NF_ACCEPT fazemos com que nosso pacote seja aceito.NF_STOLEN: Bom este não é tão óbvio hehehe NF_STOLEN faz com que o pacote morra dentro da nossa hook, ou melhor, que ele seja esquecido pelas hooks subsequentes.NF_QUEUE: Coloca o pacote num fila(não tenho certeza, mas acredito que isto seja para usar o conceito da libipq( http://www.cs.princeton.edu/~nakao/libipq.htm ), ou seja vc coloca o pacote numa fila para um processo no espaço do usuário, trata este pacote, e retorna para o modo kernel(ativa okfn), ou retorna um drop no bixin.Agora temos sabemos quase tudo que precisamos para avançar e desenvolver nosso primeiro módulo para o netfilter, mas ainda carece saber de uma coisa.Bom registramos hooks dentro do netfilter, esta hook, são structs (estruturas) que contém, a nossa função que estudamos acima( e retorna um dos valores que acabamos de ver), uma prioridade que vou falar mais a frente, e uma familia(no nosso caso PF_INET).Vamos ver esta estrutura, conforme sua declaração em ~/linux/netfilter.h :struct nf_hook_ops{   struct list_head list;   /* User fills in from here down. */   nf_hookfn *hook;   int pf;   int hooknum;   /* Hooks are ordered in ascending priority. */   int priority;};Primeira coisa que notamos ela faz parte de uma lista de hooks, nem se preocupem novamente, não vamos manipular esta lista(hoje hehehe).struct list_head list;Esta ai a função que estudamos a pouco, este membro apontaremos para nossa função de hooking :Dnf_hookfn *hook;Bom pf = protocol family, como estaremos lidando com o PF_INET, vamos atribuir este valor para este membro.int pf;Se lembram das hooks que conhecemos lah no começo do texto?? Bom aqui vamos preencher com elas (relembrando poderia ser NF_IP_PRE_ROUTING, para pacotes que ainda não foram roteados).int hooknum;Blz quase terminada nossa hook vamos ao membro que ainda não vimos neste texto oint priority;Como podem existir várias hooks, é necessário existir uma ordem de prioridade, seguindo o exemplo do bioforge no seu paper, vamos trabalhar com NF_IP_PRI_FIRST, que faz com que nossa hook seja tratada como a de maior prioridade(isto quer dizer atribuido o valor menor).enum nf_ip_hook_priorities {   NF_IP_PRI_FIRST = INT_MIN,   NF_IP_PRI_CONNTRACK = -200,   NF_IP_PRI_MANGLE = -150,   NF_IP_PRI_NAT_DST = -100,   NF_IP_PRI_FILTER = 0,   NF_IP_PRI_NAT_SRC = 100,   NF_IP_PRI_LAST = INT_MAX,};Agora temos as ferramentas necessárias para podermos começar a construção de módulos simples para o netfilter e carrega-los dentro do nosso kernel!!Para vc que não se lembra, ou nunca viu um módulo do kernel antes, eu recomendo a leitura do documento:http://www.faqs.org/docs/kernel/É um bom documento para iniciantes como eu.Mãos a obra:Vamos tentar primeiro criar um módulo simples que drop pacotes chegando na interface lo.DROPA PACOTES CHEGANDO NA INTERFACE lo:#define __KERNEL__#define MODULE#include #include #include #include #include unsigned int hookfunc(unsigned int hooknum,                      struct sk_buff **skb,                      const struct net_device *in,                      const struct net_device *out,                      int (*okfn)(struct sk_buff *))   {   static char *interface_lo = "lo";    if(strncmp(in->name, interface_lo, 2))       {       return NF_DROP;       }        return NF_ACCEPT;   } static struct nf_hook_ops hulk;int init_module()   {    hulk.hooknum = NF_IP_LOCAL_IN;   hulk.priority = NF_IP_PRI_FIRST;   hulk.hook = hookfunc;   hulk.pf = PF_INET;    nf_register_hook(&hulk);    return 0;   }void cleanup_module()   {   nf_unregister_hook(&hulk);   }Blz antes de um passo a passo no código vamos ver o que aconteceu quando compilamos e rodamos o kernel:bash-2.05b# gcc -c -O3 teste_interface.cbash-2.05b# insmod teste_interface.oWarning: loading teste_interface.o will taint the kernel: no license See http://www.tux.org/lkml/#export-tainted for information about tainted modulesModule teste_interface loaded, with warningsbash-2.05b# ping localhostPING localhost (127.0.0.1) 56(84) bytes of data.                                                                             --- localhost ping statistics ---4 packets transmitted, 0 received, 100% packet loss, time 3014ms                                                                             bash-2.05b#Blz parece que tudo funcionou como queriamos :)Partimos agora para um passo a passo:Estas definições são padrão de todos módulos que criamos, pode ser substituida pelas flags -DMODULE e -D__KERNEL__ , no entanto prefiro economizar flags hehehehehe.#define __KERNEL__#define MODULEvamos ver os includes :PEstes dois são padrão de serem incluidos em módulos do kernel tb:#include #include este declaram  nossas hooks priorities etc:#include #include Declaração de struct net_device, usamos para sabermos o nome da interface in->name#include Abaixo vemos nossa função :) que vai fazer o hook do pacote:unsigned int hookfunc(unsigned int hooknum,                      struct sk_buff **skb,                      const struct net_device *in,                      const struct net_device *out,                      int (*okfn)(struct sk_buff *))   {Declaramos um ponteiro char com o conteúdo "lo", o nome da interface que queremos bloquear   static char *interface_lo = "lo"; Fazemos uma simples comparação para sabermos se in->name é igual a nossa interface_lo, caso seja(if retorna 0), retornamos o NF_DROP, que manda o pacote para onde judas perdeu as botas.   if(strncmp(in->name, interface_lo, 2))       {       return NF_DROP;       }     Caso não seja o fluxo da função chegará aqui qeu retornará para o pacote ser aceito, e ele passará pelas outras hooks para validação, não tendo nenhum problema chegará ao espaço do usuário para a aplicação que estiver esperando por ele.   return NF_ACCEPT;   } Todo módulo tem uma função destas, ela é responsável por iniciar os módulo, isto é fazer todas as atribuições, preparar nosso triggers, ou setar alguma coisa que desejamos dentro do kernelint init_module()   {Blz até aqui, vamos agora preencher nossa estrutura nf_hook_opsprimeiro atribuimos a .hooknum o valor de NF_IP_LOCAL_IN(pacotes chegando na máquina)   hulk.hooknum = NF_IP_LOCAL_IN; Primeira regra da hook a ser obedecida   hulk.priority = NF_IP_PRI_FIRST;Apontamos .hook para nossa função   hulk.hook = hookfunc;E definimos a familia da hook   hulk.pf = PF_INET; Utilizando a função nf_register_hook(nf_hook_ops *), inserimos nossa hook dentro do netfilter   nf_register_hook(&hulk);Retornamos 0 indicando que o módulo foi carregado com sucesso    return 0;   }Esta função é responsável por arrumar nossa bagunça, em todo módulo acharemos uma destas:void cleanup_module()   {Desregistramos nossa hook, e deixamos o kernel viver em paz hehehehe   nf_unregister_hook(&hulk);   }Bom como vcs puderam ver por este nosso primeiro teste, não é difícil construir hooks úteis dentro do netfilter seguindo mais ou menos o exemplo que dei acima.Vamos a segunda parte da primeira parte deste paper :DE se quizermos rejeitar pacote para um determinado protocolo??Mãos a obra!!#define __KERNEL__#define MODULE#include #include #include #include #include #include #include #include static struct nf_hook_ops hulk;unsigned int hookfunc(unsigned int hooknum,             struct sk_buff **skb,             const struct net_device *in,             const struct net_device *out,             int (*okfn)(struct sk_buff *))             {   struct sk_buff *sk = *skb;   if(sk->nh.iph->protocol == IPPROTO_TCP)       {       return NF_DROP;       }   return NF_ACCEPT;   } int init_module()   {    hulk.hooknum = NF_IP_LOCAL_IN;   hulk.priority = NF_IP_PRI_FIRST;   hulk.hook = hookfunc;   hulk.pf = PF_INET;    nf_register_hook(&hulk);    return 0;   }void cleanup_module()   {   nf_unregister_hook(&hulk);   }  Resultado do bixo rodando:bash-2.05b# gcc -c -O3 teste_protocol.cbash-2.05b# insmod teste_protocol.oWarning: loading teste_protocol.o will taint the kernel: no license See http://www.tux.org/lkml/#export-tainted for information about tainted modulesModule teste_protocol loaded, with warningsbash-2.05b# popa3d -Dbash-2.05b# telnet localhost 110Trying 127.0.0.1...                                                                             bash-2.05b#Como vemos acima após carregar o módulo não foi possível conectar na máquina mostrando que nosso módulo teve sucesso! :)como ja conhecemos a rotina de registro de hooks não vou explica-la novamente, irei explicar somente a função de hooking :)Blz abrimos a função:unsigned int hookfunc(unsigned int hooknum,             struct sk_buff **skb,             const struct net_device *in,             const struct net_device *out,             int (*okfn)(struct sk_buff *))             {Declaramos outro ponteiro para uma struct sk_buff e deferenciamos um nível o ponteiro do **skb   struct sk_buff *sk = *skb; Vcs podem ver os membros de iph em ~/linux/ip.h e a definição de IPPROTO_TCP em ~/linux/in.h, fazemos uma comparação para ver se obedece a nossa exigencia (procotolo tcp).   if(sk->nh.iph->protocol == IPPROTO_TCP)       {     obedencendo DROPAMOS o pacote.       return NF_DROP;       }     Não obedecendo deixamos o pacote viver em paz(aceitamos)   return NF_ACCEPT;   }  Por último mas não menos importante!Vamos ver como selecionar pacotes por endereço :)por fim este código esta quase identico ao do bioforge estou escrevendo a um tempão e estou cansado ja..#define __KERNEL__#define MODULE#include #include #include #include #include #include #include #include static struct nf_hook_ops hulk; unsigned char *loopback = "\x7f\x00\x00\x01";unsigned int hookfunc(unsigned int hooknum,             struct sk_buff **skb,             const struct net_device *in,             const struct net_device *out,             int (*okfn)(struct sk_buff *))             {   struct sk_buff *sk = *skb;    if(sk->nh.iph->saddr == (unsigned int *)loopback)       {       return NF_DROP;       }   return NF_ACCEPT;   } int init_module()   {    hulk.hooknum = NF_IP_LOCAL_IN;   hulk.priority = NF_IP_PRI_FIRST;   hulk.hook = hookfunc;   hulk.pf = PF_INET;    nf_register_hook(&hulk);    return 0;   }void cleanup_module()   {   nf_unregister_hook(&hulk);   } Como sempre compilamos e testamos :)bash-2.05b# gcc -c -O3 teste_sa.cbash-2.05b# insmod teste_sa.oWarning: loading teste_sa.o will taint the kernel: no license See http://www.tux.org/lkml/#export-tainted for information about tainted modulesModule teste_sa loaded, with warningsbash-2.05b# ping localhostPING localhost (127.0.0.1) 56(84) bytes of data.                                                                             --- localhost ping statistics ---1 packets transmitted, 0 received, 100% packet loss, time 0ms                                                                             bash-2.05b# ping 192.168.0.1PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data.64 bytes from 192.168.0.1: icmp_seq=1 ttl=254 time=0.857 ms64 bytes from 192.168.0.1: icmp_seq=2 ttl=254 time=0.825 ms--- 192.168.0.1 ping statistics ---2 packets transmitted, 2 received, 0% packet loss, time 1008msrtt min/avg/max/mdev = 0.825/0.841/0.857/0.016 msbash-2.05b#Como vcs podem ver os pacotes vindos do endereço especificado foram dropados no entanto os pacotes provinientes da maquina 192.168.0.1 foram aceitos com sucesso!:).Novamente vamos analisar o codigo...unsigned int hookfunc(unsigned int hooknum,             struct sk_buff **skb,             const struct net_device *in,             const struct net_device *out,             int (*okfn)(struct sk_buff *))             {Deferenciamos o ponteiro como da ultima vez   struct sk_buff *sk = *skb;comparamos outro campo desta vez outro campo do ipheader iph->saddr    if(sk->nh.iph->saddr == (unsigned int *)loopback)       {caso seja nosso endereço dropamos o pacote       return NF_DROP;       }se não for o mesmo aceitamos :)   return NF_ACCEPT;   }Como vcs podem ver é bem fácil desenvolver filtros simples usando inserindo hooks no netfilter :)Ainda faltam terminar algumas coisas neste tutorial, talvez mais pra frente eu solte algum 1.0 dele....Até mais pessoal!Referencias:http://www.linux-mag.com/2000-06/gear_01.htmlhttp://www.phrack.orghttp://www.google.com.br