From a4dffe61bf61701f67a381a71bfc42109f1bf560 Mon Sep 17 00:00:00 2001
From: Ar1ste1a <107807560+Ar1ste1a@users.noreply.github.com>
Date: Wed, 14 May 2025 22:00:38 -0400
Subject: [PATCH] first commit
---
README.md | 124 +++++++
cmd/db.go | 656 +++++++++++++++++++++++++++++++++++++
cmd/query.go | 137 ++++++++
cmd/root.go | 126 +++++++
cmd/whois.go | 303 +++++++++++++++++
counseltrust.txt | 235 +++++++++++++
counseltrust2.txt | 241 ++++++++++++++
dehasher | Bin 0 -> 27540440 bytes
dehasher.go | 94 ++++++
go.mod | 54 +++
go.sum | 160 +++++++++
internal/badger/badger.go | 174 ++++++++++
internal/export/export.go | 77 +++++
internal/files/filetype.go | 44 +++
internal/pretty/tables.go | 48 +++
internal/query/client.go | 111 +++++++
internal/query/clientv2.go | 193 +++++++++++
internal/query/dehashed.go | 206 ++++++++++++
internal/query/errors.go | 33 ++
internal/sqlite/db.go | 365 +++++++++++++++++++++
internal/sqlite/gorm.go | 209 ++++++++++++
internal/sqlite/query.go | 20 ++
internal/sqlite/result.go | 90 +++++
internal/sqlite/structs.go | 108 ++++++
internal/sqlite/whois.go | 160 +++++++++
internal/whois/whois.go | 319 ++++++++++++++++++
ondefend.txt | 455 +++++++++++++++++++++++++
27 files changed, 4742 insertions(+)
create mode 100644 README.md
create mode 100644 cmd/db.go
create mode 100644 cmd/query.go
create mode 100644 cmd/root.go
create mode 100644 cmd/whois.go
create mode 100644 counseltrust.txt
create mode 100644 counseltrust2.txt
create mode 100755 dehasher
create mode 100644 dehasher.go
create mode 100644 go.mod
create mode 100644 go.sum
create mode 100644 internal/badger/badger.go
create mode 100644 internal/export/export.go
create mode 100644 internal/files/filetype.go
create mode 100644 internal/pretty/tables.go
create mode 100644 internal/query/client.go
create mode 100644 internal/query/clientv2.go
create mode 100644 internal/query/dehashed.go
create mode 100644 internal/query/errors.go
create mode 100644 internal/sqlite/db.go
create mode 100644 internal/sqlite/gorm.go
create mode 100644 internal/sqlite/query.go
create mode 100644 internal/sqlite/result.go
create mode 100644 internal/sqlite/structs.go
create mode 100644 internal/sqlite/whois.go
create mode 100644 internal/whois/whois.go
create mode 100644 ondefend.txt
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8196a7d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,124 @@
+# Dehasher
+## A cli tool built for interaction with the Dehash API
+
+
+

+
+
+# Features
+- Output Format Control
+- Request Limiting
+- Record Limiting
+- Regular Expression Handling
+- Exact Match Handling
+- Error Handling
+- Credential Dumping
+- Intelligent Token Usage
+# Options
+
+```bash-session
+usage: Dehasher [-h --help] {-k --key} {-a --authorized-email} [-h --help] [-m --max-records] [-r --max-requests] [-B --print-balance] [-X --exact-match] [-R --regex-match] [-t --list-tokens] [-o --output-file-name] [-T --output-txt] [-J --output-json] [-Y --output-yaml] [-x --output-xml] [-U --username-query] [-E --email-query] [-I --ip-address-query] [-P --password-query] [-Q --hashed-password-query] [-N --name-query] [-C --creds-only]
+
+Dehashed Tool
+
+options:
+ -h --help show this help message and exit
+ -m --max-records Maximum amount of records to return
+ -r --max-requests Maximum number of requests to make
+ -B --print-balance Print remaining balance after requests
+ -X --exact-match Use Exact Matching on fields
+ -R --regex-match Use Regex Matching on fields
+ -t --list-tokens List the number of tokens remaining
+ -o --output-file-name File to output results to
+ -T --output-txt Output to text file
+ -J --output-json Output to JSON file
+ -Y --output-yaml Output to YAML file
+ -x --output-xml Output to XML file
+ -U --username-query Username Query
+ -E --email-query Email Query
+ -I --ip-address-query IP Address Query
+ -P --password-query Password Query
+ -Q --hashed-password-query Hashed Password Query
+ -N --name-query Name Query
+ -C --creds-only Return Credentials Only
+ -k --key API Key
+ -a --authorized-email Email to pair with key for authentication
+
+
+v1.0
+```
+
+# Sample Run
+```bash-session
+-k ddq -a ar1ste1a@ -E @example.com -C -o example_creds
+Making 3 Requests for 10000 Records (30000 Total)
+ [*] Performing Request...
+ [*] Retrieved 60 Records
+[-] Not Enough Entries, ending queries
+[+] Discovered 60 Records
+ [*] Writing entries file: example_creds.json
+ [*] Success
+
+```
+
+# Getting Started
+
+To begin, clone the repository
+``` bash-session
+git clone https://github.com/Ar1ste1a/Dehasher.git
+cd Dehasher
+go build dehasher.go
+```
+
+# Crafting a query
+
+## Simple Query
+``` go
+# Provide credentials for emails matching @target.com
+dehasher -k ddq -a ar1ste1a@domain.tld -E @target.com
+```
+
+## Simple Credentials Query
+``` go
+# Provide credentials for emails matching @target.com
+dehasher -k ddq -a ar1ste1a@domain.tld -E @target.com -C
+```
+
+## Simple Query Returning Balance
+``` go
+# Provide credentials for emails matching @target.com
+dehasher -k ddq -a ar1ste1a@domain.tld -E @target.com -C -B
+```
+
+## Regex Query
+``` go
+# Return matches for emails matching this given regex query
+# -R e: Specify the '-E' field as a regex entry
+dehasher -k ddq -a ar1ste1a@domain.tld -E '[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)?@target.com' -C -B -R e
+```
+
+## Exact Match Query
+``` go
+# Return matches for usernames exactly matching "admin"
+# -X u: Specify the '-U' field as an exact match entry
+dehasher -k ddq -a ar1ste1a@domain.tld -C -B -U admin -X u
+```
+
+## Output Text (default JSON)
+``` go
+# Return matches for usernames exactly matching "admin" and write to text file 'admins_file.txt'
+dehasher -k ddq -a ar1ste1a@domain.tld -C -B -U admin -X u -T -o admins_file
+```
+
+## Output YAML
+``` go
+# Return matches for usernames exactly matching "admin" and write to yaml file 'admins_file.yaml'
+dehasher -k ddq -a ar1ste1a@domain.tld -C -B -U admin -X u -Y -o admins_file
+```
+
+## Output XML
+``` go
+# Return matches for usernames exactly matching "admin" and write to xml file 'admins_file.xml'
+dehasher -k ddq -a ar1ste1a@domain.tld -C -B -U admin -X u -x -o admins_file
+```
+
diff --git a/cmd/db.go b/cmd/db.go
new file mode 100644
index 0000000..d8361c9
--- /dev/null
+++ b/cmd/db.go
@@ -0,0 +1,656 @@
+package cmd
+
+import (
+ "dehasher/internal/export"
+ "dehasher/internal/files"
+ "dehasher/internal/pretty"
+ "dehasher/internal/sqlite"
+ "encoding/json"
+ "fmt"
+ "github.com/spf13/cobra"
+ "go.uber.org/zap"
+ "strings"
+ "time"
+)
+
+var (
+ // DB command flags
+ dbPath string
+
+ // DB query command flags
+ usernameDBQuery string
+ emailDBQuery string
+ ipDBQuery string
+ passwordDBQuery string
+ hashDBQuery string
+ nameDBQuery string
+ vinDBQuery string
+ licensePlateDBQuery string
+ addressDBQuery string
+ phoneDBQuery string
+ socialDBQuery string
+ cryptoCurrencyAddressDBQuery string
+ domainDBQuery string
+ limitResultsDB int
+ exactMatchDBQuery bool
+ outputFormatDB string
+ nonEmptyFieldsDBQuery string
+ displayFieldsDBQuery string
+ tableTypeDBQuery string
+
+ // DB runs command flags
+ startDateDBRuns string
+ endDateDBRuns string
+ containsQueryDBRuns string
+ lastXRunsDBRuns int
+
+ // DB command
+ dbCmd = &cobra.Command{
+ Use: "db",
+ Short: "Database operations for Dehasher",
+ Long: `Perform database operations like export, import, and query on the local Dehasher database.`,
+ }
+)
+
+func init() {
+ // Add subcommands to db command
+ dbCmd.AddCommand(dbExportCmd)
+ dbCmd.AddCommand(dbQueryCmd)
+ dbCmd.AddCommand(dbRunsCmd)
+ dbCmd.AddCommand(dbCredsCmd)
+
+ // Add flags specific to db command
+ dbCmd.PersistentFlags().StringVarP(&dbPath, "db-path", "D", "", "Path to database (default: ~/.local/share/Dehasher/dehashed.db)")
+
+ // Add flags specific to db query command
+ dbQueryCmd.Flags().StringVarP(&usernameDBQuery, "username", "u", "", "Filter by username")
+ dbQueryCmd.Flags().StringVarP(&emailDBQuery, "email", "e", "", "Filter by email")
+ dbQueryCmd.Flags().StringVarP(&ipDBQuery, "ip", "i", "", "Filter by IP address")
+ dbQueryCmd.Flags().StringVarP(&passwordDBQuery, "password", "p", "", "Filter by password")
+ dbQueryCmd.Flags().StringVarP(&hashDBQuery, "hash", "H", "", "Filter by hashed password")
+ dbQueryCmd.Flags().StringVarP(&nameDBQuery, "name", "n", "", "Filter by name")
+ dbQueryCmd.Flags().StringVarP(&vinDBQuery, "vin", "v", "", "Filter by VIN")
+ dbQueryCmd.Flags().StringVarP(&licensePlateDBQuery, "license", "L", "", "Filter by license plate")
+ dbQueryCmd.Flags().StringVarP(&addressDBQuery, "address", "a", "", "Filter by address")
+ dbQueryCmd.Flags().StringVarP(&phoneDBQuery, "phone", "P", "", "Filter by phone number")
+ dbQueryCmd.Flags().StringVarP(&socialDBQuery, "social", "s", "", "Filter by social media handle")
+ dbQueryCmd.Flags().StringVarP(&cryptoCurrencyAddressDBQuery, "crypto", "c", "", "Filter by cryptocurrency address")
+ dbQueryCmd.Flags().StringVarP(&domainDBQuery, "domain", "d", "", "Filter by domain/URL")
+ dbQueryCmd.Flags().IntVarP(&limitResultsDB, "limit", "l", 100, "Limit number of results")
+ dbQueryCmd.Flags().BoolVarP(&exactMatchDBQuery, "exact", "x", false, "Use exact matching instead of partial matching")
+ dbQueryCmd.Flags().StringVarP(&outputFormatDB, "format", "f", "table", "Output format (json, table, simple)")
+ dbQueryCmd.Flags().StringVar(&nonEmptyFieldsDBQuery, "non-empty", "", "Filter for non-empty fields (comma-separated list, e.g., 'password,email')")
+ dbQueryCmd.Flags().StringVar(&displayFieldsDBQuery, "display", "", "Fields to display in output (comma-separated list, e.g., 'username,email,password')")
+ dbQueryCmd.Flags().StringVarP(&tableTypeDBQuery, "table", "t", "results", "Table to query (results, runs, creds)")
+
+ // Add flags specific to db runs command
+ dbRunsCmd.Flags().StringVarP(&startDateDBRuns, "start-date", "s", "", "Start date for filtering runs (format: YYYY-MM-DD)")
+ dbRunsCmd.Flags().StringVarP(&endDateDBRuns, "end-date", "e", "", "End date for filtering runs (format: YYYY-MM-DD)")
+ dbRunsCmd.Flags().StringVarP(&containsQueryDBRuns, "contains", "c", "", "Filter runs containing this query string")
+ dbRunsCmd.Flags().IntVarP(&lastXRunsDBRuns, "last", "x", 0, "Show the last X runs")
+ dbRunsCmd.Flags().IntVarP(&limitResultsDB, "limit", "l", 100, "Limit number of results")
+ dbRunsCmd.Flags().StringVarP(&outputFormatDB, "format", "f", "table", "Output format (json, table, simple)")
+
+ // Add flags specific to db creds command
+ dbCredsCmd.Flags().StringVarP(&usernameDBQuery, "username", "u", "", "Filter by username")
+ dbCredsCmd.Flags().StringVarP(&emailDBQuery, "email", "e", "", "Filter by email")
+ dbCredsCmd.Flags().StringVarP(&passwordDBQuery, "password", "p", "", "Filter by password")
+ dbCredsCmd.Flags().IntVarP(&limitResultsDB, "limit", "l", 100, "Limit number of results")
+ dbCredsCmd.Flags().BoolVarP(&exactMatchDBQuery, "exact", "x", false, "Use exact matching instead of partial matching")
+ dbCredsCmd.Flags().StringVarP(&outputFormatDB, "format", "f", "table", "Output format (json, table, simple)")
+ dbCredsCmd.Flags().StringVar(&nonEmptyFieldsDBQuery, "non-empty", "", "Filter for non-empty fields (comma-separated list, e.g., 'password,email')")
+ dbCredsCmd.Flags().StringVar(&displayFieldsDBQuery, "display", "", "Fields to display in output (comma-separated list, e.g., 'username,email,password')")
+}
+
+// DB export command
+var dbExportCmd = &cobra.Command{
+ Use: "export",
+ Short: "Export database to file",
+ Run: func(cmd *cobra.Command, args []string) {
+ fmt.Println("Exporting database...")
+ // Create DBOptions with the provided parameters
+ options := &sqlite.DBOptions{
+ Username: usernameDBQuery,
+ Email: emailDBQuery,
+ IPAddress: ipDBQuery,
+ Password: passwordDBQuery,
+ HashedPassword: hashDBQuery,
+ Name: nameDBQuery,
+ Limit: limitResultsDB,
+ ExactMatch: exactMatchDBQuery,
+ }
+
+ // Parse non-empty fields if provided
+ if nonEmptyFieldsDBQuery != "" {
+ options.NonEmptyFields = strings.Split(nonEmptyFieldsDBQuery, ",")
+ }
+
+ // Parse display fields if provided
+ if displayFieldsDBQuery != "" {
+ options.DisplayFields = strings.Split(displayFieldsDBQuery, ",")
+ }
+
+ // Check if at least one search parameter is provided
+ if options.Username == "" && options.Email == "" && options.IPAddress == "" &&
+ options.Password == "" && options.HashedPassword == "" && options.Name == "" &&
+ len(options.NonEmptyFields) == 0 {
+ fmt.Println("Error: At least one search parameter is required.")
+ cmd.Help()
+ return
+ }
+
+ // Get the count of matching results
+ count, err := sqlite.GetResultsCount(options)
+ if err != nil {
+ fmt.Printf("Error counting results: %v\n", err)
+ return
+ }
+
+ // Query the database
+ results, err := sqlite.QueryResults(options)
+ if err != nil {
+ fmt.Printf("Error querying database: %v\n", err)
+ return
+ }
+ dhResults := sqlite.DehashedResults{Results: results}
+
+ fmt.Printf("Found %d results (showing %d):\n", count, len(results))
+
+ // Output results based on format
+ ft := files.GetFileType(outputFormatDB)
+ err = export.WriteToFile(dhResults, "dehasher_export", ft)
+ if err != nil {
+ zap.L().Error("write_to_file",
+ zap.String("message", "failed to write to file"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error writing to file: %v\n", err)
+ return
+ }
+ fmt.Printf("Exported successfully to file: dehasher_export%s\n", ft.Extension())
+ },
+}
+
+// DB query command
+var dbQueryCmd = &cobra.Command{
+ Use: "query",
+ Short: "Query local database",
+ Long: `Query the local database for previously run dehasher queries based on various parameters.`,
+ Run: func(cmd *cobra.Command, args []string) {
+ // Determine which table to query based on the tableTypeDBQuery parameter
+ switch tableTypeDBQuery {
+ case "results":
+ queryResultsTable(cmd)
+ case "runs":
+ queryRunsTable()
+ case "creds":
+ queryCredsTable(cmd)
+ default:
+ fmt.Printf("Error: Unknown table type '%s'. Valid options are: results, runs, creds\n", tableTypeDBQuery)
+ cmd.Help()
+ return
+ }
+ },
+}
+
+func queryRunsTable() {
+ // Parse date strings to time.Time
+ var startDate, endDate time.Time
+ var err error
+
+ if startDateDBRuns != "" {
+ startDate, err = time.Parse("2006-01-02", startDateDBRuns)
+ if err != nil {
+ fmt.Printf("Error parsing start date: %v\n", err)
+ return
+ }
+ }
+
+ if endDateDBRuns != "" {
+ endDate, err = time.Parse("2006-01-02", endDateDBRuns)
+ if err != nil {
+ fmt.Printf("Error parsing end date: %v\n", err)
+ return
+ }
+ // Set end date to end of day
+ endDate = endDate.Add(24*time.Hour - time.Second)
+ }
+
+ // Get the count of matching runs
+ count, err := sqlite.GetRunsCount(lastXRunsDBRuns, startDate, endDate, containsQueryDBRuns)
+ if err != nil {
+ fmt.Printf("Error counting runs: %v\n", err)
+ return
+ }
+
+ // Query the database
+ runs, err := sqlite.QueryRuns(limitResultsDB, lastXRunsDBRuns, startDate, endDate, containsQueryDBRuns)
+ if err != nil {
+ fmt.Printf("Error querying runs: %v\n", err)
+ return
+ }
+
+ displayRunsResults(count, runs)
+}
+
+func displayRunsResults(count int64, runs []sqlite.QueryOptions) {
+ // Display the results
+ fmt.Printf("Found %d runs (showing %d):\n", count, len(runs))
+
+ if len(runs) == 0 {
+ fmt.Println("No runs found.")
+ return
+ }
+
+ // Output results based on format
+ switch outputFormatDB {
+ case "json":
+ data, err := json.MarshalIndent(runs, "", " ")
+ if err != nil {
+ fmt.Printf("Error formatting results: %v\n", err)
+ return
+ }
+ fmt.Println(string(data))
+ case "table":
+ // Define headers and rows for the table
+ headers := []string{"ID", "Created At", "Max Records", "Username Query", "Email Query", "IP Query", "Password Query", "Hash Query", "Name Query", "Domain Query"}
+ rows := make([][]string, len(runs))
+
+ for i, run := range runs {
+ rows[i] = []string{
+ fmt.Sprintf("%d", run.ID),
+ run.CreatedAt.Format("2006-01-02 15:04:05"),
+ fmt.Sprintf("%d", run.MaxRecords),
+ truncate(run.UsernameQuery, 20),
+ truncate(run.EmailQuery, 20),
+ truncate(run.IpQuery, 20),
+ truncate(run.PassQuery, 20),
+ truncate(run.HashQuery, 20),
+ truncate(run.NameQuery, 20),
+ truncate(run.DomainQuery, 20),
+ }
+ }
+
+ pretty.Table(headers, rows)
+ default:
+ // Simple output
+ for _, run := range runs {
+ fmt.Printf("Run ID: %d\n", run.ID)
+ fmt.Printf(" Created At: %s\n", run.CreatedAt.Format("2006-01-02 15:04:05"))
+ fmt.Printf(" Max Records: %d\n", run.MaxRecords)
+ fmt.Printf(" Max Requests: %d\n", run.MaxRequests)
+ fmt.Printf(" Starting Page: %d\n", run.StartingPage)
+ fmt.Printf(" Output Format: %s\n", run.OutputFormat.String())
+ fmt.Printf(" Output File: %s\n", run.OutputFile)
+ fmt.Printf(" Regex Match: %t\n", run.RegexMatch)
+ fmt.Printf(" Wildcard Match: %t\n", run.WildcardMatch)
+ fmt.Printf(" Username Query: %s\n", run.UsernameQuery)
+ fmt.Printf(" Email Query: %s\n", run.EmailQuery)
+ fmt.Printf(" IP Query: %s\n", run.IpQuery)
+ fmt.Printf(" Password Query: %s\n", run.PassQuery)
+ fmt.Printf(" Hash Query: %s\n", run.HashQuery)
+ fmt.Printf(" Name Query: %s\n", run.NameQuery)
+ fmt.Printf(" Domain Query: %s\n", run.DomainQuery)
+ fmt.Printf(" VIN Query: %s\n", run.VinQuery)
+ fmt.Printf(" License Plate Query: %s\n", run.LicensePlateQuery)
+ fmt.Printf(" Address Query: %s\n", run.AddressQuery)
+ fmt.Printf(" Phone Query: %s\n", run.PhoneQuery)
+ fmt.Printf(" Social Query: %s\n", run.SocialQuery)
+ fmt.Printf(" Crypto Address Query: %s\n", run.CryptoAddressQuery)
+ fmt.Printf(" Print Balance: %t\n", run.PrintBalance)
+ fmt.Printf(" Creds Only: %t\n", run.CredsOnly)
+ fmt.Println()
+ }
+ }
+}
+
+// queryResultsTable queries the results table
+func queryResultsTable(cmd *cobra.Command) {
+ // Create DBOptions with the provided parameters
+ options := &sqlite.DBOptions{
+ Username: usernameDBQuery,
+ Email: emailDBQuery,
+ IPAddress: ipDBQuery,
+ Password: passwordDBQuery,
+ HashedPassword: hashDBQuery,
+ Name: nameDBQuery,
+ Vin: vinDBQuery,
+ LicensePlate: licensePlateDBQuery,
+ Address: addressDBQuery,
+ Phone: phoneDBQuery,
+ Social: socialDBQuery,
+ CryptoCurrencyAddress: cryptoCurrencyAddressDBQuery,
+ Domain: domainDBQuery,
+ Limit: limitResultsDB,
+ ExactMatch: exactMatchDBQuery,
+ }
+
+ // Parse non-empty fields if provided
+ if nonEmptyFieldsDBQuery != "" {
+ options.NonEmptyFields = strings.Split(nonEmptyFieldsDBQuery, ",")
+ }
+
+ // Parse display fields if provided
+ if displayFieldsDBQuery != "" {
+ options.DisplayFields = strings.Split(displayFieldsDBQuery, ",")
+ }
+
+ // Check if at least one search parameter is provided
+ if options.Empty() {
+ fmt.Println("Error: At least one search parameter is required.")
+ cmd.Help()
+ return
+ }
+
+ // Get the count of matching results
+ count, err := sqlite.GetResultsCount(options)
+ if err != nil {
+ fmt.Printf("Error counting results: %v\n", err)
+ return
+ }
+
+ // Query the database
+ results, err := sqlite.QueryResults(options)
+ if err != nil {
+ fmt.Printf("Error querying database: %v\n", err)
+ return
+ }
+
+ // Display the results
+ displayResultsTable(count, results, options)
+}
+
+// displayResultsTable displays the results from the results table
+func displayResultsTable(count int64, results []sqlite.Result, options *sqlite.DBOptions) {
+ // Display the results
+ fmt.Printf("Found %d results (showing %d):\n", count, len(results))
+
+ if len(results) == 0 {
+ fmt.Println("No results found.")
+ return
+ }
+
+ // Output results based on format
+ switch outputFormatDB {
+ case "json":
+ data, err := json.MarshalIndent(results, "", " ")
+ if err != nil {
+ fmt.Printf("Error formatting results: %v\n", err)
+ return
+ }
+ fmt.Println(string(data))
+ case "table":
+ // Determine which fields to display
+ type FieldInfo struct {
+ Name string
+ Width int
+ Getter func(result sqlite.Result) string
+ }
+
+ // Define all available fields
+ allFields := []FieldInfo{
+ {"Username", 20, func(r sqlite.Result) string { return truncate(arrayToString(r.Username), 20) }},
+ {"Email", 30, func(r sqlite.Result) string { return truncate(arrayToString(r.Email), 30) }},
+ {"IP Address", 15, func(r sqlite.Result) string { return truncate(arrayToString(r.IpAddress), 15) }},
+ {"Password", 20, func(r sqlite.Result) string { return truncate(arrayToString(r.Password), 20) }},
+ {"Hashed Password", 20, func(r sqlite.Result) string { return truncate(arrayToString(r.HashedPassword), 20) }},
+ {"Name", 20, func(r sqlite.Result) string { return truncate(arrayToString(r.Name), 20) }},
+ {"VIN", 20, func(r sqlite.Result) string { return truncate(arrayToString(r.Vin), 20) }},
+ {"License Plate", 15, func(r sqlite.Result) string { return truncate(arrayToString(r.LicensePlate), 15) }},
+ {"Address", 30, func(r sqlite.Result) string { return truncate(arrayToString(r.Address), 30) }},
+ {"Phone", 15, func(r sqlite.Result) string { return truncate(arrayToString(r.Phone), 15) }},
+ {"Social", 20, func(r sqlite.Result) string { return truncate(arrayToString(r.Social), 20) }},
+ {"Crypto Address", 20, func(r sqlite.Result) string { return truncate(arrayToString(r.CryptoCurrencyAddress), 20) }},
+ {"Domain/URL", 30, func(r sqlite.Result) string { return truncate(arrayToString(r.Url), 30) }},
+ }
+
+ // Select fields to display
+ var fieldsToDisplay []FieldInfo
+ var headers []string
+ if len(options.DisplayFields) > 0 {
+ // Use specified fields
+ for _, fieldName := range options.DisplayFields {
+ fieldName = strings.ToLower(strings.TrimSpace(fieldName))
+ for _, field := range allFields {
+ if strings.ToLower(field.Name) == fieldName ||
+ (fieldName == "ip" && strings.ToLower(field.Name) == "ip address") ||
+ (fieldName == "hash" && strings.ToLower(field.Name) == "hashed password") ||
+ (fieldName == "license" && strings.ToLower(field.Name) == "license plate") ||
+ (fieldName == "crypto" && strings.ToLower(field.Name) == "crypto address") ||
+ (fieldName == "url" && strings.ToLower(field.Name) == "domain/url") {
+ fieldsToDisplay = append(fieldsToDisplay, field)
+ headers = append(headers, field.Name)
+ break
+ }
+ }
+ }
+ } else {
+ // Default fields (first 6)
+ fieldsToDisplay = allFields[:6]
+ }
+
+ var rows [][]string
+ for _, result := range results {
+ rowValues := []string{}
+ for _, field := range fieldsToDisplay {
+ rowValues = append(rowValues, field.Getter(result))
+ }
+ rows = append(rows, rowValues)
+ }
+
+ pretty.Table(headers, rows)
+ default:
+ // Simple output
+ for i, result := range results {
+ fmt.Printf("Result %d:\n", i+1)
+
+ // Determine which fields to display
+ if len(options.DisplayFields) > 0 {
+ // Display only specified fields
+ for _, field := range options.DisplayFields {
+ field = strings.ToLower(strings.TrimSpace(field))
+ switch field {
+ case "username":
+ fmt.Printf(" Username: %s\n", result.Username)
+ case "email":
+ fmt.Printf(" Email: %s\n", result.Email)
+ case "ip", "ipaddress", "ip_address":
+ fmt.Printf(" IP Address: %s\n", result.IpAddress)
+ case "password":
+ fmt.Printf(" Password: %s\n", result.Password)
+ case "hash", "hashed_password":
+ fmt.Printf(" Hashed Password: %s\n", result.HashedPassword)
+ case "name":
+ fmt.Printf(" Name: %s\n", result.Name)
+ case "vin":
+ fmt.Printf(" VIN: %s\n", result.Vin)
+ case "license", "license_plate":
+ fmt.Printf(" License Plate: %s\n", result.LicensePlate)
+ case "address":
+ fmt.Printf(" Address: %s\n", result.Address)
+ case "phone":
+ fmt.Printf(" Phone: %s\n", result.Phone)
+ case "social":
+ fmt.Printf(" Social: %s\n", result.Social)
+ case "crypto", "cryptocurrency_address":
+ fmt.Printf(" Crypto Address: %s\n", result.CryptoCurrencyAddress)
+ case "domain", "url":
+ fmt.Printf(" Domain/URL: %s\n", result.Url)
+ }
+ }
+ } else {
+ // Display default fields
+ fmt.Printf(" Username: %s\n", result.Username)
+ fmt.Printf(" Email: %s\n", result.Email)
+ fmt.Printf(" IP Address: %s\n", result.IpAddress)
+ fmt.Printf(" Password: %s\n", result.Password)
+ fmt.Printf(" Hashed Password: %s\n", result.HashedPassword)
+ fmt.Printf(" Name: %s\n", result.Name)
+ }
+ fmt.Println()
+ }
+ }
+}
+
+// truncate truncates a string to the specified length and adds ellipsis if needed
+func truncate(s string, length int) string {
+ if len(s) <= length {
+ return s
+ }
+ return s[:length-3] + "..."
+}
+
+func arrayToString(a []string) string {
+ return strings.Join(a, ", ")
+}
+
+// DB runs command
+var dbRunsCmd = &cobra.Command{
+ Use: "runs",
+ Short: "Query previous query runs",
+ Long: `Query the database for previous query runs (QueryOptions) based on date range and query content.`,
+ Run: func(cmd *cobra.Command, args []string) {
+ // Call queryRunsTable directly
+ queryRunsTable()
+ },
+}
+
+// DB creds command
+var dbCredsCmd = &cobra.Command{
+ Use: "creds",
+ Short: "Query credentials",
+ Long: `Query the database for credentials based on username, email, and password.`,
+ Run: func(cmd *cobra.Command, args []string) {
+ // Call queryCredsTable directly
+ queryCredsTable(cmd)
+ },
+}
+
+// queryCredsTable queries the credentials table
+func queryCredsTable(cmd *cobra.Command) {
+ // Create DBOptions with the provided parameters
+ options := &sqlite.DBOptions{
+ Username: usernameDBQuery,
+ Email: emailDBQuery,
+ Password: passwordDBQuery,
+ Limit: limitResultsDB,
+ ExactMatch: exactMatchDBQuery,
+ }
+
+ // Parse non-empty fields if provided
+ if nonEmptyFieldsDBQuery != "" {
+ options.NonEmptyFields = strings.Split(nonEmptyFieldsDBQuery, ",")
+ }
+
+ // Parse display fields if provided
+ if displayFieldsDBQuery != "" {
+ options.DisplayFields = strings.Split(displayFieldsDBQuery, ",")
+ }
+
+ // Check if at least one search parameter is provided
+ if options.Username == "" && options.Email == "" && options.Password == "" && len(options.NonEmptyFields) == 0 {
+ fmt.Println("Error: At least one search parameter is required.")
+ cmd.Help()
+ return
+ }
+
+ // Get the count of matching credentials
+ count, err := sqlite.GetCredsCount(options)
+ if err != nil {
+ fmt.Printf("Error counting credentials: %v\n", err)
+ return
+ }
+
+ // Query the database
+ creds, err := sqlite.QueryCreds(options)
+ if err != nil {
+ fmt.Printf("Error querying credentials: %v\n", err)
+ return
+ }
+
+ // Display the results
+ displayCredsResults(count, creds)
+}
+
+// displayCredsResults displays the results from the creds table
+func displayCredsResults(count int64, creds []sqlite.Creds) {
+ // Display the results
+ fmt.Printf("Found %d credentials (showing %d):\n", count, len(creds))
+
+ if len(creds) == 0 {
+ fmt.Println("No credentials found.")
+ return
+ }
+
+ // Output results based on format
+ switch outputFormatDB {
+ case "json":
+ data, err := json.MarshalIndent(creds, "", " ")
+ if err != nil {
+ fmt.Printf("Error formatting results: %v\n", err)
+ return
+ }
+ fmt.Println(string(data))
+ case "table":
+ // Define all available fields
+ type FieldInfo struct {
+ Name string
+ Getter func(cred sqlite.Creds) string
+ }
+
+ allFields := []FieldInfo{
+ {"ID", func(c sqlite.Creds) string { return fmt.Sprintf("%d", c.ID) }},
+ {"Created At", func(c sqlite.Creds) string { return c.CreatedAt.Format("2006-01-02 15:04:05") }},
+ {"Email", func(c sqlite.Creds) string { return c.Email }},
+ {"Username", func(c sqlite.Creds) string { return c.Username }},
+ {"Password", func(c sqlite.Creds) string { return c.Password }},
+ }
+
+ // Select fields to display
+ var fieldsToDisplay []FieldInfo
+ var headers []string
+
+ if len(displayFieldsDBQuery) > 0 {
+ // Use specified display fields
+ displayFields := strings.Split(displayFieldsDBQuery, ",")
+ for _, fieldName := range displayFields {
+ fieldName = strings.ToLower(strings.TrimSpace(fieldName))
+ for _, field := range allFields {
+ if strings.ToLower(field.Name) == fieldName {
+ fieldsToDisplay = append(fieldsToDisplay, field)
+ headers = append(headers, field.Name)
+ break
+ }
+ }
+ }
+ } else {
+ // Default fields
+ fieldsToDisplay = allFields
+ for _, field := range fieldsToDisplay {
+ headers = append(headers, field.Name)
+ }
+ }
+
+ // Create rows
+ rows := make([][]string, len(creds))
+ for i, cred := range creds {
+ rowValues := []string{}
+ for _, field := range fieldsToDisplay {
+ rowValues = append(rowValues, field.Getter(cred))
+ }
+ rows[i] = rowValues
+ }
+
+ pretty.Table(headers, rows)
+ default:
+ // Simple output
+ for _, cred := range creds {
+ fmt.Printf("Credential ID: %d\n", cred.ID)
+ fmt.Printf(" Created At: %s\n", cred.CreatedAt.Format("2006-01-02 15:04:05"))
+ fmt.Printf(" Email: %s\n", cred.Email)
+ fmt.Printf(" Username: %s\n", cred.Username)
+ fmt.Printf(" Password: %s\n", cred.Password)
+ fmt.Println()
+ }
+ }
+}
diff --git a/cmd/query.go b/cmd/query.go
new file mode 100644
index 0000000..cd9860e
--- /dev/null
+++ b/cmd/query.go
@@ -0,0 +1,137 @@
+package cmd
+
+import (
+ "dehasher/internal/badger"
+ "dehasher/internal/query"
+ "dehasher/internal/sqlite"
+ "fmt"
+ "github.com/spf13/cobra"
+)
+
+var (
+ // Query command flags
+ maxRecords int
+ maxRequests int
+ startingPage int
+ credsOnly bool
+ printBalance bool
+ regexMatch bool
+ wildcardMatch bool
+ outputFormat string
+ outputFile string
+ usernameQuery string
+ emailQuery string
+ ipQuery string
+ passwordQuery string
+ hashQuery string
+ nameQuery string
+ domainQuery string
+ vinQuery string
+ licensePlateQuery string
+ addressQuery string
+ phoneQuery string
+ socialQuery string
+ cryptoCurrencyAddressQuery string
+
+ // Query command
+ queryCmd = &cobra.Command{
+ Use: "query",
+ Short: "Query the Dehashed API",
+ Long: `Query the Dehashed API for emails, usernames, passwords, hashes, IP addresses, and names.`,
+ Run: func(cmd *cobra.Command, args []string) {
+ // Check if API key and email are provided
+ key := apiKey
+ email := apiEmail
+
+ // If not provided as flags, try to get from stored values
+ if key == "" {
+ key = getStoredApiKey()
+ }
+ if email == "" {
+ email = getStoredApiEmail()
+ }
+
+ // Validate credentials
+ if key == "" || email == "" {
+ fmt.Println("API key and email are required. Use --key and --email flags or set them with set-key and set-email commands.")
+ return
+ }
+
+ // Create new QueryOptions
+ queryOptions := sqlite.NewQueryOptions(
+ maxRecords,
+ maxRequests,
+ startingPage,
+ outputFormat,
+ outputFile,
+ usernameQuery,
+ emailQuery,
+ ipQuery,
+ passwordQuery,
+ hashQuery,
+ nameQuery,
+ domainQuery,
+ vinQuery,
+ licensePlateQuery,
+ addressQuery,
+ phoneQuery,
+ socialQuery,
+ cryptoCurrencyAddressQuery,
+ regexMatch,
+ wildcardMatch,
+ printBalance,
+ credsOnly,
+ )
+
+ // Create new Dehasher
+ dehasher := query.NewDehasher(queryOptions)
+ dehasher.SetClientCredentials(
+ key,
+ )
+
+ // Start querying
+ dehasher.Start()
+ fmt.Println("\n[*] Completing Process")
+
+ sqlite.StoreQueryOptions(queryOptions)
+ },
+ }
+)
+
+func init() {
+ // Add flags specific to query command
+ queryCmd.Flags().IntVarP(&maxRecords, "max-records", "m", 30000, "Maximum amount of records to return")
+ queryCmd.Flags().IntVarP(&maxRequests, "max-requests", "r", -1, "Maximum number of requests to make")
+ queryCmd.Flags().IntVarP(&startingPage, "starting-page", "s", 1, "Starting page for requests")
+ queryCmd.Flags().BoolVarP(&printBalance, "print-balance", "b", false, "Print remaining balance after requests")
+ queryCmd.Flags().BoolVarP(®exMatch, "regex-match", "R", false, "Use regex matching on query fields")
+ queryCmd.Flags().BoolVarP(&wildcardMatch, "wildcard-match", "W", false, "Use wildcard matching on query fields (Use ? to replace a single character, and * for multiple characters)")
+ queryCmd.Flags().BoolVarP(&credsOnly, "creds-only", "C", false, "Return credentials only")
+ queryCmd.Flags().StringVarP(&outputFormat, "format", "f", "json", "Output format (json, yaml, xml, txt)")
+ queryCmd.Flags().StringVarP(&outputFile, "output", "o", "query", "File to output results to including extension")
+ queryCmd.Flags().StringVarP(&usernameQuery, "username", "U", "", "Username query")
+ queryCmd.Flags().StringVarP(&emailQuery, "email-query", "E", "", "Email query")
+ queryCmd.Flags().StringVarP(&ipQuery, "ip", "I", "", "IP address query")
+ queryCmd.Flags().StringVarP(&domainQuery, "domain", "D", "", "Domain query")
+ queryCmd.Flags().StringVarP(&passwordQuery, "password", "P", "", "Password query")
+ queryCmd.Flags().StringVarP(&vinQuery, "vin", "V", "", "VIN query")
+ queryCmd.Flags().StringVarP(&licensePlateQuery, "license", "L", "", "License plate query")
+ queryCmd.Flags().StringVarP(&addressQuery, "address", "A", "", "Address query")
+ queryCmd.Flags().StringVarP(&phoneQuery, "phone", "M", "", "Phone query")
+ queryCmd.Flags().StringVarP(&socialQuery, "social", "S", "", "Social query")
+ queryCmd.Flags().StringVarP(&cryptoCurrencyAddressQuery, "crypto", "B", "", "Crypto currency address query")
+ queryCmd.Flags().StringVarP(&hashQuery, "hash", "Q", "", "Hashed password query")
+ queryCmd.Flags().StringVarP(&nameQuery, "name", "N", "", "Name query")
+
+ // Add mutually exclusive flags to exact match and regex match
+ queryCmd.MarkFlagsMutuallyExclusive("regex-match", "wildcard-match")
+}
+
+// Helper functions to get stored API credentials
+func getStoredApiKey() string {
+ return badger.GetKey()
+}
+
+func getStoredApiEmail() string {
+ return badger.GetEmail()
+}
diff --git a/cmd/root.go b/cmd/root.go
new file mode 100644
index 0000000..e651346
--- /dev/null
+++ b/cmd/root.go
@@ -0,0 +1,126 @@
+package cmd
+
+import (
+ "dehasher/internal/badger"
+ "fmt"
+ "github.com/spf13/cobra"
+ "go.uber.org/zap"
+ "os"
+)
+
+var (
+ // Global Flags
+ apiKey string
+ apiEmail string
+
+ // rootCmd is the base command for the CLI.
+ rootCmd = &cobra.Command{
+ Use: "dehasher",
+ Short: `Dehasher is a cli tool for querying query.`,
+ Long: fmt.Sprintf(
+ "%s\n%s",
+ `
+ ______ _______ _______ _______ _______ _______
+( __ \ ( ____ \|\ /|( ___ )( ____ \|\ /|( ____ \( ____ )
+| ( \ )| ( \/| ) ( || ( ) || ( \/| ) ( || ( \/| ( )|
+| | ) || (__ | (___) || (___) || (_____ | (___) || (__ | (____)|
+| | | || __) | ___ || ___ |(_____ )| ___ || __) | __)
+| | ) || ( | ( ) || ( ) | ) || ( ) || ( | (\ (
+| (__/ )| (____/\| ) ( || ) ( |/\____) || ) ( || (____/\| ) \ \__
+(______/ (_______/|/ \||/ \|\_______)|/ \|(_______/|/ \__/
+ An Ar1ste1a Project
+`,
+ `––•–√\/––√\/––•––––•–√\/––√\/––•––––•–√\/––√\/––•––√\/––•––––•–√\/––√\/––•––
+ Dehasher can query the query API for:
+ - Emails - Usernames - Password
+ - Hashes - IP Addresses - Names
+ - VINs - License Plates - Addresses
+ - Phones - Social Media - Crypto Currency Addresses
+ Dehasher supports:
+ - Regex Matching
+ - Exact Matching
+––•–√\/––√\/––•––––•–√\/––√\/––•––––•–√\/––√\/––•––√\/––•––––•–√\/––√\/––•––
+`,
+ ),
+ Version: "v1.0",
+ }
+)
+
+// Execute adds all child commands to the root command and sets flags appropriately.
+func Execute() {
+ if err := rootCmd.Execute(); err != nil {
+ zap.L().Fatal("execute_root_command",
+ zap.String("message", "failed to execute root command"),
+ zap.Error(err),
+ )
+ fmt.Printf("[!] %v", err)
+ os.Exit(1)
+ }
+}
+
+func init() {
+ // Hide the default help command
+ rootCmd.CompletionOptions.HiddenDefaultCmd = true
+
+ // Add global flags for API key and email
+ rootCmd.PersistentFlags().StringVarP(&apiKey, "key", "k", "", "API Key for authentication")
+
+ // Add subcommands
+ rootCmd.AddCommand(dbCmd)
+ rootCmd.AddCommand(queryCmd)
+ rootCmd.AddCommand(setKeyCmd)
+ rootCmd.AddCommand(setEmailCmd)
+}
+
+// Command to set API key
+var setKeyCmd = &cobra.Command{
+ Use: "set-key [key]",
+ Short: "Set and store API key",
+ Args: cobra.ExactArgs(1),
+ Run: func(cmd *cobra.Command, args []string) {
+ key := args[0]
+ // Store key in badger DB
+ err := storeApiKey(key)
+ if err != nil {
+ fmt.Printf("Error storing API key: %v\n", err)
+ return
+ }
+ fmt.Println("API key stored successfully")
+ },
+}
+
+// Command to set API email
+var setEmailCmd = &cobra.Command{
+ Use: "set-email [email]",
+ Short: "Set and store API email",
+ Args: cobra.ExactArgs(1),
+ Run: func(cmd *cobra.Command, args []string) {
+ email := args[0]
+ // Store email in badger DB
+ err := storeApiEmail(email)
+ if err != nil {
+ fmt.Printf("Error storing API email: %v\n", err)
+ return
+ }
+ fmt.Println("API email stored successfully")
+ },
+}
+
+// Helper functions to store API credentials
+func storeApiKey(key string) error {
+ err := badger.StoreKey(key)
+ if err != nil {
+ fmt.Printf("Error storing API key: %v\n", err)
+ return err
+ }
+ return nil
+}
+
+func storeApiEmail(email string) error {
+ err := badger.StoreEmail(email)
+ if err != nil {
+ fmt.Printf("Error storing API email: %v\n", err)
+ return err
+ }
+ return nil
+}
diff --git a/cmd/whois.go b/cmd/whois.go
new file mode 100644
index 0000000..ef58098
--- /dev/null
+++ b/cmd/whois.go
@@ -0,0 +1,303 @@
+package cmd
+
+import (
+ "dehasher/internal/sqlite"
+ "dehasher/internal/whois"
+ "fmt"
+ "github.com/spf13/cobra"
+ "go.uber.org/zap"
+ "strings"
+)
+
+var (
+ // WHOIS command flags
+ whoisDomain string
+ whoisIPAddress string
+ whoisMXAddress string
+ whoisNSAddress string
+ whoisInclude string
+ whoisExclude string
+ whoisReverseType string
+ whoisOutputFormat string
+ whoisShowCredits bool
+ whoisHistory bool
+ whoisSubdomainScan bool
+
+ // WHOIS command
+ whoisCmd = &cobra.Command{
+ Use: "whois",
+ Short: "Dehashed WHOIS lookups and reverse WHOIS searches",
+ Long: `Perform WHOIS lookups, history searches, reverse WHOIS searches, IP lookups, MX lookups, NS lookups, and subdomain scans.`,
+ Run: func(cmd *cobra.Command, args []string) {
+ // Check if API key is provided
+ key := apiKey
+
+ // If not provided as flag, try to get from stored value
+ if key == "" {
+ key = getStoredApiKey()
+ }
+
+ // Validate credentials
+ if key == "" {
+ fmt.Println("API key is required. Use --key flag or set it with set-key command.")
+ return
+ }
+
+ // Show credits if requested
+ if whoisShowCredits {
+ fmt.Println("[*] Getting WHOIS credits...")
+ credits, err := whois.GetWHOISCredits(key)
+ if err != nil {
+ zap.L().Error("get_whois_credits",
+ zap.String("message", "failed to get whois credits"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error getting WHOIS credits: %v\n", err)
+ return
+ }
+ fmt.Printf("WHOIS Credits: %d\n", credits.WhoisCredits)
+ return
+ }
+
+ // Check if domain is provided for history and subdomain scan
+ if whoisHistory || whoisSubdomainScan {
+ if whoisDomain == "" {
+ fmt.Println("Domain is required for history and subdomain scan.")
+ return
+ }
+ }
+
+ // Determine which operation to perform based on flags
+ if whoisDomain != "" {
+ fmt.Println("[*] Performing WHOIS lookup...")
+ // Domain lookup
+ result, err := whois.WhoisSearch(whoisDomain, key)
+ if err != nil {
+ zap.L().Error("whois_search",
+ zap.String("message", "failed to perform whois search"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error performing WHOIS lookup: %v\n", err)
+ return
+ }
+
+ // Fix the output format to use proper formatting
+ fmt.Printf("WHOIS Lookup Result:\n%+v\n", result.Data.WhoisRecord)
+
+ // Store the record
+ err = sqlite.StoreWhoisRecord(result.Data.WhoisRecord)
+ if err != nil {
+ zap.L().Error("store_whois_record",
+ zap.String("message", "failed to store whois record"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error storing WHOIS record: %v\n", err)
+ // Continue execution even if storage fails
+ }
+
+ if whoisHistory {
+ fmt.Println("[*] Performing WHOIS history search...")
+ // Perform history search
+ history, err := whois.WhoisHistory(whoisDomain, key)
+ if err != nil {
+ zap.L().Error("whois_history",
+ zap.String("message", "failed to perform whois history lookup"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error performing WHOIS history lookup: %v\n", err)
+ } else {
+ fmt.Println("\nWHOIS History:")
+ fmt.Println(history)
+ }
+
+ err = sqlite.StoreHistoryRecord(history.Data.Records)
+ if err != nil {
+ zap.L().Error("store_history_record",
+ zap.String("message", "failed to store history record"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error storing WHOIS history record: %v\n", err)
+ }
+ }
+
+ // Perform subdomain scan
+ if whoisSubdomainScan {
+ fmt.Println("[*] Performing WHOIS subdomain scan...")
+ subdomains, err := whois.WhoisSubdomainScan(whoisDomain, key)
+ if err != nil {
+ zap.L().Error("whois_subdomain_scan",
+ zap.String("message", "failed to perform subdomain scan"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error performing subdomain scan: %v\n", err)
+ } else {
+ fmt.Println("\nSubdomain Scan:")
+ fmt.Println(subdomains)
+ }
+
+ err = sqlite.StoreSubdomainRecord(subdomains.Data.Result.Records)
+ if err != nil {
+ zap.L().Error("store_subdomain_record",
+ zap.String("message", "failed to store subdomain record"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error storing WHOIS subdomain record: %v\n", err)
+ }
+ }
+
+ // Get credits
+ credits, err := whois.GetWHOISCredits(key)
+ if err != nil {
+ zap.L().Error("get_whois_credits",
+ zap.String("message", "failed to get whois credits"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error getting WHOIS credits: %v\n", err)
+ return
+ }
+ fmt.Printf("\nWHOIS Credits Remaining: %d\n", credits.WhoisCredits)
+ return
+ }
+
+ if whoisIPAddress != "" {
+ fmt.Println("[*] Performing reverse IP lookup...")
+ // IP lookup
+ result, err := whois.WhoisIP(whoisIPAddress, key)
+ if err != nil {
+ zap.L().Error("whois_ip",
+ zap.String("message", "failed to perform ip lookup"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error performing IP lookup: %v\n", err)
+ return
+ }
+ fmt.Println("IP Lookup Result:")
+ fmt.Println(string(result))
+
+ // Get credits
+ credits, err := whois.GetWHOISCredits(key)
+ if err != nil {
+ zap.L().Error("get_whois_credits",
+ zap.String("message", "failed to get whois credits"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error getting WHOIS credits: %v\n", err)
+ return
+ }
+ fmt.Printf("\nWHOIS Credits Remaining: %d\n", credits.WhoisCredits)
+ return
+ }
+
+ if whoisMXAddress != "" {
+ fmt.Println("[*] Performing reverse MX lookup...")
+ // MX lookup
+ result, err := whois.WhoisMX(whoisMXAddress, key)
+ if err != nil {
+ zap.L().Error("whois_mx",
+ zap.String("message", "failed to perform mx lookup"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error performing MX lookup: %v\n", err)
+ return
+ }
+
+ // todo unmarshal mx lookup
+ fmt.Println("MX Lookup Result:")
+ fmt.Println(result)
+
+ // Get credits
+ credits, err := whois.GetWHOISCredits(key)
+ if err != nil {
+ zap.L().Error("get_whois_credits",
+ zap.String("message", "failed to get whois credits"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error getting WHOIS credits: %v\n", err)
+ return
+ }
+ fmt.Printf("\nWHOIS Credits Remaining: %d\n", credits.WhoisCredits)
+ return
+ }
+
+ if whoisNSAddress != "" {
+ fmt.Println("[*] Performing reverse NS lookup...")
+ // NS lookup
+ result, err := whois.WhoisNS(whoisNSAddress, key)
+ if err != nil {
+ zap.L().Error("whois_ns",
+ zap.String("message", "failed to perform ns lookup"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error performing NS lookup: %v\n", err)
+ return
+ }
+ fmt.Println("NS Lookup Result:")
+ fmt.Println(result)
+
+ // Get credits
+ credits, err := whois.GetWHOISCredits(key)
+ if err != nil {
+ zap.L().Error("get_whois_credits",
+ zap.String("message", "failed to get whois credits"),
+ zap.Error(err),
+ )
+ fmt.Printf("Error getting WHOIS credits: %v\n", err)
+ return
+ }
+ fmt.Printf("\nWHOIS Credits Remaining: %d\n", credits.WhoisCredits)
+ return
+ }
+
+ if whoisInclude != "" || whoisExclude != "" {
+ // Reverse WHOIS
+ includeTerms := []string{}
+ if whoisInclude != "" {
+ includeTerms = strings.Split(whoisInclude, ",")
+ }
+
+ excludeTerms := []string{}
+ if whoisExclude != "" {
+ excludeTerms = strings.Split(whoisExclude, ",")
+ }
+
+ if whoisReverseType == "" {
+ whoisReverseType = "registrant"
+ }
+
+ fmt.Println("[*] Performing reverse WHOIS lookup...")
+ result, err := whois.ReverseWHOIS(includeTerms, excludeTerms, whoisReverseType, key)
+ if err != nil {
+ fmt.Printf("Error performing reverse WHOIS: %v\n", err)
+ return
+ }
+ fmt.Println("Reverse WHOIS Result:")
+ fmt.Println(result)
+ return
+ }
+
+ // If no specific operation was requested
+ cmd.Help()
+ },
+ }
+)
+
+func init() {
+ // Add whois command to root command
+ rootCmd.AddCommand(whoisCmd)
+
+ // Add flags specific to whois command
+ whoisCmd.Flags().StringVarP(&whoisDomain, "domain", "d", "", "Domain for WHOIS lookup, history search, and subdomain scan")
+ whoisCmd.Flags().StringVarP(&whoisIPAddress, "ip", "i", "", "IP address for reverse IP lookup")
+ whoisCmd.Flags().StringVarP(&whoisMXAddress, "mx", "m", "", "MX address for reverse MX lookup")
+ whoisCmd.Flags().StringVarP(&whoisNSAddress, "ns", "n", "", "NS address for reverse NS lookup")
+ whoisCmd.Flags().StringVarP(&whoisInclude, "include", "I", "", "Terms to include in reverse WHOIS search (comma-separated)")
+ whoisCmd.Flags().StringVarP(&whoisExclude, "exclude", "E", "", "Terms to exclude in reverse WHOIS search (comma-separated)")
+ whoisCmd.Flags().StringVarP(&whoisReverseType, "type", "t", "registrant", "Type of reverse WHOIS search (registrant, email, organization, address, phone)")
+ whoisCmd.Flags().StringVarP(&whoisOutputFormat, "format", "f", "text", "Output format (text, json)")
+ whoisCmd.Flags().BoolVarP(&whoisShowCredits, "credits", "c", false, "Show remaining WHOIS credits")
+ whoisCmd.Flags().BoolVarP(&whoisHistory, "history", "H", false, "Perform WHOIS history search [25 Credits]")
+ whoisCmd.Flags().BoolVarP(&whoisSubdomainScan, "subdomains", "s", false, "Perform WHOIS subdomain scan")
+
+ // Add API key flag
+ whoisCmd.Flags().StringVarP(&apiKey, "key", "k", "", "Dehashed API key")
+}
diff --git a/counseltrust.txt b/counseltrust.txt
new file mode 100644
index 0000000..6430328
--- /dev/null
+++ b/counseltrust.txt
@@ -0,0 +1,235 @@
+[*] Performing WHOIS lookup...
+WHOIS Lookup Result: %v
+ {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} {2025-03-10 15:29:09 UTC 2025-03-10 15:29:09 UTC} abuse@godaddy.com 2001-09-23T21:20:52Z 2001-09-23 21:20:52 UTC counseltrust.com .com 8633 2025-09-23T21:20:52Z 2025-09-23 21:20:52 UTC {[NS51.DOMAINCONTROL.COM NS52.DOMAINCONTROL.COM] [] NS51.DOMAINCONTROL.COM
+NS52.DOMAINCONTROL.COM
+} 3259 Domain Name: counseltrust.com
+Registry Domain ID: 77675771_DOMAIN_COM-VRSN
+Registrar WHOIS Server: whois.godaddy.com
+Registrar URL: https://www.godaddy.com
+Updated Date: 2024-09-24T12:17:56Z
+Creation Date: 2001-09-23T21:20:52Z
+Registrar Registration Expiration Date: 2025-09-23T21:20:52Z
+Registrar: GoDaddy.com, LLC
+Registrar IANA ID: 146
+Registrar Abuse Contact Email: abuse@godaddy.com
+Registrar Abuse Contact Phone: +1.4806242505
+Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
+Domain Status: clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited
+Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
+Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
+Registry Registrant ID: Not Available From Registry
+Registrant Name: Registration Private
+Registrant Organization: Domains By Proxy, LLC
+Registrant Street: DomainsByProxy.com
+Registrant Street: 100 S. Mill Ave, Suite 1600
+Registrant City: Tempe
+Registrant State/Province: Arizona
+Registrant Postal Code: 85281
+Registrant Country: US
+Registrant Phone: +1.4806242599
+Registrant Phone Ext:
+Registrant Fax:
+Registrant Fax Ext:
+Registrant Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com
+Registry Tech ID: Not Available From Registry
+Tech Name: Registration Private
+Tech Organization: Domains By Proxy, LLC
+Tech Street: DomainsByProxy.com
+Tech Street: 100 S. Mill Ave, Suite 1600
+Tech City: Tempe
+Tech State/Province: Arizona
+Tech Postal Code: 85281
+Tech Country: US
+Tech Phone: +1.4806242599
+Tech Phone Ext:
+Tech Fax:
+Tech Fax Ext:
+Tech Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com
+Name Server: NS51.DOMAINCONTROL.COM
+Name Server: NS52.DOMAINCONTROL.COM
+DNSSEC: unsigned
+URL of the ICANN WHOIS Data Problem Reporting System: http://wdprs.internic.net/
+>>> Last update of WHOIS database: 2025-03-10T15:29:08Z <<<
+For more information on Whois status codes, please visit https://icann.org/epp
+
+TERMS OF USE: The data contained in this registrar's Whois database, while believed by the
+registrar to be reliable, is provided "as is" with no guarantee or warranties regarding its
+accuracy. This information is provided for the sole purpose of assisting you in obtaining
+information about domain name registration records. Any use of this data for any other purpose
+is expressly forbidden without the prior written permission of this registrar. By submitting
+an inquiry, you agree to these terms and limitations of warranty. In particular, you agree not
+to use this data to allow, enable, or otherwise support the dissemination or collection of this
+data, in part or in its entirety, for any purpose, such as transmission by e-mail, telephone,
+postal mail, facsimile or other means of mass unsolicited, commercial advertising or solicitations
+of any kind, including spam. You further agree not to use this data to enable high volume, automated
+or robotic electronic processes designed to collect or compile this data for any purpose, including
+mining this data for your own personal or commercial purposes. Failure to comply with these terms
+may result in termination of access to the Whois database. These terms may be subject to modification
+at any time without notice.
+
+**NOTICE** This WHOIS server is being retired. Please use our RDAP service instead. {Tempe UNITED STATES US Registration Private Domains By Proxy, LLC 85281 Registrant Name: Registration Private
+Registrant Organization: Domains By Proxy, LLC
+Registrant Street: DomainsByProxy.com
+Registrant Street: 100 S. Mill Ave, Suite 1600
+Registrant City: Tempe
+Registrant State/Province: Arizona
+Registrant Postal Code: 85281
+Registrant Country: US
+Registrant Phone: +1.4806242599
+Registrant Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com Arizona DomainsByProxy.com
+100 S. Mill Ave, Suite 1600 14806242599} 146 GoDaddy.com, LLC {{2025-03-10 15:29:04 UTC 2025-03-10 15:29:04 UTC} 2001-09-24T02:20:52Z 2001-09-24 02:20:52 UTC counseltrust.com 2025-09-24T02:20:52Z 2025-09-24 02:20:52 UTC {[NS51.DOMAINCONTROL.COM NS52.DOMAINCONTROL.COM] [] NS51.DOMAINCONTROL.COM
+NS52.DOMAINCONTROL.COM
+} 251 Domain Name: COUNSELTRUST.COM
+ Registry Domain ID: 77675771_DOMAIN_COM-VRSN
+ Registrar WHOIS Server: whois.godaddy.com
+ Registrar URL: http://www.godaddy.com
+ Updated Date: 2024-09-24T17:17:57Z
+ Creation Date: 2001-09-24T02:20:52Z
+ Registry Expiry Date: 2025-09-24T02:20:52Z
+ Registrar: GoDaddy.com, LLC
+ Registrar IANA ID: 146
+ Registrar Abuse Contact Email: abuse@godaddy.com
+ Registrar Abuse Contact Phone: 480-624-2505
+ Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
+ Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
+ Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
+ Domain Status: clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited
+ Name Server: NS51.DOMAINCONTROL.COM
+ Name Server: NS52.DOMAINCONTROL.COM
+ DNSSEC: unsigned
+ URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
+>>> Last update of whois database: 2025-03-10T15:28:57Z <<<
+
+For more information on Whois status codes, please visit https://icann.org/epp
+
+NOTICE: The expiration date displayed in this record is the date the
+registrar's sponsorship of the domain name registration in the registry is
+currently set to expire. This date does not necessarily reflect the expiration
+date of the domain name registrant's agreement with the sponsoring
+registrar. Users may consult the sponsoring registrar's Whois database to
+view the registrar's reported date of expiration for this registration.
+
+TERMS OF USE: You are not authorized to access or query our Whois
+database through the use of electronic processes that are high-volume and
+automated except as reasonably necessary to register domain names or
+modify existing registrations; the Data in VeriSign Global Registry
+Services' ("VeriSign") Whois database is provided by VeriSign for
+information purposes only, and to assist persons in obtaining information
+about or related to a domain name registration record. VeriSign does not
+guarantee its accuracy. By submitting a Whois query, you agree to abide
+by the following terms of use: You agree that you may use this Data only
+for lawful purposes and that under no circumstances will you use this Data
+to: (1) allow, enable, or otherwise support the transmission of mass
+unsolicited, commercial advertising or solicitations via e-mail, telephone,
+or facsimile; or (2) enable high volume, automated, electronic processes
+that apply to VeriSign (or its computer systems). The compilation,
+repackaging, dissemination or other use of this Data is expressly
+prohibited without the prior written consent of VeriSign. You agree not to
+use electronic processes that are automated and high-volume to access or
+query the Whois database except as reasonably necessary to register
+domain names or modify existing registrations. VeriSign reserves the right
+to restrict your access to the Whois database in its sole discretion to ensure
+operational stability. VeriSign may restrict or terminate your access to the
+Whois database for failure to abide by these terms of use. VeriSign
+reserves the right to modify these terms at any time.
+
+The Registry database contains ONLY .COM, .NET, .EDU domains and
+Registrars. 146 GoDaddy.com, LLC clientDeleteProhibited clientRenewProhibited clientTransferProhibited clientUpdateProhibited Domain Name: COUNSELTRUST.COM
+Registry Domain ID: 77675771_DOMAIN_COM-VRSN
+Registrar WHOIS Server: whois.godaddy.com
+Registrar URL: http://www.godaddy.com
+Updated Date: 2024-09-24T17:17:57Z
+Creation Date: 2001-09-24T02:20:52Z
+Registry Expiry Date: 2025-09-24T02:20:52Z
+Registrar: GoDaddy.com, LLC
+Registrar IANA ID: 146
+Registrar Abuse Contact Email: abuse@godaddy.com
+Registrar Abuse Contact Phone: 480-624-2505
+Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
+Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
+Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
+Domain Status: clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited
+Name Server: NS51.DOMAINCONTROL.COM
+Name Server: NS52.DOMAINCONTROL.COM
+DNSSEC: unsigned
+URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
+>>> Last update of whois database: 2025-03-10T15:28:57Z <<<
+For more information on Whois status codes, please visit https://icann.org/epp
+NOTICE: The expiration date displayed in this record is the date the
+registrar's sponsorship of the domain name registration in the registry is
+currently set to expire. This date does not necessarily reflect the expiration
+date of the domain name registrant's agreement with the sponsoring
+registrar. Users may consult the sponsoring registrar's Whois database to
+view the registrar's reported date of expiration for this registration.
+TERMS OF USE: You are not authorized to access or query our Whois
+database through the use of electronic processes that are high-volume and
+automated except as reasonably necessary to register domain names or
+modify existing registrations; the Data in VeriSign Global Registry
+Services' ("VeriSign") Whois database is provided by VeriSign for
+information purposes only, and to assist persons in obtaining information
+about or related to a domain name registration record. VeriSign does not
+guarantee its accuracy. By submitting a Whois query, you agree to abide
+by the following terms of use: You agree that you may use this Data only
+for lawful purposes and that under no circumstances will you use this Data
+to: (1) allow, enable, or otherwise support the transmission of mass
+unsolicited, commercial advertising or solicitations via e-mail, telephone,
+or facsimile; or (2) enable high volume, automated, electronic processes
+that apply to VeriSign (or its computer systems). The compilation,
+repackaging, dissemination or other use of this Data is expressly
+prohibited without the prior written consent of VeriSign. You agree not to
+use electronic processes that are automated and high-volume to access or
+query the Whois database except as reasonably necessary to register
+domain names or modify existing registrations. VeriSign reserves the right
+to restrict your access to the Whois database in its sole discretion to ensure
+operational stability. VeriSign may restrict or terminate your access to the
+Whois database for failure to abide by these terms of use. VeriSign
+reserves the right to modify these terms at any time.
+The Registry database contains ONLY .COM, .NET, .EDU domains and
+Registrars.
+ 2024-09-24T17:17:57Z 2024-09-24 17:17:57 UTC whois.godaddy.com} clientTransferProhibited clientUpdateProhibited clientRenewProhibited clientDeleteProhibited Domain Name: counseltrust.com
+Registrar WHOIS Server: whois.godaddy.com
+Registrar URL: https://www.godaddy.com
+Updated Date: 2024-09-24T12:17:56Z
+Creation Date: 2001-09-23T21:20:52Z
+Registrar Registration Expiration Date: 2025-09-23T21:20:52Z
+Registrar: GoDaddy.com, LLC
+Registrar IANA ID: 146
+Registrar Abuse Contact Email: abuse@godaddy.com
+Registrar Abuse Contact Phone: +1.4806242505
+Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
+Domain Status: clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited
+Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
+Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
+Registrant Name: Registration Private
+Registrant Organization: Domains By Proxy, LLC
+Registrant Street: DomainsByProxy.com
+Registrant Street: 100 S. Mill Ave, Suite 1600
+Registrant City: Tempe
+Registrant State/Province: Arizona
+Registrant Postal Code: 85281
+Registrant Country: US
+Registrant Phone: +1.4806242599
+Registrant Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com
+Tech Name: Registration Private
+Tech Organization: Domains By Proxy, LLC
+Tech Street: DomainsByProxy.com
+Tech Street: 100 S. Mill Ave, Suite 1600
+Tech City: Tempe
+Tech State/Province: Arizona
+Tech Postal Code: 85281
+Tech Country: US
+Tech Phone: +1.4806242599
+Tech Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com
+Name Server: NS51.DOMAINCONTROL.COM
+Name Server: NS52.DOMAINCONTROL.COM
+ {Tempe UNITED STATES US Registration Private Domains By Proxy, LLC 85281 Tech Name: Registration Private
+Tech Organization: Domains By Proxy, LLC
+Tech Street: DomainsByProxy.com
+Tech Street: 100 S. Mill Ave, Suite 1600
+Tech City: Tempe
+Tech State/Province: Arizona
+Tech Postal Code: 85281
+Tech Country: US
+Tech Phone: +1.4806242599
+Tech Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com Arizona DomainsByProxy.com
+100 S. Mill Ave, Suite 1600 14806242599} 2024-09-24T12:17:56Z 2024-09-24 12:17:56 UTC}
diff --git a/counseltrust2.txt b/counseltrust2.txt
new file mode 100644
index 0000000..4efe188
--- /dev/null
+++ b/counseltrust2.txt
@@ -0,0 +1,241 @@
+[*] Performing WHOIS lookup...
+WHOIS Lookup Result:
+{Model:{ID:0 CreatedAt:0001-01-01 00:00:00 +0000 UTC UpdatedAt:0001-01-01 00:00:00 +0000 UTC DeletedAt:{Time:0001-01-01 00:00:00 +0000 UTC Valid:false}} Audit:{CreatedDate:2025-03-10 15:29:09 UTC UpdatedDate:2025-03-10 15:29:09 UTC} ContactEmail:abuse@godaddy.com CreatedDate:2001-09-23T21:20:52Z CreatedDateNormalized:2001-09-23 21:20:52 UTC DomainName:counseltrust.com DomainNameExt:.com EstimatedDomainAge:8633 ExpiresDate:2025-09-23T21:20:52Z ExpiresDateNormalized:2025-09-23 21:20:52 UTC Footer: Header: NameServers:{HostNames:[NS51.DOMAINCONTROL.COM NS52.DOMAINCONTROL.COM] IPs:[] RawText:NS51.DOMAINCONTROL.COM
+NS52.DOMAINCONTROL.COM
+} ParseCode:3259 RawText:Domain Name: counseltrust.com
+Registry Domain ID: 77675771_DOMAIN_COM-VRSN
+Registrar WHOIS Server: whois.godaddy.com
+Registrar URL: https://www.godaddy.com
+Updated Date: 2024-09-24T12:17:56Z
+Creation Date: 2001-09-23T21:20:52Z
+Registrar Registration Expiration Date: 2025-09-23T21:20:52Z
+Registrar: GoDaddy.com, LLC
+Registrar IANA ID: 146
+Registrar Abuse Contact Email: abuse@godaddy.com
+Registrar Abuse Contact Phone: +1.4806242505
+Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
+Domain Status: clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited
+Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
+Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
+Registry Registrant ID: Not Available From Registry
+Registrant Name: Registration Private
+Registrant Organization: Domains By Proxy, LLC
+Registrant Street: DomainsByProxy.com
+Registrant Street: 100 S. Mill Ave, Suite 1600
+Registrant City: Tempe
+Registrant State/Province: Arizona
+Registrant Postal Code: 85281
+Registrant Country: US
+Registrant Phone: +1.4806242599
+Registrant Phone Ext:
+Registrant Fax:
+Registrant Fax Ext:
+Registrant Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com
+Registry Tech ID: Not Available From Registry
+Tech Name: Registration Private
+Tech Organization: Domains By Proxy, LLC
+Tech Street: DomainsByProxy.com
+Tech Street: 100 S. Mill Ave, Suite 1600
+Tech City: Tempe
+Tech State/Province: Arizona
+Tech Postal Code: 85281
+Tech Country: US
+Tech Phone: +1.4806242599
+Tech Phone Ext:
+Tech Fax:
+Tech Fax Ext:
+Tech Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com
+Name Server: NS51.DOMAINCONTROL.COM
+Name Server: NS52.DOMAINCONTROL.COM
+DNSSEC: unsigned
+URL of the ICANN WHOIS Data Problem Reporting System: http://wdprs.internic.net/
+>>> Last update of WHOIS database: 2025-03-10T15:29:08Z <<<
+For more information on Whois status codes, please visit https://icann.org/epp
+
+TERMS OF USE: The data contained in this registrar's Whois database, while believed by the
+registrar to be reliable, is provided "as is" with no guarantee or warranties regarding its
+accuracy. This information is provided for the sole purpose of assisting you in obtaining
+information about domain name registration records. Any use of this data for any other purpose
+is expressly forbidden without the prior written permission of this registrar. By submitting
+an inquiry, you agree to these terms and limitations of warranty. In particular, you agree not
+to use this data to allow, enable, or otherwise support the dissemination or collection of this
+data, in part or in its entirety, for any purpose, such as transmission by e-mail, telephone,
+postal mail, facsimile or other means of mass unsolicited, commercial advertising or solicitations
+of any kind, including spam. You further agree not to use this data to enable high volume, automated
+or robotic electronic processes designed to collect or compile this data for any purpose, including
+mining this data for your own personal or commercial purposes. Failure to comply with these terms
+may result in termination of access to the Whois database. These terms may be subject to modification
+at any time without notice.
+
+**NOTICE** This WHOIS server is being retired. Please use our RDAP service instead. Registrant:{City:Tempe Country:UNITED STATES CountryCode:US Name:Registration Private Organization:Domains By Proxy, LLC PostalCode:85281 RawText:Registrant Name: Registration Private
+Registrant Organization: Domains By Proxy, LLC
+Registrant Street: DomainsByProxy.com
+Registrant Street: 100 S. Mill Ave, Suite 1600
+Registrant City: Tempe
+Registrant State/Province: Arizona
+Registrant Postal Code: 85281
+Registrant Country: US
+Registrant Phone: +1.4806242599
+Registrant Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com State:Arizona Street1:DomainsByProxy.com
+100 S. Mill Ave, Suite 1600 Telephone:14806242599} RegistrarIANAID:146 RegistrarName:GoDaddy.com, LLC RegistryData:{Audit:{CreatedDate:2025-03-10 15:29:04 UTC UpdatedDate:2025-03-10 15:29:04 UTC} CreatedDate:2001-09-24T02:20:52Z CreatedDateNormalized:2001-09-24 02:20:52 UTC DomainName:counseltrust.com ExpiresDate:2025-09-24T02:20:52Z ExpiresDateNormalized:2025-09-24 02:20:52 UTC Footer: Header: NameServers:{HostNames:[NS51.DOMAINCONTROL.COM NS52.DOMAINCONTROL.COM] IPs:[] RawText:NS51.DOMAINCONTROL.COM
+NS52.DOMAINCONTROL.COM
+} ParseCode:251 RawText:Domain Name: COUNSELTRUST.COM
+ Registry Domain ID: 77675771_DOMAIN_COM-VRSN
+ Registrar WHOIS Server: whois.godaddy.com
+ Registrar URL: http://www.godaddy.com
+ Updated Date: 2024-09-24T17:17:57Z
+ Creation Date: 2001-09-24T02:20:52Z
+ Registry Expiry Date: 2025-09-24T02:20:52Z
+ Registrar: GoDaddy.com, LLC
+ Registrar IANA ID: 146
+ Registrar Abuse Contact Email: abuse@godaddy.com
+ Registrar Abuse Contact Phone: 480-624-2505
+ Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
+ Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
+ Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
+ Domain Status: clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited
+ Name Server: NS51.DOMAINCONTROL.COM
+ Name Server: NS52.DOMAINCONTROL.COM
+ DNSSEC: unsigned
+ URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
+>>> Last update of whois database: 2025-03-10T15:28:57Z <<<
+
+For more information on Whois status codes, please visit https://icann.org/epp
+
+NOTICE: The expiration date displayed in this record is the date the
+registrar's sponsorship of the domain name registration in the registry is
+currently set to expire. This date does not necessarily reflect the expiration
+date of the domain name registrant's agreement with the sponsoring
+registrar. Users may consult the sponsoring registrar's Whois database to
+view the registrar's reported date of expiration for this registration.
+
+TERMS OF USE: You are not authorized to access or query our Whois
+database through the use of electronic processes that are high-volume and
+automated except as reasonably necessary to register domain names or
+modify existing registrations; the Data in VeriSign Global Registry
+Services' ("VeriSign") Whois database is provided by VeriSign for
+information purposes only, and to assist persons in obtaining information
+about or related to a domain name registration record. VeriSign does not
+guarantee its accuracy. By submitting a Whois query, you agree to abide
+by the following terms of use: You agree that you may use this Data only
+for lawful purposes and that under no circumstances will you use this Data
+to: (1) allow, enable, or otherwise support the transmission of mass
+unsolicited, commercial advertising or solicitations via e-mail, telephone,
+or facsimile; or (2) enable high volume, automated, electronic processes
+that apply to VeriSign (or its computer systems). The compilation,
+repackaging, dissemination or other use of this Data is expressly
+prohibited without the prior written consent of VeriSign. You agree not to
+use electronic processes that are automated and high-volume to access or
+query the Whois database except as reasonably necessary to register
+domain names or modify existing registrations. VeriSign reserves the right
+to restrict your access to the Whois database in its sole discretion to ensure
+operational stability. VeriSign may restrict or terminate your access to the
+Whois database for failure to abide by these terms of use. VeriSign
+reserves the right to modify these terms at any time.
+
+The Registry database contains ONLY .COM, .NET, .EDU domains and
+Registrars. RegistrarIANAID:146 RegistrarName:GoDaddy.com, LLC Status:clientDeleteProhibited clientRenewProhibited clientTransferProhibited clientUpdateProhibited StrippedText:Domain Name: COUNSELTRUST.COM
+Registry Domain ID: 77675771_DOMAIN_COM-VRSN
+Registrar WHOIS Server: whois.godaddy.com
+Registrar URL: http://www.godaddy.com
+Updated Date: 2024-09-24T17:17:57Z
+Creation Date: 2001-09-24T02:20:52Z
+Registry Expiry Date: 2025-09-24T02:20:52Z
+Registrar: GoDaddy.com, LLC
+Registrar IANA ID: 146
+Registrar Abuse Contact Email: abuse@godaddy.com
+Registrar Abuse Contact Phone: 480-624-2505
+Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
+Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
+Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
+Domain Status: clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited
+Name Server: NS51.DOMAINCONTROL.COM
+Name Server: NS52.DOMAINCONTROL.COM
+DNSSEC: unsigned
+URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
+>>> Last update of whois database: 2025-03-10T15:28:57Z <<<
+For more information on Whois status codes, please visit https://icann.org/epp
+NOTICE: The expiration date displayed in this record is the date the
+registrar's sponsorship of the domain name registration in the registry is
+currently set to expire. This date does not necessarily reflect the expiration
+date of the domain name registrant's agreement with the sponsoring
+registrar. Users may consult the sponsoring registrar's Whois database to
+view the registrar's reported date of expiration for this registration.
+TERMS OF USE: You are not authorized to access or query our Whois
+database through the use of electronic processes that are high-volume and
+automated except as reasonably necessary to register domain names or
+modify existing registrations; the Data in VeriSign Global Registry
+Services' ("VeriSign") Whois database is provided by VeriSign for
+information purposes only, and to assist persons in obtaining information
+about or related to a domain name registration record. VeriSign does not
+guarantee its accuracy. By submitting a Whois query, you agree to abide
+by the following terms of use: You agree that you may use this Data only
+for lawful purposes and that under no circumstances will you use this Data
+to: (1) allow, enable, or otherwise support the transmission of mass
+unsolicited, commercial advertising or solicitations via e-mail, telephone,
+or facsimile; or (2) enable high volume, automated, electronic processes
+that apply to VeriSign (or its computer systems). The compilation,
+repackaging, dissemination or other use of this Data is expressly
+prohibited without the prior written consent of VeriSign. You agree not to
+use electronic processes that are automated and high-volume to access or
+query the Whois database except as reasonably necessary to register
+domain names or modify existing registrations. VeriSign reserves the right
+to restrict your access to the Whois database in its sole discretion to ensure
+operational stability. VeriSign may restrict or terminate your access to the
+Whois database for failure to abide by these terms of use. VeriSign
+reserves the right to modify these terms at any time.
+The Registry database contains ONLY .COM, .NET, .EDU domains and
+Registrars.
+ UpdatedDate:2024-09-24T17:17:57Z UpdatedDateNormalized:2024-09-24 17:17:57 UTC WhoisServer:whois.godaddy.com} Status:clientTransferProhibited clientUpdateProhibited clientRenewProhibited clientDeleteProhibited StrippedText:Domain Name: counseltrust.com
+Registrar WHOIS Server: whois.godaddy.com
+Registrar URL: https://www.godaddy.com
+Updated Date: 2024-09-24T12:17:56Z
+Creation Date: 2001-09-23T21:20:52Z
+Registrar Registration Expiration Date: 2025-09-23T21:20:52Z
+Registrar: GoDaddy.com, LLC
+Registrar IANA ID: 146
+Registrar Abuse Contact Email: abuse@godaddy.com
+Registrar Abuse Contact Phone: +1.4806242505
+Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
+Domain Status: clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited
+Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
+Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
+Registrant Name: Registration Private
+Registrant Organization: Domains By Proxy, LLC
+Registrant Street: DomainsByProxy.com
+Registrant Street: 100 S. Mill Ave, Suite 1600
+Registrant City: Tempe
+Registrant State/Province: Arizona
+Registrant Postal Code: 85281
+Registrant Country: US
+Registrant Phone: +1.4806242599
+Registrant Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com
+Tech Name: Registration Private
+Tech Organization: Domains By Proxy, LLC
+Tech Street: DomainsByProxy.com
+Tech Street: 100 S. Mill Ave, Suite 1600
+Tech City: Tempe
+Tech State/Province: Arizona
+Tech Postal Code: 85281
+Tech Country: US
+Tech Phone: +1.4806242599
+Tech Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com
+Name Server: NS51.DOMAINCONTROL.COM
+Name Server: NS52.DOMAINCONTROL.COM
+ TechnicalContact:{City:Tempe Country:UNITED STATES CountryCode:US Name:Registration Private Organization:Domains By Proxy, LLC PostalCode:85281 RawText:Tech Name: Registration Private
+Tech Organization: Domains By Proxy, LLC
+Tech Street: DomainsByProxy.com
+Tech Street: 100 S. Mill Ave, Suite 1600
+Tech City: Tempe
+Tech State/Province: Arizona
+Tech Postal Code: 85281
+Tech Country: US
+Tech Phone: +1.4806242599
+Tech Email: Select Contact Domain Holder link at https://www.godaddy.com/whois/results.aspx?domain=counseltrust.com State:Arizona Street1:DomainsByProxy.com
+100 S. Mill Ave, Suite 1600 Telephone:14806242599} UpdatedDate:2024-09-24T12:17:56Z UpdatedDateNormalized:2024-09-24 12:17:56 UTC}
+[*] Performing WHOIS subdomain scan...
+
+Subdomain Scan:
+{220 {{13 [{{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} cpanel.counseltrust.com 1621908519 1621908519} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} cpcalendars.counseltrust.com 1621908519 1621908519} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} sslvpn.counseltrust.com 1546654106 1546654106} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} stocks.counseltrust.com 1546654702 1546654702} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} cpcontacts.counseltrust.com 1621908519 1621908519} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} ftp.counseltrust.com 1737657659 1737657659} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} mail.counseltrust.com 1621908519 1621908519} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} webmail.counseltrust.com 1621908519 1621908519} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} webdisk.counseltrust.com 1621908519 1621908519} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} autodiscover.counseltrust.com 1743219849 1745735288} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} www.laserfiche.counseltrust.com 1649336639 1712556962} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} laserfiche.counseltrust.com 1565348884 1712556962} {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} www.counseltrust.com 1517184000 1745798400}]} counseltrust.com}}
+
+WHOIS Credits Remaining: 220
diff --git a/dehasher b/dehasher
new file mode 100755
index 0000000000000000000000000000000000000000..4490642f03dfe7545dff9b29d30753c987d541ae
GIT binary patch
literal 27540440
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