From 2a3be10d7eef3e39a140f66c30480afac1360132 Mon Sep 17 00:00:00 2001 From: Denys Vitali Date: Fri, 18 Nov 2022 11:05:20 +0100 Subject: [PATCH] initial commit --- .gitignore | 2 ++ cmd/tesla-firmware-decrypt/main.go | 40 +++++++++++++++++++++++++++++ go.mod | 12 +++++++++ go.sum | 19 ++++++++++++++ pkg/decrypt.go | 38 +++++++++++++++++++++++++++ pkg/decrypt_test.go | 41 ++++++++++++++++++++++++++++++ py/main.py | 29 +++++++++++++++++++++ resources/1_0.bin | 1 + 8 files changed, 182 insertions(+) create mode 100644 .gitignore create mode 100644 cmd/tesla-firmware-decrypt/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 pkg/decrypt.go create mode 100644 pkg/decrypt_test.go create mode 100644 py/main.py create mode 100644 resources/1_0.bin diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..04cfa0a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.idea +/*.sh \ No newline at end of file diff --git a/cmd/tesla-firmware-decrypt/main.go b/cmd/tesla-firmware-decrypt/main.go new file mode 100644 index 0000000..6cdb2a9 --- /dev/null +++ b/cmd/tesla-firmware-decrypt/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "encoding/base64" + "github.com/alexflint/go-arg" + "github.com/denysvitali/tesla-firmware-decrypt/pkg" + "github.com/sirupsen/logrus" + "os" +) + +var args struct { + InputFile string `arg:"positional,required"` + Key string `arg:"-k,required"` +} + +var logger = logrus.New() + +func main() { + arg.MustParse(&args) + f, err := os.Open(args.InputFile) + if err != nil { + logger.Fatalf("unable to open file: %v", err) + } + + keyBytes, err := base64.StdEncoding.DecodeString(args.Key) + if err != nil { + logger.Fatalf("unable to decode base64 key: %v", err) + } + if len(keyBytes) != 32 { + logger.Fatalf("invalid key size: expected 32 but received %d", len(keyBytes)) + } + + dec := decrypt.Decrypt{ + Key: keyBytes, + } + err = dec.Decrypt(f, os.Stdout) + if err != nil { + logger.Fatalf("unable to decrypt: %v", err) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b0d0a8a --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module github.com/denysvitali/tesla-firmware-decrypt + +go 1.19 + +require golang.org/x/crypto v0.3.0 + +require ( + github.com/alexflint/go-arg v1.4.3 // indirect + github.com/alexflint/go-scalar v1.1.0 // indirect + github.com/sirupsen/logrus v1.9.0 // indirect + golang.org/x/sys v0.2.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c14f20e --- /dev/null +++ b/go.sum @@ -0,0 +1,19 @@ +github.com/alexflint/go-arg v1.4.3 h1:9rwwEBpMXfKQKceuZfYcwuc/7YY7tWJbFsgG5cAU/uo= +github.com/alexflint/go-arg v1.4.3/go.mod h1:3PZ/wp/8HuqRZMUUgu7I+e1qcpUbvmS258mRXkFH4IA= +github.com/alexflint/go-scalar v1.1.0 h1:aaAouLLzI9TChcPXotr6gUhq+Scr8rl0P9P4PnltbhM= +github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/decrypt.go b/pkg/decrypt.go new file mode 100644 index 0000000..21e70ad --- /dev/null +++ b/pkg/decrypt.go @@ -0,0 +1,38 @@ +package decrypt + +import ( + "encoding/binary" + "fmt" + "golang.org/x/crypto/salsa20" + "io" +) + +type Decrypt struct { + Key []byte +} + +func (d *Decrypt) Decrypt(reader io.Reader, writer io.Writer) error { + if len(d.Key) != 32 { + return fmt.Errorf("key must be 32 bytes long") + } + blockNumber := 0 + for { + nonceBytes := make([]byte, 24) + binary.LittleEndian.PutUint64(nonceBytes, uint64(blockNumber)) + block := make([]byte, 256) + _, err := reader.Read(block) + if err == io.EOF { + return nil + } + if err != nil { + return fmt.Errorf("unable to read file: %v", err) + } + outBlock := make([]byte, 256) + salsa20.XORKeyStream(outBlock, block, nonceBytes, (*[32]byte)(d.Key)) + blockNumber++ + _, err = writer.Write(outBlock) + if err != nil { + return fmt.Errorf("unable to write: %v", err) + } + } +} diff --git a/pkg/decrypt_test.go b/pkg/decrypt_test.go new file mode 100644 index 0000000..fbfcaab --- /dev/null +++ b/pkg/decrypt_test.go @@ -0,0 +1,41 @@ +package decrypt_test + +import ( + "bytes" + "encoding/base64" + "os" + decrypt "tesla-firmware-decrypt/pkg" + "testing" +) + +func TestDecrypt_Decrypt(t *testing.T) { + ciphertext, err := os.Open("./resources/1_0.bin") + if err != nil { + t.Fatalf("unable to open file: %v", err) + } + + key := "uPaonw9KIv1tptQ3hsJsrR2Ibz3YhOcBRBaRitSi4Qg=" + keyBytes, err := base64.StdEncoding.DecodeString(key) + if err != nil { + t.Fatalf("unable to decode base64: %v", err) + } + d := decrypt.Decrypt{Key: keyBytes} + + buffer := bytes.NewBuffer(nil) + err = d.Decrypt(ciphertext, buffer) + + if err != nil { + t.Fatalf("unable to decrypt: %v", err) + } + + expectedHeader := []byte("hsqs") + header := make([]byte, len(expectedHeader)) + _, err = buffer.Read(header) + if err != nil { + t.Fatalf("unable to read into buffer: %v", err) + } + + if !bytes.Equal(expectedHeader, header) { + t.Fatalf("decryption failed: got %s but %s was expected", header, expectedHeader) + } +} diff --git a/py/main.py b/py/main.py new file mode 100644 index 0000000..089e3c8 --- /dev/null +++ b/py/main.py @@ -0,0 +1,29 @@ +import base64 +import tempfile + +import click +import salsa20 + + +@click.command() +@click.option("-i", "--input", required=True) +@click.option("-k", "--key", required=True) +def cmd(input: str, key: str): + tmp = tempfile.mktemp() + print(tmp) + + with open(tmp, mode="wb") as tmp_out: + with open(input, mode="rb") as f: + counter = 0 + key_bytes = base64.b64decode(key) + while True: + in_bytes = f.read(256) + if len(in_bytes) == 0: + return + counter_bytes = counter.to_bytes(24, byteorder='little') + res = salsa20.XSalsa20_xor(in_bytes, counter_bytes, key_bytes) + tmp_out.write(res) + counter += 1 + +if __name__ == "__main__": + cmd() diff --git a/resources/1_0.bin b/resources/1_0.bin new file mode 100644 index 0000000..6e46e1c --- /dev/null +++ b/resources/1_0.bin @@ -0,0 +1 @@ +fزt[$sIRHB% 8, Oy ]il򈻘AS;-4TfSV]/PZ~ezEʻ|g| Be1zzϣDfD%(`bAҙ"XwxN!\ܙT'FaL`7J#y֗谅9׏ @,F P./F\Wd(2Ӭ!HPg ưuPǰ m(t$ \ No newline at end of file