summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--binaryninjaapi.h1
-rw-r--r--binaryninjacore.h3
-rw-r--r--python/transform.py148
-rw-r--r--transformcontext.cpp6
4 files changed, 154 insertions, 4 deletions
diff --git a/binaryninjaapi.h b/binaryninjaapi.h
index 08c6e555..d6f4c711 100644
--- a/binaryninjaapi.h
+++ b/binaryninjaapi.h
@@ -9170,6 +9170,7 @@ namespace BinaryNinja {
std::string GetExtractionMessage() const;
BNTransformResult GetExtractionResult() const;
BNTransformResult GetTransformResult() const;
+ void SetTransformResult(BNTransformResult result);
Ref<Metadata> GetMetadata() const;
Ref<TransformContext> GetParent() const;
size_t GetChildCount() const;
diff --git a/binaryninjacore.h b/binaryninjacore.h
index 0084a41e..d62cf038 100644
--- a/binaryninjacore.h
+++ b/binaryninjacore.h
@@ -37,7 +37,7 @@
// Current ABI version for linking to the core. This is incremented any time
// there are changes to the API that affect linking, including new functions,
// new types, or modifications to existing functions or types.
-#define BN_CURRENT_CORE_ABI_VERSION 143
+#define BN_CURRENT_CORE_ABI_VERSION 144
// Minimum ABI version that is supported for loading of plugins. Plugins that
// are linked to an ABI version less than this will not be able to load and
@@ -4748,6 +4748,7 @@ extern "C"
BINARYNINJACOREAPI char* BNTransformContextGetExtractionMessage(BNTransformContext* context);
BINARYNINJACOREAPI BNTransformResult BNTransformContextGetExtractionResult(BNTransformContext* context);
BINARYNINJACOREAPI BNTransformResult BNTransformContextGetTransformResult(BNTransformContext* context);
+ BINARYNINJACOREAPI void BNTransformContextSetTransformResult(BNTransformContext* context, BNTransformResult result);
BINARYNINJACOREAPI BNMetadata* BNTransformContextGetMetadata(BNTransformContext* context);
BINARYNINJACOREAPI BNTransformContext* BNTransformContextGetParent(BNTransformContext* context);
BINARYNINJACOREAPI size_t BNTransformContextGetChildCount(BNTransformContext* context);
diff --git a/python/transform.py b/python/transform.py
index 611049ce..54827efc 100644
--- a/python/transform.py
+++ b/python/transform.py
@@ -21,16 +21,19 @@
import traceback
import ctypes
import abc
+import io
+import zipfile
from typing import List, Optional, Union
# Binary Ninja components
import binaryninja
-from .log import log_error_for_exception
+from .log import log_error_for_exception, log_error
from . import databuffer
from . import binaryview
from . import metadata
from . import _binaryninjacore as core
-from .enums import TransformType, TransformResult
+from .enums import TransformCapabilities, TransformResult, TransformType
+from .settings import Settings
class _TransformMetaClass(type):
@@ -254,7 +257,7 @@ class Transform(metaclass=_TransformMetaClass):
def _can_decode(self, ctxt, input):
try:
- input_obj = binaryview.BinaryView(handle=core.BNNewViewReference(input))
+ input_obj = binaryview.BinaryView(handle=input)
return self.can_decode(input_obj)
except:
log_error_for_exception("Unhandled Python exception in Transform._can_decode")
@@ -549,6 +552,11 @@ class TransformContext:
"""Get the transform result"""
return TransformResult(core.BNTransformContextGetTransformResult(self.handle))
+ @transform_result.setter
+ def transform_result(self, result: TransformResult):
+ """Set the transform result"""
+ core.BNTransformContextSetTransformResult(self.handle, result)
+
@property
def parent(self) -> Optional['TransformContext']:
"""Get the parent context"""
@@ -808,3 +816,137 @@ class TransformSession:
for i, ctx in enumerate(contexts):
context_array[i] = ctx.handle
core.BNTransformSessionSetSelectedContexts(self.handle, context_array, len(contexts))
+
+
+class ZipPython(Transform):
+ """
+ ``ZipPython`` is a transform that handles ZIP archive decoding using Python's built-in zipfile module.
+ It supports password-protected archives and context-aware extraction of multiple files. It is provided
+ as a reference implementation and may not be as performant as native implementations. By default, this
+ Transform is not registered; to use it, call `ZipPython.register()`.
+ """
+ transform_type = TransformType.DecodeTransform
+ capabilities = TransformCapabilities.TransformSupportsDetection | TransformCapabilities.TransformSupportsContext
+ name = "ZipPython"
+ long_name = "Zip (Python)"
+ group = "Container"
+
+ def can_decode(self, input) -> bool:
+ try:
+ head = input.read(0, 4)
+ if len(head) < 4 or head[0:2] != b"PK":
+ return False
+ signature = head[2] | (head[3] << 8)
+ return signature in (0x0403, 0x0201, 0x0605, 0x0708, 0x0606, 0x0706, 0x0505)
+ except Exception:
+ log_error("ZipPython: failed to read from BinaryView for signature check")
+ return False
+
+ def perform_decode(self, data: bytes, params: dict) -> bytes | None:
+ try:
+ zf = zipfile.ZipFile(io.BytesIO(data), "r")
+ except Exception:
+ log_error("ZipPython: failed to open data as ZIP")
+ return None
+
+ filename = None
+ if "filename" in params:
+ p = params["filename"]
+ filename = p.decode("utf-8", "replace") if isinstance(p, (bytes, bytearray)) else str(p)
+ elif zf.namelist():
+ filename = zf.namelist()[0]
+ try:
+ if filename:
+ with zf.open(filename, "r") as f:
+ return f.read()
+ except (KeyError, RuntimeError, zipfile.BadZipFile, zipfile.LargeZipFile):
+ log_error(f"ZipPython: failed to extract member '{filename}' from ZIP")
+ return None
+
+ def perform_encode(self, data, params):
+ return None
+
+ def perform_decode_with_context(self, context, params) -> bool:
+ """
+ ``perform_decode_with_context`` implements context-aware ZIP extraction.
+ Two-phase flow:
+ - Phase 1 (discovery): populate available_files, return False.
+ - Phase 2 (extraction): for each requested file, create a child context and return
+ True if all succeeded, False otherwise.
+ """
+ try:
+ zf = zipfile.ZipFile(io.BytesIO(context.input.read(0, context.input.length)), "r")
+ except Exception:
+ context.transform_result = TransformResult.TransformFailure
+ log_error(f"ZipPython: failed to open context input as ZIP: len={context.input.length}")
+ return False
+
+ # Build the file list (non-directories only)
+ files: List[str] = [n for n in zf.namelist() if not n.endswith("/")]
+
+ # Phase 1: discovery
+ if not context.has_available_files:
+ context.set_available_files(files)
+ return False
+
+ # Phase 2: extraction
+ requested = context.requested_files
+ if not requested:
+ return False
+
+ passwords = Settings().get_string_list('files.container.defaultPasswords')
+ if "password" in params:
+ p = params["password"]
+ passwords.insert(0, p.decode("utf-8", "replace") if isinstance(p, (bytes, bytearray)) else str(p))
+ passwords = [None] + passwords
+
+ complete = True
+ for name in requested:
+ if name not in files:
+ msg = f"Requested file '{name}' not found in ZIP"
+ context.create_child(databuffer.DataBuffer(b""), name, result=TransformResult.TransformFailure, message=msg)
+ complete = False
+ continue
+
+ content = None
+ error = None
+ successful_password = None
+ for password in passwords:
+ try:
+ if password is None:
+ with zf.open(name, "r") as f:
+ content = f.read()
+ else:
+ pwd = password.encode('utf-8') if isinstance(password, str) else password
+ with zf.open(name, "r", pwd=pwd) as f:
+ content = f.read()
+ successful_password = password
+ break
+
+ except RuntimeError as e:
+ error = e
+ if 'password' not in str(e).lower() and 'encrypted' not in str(e).lower():
+ break
+ except Exception as e:
+ error = e
+ break
+
+ if successful_password is not None and successful_password in passwords:
+ passwords.remove(successful_password)
+ passwords.insert(0, successful_password)
+
+ if content is not None:
+ context.create_child(databuffer.DataBuffer(content), name)
+ else:
+ if error is None:
+ error = RuntimeError(f"Failed to decrypt '{name}' with any provided password")
+ if isinstance(error, RuntimeError) and 'password' in str(error).lower():
+ transformresult = TransformResult.TransformRequiresPassword
+ else:
+ transformresult = TransformResult.TransformFailure
+ log_error(f"ZipPython: failed to extract requested file '{name}': {error}")
+
+ context.create_child(databuffer.DataBuffer(b""), name, result=transformresult, message=str(error))
+ complete = False
+
+ return complete
diff --git a/transformcontext.cpp b/transformcontext.cpp
index 0ebb4d8a..9b01e306 100644
--- a/transformcontext.cpp
+++ b/transformcontext.cpp
@@ -115,6 +115,12 @@ BNTransformResult TransformContext::GetTransformResult() const
}
+void TransformContext::SetTransformResult(BNTransformResult result)
+{
+ BNTransformContextSetTransformResult(m_object, result);
+}
+
+
Ref<Metadata> TransformContext::GetMetadata() const
{
return new Metadata(BNTransformContextGetMetadata(m_object));