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 ") } else { fmt.Println(" Usage ./winscppasswd ") } // WinSCP's ini file mode. fmt.Println("\nWinSCP.ini:") if runtime.GOOS == "windows" { fmt.Println(" Usage winscppasswd.exe ini []") fmt.Printf(" Default value : %s\n", GetDefaultWinSCPIniFilePath()) } else { fmt.Println(" Usage ./winscppasswd ini []") } } // 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 }