|
@@ -0,0 +1,178 @@
|
|
|
|
|
+package main
|
|
|
|
|
+
|
|
|
|
|
+import (
|
|
|
|
|
+ "fmt"
|
|
|
|
|
+ "log"
|
|
|
|
|
+ "net/url"
|
|
|
|
|
+ "os"
|
|
|
|
|
+ "os/user"
|
|
|
|
|
+ "runtime"
|
|
|
|
|
+ "strconv"
|
|
|
|
|
+ "strings"
|
|
|
|
|
+
|
|
|
|
|
+ "gopkg.in/ini.v1"
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+// WinSCP password encryption/decryption salts.
|
|
|
|
|
+const (
|
|
|
|
|
+ PasswordMagic = 0xA3
|
|
|
|
|
+ PasswordFlag = 0xFF
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+func main() {
|
|
|
|
|
+ args := os.Args[1:]
|
|
|
|
|
+ if len(args) != 3 && len(args) != 2 {
|
|
|
|
|
+ // In case provided arguments doesn't match the
|
|
|
|
|
+ // application usage, print application usage message.
|
|
|
|
|
+ PrintHelp()
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if args[0] == "ini" {
|
|
|
|
|
+ // In case 'ini' argument was provided,
|
|
|
|
|
+ // start the decryption of ini file.
|
|
|
|
|
+ var iniPath string
|
|
|
|
|
+ if len(args) == 2 {
|
|
|
|
|
+ iniPath = args[1]
|
|
|
|
|
+ } else {
|
|
|
|
|
+ iniPath = GetDefaultWinSCPIniFilePath()
|
|
|
|
|
+ }
|
|
|
|
|
+ DecryptIni(iniPath)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // In case any argument matches a different operation,
|
|
|
|
|
+ // perform the default decryption operation.
|
|
|
|
|
+ fmt.Println(Decrypt(args[0], args[1], args[2]))
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// PrintHelp prints a help message with instructions about the application usage.
|
|
|
|
|
+func PrintHelp() {
|
|
|
|
|
+ fmt.Println("WinSCP stored password finder")
|
|
|
|
|
+
|
|
|
|
|
+ // WinSCP's password manual decryption mode.
|
|
|
|
|
+ if runtime.GOOS == "windows" {
|
|
|
|
|
+ fmt.Println("Registry:")
|
|
|
|
|
+ fmt.Println(" Open regedit and navigate to [HKEY_CURRENT_USER\\Software\\Martin Prikryl\\WinSCP 2\\Sessions] to get the hostname, username and encrypted password")
|
|
|
|
|
+ fmt.Println(" Usage winscppasswd.exe <host> <username> <encrypted_password>")
|
|
|
|
|
+ } else {
|
|
|
|
|
+ fmt.Println(" Usage ./winscppasswd <host> <username> <encrypted_password>")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // WinSCP's ini file mode.
|
|
|
|
|
+ fmt.Println("\nWinSCP.ini:")
|
|
|
|
|
+ if runtime.GOOS == "windows" {
|
|
|
|
|
+ fmt.Println(" Usage winscppasswd.exe ini [<filepath>]")
|
|
|
|
|
+ fmt.Printf(" Default value <filepath>: %s\n", GetDefaultWinSCPIniFilePath())
|
|
|
|
|
+ } else {
|
|
|
|
|
+ fmt.Println(" Usage ./winscppasswd ini [<filepath>]")
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// GetDefaultWinSCPIniFilePath obtains default WinSCP configuration file.
|
|
|
|
|
+func GetDefaultWinSCPIniFilePath() string {
|
|
|
|
|
+ usr, err := user.Current()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ log.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+ return usr.HomeDir + "\\AppData\\Roaming\\winSCP.ini"
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// DecryptIni decrypts all entries from a WinSCP's ini file.
|
|
|
|
|
+func DecryptIni(filepath string) {
|
|
|
|
|
+ cfg, err := ini.InsensitiveLoad(filepath)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ panic(err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Print every entry of the configuration that has password field.
|
|
|
|
|
+ for _, c := range cfg.Sections() {
|
|
|
|
|
+ if c.HasKey("Password") {
|
|
|
|
|
+ name, _ := url.PathUnescape(strings.TrimPrefix(c.Name(), "sessions\\"))
|
|
|
|
|
+ fmt.Printf("%s\n", name)
|
|
|
|
|
+ fmt.Printf(" Hostname: %s\n", c.Key("HostName").Value())
|
|
|
|
|
+ fmt.Printf(" Username: %s\n", c.Key("UserName").Value())
|
|
|
|
|
+ fmt.Printf(" Password: %s\n", Decrypt(c.Key("HostName").Value(), c.Key("UserName").Value(), c.Key("Password").Value()))
|
|
|
|
|
+ fmt.Println("========================")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Decrypt decripts a specific server password.
|
|
|
|
|
+func Decrypt(host, username, password string) string {
|
|
|
|
|
+ // Build 'encryptedPasswordBytes' variable.
|
|
|
|
|
+ encryptedPasswordBytes := GetCryptedPasswordBytes(password)
|
|
|
|
|
+
|
|
|
|
|
+ // Extract 'flag' and 'cryptedPasswordlength' variables
|
|
|
|
|
+ flag, encryptedPasswordBytes := DecryptNextCharacter(encryptedPasswordBytes) // decryptNextCharacter alters the encryptedPasswordBytes variable to remove already parsed characters.
|
|
|
|
|
+ cryptedPasswordlength, encryptedPasswordBytes := GetCryptedPasswordLength(flag, encryptedPasswordBytes)
|
|
|
|
|
+
|
|
|
|
|
+ // Build 'clearpass' variable
|
|
|
|
|
+ clearpass := GetPassword(cryptedPasswordlength, encryptedPasswordBytes)
|
|
|
|
|
+
|
|
|
|
|
+ // Apply correction to the 'clearpass' variable.
|
|
|
|
|
+ if flag == PasswordFlag {
|
|
|
|
|
+ // The clearpass will contians the username, host and password.
|
|
|
|
|
+ // Substring username and host from the result password.
|
|
|
|
|
+ key := username + host
|
|
|
|
|
+ clearpass = clearpass[len(key):]
|
|
|
|
|
+ }
|
|
|
|
|
+ return clearpass
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// GetCryptedPasswordBytes obtains the crypted password byte array.
|
|
|
|
|
+func GetCryptedPasswordBytes(password string) []byte {
|
|
|
|
|
+ encryptedPasswordBytes := []byte{}
|
|
|
|
|
+ for i := 0; i < len(password); i++ {
|
|
|
|
|
+ val, _ := strconv.ParseInt(string(password[i]), 16, 8)
|
|
|
|
|
+ encryptedPasswordBytes = append(encryptedPasswordBytes, byte(val))
|
|
|
|
|
+ }
|
|
|
|
|
+ return encryptedPasswordBytes
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// GetCryptedPasswordLength obtains crypted password length from crypted password byte array.
|
|
|
|
|
+func GetCryptedPasswordLength(flag byte, encryptedPasswordBytes []byte) (byte, []byte) {
|
|
|
|
|
+ var cryptedPasswordlength byte = 0
|
|
|
|
|
+ if flag == PasswordFlag {
|
|
|
|
|
+ _, encryptedPasswordBytes = DecryptNextCharacter(encryptedPasswordBytes) // Ignore two characters of the encryptedPasswordBytes.
|
|
|
|
|
+ cryptedPasswordlength, encryptedPasswordBytes = DecryptNextCharacter(encryptedPasswordBytes) // decryptNextCharacter alters the encryptedPasswordBytes variable to remove already parsed characters.
|
|
|
|
|
+ } else {
|
|
|
|
|
+ cryptedPasswordlength = flag
|
|
|
|
|
+ }
|
|
|
|
|
+ toBeDeleted, encryptedPasswordBytes := DecryptNextCharacter(encryptedPasswordBytes) // decryptNextCharacter alters the encryptedPasswordBytes variable to remove already parsed characters.
|
|
|
|
|
+ encryptedPasswordBytes = encryptedPasswordBytes[toBeDeleted*2:]
|
|
|
|
|
+ return cryptedPasswordlength, encryptedPasswordBytes
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// GetPassword obtains clear password from crypted password byte array
|
|
|
|
|
+func GetPassword(cryptedPasswordlength byte, encryptedPasswordBytes []byte) string {
|
|
|
|
|
+ var i, character byte
|
|
|
|
|
+ var decryptedPassword string
|
|
|
|
|
+
|
|
|
|
|
+ for i = 0; i < cryptedPasswordlength; i++ {
|
|
|
|
|
+ character, encryptedPasswordBytes = DecryptNextCharacter(encryptedPasswordBytes) // decryptNextCharacter alters the encryptedPasswordBytes variable to remove already parsed characters.
|
|
|
|
|
+ decryptedPassword += string(character) // Add decrypted character to the result variable.
|
|
|
|
|
+ }
|
|
|
|
|
+ return decryptedPassword
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// DecryptNextCharacter decrypts next character from byte array.
|
|
|
|
|
+// Alters the byte array to remove already parsed bytes.
|
|
|
|
|
+func DecryptNextCharacter(encryptedPasswordBytes []byte) (byte, []byte) {
|
|
|
|
|
+ if len(encryptedPasswordBytes) <= 0 {
|
|
|
|
|
+ // In case encryptedPasswordBytes param was empty,
|
|
|
|
|
+ // stop the flow here returning '0'.
|
|
|
|
|
+ return 0, encryptedPasswordBytes
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ a := encryptedPasswordBytes[0] // Obtain first character to parse.
|
|
|
|
|
+ b := encryptedPasswordBytes[1] // Obtain second character to parse.
|
|
|
|
|
+ encryptedPasswordBytes = encryptedPasswordBytes[2:] // Remove already parsed characters.
|
|
|
|
|
+ return DecryptCharacter(a, b), encryptedPasswordBytes
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// DecryptCharacter decrypts character from two bytes.
|
|
|
|
|
+func DecryptCharacter(a, b byte) byte {
|
|
|
|
|
+ return ^(((a << 4) + b) ^ PasswordMagic) & PasswordFlag
|
|
|
|
|
+}
|