3

So, I'm trying to translate a piece of C++ code to php. The C++ is from a external source, and my knowledge of both C++ and decryption is lacking, to say the least.

The source C++ is:

void parser_t::decrypt(buffer_t &replay_data, const unsigned char *key_data) {  
    /*\ 
    |*| Performs an in place decryption of the replay using the given key.
    |*| The decryption is a (broken) variant of CBC decryption and is performed as follows:
    |*| -# Set the variable previous_block (with size of 16 bytes) to 0
    |*| -# Decrypt a block with the given key
    |*| -# XOR the block with the previous (decrypted) block
    |*| -# Go back to step 2, until there are no more blocks.
    \*/
    BF_KEY key = {{0}};
    BF_set_key(&key, 16, key_data);

    const int block_size = 8;

    size_t padding_size = (block_size - (replay_data.size() % block_size));
    if (padding_size != 0) {
        size_t required_size = replay_data.size() + padding_size;
        replay_data.resize(required_size, 0);
    }

    unsigned char previous[block_size] = {0};
    for (auto it = replay_data.begin(); it != replay_data.end(); it += block_size) {
        unsigned char decrypted[block_size] = { 0 };
        BF_ecb_encrypt(reinterpret_cast<unsigned char*>(&(*it)), decrypted, &key, BF_DECRYPT);
        std::transform(previous, previous + block_size, decrypted, decrypted, std::bit_xor<unsigned char>());
        std::copy_n(decrypted, block_size, previous);
        std::copy_n(decrypted, block_size, reinterpret_cast<unsigned char*>(&(*it)));
    }

    if (padding_size != 0) {
        size_t original_size = replay_data.size() - padding_size;
        replay_data.resize(original_size, 0);
    }
}

What I have got so far:

function decrypt($data){ // $data is a encrypted string
    $key = array(0xDE, <.....>, 0xEF); // (16 entries in the array)

    //BF_KEY key = {{0}};             // ?
    //BF_set_key(&key, 16, key_data); // ?

    $block_size = 8;

    $padding_size = ($block_size - (strlen($data) % $block_size));
    if ($padding_size != 0) {
        $required_size = strlen($data) + $padding_size;
        //replay_data.resize(required_size, 0);
        // Seems unnecessary in php? string lengths are pretty dynamic.
    }

    $keyString = '';
    for($i = 0; $i < count($key); $i++){
        $keyString .= chr($key[$i]);
    }
    $output = '';

    for ($i = 0; $i < stlen($data); $i += $block_size) {
        $char = array(0, 0, 0, 0, 0, 0, 0, 0);                     // ?
        $decrypted_piece = mcrypt_decrypt(MCRYPT_BLOWFISH, $keyString, $data, "cbc"); // ??
        // And this is where I completely get lost.
        $output = transform($in, $start, $end, $in2);
    }
}

function transform($in, $start, $end, $in2){
    $out = ''; // Yea, that won't work...
    for($x = $start; $x < $end; $x++){
        $out[$x] = $in[$x] ^ $in2[$x];
    }
    return $output
}

I realize I'm basically asking you guys to do something for me, but I'm really stuck at the contents of that for (auto it....

Hints / explanations that'd really help me along would be:

  • What does BF_ecb_encrypt do in this case? (In pseudocode or even php?) (slaps self on fingers. "don't ask for finished products")
  • Am I on the right track with the translation of the transform?
  • What is {{0}}, BF_set_key(&key, 16, key_data);?
  • What is reinterpret_cast<unsigned char*>(&(*it))?

I did get a look at these documentation pages, but to no avail:

The full source is available on github.
This specific code comes from src/parser.cpp

Cerbrus
  • 70,800
  • 18
  • 132
  • 147
  • what is 'buffer_t' (buffer_t &replay_data)? I see that this is likely proprietary code, but I can likely whip-up a quick PHP extension using this C++ if you can post how this type is defined. *MUCH* faster than translating into PHP, and the resulting function will likely perform better. – JSON Feb 24 '14 at 01:43
  • Note that I don't need to see the actual code, just the details of how it works (is it std::map made by splitting an existing file? What size/type are the segments? etc). But code would be best if it's not too sensitive. – JSON Feb 24 '14 at 01:50
  • @JSON: I added a link to the source to my question. `buffer_t` seems to be: `typedef std::vector buffer_t;`. The file I'm trying to convert can be found [here](http://wotreplays.com/site/613099#prokhorovka-cerbrusnl-pz_kpfw_i_ausf_c) (or [direct link](http://wotreplays.com/site/download/613099)). – Cerbrus Feb 24 '14 at 07:37

3 Answers3

2

The "broken variant of CBC decryption" that the original code is doing can, equivalently, be described as ECB decryption followed by (cumulatively) XORing each plaintext block with the previous one.

For the XOR, you don't need anything fancy: the PHP bitwise XOR operator can operate on strings.

Thus, a simple PHP version of your C++ code could look something like this (warning: untested code):

function decrypt( $ciphertext, $key ) {
    $plaintext = mcrypt_decrypt( MCRYPT_BLOWFISH, $key, $ciphertext, "ecb" );

    $block_size = 8;  // Blowfish block size = 64 bits = 8 bytes
    $blocks = str_split( $plaintext, $block_size );

    $previous = str_repeat( "\0", $block_size );
    foreach ( $blocks as &$block ) {
        $block ^= $previous;
        $previous = $block;
    }

    return implode( $blocks );
}

Note that I haven't implemented any padding for truncated last blocks; there's something very screwy about the padding handling in the original code, and I don't see how it could possibly correctly decrypt messages whose length is not divisible by 8 bytes. (Does it, actually?) Rather than try to guess what the heck is going on and how to translate it to PHP, I just chose to ignore all that stuff and assume that the message length is divisible by the block size.

Ilmari Karonen
  • 49,047
  • 9
  • 93
  • 153
  • As I mentioned, I didn't write the original code. It should be decoding a part of the data stored in a [World of Tanks](http://worldoftanks.eu) replay file. Apparently, the C++ implementation works. I'll see if I can get some sensible data returned from your code. Thanks for your input, so far :) – Cerbrus Feb 23 '14 at 12:21
  • Oh, should I be using my `$keyString` variable for the `mcrypt_decrypt`? – Cerbrus Feb 23 '14 at 12:24
  • Yes, if I understand the mcrypt docs right, your `$keyString` looks like what it'll expect. – Ilmari Karonen Feb 23 '14 at 12:26
  • I'm not having any luck, so far... Theoretically, the function should be able to only decrypt the first, say, 160 bytes, right? That'd make it a little easier to debug. – Cerbrus Feb 23 '14 at 12:56
  • I'm suspecting my $key is incorrect... Comparing your code to the C++ source, it should work. Although the C++ is "old", the encryption shouldn't have changed. The key might have... – Cerbrus Feb 23 '14 at 20:22
  • If you can run the C++ code, you could test it with the same key as you have to see if you get the same results. And yes, the code should be able to decrypt any prefix of the data that's a multiple of 8 bytes. – Ilmari Karonen Feb 23 '14 at 20:40
  • I've been trying to compile the [source code](https://github.com/evido/wotreplay-parser), but I can't get a `/bin/` folder to show up after a `cmake`... This is where my lack of knowledge of C++ kicks in. – Cerbrus Feb 23 '14 at 22:54
2

What does BF_ecb_encrypt do in this case?

BF_ecb_encrypt() is a function for encrypting using blowfish. The PHP equivalent (as previously mentioned by Ilmari Karonen) is $plaintext = mcrypt_decrypt( MCRYPT_BLOWFISH, $key, $ciphertext, "ecb" );

What is reinterpret_cast(&(*it))?

BF_ecb_encrypt() expects it's first parameter to be a unsigned char*. reinterpret_cast<unsigned char*>(&(*it)) is a type cast, casting 'it' into a unsigned char*. 'it' is an iterator of an unspecified type (at least in the code provided) and the keyword 'auto' is used to initialize 'it' as the correct type automatically. reinterpret_cast<unsigned char*>(&(*it)) is then used to convert it unsigned char* automatically.

What is {{0}}, BF_set_key(&key, 16, key_data);?

This is used to initialize BF_KEY 'key', and then set the key using the value of key_data. This has no PHP equivalent, mcrypt will set the key internally.

Am I on the right track with the translation of the transform?

By the looks of it, the C++ version handles padding in an odd way. This could be intentional to throw a wrench into cracking attempts. Translating into PHP is not really possible unless you fully understand the algorithm of the original C++ - not just the encryption algo, but the full process being used, including pre and post encryption.

Have you considered making a simple PHP extension using the existing C/C++ code rather than converting to PHP? This should be very strait forward, much easier than converting a more complicated algorithm from C++ into PHP. The existing code can more or less be copy-pasted into an extension, with buffer_t &replay_data likely being registered as a PHP resource.

JSON
  • 1,819
  • 20
  • 27
  • A php extension would be the perfect solution. It'd probably be a lot cleaner than the php port I'm working on. Do you happen to have some pointers on how to get started, considering the source code I've linked in the question, and in case it's relevant, the fact I'm on a windows machine? – Cerbrus Feb 24 '14 at 07:43
  • @Cerbrus - it would be cleaner and will likely perform better. Equally importantly, it will be easy to keep it in-line with the actual mainline code. From what I'm gathering your trying to create a PHP implementation of an existing, live system. Unless the existing system is converting to PHP, your better off sticking with whats mainline. Otherwise you'll end up in the same position of trying to re-convert C/C++ to PHP if they change. I can at least post a pseudo template, but I should be able to post a ready to build ext if the dependencies don't take much time to work out. – JSON Feb 24 '14 at 08:22
  • Yea, I'm writing a parser for those replay files, in php. It's a bit of a personal project (re-inventing the wheel as a "puzzle"), and I managed to get all of the non-encrypted data out of there on my own. The encryption's not my strong point, though. I'm eager to see what you can whip up :D – Cerbrus Feb 24 '14 at 08:28
  • 1
    @Cerbrus The wheel didn't go from stone to composites on it's own :) – JSON Feb 24 '14 at 08:30
  • Evening, JSON. I was wondering if you had some time to look into the php library? – Cerbrus Feb 24 '14 at 21:44
  • Looking it over now. Should have something posted by morning – JSON Feb 25 '14 at 03:41
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/48320/discussion-between-json-and-cerbrus) – JSON Feb 25 '14 at 03:57
0

Would this be helpful, using the php-cpp library (see http://www.php-cpp.com):

/**
 *  Decrypt function made accessible from PHP
 */

/**
 *  Dependencies
 */
#include <phpcpp.h>
#include <openssl/blowfish.h>
#include <algorithm>

/**
 *  Define buffer_t to be a vector
 */
typedef std::vector<uint8_t> buffer_t;

/**
 *  Function that should be ported to PHP
 *  @param  data
 *  @param  key_data
 */
static void decrypt(std::string &replay_data, const unsigned char *key_data) {  
    /*\ 
    |*| Performs an in place decryption of the replay using the given key.
    |*| The decryption is a (broken) variant of CBC decryption and is performed as follows:
    |*| -# Set the variable previous_block (with size of 16 bytes) to 0
    |*| -# Decrypt a block with the given key
    |*| -# XOR the block with the previous (decrypted) block
    |*| -# Go back to step 2, until there are no more blocks.
    \*/
    BF_KEY key = {{0}};
    BF_set_key(&key, 16, key_data);

    const int block_size = 8;

    size_t padding_size = (block_size - (replay_data.size() % block_size));
    if (padding_size != 0) {
        size_t required_size = replay_data.size() + padding_size;
        replay_data.resize(required_size, 0);
    }

    unsigned char previous[block_size] = {0};
    for (auto it = replay_data.begin(); it != replay_data.end(); it += block_size) {
        unsigned char decrypted[block_size] = { 0 };
        BF_ecb_encrypt(reinterpret_cast<unsigned char*>(&(*it)), decrypted, &key, BF_DECRYPT);
        std::transform(previous, previous + block_size, decrypted, decrypted, std::bit_xor<unsigned char>());
        std::copy_n(decrypted, block_size, previous);
        std::copy_n(decrypted, block_size, reinterpret_cast<unsigned char*>(&(*it)));
    }

    if (padding_size != 0) {
        size_t original_size = replay_data.size() - padding_size;
        replay_data.resize(original_size, 0);
    }
}

/**
 *  The PHP function that will take care of this
 *  @param  parameters
 *  @return Value
 */
static Php::Value php_decrypt(Php::Parameters &params)
{
    // check number of parameters
    if (params.size() != 2) throw Php::Exception("2 parameters expected");

    // read in the parameters
    std::string replay_data = params[0];
    std::string key_data = params[1];

    // decrypt it
    decrypt(replay_data, (const unsigned char *)key_data.c_str());

    // return the result
    return replay_data;
}

/**
 *  Symbols are exported according to the "C" language
 */
extern "C" 
{
    // export the "get_module" function that will be called by the Zend engine
    PHPCPP_EXPORT void *get_module()
    {
        // create extension
        static Php::Extension extension("my_decrypt","1.0");

        // add custom function
        extension.add("my_decrypt", php_decrypt);

        // return the extension module
        return extension.module();
    }
}

You can compile the code using the following command:

g++ -std=c++11 -fpic -shared my_decrypt.cpp -o my_decrypt.so -lphpcpp

The my_descript.so should be copied to you PHP extensions directory, and an "extension=my_decrypt.so" line should be added to your php.ini.