// provide aiw4421.bin on stdin, get unpacked binary config on stdout. import java.security.MessageDigest; import javax.crypto.Cipher; import javax.crypto.spec.*; import java.io.*; import java.util.*; class XtractCfg { static class BytePairDecompressionStream extends FilterOutputStream { boolean escaped = false; int escapechar; ByteArrayOutputStream mapstream = new ByteArrayOutputStream(); int mapstreamsize = -2; java.util.Map pairexpansion; public BytePairDecompressionStream(OutputStream parent) { super(parent); } public void write(int b) throws IOException { if (pairexpansion != null) { if (escaped) { super.write(b); escaped = false; } else if((b & 0xff) == escapechar) { escaped = true; } else { Short expansion = pairexpansion.get(b & 0xFF); if (expansion == null) super.write(b); else { super.write(expansion.shortValue() >> 8); super.write(expansion.shortValue() & 0xFF); } } } else if (mapstreamsize == -2) { escapechar = b & 0xff; mapstreamsize = -1; } else if (mapstreamsize == -1) { mapstreamsize = 3*b; } else if (mapstream.size() < mapstreamsize) { mapstream.write(b); } else { if (b != 0x5b) throw new IOException("Bad magic after map"); pairexpansion = new TreeMap(); byte[] mapdata = mapstream.toByteArray(); mapstream = null; for(int i = 0;i < mapdata.length / 3;i++) pairexpansion.put(new Integer(mapdata[i*3] & 0xFF),new Short((short)((mapdata[i*3+1] & 0xFF) * 0x100 + (mapdata[i*3+2] & 0xFF)))); } } } static final byte[] key0 = {(byte)0x1d,(byte)0xe5,(byte)0xfa,(byte)0xb8,(byte)0x81,(byte)0x91,(byte)0x00,(byte)0x84, (byte)0x11,(byte)0xaf,(byte)0x53,(byte)0xdd,(byte)0xe4,(byte)0x89,(byte)0xea,(byte)0xfd}; static final byte[] key1 = {(byte)0xa9,(byte)0xdc,(byte)0x0e,(byte)0xf0,(byte)0x28,(byte)0x67,(byte)0x91,(byte)0xac, (byte)0xc9,(byte)0x13,(byte)0x86,(byte)0x5d,(byte)0x2d,(byte)0xf8,(byte)0x20,(byte)0x99}; static final byte[] key2 = {(byte)0x1a,(byte)0xa8,(byte)0x80,(byte)0xb5,(byte)0x44,(byte)0x11,(byte)0xf1,(byte)0x19, (byte)0x0c,(byte)0xca,(byte)0x3d,(byte)0xe8,(byte)0x77,(byte)0x41,(byte)0x37,(byte)0x89}; static final byte[] key3 = {(byte)0x45,(byte)0x68,(byte)0x70,(byte)0x98,(byte)0x7a,(byte)0xcd,(byte)0xeb,(byte)0xbb, (byte)0xbd,(byte)0xed,(byte)0xa8,(byte)0x03,(byte)0x98,(byte)0x73,(byte)0x40,(byte)0x24}; static final byte[][] keys = {key0, key1, key2, key3}; static void doit(InputStream source, OutputStream sink) throws Exception { Cipher c = Cipher.getInstance("AES/CTS/NoPadding"); byte[] iv = new byte[16]; source.read(iv); c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key0, "AES"), new IvParameterSpec(iv)); byte[] header = new byte[0x28]; source.read(header); DataInput hdrstream = new DataInputStream(new ByteArrayInputStream(c.doFinal(header))); if(hdrstream.readInt() != 0x59585756) throw new Exception("Bad magic"); int size = hdrstream.readInt(); int keynum = hdrstream.readByte(); if(keynum > 3 || keynum < 0) throw new Exception("Bad key number (0-3 expected)"); byte[] expectedmd5 = new byte[16]; hdrstream.readFully(expectedmd5); byte[] body = new byte[size]; source.read(body); byte[] actualmd5 = MessageDigest.getInstance("MD5").digest(body); if(!MessageDigest.isEqual(actualmd5, expectedmd5)) throw new Exception("Body MD5 does not match"); c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keys[keynum], "AES"), new IvParameterSpec(body, 0, 16)); byte[] payload = c.doFinal(body,16, size-16); DataInput bodystream = new DataInputStream(new ByteArrayInputStream(payload)); if(bodystream.readInt() != 0x48464442) throw new Exception("Bad body magic"); int payloadsize = bodystream.readInt(); bodystream.skipBytes(1); /* key ID - unused as no further crypto layer is present */ bodystream.readFully(expectedmd5); MessageDigest md5 = MessageDigest.getInstance("MD5"); md5.update(payload, 0x28, payloadsize); actualmd5 = md5.digest(); if(!MessageDigest.isEqual(actualmd5, expectedmd5)) throw new Exception("Payload MD5 does not match"); DataInputStream payloadhdrstream = new DataInputStream(new ByteArrayInputStream(payload, 0x28, 6)); if(payloadhdrstream.readUnsignedShort() != 0x6562) throw new Exception("Byte-pair compression magic does not match"); int levels = payloadhdrstream.readUnsignedByte(); int compressedsize = payloadhdrstream.readUnsignedByte() * 0x10000; compressedsize += payloadhdrstream.readUnsignedShort(); OutputStream str = sink; for(int i = 0; i < levels; i++) str = new BytePairDecompressionStream(str); str.write(payload, 0x28+6, compressedsize); str.flush(); } public static void main(String[] args) throws Exception{ doit(System.in, System.out); } }