Hash Aplicado na Forense Computacional - Prática
Usando Banco de Dados e hash parcial
Este é um complemento do artigo anterior, onde estudamos o uso do utilitário ssdeep, que calcula hashes parciais dos arquivos, para fazer a classificação dos arquivos entre conhecidos e desconhecidos, ou ainda para localizar na imagem forense possíveis arquivos maliciosos ou criminosos, mas que receberam algum tipo de alteração para serem descaracterizados.
A técnica, inicialmente, seria substituir o uso da base NSRL (que só contém hashes MD5 e SHA-1) por uma criada por nós, onde o hash dos arquivos seriam computados pelo ssdeep. Essa base tem a tendência de crcescer cada vez mais (e quanto maior melhor, certo ?), porém o utilitário teria problemas de performance e de memória para realizar a operação. Esses problemas já são conhecidos nossos e falamos deles em um artigo passado (Parte V).
A idéia agora é aplicar o mesmo princípio, mas com algumas dificuldades a mais, porque enquanto a comparação com hashes MD5 ou SHA-1 é imediata (se são ou não iguais), a comparação dos hashes parciais precisa de um cálculo entre ambos, indicando o score de proximidade entre os arquivos. Por conta disso, estudei os fontes do ssdeep e converti as partes correlacionadas à nossa necessidade do C para o Transact SQL.
Vamos ao procedimento.
1) Montando uma base de hashes ssdeep:
A base pode ser obtida executando o utilitário ssdeep sobre os arquivos que quisermos catalogar. O comando é:
C:\catalogar>ssdeep -l * > base.txt
Esse comando deve ser usado na primeira vez que estiver carregando a base, e em todas as vezes que for necessário adicionar hashes a base.
2) Importando a base de hashes parciais:
O procedimento de importação é o seguinte (Antes de iniciar, remova a tabela Aux, se existir):
1) Clique com o botão direito sobre Tables e em seguida, clique na opção Import Tables.
2) Indique que o Data Source é um arquivo texto, e indique o arquivo base.txt
3) Deixe as opções default do File Format, mas marque First Row Has Column Names, pois a prmeira linha do arquivo base.txt é um cabeçalho.
4) Clique em Next, e marque Comma como delimitador.
5) Clique em Next e em Next novamente. Troque o nome da tabela destino para Aux.
6) Clique em Next e finalize o processo, importando os dados.
A tabela Aux será criada com os registros importados. Logo em seguida, faça o seguinte:
a) Se essa for a primeira importação da base, execute as seguintes queries no Query Analyser:
select
block=left(ssdeep,charindex(':',ssdeep)-1),
outro=substring(ssdeep, charindex(':',ssdeep)+1, 255),
nome=[1.0--blocksize:hash:hash]
into
#x
from
aux
select
block,
hash1=left(outro,charindex(':',outro)-1),
hash2=substring(outro, charindex(':',outro)+1, 255),
nome
into
sshash
from
#x
Após ser executado, o código acima cria a tabela sshash com a primeira importação dos hashes parciais.
b) Se essa NÃO for a primeira importação da base, execute as seguintes queries no Query Analyser:
select
block=left(ssdeep,charindex(':',ssdeep)-1),
outro=substring(ssdeep, charindex(':',ssdeep)+1, 255),
nome=[1.0--blocksize:hash:hash]
into
#x
from
aux
Insert
sshash
select
block,
hash1=left(outro,charindex(':',outro)-1),
hash2=substring(outro, charindex(':',outro)+1, 255),
nome
from
#x
Após ser executado, o código acima adiciona à tabela sshash os hashes parciais recém importados.
3) Calculando os hashes ssdeep de uma imagem:
Após a imagem montada, deve-se ir para o diretório raiz da mesma e executar o seguinte comando:
/media/mnt>ssdeep -r * > /reports/sshashes.txt
Esse comando calculará os hashes de todos os arquivos em todos os diretórios da imagem.
4) Importando o arquivo de hashes
Esse processo é idêntico ao procedimento 2 em relação a Importação do arquivo para a tabela Aux.
As queries a serem aplicadas logo após são:
a) Caso seja a primeira vez que esse processo estiver sendo usado:
select
block=left(ssdeep,charindex(':',ssdeep)-1),
outro=substring(ssdeep, charindex(':',ssdeep)+1, 255),
nome=[1.0--blocksize:hash:hash]
into
#x
from
aux
select
[case] = 'Codigo-do-caso',
block,
hash1=left(outro,charindex(':',outro)-1),
hash2=substring(outro, charindex(':',outro)+1, 255),
nome
into
sshashComp
from
#x
b) Se essa NÃO for o primeiro caso a ser verificado, execute as seguintes queries no Query Analyser:
select
block=left(ssdeep,charindex(':',ssdeep)-1),
outro=substring(ssdeep, charindex(':',ssdeep)+1, 255),
nome=[1.0--blocksize:hash:hash]
into
#x
from
aux
Insert
sshashComp
select
[Case] = 'Codigo-do-caso'
block,
hash1=left(outro,charindex(':',outro)-1),
hash2=substring(outro, charindex(':',outro)+1, 255),
nome
from
#x
5) Comparando os arquivos da imagem:
Execute no Query Analyser a rotina sscompara. O primeiro parâmetro é o código do caso e o segundo é o score mínimo que desejamos acusar como arquivos próximos.
Ex:
exec sscompara '2007-001', 80
O comando acima vai separar os arquivos do caso 2007-001 que tenham no mínimo 80 de score de proximidade com os arquivos cadastrados, indicando qual é o arquivo na imagem e qual é o arquivo cadastrado.
Ilustrando ...
Imagine que você trabalhe para algum órgão de investigação contra a pedofilia e tenha um diretório cheio de imagens e vídeos com esse conteúdo, capturados e catalogados. Seguindo os passos acima, você poderá criar e manter uma base de hashes parciais, e ao analisar uma imagem forense de um hard disk suspeito, verificar se há algum arquivo na imagem que se assemelhe aos existentes em sua base. Note que se o suspeito usou algum tipo de descaracterização dos vídeos e imagens (troca de bits, crop, etc), ainda assim o método descrito irá indicar a semelhança.
Sugestões de melhoria
- Criar índices nas tabelas
- Criar stored procedures para os comandos após a carga, listados acima.
- Adaptar a rotina sscompara para dar como saída os arquivos da imagem que não tem proximidade nenhuma com a base. Essa alteração é bem simples e vai permitir anular a técnica de Anti-Forense comentada no artigo Parte VI.
Um estudo interessante seria apurar a média do score obtido entre uma imagem original e imagens alteradas a partir dessa, para situações onde apenas um bit é modificado, uso de crops diversos, modificações na resolução, tamanho, ou até mesmo inserção de informações por esteganografia. Alguém se habilita ?
Até o próximo post !
Fontes dos SQLs e Stored Procedures:
CREATE Procedure dbo.eliminate_sequences @seq varchar(255) OUTPUT
as
Declare @ret varchar(255),
@i int,
@j int,
@len int
Set @ret = substring(@seq,1,3)
IF (@ret ='')
return
Set @len = Len(@seq)
Set @i = 4
Set @j = 4
While (@i <= @len) Begin
IF (substring(@seq,@i,1) <> substring(@seq,@i-1,1)) Or
(substring(@seq,@i,1) <> substring(@seq,@i-2,1)) Or
(substring(@seq,@i,1) <> substring(@seq,@i-3,1)) Begin
Set @ret = @ret + substring(@seq, @i,1)
Set @j = @j + 1
End
Set @i = @i + 1
End
Set @seq = @ret
Return
GO
----------------------------------------------------------------------------
Create Procedure dbo.has_common_substring @s1 varchar(255), @s2 varchar(255)
as
If (Len(@s1) <>
Return 0
If (Len(@s2) <>
Return 0
Declare
@i int
Set @i = 1
While (@i <= (Len(@s1)-6)) Begin
If (charindex(substring(@s1,@i,7), @s2) > 0)
Return 1
Set @i = @i + 1
End
Return 0
go
-------------------------------------------------------------------------------
CREATE PROCEDURE dbo.edit_distn @from varchar(255), @from_len int,
@to varchar(255), @to_len int
as
Declare
@row int,
@col int,
@indice int,
@radix int,
@low int,
@swp_int int,
@swp_char varchar(255),
@menor int,
@valor int,
@prim int,
@seg int,
@ter int
Declare @buf table (indice int, valor int)
/* Handle trivial cases when one string is empty */
If ((@from = '') Or (@from_len = 0)) Begin
If ((@to = '') Or (@to_len = 0))
Return 0
Else
Return @to_len
End
Else
If ((@to = '') Or (@to_len = 0))
Return @from_len
Set @radix = 2 * @from_len + 3
If ((@from_len > @to_len) And (@from_len > 2000)) Begin
Set @swp_int = @to_len
Set @to_len = @from_len
Set @from_len = @swp_int
Set @swp_char = @to
Set @to = @from
Set @from = @swp_char
End
Set @indice = 0
If (substring(@from,1,1) = substring(@to,1,1))
Set @menor = 0
Else
Set @menor = 2
Insert @buf values (@indice, @menor)
Set @indice = @indice + 1
Set @low = @menor
Set @col = 1
While (@col < @from_len) Begin
If (substring(@from,@col+1,1) = substring(@to,1,1))
Set @menor = @col
Else
Set @menor = @col + 3
If (@menor > (@col+2))
Set @menor = @col+2
Select @valor = valor from @buf where indice = (@indice-1)
If (@menor > (@valor+1))
Set @menor = @valor + 1
Insert @buf values (@indice, @menor)
If (@menor < @low)
Set @low = @menor
Set @indice = @indice + 1
Set @col = @col + 1
End
/* Now handle the rest of the matrix */
Set @row = 1
While (@row < @to_len) Begin
Set @col = 0
While (@col < @from_len) Begin
If (@row = 0)
Set @prim = @col
Else
If (@col = 0)
Set @prim = @row
Else
Select @prim = valor from @buf where indice = ((@indice + @from_len + 2) % @radix)
If (substring(@from,@col+1,1) != substring(@to, @row+1,1))
Set @prim = @prim+3
If (@row = 0)
Set @seg = @col+1
Else
Select @seg = valor from @buf where indice = ((@indice + @from_len + 3) % @radix)
Set @seg = @seg+1
If (@col = 0)
Set @ter = @row+1
Else
Select @ter = valor from @buf where indice = ((@indice + @radix - 1) % @radix)
Set @ter = @ter+1
Set @menor = @prim
If (@seg < @menor)
Set @menor = @seg
If (@ter < @menor)
Set @menor = @ter
Insert @buf values (@indice, @menor)
If ((substring(@from,@col+1,1) = substring(@to,@row,1)) And
(@col > 0) And
(substring(@from, @col, 1) = substring(@to, @row+1,1))) Begin
If ((@row-1) = 0)
Set @prim = @col-1
Else
If ((@col-1) = 0)
Set @prim = @row-1
Else
Select @prim = valor from @buf where indice = ((@indice + 1) % @radix)
Set @prim = @prim+5
If (@menor > @prim)
Set @menor = @prim
Insert @buf values (@indice, @menor)
End
Select @valor = valor from @buf where indice = @indice
If ((@valor < @low) Or (@col = 0))
Set @low = @valor
Set @indice = (@indice+1) % @radix
Set @col = @col + 1
End
If (@low > 100)
Break
Set @row = @row+1
End
Select @row = valor from @buf where indice = ((@indice+@radix-1) % @radix)
Return @row
Go
---------------------------------------------------------------------------------
CREATE PROCEDURE dbo.score_strings @s1 varchar(255), @s2 varchar(255), @bloco int
as
Declare
@score int,
@len1 int,
@len2 int,
@menor int,
@ret int
Set @len1 = len(@s1)
Set @len2 = len(@s2)
if (@len1 > 64) Or (@len2 > 64)
Return 0
exec @ret = has_common_substring @s1, @s2
If (@ret = 0)
Return 0
exec @score = edit_distn @s1, @len1, @s2, @len2
Set @score = (@score * 64)/(@len1+@len2)
Set @score = (100 * @score) / 64
If (@score >= 100)
return 0
Set @score = 100 - @score
Set @menor = @len1
If (@len2 < @len1)
Set @menor = @len2
if (@score > (@bloco/3 * @menor))
Set @score = (@bloco/3 * @menor)
Return @score
go
----------------------------------------------------------------
CREATE PROCEDURE dbo.spamsum @bloco1 int, @hash11 varchar(255), @hash12 varchar(255), @bloco2 int, @hash21 varchar(255), @hash22 varchar(255)
as
Declare @score int,
@score1 int,
@score2 int
Set @score = 0
IF ((@bloco1 <> @bloco2) AND
(@bloco1 <> (@bloco2*2)) AND
(@bloco2 <> (@bloco1*2)))
return 0
exec eliminate_sequences @hash11 OUTPUT
exec eliminate_sequences @hash12 OUTPUT
exec eliminate_sequences @hash21 OUTPUT
exec eliminate_sequences @hash22 OUTPUT
IF (@hash12 = '') OR (@hash22 = '')
Return 0
If (@bloco1 = @bloco2) BEGIN
exec @score1 = score_strings @hash11, @hash21, @bloco1
exec @score2 = score_strings @hash12, @hash22, @bloco2
Set @score = @score1
IF (@score2 > @score)
Set @score = @score2
END
ELSE
IF (@bloco1 = @bloco2*2)
exec @score = score_strings @hash11, @hash22, @bloco1
ELSE
exec @score = score_strings @hash12, @hash21, @bloco2
return @score
go
------------------------------------------------------------------------------------
-------------------------------------------------------------------
Create Procedure dbo.sscompara @caso varchar(255), @minscore int
as
-- Separa quem tem algum match, obedecendo o score mínimo
Declare @tabCaso Cursor,
@tabBase Cursor,
@score int,
@lbloco1 int,
@lhash11 varchar(255),
@lhash12 varchar(255),
@filename varchar(255),
@lbloco2 int,
@lhash21 varchar(255),
@lhash22 varchar(255),
@nome varchar(255)
Declare @saida table (score int, arq1 varchar(255), base varchar(255))
Set @tabBase = Cursor Fast_Forward
For
Select block, hash1, hash2, [nome] from sshash
Set @tabCaso = Cursor Scroll Read_only
For
Select block, hash1, hash2, nome from sshashComp where [case] = @caso
Open @tabBase
Open @tabCaso
Fetch Next from @tabBaseinto @lbloco1, @lhash11, @lhash12, @filename
While (@@Fetch_Status = 0) Begin
Fetch First from @tabCaso
into @lbloco2, @lhash21, @lhash22, @nome
While (@@Fetch_Status = 0) Begin
exec @score = spamsum @lbloco1, @lhash11, @lhash12, @lbloco2, @lhash21,
@lhash22
if (@score >= @minscore)
Insert @saida values (@score, @nome, @filename)
Fetch Next from @tabCaso
into @lbloco2, @lhash21, @lhash22, @nome
End
Fetch Next from @tabBase
into @lbloco1, @lhash11, @lhash12, @filename
End
Close @tabBase
Close @tabCaso
Deallocate @tabBase
Deallocate @tabCaso
Select * from @saida
go
2 comentários:
Bom dia.
Estou fazendo um trabalho sobre o ssdeep e gostaria de saber se você teria esta mesma procedure para o Postgres.
Obrigada
Daniele
Infelizmente não, Dani. Imagino que não seja tão complicado fazer o port, pois o script usa funcionalidades presentes na maioria das sintaxes de SQL dos databases.
[]s
Tony
Postar um comentário