Savedata: Use file hash to validate hash mode.

This makes older PPSSPP save data also work, and also logs when save data
is detected as corrupt.
This commit is contained in:
Unknown W. Brackets 2018-06-30 12:17:52 -07:00
parent 5e6429abf7
commit 1976be48ab
2 changed files with 97 additions and 63 deletions

View file

@ -435,39 +435,31 @@ int SavedataParam::Save(SceUtilitySavedataParam* param, const std::string &saveD
sfoFile.SetValue("SAVEDATA_DIRECTORY", GetSaveDir(param, saveDirName), 64); 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 // For each file, 13 bytes for filename, 16 bytes for file hash (0 in PPSSPP), 3 byte for padding
if (secureMode) if (secureMode) {
{
const int FILE_LIST_ITEM_SIZE = 13 + 16 + 3;
const int FILE_LIST_COUNT_MAX = 99; 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; u32 tmpDataSize = 0;
u8 *tmpDataOrig = sfoFile.GetValueData("SAVEDATA_FILE_LIST", &tmpDataSize); SaveSFOFileListEntry *tmpDataOrig = (SaveSFOFileListEntry *)sfoFile.GetValueData("SAVEDATA_FILE_LIST", &tmpDataSize);
u8 *tmpData = new u8[FILE_LIST_TOTAL_SIZE]; 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 (tmpDataOrig != NULL) if (param->dataBuf.IsValid()) {
memcpy(tmpData, tmpDataOrig, tmpDataSize > FILE_LIST_TOTAL_SIZE ? FILE_LIST_TOTAL_SIZE : tmpDataSize); const std::string saveFilename = GetFileName(param);
else for (auto entry = updatedList; entry < updatedList + FILE_LIST_COUNT_MAX; ++entry) {
memset(tmpData, 0, FILE_LIST_TOTAL_SIZE); if (entry->filename[0] != '\0') {
if (strncmp(entry->filename, saveFilename.c_str(), sizeof(entry->filename)) != 0)
if (param->dataBuf.IsValid()) continue;
{
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;
} }
if (fName + 13 <= (char*)tmpData + FILE_LIST_TOTAL_SIZE) snprintf(entry->filename, sizeof(entry->filename), "%s", saveFilename.c_str());
snprintf(fName, 13, "%s",GetFileName(param).c_str()); memcpy(entry->hash, cryptedHash, 16);
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 // 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); WARN_LOG_REPORT(SCEUTILITY, "Savedata version requested: %d", param->secureVersion);
} }
u8 *data_ = param->dataBuf; u8 *data_ = param->dataBuf;
std::string filePath = dirPath+"/"+GetFileName(param); std::string filename = GetFileName(param);
std::string filePath = dirPath + "/" + filename;
s64 readSize; s64 readSize;
INFO_LOG(SCEUTILITY,"Loading file with size %u in %s",param->dataBufSize,filePath.c_str()); INFO_LOG(SCEUTILITY,"Loading file with size %u in %s",param->dataBufSize,filePath.c_str());
u8* saveData = 0; u8 *saveData = nullptr;
int saveSize = -1; int saveSize = -1;
if (!ReadPSPFile(filePath, &saveData, saveSize, &readSize)) { if (!ReadPSPFile(filePath, &saveData, saveSize, &readSize)) {
ERROR_LOG(SCEUTILITY,"Error reading file %s",filePath.c_str()); 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 (isCrypted) {
if (DetermineCryptMode(param) > 1 && !HasKey(param)) if (DetermineCryptMode(param) > 1 && !HasKey(param))
return SCE_UTILITY_SAVEDATA_ERROR_LOAD_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. // TODO: Should return SCE_UTILITY_SAVEDATA_ERROR_LOAD_DATA_BROKEN here if !saveDone.
} }
if (!saveDone) { if (!saveDone) {
@ -646,13 +642,14 @@ int SavedataParam::DetermineCryptMode(const SceUtilitySavedataParam *param) cons
return decryptMode; 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); int align_len = align16(saveSize);
u8 *data_base = new u8[align_len]; u8 *data_base = new u8[align_len];
u8 *cryptKey = new u8[0x10]; u8 *cryptKey = new u8[0x10];
memset(cryptKey, 0, 0x10); memset(cryptKey, 0, 0x10);
int decryptMode = DetermineCryptMode(param); int decryptMode = DetermineCryptMode(param);
const int detectedMode = decryptMode;
bool hasKey = decryptMode > 1; bool hasKey = decryptMode > 1;
if (hasKey) { if (hasKey) {
memcpy(cryptKey, param->key, 0x10); memcpy(cryptKey, param->key, 0x10);
@ -688,7 +685,19 @@ void SavedataParam::LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8
hasKey = decryptMode > 1; 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()) if (param->dataBuf.IsValid())
memcpy(data, data_base, std::min((u32)saveSize, (u32)param->dataBufSize)); memcpy(data, data_base, std::min((u32)saveSize, (u32)param->dataBufSize));
saveDone = true; saveDone = true;
@ -721,39 +730,54 @@ void SavedataParam::LoadSFO(SceUtilitySavedataParam *param, const std::string& d
} }
} }
std::set<std::string> SavedataParam::getSecureFileNames(std::string dirPath) { std::vector<SaveSFOFileListEntry> SavedataParam::GetSFOEntries(const std::string &dirPath) {
PSPFileInfo sfoFileInfo = pspFileSystem.GetFileInfo(dirPath + "/" + SFO_FILENAME); std::vector<SaveSFOFileListEntry> result;
std::set<std::string> secureFileNames; const std::string sfoPath = dirPath + "/" + SFO_FILENAME;
if (!sfoFileInfo.exists) if (!pspFileSystem.GetFileInfo(sfoPath).exists)
return secureFileNames; return result;
ParamSFOData sfoFile; ParamSFOData sfoFile;
std::vector<u8> sfoData; std::vector<u8> sfoData;
if (pspFileSystem.ReadEntireFile(dirPath + "/" + SFO_FILENAME, sfoData) >= 0) { if (pspFileSystem.ReadEntireFile(dirPath + "/" + SFO_FILENAME, sfoData) >= 0)
sfoFile.ReadSFO(sfoData); sfoFile.ReadSFO(sfoData);
}
const int FILE_LIST_COUNT_MAX = 99;
u32 sfoFileListSize = 0; u32 sfoFileListSize = 0;
char *sfoFileList = (char *)sfoFile.GetValueData("SAVEDATA_FILE_LIST", &sfoFileListSize); SaveSFOFileListEntry *sfoFileList = (SaveSFOFileListEntry *)sfoFile.GetValueData("SAVEDATA_FILE_LIST", &sfoFileListSize);
const int FILE_LIST_ITEM_SIZE = 13 + 16 + 3; const u32 count = std::min((u32)FILE_LIST_COUNT_MAX, sfoFileListSize / (u32)sizeof(SaveSFOFileListEntry));
const u32 FILE_LIST_COUNT_MAX = 99;
// Filenames are 13 bytes long at most. Add a NULL so there's no surprises. for (u32 i = 0; i < count; ++i) {
char temp[14]; if (sfoFileList[i].filename[0] != '\0')
temp[13] = '\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;
} }
strncpy(temp, &sfoFileList[i * FILE_LIST_ITEM_SIZE], 13); return result;
}
std::set<std::string> SavedataParam::GetSecureFileNames(const std::string &dirPath) {
auto entries = GetSFOEntries(dirPath);
std::set<std::string> secureFileNames;
for (auto entry : entries) {
char temp[14];
truncate_cpy(temp, entry.filename);
secureFileNames.insert(temp); secureFileNames.insert(temp);
} }
return secureFileNames; 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) { void SavedataParam::LoadFile(const std::string& dirPath, const std::string& filename, PspUtilitySavedataFileData *fileData) {
std::string filePath = dirPath + "/" + filename; std::string filePath = dirPath + "/" + filename;
s64 readSize = -1; s64 readSize = -1;
@ -816,13 +840,7 @@ int SavedataParam::EncryptData(unsigned int mode,
return 0; return 0;
} }
int SavedataParam::DecryptSave(unsigned int mode, int SavedataParam::DecryptSave(unsigned int mode, unsigned char *data, int *dataLen, int *alignedLen, unsigned char *cryptkey, const u8 *expectedHash) {
unsigned char *data,
int *dataLen,
int *alignedLen,
unsigned char *cryptkey)
{
pspChnnlsvContext1 ctx1; pspChnnlsvContext1 ctx1;
pspChnnlsvContext2 ctx2; pspChnnlsvContext2 ctx2;
@ -852,6 +870,14 @@ int SavedataParam::DecryptSave(unsigned int mode,
if (sceChnnlsv_21BE78B4_(ctx2) < 0) if (sceChnnlsv_21BE78B4_(ctx2) < 0)
return -7; 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. */ /* The decrypted data starts at data + 0x10, so shift it back. */
memmove(data, data + 0x10, *dataLen); memmove(data, data + 0x10, *dataLen);
return 0; return 0;
@ -1154,7 +1180,7 @@ int SavedataParam::GetFilesList(SceUtilitySavedataParam *param)
std::set<std::string> secureFilenames; std::set<std::string> secureFilenames;
if (sfoFileInfo.exists) { if (sfoFileInfo.exists) {
secureFilenames = getSecureFileNames(dirPath); secureFilenames = GetSecureFileNames(dirPath);
} else { } else {
return SCE_UTILITY_SAVEDATA_ERROR_RW_DATA_BROKEN; return SCE_UTILITY_SAVEDATA_ERROR_RW_DATA_BROKEN;
} }

View file

@ -293,6 +293,12 @@ struct SaveFileInfo
void DoState(PointerWrap &p); void DoState(PointerWrap &p);
}; };
struct SaveSFOFileListEntry {
char filename[13];
u8 hash[16];
u8 pad[3];
};
class SavedataParam class SavedataParam
{ {
public: public:
@ -355,18 +361,20 @@ private:
void ClearFileInfo(SaveFileInfo &saveInfo, const std::string &saveName); void ClearFileInfo(SaveFileInfo &saveInfo, const std::string &saveName);
int LoadSaveData(SceUtilitySavedataParam *param, const std::string &saveDirName, const std::string& dirPath, bool secureMode); 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 LoadNotCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize);
void LoadSFO(SceUtilitySavedataParam *param, const std::string& dirPath); void LoadSFO(SceUtilitySavedataParam *param, const std::string& dirPath);
void LoadFile(const std::string& dirPath, const std::string& filename, PspUtilitySavedataFileData *fileData); 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 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 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 BuildHash(unsigned char *output, unsigned char *data, unsigned int len, unsigned int alignedLen, int mode, unsigned char *cryptkey);
int DetermineCryptMode(const SceUtilitySavedataParam *param) const; 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; SceUtilitySavedataParam* pspParam;
int selectedSave; int selectedSave;