Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement tag for the naming convention of the database file name #13

Open
charliebabbitt1988 opened this issue Sep 18, 2023 · 3 comments

Comments

@charliebabbitt1988
Copy link

Please consider implementing a '-t' or '--tag' command line option that would set the naming convention for 'blue_hydra.db" to something like this in ./lib/blue_hydra.rb:

DB_NAME = tag ? "blue_hydra_#{tag}.db" : "blue_hydra.db"

I'm not sure on the exact syntax needed, but something like this for ./bin/blue_hydra:

opts.on("-t", "--tag TAG", "Specify a tag for the database name") do |t|
options[:tag] = t
end

The use case for this would be for having something resembling unique names for database files. For example, if different sessions/sqlite database files need to have different naming other than "blue_hydra.rb", then this would be helpful.

@charliebabbitt1988
Copy link
Author

charliebabbitt1988 commented Sep 18, 2023

I should clarify further. I am asking for this because I want to be able to query devices across multiple "blue_hydra.db" files, preferably tagged. Example python code follows (untested):

#!/usr/bin/python3

# this script is used for identifying targets across multiple Blue Hydra captures/databases
# it will query n number of database files and use the blue_hydra_devices table to see which devices are common to all capture sessions
# this will greatly aid in determining which devices need to be tracked
# the related address/mac address values can then be considered for tracking as known hostile or friendly devices

import sqlite3
import argparse
import os

def check_database_file(filename):
    # Check if the file exists
    if not os.path.exists(filename):
        raise argparse.ArgumentTypeError(f"'{filename}' does not exist.")

    # Check if the file is a SQLite database
    try:
        conn = sqlite3.connect(filename)
        cursor = conn.cursor()
        cursor.execute('SELECT name FROM sqlite_master WHERE type="table" AND name="blue_hydra_devices"')
        table_exists = cursor.fetchone() is not None
        conn.close()
        if not table_exists:
            raise argparse.ArgumentTypeError(f"'{filename}' does not contain the 'blue_hydra_devices' table.")
    except sqlite3.DatabaseError:
        raise argparse.ArgumentTypeError(f"'{filename}' is not a valid SQLite database.")

    return filename

def find_common_devices(database_files, use_address_only):
    if len(database_files) < 2:
        print("Please specify at least two database files to compare.")
        return

    common_devices = set()

    for database_file in database_files:
        conn = sqlite3.connect(database_file)
        cursor = conn.cursor()

        # Select either "address" or both "address" and "name" based on the command line option
        columns = ["address"] if use_address_only else ["address", "name"]
        columns_str = ', '.join(columns)

        cursor.execute(f'''
            SELECT DISTINCT {columns_str}
            FROM blue_hydra_devices
        ''')

        devices = set(cursor.fetchall())

        if not common_devices:
            common_devices = devices
        else:
            common_devices &= devices  # Intersection of sets

        conn.close()

    return common_devices

def main():
    parser = argparse.ArgumentParser(description='Find common devices between SQLite databases.')
    parser.add_argument('database_files', type=check_database_file, nargs='+',
                        help='Full path and filename(s) of SQLite database file(s) to compare.')
    parser.add_argument('--address-only', action='store_true',
                        help='Use only the address column for comparison.')
    args = parser.parse_args()

    common_devices = find_common_devices(args.database_files, args.address_only)

    # Print the common devices
    if common_devices:
        for device in common_devices:
            if args.address_only:
                print(f"Address: {device[0]}")
            else:
                print(f"Address: {device[0]}, Name: {device[1]}")
    else:
        print("No common devices found.")

if __name__ == '__main__':
    main()

@charliebabbitt1988
Copy link
Author

charliebabbitt1988 commented Oct 14, 2023

I have implemented a working version of this that I will make available in a fork.

The edits in ./bin/blue_hydra go something like this:

#!/usr/bin/env ruby
# encoding: UTF-8
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8
$0 = "BlueHydra"
Version = '1.9.17'

# add ../lib/ to load path 
#$:.unshift(File.dirname(File.expand_path('../../lib/blue_hydra.rb',__FILE__)))

# require for option parsing
require 'optparse'

# parse all command line arguments and store in options Hash for run time 
# configurations
options = {}

OptionParser.new do |opts|
  opts.on("-d", "--daemonize", "Suppress output and run in daemon mode") do |v|
    options[:daemonize] = true
  end
  opts.on("-z", "--demo", "Hide mac addresses in CLI UI") do |v|
    options[:demo] = true
  end
  opts.on("-p", "--pulse", "Send results to hermes") do |v|
    options[:pulse] = true
  end
  opts.on("--pulse-debug", "Store results in a file for review") do |v|
    options[:pulse_debug] = true
  end
  # This gets parsed directly and is not available as BlueHydra.db
  opts.on("--no-db", "Keep db in ram only") do |v|
    options[:no_db] = true
  end
  opts.on("--rssi-api", "Open 127.0.0.1:1124 to allow other processes to poll for seen devices and rssi") do |v|
    options[:signal_spitter] = true
  end
  opts.on("--no-info", "For the purposes for fox hunting, don't info scan.  Some info may be missing, but there will be less gaps during tracking") do |v|
    options[:no_info_scan] = true
  end
  opts.on("--mohawk-api", "For the purposes of making a hat to cover a mohawk, shit out the ui as json at /dev/shm/blue_hydra.json") do |v|
    options[:file_api] = true
  end
  opts.on('-t', '--tag TAG', 'Specify a tag for the database file name') do |t|
    options[:tag] = t
  end
  opts.on("-v", "--version", "Show version and quit") do |v|
    puts "#{$0}: #{Version}"
    exit
  end
  opts.on_tail("-h", "--help", "Show this message") do
    puts opts
    exit
  end
end.parse!

unless Process.uid == 0
  puts "BlueHydra must be run as root to function"
  exit 1
end

if options[:tag]
  $global_tag = options[:tag]
end

# add ../lib/ to load path 
$:.unshift(File.dirname(File.expand_path('../../lib/blue_hydra.rb',__FILE__)))

# require the actual Blue Hydra code from lib
require 'blue_hydra'

The edit in the ./lib/blue_hydra.rb file is simple:

DB_NAME = $global_tag ? "blue_hydra_#{$global_tag}.db" : "blue_hydra.db"

@ZeroChaos-
Copy link
Owner

I don't see any open PR for this, but I'd be interested.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants