├── .gitignore ├── Makefile ├── README.md ├── cmd └── hypercam │ └── hypercam.go ├── go.mod ├── go.sum ├── main.go └── pkg ├── freezer └── freezer.go ├── ns └── ns.go └── proc ├── proc.go └── types.go /.gitignore: -------------------------------------------------------------------------------- 1 | test* 2 | main 3 | notes.txt 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | main: 2 | go build -o hypercam main.go 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hypercam 2 | 3 | hypercam is a tool that lets you interact with processes and containers at a lower level than most other interfaces, letting you do some neat things that would otherwise not be possible. 4 | 5 | ## Features 6 | 7 | * Pause and unpause containers/processes 8 | * Open a live shell into paused containers, allowing you to inspect container state as is 9 | * Open shells (or any arbitrary program!) inside containers whose images don't come with them 10 | * Mount a directory into running containers to make moving files in and out easier 11 | * Dump strings/a hexdump of the heap and stack of any process 12 | 13 | ``` 14 | $ sudo hypercam -h 15 | NAME: 16 | hypercam - A new cli application 17 | 18 | USAGE: 19 | hypercam [global options] command [command options] [arguments...] 20 | 21 | COMMANDS: 22 | pause pause an entire cgroup by its pid 23 | unpause resume an entire cgroup by its pid 24 | info view open files and sockets for a given target 25 | splice splice an interactive shell into the target 26 | help, h Shows a list of commands or help for one command 27 | 28 | GLOBAL OPTIONS: 29 | --help, -h show help (default: false) 30 | 31 | $ sudo hypercam info 4106 32 | nginx: master process nginx -g daemon off; (/usr/sbin/nginx) 4106 33 | - Open Socket: 0.0.0.0:80<->0.0.0.0:0 34 | bash (/bin/bash) 24355 35 | bash (/bin/bash) 30267 36 | nginx: worker process (/usr/sbin/nginx) 31689 37 | - Open Socket: 172.17.0.2:80<->151.101.117.140:52200 38 | - Open Socket: 0.0.0.0:80<->0.0.0.0:0 39 | 40 | $ sudo hypercam pause 4106 41 | 42 | $ sudo hypercam splice 4106 43 | root@nginx:/# ps aux 44 | USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND 45 | root 1 0.0 0.2 10644 2808 ? Ds Mar16 0:00 nginx: master process nginx 46 | root 284 0.0 0.0 3868 0 pts/0 Ds+ Mar16 0:00 bash 47 | root 4214 0.0 0.0 3864 96 pts/1 Ds+ Sep05 0:00 bash 48 | nginx 4263 0.0 0.0 11100 604 ? D Sep05 0:00 nginx: worker process 49 | root 7226 0.0 0.2 7636 2708 ? R+ 16:36 0:00 ps aux 50 | root@nginx:/# etc... 51 | root@nginx:/# exit 52 | 53 | $ sudo hypercam unpause 4106 54 | 55 | ``` 56 | -------------------------------------------------------------------------------- /cmd/hypercam/hypercam.go: -------------------------------------------------------------------------------- 1 | package hypercam 2 | 3 | import ( 4 | "encoding/hex" 5 | "errors" 6 | "fmt" 7 | "os" 8 | "strings" 9 | "syscall" 10 | "unicode" 11 | 12 | "github.com/ancat/hypercam/pkg/freezer" 13 | "github.com/ancat/hypercam/pkg/proc" 14 | "github.com/ancat/hypercam/pkg/ns" 15 | ) 16 | 17 | func Pr(pid int) error { 18 | cgroup_name, err := freezer.GetFreezerInfo(pid) 19 | if err != nil { 20 | return errors.New("no such pid") 21 | } 22 | 23 | var pids []int 24 | if cgroup_name == "" || cgroup_name == "/" { 25 | pids = []int{pid} 26 | } else { 27 | pids = freezer.GetPidsByCgroup(cgroup_name) 28 | } 29 | 30 | for _, pid := range pids { 31 | fmt.Printf("%s (%s) %d\n", proc.GetComm(pid), proc.GetExe(pid), pid) 32 | 33 | files, sockets := proc.GetFds(pid) 34 | for _, file := range files { 35 | fmt.Printf("- Open File: %s\n", file) 36 | } 37 | 38 | for _, socket := range sockets { 39 | fmt.Printf("- Open Socket: %s<->%s\n", socket.LocalAddress, socket.RemoteAddress) 40 | } 41 | } 42 | 43 | return nil 44 | } 45 | 46 | func SpawnShellInside(pid int, portal bool, host_executable string, guest_executable string) { 47 | var handle *os.File 48 | var err error 49 | 50 | handle, err = ns.GetFdForPidNs(pid, "cgroup") 51 | ns.SetNs(handle.Fd(), ns.CLONE_NEWCGROUP) 52 | if err != nil { 53 | panic(err) 54 | } 55 | defer handle.Close() 56 | 57 | handle, err = ns.GetFdForPidNs(pid, "ipc") 58 | ns.SetNs(handle.Fd(), ns.CLONE_NEWIPC) 59 | if err != nil { 60 | panic(err) 61 | } 62 | defer handle.Close() 63 | 64 | handle, err = ns.GetFdForPidNs(pid, "uts") 65 | ns.SetNs(handle.Fd(), ns.CLONE_NEWUTS) 66 | if err != nil { 67 | panic(err) 68 | } 69 | defer handle.Close() 70 | 71 | handle, err = ns.GetFdForPidNs(pid, "net") 72 | ns.SetNs(handle.Fd(), ns.CLONE_NEWNET) 73 | if err != nil { 74 | panic(err) 75 | } 76 | defer handle.Close() 77 | 78 | handle, err = ns.GetFdForPidNs(pid, "pid") 79 | ns.SetNs(handle.Fd(), ns.CLONE_NEWPID) 80 | if err != nil { 81 | panic(err) 82 | } 83 | defer handle.Close() 84 | 85 | if portal { 86 | portal_path := fmt.Sprintf("/proc/%d/cwd/portal420", pid) 87 | host_path, _ := os.MkdirTemp("", "portal") 88 | 89 | os.Mkdir(portal_path, 0700) 90 | os.RemoveAll(host_path) 91 | os.Symlink(portal_path, host_path) 92 | fmt.Printf("Host Endpoint: %s\nGuest Endpoint: %s\n", host_path, "/portal420") 93 | } 94 | 95 | var sneaky_handle *os.File 96 | if host_executable != "" { 97 | sneaky_handle, err = os.Open(host_executable) 98 | if err != nil { 99 | panic(err) 100 | } 101 | } 102 | 103 | dir, _ := proc.GetPidRoot(pid) 104 | syscall.Fchdir(int(dir.Fd())) 105 | syscall.Chroot(".") 106 | 107 | if host_executable != "" { 108 | proc.SneakyExec(sneaky_handle, []string{"hypercam shell"}) 109 | panic("FAIL") 110 | } 111 | 112 | if guest_executable == "" { 113 | guest_executable = "/bin/sh" 114 | } 115 | 116 | syscall.Exec(guest_executable, nil, nil) 117 | panic("FAIL") 118 | } 119 | 120 | func DumpMaps(pid int, hex_dump bool) { 121 | maps := proc.GetMaps(pid) 122 | for _, memory_map := range maps { 123 | var page *proc.MemoryMap 124 | if memory_map.Name == "[stack]" { 125 | page = memory_map 126 | fmt.Printf("Stack Located\n") 127 | } 128 | 129 | if memory_map.Name == "[heap]" { 130 | page = memory_map 131 | fmt.Printf("Heap Located\n") 132 | } 133 | 134 | if page == nil { 135 | continue 136 | } 137 | 138 | page_contents, err := proc.ReadProcessMemory( 139 | pid, 140 | memory_map.Base, 141 | memory_map.Length, 142 | ) 143 | 144 | if err != nil { 145 | fmt.Printf("Failed to read page: %s\n", err) 146 | continue 147 | } 148 | 149 | if hex_dump { 150 | fmt.Printf("%s", hex.Dump(page_contents)) 151 | } else { 152 | print_strings(page_contents, 10) 153 | } 154 | } 155 | } 156 | 157 | func print_strings(buffer []byte, min_len int) { 158 | start := 0 159 | for i, c := range(buffer) { 160 | if !unicode.IsPrint(rune(c)) { 161 | strlen := i - start 162 | if strlen >= min_len { 163 | trimmed := strings.TrimSpace(string(buffer[start:i])) 164 | if len(trimmed) >= min_len { 165 | fmt.Printf("%s\n", trimmed) 166 | } 167 | } 168 | 169 | start = i 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ancat/hypercam 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect 7 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 8 | github.com/urfave/cli/v2 v2.11.2 // indirect 9 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect 10 | golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= 2 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 3 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 4 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 5 | github.com/urfave/cli/v2 v2.11.2 h1:FVfNg4m3vbjbBpLYxW//WjxUoHvJ9TlppXcqY9Q9ZfA= 6 | github.com/urfave/cli/v2 v2.11.2/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= 7 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= 8 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= 9 | golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2 h1:wM1k/lXfpc5HdkJJyW9GELpd8ERGdnh8sMGL6Gzq3Ho= 10 | golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 11 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | 7 | "github.com/ancat/hypercam/cmd/hypercam" 8 | "github.com/ancat/hypercam/pkg/freezer" 9 | "github.com/urfave/cli/v2" 10 | ) 11 | 12 | func requirePid(cCtx *cli.Context) (int, error) { 13 | if cCtx.NArg() > 0 { 14 | pid, err := strconv.Atoi(cCtx.Args().Get(0)) 15 | if err != nil { 16 | panic(err) 17 | } 18 | 19 | return pid, nil 20 | } 21 | 22 | return 0, nil 23 | } 24 | 25 | func main() { 26 | app := &cli.App{ 27 | Flags: []cli.Flag{ 28 | }, 29 | Commands: []*cli.Command{ 30 | { 31 | Name: "pause", 32 | Usage: "pause an entire cgroup by its pid", 33 | Flags: []cli.Flag{ 34 | &cli.StringFlag{ 35 | Name: "pid", 36 | Usage: "process id", 37 | }, 38 | }, 39 | Action: func(cCtx *cli.Context) error { 40 | pid, _ := requirePid(cCtx) 41 | if pid == 0 { 42 | return cli.Exit("pid missing", 1) 43 | } 44 | 45 | cgroup_name, _ := freezer.GetFreezerInfo(pid) 46 | if cgroup_name == "" || cgroup_name == "/" { 47 | return cli.Exit("no cgroup", 1) 48 | } 49 | 50 | freezer.UpdateFreezerStateByName(cgroup_name, "FROZEN") 51 | return nil 52 | }, 53 | }, 54 | { 55 | Name: "unpause", 56 | Usage: "resume an entire cgroup by its pid", 57 | Action: func(cCtx *cli.Context) error { 58 | pid, _ := requirePid(cCtx) 59 | if pid == 0 { 60 | return cli.Exit("pid missing", 1) 61 | } 62 | 63 | cgroup_name, _ := freezer.GetFreezerInfo(pid) 64 | if cgroup_name == "" || cgroup_name == "/" { 65 | return cli.Exit("no cgroup", 1) 66 | } 67 | 68 | freezer.UpdateFreezerStateByName(cgroup_name, "THAWED") 69 | return nil 70 | }, 71 | }, 72 | { 73 | Name: "info", 74 | Usage: "view open files and sockets for a given target", 75 | Action: func(cCtx *cli.Context) error { 76 | pid, _ := requirePid(cCtx) 77 | if pid == 0 { 78 | return cli.Exit("pid missing", 1) 79 | } 80 | 81 | hypercam.Pr(pid) 82 | return nil 83 | }, 84 | }, 85 | { 86 | Name: "splice", 87 | Usage: "splice an interactive shell into the target", 88 | Flags: []cli.Flag { 89 | &cli.BoolFlag { 90 | Name: "no-portal", 91 | Usage: "don't create a portal into the container", 92 | }, 93 | &cli.StringFlag { 94 | Name: "exec", 95 | Usage: "path to shell executable (default /bin/sh)", 96 | }, 97 | &cli.StringFlag { 98 | Name: "exec-from-host", 99 | Usage: "path to shell executable, copied from the host; useful if the target doesn't contain a usable shell. may need to be statically compiled.", 100 | }, 101 | }, 102 | Action: func(cCtx *cli.Context) error { 103 | pid, _ := requirePid(cCtx) 104 | if pid == 0 { 105 | return cli.Exit("pid missing", 1) 106 | } 107 | 108 | use_portals := !cCtx.Bool("no-portal") 109 | host_executable := cCtx.String("exec-from-host") 110 | guest_executable := cCtx.String("exec") 111 | if host_executable != "" && guest_executable != "" { 112 | return cli.Exit("--exec and --exec-from-host are mutually exclusive", 1) 113 | } else if host_executable == "" && guest_executable != "" { 114 | hypercam.SpawnShellInside(pid, use_portals, "", guest_executable) 115 | } else if host_executable != "" && guest_executable == "" { 116 | hypercam.SpawnShellInside(pid, use_portals, host_executable, "") 117 | } else { 118 | // neither is set 119 | hypercam.SpawnShellInside(pid, use_portals, "", "/bin/sh") 120 | } 121 | 122 | return nil 123 | }, 124 | }, 125 | { 126 | Name: "scan", 127 | Usage: "scan a process' stack and heap", 128 | Flags: []cli.Flag { 129 | &cli.BoolFlag { 130 | Name: "hex", 131 | Usage: "print a hex dump instead", 132 | }, 133 | }, 134 | Action: func(cCtx *cli.Context) error { 135 | pid, _ := requirePid(cCtx) 136 | if pid == 0 { 137 | return cli.Exit("pid missing", 1) 138 | } 139 | 140 | hex_dump := cCtx.Bool("hex") 141 | hypercam.DumpMaps(pid, hex_dump) 142 | return nil 143 | }, 144 | }, 145 | }, 146 | } 147 | 148 | if err := app.Run(os.Args); err != nil { 149 | panic(err) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /pkg/freezer/freezer.go: -------------------------------------------------------------------------------- 1 | package freezer 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | //"io/fs" 8 | //"errors" 9 | "strconv" 10 | "strings" 11 | "os" 12 | ) 13 | 14 | /* 15 | 16 | cat /sys/fs/cgroup/pids/docker/902e76b310599cce945cdcb6810d02dd8ed2b45ff7d2f833bbcecea2b78d84f4/cgroup.procs 17 | 18 | */ 19 | 20 | func GetFreezerInfo(pid int) (string, error) { 21 | handle, err := os.Open(fmt.Sprintf("/proc/%d/cgroup", pid)) 22 | if err != nil { 23 | return "", err 24 | } 25 | 26 | defer handle.Close() 27 | scanner := bufio.NewScanner(handle) 28 | 29 | for scanner.Scan() { 30 | s := strings.SplitN(scanner.Text(), ":", 3) 31 | if s[1] == "freezer" { 32 | return s[2], nil 33 | } 34 | } 35 | 36 | return "", nil 37 | } 38 | 39 | func GetPidsByCgroup(name string) []int { 40 | path := fmt.Sprintf("/sys/fs/cgroup/pids/%s/tasks", name); 41 | tasks, err := os.ReadFile(path) 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | var pids []int 47 | for _, line := range strings.Split(string(tasks), "\n") { 48 | if line == "" { 49 | continue 50 | } 51 | 52 | pid, err := strconv.Atoi(line) 53 | if err != nil || pid == 0 { 54 | continue 55 | } 56 | 57 | pids = append(pids, pid) 58 | } 59 | 60 | return pids 61 | } 62 | 63 | func UpdateFreezerTasks(name string, pids []int) { 64 | err := os.MkdirAll(fmt.Sprintf("/sys/fs/cgroup/freezer/%s", name), 0755) 65 | if err != nil { 66 | panic(err) 67 | } 68 | 69 | handle, err := os.OpenFile(fmt.Sprintf("/sys/fs/cgroup/freezer/%s/tasks", name), os.O_RDWR|os.O_CREATE, 0755) 70 | if err != nil { 71 | panic(err) 72 | } 73 | defer handle.Close() 74 | 75 | for _, pid := range pids { 76 | handle.WriteString(fmt.Sprintf("%d\n", pid)) 77 | } 78 | } 79 | 80 | func GetFreezerStateByName(name string) string { 81 | state, err := os.ReadFile(fmt.Sprintf("/sys/fs/cgroup/freezer/%s/freezer.state", name)) 82 | if err != nil { 83 | panic(err) 84 | } 85 | 86 | return string(bytes.TrimSpace(state)) 87 | } 88 | 89 | func GetFreezerStateByPid(pid int) string { 90 | cgroup_name, err := GetFreezerInfo(pid) 91 | if err != nil { 92 | panic(err) 93 | } 94 | 95 | return GetFreezerStateByName(cgroup_name) 96 | } 97 | 98 | func UpdateFreezerStateByName(name string, state string) { 99 | handle, err := os.OpenFile(fmt.Sprintf("/sys/fs/cgroup/freezer/%s/freezer.state", name), os.O_RDWR, 0755) 100 | if err != nil { 101 | panic(err) 102 | } 103 | defer handle.Close() 104 | 105 | handle.WriteString(fmt.Sprintf("%s\n", state)) 106 | } 107 | 108 | /* 109 | 41 def update_freezer_state_by_pid(pid, state): 110 | 42 return update_freezer_state_by_cgroup(get_cgroup_info(pid), state) 111 | */ 112 | func UpdateFreezerStateByPid(pid int, state string) {} 113 | 114 | -------------------------------------------------------------------------------- /pkg/ns/ns.go: -------------------------------------------------------------------------------- 1 | package ns 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "os" 8 | "strconv" 9 | "strings" 10 | "syscall" 11 | ) 12 | 13 | var CLONE_THREAD = 0x00010000 14 | var CLONE_NEWNS = 0x00020000 15 | var CLONE_CHILD_SETTID = 0x01000000 16 | var CLONE_NEWCGROUP = 0x02000000 17 | var CLONE_NEWUTS = 0x04000000 18 | var CLONE_NEWIPC = 0x08000000 19 | var CLONE_NEWUSER = 0x10000000 20 | var CLONE_NEWPID = 0x20000000 21 | var CLONE_NEWNET = 0x40000000 22 | var CLONE_IO = 0x80000000 23 | var SYS_SETNS, NSERR = setns_syscall_number() 24 | 25 | func SetNs(fd uintptr, nstype int) { 26 | if NSERR != nil { 27 | panic(NSERR) 28 | } 29 | 30 | syscall.Syscall(uintptr(SYS_SETNS), uintptr(fd), uintptr(nstype), 0) 31 | } 32 | 33 | func GetFdForPidNs(pid int, ns string) (*os.File, error) { 34 | handle, err := os.Open(fmt.Sprintf("/proc/%d/ns/%s", pid, ns)) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | return handle, nil 40 | } 41 | 42 | func setns_syscall_number() (int, error) { 43 | handle, err := os.Open("/usr/include/asm/unistd_64.h") 44 | if err != nil { 45 | return -1, err 46 | } 47 | 48 | scanner := bufio.NewScanner(handle) 49 | for scanner.Scan() { 50 | line := scanner.Text() 51 | fields := strings.Fields(line) 52 | if len(fields) == 3 && fields[1] == "__NR_setns" { 53 | return strconv.Atoi(fields[2]) 54 | } 55 | } 56 | 57 | return -1, errors.New("couldn't find it I guess") 58 | } 59 | -------------------------------------------------------------------------------- /pkg/proc/proc.go: -------------------------------------------------------------------------------- 1 | package proc 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "errors" 7 | "fmt" 8 | "encoding/hex" 9 | "io/fs" 10 | "net" 11 | "os" 12 | "strconv" 13 | "strings" 14 | "syscall" 15 | "unsafe" 16 | 17 | "golang.org/x/sys/unix" 18 | ) 19 | 20 | var SYS_EXECVEAT, EXECVEAT_ERR = execveat_syscall_number() 21 | func ReadProcessMemory(pid int, address uintptr, length uint64) ([]byte, error) { 22 | destination := make([]byte, length) 23 | // _ = new(unix.Iovec) 24 | local_iovec := make([]unix.Iovec, 1) 25 | local_iovec[0].Base = (*byte) (unsafe.Pointer(&destination[0])) 26 | local_iovec[0].Len = length 27 | 28 | remote_iovec := make([]unix.RemoteIovec, 1) 29 | remote_iovec[0].Base = address 30 | remote_iovec[0].Len = int(length) // why is local a uint64 and remote int¿ 31 | 32 | _, err := unix.ProcessVMReadv(pid, local_iovec, remote_iovec, 0) 33 | return destination, err 34 | } 35 | 36 | func GetMaps(pid int) []*MemoryMap { 37 | handle, err := os.Open(fmt.Sprintf("/proc/%d/maps", pid)) 38 | if err != nil { 39 | panic(err) 40 | } 41 | 42 | defer handle.Close() 43 | scanner := bufio.NewScanner(handle) 44 | maps := make([]*MemoryMap, 0) 45 | for scanner.Scan() { 46 | line := scanner.Text() 47 | cur_map := new(MemoryMap) 48 | 49 | var t_map_start uint64 50 | var t_map_end uint64 51 | var t_perms string 52 | var t_len uint64 53 | var t_dev string 54 | var t_inode int 55 | var t_name string 56 | 57 | // 7ffd4d9f1000-7ffd4d9f3000 r-xp 00000000 00:00 0 [vdso] 58 | scanned, _ := fmt.Sscanf(line, "%x-%x %s %x %s %d %s", 59 | &t_map_start, 60 | &t_map_end, 61 | &t_perms, 62 | &t_len, 63 | &t_dev, 64 | &t_inode, 65 | &t_name) 66 | 67 | if scanned != 7 { 68 | continue 69 | } 70 | 71 | // address_space := fields[0] 72 | perms := 0 73 | for i := 0; i < 3; i++ { 74 | switch t_perms[i] { 75 | case 'r': 76 | perms |= 0x1 77 | case 'w': 78 | perms |= 0x2 79 | case 'x': 80 | perms |= 0x4 81 | } 82 | } 83 | 84 | cur_map.Name = t_name 85 | cur_map.Base = uintptr(t_map_start) 86 | cur_map.Length = t_map_end - t_map_start 87 | cur_map.Perms = perms 88 | maps = append(maps, cur_map) 89 | } 90 | 91 | return maps 92 | } 93 | 94 | func SneakyExec(handle *os.File, argv []string) { 95 | if EXECVEAT_ERR != nil { 96 | panic(EXECVEAT_ERR) 97 | } 98 | 99 | empty_path, _ := syscall.BytePtrFromString("") 100 | p_empty_path := unsafe.Pointer(empty_path) 101 | p_argv, _ := syscall.SlicePtrFromStrings(argv) 102 | syscall.Syscall6( 103 | uintptr(SYS_EXECVEAT), 104 | uintptr(handle.Fd()), 105 | uintptr(p_empty_path), // if empty, we execute the fd instead 106 | uintptr(unsafe.Pointer(&p_argv[0])), 107 | uintptr(0), 108 | uintptr(0x1000), // AT_EMPTY_PATH 109 | uintptr(0), 110 | ) 111 | } 112 | 113 | func execveat_syscall_number() (int, error) { 114 | handle, err := os.Open("/usr/include/asm/unistd_64.h") 115 | if err != nil { 116 | return -1, err 117 | } 118 | 119 | scanner := bufio.NewScanner(handle) 120 | for scanner.Scan() { 121 | line := scanner.Text() 122 | fields := strings.Fields(line) 123 | if len(fields) == 3 && fields[1] == "__NR_execveat" { 124 | return strconv.Atoi(fields[2]) 125 | } 126 | } 127 | 128 | return -1, errors.New("couldn't find it I guess") 129 | } 130 | 131 | func GetExe(pid int) string { 132 | fd_name := fmt.Sprintf("/proc/%d/exe", pid) 133 | link, err := os.Readlink(fd_name) 134 | 135 | if err != nil { 136 | panic(err) 137 | } 138 | 139 | return link 140 | } 141 | 142 | func GetComm(pid int) string { 143 | path := fmt.Sprintf("/proc/%d/cmdline", pid) 144 | comm, err := os.ReadFile(path) 145 | if err != nil { 146 | panic(err) 147 | } 148 | 149 | return string(bytes.TrimSpace(comm)) 150 | } 151 | 152 | func GetFds(pid int) ([]string, []Connection) { 153 | handle, _ := os.Open(fmt.Sprintf("/proc/%d/fd", pid)) 154 | names, _ := handle.Readdirnames(0) 155 | file_list := make([]string, 0) 156 | socket_list := make([]Connection, 0) 157 | tcp_table := GetTcp4Table(pid) 158 | 159 | for _, name := range(names) { 160 | fd_name := fmt.Sprintf("/proc/%d/fd/%s", pid, name) 161 | link, _ := os.Readlink(fd_name) 162 | stat, _ := os.Stat(fd_name) 163 | if stat.Mode()&fs.ModeSocket != 0 { 164 | var socket_inode int 165 | n, err := fmt.Sscanf(link, "socket:[%d]", &socket_inode) 166 | if n == 0 { 167 | panic(err) 168 | } 169 | 170 | socket_list = append(socket_list, tcp_table[socket_inode]) 171 | } 172 | 173 | if stat.Mode().IsRegular() && !strings.HasPrefix(link, "anon_inode") { 174 | file_list = append(file_list, link) 175 | } 176 | } 177 | 178 | return file_list, socket_list 179 | } 180 | 181 | func decode_ipv4_address(ip string) string { 182 | fields := strings.Split(ip, ":") 183 | integer_ip, _ := hex.DecodeString(fields[0]) 184 | integer_port, _ := strconv.ParseUint(fields[1], 16, 16) 185 | 186 | decoded_ip := net.IPv4(integer_ip[3], integer_ip[2], integer_ip[1], integer_ip[0]) 187 | return fmt.Sprintf("%s:%d", decoded_ip.String(), integer_port) 188 | } 189 | 190 | func GetTcp4Table(pid int) map[int]Connection { 191 | inode_table := make(map[int]Connection) 192 | handle, err := os.Open(fmt.Sprintf("/proc/%d/net/tcp", pid)) 193 | if err != nil { 194 | panic(err) 195 | } 196 | 197 | scanner := bufio.NewScanner(handle) 198 | for scanner.Scan() { 199 | line := scanner.Text() 200 | fields := strings.Fields(line) 201 | if fields[1] == "local_address" { 202 | continue 203 | } 204 | 205 | local_address := decode_ipv4_address(fields[1]) 206 | remote_address := decode_ipv4_address(fields[2]) 207 | inode, _ := strconv.Atoi(fields[9]) 208 | inode_table[inode] = Connection{local_address, remote_address}; 209 | } 210 | 211 | return inode_table 212 | } 213 | 214 | func GetPidRoot(pid int) (*os.File, error) { 215 | handle, err := os.Open(fmt.Sprintf("/proc/%d/root", pid)) 216 | if err != nil { 217 | return nil, err 218 | } 219 | 220 | return handle, nil 221 | } 222 | -------------------------------------------------------------------------------- /pkg/proc/types.go: -------------------------------------------------------------------------------- 1 | package proc 2 | 3 | type Connection struct { 4 | LocalAddress string 5 | RemoteAddress string 6 | } 7 | 8 | type MemoryMap struct { 9 | Name string 10 | Base uintptr 11 | Length uint64 12 | Perms int 13 | } 14 | 15 | type process_metadata struct { 16 | executable string 17 | comm string 18 | open_files []string 19 | network_connections []Connection 20 | } 21 | --------------------------------------------------------------------------------