Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 122 additions & 64 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,119 +2,177 @@ package main

import (
"fmt"
"log"
"net/url"
"os"
"os/user"
"runtime"
"strconv"
"github.com/go-ini/ini"
"strings"
"os/user"
"log"

"gopkg.in/ini.v1"
)

// WinSCP password encryption/decryption salts.
const (
PW_MAGIC = 0xA3
PW_FLAG = 0xFF
PasswordMagic = 0xA3
PasswordFlag = 0xFF
)

func main() {
args := os.Args[1:]
if len(args) != 3 && len(args) != 2 {
fmt.Println("WinSCP stored password finder\n")
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\n")
if runtime.GOOS == "windows" {
fmt.Println(" Usage winscppasswd.exe <host> <username> <encrypted_password>")
} else {
fmt.Println(" Usage ./winscppasswd <host> <username> <encrypted_password>")
}
fmt.Println("\n\nWinSCP.ini:")
if runtime.GOOS == "windows" {
fmt.Println(" Usage winscppasswd.exe ini [<filepath>]")
} else {
fmt.Println(" Usage ./winscppasswd ini [<filepath>]")
}
fmt.Printf(" Default value <filepath>: %s\n", defaultWinSCPIniFilePath());
// In case provided arguments doesn't match the
// application usage, print application usage message.
PrintHelp()
return
}

if args[0] == "ini" {
if (len(args) == 2) {
decryptIni(args[1]);
// In case 'ini' argument was provided,
// start the decryption of ini file.
var iniPath string
if len(args) == 2 {
iniPath = args[1]
} else {
decryptIni(defaultWinSCPIniFilePath());
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(decrypt(args[0], args[1], args[2]))
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>]")
}
}

func defaultWinSCPIniFilePath() string {
// GetDefaultWinSCPIniFilePath obtains default WinSCP configuration file.
func GetDefaultWinSCPIniFilePath() string {
usr, err := user.Current()
if err != nil {
log.Fatal( err )
log.Fatal(err)
}
return usr.HomeDir + "\\AppData\\Roaming\\winSCP.ini"
}

func decryptIni(filepath string) {
// DecryptIni decrypts all entries from a WinSCP's ini file.
func DecryptIni(filepath string) {
cfg, err := ini.InsensitiveLoad(filepath)
if (err != nil) {
panic(err);
if err != nil {
panic(err)
}

// Print every entry of the configuration that has password field.
for _, c := range cfg.Sections() {
if c.HasKey("Password") {
fmt.Printf("%s\n", strings.TrimPrefix(c.Name(), "sessions\\"));
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.Printf(" Password: %s\n", Decrypt(c.Key("HostName").Value(), c.Key("UserName").Value(), c.Key("Password").Value()))
fmt.Println("========================")
}
}

}

func decrypt(host, username, password string) string {
key := username + host
passbytes := []byte{}
// 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)
passbytes = append(passbytes, byte(val))
encryptedPasswordBytes = append(encryptedPasswordBytes, byte(val))
}
var flag byte
flag, passbytes = dec_next_char(passbytes)
var length byte = 0
if flag == PW_FLAG {
_, passbytes = dec_next_char(passbytes)
return encryptedPasswordBytes
}

length, passbytes = dec_next_char(passbytes)
// 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 {
length = flag
}
toBeDeleted, passbytes := dec_next_char(passbytes)
passbytes = passbytes[toBeDeleted*2:]

clearpass := ""
var (
i byte
val byte
)
for i = 0; i < length; i++ {
val, passbytes = dec_next_char(passbytes)
clearpass += string(val)
cryptedPasswordlength = flag
}
toBeDeleted, encryptedPasswordBytes := DecryptNextCharacter(encryptedPasswordBytes) // decryptNextCharacter alters the encryptedPasswordBytes variable to remove already parsed characters.
encryptedPasswordBytes = encryptedPasswordBytes[toBeDeleted*2:]
return cryptedPasswordlength, encryptedPasswordBytes
}

if flag == PW_FLAG {
clearpass = clearpass[len(key):]
// 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 clearpass
return decryptedPassword
}

func dec_next_char(passbytes []byte) (byte, []byte) {
if len(passbytes) <= 0 {
return 0, passbytes
// 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 := passbytes[0]
b := passbytes[1]
passbytes = passbytes[2:]
return ^(((a << 4) + b) ^ PW_MAGIC) & 0xff, passbytes

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
}