Kontrola integralności plików i detekcja zmian w zasobach dyskowych – narzędzia skryptowe

keep-calm-and-have-integrity-3Niejednokrotnie, w informatyce śledczej, dla bezpieczeństwa lub po prostu w codziennej praktyce administratora systemu, stajemy wobec konieczności przeprowadzenia weryfikacji integralności plików i zmian zawartości zasobów na dyskach. Wykonujemy ją metodą porównawczą, odnosząc aktualny stan do określonego wzorca. Na początek zatem, przygotowujemy ów wzorzec, czyli obraz stanu wyjściowego do późniejszych odniesień, licząc po kolei sumy kontrolne wszystkich plików wskazanego katalogu, łącznie z plikami znajdującymi się głębiej w jego pod-strukturach czyli podkatalogach (rekursywnie). Potem, okresowo lub ad hoc przeprowadzamy kontrole polegające na detekcji wszelkich ingerencji w nasze zasoby. Sprawdzamy wówczas: czy ktoś nie usunął z nich ważnych plików, nie dodał nowych, czy też nie dokonał zmiany w treści istniejących plików. Na przykład zmiana sumy kontrolnej binarnego pliku oprogramowania może – choć oczywiście niekoniecznie musi – oznaczać jego zainfekowanie wirusem. Zmiana sumy kontrolnej tekstowego pliku konfiguracyjnego lub kodu skryptu może zaś być przesłanką wskazującą na ślad po działaniach intruza w systemie. Wreszcie administrator, w toku codziennej pracy, może zechcieć zapanować nad zmianami w repozytoriach ważnych plików konfiguracyjnych, „zrzuconych” jako kopie bezpieczeństwa na serwer FTP, TFTP, SCP, HTTP z urządzeń sieciowych. Można oczywiście poszukać jeszcze wielu innych, dalszych zastosowań, ale ja chciałbym już przejść do omówienia narzędzia, które opracowałem, by pokazać, jak bez inwestycji w komercyjne rozwiązania (na przykład Tripwire), albo nawet bez konieczności instalowania darmowych programów OpenSource, mając do dyspozycji wyłącznie interpreter języka skryptowego (w moim przypadku ulubiony Ruby) i kilka bibliotek, w mig napisać użyteczny skrypt.

Skrypt liczy sumy kontrolne każdego z plików we wskazanej w swym parametrze wywołania lokalizacji. Jeśli napotka na podkatalog, to oczywiście podąża rekursywnie w dół struktury. Sumy kontrolne liczone są z wykorzystaniem algorytmu MD5, choć oczywiście nic nie stoi na przeszkodzie, by stosować w tym celu inny algorytm, na przykład SHA-1, RIPEMD czy SHA-256. W charakterze sum kontrolnych można także wykorzystywać struktury HMAC lub – najprościej – dodatkowo szyfrować obliczone sumy kontrolne przy pomocy algorytmu AES-256 (lub jakiegokolwiek innego, na przykład Blowfish). Kilka odpowiadających takim właśnie alternatywnym rozwiązaniom drobnych modyfikacji kodu zaznaczyłem w skrypcie, umieszczając je w komentarzu, tuż po wywołaniu metody bazowej. W celu poeksperymentowania ze skryptem starczy „odkomentować” odpowiedni fragment takiego kodu i uruchomić skrypt.

Wyniki wyliczeń sum kontrolnych są przechowywane w tablicach asocjacyjnych w pamięci operacyjnej. Operacje dalszego ich przetwarzania są zatem szybkie, lecz przy dużych rozmiarach tych struktur mogą jednak okazać się kosztowne, ze względu na wykorzystanie sporych zasobów RAM. Alternatywą w takiej sytuacji może być ich zapis do bazy SQL-owej, na przykład – w najprostszym przypadku – do bezserwerowej bazy SQLite.  Wówczas skrypt trzeba by było lekko zmodyfikować. W tym celu, by móc zapisać obraz stanu początkowego (stanu odniesienia) sum kontrolnych zasobów dyskowych, skrypt wykorzystuje tekstowe pliki płaskie, choć i tu można by znaleźć eleganckie zastosowanie dla baz danych, zamiast plików. Rezultaty działania skryptu są wyprowadzane na ekran konsoli znakowej. Można, planując modyfikację skryptu, zastanowić się nad zapisywaniem raportu do pliku.

Przykładowe dane na standardowym wyjściu skryptu przedstawiają się następująco:

File: ./excel_ole32.rb: Checksum: df4ad84203345cd23c72726283f66ab3
File: ./exif_reader.rb: Checksum: f9c259e22c7736782389b5edbc4106fd
File: ./facebook_friends.tcl: Checksum: 3986d889ab9ad229096dd0f7bd7f3d10
File: ./file_checksum.rb: Checksum: 5548947b44da6c16fdedba96f6fb1435
File: ./filename_generation.rb: Checksum: b5df260500beef111e7665f0c0817178
File: ./google_graph.rb: Checksum: 7a8c9b4dc85a428fe5a09c9c000a0410
File: ./gtk_encrypt_decrypt.rb: Checksum: 0ce9cf213984bd9999f2275098a6f578
File: ./ip_subnet_tk.rb: Checksum: 08de840bf762fe7378642cf76a693ec7
File: ./ip_subnet_tk.rbw: Checksum: 08de840bf762fe7378642cf76a693ec7
File: ./out.pcap: Checksum: bf55677912babdd0c158747b16b071a3
File: ./packetfu_arp.rb: Checksum: efed1c92a84ac02315eb59b7f1bf028f
File: ./sh_ver.tcl: Checksum: 3054bc0fef776d356796e4cef95fd358
File: ./sh_ver_graph.tcl: Checksum: 83a53d7c5f0067cf83f080741735a746
File: ./show_proc_cpu.tcl: Checksum: 6e674a305b4f42fb48a05f8575580675
File: ./show_proc_cpu_output.txt: Checksum: 2aef380d7aff7d23a03b6e1128abaebb
File: ./show_proc_mem.tcl: Checksum: 4a3d04dfa91c96b0b5a77494f72c328f
File: ./show_proc_mem.txt: Checksum: 09f62fe729c590643243a1b6050c85b9
...
----------------------------------------
Deleted files number: 0
----------------------------------------
----------------------------------------
New files number: 1
----------------------------------------
New file: ./script_output.txt (hash: )
----------------------------------------
Changed files number: 2
----------------------------------------
Changed file: ./snapshot-arch.dat (hash:  c3b767d76713596a50adc91b015511ad)
Changed file: ./snapshot.dat (hash:  b517d0a8b410a4f0a5d5e3a4bd9466ff)
----------------------------------------

Dalsze modyfikacje skryptu mogą pójść w kierunku:

  • Możliwości znakowania, znacznikiem czasu, obrazów stanu systemów plikowych i porównywania dwóch dowolnie wybranych obrazów, nie tylko stanu aktualnego z jedynym dostępnym stanem historycznym.
  • Możliwości rejestrowania nie tylko faktu zmiany, ale też różnic treści dla zmienionych plików tekstowych.
  • Możliwości podpisywania plików sygnaturą cyfrową z wykorzystaniem kryptografii asymetrycznej.
  • Zbudowania graficznego interfejsu użytkownika🙂

Zachęcam zatem do eksperymentów i dalszego udoskonalania skryptu.

Oto kod źródłowy skryptu:

# encode UTF-8
require 'digest/md5'
require 'find'
require 'openssl'
require 'base64'

def encrypt(plaintext, cipher_key)
   # Encryption based on OpenSSL library classes
   # You can use a plenty of other crypto algorithms if needed.
   # Just type: puts OpenSSL::Cipher.ciphers
   # in your source code to obtain a list of all supported crypto algorithms
   cipher = OpenSSL::Cipher::AES.new(256, :CBC)
   cipher.encrypt
   key = cipher_key
   encrypted = cipher.update(plaintext) + cipher.final
   return Base64.encode64(encrypted)
end

def diff_file(from_file, to_file)
   del_hash = Hash.new; new_hash = Hash.new; chg_hash = Hash.new
   from_hash = Hash.new; to_hash = Hash.new
   unless File::exists?(from_file) and File::exists?(to_file)
      puts "Problem with access to files in diff_file() function"
      exit
   end
   from_data = File.new(from_file, "r")
   while (line = from_data.gets)
      key = line.split(";").first
      value = line.split(";").last
      from_hash[key] = value
   end
   from_data.close
   to_data = File.new(to_file, "r")
   while (line = to_data.gets)
      key = line.split(";").first
      value = line.split(";").last
      to_hash[key] = value
   end
   to_data.close
   to_hash.each_key do |key|
      if from_hash.has_key?(key)
         unless from_hash[key] == to_hash[key]
            chg_hash[key] = from_hash[key]
         end
      else
         del_hash[key] = from_hash[key]
      end
   end
   from_hash.each_key do |key|
      unless to_hash.has_key?(key)
         new_hash[key] = to_hash[key]
      end
   end
   puts "----------------------------------------"
   puts "Deleted files number: #{del_hash.length}"
   puts "----------------------------------------"
   del_hash.each do |key, value|
      puts "Deleted file: #{key} (hash: #{value})"
   end
   puts "----------------------------------------"
   puts "New files number: #{new_hash.length}"
   puts "----------------------------------------"
   new_hash.each do |key, value|
      puts "New file: #{key} (hash: #{value})"
   end
   puts "----------------------------------------"
   puts "Changed files number: #{chg_hash.length}"
   puts "----------------------------------------"
   chg_hash.each do |key, value|
      puts "Changed file: #{key} (hash: #{value})"
   end
   puts "----------------------------------------"
end

if ARGV.length < 1 	
   puts "Proper syntax: ruby #{$0} directory" 	
   exit 
end 
# Check if the directory exists and if so, calculate of the MD5 checksum recursively 
# for all of the files inside. 
if File.directory? ARGV[0]
   path = ARGV[0] 
else 	
   puts "${ARGV[0]} is not a valid directory" 	
   exit 
end 
hashes = Hash.new 
Find.find(path) do |fname|   
   unless FileTest.directory?(fname) 	
      begin 		
         hash = Digest::MD5.hexdigest(File.read(fname))
         hashes[fname] = hash
         # A crypto checksum may be created if needed. It this case you should provide your own secure key.
         # You can enable crypto-version by removing a hash sign from the line that follows and hashing subsequent line of code.
         # puts "File: #{fname}: Encrypted checksum: #{encrypt(hash, '$3CYouRe')}"
         puts "File: #{fname}: Checksum: #{hash}"
         # We may also create signature-file for all of the verified files, with crypto-checksum inside
         # To enable this functionality, uncomment the section between lines
         # -----------------------------------------------------------------
         # sig_file = File.new("#{fname}.sig", "w")
         # if sig_file
         #    sig_file.syswrite("#{fname}; #{encrypt(hash, '$3CYouRe')}\n")
         # else
         #   puts "Unable to create #{fname}.sig file"
         # end
         # -----------------------------------------------------------------
      rescue => e
         puts "Error in opening file: #{fname} during checksum calculation"
         puts "Exception: #{e}"
      end	
   end
end

if File::exists?("snapshot.dat")
   File::rename("snapshot.dat", "snapshot-arch.dat")
end

output_file = File.new("snapshot.dat", "w")

if output_file
   # A crypto checksum may be created if needed. It this case you should provide your own secure key.
   # hashes.each { |key,value| output_file.syswrite("#{key}; #{encrypt(value, '$3CYouRe')}\n") } 
   hashes.each { |key,value| output_file.syswrite("#{key}; #{value}\n") }
else
   puts "Unable to create snapshot.dat file"
   exit
end

diff_file("snapshot.dat", "snapshot-arch.dat")

Informacje Janusz Nawrat
Just ordinary man who likes thinking...

One Response to Kontrola integralności plików i detekcja zmian w zasobach dyskowych – narzędzia skryptowe

  1. Przydatna rzecz także dla informatyka śledczego, bo jakoś twórcy pakietów wprawdzie oczywiście udostępniają funkcje kalkulacji sum kontrolnych, ale z funkcją stałego automatycznego monitoringu integralności procesowanego materiału się nie spotkałem (aczkolwiek także jej nie szukałem). Wprawdzie wszystkie tego typu aplikacje działają w trybie read-only, ale strzeżonego…
    Jeśli ktoś nie lubi się bawić w skrypty może skorzystać z aplikacji, które zrobią powyższą robotę za niego – polecam np. Md5Checker

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Log Out / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Log Out / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s

TOMASZ WEŁNA

artysta grafik | wykładowca

PRACOWNIA OKO

Szkoła Rysunku Malarstwa i Grafiki DR TOMASZA WEŁNY | KRAKÓW | Plac Matejki 10 | tel 691 81 75 74

Piękno neurobiologii

Blog Jerzego Vetulaniego

Teoria muzyki, zasady muzyki, podstawy muzyki

Teoria muzyki, zasady muzyki, podstawy muzyki - czyli to co każdy amator muzyki wiedzieć powinien :)

Personal Development & Inspirations

Przemyślenia i refleksje, którymi warto się podzielić (blog by Janusz Nawrat)

Business IT Cooperation Platform

Biznes i IT - dwa światy, które muszą współdziałać

%d bloggers like this: