Aprenda a analisar um dump de memória compartilhada que pode ser interpretado por máquina em uma plataforma Linux e extrair o formato esperado de dados usando o Python e o utilitário struct.
Neste artigo, primeiro será apresentado como determinar o formato dos dados lendo o formato do arquivo binário do arquivo de dump. Isso é necessário para analisar, extrair e examinar os dados. Em seguida, você verá como analisar o arquivo com base no formato e depois estabelecer a correspondência entre os resultados e o formato esperado para realizar a saída de um resultado de validação.
Os dumps de memória revelam o estado registrado da memória de
trabalho em um determinado momento da operação. Eles são ferramentas
importantes na administração de ferramenta, porque fornecem provas
“forenses” da condição do sistema.
Antes de iniciar
Para as instruções e amostras de código deste artigo, eu usei o
Python versão 2.4, que pode ser obtida por download a partir do site do
Python. Talvez você obtenha resultados diferentes com outras versões.
Antes de começar, certifique-se de estar familiarizado com o seguinte:
- A implementação /dev/shm da memória compartilhada tradicional
- A visualização de dados de memória compartilhada manualmente em um sistema Linux
- Certas dependências (conceitos de abertura, leitura,
gravação e fechamento de arquivos em Linux; uso do descritor de arquivo
e dos modos em que o arquivo pode ser aberto; conceitos básicos de
estrutura do Python) - GNU/Linux, em geral
Entendendo os dumps de memória compartilhada no Linux
/dev/shm é uma implementação do conceito tradicional de memória
compartilhada. É um meio amplamente usado e aceito de passar dados entre
programas. Em /dev/shm, um programa ou daemon cria uma parte da memória
que outros processos (em níveis relevantes de permissão) podem acessar.
É um método fácil e rápido de compartilhar dados entre processos.
Cada programa cria o seu próprio arquivo. Nos meus exemplos, eu uso o nome de arquivo dumpmem localizado em /dev/shm/dumpmem.
Visualizando o dump de memória compartilhada manualmente no Linux
Não é possível visualizar os arquivos de memória compartilhada (normalmente conhecidos como arquivos shm) usando o utilitário cat – que geralmente é utilizado para exibir arquivos em Linux.
Já que esses arquivos shm estão em um formato binário. Se você tentar visualizá-los com os métodos genéricos de visualização de arquivos, eles parecerão um monte de caracteres distorcidos.
Eu uso o utilitário hexdump) para ler os arquivos de memória e visualizá-los em um formato legível. Há outros utilitários disponíveis para esse fim.
Para este artigo, o padrão de uso de hexdump é assim:
hexdump <optional switches> /dev/shm/dumpmem for <switches> supported
Ao final do artigo consulte a seção Recursos para obter um link com mais informações sobre o hexdump.
Definindo o cenário
O cenário com o qual trabalharemos é um farejador de rede que
analisa os pacotes recebidos pelo host e armazena os dados em um arquivo
de memória compartilhada, o /dev/shm/dumpmem. Esses dados contêm
informações sobre o pacote recebido.
Geralmente o arquivo é assim:
- O armazenamento do arquivo de memória é /dev/shm/dumpmem
- O formato de arquivo dumpmem contém:
- 4 bytes de endereço de origem para notificar quem o enviou
- 4 bytes de endereço de destino para notificar para quem ele vai
- 2 bytes de porta de origem (em outras palavras, a porta na origem que o pacote usou)
- 2 bytes de porta de destino (da mesma forma, a porta no destino que o pacote usará)
- 2 bytes de protocolo (o protocolo do qual o pacote faz parte)
- 4 bytes de hora para indicar a indicação da hora em que o pacote foi visto pelo fragmento da rede
- 1 comprimento de registro = soma das especificações de dumpmem (ou seja, 18 bytes)
- O tamanho máximo do arquivo de memória é 1 KByte, portanto, ele pode conter 1024 bytes (1024 / 18 = 56 registros)
Se você usar o hexdump e exibir o arquivo manualmente em um terminal Linux, ele ficará assim:
Listagem 1. Exibindo um arquivo de dump
# hexdump /dev/shm/fdshm 0000000 0004 0000 0400 0000 fc64 0a00 00fb e000 0000010 14e9 14e9 0011 0000 0000 0000 0000 0000 0000020 0000 0000 0000 0000 0000 0000 0000 0800 0000030 1668 0000 0000 0000 0032 0000 0000 0000 0000040 0000 0000 0001 e000 0000 0000 0002 0000 0000050 0000 0000 0000 0000 0000 0000 0000 0000 0000060 0000 0000 0000 0800 0100 0000 0000 0000 0000070 0008 0000 0000 0000 fc64 0a00 fd64 0a00 0000080 2328 03ea 0006 0000 0000 0000 0000 0000 0000090 0000 0000 0000 0000 0000 0000 0000 0800 00000a0 7700 0001 0000 0000 0040 0000 0000 0000 00000b0 fd64 0a00 fc64 0a00 03ea 2328 0006 0000 00000c0 0000 0000 0000 0000 0000 0000 0000 0000 00000d0 0000 0000 0000 0800 0a00 0000 0000 0000 00000e0 0040 0000 0000 0000 fc64 0a00 fd64 0a00 00000f0 2328 03ec 0006 0000 0000 0000 0000 0000 0000100 0000 0000 0000 0000 0000 0000 0000 0800 0000110 7700 0001 0000 0000 0040 0000 0000 0000
Vamos examinar as etapas envolvidas na análise do arquivo.
Analisando o arquivo de dump
As etapas para entender os dados em um arquivo de dump de memória
(identificar o formato, analisar e ler o arquivo) são relativamente
simples:
- Abra o arquivo
- Leia os bytes com o descritor de arquivos
- Converta os dados para um formato legível de cadeia de caractere quando for necessário
- Verifique se o buffer que foi lido está intacto e se tem truncamentos ou erros
- Desempacote os dados do buffer
- Extraia as informações
- Imprima os dados
- Desenvolva um loop para realizar as etapas de 1 a 7 em cada registro em um dump de dados compartilhados.
(Você não quer fazer isso manualmente, não é?)
Vamos repassar o fluxo de processo com mais detalhes.
Abra o arquivo
Para abrir um arquivo de memória compartilhada, use a forma geral fd = open(fileName, mode).
fd é o descritor de arquivo, um apontador para o arquivo. Para este exemplo, use o seguinte:
- fileName: /dev/shm/dumpmem
- mode: rb (somente leitura no modo binário)
Listagem 2. Abrindo um arquivo de memória compartilhada
fd = open('/dev/shm/dumpmem ','rb')
Leia os bytes
Para ler os bytes usando o descritor de arquivo obtido na chamada de
função anterior, eu uso o código a seguir. Ele lê o número indicado de
bytes a partir do parâmetro de arquivo que é passado:
Listagem 3. Abrindo um arquivo de memória compartilhada
def ReadBytes(self, fd, noBytes): ''' Read file and return the number of bytes as specified ''' data = fd.read(noBytes) return data buffer = ReadBytes('/dev/shm/dumpmem ', 18) # Pass the file name and pass the number of bytes # Number of bytes is 18 since in the example scenario each record # is of length 18
Aqui, a leitura dos bytes não é suficiente para extrair as informações
necessárias. Ela retorna um buffer se a cadeia de caractere é lida. É
necessário analisar e converter para um formato de cadeia de caractere
inteligível.
Converta os dados
O struct do Python pode ser usado para tratar os dados binários armazenados em arquivos ou a partir de conexões de rede, entre outras origens. O struct do Python tem duas funcionalidades amplas: pack e unpack.
A tarefa de pack é retornar uma cadeia de caractere contendo os valores v1, v2, empacotados de acordo com o formato dado. Os argumentos devem corresponder exatamente aos valores de que o formato precisa.
A função de unpack é desempacotar a cadeia de caractere (supostamente empacotada por pack(fmt, …)) de acordo com o formato dado. O resultado é uma tupla, mesmo se ele contiver exatamente um item. A cadeia de caractere deve conter exatamente a quantidade de dados de que o formato precisa: len(string) deve ser igual a calcsize(fmt).
Os formatos aceitáveis:
- Formatos de 1 byte
- b para caractere assinado
- B para caractere não assinado
- Formatos de 2 bytes
- h para inteiro curto
- H para curto não assinado
- Formatos de 4 bytes:
- l para longo
- L para longo não assinado
- Formatos de 8 bytes:
- q para longo longo
- Q para longo longo não assinado
Para ver os outros formatos suportados para empacotar e desempacotar os bytes de buffer, consulte a literatura do Python listada em Recursos.
Verifique o buffer
Para se certificar de que o buffer de 18 bytes que foi lido esteja intacto e não tenha truncamentos ou erros, pode-se usar a função calcsize para verificar se o tamanho de byte ainda é 18 conforme o esperado quando é lido. Pode-se usar a função asserts do Python para esse fim.
Listagem 4. Certificando-se de que o buffer está correto
self.assertEqual(len(buffer), struct.calcsize('llllh')) # 4 l's is 4*4 bytes = 16 bytes and h is 2 bytes so that is 18 bytes # we could use QQh which is 2*8 + 2 = 18 bytes as well
Desempacote os dados
Agora que você verificou que o buffer é realmente de 18 bytes, é possível desempacotar os dados do buffer. struct fornece uma função unpack_from bastante útil que fornece o número de bytes, o nome do buffer e a compensação com a qual deve ser lido:
struct.unpack_from(fmt, buffer[, offset=0])
Extraia os detalhes
No nosso cenário, estes são os detalhes que queremos extrair:
Listagem 5. Detalhes a serem extraídos
sourceAddress = (struct.unpack_from('B', buffer,0), struct.unpack_from('B', buffer,1), struct.unpack_from('B', buffer,2), struct.unpack_from('B', buffer,3)) destinationAddress = (struct.unpack_from('B', buffer,4), struct.unpack_from('B', buffer,5), struct.unpack_from('B', buffer,6), struct.unpack_from('B', buffer,7)) sourcePort = (struct.unpack_from('B', buffer,8), struct.unpack_from('B', buffer,9)) destinationPort = (struct.unpack_from('B', buffer,10), struct.unpack_from('B', buffer,11)) protocolUsed = (struct.unpack_from('B', buffer,12), struct.unpack_from('B', buffer,13)) timeStamp = (struct.unpack_from('B', buffer,14), struct.unpack_from('B', buffer,15), struct.unpack_from('B', buffer,16), struct.unpack_from('B', buffer,17))
Observação: dependendo da plataforma e da estrutura da memória (big
endian ou little endian) talvez seja necessário trocar a ordem de
leitura dos dados.
Imprima a saída
Agora que você tem os valores desempacotados do buffer binário que você leu, pode usar os comandos padrão de print para obter a saída necessária.
Listagem 6. Imprimindo os detalhes
print "sourceAddress =" , (struct.unpack_from('B', buffer,0),struct.unpack_from('B', buffer,1), struct.unpack_from('B', buffer,2),struct.unpack_from('B', buffer,3)) print "destinationAddress = " , (struct.unpack_from('B', buffer,4),struct.unpack_from('B', buffer,5), struct.unpack_from('B', buffer,6),struct.unpack_from('B', buffer,7)) print "sourcePort = " , (struct.unpack_from('H',buffer,8)) print "destinationPort = " , (struct.unpack_from('H',buffer,10)) print "protocolUsed = " , (struct.unpack_from('H',buffer,12)) print "timeStamp = " , (struct.unpack_from('B', buffer,14),struct.unpack_from('B', buffer,15), struct.unpack_from('B', buffer,16),struct.unpack_from('B', buffer,17))
A saída esperada da Listagem 6 deve ter este formato:
Listagem 7. Saída da impressão
sourceAddress = ((192,), (168,), (10,), (102,)) destinationAddress = ((207,), (168,), (1,), (103,)) sourcePort = (11299,) destinationPort = (11555,) protocolUsed = (256,) timeStamp = ((1,), (12,), (0,), (1,))
Automatize o processo para todos os registros
Agora, para ler e imprimir todos os registros de todo o arquivo de memória compartilhada, crie um loop:
Listagem 8. Criando um loop para ler e imprimir todos os registros
for element in range (0,56): #loop 18 since we know the file size and #the record length: 1024/18 = 56 records buffer = ReadBytes('/dev/shm/dumpmem ', 18) self.assertEqual(len(buffer), struct.calcsize('llllh')) sourceAddress = struct.unpack_from('B', buffer,0), struct.unpack_from('B', buffer,1), struct.unpack_from('B', buffer,2), struct.unpack_from('B', buffer,3)) destinationAddress = struct.unpack_from('B', buffer,4), struct.unpack_from('B', buffer,5), struct.unpack_from('B', buffer,6), struct.unpack_from('B', buffer,7)) sourcePort = struct.unpack_from('B', buffer,8), struct.unpack_from('B', buffer,9) destinationPort = struct.unpack_from('B', buffer,10), struct.unpack_from('B', buffer,11)) protocolUsed = ,struct.unpack_from('B', buffer,12), struct.unpack_from('B', buffer,13)) timeStamp = struct.unpack_from('B', buffer,14), struct.unpack_from('B', buffer,15), struct.unpack_from('B', buffer,16), struct.unpack_from('B', buffer,17)) print "sourceAddress = " , struct.unpack_from('B', buffer,0), struct.unpack_from('B', buffer,1), struct.unpack_from('B', buffer,2), struct.unpack_from('B', buffer,3)) print "destinationAddress = " , struct.unpack_from('B', buffer,4), struct.unpack_from('B', buffer,5), struct.unpack_from('B', buffer,6), struct.unpack_from('B', buffer,7)) print "sourcePort = " , struct.unpack_from('H',buffer,8)) print "destinationPort = " , struct.unpack_from('H',buffer,10)) print "protocolUsed = " , struct.unpack_from('H',buffer,12)) print "timeStamp = " , struct.unpack_from('B', buffer,14), struct.unpack_from('B', buffer,15), struct.unpack_from('B', buffer,16), struct.unpack_from('B', buffer,17))
E isso é tudo! Analisamos um formato conhecido de dump de memória binário no Linux e usamos struct do Python para ler o dump de dados binários e exibi-los em um formato legível.
Recursos
Aprender
- Saiba mais sobre o Python na Python Software Foundation.
- Aprenda a usar o utilitário hexdump para exibir arquivos especificados em um formato especificado pelo usuário.
- Procure mais artigos do developerWorks sobre o Python.
-
Na zona Linux do developerWorks,
você encontrará muitos artigos de instruções e tutoriais, bem como downloads, fóruns de discussão e muitos outros recursos para desenvolvedores e administradores Linux. - Fique por dentro dos eventos técnicos e webcasts do developerWorks com foco em uma variedade de produtos da IBM e tópicos do segmento de mercado de TI.
- Participe de um briefing ao vivo e gratuito do developerWorks para se atualizar rapidamente sobre produtos e ferramentas IBM, bem como tendências do segmento de mercado de TI.
-
Acompanhe as Demos on demand do developerWorks
, que abrangem desde demos de instalação e configuração de produtos para
iniciantes até funcionalidades avançadas para desenvolvedores
experientes. - Siga o developerWorks no Twitter.
Obter produtos e tecnologias
- Faça o download do Python a partir do Web site do Python.
-
Avalie produtos IBM
da maneira que for melhor para você: faça download da versão de teste de
um produto, avalie um produto on-line, use-o em um ambiente de nuvem ou
passe algumas horas na SOA Sandbox
aprendendo a implementar Arquitetura Orientada a Serviços de modo eficiente.
Discutir
-
Participe da comunidade My developerWorks.
Entre em contato com outros usuários do developerWorks, enquanto
explora os blogs, fóruns, grupos e wikis orientados ao desenvolvedor.
***
artigo publicado originalmente no developerWorks Brasil, por Asha Shivalingaiah
Asha Shivalingaiah é engenheira de software e trabalha como testadora no
Australia Development Lab para a IBM Security Solution, Tivoli. Desde
que passou a fazer parte da equipe IBM Rational em 2008, trabalha com
Rational Rhapsody e outras ferramentas do portfólio Rational para
gerenciar o ciclo de vida de desenvolvimento de software. É especialista
em linguagens de modelagem como UML 2 e SysML e no uso do Rational
Rhapsody para desenvolvimento baseado em modelo.