Jump to content
  • Sign in to follow this  

    Artigos


    Fernando Mercês
    Keygen, que abrevia “Key Generator” é um software capaz de gerar chaves válidas de registro para um software protegido. A prática desta técnica pode (e provavelmente vai) infringir algumas leis quando usada em softwares comerciais. No entanto, existem alguns desafios na internet chamados de “keygenmes”, que são programas feitos justamente para serem quebrados. O desafio está em criar keygens para eles. Este é um estudo muito interessante que treina bastante a lógica, matemática, assembly e até mesmo massageia o ego, se você vencer.
    Obviamente você não tem acesso ao código-fonte do desafio, então um disassembler, software capaz de interpretar os bytes do binário como mnemônicos assembly, precisará ser usado. Para facilitar o keygenning, também é interessante usar um debugger.
    Neste artigo eu vou usar um poderoso debugger e disassembler multiplataforma chamado EDB, projeto este que apoio. Ele foi escrito em C++ e Qt (então você precisa ter a Qt instalada para rodar). No github do projeto há instruções para instalação no Debian/Ubuntu, Fedora, Gentoo e para compilação (em qualquer ambiente).
    Neste artigo, usaremos dois arquivos:
    keygenme.c -> código-fonte do keygenme (desafio). keygen.c -> código-fonte de um keygen para o keygenme (solução). NOTA: O texto prevê que não conhecemos o código-fonte do desafio. Logo, se você olhar o fonte do keygenme (keygenme.c) ou do keygen proposto (keygen.c) antes de seguir a leitura, tudo perde a graça! Fica tranqüilo que no final dá tudo certo. Aliás, se ainda não deu certo, é porque não chegou ao final.
    Depois de baixar os caras, pode compilar só o keygenme.c, com a sintaxe básica do gcc:
    $ gcc -o keygenme keygenme.c O keygenme espera que você passe como argumentos o seu nome e sua chave (esta última você não sabe). Vamos ver se damos sorte:
    $ ./keygenme Fernando FCKGW-90908-30BCD Chave inválida Claro que eu chutei uma chave qualquer para simular uma tentativa de registro mas não acertei (eu tinha alguma chance de acertar?). Até agora não sabemos nada sobre a chave. Ela pode ser alfanumérica, só numérica, ter ou não hífens, enfim, as possibilidades são infinitas. Desafio é desafio!
    Vamos abrir o binário no EDB. Aliás, uma das facilidades dele é já colocar automaticamente um breakpoint no entrypoint do binário, ou seja, ao abrir um binário, basta mandar o EDB rodá-lo (tecla F9) e ele parará justamente no início da main(). Após fazer isso, você provavelmente verá algo como:
    00000000004006cc: push rbp 00000000004006cd: mov rbp, rsp 00000000004006d0: push rbx 00000000004006d1: sub rsp, 56 00000000004006d5: mov dword ptr [rbp-52], edi 00000000004006d8: mov qword ptr [rbp-64], rsi 00000000004006dc: mov rax, qword ptr [rbp-64] NOTA: Ao compilar o keygenme.c na sua máquina, os endereços vão mudar, mas é só ter isso em mente e seguir tranqüilo, fazendo as adaptações.
    Aqui você tem que notar 4 coisas:
    A seta vermelha (que aparece no EDB) indica qual é a próxima instrução a ser executada pelo processador. A sintaxe do assembly é Intel. Eu estou num sistema de 64-bits, por isso os registradores aparecem como rbp, rbx, rsp, rax, etc. Se fosse um sistema de 32-bits, seria ebp, ebx, esp, eax e por aí vai (só trocar o “r” pelo “e”). De um modo geral, toda função (e a main não é uma exceção), começa com um “push rbp” e termina com um “ret”. Não temos tempo para destrinchar linha a linha, mas vamos tratar das linhas mais importates. Aliás, o debugger te ajuda a não precisar de muito conhecimento em assembly para entender o que as linhas fazem.
    Teclando F8 (Step out), passamos para a próxima instrução. Podemos ir teclando F8 calmamente até chegar nas seguintes linhas:
    00000000004006e7: test rax, rax 00000000004006ea: jz 0x00000000004006fc As instruções acima trabalham em conjunto. Em 0x4006e7, o registrador rax é verificado pela instrução test. Se seu valor for zero, a próxima instrução será um salto para 0x4006fc. Se seguirmos dando F8, veremos que este salto vai nos jogar para a linha a seguir:
    00000000004006fc: call 0x00000000004006b4 Nela tem a instrução call, que é uma chamada de função. Mais um F8 e pimba, o programa encerra. Ok, e para que eu faço isso? Bem, a questão é perguntar-se: por que o programa encerrou? Porque ele chamou a função 0x4006b4. E por que ele a chamou? Porque o salto em 0x4006ea aconteceu. E por que o salto aconteceu? Porque o teste em 0x4006e7 deu verdadeiro. E porque deu verdadeiro? Porque rax estava zerado. Logo, para que o programa não encerre logo no início de sua execução, é preciso ter algo em rax. Para não alongar muito o texto, eu vou dar a cola: rax precisa apontar para um argumento (char *), do contrário, não dá pra começar a brincadeira né? A execução que acompanhamos foi como se tivéssemos feito:
    $ ./keygenme Chave inválida E pronto. Não passamos argumentos, então não há o que testar. O programa encerra depois de imprimir a mensagem de erro. Vamos corrigir isso.
    No EDB, é preciso colocar os argumentos em Options -> Applications Arguments. Coloquei dois argumentos para chamar o binário como:
    $ ./keygenme Fernando 30303030 Agora sim a gente passa naquele teste teste em 0x4006ea (e também no teste em 0x4006fa, que testa o segundo argumento). Mas é preciso reabrir o arquivo no EDB depois de configurar argumentos.
    Começando novamente com F8, ao passar por 0x4006ea sem pular, caímos em 0x4006ec. Esta instrução mov copia (pois é, ela não move!) o endereço de memória do primeiro argumento para o registrador rax.
    00000000004006ec: mov rax, qword ptr [rbp-64] Ainda bem que agora ele não é nulo. Do contrário, teríamos um belo Segmentation Fault. Por isso do teste antes.
    O mesmo acontece em 0x400701, só que para o segundo argumento. Agora muita atenção no trecho abaixo:
    0000000000400709: mov rax, qword ptr [rax] 000000000040070c: mov rdi, rax 000000000040070f: call 0x0000000000400588 Novamente o primeiro argumento (Fernando) é endereçado em rax. E ao passar da call em 0x40070f, o número 8 é posto em rax. Sabendo que o rax é o registrador geralmente usado tanto para passagem de parâmetro quanto para retorno de função, podemos entender que o endereço da string “Fernando” foi passado para a função 0x400588 e esta retornou 8. Consegue ver alguma relação? O que o número 8 tem a ver com a string “Fernando”? Se quiser confirmar sua suspeita, pode mudar este argumento no EDB, reabrir o keygenme e avaliar o novo número de retorno.
    Mais abaixo, segue uma tremenda sacanagem:
    0000000000400717: cmp dword ptr [rbp-40], 3 000000000040071b: jle 0x0000000000400723 000000000040071d: cmp dword ptr [rbp-40], 20 0000000000400721: jle 0x0000000000400728 0000000000400723: call 0x00000000004006b4 Em 0x400717 o valor 8 (no caso do meu exemplo) é comparado com 3. Na seqüência vemos um salto jle (Jump if Lower or Equals) para 0x400723. E neste endereço, tem uma call pra 0x4006b4. Lembra desta call? Não foi ela quem encerrou o programa da outra vez? Não podemos cair nela. Sorte que 8 é maior que 3.
    Certo, não saltamos. Agora abaixo:
    000000000040071d: cmp dword ptr [rbp-40], 20 0000000000400721: jle 0x0000000000400728 0000000000400723: call 0x00000000004006b4 0000000000400728: mov eax, dword ptr [rbp-40] Outra comparação. Desta vez para ver se o 8 é menor ou igual a 20. Se não for, ele não salta e cai na call maldita novamente, para encerrar o programa. Que conclusões podemos chegar?
    O programa testa se os dois argumentos existem. Basta que um não exista para que o programa seja encerrado. O 8 visto aqui é o tamanho da string nome (primeiro parâmetro, que no meu exemplo foi “Fernando”). Caso o tamanho da string não esteja entre 4 e 20 caracteres, o programa encerra. Seguindo com F8, chegamos neste bloco:
    000000000040075d: movsxd rax, rbx 0000000000400760: add rax, qword ptr [rbp-32] 0000000000400764: movzx eax, byte ptr [rax] 0000000000400767: movsx eax, al 000000000040076a: mov edi, eax 000000000040076c: call 0x00000000004005a8 0000000000400771: test eax, eax 0000000000400773: jnz 0x000000000040077a 0000000000400775: call 0x00000000004006b4 000000000040077a: add ebx, 1 000000000040077d: cmp ebx, dword ptr [rbp-40] 0000000000400780: jl 0x000000000040075d Se você não conhece assembly, pode ser que não esteja claro, mas o debugger com certeza vai te entregar que isso é um loop determinado (um for). Vou deixar essa análise de lado, mas quem não conhece pode olhar o fonte em C depois e tentar identificá-lo aqui.
    Mais abaixo, outro loop:
    0000000000400789: movsxd rax, rbx 000000000040078c: add rax, qword ptr [rbp-32] 0000000000400790: movzx eax, byte ptr [rax] 0000000000400793: movsx eax, al 0000000000400796: add eax, 10 0000000000400799: add dword ptr [rbp-36], eax 000000000040079c: add ebx, 1 000000000040079f: cmp ebx, dword ptr [rbp-40] 00000000004007a2: jl 0x0000000000400789 Esse já é mais simples. Pelo debugger você vai perceber que ele pega o valor ASCII de cada caracter do primeiro parâmetro e soma com 10. E vai somando esses resultados também (em memória, no endereço [rbp-36]). Quando este loop acabar, o endereço [rbp-36] conterá a soma em ASCII de todos os caracteres da string do nome somados, mais o resultado de 10 vezes o número de caracteres da string. Ou seja, se o nome fosse “ABCDE”, teríamos:
    A -> 65 B -> 66 C -> 67 D -> 68 E -> 69 65+10 + 66+10 + 67+10 + 68+10 + 69+10 = 385 Dá no mesmo que:
    65 + 66 + 67 + 68 + 69 + 10 * 5 = 385 E esta é a lógica do programa. Ele pega o nome de usuário inserido, aumenta os valores de cada caracter em 10 unidades e depois os soma. O resultado é a chave para o nome de usuário inserido. Além disso, há as restrições de tamanho de nome e mais algumas que precisam ser implementadas no keygen.
    Agora é só fazer o keygen (lembrando que propus um no começo do artigo). Se quiser brincar, pode escrever um programa na sua linguagem preferida que receba um nome de usuário de acordo com as regras impostas pelo keygenme e gere uma chave válida para este usuário.

    Fernando Mercês
    1. Introdução 
    Um analisador de executáveis é um software capaz de prover informações sobre um executável que podem ser muito úteis para pesquisadores de Segurança da Informação na análise de malware, forense computacional ou engenharia reversa. Este artigo objetiva demonstrar como um analisador de executáveis é construído, abordando técnicas para uma análise precisa e eficiente. É utilizado como base um software de código aberto chamado “pev”, de desenvolvimento próprio, que analisa binários PE32 (usados no MS-Windows) e pode ser compilador tanto em sistemas UNIXlike quanto no próprio Windows, pois foi escrito em ANSI C. O processo de construção exige conhecimento do ambiente e da linguagem de programação escolhida. O estudo necessário para tal é de grande valor na carreira do pesquisador de segurança.
    2. O executável 
    Antes de iniciar, precisamos compreender o que é um arquivo executável. Sabemos que todo e qualquer arquivo no disco rígido não passa de uma sequência de bits armazenados por um processo elteromagnético nos pratos do disco. A diferença entre um arquivo MP3 e um PNG, por exemplo, é a forma como esses bits serão interpretados. No caso do executável, os bits presentes no arquivo representam instruções de máquina (Assembly) para o microprocessador da arquitetura em questão (Intel, SPARC etc). Veja:
    Binário      Decimal      Hexadecimal      Assembly x86      ASCII
    01010101       85                        55                       push ebp                U
    O mesmo byte (conjunto de 8 bits) pode ser interpretado de diversas formas. De fato, é por este motivo que um software editor hexadecimal abre qualquer tipo de arquivo, inclusive áreas do disco diretamente, lendo byte a byte sem qualquer interpretação. O arquivo executável é um formato complexo (bem diferente de um arquivo em texto puro – clear text, por exemplo). Além dos bytes referentes ao código do programa em si, é preciso adicionar milhares de bytes que constituem informações para guiar o kernel do SO à execução do binário. É preciso informar, por exemplo, para qual arquitetura o executável foi compilado, quanto de memória será alocada para rodar o programa, que partes do programa em memória serão exclusivas, somente para leitura e mais uma série de diretivas.
    3. O formato
    Para suprir todas essas necessidades de informações é que existem os formatos. Estes definem padrões que um arquivo deve seguir para ser corretamente interpretado pelo seu programa associado ou pelo próprio SO, no caso de um executável. Atualmente lidamos basicamente com dois formatos de executáveis: o PE e o ELF. O primeiro é utilizado pela família Windows e o segundo, pelos sistemas UNIX-like. O que um analisador precisa informar? Já dissemos que num executável não há somente o código que o programador escreveu na linguagem de programação convertido para código de máquina. Por isso, em tese, um analisador deveria nos dar toda esta informação “escondida” no executável. Os desenvolvedores dos formatos de executáveis geralmente liberam esta documentação porque todos os compiladores precisam gerar executáveis compatíveis e, por isso, têm de conhecer a especificação. Então o primeiro passo para se construir um analisador é obter a documentação do formato:
    Formato PE:  https://www.mentebinaria.com.br/files/file/18-microsoft-portable-executable-and-common-object-file-format-specification/ Formato ELF: https://refspecs.linuxfoundation.org/ Na documentação do formato, constam todos os campos pré-definidos que se espera encontrar num executável. Mas é claro que nem tudo é necessário para se construir um bom analisador. Alguns campos possuem grande utilidade prática, enquanto outros raramente são necessários. Cabe a nós filtrar o que é importante para o objetivo.
    4. O analisador
    Um código que consiga interpretar os campos que o formato define num executável precisa:  Verificar se o binário é de tal formato.  Ler os bytes do binário de acordo com a especificação.  Imprimir os nomes do campo e seus respectivos valores na tela.
    Simples? Nem tanto. Geralmente um analisador é um software pequeno que roda rápido (porque já sabe o que vai ler), mas o código-fonte é grande e pode vir a ser complexo.
    Para um exemplo prático, imagine que o formato PE defina o seguinte:
    ➔ Para ser um arquivo PE válido, os primeiros dois bytes do arquivo binário devem ser 0x4D e 0x5A. Neste caso, o analisador precisa fazer tal verificação:
    int verify(char *filename) { FILE *fp = fopen(filename, “rb”); char bytes[2]; fread(bytes, 2, 1, fp); if (bytes[0] == ‘M’ && bytes[1] == ‘Z’) return 1; return 0; } E fim de papo! Sim, um malware não pode alterar estes bytes, do contrário o Windows não o executará, portanto, não tenha medo em testar e encerrar o program caso não haja as letras MZ no início do arquivo, que são a representação em ASCII dos bytes 4D e 5A, em hexa. Experimente alterar um desses bytes de um executável PE e tente rodá-lo para ver o que acontece. Humm… será que um executável PE com o primeiro byte alterado passaria via e-mail pelo firewall de sua empresa?
    Agora digamos que a especificação do formato PE também diga:
    ➔ 16 bytes à frente desta assinatura MZ encontra-se o checksum do arquivo, que tem um comprimento também de 2 bytes. Bastaria “andar” pelo arquivo para ler:
    unsigned short checksum; fseek(fp, 16, SEEK_CUR); fread(&checksum, 2, 1, fp); printf(“%dn”, checksum); PS.: Em C, o tipo short, que abrevia short int, tem 2 bytes na arquitetura Intel x86. Seguindo essa lógica, podemos imprimir todos os campos de um binário, bastando apenas seguir a especificação do formato. No entanto, há recursos de linguagem que podem facilitar a vida. Veja um trecho interessante da biblioteca windows.h abaixo:
    typedef struct _IMAGE_FILE_HEADER { WORD Machine; WORD NumberOfSections; DWORD TimeDateStamp; DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; WORD Characteristics; } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; No caso do C, se definirmos um tipo WORD e DWORD com o typedef, obtemos um struct prontinho para ser usado e com os nomes dos campos. O mesmo existe para o formato ELF.
    5. Fazendo mais
    Imprimir informações brutas faz parte, mas a graça de um analisador está em sua capacidade de fazer mais que isso. Por exemplo, tratar um timestamp ou realçar o Entry Point (EP) de um binário são técnicas simples e que vão ajudar muito quem utilizará o software.
    6. Binários com proteção
    Um bom analisador deve esperar um binário que contenha um packer, crypter ou qualquer outro tipo de proteção. Neste caso, é necessário estudar e entender a rotina da proteção, fazer engenharia reversa, e inserir rotinas no analyzer para detectar ou mesmo remover as proteções dos executáveis. Isso vai dar um trabalho extra (e constante, porque novas proteções não param de surgir, além de atualizações das proteções existentes) mas sua implementação depende do objetivo desejado. A maioria dos analisadores somente reconhece que há uma proteção (alguns dizem qual é ela, batendo a assinatura contra um banco de dados), mas poucos a removem sem plugins adicionais.
    7. pev 
    Software livre (GPLv3) inicialmente desenvolvido para exibir o valor do campo “Product Version” de um executável PE.
    fernando@brussels:~$ pev -c ~/winapp/wrar393br.exe COFF header: Machine: 0x14c Number of sections: 5 Date/time stamp: 1268634487 (03/15/2010 at 06:28:07 AM) Symbol Table offset: 0 Number of symbols: 0 Size of optional header: 0xe0 Characteristics: 0x103 Página do projeto: https://github.com/merces/pev
    8. Conclusão 
    Conhecer bem os executáveis é obrigação de quem trabalha ou pretende trabalhar com análise de malware ou forense computacional e nada melhor que um estudo dirigido, que force resultados para atingir este objetivo. Desenvolver uma aplicação que interprete um executável “de cabo-a-rabo” é um ótimo começo.

    Fernando Mercês
    Com as vantagens óbvias das câmeras digitais, incluindo as dos smartphones, mais e mais pessoas tiram fotos todos os dias, em todos os lugares, fazendo as mais variadas “tripulias”. O que pouca gente sabe é que esses arquivos de fotos podem conter dados um tanto quanto comprometedores, como data, condições em que a foto foi tirada e até mesmo onde você estava!
    Se você olhar as propriedades de uma foto em .jpg tirada com uma câmera digital, poderá se surpreender com as informações que estão nela.
    No GNU/Linux:

    No Windows XP:

    Percebeu a marca e modelo da câmera, além da data quando a foto foi tirada? Por alguma razão o Windows não mostrou, mas o Linux sim, então este tipo de informação está em algum lugar no arquivo. Vamos ver…
    Eu não lembro de ter visto câmera digital que não salvasse os arquivos no formato JPEG. Por um lado é bom: padronização e compressão, já que o formato tem uma boa compressão e perde pouca qualidade, se bem ajustado.
    O formato JPEG, assim como qualquer outro formato de arquivo que se preze, define campos que serão interpretados pelos aplicativos que vão trabalhar com ele. Ou seja, poderia dizer que do primeiro byte de um arquivo .jpg até o enésimo byte, está a informação X. Deste em diante até um outro byte, está a informação Y e assim por diante.
    Claro que a imagem em si (leia-se o que é renderizável) ocupa um campo geralmente grande e de tamanho variável. Sem problemas se estudarmos o formato JPEG um pouquinho…
    No cabeçalho do formato JPEG, cada campo (chamado de “marker”) é iniciado por um byte 0xff e o próximo byte define que tipo de campo é.
    De acordo com a especificação [1], o campo “0xff 0xd8” é o SOI (Start Of Image) e marca o início da imagem. Então se olharmos o conteúdo com um visualizador hexa, podemos observar:
    $ hd -n 64 tracking.jpg 00000000 ff d8 ff e1 48 88 45 78 69 66 00 00 4d 4d 00 2a |….H.Exif..MM.*| 00000010 00 00 00 08 00 0a 01 0f 00 02 00 00 00 08 00 00 |…………….| 00000020 00 86 01 10 00 02 00 00 00 0a 00 00 00 8e 01 12 |…………….| 00000030 00 03 00 00 00 01 00 01 00 00 01 1a 00 05 00 00 |…………….| A documentação prevê campos para abrigar as dimensões da imagem, compressão utililizada, cores etc. Inclusive há um campo para comentários definido por “0xff 0xfe”, de tamanho variável. Vamos procurá-lo? Pode ser que nossa informação esteja lá:

    Nada encontrado. O arquivo tem várias informações mas nem consta o campo de comentários previsto na especificação JPEG. Neste caso, vamos dar uma olhada com mais carinho nos bytes:
    $ hd -n 512 tracking.jpg 00000000 ff d8 ff e1 48 88 45 78 69 66 00 00 4d 4d 00 2a |….H.Exif..MM.*| 00000010 00 00 00 08 00 0a 01 0f 00 02 00 00 00 08 00 00 |…………….| 00000020 00 86 01 10 00 02 00 00 00 0a 00 00 00 8e 01 12 |…………….| 00000030 00 03 00 00 00 01 00 01 00 00 01 1a 00 05 00 00 |…………….| 00000040 00 01 00 00 00 98 01 1b 00 05 00 00 00 01 00 00 |…………….| 00000050 00 a0 01 28 00 03 00 00 00 01 00 02 00 00 01 32 |…(………..2| 00000060 00 02 00 00 00 32 00 00 00 a8 02 13 00 03 00 00 |…..2……….| 00000070 00 01 00 01 00 00 87 69 00 04 00 00 00 01 00 00 |…….i……..| 00000080 00 da 88 25 00 04 00 00 00 01 00 00 02 56 00 00 |…%………V..| 00000090 02 c4 53 41 4d 53 55 4e 47 00 47 54 2d 49 35 35 |..SAMSUNG.GT-I55| 000000a0 30 30 42 00 00 00 00 48 00 00 00 01 00 00 00 48 |00B….H…….H| 000000b0 00 00 00 01 32 30 31 30 2d 31 31 2d 31 38 20 30 |….2010-11-18 0| 000000c0 30 3a 34 36 3a 32 33 20 00 00 00 00 00 00 00 00 |0:46:23 ……..| 000000d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |…………….| 000000e0 00 00 00 00 00 00 00 11 82 9a 00 05 00 00 00 01 |…………….| 000000f0 00 00 01 ac 82 9d 00 05 00 00 00 01 00 00 01 b4 |…………….| 00000100 88 22 00 03 00 00 00 01 00 03 00 00 88 27 00 03 |.”………..’..| 00000110 00 00 00 01 00 c8 00 00 90 00 00 02 00 00 00 0f |…………….| 00000120 00 00 01 bc 90 03 00 02 00 00 00 32 00 00 01 cc |………..2….| 00000130 90 04 00 02 00 00 00 32 00 00 01 fe 91 01 00 07 |…….2……..| 00000140 00 00 00 04 01 02 03 00 92 07 00 03 00 00 00 01 |…………….| 00000150 00 02 00 00 92 0a 00 05 00 00 00 01 00 00 02 30 |……………0| 00000160 a0 00 00 07 00 00 00 04 30 31 30 30 a0 01 00 03 |……..0100….| 00000170 00 00 00 01 00 01 00 00 a0 02 00 04 00 00 00 01 |…………….| 00000180 00 00 06 40 a0 03 00 04 00 00 00 01 00 00 04 b0 |…@…………| 00000190 a0 05 00 04 00 00 00 01 00 00 02 38 a4 03 00 03 |………..8….| 000001a0 00 00 00 01 00 00 00 00 a4 06 00 03 00 00 00 01 |…………….| 000001b0 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 08 |…………….| 000001c0 00 00 00 87 00 00 00 32 56 65 72 73 69 6f 6e 20 |…….2Version | 000001d0 32 2e 32 00 00 00 00 00 32 30 31 30 2d 31 31 2d |2.2…..2010-11-| 000001e0 31 38 20 30 30 3a 34 36 3a 32 33 20 00 00 00 00 |18 00:46:23 ….| Opa, começamos a achar as informações que queremos, mas em qual campo estamos?
    Veja que começou o “0xff 0xe1” e até o fim deste dump de 512 bytes não começou outro campo, ou seja, ainda estamos nele. A documentação do formato JPEG diz que os campos “0xff 0xeN”, onde “N” pode variar, representa um campo chamado APPN (no nosso caso, N=1, entao nosso campo é APP1). Este campo é usado para gravar metadados no formato EXIF, usado por vários tipos de arquivo para abrigar informações adicionais.
    O objetivo agora é destrinchar o EXIF e ver como ele está estruturado. O primeiro passo é olhar a documentação [2] em busca da especificação de seus campos. O EXIF é usado “dentro” de outros formatos. Não vou comentar sobre a estrutura do EXIF aqui, pois demandaria um longo texto. Por hora, basta otimizarmos procurando um software que seja capaz de ler os metadados EXIF em um JPG, certo?
    O software que mais me atendeu foi o jhead [3]. Além de ser livre, é cheio de recursos interessantes, inclusive pode apagar todas as informações EXIF de um arquivo .jpg, a fim de evitar rastros.
    $ jhead tracking.jpg File name : tracking.jpg File size : 314085 bytes File date : 2011:03:30 02:13:53 Camera make : SAMSUNG Camera model : GT-I5500B Date/Time : 2010-11-18 00:46:23 Resolution : 1600 x 1200 Focal length : 2.7mm Exposure time: 0.125 s (1/8) Aperture : f/2.7 ISO equiv. : 200 Whitebalance : Auto Metering Mode: center weight Exposure : aperture priority (semi-auto) GPS Latitude : ? 12d 23m 28.937s GPS Longitude: ? 13d 20m 33.702s Epa! Latitude e longitude? Por que isso não apareceu no Windows, nem no Linux, no ambiente gráfico? Sério que dá pra saber onde eu estou? Sério…
    Qualquer pessoa que tiver sua foto (Facebook?) pode pegar essas informações e converter para coordenadas de GPS, existem até conversores na internet. Depois é só usar alguma API de “Reverse Geocoding”, que é a técnica de obter um mapa de um lugar a partir de suas coordenadas GPS.
    Moral da história: muito cuidado onde coloca suas fotos. Se quiser um pouquinho menos de exposição, você pode remover todas as informações EXIF com a opção -purejpg do jhead:
    $ jhead -purejpg tracking.jpg Modified: tracking.jpg $ jhead tracking.jpg File name : tracking.jpg File size : 295533 bytes File date : 2011:03:30 02:13:53 Resolution : 1600 x 1200 Veja que agora só ficam informações básicas como resolução e data de modificação do arquivo (o mesmo usado pelo SO). É, a vida digital tem um preço…
    [1] http://en.wikipedia.org/wiki/JPEG#Syntax_and_structure
    [2] http://www.exif.org/Exif2-2.PDF
    [3] http://www.sentex.net/~mwandel/jhead/

    Fernando Mercês
    Introdução
    Em muitas faculdades brasileiras a linguagem C é ensinada aos alunos de cursos de tecnologia. Mesmo assustando os novatos, vários alunos resistem e vencem a matéria. O problema é entender por qual motivo o C foi escolhido para iniciar o curso de programação. Seria uma linguagem didática para se aprender a programar? Um teste para ver quem tem ou não o “jeito pra coisa”? Alguns diriam que o correto seria começar com Pascal, mas há quem defenda linguagens mais modernas como Python, Perl, Ruby ou PHP. E aí, para que serve o C no primeiro período? Neste artigo farei uma análise sobre o que se aprende da linguagem, o motivo pelo qual ela surge no início do curso, seu valor de mercado e o que é possível fazer com esse start que a faculdade nos dá.
    A linguagem C
    A importância histórica da linguagem C é inegável e dispensa maiores comentários. Sabemos que até hoje a maioria dos softwares mais poderosos são feitos em C e/ou C++ (um super conjunto de C, orientado à objetos). O kernel Linux e outros núcleos de SOs são feitos basicamente em C. Muitos drivers de dispositivos como placas de rede, som, vídeo etc são feitos em C. Se contarmos o C++ nesta conta, chegamos perto de 100% dos kernels e drivers. Os interpretadores e compiladores das principais linguagens de programação também não fogem à regra e são feitos em C. Existe uma frase que afirma: metade do universo é feito em C. E é bem verdade. Pelo visto, a linguagem serve para alguma coisa…
    Ensino da linguagem C
    Você acabou de entrar na faculdade, está tendo aulas desta linguagem e não está entendendo nada? Não se preocupe, você não está sozinho. Algumas instituições de ensino acham que C é uma liguagem didática, quando não é. Para se aprender a programar, usa-se pseudo-linguagem, PORTUGOL e ferramentas do gênero. Nem mesmo o Pascal, considerado mais fácil de se aprender que o C, é atraente ou interessante à primeira vista. O grande monstro que aterroriza o aluno é a pergunta: “Por que eu vou fazer isso? Para que?”. Pois é, para que escrever um programa em C que calcule a média de três alunos e imprima na tela? Qual a lição tirada disso? A resposta é simples: nenhuma. A maneira como a linguagem é lecionada tenta empurrar o C guela abaixo em estudantes que viram, por exemplo, Visual Basic e Delphi no segundo grau. Isto é, se é que estudaram tais linguagens ou lembram-se delas. Não poderia dar certo mesmo.
    Antes de criar um programa, o aluno tem que saber o que está fazendo. O que é um programa, para que serve isso, o que é um arquivo executável, um binário, bits, bytes, o processador, dentre outros conceitos importantíssimos antes de se escrever o primeiro “Hello World”.
    O resultado do ensino forçado é o alto íncide de reprovação, abandono, mudança de curso e desistência. É comum encontrar alunos que estão no fim do curso de programação mas ainda não passaram nas matérias mais básicas de C. É o terror da faculdade. Definitvamente, a linguagem C vira uma vilã e a frase mais ouvida nos corredores sobre o assunto é que “C é chato”.
    Por que a linguagem C é chata?
    Porque ela não te mima. Numa escala onde o nível mais alto é o mais próximo da linguagem usada pelo ser humano e o mais baixo, da linguagem usada pelos microprocessadores, a linguagem C é considerada de nível médio. Assembly, por exemplo, é de baixo nível, enquanto Object Pascal (usada no Delphi), de alto nível. Isso significa que para programar em C é preciso conhecer conceitos mais próximos do hardware, que as linguagens de alto nível abstraem para o programador, tornando o trabalho mais fácil. Por isso temos a impressão de que C é chato, difícil, sem sentido. Realmente, sem os conceitos básicos de computação bem sólidos, um código em C pode tornar-se incompreensível. Vejamos um exemplo.
    Um código em PHP (alto nível) para se declarar uma variável e armazenar uma frase nela:
    <?php $str = “Essa é minha string”; ?> Um código equivalente em C, seria:
    void main(void) { char str[] = “Essa é minha string”; } No código em C, uma função teve de ser escrita (a main, que é a função principal de um programa), inclusive com seu tipo de retorno e parâmetros, onde usei void para não retornar nem receber nada. Além disso, foi criado um vetor de caracteres (char) para armazenar a frase. Em C, entende-se como string um vetor de caracteres (ou ponteiro para um conjunto deles) onde o último caracter é o NULL, código 0x00 na tabela ASCII. Tá vendo por que se precisa dos conceitos de computação até para começar uma frase em C? Agora perceba a jogada:
     
    #include <string.h> void main(void) { char str[21]; strcpy(str, “Veja, sou uma string”); } A função strcpy(), fornecida pelo header string.h, copia caracteres para uma variável do tipo vetor (ponteiro, na verdade, mas isto é outro assunto) de caracteres e adiciona um caractere nulo (NULL), zerado, na última posição. Perceba que iniciamos o vetor de char com 21 posições, para abrigar os 20 caracteres da frase proposta mais o NULL, que é um caractere só. As coisas começam a fazer sentido, apesar de “feias”, não?
    E assim é o C. Exigente, porém elegante. Se tem os conceitos de computação, sem dúvida não terá grandes dificuldades com a linguagem.
    Usando o C na vida e no mercado de trabalho
    Certo, você se convenceu de que C é legal de aprender, poderoso e aprendeu. E agora, faz o quê? Tem um colega seu ganhando dinheiro fazendo sites em Ruby on Rails. Outro faturando uma grana fazendo sistemas em Delphi para clientes, com imagens, botões brilhantes e multimídia. O que você, recém-estudado programador em C vai fazer com aquela tela preta pedindo dados com scanf()? Nada. Não é assim que se trabalha com C, ou pelo menos, não mais. Já foi o tempo em que os sistemas eram feitos dessa maneira. Além disso, mesmo nesse tempo a linguagem C foi rapidamente substituída neste meio pela linguagem CLIPPER no mundos dos PCs e pelo COBOL, nos mainframes.
    O forte do C hoje são aplicações desktop, inclusive as baseadas em rede e daemons (serviços). C também é útil para escrever compiladores e interpretadores para outras linguagens, por exemplo. Sabia que o PHP é escrito em C? Pois é, assim como Python, Ruby, BASH e muitos outros interpretadores. Então tem alguém ganhando dinheiro com C por aí, concorda?
    Vale a pena citar também o desenvolvimento embarcados, para microcontroladores e vários microprocessadores, incluindo ARM (usado em vários aparelhos Android).
    Em novembro do ano passado houve uma edição de um evento chamado Universidade Livre em que Olivier Hallot, diretor da ALTA (antiga BrOffice.org) falou durante alguns minutos numa faculdade carioca da dificuldade de encontrar programadores para contratar e fez um apelo para que os alunos levem a sério que o mercado está muito carente de bons programadores, principalmente em C/C++. Também em setembro do ano passado uma empresa publicou uma vaga no Rio de Janeiro buscando um profissional com os seguintes conhecimentos:
    Sistema Operacional Linux; Banco de dados MySQL; Criação e manutenção de tabelas, relacionamentos, scripts, etc.; Linguagem C, e das APIs: (V4L2), GTK, além de OpenGL; Adobe Flex. O salário inicial era de R$ 5.000,00. A vaga deve estar aberta até hoje…
    Em dezembro de 2011, uma grande operadora telefônica abriu nada menos que 20 vagas para desenvolvedores em C no Rio de Janeiro. Empresas que atendem infraestrutura, telecomunicações, embarcados, móveis, desenvolvimento do Linux e kernels derivados também precisam muito de programadores deste tipo. Enfim, vagas não faltam!
    Então por que aprendo Java na faculdade?
    A faculdade tenta ser a mais moderna possível, mas esquece de verdadeiramente orientar na profissão. Java é uma linguagem potente, flexível e poderosa mas tem um fim completamente diferente da linguagem C. Com Java se programa para web, dispositivos móveis, aplicações locais (pouco usada), sistemas de informação, embarcados etc. A flexibilidade é enorme, mas o foco é outro. Não se faz uma suíte de aplicativos em Java, simplesmente porque existe o C pra isso. Um sniffer de rede ou um software ping, por exemplo, são feitos em C, porque C é pra isso. Já uma interface de um aparelho GPS, é feita em Java. Questão de adeqüação. O mercado de Java é tão grande quanto o de C no mundo, mas é maior no Brasil. No entanto, o que não pode é a faculdade tratar a linguagem C como uma introdução à programação, para que o aluno depois aprenda Java. Uma coisa não tem nada a ver com a outra. São dois nichos completamente diferentes e em ambos os casos, é possível conseguir um bom emprego e alavancar na profissão, tanto aqui quanto fora.
    Minha faculdade usa Python para ensinar a programar. É legal?
    Não creio. Python é super divertido e viciante mas não exige os conceitos de computação que todo programador deve ter. A resposta é a mesma para todas as linguagens de alto nível. Como escrevi anteriormente, se começa a programar com uma pseudo-linguagem, para desenvolver a lógica. Antes do estudo de programação médio/alto nível, é preciso estudar computação, do ponto de vista da arquitetura em si (que vai incluir Assembly, SO etc) e aí sim, subir de nível. Se bem gerenciado, é possível manter estas disciplinas em paralelo, mas o programa deve ser cuidadoso (o que as instituições não andam respeitando – Eu já vi projeto de bancos de dados no segundo período. O aluno, teoricamente, nunca usou uma mysql.h ou outras bibliotecas para acesso a SGBD’s em outras linguagens).
    Quem aprende direto no alto nível e se dá bem, ótimo – e está de parabéns. Mas o objetivo do artigo é trazer a linguagem C à tona e não competir com outras linguagens.
    Venho comprovando a tese de que aprender “de baixo para cima” dá certo. Já consegui fazer um amigo escrever um programa em Assembly do zero para calcula a média de alunos. Aí sim ele viu o que é obter dados do teclado, calcular e exibir. Teve de entender por completo a tabela ASCII, uso de registradores gerais, syscalls e interrupções de software. Quando foi para o C, não teve o menor problema.
    E o que dá pra fazer com o C aprendido na faculdade?
    Só com ele, não muita coisa, mas com um pouquinho de pesquisa e afinco, gera-se resultados. Um exemplo é o grupo Coding 40°, onde eu e mais três alunos do curso de Ciência da Computação nos unimos para estudar e acabamos desenvolvendo um pequeno software, capaz de imprimir informações sobre executáveis PE (.exe, .dll etc) na tela. Nada complicado, agora que já está pronto. rs
    Sabe quando você está no Windows e vai nas propriedades de um .exe ou .dll e há uma aba “Versão” como na imagem abaixo?

    A proposta inicial era criar um software capaz de conseguir essa informação, recebendo como entrada o caminho do arquivo executável. O software deveria funcionar no Linux, já que nesse SO não é possível ver esta aba “Versão” nas propriedades dos executáveis de Windows, obviamente. Foi aí que fizemos o pev. Hoje ele já exibe várias outras informações sobre o executável além da versão.
    Conclusão
    Estudar C, C++, Assembly e outras linguagens tidas como “terríveis” é, sem dúvida, uma boa pedida. Há inúmeros projetos no mundo todo precisando de bons programadores nessas linguagens. Não encare o “C de faculdade” como um vilão ou uma introdução à programação porque não é. A linguagem C é uma linguagem poderosa e comercial. Nada de dizer que C é coisa de maluco.
    Ainda não sabe o que fazer com C? Está em dúvida sobre seus aspectos modernos? Nós temos um curso de programação moderna utilizando a linguagem C para você

    Fernando Mercês
    É notória a quantidade de pen drives e cartões de memória infectados com vírus. Freqüentemente nos deparamos com vírus novos, que os antivírus não conhecem (a vacina é criada depois de certo tempo). Essas pragas disseminadas por pen drives são tão perigosas quanto as disseminadas pela internet e às vezes até mais destruidoras, ou seja, o cuidado deve ser redobrado. Veja neste artigo como se defender de um pen drive ou cartão de memória infectado e saiba como remover o vírus destes dispositivos.
    O primeiro ponto a entender é como um vírus infecta um pen drive. Sabemos que vírus são programas (executáveis) e a infecção de um pen drive acontece quando o inserimos em um PC infectado (ou quando o PC infectado já possui um pen drive inserido na porta USB). Se o pen drive infectado é inserido em PC saudável e sem proteção adequada, este PC é infectado e passa a infectar todos os pen drives inseridos nele posteriormente, ou seja, a disseminação da praga fica fora de controle.
    Infelizmente, tudo funciona muito bem graças a uma ajudinha da arquitetura e controle de permissões do Windows. Acontece que normalmente o usuário logado num sistema Windows pertence ao grupo “Administradores”, isto é, possui todas as permissões de escrita e leitura em praticamente todo o disco rígido e áreas de memória. Para um vírus, isto é um prato cheio. Da mesma maneira que um programa de instalação qualquer pode ser executado com sucesso pelo usuário logado (copiando arquivos para diretórios de sistema e criando processos privilegiados), um vírus também pode, pois rodará com as permissões do usuário, que são as de administrador do sistema (as máximas).
    Quando o usuário incauto executa um vírus, este normalmente carrega-se em memória, copia-se para vários locais (backup), tenta infectar outras máquinas na rede e faz com que seja inicializado a cada reinicialização do sistema. É mais ou menos a definição de um worm. Tratamos de algumas técnicas manuais utilizadas para remover estes vírus no artigo Remoção manual de malware no Windows.
    Assim que um pen drive sadio é inserido na porta USB, a praga copia-se para ele. Até aí, nenhuma novidade. Mas como, ao inserir este pen drive em outro computador, ocorre a infecção? Acontece que o Windows possui uma facilidade chamada autorun (ou auto-inicialização) para drives montados e com letra atribuída. Como os pen drives são reconhecidos como uma unidade de disco (recebem inclusive uma letra disponível para serem acessados, por exemplo, F:), esta facilidade pode ser usada nestes dispositivos.
    Funcionamento da auto-inicialização:
    O autorun serve para executar algum aplicativo assim que o drive é montado. O exemplo clássico são os CD-ROMs que, ao serem inseridos no drive de CD, abrem uma aplicação com menus e outros recursos. A questão é que o autorun pode ser utilizado em qualquer drive, seja uma partição do disco, CD-ROM, DVD, pen-drive, câmera digital, etc.
    Para usar esta facilidade, basta adicionar um arquivo chamado autorun.inf no diretório raiz do drive em questão. Neste arquivo são escritas rotinas que o Windows deverá seguir quando assim que o drive for montado. Veja um exemplo:
    [autorun] open=menu.exe icon=menu.ico Se você salvar o conteúdo do exemplo acima num arquivo de texto, nomeá-lo autorun.inf e movê-lo para o diretório raiz de um drive, ao montar ou acessar este drive, as instruções contidas neste arquivo serão executadas.
    Creio que as instruções sejam auto-explicativas. Basicamente o ícone do drive exibirá agora o ícone menu.ico, contido no diretório raiz do drive (do CD-ROM, por exemplo) e a aplicação menu.exe será carregada.
    À esta altura você deve imaginar como o vírus de pen-drive é executado assim que ele é inserido. Isso mesmo, usando essa técnica. Todos os pen-drives infectados que analisei continham um arquivo autorun.inf, com instruções para execução do vírus.
    Evitando a contaminação:
    Ficar imune é impossível. Nunca poderemos prever as técnicas de contaminação que serão inventadas. Aliás, cá entre nós, são muito “boas”. No entanto, algumas medidas podem ser tomadas para diminuir os riscos de contaminação. São elas:
    1. Desabilitar a auto-reprodução em unidades removíveis.
    No registro, navegue até a chave HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionpoliciesExplorer e sete o valor NoDriveTypeAutoRun para 4. Isso desabilitará a auto-reprodução em unidades removíveis. Se quiser desabilitar em qualquer tipo de unidade (recomendo), utilize o valor ff.
    2. Sempre abrir a unidade removível (pen-drive, cartão de memória, etc) pelo Windows Explorer, ao invés de dar duplo-clique no ícone.
    Basta clicar com o botão direito na unidade e escolher “Explorar” ou abrir o Windows Explorer e clicar sobre a unidade.
    3. Desabilitar a execução do arquivo autorun.inf, geralmente responsável pela infecção.Copie e cole as linhas abaixo para o Bloco de Notas e salve como inf.reg:
    REGEDIT4 [HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindows NTCurrentVersionIniFileMappingAutorun.inf] @=”@SYS:DoesNotExistSEUNOME” Substitua SEUNOME pelo seu primeiro nome (sem acento ou espaços). Depois basta executar este inf.reg que você gerou.
    Para facilitar as coisas, escrevi um software que faz este trabalho. Chamei-o de USBForce. Funciona assim:
    Na primeira vez que é executado na máquina, o USBForce:
    Desabilita a auto-reprodução em todas as unidades. Desabilita o reconhecimento de arquivos autorun.inf. Habilita sua proteção. A partir deste momento, ao inserir um pen-drive, cartão de memória ou qualquer dispositivo removível, o Windows o reconhecerá mas nenhuma tela será aberta. Você deve então clicar no ícone do USBForce em sua área de trabalho. Ele abrirá o dispotivo pra você e tentará detectar se existe algum vestígio de infecção de vírus no dispositivo. Em caso afirmativo, ele tentará localizar o vírus e deletar (você será avisado antes).
    Naturalmente é interessante colocá-lo em alguma pasta e criar um atalho pra ele na área de trabalho, para facilitar o acesso.

    Fernando Mercês
    Recentemente fui requisitado para verificar a possibilidade de um determinado sistema cliente-servidor funcionar em thin clients (terminais leves, que usam o processamento e o SO de um servidor de terminais).
    O sistema baseia-se em um aplicativo servidor e um pequeno aplicativo cliente, que deve ser instalado em todas as estações. Mas ele não foi feito para funcionar com terminal services: este pequeno aplicativo instalado nas estações mantém suas configurações numa chave de registro em HKEY_LOCAL_MACHINE. Logo, cada estação precisa de um SO para que o aplicativo crie esta chave e armazene suas configurações individuais.
    Imediatamente pensei que se este aplicativo armazenasse suas configurações em HKEY_CURRENT_USER, seu funcionamento em thin client seria viável, uma vez que esta chave do registro existe para cada usuário que se loga no sistema (diferente da chave HKEY_LOCAL_MACHINE, que é única no SO).
    Neste artigo vou mostrar como alterar o comportamento de um executável que armazena suas configurações em HKLM, fazendo-o armazenar em HKCU, para atingir o objetivo desejado.
    O primeiro passo é analisar o executável e verificar que valores ele cria no registro e qual o momento em que isto acontece. Para isso podemos usar o RegMon (Registry Monitor).
    Com o RegMon aberto, criei um filtro para incluir somente o cliente.exe na monitoração do RegMon e dar um highlight quando a operação for CreateKey (criar uma chave no registro).

     

     
    Ao executar cliente.exe, vamos ver o que o RegMon informa.

    Na primeira linha com highlight em vermelho, vemos que o software procura pela chave HKCUSoftwarePCSCliente. Como não existia tal chave, ele a criou (visível nas linhas que seguem).
    Depois o software procura pela chave HKLMSoftwarePCSCliente e também não encontra (veja a coluna RESULT) e também a cria.
    Rolando mais abaixo, vemos que ele procura por sub-chaves também, mas ainda não as cria:

    Cliquei no botão Aplicar do software, que salva as configurações e aí sim, o RegMon acusou várias criações de sub-chaves (CreateKey) e inclusive a criação de valores (SetValue). Veja que todos são em HKLM:

    Nossa intenção é fazer com que o software crie estes valores em HKCU, para permitir o uso de thin clients. Já sabemos quando e onde o programa cria as chaves, agora precisamos saber qual é a API do Windows responsável por criar as chaves no registro. Para isso, basta consultar o Win32 SDK Online Help (win32.hlp). Na busca, procurei por “reg” e imediatamente achei a RegCreateKey e a RegCreateKeyEx. Mas a RegCreateKey é para compatibilidade com aplicativos do Windows 3.11, portanto a API correta é a RegCreateKeyEx. Vamos olhar o trecho inicial da documentação sobre ela:
    LONG RegCreateKeyEx( HKEY hKey, // handle of an open key LPCTSTR lpszSubKey, // address of subkey name DWORD dwReserved, // reserved LPTSTR lpszClass, // address of class string DWORD fdwOptions, // special options flag REGSAM samDesired, // desired security access LPSECURITY_ATTRIBUTES lpSecurityAttributes, // address of key security structure PHKEY phkResult, // address of buffer for opened handle LPDWORD lpdwDisposition // address of disposition value buffer ); Parameters: hKey Identifies a currently open key or any of the following predefined reserved handle values: HKEY_CLASSES_ROOT HKEY_CURRENT_USER HKEY_LOCAL_MACHINE HKEY_USERS The key opened or created by the RegCreateKeyEx function is a subkey of the key identified by the hKey parameter. Basta ler este trecho da documentação para perceber que o parâmetro hKey, que é passado para a função RegCreateKey é o responsável por definir se a criação da sub-chave será em HKCR, HKCU, HKLM ou HKU.
    Então temos que encontrar o ponto onde o programa chama a função RegCreateKey, passando como parâmetro hKey o valor que representa HKCL e substituir pelo valor que representa HKCU. Mas que valores são esses? Bem, eu não achei na documentação mas podemos encontrar isso já debugando o programa. Mãos à obra!
    Ao abrir o cliente.exe no OllyDbg (um debugger de executáveis 32-bits), antes de rodar o software, precisamos definir breakpoints (pontos de parada, numa tradução livre) para que o debugger interrompa a execução do software quando a API que queremos for chamada, no caso, a RegCreateKeyEx. Para isso, na Command Bar, basta digitar “BP RegCreateKeyExA” (sem aspas) e pressionar [ENTER]. Este “A” é porque a função trabalha com caractes em ASCII. Para caracteres UNICODE, usa-se “W”.

    Agora é só rodar o programa no OllyDbg (F9) e esperar parar.
    A primeira parada (breakpoint) na função RegCreateKeyExA não é a que esperamos, pois antes de criar a nossa chave, o software precisa criar outras chaves (de sistema) que são essenciais ao seu funcionamento. Portanto, vamos apertando F9 (Run) até que a parada seja na hora da criação de nossa chave. Aqui precisei rodar (F9) mais cinco vezes. Quando for a correta, a stack (pilha de memória) que o OllyDbg mostra, ficará como na imagem abaixo:

    Perceba que foi passado o valor hexa 80000002 como parâmetro hKey e isso resultou HKEY_LOCAL_MACHINE (o OllyDbg nos mostra).
    Os possíveis valores são:
    Chave                                            Valor (em hexa)
    HKEY_CLASSES_ROOT                 80000000
    HKEY_CURRENT_USER                80000001
    HKEY_LOCAL_MACHINE              80000002
    HKEY_USERS                                    80000003
    HKEY_PERFORMANCE_DATA   80000004
    HKEY_CURRENT_CONFIG          80000005
    HKEY_DYN_DATA                          80000006
    DICA: Se clicarmos com o botão direito no valor 80000002, na stack, e escolhermos a opção “Modify”, poderemos mudar este valor em tempo de execução. Mas é claro que a alteração não será permanente pois estaríamos alterando na memória. Para alterar permanentemente, precisamos saber em que parte do programa está este valor 80000002 (HKLM) e mudar para 80000001 (HKCU), mas em respeito ao desenvolvedor do software, não mostrarei publicamente como fazer isso.

    Fernando Mercês
    Muitos jogos antigos apresentam problemas ao serem executados ou simplesmente instalados no Windows Vista. Isto acontece por conseqüência de diversos fatores, mas o principal é que quando o jogo (ou software) foi desenvolvido, o Windows Vista ainda não estava no mercado, o que impediu testes de serem realizados, entre outros aspectos.
    Este artigo mostra um exemplo de como utilizar a ER para estudar o executável do game e saber o que o impede de rodar no Vista.
    Você verá como uma simples alteração em 2 bytes de um arquivo PE pode salvar seu fim de semana.
    Ao tentar instalar o jogo Mortal Kombat 4 (PC) no Windows Vista Home Basic, obtivemos um erro fatal que dizia: “Start Menu Error”. A única opção era clicar no botão OK, que encerraria a execução do programa de instalação.
    Numa tentativa de contornar tal situção, copiamos o conteúdo do CD-ROM para um diretório no disco rígido e tentamos executar o game pelo seu executável direto, o MK4.EXE. Isso resultou no seguinte erro:

    É notável que o executável checa se a instalação do jogo foi feita e, como não foi feita, recebemos a mensagem acima. Ao clicar em OK, o processo é encerrado.
    Mas o que será que o jogo checa para saber se está instalado ou não? Para responder a essa pergunta precisaremos de um debugger de executáveis. Um aplicativo que mostra, em assembly, as rotinas executadas por arquivo PE. Usaremos o OllyDbg para tal função.
    Ao abrir o executável MK4.EXE no OllyDbg, vamos procurar pela string de texto contida na mensagem de erro da primeira imagem. Para isto, clique com o botão direito do mouse no primeiro quadrante e escolha “Search for > All referenced text strings”, como sugere a imagem abaixo:

    A próxima tela mostra uma lista contendo todas as strings de texto encontradas e entendidas no arquivo MK4.EXE. Nela, clicando novamente com o botão direito do mouse e escolhendo “Search text”, abrirá uma janela como a mostrada abaixo e então digitamos o texto “CD” (sem aspas) e marcamos a opção para diferenciar o caso, para filtrar a pesquisa.

    Isso foi feito para encontrarmos a string de texto que nos foi exibida no erro incial, lembra-se? O texto era “Mortal Kombat 4 is not installed. Run Setup from the CD”. Por isso buscamos a palavra “CD”, para achar essa string dentro do executável do jogo, o que nos leva para próximo da rotina onde esta mensagem é chamada.
    Vamos ver o resultado na imagem abaixo:

    O Olly nos mostra que no endereço 004AD2B1, o comando é PUSH 004F474C, que vai empurrar para a memória (stack) o nosso texto. Para localizarmos exatamente onde está este comando no programa, basta darmos um ENTER nesta linha e a tela abaixo é exibida.
     
    Entramos no bloco onde o texto do erro é exibido na tela. Vamos subir um pouco para ver o que vem antes.
    Na linha 004AD299 temos um CALL (como um GOTO) e, depois que a CALL temina e o programa volta para sua execução normal, temos um TEST EAX, EAX, que é um comando que verifica se o conteúdo de EAX é zero. Então podemos prever que a CALL altera o valor de EAX.
    Mais abaixo, temos um JNZ 004AD2D4. Esse JNZ significa Jump if Not Zero (Pule se não for zero), o que quer dizer que a execução do programa saltará para a linha 004AD2D4 se o conteúdo de EAX não for zero.
    Bom, se a execução não saltar e seguir abaixo, cairemos na mensagem de erro. Se saltar, a pularemos. Então seria interessante alterar essa parte do programa para que sempre salte para a linha 004AD2D4. Isso significa que independente do resultado do teste anterior (TEST EAX, EAX) o salto ocorrerá, sempre. Assim nunca cairemos na mensagem novamente e programa continuará sua execução normal.
    O comando que faz o papel de salto incondicional em assembly é o JMP (Jump). Então, vamos alterar o JNZ da linha 004AD2A0 para JMP (e manter o resto da linha). Para isso, basta selecionar a linha e apertar a barra de espaços, depois clicar em Assemble.

    Feito isso, o Olly marca nossa alteração em vermelho. Clicando com o botão direito do mouse sobre a alteração (ou próximo) e escolhendo “Copy > All modifications”, uma tela com as modificações abre e então basta clicar novamente com o botão direito e escolher “Save file”.

    Dei o nome de MK4-mod.EXE para facilitar o reconhecimento. Agora vamos ao teste. Ao executar este novo executável modificando, vemos o jogo rodando:

    É importante esclarecer que os passos descritos aqui não são genéricos e não servem para todos os softwares que não funcionam, em primeira instância, no Vista. O funcionamento depende de vários fatores e principalmente do nível de integração do software com o SO para o qual ele foi desenvoldido.
    O objetivo deste artigo foi demonstrar como a ER pode nos ajudar a resolver pequenos (e grandes, por quê não?) problemas do dia-a-dia na informática. Este é um dentre dezenas de exemplos de uso da ER para soluções que seriam um pouco difíceis sem ela.

    Fernando Mercês
    A maioria dos vírus e pragas virtuais compartilham de certos métodos de auto-inicialização com o SO. Isto inclui os spywares e seus similares.
    Os sistemas Windows possuem métodos para inicializar programas junto ao seu carregamento limitados. Na maioria das vezes os vírus iniciam justamente por eles e daí a importância de conhecê-los e saber gerenciá-los. Desta forma, o técnico pode remover manualmente muitas pragas, o que economizará tempo com scans de softwares antivírus e anti-spys, além de ser extremamente útil quando o vírus ataca estes softwares de proteção, impedindo sua inicialização.
    Primeiro vamos ver de que jeito um aplicativo pode ser iniciado junto ao Windows. A maior parte dos vírus age deste jeito.
    Em sistemas baseados em Windows NT, o que inclui os Windows 2000, XP e 2003, os métodos de inicialização de programas são:
    Através da pasta Inicializar do Menu Iniciar. No registro do sitema. Por serviço de sistema (o que inclui certa parte do registro). Na primeira maneira, basta colocar um atalho para o programa que se deseja executar na pasta Inicializar do Menu Iniciar. Por exemplo, na imagem abaixo, a cada inicialização do Windows, inicializaremos a Calculadora junto.

    Obviamente um vírus pode se aproveitar deste recurso e colocar um atalho para si neta pasta mas não é comum isso acontecer pois o vírus ficaria facilmente visível e uma das intenções de vírus complexos é passar despercebido ao usuário/técnico. De qualquer forma, não custa conferir.
    Agora vamos ao método mais usado, o registro do sistema. Aqui precisaremos explicar resumidamente como o registro destas versões do Windows funciona.
    O registro é um banco de dados que armazena informações essenciais sobre diversos softwares instalados no Windows, além de informações pertinentes ao próprio sistema. Por conta disto, é comum apelidar o registro de “alma do sistema”.
    Esse banco de dados possui, além de outros dados, chaves, sub-chaves e valores numa organização hierárquica (similar ao Windows Explorer).
    Para o artigo, precisaremos conhecer essas três chaves:
    HKEY_LOCAL_MACHINE – Esta é a chave mais importante do registro. Nela estão contidas informações sobre o PC (hardware instalado, softwares com sua opções e configurações e outros itens). Inclusive veremos que um programa pode ser inicializado por uma sub-chave desta chave. HKEY_USER – Nesta chave são definidas configurações personalizadas para cada usuário do sistema, já que as versões do Windows mais novas permitem logon simultâneo ou não de usuários diferentes. De maneira similar à chave anterior, um vírus pode inicializar-se junto ao SO somente para um usuário específico, usando uma sub-chave desta chave. HKEY_CURRENT_USER – Como o nome sugere, mantém informações sobre o usuário que está atualmente logado no sistema. Todo o registro é dinâmico mas esta chave merece uma definição de dinamismo especial pois muda os valores de suas sub-chaves completamente quando logamos com outro usuário no Windows. Não é difícil de deduzir que ela é um atalho para uma sub-chave de HKEY_USER, já que esta última mantém uma sub-chave para cada usuário cadastrado no sistema. Por exemplo, se logarmos com o usuário “Fernando”, esta chave será uma cópia da sub-chave HKEY_USER. SID (Security Identifier) é uma identificação única que cada usuário tem e o SO conhece os usuários através deste SID. Abaixo, o utilitário “regedit” (Registry Editor), usado para visualizar o conteúdo do registro do sitema.

    Perceba a igualdade entre as áreas destacadas em vermelho. É justamente o que falamos na explicação da chave HKEY_CURRENT_USER. Note o SID do meu suário também.
    Depois desta breve introdução ao registro do sistema, podemos partir para as sub-chaves que realmente importam na questão da remoção manual de vírus.
    São elas:
    HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionRun HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionRunOnce HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionRunOnceEx HKEY_USERS\SOFTWAREMicrosoftWindowsCurrentVersionRun HKEY_USERS\SOFTWAREMicrosoftWindowsCurrentVersionRunOnce As duas últimas chaves acima dependem do SID do usuário mas se a suspeita de vírus for no usuário que está logado, você pode acessá-las pelo atalho como comentamos acima.
    Tudo o que estiver nestas chaves será inicializado junto ao sistema. Faça o teste: verifique o que tem nas sub-chaves de seu PC e veja os caminhos para os arquivos que incializam. No exemplo HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionRun da máquina que usei, está assim.

    Como podemos ver, só há três valores, que são os caminhos absolutos dos executáveis que inicializam junto à máquina usada. Esta é uma máquina virtual. Numa máquina real, os valores normais são outros.
    Para remover programas da inicialização, basta remover os valores desejados.
    É importante salientar que muitos arquivos presentes nestas sub-chaves são essenciais ao sistema e não devem ser removidos. Cabe ao técnico saber identificar caminhos e nomes de executáveis suspeitos. Se você tiver dúvida quando à procedência de algum arquivo, pode digitar seu nome no site Process Library, que mantém uma lista atualizadas de processos (programas em execução) para nos ajudar a identificar se são do sistema ou não.
    Há ainda o método de serviços que podem ser utilizado por alguns vírus mais complexos. A tela de serviços você tem acesso indo no menu Iniciar > Executar, digitando services.msc e clicando no botão OK. Eis a da máquina que usamos para o artigo.

    Esses serviços são na verdade processos (programas) inicializados que recebem este nome pela capacidade de poderem ser muito melhor gerenciados pelo sistema operacional que um processo comum. Perceba a coluna “Status” na imagem. Um serviço pode ser iniciado, reiniciado, pausado ou parado e seu método de inicialização pode ser manual (quando clicado), automático (a cada inicialização do sistema) ou desativado. Um vírus obviamente se aproveitaria do método automático e poderíamos pará-lo e depois desativá-lo numa remoação manual.
    Assim como os processos comuns, a maioria dos serviços é essencial ao sistema, portanto, é bom que se faça uma pesquisa sobre ele (no Google e sites similares) antes de parar ou desativar um serviço suspeito ou não.
    Ao entrarmos nas propriedades de um serviço, vemos o caminho do executável ao qual ele se refere além de uma caixa drop-down para alterar o tipo de inicialização, como mostra a imagem abaixo.

    Com este básico conhecimento, muitos vírus podem ser removidos mas é claro que não basta. Recomendamos sempre um scan com um bom antivírus atualizado e com um anti-spy, mesmo após a remoção manual.
    Existem alguns programas que podem ajudar na identificação de vírus e na remoção manual. Abaixo segue uma lista com descrição:
    Process Explorer – monitora os processos em execuçao em tempo real, o que permite identificarmos se algum processo suspeito está sendo executado. O Process Explorer também mostra o que o processo está fazendo ou tentou fazer (quando bloqueado pelo SO). HiJackThis – gera uma lista e um arquivo de log com todos os processos que inicializam junto ao sistema, podendo ser utlizado inclusive para remover o que sejam julgados suspeitos (muito cuidado com seu julgamento). NOTA: O projeto original foi descontinuado, mas o usuário Polshyn Stanislav da Ucrânia o continuou. Gmer - uma aplicação que detecta e remove rootkits (dentro de um limite, claro). A ideia é procurar por uma lista de coisas escondidas, tais como processos, threads, serviços, arquivos etc. MSCONFIG – utilitário presente no Windows XP mas que pode ser copiado a partir do arquivo msconfig.exe para outros sitemas Windows. Ele mostra de forma interativa o que está sendo inicializado junto ao sistema além de permitir a consulta de outras informações. Você pode chamá-lo a partir do menu executar.

    Fernando Mercês
    O Wireshark é um dos sniffers eanalisadores de tráfego de rede mais conhecidos atualmente. Ficou muito famoso com seu antigo nome, Ethereal. Além de ter uma ótima interface gráfica, também possui uma interface em linha de comando muito poderosa. Neste artigo vamos ver como são frágeis os sistemas de login em texto puro. Sniffaremos uma tentativa de login num servidor FTP e num site HTTP, que inclusive, é o nosso.  
    Um sniffer coloca a interface de rede da máquina onde está instalado em modo promíscuo, neste modo a interface capturará qualquer pacote que receba, mesmo os não destinados a ela. Normalmente as interfaces de rede descartam os pacotes que não são destinados à elas. É o famoso broadcasting de pacotes.
    Há também sniffers que não colocam a interface em modo promíscuo (para não serem detectados) mas neste caso, a máquina com o sniffer instalado tem que ficar entre os comunicantes e não só na mesma estrutura de rede.
    Outro detalhe é que um hub sempre manda o pacote que recebe por uma porta para todas as outras portas, o que facilita o sniffing. Já os switchs, não fazem isso (na verdade fazem só na primeira vez em que são ligados na rede e estão escrevendo sua tabela ARP. Depois mantêm tal tabela até ser desligado da energia). Mas não é impossível sniffar numa rede só com switchs, só é mais difícil.
    O primeiro passo é saber onde instalar o Wireshark. Este passo é muito importante pois definirá o sucesso na captura de pacotes. Atente para as disposições da máquina onde estará o Wireshark. Se for uma rede Ethernet concentrada por um hub, a máquina que rodará o Wireshark poderá ficar em qualquer lugar, basta ser conectada ao hub:

    Já que o hub envia todo o tráfego em broadcast, qualquer máquina recebe todo o tráfego da rede, então basta que a interface de uma delas esteja em modo promíscuo (Wireshark instalado e rodando) para que o sniffing tenha sucesso.
    Se o concentrador central de sua rede for um switch, você poderá colocar um hub entre o servidor e o switch e conectar uma máquina com o Wireshark neste hub. Assim, toda comunicação entre servidor e rede passará pela máquina com o Wireshark. Veja a imagem a seguir:

    Há outras maneiras de se fazer o sniffing com switchs, principalmente se o switch for configurável (que possibilita port spanning). Mas deixaremos tal discussão para outra hora. Pode-se discutir sobre assunto em nosso fórum também.
    Instalação do Wireshark
    Depois de escolhida a máquina onde será instalado o Wireshark, vamos para sua instalação, que pode ser feita de várias maneiras (baixando e compilando seu código-fonte, baixando sua instalação, instalando via repositório para sistemas baseados em Linux, etc).
    Como sempre, utilizei o Ubuntu para escrever este tutorial. No Ubuntu a instalação é fácil, basta comandar:
    $ sudo apt-get install wireshark
    Abra o Wireshark e clique no primeiro botão, entitulado "List available capture interfaces...". A janela que abrir mostrará uma lista com as interfaces de rede da máquina. Aqui a minha disposição na rede é como mostra a figura abaixo:

    Perceba que tenho o Wireshark instalado na máquina que dá acesso ao servidor. As outras duas máquinas estão conectadas ao servidor principal através dela.
    Captura dos pacotes
    Uma das máquinas tem o IP 192.168.0.23, e é através dela que vou acessar a internet e abrir um site FTP e um HTTP para que a máquina com o Wireshark capture os pacotes de tais conexões.
    Voltando ao Wireshark, escolhi a minha conexão eth1 (192.168.0.5) que é a placa de rede da máquina onde o Wireshark está instalado, que está ligada ao switch do esquema acima.

    Ao clicar em "Start", inicia-se a captura dos pacotes. Então, a partir da máquina 192.168.0.23, acessei o site FTP da Asus (ftp.asus.com) para uma tentativa de login. Fui pelo próprio comando FTP do Windows 98:
    c:\> ftp ftp.asus.com
    E inseri o nome de usuário fernando e password 3966lop98. Obviamente recebi um erro de falha no login. Neste momento podemos clicar no botão Stop (quarto botão da barra do Wireshark).
    Vamos analisar o que o Wireshark capturou:

    Perceba que no pacote de número 14, há menção ao meu nome de usuário no comando USER do FTP. É importante conhecer o protocolo que será analisado para obter bons resultados com o sniffing.
    No pacote 16 o servidor da Asus me informa que preciso informar um password. E nos pacotes 18 e 19, abro uma conexão TCP e envio meu password com um comando PASS, respectivamente.
    Veja que o argumento do comando PASS é 3966lop98, que é justamente o password que informei através da máquina 192.168.0.23!
    O protolo FTP transmite os comandos e argumentos em texto puro. É extremamente inseguro e em seu lugar é recomendável utilizar o SFTP (Secure File Transfer Protocol).
    Claro que foi uma transmissão pequena e premeditada. Se fôssemos analisar diante de vários milhares de pacotes, seria difícil achar manualmente o pacote que se relaciona com o login. Para isso existem os filtros personalizados e montados com expressões regulares pelo próprio Wireshark. Usaremos um filtro no próximo sniffing, que será HTTP.
    Sniffing em login do Mente Binária
    Agora vamos acessar um site a partir da máquina 192.168.0.23 para analisarmos os pacotes. Seguindo a mesma lógica do item anterior, vamos capturar os pacotes até que o login seja efetuado. Loguei com um usuário. Vamos descobrir quem é (e sua senha) através do Wireshark.
    A análise de pacotes retornou 742 pacotes até eu clicar em Stop. Não vamos olhar um a um e sim usar um filtro.
    No campo "Filter", digite: "http matches POST" (sem aspas, respeitando a caixa). E clique em "Apply".
    Vamos ver o resultado:

    Veja que filtramos só para pacotes HTTP que contenham o comando POST. Este é um comando de formulário HTTP (comumente usado em logins). É o comando dado pelo botão "Enviar" do nosso site. No pacote de número 150, vemos o nome de usuário e a senha sendo enviados para o servidor de nosso site!
    Conclusão
    Espero que o propósito deste artigo tenha sido atingido: mostrar que alguns protocolos são facilmente vulneráveis. Com um rápido sniffing, podemos reunir informações que seriam suficientes para um invasor se apossar de um conta da vítima. Teste o Wireshark em sua rede, veja como ele se comporta capturando pacotes HTTPS, SFTP, SSH, etc, que são protocolos seguros. Veja que as informações estarão encriptadas.

    Fernando Mercês
    Apesar de ser um assunto que já foi tratado em muitos sites e fóruns, continuo a observar que alguns usuários do Windows devem ter mais atenção com o tipo de arquivo que aplica o famoso duplo-clique. Identificar se o arquivo é inofensivo ou se é uma ameaça pode ser mais fácil do que se imagina. Este artigo tem como intenção desmistificar as lendas sobre as extensões de arquivos maliciosos e alertar sobre as extensões perigosas.
    O sistema operacional Windows reconhece (deduz o conteúdo) dos arquvios por sua extensão. Mas o que é extensão de arquivo?
    Extensão de arquivo para o MS-Windows, comumente são os três caracteres após o ponto, no nome completo do arquivo. Por exemplo, documento001.txt, é um arquivo com extensão .TXT, logo, o Windows deduzirá que é um documento de texto e associará este documento à um certo programa, que poderá abrí-lo (recebendo seu caminho como parâmetro) mediante um duplo-clique neste documento.
    Confundiu? Bom, em termos práticos, cada extensão de arquivo que mereça, possui um determinado programa responsável por interpretar um duplo-clique num arquivo que possua tal extensão. Essas informações (quais extensões são abertas por qual programa) ficam no registro do Windows.
    Ainda com o exemplo do documento001.txt, vamos analisar sua associação: ao dar um duplo-clique nele, vemos que ele é aberto pelo Bloco de Notas (notepad.exe).
    A chave do registro responsável por armazenar as informações desta associação é, no Windows XP: HKEY_CLASSES_ROOT\txtfile\shell\open\command. Veja a imagem:

    Perceba que na coluna "Dados" há o caminho completo do notepad.exe (usando uma variável de sistema sim, mas não deixa de ser o caminho absoluto), seguido de %1. Já sabemos que é o Bloco de Notas (notepad.exe) que abrirá arquivos de texto, agora vamos entender o parâmetro.
    Eu escrevi mais acima que o caminho do arquivo a ser aberto era passado por parâmetro. É justamente isso que o "%1" faz. Essa variável armazena o caminho absoluto do arquivo que está sendo acessado, no instante do acesso. Portanto, se você clicou no arquivo documento001.txt e ele está em C:\docs\, esta variável conterá o valor C:\docs\documento001.txt. Isso informa ao Bloco de Notas onde está o arquivo.
    Agora que já sabemos o que é e como funciona a extensão, vamos aos riscos.
    Um arquivo executável precisa ter uma extensão de executável para ser executado. As extensões mais comuns de arquivos executáveis são: EXE, COM, BAT, VBS, MSI, SCR, arquivos do Office (porque podem conter macros, que são executáveis).
    Um vírus, obrigatoriamente, tem que ter uma dessas extensões. Ou seja, ele tem que ser um executável.
    O problema é que há certos disfarces utilizados pelos disseminadores de vírus. Um deles é colocar um nome de arquivo do tipo: "arquivo.jpg .exe". Assim mesmo, com vários espaços entre o .jpg e o .exe. A extensão deste arquivo de exemplo é .EXE e ele será executado como tal! Não é .jpg! O texto ".jpg" neste caso faz parte do nome do arquivo e não da extensão. A "técnica" de colocar espaços é para que os programas de email e webmail identifiquem um nome muito grande e exibam reticências após o .jpg, dando a impressão que é um arquivo de imagem.
    Detalhe que o ícone de um executável pode ser facilmente alterado para o ícone de uma imagem, o que aumenta as chances da vítima de ser enganada.
    Os vírus são programas. Logo, repito, são executáveis. Um arquivo de áudio puro, por exemplo, não pode ser vírus! A extensão .mp3 estaria associada ao Windows Media Player, ou Winamp, ou qualquer outro. Esses software não executam rotinas de executáveis. Só entendem fluxo de mídia, portanto, não executam as rotinas virais diretamente. Mas eles podem conter falhas que sejam exploradas através de payloads maliciosos de arquivos de mídia ou playlists (como os .m3u).
    Tenha certeza da extensão do arquivo e de sua procedência, e estará praticamente livre de ser infectado por um vírus anexo à um e-mail ou disponível para download. Mas lembre-se que os arquivos podem também estar zipados (.ZIP) ou compactados com outro compactador (RAR, LZIP, GZIP, LHA, JAR, etc). Dentro deles é que você deve examinar a extensão do arquivo.

×
×
  • Create New...