It's likely everyone is using different clients and I don't want to deal with unpacking or Xtrap removal. So I've decided to explain everything with these
client files I found via ragezone and posted by cyber37, apparently from chumpywumpy's v2.5 server files. All clients employ the same logic.
The client uses the popular inflation/deflation algorithms found in
zlib with a suppressed header (
negative windowBits). Albeit they use an older public domain code base from the same author, Mark Adler, it can be found
here. The code was compiled without the
PKZIP_BUG_WORKAROUND definition at line 172 and uses a custom
FLUSH and
NEXTBYTE definition at lines 214 and 206. Otherwise, everything else is exactly alike. The custom
NEXTBYTE instructions contain
XOR encryption for only the first 4 inflated bytes while the rest pass through without modification.
Client offsets of the functions are (look at your keyboard to replace back the missing digits):
inflate = ))$@D#B^
inflate_block = ))$@D#@$
inflate_dynamic = ))$@CF(#
inflate_stored = ))$@CDAA
inflate_fixed = ))$@CEB$
inflate_codes = ))$@CAE^
huft_build = ))$@C&@)
huft_free = ))$@CACA
NEEDBYTE = ))$@D$$*
XOR:
byte 1 xor 0x92
byte 2 xor 0x65
byte 3 xor 0x67
byte 4 xor 0x57
The first 4 bytes of the files contain the uncompressed data size which is used to allocate the correct amount of memory. The next 4 bytes are encrypted. With this and the above knowledge you can now program your own application. I've thrown together a quick hack job sample using the zpipe example from
zlib. You can download and apply this
patch file or get the
full source. Be warned my development platform is Linux and it won't compile for Windows. For Windows you would need to remove the code that retrieves the file size in the def function and replace it with the Win32 equivalent, eg.
Code:
HANDLE hFile;
hFile = GetStdHandle(STD_INPUT_HANDLE);
if (GetFileSizeEx(hFile, &dest_size) == 0)
return Z_ERRNO;
Apply Patch:
patch < zpipe-enc.patch
Compile ZPipe:
gcc -o zpipe zpipe.c -lz
Compress Usage:
zpipe < phantom.txt > phantom.enc
Decompress Usage:
zpipe -d < phantom.enc > phantom.txt
Note: Newer clients have a list of 128bit checksums to deter modification. It's far easier to remove the corrupt file check than it is to replace the list. Both require modifying the client.
Code:
/* zpipe.c: example of proper use of zlib's inflate() and deflate()
Not copyrighted -- provided to the public domain
Version 1.2 9 November 2004 Mark Adler */
/* Version history:
1.0 30 Oct 2004 First version
1.1 8 Nov 2004 Add void casting for unused return values
Use switch statement for inflate() return values
1.2 9 Nov 2004 Add assertions to document zlib guarantees
1.3 6 Apr 2005 Remove incorrect assertion in inf()
*/
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "zlib.h"
#include <sys/types.h>
#include <sys/stat.h>
#define CHUNK 16384
/* Compress from file source to file dest until EOF on source.
def() returns Z_OK on success, Z_MEM_ERROR if memory could not be
allocated for processing, Z_STREAM_ERROR if an invalid compression
level is supplied, Z_VERSION_ERROR if the version of zlib.h and the
version of the library linked do not match, or Z_ERRNO if there is
an error reading or writing the files. */
int def(FILE *source, FILE *dest, int level)
{
int ret, flush;
unsigned have;
z_stream strm;
char in[CHUNK];
char out[CHUNK];
char *buf;
unsigned long dest_size;
unsigned long indx = 0;
struct stat sb;
have = fileno(source);
if (fstat(have, &sb) < 0)
return Z_ERRNO;
if (!S_ISREG(sb.st_mode))
return Z_ERRNO;
if ((dest_size = (unsigned long)sb.st_size) <= 0)
return Z_ERRNO;
if (fwrite(&dest_size, 4, 1, dest) != 1 || ferror(dest))
return Z_ERRNO;
/* allocate deflate state */
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
ret = deflateInit2(&strm, level, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY);
if (ret != Z_OK)
return ret;
/* compress until end of file */
do {
strm.avail_in = fread(in, 1, CHUNK, source);
if (ferror(source)) {
(void)deflateEnd(&strm);
return Z_ERRNO;
}
flush = feof(source) ? Z_FINISH : Z_NO_FLUSH;
strm.next_in = in;
/* run deflate() on input until output buffer not full, finish
compression if all of source has been read in */
do {
strm.avail_out = CHUNK;
strm.next_out = out;
ret = deflate(&strm, flush); /* no bad return value */
assert(ret != Z_STREAM_ERROR); /* state not clobbered */
have = CHUNK - strm.avail_out;
if (have > 0) {
buf = out;
do {
if (dest_size > indx++) {
switch (indx) {
case 1:
*buf ^= 0x92;
break;
case 2:
*buf ^= 0x65;
break;
case 3:
*buf ^= 0x67;
break;
case 4:
*buf ^= 0x57;
}
}
} while (*buf++);
if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
(void)deflateEnd(&strm);
return Z_ERRNO;
}
}
} while (strm.avail_out == 0);
assert(strm.avail_in == 0); /* all input will be used */
/* done when last data in file processed */
} while (flush != Z_FINISH);
assert(ret == Z_STREAM_END); /* stream will be complete */
/* clean up and return */
(void)deflateEnd(&strm);
return Z_OK;
}
/* Decompress from file source to file dest until stream ends or EOF.
inf() returns Z_OK on success, Z_MEM_ERROR if memory could not be
allocated for processing, Z_DATA_ERROR if the deflate data is
invalid or incomplete, Z_VERSION_ERROR if the version of zlib.h and
the version of the library linked do not match, or Z_ERRNO if there
is an error reading or writing the files. */
int inf(FILE *source, FILE *dest)
{
int ret;
unsigned have;
z_stream strm;
char in[CHUNK];
char out[CHUNK];
char *buf;
unsigned long dest_size;
unsigned long indx = 0;
have = fread(in, 1, 4, source);
if (have != 4 || ferror(source)) {
return Z_ERRNO;
}
dest_size = ((unsigned long)in[0]) |
((unsigned long)in[1] << 8) |
((unsigned long)in[2] << 16) |
((unsigned long)in[3] << 24);
/* allocate inflate state */
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = 0;
strm.next_in = Z_NULL;
ret = inflateInit2(&strm, -MAX_WBITS);
if (ret != Z_OK)
return ret;
/* decompress until deflate stream ends or end of file */
do {
strm.avail_in = fread(in, 1, CHUNK, source);
if (ferror(source)) {
(void)inflateEnd(&strm);
return Z_ERRNO;
}
if (strm.avail_in == 0)
break;
buf = in;
do {
if (dest_size > indx++) {
switch (indx) {
case 1:
*buf ^= 0x92;
break;
case 2:
*buf ^= 0x65;
break;
case 3:
*buf ^= 0x67;
break;
case 4:
*buf ^= 0x57;
}
}
} while (*buf++);
strm.next_in = in;
/* run inflate() on input until output buffer not full */
do {
strm.avail_out = CHUNK;
strm.next_out = out;
ret = inflate(&strm, Z_NO_FLUSH);
assert(ret != Z_STREAM_ERROR); /* state not clobbered */
switch (ret) {
case Z_NEED_DICT:
ret = Z_DATA_ERROR; /* and fall through */
case Z_DATA_ERROR:
case Z_MEM_ERROR:
(void)inflateEnd(&strm);
return ret;
}
have = CHUNK - strm.avail_out;
if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
(void)inflateEnd(&strm);
return Z_ERRNO;
}
} while (strm.avail_out == 0);
/* done when inflate() says it's done */
} while (ret != Z_STREAM_END);
/* clean up and return */
(void)inflateEnd(&strm);
return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
}
/* report a zlib or i/o error */
void zerr(int ret)
{
fputs("zpipe: ", stderr);
switch (ret) {
case Z_ERRNO:
if (ferror(stdin))
fputs("error reading stdin\n", stderr);
if (ferror(stdout))
fputs("error writing stdout\n", stderr);
break;
case Z_STREAM_ERROR:
fputs("invalid compression level\n", stderr);
break;
case Z_DATA_ERROR:
fputs("invalid or incomplete deflate data\n", stderr);
break;
case Z_MEM_ERROR:
fputs("out of memory\n", stderr);
break;
case Z_VERSION_ERROR:
fputs("zlib version mismatch!\n", stderr);
}
}
/* compress or decompress from stdin to stdout */
int main(int argc, char **argv)
{
int ret;
/* do compression if no arguments */
if (argc == 1) {
ret = def(stdin, stdout, Z_DEFAULT_COMPRESSION);
if (ret != Z_OK)
zerr(ret);
return ret;
}
/* do decompression if -d specified */
else if (argc == 2 && strcmp(argv[1], "-d") == 0) {
ret = inf(stdin, stdout);
if (ret != Z_OK)
zerr(ret);
return ret;
}
/* otherwise, report usage */
else {
fputs("zpipe usage: zpipe [-d] < source > dest\n", stderr);
return 1;
}
}