VI Encontro de Software Livre da Universidade de Pernambuco

August 14th, 2009 Marcos Álvares No comments

opensource-250x216

Caros, venho por meio deste anunciar mais uma edição do já tradicional Encontro de Software Livre da Universidade de Pernambuco [1]. O evento está em sua sexta edição e contará com palestras, mini-cursos e install fest oferecendo ao espectador um panorama da produção de Software Livre do estado de Pernambuco.

O encontro ocorrerá no próximo sábado dia 22 de Agosto das 10h às 17h no auditório da Escola Politécnica de Pernambuco (núcleo de engenharia da UPE) [2]. O evento é gratuito e para participar é só seguir os detalhes descritos no site [3].

A agenda do evento pode ser conferida em [4] e a grade de palestras em [5].

Estou na organização do evento e irei palestrar a respeito do meu projeto open-source: o Ruby Neuron [6]. Tenho como objetivo tentar eliminar o estigma que as técnicas de IA possuem de complexas e experimentais além de diminuir o gap entre a pesquisa e o uso prático da ciência produzida para melhoria de vida das pessoas. Em breve vou começar a escrever um artigo sobre o Ruby Neuron e como usa-lo como ferramenta para solução de problemas de classificação e regressão.

Referências:
[1] http://slpoli.dsc.upe.br/
[2] http://slpoli.dsc.upe.br/place/
[3] http://slpoli.dsc.upe.br/registration/
[4] http://slpoli.dsc.upe.br/schedule/
[5] http://slpoli.dsc.upe.br/speaker/
[6] http://rubyneuron.org/

Categories: Technology Tags:

[VD03.1] Heap Overflow

July 9th, 2009 Marcos Álvares No comments

O foco desse artigo é explicar a essência da técnica de exploração de overflows baseados na heap, para plataformas Intel x86 32 bits usando como sistema operacional o Linux. Esse artigo ficou muito grande para caber em um único post e resolvi dividi-lo em duas partes. A primeira irá descrever a fundamentação teórica necessária para o entendimento do ataque (meio frustrante, mas extremamente necessária) e a segunda irá descrever mecanismos de bypassing necessários para a exploração em ambientes atualizados. Os exemplos serão baseados na implementação de Doug Lea e Wolfram Gloger presente no pacote da glibc 2.9 [1].
A heap nada mais é do que uma região de memória reservada pelo sistema operacional para cada processo armazenar os dados referentes à variáveis alocadas dinamicamente usando as chamadas de sistema mmap(reserva uma nova área) ou brk(libera uma determinada área) através de funções da glibc como malloc(), calloc(), realloc() e free(). Cada processo mapeado em memória possui a sua região de heap privada. A seguir, podemos observar um exemplo de código que faz uso da heap:

#include <stdio.h>
#include <stdlib.h>

int main (int argc, char *argv[]) {
        char *buffer;

        /*
            Cria uma porção de 200 bytes na heap e armazena o endereço
            para esta nova porção na variável buffer. Faz uma chamada de
            sistema mmap.Podemos acompanhar esse fluxo de chamadas
            de sistemas através do comando strace.
       */

        buffer = (char *) malloc (200 * sizeof(char));   

        /*
            Avisa ao sistema operacional que a porção armazenada no endereço
            "buffer" não é mais necessária, através da chamada de sistema brk.
        */
        free(buffer);
        return 0;
}

.
Na literatura e em diversos advisories, quando se fala de “heap overflow“, de uma forma geral, a intenção é se referir a buffer overflow em qualquer porção de memória diferente da pilha, que tenha permissão de escrita. Isso é algo a se levar em consideração já que muitas vezes iremos estudar códigos que atacam regiões diferentes da heap e serão classificados como “heap based vulnerabilities“.

Fig 01 - Organização de um processo no Linux

A motivação para explorar essa categoria de vulnerabilidades consiste em dois argumentos:
.
- Linha do tempo: os ataques à heap são mais recentes em relação aos baseados na pilha, com isso, existem menos mecanismos de proteção internos aos sistemas operacionais (falo quantitativamente e não qualitativamente). A heap é usada por programas que necessitam de uso intensivo de memória, portanto projetar um mecanismo de proteção para essa área nos leva ao dilema de segurança versus performance, pois qualquer verificação de segurança adicionada nessa camada será executada inúmeras vezes, introduzindo um overhead em processos que fazem uso intensivo da heap (banco de dados, servidores web, servidores DNS …).

- Complexidade: com uma busca rápida em qualquer sistema de indexação de vulnerabilidades, percebemos que existem bem mais explorações bem sucedidas e publicadas, usando ataques baseados na pilha do que na heap. Isso acontece devido a complexidade envolvida no processo de busca e exploração de falhas no uso da heap. Essa complexidade é um fator decisivo no tempo de vida do 0-day.

Para iniciarmos nosso estudo é necessário uma visão geral do sistema de gerenciamento de memória alocada dinâmicamente projetado por Doug Lea e Wolfram Gloger, usado na maioria das distribuições de Linux. Esse estudo é necessário, pois as técnicas para exploração de problemas na heap se baseiam justamente na forma como as funções da glibc tratam as porções de memória alocadas.

O sistema de alocação de memória usado no Linux, através da glibc, não é o mais otimizado que existe, porém com certeza ele está entre um dos mais flexíveis, portáveis e aceitáveis em termos de conservação de espaço em memória. Balanceando consistentemente esses fatores, temos um bom alocador, em termos gerais, para uso em programas que requerem um uso intensivo do recurso de alocação dinâmica de memória.

As propriedades do algoritmo de alocação variam em quatro modos de acordo com a quantidade de memória solicitada:

- Para requisições menores que 64 bytes é usado um alocador baseado em cache, para ser possível uma rápida limpeza da memória quando essas não forem mais necessárias;
- Para requisições intermediárias entre 64 bytes e 512 bytes o algoritmo se adapta a situação do ambiente para escolha da melhor solução para a situação.
- Para requisições de porções maiores que 512 bytes é usado um algoritmo best-fit allocator [2] puro e tradicional (será explicado mais na frente).
- Para requisições maiores que 128 Kb, malloc delega a requisição para o gerenciador de memória do sistema (se esse suportar, no caso do Linux é suportado).

Para indexar certa quantidade de memória alocada, malloc adiciona um cabeçalho que contém a informação da quantidade de memória reservada para aquele segmento, esse cabeçalho será usado para identificação, localização e liberação dessa porção de memória.

Dependendo do estado da porção (livre ou em uso), o cabeçalho pode assumir dois formatos:
.
1) Cabeçalho para porções em uso:

    chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |             Size of previous chunk, if not allocated        | |
            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |             Size of chunk, in bytes                       |M|P|
      mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |             User data starts here...                          .
            .                                                               .
            .             (malloc_usable_size() bytes)                      .
            .                                                               |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |             Size of chunk                                     |
            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

.
2) Cabeçalho para porções livres:

    chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |             Size of previous chunk, if not allocated        | |
            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |             Size of chunk, in bytes                       |M|P|
      mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |             Forward Pointer                                   |
            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |             Backward Pointer                                  |
            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            .                                                               .
            .                                                               |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |             Size of chunk                                     |
            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

.
Em comum, ambas as estruturas possuem o tamanho da porção alocada e a informação do tamanho da porção imediatamente anterior caso essa esteja livre. O cabeçalho de uma porção livre possui mais dois campos que são ponteiros para a porção livre anterior e posterior, formando uma lista duplamente encadeada. Os dois bits menos significativos do segundo byte do cabeçalho corresponde a uma indicação se a porção anterior estar livre (bit “P”) e se a gerencia da porção atual foi delegada ao sistema operacional (bit “M”).

A seguir é apresentada a estrutura de dados usada pela glibc para representar uma porção de memória, nela podemos visualizar os campos usados pelos quatro modos de alocação de memória (nós só iremos trabalhar com porções menores que 512 bytes).
.

struct malloc_chunk {

  INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */

  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;

  /* Only used for large blocks: pointer to next larger size.  */
  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
};

.
Lembrando que dentro do campo “size” da estrutura existem os bits de controle da porção. (esses bits são retirados desse campo através de mascaras).

Um ponto chave é a forma como as porções livres são organizadas e re-aproveitadas na heap. Para otimizar a busca por porções livres de memória o sistema organiza essas em forma de lista duplamente encadeada como mostrado na figura 02.

Fig 02 - Visualização da heap

Fig 02 - Visualização da heap

.
A figura 2 mostra cinco porções alocadas na heap. As porções em azul estão em uso e as em cinza são as livres que possuem links em seus cabeçalhos, dando forma a lista encadeada. A porção de endereço 0×80c5090 é o elemento raiz da lista de porções livres, todo o processo ao iniciar a sua região de heap já cria esse elemento.

O processo de alocação é baseado no algoritmo “Best Fit Allocation” [2], através da pesquisa na lista encadeada de qual a menor porção de memória livre que comporta o tamanho solicitado.

O algoritmo de liberação de uma determinada porção de memória é parte fundamental dentro o sistema de gerencia da heap. Esse algoritmo é responsável pela reciclagem da memória de forma eficiente, observando o estado da heap no momento da liberação para realização de otimizações, garantindo um melhor aproveitamento do espaço em memória e preparando o espaço liberado para ser reaproveitado. O processo de liberação de memória é responsável pela execução de “merges“, evitando uma fragmentação desnecessária da heap. Quanto mais fragmentada a heap mais custoso será o processo de alocação de memória (uma lista maior para fazer busca).

Fiz um “artifício técnico” (a.k.a. gambiarra), para melhor visualização da organização da heap usando a própria estrutura da glibc (vou disponibilizar todo o código dos experimentos [aqui]). São basicamente duas funções:

- __pretty_print_chunk(): Recebe por parâmetro a estrutura da porção e imprime na tela o seu cabeçalho.

void __pretty_print_chunk (struct malloc_chunk *p, int chunk_id) {
  printf("::. Manipulando head do chunk_%02d: \n\n", chunk_id);
  printf("\t- Endereço do chunk:                       [%#x]\n", (uint) p);
  printf("\t- Tamanho do elemento anterior (se livre): [%d]\n" , p->prev_size);
  printf("\t- Tamanho do elemento atual:               [%d]\n" , chunksize(p));
  printf("\t- Foward link:                             [%#x]\n", (uint)p->fd);
  printf("\t- Backward link:                           [%#x]\n", (uint)p->bk);
  printf("\n");
}

- __malloc_char_chunk(): Cria uma nova porção na heap.

struct malloc_chunk *__malloc_char_chunk(uint size) {
  char *temp_buffer = (char *)malloc(size * sizeof(char));
  memset(temp_buffer, 'A', size/2);
  return (struct malloc_chunk *)(temp_buffer - 0x8);
}

.
Usando essas funções conseguimos ter uma interface para visualização das porções alocadas na heap. Vamos usar essa interface para criar quatro porções na memória e vamos liberar a primeira e a terceira porção.
.

int main (int argc, char *argv[]) {
  struct malloc_chunk *chunk_01, *chunk_02,*chunk_03,*chunk_04;
  chunk_01 = __malloc_char_chunk(BUFFER_SIZE);
  chunk_02 = __malloc_char_chunk(BUFFER_SIZE);
  chunk_03 = __malloc_char_chunk(BUFFER_SIZE);
  chunk_04 = __malloc_char_chunk(BUFFER_SIZE);
  free((char *)((uint)chunk_01 + 0x8)); // O free() recebe o endereço do inicio
  free((char *)((uint)chunk_03 + 0x8)); // região de dados da porção
  __pretty_print_chunk(chunk_01, 1);
  __pretty_print_chunk(chunk_02, 2);
  __pretty_print_chunk(chunk_03, 3);
  __pretty_print_chunk(chunk_04, 4);
}

.

::. Manipulando head do chunk_01:

        - Endereço do chunk:                       [0x8cd1680]
        - Tamanho do elemento anterior (se livre): [0]
        - Tamanho do elemento atual:               [208]
        - Foward link:                             [0x80c5090]
        - Backward link:                           [0x8cd1820]

::. Manipulando head do chunk_02:

        - Endereço do chunk:                       [0x8cd1750]
        - Tamanho do elemento anterior (se livre): [208]
        - Tamanho do elemento atual:               [208]
        - Foward link:                             [0x41414141]
        - Backward link:                           [0x41414141]

::. Manipulando head do chunk_03:

        - Endereço do chunk:                       [0x8cd1820]
        - Tamanho do elemento anterior (se livre): [0]
        - Tamanho do elemento atual:               [208]
        - Foward link:                             [0x8cd1680]
        - Backward link:                           [0x80c5090]

::. Manipulando head do chunk_04:

        - Endereço do chunk:                       [0x8cd18f0]
        - Tamanho do elemento anterior (se livre): [208]
        - Tamanho do elemento atual:               [208]
        - Foward link:                             [0x41414141]
        - Backward link:                           [0x41414141]

.
Observe a lista duplamente encadeada de porções livres na heap.
.

Fig 03 - Lista encadeada de porções livres

Fig 03 - Lista encadeada de porções livres


O que acontece se nós liberarmos a segunda porção de memória?
.
O algoritmo do free() irá detectar que as porções três e um também estão livre e criará uma única porção que corresponde a soma das três regiões (um, dois e três).

::. Manipulando head do chunk_01:

        - Endereço do chunk:                       [0x8cfe680]
        - Tamanho do elemento anterior (se livre): [0]
        - Tamanho do elemento atual:               [624]
        - Foward link:                             [0x80c5090]
        - Backward link:                           [0x80c5090]

::. Manipulando head do chunk_02:

        - Endereço do chunk:                       [0x8cfe750]
        - Tamanho do elemento anterior (se livre): [208]
        - Tamanho do elemento atual:               [208]
        - Foward link:                             [0x41414141]
        - Backward link:                           [0x41414141]

::. Manipulando head do chunk_03:

        - Endereço do chunk:                       [0x8cfe820]
        - Tamanho do elemento anterior (se livre): [416]
        - Tamanho do elemento atual:               [208]
        - Foward link:                             [0x41414141]
        - Backward link:                           [0x41414141]

::. Manipulando head do chunk_04:

        - Endereço do chunk:                       [0x8cfe8f0]
        - Tamanho do elemento anterior (se livre): [624]
        - Tamanho do elemento atual:               [208]
        - Foward link:                             [0x41414141]
        - Backward link:                           [0x41414141]

Reparem que na porção quatro diz que existe uma porção livre com 624 bytes (3 * 208) na porção imediatamente anterior. Se obsevarmos o cabeçalho dessa porção vamos verificar que o merge das porções foi realizado com sucesso.

Fig 04 - Região de Merge na heap

Fig 04 - Região de Merge na heap


.
Na Figura 04 mostramos a região em que será realizado o merge. Após a liberação da porção 2 será criada uma nova porção contendo a soma das porções 1, 2 e 3, como mostrado na figura abaixo.
.
Fig 05 - Lista de porções livres após o Merge

Fig 05 - Lista de porções livres após o Merge

.
Nesse tipo de merge, o algoritimo fez alterações na porção livre que estava imediatamente abaixo da porção três para apontar para a nova porção e fez alteração na nova porção para apontar para a porção que era apontada pela porção três.
.

(porção 3)->bd + 8    < -  (porção 3)->fd
(porçao 3)->fd + 12   < -  (porção 3)->bd

.
É justamente explorando essas heurísticas de merge, que conseguimos tirar proveito de falhas no uso da heap. E é ai que a brincadeira começa …

Imagine se pudéssemos manipular o cabeçalho da porção imediatamente após da que nós iríamos liberar (a porção 3 do exemplo anterior)… Isso significa que nós poderíamos manipular os valores dos links usados para ligar a lista de porções livres apos a operação de merge.

Quando é possível preencher uma porção na heap com mais dados do que essa foi inicialmente planejada para comportar, conseguimos sobrescrever regiões da porção de memória conseguinte, colocando um cabeçalho falso nessa seção e criando porções adjacentes falsas, de forma que quando a função free() for chamada recebendo como parâmetro o endereço para essa porção de memória, ela irá buscar informações no header falso e tentar realizar a operação de merge, com isso conseguimos realizar o ataque sobrescrevendo uma posição qualquer na memória que tenha permissão de escrita com um conteúdo especificado (Já que a “porção 3″ será injetada na “porção 2″ através da “porção 1″ que está vulnerável \O/).

É … não é simples de entender mesmo não … leia e releia o parágrafo acima. Desenhe no papel que talvez fique mais fácil. Tudo gira em torno do funcionamento do merge. Eu também pensei a mesma coisa: “O cara que fez isso pela primeira vez devia ter muitas drogas em mente.

Agora, que já temos idéia na prática como o algoritmo de merge funciona, vamos montar um ambiente com dois buffers alocados na heap em que o primeiro está vulnerável e recebe dados direto da linha de comando sem nenhuma espécie de verificação.

Antes de tudo vamos desabilitar algumas proteções existentes no nosso Linux, fazer o bypass desses mecanismos de segurança não faz parte do escopo dessa primeira parte do artigo. Em breve irei escrever um artigo mostrando como funcionam esses mecanismos de proteção e se possível como fazer um bypass de forma eficiente e confiável (não tem quem goste dessas técnicas probabilísticas). Esse artigo sobre as técnicas usadas para proteção da memória, será útil para todos os posts de vulndev publicados aqui no blog.
.
Primeiro vamos desabilitar ASLR (Adress Space Layout Randomization):

root@envy:/# /bin/echo 0 > /proc/sys/kernel/randomize_va_space

.
Habilitar o modo de depuração do mecanismo de proteção em tempo de execução interno a glibc:

export MALLOC_CHECK_=1

.
O código usado como exemplo para todo o resto do artigo será o seguinte:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUFFER_SIZE 200

int main (int argc, char *argv[]) {
  char *buffer_01, *buffer_02;

  buffer_01 = (char *) malloc(BUFFER_SIZE*sizeof(char));
  buffer_02 = (char *) malloc(BUFFER_SIZE*sizeof(char));

  strcpy(buffer_01, argv[1]);

  free(buffer_02);

  return 1;
}

Podemos sobrescrever o “buffer_02” através da vulnerabilidade no “buffer_01“, com isso nós iremos criar duas porções de memória falsas ao redor de “buffer_02“, de forma que quando dermos um free() nessa porção possamos induzir o merge dessas três áreas (fake_01, buffer_02 e fake_02) e com isso explorarmos a falha. O layout do nosso exploit irá ficar como na figura a seguir.

Fig 06 - Layout da heap após a execução do exploit

Fig 06 - Layout da heap após a execução do exploit

Não se esquecer que devemos sobrescrever o cabeçalho da porção dois para sinalizarmos que essa possui uma porção anterior livre, através da flag “P” e ajustarmos o campo de tamanho para a porção terminar no inicio da segunda porção falsa (a que irá conter os endereços). Sem isso, o processo de merge não será iniciado.

Com uma pequena ajuda do interpretador Python podemos construir o nosso exploit.
.

./heap `python -c 'print "A"*184    +    "\xff"*8+"A"*8    +
  "\xf8\xff\xff\xff"+"\xf0\xff\xff\xff"    +    "\xff"*8 + "AAAA" + "BBBB"'`

.
Com o código acima nós conseguiríamos sobrescrever porções específicas do nosso interesse na memória (return pointer, locks, global offset table … ): *(AAAA + 12) = BBBB
.
Quando executamos ele temos:

mabj@envy:~/Documents/project/heap_based_overflow$ ./heap `python -c 'print "A"*188+"\xff"*4+"A"*8+"\xf8\xff\xff\xff"+"\xf0\xff\xff\xff"+"\xff"*8 + "AAAA" + "BBBB"'`
malloc: using debugging hooks
buffer_01 (0x80c8690) - size: [0x15] previous [0xffffffd1]
buffer_02 (0x80c8760)- size: [0xfffffff8] previous [0xfffffff0]

*** glibc detected *** ./heap: free(): invalid pointer: 0x080c8760 ***


.
Essa mensagem ocorre devido a seguinte verificação de segurança interna a glibc:
.

  /* Little security check which won't hurt performance: the
     allocator never wrapps around at the end of the address space.
     Therefore we can exclude some size values which might appear
     here by accident or by "design" from some intruder.  */
if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
    || __builtin_expect (misaligned_chunk (p), 0))
  {
    errstr = "free(): invalid pointer";
  errout:
    malloc_printerr (check_action, errstr, mem);
    return;
  }

.
Pronto. É aqui que chegamos ao fim da primeira etapa no nosso estudo a respeito de vulnerabilidades baseadas na heap. Já sabemos o que é a heap, como funciona os mecanismos de gerencia da heap e onde estão os pontos de exploração. No próximo passo, vamos focar no estudo dos mecanismos de proteção e na exploração.

<frustation mode=”on”>
Tudo que foi mostrado até agora foram os fundamento para exploração de vulnerabilidades baseadas na heap. Todos os exploit writers e heap lovers já passaram por tudo o que foi descrito acima até chegar no estado que vamos mostrar no segundo tempo do jogo (VD03.2). A quatro anos atrás o conhecimento acumulado até agora seria o suficiente para a nossa exploração. Hoje em dia, a glibc mais recente possui mecanismos de proteção, como o mostrado acima, que impede nosso ataque. Mas não desanimem! O ‘#’ já está mais perto do que vocês imaginam.
</frustation>
.
Em balanço está a Força. A Escuridão e a Luz. Sem um, não há o outro. O Lado Negro tentador é. Rápido, fácil no início, mas uma armadilha é o Lado Negro. Mau, corrupto. Uma vez que se começa no Lado Negro, para sempre dominará seu destino. Para o caminho da Luz, paciência você precisa. Controle. Paz e harmonia ele é.”

yoda
.

TO BE CONTINUED …

Referências:

[1] http://www.gnu.org/software/libc/
[2] http://g.oswego.edu/dl/html/malloc.html
[3] The Shellcoder’s handbook, segunda edição, capitulo 5 (Gera, FX e Chris Anley)
[4] http://www.w00w00.org/files/articles/heaptut.txt
[5] http://www.phrack.org/issues.html?issue=61&id=6&mode=txt
[6] http://www.phrack.org/issues.html?issue=57&id=8&mode=txt
[7] http://www.phrack.org/issues.html?issue=57&id=9&mode=txt
[8] http://sourceware.org/glibc/

Projeto Ruby Neuron

July 2nd, 2009 Marcos Álvares 2 comments

Em Abril iniciei um projeto open source chamado Ruby Neuron, esse projeto tem por objetivo fazer uma ligação entre as novas técnicas na área de Redes Neurais Artificiais (RNAs) e a aplicação destas para solução de problemas reais encontrados na sociedade. A inspiração inicial surgiu da observação que a ciência avança bem rápido, descobrindo novas técnicas e novas otimizações de algorítimos, e essas não são usadas nos frameworks tradicionais para melhoria de performances e resultados. Outro objetivo, não menos importante que o primeiro, é desfazer o estigma relacionado a complexidade relacionada ao uso das técnicas de inteligencia computacional.

O Ruby Neuron além de fornecer uma poderosa ferramenta para classificação e inferência ainda funciona como uma API flexível para simulação e testes de novos modelos de Redes Neurais Artificiais. Com isso teremos uma ferramenta para auxílio a pesquisa e desenvolvimento na área de inteligencia computacional.

O escopo do projeto será restrito a Redes Neurais Artificiais, não se estendendo a outras técnicas de computação inteligente (tenho experiências negativas de projetos generalistas). O mesmo tempo dedicado a desenvolvimento do código será dedicado a infra-estrutura, documentação e testes, de forma a construirmos uma boa base de exemplos e documentos disponibilizados para a comunidade.

O escopo e a infra-estrutura do projeto já estão formados, em breve será lançado o primeiro release e com isso espero receber novos usuários, desenvolvedores e pesquisadores.

Décimo Fórum Internacional de Software Livre

June 28th, 2009 Marcos Álvares 3 comments

No período de 24 ao dia 27 de Junho ocorreu na PUC de Porto Alegre a décima edição do FISL (Fórum Internacional de Software Livre), o qual tive o prazer de participar. Foi um evento de grandes proporções, contando com mais de oito mil inscritos, oito palestras simultâneas e nove rodadas de palestras por dia, workshops e oficinas.

A princípio fiquei desconfiado, pois um evento de “software livre” é algo que abrange as mais diversas áreas da computação, algo muito generalista e diferente do modelo de evento mais especifico que acompanho. A diversidade de temas foi impressionante, tentando satisfazer desde profissionais de administração de sistemas à designers gráficos. Consegui montar uma agenda, com praticamente todos os slots lotados só com palestras com temas de meu interesse (\O/).

Stands de empresas no FISL10

Stands de empresas no FISL10

Tive a oportunidade de assistir algumas apresentações e a que me chamou mais atenção foi a de D.J. Bernstein (o criador do djbdns [1] e qmail [2]) sobre uma alternativa ao modelo de proteção usado para o protocolo DNS. Ele mostrou os problemas de segurança encontrados na infra-estrutura de tradução de nomes de uma forma concisa e as dificuldades encontradas na tentativa de achar soluções para tais problemas. DJB propôs uma alternativa a solução até então usada (o DNSSEC [3]), chamada DNSCurve [4] que faz uso de algorítimos criptograficos baseados curvas elípticas [5](Elliptic Curve Cryptography).

Bernstein é pesquisador ligado a universidade de Illinois, matemático e engenheiro de software. Na minha opinião, o cara é um exemplo de engenharia e pragmatismo. Tem uma visão ímpar de problemas centrais na infra-estrutura da internet além de se expressar muito bem.

DJB falando sobre o DNSCurve

DJB falando sobre o DNSCurve

O evento também contou com a presença de dinossauros do software livre como John Maddog Hall e Richard Stalman (falando de filosofia, liberdade e etc).

Outro ponto alto do evento foi a apresentação de Peter Sunde mostrando a sua “cruzada”, em um formato bem descontraído, com o Pirate Bay.

Peter Sunde e o Pirate Bay

Peter Sunde e o Pirate Bay

Em fim, o FISL foi um evento muito enriquecedor e bem organizado, recomendo a todos e com certeza voltarei próximo ano.

Depurando com o GDB na prática

April 12th, 2009 Marcos Álvares 4 comments

Recebi algum feedback a respeito da série sobre desenvolvimento para vulnerabilidades e percebi que muitas pessoas estão com dificuldades com o GDB (GNU Debugger), que é uma excelente ferramenta, de código aberto, para estudo e depuração de problemas em softwares. Por isso, resolvi escrever esse post, como uma tentativa de apresentar o básico das funcionalidades desse depurador.

Em primeiro lugar, vale salientar, que o que irei listar aqui é apenas o básico de depuração usando o GDB em relação a imensidão de recursos que essa  ferramenta provê e que é muito bem documentado [1], [2] e [3].

Esse depurador, que já possui 21 anos, foi escrito por Richard Stallman em 1988 sob a licença GPL General Public License da GNU para auxiliar no desenvolvimento de aplicações em C, C++ e Fortran. Ele oferece suporte a diversas arquiteturas como: Alpha, ARM, H8/300, System/370, System 390, X86 e X86-64, IA-64 “Itanium”, Motorola 68000, MIPS, PA-RISC, PowerPC, SuperH, SPARC, VAX. Em fim, é uma ferramenta já madura, usada e aperfeiçoada por muitas gerações de programadores como apoio ao desenvolvimento de softwares.

Muitas pessoas confundem os termos disassembler (decompilador) com debugger (depurador) e há uma diferença significativa entre essas duas categorias de ferramentas. A primeira é usada para fazer engenharia reversa de código para análise de malwares e forense, essas ferramentas tentam tornar mais simples a análise de um executável fazendo compilação de linguagem de máquina apara uma linguagem cuja sintaxe seja mais próxima da natural ou da linguagem de programação original do código. Os depuradores funcionam como ferramenta de apoio ao desenvolvimento permitindo ao programador fazer pausas na execução e alterações no ambiente para análise do comportamento da execução do sistema. Em suma, uma é mais apropriada para entender o significado de um código e a outra para auxílio ao desenvolvimento e observação do comportamento da execução de um código.

Para todos os exemplos desse artigo será usado o seguinte código:

#include <stdio.h>
#include <stdlib.h>

int soma (int a, int b) {
        return a + b;
}

int main() {
        printf("\n\n\t[+] A soma de 2 + 3 = %d\n\n", soma(2,3));
        return 0;
}

Vamos mostrar o funcionamento de duas ferramentas: o IDApro (decompilador e depurador) e o GDB (depurador).

O IDApro [4], é sem sombra de dúvidas é o engenho mais usado para fazer engenharia reversa do mundo. Um ponto muito positivo é que o IDA é altamente extensível, possibilitando a construção de plugins para análise estática e visualização de código. O IDA é um software privado (módica quantia de U$ 515,00), desenvolvido por Ilfak Guilfanov e é distribuído pela Hex-Rays[5].

Em primeiro lugar, vamos mostrar o uso do IDApro (a versão para Linux) para decompilar o programa acima exibido. Note como a interface gráfica torna a leitura do código Assembly mais simples, mostrando os segmentos do programa em memória de uma forma que permite você navegar e fazer saltos para qualquer endereço exibido na tela apenas clicando em cima dos símbolos.

Decompilação da função main pelo IDApro

Fig 01 - Decompilação da função main pelo IDApro

Fig 02 - Decompilação da função soma

Fig 02 - Decompilação da função "soma"

Fig 03 - Visualização de contexto de registradores

Fig 03 - Visualização de contexto dos registradores

A versão para Windows possui um output mais intuitivo em forma de grafo, explicitando o fluxo do software. Segue um screenshot do IDA usando um plugin de visualização chamado Binnavi desenvolvido pela Zynamics [6]:

Fig. 05 - IDA usando o plugin Binnavi

Fig. 04 - IDA usando o plugin Binnavi

Essa foi uma visão geral sobre o IDA, porém, como foco desse post é depurar usando o GDB, agora vamos separar alguns grupos de funções dessa ferramenta e tentar detalhar cada categoria de funções através de exemplos. Apesar de ter integração com diversas IDEs como o Eclipse e o Kdevelop o GDB possui um ponto fraco que é o fato de não possuir uma interface gráfica oficial. Porém, a maior parte dos usuários do GDB preferem usá-lo através da sua interface texto, que uma vez aprendida a sintaxe dos comandos se ganha uma boa flexibilidade e agilidade no processo de depuração.

* Execução:

Vamos compilar e executar nosso programa passando parâmetros para o nosso executável (mesmo que ele não esteja esperando). Vamos mostrar como se inicia o processo de depuração usando o GDB e a sintáxe para passagem de parâmetros através deste.

$ gcc -ggdb -o teste teste.c

$ ./teste
        [+] A soma de 2 + 3 = 5

$ gdb teste
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...
(gdb)

Aqui nós “caímos” no prompt de comando do GDB. O nosso binário “teste” ainda não foi executado. O que acontece é que agora o GDB está funcionando como um “proxy” entre o software depurado e o sistema operacional. Isso significa que toda chamada de sistema, antes de chegar no sistema operacional, passará por dentro do GDB e com isso ele pode controlar e examinar o nosso processo. Para fazer tal façanha o GDB faz uso de uma biblioteca chamada ptrace [7].

Posição do GDB em relação ao SO e ao processo depurado

Fig. 05 - Posição do GDB em relação ao SO e ao processo depurado

Para executar o programa no GDB usamos o comando “run” ou simplesmente o atalho “r“:

(gdb) run
Starting program: /home/mabj/tmp/gdb/teste
        [+] A soma de 2 + 3 = 5
Program exited normally.

(gdb) run param1 param2    < --- Executando o nosso binário passando parâmetros
Starting program: /home/mabj/tmp/gdb/teste param1 param2
        [+] A soma de 2 + 3 = 5
Program exited normally

Note a sintaxe para passar parâmetros, pois isso será muito útil em diversas situações. Nós podemos também usar código shell para formar nossos parâmetros e com isso gerar entradas que não são triviais de serem geradas na mão:

(gdb) run $'\x41\x41'
Starting program: /home/mabj/tmp/gdb/teste $'\x41\x41'
        [+] A soma de 2 + 3 = 5
Program exited normally.
(gdb) r $(perl -e "print 'marcos'")
Starting program: /home/mabj/tmp/gdb/teste $(perl -e "print 'marcos'")
        [+] A soma de 2 + 3 = 5
Program exited normally.

* Exibindo o código:

Nessa sessão vamos mostrar os comandos de visualização e navegação no código Assembly do processo depurado. O comando principal é o “disassemble“, que mostra o código referente a um símbolo conhecido no nosso programa. No nosso caso nós temos duas funções no nosso programa: “main” e “soma”. Vamos mostrar o Assembly da nossa função ” main”.

(gdb) disassemble main
Dump of assembler code for function main:
0x080483d1 <main+0>:    lea    0x4(%esp),%ecx
0x080483d5 <main+4>:    and    $0xfffffff0,%esp
0x080483d8 <main+7>:    pushl  -0x4(%ecx)
0x080483db <main+10>:   push   %ebp
0x080483dc <main+11>:   mov    %esp,%ebp
0x080483de <main+13>:   push   %ecx
0x080483df <main+14>:   sub    $0x14,%esp
0x080483e2 <main+17>:   movl   $0x3,0x4(%esp)
0x080483ea <main+25>:   movl   $0x2,(%esp)
0x080483f1 <main+32>:   call   0x80483c4 <soma>
0x080483f6 <main+37>:   mov    %eax,0x4(%esp)
0x080483fa <main+41>:   movl   $0x80484e0,(%esp)
0x08048401 <main+48>:   call   0x80482f8 <printf@plt>
0x08048406 <main+53>:   mov    $0x0,%eax
0x0804840b <main+58>:   add    $0x14,%esp
0x0804840e <main+61>:   pop    %ecx
0x0804840f <main+62>:   pop    %ebp
0x08048410 <main+63>:   lea    -0x4(%ecx),%esp
0x08048413 <main+66>:   ret
End of assembler dump.

Podemos exibir o código das outras funções presentes dentro do nosso bloco de código principal.

(gdb) disassemble soma
Dump of assembler code for function soma:
0x080483c4 <soma+0>:    push   %ebp
0x080483c5 <soma+1>:    mov    %esp,%ebp
0x080483c7 <soma+3>:    mov    0xc(%ebp),%edx
0x080483ca <soma+6>:    mov    0x8(%ebp),%eax
0x080483cd <soma+9>:    add    %edx,%eax
0x080483cf <soma+11>:   pop    %ebp
0x080483d0 <soma+12>:   ret
End of assembler dump.
(gdb) disassembe printf
Dump of assembler code for function printf:
0xb7f70c10 <printf+0>:  push   %ebp
0xb7f70c11 <printf+1>:  mov    %esp,%ebp
0xb7f70c13 <printf+3>:  push   %ebx
0xb7f70c14 <printf+4>:  call   0xb7f3d4bf
0xb7f70c19 <printf+9>:  add    $0x1103db,%ebx
0xb7f70c1f <printf+15>: sub    $0xc,%esp
0xb7f70c22 <printf+18>: lea    0xc(%ebp),%eax
0xb7f70c25 <printf+21>: mov    %eax,0x8(%esp)
0xb7f70c29 <printf+25>: mov    0x8(%ebp),%eax
0xb7f70c2c <printf+28>: mov    %eax,0x4(%esp)
0xb7f70c30 <printf+32>: mov    -0xbc(%ebx),%eax
0xb7f70c36 <printf+38>: mov    (%eax),%eax
0xb7f70c38 <printf+40>: mov    %eax,(%esp)
0xb7f70c3b <printf+43>: call   0xb7f66990 <vfprintf>
0xb7f70c40 <printf+48>: add    $0xc,%esp
0xb7f70c43 <printf+51>: pop    %ebx
0xb7f70c44 <printf+52>: pop    %ebp
0xb7f70c45 <printf+53>: ret
End of assembler dump.

Quando o processo é carregado em memória existe uma sessão específica chamada “.text” que é onde ficará o código da nossa aplicação. Nós podemos exibir trechos essa sessão através do endereço direto. Por exemplo, nós sabemos que o endereço da nossa função “soma” é “0×080483c4“, nós podemos solicitar ao GDB o Assembly dessa região, em específico, através do comando “x/(n)i” no lugar de “n” nós colocamos a quantidade de posições que após aquele endereço, será exibido o código.

(gdb) x/i 0x080483c4
0x80483c4 <soma>:       push   %ebp
(gdb) x/5i 0x080483c4
0x80483c4 <soma>:       push   %ebp
0x80483c5 <soma+1>:     mov    %esp,%ebp
0x80483c7 <soma+3>:     mov    0xc(%ebp),%edx
0x80483ca <soma+6>:     mov    0x8(%ebp),%eax
0x80483cd <soma+9>:     add    %edx,%eax

Com esses comandos nós podemos visualizar qualquer parte do espaço de endereçamento do processo que contenha código, não apenas a região “.text” mas qualquer região que possua instruções válidas.

* Manipulação de Breakpoints:

A função dos breakpoints é solicitar ao GDB que ele suspenda a execução do software em um determinado ponto, para que nós analisemos o contexto de memória e registradores alterados. O comando para colocarmos um breakpoint é o “breakpoint *n“, onde “n” é o endereço onde você quer que o breakpoint seja colocado e o “*” é para indicar que ali você esta passando um endereço diretamente para o depurador. Para exemplificar, vamos colocar um endereço antes de entrar na função “soma” (0×080483f1 <main +32>) e outro antes do printf (0×08048401 <main +48>).

Para visualizar os breakpoints podemos usar o comando “info breakpoint“. Cada breakpoint tem um identificador, mostrado na coluna “Num“, quando executarmos nosso programa, o fluxo dos breakpoints percorridos será indicado por esse índice.

(gdb) break *0x080483f1
Breakpoint 1 at 0x80483f1: file teste.c, line 9.

(gdb) break *0x08048401
Breakpoint 2 at 0x8048401: file teste.c, line 9.

(gdb) info breakpoint
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x080483f1 in main at teste.c:9
2       breakpoint     keep y   0x08048401 in main at teste.c:9

Para excluir um breakpoint é usado o comando “delete breakpoint ID“, onde ID é o número identificador do breakpoint.

(gdb) delete breakpoint 2
(gdb) info breakpoint
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x080483f1 in main at teste.c:9

Com isso só restou o breakpoint 1 que é acionado antes da chamada a função “soma“. Vamos ver como que isso funciona ao executarmos o nosso programa exemplo com o comando de execução que já vimos.

(gdb) run
Starting program: /home/mabj/tmp/gdb/teste
Breakpoint 1, 0x080483f1 in main () at teste.c:9
9               printf("\n\n\t[+] A soma de 2 + 3 = %d\n\n", soma(2,3));

O fluxo do nosso programa esta paralisado no endereço 0×080483f1, que é o local onde temos a chamada para a função “soma“. Para darmos continuidade a execução do software podemos fazer isso passo-a-passo (step-by-step) que executa o comando chamando uma instrução por vez e parando o fluxo novamente após a chamada de cada instrução, essa modalidade é realizada através do comando “step“. Ou podemos mandar o programa continuar a execução até que ache outro breakpoint através do comando “continue“.

(gdb) run
Starting program: /home/mabj/tmp/gdb/teste

Breakpoint 1, 0x080483f1 in main () at teste.c:9
9               printf("\n\n\t[+] A soma de 2 + 3 = %d\n\n", soma(2,3));
(gdb) step
soma (a=2, b=3) at teste.c:5
5               return a + b;
(gdb) step
6       }

Coloquei novamente o breakpoint 2 para que possamos testar o comando “continue“.

(gdb) run
Starting program: /home/mabj/tmp/gdb/teste

Breakpoint 1, 0x080483f1 in main () at teste.c:9  < -- Parou em "soma"
9               printf("\n\n\t[+] A soma de 2 + 3 = %d\n\n", soma(2,3));
(gdb) continue
Continuing.
Breakpoint 2, 0x08048401 in main () at teste.c:9   < -- Parou em "printf"
9               printf("\n\n\t[+] A soma de 2 + 3 = %d\n\n", soma(2,3));
(gdb) continue
Continuing.
        [+] A soma de 2 + 3 = 5
Program exited normally.

Com esses comandos nós podemos parar o fluxo do programa e ir executando passo-a-passo para analisar o contexto e as modificações realizadas na memória e nos registradores. Aprendemos até agora a visualizar o código e controlar o fluxo de execução. Vamos mostrar como visualizar informações importantes para o entendimento do código depurado.

* Visualizando informações:

Vamos mostrar alguns comandos para visualizar conteúdo na memória, isso é bem útil para visualizarmos o estado de variáveis e de endereços específicos na memória ao decorrer da execução do programa. Usando o nosso modo de compilação específico para depuração, usando o (”gcc -ggdb“), nós podemos ver as variáveis diretamente pelo seu símbolo. Para exemplificar coloquei um breakpoint na adição realizada na função “soma“. Vamos visualizar os valores dos parâmetros da função “soma” armazenados nas variáveis “a” e “b“. Vamos alterar algum valor e mandar continuar a execução do código.

(gdb) run
Starting program: /home/mabj/tmp/gdb/teste
Breakpoint 4, soma (a=2, b=3) at teste.c:5
5               return a + b;
(gdb) print a
$4 = 2  <-- O valor do parâmetro "a"
(gdb) print b
$5 = 3 <-- O valor do parâmetro "b"
(gdb) set variable a=50 <; -- Alterei o valor da variável "a"
(gdb) continue
Continuing.
        [+] A soma de 2 + 3 = 53  <-- Mudei  o resultado do programa
Program exited normally.

É possível também, mandar alterar uma região de memória especificada pelo seu endereço diretamente. Vamos exemplificar com a variável “a” do parâmetro de “soma“. Vamos obter o endereço dessa variável e a partir deste obtermos o seu conteúdo.

(gdb) run
Starting program: /home/mabj/tmp/gdb/teste
Breakpoint 4, soma (a=2, b=3) at teste.c:5
5               return a + b;
(gdb) x/x &a  <-- Consegui o endereço de "a"
0xbf98bef0:     0x00000002
(gdb) x/x 0xbf98bef0 <-- Obtive diretamente o conteúdo de "a"
0xbf98bef0:     0x00000002
(gdb) set variable *0xbf98bef0=0x30
(gdb) continue
Continuing.
        [+] A soma de 2 + 3 = 51 <-- Note que alteramos o valor de "a"
Program exited normally.

Observe que a mesma notação e semântica de ponteiro que temos em C/C++ pode ser usada com variáveis no GDB.
Para visualizar os valores que estão nos registradores durante a depuração, nós podemos usar o comando “info register” para visualizar uma tabela com todos os registradores e seus respectivos valores ou o comando “info register r” onde “r” é o nome do registrador a ser visualizado.

(gdb) info register
eax            0xbfe49c64       -1075536796
ecx            0xbfe49be0       -1075536928
edx            0x1      1
ebx            0xb7f19ff4       -1208901644
esp            0xbfe49bb0       0xbfe49bb0
ebp            0xbfe49bc8       0xbfe49bc8
esi            0x8048430        134513712
edi            0x8048310        134513424
eip            0x80483e2        0x80483e2 
eflags 0x282 [ SF IF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 (gdb) info register eax <-- Vendo o valor armazenado no registrador eax eax 0xbfe49c64 -1075536796

* Examinando a Pilha:

A pilha é a região da memória alocada para armazenar os valores de variáveis locais a um determinado escopo (funções, métodos, blocos), ela funciona de acordo com um algoritmo de LIFO (ultimo a entrar será o primeiro a sair) [8]. Um frame na pilha, significa a porção de memória reservada para armazenar os valores presentes dentro de um escopo durante sua execução. O GDB organiza e exibe a pilha em forma de frames, durante a depuração de problemas, nós podemos acompanhar e manipular a o fluxo de frames na pilha. Assim podemos fazer backtraces para observarmos qual o caminho percorrido pelo nosso código até então ou o aninhamento de chamadas a funções e blocos.

Para exemplificar, colocamos um breakpoint dentro da função “soma” e digitamos o comando backtrace para exibir a lista de frames armazenadas na pilha, nós podemos também apenas digitar o comando “frame” para exibir o frame atual.

(gdb) run
Starting program: /home/mabj/tmp/gdb/teste
Breakpoint 4, soma (a=2, b=3) at teste.c:5
5               return a + b;
(gdb) backtrace
#0  soma (a=2, b=3) at teste.c:5
#1  0x080483f6 in main () at teste.c:9
(gdb) frame
#0  soma (a=2, b=3) at teste.c:5
5               return a + b;

Nós podemos mudar o contexto do nosso frame atual para depurar dentro de um escopo mais externo com o comando “frame n”. No exemplo anterior nós suspendemos a execução com um breakpoint dentro da função “soma”, com isso nós podemos investigar todas as variáveis dentro dessa função. Para investigar o contexto de “main” temos que mudar o nosso frame atual.

(gdb) r
Starting program: /home/tmp/gdb/teste
Breakpoint 4, soma (a=2, b=3) at teste.c:5
5               return a + b;
(gdb) backtrace
#0  soma (a=2, b=3) at teste.c:5
#1  0x080483f6 in main () at teste.c:9
(gdb) frame
#0  soma (a=2, b=3) at teste.c:5 <-- Frame atual é o "0"
5               return a + b;
(gdb) print a <-- Conseguimos enxergar a variável "a"
$6 = 2
(gdb) frame 1 <-- Mudamos o contexto atual para o frame "1"
#1  0x080483f6 in main () at teste.c:9
9               printf("\n\n\t[+] A soma de 2 + 3 = %d\n\n", soma(2,3));
(gdb) frame
#1  0x080483f6 in main () at teste.c:9
9               printf("\n\n\t[+] A soma de 2 + 3 = %d\n\n", soma(2,3));
(gdb) print a <-- Agora não enxergamos mais a variável "a"
No symbol "a" in current context.

Vamos depurar agora usando como referência o nosso stack pointer (esp), nesse registrador temos o endereço do topo da nossa pilha. Nós sabemos que os parâmetros da função “soma” estão na pilha e podemos navegar por endereços acima do nosso stack pointer (durante o breakpoint interno a função “soma”) até encontrarmos esse valor e depois manipulá-lo diretamente através do seu endereço.

(gdb) i r esp
esp            0xbfc0bc18       0xbfc0bc18
(gdb) x/x esp+4
No symbol "esp" in current context.
(gdb) x/x $esp+4
0xbfc0bc1c:     0x080483f6
(gdb) x/x $esp+4
0xbfc0bc1c:     0x080483f6
(gdb)
0xbfc0bc20:     0x00000002  <-- Variável "a"
(gdb)
0xbfc0bc24:     0x00000003  <-- Variável "b"
(gdb) set variable *0xbfc0bc24=0x30 <-- colocamos 48 dentro do valor de b
(gdb) continue
Continuing.
        [+] A soma de 2 + 3 = 50 <-- Note que alteramos o valor de "b"
Program exited normally.

Bom, esse artigo foi uma visão bem geral de algumas funcionalidades do GNU Debugger, espero que essas dicas sejam úteis para depuração de problemas em softwares reais. Recomendo fortemente a leitura dos artigos indicados nas referências. Aprender a depurar é semelhante a aprender a programar, sem prática não temos resultados, por isso é necessário que você reserve um tempo para praticar os conceitos apresentados. Espero que esse artigo ajude as pessoas que estavam com dificuldades nos artigos de desenvolvimento para vulnerabilidades. ; ]

[Referências]
[1] Documentação de uso do GDB
[2] Documentação do engenho interno ao GDB
[3] Tutorial sobre o GDB
[4] Site do IDApro
[5] Site da empresa hex-rays que fornece o IDApro
[6] Site da empresa Zynamics que fornece o Binnave
[7] Manpage da biblioteca ptrace
[8] Algoritmo usado para a construção de uma pilha

Categories: Technology Tags: , , ,