[Fix] MapleAESOFB.Java

Newbie Spellweaver
Joined
Dec 31, 2010
Messages
13
Reaction score
2
Hello : ) ?

1) Odin has some d/c issue..
2) packets becomes haywire or stop receiving..
3) changing channel sometimes cause the iv to be broken

well after noticing I realised that odin used a very different way to deal with the mapleaesofb encryption by using a static aes key which they should not

SecretKeySpec sKeySpec = new SecretKeySpec(Key, "AES");

the key part should be the AESKey by maple and various wrong stuff which just kills it then why does it work cause it is not fully wrong it works sort..

so I rewrote it based on MapleShark and Chronicle :) so far best this should fix any iv probelm and hopefully d/c issue

;) Hint : upgrade Mina to 2.1 odin one is old... and please dont :sneaky2: hated I am just trying to help...

Code:



:ott:

Enjoy..
 
Last edited by a moderator:
Work for All Version?

---------- Post added at 03:53 PM ---------- Previous post was at 03:37 PM ----------

Mina-Core ?



is that?
 
error:

Code:
C:\xkelvin94xsource2\src\net\mina\MaplePacketDecoder.java:48: cannot find symbol
symbol  : method checkPacket(int)
location: class tools.MapleAESOFB
            if (!client.getReceiveCrypto().checkPacket(packetHeader)) {
C:\xkelvin94xsource2\src\net\mina\MaplePacketDecoder.java:52: cannot find symbol
symbol  : method getPacketLength(int)
location: class tools.MapleAESOFB
            decoderState.packetlength = MapleAESOFB.getPacketLength(packetHeader);
C:\xkelvin94xsource2\src\net\mina\MaplePacketDecoder.java:60: cannot find symbol
symbol  : method crypt(byte[])
location: class tools.MapleAESOFB
            client.getReceiveCrypto().crypt(decryptedPacket);
C:\xkelvin94xsource2\src\net\mina\MaplePacketEncoder.java:39: cannot find symbol
symbol  : method getPacketHeader(int)
location: class tools.MapleAESOFB
            final byte[] header = client.getSendCrypto().getPacketHeader(unencrypted.length);
C:\xkelvin94xsource2\src\net\mina\MaplePacketEncoder.java:42: cannot find symbol
symbol  : method crypt(byte[])
location: class tools.MapleAESOFB
                client.getSendCrypto().crypt(unencrypted);
Note: Some input files use unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

Using MoopleDEV latest version . {V83)
 
I get what you have to change in MaplePacketEncoder, but not too sure about Decoder :\.

Is there anything you have to change, or can you lead me as how to do so in MaplePacketDecoder? I don't know where to use confirmHeader and getHeaderLength...
 
I'm going to use this for my server. Will post with results.

Oh and by the way, please put your changes in red so I could know what exactly changed.
 
FateJiki

The they used was not working well,cause for 1 the Crypt function where you encrypt and get new iv was their main cause,they used a static aes key why it should be the MapleAESKey. in other words they was just feeding the client with a hackish encryption,I believe it was due to limited resources back then.

for the mapleencoder and mapledecoder I still have to look at it :S and see what they did it will take time for sure.reversing MapleShark and Looking at Chronicle.. see what I can pull up

@LikeReally

you nick say it all ;) ?
 
Last edited:
Re: [Source] [Fix] MapleAESOFB.Java

Heres an idea, since SecretKeySpec is a certificate and does not change, therefore instead of creating an instance each time and wasting memory. Its much smarter to use one static object each time.

This is my MapleAESOFB:
Code:
package net.sf.odinms.tools;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

/**
 * Provides a class for encrypting MapleStory packets with AES OFB encryption.
 *
 * @author Frz
 * @version 1.0
 * @since Revision 320
 */
public class MapleAESOFB {
	private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MapleAESOFB.class);
	private static final SecretKeySpec CERTIFICATE = new SecretKeySpec(new byte[] {0x13, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, (byte) 0xB4, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00}, "AES");
	private static final byte ivBytes[] = new byte[] {
		(byte)0xEC, (byte)0x3F, (byte)0x77, (byte)0xA4, (byte)0x45,
		(byte)0xD0, (byte)0x71, (byte)0xBF, (byte)0xB7, (byte)0x98,
		(byte)0x20, (byte)0xFC, (byte)0x4B, (byte)0xE9, (byte)0xB3,
		(byte)0xE1, (byte)0x5C, (byte)0x22, (byte)0xF7, (byte)0x0C,
		(byte)0x44, (byte)0x1B, (byte)0x81, (byte)0xBD, (byte)0x63,
		(byte)0x8D, (byte)0xD4, (byte)0xC3, (byte)0xF2, (byte)0x10,
		(byte)0x19, (byte)0xE0, (byte)0xFB, (byte)0xA1, (byte)0x6E,
		(byte)0x66, (byte)0xEA, (byte)0xAE, (byte)0xD6, (byte)0xCE,
		(byte)0x06, (byte)0x18, (byte)0x4E, (byte)0xEB, (byte)0x78,
		(byte)0x95, (byte)0xDB, (byte)0xBA, (byte)0xB6, (byte)0x42,
		(byte)0x7A, (byte)0x2A, (byte)0x83, (byte)0x0B, (byte)0x54,
		(byte)0x67, (byte)0x6D, (byte)0xE8, (byte)0x65, (byte)0xE7,
		(byte)0x2F, (byte)0x07, (byte)0xF3, (byte)0xAA, (byte)0x27,
		(byte)0x7B, (byte)0x85, (byte)0xB0, (byte)0x26, (byte)0xFD,
		(byte)0x8B, (byte)0xA9, (byte)0xFA, (byte)0xBE, (byte)0xA8,
		(byte)0xD7, (byte)0xCB, (byte)0xCC, (byte)0x92, (byte)0xDA,
		(byte)0xF9, (byte)0x93, (byte)0x60, (byte)0x2D, (byte)0xDD,
		(byte)0xD2, (byte)0xA2, (byte)0x9B, (byte)0x39, (byte)0x5F,
		(byte)0x82, (byte)0x21, (byte)0x4C, (byte)0x69, (byte)0xF8,
		(byte)0x31, (byte)0x87, (byte)0xEE, (byte)0x8E, (byte)0xAD,
		(byte)0x8C, (byte)0x6A, (byte)0xBC, (byte)0xB5, (byte)0x6B,
		(byte)0x59, (byte)0x13, (byte)0xF1, (byte)0x04, (byte)0x00,
		(byte)0xF6, (byte)0x5A, (byte)0x35, (byte)0x79, (byte)0x48,
		(byte)0x8F, (byte)0x15, (byte)0xCD, (byte)0x97, (byte)0x57,
		(byte)0x12, (byte)0x3E, (byte)0x37, (byte)0xFF, (byte)0x9D,
		(byte)0x4F, (byte)0x51, (byte)0xF5, (byte)0xA3, (byte)0x70,
		(byte)0xBB, (byte)0x14, (byte)0x75, (byte)0xC2, (byte)0xB8,
		(byte)0x72, (byte)0xC0, (byte)0xED, (byte)0x7D, (byte)0x68,
		(byte)0xC9, (byte)0x2E, (byte)0x0D, (byte)0x62, (byte)0x46,
		(byte)0x17, (byte)0x11, (byte)0x4D, (byte)0x6C, (byte)0xC4,
		(byte)0x7E, (byte)0x53, (byte)0xC1, (byte)0x25, (byte)0xC7,
		(byte)0x9A, (byte)0x1C, (byte)0x88, (byte)0x58, (byte)0x2C,
		(byte)0x89, (byte)0xDC, (byte)0x02, (byte)0x64, (byte)0x40,
		(byte)0x01, (byte)0x5D, (byte)0x38, (byte)0xA5, (byte)0xE2,
		(byte)0xAF, (byte)0x55, (byte)0xD5, (byte)0xEF, (byte)0x1A,
		(byte)0x7C, (byte)0xA7, (byte)0x5B, (byte)0xA6, (byte)0x6F,
		(byte)0x86, (byte)0x9F, (byte)0x73, (byte)0xE6, (byte)0x0A,
		(byte)0xDE, (byte)0x2B, (byte)0x99, (byte)0x4A, (byte)0x47,
		(byte)0x9C, (byte)0xDF, (byte)0x09, (byte)0x76, (byte)0x9E,
		(byte)0x30, (byte)0x0E, (byte)0xE4, (byte)0xB2, (byte)0x94,
		(byte)0xA0, (byte)0x3B, (byte)0x34, (byte)0x1D, (byte)0x28,
		(byte)0x0F, (byte)0x36, (byte)0xE3, (byte)0x23, (byte)0xB4,
		(byte)0x03, (byte)0xD8, (byte)0x90, (byte)0xC8, (byte)0x3C,
		(byte)0xFE, (byte)0x5E, (byte)0x32, (byte)0x24, (byte)0x50,
		(byte)0x1F, (byte)0x3A, (byte)0x43, (byte)0x8A, (byte)0x96,
		(byte)0x41, (byte)0x74, (byte)0xAC, (byte)0x52, (byte)0x33,
		(byte)0xF0, (byte)0xD9, (byte)0x29, (byte)0x80, (byte)0xB1,
		(byte)0x16, (byte)0xD3, (byte)0xAB, (byte)0x91, (byte)0xB9,
		(byte)0x84, (byte)0x7F, (byte)0x61, (byte)0x1E, (byte)0xCF,
		(byte)0xC5, (byte)0xD1, (byte)0x56, (byte)0x3D, (byte)0xCA,
		(byte)0xF4, (byte)0x05, (byte)0xC6, (byte)0xE5, (byte)0x08,
		(byte)0x49};
	private byte iv[];
	private Cipher cipher;
	private short mapleVersion;

	/**
	 * Class constructor - Creates an instance of the MapleStory encryption
	 * cipher.
	 *
	 * @param key The 256 bit AES key to use.
	 * @param iv The 4-byte IV to use.
	 */
	public MapleAESOFB(byte iv[], short mapleVersion) {
		try {
			cipher = Cipher.getInstance("AES");
		} catch (NoSuchAlgorithmException e) {
			log.error("ERROR", e);
		} catch (NoSuchPaddingException e) {
			log.error("ERROR", e);
		}
		try {
			cipher.init(Cipher.ENCRYPT_MODE, CERTIFICATE);
		} catch (InvalidKeyException e) {
			log.error("[Error] JCE Files are not in use.");
		}
		this.setIv(iv);
		this.mapleVersion = (short) (((mapleVersion >> 8) & 0xFF) | ((mapleVersion << 8) & 0xFF00));
	}

	/**
	 * Sets the IV of this instance.
	 *
	 * @param iv The new IV.
	 */
	private void setIv(byte[] iv) {
		this.iv = iv;
	}

	/**
	 * For debugging/testing purposes only.
	 *
	 * @return The IV.
	 */
	public byte[] getIv() {
		return this.iv;
	}

	/**
	 * Encrypts <code>data</code> and generates a new IV.
	 *
	 * @param data The bytes to encrypt.
	 * @return The encrypted bytes.
	 */
	public byte[] crypt(byte[] data) {
		int remaining = data.length;
		int llength = 0x5B0;
		int start = 0;
		while (remaining > 0) {
			byte[] myIv = BitTools.multiplyBytes(this.iv, 4, 4);
			if (remaining < llength) {
				llength = remaining;
			}
			for (int x = start; x < (start + llength); x++) {
				if ((x - start) % myIv.length == 0) {
					try {
						byte[] newIv = cipher.doFinal(myIv);
						for (int j = 0; j < myIv.length; j++) {
							myIv[j] = newIv[j];
						}
					} catch (IllegalBlockSizeException e) {
						e.printStackTrace();
					} catch (BadPaddingException e) {
						e.printStackTrace();
					}
				}
				data[x] ^= myIv[(x - start) % myIv.length];
			}
			start += llength;
			remaining -= llength;
			llength = 0x5B4;
		}
		updateIv();
		return data;
	}

	/**
	 * Generates a new IV.
	 */
	private void updateIv() {
		this.iv = getNewIv(this.iv);
	}

	/**
	 * Generates a packet header for a packet that is <code>length</code>
	 * long.
	 *
	 * @param length How long the packet that this header is for is.
	 * @return The header.
	 */
	public byte[] getPacketHeader(int length) {
		int iiv = (iv[3]) & 0xFF;
		iiv |= (iv[2] << 8) & 0xFF00;
		iiv ^= mapleVersion;
		int mlength = ((length << 8) & 0xFF00) | (length >>> 8);
		int xoredIv = iiv ^ mlength;
		byte[] ret = new byte[4];
		ret[0] = (byte) ((iiv >>> 8) & 0xFF);
		ret[1] = (byte) (iiv & 0xFF);
		ret[2] = (byte) ((xoredIv >>> 8) & 0xFF);
		ret[3] = (byte) (xoredIv & 0xFF);
		return ret;
	}

	/**
	 * Gets the packet length from a header.
	 *
	 * @param packetHeader The header as an integer.
	 * @return The length of the packet.
	 */
	public static int getPacketLength(int packetHeader) {
		int packetLength = ((packetHeader >>> 16) ^ (packetHeader & 0xFFFF));
		packetLength = ((packetLength << 8) & 0xFF00) | ((packetLength >>> 8) & 0xFF); // fix
		return packetLength;
	}

	/**
	 * Check the packet to make sure it has a header.
	 *
	 * @param packet The packet to check.
	 * @return <code>True</code> if the packet has a correct header,
	 *         <code>false</code> otherwise.
	 */
	public boolean checkPacket(byte[] packet) {
		return ((((packet[0] ^ iv[2]) & 0xFF) == ((mapleVersion >> 8) & 0xFF)) && (((packet[1] ^ iv[3]) & 0xFF) == (mapleVersion & 0xFF)));
	}

	/**
	 * Check the header for validity.
	 *
	 * @param packetHeader The packet header to check.
	 * @return <code>True</code> if the header is correct, <code>false</code> otherwise.
	 */
	public boolean checkPacket(int packetHeader) {
		byte packetHeaderBuf[] = new byte[2];
		packetHeaderBuf[0] = (byte) ((packetHeader >> 24) & 0xFF);
		packetHeaderBuf[1] = (byte) ((packetHeader >> 16) & 0xFF);
		return checkPacket(packetHeaderBuf);
	}

	/**
	 * Gets a new IV from <code>oldIv</code>
	 *
	 * @param oldIv The old IV to get a new IV from.
	 * @return The new IV.
	 */
	public static byte[] getNewIv(byte oldIv[]) {
		byte[] in = {(byte) 0xf2, 0x53, (byte) 0x50, (byte) 0xc6};
		for (int x = 0; x < 4; x++) {
			encodeBytes(oldIv[x], in);
		}
		return in;
	}

	/**
	 * Does funny stuff. <code>this.OldIV</code> must not equal
	 * <code>in</code> Modifies <code>in</code> and returns it for
	 * convenience.
	 *
	 * @param inputByte The byte to apply the funny stuff to.
	 * @param in Something needed for all this to occur.
	 * @return The modified version of <code>in</code>.
	 */
	public static byte[] encodeBytes(byte inputByte, byte[] in) {
		byte b1 = in[1];
		byte b2 = inputByte;
		byte b3 = ivBytes[(int) b1 & 0xFF];
		b3 -= inputByte;
		in[0] += b3;
		b3 = in[2];
		b3 ^= ivBytes[(int) b2 & 0xFF];
		b1 -= (int) b3 & 0xFF;
		in[1] = b1;
		b1 = in[3];
		b3 = b1;
		b1 -= (int) in[0] & 0xFF;
		b3 = ivBytes[(int) b3 & 0xFF];
		b3 += inputByte;
		b3 ^= in[2];
		in[2] = b3;
		b1 += (int) ivBytes[(int) b2 & 0xFF] & 0xFF;
		in[3] = b1;
		int i1 = ((int) in[0]) & 0xFF;
		i1 |= (in[1] << 8) & 0xFF00;
		i1 |= (in[2] << 16) & 0xFF0000;
		i1 |= (in[3] << 24) & 0xFF000000;
		int ret_value = i1;
		ret_value = ret_value >>> 0x1d;
		i1 = i1 << 3;
		ret_value = ret_value | i1;
		in[0] = (byte) (ret_value & 0xFF);
		in[1] = (byte) ((ret_value >> 8) & 0xFF);
		in[2] = (byte) ((ret_value >> 16) & 0xFF);
		in[3] = (byte) ((ret_value >> 24) & 0xFF);
		return in;
	}

	/**
	 * Returns the IV of this instance as a string.
	 */
	@Override
	public String toString() {
		return "IV: " + HexTool.toString(this.iv);
	}
}

As you can see, I only use one instance of the certificate for all of my Ciphers since it is static. The final keyword also guarantees that this certificate does not change so it doesn't screw up packet encryption. Feel free to use this.
 
Last edited:
BitTools is actually in your tools folder.
I think you're looking for this?..
Code:
private static byte[] multiplyBytes(byte[] in, int count, int mul) {
        byte[] ret = new byte[count * mul];
        for (int x = 0; x < count * mul; x++) {
            ret[x] = in[x % count];
        }
        return ret;
    }
 
Back