Files
iknowyou/back/internal/tools/ghunt/tool.go
2026-04-11 21:29:59 +02:00

124 lines
3.3 KiB
Go

package ghunt
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"github.com/anotherhadi/iknowyou/internal/tools"
)
var ansiRe = regexp.MustCompile(`\x1b[\x5b-\x5f][0-9;]*[A-Za-z]|\x1b[^[\x5b-\x5f]`)
const (
name = "ghunt"
description = "Google account OSINT via GHunt. Extracts profile info, linked services, and activity from a Google email address."
link = "https://github.com/mxrch/GHunt"
icon = "google"
)
type Config struct {
Creds string `yaml:"creds" iky:"desc=GHunt credentials (content of ~/.malfrats/ghunt/creds.m). To obtain: (1) install GHunt and run 'ghunt login' on your machine, (2) copy the full content of ~/.malfrats/ghunt/creds.m, (3) paste it here.;required=true"`
}
type Runner struct {
cfg Config
}
func New() tools.ToolRunner {
cfg := Config{}
tools.ApplyDefaults(&cfg)
return &Runner{cfg: cfg}
}
func (r *Runner) Name() string { return name }
func (r *Runner) Description() string { return description }
func (r *Runner) Link() string { return link }
func (r *Runner) Icon() string { return icon }
func (r *Runner) InputTypes() []tools.InputType {
return []tools.InputType{
tools.InputTypeEmail,
}
}
func (r *Runner) ConfigPtr() interface{} { return &r.cfg }
func (r *Runner) ConfigFields() []tools.ConfigField {
return tools.ReflectConfigFields(r.cfg)
}
func (r *Runner) Available() (bool, string) {
if _, err := exec.LookPath("ghunt"); err != nil {
return false, "ghunt binary not found in PATH"
}
return true, ""
}
func (r *Runner) Dependencies() []string { return []string{"ghunt"} }
func (r *Runner) writeCreds() error {
home, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("cannot determine home directory: %w", err)
}
dir := filepath.Join(home, ".malfrats", "ghunt")
if err := os.MkdirAll(dir, 0700); err != nil {
return fmt.Errorf("cannot create ghunt dir: %w", err)
}
return os.WriteFile(filepath.Join(dir, "creds.m"), []byte(r.cfg.Creds), 0600)
}
func (r *Runner) Run(ctx context.Context, target string, _ tools.InputType, out chan<- tools.Event) error {
defer close(out)
if err := r.writeCreds(); err != nil {
out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: err.Error()}
out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
return nil
}
cmd := exec.CommandContext(ctx, "ghunt", "email", target)
output, err := tools.RunWithPTY(ctx, cmd)
output = strings.ReplaceAll(output, "\r\n", "\n")
output = strings.ReplaceAll(output, "\r", "\n")
lines := strings.Split(output, "\n")
parsed := make([]string, len(lines))
for i, l := range lines {
parsed[i] = ansiRe.ReplaceAllString(l, "")
}
start := 0
for i, l := range parsed {
if strings.Contains(l, "[+] Authenticated !") {
start = i + 1
break
}
}
end := len(lines)
for i := start; i < len(parsed); i++ {
if strings.Contains(parsed[i], "Traceback (most recent call last)") {
end = i
break
}
}
output = strings.TrimSpace(strings.Join(lines[start:end], "\n"))
if err != nil && ctx.Err() != nil {
out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "scan cancelled"}
} else if output != "" {
out <- tools.Event{Tool: name, Type: tools.EventTypeOutput, Payload: output}
out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 1}
}
out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
return nil
}