Module refinery.lib.scripts.ps1.deobfuscation.typecast

PowerShell type cast simplification transforms.

Expand source code Browse git
"""
PowerShell type cast simplification transforms.
"""
from __future__ import annotations

import string

from refinery.lib.scripts import Transformer
from refinery.lib.scripts.ps1.deobfuscation.helpers import (
    collect_int_arguments,
    collect_string_arguments,
    make_string_literal,
    normalize_dotnet_type_name,
    string_value,
    unwrap_integer,
    unwrap_single_paren,
)
from refinery.lib.scripts.ps1.model import (
    Ps1BinaryExpression,
    Ps1CastExpression,
    Ps1IntegerLiteral,
    Ps1TypeExpression,
)

_INTEGER_TYPE_NAMES = frozenset({
    'byte',
    'int',
    'int16',
    'int32',
    'int64',
    'long',
    'sbyte',
    'short',
    'uint16',
    'uint32',
    'uint64',
    'ushort',
})


class Ps1TypeCasts(Transformer):

    def visit_Ps1BinaryExpression(self, node: Ps1BinaryExpression):
        self.generic_visit(node)
        if node.operator.lower() != '-as':
            return None
        if not isinstance(node.right, Ps1TypeExpression):
            return None
        cast = Ps1CastExpression(
            offset=node.offset,
            type_name=node.right.name,
            operand=node.left,
        )
        return self.visit_Ps1CastExpression(cast) or cast

    def visit_Ps1CastExpression(self, node: Ps1CastExpression):
        self.generic_visit(node)
        tn = normalize_dotnet_type_name(node.type_name)
        if tn in ('string', 'char[]'):
            if node.operand and string_value(node.operand) is not None:
                return node.operand
        if tn == 'string':
            if node.operand is not None:
                inner = unwrap_single_paren(node.operand)
                parts = collect_string_arguments(inner)
                if parts is not None and len(parts) > 1:
                    return make_string_literal(' '.join(parts))
        if tn in _INTEGER_TYPE_NAMES:
            result = unwrap_integer(node.operand)
            if result is not None:
                return Ps1IntegerLiteral(value=result.value, raw=str(result.value))
            sv = string_value(node.operand) if node.operand else None
            if sv is not None:
                sv = sv.strip()
                try:
                    value = int(sv, 0)
                except (ValueError, OverflowError):
                    pass
                else:
                    return Ps1IntegerLiteral(value=value, raw=str(value))
        if tn == 'char':
            result = unwrap_integer(node.operand)
            if result is not None:
                if result.value == 0:
                    return make_string_literal('')
                try:
                    ch = chr(result.value)
                except (ValueError, OverflowError):
                    return None
                return make_string_literal(ch)
        if tn == 'char[]':
            if node.operand is not None:
                inner = unwrap_single_paren(node.operand)
                int_values = collect_int_arguments(inner)
                if int_values is not None:
                    try:
                        result_bytes = bytes(int_values)
                        result = result_bytes.decode('ascii')
                        if not all(c in string.printable or c.isspace() for c in result):
                            return None
                    except (ValueError, UnicodeDecodeError, OverflowError):
                        return None
                    return make_string_literal(result)
        if tn == 'type':
            sv = string_value(node.operand) if node.operand else None
            if sv is not None:
                return Ps1TypeExpression(offset=node.offset, name=sv)
        return None

Classes

class Ps1TypeCasts

In-place tree rewriter. Each visit method may return a replacement node or None to keep the original. Tracks whether any transformation was applied via the changed flag.

Expand source code Browse git
class Ps1TypeCasts(Transformer):

    def visit_Ps1BinaryExpression(self, node: Ps1BinaryExpression):
        self.generic_visit(node)
        if node.operator.lower() != '-as':
            return None
        if not isinstance(node.right, Ps1TypeExpression):
            return None
        cast = Ps1CastExpression(
            offset=node.offset,
            type_name=node.right.name,
            operand=node.left,
        )
        return self.visit_Ps1CastExpression(cast) or cast

    def visit_Ps1CastExpression(self, node: Ps1CastExpression):
        self.generic_visit(node)
        tn = normalize_dotnet_type_name(node.type_name)
        if tn in ('string', 'char[]'):
            if node.operand and string_value(node.operand) is not None:
                return node.operand
        if tn == 'string':
            if node.operand is not None:
                inner = unwrap_single_paren(node.operand)
                parts = collect_string_arguments(inner)
                if parts is not None and len(parts) > 1:
                    return make_string_literal(' '.join(parts))
        if tn in _INTEGER_TYPE_NAMES:
            result = unwrap_integer(node.operand)
            if result is not None:
                return Ps1IntegerLiteral(value=result.value, raw=str(result.value))
            sv = string_value(node.operand) if node.operand else None
            if sv is not None:
                sv = sv.strip()
                try:
                    value = int(sv, 0)
                except (ValueError, OverflowError):
                    pass
                else:
                    return Ps1IntegerLiteral(value=value, raw=str(value))
        if tn == 'char':
            result = unwrap_integer(node.operand)
            if result is not None:
                if result.value == 0:
                    return make_string_literal('')
                try:
                    ch = chr(result.value)
                except (ValueError, OverflowError):
                    return None
                return make_string_literal(ch)
        if tn == 'char[]':
            if node.operand is not None:
                inner = unwrap_single_paren(node.operand)
                int_values = collect_int_arguments(inner)
                if int_values is not None:
                    try:
                        result_bytes = bytes(int_values)
                        result = result_bytes.decode('ascii')
                        if not all(c in string.printable or c.isspace() for c in result):
                            return None
                    except (ValueError, UnicodeDecodeError, OverflowError):
                        return None
                    return make_string_literal(result)
        if tn == 'type':
            sv = string_value(node.operand) if node.operand else None
            if sv is not None:
                return Ps1TypeExpression(offset=node.offset, name=sv)
        return None

Ancestors

Methods

def visit_Ps1BinaryExpression(self, node)
Expand source code Browse git
def visit_Ps1BinaryExpression(self, node: Ps1BinaryExpression):
    self.generic_visit(node)
    if node.operator.lower() != '-as':
        return None
    if not isinstance(node.right, Ps1TypeExpression):
        return None
    cast = Ps1CastExpression(
        offset=node.offset,
        type_name=node.right.name,
        operand=node.left,
    )
    return self.visit_Ps1CastExpression(cast) or cast
def visit_Ps1CastExpression(self, node)
Expand source code Browse git
def visit_Ps1CastExpression(self, node: Ps1CastExpression):
    self.generic_visit(node)
    tn = normalize_dotnet_type_name(node.type_name)
    if tn in ('string', 'char[]'):
        if node.operand and string_value(node.operand) is not None:
            return node.operand
    if tn == 'string':
        if node.operand is not None:
            inner = unwrap_single_paren(node.operand)
            parts = collect_string_arguments(inner)
            if parts is not None and len(parts) > 1:
                return make_string_literal(' '.join(parts))
    if tn in _INTEGER_TYPE_NAMES:
        result = unwrap_integer(node.operand)
        if result is not None:
            return Ps1IntegerLiteral(value=result.value, raw=str(result.value))
        sv = string_value(node.operand) if node.operand else None
        if sv is not None:
            sv = sv.strip()
            try:
                value = int(sv, 0)
            except (ValueError, OverflowError):
                pass
            else:
                return Ps1IntegerLiteral(value=value, raw=str(value))
    if tn == 'char':
        result = unwrap_integer(node.operand)
        if result is not None:
            if result.value == 0:
                return make_string_literal('')
            try:
                ch = chr(result.value)
            except (ValueError, OverflowError):
                return None
            return make_string_literal(ch)
    if tn == 'char[]':
        if node.operand is not None:
            inner = unwrap_single_paren(node.operand)
            int_values = collect_int_arguments(inner)
            if int_values is not None:
                try:
                    result_bytes = bytes(int_values)
                    result = result_bytes.decode('ascii')
                    if not all(c in string.printable or c.isspace() for c in result):
                        return None
                except (ValueError, UnicodeDecodeError, OverflowError):
                    return None
                return make_string_literal(result)
    if tn == 'type':
        sv = string_value(node.operand) if node.operand else None
        if sv is not None:
            return Ps1TypeExpression(offset=node.offset, name=sv)
    return None