Ir para conteúdo
  • Cadastre-se

Pesquisar na Comunidade

Mostrando resultados para as tags ''programação''.



Mais opções de pesquisa

  • Pesquisar por Tags

    Digite tags separadas por vírgulas
  • Pesquisar por Autor

Tipo de Conteúdo


Fóruns

  • Conteúdo Mente Binária
    • Núcleo
    • Projeto Bumerangue
    • Fale com a gente!
  • Comunidade
    • Engenharia Reversa
    • Programação
    • Redes
    • Análise de malware
    • Teste de intrusão
    • Sistemas operacionais
    • Certificações
    • Oportunidades
    • Outros
  • Segurança na Internet's Discussão

Categorias

  • Crackmes
  • Documentação
  • Debuggers
  • Ferramentas para PE
  • Utilitários
  • Packers
  • Unpackers

Encontrar resultados em...

Encontrar resultados que...


Data de Criação

  • Início

    FIM


Data de Atualização

  • Início

    FIM


Filtrar pelo número de...

Data de Registro

  • Início

    FIM


Grupo


Website


Github


LinkedIn

Encontrado 7 registros

  1. Eduardo Cintra

    Indicação de Livro socket em C

    Alguém me indica um bom livro para aprender programação utilizando sockets em C? Pode ser em inglês. Desde já agradeço!
  2. HiroMarikame

    Gerador de paginas estáticas

    Olá, bom dia/tarde/noite m/ Pessoal fiz um script para me gerar paginas estaticas usando python. SEI QUE EXISTEM PLATAFORMAS que não exigem conhecimentos tecnicos para criar sites mas aproveitei a oportunidade para estudar algo interessante para minha pessoa... É algo simples mas que rendeu a aplicação de conceitos como OOP, Ajax, JSON, requests etc. A ideia é que ele seja simples, então tentei simplificar utilizando JSON. Porém, mesmo assim há algumas "complicações", ainda tenho que escrever parte do código HTML para colocar o conteudo como: textos, imagens etc. sem contar que o JSON tem uma limitação quanto ao tamanho da string, ou seja, é dificil manter um texto estruturado no JSON. Há erros, tanto no código quanto no design. E não sou bom com design portanto tentei fazer o basico do basico. Quem estiver disposto a contribuir para melhorar esse pequeno projeto é só seguir o link do github \m/ : https://github.com/HiroTatsuo/Static-Project PS: Qualquer coisa implementada será estudada por mim, então não será uma contribuição para o projetinho apenas. mas sim para meus conhecimentos '--' rsrsrs Esse é um print de como ficam as paginas.
  3. Fernando Mercês

    Como são feitos os keygens

    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.
  4. Fernando Mercês

    Como hookei a execve() em x86-64 num LSM

    Este foi um problema que tive há bastante tempo, quando precisei fazer com que o módulo de kernel do antivírus Trend Micro ServerProtect funcionasse em versões de 64-bit do Linux. Acontece que este módulo utiliza o LSM (Linux Security Module) framework. Até aí tudo bem, foi como o jeito que programadores do módulo usaram para hookar a execve() nesta arquitetura, essencial para um antivírus por motivos óbvios: toda vez que um arquivo fosse executado no sistema, este seria escaneado por vírus. Apesar de um pouco defasado, decidi escrever agora pois me animei quando fui ao 11º Encontro da comunidade C/C++ Brasil [3]. De fato, atualmente um LSM não pode mais ser carregado num kernel rodando, como um LKM (Loadable Kernel Module) e isso tira toda a praticidade deste patch em específico, mas a técnica utilizada pode ser útil em outras ocasiões. Ou não. Indo direto ao ponto, a estrutura default_security_ops não era mais acessível diretamente na versão do kernel que trabalhei e por isso o seguinte trecho de código não funcionava num contexto LSM: unsigned long addr = kallsyms_lookup_name("default_security_ops"); Minha intenção não era reescrever todo o módulo, então tive que dar um jeito rápido. A ideia foi buscar uma função exportada que manipulasse a estrutura default_security_ops, assim eu teria acesso ao endereço dela. Várias funções o fazem, mas buscando pela mais simples/menor encontrei a reset_security_ops(), presente em security/linux.c: void reset_security_ops(void) { security_ops = &default_security_ops; } Essa função só faz uma coisa: coloca o endereço da estrutura que quero em outra. Sendo assim, é uma perfeita candidata para minha gambiarra. Pelo tamanho daria para tentar advinhar quão distante está o endereço da estrutura default_security_ops do endereço da função reset_security_ops(), mas vamos disassemblar só pra garantir: 1. Extraindo a imagem do kernel O kernel fica comprimido em /boot e no próprio fonte existe um script para descomprimi-lo chamado extract-vmlinux. Usei da seguinte maneira: $ sudo cp /boot/vmlinuz-$(uname -r)-generic vmlinuz # trabalhar com um backup, só pra garantir ;) $ sudo chown $(whoami): vmlinuz $ /usr/src/linux-headers-$(uname -r)/scripts/extract-vmlinux vmlinuz > vmlinux O arquivo vmlinux (com “x” ao invés de “z”) criado é a imagem descomprimida do kernel, que precisamos disassemblar. 2. Disassemblando a imagem Por padrão a imagem do kernel não contém símbolos, então minha tentativa com o gdb foi frustrada: $ gdb -q ./vmlinux Reading symbols from ./vmlinux...(no debugging symbols found)...done. (gdb) disassemble reset_security_ops No symbol table is loaded. Use the "file" command. Mas nem tudo está perdido. No kernel rodando, dá pra ver o endereço das funções no arquivo System.map: # grep reset_security_ops /boot/System.map-$(uname -r) ffffffff812d6be0 T reset_security_ops Sabendo o endereço, voltei ao gdb e pedi pra printar 8 intruções começando neste endereço, mas não antes de setar a sintaxe para Intel. (gdb) set disassembly-flavor intel (gdb) x/8i 0xffffffff812d6be0 0xffffffff812d6be0: call 0xffffffff81731480 0xffffffff812d6be5: push rbp 0xffffffff812d6be6: mov QWORD PTR [rip+0xcdd14f],0xffffffff81c80100 # 0xffffffff81fb3d40 0xffffffff812d6bf1: mov rbp,rsp 0xffffffff812d6bf4: pop rbp 0xffffffff812d6bf5: ret 0xffffffff812d6bf6: nop WORD PTR cs:[rax+rax*1+0x0] 0xffffffff812d6c00: call 0xffffffff81731480 (gdb) Essa call no início apontar para um ret e honestamente eu não sei por que ela existe: (gdb) x/i 0xffffffff81731480 0xffffffff81731480: ret 3. Contando os bytes Lembrando que esta função simplesmente implementa a atribuição do endereço que queremos (default_security_ops) para uma variável, fica fácil perceber que a varíavel (security_ops) é RIP+0xcdd14f e o endereço da estrutura que queremos é 0xffffffff81c80100. Mas claro, a ideia aqui é fazer de forma genérica, então não posso trabalhar com esse endereço fixo. Bem, admitindo que essa função não mude, é razoável dizer que o endereço é um número de 64-bits que tem seu primeiro byte em uma posição fixa a partir do endereço da função reset_security_ops(). Isto está longe de ser uma solução profissional, mas resolveu meu problema na época. Ao invés do x/i (examine as instruction), vou usar o disassemble pra poder contar melhor os bytes: (gdb) disassemble /r 0xffffffff812d6be0, 0xffffffff812d6bff Dump of assembler code from 0xffffffff812d6be0 to 0xffffffff812d6bf4: 0xffffffff812d6be0: e8 9b a8 45 00 call 0xffffffff81731480 0xffffffff812d6be5: 55 push rbp 0xffffffff812d6be6: 48 c7 05 4f d1 cd 00 00 01 c8 81 mov QWORD PTR [rip+0xcdd14f],0xffffffff81c80100 0xffffffff812d6bf1: 48 89 e5 mov rbp,rsp 0xffffffff812d6bf5: c3 ret End of assembler dump. Para disassemblar sem símbolo você precisa dizer ao gdb até onde ir, por isso fui até 0xffffffff812d6bff, mas cortei o que veio depois do ret no dump. Se contarmos, vamos perceber que o número começa em (endereço da função) + 13 e tem 4 bytes. Só pra confirmar: (gdb) x/x 0xffffffff812d6be0 + 13 0xffffffff812d6bed: 0x81c80100 Agora vamos seguir (#medo)! 4. Implementação Ficou deste jeito: unsigned long default_security_ops = 0xffffffff00000000; unsigned long _reset_security_ops = 0; _reset_security_ops = kallsyms_lookup_name("reset_security_ops"); memcpy(&default_security_ops, (void *) (_reset_security_ops + 13), 4); A variável default_security_ops já é inicializada com sua parte alta toda setada, pois só a parte baixa do endereço está no assembly. A kallsyms_lookup_name() me dá o endereço da função reset_security_ops(). A memcpy() então copia os 4 bytes que compõem o segundo operando da instrução mov, que é o nosso endereço. O resultado abaixo foi obtido com este código implementado em um módulo: # grep default_security_ops /boot/System.map-3.13.0-44-generic ffffffff81c80100 d default_security_ops # rmmod teste; make >/dev/null && insmod teste.ko && dmesg | tail -1 [15999.471619] default_security_ops address: 0xffffffff81c80100 Os endereços batem, no entanto, essa coisa não foi para produção (graças ao Divino) e não recomendo que utilizem nada parecido em nenhum sistema crítico, mas será que dá pra aplicar essa técnica de “contagem de bytes” para outras coisas?
  5. Fernando Mercês

    Especificações da linguagem C

    Há algum tempo tinha vontade de escrever algo sobre os padrões da linguagem C e recentemente, motivado por uma discussão na lista exploits-brasil [1] e aproveitando um domingo nublado no Rio de Janeiro, decidi começar. Este artigo vai ajudá-lo a entender (e quando utilizar) os padrões de programação da linguagem C. Não ache que a utilidade é teórica, pois não é. Seguir um padrão pode ajudar na segurança de código, otimização e até aumentar a produtividade, como veremos mais a diante (sabia que C tem o tipo boolean, por exemplo?). 1. Introdução A maioria das linguagens de programação seguem padrões abertos, que os criadores de compiladores (ou interpretadores) precisam seguir para suportar a linguagem completamente. A linguagem C foi criada por Denis Ritchie (falecido em 2011) entre 1969 e 1973 com o objetivo de substituir o Assembly (o que explica a existência comandos como goto na linguagem). Desde então, várias versões de compiladores foram criadas, por várias pessoas e empresas, sem seguir uma especificação oficial. Por isso, em 1983, o American National Standards Institute, mais conhecido pela siga ANSI, formou um comitê para estabelecer um padrão, que só foi concluído em 1989. 2. Linha do tempo da linguagem C 1969-1973: Denis Ritchie cria a linguagem C 1983: O ANSI cria um comitê para especificar a linguagem 1989: A especificação ANSI X3.159-1989 é concluída. O nome informal dela é C89 e também é conhecida por ANSI C. 1990: O ISO (International Organization for Standardization) assume a especificação de linguagem e cria um grupo de trabalho (working group) chamado WG14 [2] para cuidar da especificação do C, que é renomeada para ISO/IEC 9899:1990, informalmente conhecida como C90. Não houve mudanças na linguagem. Esta especificação se mantém por vários anos e define a linguagem C que “todos conhecem”. 1999: O WG14 publica a especificação ISO/IEC 9899:1999, informalmente chamada de C99. Esta já traz várias novidades que serão tratadas mais à frente neste artigo. 2011: O WG14 publica a ISO/IEC 9899:2011, conhecida como C11. Este é o padrão atual da linguagem. Vários recursos cobertos nas especificações mais recentes já eram implementados pelos desenvolvedores de compiladores por sua conta. No caso do GCC, por exemplo, o Projeto GNU criou os padrões GNU89, GNU99 e GNU11 com suas próprias extensões. O Visual C++ da Microsoft também tem várias extensões da linguagem (lembre-se que em geral qualquer compilador C++ compila C, pois a linguagem é a mesma, mas a recíproca não é verdadeira). Vou mostrar alguns recursos das especificações atuais, utilizando o gcc 4.6 para os exemplos. A opção -std do gcc define qual especificação usar. Já a -pedantic faz o gcc alertar sobre qualquer construção que seja proibida pela especificação, de forma bem rigorosa. 3. Recursos da C99 Tipo booleano: Incluindo a stdbool.h, você pode declarar variáveis do tipo bool e usar os estados verdadeiro e falso, assim: #include <stdbool.h> void main() { bool var; if (var) var = false; } É fato que isso já era feito usando-se 1 e 0 e na verdade os estados true e false não passam de outros nomes (#define) para estes números, respectivamente. No entanto, a legibilidade do código melhora com o tipo bool. Comentários com // A C89 só permite comentários entre /* e */, o que dá um trabalho extra para comentários de uma linha. Usando a C99, você pode iniciar a linha com //, como no C++, PHP etc. e esta linha será um comentário perfeitamente válido. Funções em linha Quando você chama uma função no código, o compilador normalmente gera uma instrução CALL (do Assembly) para desviar o fluxo de código para o endereço da função. Isso envolve passagem de parâmetros pela pilha de memória (stack), retorno etc. Ou seja, isto gera um ônus de processamento e consumo de memória considerável, principalmente associado ao número e tamanho dos argumentos. Um código que não use funções é mais otimizado, no entanto, é mais difícil de manter. Para resolver isso, você pode dizer que uma função é em linha (inline). Dessa forma, toda vez que a função for chamada, o código dela será copiado para o trecho onde a chamada aconteceu. É como se você copiasse e colasse a função em vários trechos, mas sem precisar fazer isso. Imagine que temos a função: inline int soma(int num1, num2) { return num1 + num2; } Como ela foi declarada como inline, ao chamá-la por exemplo como: var = soma(3, 5); O compilador vai substiuir por: var = 3 + 5; Vetores de tamanho variável Na C99 você pode declarar um vetor assim: void funcao(int numero) { char vet[numero]; } O tamanho do array de char vet depende do parâmetro recebido. Isso era resolvido antes com funções para alocação dinâmica. Há adições também do tipo complex, para números complexos (e imaginários) e muito mais novidades. No site do WG14 [2] há toda a especificação para download. Para utilizar a C99 com o gcc, use: $ gcc -std=c99 fonte.c 4. Recursos da C11 Novas funções seguras (com bound checking) As funções tidas como inseguras como strcpy, strcat etc ganharam novas versões sufixadas com “_s”, de safe. Basicamente elas recebem um argumento a mais que é o número de caracteres para trabalhar. São elas: Multithreading (thread.h) Apesar de os SOs modernos fornecerem funções de API para multithreading, ao ser inserida oficialmente no C possivelmente vai ajudar na portabilidade de aplicativos que utilizem este recurso. É algo muito bom para a linguagem. Estruturas e uniões anônimas Útil quando estão aninhados, este foi um recurso que senti falta quando programei a primeira versao do pev, que usa muitas estruturas. Se queremos que um campo de uma estrutura seja outra estrutura, antes da C11, é preciso fazer: struct ponto { int x; int y; } struct reta { struct ponto p; } Aí para acessar: struct reta r; r.p.x = 10; Com a C11, a estrutura reta pode ser: struct reta { struct ponto; } E o acesso: r.x = 10; Dizemos então que a estrutura reta possui uma estrutura anônima (sem nome) como campo. 5. Qual especificação usar? Na hora de usar uma especificação e seguir suas regras, você deve pesar como é o seu projeto, em quais sistemas ele vai rodar, como será distribuído etc. Por exemplo, se é um projeto interno e o compilador que você utiliza suporta a C99, não vejo por que não utilizá-la. Agora, se você vai distribuir seu código, aí já tem que pensar nas versões dos compiladores de quem for baixar. Será que o gcc do usuário do seu software é a última versão? Será que suporta a C11? No hdump, um dumper hexa/ASCII que fiz para usar no Windows e no Solaris, eu optei por trabalhar com C89 por dois motivos: o código é pequeno e eu precisava levar o fonte para compilar em versões antigas do Solaris, com compiladores que não suportavam a C99, por exemplo. Já para o pev um analisador de binários de Windows, eu comecei em ANSI mas já estou atualizando para C99, para deixar o código mais legível e utilizar os novos recursos. Eu distribuo o fonte e binários já compilados, inclusive para Windows, então não tem problema para os usuários. O importante é ler as especificações e perceber se tem algo que te ajuda/atrapalha no projeto, pensando no escopo, usuários e sistemas alvo. Só a C11 que acho que ainda não dá para distribuir um código em produção, visto que nenhum compilador à suporta completamente. Recomendo usá-la internamente e esperar um pouco antes de liberar código com ela, pois está muito recente. 6. Conclusão Existem outros recursos interessantes na C11 [3] mas ainda não conheço um compilador que a suporte 100%. Na verdade o gcc também não suporta a C99 por inteiro ainda. Existe até uma página onde você pode acompanhar as mudanças no suporte à C99 [4]. Na versão 4.6, o gcc já estreiou o suporte inicial à C11, sob a opção -std=c1x. A versão 4.7 já vai vir com suporte a mais recursos desta especificação, sob a opção -std=c11. Inslusive cabe um comentário: é muito bom poder contar com um conjunto de compiladores constantemente atualizado e bem feitos como são os do GCC. Essa maravilhosa suíte inclui compiladores para C, C++, Objective-C, Fortran, Java, Ada, e Go. Todos livres. É um espetáculo mesmo. O último rascunho ISO/IEC 9899:201x, divulgado em abril do ano passado está disponível em PDF [5] mas a especificação mesmo, publicada em dezembro, está à venda por cerca de R$ 450,00 na ISO Store [6]. Referências [1] https://groups.google.com/forum/?fromgroups#!forum/exploits-brasil [2] http://www.open-std.org/JTC1/SC22/WG14/ [3] http://en.wikipedia.org/wiki/C11_%28C_standard_revision%29 [4] http://gcc.gnu.org/gcc-4.6/c99status.html [5] http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf [6] http://www.iso.org/iso/iso_catalogue/catalogue_tc/catalogue_detail.htm?csnumber=57853
  6. 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.
  7. 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ê
×