Skip to content

Commit

Permalink
Add hash checking
Browse files Browse the repository at this point in the history
  • Loading branch information
Xpl0itU committed Jul 14, 2023
1 parent 4e94407 commit c3caebd
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 8 deletions.
8 changes: 7 additions & 1 deletion cmd/MLCRestorerDownloader/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,18 @@ func downloadTitles(region string, titles map[string][]string, titleType string)

allTitles := append(selectedRegionTitles, allRegionTitles...)

commonKey, err := getCommonKey()
if err != nil {
fmt.Println("Error:", err)
return
}

for _, titleID := range allTitles {
if titleID == "dummy" {
continue
}
fmt.Printf("Downloading files for title %s on region %s for type %s\n", titleID, region, titleType)
err := downloader.DownloadTitle(titleID, fmt.Sprintf("output/%s/%s/%s", titleType, region, titleID))
err := downloader.DownloadTitle(titleID, fmt.Sprintf("output/%s/%s/%s", titleType, region, titleID), commonKey)
if err != nil {
fmt.Println("Error:", err)
os.Exit(1)
Expand Down
44 changes: 44 additions & 0 deletions cmd/MLCRestorerDownloader/otp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package main

import (
"crypto/sha1"
"encoding/hex"
"fmt"
"os"
"strings"
)

func getCommonKey() ([]byte, error) {
if fileExists("otp.bin") {
otp, err := os.ReadFile("otp.bin")
if err != nil {
return nil, err
}
commonKey := otp[0x0E0 : 0x0E0+0x10]
commonKeyHash := sha1.Sum(commonKey)
if hex.EncodeToString(commonKeyHash[:]) != "6a0b87fc98b306ae3366f0e0a88d0b06a2813313" {
return nil, fmt.Errorf("invalid common key from otp")
}
return commonKey, nil
}
fmt.Print("Common key not found. Enter it here: ")
var inputKey string
fmt.Scanln(&inputKey)
commonKey, err := hex.DecodeString(strings.TrimSpace(inputKey))
if err != nil {
return nil, err
}
commonKeyHash := sha1.Sum(commonKey)
if hex.EncodeToString(commonKeyHash[:]) != "6a0b87fc98b306ae3366f0e0a88d0b06a2813313" {
return nil, fmt.Errorf("invalid common key")
}
return commonKey, nil
}

func fileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}
39 changes: 32 additions & 7 deletions downloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mlcrestorerdownloader
import (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"os"
"path/filepath"
Expand All @@ -25,11 +26,15 @@ func downloadFile(client *grab.Client, url string, outputPath string) error {
return nil
}

func DownloadTitle(titleID string, outputDirectory string) error {
func DownloadTitle(titleID string, outputDirectory string, commonKey []byte) error {
outputDir := strings.TrimRight(outputDirectory, "/\\")
baseURL := fmt.Sprintf("http://ccs.cdn.c.shop.nintendowifi.net/ccs/download/%s", titleID)
titleKeyBytes, err := hex.DecodeString(titleID)
if err != nil {
return err
}

err := os.MkdirAll(outputDir, os.ModePerm)
err = os.MkdirAll(outputDir, os.ModePerm)
if err != nil {
return err
}
Expand Down Expand Up @@ -59,39 +64,44 @@ func DownloadTitle(titleID string, outputDirectory string) error {
if err != nil {
return err
}
tikData, err := os.ReadFile(tikPath)
if err != nil {
return err
}
encryptedTitleKey := tikData[0x1BF : 0x1BF+0x10]

var contentCount uint16
err = binary.Read(bytes.NewReader(tmdData[478:480]), binary.BigEndian, &contentCount)
if err != nil {
return err
}

cetk := bytes.Buffer{}
cert := bytes.Buffer{}

cert0, err := getCert(tmdData, 0, contentCount)
if err != nil {
return err
}
cetk.Write(cert0)
cert.Write(cert0)

cert1, err := getCert(tmdData, 1, contentCount)
if err != nil {
return err
}
cetk.Write(cert1)
cert.Write(cert1)

defaultCert, err := getDefaultCert()
if err != nil {
return err
}
cetk.Write(defaultCert)
cert.Write(defaultCert)

certPath := filepath.Join(outputDir, "title.cert")
certFile, err := os.Create(certPath)
if err != nil {
return err
}
err = binary.Write(certFile, binary.BigEndian, cetk.Bytes())
err = binary.Write(certFile, binary.BigEndian, cert.Bytes())
if err != nil {
return err
}
Expand Down Expand Up @@ -119,6 +129,21 @@ func DownloadTitle(titleID string, outputDirectory string) error {
if err != nil {
return err
}
var content contentInfo
content.Hash = tmdData[offset+16 : offset+0x14]
content.ID = fmt.Sprintf("%08X", id)
content.Size = int64(tmdData[offset+8])<<56 |
int64(tmdData[offset+9])<<48 |
int64(tmdData[offset+10])<<40 |
int64(tmdData[offset+11])<<32 |
int64(tmdData[offset+12])<<24 |
int64(tmdData[offset+13])<<16 |
int64(tmdData[offset+14])<<8 |
int64(tmdData[offset+15])
err = checkContentHashes(outputDirectory, commonKey, encryptedTitleKey, titleKeyBytes, content)
if err != nil {
return err
}
}
}

Expand Down
109 changes: 109 additions & 0 deletions hash.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package mlcrestorerdownloader

import (
"crypto/aes"
"crypto/cipher"
"crypto/sha1"
"fmt"
"os"
"reflect"
)

func checkContentHashes(path string, commonKey []byte, encryptedTitleKey []byte, titleID []byte, content contentInfo) error {
c, err := aes.NewCipher(commonKey)
if err != nil {
return fmt.Errorf("failed to create AES cipher: %w", err)
}

decryptedTitleKey := make([]byte, len(encryptedTitleKey))
cbc := cipher.NewCBCDecrypter(c, append(titleID, make([]byte, 8)...))
cbc.CryptBlocks(decryptedTitleKey, encryptedTitleKey)

h3Data, err := os.ReadFile(fmt.Sprintf("%s/%s.h3", path, content.ID))
if err != nil {
return fmt.Errorf("failed to read H3 hash tree file: %w", err)
}
encryptedFile, err := os.OpenFile(fmt.Sprintf("%s/%s.app", path, content.ID), os.O_RDONLY, 0)
if err != nil {
return fmt.Errorf("failed to open encrypted file: %w", err)
}

h3Hash := sha1.Sum(h3Data)
if !equalSlices(h3Hash[:8], content.Hash[:8]) {
return fmt.Errorf("h3 Hash mismatch")
}

chunkCount := int(content.Size / 0x10000)
decryptedContent := make([]byte, content.Size)

h0HashNum := 0
h1HashNum := 0
h2HashNum := 0
h3HashNum := 0

for chunkNum := 0; chunkNum < chunkCount; chunkNum++ {
cipherHashTree, err := aes.NewCipher(decryptedTitleKey)
if err != nil {
return fmt.Errorf("failed to create AES cipher: %w", err)
}
hashTree := cipher.NewCBCDecrypter(cipherHashTree, make([]byte, aes.BlockSize))
buffer := make([]byte, 0x400)
encryptedFile.Read(buffer)
hashTree.CryptBlocks(decryptedContent, buffer)

h0Hashes := decryptedContent[0:0x140]
h1Hashes := decryptedContent[0x140:0x280]
h2Hashes := decryptedContent[0x280:0x3c0]

h1Hash := h1Hashes[(h1HashNum * 0x14):((h1HashNum + 1) * 0x14)]
h2Hash := h2Hashes[(h2HashNum * 0x14):((h2HashNum + 1) * 0x14)]
h3Hash := h3Data[(h3HashNum * 0x14):((h3HashNum + 1) * 0x14)]

h0HashesHash := sha1.Sum(h0Hashes)
h1HashesHash := sha1.Sum(h1Hashes)
h2HashesHash := sha1.Sum(h2Hashes)

if !reflect.DeepEqual(h0HashesHash[:], h1Hash) {
return fmt.Errorf("h0 Hashes Hash mismatch")
}
if !reflect.DeepEqual(h1HashesHash[:], h2Hash) {
return fmt.Errorf("h1 Hashes Hash mismatch")
}
if !reflect.DeepEqual(h2HashesHash[:], h3Hash) {
return fmt.Errorf("h2 Hashes Hash mismatch")
}
encryptedFile.Seek(0xFC00, 1)
h0HashNum++
if h0HashNum >= 16 {
h0HashNum = 0
h1HashNum++
}
if h1HashNum >= 16 {
h1HashNum = 0
h2HashNum++
}
if h2HashNum >= 16 {
h2HashNum = 0
h3HashNum++
}
}
return nil
}

type contentInfo struct {
ID string
Size int64
Hash []byte
}

func equalSlices(slice1, slice2 []byte) bool {
if len(slice1) != len(slice2) {
return false
}
for i := range slice1 {
if slice1[i] != slice2[i] {
return false
}
}
return true
}

0 comments on commit c3caebd

Please sign in to comment.