mirror of
https://github.com/mandiant/capa.git
synced 2025-12-25 04:14:55 -08:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ad1dda918 | ||
|
|
eabb2cc809 | ||
|
|
a34c3ecc57 | ||
|
|
d22de5cf7f | ||
|
|
8f78834cae | ||
|
|
08dbb0e02d | ||
|
|
98725c52dc | ||
|
|
eb87153064 | ||
|
|
56aa7176b0 | ||
|
|
8b41671409 | ||
|
|
5dbbc2b468 | ||
|
|
96d1eb64c3 | ||
|
|
9234b33051 | ||
|
|
51f5114ad7 | ||
|
|
4b72f8a872 | ||
|
|
8206a97b0f | ||
|
|
5a33b4b2a8 | ||
|
|
fcfdeec377 | ||
|
|
37a63a751c | ||
|
|
3a9f2136bb | ||
|
|
390e2a6315 | ||
|
|
6a43084915 | ||
|
|
6d7ca57fa9 | ||
|
|
d1090e8391 | ||
|
|
b07efe773b | ||
|
|
9d3d3be21d | ||
|
|
8251a4c16f | ||
|
|
7407cb39ca | ||
|
|
0162e447fd | ||
|
|
829dae388f | ||
|
|
2a4d0ae080 | ||
|
|
d9a754730c | ||
|
|
4acacba9d6 | ||
|
|
d00f172973 |
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -22,12 +22,12 @@ jobs:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-20.04
|
||||
- os: ubuntu-24.04
|
||||
# use old linux so that the shared library versioning is more portable
|
||||
artifact_name: capa
|
||||
asset_name: linux
|
||||
python_version: '3.10'
|
||||
- os: ubuntu-20.04
|
||||
- os: ubuntu-24.04
|
||||
artifact_name: capa
|
||||
asset_name: linux-py312
|
||||
python_version: '3.12'
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0
|
||||
with:
|
||||
python-version: ${{ matrix.python_version }}
|
||||
- if: matrix.os == 'ubuntu-20.04'
|
||||
- if: matrix.os == 'ubuntu-24.04'
|
||||
run: sudo apt-get install -y libyaml-dev
|
||||
- name: Upgrade pip, setuptools
|
||||
run: python -m pip install --upgrade pip setuptools
|
||||
@@ -82,10 +82,10 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
# OSs not already tested above
|
||||
- os: ubuntu-22.04
|
||||
- os: ubuntu-24.04
|
||||
artifact_name: capa
|
||||
asset_name: linux
|
||||
- os: ubuntu-22.04
|
||||
- os: ubuntu-24.04
|
||||
artifact_name: capa
|
||||
asset_name: linux-py312
|
||||
- os: windows-2022
|
||||
|
||||
20
.github/workflows/tests.yml
vendored
20
.github/workflows/tests.yml
vendored
@@ -26,7 +26,7 @@ env:
|
||||
|
||||
jobs:
|
||||
changelog_format:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout capa
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
if [ $number != 1 ]; then exit 1; fi
|
||||
|
||||
code_style:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout capa
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
run: pre-commit run deptry --hook-stage manual
|
||||
|
||||
rule_linter:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout capa with submodules
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
@@ -88,16 +88,16 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, windows-2019, macos-13]
|
||||
os: [ubuntu-24.04, windows-2019, macos-13]
|
||||
# across all operating systems
|
||||
python-version: ["3.10", "3.11"]
|
||||
include:
|
||||
# on Ubuntu run these as well
|
||||
- os: ubuntu-20.04
|
||||
- os: ubuntu-24.04
|
||||
python-version: "3.10"
|
||||
- os: ubuntu-20.04
|
||||
- os: ubuntu-24.04
|
||||
python-version: "3.11"
|
||||
- os: ubuntu-20.04
|
||||
- os: ubuntu-24.04
|
||||
python-version: "3.12"
|
||||
steps:
|
||||
- name: Checkout capa with submodules
|
||||
@@ -109,7 +109,7 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install pyyaml
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
if: matrix.os == 'ubuntu-24.04'
|
||||
run: sudo apt-get install -y libyaml-dev
|
||||
- name: Install capa
|
||||
run: |
|
||||
@@ -126,7 +126,7 @@ jobs:
|
||||
name: Binary Ninja tests for ${{ matrix.python-version }}
|
||||
env:
|
||||
BN_SERIAL: ${{ secrets.BN_SERIAL }}
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
needs: [tests]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -168,7 +168,7 @@ jobs:
|
||||
|
||||
ghidra-tests:
|
||||
name: Ghidra tests for ${{ matrix.python-version }}
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
needs: [tests]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
55
CHANGELOG.md
55
CHANGELOG.md
@@ -6,16 +6,11 @@
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
### New Rules (4)
|
||||
### New Rules (0)
|
||||
|
||||
- communication/socket/connect-socket moritz.raabe@mandiant.com joakim@intezer.com mrhafizfarhad@gmail.com
|
||||
- communication/socket/udp/connect-udp-socket mrhafizfarhad@gmail.com
|
||||
- nursery/enter-debug-mode-in-dotnet @v1bh475u
|
||||
-
|
||||
|
||||
### Bug Fixes
|
||||
- cape: make some fields optional @williballenthin #2631 #2632
|
||||
- lint: add WARN for regex features that contain unescaped dot #2635
|
||||
|
||||
### capa Explorer Web
|
||||
|
||||
@@ -24,8 +19,52 @@
|
||||
### Development
|
||||
|
||||
### Raw diffs
|
||||
- [capa v9.1.0...master](https://github.com/mandiant/capa/compare/v9.1.0...master)
|
||||
- [capa-rules v9.1.0...master](https://github.com/mandiant/capa-rules/compare/v9.1.0...master)
|
||||
- [capa v9.2.0...master](https://github.com/mandiant/capa/compare/v9.2.0...master)
|
||||
- [capa-rules v9.2.0...master](https://github.com/mandiant/capa-rules/compare/v9.2.0...master)
|
||||
|
||||
## v9.2.0
|
||||
|
||||
This release improves a few aspects of dynamic analysis, including relaxing our validation on fields across many CAPE versions and processing additional VMRay submission file types, for example.
|
||||
It also includes an updated rule pack containing new rules and rule fixes.
|
||||
|
||||
### New Features
|
||||
- vmray: do not restrict analysis to PE and ELF files, e.g. docx @mike-hunhoff #2672
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
### New Rules (22)
|
||||
|
||||
- communication/socket/connect-socket moritz.raabe@mandiant.com joakim@intezer.com mrhafizfarhad@gmail.com
|
||||
- communication/socket/udp/connect-udp-socket mrhafizfarhad@gmail.com
|
||||
- nursery/enter-debug-mode-in-dotnet @v1bh475u
|
||||
- nursery/decrypt-data-using-tripledes-in-dotnet 0xRavenspar
|
||||
- nursery/encrypt-data-using-tripledes-in-dotnet 0xRavenspar
|
||||
- nursery/disable-system-features-via-registry-on-windows mehunhoff@google.com
|
||||
- data-manipulation/encryption/chaskey/encrypt-data-using-chaskey still@teamt5.org
|
||||
- data-manipulation/encryption/speck/encrypt-data-using-speck still@teamt5.org
|
||||
- load-code/dotnet/load-assembly-via-iassembly still@teamt5.org
|
||||
- malware-family/donut-loader/load-shellcode-via-donut still@teamt5.org
|
||||
- nursery/disable-device-guard-features-via-registry-on-windows mehunhoff@google.com
|
||||
- nursery/disable-firewall-features-via-registry-on-windows mehunhoff@google.com
|
||||
- nursery/disable-system-restore-features-via-registry-on-windows mehunhoff@google.com
|
||||
- nursery/disable-windows-defender-features-via-registry-on-windows mehunhoff@google.com
|
||||
- host-interaction/file-system/write/clear-file-content jakeperalta7
|
||||
- host-interaction/filter/unload-minifilter-driver JakePeralta7
|
||||
- exploitation/enumeration/make-suspicious-ntquerysysteminformation-call zdw@google.com
|
||||
- exploitation/gadgets/load-ntoskrnl zdw@google.com
|
||||
- exploitation/gadgets/resolve-ntoskrnl-gadgets zdw@google.com
|
||||
- exploitation/spraying/make-suspicious-ntfscontrolfile-call zdw@google.com
|
||||
- anti-analysis/anti-forensic/unload-sysmon JakePeralta7
|
||||
|
||||
### Bug Fixes
|
||||
- cape: make some fields optional @williballenthin #2631 #2632
|
||||
- lint: add WARN for regex features that contain unescaped dot #2635
|
||||
- lint: add ERROR for incomplete registry control set regex #2643
|
||||
- binja: update unit test core version #2670
|
||||
|
||||
### Raw diffs
|
||||
- [capa v9.1.0...v9.2.0](https://github.com/mandiant/capa/compare/v9.1.0...v9.2.0)
|
||||
- [capa-rules v9.1.0...v9.2.0](https://github.com/mandiant/capa-rules/compare/v9.1.0...v9.2.0)
|
||||
|
||||
## v9.1.0
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@ import capa.features.extractors.cape.file
|
||||
import capa.features.extractors.cape.thread
|
||||
import capa.features.extractors.cape.global_
|
||||
import capa.features.extractors.cape.process
|
||||
from capa.exceptions import EmptyReportError
|
||||
from capa.exceptions import EmptyReportError, UnsupportedFormatError
|
||||
from capa.features.common import Feature
|
||||
from capa.features.address import NO_ADDRESS, Address, AbsoluteVirtualAddress, _NoAddress
|
||||
from capa.features.address import Address, AbsoluteVirtualAddress, _NoAddress
|
||||
from capa.features.extractors.cape.models import Call, Static, Process, CapeReport
|
||||
from capa.features.extractors.base_extractor import (
|
||||
CallHandle,
|
||||
@@ -53,14 +53,9 @@ class CapeExtractor(DynamicFeatureExtractor):
|
||||
self.global_features = list(capa.features.extractors.cape.global_.extract_features(self.report))
|
||||
|
||||
def get_base_address(self) -> Union[AbsoluteVirtualAddress, _NoAddress, None]:
|
||||
if self.report.static is None:
|
||||
return NO_ADDRESS
|
||||
|
||||
if self.report.static.pe is None:
|
||||
# TODO: handle ELF
|
||||
return NO_ADDRESS
|
||||
|
||||
# value according to the PE header, the actual trace may use a different imagebase
|
||||
assert self.report.static is not None
|
||||
assert self.report.static.pe is not None
|
||||
return AbsoluteVirtualAddress(self.report.static.pe.imagebase)
|
||||
|
||||
def extract_global_features(self) -> Iterator[tuple[Feature, Address]]:
|
||||
@@ -125,10 +120,8 @@ class CapeExtractor(DynamicFeatureExtractor):
|
||||
parts.append(" -> ")
|
||||
if call.pretty_return:
|
||||
parts.append(call.pretty_return)
|
||||
elif call.return_:
|
||||
parts.append(hex(call.return_))
|
||||
else:
|
||||
parts.append("?")
|
||||
parts.append(hex(call.return_))
|
||||
|
||||
return "".join(parts)
|
||||
|
||||
@@ -139,11 +132,25 @@ class CapeExtractor(DynamicFeatureExtractor):
|
||||
if cr.info.version not in TESTED_VERSIONS:
|
||||
logger.warning("CAPE version '%s' not tested/supported yet", cr.info.version)
|
||||
|
||||
# TODO(mr-tz): support more file types
|
||||
# https://github.com/mandiant/capa/issues/1933
|
||||
if "PE" not in cr.target.file.type:
|
||||
logger.error(
|
||||
"capa currently only supports PE target files, this target file's type is: '%s'.\nPlease report this at: https://github.com/mandiant/capa/issues/1933",
|
||||
cr.target.file.type,
|
||||
)
|
||||
|
||||
# observed in 2.4-CAPE reports from capesandbox.com
|
||||
if cr.static is None and cr.target.file.pe is not None:
|
||||
cr.static = Static()
|
||||
cr.static.pe = cr.target.file.pe
|
||||
|
||||
if cr.static is None:
|
||||
raise UnsupportedFormatError("CAPE report missing static analysis")
|
||||
|
||||
if cr.static.pe is None:
|
||||
raise UnsupportedFormatError("CAPE report missing PE analysis")
|
||||
|
||||
if len(cr.behavior.processes) == 0:
|
||||
raise EmptyReportError("CAPE did not capture any processes")
|
||||
|
||||
|
||||
@@ -32,13 +32,7 @@ def get_processes(report: CapeReport) -> Iterator[ProcessHandle]:
|
||||
"""
|
||||
seen_processes = {}
|
||||
for process in report.behavior.processes:
|
||||
if process.parent_id is None:
|
||||
# on CAPE for Linux, the root process may have no parent id, so we set that to 0
|
||||
ppid = 0
|
||||
else:
|
||||
ppid = process.parent_id
|
||||
|
||||
addr = ProcessAddress(pid=process.process_id, ppid=ppid)
|
||||
addr = ProcessAddress(pid=process.process_id, ppid=process.parent_id)
|
||||
yield ProcessHandle(address=addr, inner=process)
|
||||
|
||||
# check for pid and ppid reuse
|
||||
@@ -58,13 +52,7 @@ def extract_import_names(report: CapeReport) -> Iterator[tuple[Feature, Address]
|
||||
"""
|
||||
extract imported function names
|
||||
"""
|
||||
if report.static is None:
|
||||
return
|
||||
|
||||
if report.static.pe is None:
|
||||
# TODO: elf
|
||||
return
|
||||
|
||||
assert report.static is not None and report.static.pe is not None
|
||||
imports = report.static.pe.imports
|
||||
|
||||
if isinstance(imports, dict):
|
||||
@@ -82,25 +70,13 @@ def extract_import_names(report: CapeReport) -> Iterator[tuple[Feature, Address]
|
||||
|
||||
|
||||
def extract_export_names(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
|
||||
if report.static is None:
|
||||
return
|
||||
|
||||
if report.static.pe is None:
|
||||
# TODO: elf
|
||||
return
|
||||
|
||||
assert report.static is not None and report.static.pe is not None
|
||||
for function in report.static.pe.exports:
|
||||
yield Export(function.name), AbsoluteVirtualAddress(function.address)
|
||||
|
||||
|
||||
def extract_section_names(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
|
||||
if report.static is None:
|
||||
return
|
||||
|
||||
if report.static.pe is None:
|
||||
# TODO: elf
|
||||
return
|
||||
|
||||
assert report.static is not None and report.static.pe is not None
|
||||
for section in report.static.pe.sections:
|
||||
yield Section(section.name), AbsoluteVirtualAddress(section.virtual_address)
|
||||
|
||||
|
||||
@@ -42,6 +42,9 @@ def extract_arch(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
|
||||
yield Arch(ARCH_AMD64), NO_ADDRESS
|
||||
else:
|
||||
logger.warning("unrecognized Architecture: %s", report.target.file.type)
|
||||
raise ValueError(
|
||||
f"unrecognized Architecture from the CAPE report; output of file command: {report.target.file.type}"
|
||||
)
|
||||
|
||||
|
||||
def extract_format(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
|
||||
@@ -51,6 +54,9 @@ def extract_format(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
|
||||
yield Format(FORMAT_ELF), NO_ADDRESS
|
||||
else:
|
||||
logger.warning("unknown file format, file command output: %s", report.target.file.type)
|
||||
raise ValueError(
|
||||
f"unrecognized file format from the CAPE report; output of file command: {report.target.file.type}"
|
||||
)
|
||||
|
||||
|
||||
def extract_os(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
|
||||
@@ -74,10 +80,7 @@ def extract_os(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
|
||||
else:
|
||||
# if the operating system information is missing from the cape report, it's likely a bug
|
||||
logger.warning("unrecognized OS: %s", file_output)
|
||||
elif report.info.machine and report.info.machine.platform == "windows":
|
||||
yield OS(OS_WINDOWS), NO_ADDRESS
|
||||
elif report.info.machine and report.info.machine.platform == "linux":
|
||||
yield OS(OS_LINUX), NO_ADDRESS
|
||||
raise ValueError(f"unrecognized OS from the CAPE report; output of file command: {file_output}")
|
||||
else:
|
||||
# the sample is shellcode
|
||||
logger.debug("unsupported file format, file command output: %s", file_output)
|
||||
|
||||
@@ -29,26 +29,8 @@ def validate_hex_bytes(value):
|
||||
return bytes.fromhex(value) if isinstance(value, str) else value
|
||||
|
||||
|
||||
def validate_status_code(value):
|
||||
if isinstance(value, str):
|
||||
if value == "?":
|
||||
# TODO: check for this in the return handling
|
||||
return None
|
||||
|
||||
# like: -1 EINVAL (Invalid argument)
|
||||
# like: 0 (Timeout)
|
||||
# like: 0x8002 (flags O_RDWR|O_LARGEFILE)
|
||||
assert value.endswith(")")
|
||||
num = value.partition(" ")[0]
|
||||
return int(num, 16) if num.startswith("0x") else int(num, 10)
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
HexInt = Annotated[int, BeforeValidator(validate_hex_int)]
|
||||
HexBytes = Annotated[bytes, BeforeValidator(validate_hex_bytes)]
|
||||
# this is a status code, such as returned by CAPE for Linux, like: "0 (Timeout)" or "0x8002 (flags O_RDWR|O_LARGEFILE)
|
||||
StatusCode = Annotated[int | None, BeforeValidator(validate_status_code)]
|
||||
|
||||
|
||||
# a model that *cannot* have extra fields
|
||||
@@ -89,13 +71,8 @@ Emptydict: TypeAlias = BaseModel
|
||||
EmptyList: TypeAlias = list[Any]
|
||||
|
||||
|
||||
class Machine(FlexibleModel):
|
||||
platform: Optional[str] = None
|
||||
|
||||
|
||||
class Info(FlexibleModel):
|
||||
version: str
|
||||
machine: Optional[Machine] = None
|
||||
|
||||
|
||||
class ImportedSymbol(FlexibleModel):
|
||||
@@ -310,38 +287,16 @@ class Argument(FlexibleModel):
|
||||
pretty_value: Optional[str] = None
|
||||
|
||||
|
||||
def validate_argument(value):
|
||||
if isinstance(value, str):
|
||||
# for a few calls on CAPE for Linux, we see arguments like in this call:
|
||||
#
|
||||
# timestamp: "18:12:17.199276"
|
||||
# category: "misc"
|
||||
# api: "uname"
|
||||
# return: "0"
|
||||
# ▽ arguments:
|
||||
# [0]: "{sysname=\"Linux\", nodename=\"laptop\", ...}"
|
||||
#
|
||||
# which is just a string with a JSON-like thing inside,
|
||||
# that we want to map a default unnamed argument.
|
||||
return Argument(name="", value=value)
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
# mypy isn't happy about assigning to type
|
||||
Argument = Annotated[Argument, BeforeValidator(validate_argument)] # type: ignore
|
||||
|
||||
|
||||
class Call(FlexibleModel):
|
||||
# timestamp: str
|
||||
thread_id: int | None = None
|
||||
thread_id: int
|
||||
# category: str
|
||||
|
||||
api: str
|
||||
|
||||
arguments: list[Argument]
|
||||
# status: bool
|
||||
return_: HexInt | StatusCode = Field(alias="return")
|
||||
return_: HexInt = Field(alias="return")
|
||||
pretty_return: Optional[str] = None
|
||||
|
||||
# repeated: int
|
||||
@@ -360,12 +315,12 @@ class Call(FlexibleModel):
|
||||
class Process(FlexibleModel):
|
||||
process_id: int
|
||||
process_name: str
|
||||
parent_id: int | None
|
||||
parent_id: int
|
||||
# module_path: str
|
||||
# first_seen: str
|
||||
calls: list[Call]
|
||||
threads: list[int] | None = None # this can be None for CAPE for Linux, which doesn't track threads.
|
||||
environ: dict[str, str] = Field(default_factory=dict) # type: ignore
|
||||
threads: list[int]
|
||||
environ: dict[str, str]
|
||||
|
||||
|
||||
"""
|
||||
|
||||
@@ -29,13 +29,6 @@ def get_threads(ph: ProcessHandle) -> Iterator[ThreadHandle]:
|
||||
get the threads associated with a given process
|
||||
"""
|
||||
process: Process = ph.inner
|
||||
|
||||
if not process.threads:
|
||||
# CAPE for linux doesn't record threads
|
||||
# so we return a default 0 value
|
||||
yield ThreadHandle(address=ThreadAddress(process=ph.address, tid=0), inner={})
|
||||
return
|
||||
|
||||
threads: list[int] = process.threads
|
||||
|
||||
for thread in threads:
|
||||
@@ -49,9 +42,6 @@ def extract_environ_strings(ph: ProcessHandle) -> Iterator[tuple[Feature, Addres
|
||||
"""
|
||||
process: Process = ph.inner
|
||||
|
||||
if not process.environ:
|
||||
return
|
||||
|
||||
for value in (value for value in process.environ.values() if value):
|
||||
yield String(value), ph.address
|
||||
|
||||
|
||||
@@ -29,16 +29,8 @@ def get_calls(ph: ProcessHandle, th: ThreadHandle) -> Iterator[CallHandle]:
|
||||
|
||||
tid = th.address.tid
|
||||
for call_index, call in enumerate(process.calls):
|
||||
|
||||
if call.thread_id is None:
|
||||
# CAPE for linux doesn't record threads
|
||||
# so this must be the 0 value
|
||||
# and we'll enumerate all the calls in this process
|
||||
assert tid == 0
|
||||
|
||||
else:
|
||||
if call.thread_id != tid:
|
||||
continue
|
||||
if call.thread_id != tid:
|
||||
continue
|
||||
|
||||
for symbol in generate_symbols("", call.api):
|
||||
call.api = symbol
|
||||
|
||||
@@ -96,14 +96,7 @@ class VMRayAnalysis:
|
||||
% (self.submission_name, self.submission_type)
|
||||
)
|
||||
|
||||
if self.submission_static is not None:
|
||||
if self.submission_static.pe is None and self.submission_static.elf is None:
|
||||
# we only support static analysis for PE and ELF files for now
|
||||
raise UnsupportedFormatError(
|
||||
"archive does not contain a supported file format (submission_name: %s, submission_type: %s)"
|
||||
% (self.submission_name, self.submission_type)
|
||||
)
|
||||
else:
|
||||
if self.submission_static is None:
|
||||
# VMRay may not record static analysis for certain file types, e.g. MSI, but we'd still like to match dynamic
|
||||
# execution so we continue without and accept that the results may be incomplete
|
||||
logger.warning(
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
__version__ = "9.1.0"
|
||||
__version__ = "9.2.0"
|
||||
|
||||
|
||||
def get_major_version():
|
||||
|
||||
@@ -121,11 +121,11 @@ dev = [
|
||||
# we want all developer environments to be consistent.
|
||||
# These dependencies are not used in production environments
|
||||
# and should not conflict with other libraries/tooling.
|
||||
"pre-commit==4.1.0",
|
||||
"pre-commit==4.2.0",
|
||||
"pytest==8.0.0",
|
||||
"pytest-sugar==1.0.0",
|
||||
"pytest-instafail==0.5.0",
|
||||
"flake8==7.1.1",
|
||||
"flake8==7.2.0",
|
||||
"flake8-bugbear==24.12.12",
|
||||
"flake8-encodings==0.5.1",
|
||||
"flake8-comprehensions==3.16.0",
|
||||
@@ -133,7 +133,7 @@ dev = [
|
||||
"flake8-no-implicit-concat==0.3.5",
|
||||
"flake8-print==5.0.0",
|
||||
"flake8-todos==0.3.1",
|
||||
"flake8-simplify==0.21.0",
|
||||
"flake8-simplify==0.22.0",
|
||||
"flake8-use-pathlib==0.3.0",
|
||||
"flake8-copyright==0.2.4",
|
||||
"ruff==0.11.0",
|
||||
@@ -157,7 +157,7 @@ build = [
|
||||
# These dependencies are not used in production environments
|
||||
# and should not conflict with other libraries/tooling.
|
||||
"pyinstaller==6.12.0",
|
||||
"setuptools==76.0.0",
|
||||
"setuptools==80.9.0",
|
||||
"build==1.2.2"
|
||||
]
|
||||
scripts = [
|
||||
|
||||
@@ -21,12 +21,12 @@ mdurl==0.1.2
|
||||
msgpack==1.0.8
|
||||
networkx==3.4.2
|
||||
pefile==2024.8.26
|
||||
pip==25.0
|
||||
pip==25.1.1
|
||||
protobuf==6.30.1
|
||||
pyasn1==0.5.1
|
||||
pyasn1-modules==0.3.0
|
||||
pycparser==2.22
|
||||
pydantic==2.10.1
|
||||
pydantic==2.11.4
|
||||
# pydantic pins pydantic-core,
|
||||
# but dependabot updates these separately (which is broken) and is annoying,
|
||||
# so we rely on pydantic to pull in the right version of pydantic-core.
|
||||
@@ -36,10 +36,10 @@ pyelftools==0.32
|
||||
pygments==2.19.1
|
||||
python-flirt==0.9.2
|
||||
pyyaml==6.0.2
|
||||
rich==13.9.2
|
||||
rich==14.0.0
|
||||
ruamel-yaml==0.18.6
|
||||
ruamel-yaml-clib==0.2.8
|
||||
setuptools==76.0.0
|
||||
setuptools==80.9.0
|
||||
six==1.17.0
|
||||
sortedcontainers==2.4.0
|
||||
viv-utils==0.8.0
|
||||
|
||||
2
rules
2
rules
Submodule rules updated: e85887a875...edabdffa8c
@@ -175,8 +175,6 @@ def convert_rule(rule, rulename, cround, depth):
|
||||
depth += 1
|
||||
logger.info("recursion depth: %d", depth)
|
||||
|
||||
global var_names
|
||||
|
||||
def do_statement(s_type, kid):
|
||||
yara_strings = ""
|
||||
yara_condition = ""
|
||||
|
||||
@@ -406,6 +406,7 @@ class DoesntMatchExample(Lint):
|
||||
return True
|
||||
|
||||
if rule.name not in capabilities:
|
||||
logger.info('rule "%s" does not match for sample %s', rule.name, example_id)
|
||||
return True
|
||||
|
||||
|
||||
@@ -721,6 +722,29 @@ class FeatureStringTooShort(Lint):
|
||||
return False
|
||||
|
||||
|
||||
class FeatureRegexRegistryControlSetMatchIncomplete(Lint):
|
||||
name = "feature regex registry control set match incomplete"
|
||||
recommendation = (
|
||||
'use "(ControlSet\\d{3}|CurrentControlSet)" to match both indirect references '
|
||||
+ 'via "CurrentControlSet" and direct references via "ControlSetXXX"'
|
||||
)
|
||||
|
||||
def check_features(self, ctx: Context, features: list[Feature]):
|
||||
for feature in features:
|
||||
if not isinstance(feature, (Regex,)):
|
||||
continue
|
||||
|
||||
assert isinstance(feature.value, str)
|
||||
|
||||
pat = feature.value.lower()
|
||||
|
||||
if "system\\\\" in pat and "controlset" in pat or "currentcontrolset" in pat:
|
||||
if "system\\\\(controlset\\d{3}|currentcontrolset)" not in pat:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class FeatureRegexContainsUnescapedPeriod(Lint):
|
||||
name = "feature regex contains unescaped period"
|
||||
recommendation_template = 'escape the period in "{:s}" unless it should be treated as a regex dot operator'
|
||||
@@ -983,6 +1007,7 @@ FEATURE_LINTS = (
|
||||
FeatureNegativeNumber(),
|
||||
FeatureNtdllNtoskrnlApi(),
|
||||
FeatureRegexContainsUnescapedPeriod(),
|
||||
FeatureRegexRegistryControlSetMatchIncomplete(),
|
||||
)
|
||||
|
||||
|
||||
|
||||
Submodule tests/data updated: 6cb0838954...5ae1804c85
@@ -70,4 +70,4 @@ def test_standalone_binja_backend():
|
||||
@pytest.mark.skipif(binja_present is False, reason="Skip binja tests if the binaryninja Python API is not installed")
|
||||
def test_binja_version():
|
||||
version = binaryninja.core_version_info()
|
||||
assert version.major == 4 and version.minor == 2
|
||||
assert version.major == 5 and version.minor == 0
|
||||
|
||||
86
web/explorer/package-lock.json
generated
86
web/explorer/package-lock.json
generated
@@ -27,7 +27,7 @@
|
||||
"eslint-plugin-vue": "^9.23.0",
|
||||
"jsdom": "^24.1.0",
|
||||
"prettier": "^3.2.5",
|
||||
"vite": "^6.2.2",
|
||||
"vite": "^6.3.4",
|
||||
"vite-plugin-singlefile": "^2.2.0",
|
||||
"vitest": "^3.0.9"
|
||||
}
|
||||
@@ -3426,6 +3426,51 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.13",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
|
||||
"integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fdir": "^6.4.4",
|
||||
"picomatch": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby/node_modules/fdir": {
|
||||
"version": "6.4.4",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
|
||||
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"picomatch": "^3 || ^4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"picomatch": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby/node_modules/picomatch": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/tinypool": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz",
|
||||
@@ -3561,15 +3606,18 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.2.tgz",
|
||||
"integrity": "sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ==",
|
||||
"version": "6.3.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.4.tgz",
|
||||
"integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
"picomatch": "^4.0.2",
|
||||
"postcss": "^8.5.3",
|
||||
"rollup": "^4.30.1"
|
||||
"rollup": "^4.34.9",
|
||||
"tinyglobby": "^0.2.13"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
@@ -3672,6 +3720,34 @@
|
||||
"vite": "^5.4.11 || ^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/fdir": {
|
||||
"version": "6.4.4",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
|
||||
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"picomatch": "^3 || ^4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"picomatch": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/picomatch": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.9.tgz",
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"eslint-plugin-vue": "^9.23.0",
|
||||
"jsdom": "^24.1.0",
|
||||
"prettier": "^3.2.5",
|
||||
"vite": "^6.2.2",
|
||||
"vite": "^6.3.4",
|
||||
"vite-plugin-singlefile": "^2.2.0",
|
||||
"vitest": "^3.0.9"
|
||||
}
|
||||
|
||||
@@ -239,6 +239,12 @@
|
||||
|
||||
<h2 class="mt-3">Tool Updates</h2>
|
||||
|
||||
<h3 class="mt-2">v9.2.0 (<em>2025-06-03</em>)</h3>
|
||||
<p class="mt-0">
|
||||
This release improves a few aspects of dynamic analysis, including relaxing our validation on fields across many CAPE versions and processing additional VMRay submission file types, for example.
|
||||
It also includes an updated rule pack containing new rules and rule fixes.
|
||||
</p>
|
||||
|
||||
<h3 class="mt-2">v9.1.0 (<em>2025-03-02</em>)</h3>
|
||||
<p class="mt-0">
|
||||
This release improves a few aspects of dynamic analysis, relaxing our validation on fields across many CAPE versions, for example.
|
||||
|
||||
Reference in New Issue
Block a user