Skip to content
Permalink
Browse files

Merge pull request #11235 from unknownbrackets/savedata

Savedata: Use file hash to validate hash mode
  • Loading branch information...
hrydgard committed Jun 30, 2018
2 parents 5e6429a + 1976be4 commit 53f16243277ba9b6abb439ad8e0028d5c819790c
Showing with 97 additions and 63 deletions.
  1. +86 −60 Core/Dialog/SavedataParam.cpp
  2. +11 −3 Core/Dialog/SavedataParam.h
@@ -435,39 +435,31 @@ int SavedataParam::Save(SceUtilitySavedataParam* param, const std::string &saveD
sfoFile.SetValue("SAVEDATA_DIRECTORY", GetSaveDir(param, saveDirName), 64);

// For each file, 13 bytes for filename, 16 bytes for file hash (0 in PPSSPP), 3 byte for padding
if (secureMode)
{
const int FILE_LIST_ITEM_SIZE = 13 + 16 + 3;
if (secureMode) {
const int FILE_LIST_COUNT_MAX = 99;
const u32 FILE_LIST_TOTAL_SIZE = FILE_LIST_ITEM_SIZE * FILE_LIST_COUNT_MAX;
const u32 FILE_LIST_TOTAL_SIZE = sizeof(SaveSFOFileListEntry) * FILE_LIST_COUNT_MAX;
u32 tmpDataSize = 0;
u8 *tmpDataOrig = sfoFile.GetValueData("SAVEDATA_FILE_LIST", &tmpDataSize);
u8 *tmpData = new u8[FILE_LIST_TOTAL_SIZE];

if (tmpDataOrig != NULL)
memcpy(tmpData, tmpDataOrig, tmpDataSize > FILE_LIST_TOTAL_SIZE ? FILE_LIST_TOTAL_SIZE : tmpDataSize);
else
memset(tmpData, 0, FILE_LIST_TOTAL_SIZE);
SaveSFOFileListEntry *tmpDataOrig = (SaveSFOFileListEntry *)sfoFile.GetValueData("SAVEDATA_FILE_LIST", &tmpDataSize);
SaveSFOFileListEntry *updatedList = new SaveSFOFileListEntry[FILE_LIST_COUNT_MAX];
if (tmpDataSize != 0)
memcpy(updatedList, tmpDataOrig, std::min(tmpDataSize, FILE_LIST_TOTAL_SIZE));
if (tmpDataSize < FILE_LIST_TOTAL_SIZE)
memset(updatedList + tmpDataSize, 0, FILE_LIST_TOTAL_SIZE - tmpDataSize);

if (param->dataBuf.IsValid()) {
const std::string saveFilename = GetFileName(param);
for (auto entry = updatedList; entry < updatedList + FILE_LIST_COUNT_MAX; ++entry) {
if (entry->filename[0] != '\0') {
if (strncmp(entry->filename, saveFilename.c_str(), sizeof(entry->filename)) != 0)
continue;
}

if (param->dataBuf.IsValid())
{
char *fName = (char*)tmpData;
for(int i = 0; i < FILE_LIST_COUNT_MAX; i++)
{
if(fName[0] == 0)
break; // End of list
if(strncmp(fName,GetFileName(param).c_str(),20) == 0)
break;
fName += FILE_LIST_ITEM_SIZE;
snprintf(entry->filename, sizeof(entry->filename), "%s", saveFilename.c_str());
memcpy(entry->hash, cryptedHash, 16);
}

if (fName + 13 <= (char*)tmpData + FILE_LIST_TOTAL_SIZE)
snprintf(fName, 13, "%s",GetFileName(param).c_str());
if (fName + 13 + 16 <= (char*)tmpData + FILE_LIST_TOTAL_SIZE)
memcpy(fName+13, cryptedHash, 16);
}
sfoFile.SetValue("SAVEDATA_FILE_LIST", tmpData, FILE_LIST_TOTAL_SIZE, (int)FILE_LIST_TOTAL_SIZE);
delete[] tmpData;
sfoFile.SetValue("SAVEDATA_FILE_LIST", (u8 *)updatedList, FILE_LIST_TOTAL_SIZE, (int)FILE_LIST_TOTAL_SIZE);
delete[] updatedList;
}

// Init param with 0. This will be used to detect crypted save or not on loading
@@ -599,10 +591,11 @@ int SavedataParam::LoadSaveData(SceUtilitySavedataParam *param, const std::strin
WARN_LOG_REPORT(SCEUTILITY, "Savedata version requested: %d", param->secureVersion);
}
u8 *data_ = param->dataBuf;
std::string filePath = dirPath+"/"+GetFileName(param);
std::string filename = GetFileName(param);
std::string filePath = dirPath + "/" + filename;
s64 readSize;
INFO_LOG(SCEUTILITY,"Loading file with size %u in %s",param->dataBufSize,filePath.c_str());
u8* saveData = 0;
u8 *saveData = nullptr;
int saveSize = -1;
if (!ReadPSPFile(filePath, &saveData, saveSize, &readSize)) {
ERROR_LOG(SCEUTILITY,"Error reading file %s",filePath.c_str());
@@ -619,7 +612,10 @@ int SavedataParam::LoadSaveData(SceUtilitySavedataParam *param, const std::strin
if (isCrypted) {
if (DetermineCryptMode(param) > 1 && !HasKey(param))
return SCE_UTILITY_SAVEDATA_ERROR_LOAD_PARAM;
LoadCryptedSave(param, data_, saveData, saveSize, prevCryptMode, saveDone);

u8 hash[16];
bool hasExpectedHash = GetExpectedHash(dirPath, filename, hash);
LoadCryptedSave(param, data_, saveData, saveSize, prevCryptMode, hasExpectedHash ? hash : nullptr, saveDone);
// TODO: Should return SCE_UTILITY_SAVEDATA_ERROR_LOAD_DATA_BROKEN here if !saveDone.
}
if (!saveDone) {
@@ -646,13 +642,14 @@ int SavedataParam::DetermineCryptMode(const SceUtilitySavedataParam *param) cons
return decryptMode;
}

void SavedataParam::LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize, int prevCryptMode, bool &saveDone) {
void SavedataParam::LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize, int prevCryptMode, const u8 *expectedHash, bool &saveDone) {
int align_len = align16(saveSize);
u8 *data_base = new u8[align_len];
u8 *cryptKey = new u8[0x10];
memset(cryptKey, 0, 0x10);

int decryptMode = DetermineCryptMode(param);
const int detectedMode = decryptMode;
bool hasKey = decryptMode > 1;
if (hasKey) {
memcpy(cryptKey, param->key, 0x10);
@@ -688,7 +685,19 @@ void SavedataParam::LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8
hasKey = decryptMode > 1;
}

if (DecryptSave(decryptMode, data_base, &saveSize, &align_len, (hasKey?cryptKey:0)) == 0) {
int err = DecryptSave(decryptMode, data_base, &saveSize, &align_len, hasKey ? cryptKey : nullptr, expectedHash);
// Perhaps the file had the wrong mode....
if (err != 0 && detectedMode != decryptMode) {
hasKey = detectedMode > 1;
err = DecryptSave(detectedMode, data_base, &saveSize, &align_len, hasKey ? cryptKey : nullptr, expectedHash);
}
// TODO: Should return an error, but let's just try with a bad hash.
if (err != 0 && expectedHash != nullptr) {
WARN_LOG(SCEUTILITY, "Incorrect hash on save data, likely corrupt");
err = DecryptSave(decryptMode, data_base, &saveSize, &align_len, hasKey ? cryptKey : nullptr, nullptr);
}

if (err == 0) {
if (param->dataBuf.IsValid())
memcpy(data, data_base, std::min((u32)saveSize, (u32)param->dataBufSize));
saveDone = true;
@@ -721,39 +730,54 @@ void SavedataParam::LoadSFO(SceUtilitySavedataParam *param, const std::string& d
}
}

std::set<std::string> SavedataParam::getSecureFileNames(std::string dirPath) {
PSPFileInfo sfoFileInfo = pspFileSystem.GetFileInfo(dirPath + "/" + SFO_FILENAME);
std::set<std::string> secureFileNames;
if (!sfoFileInfo.exists)
return secureFileNames;
std::vector<SaveSFOFileListEntry> SavedataParam::GetSFOEntries(const std::string &dirPath) {
std::vector<SaveSFOFileListEntry> result;
const std::string sfoPath = dirPath + "/" + SFO_FILENAME;
if (!pspFileSystem.GetFileInfo(sfoPath).exists)
return result;

ParamSFOData sfoFile;
std::vector<u8> sfoData;
if (pspFileSystem.ReadEntireFile(dirPath + "/" + SFO_FILENAME, sfoData) >= 0) {
if (pspFileSystem.ReadEntireFile(dirPath + "/" + SFO_FILENAME, sfoData) >= 0)
sfoFile.ReadSFO(sfoData);
}

const int FILE_LIST_COUNT_MAX = 99;
u32 sfoFileListSize = 0;
char *sfoFileList = (char *)sfoFile.GetValueData("SAVEDATA_FILE_LIST", &sfoFileListSize);
const int FILE_LIST_ITEM_SIZE = 13 + 16 + 3;
const u32 FILE_LIST_COUNT_MAX = 99;
SaveSFOFileListEntry *sfoFileList = (SaveSFOFileListEntry *)sfoFile.GetValueData("SAVEDATA_FILE_LIST", &sfoFileListSize);
const u32 count = std::min((u32)FILE_LIST_COUNT_MAX, sfoFileListSize / (u32)sizeof(SaveSFOFileListEntry));

// Filenames are 13 bytes long at most. Add a NULL so there's no surprises.
char temp[14];
temp[13] = '\0';
for (u32 i = 0; i < count; ++i) {
if (sfoFileList[i].filename[0] != '\0')
result.push_back(sfoFileList[i]);
}

for (u32 i = 0; i < FILE_LIST_COUNT_MAX; ++i) {
// Ends at a NULL filename.
if (i * FILE_LIST_ITEM_SIZE >= sfoFileListSize || sfoFileList[i * FILE_LIST_ITEM_SIZE] == '\0') {
break;
}
return result;
}

std::set<std::string> SavedataParam::GetSecureFileNames(const std::string &dirPath) {
auto entries = GetSFOEntries(dirPath);

strncpy(temp, &sfoFileList[i * FILE_LIST_ITEM_SIZE], 13);
std::set<std::string> secureFileNames;
for (auto entry : entries) {
char temp[14];
truncate_cpy(temp, entry.filename);
secureFileNames.insert(temp);
}
return secureFileNames;
}

bool SavedataParam::GetExpectedHash(const std::string &dirPath, const std::string &filename, u8 hash[16]) {
auto entries = GetSFOEntries(dirPath);

for (auto entry : entries) {
if (strncmp(entry.filename, filename.c_str(), sizeof(entry.filename)) == 0) {
memcpy(hash, entry.hash, sizeof(entry.hash));
return true;
}
}
return false;
}

void SavedataParam::LoadFile(const std::string& dirPath, const std::string& filename, PspUtilitySavedataFileData *fileData) {
std::string filePath = dirPath + "/" + filename;
s64 readSize = -1;
@@ -816,13 +840,7 @@ int SavedataParam::EncryptData(unsigned int mode,
return 0;
}

int SavedataParam::DecryptSave(unsigned int mode,
unsigned char *data,
int *dataLen,
int *alignedLen,
unsigned char *cryptkey)
{

int SavedataParam::DecryptSave(unsigned int mode, unsigned char *data, int *dataLen, int *alignedLen, unsigned char *cryptkey, const u8 *expectedHash) {
pspChnnlsvContext1 ctx1;
pspChnnlsvContext2 ctx2;

@@ -852,6 +870,14 @@ int SavedataParam::DecryptSave(unsigned int mode,
if (sceChnnlsv_21BE78B4_(ctx2) < 0)
return -7;

if (expectedHash) {
u8 hash[16];
if (sceSdGetLastIndex_(ctx1, hash, cryptkey) < 0)
return -7;
if (memcmp(hash, expectedHash, sizeof(hash)) != 0)
return -8;
}

/* The decrypted data starts at data + 0x10, so shift it back. */
memmove(data, data + 0x10, *dataLen);
return 0;
@@ -1154,7 +1180,7 @@ int SavedataParam::GetFilesList(SceUtilitySavedataParam *param)
std::set<std::string> secureFilenames;

if (sfoFileInfo.exists) {
secureFilenames = getSecureFileNames(dirPath);
secureFilenames = GetSecureFileNames(dirPath);
} else {
return SCE_UTILITY_SAVEDATA_ERROR_RW_DATA_BROKEN;
}
@@ -293,6 +293,12 @@ struct SaveFileInfo
void DoState(PointerWrap &p);
};

struct SaveSFOFileListEntry {
char filename[13];
u8 hash[16];
u8 pad[3];
};

class SavedataParam
{
public:
@@ -355,18 +361,20 @@ class SavedataParam
void ClearFileInfo(SaveFileInfo &saveInfo, const std::string &saveName);

int LoadSaveData(SceUtilitySavedataParam *param, const std::string &saveDirName, const std::string& dirPath, bool secureMode);
void LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize, int prevCryptMode, bool &saveDone);
void LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize, int prevCryptMode, const u8 *expectedHash, bool &saveDone);
void LoadNotCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize);
void LoadSFO(SceUtilitySavedataParam *param, const std::string& dirPath);
void LoadFile(const std::string& dirPath, const std::string& filename, PspUtilitySavedataFileData *fileData);

int DecryptSave(unsigned int mode, unsigned char *data, int *dataLen, int *alignedLen, unsigned char *cryptkey);
int DecryptSave(unsigned int mode, unsigned char *data, int *dataLen, int *alignedLen, unsigned char *cryptkey, const u8 *expectedHash);
int EncryptData(unsigned int mode, unsigned char *data, int *dataLen, int *alignedLen, unsigned char *hash, unsigned char *cryptkey);
int UpdateHash(u8* sfoData, int sfoSize, int sfoDataParamsOffset, int encryptmode);
int BuildHash(unsigned char *output, unsigned char *data, unsigned int len, unsigned int alignedLen, int mode, unsigned char *cryptkey);
int DetermineCryptMode(const SceUtilitySavedataParam *param) const;

std::set<std::string> getSecureFileNames(std::string dirPath);
std::vector<SaveSFOFileListEntry> GetSFOEntries(const std::string &dirPath);
std::set<std::string> GetSecureFileNames(const std::string &dirPath);
bool GetExpectedHash(const std::string &dirPath, const std::string &filename, u8 hash[16]);

SceUtilitySavedataParam* pspParam;
int selectedSave;

0 comments on commit 53f1624

Please sign in to comment.
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.