main.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "net/url"
  6. "os"
  7. "os/user"
  8. "runtime"
  9. "strconv"
  10. "strings"
  11. "gopkg.in/ini.v1"
  12. )
  13. // WinSCP password encryption/decryption salts.
  14. const (
  15. PasswordMagic = 0xA3
  16. PasswordFlag = 0xFF
  17. )
  18. func main() {
  19. args := os.Args[1:]
  20. if len(args) != 3 && len(args) != 2 {
  21. // In case provided arguments doesn't match the
  22. // application usage, print application usage message.
  23. PrintHelp()
  24. return
  25. }
  26. if args[0] == "ini" {
  27. // In case 'ini' argument was provided,
  28. // start the decryption of ini file.
  29. var iniPath string
  30. if len(args) == 2 {
  31. iniPath = args[1]
  32. } else {
  33. iniPath = GetDefaultWinSCPIniFilePath()
  34. }
  35. DecryptIni(iniPath)
  36. return
  37. }
  38. // In case any argument matches a different operation,
  39. // perform the default decryption operation.
  40. fmt.Println(Decrypt(args[0], args[1], args[2]))
  41. }
  42. // PrintHelp prints a help message with instructions about the application usage.
  43. func PrintHelp() {
  44. fmt.Println("WinSCP stored password finder")
  45. // WinSCP's password manual decryption mode.
  46. if runtime.GOOS == "windows" {
  47. fmt.Println("Registry:")
  48. fmt.Println(" Open regedit and navigate to [HKEY_CURRENT_USER\\Software\\Martin Prikryl\\WinSCP 2\\Sessions] to get the hostname, username and encrypted password")
  49. fmt.Println(" Usage winscppasswd.exe <host> <username> <encrypted_password>")
  50. } else {
  51. fmt.Println(" Usage ./winscppasswd <host> <username> <encrypted_password>")
  52. }
  53. // WinSCP's ini file mode.
  54. fmt.Println("\nWinSCP.ini:")
  55. if runtime.GOOS == "windows" {
  56. fmt.Println(" Usage winscppasswd.exe ini [<filepath>]")
  57. fmt.Printf(" Default value <filepath>: %s\n", GetDefaultWinSCPIniFilePath())
  58. } else {
  59. fmt.Println(" Usage ./winscppasswd ini [<filepath>]")
  60. }
  61. }
  62. // GetDefaultWinSCPIniFilePath obtains default WinSCP configuration file.
  63. func GetDefaultWinSCPIniFilePath() string {
  64. usr, err := user.Current()
  65. if err != nil {
  66. log.Fatal(err)
  67. }
  68. return usr.HomeDir + "\\AppData\\Roaming\\winSCP.ini"
  69. }
  70. // DecryptIni decrypts all entries from a WinSCP's ini file.
  71. func DecryptIni(filepath string) {
  72. cfg, err := ini.InsensitiveLoad(filepath)
  73. if err != nil {
  74. panic(err)
  75. }
  76. // Print every entry of the configuration that has password field.
  77. for _, c := range cfg.Sections() {
  78. if c.HasKey("Password") {
  79. name, _ := url.PathUnescape(strings.TrimPrefix(c.Name(), "sessions\\"))
  80. fmt.Printf("%s\n", name)
  81. fmt.Printf(" Hostname: %s\n", c.Key("HostName").Value())
  82. fmt.Printf(" Username: %s\n", c.Key("UserName").Value())
  83. fmt.Printf(" Password: %s\n", Decrypt(c.Key("HostName").Value(), c.Key("UserName").Value(), c.Key("Password").Value()))
  84. fmt.Println("========================")
  85. }
  86. }
  87. }
  88. // Decrypt decripts a specific server password.
  89. func Decrypt(host, username, password string) string {
  90. // Build 'encryptedPasswordBytes' variable.
  91. encryptedPasswordBytes := GetCryptedPasswordBytes(password)
  92. // Extract 'flag' and 'cryptedPasswordlength' variables
  93. flag, encryptedPasswordBytes := DecryptNextCharacter(encryptedPasswordBytes) // decryptNextCharacter alters the encryptedPasswordBytes variable to remove already parsed characters.
  94. cryptedPasswordlength, encryptedPasswordBytes := GetCryptedPasswordLength(flag, encryptedPasswordBytes)
  95. // Build 'clearpass' variable
  96. clearpass := GetPassword(cryptedPasswordlength, encryptedPasswordBytes)
  97. // Apply correction to the 'clearpass' variable.
  98. if flag == PasswordFlag {
  99. // The clearpass will contians the username, host and password.
  100. // Substring username and host from the result password.
  101. key := username + host
  102. clearpass = clearpass[len(key):]
  103. }
  104. return clearpass
  105. }
  106. // GetCryptedPasswordBytes obtains the crypted password byte array.
  107. func GetCryptedPasswordBytes(password string) []byte {
  108. encryptedPasswordBytes := []byte{}
  109. for i := 0; i < len(password); i++ {
  110. val, _ := strconv.ParseInt(string(password[i]), 16, 8)
  111. encryptedPasswordBytes = append(encryptedPasswordBytes, byte(val))
  112. }
  113. return encryptedPasswordBytes
  114. }
  115. // GetCryptedPasswordLength obtains crypted password length from crypted password byte array.
  116. func GetCryptedPasswordLength(flag byte, encryptedPasswordBytes []byte) (byte, []byte) {
  117. var cryptedPasswordlength byte = 0
  118. if flag == PasswordFlag {
  119. _, encryptedPasswordBytes = DecryptNextCharacter(encryptedPasswordBytes) // Ignore two characters of the encryptedPasswordBytes.
  120. cryptedPasswordlength, encryptedPasswordBytes = DecryptNextCharacter(encryptedPasswordBytes) // decryptNextCharacter alters the encryptedPasswordBytes variable to remove already parsed characters.
  121. } else {
  122. cryptedPasswordlength = flag
  123. }
  124. toBeDeleted, encryptedPasswordBytes := DecryptNextCharacter(encryptedPasswordBytes) // decryptNextCharacter alters the encryptedPasswordBytes variable to remove already parsed characters.
  125. encryptedPasswordBytes = encryptedPasswordBytes[toBeDeleted*2:]
  126. return cryptedPasswordlength, encryptedPasswordBytes
  127. }
  128. // GetPassword obtains clear password from crypted password byte array
  129. func GetPassword(cryptedPasswordlength byte, encryptedPasswordBytes []byte) string {
  130. var i, character byte
  131. var decryptedPassword string
  132. for i = 0; i < cryptedPasswordlength; i++ {
  133. character, encryptedPasswordBytes = DecryptNextCharacter(encryptedPasswordBytes) // decryptNextCharacter alters the encryptedPasswordBytes variable to remove already parsed characters.
  134. decryptedPassword += string(character) // Add decrypted character to the result variable.
  135. }
  136. return decryptedPassword
  137. }
  138. // DecryptNextCharacter decrypts next character from byte array.
  139. // Alters the byte array to remove already parsed bytes.
  140. func DecryptNextCharacter(encryptedPasswordBytes []byte) (byte, []byte) {
  141. if len(encryptedPasswordBytes) <= 0 {
  142. // In case encryptedPasswordBytes param was empty,
  143. // stop the flow here returning '0'.
  144. return 0, encryptedPasswordBytes
  145. }
  146. a := encryptedPasswordBytes[0] // Obtain first character to parse.
  147. b := encryptedPasswordBytes[1] // Obtain second character to parse.
  148. encryptedPasswordBytes = encryptedPasswordBytes[2:] // Remove already parsed characters.
  149. return DecryptCharacter(a, b), encryptedPasswordBytes
  150. }
  151. // DecryptCharacter decrypts character from two bytes.
  152. func DecryptCharacter(a, b byte) byte {
  153. return ^(((a << 4) + b) ^ PasswordMagic) & PasswordFlag
  154. }