From 303631f9d7f753dacffb1e038dd273dc204e1ea9 Mon Sep 17 00:00:00 2001 From: Ladislav Zezula Date: Sat, 18 Jun 2016 20:32:15 +0200 Subject: + Updated for protectors fiddling with too big block table index --- src/SBaseCommon.cpp | 4 ++-- src/SBaseFileTable.cpp | 46 ++++++++++++++++++---------------------------- src/SBaseSubTypes.cpp | 8 +++----- src/SFileFindFile.cpp | 2 +- src/SFileListFile.cpp | 2 +- src/SFileOpenArchive.cpp | 4 ++-- src/StormLib.h | 7 ++++++- test/StormTest.cpp | 6 +++--- 8 files changed, 36 insertions(+), 43 deletions(-) diff --git a/src/SBaseCommon.cpp b/src/SBaseCommon.cpp index 54fc6d0..95bdeb4 100644 --- a/src/SBaseCommon.cpp +++ b/src/SBaseCommon.cpp @@ -628,7 +628,7 @@ TMPQHash * GetFirstHashEntry(TMPQArchive * ha, const char * szFileName) TMPQHash * pHash = ha->pHashTable + dwIndex; // If the entry matches, we found it. - if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && pHash->dwBlockIndex < ha->dwFileTableSize) + if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && MPQ_BLOCK_INDEX(pHash) < ha->dwFileTableSize) return pHash; // If that hash entry is a free entry, it means we haven't found the file @@ -663,7 +663,7 @@ TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash * pHash = ha->pHashTable + dwIndex; // If the entry matches, we found it. - if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && pHash->dwBlockIndex < ha->dwFileTableSize) + if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && MPQ_BLOCK_INDEX(pHash) < ha->dwFileTableSize) return pHash; // If that hash entry is a free entry, it means we haven't found the file diff --git a/src/SBaseFileTable.cpp b/src/SBaseFileTable.cpp index 9d37ff1..981328b 100644 --- a/src/SBaseFileTable.cpp +++ b/src/SBaseFileTable.cpp @@ -581,11 +581,9 @@ int ConvertMpqHeaderToFormat4( // Hash entry verification when the file table does not exist yet bool IsValidHashEntry(TMPQArchive * ha, TMPQHash * pHash) { - TFileEntry * pFileEntry = ha->pFileTable + MPQ_BLOCK_INDEX(pHash->dwBlockIndex); - DWORD dwBlockIndex = MPQ_BLOCK_INDEX(pHash->dwBlockIndex); + TFileEntry * pFileEntry = ha->pFileTable + MPQ_BLOCK_INDEX(pHash); - pFileEntry = ha->pFileTable + dwBlockIndex; - return ((dwBlockIndex < ha->dwFileTableSize) && (pFileEntry->dwFlags & MPQ_FILE_EXISTS)) ? true : false; + return ((MPQ_BLOCK_INDEX(pHash) < ha->dwFileTableSize) && (pFileEntry->dwFlags & MPQ_FILE_EXISTS)) ? true : false; } // Hash entry verification when the file table does not exist yet @@ -593,20 +591,12 @@ static bool IsValidHashEntry1(TMPQArchive * ha, TMPQHash * pHash, TMPQBlock * pB { ULONGLONG ByteOffset; TMPQBlock * pBlock; - DWORD dwBlockIndex; - - // We need to mask out the upper 4 bits of the block table index. - // This is because it gets shifted out when calculating block table offset - // BlockTableOffset = pHash->dwBlockIndex * 0x10 - // Malformed MPQ maps may contain invalid entries - // Note that Storm.dll does not perfom this check - dwBlockIndex = MPQ_BLOCK_INDEX(pHash->dwBlockIndex); // The block index is considered valid if it's less than block table size - if(dwBlockIndex < ha->pHeader->dwBlockTableSize) + if(MPQ_BLOCK_INDEX(pHash) < ha->pHeader->dwBlockTableSize) { // Calculate the block table position - pBlock = pBlockTable + dwBlockIndex; + pBlock = pBlockTable + MPQ_BLOCK_INDEX(pHash); // Check whether this is an existing file // Also we do not allow to be file size greater than 2GB @@ -787,28 +777,28 @@ static int BuildFileTableFromBlockTable( // - Multiple hash entries (different file name) point to the same block entry // // Ignore all hash table entries where: - // - dwBlockIndex >= BlockTableSize + // - Block Index >= BlockTableSize // - Flags of the appropriate block table entry // if(IsValidHashEntry1(ha, pHash, pBlockTable)) { - DWORD dwBlockIndex = MPQ_BLOCK_INDEX(pHash->dwBlockIndex); - DWORD dwNewIndex = MPQ_BLOCK_INDEX(pHash->dwBlockIndex); + DWORD dwOldIndex = MPQ_BLOCK_INDEX(pHash); + DWORD dwNewIndex = MPQ_BLOCK_INDEX(pHash); // Determine the new block index if(DefragmentTable != NULL) { // Need to handle case when multiple hash // entries point to the same block entry - if(DefragmentTable[dwBlockIndex] == HASH_ENTRY_FREE) + if(DefragmentTable[dwOldIndex] == HASH_ENTRY_FREE) { - DefragmentTable[dwBlockIndex] = dwItemCount; + DefragmentTable[dwOldIndex] = dwItemCount; dwNewIndex = dwItemCount++; } else { - dwNewIndex = DefragmentTable[dwBlockIndex]; + dwNewIndex = DefragmentTable[dwOldIndex]; } // Fix the pointer in the hash entry @@ -820,7 +810,7 @@ static int BuildFileTableFromBlockTable( // Get the pointer to the file entry and the block entry pFileEntry = ha->pFileTable + dwNewIndex; - pBlock = pBlockTable + dwBlockIndex; + pBlock = pBlockTable + dwOldIndex; // ByteOffset is only valid if file size is not zero pFileEntry->ByteOffset = pBlock->dwFilePos; @@ -1814,11 +1804,11 @@ TFileEntry * GetFileEntryLocale2(TMPQArchive * ha, const char * szFileName, LCID if(ha->pHashTable != NULL) { pHash = GetHashEntryLocale(ha, szFileName, lcLocale); - if(pHash != NULL && pHash->dwBlockIndex < ha->dwFileTableSize) + if(pHash != NULL && MPQ_BLOCK_INDEX(pHash) < ha->dwFileTableSize) { if(PtrHashIndex != NULL) PtrHashIndex[0] = (DWORD)(pHash - ha->pHashTable); - return ha->pFileTable + pHash->dwBlockIndex; + return ha->pFileTable + MPQ_BLOCK_INDEX(pHash); } } @@ -1848,11 +1838,11 @@ TFileEntry * GetFileEntryExact(TMPQArchive * ha, const char * szFileName, LCID l if(ha->pHashTable != NULL) { pHash = GetHashEntryExact(ha, szFileName, lcLocale); - if(pHash != NULL && pHash->dwBlockIndex < ha->dwFileTableSize) + if(pHash != NULL && MPQ_BLOCK_INDEX(pHash) < ha->dwFileTableSize) { if(PtrHashIndex != NULL) PtrHashIndex[0] = (DWORD)(pHash - ha->pHashTable); - return ha->pFileTable + pHash->dwBlockIndex; + return ha->pFileTable + MPQ_BLOCK_INDEX(pHash); } } @@ -2640,11 +2630,11 @@ int DefragmentFileTable(TMPQArchive * ha) for(pHash = ha->pHashTable; pHash < pHashTableEnd; pHash++) { - if(pHash->dwBlockIndex < ha->dwFileTableSize) + if(MPQ_BLOCK_INDEX(pHash) < ha->dwFileTableSize) { // If that block entry is there, set it to the hash entry // If not, set it as DELETED - dwNewBlockIndex = DefragmentTable[pHash->dwBlockIndex]; + dwNewBlockIndex = DefragmentTable[MPQ_BLOCK_INDEX(pHash)]; pHash->dwBlockIndex = (dwNewBlockIndex != HASH_ENTRY_FREE) ? dwNewBlockIndex : HASH_ENTRY_DELETED; } } @@ -2753,7 +2743,7 @@ int RebuildFileTable(TMPQArchive * ha, DWORD dwNewHashTableSize) { if(IsValidHashEntry(ha, pHash)) { - pFileEntry = ha->pFileTable + pHash->dwBlockIndex; + pFileEntry = ha->pFileTable + MPQ_BLOCK_INDEX(pHash); AllocateHashEntry(ha, pFileEntry, pHash->lcLocale); } } diff --git a/src/SBaseSubTypes.cpp b/src/SBaseSubTypes.cpp index 9807701..1f72c17 100644 --- a/src/SBaseSubTypes.cpp +++ b/src/SBaseSubTypes.cpp @@ -187,7 +187,6 @@ TMPQHash * LoadSqpHashTable(TMPQArchive * ha) TSQPHash * pSqpHashEnd; TSQPHash * pSqpHash; TMPQHash * pMpqHash; - DWORD dwBlockIndex; int nError = ERROR_SUCCESS; // Load the hash table @@ -203,8 +202,7 @@ TMPQHash * LoadSqpHashTable(TMPQArchive * ha) if(pSqpHash->dwBlockIndex != HASH_ENTRY_FREE) { // Check block index against the size of the block table - dwBlockIndex = pSqpHash->dwBlockIndex; - if(pHeader->dwBlockTableSize <= dwBlockIndex && dwBlockIndex < HASH_ENTRY_DELETED) + if(pHeader->dwBlockTableSize <= MPQ_BLOCK_INDEX(pSqpHash) && MPQ_BLOCK_INDEX(pSqpHash) < HASH_ENTRY_DELETED) nError = ERROR_FILE_CORRUPT; // We do not support nonzero locale and platform ID @@ -216,8 +214,8 @@ TMPQHash * LoadSqpHashTable(TMPQArchive * ha) pMpqHash->dwName2 = pSqpHash->dwName2; // Store the rest. Note that this must be done last, - // because pSqpHash->dwBlockIndex corresponds to pMpqHash->dwName2 - pMpqHash->dwBlockIndex = dwBlockIndex; + // because block index corresponds to pMpqHash->dwName2 + pMpqHash->dwBlockIndex = MPQ_BLOCK_INDEX(pSqpHash); pMpqHash->wPlatform = 0; pMpqHash->lcLocale = 0; } diff --git a/src/SFileFindFile.cpp b/src/SFileFindFile.cpp index 41f31e1..18bf4d5 100644 --- a/src/SFileFindFile.cpp +++ b/src/SFileFindFile.cpp @@ -299,7 +299,7 @@ static int DoMPQSearch_HashTable(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileDa if(IsValidHashEntry(ha, pHash)) { // Check if this file entry should be included in the search result - if(DoMPQSearch_FileEntry(hs, lpFindFileData, ha, pHash, ha->pFileTable + pHash->dwBlockIndex)) + if(DoMPQSearch_FileEntry(hs, lpFindFileData, ha, pHash, ha->pFileTable + MPQ_BLOCK_INDEX(pHash))) return ERROR_SUCCESS; } } diff --git a/src/SFileListFile.cpp b/src/SFileListFile.cpp index 62b8f11..3918bd9 100644 --- a/src/SFileListFile.cpp +++ b/src/SFileListFile.cpp @@ -350,7 +350,7 @@ static int SListFileCreateNodeForAllLocales(TMPQArchive * ha, const char * szFil while(pHash != NULL) { // Allocate file name for the file entry - AllocateFileName(ha, ha->pFileTable + pHash->dwBlockIndex, szFileName); + AllocateFileName(ha, ha->pFileTable + MPQ_BLOCK_INDEX(pHash), szFileName); // Now find the next language version of the file pHash = GetNextHashEntry(ha, pFirstHash, pHash); diff --git a/src/SFileOpenArchive.cpp b/src/SFileOpenArchive.cpp index 2cb6698..62effe7 100644 --- a/src/SFileOpenArchive.cpp +++ b/src/SFileOpenArchive.cpp @@ -369,8 +369,8 @@ bool WINAPI SFileOpenArchive( // higher than 0x10000000, it would overflow in 32-bit version // Observed in the malformed Warcraft III maps // Example map: MPQ_2016_v1_ProtectedMap_TableSizeOverflow.w3x - ha->pHeader->dwBlockTableSize = MPQ_BLOCK_INDEX(ha->pHeader->dwBlockTableSize); - ha->pHeader->dwHashTableSize = MPQ_BLOCK_INDEX(ha->pHeader->dwHashTableSize); + ha->pHeader->dwBlockTableSize = (ha->pHeader->dwBlockTableSize & BLOCK_INDEX_MASK); + ha->pHeader->dwHashTableSize = (ha->pHeader->dwHashTableSize & BLOCK_INDEX_MASK); // Both MPQ_OPEN_NO_LISTFILE or MPQ_OPEN_NO_ATTRIBUTES trigger read only mode if(dwFlags & (MPQ_OPEN_NO_LISTFILE | MPQ_OPEN_NO_ATTRIBUTES)) diff --git a/src/StormLib.h b/src/StormLib.h index c25a1a8..3621096 100644 --- a/src/StormLib.h +++ b/src/StormLib.h @@ -230,7 +230,12 @@ extern "C" { MPQ_FILE_SIGNATURE | \ MPQ_FILE_EXISTS) -#define MPQ_BLOCK_INDEX(bi) ((bi) & 0x0FFFFFFF) // Index mask for block table index +// We need to mask out the upper 4 bits of the block table index. +// This is because it gets shifted out when calculating block table offset +// BlockTableOffset = pHash->dwBlockIndex << 0x04 +// Malformed MPQ maps may contain block indexes like 0x40000001 or 0xF0000023 +#define BLOCK_INDEX_MASK 0x0FFFFFFF +#define MPQ_BLOCK_INDEX(pHash) (pHash->dwBlockIndex & BLOCK_INDEX_MASK) // Compression types for multiple compressions #define MPQ_COMPRESSION_HUFFMANN 0x01 // Huffmann compression (used on WAVE files only) diff --git a/test/StormTest.cpp b/test/StormTest.cpp index 2d73bd9..e19d7c3 100644 --- a/test/StormTest.cpp +++ b/test/StormTest.cpp @@ -4405,7 +4405,7 @@ int main(int argc, char * argv[]) // Open a partial MPQ with compressed hash table if(nError == ERROR_SUCCESS) nError = TestOpenArchive("part-file://MPQ_2010_v2_HashTableCompressed.MPQ.part"); - +*/ if(nError == ERROR_SUCCESS) nError = TestOpenArchive("MPQ_2002_v1_ProtectedMap_HashTable_FakeValid.w3x"); @@ -4464,9 +4464,9 @@ int main(int argc, char * argv[]) // Protector from China (2016-05-27) if(nError == ERROR_SUCCESS) nError = TestOpenArchive("MPQ_2016_v1_WME4_4.w3x"); -*/ + if(nError == ERROR_SUCCESS) - nError = TestOpenArchive("MPQ_2016_v1_AnotherProtectedMap.w3x"); + nError = TestOpenArchive("MPQ_2016_v1_SP_(4)Adrenaline.w3x"); /* // Open the multi-file archive with wrong prefix to see how StormLib deals with it if(nError == ERROR_SUCCESS) -- cgit v1.2.3