Package tv.porst.swfretools.utils.as3

Source Code of tv.porst.swfretools.utils.as3.ActionScript3Resolver

package tv.porst.swfretools.utils.as3;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import tv.porst.swfretools.parser.structures.AS3Code;
import tv.porst.swfretools.parser.structures.AS3Data;
import tv.porst.swfretools.parser.structures.ClassInfo;
import tv.porst.swfretools.parser.structures.ConstantPool;
import tv.porst.swfretools.parser.structures.InstanceInfo;
import tv.porst.swfretools.parser.structures.MethodBody;
import tv.porst.swfretools.parser.structures.MethodBodyList;
import tv.porst.swfretools.parser.structures.MethodInfo;
import tv.porst.swfretools.parser.structures.MethodInfoList;
import tv.porst.swfretools.parser.structures.MultinameInfo;
import tv.porst.swfretools.parser.structures.MultinameInfoList;
import tv.porst.swfretools.parser.structures.NamespaceInfoList;
import tv.porst.swfretools.parser.structures.StringInfoList;
import tv.porst.swfretools.parser.structures.TraitMethod;
import tv.porst.swfretools.parser.structures.TraitsInfo;
import tv.porst.swfretools.parser.structures.TraitsInfoList;
import tv.porst.swfretools.utils.ActionScript3Helpers;
import tv.porst.swfretools.utils.ResolverException;

/**
* This class can take ActionScript 3 information and resolve all the classes
* and methods that belong to all of the classes. This is very important as the
* information that is stored in ActionScript 3 data in an SWF file is very
* fragmented and must be reconstructed before it can be used in any useful
* way, for example to display the ActionScript 3 code in the GUI.
*
* This class takes all the fragmented data and turns it into something saner
* that can easily be processed. While processing the data, this class is
* extremely conservative with errors. Even when, for example, the name of a
* method can not be resolved, the code will still continue and try to
* resolve other aspects of the method such as its return type or its
* arguments. This is important as we can expect this resolver class to try
* make sense of fuzzed ActionScript 3 code in the future.
*/
public final class ActionScript3Resolver {

  /**
   * This function creates a mapping between method headers and method bodies.
   * This is important because whenever a method is referenced, it is
   * referenced as an ID into the table of method headers. There is no quick
   * way to map from method header to method body though, for example to
   * find the code associated with a method. Therefore we just pre-calculate
   * this mapping and pass it to all functions that require it.
   *
   * @param data The ActionScript 3 data that provides method header and
   * method body information.
   * @param errors Errors that occur during resolving are logged here.
   *
   * @return A mapping from method info objects to objects that give
   * information about the method body.
   */
  private static Map<MethodInfo, ResolvedMethod> createMethodInformationMapping(final AS3Data data, final List<String> errors) {

    assert data != null : "Data argument must not be null";
    assert errors != null : "Errors argument must not be null";

    final Map<MethodInfo, ResolvedMethod> methodMapping = new HashMap<MethodInfo, ResolvedMethod>();

    final MethodInfoList methodInfos = data.getMethodInfos();
    final MethodBodyList methodBodies = data.getMethodBodies();

    final ConstantPool constantPool = data.getConstantPool();
    final StringInfoList constantStrings = constantPool.getStrings();
    final MultinameInfoList multiNames = constantPool.getMultinames();
    final NamespaceInfoList namespaces = constantPool.getNamespaces();

    // In the first round we are using the existing mapping between method bodies
    // and method information fields to create the inverse mapping.
    for (final MethodBody methodBody : methodBodies) {

      final int methodIndex = methodBody.getMethod().value();

      if (methodIndex >= methodInfos.size()) {
        errors.add(String.format("Can not find method header for method %d because method header ID is out of bounds", methodIndex));
      }
      else {
        final MethodInfo methodInfo = data.getMethodInfos().get(methodIndex);

        final AS3Code code = methodBody.getCode();

        final ResolvedMethod resolvedMethod = resolveMethod(methodInfo, code, constantStrings, multiNames, namespaces, errors);
        methodMapping.put(methodInfo, resolvedMethod);
      }
    }

    // In the second round we are processing all those method information
    // fields that do not have an associated method body. Those are the
    // methods that do not contain any ActionScript 3 code.
    for (final MethodInfo methodInfo : data.getMethodInfos()) {

      if (methodMapping.containsKey(methodInfo)) {
        continue;
      }

      final ResolvedMethod resolvedMethod = resolveMethod(methodInfo, null, constantStrings, multiNames, namespaces, errors);
      methodMapping.put(methodInfo, resolvedMethod);
    }

    return methodMapping;
  }

  /**
   * Reconstructs the given class and its methods.
   *
   * @param data The fragmented ActionScript 3 data.
   * @param classInfo The class information of the class.
   * @param instanceInfo The instance information of the class.
   * @param methodMapping The previously generated mapping between method
   * headers and their associated method bodies.
   * @param errors Errors that occur during resolving are logged here.
   *
   * @return A list of all classes defined in the ActionScript 3 code.
   */
  private static ResolvedClass resolveClass(final AS3Data data, final ClassInfo classInfo, final InstanceInfo instanceInfo, final Map<MethodInfo, ResolvedMethod> methodMapping, final List<String> errors) {

    assert data != null : "Data argument must not be null";
    assert classInfo != null : "Class Info argument must not be null";
    assert instanceInfo != null : "Instance Info argument must not be null";
    assert methodMapping != null : "Method mapping argument must not be null";
    assert errors != null : "Errors list must not be null";

    final ConstantPool constantPool = data.getConstantPool();
    final StringInfoList constantStrings = constantPool.getStrings();
    final MultinameInfoList multiNames = constantPool.getMultinames();
    final NamespaceInfoList namespaces = constantPool.getNamespaces();
    final MethodInfoList methodList = data.getMethodInfos();

    final ResolvedMethod staticConstructor = resolveStaticConstructor(classInfo, data, methodMapping, errors);
    final ResolvedMethod constructor = resolveConstructor(instanceInfo, data, methodMapping, errors);

    final String[] className = resolveClassName(instanceInfo, constantStrings, multiNames, namespaces, errors);
    final List<ResolvedMethod> methods = resolveMethods(instanceInfo.getTraits(), methodList, methodMapping, constantStrings, multiNames, namespaces, errors);

    return new ResolvedClass(className, staticConstructor, constructor, methods);
  }

  /**
   * Takes the fragmented information from the ActionScript 3 data and
   * reconstructs the defined classes and their methods.
   *
   * @param data The fragmented ActionScript 3 data.
   * @param methodMapping The previously generated mapping between method
   * headers and their associated method bodies.
   * @param errors Errors that occur during resolving are logged here.
   *
   * @return A list of all classes defined in the ActionScript 3 code.
   */
  private static List<ResolvedClass> resolveClasses(final AS3Data data, final Map<MethodInfo, ResolvedMethod> methodMapping, final List<String> errors) {

    assert data != null : "Data argument must not be null";
    assert methodMapping != null : "Method mapping argument must not be null";
    assert errors != null : "Errors list must not be null";

    final List<ResolvedClass> classes = new ArrayList<ResolvedClass>();

    for (int i=0;i<data.getClassCount().value();i++) {

      if (i >= data.getClasses().size()) {
        errors.add(String.format("Class at offset %08X could not be resolved as its class index is out of bounds"));
        continue;
      }

      if (i >= data.getInstances().size()) {
        errors.add(String.format("Class at offset %08X could not be resolved as its instance index is out of bounds"));
        continue;
      }

      final ClassInfo classInfo = data.getClasses().get(i);
      final InstanceInfo instanceInfo = data.getInstances().get(i);

      classes.add(resolveClass(data, classInfo, instanceInfo, methodMapping, errors));
    }

    return classes;
  }

  /**
   * Resolves the name of a class.
   *
   * @param instanceInfo The instance information of the class whose name is returned.
   * @param constantStringList List of constant strings from the ActionScript 3 data.
   * @param multinameList List of multinames from the ActionScript 3 data.
   * @param namespaceList List of namespaces from the ActionScript 3 data.
   * @param errors Errors that occur during resolving are logged here.
   *
   * @return The fully qualified name of the class.
   */
  private static String[] resolveClassName(final InstanceInfo instanceInfo, final StringInfoList constantStringList, final MultinameInfoList multinameList, final NamespaceInfoList namespaceList, final List<String> errors) {

    assert instanceInfo != null : "Instance information argument must not be null";
    assert constantStringList != null : "Constant string list argument must not be null";
    assert multinameList != null : "Multiname list argument must not be null";
    assert namespaceList != null : "Namespace list argument must not be null";
    assert errors != null : "Errors list must not be null";

    final int classNameIndex = instanceInfo.getName().value();

    if (classNameIndex == 0) {
      // Not totally sure what to do here.
      return new String[] { "???"};
    }
    else if (classNameIndex - 1 >= multinameList.size()) {
      errors.add(String.format("The class name of the method at address %08X could not be resolved as the name index is out of bounds", instanceInfo.getBitPosition() / 8));
      return null;
    }
    else {
      final MultinameInfo multinameInfo = multinameList.get(classNameIndex - 1);

      try {
        return ActionScript3Helpers.resolveMultiname(multinameInfo, constantStringList, namespaceList);
      } catch (final ResolverException e) {
        errors.add(String.format("The class name of the method at address %08X could not be resolved as the name index is out of bounds", instanceInfo.getBitPosition() / 8));
        return null;
      }
    }
  }

  /**
   * Resolves the constructor of a class.
   *
   * @param instanceInfo The class whose constructor is resolved.
   * @param data The fragmented ActionScript 3 data.
   * @param methodMapping The previously generated mapping between method
   * headers and their bodies.
   * @param errors Errors that occur during resolving are logged here.
   *
   * @return The resolved constructor for the given class or null if
   * the constructor could not be resolved.
   */
  private static ResolvedMethod resolveConstructor(final InstanceInfo instanceInfo, final AS3Data data, final Map<MethodInfo, ResolvedMethod> methodMapping, final List<String> errors) {

    assert instanceInfo != null : "Instance information argument must not be null";
    assert data != null : "Data argument must not be null";
    assert methodMapping != null : "Method mapping argument must not be null";
    assert errors != null : "Errors list must not be null";

    final int constructorId = instanceInfo.getIinit().value();

    final MethodInfoList methodInfos = data.getMethodInfos();

    if (constructorId >= methodInfos.size()) {
      errors.add(String.format("The constructor of the class at offset %08X could not be resolved as its method index is out of bounds", instanceInfo.getBitPosition() / 8));
      return null;
    }
    else {
      final ResolvedMethod resolvedConstructor = methodMapping.get(methodInfos.get(constructorId));
      return new ResolvedMethod(new String[0], new String[] { "constructor" }, resolvedConstructor.getArguments(), resolvedConstructor == null ? null : resolvedConstructor.getCode());
    }
  }

  /**
   * This function takes all the information that is required to create
   * resolved method object that contains information about a method
   * such as its name, return type, argument types, and method body.
   *
   * @param methodInfo Header information of the method to resolve.
   * @param code ActionScript code that forms the method body.
   * This value can be null for methods without associated code.
   * @param constantStringList List of constant strings from the ActionScript 3 data.
   * @param multinameList List of multinames from the ActionScript 3 data.
   * @param namespaceList List of namespaces from the ActionScript 3 data.
   * @param errors Errors that occur during resolving are logged here.
   *
   * @return The resolved method.
   */
  private static ResolvedMethod resolveMethod(final MethodInfo methodInfo, final AS3Code code, final StringInfoList constantStringList, final MultinameInfoList multinameList, final NamespaceInfoList namespaceList, final List<String> errors) {

    assert methodInfo != null : "Method information argument must not be null";
    assert constantStringList != null : "Constant string list argument must not be null";
    assert multinameList != null : "Multiname list argument must not be null";
    assert namespaceList != null : "Namespace list argument must not be null";
    assert errors != null : "Errors list must not be null";

    final String name = resolveMethodName(methodInfo, constantStringList, errors);
    final String[] returnType = resolveMethodReturnType(methodInfo, constantStringList, multinameList, namespaceList, errors);
    final List<String[]> arguments = resolveMethodArguments(methodInfo, constantStringList, multinameList, namespaceList, errors);

    return new ResolvedMethod(new String[] { name } , returnType, arguments, code);
  }

  /**
   * Resolves a single class method.
   *
   * @param traitsInfo The trait that defines the method.
   * @param methodList The methods list parsed from the SWF file.
   * @param methodMapping The previously generated mapping between method
   * headers and their associated method bodies.
   * @param constantStringList List of constant strings from the ActionScript 3 data.
   * @param multinameList List of multinames from the ActionScript 3 data.
   * @param namespaceList List of namespaces from the ActionScript 3 data.
   * @param errors Errors that occur during resolving are logged here.
   *
   * @return The resolved method or null if the method could not be resolved.
   */
  private static ResolvedMethod resolveMethod(final TraitsInfo traitsInfo,
      final MethodInfoList methodList,
      final Map<MethodInfo, ResolvedMethod> methodMapping,
      final StringInfoList constantStringList,
      final MultinameInfoList multinameList,
      final NamespaceInfoList namespaceList,
      final List<String> errors) {

    assert traitsInfo != null : "Traits information argument must not be null";
    assert methodList != null : "Method information argument must not be null";
    assert methodMapping != null : "Method mapping argument must not be null";
    assert constantStringList != null : "Constant string list argument must not be null";
    assert multinameList != null : "Multiname list argument must not be null";
    assert namespaceList != null : "Namespace list argument must not be null";
    assert errors != null : "Errors list must not be null";

    final TraitMethod traitMethod = (TraitMethod) traitsInfo.getData();

    final int methodIndex = traitMethod.getMethod().value();

    if (methodIndex >= methodList.size()) {
      errors.add(String.format("The method at offset %08X could not be resolved as its method index is out of bounds", traitMethod.getBitPosition() / 8));
      return null;
    }

    final String[] methodName = resolveMethodName(traitsInfo, constantStringList, multinameList, namespaceList, errors);

    final MethodInfo method = methodList.get(methodIndex);
    final ResolvedMethod resolvedMethod = methodMapping.get(method);
    final AS3Code code = resolvedMethod.getCode();

    return new ResolvedMethod(resolvedMethod.getReturnType(), methodName, resolvedMethod.getArguments(), code);
  }

  /**
   * Resolves all the argument value types of a method.
   *
   * @param methodInfo Header information of the method to resolve.
   * This value can be null for methods without associated code.
   * @param constantStringList List of constant strings from the ActionScript 3 data.
   * @param multinameList List of multinames from the ActionScript 3 data.
   * @param namespaceList List of namespaces from the ActionScript 3 data.
   * @param errors Errors that occur during resolving are logged here.
   *
   * @return A list of the fully qualified types of the method arguments.
   */
  private static List<String[]> resolveMethodArguments(final MethodInfo methodInfo, final StringInfoList constantStringList, final MultinameInfoList multinameList, final NamespaceInfoList namespaceList, final List<String> errors) {

    assert methodInfo != null : "Method information argument must not be null";
    assert constantStringList != null : "Constant string list argument must not be null";
    assert multinameList != null : "Multiname list argument must not be null";
    assert namespaceList != null : "Namespace list argument must not be null";
    assert errors != null : "Errors list must not be null";

    final List<String[]> arguments = new ArrayList<String[]>();

    for (int i=0;i<methodInfo.getParamTypes().size();i++) {

      final int paramTypeIndex = methodInfo.getParamTypes().get(i).value();

      if (paramTypeIndex == 0) {
        // Type index 0 means the argument can be of any type.
        arguments.add(new String[] { "*" });
      }
      else if (paramTypeIndex - 1 >= multinameList.size()) {
        errors.add(String.format("Could not resolve argument of method at offset %08X because name index is out of bounds", methodInfo.getBitPosition() / 8));
        arguments.add(null);
      }
      else {
        final MultinameInfo multinameInfo = multinameList.get(paramTypeIndex - 1);

        try {
          final String[] name = ActionScript3Helpers.resolveMultiname(multinameInfo, constantStringList, namespaceList);
          arguments.add(name);
        } catch (final ResolverException e) {
          errors.add(String.format("Could not resolve argument of method at offset %08X because name index is out of bounds", methodInfo.getBitPosition() / 8));
        }
      }
    }

    return arguments;
  }

  /**
   * Tries to resolve the name of a method.
   *
   * @param methodInfo Method header information of the method to process.
   * @param constantStringList List of constant strings from the ActionScript 3 data.
   * @param errors Errors that occur during resolving are logged here.
   *
   * @return The name of the method or null if it could not be resolved.
   */
  private static String resolveMethodName(final MethodInfo methodInfo, final StringInfoList constantStringList, final List<String> errors) {

    assert methodInfo != null : "Method information argument must not be null";
    assert constantStringList != null : "Constant string list argument must not be null";

    final int nameIndex = methodInfo.getName().value();

    if (nameIndex >= constantStringList.size()) {
      errors.add(String.format("Could not resolve name of method at offset %08X because name index is out of bounds", methodInfo.getBitPosition() / 8));
      return null;
    }
    else {
      return constantStringList.get(nameIndex).getName().value();
    }
  }

  /**
   * Resolves the name of a method.
   *
   * @param traitsInfo Identifies the method.
   * @param constantStringList List of constant strings from the ActionScript 3 data.
   * @param multinameList List of multinames from the ActionScript 3 data.
   * @param namespaceList List of namespaces from the ActionScript 3 data.
   * @param errors Errors that occur during resolving are logged here.
   *
   * @return The fully qualified method name.
   */
  private static String[] resolveMethodName(final TraitsInfo traitsInfo, final StringInfoList constantStringList, final MultinameInfoList multinameList, final NamespaceInfoList namespaceList, final List<String> errors) {

    assert traitsInfo != null : "Traits information argument must not be null";
    assert constantStringList != null : "Constant string list argument must not be null";
    assert multinameList != null : "Multiname list argument must not be null";
    assert namespaceList != null : "Namespace list argument must not be null";
    assert errors != null : "Errors list must not be null";

    final int nameIndex = traitsInfo.getName().value();

    if (nameIndex == 0) {
      // Not sure what to do here.
      return new String[] { "???" };
    }
    else if (nameIndex -1 >= multinameList.size()) {
      errors.add(String.format("Name of method at offset %08X could not be resolved as name index is out of bounds", traitsInfo.getBitPosition() / 8));
      return null;
    }
    else {
      final MultinameInfo multiNameInfo = multinameList.get(traitsInfo.getName().value() - 1);

      try {
        return ActionScript3Helpers.resolveMultiname(multiNameInfo, constantStringList, namespaceList);
      } catch (final ResolverException e) {
        errors.add(String.format("Name of method at offset %08X could not be resolved as name index is out of bounds", traitsInfo.getBitPosition() / 8));
        return null;
      }
    }
  }

  /**
   * Resolves the return type of a method.
   *
   * @param methodInfo Method information of the methods whose return type is resolved.
   * @param constantStringList List of constant strings from the ActionScript 3 data.
   * @param multinameList List of multinames from the ActionScript 3 data.
   * @param namespaceList List of namespaces from the ActionScript 3 data.
   * @param errors Errors that occur during resolving are logged here.
   *
   * @return An array containing all the individual parts of the fully qualified
   * name of the method return type.
   */
  private static String[] resolveMethodReturnType(final MethodInfo methodInfo, final StringInfoList constantStringList, final MultinameInfoList multinameList, final NamespaceInfoList namespaceList, final List<String> errors) {

    assert methodInfo != null : "Method information argument must not be null";
    assert constantStringList != null : "Constant string list argument must not be null";
    assert multinameList != null : "Multiname list argument must not be null";
    assert namespaceList != null : "Namespace list argument must not be null";
    assert errors != null : "Errors list must not be null";

    final int returnTypeIndex = methodInfo.getReturnType().value();

    if (returnTypeIndex == 0) {
      // Return type index 0 means the return value can be of any type.
      return new String[] { "*" };
    }
    else if (returnTypeIndex - 1 >= multinameList.size()) {
      errors.add(String.format("Return type of method at offset %08X could not be resolved because its return type index is out of bounds", methodInfo.getBitPosition() / 8));
      return null;
    }
    else {
      final MultinameInfo multinameInfo = multinameList.get(returnTypeIndex - 1);

      try {
        return ActionScript3Helpers.resolveMultiname(multinameInfo, constantStringList, namespaceList);
      } catch (final ResolverException e) {
        errors.add(String.format("Return type of method at offset %08X could not be resolved because its return type index is out of bounds", methodInfo.getBitPosition() / 8));
        return null;
      }
    }
  }

  /**
   * Resolves the methods of a class.
   *
   * @param traits The traits of the class. These define the methods.
   * @param methodList The methods list parsed from the SWF file.
   * @param methodMapping The previously generated mapping between method
   * headers and their associated method bodies.
   * @param constantStringList List of constant strings from the ActionScript 3 data.
   * @param multinameList List of multinames from the ActionScript 3 data.
   * @param namespaceList List of namespaces from the ActionScript 3 data.
   * @param errors Errors that occur during resolving are logged here.
   *
   * @return The resolved methods of the class.
   */
  private static List<ResolvedMethod> resolveMethods(final TraitsInfoList traits, final MethodInfoList methodList, final Map<MethodInfo, ResolvedMethod> methodMapping, final StringInfoList constantStringList, final MultinameInfoList multinameList, final NamespaceInfoList namespaceList, final List<String> errors) {

    assert traits != null : "Traits argument must not be null";
    assert methodList != null : "Method list argument must not be null";
    assert methodMapping != null : "Method mapping argument must not be null";
    assert constantStringList != null : "Constant string list argument must not be null";
    assert multinameList != null : "Multiname list argument must not be null";
    assert namespaceList != null : "Namespace list argument must not be null";
    assert errors != null : "Errors list must not be null";

    final List<ResolvedMethod> methods = new ArrayList<ResolvedMethod>();

    for (final TraitsInfo traitsInfo : traits) {
      if (traitsInfo.getData() instanceof TraitMethod) {
        methods.add(resolveMethod(traitsInfo, methodList, methodMapping, constantStringList, multinameList, namespaceList, errors));
      }
    }

    return methods;
  }

  /**
   * Resolves the static constructor of a class.
   *
   * @param classInfo The class whose static constructor is resolved.
   * @param data The fragmented ActionScript 3 data.
   * @param methodMapping The previously generated mapping between method
   * headers and their bodies.
   * @param errors Errors that occur during resolving are logged here.
   *
   * @return The resolved static constructor for the given class or null if
   * the static constructor could not be resolved.
   */
  private static ResolvedMethod resolveStaticConstructor(final ClassInfo classInfo,  final AS3Data data, final Map<MethodInfo, ResolvedMethod> methodMapping, final List<String> errors) {

    assert classInfo != null : "Class info argument must not be null";
    assert data != null : "Data argument must not be null";
    assert methodMapping != null : "Method mapping argument must not be null";
    assert errors != null : "Errors list must not be null";

    final int staticConstructorId = classInfo.getcInit().value();

    final MethodInfoList methodInfos = data.getMethodInfos();

    if (staticConstructorId >= methodInfos.size()) {
      errors.add(String.format("The static constructor of the class at offset %08X could not be resolved as its method index is out of bounds", classInfo.getBitPosition() / 8));
      return null;
    }
    else {
      final ResolvedMethod resolvedStaticConstructor = methodMapping.get(methodInfos.get(staticConstructorId));
      return new ResolvedMethod(new String[0], new String[] { "staticconstructor" }, new ArrayList<String[]>(), resolvedStaticConstructor == null ? null : resolvedStaticConstructor.getCode());
    }
  }

  /**
   * Takes fragmented ActionScript 3 data as parsed from a SWF file and turns it
   * into something that fits the model of Flash code analysis better.
   *
   * @param data The fragmented data from the SWF file.
   *
   * @return The resolved ActionScript 3 code.
   */
  public static ResolvedCode resolve(final AS3Data data) {

    if (data == null) {
      throw new IllegalArgumentException("Data argument must not be null.");
    }

    // This list is used to collect errors that occur during resolving.
    final List<String> errors = new ArrayList<String>();

    // In the first step we are creating a mapping between method headers and
    // method bodies. This is needed to quickly access method bodies later.
    final Map<MethodInfo, ResolvedMethod> methodMapping = createMethodInformationMapping(data, errors);

    // In the second step we are going through all the classes defined in the
    // input data and we are trying to recover the individual elements that
    // make up that class.
    final List<ResolvedClass> classes = resolveClasses(data, methodMapping, errors);

    return new ResolvedCode(data, classes);
  }
}
TOP

Related Classes of tv.porst.swfretools.utils.as3.ActionScript3Resolver

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.