Skip to main content
Obfuscated Zip        

General

Many content providers require that licensed assets are packaged into a protected form when shipped with your application. An obfuscated zip file is one way to pack up your application resources in a way that prevents a casual user from accessing them. The method uses a 'magic number' and a simple xor to make the file unreadable. The classes presented with this code snippet abstracts an Ogre::Archive (almost an exact copy of Ogre::ZipArchive) and seamlessly hooks into the Ogre::ArchiveManager to handle decoding and reading directly from an obfuscated zip file.

Why not password protect the zip file?
The zZipLib website provides a good rational for why password protection in a zip file is weak. A casual user can download a cracking tool from any number of websites and effortlessly open your zip file.

Isn't an xor'd zip file easily cracked?
A user with a more advanced level of skill can guess (or write a program to guess) the 'magic number' and un-obfuscate the zip file. The key advantage to the xor technique is that there are no ready-made tools available to de-obfuscate the file (at least none that I'm aware of). For information about the xor method used in this code snippet see Code Obfuscation on the zZipLib website.

You want stronger obfuscation?
The zZipLib website offers a technique to provide stronger obfuscation. Implementing the stronger method will be left as an exercise for the reader.

Header ObfuscatedZip.h

Copy to clipboard
/* * ObfuscatedZip.h */ #ifndef obfuscated_zip_h #define obfuscated_zip_h #include "OgreArchive.h" #include "OgreArchiveFactory.h" // Forward declaration for zziplib to avoid header file dependency. typedef struct zzip_dir ZZIP_DIR; typedef struct zzip_file ZZIP_FILE; namespace MyNameSpace { /** Specialisation of the Archive class to allow reading of files from an obfuscated zip format source archive. @remarks This archive format supports obfuscated zip archives. */ class ObfuscatedZip : public Ogre::Archive { protected: /// Handle to root zip file ZZIP_DIR* mZzipDir; /// Handle any errors from zzip void checkZzipError(int zzipError, const Ogre::String& operation) const; /// File list (since zziplib seems to only allow scanning of dir tree once) Ogre::FileInfoList mFileList; public: ObfuscatedZip(const Ogre::String& name, const Ogre::String& archType ); ~ObfuscatedZip(); /// @copydoc Archive::isCaseSensitive bool isCaseSensitive(void) const { return false; } /// @copydoc Archive::load void load(); /// @copydoc Archive::unload void unload(); /// @copydoc Archive::open Ogre::DataStreamPtr open(const Ogre::String& filename) const; /// @copydoc Archive::list Ogre::StringVectorPtr list(bool recursive = true, bool dirs = false); /// @copydoc Archive::listFileInfo Ogre::FileInfoListPtr listFileInfo(bool recursive = true, bool dirs = false); /// @copydoc Archive::find Ogre::StringVectorPtr find(const Ogre::String& pattern, bool recursive = true, bool dirs = false); /// @copydoc Archive::findFileInfo Ogre::FileInfoListPtr findFileInfo(const Ogre::String& pattern, bool recursive = true, bool dirs = false); /// @copydoc Archive::exists bool exists(const Ogre::String& filename); /// @copydoc Archive::getModifiedTime time_t getModifiedTime(const Ogre::String& filename); }; /** Specialisation of ArchiveFactory for Obfuscated Zip files. */ class ObfuscatedZipFactory : public Ogre::ArchiveFactory { public: virtual ~ObfuscatedZipFactory() {} /// @copydoc FactoryObj::getType const Ogre::String& getType(void) const; /// @copydoc FactoryObj::createInstance Ogre::Archive *createInstance( const Ogre::String& name ) { // Change "OBFUSZIP" to match the file extension of your choosing. return new ObfuscatedZip(name, "OBFUSZIP"); } /// @copydoc FactoryObj::destroyInstance void destroyInstance( Ogre::Archive* arch) { OGRE_DELETE arch; } }; /** Specialisation of DataStream to handle streaming data from zip archives. */ class ObfuscatedZipDataStream : public Ogre::DataStream { protected: ZZIP_FILE* mZzipFile; public: /// Unnamed constructor ObfuscatedZipDataStream(ZZIP_FILE* zzipFile, size_t uncompressedSize); /// Constructor for creating named streams ObfuscatedZipDataStream(const Ogre::String& name, ZZIP_FILE* zzipFile, size_t uncompressedSize); ~ObfuscatedZipDataStream(); /// @copydoc DataStream::read size_t read(void* buf, size_t count); /// @copydoc DataStream::skip void skip(long count); /// @copydoc DataStream::seek void seek( size_t pos ); /// @copydoc DataStream::seek size_t tell(void) const; /// @copydoc DataStream::eof bool eof(void) const; /// @copydoc DataStream::close void close(void); }; } #endif

Source ObfuscatedZip.cpp

Copy to clipboard
/* * ObfuscatedZip.cpp */ #include "ObfuscatedZip.h" #include <zzip/zzip.h> #include <zzip/plugin.h> namespace MyNameSpace { // Change this magic number to a value of your choosing. static const int xor_value = 0x10; static zzip_plugin_io_handlers xor_handlers = { }; // Change "OBFUSZIP" to match the file extension of your choosing. static zzip_strings_t xor_fileext[] = { ".OBFUSZIP", 0 }; // Static method that un-obfuscates an obfuscated file. static zzip_ssize_t xor_read (int fd, void* buf, zzip_size_t len) { const zzip_ssize_t bytes = read(fd, buf, len); zzip_ssize_t i; char* pch = (char*)buf; for (i=0; i<bytes; ++i) { pch[i] ^= xor_value; } return bytes; } /// Utility method to format out zzip errors Ogre::String getZzipErrorDescription(zzip_error_t zzipError) { Ogre::String errorMsg; switch (zzipError) { case ZZIP_NO_ERROR: break; case ZZIP_OUTOFMEM: errorMsg = "Out of memory."; break; case ZZIP_DIR_OPEN: case ZZIP_DIR_STAT: case ZZIP_DIR_SEEK: case ZZIP_DIR_READ: errorMsg = "Unable to read zip file."; break; case ZZIP_UNSUPP_COMPR: errorMsg = "Unsupported compression format."; break; case ZZIP_CORRUPTED: errorMsg = "Corrupted archive."; break; default: errorMsg = "Unknown error."; break; }; return errorMsg; } //----------------------------------------------------------------------- ObfuscatedZip::ObfuscatedZip(const Ogre::String& name, const Ogre::String& archType ) : Archive(name, archType), mZzipDir(0) { zzip_init_io(&xor_handlers, 0); xor_handlers.fd.read = &xor_read; } //----------------------------------------------------------------------- ObfuscatedZip::~ObfuscatedZip() { unload(); } //----------------------------------------------------------------------- void ObfuscatedZip::load() { if (!mZzipDir) { zzip_error_t zzipError; mZzipDir = zzip_dir_open_ext_io(mName.c_str(), &zzipError, xor_fileext, &xor_handlers); checkZzipError(zzipError, "opening OBFUSZIP file"); // Cache names ZZIP_DIRENT zzipEntry; while (zzip_dir_read(mZzipDir, &zzipEntry)) { Ogre::FileInfo info; info.archive = this; // Get basename / path Ogre::StringUtil::splitFilename(zzipEntry.d_name, info.basename, info.path); info.filename = zzipEntry.d_name; // Get sizes info.compressedSize = static_cast<size_t>(zzipEntry.d_csize); info.uncompressedSize = static_cast<size_t>(zzipEntry.st_size); // folder entries if (info.basename.empty()) { info.filename = info.filename.substr (0, info.filename.length () - 1); Ogre::StringUtil::splitFilename(info.filename, info.basename, info.path); // Set compressed size to -1 for folders; anyway nobody will check // the compressed size of a folder, and if he does, its useless anyway info.compressedSize = size_t (-1); } mFileList.push_back(info); } } } //----------------------------------------------------------------------- void ObfuscatedZip::unload() { if (mZzipDir) { zzip_dir_close(mZzipDir); mZzipDir = 0; mFileList.clear(); } } //----------------------------------------------------------------------- Ogre::DataStreamPtr ObfuscatedZip::open(const Ogre::String& filename) const { // Format not used here (always binary) ZZIP_FILE* zzipFile = zzip_file_open(mZzipDir, filename.c_str(), ZZIP_ONLYZIP | ZZIP_CASELESS); if (!zzipFile) { int zerr = zzip_error(mZzipDir); Ogre::String zzDesc = getZzipErrorDescription((zzip_error_t)zerr); Ogre::LogManager::getSingleton().logMessage( mName + " - Unable to open file " + filename + ", error was '" + zzDesc + "'"); // return null pointer return Ogre::DataStreamPtr(); } // Get uncompressed size too ZZIP_STAT zstat; zzip_dir_stat(mZzipDir, filename.c_str(), &zstat, ZZIP_CASEINSENSITIVE); // Construct & return stream return Ogre::DataStreamPtr(OGRE_NEW ObfuscatedZipDataStream(filename, zzipFile, static_cast<size_t>(zstat.st_size))); } //----------------------------------------------------------------------- Ogre::StringVectorPtr ObfuscatedZip::list(bool recursive, bool dirs) { Ogre::StringVectorPtr ret = Ogre::StringVectorPtr(OGRE_NEW_T(Ogre::StringVector, Ogre::MEMCATEGORY_GENERAL)(), Ogre::SPFM_DELETE_T); Ogre::FileInfoList::iterator i, iend; iend = mFileList.end(); for (i = mFileList.begin(); i != iend; ++i) if ((dirs == (i->compressedSize == size_t (-1))) && (recursive || i->path.empty())) ret->push_back(i->filename); return ret; } //----------------------------------------------------------------------- Ogre::FileInfoListPtr ObfuscatedZip::listFileInfo(bool recursive, bool dirs) { Ogre::FileInfoList* fil = OGRE_NEW_T(Ogre::FileInfoList, Ogre::MEMCATEGORY_GENERAL)(); Ogre::FileInfoList::const_iterator i, iend; iend = mFileList.end(); for (i = mFileList.begin(); i != iend; ++i) if ((dirs == (i->compressedSize == size_t (-1))) && (recursive || i->path.empty())) fil->push_back(*i); return Ogre::FileInfoListPtr(fil, Ogre::SPFM_DELETE_T); } //----------------------------------------------------------------------- Ogre::StringVectorPtr ObfuscatedZip::find(const Ogre::String& pattern, bool recursive, bool dirs) { Ogre::StringVectorPtr ret = Ogre::StringVectorPtr(OGRE_NEW_T(Ogre::StringVector, Ogre::MEMCATEGORY_GENERAL)(), Ogre::SPFM_DELETE_T); // If pattern contains a directory name, do a full match bool full_match = (pattern.find ('/') != Ogre::String::npos) || (pattern.find ('\\') != Ogre::String::npos); Ogre::FileInfoList::iterator i, iend; iend = mFileList.end(); for (i = mFileList.begin(); i != iend; ++i) if ((dirs == (i->compressedSize == size_t (-1))) && (recursive || full_match || i->path.empty())) // Check basename matches pattern (zip is case insensitive) if (Ogre::StringUtil::match(full_match ? i->filename : i->basename, pattern, false)) ret->push_back(i->filename); return ret; } //----------------------------------------------------------------------- Ogre::FileInfoListPtr ObfuscatedZip::findFileInfo(const Ogre::String& pattern, bool recursive, bool dirs) { Ogre::FileInfoListPtr ret = Ogre::FileInfoListPtr(OGRE_NEW_T(Ogre::FileInfoList, Ogre::MEMCATEGORY_GENERAL)(), Ogre::SPFM_DELETE_T); // If pattern contains a directory name, do a full match bool full_match = (pattern.find ('/') != Ogre::String::npos) || (pattern.find ('\\') != Ogre::String::npos); Ogre::FileInfoList::iterator i, iend; iend = mFileList.end(); for (i = mFileList.begin(); i != iend; ++i) if ((dirs == (i->compressedSize == size_t (-1))) && (recursive || full_match || i->path.empty())) // Check name matches pattern (zip is case insensitive) if (Ogre::StringUtil::match(full_match ? i->filename : i->basename, pattern, false)) ret->push_back(*i); return ret; } //----------------------------------------------------------------------- bool ObfuscatedZip::exists(const Ogre::String& filename) { ZZIP_STAT zstat; int res = zzip_dir_stat(mZzipDir, filename.c_str(), &zstat, ZZIP_CASEINSENSITIVE); return (res == ZZIP_NO_ERROR); } //--------------------------------------------------------------------- time_t ObfuscatedZip::getModifiedTime(const Ogre::String& filename) { // Zziplib doesn't yet support getting the modification time of individual files // so just check the mod time of the zip itself struct stat tagStat; bool ret = (stat(mName.c_str(), &tagStat) == 0); if (ret) { return tagStat.st_mtime; } else { return 0; } } //----------------------------------------------------------------------- void ObfuscatedZip::checkZzipError(int zzipError, const Ogre::String& operation) const { if (zzipError != ZZIP_NO_ERROR) { Ogre::String errorMsg = getZzipErrorDescription(static_cast<zzip_error_t>(zzipError)); OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, mName + " - error whilst " + operation + ": " + errorMsg, "ObfuscatedZip::checkZzipError"); } } //----------------------------------------------------------------------- //----------------------------------------------------------------------- //----------------------------------------------------------------------- ObfuscatedZipDataStream::ObfuscatedZipDataStream(ZZIP_FILE* zzipFile, size_t uncompressedSize) : mZzipFile(zzipFile) { mSize = uncompressedSize; } //----------------------------------------------------------------------- ObfuscatedZipDataStream::ObfuscatedZipDataStream(const Ogre::String& name, ZZIP_FILE* zzipFile, size_t uncompressedSize) :DataStream(name), mZzipFile(zzipFile) { mSize = uncompressedSize; } //----------------------------------------------------------------------- ObfuscatedZipDataStream::~ObfuscatedZipDataStream() { close(); } //----------------------------------------------------------------------- size_t ObfuscatedZipDataStream::read(void* buf, size_t count) { zzip_ssize_t r = zzip_file_read(mZzipFile, (char*)buf, count); if (r<0) { ZZIP_DIR *dir = zzip_dirhandle(mZzipFile); Ogre::String msg = zzip_strerror_of(dir); OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, mName+" - error from zziplib: "+msg, "ObfuscatedZipDataStream::read"); } return (size_t) r; } //----------------------------------------------------------------------- void ObfuscatedZipDataStream::skip(long count) { zzip_seek(mZzipFile, static_cast<zzip_off_t>(count), SEEK_CUR); } //----------------------------------------------------------------------- void ObfuscatedZipDataStream::seek( size_t pos ) { zzip_seek(mZzipFile, static_cast<zzip_off_t>(pos), SEEK_SET); } //----------------------------------------------------------------------- size_t ObfuscatedZipDataStream::tell(void) const { return zzip_tell(mZzipFile); } //----------------------------------------------------------------------- bool ObfuscatedZipDataStream::eof(void) const { return (zzip_tell(mZzipFile) >= static_cast<zzip_off_t>(mSize)); } //----------------------------------------------------------------------- void ObfuscatedZipDataStream::close(void) { if (mZzipFile != 0) { zzip_file_close(mZzipFile); mZzipFile = 0; } } //----------------------------------------------------------------------- const Ogre::String& ObfuscatedZipFactory::getType(void) const { static Ogre::String name = "OBFUSZIP"; return name; } }

Usage


1) Somewhere in your application header declare:

Copy to clipboard
// Obfuscated Zip ObfuscatedZipFactory *mObfuscatedZipFactory;


2) Somewhere after your Ogre::Root is created:

Copy to clipboard
// Create ObfuscatedZipFactory. mObfuscatedZipFactory = new ObfuscatedZipFactory(); Ogre::ArchiveManager::getSingleton().addArchiveFactory(mObfuscatedZipFactory);


3) To clean-up, after Ogre::Root is deleted:

Copy to clipboard
// Need to delete mObfuscatedZipFactory after Ogre is destroyed otherwise // Ogre::ArchiveFactory may be destroyed prematurely. if(mObfuscatedZipFactory != NULL) { delete mObfuscatedZipFactory; mObfuscatedZipFactory = 0; }

How to obfuscate your zip file

The following command line utility will obfuscate your zip file using your chosen magic number.

 WARNING
Make sure all of the magic numbers match.

Copy to clipboard
#include <string> #include <zzip/plugin.h> // Your magic number. Change this to match the magic number in your un-obfuscator. static int xor_value = 0x10; // Override the read handler for zzip. static zzip_ssize_t our_read(int fd, void* buf, zzip_size_t len) { const zzip_ssize_t bytes = read(fd, buf, len); zzip_ssize_t i; char* pch = (char*)buf; for (i=0; i<bytes; ++i) { pch[i] ^= xor_value; } return bytes; } int main (int argc, char* argv[]) { std::string usage = " Obfuscate <fileName>\n" " - This program will (un)obfuscate a (.OBFUSZIP).zip file.\n" " Feed it a .zip file and it will obfuscate it.\n" " Feed it a .OBFUSZIP file and it will unobfuscate it.\n"; if(argc != 2) { printf(usage.c_str()); return 0; } if(strlen(argv[1]) > 128) { fprintf(stderr, "Please provide a filename shorter than 128 chars.\n"); exit(1); } std::string fileName_ext = argv[1]; std::string outFileName; // Get the extension and filename size_t delim = fileName_ext.find_last_of("."); std::string fileName = fileName_ext.substr(0 , delim); std::string extension = fileName_ext.substr(delim + 1); // Convert the extension to lower case. for(size_t position = 0; position < extension.size(); ++position) { extension[position] = tolower(extension[position]); } if(extension == "zip") { outFileName = fileName + ".OBFUSZIP"; } else if(extension == "obfuszip") { outFileName = fileName + ".zip"; } else { printf(usage.c_str()); return 0; } /* Convert the file */ FILE* fin = fopen(fileName_ext.c_str(), "rb"); if(!fin) { fprintf(stderr, "Can't open input file \"%s\"\n", fileName_ext.c_str()); exit(1); } FILE* fout = fopen(outFileName.c_str(), "wb"); if(!fout) { fprintf(stderr, "Can't open output file \"%s\"\n", outFileName.c_str()); exit(1); } int ch; while((ch = fgetc(fin)) != EOF) { ch ^= xor_value; fputc(ch, fout); } fclose(fout); fclose(fin); return 0; }

 Note
The command line utility above has been compiled with MinGW and tested on WinXP. Other platforms and other compilers have not been tested.