Jump to content
  • Disassemblando x86 em Go

       (3 reviews)

    Comecei a estudar a linguagem Go há alguns dias e fiquei muito impressionado com seus recursos. A facilidade para programação paralela, o fato de ter ponteiros, funções que retornam mais de um valor, código enxuto (se você declarar uma variável e não usar, o programa nem compila!) e outros realmente me encantaram.

    Recentemente precisei disassemblar um trecho de código de um binário PE para um projeto que está escrito em Go. Vi que existem algumas bibliotecas prontas para serem usadas, como gapstone (bindings da Capstone) e go-zydis (bindings da Zydis) mas não encontrei uma nativa.

    No entanto, vi que existe uma ferramenta nativa no toolset da linguagem similar ao objdump do GNU binutils:

    $ go doc cmd/objdump
    Objdump disassembles executable files.
    
    Usage:
    
        go tool objdump [-s symregexp] binary
    
    Objdump prints a disassembly of all text symbols (code) in the binary. If
    the -s option is present, objdump only disassembles symbols with names
    matching the regular expression.

    Compilei um "hello, world" em Go só pra ver:

    ~/hello $ cat main.go
    package main
    
    import "fmt"
    
    func main() {
    	fmt.Println("menteb.in")
    }
    
    ~/hello $ go build

    E de fato o objdump da Go funciona:

    ~/hello $ go tool objdump hello | head
    TEXT go.buildid(SB)
      :-134217728		0x1001000		ff20			JMP 0(AX)
      :-134217728		0x1001002		476f			OUTSD DS:0(SI), DX
      :-134217728		0x1001004		206275			ANDB AH, 0x75(DX)
      :-134217728		0x1001007		696c642049443a20	IMULL $0x203a4449, 0x20(SP), BP
      :-1			0x100100f		226d35			ANDB 0x35(BP), CH
      :-1			0x1001012		4c6f			OUTSD DS:0(SI), DX
      :-1			0x1001014		6a52			PUSHL $0x52
      :-1			0x1001016		436e			OUTSB DS:0(SI), DX
      :-1			0x1001018		4a31794f		XORQ DI, 0x4f(CX)

    Mas ao tentar com o um PE compilado pra 64-bits, descobri que só funciona com binários feito em Go. ?

    $ go tool objdump putty.exe
    objdump: disassemble putty.exe: no runtime.pclntab symbol found

    De qualquer forma, resolvi olhar o código-fonte deste objdump interno da linguagem pra ver qual é dessa mandinga.  Na linha 43 do main.go do objdump tem um import pra uma biblioteca chamada objfile. Pensei: Wow, deve ser uma biblioteca de disassembly, talvez eu possa alterar ! E na hora já criei um projeto tentando usá-la mas fui surpreendido com um errão! kkkk

    ~hello $ cat main.go
    package main
    
    import "fmt"
    import "cmd/internal/objfile"
    
    func main() {
    	fmt.Println("menteb.in")
    }
    
    ~hello $ go build
    main.go:4:8: use of internal package cmd/internal/objfile not allowed

    Não pesquisei muito sobre essa história sobre eu não poder usar um pacote interno (por quê o objdump pode e eu não posso?!), mas fui olhar esta objfile e terminei encontrando seu fonte. Para minha alegria, neste arquivos disasm.go vi os seguintes imports:

    "golang.org/x/arch/arm/armasm"
    "golang.org/x/arch/arm64/arm64asm"
    "golang.org/x/arch/ppc64/ppc64asm"
    "golang.org/x/arch/x86/x86asm"

    Agora sim, carái! É tudo público e posso usar. Desculpe o desabafo.. hehe o artigo na verdade começa aqui mas quis contar como cheguei porque né. ?

    Cada uma dessas bibliotecas possui uma função Decode() justamente pra decodificar uma instrução (tipo Inst). Testei com um NOP em 64-bits, só pra ver:

    package main
    
    import (
    	"fmt"
    	"log"
    
    	"golang.org/x/arch/x86/x86asm"
    )
    
    func main() {
    	dados := []byte{0x90}
    
    	ins, err := x86asm.Decode(dados, 64)
    
    	if err != nil {
    		log.Fatalln(err)
    	}
    
    	fmt.Println(ins)
    }

    A saída foi exatamente a esperada:

    $ ./hello
    NOP

    Show. Agora é abrir um PE, ler de onde quero e daí disassemblar usado essa x86asm.Decode() num loop, mas vou deixar esse exercício aí pra quem quiser treinar Go. Ou se acharem útil posso postar um aqui mais tarde. Aqui já funcionou mas precisa de uma polida. ?

    Perceba também que há bibliotecas para ARM e PowerPC. Achei bem maneiro. Talvez em breve o time da Go adicione suporte a mais arquiteturas. Amém! ? 

    Edited by Fernando Mercês


    User Feedback

    Join the conversation

    You can post now and register later. If you have an account, sign in now to post with your account.
    Note: Your post will require moderator approval before it will be visible.

    Guest

    • This will not be shown to other users.
    • Add a review...

      ×   Pasted as rich text.   Paste as plain text instead

        Only 75 emoji are allowed.

      ×   Your link has been automatically embedded.   Display as a link instead

      ×   Your previous content has been restored.   Clear editor

      ×   You cannot paste images directly. Upload or insert images from URL.



  • Similar Content

    • By Bruna Chieco
      O professor, analista de sistemas e economista José Augusto N. G. Manzano lançou o livro "Algoritmos Funcionais - Introdução minimalista à lógica de programação funcional pura aplicada à teoria dos conjuntos". 
      O livro busca fundamentar, dentro de seu escopo, diversas ações matemáticas e de programação importantes dentro do universo funcional. "Este livro foi escrito exclusivamente para uso em sala de aula. É um material voltado à apresentação e estudo da 'Lógica de Programação Funcional', que vem tomando grandes posições dentro do universo comercial no desenvolvimento de software", escreveu o autor em publicação no seu LinkedIn.
      Paradigma funcional – O paradigma funcional tem um jeito de programar específico, diferenciado do tradicional, com algumas vantagens para certas aplicações. Uma das áreas em que o paradigma funcional se destaca é a ciência de dados, que tem sido uma grande aposta das empresas, especialmente startups, na busca de profissionais.
      Dentro do paradigma funcional, há várias linguagens de programação. O Nubank, por exemplo, utiliza bastante uma linguagem chamada Clojure, e tem uma alta demanda por profissionais que programem nessa linguagem (saiba mais). 
      Este livro, portanto, é uma introdução para quem quer adentrar nesse mundo das linguagens funcionais. Qualquer futuro programador em uma dessas linguagens funcionais vai se beneficiar deste conteúdo. 

      Clique e veja como comprar
      O professor Manzano se destaca também por produzir muitos materiais da área, se mantendo sempre atualizado sobre o que o mercado está precisando no momento. Já divulgamos outros trabalhos de sua autoria aqui no Mente Binária, entre eles o livro 'Fundamentos em Programação Assembly':
       
      Inclusive, o canal do professor Manzano no YouTube possui mini aulas sobre programação de computadores e outros assuntos relacionados à área da computação e tecnologia da informação. Vale conferir!
    • By Bruna Chieco
      Pesquisadores da Sayfer fizeram engenharia reversa no WhatsApp Web e acabaram encontrando "sem querer" um recurso desabilitado por padrão, ou seja, que ainda não está liberado. A função pre-released descoberta permite que a velocidade de um áudio seja acelerada. 
      Os pesquisadores, na verdade, estavam fazendo uma pesquisa sobre outro projeto quando descobriram acidentalmente que o WhatsApp tem um sinalizador para o recurso oculto que permite aos usuários alterar a taxa de reprodução de mensagens de voz.
      Uma das ferramentas de pesquisa utilizadas pelos pesquisadores permitiu essa alteração, sendo cada mensagem de voz é essencialmente uma marca de áudio com um blob de uma fonte de áudio. Para alterar a velocidade, contudo, foi necessário fazer engenharia reversa no código-fonte minimizado do WhatsApp para a web.
      Eles descobriram que o WhatsApp tem três velocidades pré-determinadas para os áudios, porém, desabilitadas. Em publicação, os pesquisadores explicam o passo a passo do que fizeram para conseguir alterar a taxa de reprodução dos áudios.
      E se você quiser saber mais sobre engenharia reversa, o Mente Binária tem um curso com 24 aulas que pode ser acessado por meio do nossos canal no YouTube:
       
    • By Felipe.Silva
      Injeção de código é uma técnica que consiste em adicionar instruções extras em um executável. Essas instruções podem ser adicionadas em vários lugares do programa, inclusive executar antes do entry point original.
      O uso mais comum para injeção de código é para fins maliciosos, onde um shellcode poderia ser inserido no executável e dar acesso remoto para um atacante. Mas um exemplo de uso "justo" para essa técnica é para fins de patching no executável quando você quer que algo seja alterado em tempo de execução no binário.
      Se você já tentou injetar código em um executável manualmente deve saber que não é uma tarefa tão divertida. Pensando neste tipo de impasse, imaginei que seria interessante ter uma ferramenta para automatizar esse tipo de manipulação de um executável. Por esse motivo criei o pei, uma ferramenta para automatizar injeção de código e outros tipos de manipulações em executáveis PE de 32-bit e 64-bit.
      O pei foi programado pensando na automação e por isso é fácil usar a ferramenta a partir de um script. Com ela você pode obter e modificar valores no executável, e é claro, injetar código.
      Antes de qualquer coisa você pode instalar o pei em seu Linux rodando os comandos abaixo:
      git clone https://github.com/Silva97/pei cd pei make sudo make install Nota: Caso use Windows e não esteja usando WSL ou um MSYS2 da vida, você pode compilar o projeto instalando o make e o MinGW (recomendo usar o Chocolatey). No entanto, o “sudo make install” não vai funcionar no Windows, você vai ter que adicionar o executável ao PATH manualmente.
      Se você não estiver a fim de compilar o executável, fiz o favor de adicionar o binário da ferramenta compilado para Windows nos releases dela. Você pode baixá-lo no link https://github.com/Silva97/pei/releases/latest.
      O uso básico da ferramenta segue o seguinte formato:
      pei [opções] <operação> <executável> [argumento] Se você quiser ver mais detalhes de como usar a ferramenta você pode rodar “pei -h”.
      Operações
      As operações são o que determinam o que a ferramenta irá fazer com o executável, indo desde exibir informações sobre ele até modificar campos dos cabeçalhos.
      show
      A operação show serve para exibir informações sobre o executável e campos dos cabeçalhos. Se você não passar argumentos para a operação por padrão ela irá exibir informações básicas do executável:

      Você também pode especificar uma combinação de letras para escolher quais campos serão exibidos, dentre elas: c (COFF header), o (optional header), d (data directories) e s (section). Exemplo:
      $ pei show test.exe co Esse comando, por exemplo, exibe o COFF header e optional header do executável.
      get
      A operação get pega o valor de um campo individual de um dos cabeçalhos (coff, optional ou section) do executável.
      Seguindo uma notação com pontos, semelhante à acessar campos de estruturas em C, você pode especificar o cabeçalho e o nome do campo para ser obtido. Por exemplo, para obter o entry point do executável o comando ficaria:
      $ pei get executavel.exe optional.entry_point '%x' 14f0 Dica: Veja o nome dos campos dos cabeçalhos usando a operação show.
      O argumento após o nome do campo é uma string de formatação idêntica a da função printf() da libc, que aceita quaisquer flags de formatação disponíveis para a função.
      Para acessar os campos de uma seção é necessário especificar o número da seção também, como demonstrado no print abaixo:

      edit
      A operação edit serve para modificar o valor de campos, onde o nome do campo é especificado de maneira idêntica à operação get.
      Você pode utilizar os operadores `=`, `|=` e `&=` que fazem exatamente a mesma coisa que na linguagem C. Exemplos:
      $ pei edit executavel.exe section.0.name = .code $ pei edit executavel.exe optional.entry_point = 0xabcd1234 Esta operação aceita números em decimal, hexadecimal ou octal na hora de definir o valor de campos numéricos.
      zeros
      Esta operação simplesmente exibe uma lista das maiores sequências de bytes nulo em cada seção do executável. É este espaço que é utilizado para injetar o código, tornando a operação útil para você poder escolher em qual seção injetar o código.
      $ pei zeros executavel.exe Section #0 '.text': 0x00000000000022fb of 13 bytes Section #1 '.data': 0x000000000000242c of 1012 bytes Section #2 '.rdata': 0x0000000000002a5b of 37 bytes Section #6 '.idata': 0x0000000000003a26 of 22 bytes Section #7 '.CRT': 0x000000000000420b of 21 bytes Section #9 '/4': 0x0000000000004649 of 23 bytes Section #10 '/19': 0x0000000000004cbe of 10 bytes Section #12 '/45': 0x000000000003e2fc of 5 bytes Section #13 '/57': 0x0000000000041019 of 8 bytes Section #15 '/81': 0x0000000000043c33 of 44 bytes Section #16 '/92': 0x0000000000045509 of 23 bytes inject - A cereja do bolo 🙂
      Esta é a operação que injeta o código. Você pode usar a opção -f para especificar o arquivo contendo o código a ser injetado, que seria um raw binary. Onde esse arquivo deve conter apenas as instruções de código de máquina a ser injetado, como um shellcode por exemplo.
      Opcionalmente, você pode usar a opção -s para especificar o número da seção que você quer injetar o código. Se a opção não for especificada, por padrão o pei vai injetar onde houver mais espaço disponível.
      $ pei -f my_code.bin inject executavel.exe Writed code of 12 bytes on offset 0x0000000000043924 of section #15 '/81' Após o código injetado, o pei insere um jump absoluto para o entry point original do executável, fazendo com que após o código injetado ser executado o fluxo de execução do programa continue normalmente.
      Outra modificação que o pei faz é desabilitar o dynamic base do executável, para evitar que o jump aponte para o endereço errado.
      Dica: Injetando Código Muito Grande
      Se você precisar injetar um código maior do que o espaço disponível apontado pela operação zeros, você pode dividir o código a ser injetado em várias partes e injetar cada parte por vez, seguindo a ordem da última até a primeira parte. Isso funciona porque o pei irá adicionar um jump no final do código para o endereço que está sendo indicado como entry point. Se você já injetou código antes, esse endereço é o endereço do código anteriormente injetado. 🙂
      Dessa forma você pode fazer um chain de saltos da primeira parte até a última e então saltar para o entry point original. Exemplo:
      $ pei inject -f parte-3.bin executavel.exe Writed code of 87 bytes on offset 0x0000000000043833 of section #15 '/81' $ pei inject -f parte-2.bin executavel.exe Writed code of 80 bytes on offset 0x0000000000044924 of section #11 '/23' $ pei inject -f parte-1.bin executavel.exe Writed code of 32 bytes on offset 0x0000000000401a15 of section #1 '.text' Isso irá criar a seguinte ordem de execução: parte-1.bin -> parte-2.bin -> parte-3.bin -> entry point. Onde as setas “->” representam os saltos.
      diff
      Esta operação exibe diferenças entre dois executáveis. Ela compara cada campo dos cabeçalhos do executável e o conteúdo das seções, e depois exibe estas diferenças no terminal. Você pode usar a opção -c (ou --color) para que a saída da operação seja colorida:

      Em vermelho são os valores no executável original que foram modificados e em verde são os valores novos no executável modificado.
      patch
      Essa operação lê um texto de entrada no mesmo formato que a saída da operação diff e replica as mesmas modificações no executável. Exemplo:
      $ pei patch executavel2.exe diff-output.txt Caso você não especifique um patch file, o pei irá ler de stdin. Assim, é possível que você faça um pipe entre uma operação de diff e patch:
      $ pei diff original.exe mod.exe | pei patch outro.exe A diferença de fazer isto e simplesmente fazer uma cópia do executável modificado, é que a ideia é replicar somente as diferenças entre os executáveis. Quaisquer outros campos não são tocados pela operação patch, o que te permite salvar alterações e replicá-las por meio de um script. 🙂
      Se você quiser, também é possível escrever um patch file manualmente, bastando imitar a saída de diff. Uma dica é que os valores dos campos originais (em vermelho) não são necessários para a operação patch, então você pode inserir somente os valores novos. Seguindo o print da saída do diff que utilizamos agora pouco como exemplo:
      optional.entry_point xxx 0x4ca33 section.0.name xxx .code section.15.characteristics xxx 0x62100040 // @O texto não faz diferença, só importa o @ no início da linha // Daqui para baixo qualquer linha que não inicie com + será ignorada. +0x43830 00 30 9f 61 62 63 0a b8 f0 14 40 00 ff e0 00 00 // O formato é: +0xoffset bytes em hexadecimal Nota: Onde há o “xxx” seriam os campos dos valores originais (em vermelho) que não são lidos pelo pei porém são necessários para imitar a saída da operação diff. Você pode colocar qualquer valor aí que não dará erro já que são ignorados.
      Você pode usar a operação patch em scripts para automatizar várias modificações em um executável ao mesmo tempo, inclusive em bytes de um determinado offset.
    • By Edinho Sousa
      Os compiladores são ferramentas muito úteis e importantes no mundo da programação e desenvolvimento. A função básica dos compiladores é pegar uma linguagem de "alto nível" (linguagem com maior nível de abstração do hardware) e produzir um código semanticamente equivalente em "baixo nível". A velocidade de execução do código compilado é uma vantagem que se destaca, tendo em vista que o compilador faz otimizações no processo de compilação. Verificações de erros sintáticos e semânticos são outras funcionalidades também executadas pelo compilador.
      Por que criar um compilador?
      Além dos motivos mencionados anteriormente, a forma mais simples e rápida de entender como os compiladores funcionam é criando um. Neste tutorial iremos criar um compilador simples, porém abordando os principais conceitos da compilação de forma teórica e prática.
      Para seguir esse tutorial será necessário o conhecimento de algoritmo e no mínimo uma linguagem de programação. Neste artigo estarei utilizando a linguagem C.
      Antes de começarmos a criação do projeto, vamos organizar o nosso projeto:
      Criaremos uma linguagem que trabalha com números inteiros e reais; Utilizaremos condições (if, else, etc); Utilizaremos expressões aritméticas e relacionais; Etapas da compilação
      As etapas que um compilador executa são: Análise léxica, Análise sintática, análise semântica, otimizador de código e gerador de código objeto. Alguns compiladores tem uma estrutura bem mais complexa, dependendo da linguagem a ser compilada:

      Nosso projeto terá as seguintes etapas: análise léxica, análise sintática, análise semântica e gerador de código. O gerador de código vai gerar um bytecode para uma máquina virtual que também vamos implementar. Bytecodes são instruções para uma máquina virtual, como mover um valor para a memória ou para um registrador, por exemplo. Abaixo podemos ver um trecho de código em Python e seus respectivos bytecodes:
      def soma(): print(10 + 10)  
      0 LOAD_GLOBAL 0 (print) 2 LOAD_CONST 1 (20) 4 CALL_FUNCTION 1 6 POP_TOP 8 LOAD_CONST 0 (None) 10 RETURN_VALUE No final desta série estaremos executando o seguinte código:
      INIT VAR max := 10 VAR num INPUT num IF (num < max) INIT PRINT 0 END ELSE INIT PRINT 1 END END Análise Léxica
      A análise léxica consiste em pegar cada caractere de uma linguagem e identificar os padrões da linguagem. Exemplo:
      int a = 10 Aqui podemos identificar os seguintes padrões:
      int é uma palavra reservada do compilador; a é um identificador/variável; = é um sinal de atribuição; 10 é um número inteiro; Ao realizar esse processo estamos identificando os lexemas, que são pedaços de uma string (texto), reconhecidos pelo analisador léxico. Os tokens são um par constituído de um nome e um valor de atributo, sendo este último opcional:
      <tipo, valor> Onde:
      tipo como o nome já diz seria o tipo do token. valor é o valor de um token. Alguns tokens não utilizam este campo. Representação da análise léxica:

      Para uma entrada como VAR num := 100 + 10 obtemos os seguintes tokens:
      <PC_VAR> <ID, num> <OP_ATR> <T_INT, 100> <OP_MAIS> <T_INT, 10> Onde:
      <PC_VAR> representa a palavra chave VAR; <ID, num> representa um identificador (variável ou função) tendo o valor num; <OP_ART> representa o operador de atribuição =; <OP_MAIS> representa o operador aritmético mais (+); <T_INT, 100>, <T_INT, 10> representa um inteiro com o valor 100 e 10 respectivamente; Não se esqueça que os tipos de token são definidos por você!
      Usarei o gcc como compilador C e o vscode como editor. Iremos começar de uma forma simples,  melhorando tudo aos poucos, vamos nessa!
      Essa é a estrutura de pastas do nosso projeto. Temos uma pasta para os headers, uma pasta src para o código fonte e a pasta exe, que terá o executável:

      Escreva o texto seguinte no arquivo teste.txt:
      INIT PRINT 1 + 2 * 3 END
      include/lex.h - Aqui simplesmente criamos um módulo para tratar da análise léxica e definimos a função que retorna um token:
      #ifndef art_lex_h #define art_lex_h void proximo_token(); #endif src/lex.c: Esta é nossa função inicial que lê cada caractere e mostra na console. Se o caractere for EOF, significa que não há mais caracteres no arquivo (fim de arquivo) e então paramos o loop:
      #include <string.h> #include <ctype.h> #include "glob.h" #include "lex.h" // variável que passará por cada caractere do arquivo static int c; void proximo_token() { while (1) { c = fgetc(file); if (c == EOF) break; else printf("%c", c); } } includes/glob.h: Este outro arquivo serve para algumas definições globais (que vamos usar em mais de um arquivo). Definimos os tipos dos tokens, um enum para representar o token e uma struct com os campos tipo e val:
      #ifndef art_glob_h #define art_glob_h #include <stdio.h> #include <stdlib.h> FILE *file; // linha atual static int linha = 1; // tipos de tokens enum { // palavras chave PC_INIT, PC_END, PC_PRINT, PC_INPUT, PC_VAR, PC_IF, PC_ELSE, // numeros T_INT, // operadores OP_MAIS, OP_MENOS, OP_MULT, OP_DIVI, // ( ) := < > <= >= = T_LPARENT, T_RPARENT, T_ATRIB, T_MENOR, T_MAIOR, T_MENOR_I, T_MAIOR_I, T_IGUAL, // identificador ID }; typedef struct { int tipo; int val; } Token; Token tok; #endif src/main.c: Na função main iremos tentar abrir um arquivo. Caso haja algum erro o programa sairá mostrando a mensagem de erro. Caso contrário, leremos todos os caracteres do arquivo teste.txt. Vamos ver se funciona:
      #include <stdlib.h> #include "lex.h" #include "glob.h" int main(int argc, char *argv[]) { // abrir o arquivo file = fopen(argv[1], "r"); if (file == NULL) { printf("Erro ao abrir o arquivo"); exit(EXIT_FAILURE); } proximo_token(); fclose(file); return EXIT_SUCCESS; // ou return 0 } Para facilitar o processo de compilação usaremos o seguinte Makefile:
      all: gcc -c src/lex.c -I includes -o exe/lex.o gcc src/main.c exe/*.o -I includes -o exe/main rm -r exe/*.o *Se você estiver em um ambiente Windows saiba que o comando rm -r exe/*.o  não funcionará.
      Ao executar o Makefile teremos na pasta exe o arquivo compilado. Ao executarmos teremos a seguinte saída:
      INIT PRINT 1 + 2 * 3 END Perfeito! Por agora vamos ignorar espaços em branco, tabulação e quebra de linha.
      Criaremos agora uma função que vai criar um token. Por enquanto ela irá apenas mostrar na saída algo como <’+’, 0> <’INIT’, 0>, mas depois vamos mudar isso.
      lex.c: Aqui estamos somando 1 na variável linha para uso posterior em caso de nosso compilador ache um caractere que não existe em nossa linguagem (como um “$”, por exemplo):
      void makeToken(char *nome, int val) // mostrar o token { printf("<%s, %d>", nome, val); } void voltaPonteiro() // volta um caracter se necessário { if (c != EOF) fseek(file, ftell(file)-1, SEEK_SET); } void proximo_token() { // após o if else if (c == ' ' || c == '\t') continue; else if (c == '\n') { linha++; continue; } } No código acima temos uma função voltaPonteiro, que é responsável por voltar um caractere no arquivo. Em alguns casos vamos ter que ver o caractere a frente e depois voltar o caractere quando estivermos analisando uma palavra chave. Enquanto o caractere for alfanumérico o ponteiro avança.
      Para facilitar o entendimento vamos utilizar a imagem abaixo como exemplo. Aqui reconhecemos a palavra num e paramos no caractere =, ou seja, reconhecemos o token <ID, num>. Quando vamos continuar o processo iniciamos do =, isto é, o próximo caractere é o espaço, seguido do número 1 e assim por diante. Tendo em vista que = é um caractere diferente do que estaríamos esperando iremos esquece-lo e então voltaremos um caractere parando assim no m.

      lex.c: vamos reconhecer operadores aritméticos como mais (+), menos (-), multiplicação (*) e divisão (/):
      void proximo_token() { // codigo anterior else if (c == '+') makeToken("+", 0); else if (c == '-') makeToken("-", 0); else if (c == '*') makeToken("*", 0); else if (c == '/') makeToken("/", 0); // codigo else Ao compilar o código e executar teremos algo como:
      $ ./exe/main.exe teste.txt INITPRINT1<+, 0>2<*, 0>3END lex.c: Agora vamos reconhecer os demais números, palavras, parênteses, etc:
      else if (c == '+') { makeToken("+", 0); } else if (c == '-') { makeToken("-", 0); } else if (c == '*'){ makeToken("*", 0); } else if (c == '/') { makeToken("/", 0); } else if (c == '(') { makeToken("(", 0); } else if (c == ')') { makeToken(")", 0); } else if (c == ':') { c = fgetc(file); // pega o próximo caractere if (c == '=') // se for '=' sabemos que é o token ':=' makeToken(":=", 0); } else if (c == '<') { c = fgetc(file); // pega o próximo caractere if (c == '=') // se for '=' sabemos que é o token '<=' makeToken("<=", 0); else makeToken("<", 0); } else if (c == '>') { c = fgetc(file); if (c == '=') makeToken(">=", 0); else makeToken(">", 0); } else if (c == '=') { makeToken("=", 0); } else if (isdigit(c)) { numero(); } else if (isalpha(c)) { palavra(); } else { printf("O caracter '%c' na linha %d nao reconhecido.\n", c, linha); exit(EXIT_FAILURE); } lex.c: Temos duas novas funções, são elas palavra e numero:
      void palavra() { char palavra[100] = ""; int pos = 0; while (isalnum(c)) { palavra[pos++] = c; c = fgetc(file); } voltaPonteiro(); if (strcmp(palavra, "INIT") == 0) makeToken("INIT", 0); else if (strcmp(palavra, "PRINT") == 0) makeToken("PRINT", 0); else if (strcmp(palavra, "INPUT") == 0) makeToken("INPUT", 0); else if (strcmp(palavra, "VAR") == 0) makeToken("VAR", 0); else if (strcmp(palavra, "IF") == 0) makeToken("IF", 0); else if (strcmp(palavra, "ELSE") == 0) makeToken("ELSE", 0); else if (strcmp(palavra, "END") == 0) makeToken("END", 0); else makeToken("ID", 0); } Não é a função mais otimizada que você já viu, mas funciona:
      void numero() { int k = 0; while (isdigit(c)) { k = k * 10 + c - '0'; c = fgetc(file); } voltaPonteiro(); makeToken("T_INT", k); } Testamos o código agora:
      $ ./exe/main teste.txt <INIT, 0><PRINT, 0><T_INT, 1><+, 0><T_INT, 2><*, 0><T_INT, 3><END, 0> Olha só, reconhecemos a maior parte dos tokens de nossa linguagem! Agora que tal mais um teste utilizando outro teste.txt?
      INIT VAR max := 10 VAR num INPUT num IF (num < max) INIT PRINT 0 END ELSE INIT PRINT 1 END END  
      $ ./exe/main teste.txt <INIT, 0><VAR, 0><END, 0><:=, 0><=, 0><T_INT, 10><VAR, 0><END, 0><INPUT, 0><END, 0><IF, 0> <(, 0><END, 0><<, 0><END, 0><), 0><INIT, 0><PRINT, 0><T_INT, 0><END, 0><ELSE, 0><INIT, 0> <PRINT, 0><T_INT, 1><END, 0><END, 0> Na próxima parte vamos fazer algumas alterações no analisador léxico e depois daremos início ao analisador sintático. Até lá. 🙂
    • By Leandro Fróes
      Se você é da área de Segurança da Informação ou simplesmente tem interesse pelo assunto já deve ter notado que todo dia temos notícias de novos malwares surgindo, sejam eles malwares completamente novos ou variantes de um malware já conhecido. Com isto em mente, faz algum tempo que as empresas de segurança, inteligência e até mesmo pesquisadores independentes passaram a buscar métodos de automatizar não só a análise destes malwares, mas também a administração e armazenamento do arquivo em si, suas características e relacionamentos com outros arquivos demais entidades (domínios, campanhas, endereços IP, etc). Obviamente a análise automatizada não substitui a análise humana, mas já é uma ajuda e tanto considerando o número de malwares surgindo diariamente.
      Para cada uma destas necessidades descritas anteriormente existe uma ou mais ferramentas/plataformas que podem ser utilizadas para cumprir estes objetivos. Dentre elas estão plataformas de sandboxing  como Hybrid-Analysis e AnyRun, ferramentas de análise estática de arquivos como o DIE (Detect It Easy), pev, yara, capa, e também repositórios de malware como o VirusShare e o Malware Bazaar.
      Não podemos negar que todas estas ferramentas/plataformas ajudam e muito no nosso dia a dia, mas ainda assim não conseguiríamos organizar nossas informações e centralizá-las em um único lugar de forma automática, tendo em vista que as as soluções descritas acima são isoladas e não conectam umas com as outras de forma nativa. A plataforma que chegou mais próximo de atingir as quatro exigências (isto é: análise automatizada, administração, armazenamento, relacionamento com demais entidades) foi uma plataforma chamada Virus Total, também conhecido como VT, atualmente administrado pelo Google.
      Virus Total
      O Virus Total trouxe para a comunidade uma forma simples e rápida de análise de IoCs (Indicator of Compromise) e também uma API bem simples de se utilizar para fins de automação. Dentre as diversas funcionalidades da plataforma estão inclusas análise estática, checagem de assinatura utilizando uma lista gigantesca de Anti-Virus, descrição das características gerais do IoC e comentários da comunidade. Além disso, ele também possui uma versão paga (bem cara por sinal) onde você pode fazer hunting de malwares utilizando regras de Yara, download de arquivos, buscas baseadas em histórico, visualização gráfica e uma API bem mais robusta e permissiva.
      É importante deixar claro que o termo IoC não se refere apenas à arquivos e seus hash, mas também à URL, domínios e IP. Ou seja, o VT realmente acaba se tornando uma opção super viável para começar qualquer tipo de investigação.
      O cenário atual de Segurança da Informação
      Com o passar do tempo não só a comunidade, mas também o mercado de Segurança da Informação no geral passou a notar que a única forma de se posicionar contra os ataques atuais é através de contribuição. Pelo mesmo motivo que gerou a necessidade de se criar formas automatizadas de análise, a contribuição se mostra cada dia mais que necessária pois ela não impõe limites, muito pelo contrário, ela dá liberdade o suficiente para você contribuir da forma que quiser.
      Um ótimo exemplo que mostra o exercício da contribuição e o quão valioso isto pode ser é o próprio Linux, que desde sua primeira versão foi liberado para receber contribuições e hoje é um dos maiores projetos existentes na área de tecnologia, com milhares de contribuidores ao redor do mundo.
      Com isto em mente, podemos notar uma desvantagem no VT: o espaço para contribuição é limitado.
      Desafios
      Como já comentado anteriormente, as principais funcionalidades são suportadas apenas na versão paga e infelizmente não são todos que podem pagar pelo valor do serviço.
      Um dos principais motivos dessa limitação é fato do código não ser aberto, isto é, estamos presos às funcionalidades que o time do VT disponibiliza. Se o código fosse disponível para a comunidade, resolveríamos tanto o problema monetário quanto a limitação de funcionalidades disponíveis.
      Uma outra porta que seria aberta no cenário descrito acima é a seguinte: Imagine que você, sua empresa, seu time ou um grupo de amigos estão com um projeto em mãos que envolve análise, classificação, categorização ou qualquer tipo de manipulação de malware. Com o código em mãos você teria liberdade de fazer a instalação da plataforma localmente ou em um servidor do qual você controla, limitando o acesso à quem você quiser e como quiser.
      A comunidade
      Tendo estes desafios em mente, a comunidade começou a criar alternativas para resolver alguns problemas encontrados no cenário atual. A ideia do artigo não é de forma alguma dizer que uma plataforma é melhor que outra ou que o Virus Total está errado em trabalhar no modelo que trabalha, muito pelo contrário, o objetivo aqui é mostrar as várias formas que temos de se chegar no mesmo objetivo. Uns mais flexíveis, outros com mais conteúdo disponível, mas todos conseguem te ajudar a chegar no mesmo lugar:
      Saferwall: Este é o projeto mais maduro que temos atualmente quando o assunto é análise automatizada e contribuição da comunidade. Robusto e flexível para ser instalado em  diversos ambientes, o Saferwall consegue entregar informações estáticas de arquivos, detecções baseadas em assinaturas de alguns antivírus, identificações de packers e download dos arquivos submetidos anteriormente. Além disso, o Saferwall possui uma plataforma aberta e que aceita colaboração, além de disponibilizar o código para você instalar onde e como bem entender. Dentre as formas de instalação estão inclusas o minikube (indicado para ambientes de testes), em nuvem utilizando AWS e On-Premise.


      Freki: O projeto Freki foi criado por uma única pessoa, mas não deixa a desejar quando o assunto é funcionalidade e fácil instalação. Com possibilidade de ser instalado utilizando Docker, este projeto possui não só análise estática dos arquivos PE submetidos, mas também disponibiliza sua própria API e puxa informações do VT para garantir que não falte nada.


      Aleph: focando bastante na parte de inteligência, o projeto Aleph entrega para você não só informações estáticas dos arquivos submetidos, mas também análise dinâmica utilizando sandbox, visualização gráfica dos resultados e uma saída em JSON formatada para ser utilizada em backends como Elasticsearch, por exemplo. Além disso, o Aleph também consegue mapear as técnicas utilizadas pelo malware utilizando o MITRE ATT&CK Framework. Eu realmente aconselho você dar uma olhada na palestra da MBConf v3 sobre o Aleph para saber mais sobre o projeto.
       


      A tabela à seguir foi criada para facilitar a visualização das funcionalidades descritas acima. É importante deixar claro que a versão do VT utilizada para a criação da tabela é a gratuita:
       
       
      VirusTotal
      Saferwall
      Freki
      Aleph
      Análise Estática
      ✔️
      ✔️
      ✔️
      ✔️
      Análise Dinâmica
       
      X
       
      ✔️
       
      X
       
      ✔️
       
      Suporte à múltiplos SO
      ✔️
       
      ✔️
       
      X
       
      ✔️
       
      Análise de IoC de rede
      ✔️
       
      X
       
      X
       
      X
       
      Código Aberto
      X
       
      ✔️
       
      ✔️
       
      ✔️
       
      Download de arquivos
       
      X
       
      ✔️
       
      ✔️
       
      ✔️
       
      Instalação local
      X
       
      ✔️
       
      ✔️
       
      ✔️
       
      Controle total do backend
      X
       
      ✔️
       
      ✔️
       
      ✔️
       
      API
       
      ✔️
       
      ✔️
       
      ✔️
       
      X
      Como podemos ver, todos estes projetos são de código aberto, o que permite a seus usuários livre contribuição. Caso você tenha interesse em contribuir para alguns desses projetos, aqui vai uma dica: nenhum deles possui ainda análise de URL/IP/domínio de forma isolada, isto é, independente do arquivo. Tenho certeza que uma contribuição deste tipo seria bem vinda. ?
      Conclusão
      Ajudando estes projetos nós não só melhoramos a ferramenta/plataforma em si, mas ajudamos todos que a utilizam e também construímos um sistema livre e aberto de análise, inteligência e investigação.
      Se você é da área ou simplesmente curte contribuir, não deixe de dar uma olhada em cada um destes projetos e, se possível, contribuir com eles. Lembrando que quando falamos de contribuição, não há limites. Pode ser um commit, uma ideia, ajuda monetária ou um simples OBRIGADO aos desenvolvedores e contribuidores por disponibilizarem projetos tão úteis para a comunidade.
×
×
  • Create New...