Cybersecurity-Projects/PROJECTS/advanced/honeypot-network/internal/sshd/filesystem.go

266 lines
6.3 KiB
Go

/*
©AngelaMos | 2026
filesystem.go
In-memory fake filesystem for the SSH shell environment
Presents a realistic Ubuntu server filesystem with populated
/etc, /proc, /var, /home directories. Each file has plausible
contents that engaged attackers would expect to see after
compromising a Linux host.
*/
package sshd
import (
"fmt"
"sort"
"strings"
"time"
)
type fileEntry struct {
name string
isDir bool
content string
mode string
}
type FakeFS struct {
hostname string
files map[string]*fileEntry
}
func NewFakeFS(hostname string) *FakeFS {
fs := &FakeFS{
hostname: hostname,
files: make(map[string]*fileEntry),
}
fs.populate()
return fs
}
func (fs *FakeFS) populate() {
dirs := []string{
"/", "/bin", "/boot", "/dev", "/etc",
"/etc/ssh", "/etc/cron.d", "/etc/default",
"/home", "/home/admin", "/lib", "/lib64",
"/media", "/mnt", "/opt", "/proc",
"/root", "/run", "/sbin", "/srv",
"/sys", "/tmp", "/usr", "/usr/bin",
"/usr/lib", "/usr/local", "/usr/sbin",
"/var", "/var/log", "/var/lib", "/var/tmp",
"/var/run", "/var/spool",
}
for _, d := range dirs {
fs.files[d] = &fileEntry{
name: d,
isDir: true,
mode: "drwxr-xr-x",
}
}
fs.files["/etc/hostname"] = &fileEntry{
name: "hostname",
content: fs.hostname + "\n",
mode: "-rw-r--r--",
}
fs.files["/etc/passwd"] = &fileEntry{
name: "passwd",
content: "root:x:0:0:root:/root:/bin/bash\n" +
"daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n" +
"bin:x:2:2:bin:/bin:/usr/sbin/nologin\n" +
"sys:x:3:3:sys:/dev:/usr/sbin/nologin\n" +
"sync:x:4:65534:sync:/bin:/bin/sync\n" +
"games:x:5:60:games:/usr/games:/usr/sbin/nologin\n" +
"man:x:6:12:man:/var/cache/man:/usr/sbin/nologin\n" +
"lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin\n" +
"mail:x:8:8:mail:/var/mail:/usr/sbin/nologin\n" +
"news:x:9:9:news:/var/spool/news:/usr/sbin/nologin\n" +
"uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin\n" +
"proxy:x:13:13:proxy:/bin:/usr/sbin/nologin\n" +
"www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin\n" +
"nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin\n" +
"sshd:x:110:65534::/run/sshd:/usr/sbin/nologin\n" +
"admin:x:1000:1000:admin:/home/admin:/bin/bash\n",
mode: "-rw-r--r--",
}
fs.files["/etc/shadow"] = &fileEntry{
name: "shadow",
content: "",
mode: "-rw-r-----",
}
fs.files["/etc/os-release"] = &fileEntry{
name: "os-release",
content: "PRETTY_NAME=\"Ubuntu 22.04.4 LTS\"\n" +
"NAME=\"Ubuntu\"\n" +
"VERSION_ID=\"22.04\"\n" +
"VERSION=\"22.04.4 LTS (Jammy Jellyfish)\"\n" +
"VERSION_CODENAME=jammy\n" +
"ID=ubuntu\n" +
"ID_LIKE=debian\n",
mode: "-rw-r--r--",
}
fs.files["/etc/issue"] = &fileEntry{
name: "issue",
content: "Ubuntu 22.04.4 LTS \\n \\l\n\n",
mode: "-rw-r--r--",
}
fs.files["/proc/version"] = &fileEntry{
name: "version",
content: "Linux version 5.15.0-105-generic " +
"(buildd@lcy02-amd64-032) " +
"(gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, " +
"GNU ld (GNU Binutils for Ubuntu) 2.38) " +
"#115-Ubuntu SMP Mon Apr 15 09:52:04 UTC 2024\n",
mode: "-r--r--r--",
}
fs.files["/proc/cpuinfo"] = &fileEntry{
name: "cpuinfo",
content: "processor\t: 0\n" +
"vendor_id\t: GenuineIntel\n" +
"cpu family\t: 6\n" +
"model\t\t: 85\n" +
"model name\t: Intel(R) Xeon(R) Platinum 8175M CPU @ 2.50GHz\n" +
"stepping\t: 4\n" +
"microcode\t: 0x2007006\n" +
"cpu MHz\t\t: 2500.000\n" +
"cache size\t: 33792 KB\n" +
"physical id\t: 0\n" +
"siblings\t: 2\n" +
"core id\t\t: 0\n" +
"cpu cores\t: 2\n" +
"bogomips\t: 5000.00\n" +
"flags\t\t: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology cpuid pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor\n\n",
mode: "-r--r--r--",
}
fs.files["/proc/meminfo"] = &fileEntry{
name: "meminfo",
content: "MemTotal: 4028440 kB\n" +
"MemFree: 1245680 kB\n" +
"MemAvailable: 2876340 kB\n" +
"Buffers: 186420 kB\n" +
"Cached: 1580240 kB\n" +
"SwapCached: 0 kB\n" +
"SwapTotal: 2097148 kB\n" +
"SwapFree: 2097148 kB\n",
mode: "-r--r--r--",
}
fs.files["/etc/ssh/sshd_config"] = &fileEntry{
name: "sshd_config",
content: "Port 22\n" +
"PermitRootLogin yes\n" +
"PubkeyAuthentication yes\n" +
"PasswordAuthentication yes\n" +
"UsePAM yes\n" +
"X11Forwarding yes\n" +
"PrintMotd no\n" +
"AcceptEnv LANG LC_*\n" +
"Subsystem\tsftp\t/usr/lib/openssh/sftp-server\n",
mode: "-rw-r--r--",
}
}
func (fs *FakeFS) ReadFile(path string) (string, bool) {
entry, exists := fs.files[path]
if !exists || entry.isDir {
return "", false
}
return entry.content, true
}
func (fs *FakeFS) IsDir(path string) bool {
entry, exists := fs.files[path]
return exists && entry.isDir
}
func (fs *FakeFS) Exists(path string) bool {
_, exists := fs.files[path]
return exists
}
func (fs *FakeFS) ListDir(path string) string {
if !fs.IsDir(path) {
return ""
}
prefix := path
if prefix != "/" {
prefix += "/"
}
type namedEntry struct {
name string
formatted string
}
var collected []namedEntry
seen := make(map[string]bool)
for p, entry := range fs.files {
if p == path {
continue
}
if !strings.HasPrefix(p, prefix) {
continue
}
rest := strings.TrimPrefix(p, prefix)
parts := strings.SplitN(rest, "/", 2)
name := parts[0]
if seen[name] || len(parts) != 1 {
continue
}
seen[name] = true
var line string
if entry.isDir {
line = fmt.Sprintf(
"%s 2 root root 4096 %s %s",
entry.mode,
time.Now().AddDate(0, -1, 0).Format("Jan 2 15:04"),
name,
)
} else {
line = fmt.Sprintf(
"%s 1 root root %4d %s %s",
entry.mode,
len(entry.content),
time.Now().AddDate(0, 0, -7).Format("Jan 2 15:04"),
name,
)
}
collected = append(collected, namedEntry{name: name, formatted: line})
}
if len(collected) == 0 {
return ""
}
sort.Slice(collected, func(i, j int) bool {
return collected[i].name < collected[j].name
})
entries := make([]string, len(collected))
for i, ne := range collected {
entries[i] = ne.formatted
}
return fmt.Sprintf(
"total %d\n%s\n",
len(entries)*4,
strings.Join(entries, "\n"),
)
}