Jump to content
  • Comando find e suas miscelâneas

       (2 reviews)

    Leandro Fróes
     Share

    Após ver sobre o comando find no nosso canal Papo Binário decidi estudar um pouco mais sobre o mesmo. Revisando estas anotações pensei que seria interessante compartilhá-las, tendo em vista que o find é um comando extremamente poderoso. Alguns dos parâmetros já foram abordados no vídeo, mas vou repassar alguns aqui, não custa nada, não é mesmo?!

    Este comando pode ser útil para diversas tarefas, dentre elas investigação, administração ou mesmo aprendizado sobre o sistema.

    Indo direto ao ponto, o find é um comando para procurar itens no filesystem (arquivos, links, diretórios, etc). O que o difere de outros programas que fazem isto é a quantidade de opções que a ferramenta possui e o fato de não depender da variável $PATH para encontrar um binário. O comando leva como principal parâmetro um path, ou seja, um caminho para procurar algo. Se não passarmos nada ele entenderá que o path é o diretório atual:

    find 
    find /etc

    Se não especificarmos exatamente o que queremos buscar o find simplesmente nos mostra tudo o que achar pois ele varre o filesystem recursivamente na hora de procurar algo, mas não queremos isso tudo, até porque não seria muito útil. ?

    Vamos tentar entender alguns filtros interessantes... Imagine que você é um administrador e precisa verificar todos os arquivos que pertencem a um usuário em específico:

    find / -type f -user leandro 

    O que fizemos aqui? Utilizamos 2 tipos de filtros, um deles foi o -user, que busca arquivos que pertencem apenas à aquele usuário. O -type filtra pelo tipo de item no filesystem e suporta os seguintes tipos:

    • d -> diretório
    • f -> arquivo regular
    • l -> link simbólico
    • s -> socket
     
     
    Procurando por arquivos perdidos:
     
    Imagine agora que seu sistema está uma bagunça e você não faz ideia onde está um arquivo em específico, pense que você tem no mínimo 8 subdiretórios lotados de arquivos e você não lembra onde está o que você está procurando, só lembra que existe a palavra "mentebinaria" no nome dele. Além disso, você também sabe que não está nos primeiros 2 subdiretórios. Podemos resolver com:
    find . -mindepth 2 -name "*mentebinaria*" -type f
    A primeira coisa que fizemos foi utilizar a opção -mindepth, que especifica quantos níveis na hierarquia o find deve olhar no mínimo (a opção -maxdepth especifica o máximo). A outra opção foi a -name, que procura por um nome completo ou parte dele como fizemos no exemplo utilizando o wildcard * (asterisco) para bater com qualquer string antes de depois da palavra "mentebinaria".
     

    Executando comandos:

    Na minha opinião uma das opções mais interessantes do find é a -exec, que praticamente executa comandos em cima do que o find encontrar. Não entendeu? Vamos lá... supondo que queiramos ver qual o tipo de arquivo de todos os arquivo que encontrarmos em um diretório em específico com o comando file:

    find . -type f -exec file {} \;

    Temos muita coisa pra entender nesta linha. Primeiro, o -exec trabalha com o conceito de targets (as chaves {} ) e isto significa: coloque tudo o que o find devolver no local da chave. Para cada arquivo que o find achar ele rodará o comando file naquele arquivo. Incrível, não?

    Sim, mas com isto estaremos executanto o mesmo comandos múltiplas vezes, por exemplo:

    leandro@teste:~$ find . -type f | wc -l
    295

    Imagine rodar isto 295 vezes, muita coisa, não? Se notarmos no primeiro exemplo do -exec vemos que no fim da linha tem um ponto de vírgula e este indica o fim do -exec para o find (e não para o shell). Temos que usar a contra barra para escapar e o shell não pensar que é para ele.

    Ok, mas até agora não vimos como melhorar isto. Concordam que o comando file aceita mais de um parâmetro?

    file arq1 arq2 arq3

    E se pudéssemos pegar tudo que o find achar e, ao invés de rodar um comando do -exec por vez passamos tudo um atrás do outro? É exatamente isto o que o + faz e para ele não precisamos escapar:

    find . -type f -exec file {} +

    Este exemplo é a mesma coisa do anterior, mas de forma mais automatizada. Vamos medir a velocidade dos dois comandos:

    root@teste:~# time find / -type l -exec file {} \;
    
    ...
    
    real    0m15,127s
    user    0m0,336s
    sys     0m1,640s
    root@teste:~# time find / -type l -exec file {} +
    
    ...
    
    real    0m1,119s
    user    0m0,212s
    sys     0m0,396s

    Bem mais rápido com o +, não acham? ?

     

    Investigando o sistema:

    Seu servidor foi atacado, você não sabe exatamente o que aconteceu e como aconteceu, só sabe que nem tudo está funcionando do jeito que deveria. Uma coisa interessante à se fazer é tentar olhar para o que exatamente foi alterado desde o ataque. Imagine que isto ocorreu à 2 dias:
    find / -mtime -2

    Aqui estamos dizendo que a partir da hora que rodarmos o comando olhar para tudo que foi modificado 48 horas atrás. Podemos também verificar se algo foi acessado com -atime.

    E se você não sabe exatamente quando foi o ataque? A única coisa que você sabe é que a última coisa que você fez foi adicionar novas funcionalidades à um script que você tem. Podemos procurar por tudo que foi modificado após este arquivo com a opção -newer:

    find /etc -newer <arquivo_velho>

    Mas como isto? O Linux guarda um tipo de informação chamada MAC no inode de cada arquivo, resumindo é simplesmente a data da última modificação, acesso e criação do arquivo ao qual aquele inode se refere. Apenas como curiosidade, o comando stat lê essas informações também. ?

     

    Mais algumas informações:

    Ok, agora você não teve nenhum problema, só quer algumas informações sobre os arquivos que o find encontrar. A opção -size <n> pode ajudar a procurar por arquivos maiores (+) ou menores (-) que o especificado:

    find /var -size +20k

    Podemos trabalhar com os seguintes formatos:

    • c -> bytes
    • k -> KB
    • 0 ou -empty -> vazio
    find . -empty

    Não está satisfeito? Ok, a opção -ls ti da muito mais informações (praticamente aplica um ls -lids em cima de tudo que o find achar)

    find . -user leandro -type d -ls 

     

    Facilitando o parsing:

    Achou as opções de informações fracas? De fato a saída fica bem poluída. E se você precisasse todo dia monitorar informações específicas sobre arquivos específicos e criasse um script para isso, como você faria para obter estas informações? O find ti ajuda nisso também!!! Se você está familiarizado com a linguagem C (se não está veja isto) a função printf do C pode imprimir uma saída formatada de acordo com o que você escolher (string, inteiro, inteiro sem sinal, etc).

    Assim como em C, a opção -printf possui uma série de diretivas para formatarmos a saída do find como quisermos, algumas delas são:

    • %f -> nome do arquivo
    • %p -> path completo
    • %i -> inode
    • %M -> permissões
    • %n -> número de hard links
    find / -type f -atime -1 -printf '%p %i %M \n'

    O único detalhe aqui é que por padrão o -printf não coloca um caractere de nova linha, devemos adicionar como no exemplo. Com isto a saída fica bem mais interesante para um script ler, não acham?! Aqui está o exemplo de uma saída:

    file1 262295 -rw-r--r--
    file2 262283 -rw-r--r--
    file3 262296 -rw-r--r--

    Estas foram algumas dicas sobre o comando find. Com certeza informações mais completas podem ser encontradas no manual do comando, este tutorial tem como objetivo simplesmente compartilhar minhas anotações sobre o que acho bem interessante e usual sobre o comando find.

    Qualquer dúvida, crítica ou sugestão, por favor, sinta-se à vontade para comentar e obrigado! ?


    Revisão: Fernando Mercês
     Share


    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.


    fredericopissarra

       4 of 4 members found this review helpful 4 / 4 members

    Cobre o básico do uso de find muito bem. Eu acrescentaria um detalhe:

    É possível criar expressões mais complexas com o uso de \( e \) e as opções -and (que é implícita) ou -or. Por exemplo:

    $ find ~/videos/ -type f \( -name '*.webm' -or -name '*.avi' \) \
      -exec convert2mp4.sh '{}' \;

    Aqui todos os arquivos encontrador a partir do diretório ~/videos/, nomeados *.webm ou *.avi, serão passados para o script convert2mp4.sh.

    Notar que, assim como o ';' final é "escapado", os parênteses também tém que sê-lo.

    Link to review
    Share on other sites


  • Similar Content

    • By Fernando Mercês
      No últmo artigo falei sobre como montar uma máquina virtual básica para ER em Windows. Agora chegou a vez do Linux, já que o mundo não é feito só de PE. 🙂
       A ideia aqui é montar um ambiente legal para reverter binários ELF. Dá uma olhada neste vídeo (em tela cheia de preferência) pra ter uma ideia do poder dessas ferramentas:
      Legal né? Então mãos à obra!
      Máquina virtual
      1 GB de memória Placa de rede em modo NAT Sistema operacional
      Utilizo a ISO netinstall do Debian 64-bits, mas como sempre você é livre para utilizar a distribuição que quiser, só que este artigo é escrito com base nesta. 😉
      Algumas dicas de instalação:
      Não utilizar seu nome verdadeiro na criação da conta de usuário. Não instalar as extensões do software virtualizador como VMware Tools ou VirtualBox Guest Additions. Não configurar nenhuma regra de firewall ou qualquer software de proteção. /etc/apt/sources.list
      Eu deixo do seguinte jeito:
      deb http://deb.debian.org/debian/ testing main contrib non-free deb-src http://deb.debian.org/debian/ testing main contrib deb http://security.debian.org/debian-security testing-security main contrib non-free deb-src http://security.debian.org/debian-security testing-security main contrib O importante aqui é ter o contrib non-free após o main, já que alguns programas como o rar não são livres.
      Depois de configurado este arquivo, é bom atualizar os pacotes existentes:
      # apt update # apt upgrade -y Instalação de software
      Agora é hora de instalar os pacotes necessários:
      # apt install man gdb binutils vim strace ltrace ht build-essential tcpdump unhide foremost sudo p7zip-full rar unrar fasm gcc-multilib git file zip unzip python3-pip GEF
      Este é um plugin para o GDB que adiciona muitos recursos:
      # pip3 install capstone keystone-engine unicorn ropper # git clone https://github.com/hugsy/gef.git # echo "source $PWD/gef/gef.py" >> ~/.gdbinit Observações finais
      Após instalar todos os softwares, é de extrema utilidade criar um snapshot da máquina virtual, pra você voltar facilmente a este estado limpo após analisar um arquivo suspeito, por exemplo. Não precisa instalar um ambiente gráfico. Existe uma distribuição Linux chamada REMnux com foco em engenharia reversa, se você preferir.
    • By Fabiano Furtado
      Pessoal, disponibilizei no meu GitHub o meu primeiro projeto OpenSource, como forma de retornar para a comunidade o que tenho aprendido nestes últimos anos.
      O projeto se chama LIBCPF (C Plugin Framework - https://github.com/fabianofurtado/libcpf/) e se trata de uma biblioteca/framework pra gerenciamento de Plugins (".so") no Linux, feita em C. Para maiores detalhes, veja a documentação no arquivo README.md.
      Ela foi lançada utilizando-se a licença GPL v2, uma vez que não entendo muito sobre esse assunto e precisava de uma licença para o projeto.
      Espero que, de alguma forma, este projeto possa ser útil para você. Mesmo que não exista a necessidade de utilização desta lib em sua aplicação, a mesma pode ser usada para listar os nomes das funções e os offsets que existem dentro de uma shared library, algo que considero bem interessante. Veja no exemplo como isso funciona.
      Como qualquer programa em C, o desenvolvimento deu muito trabalho pois tentei desenvolvê-la com foco na segurança, tentando evitar possíveis "buffer overflows" ou qualquer outro tipo de vulnerabilidades.
      Sugestões e críticas serão sempre bem-vindas!
      Desde já, agradeço.
    • By Bruna Chieco
      O Google está trabalhando para permitir que código Rust seja utilizado no kernel Linux, o que se trata de uma grande mudança tecnológica e cultural após décadas usando apenas a linguagem C. Para isso, a empresa financiará o projeto com o objetivo aumentar a segurança do sistema operacional, conforme publicou o CNet.
      A empresa já financia um projeto do Internet Security Research Group para deixar toda a Internet mais segura. Trata-se de um módulo para o Apache HTTP web server (ou simplesmente httpd), que é um software livre, sendo o servidor web mais utilizado no mundo, também escrito em linguagem C, utilizando a linguagem chamada Rust.
      Agora, o projeto permite possível adicionar novos elementos escritos em Rust no coração do Linux, chamados de kernel, o que tornaria os sistemas operacionais Android e Chrome do Google mais seguros. Miguel Ojeda está sendo contratado para escrever software em Rust para o kernel Linux. O Google está pagando pelo contrato, que será estendido por meio do Internet Security Research Group. 
      A melhor segurança para o Linux é uma boa notícia para todos, exceto para os atacantes. Além dos sistemas operacionais Android e Chrome, os serviços do Google, como YouTube e Gmail, contam com servidores que executam Linux. Ele também capacita a Amazon e o Facebook, e é um acessório nos serviços de computação em nuvem.
      Não está claro se os líderes do kernel do Linux irão acomodar o Rust. Segundo o CNet, Linus Torvalds, o fundador do Linux, disse que está aberto a mudanças se o Rust para Linux provar seu valor. 
    • By Felipe.Silva
      A heap é uma estrutura especial de memória usada pelo processo. O que tem de especial nela é o fato de seu tamanho ser variável, já que sua memória pode ser alocada ou desalocada dinamicamente pelo processo. Isso pode ser feito usando syscalls do sistema operacional e o mesmo é responsável por alocar mais páginas de memória para a seção caso seja necessário.
      No Linux o segmento de dados pode ser aumentado ou diminuído usando a syscall brk, e é esse espaço de memória que os programas normalmente usam para a heap. Em C nós normalmente não usamos a heap diretamente e, ao invés disso, usamos a função malloc() e semelhantes para lidar com a alocação dinâmica de memória. O que alguns não sabem é que na verdade chamadas para a função malloc() ou free() não necessariamente irão resultar na alocação ou desalocação de memória para o processo. Isso se dá porque é a libc quem de fato gerencia a heap e nós não estamos diretamente solicitando ou liberando memória para o sistema operacional.
      *libc é a biblioteca padrão da linguagem C que contém funções essenciais para tarefas básicas como: Manipulação de strings e arquivos, entrada e saída de dados, funções básicas de matemática etc.
      A função malloc(), de acordo com a implementação da glibc, usa o segmento de dados, o dividindo em uma ou mais regiões de memória que eles chamam de “arenas”. A arena principal corresponde à heap original do processo, porém outras arenas também podem ser alocadas para o processo. Inicialmente cada thread criada no processo tem uma arena individual até atingir um certo limite pré-definido de arenas que podem ser alocadas no processo. Atingindo esse limite, as threads posteriores passam a compartilhar a mesma arena. Em cada arena de memória existem divisões da memória que são chamadas de maneira homônima de heap, e são nessas heaps que a função malloc() de fato aloca memória para o usuário da libc.
      Cada bloco de memória que malloc() aloca na heap é chamado de chunk, e cada chunk contém metadados usados pelo sistema interno de malloc para organizar a heap como uma lista duplamente encadeada. Para fins de ilustração, abaixo está a estrutura de um chunk, usada na glibc:
      struct malloc_chunk { INTERNAL_SIZE_T mchunk_prev_size; INTERNAL_SIZE_T mchunk_size; struct malloc_chunk* fd; struct malloc_chunk* bk; /* Only used for large blocks: pointer to next larger size. */ struct malloc_chunk* fd_nextsize; struct malloc_chunk* bk_nextsize; O valor mchunk_prev_size seria o tamanho do chunk anterior e mchunk_size o tamanho do chunk atual. Os ponteiros *fd e *bk são usados somente quando o chunk está livre, e seriam ponteiros usados para a lista circular duplamente encadeada de chunks que apontam para o chunk posterior e anterior, respectivamente. No entanto, essa estrutura não representa muito claramente como o chunk é de fato usado pelo sistema de malloc, na figura abaixo isso é ilustrado com mais precisão.

      O ponteiro que malloc() retorna não aponta para o início do chunk mas sim para o início do espaço de memória que pode ser usado pelo usuário. O tamanho do espaço de memória de um chunk é alinhado pelo tamanho de um double word na arquitetura. Caso malloc() seja chamado passando um tamanho desalinhado como argumento, um espaço extra é alocado para manter o alinhamento. Por exemplo, se o alinhamento está sendo feito para 8 bytes e malloc é chamada com 9 como argumento, malloc irá te devolver um chunk com 16 bytes de espaço de memória usável. Além do alinhamento no tamanho do chunk, também existe um alinhamento no endereço de memória retornado por malloc() que é sempre alinhado para o tamanho de uma word. Isso é feito porque em algumas arquiteturas esse alinhamento de memória é necessário para se evitar uma exceção. Em outras arquiteturas (x86, por exemplo) o alinhamento melhora a performance do processador no acesso à memória.
      Como existe esse alinhamento no tamanho de um chunk isso garante que os três bits menos significativos de mchunk_size não sejam necessários para definir o tamanho do chunk. Se aproveitando disso, os três últimos bits são usados como flags para determinar alguns metadados usados pelo sistema de chunks.

      O bit M indica que o chunk não pertence a nenhuma arena e, ao invés disso, foi alocado dinamicamente em uma memória mapeada. Caso este bit esteja ligado, os outros dois são ignorados. No contexto de um chunk livre este bit está sempre desligado, tendo em vista que a lista encadeada de chunks livres somente se aplica a chunks que estão em alguma arena.
      Os chunks diretamente mapeados na memória (com bit M ligado) são criados para chunks muito grandes. Esses chunks quando liberados com a função free() são imediatamente liberados da memória. Por outro lado, usar free() em um chunk comum não necessariamente irá liberar memória para o sistema operacional. O que free() faz nesse caso é marcar o chunk como livre o adicionando de volta à lista de chunks livres. Assim como é indicado nesse trecho da glibc:
      /* Mark the chunk as belonging to the library again. */ (void)tag_region (chunk2mem (p), memsize (p)); Repare como o comentário descreve a ação como “marcar o chunk como pertencente à biblioteca novamente”, e é efetivamente isso que a função free() faz, não sendo necessariamente uma liberação de memória para o sistema operacional. Inclusive um recurso de otimização que a glibc usa é o que eles chamam de tcache (Thread Local Cache), que se trata de uma lista de chunks existente em cada thread individualmente. Quando você aloca um novo chunk na thread e posteriormente o libera, ele é adicionado ao tcache daquela thread e pode ser reutilizado em uma nova alocação posterior.
      Um adendo que a função free() pode efetivamente liberar memória para o sistema operacional se houver vários chunks livres no topo do segmento de dados (o que raramente acontece). Ela faz isso chamando a função interna systrim(), que por sua vez (no Linux) usa a syscall brk para diminuir novamente o segmento de dados.
      Um detalhe interessante que vale citar aqui é que na glibc (no Linux) existem as funções brk e sbrk que servem como wrappers para aumentar/diminuir o segmento de dados. O sistema de liberação de memória do systrim() espera que essas funções não sejam utilizadas diretamente para poder fazer a liberação de memória apropriadamente. Se você usá-las em seu código por algum motivo, irá “quebrar” o sistema de liberação de memória automático do free(), o fazendo não mais liberar memória quando é usado em chunks de arenas. Logo, não é recomendável que você use essas funções diretamente a menos que você esteja implementando seu próprio sistema de gerenciamento de memória dinâmica.
      O código abaixo é um experimento a fim de vermos na prática os metadados do chunk no Linux:
      // gcc test.c -o test #include <stdio.h> #include <stdlib.h> int main(void) { size_t *content = malloc(8); size_t chunk_size = content[-1] & ~0b111; size_t chunk_flags = content[-1] & 0b111; printf("size: %zu\nflags: %zu\n", chunk_size, chunk_flags); return 0; } No meu Linux é retornado 32 como tamanho do chunk e 1 como flag, indicando que somente o bit P está ligado. Sugiro ao leitor variar o tamanho passado para malloc a fim de comprovar que o alinhamento do tamanho do chunk de fato ocorre. Também sugiro passar um número grande para malloc() a fim de ver a partir de qual tamanho malloc() irá deixar de usar uma arena e irá alocar o chunk com mmap(). Caso isso ocorra o bit M será ligado e o número 2 (decimal) será indicado como flags.
      Nota: Esse código propositalmente não utiliza free() antes de finalizar o programa. É redundante e desnecessário usá-la quando o programa é finalizado, tendo em vista que todas as páginas de memória usadas pelo processo serão liberadas pelo sistema operacional.
      Referências
      https://man7.org/linux/man-pages/man2/brk.2.html https://sourceware.org/glibc/wiki/MallocInternals https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=e2d7b1b58396906375ba0e953a20ac57f0904378;hb=refs/heads/master http://c-faq.com/malloc/freeb4exit.html
    • By Bruna Chieco
      O Cisco Talos, grupo global de inteligência de ameaças de cibersegurança da Cisco, descobriu uma vulnerabilidade de divulgação de informações no kernel do Linux.
      A vulnerabilidade, rastreada como CVE-2020-28588, pode permitir que um invasor visualize a pilha de memória do kernel, o que significa que dados ou informações que não deveriam ser vistas possam ser acessadas. O problema foi visto pela primeira vez pelo Cisco Talos em um dispositivo Azure Sphere (versão 20.10), um dispositivo ARM de 32 bits que executa um kernel do Linux corrigido.
      O kernel do Linux é o núcleo livre e de código aberto dos sistemas operacionais do tipo Unix. A vulnerabilidade existe especificamente na funcionalidade /proc/pid/syscall de dispositivos ARM de 32 bits executando Linux.
      Um invasor pode explorá-la lendo /proc/<pid>/syscall, um arquivo legítimo do sistema operacional Linux, podendo aproveitar esse vazamento de informações para explorar com êxito vulnerabilidades adicionais não corrigidas.
      O Cisco Talos trabalhou com o Linux para garantir que esse problema seja resolvido e uma atualização já está disponível para os clientes afetados. Os usuários são incentivados a atualizar esses produtos afetados o mais rápido possível para o Kernel Linux versões 5.10-rc4, 5.4.66 e 5.9.8.
×
×
  • Create New...