Oleksandr A. Letychevskyi

24 papers A 1Misc 3Journal 2Unranked 18
YearRankTypeTitle / Venue / Authors
2023 conf
IDAACS
Oleksandr A. Letychevskyi, Yuliia Tarasich, Volodymyr Peschanenko
2023 conf
DESSERT
Oleg Odarushchenko, Oleksiy Striuk, Viacheslav Shamanskyi, Oleksandr A. Letychevskyi, Aleksandr Ivasiuk, Elena Odarushchenko
2022 conf
IntelITSIS
Oleksandr A. Letychevskyi, Volodymyr Peschanenko, Serhii Horbatiuk
2021 conf
ICTERI (Revised Selected Papers)
Oleksandr A. Letychevskyi, Yuliia Tarasich, Volodymyr Peschanenko, Vladislav Volkov, Hanna Sokolova, Maksym Poltoratskiy
2021 conf
ICTERI (Revised Selected Papers)
Oleksandr A. Letychevskyi, Volodymyr Peschanenko, Vlad Volkov
2021 conf
IDAACS
Serhii Naumenko, Viktoriia Moskalets, Oleg Odarushchenko, Elena Odarushchenko, Volodymyr Peschanenko, Larysa Degtyareva, Oleksandr A. Letychevskyi
2020 conf
IDAACS-SWS
Oleksandr A. Letychevskyi, Serhii Horbatiuk, Viktor Horbatiuk
2020 conf
IEEE BigData
Oleksandr A. Letychevskyi, Yaroslav Hryniuk
2020 conf
ICTERI Workshops
Oleksandr A. Letychevskyi, Volodymyr Peschanenko, Maksym Poltoratskiy, Yuliia Tarasich
2019 conf
IECC
Oleksandr A. Letychevskyi, Volodymyr Peschanenko, Viktor Radchenko, Maxim Orlovsky, Andrey Sobol
2019 J jnl
ISC Int. J. Inf. Secur.
Oleksandr A. Letychevskyi, Yaroslav Hryniuk, Viktor Yakovlev, Volodymyr Peschanenko, Viktor Radchenko
2019 conf
DESSERT
Oleksandr A. Letychevskyi, Volodymyr Peschanenko, Viktor Radchenko, Yaroslav Hryniuk, Viktor Yakovlev
2019 conf
IEEE BigData
Oleksandr A. Letychevskyi, Tetiana Polhul
2019 conf
ICTERI Workshops
Oleksandr A. Letychevskyi, Volodymyr Peschanenko, Viktor Radchenko, Maksym Poltoratskiy, Yulia Tarasich
2019 conf
ICTERI (Revised Selected Papers)
Oleksandr A. Letychevskyi, Volodymyr Peschanenko, Maksym Poltoratskiy, Yulia Tarasich
2019 conf
ICTERI Workshops
Andrey Sobol, Volodymyr G. Skobelev, Julian Konchunas, Viktor Radchenko, Sabina Sachtachtinskagia, Oleksandr A. Letychevskyi, Volodymyr Peschanenko, Maxim Orlovsky
2019 conf
IDAACS
Oleksandr A. Letychevskyi
2017 conf
RE Workshops
Alexander A. Letichevsky, Oleksandr A. Letychevskyi, Vladimir S. Peschanenko, Maxsim Poltorackij
2017 Misc conf
ICTERI
Alexander Godlevskyi, Aleksander Letichevskyi, Vladimir S. Peschanenko, Oleksandr A. Letychevskyi, Maryna Morokhovets, Volodymyr G. Skobelev, Maksym Poltorackiy
2017 Misc conf
ICTERI
Michael Lvov, Vladimir S. Peschanenko, Oleksandr A. Letychevskyi, Yulia Tarasich
2016 J jnl
Comput. Sci. J. Moldova
Alexander A. Letichevsky, Oleksandr A. Letychevskyi, Vladimir S. Peschanenko
2015 Misc conf
ICTERI
Alexander A. Letichevsky, Oleksandr A. Letychevskyi, Vladimir S. Peschanenko
2015 conf
SDL Forum
Alexander A. Letichevsky, Oleksandr A. Letychevskyi, Volodymyr Peschanenko, Thomas Weigert
2014 A conf
RE
Oleksandr A. Letychevskyi, Thomas Weigert
redb/extractors/decompiler/apk/smali_normalization.py
← Index redb/extractors/decompiler/apk/smali_normalization.py python
"""Semantic normalization of Dalvik/smali instructions.

Analogous to Binary Ninja's LLIL normalization: strips register allocation
noise and instruction encoding variants while preserving semantic operations.

Three normalization levels (most aggressive to most detailed):
  - 'category':    semantic category only (MOV, ALU, CALL, ...)
  - 'opcode':      base opcode, width-invariant (add, sub, invoke, ...)
  - 'opcode_api':  opcode category + API method/field references for
                   invoke/field/alloc instructions (default for MinHash)

References:
  - Smali+ 12-category reduction (Canfora et al.)
  - MOSDroid opcode family grouping
  - DroidSIFT/DroidSim API-sensitive similarity
"""

import re
from typing import List

# ---------------------------------------------------------------------------
# Dalvik opcode -> semantic category mapping
# ---------------------------------------------------------------------------
# Prefix-matched against instruction opcodes. Order matters for overlapping
# prefixes (longer/more-specific prefixes should come first in iteration,
# but since we use startswith and break on first match, we order by
# specificity within the list).

OPCODE_CATEGORIES = {
    # Arithmetic/logic
    "add": "ALU", "sub": "ALU", "mul": "ALU", "div": "ALU",
    "rem": "ALU", "and": "ALU", "or": "ALU", "xor": "ALU",
    "shl": "ALU", "shr": "ALU", "ushr": "ALU", "neg": "ALU",
    "not": "ALU",
    # Data movement
    "move": "MOV", "const": "CONST",
    # Memory access (field/array)
    "iget": "LOAD", "sget": "LOAD", "aget": "LOAD",
    "iput": "STORE", "sput": "STORE", "aput": "STORE",
    # Invocations
    "invoke": "CALL",
    # Control flow
    "if": "BRANCH", "goto": "JMP",
    "switch": "SWITCH",
    "return": "RET",
    # Object/type
    "new": "ALLOC", "check": "TYPE", "instance": "TYPE",
    # Array
    "fill": "ARR", "array": "ARR",
    # Comparison
    "cmpl": "CMP", "cmpg": "CMP", "cmp": "CMP",
    # Exception / synchronization
    "throw": "EXC", "monitor": "SYNC",
    # Conversion (int-to-long, float-to-int, etc.)
    "int-to": "CONV", "long-to": "CONV", "float-to": "CONV",
    "double-to": "CONV",
}

# Pre-compiled regexes for operand extraction
_METHOD_REF_RE = re.compile(r"(L[\w/$]+;->[\w<>]+\(.*?\)[\w/$;\[]*)")
_FIELD_REF_RE = re.compile(r"(L[\w/$]+;->[\w]+:[\w/$;\[]+)")
_CLASS_REF_RE = re.compile(r"(L[\w/$]+;)")
_CONST_STRING_RE = re.compile(r'^const-string(?:/jumbo)?\s')


def categorize_opcode(opcode: str) -> str:
    """Map a Dalvik opcode to its semantic category.

    Prefix-matched: 'add-int/2addr' matches 'add' -> 'ALU'.
    Returns 'OTHER' for unrecognized opcodes.
    """
    for prefix, cat in OPCODE_CATEGORIES.items():
        if opcode.startswith(prefix):
            return cat
    return "OTHER"


# Mapping from semantic categories to the ACFG feature vector indices
# used by Binary Ninja's build_block_features (cfg_features.py).
# This enables cross-platform ACFG feature comparison.
CATEGORY_TO_ACFG_INDEX = {
    "ALU": 0,       # CAT_ARITHMETIC
    "CONV": 0,      # arithmetic-adjacent
    "CMP": 4,       # CAT_COMPARISON
    "MOV": 2,       # CAT_TRANSFER
    "CONST": 2,     # transfer-adjacent (loading constants)
    "LOAD": 5,      # CAT_MEMORY
    "STORE": 5,     # CAT_MEMORY
    "CALL": 3,      # CAT_CALL
    "BRANCH": 1,    # CAT_LOGIC (conditional logic)
    "JMP": 1,       # CAT_LOGIC
    "SWITCH": 1,    # CAT_LOGIC
    "RET": 2,       # CAT_TRANSFER
    "ALLOC": 5,     # CAT_MEMORY (heap allocation)
    "TYPE": 6,      # CAT_OTHER
    "ARR": 5,       # CAT_MEMORY
    "EXC": 6,       # CAT_OTHER
    "SYNC": 6,      # CAT_OTHER
    "OTHER": 6,     # CAT_OTHER
}


def normalize_instruction(line: str, level: str = "opcode_api") -> str:
    """Normalize a single smali instruction line.

    Args:
        line: A single smali instruction (whitespace-stripped).
        level: Normalization level:
            'category'   - most aggressive: just semantic category
            'opcode'     - base opcode only, width/addressing-mode invariant
            'opcode_api' - category + API references for invoke/field/alloc
                          (default, best for MinHash similarity)

    Returns:
        Normalized instruction string, or empty string for non-instructions.
    """
    stripped = line.strip()
    if not stripped:
        return ""

    parts = stripped.split(None, 1)
    opcode = parts[0]
    operands = parts[1] if len(parts) > 1 else ""

    if level == "category":
        return categorize_opcode(opcode)

    if level == "opcode":
        # Strip type/width suffixes for invariance:
        # add-int, add-long, add-float -> 'add'
        # add-int/2addr -> 'add'
        base = re.split(r"[-/]", opcode)[0]
        return base

    if level == "opcode_api":
        # const-string: preserve string content (encrypted strings are a
        # key malware indicator)
        if _CONST_STRING_RE.match(stripped):
            # Extract the string literal
            str_match = re.search(r'"(.*)"', operands)
            if str_match:
                return f"CONST_STR \"{str_match.group(1)}\""
            return "CONST_STR"

        # invoke-*: preserve method reference
        if opcode.startswith("invoke"):
            ref = _METHOD_REF_RE.search(operands)
            if ref:
                return f"CALL {ref.group(1)}"
            return "CALL"

        # Field access: preserve field reference
        if opcode.startswith(("iget", "iput", "sget", "sput")):
            ref = _FIELD_REF_RE.search(operands)
            if ref:
                cat = "LOAD" if "get" in opcode else "STORE"
                return f"{cat} {ref.group(1)}"
            # Fallback: try space-separated format from androguard
            # e.g. "iget v0, p0, Lcom/Foo;->field Ljava/lang/String;"
            space_ref = re.search(
                r"(L[\w/$]+;->[\w]+)\s+([\w/$;\[]+)", operands
            )
            if space_ref:
                cat = "LOAD" if "get" in opcode else "STORE"
                return f"{cat} {space_ref.group(1)}:{space_ref.group(2)}"
            cat = "LOAD" if "get" in opcode else "STORE"
            return cat

        # new-instance: preserve allocated type
        if opcode.startswith("new-instance") or opcode == "new-array":
            ref = _CLASS_REF_RE.search(operands)
            if ref:
                return f"ALLOC {ref.group(1)}"
            return "ALLOC"

        # Everything else: just the category
        return categorize_opcode(opcode)

    # Unknown level: return raw opcode
    return opcode


def normalize_method_body(
    body: str, level: str = "opcode_api"
) -> List[str]:
    """Normalize all instructions in a smali method body.

    Filters out directives (.), labels (:), comments (#), and blank lines.
    Returns a list of normalized instruction strings.

    Args:
        body: Raw smali method body text.
        level: Normalization level (see normalize_instruction).

    Returns:
        List of normalized instruction strings (no empty strings).
    """
    normalized = []
    for line in body.split("\n"):
        stripped = line.strip()
        # Skip non-instructions
        if not stripped:
            continue
        if stripped.startswith((".",":", "#")):
            continue
        result = normalize_instruction(stripped, level)
        if result:
            normalized.append(result)
    return normalized