A bit of reverse engineering

I spent the last two days to tease out the data from the files of TGEB’s tariff data files. The actual data is compressed first and then encrypted with some kind of cipher feedback/chaining mechanism.

While it was relatively easy to follow the decryption process in a debugger, the compression was a bit of a obstacle for me at first.

In the end I was able to implement two small Java snippets. The first one decrypts the data and the second one creates a lzh file from the output. Extractable with any archive app, provided it supports extracting files where the CRC-16 check sum does not validate.

To decrypt:

import java.io.FileInputStream;
import java.io.FileOutputStream;

public class Decrypt {
    public static void main(String[] args) throws Exception {
        String infilename = "original.dat";
        String outfilename = "decrypted.dat";

        FileInputStream fis = new FileInputStream(infilename);
        FileOutputStream fos = new FileOutputStream(outfilename);

        short varA = (short)0xb6f2;
        short var15;
        short eax;
        short edx;

        System.out.println(fis.available());

        while(fis.available() > 0) {
            var15 = (short)fis.read();
            var15 &= 0xff;

            eax = var15;
            edx = varA;

            edx >>>= 8;
            edx &= 0xff;

            var15 = (short)(var15 ^ edx);
            var15 &= 0xffff;

            eax &= 0xff;
            eax += varA;
            eax *= 0xDC1B;
            eax += 0x41A5;
            varA = eax;

            fos.write(var15);
        }

        fis.close();
        fos.flush();
        fos.close();
    }
}

To create the lzh file:

import java.io.FileInputStream;
import java.io.FileOutputStream;

public class Decrypt2LZH {
    public static void main(String[] args) throws Exception {
        String infilename = "decrypted.dat";
        String outfilename = "data.lzh";

        FileInputStream fis = new FileInputStream(infilename);
        FileOutputStream fos = new FileOutputStream(outfilename);

        byte[] header = {
            (byte)0x1b, (byte)0x00, // header size, header checksum
            (byte)'-', (byte)'l', (byte)'h', (byte)'5', (byte)'-', // compression method -lh5-
            (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, // compressed size
            (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, // original size
            (byte)0x00, (byte)0x00, (byte)0x21, (byte)0x00, // msdos last modified date/time
            (byte)0x20, (byte)0x00, // msdos file attributes
            (byte)0x05, // size of filename
            (byte)'x', (byte)'.', (byte)'d', (byte)'a', (byte)'t', // filename x.dat
            (byte)0x00, (byte)0x00 // crc-16 of original file
        };

        int compressed = fis.read();
        int encrypted = fis.read();

        if(compressed != 0x26)
            return;

        if(encrypted == 0x26)
            throw new IllegalArgumentException("blowfish encryption not supported");

        // read original file size from inputfile and put it in the lha header
        header[11] = (byte)fis.read();
        header[12] = (byte)fis.read();
        header[13] = (byte)fis.read();
        header[14] = (byte)fis.read();

        // put the compressed size in the header
        int csize = fis.available();
        header[7]  = (byte)(csize & 0xff);
        header[8]  = (byte)((csize >>> 8 ) & 0xff);
        header[9]  = (byte)((csize >>> 16 ) & 0xff);
        header[10]  = (byte)((csize >>> 24 ) & 0xff);

        // calculate the header checksum
        int sum = 0;
        for(byte b: header)
            sum += b;

        header[1] = (byte)(sum & 0xff);

        // write lzh file
        fos.write(header);

        while(fis.available() > 0)
            fos.write(fis.read());

        fos.flush();
        fos.close();
        fis.close();
    }
}

Contact

Alexander Gitter
contact at agitter net