1. // provide aiw4421.bin on stdin, get unpacked binary config on stdout.
  2. import java.security.MessageDigest;
  3. import javax.crypto.Cipher;
  4. import javax.crypto.spec.*;
  5. import java.io.*;
  6. import java.util.*;
  7.  
  8. class XtractCfg {
  9.   static class BytePairDecompressionStream extends FilterOutputStream {
  10.     boolean escaped = false;
  11.     int escapechar;
  12.     ByteArrayOutputStream mapstream = new ByteArrayOutputStream();
  13.     int mapstreamsize = -2;
  14.     java.util.Map<Integer,Short> pairexpansion;
  15.  
  16.     public BytePairDecompressionStream(OutputStream parent) {
  17.       super(parent);
  18.     }
  19.    
  20.     public void write(int b) throws IOException {
  21.       if (pairexpansion != null) {
  22.         if (escaped)
  23.         {
  24.           super.write(b);
  25.           escaped = false;
  26.         } else if((b & 0xff) == escapechar) {
  27.           escaped = true;
  28.         } else {
  29.           Short expansion = pairexpansion.get(b & 0xFF);
  30.           if (expansion == null)
  31.             super.write(b);
  32.           else {
  33.             super.write(expansion.shortValue() >> 8);
  34.             super.write(expansion.shortValue() & 0xFF);
  35.           }
  36.         }
  37.       }
  38.       else if (mapstreamsize == -2) {
  39.         escapechar = b & 0xff;
  40.         mapstreamsize = -1;
  41.       } else if (mapstreamsize == -1) {
  42.         mapstreamsize = 3*b;
  43.       } else if (mapstream.size() < mapstreamsize) {
  44.         mapstream.write(b);
  45.       } else {
  46.         if (b != 0x5b)
  47.           throw new IOException("Bad magic after map");
  48.         pairexpansion = new TreeMap<Integer,Short>();
  49.         byte[] mapdata = mapstream.toByteArray();
  50.         mapstream = null;
  51.         for(int i = 0;i < mapdata.length / 3;i++)
  52.           pairexpansion.put(new Integer(mapdata[i*3] & 0xFF),new Short((short)((mapdata[i*3+1] & 0xFF) * 0x100 +
  53.                                          (mapdata[i*3+2] & 0xFF))));
  54.       }
  55.     }
  56.   }
  57.   static final byte[] key0 = {(byte)0x1d,(byte)0xe5,(byte)0xfa,(byte)0xb8,(byte)0x81,(byte)0x91,(byte)0x00,(byte)0x84,
  58.                               (byte)0x11,(byte)0xaf,(byte)0x53,(byte)0xdd,(byte)0xe4,(byte)0x89,(byte)0xea,(byte)0xfd};
  59.   static final byte[] key1 = {(byte)0xa9,(byte)0xdc,(byte)0x0e,(byte)0xf0,(byte)0x28,(byte)0x67,(byte)0x91,(byte)0xac,
  60.                               (byte)0xc9,(byte)0x13,(byte)0x86,(byte)0x5d,(byte)0x2d,(byte)0xf8,(byte)0x20,(byte)0x99};
  61.   static final byte[] key2 = {(byte)0x1a,(byte)0xa8,(byte)0x80,(byte)0xb5,(byte)0x44,(byte)0x11,(byte)0xf1,(byte)0x19,
  62.                               (byte)0x0c,(byte)0xca,(byte)0x3d,(byte)0xe8,(byte)0x77,(byte)0x41,(byte)0x37,(byte)0x89};
  63.   static final byte[] key3 = {(byte)0x45,(byte)0x68,(byte)0x70,(byte)0x98,(byte)0x7a,(byte)0xcd,(byte)0xeb,(byte)0xbb,
  64.                               (byte)0xbd,(byte)0xed,(byte)0xa8,(byte)0x03,(byte)0x98,(byte)0x73,(byte)0x40,(byte)0x24};
  65.   static final byte[][] keys = {key0, key1, key2, key3};
  66.  
  67.  
  68.   static void doit(InputStream source, OutputStream sink) throws Exception {
  69.     Cipher c = Cipher.getInstance("AES/CTS/NoPadding");
  70.     byte[] iv = new byte[16];
  71.     source.read(iv);
  72.     c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key0, "AES"), new IvParameterSpec(iv));
  73.  
  74.     byte[] header = new byte[0x28];
  75.     source.read(header);
  76.     DataInput hdrstream = new DataInputStream(new ByteArrayInputStream(c.doFinal(header)));
  77.     if(hdrstream.readInt() != 0x59585756)
  78.       throw new Exception("Bad magic");
  79.     int size = hdrstream.readInt();
  80.     int keynum = hdrstream.readByte();
  81.     if(keynum > 3 || keynum < 0)
  82.       throw new Exception("Bad key number (0-3 expected)");
  83.     byte[] expectedmd5 = new byte[16];
  84.     hdrstream.readFully(expectedmd5);
  85.  
  86.     byte[] body = new byte[size];
  87.     source.read(body);
  88.     byte[] actualmd5 = MessageDigest.getInstance("MD5").digest(body);
  89.     if(!MessageDigest.isEqual(actualmd5, expectedmd5))
  90.       throw new Exception("Body MD5 does not match");
  91.     c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keys[keynum], "AES"), new IvParameterSpec(body, 0, 16));
  92.     byte[] payload = c.doFinal(body,16, size-16);
  93.  
  94.     DataInput bodystream = new DataInputStream(new ByteArrayInputStream(payload));
  95.     if(bodystream.readInt() != 0x48464442)
  96.       throw new Exception("Bad body magic");
  97.     int payloadsize = bodystream.readInt();
  98.     bodystream.skipBytes(1);    /* key ID - unused as no further crypto layer is present */
  99.     bodystream.readFully(expectedmd5);
  100.     MessageDigest md5 = MessageDigest.getInstance("MD5");
  101.     md5.update(payload, 0x28, payloadsize);
  102.     actualmd5 = md5.digest();
  103.     if(!MessageDigest.isEqual(actualmd5, expectedmd5))
  104.       throw new Exception("Payload MD5 does not match");
  105.  
  106.     DataInputStream payloadhdrstream = new DataInputStream(new ByteArrayInputStream(payload, 0x28, 6));
  107.     if(payloadhdrstream.readUnsignedShort() != 0x6562)
  108.       throw new Exception("Byte-pair compression magic does not match");
  109.     int levels = payloadhdrstream.readUnsignedByte();
  110.     int compressedsize = payloadhdrstream.readUnsignedByte() * 0x10000;
  111.     compressedsize += payloadhdrstream.readUnsignedShort();
  112.     OutputStream str = sink;
  113.     for(int i = 0; i < levels; i++)
  114.       str = new BytePairDecompressionStream(str);
  115.     str.write(payload, 0x28+6, compressedsize);
  116.     str.flush();
  117.   }
  118.   public static void main(String[] args) throws Exception{
  119.     doit(System.in, System.out);
  120.   }
  121. }