patterncModerate
Simple extractor for Quake-2 PAK archives
Viewed 0 times
simpleextractorquakeforarchivespak
Problem
I was fiddling with the source code of Quake-2 today and at some point wanted to extract files from the
```
#include
#include
#include
#include
// For mkdir/stat
#include
#include
/*
* From Quake2:
*/
// Not enforced by this extractor. Was enforced by the game, we just warn.
#define MAX_FILES_IN_PACK 4096
// 4CC 'PACK'
#define ID_PAK_HEADER (('K' dirlen / sizeof(pak_file_t);
if (num_files_in_pak > MAX_FILES_IN_PACK)
{
fprintf(stderr, "Warning MAX_FILES_IN_PACK exceeded!\n");
// Allow it to continue.
}
pak_file_entries = malloc(num_files_in_pak * sizeof(pak_file_t));
if (pak_file_entries == NULL)
{
fprintf(stderr, "Out-of-memory in unpak!\n");
return false;
}
fseek(pak_file, pak_header->dirofs, SEEK_SET);
fread(pak_file_entries, 1, pak_header->dirlen, pak_file);
if (ferror(pak_file))
{
fprintf(stderr, "Error reading pak_file_entries block!\n");
free(pak_file_entries);
return false;
}
for (int i = 0; i name, entry->filepos, entry->filelen))
{
fprintf(stderr, "Failed to extract pak entry '%s' #%d\n", entry->name, i);
// Try another one...
}
}
free(pak_file_entries);
return true;
}
int main(int argc, const char * argv[])
{
char dest_dir_name[512];
char * ext_ptr;
pak_header_t pak_header;
const char * pak_name;
FILE * pak_file;
if (argc \n"
" Unpacks the whole archive to a directory with the same name as the input.\n"
" Internal file paths are preserved.\n",
argv[0]);
return EXIT_FAILURE;
}
pak_name = argv[1];
pak_file = fopen(pak_name, "rb");
if (pak_file == NULL)
{
fprintf(stderr, "Can't fopen() the file! %s\n", pak_
.pak archives used by the game. Since I couldn't find any tool to do that on my Operating System, I coded this quick-'n-dirty command line extractor for Quake-2 PAKs:```
#include
#include
#include
#include
// For mkdir/stat
#include
#include
/*
* From Quake2:
*/
// Not enforced by this extractor. Was enforced by the game, we just warn.
#define MAX_FILES_IN_PACK 4096
// 4CC 'PACK'
#define ID_PAK_HEADER (('K' dirlen / sizeof(pak_file_t);
if (num_files_in_pak > MAX_FILES_IN_PACK)
{
fprintf(stderr, "Warning MAX_FILES_IN_PACK exceeded!\n");
// Allow it to continue.
}
pak_file_entries = malloc(num_files_in_pak * sizeof(pak_file_t));
if (pak_file_entries == NULL)
{
fprintf(stderr, "Out-of-memory in unpak!\n");
return false;
}
fseek(pak_file, pak_header->dirofs, SEEK_SET);
fread(pak_file_entries, 1, pak_header->dirlen, pak_file);
if (ferror(pak_file))
{
fprintf(stderr, "Error reading pak_file_entries block!\n");
free(pak_file_entries);
return false;
}
for (int i = 0; i name, entry->filepos, entry->filelen))
{
fprintf(stderr, "Failed to extract pak entry '%s' #%d\n", entry->name, i);
// Try another one...
}
}
free(pak_file_entries);
return true;
}
int main(int argc, const char * argv[])
{
char dest_dir_name[512];
char * ext_ptr;
pak_header_t pak_header;
const char * pak_name;
FILE * pak_file;
if (argc \n"
" Unpacks the whole archive to a directory with the same name as the input.\n"
" Internal file paths are preserved.\n",
argv[0]);
return EXIT_FAILURE;
}
pak_name = argv[1];
pak_file = fopen(pak_name, "rb");
if (pak_file == NULL)
{
fprintf(stderr, "Can't fopen() the file! %s\n", pak_
Solution
A common problem with file archive extractors is vulnerability to directory traversal attacks. A maliciously crafted archive could contain an entry with a path like
File structures have a fixed-size representation and a pre-determined endianness. You should use
If any or all of the files cannot be extracted, you print an error message, but return a success code. I would expect such failures to result in
Consider using
You call
../../../../../../../etc/resolv.conf, which could overwrite a system file if the extraction is done by a privileged user. You should take measures to ensure that any file you create lies within the intended destination directory.File structures have a fixed-size representation and a pre-determined endianness. You should use
uint32_t instead of assuming that int is 32 bits. Your treatment of ID_PAK_HEADER looks like it is little-endian only.If any or all of the files cannot be extracted, you print an error message, but return a success code. I would expect such failures to result in
EXIT_FAILURE or some other non-zero exit status.Consider using
pathconf() instead of hard-coding path length limits like 512.You call
fread() a couple of times without inspecting its return value. It would also be better to reorder the second and third arguments so that size comes before nitems, to match the POSIX standard (even if it makes no practical difference).Context
StackExchange Code Review Q#107905, answer score: 10
Revisions (0)
No revisions yet.