-
Notifications
You must be signed in to change notification settings - Fork 15
ActionScript Byte Code Manipulation
To be able to modify/view the instructions contained within the SWF we will need to find a tag of type DoABCTag. This type contains a property of type byte[] named ABCData, and we will use this to initialize an instance of ABCFile.
string path = @"C:\Client.swf";
using (var flash = new ShockwaveFlash(path))
{
flash.Disassemble();
foreach (DoABCTag doABCTag in flash.Tags.Where(t => t.Kind == TagKind.DoABC))
{
using (var abc = new ABCFile(doABCTag.ABCData))
{
// We need to convert this ABCFile instance to a byte array, and assign it to the ABCData property if we wish to apply the changes.
// doABCTag.ABCData = abc.ToArray();
}
}
}
This object will allow us to inspect the methods/classes/constants/strings contained within the file, as well as allowing us to modify any of these values as we please.
Let's say for instance, that we want to force a method that returns a Boolean to always return true no matter what. We know for a fact that this method is not static, and that it is contained within a class named IM_A_SIMPLE_CLASS. Also, let's just say that this is the only method that exists within this class, so the first thing we need to do is grab an object that represents a non-static version of this class inside the SWF.
using (var abc = new ABCFile(doABCTag.ABCData))
{
ASInstance imASimpleClassInstance = abc.GetFirstInstance("IM_A_SIMPLE_CLASS");
}
Luckily for us, the method GetFirstInstance will attempt to look through a cache of ASClass/ASInstance types that have been linked to their given Qualified Name, and return the first match it finds(null if none). Although, if the SWF file happens to contain two or more types that are named the same(different namespaces/packages), then you could also enumerate through the matches with the GetInstances(string qualifiedName) method.
using (var abc = new ABCFile(doABCTag.ABCData))
{
foreach (ASInstance instance in abc.GetInstances("I_HAVE_DUPLICATES"))
{ }
}
At this point you've probably realized I keep saying class, but I'm not using the ASClass type anywhere in the code(even though, yes, it exists). The reason being is that a ASClass object is the static version of a class/container, and a ASInstance is the non-static version of a class/container. Meaning, when we attempt to grab the ASInstance object linked to the qualified name of a class, we will not be getting any static members from the class in this object.
Once we finally find an instance of this class, let's search for the method. Unfortunately, we do not know the name of this method, or we probably shouldn't rely on the name all the time. Instead, let's just search for this method by checking its' return type, since we know for a fact that it returns a Boolean type object.
ASInstance imASimpleClassInstance = abc.GetFirstInstance("IM_A_SIMPLE_CLASS");
foreach (ASMethod method in imASimpleClassInstance.GetMethods("Boolean"))
{ }
We do this by utilizing the GetMethods(string returnTypeName) method that will return all methods in this container that have this Qualified Name as their return type. Both ASClass and ASInstance inherit from ASContainer that contain these methods, so yes this can also be used on ASClass. There are a couple of other methods in this class that will aid you, but let's not get distracted from our goal.
The AVM2 instructions that are contained within the method itself are now accessible. We can initialize a ASCode instance by doing the following.
ASInstance imASimpleClassInstance = abc.GetFirstInstance("IM_A_SIMPLE_CLASS");
foreach (ASMethod method in imASimpleClassInstance.GetMethods("Boolean"))
{
// This is initialize from the byte code found in 'method.Body.Code'(byte[])
ASCode code = method.Body.ParseCode();
}
This will convert the byte code into something that is readable, and easily modifiable. This ASCode object inherits from IList<ASInstruction>, meaning the object itself is a list of instructions that can be enumerated. What we want to do now is force the method to always return true, so it seems we need to place a PushTrueIns object at the top, followed by a ReturnValueIns object(all instruction types must inherit from ASInstruction).
ASCode code = method.Body.ParseCode();
code.InsertRange(0, new ASInstruction[]
{
new PushTrueIns(),
new ReturnValueIns()
});
Since this type inherits from IList<ASInstruction> we can treat this object as an actual list of instructions we can remove/insert. I probably won't be able to tell you what each instruction type does, since there are so many of them. If you really want to look into what type of instruction type does what, I suggest you give this a look: https://wwwimages2.adobe.com/content/dam/acom/en/devnet/pdf/avm2overview.pdf
You are now almost ready to re-assemble the SWF file with the instruction changes you've applied in the ASCode object, but first we need to adjust some things in the method's Body property. Depending on what changes you did in the instruction list, you may need to adjust the MaxStack, and LocalCount properties found in the ASMethodBody object.
- MaxStack - Signifies the maximum amount of values that will be available on the stack at some point during the code execution.
- LocalCount - This will tell the VM how many locals are found in this method's body, and signature(parameters).
Adjusting these properties is very important, otherwise it'll "corrupt" the method and break when the VM reaches a point where this method is called. Once you have adjusted those properties properly, you're ready to re-assemble your file.
string path = @"C:\Client.swf";
using (var flash = new ShockwaveFlash(path))
{
flash.Disassemble();
foreach (DoABCTag doABCTag in flash.Tags.Where(t => t.Kind == TagKind.DoABC))
{
using (var abc = new ABCFile(doABCTag.ABCData))
{
ASInstance imASimpleClassInstance = abc.GetFirstInstance("IM_A_SIMPLE_CLASS");
foreach (ASMethod method in imASimpleClassInstance.GetMethods("Boolean"))
{
ASCode code = method.Body.ParseCode();
code.InsertRange(0, new ASInstruction[]
{
new PushTrueIns(),
new ReturnValueIns()
});
method.Body.MaxStack += 1; // In case MaxStack is at 0, and since we're pushing one value to the stack.
method.Body.Code = code.ToArray(); // Turn these instructions back into byte code, and assign them to the body of the method.
}
// We need to convert this ABCFile instance to a byte array, and assign it to the ABCData property if we wish to apply the changes.
doABCTag.ABCData = abc.ToArray();
}
}
string asmPath = @"C:\asm_Client.swf";
using (Stream fileStream = File.Open(asmPath, FileMode.Create))
using (var output = new FlashWriter(fileStream))
{
flash.Assemble(output);
// OR
// flash.Assemble(output, CompressionKind.None);
}
}
Congratulations, you will now understand what each line of this code does, hopefully I explained myself properly. I may have skipped some things, but this page was starting to get bigger than expected.