Skip to content

Commit dc9acc6

Browse files
committed
An option to use filename-based decryption key
Previously, libmpq tried to brute-force the decryption key based on know contents, and only for packed files. This caused issues, such as mbroemme#13. Adds a new function, `libmpq__block_open_offset_with_filename`, that uses the filename to obtain the decryption key.
1 parent 8a08290 commit dc9acc6

File tree

5 files changed

+215
-0
lines changed

5 files changed

+215
-0
lines changed

Diff for: libmpq/common.c

+25
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,31 @@ int32_t libmpq__decrypt_block(uint32_t *in_buf, uint32_t in_size, uint32_t seed)
9999
return LIBMPQ_SUCCESS;
100100
}
101101

102+
/* returns the last component of a path after / or \ */
103+
static const char *get_basename(const char *filename) {
104+
const char *basename = strrchr(filename, '\\');
105+
if (*basename != '\0') return basename + 1;
106+
basename = strrchr(filename, '/');
107+
if (*basename != '\0') return basename + 1;
108+
return filename;
109+
}
110+
111+
int32_t libmpq__encryption_key_from_filename(const char *filename, uint32_t *key) {
112+
const char *basename = get_basename(filename);
113+
if (*basename == '\0')
114+
return LIBMPQ_ERROR_DECRYPT;
115+
*key = libmpq__hash_string(basename, 0x300);
116+
return LIBMPQ_SUCCESS;
117+
}
118+
119+
int32_t libmpq__encryption_key_from_filename_v2(const char *filename, uint32_t offset, uint32_t unpacked_size, uint32_t *key) {
120+
int32_t result = libmpq__encryption_key_from_filename(filename, key);
121+
if (result < 0)
122+
return result;
123+
*key = (*key + offset) ^ unpacked_size;
124+
return LIBMPQ_SUCCESS;
125+
}
126+
102127
/* function to detect decryption key. */
103128
int32_t libmpq__decrypt_key(uint8_t *in_buf, uint32_t in_size, uint32_t block_size, uint32_t *key) {
104129

Diff for: libmpq/common.h

+14
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,20 @@ int32_t libmpq__decrypt_key(
4949
uint32_t *key
5050
);
5151

52+
/* function to obtain the decryption key given a filename. */
53+
int32_t libmpq__encryption_key_from_filename(
54+
const char *filename,
55+
uint32_t *key
56+
);
57+
58+
/* function to obtain the decryption key given a filename for files with LIBMPQ_FLAG_ENCRYPTION_KEY_V2. */
59+
int32_t libmpq__encryption_key_from_filename_v2(
60+
const char *filename,
61+
uint32_t offset,
62+
uint32_t unpacked_size,
63+
uint32_t *key
64+
);
65+
5266
/* function to decompress or explode block from archive. */
5367
int32_t libmpq__decompress_block(
5468
uint8_t *in_buf,

Diff for: libmpq/mpq-internal.h

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#define LIBMPQ_FLAG_COMPRESS_PKZIP 0x00000100 /* compression made by pkware data compression library. */
4444
#define LIBMPQ_FLAG_COMPRESS_MULTI 0x00000200 /* multiple compressions. */
4545
#define LIBMPQ_FLAG_COMPRESS_NONE 0x00000300 /* no compression (no blizzard flag used by myself). */
46+
#define LIBMPQ_FLAG_ENCRYPTION_KEY_V2 0x00020000 /* File decryption key is additionally derived from file offset and unpacked size. */
4647
#define LIBMPQ_FLAG_SINGLE 0x01000000 /* file is stored in one single sector, first seen in world of warcraft. */
4748
#define LIBMPQ_FLAG_CRC 0x04000000 /* compressed block offset table has CRC checksum. */
4849

Diff for: libmpq/mpq.c

+174
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,180 @@ int32_t libmpq__file_read(mpq_archive_s *mpq_archive, uint32_t file_number, uint
647647
return LIBMPQ_SUCCESS;
648648
}
649649

650+
/* opens a file and calculates the filename-based decryption key if needed. */
651+
int32_t libmpq__block_open_offset_with_filename(mpq_archive_s *mpq_archive, uint32_t file_number, const char *filename) {
652+
653+
/* some common variables. */
654+
uint32_t i;
655+
uint32_t packed_size;
656+
int32_t result = 0;
657+
658+
/* check if given file number is not out of range. */
659+
CHECK_FILE_NUM(file_number, mpq_archive)
660+
661+
if (mpq_archive->mpq_file[file_number]) {
662+
663+
/* file already opened, so increment counter */
664+
mpq_archive->mpq_file[file_number]->open_count++;
665+
return LIBMPQ_SUCCESS;
666+
}
667+
668+
mpq_block_s *mpq_block = &mpq_archive->mpq_block[mpq_archive->mpq_map[file_number].block_table_indices];
669+
670+
/* check if file is not stored in a single sector. */
671+
if ((mpq_block->flags & LIBMPQ_FLAG_SINGLE) == 0) {
672+
673+
/* get packed size based on block size and block count. */
674+
packed_size = sizeof(uint32_t) * (((mpq_block->unpacked_size + mpq_archive->block_size - 1) / mpq_archive->block_size) + 1);
675+
} else {
676+
677+
/* file is stored in single sector and we need only two entries for the packed block offset table. */
678+
packed_size = sizeof(uint32_t) * 2;
679+
}
680+
681+
/* check if data has one extra entry. */
682+
if ((mpq_block->flags & LIBMPQ_FLAG_CRC) != 0) {
683+
684+
/* add one uint32_t. */
685+
packed_size += sizeof(uint32_t);
686+
}
687+
688+
/* allocate memory for the file. */
689+
if ((mpq_archive->mpq_file[file_number] = calloc(1, sizeof(mpq_file_s))) == NULL) {
690+
691+
/* memory allocation problem. */
692+
result = LIBMPQ_ERROR_MALLOC;
693+
goto error;
694+
}
695+
696+
/* allocate memory for the packed block offset table. */
697+
if ((mpq_archive->mpq_file[file_number]->packed_offset = calloc(1, packed_size)) == NULL) {
698+
699+
/* memory allocation problem. */
700+
result = LIBMPQ_ERROR_MALLOC;
701+
goto error;
702+
}
703+
704+
/* initialize counter to one opening */
705+
mpq_archive->mpq_file[file_number]->open_count = 1;
706+
707+
/* check if we need to load the packed block offset table, we will maintain this table for unpacked files too. */
708+
if ((mpq_block->flags & LIBMPQ_FLAG_COMPRESSED) != 0 &&
709+
(mpq_block->flags & LIBMPQ_FLAG_SINGLE) == 0) {
710+
711+
/* seek to block position. */
712+
if (fseeko(mpq_archive->fp, mpq_block->offset + (((long long)mpq_archive->mpq_block_ex[mpq_archive->mpq_map[file_number].block_table_indices].offset_high) << 32) + mpq_archive->archive_offset, SEEK_SET) < 0) {
713+
714+
/* seek in file failed. */
715+
result = LIBMPQ_ERROR_SEEK;
716+
goto error;
717+
}
718+
719+
/* read block positions from begin of file. */
720+
if (fread(mpq_archive->mpq_file[file_number]->packed_offset, 1, packed_size, mpq_archive->fp) != packed_size) {
721+
722+
/* something on read from archive failed. */
723+
result = LIBMPQ_ERROR_READ;
724+
goto error;
725+
}
726+
727+
/* check if the archive is protected some way, sometimes the file appears not to be encrypted, but it is.
728+
* a special case are files with an additional sector but LIBMPQ_FLAG_CRC not set. we don't want to handle
729+
* them as encrypted. */
730+
if (mpq_archive->mpq_file[file_number]->packed_offset[0] != packed_size &&
731+
mpq_archive->mpq_file[file_number]->packed_offset[0] != packed_size + 4) {
732+
733+
/* file is encrypted. */
734+
mpq_block->flags |= LIBMPQ_FLAG_ENCRYPTED;
735+
}
736+
737+
/* check if packed offset block is encrypted, we have to decrypt it. */
738+
if (mpq_block->flags & LIBMPQ_FLAG_ENCRYPTED) {
739+
740+
/* get the file seed. */
741+
uint32_t seed;
742+
if (mpq_block->flags & LIBMPQ_FLAG_ENCRYPTION_KEY_V2) {
743+
result = libmpq__encryption_key_from_filename_v2(filename, mpq_block->offset, mpq_block->unpacked_size, &seed);
744+
} else {
745+
result = libmpq__encryption_key_from_filename(filename, &seed);
746+
}
747+
if (result < 0) {
748+
result = LIBMPQ_ERROR_DECRYPT;
749+
goto error;
750+
}
751+
mpq_archive->mpq_file[file_number]->seed = seed;
752+
753+
/* decrypt block in input buffer. */
754+
if (libmpq__decrypt_block(mpq_archive->mpq_file[file_number]->packed_offset, packed_size, seed - 1) < 0 ) {
755+
756+
/* something on decrypt failed. */
757+
result = LIBMPQ_ERROR_DECRYPT;
758+
goto error;
759+
}
760+
761+
/* check if the block positions are correctly decrypted. */
762+
if (mpq_archive->mpq_file[file_number]->packed_offset[0] != packed_size) {
763+
764+
/* sorry without seed, we cannot extract file. */
765+
result = LIBMPQ_ERROR_DECRYPT;
766+
goto error;
767+
}
768+
}
769+
} else {
770+
771+
/* check if file is not stored in a single sector. */
772+
if ((mpq_block->flags & LIBMPQ_FLAG_SINGLE) == 0) {
773+
774+
/* loop through all blocks and create packed block offset table based on block size. */
775+
for (i = 0; i < ((mpq_block->unpacked_size + mpq_archive->block_size - 1) / mpq_archive->block_size + 1); i++) {
776+
777+
/* check if we process the last block. */
778+
if (i == ((mpq_block->unpacked_size + mpq_archive->block_size - 1) / mpq_archive->block_size)) {
779+
780+
/* store size of last block. */
781+
mpq_archive->mpq_file[file_number]->packed_offset[i] = mpq_block->unpacked_size;
782+
} else {
783+
784+
/* store default block size. */
785+
mpq_archive->mpq_file[file_number]->packed_offset[i] = i * mpq_archive->block_size;
786+
}
787+
}
788+
} else {
789+
790+
/* store offsets. */
791+
mpq_archive->mpq_file[file_number]->packed_offset[0] = 0;
792+
mpq_archive->mpq_file[file_number]->packed_offset[1] = mpq_block->packed_size;
793+
}
794+
795+
if (mpq_block->flags & LIBMPQ_FLAG_ENCRYPTED) {
796+
/* get the file seed. */
797+
uint32_t seed;
798+
if (mpq_block->flags & LIBMPQ_FLAG_ENCRYPTION_KEY_V2) {
799+
result = libmpq__encryption_key_from_filename_v2(filename, mpq_block->offset, mpq_block->unpacked_size, &seed);
800+
} else {
801+
result = libmpq__encryption_key_from_filename(filename, &seed);
802+
}
803+
if (result < 0) {
804+
result = LIBMPQ_ERROR_DECRYPT;
805+
goto error;
806+
}
807+
mpq_archive->mpq_file[file_number]->seed = seed;
808+
}
809+
}
810+
811+
/* if no error was found, return zero. */
812+
return LIBMPQ_SUCCESS;
813+
814+
error:
815+
816+
/* free packed block offset table and file pointer. */
817+
free(mpq_archive->mpq_file[file_number]->packed_offset);
818+
free(mpq_archive->mpq_file[file_number]);
819+
820+
/* return error constant. */
821+
return result;
822+
}
823+
650824
/* this function open a file in the given archive and caches the block offset information. */
651825
int32_t libmpq__block_open_offset(mpq_archive_s *mpq_archive, uint32_t file_number) {
652826

Diff for: libmpq/mpq.h

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ extern LIBMPQ_API int32_t libmpq__file_read(mpq_archive_s *mpq_archive, uint32_t
9090

9191
/* generic block processing functions. */
9292
extern LIBMPQ_API int32_t libmpq__block_open_offset(mpq_archive_s *mpq_archive, uint32_t file_number);
93+
extern LIBMPQ_API int32_t libmpq__block_open_offset_with_filename(mpq_archive_s *mpq_archive, uint32_t file_number, const char *filename);
9394
extern LIBMPQ_API int32_t libmpq__block_close_offset(mpq_archive_s *mpq_archive, uint32_t file_number);
9495
extern LIBMPQ_API int32_t libmpq__block_size_unpacked(mpq_archive_s *mpq_archive, uint32_t file_number, uint32_t block_number, libmpq__off_t *unpacked_size);
9596
extern LIBMPQ_API int32_t libmpq__block_read(mpq_archive_s *mpq_archive, uint32_t file_number, uint32_t block_number, uint8_t *out_buf, libmpq__off_t out_size, libmpq__off_t *transferred);

0 commit comments

Comments
 (0)