Bytecode Tutorial [scraped from KH]
Original Post by pkedpker » Thu Apr 30, 2009 5:17 pm
Bytecode Tutorial [scraped from KH] Posted on: 03/11/2012 4:54am
Quote Post
maximillian wrote:


You will need:
Flasm: http://flasm.sourceforge.net/
sothink: http://isohunt.com/download/69837813/sothink.torrent
notepad++: http://notepad-plus.sourceforge.net/uk/site.htm

Like I said follow ROUGHLY what I do, there are differences between games.



Wildcards.

Sorry for stealing your post PK but I wanted this on the front page.


ActionScript 1 OpCodes wrote:No Operation (NOP) = 0x02,
ActionCall = 0x9E,
ActionDefineLocal = 0x3C,
ActionDefineLocal2 = 0x41,
ActionSetMember = 0x4F,
ActionAdd = 0x0A,
ActionAdd2 = 0x47,
ActionAnd = 0x10,
ActionAsciiToChar = 0x33,
ActionBitAnd = 0x60,
ActionBitLShift = 0x63,
ActionBitOR = 0x61,
ActionBitRShift = 0x64,
ActionBitURShift = 0x65,
ActionBitXor = 0x62,
ActionCharToAscii = 0x32,
ActionCloneSprite = 0x24,
ActionConstantPool = 0x88,
ActionDecrement = 0x51,
ActionDelete = 0x3A,
ActionDelete2 = 0x3B,
ActionDivide = 0x0D,
ActionEndDrag = 0x28,
ActionEnumerate = 0x46,
ActionEnumerate2 = 0x55,
ActionEquals = 0x0E,
ActionEquals2 = 0x49,
ActionGetMember = 0x4E,
ActionGetProperty = 0x22,
ActionGetTime = 0x34,
ActionGetVariable = 0x1C,
ActionGotoFrame = 0x81,
ActionGotoFrame2 = 0x9F,
ActionGoToLabel = 0x8C,
ActionGreater = 0x67,
ActionIncrement = 0x50,
ActionInitArray = 0x42,
ActionInitObject = 0x43,
ActionInstanceOf = 0x54,
ActionLess = 0x0F,
ActionLess2 = 0x48,
ActionMBAsciiToChar = 0x37,
ActionMBCharToAscii = 0x36,
ActionMBStringExtract = 0x35,
ActionMBStringLength = 0x31,
ActionModulo = 0x3F,
ActionMultiply = 0x0C,
ActionNextFrame = 0x04,
ActionNone = 0x0,
ActionNot = 0x12,
ActionOr = 0x11,
ActionPlay = 0x06,
ActionPop = 0x17,
ActionPreviousFrame = 0x05,
ActionPush = 0x96,
ActionPushDuplicate = 0x4C,
ActionRandomNumber = 0x30,
ActionRemoveSprite = 0x25,
ActionReturn = 0x3E,
ActionSetProperty = 0x23,
ActionSetTarget = 0x8B,
ActionSetTarget2 = 0x20,
ActionSetVariable = 0x1D,
ActionStackSwap = 0x4D,
ActionStartDrag = 0x27,
ActionStop = 0x07,
ActionStopSounds = 0x09,
ActionStoreRegister = 0x87,
ActionStrictEquals = 0x66,
ActionStringAdd = 0x21,
ActionStringEquals = 0x13,
ActionStringExtract = 0x15,
ActionStringGreater = 0x68,
ActionStringLength = 0x14,
ActionStringLess = 0x29,
ActionSubtract = 0x0B,
ActionTargetPath = 0x45,
ActionToggleQuality = 0x08,
ActionToInteger = 0x18,
ActionToNumber = 0x4A,
ActionToString = 0x4B,
ActionTrace = 0x26,
ActionTypeOf = 0x44,
ActionWaitForFrame = 0x8A,
ActionWaitForFrame2 = x8D,
ActionWith = 0x94,
ActionCallFunction = 0x3D,
ActionCallMethod = 0x52,
ActionNewMethod = 0x53,
ActionNewObject = 0x40,
ActionDefineFunction = 0x9B,
ActionDefineFunction2 = x8E,
ActionIf = 0x9D,
ActionJump = 0x99,


ActionScript 3 opcodes wrote: final int OP_bkpt = 0x01;
final int OP_nop = 0x02;
final int OP_throw = 0x03;
final int OP_getsuper = 0x04;
final int OP_setsuper = 0x05;
final int OP_dxns = 0x06;
final int OP_dxnslate = 0x07;
final int OP_kill = 0x08;
final int OP_label = 0x09;
final int OP_ifnlt = 0x0C;
final int OP_ifnle = 0x0D;
final int OP_ifngt = 0x0E;
final int OP_ifnge = 0x0F;
final int OP_jump = 0x10;
final int OP_iftrue = 0x11;
final int OP_iffalse = 0x12;
final int OP_ifeq = 0x13;
final int OP_ifne = 0x14;
final int OP_iflt = 0x15;
final int OP_ifle = 0x16;
final int OP_ifgt = 0x17;
final int OP_ifge = 0x18;
final int OP_ifstricteq = 0x19;
final int OP_ifstrictne = 0x1A;
final int OP_lookupswitch = 0x1B;
final int OP_pushwith = 0x1C;
final int OP_popscope = 0x1D;
final int OP_nextname = 0x1E;
final int OP_hasnext = 0x1F;
final int OP_pushnull = 0x20;
final int OP_pushundefined = 0x21;
final int OP_pushintant = 0x22;
final int OP_nextvalue = 0x23;
final int OP_pushbyte = 0x24;
final int OP_pushshort = 0x25;
final int OP_pushtrue = 0x26;
final int OP_pushfalse = 0x27;
final int OP_pushnan = 0x28;
final int OP_pop = 0x29;
final int OP_dup = 0x2A;
final int OP_swap = 0x2B;
final int OP_pushstring = 0x2C;
final int OP_pushint = 0x2D;
final int OP_pushuint = 0x2E;
final int OP_pushdouble = 0x2F;
final int OP_pushscope = 0x30;
final int OP_pushnamespace = 0x31;
final int OP_hasnext2 = 0x32;
final int OP_newfunction = 0x40;
final int OP_call = 0x41;
final int OP_construct = 0x42;
final int OP_callmethod = 0x43;
final int OP_callstatic = 0x44;
final int OP_callsuper = 0x45;
final int OP_callproperty = 0x46;
final int OP_returnvoid = 0x47;
final int OP_returnvalue = 0x48;
final int OP_constructsuper = 0x49;
final int OP_constructprop = 0x4A;
final int OP_callsuperid = 0x4B;
final int OP_callproplex = 0x4C;
final int OP_callinterface = 0x4D;
final int OP_callsupervoid = 0x4E;
final int OP_callpropvoid = 0x4F;
final int OP_newobject = 0x55;
final int OP_newarray = 0x56;
final int OP_newactivation = 0x57;
final int OP_newclass = 0x58;
final int OP_getdescendants = 0x59;
final int OP_newcatch = 0x5A;
final int OP_findpropstrict = 0x5D;
final int OP_findproperty = 0x5E;
final int OP_finddef = 0x5F;
final int OP_getlex = 0x60;
final int OP_setproperty = 0x61;
final int OP_getlocal = 0x62;
final int OP_setlocal = 0x63;
final int OP_getglobalscope = 0x64;
final int OP_getscopeobject = 0x65;
final int OP_getproperty = 0x66;
final int OP_getpropertylate = 0x67;
final int OP_initproperty = 0x68;
final int OP_setpropertylate = 0x69;
final int OP_deleteproperty = 0x6A;
final int OP_deletepropertylate = 0x6B;
final int OP_getslot = 0x6C;
final int OP_setslot = 0x6D;
final int OP_getglobalslot = 0x6E;
final int OP_setglobalslot = 0x6F;
final int OP_convert_s = 0x70;
final int OP_esc_xelem = 0x71;
final int OP_esc_xattr = 0x72;
final int OP_convert_i = 0x73;
final int OP_convert_u = 0x74;
final int OP_convert_d = 0x75;
final int OP_convert_b = 0x76;
final int OP_convert_o = 0x77;
final int OP_coerce = 0x80;
final int OP_coerce_b = 0x81;
final int OP_coerce_a = 0x82;
final int OP_coerce_i = 0x83;
final int OP_coerce_d = 0x84;
final int OP_coerce_s = 0x85;
final int OP_astype = 0x86;
final int OP_astypelate = 0x87;
final int OP_coerce_u = 0x88;
final int OP_coerce_o = 0x89;
final int OP_negate = 0x90;
final int OP_increment = 0x91;
final int OP_inclocal = 0x92;
final int OP_decrement = 0x93;
final int OP_declocal = 0x94;
final int OP_typeof = 0x95;
final int OP_not = 0x96;
final int OP_bitnot = 0x97;
final int OP_concat = 0x9A;
final int OP_add_d = 0x9B;
final int OP_add = 0xA0;
final int OP_subtract = 0xA1;
final int OP_multiply = 0xA2;
final int OP_divide = 0xA3;
final int OP_modulo = 0xA4;
final int OP_lshift = 0xA5;
final int OP_rshift = 0xA6;
final int OP_urshift = 0xA7;
final int OP_bitand = 0xA8;
final int OP_bitor = 0xA9;
final int OP_bitxor = 0xAA;
final int OP_equals = 0xAB;
final int OP_strictequals = 0xAC;
final int OP_lessthan = 0xAD;
final int OP_lessequals = 0xAE;
final int OP_greaterthan = 0xAF;
final int OP_greaterequals = 0xB0;
final int OP_instanceof = 0xB1;
final int OP_istype = 0xB2;
final int OP_istypelate = 0xB3;
final int OP_in = 0xB4;
final int OP_increment_i = 0xC0;
final int OP_decrement_i = 0xC1;
final int OP_inclocal_i = 0xC2;
final int OP_declocal_i = 0xC3;
final int OP_negate_i = 0xC4;
final int OP_add_i = 0xC5;
final int OP_subtract_i = 0xC6;
final int OP_multiply_i = 0xC7;
final int OP_getlocal0 = 0xD0;
final int OP_getlocal1 = 0xD1;
final int OP_getlocal2 = 0xD2;
final int OP_getlocal3 = 0xD3;
final int OP_setlocal0 = 0xD4;
final int OP_setlocal1 = 0xD5;
final int OP_setlocal2 = 0xD6;
final int OP_setlocal3 = 0xD7;
final int OP_debug = 0xEF;
final int OP_debugline = 0xF0;
final int OP_debugfile = 0xF1;
final int OP_bkptline = 0xF2;


I started to bytecode hack with CE myself I picked it pretty quickly it's very fun to create these kind of hacks its like editing assembly in native programs to create hacks like I used to in MMORPGS but one problem with editing bytecode in SWF.. my question is how do I NOP.. data in order to keep the size limit the same.


Okay so here is some original data for a example game I want to hack

Code: Select all ? ? //96 10 00 00 73 63 6f 72 65 00 06 00 00 00 00 00 00 00 00
? ? _push "score" 0
? ? //1d
? ? _setVariable
? ? //96 0b 00 00 61 6d 6d 6f 00 07 05 00 00 00
? ? _push "ammo" 5
? ? //1d
? ? _setVariable


which means

Code: Select all score = 0;
ammo = 5;

of course this is the initializer for start of game.. so if I initialize a higher ammo or score.. I'll hack the game

now the values are 4 bytes aka DWORD but it's saved in reverse order small endian or idk..

but i know in C++ programming its easy to replicate using Lo/Hi's


So changing ammo is easy I just modify these
Code: Select all 05 00 00 00

now what about score? when I try to edit score.. it changes assembly completely.

Code: Select all ? ? //96 0c 00 00 73 63 6f 72 65 00 07 [a3 07 64 00]
? ? _push "score" 6555555
? ? //1d
? ? _setVariable
? ? //96 0b 00 00 61 6d 6d 6f 00 07 05 00 00 00
? ? _push "ammo" 5
? ? //1d
? ? _setVariable

I just changed it to

Code: Select all score = 6555555;
ammo = 5;

of course I still managed to change the score.. but I changed the algorithm that generates the score like changing addition to multiplication making my score increase much more rapidly(easy bytecode hacks).

But I want to modify the score to anything I want but you see the assembly is different.. so what must I do?

so i'm changing
This ->
96 10 00 00 73 63 6f 72 65 00 06 00 00 00 00 00 00 00 00
To This ->
96 0c 00 00 73 63 6f 72 65 00 07 a3 07 64 00 1d

I have to nop 5 bytes in order for flash not to mess up the data by considering the extra 00 00 00 00 as another opcode.. and screwing up the flow.

Adobe's SWF Specifications
http://www.adobe.com/devnet/swf/
You can download the newest spec PDF which lists all the AVM0 bytecode information.

SWF File Reference
http://www.m2osw.com/swf_alexref.html
This can sometimes be easier to read than the above PDF for SWF specific information.

AVM2 ByteCode Insturctions (for AS3 games)
http://www.anotherbigidea.com/javaswf/avm2/AVM2Instructions.html
A good quick reference for AVM2. I wish something like this existed for AVM0. AVM2 is much easier to read, but the use of the constant pool limits what you can do. AVM0 is much more flexible when hacking.

AVM2 Constant Pool Viewer
http://bluecork.net/cheats/scripts/cpool.py
This is a python script I wrote to view constant pool data in AVM2 SWF files. This can make it a lot easier when trying to manipulate bytecode in AS3 games. You can run this in Windows by downloading Cygwin, and installing Python. I messed up on first attempt at programing a zlib decompressor in python for SWF files, so you need to decompress the SWF files with something like SWFTools first until I have time to update it.

AMV2 u30 Converter
Web: http://bluecork.net/cheats/short.php
Script: http://bluecork.net/cheats/scripts/short.py
This is a script I wrote to convert u30 hex to an integer, and vice-versa. This is needed for the AMV2 pushshort (0x25) command.

The only time I use wildcards is when the bytecode could potentially change, or you need to find multiple locations that are slighty different. This is usually because of references to the constant pool, but can also be because of if statement offsets, and different values for the same variable. In AVM0 the cpool is just strings, while in AVM2 (AS3) the cpool has been expanded to use strings, ints, uints, doubles, and other stuff not that important for hacking.

Here are some real world examples of what I am talking about.

AVM0 Cpool example for the game Bunny Invasion II. You start out with 1000 money.

Real code:
Code: Select all money = 1000;

Peusdo code and Bytecode: (from Sothink)
Code: Select all //96 07 00 08 07 07 e8 03 00 00
_push "money" 1000
//1d
_setVariable

We can break the push down into its segments.

96 = push
07 00 = Size (7 bytes)
08 = Constant Pool 8 (the next byte is the location in the cpool)
07 = Cpool location
07 = 4-byte Integer (the next four bytes are the value)
e8 03 00 00 = 1000

If we look at the first seven objects in the cpool we see this.

Code: Select all //88 = Set Constant Pool
//c4 05 9b = Length/Objects
//00 77 61 74 63 68 69 6e 67 76 69 64 = watchingvid
//00 63 6f 6d 70 6c 65 74 65 64 67 61 6d 65 = completedgame
//00 76 6f 6c 75 6d 65 73 65 74 = volumeset
//00 74 69 6d 65 72 = timer
//00 61 64 64 65 64 74 69 6d 65 = addedtime
//00 73 74 61 72 74 54 69 6d 65 = startTime
//00 77 65 61 70 6e 75 6d 62 65 72 = weapnumber
//00 6d 6f 6e 65 79 = money
_constantPool "watchingvid" "completedgame" "volumeset" "timer" "addedtime" "startTime" "weapnumber" "money"

Notice that money is located in the seventh spot, which coorelates to the '08 07' in the push statement. The reason why this is important is because if you wanted to replace money=1000 with money=100000 you would normally do this.

96 07 00 08 07 07 e8 03 00 00 -> 96 07 00 08 07 07 a0 86 01 00

However if the game was updated, and another variable was added above money=1000, then suddenly money is located in the eight spot in the constant pool instead of seventh. So you would have to change what you're looking for to '96 07 00 08 08 07 e8 03 00 00'. Since we already know that the '08 07' is a reference to the constant pool we can just make the 07 a wildcard, and search for '96 07 00 08 ?? 07 e8 03 00 00'. That way if the game ever gets updated this search will continue to work.

The only time we would have to significantly change our search is if the author added enough variables to break the 255 size limit. Then the push would change from using the 08 Cpool 8 command to the 09 Cpool 16 command, which uses two bytes to reference the cpool location. In that case our new search would look like this '96 08 00 09 ?? ?? 07 e8 03 00 00'.

This is much more important in AVM2 where every other byte can be a reference to the cpool. For example lets look at Hero's Arms. Let's change the code so that when we pick up a 3 gold coin it is worth much more.

Real code:
Code: Select all if (param1 is Coin3)
{
? ? ? metaHero.AddGold(3);
? ? ? Jukebox.instance.PlaySound(HA_SfxFactory.instance.ID_SFX_CASH);
}

Peusdo code and Bytecode: (this is only for the line 'metaHero.AddGold(3);')
Code: Select all //60 81 02
_as3_getlex metaHero
//24 03
_as3_pushbyte 3
//4f c9 05 01
_as3_callpropvoid AddGold(param count:1)

So lets break this all down.

60 = Get a namespace from the cpool
81 02 = location of object in cpool
24 = Push byte onto stack
03 = 3
4f = Call a function with given namespace referenced in the cpool
c9 05 = location of object in cpool
01 = Number of parameters on the stack to send to this function

This all makes little sense unless we can read the cpool. Lucky for us Sothink will do that for us, which is why we can see the names of the objects in the peusdo code. Since we know which bytes are cpool locations we can replace those bytes with wildcards to come up with a search of '60 ?? ?? 24 03 4f ?? ?? 01'.

Here is where we now run into a limitation with AVM2 hacking. The command '24' only pushes a single singed byte, which has a limited value of 127 (which would be 7F). So we can change the '24 03' to '24 7F', and any coin we pick up with the value of 3 will be worth 127. However the good stuff all costs 5000 gold, and even getting 127 for each coin would still take us too much grinding to get to 5000. So we now have to dip into the cpool to reference a value larger than 127. Instead of using the '24' command we should instead use the '2d' command which pushes a 4 byte int from the cpool. However we have to find out what all the values are in the cpool to know which one to use. Using the handy python script I pasted in an early post we can decode the cpool and see the values for all the ints. I will only paste in the first 16 to save space.

Code: Select all int index count = 804
001 -1? ? ? ? ? ? 002 0? ? ? ? ? ? 003 2? ? ? ? ? ? 004 16? ? ? ? ?
005 10? ? ? ? ? ? 006 1? ? ? ? ? ? 007 100? ? ? ? ? 008 1800? ? ? ?
009 4? ? ? ? ? ? 00a 1700? ? ? ? ? 00b 1210? ? ? ? ? 00c 20? ? ? ? ?
00d 460? ? ? ? ? 00e 660? ? ? ? ? 00f 860? ? ? ? ? 010 1150

Okay, so we want to make a coin worth 1800 instead of 3. To do this we would change the '24 03' to '2d 08'. In this case the 08 following the 2d is the location of the int in the cpool, so '2d 0b' would make the coins worth 1210.

We can expand our search with another wildcard. Instead of searching for '24 03' we can search for '24 ??' to include all instances of where coins are being added, and not just when a value of 3 is added. This will let us find the bytecode for adding coin values of 10 and 20 as well. The downside of this is that we are now finding 65 results when we only want 3. The reason for this is because we are using so many wildcards that we are finding cases of code unrelated to coin adding. To get around this you can either stop using some wildcards, or expand your searching. The bytecode we are searching for is only a single line of code. If we expand it include the next line that plays a sound we can further refine our search.

Code: Select all Jukebox.instance.PlaySound(HA_SfxFactory.instance.ID_SFX_CASH);

//60 a5 01
_as3_getlex com.lachhhEngine.games.sfx::Jukebox
//66 c6 2b
_as3_getproperty instance
//60 1b
_as3_getlex com.herosarm.factories::HA_SfxFactory
//66 c6 2b
_as3_getproperty instance
//66 97 03
_as3_getproperty ID_SFX_CASH

The commands 60 and 66 are just reference more cpool objects, and can be wildcarded as well. If we add '60 ?? ??' to the end of our search we get 37 results. Adding '66 ?? ??' gets us down to 8. Adding any more doesn't refine our search any so we can now try adding the code that comes before.

Code: Select all if (param1 is Coin3)

//60 aa 15
_as3_getlex com.herosarm.items.dropitems::Coin3
//b3
_as3_istypelate
//12 1f 00 00
_as3_iffalse offset: 31

This will probably be more sucessful at refining our search because there are less cpool references. This translates out to '60 ?? ?? b3 12 ?? ?? 00'. Adding that before our search reduces our results to three. The full search is now '60 ?? ?? b3 12 ?? ?? 00 60 ?? ?? 24 ?? 4f ?? ?? 01 60 ?? ??'. You can add all the results, and change the '24 ??' to '2d 08'. Now all 3, 10, and 20 valued coins will be worth 1800. It would be easier to just not use wildcards, but using them allows you to keep using the same search if the game gets updated.

Instead of using wildcards to make crazy searches you can also use them for just minor tweaks. For example these are the bytecodes for adding 3, 10, and 20 coins.

60 AA 15 B3 12 1F 00 00 60 81 02 24 03 4F C9 05 01 60 A5 01 = 3
60 A7 15 B3 12 1F 00 00 60 81 02 24 0A 4F C9 05 01 60 A5 01 = 10
60 A3 15 B3 12 1F 00 00 60 81 02 24 14 4F C9 05 01 60 A5 01 = 20

You'll notice looking at all of these that there are only a couple bytes different. Just replace those instances where the byte is different with a wildcard to find all three at once.

Another problem you may have noticed. Lets say that we don't want our coins to be worth 1800. We want our coins to be worth 5000, but there are no ints with a value of 5000. This is another limitation of AVM2 which is very difficult to get around. One thing you can do is change the value of one of the int objects in the cpool to 5000, and reference it. The problem with doing that is that whatever variable was referencing the value you changed to 5000 will now also be worth 5000, and that can have drastic negitive effects on the game. Every int is also unique, and can be referenced by multiple variables. For example a game has two variables: 'distance=2000' and 'money=2000'. You change the value of the 2000 int to 5000 to start off with more money. The unintended consequence is that distance will now also be set to 5000, and who knows what that will do to the game. Its best just to use an existing cpool object and deal with the fact that it may not be exactly what you want.

If you can subtract bytes from somewhere else in the game you can also use pushshort (0x25) instead of pushbyte (0x24) and pushint (0x2D). Pushshort lets you push an unsigned 30 bit value instead of a signed 8 bit value. So where pushbyte was limited to 24 7F for a max value of 127, pushshort can do 25 FF FF FF FF 03 for a max value of 1073741823.

The problem with pushshort is that its using Flash's special u30 variable length 30 bit integer. This makes it difficult to properly calculate what you need to use to get the right value without a good understanding of bit calculation.



Posted on: 03/11/2012 4:55am
Quote Post
Vids posted with the original:
http://www.youtube.com/watch?v=jrhKRcC1Mb0&feature=player_embedded

http://www.youtube.com/watch?v=r9HpcnkqF1E&feature=player_embedded