Jump to content
  • Como criar um pacote deb “na unha”

       (0 reviews)

    Aproveitando que farei uma apresentação sobre pacotes deb na comemoração do 18º aniversário do Debian GNU/Linux, decidi escrever este artigo, para mostrar o que é um pacote deb e como ele pode ser gerado de forma quase artesanal.

    O objetivo deste artigo é mostrar o que é um pacote deb e como ele se comporta. Mostrarei como olhar o conteúdo de um pacote deb a fim de copiar sua estrutura para gerar um novo, coisa que fiz quando comecei a me interessar pelo assunto.

    Este artigo faz parte de um objetivo maior, que é atrair colaboradores para a comunidade Debian. Um pacote “caseiro” não será aceito nos repositórios oficiais, mas iniciará o leitor no mundo do empacotamento e, quem sabe, despertará o Debian Maintainer que há em você.:)

    1. Introdução

     

    Imagine um mundo sem pacotes deb. Todos os softwares livres seriam distribuídos em código-fonte. Você seria obrigado a baixar, compilar e instalar. Fácil? Nem sempre. Os programas possuem dependências de bibliotecas que o desenvolvedor usou. Na máquina dele essas bibliotecas estão instaladas, na sua provavelmente não. Então seria preciso instalá-las antes de compilar o programa que você quer.

    Claro que os jargões utilizados aqui só fazem sentido para usuários avançados. O Linux não teria conquistado a popularidade que tem hoje se os usuários leigos tivessem que compilar o Firefox para navegar na internet. Os pacotes supriram esta necessidade e hoje um software inteiro com suas dezenas de dependências pode ser instalado graficamente ou com uma curta e simples linha de comando. Cabe aqui um viva ao software livre. o/

    Este artigo não é para leigos, mas para quem gostaria de entender como o pacote deb funciona.

     

    2. Pacote “caseiro” x pacote oficial

     

    Para fazer um pacote e submetê-lo ao repositório oficial, você precisa ler, compreender e seguir rígidas regras de qualidade, que podem ser encontradas nos documentos oficiais [1]. No entanto, creio que o modo mais fácil é começar por um pacote “caseiro”, que não segue a política, para entender o funcionamento e então partir para o estudo dos documentos. Foi assim que deu certo comigo e hoje mantenho o pacote do pev [2] no repositório oficial (testing).

    A comunidade Debian oferece uma série de ferramentas para criação de pacotes, sempre com foco nos pacotes oficiais. Neste artigo evitarei usar tais ferramentas, mas vou comentar, para que o leitor já se ambiente.

     

    3. Do que é feito o pacote deb

     

    Se você der uma olhada em no diretório /var/cache/apt/archives provavelmente vai encontrar vários pacotes deb. Eles estão lá porque foram baixados por você seja via Synaptic, apt-get, aptitude ou outro gerenciador de pacotes que use o dpkg.

    Antes de usar o dpkg (a ferramenta oficial) para analisar um pacote deb, vamos ver do que um pacote deb é feito. Escolhi como exemplo o pacote do wget.

    $ ls -lh wget*
    -rw-r–r– 1 fernando fernando 717K Aug 17 00:26 wget_1.12-5_amd64.deb 

    Vamos copiar o pacote para o /tmp, para manter o cache intacto:

    $ cp wget_1.12-5_amd64.deb /tmp 

    No diretório /tmp, podemos usar o comando file para ver o tipo de arquivo do pacote deb:

    $ cd /tmp
    $ file wget_1.12-5_amd64.deb
    wget_1.12-5_amd64.deb: Debian binary package (format 2.0)

    A libmagic (usada pelo file) reconhece o pacote corretamente. Mas será que os desenvolvedores criaram realmente um tipo de arquivo completamente novo para armazenar o conteúdo de um programa? Sabemos que dentro de um pacote deb há os arquivos executáveis do programa, documentação, ícones etc. Não seria viávei utilizar um agrupador de arquivos com compressão ou coisa do tipo?

    Eric Raymond, um dos hackers mais respeitados do mundo detém a seguinte crença, escrita em seu documento “How to become a hacker” [3]: “No problem should ever have to be solved twice” (Nenhum problema deve ser resolvido duas vezes). Ou seja, não é preciso “reinventar a roda”, como dizemos popularmente. Com base nesta inteligente frase, os desenvolvedores do dpkg e do formato deb usaram sim o que já exitia para atingir seus objetivos, o que foi brilhante.

    Na página de manual do formato deb(5), podemos ler:

    “The file is an ar archive with a magic value of !<arch>.”

    Para conferir, comande:
     

    $ man deb

    Então estamos falando de um arquivo ar [4]. Conforme você pode ver na referência, ar é um utilitário do conjunto binutils, do projeto GNU, para criar, modificar e extrair arquivos. É um agrupador, assim como o tar.

    Vamos conferir o magic value como disse o man?

    $ hd -n 64 wget*
    00000000  21 3c 61 72 63 68 3e 0a  64 65 62 69 61 6e 2d 62  |!<arch>.debian-b|
    00000010  69 6e 61 72 79 20 20 20  31 33 31 31 34 35 31 34  |inary   13114514|
    00000020  35 31 20 20 30 20 20 20  20 20 30 20 20 20 20 20  |51  0     0     |
    00000030  31 30 30 36 34 34 20 20  34 20 20 20 20 20 20 20  |100644  4       |

    Certinho. O tal “!<arch>” esta no início do arquivo, o que sugere ser o seu magic number. Vamos listar o conteúdo deste arquivo com a ferramenta ar então:

    $ ar tv wget*
    rw-r–r– 0/0      4 Jul 23 17:04 2011 debian-binary
    rw-r–r– 0/0   2432 Jul 23 17:04 2011 control.tar.gz
    rw-r–r– 0/0 731281 Jul 23 17:04 2011 data.tar.gz

    Então temos um arquivo ar com três arquivos. E se criarmos um ar com um arquivo qualquer, o que será que o file retorna?

    $ echo “meu texto” > texto.txt
    
    $ ar r teste.deb texto.txt
    ar: creating teste.deb

    O comando acima criou um arquivo ar chamado teste.deb (lembre-se que o Linux despreza extensões) e adicionou o arquivo texto.txt nele.

    $ file teste.deb
    teste.deb: current ar archive

    O file retorna que é um arquivo ar, corretamente.

    Mas por que o file reconhece como pacote do wget corretamente como binário do debian? A resposta está no código-fonte do file [5], que identifica um pacote binário se depois do magic number do ar, o arquivo contiver a string “debian-binary”. De fato, o formato ar define o magic e o nome do primeiro arquivo agrupado em seguida. Então bastaria que criássemos um arquivo ar com um arquivo debian-binary qualquer para o “enganar” o file.

    $ echo 2011 > debian-binary
    
    $ ar r fake.deb debian-binary
    ar: creating fake.deb $ file fake.deb
    fake.deb: Debian binary package (format 2011) 

    Agora sim. O file não faz nenhuma verificação adicional (e nem deveria). Mas a intenção aqui não é hackear o file (até porque estamos falando de um formato livre, com extensa documentação), e sim criar um deb “na mão”. Perceba que o formato apareceu como “2011”. Claro que é um pacote inválido e se você tentar instalar o dpkg vai gerar um erro. Nem perca seu tempo.;)

    4. Extraindo um pacote deb

     

    Voltando ao que interessa, vamos extrair o conteúdo do pacote deb do wget para conferir o que há de interessante:

    $ mkdir wget
    $ cd wget
    $ ar xv ../wget*
    x – debian-binary
    x – control.tar.gz
    x – data.tar.gz $ cat debian-binary
    2.0

    Exatamente o que o file informou. Versão 2.0 do formato deb. Beleza.

    $ tar tvzf control.tar.gz
    drwxr-xr-x root/root         0 2011-07-23 17:04 ./
    -rw-r–r– root/root      3832 2011-07-23 17:04 ./md5sums
    -rw-r–r– root/root        12 2011-07-23 17:04 ./conffiles
    -rw-r–r– root/root      1327 2011-07-23 17:04 ./control

    Acima vemos alguns arquivos de controle do pacote. O interesse maior é no arquivo “control”, necessário para um pacote funcionar. O md5sums também é legal de se ter.

    $ tar tvzf data.tar.gz
    <saída suprimida=””>
    -rw-r–r– root/root       651 2009-09-21 23:52
    ./usr/share/doc/wget/ChangeLog.README
    drwxr-xr-x root/root         0 2011-07-23 17:04 ./usr/bin/
    -rwxr-xr-x root/root    353824 2011-07-23 17:04 ./usr/bin/wget

    Já o data.tar.gz contém os dados do pacote em si, incluindo binários executáveis e documentação, todos numa estrutura bem definida. Aliás, é esta estrutura que o pacote cria ao ser instalado.

    Estou mais interessado no control.tar.gz. Vamos extraí-lo:

    $ tar xvzf control.tar.gz
    ./
    ./md5sums
    ./conffiles
    ./control $ head -5 md5sums
    1b2acae8540b64a3170dc4ce0200809e  usr/bin/wget
    d62b0aafbbacf1d54031ded4d1a5f232  usr/share/doc/wget/AUTHORS
    2f58d6d92cabcf358718a564d3e132d4  usr/share/doc/wget/ChangeLog.README
    2b95a82f1c7499025d67ff86af2d7ecd  usr/share/doc/wget/MAILING-LIST
    9e83cee67a496f5eb62aecf283e14367  usr/share/doc/wget/NEWS.gz

    Certo, vemos no arquivo md5sums, o hash MD5 de cada arquivo incluso no data.tar.gz (no entanto só imprimi 5 linhas com o head). Não seria difícil gerar isso para o nosso pacote “artesenal”.

    $ cat control
    Package: wget
    Version: 1.12-5
    Architecture: amd64
    Maintainer: Noël Köthe
    Installed-Size: 2344
    Depends: libc6 (>= 2.3), libidn11 (>= 1.13), libssl1.0.0 (>= 1.0.0), dpkg (>=
    1.15.4) | install-info
    Conflicts: wget-ssl
    Section: web
    Priority: important
    Multi-Arch: foreign
    Homepage: http://www.gnu.org/software/wget/
    Description: retrieves files from the web
    Wget is a network utility to retrieve files from the web
    using HTTP(S) and FTP, the two most widely used internet
    protocols. It works non-interactively, so it will work in
    the background, after having logged off. The program supports
    recursive retrieval of web-authoring pages as well as ftp
    sites — you can use wget to make mirrors of archives and
    home pages or to travel the web like a WWW robot.
    .
    Wget works particularly well with slow or unstable connections
    by continuing to retrieve a document until the document is fully
    downloaded. Re-getting files from where it left off works on
    servers (both HTTP and FTP) that support it. Both HTTP and FTP
    retrievals can be time stamped, so wget can see if the remote
    file has changed since the last retrieval and automatically
    retrieve the new version if it has.
    .
    Wget supports proxy servers; this can lighten the network load,
    speed up retrieval, and provide access behind firewalls.

    Este é o arquivo que descreve o pacote. A referência deste arquivo é tratada na Debian Policy [6], mas os campos mais comumente usados para pacotes simples são:

    • Package  – Nome do pacote
    • Version  – Versão do programa-versão do pacote
    • Architecture– Arquitetura para qual o programa foi compilado. Pode ser i386, amd64, dentre outras. Pode ser “all” para scripts, por exemplo.
    • Maintainer  – Aqui vai o seu nome e e-mail. O criador do pacote (não do programa a ser empacotado).
    • Installed-Size – O espaço estimado, em bytes, requerido para instalar o pacote.
    • Depends  – Os pacotes dos quais seu pacote depende. Se for um programa em Python, vai depender do interpretador python, por exemplo.
    • Homepage  – O site do programa empacotado
    • Description-A descrição do pacote. Uma linha para a descrição curta e demais para descrição longa, onde todas devem começar com um espaço e as linhas em branco devem possuir um ponto (.), que não vai aparecer no final das contas.

    Agora que já explicamos a estrutura de um pacote deb básico, vamos ver como criar um.

     

    5. Criando um pacote “artesanal”

     

    Precisamos ter o que empcotar. Então vamos criar um software de teste.

    $ mkdir /tmp/nada-1.0
    $ cd /tmp/nada-1.0
    
    $ echo -e “#include <stdio.h>nnint main()n{ntputs(“nada…”);ntreturn 0;n}” > nada.c

    Os comandos acima devem criar um arquivo nada.c, no diretório /tmp/nada-1.0, com o seguinte conteúdo:

    #include <stdio.h>
    
    int main()
    {
    	puts(“nada…”);
    	return 0;
    }

    Agora precisamos compilar o programa:

    $ gcc -o nada nada.c

    O binário “nada” precisa de uma estrutura. Então vamos colocá-lo num usr/bin:

    $ mkdir -p usr/bin
    $ mv nada usr/bin/

    NOTA: Cuidado para não tentar mover o “nada” para o /usr/bin do sistema.

    Agora temos a seguinte estrutura:

    $ find .
    . ./usr
    ./usr/bin
    ./usr/bin/nada
    ./nada.c

    Precisamos do arquivo de controle. Que tal este?

    Package: nada
    Version: 1.0
    Architecture: amd64
    Maintainer: Você <seu@email.com.br>
    Homepage: http://www.tacomnada.com
    Installed-Size: 6560
    Depends: libc6 (>= 2.2.5)
    Description: program that does nothing
    Na realidade o nada conseguie imprimir a string “nada…” na tela. Claro que
    pode servir para algo, mas nao saberia dizer para que. Talvez para aprender
    a empacotar no Debian, ou para nao fazer nada.
    .
    Depois de falar tanto, nao vou dizer mais nada.

    Basta salvar como control (sem extensão mesmo).

    Para o campo Installed-Size, eu contei os bytes do binário “nada”:

    $ wc -c nada
    6560 nada

    Já no campo Depends, é interessante avaliar a saída do ldd:

    $ ldd nada
    linux-vdso.so.1 => (0x00007fffea5ff000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fcebbf5d000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fcebc2fe000)

    A primeira entrada é a uma biblioteca virtual para interface com o kernel. Ela sempre existirá e também pode aparecer como linux-gate.so.1.

    Em seguida temos duas bibliotecas reais. Supondo que não saibamos em qual pacote elas se encontram, podemos usar o apt-file:

    $ apt-file search libc.so.6
    libc6: /lib/x86_64-linux-gnu/libc.so.6
    libc6-i386: /lib32/libc.so.6

    O pacote de nosso interece é o libc6. Agora, a versão da biblioteca requerida vai depender das funções que o programador utilizou. Isto consultado ser pego no site do projeto ou diretamente com o desenvolvedor. Usei 2.2.5.

    Vamos buscar a última lib que o ldd detectou:

    $ apt-file search ld-linux-x86-64.so.2
    libc6: /lib/ld-linux-x86-64.so.2
    libc6: /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2

    Também está no pacote libc6. Beleza, então só temos esta dependência.

    Agora precisamos gerar os arquivos control.tar.gz (com o control dentro), data.tar.gz (com toda a árvode usr dentro) e debian-binary (com a string 2.0 dentro):

    $ tar cvzf data.tar.gz usr/
    usr/
    usr/bin/
    usr/bin/nada $ tar cvzf control.tar.gz control
    control
    
    $ echo 2.0 > debian-binary
    
    $ ar r nada-1.0_amd64.deb debian-binary control.tar.gz data.tar.gz
    ar: creating nada-1.0_amd64.deb
    fernando@localhost:/tmp/wget/nada-1.0$ file nada-1.0_amd64.deb
    nada-1.0_amd64.deb: Debian binary package (format 2.0)

    Perceba que o primeiro arquivo a ser adicionado no ar é o debian-binary.

    E agora vamos testar.

    $ sudo dpkg -i nada-1.0_amd64.deb
    Selecting previously deselected package nada.
    (Reading database … 146331 files and directories currently installed.)
    Unpacking nada (from nada-1.0_amd64.deb) …
    Setting up nada (1.0) … $ nada
    nada… $ sudo dpkg -P nada
    (Reading database … 146332 files and directories currently installed.)
    Removing nada …

     

    6. Conclusão

     

    O pacote funciona. Inclusive eu fiz um script (makedeb_v2.sh) para empacotar [quase que] desta forma o Evan’s Debugger [7]. Vale a pena dar uma olhada, pois tem outros arquivos e comandos que não mencionei aqui. No entanto, ressalto que a preferência deve ser para pacotes com alta qualidade.

    Provavelmente escreverei um outro artigo sobre a forma correta de se criar pacotes, com base na política do Debian e com as ferramentas providas pela comunidade. Não é fácil fazer um pacote de qualidade, mas o primeiro passo, na minha opinião, é entender com o que estamos lidando e espero que este artigo tenha atingido este objetivo.

    [1] http://www.debian.org/doc/#manuals
    [2] http://packages.debian.org/wheezy/pev
    [3] http://catb.org/~esr/faqs/hacker-howto.html#believe2
    [4] http://www.gnu.org/s/binutils/
    [5] ftp://ftp.astron.com/pub/file/
    [6] http://www.debian.org/doc/debian-policy/ch-controlfields.html
    [7] http://codef00.com/projects#debugger


    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.

    Guest

  • Similar Content

    • 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.
    • By julio neves
      Livro do Julio Cezar Neves com dicas importantes (e raras de serem encontradas) sobre shell, incluindo sincronismo de processos, novidades do Bash 4.0, uso do ImageMagik e YAD (o melhor da categoria dos dialog da vida). Vale ler cada palavra. ?
       
      E se quiser ver se tem turma aberta do curso dele é só clicar aqui. ?
    • By anderson_leite
      Já faz um bom tempo (quase 1 ano!) desde o último artigo da série de desenvolvimento de debuggers. Este é o último artigo da série e iremos finalmente criar nosso primeiro prototipo de debugger.
      A ideia aqui, é compilar tudo que foi ensinado nos artigos anteriores sobre Sinais, Forks e ptrace . Com isso, criaremos um simples tracer em C que irá receber um endereço como argumento e colocar um breakpoint no mesmo.
      Diagrama
      Antes vamos definir um pouco o escopo do nosso software:
       

      O nosso tracer irá criar um fork e nesse fork será feita a chamada para a execv, que por sua vez irá trocar a imagem do atual processo (seu conteúdo) pela de outro processo, fazendo com que de fato vire outro processo. Já o nosso debugger, dentro de um loop, irá se comunicar via sinais com o processo filho.
      Breakpoints
      Existem dois tipos de breakpoints: software breakpoints e hardware breakpoints. Ambos servem para interromper a execução do nosso software em determinada instrução. Para que isso seja possível é necessário que a execução do processo seja interrompida na nossa CPU.
      Interrupções
      Quando ocorre algum evento no computador que precisa de um tratamento imediato, a CPU invoca uma interrupção. Cada evento desse contém uma ação especifica que nosso kernel irá lidar de alguma maneira e a estrutura responsável por salvar os valores e significados das mesmas é a Interrupt Descriptor Table.
       

      A imagem acima representa visualmente uma implementação desse vetor, onde cada posição (offset) contém uma struct associada e nela os valores necessários para lidar com isso. Você pode ter uma explicação mais detalhada aqui.
      Mas por que eu estou falando de tudo isso? Porque breakpoints nada mais são do que uma interrupção em um dado endereço que faz com que o processador pare a execução do seu programa.
      O valor que interrompe a CPU para um breakpoint é o 0x03. Vamos testar isto nesse pequeno bloco de código:
      main() { int x = 4; // Iniciando qualquer coisa __asm__( "int $0x03" ); } A macro __asm__ permite que seja colocado o código direto em assembly, nesse caso, foi colocado o mnémonico INT, que cuida das interrupções com o valor 3 (offset comentado acima na IDT). Se você compilar e executar esse programa:
      ~ ./code zsh: trace trap (core dumped) ./code Nesse momento o trabalho de fazer o handle dessa interrupção é do nosso software. O que fizemos aqui foi implementar um software breakpoint. Agora vamos executar esse programa no gdb e não por breakpoint algum (dentro do gdb) e só executar:
      (gdb) r Starting program: /home/zlad/code Program received signal SIGTRAP, Trace/breakpoint trap. 0x000055555555515f in main () (gdb) disas Dump of assembler code for function main: 0x0000555555555139 <+0>: push %rbp 0x000055555555513a <+1>: mov %rsp,%rbp 0x000055555555513d <+4>: sub $0x10,%rsp 0x0000555555555141 <+8>: movl $0x2,-0x4(%rbp) 0x0000555555555148 <+15>: mov -0x4(%rbp),%eax 0x000055555555514b <+18>: mov %eax,%esi 0x000055555555514d <+20>: lea 0xeb0(%rip),%rdi 0x0000555555555154 <+27>: mov $0x0,%eax 0x0000555555555159 <+32>: callq 0x555555555030 <printf@plt> 0x000055555555515e <+37>: int3 => 0x000055555555515f <+38>: mov $0x0,%eax 0x0000555555555164 <+43>: leaveq 0x0000555555555165 <+44>: retq End of assembler dump. (gdb) Veja que a nossa interrupção foi capturada pelo GDB, pois ele detectou um breakpoint trap e é exatamente isso que iremos fazer. Nosso tracer será capaz de detectar quando irá ocorrer um SIGTRAP, ou seja, um sinal que deve ser tratado por nosso sistema operacional.
      Finalmente implementando
      Vamos finalmente começar o nosso pequeno tracer, que será capaz colocar breakpoints, executar instrução por instrução e imprimir os registradores na tela!
      Para inserir a interrupção de breakpoint (int 3) não precisamos de muito, pois já existe um mnemónico para isso que é o int3 e que tem como valor 0xCC. Para inserir breakpoints precisamos de um endereço (que vá ser executado) e uma maneira de escrever nesse local na memória virtual do nosso processo.
      Já vimos anteriormente o ptracer e nele sabemos que temos alguns enums que podem ser passados como seu primeiro argumento. São eles o PEEK_DATA e o POKE_DATA, que servem para buscar algo na memória de um processo e escrever algo na memória de um processo, respectivamente. Segue a função que vamos usar para adicionar breakpoints no nosso tracer:
      uint64_t add_breakpoint(pid_t pid, uint64_t address) { uint64_t break_opcode = 0xCC; uint64_t code_at = ptrace(PTRACE_PEEKDATA, pid, address, NULL); uint64_t breakpoint_code = (code_at & ~0xFF) | break_opcode; ptrace(PTRACE_POKEDATA, pid, address, breakpoint_code); return code_at; } Respire fundo e vamos em partes, a ideia aqui é a seguinte:
      Dado o pid do nosso processo filho e um endereço de memória, vamos buscar o código que estava naquele local (code_at), salvar esse código (não só queremos adicionar um novo opcode, mas podemos futuramente querer executá-lo) e então vamos adicionar nossa instrução nos bytes menos significativos, ou seja, vamos executar ela primeiro.
      Usamos aqui uma variável de 64 bits por conta da arquitetura do meu sistema. Se você quiser tornar isto portável, é possível criar uma variável genérica baseada na arquitetura:
      #ifdef __i386__ #define myvar uint32_t #else #define myvar uint64_t #endif Isso é opcional, mas caso você queira criar algo mais genérico, esse é o caminho.
      A operação bitwise que fizemos aqui também pode ser “nebulosa” para alguns, mas segue o equivalente de maneira mais “verbosa” e em python:
      >>> hex(0xdeadbeef & ~0xFF) # Mascarando byte menos significativo '0xdeadbe00' >>> hex(0xdeadbeef & ~0xFF | 0xCC) # Mascarando byte e adicionado opcode int3(0xCC) '0xdeadbecc' O que é feito aqui é uma jogada lógica. Vamos quebrar isso em passos:
      Fazemos um AND com algo negado (0xFFFFFF00); Fazemos um OR com o resultado que irá "preencher" o espaço vazio, visto que um valor OR 0 será sempre o valor com conteúdo; No final mascaramos o último byte e colocamos nosso opcode; O nosso loop precisa executar enquanto nosso processo filho estiver sendo debugado. Em termos de estrutura de códigos vamos usar um laço que irá receber uma flag para sua execução:
      while (!WIFEXITED(status)) { // Our code } Caso você esteja perdido nessa função WIFEXITED, vale a pena dar uma olhada no artigo desta série sobre Forks. Agora é puramente uma questão de jogar com sinais e estruturar nosso código da maneira mais coesa possível, resumindo, pura programação ?
      Após nosso breakpoint ser definido em memória precisamos fazer o handling disso. Para isso usamos a função WSTOPSIG, que irá receber o status do nosso processo (que é atribuído na função wait) e irá nos dizer qual tipo de interrupção ocorreu:
      while (!WIFEXITED(status)) { wait(&status); signal = WSTPOPSIG(status); switch(signal) { case SIGTRAP: puts("We just hit a breakpoint!\n"); display_process_info(pid); break; } } No momento que uma sigtrap for enviada para a gente podemos considerar que caímos no nosso breakpoint. Nesse momento, nosso processo filho está block (pois sofreu uma interrupção), esperando algum tipo de ação para continuar.
      A função display_process_info(pid) irá mostrar o atual estado dos nossos registrados, usando o enum PTRACE_GETREGS que recebe a struct regs (também já visto no artigo passado):
      void display_process_info(pid_t pid) { struct user_regs_struct regs; ptrace(PTRACE_GETREGS, pid, NULL, &regs); printf("Child %d Registers:\n", pid); printf("R15: 0x%x\n", regs.r15); printf("R14: 0x%x\n", regs.r14); printf("R12: 0x%x\n", regs.r12); printf("R11: 0x%x\n", regs.r11); printf("R10: 0x%x\n", regs.r10); printf("RBP: 0x%x\n", regs.rbp); printf("RAX: 0x%x\n", regs.rax); printf("RCX: 0x%x\n", regs.rcx); printf("RDX: 0x%x\n", regs.rdx); printf("RSI: 0x%x\n", regs.rsi); printf("RDI: 0x%x\n", regs.rdi); printf("RIP: 0x%x\n", regs.rip); printf("CS: 0x%x\n", regs.cs); printf("EFGLAS: 0x%x\n", regs.eflags); } O código do nosso loop final fica da seguinte forma:
      while (!WIFEXITED(status)) { signal = WSTOPSIG(status); switch(signal) { case SIGTRAP: puts("We just hit a breakpoint!\n"); break; } printf("> "); fgets(input, 100, stdin); if (!strcmp(input, "infor\n")) { display_process_info(pid); } else if (!strcmp(input, "continue\n")) { ptrace(PTRACE_CONT, pid, NULL, NULL); wait(&status); } } printf("Child %d finished...\n", pid); return 0; } Não iremos focar em implementação pela parte da interação do úsuario pois não é o foco dessa série de artigos. Tentei ser o mais “verboso” possível no quesito UX ?. No projeto original usei a lib linenoise para criar uma shell interativa, mas isso fica para sua imaginação.
      Vamos executar:
      ~/.../mentebinaria/artigos >>> ./tracer hello 0x401122 #<== Endereco da main [130] Forking... Adding breakpoint on 0x401122 We just hit a breakpoint! > infor Child 705594 Registers: R15: 0x0 R14: 0x0 R12: 0x401050 R11: 0x2 R10: 0x7 RBP: 0x0 RAX: 0x401122 RCX: 0x225d7578 RDX: 0x19a402c8 RSI: 0x19a402b8 RDI: 0x1 RIP: 0x401123 CS: 0x33 EFGLAS: 0x246 We just hit a breakpoint! > continue Hello world Child 705594 finished... A ideia aqui não é criar tudo para você. A partir de agora, com o conhecimento básico dessa série de artigos, é possível criar o seu próprio debugger ou ferramenta semelhante. Deixo aqui o meu projeto, sdebugger, que foi fruto do meu estudo sobre este tema. Todo conhecimento base que eu passei aqui foi o necessário para criar este projetinho.
      Agradeço a toda turma do Mente Binária pelo apoio e desculpa à todos pela demora para finalizar essa série de artigos. Tenho várias ideias para artigos futuros, então vamos nos ver em breve!
      Links úteis:
      ELF Interruptions Breakpoints Interrupt Descriptor Table Qualquer problema/erro por favor me chame ?
    • By Marioh
      Cá estava eu programando com o nasm, tentando (apenas tentando mesmo) reproduzir os wrappers de systemcall que existem na glibc, quando me deparei com o tamanho de um bináriozinho em assembly que só retorna um valor, um "hello world" no nasm, ali no canto do diretório. O binário tinha 4.2K, nada realmente muito pesado, mas para um programa que não utiliza nenhuma biblioteca e só retorna um valor me pareceu muito estranho.
      Código do programa:
      BITS 32 global _start _start: mov eax, 1 mov ebx, 10 int 0x80 Para compilar e testar:
      [mario@zrmt rivendell]$ nasm -f elf32 elrond.asm [mario@zrmt rivendell]$ ld -m elf_i386 -s elrond.o -o elrond [mario@zrmt rivendell]$ ./elrond [mario@zrmt rivendell]$ echo $? 10 Aqui vai o hexdump do binário:
      [mario@zrmt rivendell]$ hexdump -C elrond 00000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| 00000010 02 00 03 00 01 00 00 00 00 90 04 08 34 00 00 00 |............4...| 00000020 20 10 00 00 00 00 00 00 34 00 20 00 02 00 28 00 | .......4. ...(.| 00000030 03 00 02 00 01 00 00 00 00 00 00 00 00 80 04 08 |................| 00000040 00 80 04 08 74 00 00 00 74 00 00 00 04 00 00 00 |....t...t.......| 00000050 00 10 00 00 01 00 00 00 00 10 00 00 00 90 04 08 |................| 00000060 00 90 04 08 0c 00 00 00 0c 00 00 00 05 00 00 00 |................| 00000070 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00001000 b8 01 00 00 00 bb 2a 00 00 00 cd 80 00 2e 73 68 |......*.......sh| 00001010 73 74 72 74 61 62 00 2e 74 65 78 74 00 00 00 00 |strtab..text....| 00001020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00001040 00 00 00 00 00 00 00 00 0b 00 00 00 01 00 00 00 |................| 00001050 06 00 00 00 00 90 04 08 00 10 00 00 0c 00 00 00 |................| 00001060 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00 |................| 00001070 01 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 |................| 00001080 0c 10 00 00 11 00 00 00 00 00 00 00 00 00 00 00 |................| 00001090 01 00 00 00 00 00 00 00 |........| 00001098 Da pra perceber que de 0x72 à 0xfff todos os bytes são 0. Humm... suspeito. Não sou especialista e posso estar terrívelmente errado, mas não lembro dessa quantidade de zeros no manual do formato ELF. Se abrirmos o binário com o readelf veremos o seguinte:
      [mario@zrmt rivendell]$ readelf elrond -h ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Intel 80386 Version: 0x1 Entry point address: 0x8049000 Start of program headers: 52 (bytes into file) Start of section headers: 4128 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 2 Size of section headers: 40 (bytes) Number of section headers: 3 Section header string table index: 2 Três Section Headers, dois Program Headers e mais um bando de coisa. Como não precisamos das seções para executar o programa irei ignorá-las por agora. Não precisamos das seções para executar o programa devido ao fato de que elas são feitas para auxiliar o linker no momento de construção do binário. Como o binário já está construído e nenhuma das seções representa objetos dinâmicos, elas podem ser ignoradas.
      Então vamos diminuir esse programa aí. Primeiramente, devemos descobrir o endereço base do programa, para isto, basta pegar o entrypoint (0x8049000) e diminuir o offset do Program Header que tem a flag de executável (que vai conter o devido código do programa). Lembrando que o entrypoint é composto pelo endereço base do programa (para ser mapeado em memória) + “endereço” (no arquivo) do primeiro byte que corresponde ao código executável. O que vamos fazer aqui é achar esse primeiro byte, que pode ser encontrado no Program Header, onde se tem a flag de executável que recebe o nome de p_offset. Vejamos o readelf -l:
      [mario@zrmt rivendell]$ readelf -l elrond Elf file type is EXEC (Executable file) Entry point 0x8049000 There are 2 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align LOAD 0x000000 0x08048000 0x08048000 0x00074 0x00074 R 0x1000 LOAD 0x001000 0x08049000 0x08049000 0x0000c 0x0000c R E 0x1000 Section to Segment mapping: Segment Sections... 00 01 .text Para ajudar: de acordo com o manual o campo p_offset é “O offset do início do arquivo onde o primeiro byte do segmento se encontra”. Como estamos lidando com um segmento executável esse primeiro byte vai ser o início do nosso código.
      Então dá para ver que o segundo Program Header (que possui a flag de executável) tem offset 0x001000! Então o endereço base é 0x08048000 (0x08049000 - 0x00001000) ! Já que temos o endereço base podemos excluir os zeros (caso contrário o programa ficaria quebrado e não iríamos conseguir analisá-lo com o readelf), alto lá! Apenas os inúteis! Mas quais são os inúteis ? Todos os que os Program Headers apontam, pois esses serão os  bytes do programa mapeados em memória, então vamos deixar eles lá. Vou usar o hyx como editor hexa, mas o hte também funciona.
      Após excluirmos todos os zeros entre 0x74 e 0x1000:
      [mario@zrmt rivendell]$ hyx elrond 0000> 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| 0010: 02 00 03 00 01 00 00 00 00 90 04 08 34 00 00 00 |............4...| 0020: 20 10 00 00 00 00 00 00 34 00 20 00 02 00 28 00 | .......4. ...(.| 0030: 03 00 02 00 01 00 00 00 00 00 00 00 00 80 04 08 |................| 0040: 00 80 04 08 74 00 00 00 74 00 00 00 04 00 00 00 |....t...t.......| 0050: 00 10 00 00 01 00 00 00 00 10 00 00 00 90 04 08 |................| 0060: 00 90 04 08 0c 00 00 00 0c 00 00 00 05 00 00 00 |................| 0070: 00 10 00 00 00 b8 01 00 00 00 bb 2a 00 00 00 cd |...........*....| 0080: 80 00 2e 73 68 73 74 72 74 61 62 00 2e 74 65 78 |...shstrtab..tex| 0090: 74 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |t...............| 00a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 0b 00 00 |................| 00c0: 00 01 00 00 00 06 00 00 00 00 90 04 08 00 10 00 |................| 00d0: 00 0c 00 00 00 00 00 00 00 00 00 00 00 10 00 00 |................| 00e0: 00 00 00 00 00 01 00 00 00 03 00 00 00 00 00 00 |................| 00f0: 00 00 00 00 00 0c 10 00 00 11 00 00 00 00 00 00 |................| 0100: 00 00 00 00 00 01 00 00 00 00 00 00 00 |.............| Ahh muito mais enxuto! Porém o bicho tá todo quebrado. Se executarmos:
      [mario@zrmt rivendell]$ ./elrond Bus error (core dumped) Um “Bus error” não é nada mais que uma tentativa de read ou write em um espaço de memória desalinhado. Como citado no manual os mapeamentos tem que ser alinhados com as páginas de memória, ou seja, 4KB.
      Vamos consertá-lo! Vamos ter que consertar: o entrypoint e o mapeamento do segundo Program Header, ou seja, seu endereço virtual, físico e seu offset. Como estamos alterando as posições dos segmentos (isto é, o nome oficial para o que um Program Header mapeia)  teremos que alterar seu mapeamento no arquivo junto com o entrypoint (que aponta para o primeiro byte de um segmento executável). Na verdade, o endereço físico pode ser ignorado, o manual cita que os “System V” ignoram endereços físicos de aplicações, mas iremos adicioná-los em prol da completude.
      Revisando... o entrypoint vai ser o endereço base mais o offset do segundo Program Header, e esse offset vai ser 0x75 (lembre-se que era 0x1000, mas com a retirada dos zeros entre 0x74 e 0x1000 efetivamente reduzimos o entrypoint em 0xFFF - 0x74 = 0xF8B,  logo, o entrypoint vai ser 0x1000 - 0xF8B = 0x75) então nosso entrypoint vai ser 0x08048075. Esse também vai ser o endereço virtual e o endereço físico do header.
      Então troquemos:
      O entrypoint no Header ELF por 0x08048075 O offset do section header por 0x00000075 Os endereços virtuais e físicos do segundo Program Header por 0x08048075 Agora mais do que nunca teremos que ter atenção. Saque seu editor de hexa preferido e lembre-se que estamos lidando com little endian. Vou usar o hyx, que é um editor hexa um pouco parecido com o vi:

      No terminal de cima temos o arquivo original sem os zeros, já no de baixo temos o arquivo já alterado.
      Para ajudar:
      Vermelho: Entrypoint Amarelo: Offset do Header Verde: Endereço Virtual do Header Azul: Endereço Físico do Header Agora se executarmos:
      [mario@zrmt rivendell]$ ./elrond [mario@zrmt rivendell]$ echo $? 10 Como disse lá em cima, não alterei as seções e nesse caso (binário já linkado e sem bibliotecas dinâmicas) elas não são importantes. Tente ler elas pra ver o que acontece.
      No fim passamos de 4.2k para ...
      [mario@zrmt rivendell]$ ls -lh elrond -rwxr-xr-x 1 mario mario 269 --- -- --:-- elrond 269!
      Achei que a galera poderia gostar dessa pequena aventura, acho bem interessante principalmente para aprender bem sobre o formato. Se gostarem tenho planos pra parte dois!
    • By Fernando Mercês
      Ano passado eu assisti à uma palestra sobre esse novo utilitário da suíte GNU chamado poke. Ele é um editor de dados binários de linha de comando bem diferente dos que costumo usar (HT Editor, Hiew, etc). Hoje decidi testá-lo e curti bastante. Tá em mega beta, então não tá nem perto de ter pacote disponível nos repositórios oficiais das distros Linux, mas consegui compilar e neste artigo vou dar as instruções, que podem variar em cada ambiente, até porque o poke está em constante desenvolvimento. Usei um ambiente Debian testing aqui.
      Instalando as dependências
      A dependência mais chatinha de instalar foi a gettext, porque o pacote pronto dela não foi suficiente. Então tive que clonar e compilar:
      $ sudo apt install perf fp-compiler fp-units-fcl groff build-essential git $ git clone https://git.savannah.gnu.org/git/gettext.git $ cd gettext $ ./gitsub.sh pull $ ./autogen.sh $ ./configure $ make $ sudo make install Com a gettext instalada, agora podemos partir para as demais dependências do poke:
      $ sudo apt install build-essential libgc-dev libreadline-dev flex libnbd-dev help2man texinfo Só então podemos seguir para a compilação do poke.
      Compilando o poke
      $ git clone git://git.savannah.gnu.org/poke.git $ cd poke $ ./bootstrap $ ./configure $ make $ sudo make install Criando links para as bibliotecas
      Como instalei as bibliotecas do poke em /usr/local e o meu sistema não tinha este diretório configurado para que o loader busque as bibliotecas, precisei criar dois links para elas em /usr/lib:
      $ sudo ln -s /usr/local/lib/libpoke.so.0 /usr/lib/libpoke.so.0 $ sudo ln -s /usr/local/lib/libtextstyle.so.0 /usr/lib/libtextstyle.so.0 Sei que há outras maneiras de resolver isso, mas fiz assim pra acelerar, afinal eu queria mexer no poke logo! ?
      Abrindo um binário PE no poke
      Baixei o executável do PuTTY para brincar um pouco e abri assim:
      $ poke putty.exe _____ ---' __\_______ ______) GNU poke 0.1-beta __) __) ---._______) Copyright (C) 2019, 2020 Jose E. Marchesi. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>. This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Powered by Jitter 0.9.212. Perpetrated by Jose E. Marchesi. hserver listening in port 47209. For help, type ".help". Type ".exit" to leave the program. (poke) Gerenciando os arquivos abertos
      O poke permite trabalhar com múltiplos arquivos de uma vez. Você pode ver a lista de arquivos abertos com o seguinte comando:
      (poke) .info ios Id Mode Size Name * #0 rw 0x0010b990#B ./putty.exe ios signifca "IO Spaces". Não tem nada a ver com o SO da Cisco ou com o da Apple. hehe
      Se quiser abrir outro arquivo, pode usar o comando .file <arquivo> e aí pode selecionar em qual você quer trabalhar com o comando .ios #n onde n é o número que identifica o arquivo, mas vou seguir o artigo com somente um arquivo aberto mesmo, então só teremos a tag #0.
      Dumpando dados
      Um dos principais comandos do poke é o dump (perceba este não começa com um ponto) que basicamente visualiza o conteúdo do arquivo, mas este tem várias opções. Vamos à mais básica:

      A primeira linha na saída acima é só uma régua pra te ajudar a encontrar os bytes.
      Fiz questão de colar uma captura de tela aí acima pra você ver que o poke colore a saída, mas nos exemplos seguintes vou colar a saída em texto pelo bem da sua largura de banda. ?
      Por padrão, o dump exibe 128 bytes do arquivo, começando do seu primeiro byte. O número de bytes pode ser alterado na própria linha de comando:
      (poke) dump :size 64#B 76543210 0011 2233 4455 6677 8899 aabb ccdd eeff 0123456789ABCDEF 00000000: 4d5a 7800 0100 0000 0400 0000 0000 0000 MZx............. 00000010: 0000 0000 0000 0000 4000 0000 0000 0000 ........@....... 00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000030: 0000 0000 0000 0000 0000 0000 7800 0000 ............x... A sintaxe pode parecer um pouco estranha no início, mas você acostuma rápido. O sufixo #B diz que a unidade usada é bytes. Você pode testar outros valores como 2#KB ou 1#MB por exemplo.  ?
      Dumpando a partir de posições específicas
      Para dumpar a partir de uma posição específica, podemos usar a opção :from do comando dump:
      (poke) dump :from 0x30#B :size 32#B 76543210 0011 2233 4455 6677 8899 aabb ccdd eeff 0123456789ABCDEF 00000030: 0000 0000 0000 0000 0000 0000 7800 0000 ............x... 00000040: 0e1f ba0e 00b4 09cd 21b8 014c cd21 7468 ........!..L.!th No comando acima eu pedi para o poke me mostrar 32 bytes a partir da posição 0x30. Seria o equivalente a fazer hd -n 32 -s 0x30 <arquivo>.
      O poke mantém um ponteiro de leitura no arquivo, por isso se você comandar somente dump novamente, o dump ocorrerá a partir da última posição lida (no caso, 0x30). Se quiser voltar o ponteiro para a posição zero, é a mesma sintaxe: dump :from 0#B.
      Interpretando dados
      O dump sempre te entrega uma saída em hexadecimal, mas e se quisermos interpretar os dados e exibi-los de maneiras diferentes? Para  isso a gente larga de mão o comando dump e começa a operar com o jeito do poke de ler e interpretar especificamente, assim:
      (poke) byte @ 0#B 77UB O sufixo UB significa Unsigned Byte.
      Se eu quiser a saída em hexa por exemplo, basta eu setar a variável obase (output base):
      (poke) .set obase 16 (poke) byte @ 0#B 0x4dUB Eu poderia querer ler 2 bytes. Tranquilo:
      (poke) byte[2] @ 0#B [0x4dUB,0x5aUB] Posso interpretar o conteúdo como número também:
      (poke) uint16 @ 0#B 0x4d5aUH O prefixo UH significa Unsigned Half (Integer). Perceba que o poke sabe que um uint16 tem 2 bytes e por isso os lê sem a necessidade que especifiquemos o número de bytes a serem lidos.
      À essa altura você já sacou que equivalentes aos tipos padrão da linguagem C (da inttypes.h na real) estão disponíveis para uso né? Fique à vontade pra testar off64, int64, int32, etc.
      Lendo strings
      Além dos tipos numéricos, o poke tem o tipo string, onde ele lê até encontrar um nullbyte:
      (poke) dump 76543210 0011 2233 4455 6677 8899 aabb ccdd eeff 0123456789ABCDEF 00000000: 4d5a 7800 0100 0000 0400 0000 0000 0000 MZx............. 00000010: 0000 0000 0000 0000 4000 0000 0000 0000 ........@....... 00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000030: 0000 0000 0000 0000 0000 0000 7800 0000 ............x... 00000040: 0e1f ba0e 00b4 09cd 21b8 014c cd21 5468 ........!..L.!Th 00000050: 6973 2070 726f 6772 616d 2063 616e 6e6f is program canno 00000060: 7420 6265 2072 756e 2069 6e20 444f 5320 t be run in DOS 00000070: 6d6f 6465 2e24 0000 5045 0000 4c01 0700 mode.$..PE..L... (poke) string @ 0x4d#B "!This program cannot be run in DOS mode.$" Patch simples
      Vamos fazer um patch simples: alterar o "T" desta string acima de maiúsculo para minúsculo. Basicamente é só colocar à esquerda o jeito que acessamos uma determinada posição do arquivo e igualar ao que a gente quer. Sabendo que para converter maiúsculo para minúsculo na tabela ASCII basta somar 32 (0x20), podemos fazer:
      (poke) byte @ 0x4e#B = 0x74 Perceba que fui na posição 0x4e, porque na 0x4d temos o '!' e não o 'T'. Só pra checar se funcionou:
      (poke) string @ 0x4d#B "!this program cannot be run in DOS mode.$" (poke) Legal né? Mas dá pra ficar melhor. O poke suporta char, então podemos meter direto:
      (poke) char @ 0x4e#B = 't' (poke) string @ 0x4d#B "!this program cannot be run in DOS mode.$" Por hora é só. Fica ligado aí que postarei a parte 2 em breve, onde vou mostrar mais recursos do poke que tô achando bem úteis para engenharia reversa. Até lá! ?
×
×
  • Create New...