hashing favicons for shodan search

2024.01.06 :: 2 min.

I recently needed to use Shodan's favicon search to get an idea what sort of device may be in a customer environment. I took this as an opportunity to play around scripting with Elixir over Python or Bash. A further explanation is provided under the code snippet below.

#!/usr/bin/env elixir


# get the favicon URL
favicon_url = 
  IO.gets("Enter the URL of the favicon to be hashed:\n")
  |> String.trim_trailing()

%{body: favicon} = Req.get!(favicon_url)

favicon_hash =
  |> Base.encode64()
  |> :binary.bin_to_list()
  # add a newline every 76 bytes (RFC 2045)
  |> Enum.chunk_every(76)
  |> Enum.map(&List.to_string/1)
  |> Enum.join("\n")
  |> then(fn str -> str <> "\n" end)
  |> Murmur.hash_x86_32()

# cast as a signed 32-bit integer
<<i32_favicon_hash::signed-integer-size(32)>> = <<favicon_hash::unsigned-integer-size(32)>>


going deeper.

Elixir's Base module does not have a MIME base64 implementation like Python's base64.encodebytes builtin but it's an easy enough spec to implement. The base64 encoding needs to have a newline inserted every 76 characters and at the very end.

Lastly, after getting the the MurmurHash it must be cast as a 32-bit signed integer in order to be properly accepted by Shodan's search API.

wrapping up.

While Elixir is not an obvious choice for scripting, the change of pace and avoiding the dependency headaches that come with Python is nice. Its much easier to keep a global asdf config in my $HOME, constraining scripts to a version of Elixir versus the bag of snakes that comes with installing dependencies in a System Python. I will leave that particular headache for the package maintainers.

Lets see where else I can apply this stack this year.

Until next time.