2

I have a password-protected zip file, where the paths and filenames of each file are encrypted using PKWARE encryption.

The password is known and correct, and so is the encryption algorithm.

I also have sample plaintext, and an application (32-bit closed-source, optimized, no debug symbols) that can correctly decrypt the files.

Below is a sample of the partially decrypted file names, alongside some fully decrypted file names. Partially correctly decrypted file names

Below is a sample of the fully decrypted files, generated by the closed-source program.

Fully correctly decrypted files

As you can observe, for the majority of the files, the file names can be correctly decrypted. However, for a small set, I get partially decoded file names, and mojibake for the rest.

So my question is, why is this happening and what should I do to rectify it? I am confused because I was expecting 100% rubbish if my decryption implementation was wrong. Partially correct output was not really what I expected.

Note that each file name is independently decrypted, in other words, the decryption of the next file does not depend on the decryption of the previous file.

The decryption implementation is more or less the same as that is described here:

static uint32_t keys[3];

// CRC32_table is from zlib
uint32_t compute_crc32(uint32_t crc, char c) {
    return CRC32_table[(crc ^ c) & 0xff] ^ (crc >> 8);
}

void init_keys(const std::string& password) {
    keys = { 0x12345678, 0x23456789, 0x34567890 };
    for (size_t i = 0; i < password.size(); ++i)
        key_update(keys, password[i]);
}

void update_keys(char c) {
    keys[0] = compute_crc32(keys[0], c);
    keys[1] = keys[1] + (keys[0] & 0xff);
    keys[1] = (keys[1] * 0x8088405) + 1;
    keys[2] = compute_crc32(keys[2], keys[1] >> 24);
}

char decrypt_byte() {
    uint16_t temp = (keys[2] & 0xffff) | 2;
    return char(((temp * (temp ^ 1))>> 8) & 0xff);
}

std::string decrypt(const std::string& password,
                    const std::string& input) {
    init_keys(password);
    auto ret = std::string(input.size(), '\0');
    for (int i = 0; i < input.size(); ++i) {
        char c = input[i] ^ decrypt_byte();
        update_keys(c);
        ret[i] = c;
    }
    return ret;
}

Note that the files are still correctly decrypted and unzipped, it is only the file names that are scrambled.

1

Afaik the standard ZIP format does not encrypt filenames but only the files' content. So it seems that this software implemented a custom ZIP extension for encrypting filenames based on the same algorithm. It looks like you've got it very close, so now you just need to step through the code in the original binary and your algorithm working on the same data and see where the discrepancy occurs.

  • 1
    Thanks for the response. I have loaded the binary into Ghidra and examined the disassembled routines involved in the decryption of the strings. It is very close, pretty much identical to my current implementation with some optimizations. I will edit my question once I organize the disassembled code a bit. And yes, it is a custom format based on Zip. It can be unzipped normally if the password is supplied; the only difference is that all filenames will remain encrypted. – user3105833 Aug 30 at 11:40
  • After further reverse engineering, I realize that the input string itself was different at very strange, specific places. This question no longer has value, but I lack reputation to do anything meaningful. – user3105833 Sep 2 at 14:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.