oscura 0.5.1__py3-none-any.whl → 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscura/__init__.py +169 -167
- oscura/analyzers/__init__.py +3 -0
- oscura/analyzers/classification.py +659 -0
- oscura/analyzers/digital/edges.py +325 -65
- oscura/analyzers/digital/quality.py +293 -166
- oscura/analyzers/digital/timing.py +260 -115
- oscura/analyzers/digital/timing_numba.py +334 -0
- oscura/analyzers/entropy.py +605 -0
- oscura/analyzers/eye/diagram.py +176 -109
- oscura/analyzers/eye/metrics.py +5 -5
- oscura/analyzers/jitter/__init__.py +6 -4
- oscura/analyzers/jitter/ber.py +52 -52
- oscura/analyzers/jitter/classification.py +156 -0
- oscura/analyzers/jitter/decomposition.py +163 -113
- oscura/analyzers/jitter/spectrum.py +80 -64
- oscura/analyzers/ml/__init__.py +39 -0
- oscura/analyzers/ml/features.py +600 -0
- oscura/analyzers/ml/signal_classifier.py +604 -0
- oscura/analyzers/packet/daq.py +246 -158
- oscura/analyzers/packet/parser.py +12 -1
- oscura/analyzers/packet/payload.py +50 -2110
- oscura/analyzers/packet/payload_analysis.py +361 -181
- oscura/analyzers/packet/payload_patterns.py +133 -70
- oscura/analyzers/packet/stream.py +84 -23
- oscura/analyzers/patterns/__init__.py +26 -5
- oscura/analyzers/patterns/anomaly_detection.py +908 -0
- oscura/analyzers/patterns/clustering.py +169 -108
- oscura/analyzers/patterns/clustering_optimized.py +227 -0
- oscura/analyzers/patterns/discovery.py +1 -1
- oscura/analyzers/patterns/matching.py +581 -197
- oscura/analyzers/patterns/pattern_mining.py +778 -0
- oscura/analyzers/patterns/periodic.py +121 -38
- oscura/analyzers/patterns/sequences.py +175 -78
- oscura/analyzers/power/conduction.py +1 -1
- oscura/analyzers/power/soa.py +6 -6
- oscura/analyzers/power/switching.py +250 -110
- oscura/analyzers/protocol/__init__.py +17 -1
- oscura/analyzers/protocols/base.py +6 -6
- oscura/analyzers/protocols/ble/__init__.py +38 -0
- oscura/analyzers/protocols/ble/analyzer.py +809 -0
- oscura/analyzers/protocols/ble/uuids.py +288 -0
- oscura/analyzers/protocols/can.py +257 -127
- oscura/analyzers/protocols/can_fd.py +107 -80
- oscura/analyzers/protocols/flexray.py +139 -80
- oscura/analyzers/protocols/hdlc.py +93 -58
- oscura/analyzers/protocols/i2c.py +247 -106
- oscura/analyzers/protocols/i2s.py +138 -86
- oscura/analyzers/protocols/industrial/__init__.py +40 -0
- oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
- oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
- oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
- oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
- oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
- oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
- oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
- oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
- oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
- oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
- oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
- oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
- oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
- oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
- oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
- oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
- oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
- oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
- oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
- oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
- oscura/analyzers/protocols/jtag.py +180 -98
- oscura/analyzers/protocols/lin.py +219 -114
- oscura/analyzers/protocols/manchester.py +4 -4
- oscura/analyzers/protocols/onewire.py +253 -149
- oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
- oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
- oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
- oscura/analyzers/protocols/spi.py +192 -95
- oscura/analyzers/protocols/swd.py +321 -167
- oscura/analyzers/protocols/uart.py +267 -125
- oscura/analyzers/protocols/usb.py +235 -131
- oscura/analyzers/side_channel/power.py +17 -12
- oscura/analyzers/signal/__init__.py +15 -0
- oscura/analyzers/signal/timing_analysis.py +1086 -0
- oscura/analyzers/signal_integrity/__init__.py +4 -1
- oscura/analyzers/signal_integrity/sparams.py +2 -19
- oscura/analyzers/spectral/chunked.py +129 -60
- oscura/analyzers/spectral/chunked_fft.py +300 -94
- oscura/analyzers/spectral/chunked_wavelet.py +100 -80
- oscura/analyzers/statistical/checksum.py +376 -217
- oscura/analyzers/statistical/classification.py +229 -107
- oscura/analyzers/statistical/entropy.py +78 -53
- oscura/analyzers/statistics/correlation.py +407 -211
- oscura/analyzers/statistics/outliers.py +2 -2
- oscura/analyzers/statistics/streaming.py +30 -5
- oscura/analyzers/validation.py +216 -101
- oscura/analyzers/waveform/measurements.py +9 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
- oscura/analyzers/waveform/spectral.py +500 -228
- oscura/api/__init__.py +31 -5
- oscura/api/dsl/__init__.py +582 -0
- oscura/{dsl → api/dsl}/commands.py +43 -76
- oscura/{dsl → api/dsl}/interpreter.py +26 -51
- oscura/{dsl → api/dsl}/parser.py +107 -77
- oscura/{dsl → api/dsl}/repl.py +2 -2
- oscura/api/dsl.py +1 -1
- oscura/{integrations → api/integrations}/__init__.py +1 -1
- oscura/{integrations → api/integrations}/llm.py +201 -102
- oscura/api/operators.py +3 -3
- oscura/api/optimization.py +144 -30
- oscura/api/rest_server.py +921 -0
- oscura/api/server/__init__.py +17 -0
- oscura/api/server/dashboard.py +850 -0
- oscura/api/server/static/README.md +34 -0
- oscura/api/server/templates/base.html +181 -0
- oscura/api/server/templates/export.html +120 -0
- oscura/api/server/templates/home.html +284 -0
- oscura/api/server/templates/protocols.html +58 -0
- oscura/api/server/templates/reports.html +43 -0
- oscura/api/server/templates/session_detail.html +89 -0
- oscura/api/server/templates/sessions.html +83 -0
- oscura/api/server/templates/waveforms.html +73 -0
- oscura/automotive/__init__.py +8 -1
- oscura/automotive/can/__init__.py +10 -0
- oscura/automotive/can/checksum.py +3 -1
- oscura/automotive/can/dbc_generator.py +590 -0
- oscura/automotive/can/message_wrapper.py +121 -74
- oscura/automotive/can/patterns.py +98 -21
- oscura/automotive/can/session.py +292 -56
- oscura/automotive/can/state_machine.py +6 -3
- oscura/automotive/can/stimulus_response.py +97 -75
- oscura/automotive/dbc/__init__.py +10 -2
- oscura/automotive/dbc/generator.py +84 -56
- oscura/automotive/dbc/parser.py +6 -6
- oscura/automotive/dtc/data.json +17 -102
- oscura/automotive/dtc/database.py +2 -2
- oscura/automotive/flexray/__init__.py +31 -0
- oscura/automotive/flexray/analyzer.py +504 -0
- oscura/automotive/flexray/crc.py +185 -0
- oscura/automotive/flexray/fibex.py +449 -0
- oscura/automotive/j1939/__init__.py +45 -8
- oscura/automotive/j1939/analyzer.py +605 -0
- oscura/automotive/j1939/spns.py +326 -0
- oscura/automotive/j1939/transport.py +306 -0
- oscura/automotive/lin/__init__.py +47 -0
- oscura/automotive/lin/analyzer.py +612 -0
- oscura/automotive/loaders/blf.py +13 -2
- oscura/automotive/loaders/csv_can.py +143 -72
- oscura/automotive/loaders/dispatcher.py +50 -2
- oscura/automotive/loaders/mdf.py +86 -45
- oscura/automotive/loaders/pcap.py +111 -61
- oscura/automotive/uds/__init__.py +4 -0
- oscura/automotive/uds/analyzer.py +725 -0
- oscura/automotive/uds/decoder.py +140 -58
- oscura/automotive/uds/models.py +7 -1
- oscura/automotive/visualization.py +1 -1
- oscura/cli/analyze.py +348 -0
- oscura/cli/batch.py +142 -122
- oscura/cli/benchmark.py +275 -0
- oscura/cli/characterize.py +137 -82
- oscura/cli/compare.py +224 -131
- oscura/cli/completion.py +250 -0
- oscura/cli/config_cmd.py +361 -0
- oscura/cli/decode.py +164 -87
- oscura/cli/export.py +286 -0
- oscura/cli/main.py +115 -31
- oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
- oscura/{onboarding → cli/onboarding}/help.py +80 -58
- oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
- oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
- oscura/cli/progress.py +147 -0
- oscura/cli/shell.py +157 -135
- oscura/cli/validate_cmd.py +204 -0
- oscura/cli/visualize.py +158 -0
- oscura/convenience.py +125 -79
- oscura/core/__init__.py +4 -2
- oscura/core/backend_selector.py +3 -3
- oscura/core/cache.py +126 -15
- oscura/core/cancellation.py +1 -1
- oscura/{config → core/config}/__init__.py +20 -11
- oscura/{config → core/config}/defaults.py +1 -1
- oscura/{config → core/config}/loader.py +7 -5
- oscura/{config → core/config}/memory.py +5 -5
- oscura/{config → core/config}/migration.py +1 -1
- oscura/{config → core/config}/pipeline.py +99 -23
- oscura/{config → core/config}/preferences.py +1 -1
- oscura/{config → core/config}/protocol.py +3 -3
- oscura/{config → core/config}/schema.py +426 -272
- oscura/{config → core/config}/settings.py +1 -1
- oscura/{config → core/config}/thresholds.py +195 -153
- oscura/core/correlation.py +5 -6
- oscura/core/cross_domain.py +0 -2
- oscura/core/debug.py +9 -5
- oscura/{extensibility → core/extensibility}/docs.py +158 -70
- oscura/{extensibility → core/extensibility}/extensions.py +160 -76
- oscura/{extensibility → core/extensibility}/logging.py +1 -1
- oscura/{extensibility → core/extensibility}/measurements.py +1 -1
- oscura/{extensibility → core/extensibility}/plugins.py +1 -1
- oscura/{extensibility → core/extensibility}/templates.py +73 -3
- oscura/{extensibility → core/extensibility}/validation.py +1 -1
- oscura/core/gpu_backend.py +11 -7
- oscura/core/log_query.py +101 -11
- oscura/core/logging.py +126 -54
- oscura/core/logging_advanced.py +5 -5
- oscura/core/memory_limits.py +108 -70
- oscura/core/memory_monitor.py +2 -2
- oscura/core/memory_progress.py +7 -7
- oscura/core/memory_warnings.py +1 -1
- oscura/core/numba_backend.py +13 -13
- oscura/{plugins → core/plugins}/__init__.py +9 -9
- oscura/{plugins → core/plugins}/base.py +7 -7
- oscura/{plugins → core/plugins}/cli.py +3 -3
- oscura/{plugins → core/plugins}/discovery.py +186 -106
- oscura/{plugins → core/plugins}/lifecycle.py +1 -1
- oscura/{plugins → core/plugins}/manager.py +7 -7
- oscura/{plugins → core/plugins}/registry.py +3 -3
- oscura/{plugins → core/plugins}/versioning.py +1 -1
- oscura/core/progress.py +16 -1
- oscura/core/provenance.py +8 -2
- oscura/{schemas → core/schemas}/__init__.py +2 -2
- oscura/{schemas → core/schemas}/device_mapping.json +2 -8
- oscura/{schemas → core/schemas}/packet_format.json +4 -24
- oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
- oscura/core/types.py +4 -0
- oscura/core/uncertainty.py +3 -3
- oscura/correlation/__init__.py +52 -0
- oscura/correlation/multi_protocol.py +811 -0
- oscura/discovery/auto_decoder.py +117 -35
- oscura/discovery/comparison.py +191 -86
- oscura/discovery/quality_validator.py +155 -68
- oscura/discovery/signal_detector.py +196 -79
- oscura/export/__init__.py +18 -8
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- oscura/export/wireshark/generator.py +1 -1
- oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
- oscura/export/wireshark_dissector.py +746 -0
- oscura/guidance/wizard.py +207 -111
- oscura/hardware/__init__.py +19 -0
- oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
- oscura/{acquisition → hardware/acquisition}/file.py +2 -2
- oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
- oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
- oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
- oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
- oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
- oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
- oscura/hardware/firmware/__init__.py +29 -0
- oscura/hardware/firmware/pattern_recognition.py +874 -0
- oscura/hardware/hal_detector.py +736 -0
- oscura/hardware/security/__init__.py +37 -0
- oscura/hardware/security/side_channel_detector.py +1126 -0
- oscura/inference/__init__.py +4 -0
- oscura/inference/active_learning/observation_table.py +4 -1
- oscura/inference/alignment.py +216 -123
- oscura/inference/bayesian.py +113 -33
- oscura/inference/crc_reverse.py +101 -55
- oscura/inference/logic.py +6 -2
- oscura/inference/message_format.py +342 -183
- oscura/inference/protocol.py +95 -44
- oscura/inference/protocol_dsl.py +180 -82
- oscura/inference/signal_intelligence.py +1439 -706
- oscura/inference/spectral.py +99 -57
- oscura/inference/state_machine.py +810 -158
- oscura/inference/stream.py +270 -110
- oscura/iot/__init__.py +34 -0
- oscura/iot/coap/__init__.py +32 -0
- oscura/iot/coap/analyzer.py +668 -0
- oscura/iot/coap/options.py +212 -0
- oscura/iot/lorawan/__init__.py +21 -0
- oscura/iot/lorawan/crypto.py +206 -0
- oscura/iot/lorawan/decoder.py +801 -0
- oscura/iot/lorawan/mac_commands.py +341 -0
- oscura/iot/mqtt/__init__.py +27 -0
- oscura/iot/mqtt/analyzer.py +999 -0
- oscura/iot/mqtt/properties.py +315 -0
- oscura/iot/zigbee/__init__.py +31 -0
- oscura/iot/zigbee/analyzer.py +615 -0
- oscura/iot/zigbee/security.py +153 -0
- oscura/iot/zigbee/zcl.py +349 -0
- oscura/jupyter/display.py +125 -45
- oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
- oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
- oscura/jupyter/exploratory/fuzzy.py +746 -0
- oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
- oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
- oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
- oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
- oscura/jupyter/exploratory/sync.py +612 -0
- oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
- oscura/jupyter/magic.py +4 -4
- oscura/{ui → jupyter/ui}/__init__.py +2 -2
- oscura/{ui → jupyter/ui}/formatters.py +3 -3
- oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
- oscura/loaders/__init__.py +183 -67
- oscura/loaders/binary.py +88 -1
- oscura/loaders/chipwhisperer.py +153 -137
- oscura/loaders/configurable.py +208 -86
- oscura/loaders/csv_loader.py +458 -215
- oscura/loaders/hdf5_loader.py +278 -119
- oscura/loaders/lazy.py +87 -54
- oscura/loaders/mmap_loader.py +1 -1
- oscura/loaders/numpy_loader.py +253 -116
- oscura/loaders/pcap.py +226 -151
- oscura/loaders/rigol.py +110 -49
- oscura/loaders/sigrok.py +201 -78
- oscura/loaders/tdms.py +81 -58
- oscura/loaders/tektronix.py +291 -174
- oscura/loaders/touchstone.py +182 -87
- oscura/loaders/tss.py +456 -0
- oscura/loaders/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -0
- oscura/reporting/analyze.py +352 -146
- oscura/reporting/argument_preparer.py +69 -14
- oscura/reporting/auto_report.py +97 -61
- oscura/reporting/batch.py +131 -58
- oscura/reporting/chart_selection.py +57 -45
- oscura/reporting/comparison.py +63 -17
- oscura/reporting/content/executive.py +76 -24
- oscura/reporting/core_formats/multi_format.py +11 -8
- oscura/reporting/engine.py +312 -158
- oscura/reporting/enhanced_reports.py +949 -0
- oscura/reporting/export.py +86 -43
- oscura/reporting/formatting/numbers.py +69 -42
- oscura/reporting/html.py +139 -58
- oscura/reporting/index.py +137 -65
- oscura/reporting/output.py +158 -67
- oscura/reporting/pdf.py +67 -102
- oscura/reporting/plots.py +191 -112
- oscura/reporting/sections.py +88 -47
- oscura/reporting/standards.py +104 -61
- oscura/reporting/summary_generator.py +75 -55
- oscura/reporting/tables.py +138 -54
- oscura/reporting/templates/enhanced/protocol_re.html +525 -0
- oscura/sessions/__init__.py +14 -23
- oscura/sessions/base.py +3 -3
- oscura/sessions/blackbox.py +106 -10
- oscura/sessions/generic.py +2 -2
- oscura/sessions/legacy.py +783 -0
- oscura/side_channel/__init__.py +63 -0
- oscura/side_channel/dpa.py +1025 -0
- oscura/utils/__init__.py +15 -1
- oscura/utils/bitwise.py +118 -0
- oscura/{builders → utils/builders}/__init__.py +1 -1
- oscura/{comparison → utils/comparison}/__init__.py +6 -6
- oscura/{comparison → utils/comparison}/compare.py +202 -101
- oscura/{comparison → utils/comparison}/golden.py +83 -63
- oscura/{comparison → utils/comparison}/limits.py +313 -89
- oscura/{comparison → utils/comparison}/mask.py +151 -45
- oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
- oscura/{comparison → utils/comparison}/visualization.py +147 -89
- oscura/{component → utils/component}/__init__.py +3 -3
- oscura/{component → utils/component}/impedance.py +122 -58
- oscura/{component → utils/component}/reactive.py +165 -168
- oscura/{component → utils/component}/transmission_line.py +3 -3
- oscura/{filtering → utils/filtering}/__init__.py +6 -6
- oscura/{filtering → utils/filtering}/base.py +1 -1
- oscura/{filtering → utils/filtering}/convenience.py +2 -2
- oscura/{filtering → utils/filtering}/design.py +169 -93
- oscura/{filtering → utils/filtering}/filters.py +2 -2
- oscura/{filtering → utils/filtering}/introspection.py +2 -2
- oscura/utils/geometry.py +31 -0
- oscura/utils/imports.py +184 -0
- oscura/utils/lazy.py +1 -1
- oscura/{math → utils/math}/__init__.py +2 -2
- oscura/{math → utils/math}/arithmetic.py +114 -48
- oscura/{math → utils/math}/interpolation.py +139 -106
- oscura/utils/memory.py +129 -66
- oscura/utils/memory_advanced.py +92 -9
- oscura/utils/memory_extensions.py +10 -8
- oscura/{optimization → utils/optimization}/__init__.py +1 -1
- oscura/{optimization → utils/optimization}/search.py +2 -2
- oscura/utils/performance/__init__.py +58 -0
- oscura/utils/performance/caching.py +889 -0
- oscura/utils/performance/lsh_clustering.py +333 -0
- oscura/utils/performance/memory_optimizer.py +699 -0
- oscura/utils/performance/optimizations.py +675 -0
- oscura/utils/performance/parallel.py +654 -0
- oscura/utils/performance/profiling.py +661 -0
- oscura/{pipeline → utils/pipeline}/base.py +1 -1
- oscura/{pipeline → utils/pipeline}/composition.py +1 -1
- oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
- oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
- oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
- oscura/{search → utils/search}/__init__.py +3 -3
- oscura/{search → utils/search}/anomaly.py +188 -58
- oscura/utils/search/context.py +294 -0
- oscura/{search → utils/search}/pattern.py +138 -10
- oscura/utils/serial.py +51 -0
- oscura/utils/storage/__init__.py +61 -0
- oscura/utils/storage/database.py +1166 -0
- oscura/{streaming → utils/streaming}/chunked.py +302 -143
- oscura/{streaming → utils/streaming}/progressive.py +1 -1
- oscura/{streaming → utils/streaming}/realtime.py +3 -2
- oscura/{triggering → utils/triggering}/__init__.py +6 -6
- oscura/{triggering → utils/triggering}/base.py +6 -6
- oscura/{triggering → utils/triggering}/edge.py +2 -2
- oscura/{triggering → utils/triggering}/pattern.py +2 -2
- oscura/{triggering → utils/triggering}/pulse.py +115 -74
- oscura/{triggering → utils/triggering}/window.py +2 -2
- oscura/utils/validation.py +32 -0
- oscura/validation/__init__.py +121 -0
- oscura/{compliance → validation/compliance}/__init__.py +5 -5
- oscura/{compliance → validation/compliance}/advanced.py +5 -5
- oscura/{compliance → validation/compliance}/masks.py +1 -1
- oscura/{compliance → validation/compliance}/reporting.py +127 -53
- oscura/{compliance → validation/compliance}/testing.py +114 -52
- oscura/validation/compliance_tests.py +915 -0
- oscura/validation/fuzzer.py +990 -0
- oscura/validation/grammar_tests.py +596 -0
- oscura/validation/grammar_validator.py +904 -0
- oscura/validation/hil_testing.py +977 -0
- oscura/{quality → validation/quality}/__init__.py +4 -4
- oscura/{quality → validation/quality}/ensemble.py +251 -171
- oscura/{quality → validation/quality}/explainer.py +3 -3
- oscura/{quality → validation/quality}/scoring.py +1 -1
- oscura/{quality → validation/quality}/warnings.py +4 -4
- oscura/validation/regression_suite.py +808 -0
- oscura/validation/replay.py +788 -0
- oscura/{testing → validation/testing}/__init__.py +2 -2
- oscura/{testing → validation/testing}/synthetic.py +5 -5
- oscura/visualization/__init__.py +9 -0
- oscura/visualization/accessibility.py +1 -1
- oscura/visualization/annotations.py +64 -67
- oscura/visualization/colors.py +7 -7
- oscura/visualization/digital.py +180 -81
- oscura/visualization/eye.py +236 -85
- oscura/visualization/interactive.py +320 -143
- oscura/visualization/jitter.py +587 -247
- oscura/visualization/layout.py +169 -134
- oscura/visualization/optimization.py +103 -52
- oscura/visualization/palettes.py +1 -1
- oscura/visualization/power.py +427 -211
- oscura/visualization/power_extended.py +626 -297
- oscura/visualization/presets.py +2 -0
- oscura/visualization/protocols.py +495 -181
- oscura/visualization/render.py +79 -63
- oscura/visualization/reverse_engineering.py +171 -124
- oscura/visualization/signal_integrity.py +460 -279
- oscura/visualization/specialized.py +190 -100
- oscura/visualization/spectral.py +670 -255
- oscura/visualization/thumbnails.py +166 -137
- oscura/visualization/waveform.py +150 -63
- oscura/workflows/__init__.py +3 -0
- oscura/{batch → workflows/batch}/__init__.py +5 -5
- oscura/{batch → workflows/batch}/advanced.py +150 -75
- oscura/workflows/batch/aggregate.py +531 -0
- oscura/workflows/batch/analyze.py +236 -0
- oscura/{batch → workflows/batch}/logging.py +2 -2
- oscura/{batch → workflows/batch}/metrics.py +1 -1
- oscura/workflows/complete_re.py +1144 -0
- oscura/workflows/compliance.py +44 -54
- oscura/workflows/digital.py +197 -51
- oscura/workflows/legacy/__init__.py +12 -0
- oscura/{workflow → workflows/legacy}/dag.py +4 -1
- oscura/workflows/multi_trace.py +9 -9
- oscura/workflows/power.py +42 -62
- oscura/workflows/protocol.py +82 -49
- oscura/workflows/reverse_engineering.py +351 -150
- oscura/workflows/signal_integrity.py +157 -82
- oscura-0.7.0.dist-info/METADATA +661 -0
- oscura-0.7.0.dist-info/RECORD +591 -0
- oscura/batch/aggregate.py +0 -300
- oscura/batch/analyze.py +0 -139
- oscura/dsl/__init__.py +0 -73
- oscura/exceptions.py +0 -59
- oscura/exploratory/fuzzy.py +0 -513
- oscura/exploratory/sync.py +0 -384
- oscura/exporters/__init__.py +0 -94
- oscura/exporters/csv.py +0 -303
- oscura/exporters/exporters.py +0 -44
- oscura/exporters/hdf5.py +0 -217
- oscura/exporters/html_export.py +0 -701
- oscura/exporters/json_export.py +0 -291
- oscura/exporters/markdown_export.py +0 -367
- oscura/exporters/matlab_export.py +0 -354
- oscura/exporters/npz_export.py +0 -219
- oscura/exporters/spice_export.py +0 -210
- oscura/search/context.py +0 -149
- oscura/session/__init__.py +0 -34
- oscura/session/annotations.py +0 -289
- oscura/session/history.py +0 -313
- oscura/session/session.py +0 -520
- oscura/workflow/__init__.py +0 -13
- oscura-0.5.1.dist-info/METADATA +0 -583
- oscura-0.5.1.dist-info/RECORD +0 -481
- /oscura/core/{config.py → config/legacy.py} +0 -0
- /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
- /oscura/{extensibility → core/extensibility}/registry.py +0 -0
- /oscura/{plugins → core/plugins}/isolation.py +0 -0
- /oscura/{schemas → core/schemas}/bus_configuration.json +0 -0
- /oscura/{builders → utils/builders}/signal_builder.py +0 -0
- /oscura/{optimization → utils/optimization}/parallel.py +0 -0
- /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
- /oscura/{streaming → utils/streaming}/__init__.py +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
"""Entropy analysis and cryptographic data detection.
|
|
2
|
+
|
|
3
|
+
This module provides tools for detecting encrypted, compressed, or random data
|
|
4
|
+
in protocol messages and binary streams using entropy analysis and statistical tests.
|
|
5
|
+
|
|
6
|
+
Key capabilities:
|
|
7
|
+
- Shannon entropy calculation (information content measurement)
|
|
8
|
+
- Chi-squared test for uniform distribution (randomness detection)
|
|
9
|
+
- Sliding window entropy analysis (find encrypted regions)
|
|
10
|
+
- Crypto field detection across multiple messages
|
|
11
|
+
- Compression vs encryption distinction
|
|
12
|
+
|
|
13
|
+
Typical use cases:
|
|
14
|
+
- Identify encrypted payload fields in unknown protocols
|
|
15
|
+
- Detect compression in protocol messages
|
|
16
|
+
- Find random/high-entropy regions in binary data
|
|
17
|
+
- Distinguish structured vs random data
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
>>> from oscura.analyzers.entropy import CryptoDetector
|
|
21
|
+
>>> detector = CryptoDetector()
|
|
22
|
+
>>> result = detector.analyze_entropy(data)
|
|
23
|
+
>>> if result.is_high_entropy:
|
|
24
|
+
... print(f"Likely encrypted: {result.encryption_likelihood:.2%}")
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import logging
|
|
30
|
+
from dataclasses import dataclass
|
|
31
|
+
from typing import TYPE_CHECKING, Any
|
|
32
|
+
|
|
33
|
+
import numpy as np
|
|
34
|
+
from scipy.stats import chisquare
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from numpy.typing import NDArray
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class EntropyResult:
|
|
44
|
+
"""Results from entropy analysis.
|
|
45
|
+
|
|
46
|
+
Attributes:
|
|
47
|
+
shannon_entropy: Shannon entropy in bits per byte (0.0-8.0).
|
|
48
|
+
Higher values indicate more randomness/information content.
|
|
49
|
+
is_high_entropy: True if entropy exceeds threshold for encryption/compression.
|
|
50
|
+
is_random: True if chi-squared test indicates uniform distribution.
|
|
51
|
+
compression_likelihood: Probability data is compressed (0.0-1.0).
|
|
52
|
+
encryption_likelihood: Probability data is encrypted (0.0-1.0).
|
|
53
|
+
confidence: Overall confidence score for classification (0.0-1.0).
|
|
54
|
+
Higher for larger samples with clear characteristics.
|
|
55
|
+
chi_squared_p_value: P-value from chi-squared test (high = random).
|
|
56
|
+
|
|
57
|
+
Example:
|
|
58
|
+
>>> result = detector.analyze_entropy(encrypted_data)
|
|
59
|
+
>>> print(f"Entropy: {result.shannon_entropy:.2f} bits/byte")
|
|
60
|
+
>>> print(f"Encrypted: {result.encryption_likelihood:.2%}")
|
|
61
|
+
>>> print(f"Confidence: {result.confidence:.2%}")
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
shannon_entropy: float
|
|
65
|
+
is_high_entropy: bool
|
|
66
|
+
is_random: bool
|
|
67
|
+
compression_likelihood: float
|
|
68
|
+
encryption_likelihood: float
|
|
69
|
+
confidence: float
|
|
70
|
+
chi_squared_p_value: float
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class CryptoDetector:
|
|
74
|
+
"""Detect encrypted, compressed, or random data using entropy analysis.
|
|
75
|
+
|
|
76
|
+
This class provides multiple analysis methods for identifying cryptographic
|
|
77
|
+
or compressed data in binary streams. It uses Shannon entropy, chi-squared
|
|
78
|
+
tests, and heuristics to distinguish between different data types.
|
|
79
|
+
|
|
80
|
+
Thresholds:
|
|
81
|
+
ENTROPY_THRESHOLD_ENCRYPTED: 7.5 bits/byte - typical for AES/ChaCha20
|
|
82
|
+
ENTROPY_THRESHOLD_COMPRESSED: 6.5 bits/byte - typical for gzip/zlib
|
|
83
|
+
CHI_SQUARED_ALPHA: 0.05 - significance level for randomness test
|
|
84
|
+
|
|
85
|
+
Example:
|
|
86
|
+
>>> detector = CryptoDetector()
|
|
87
|
+
>>> # Analyze single message
|
|
88
|
+
>>> result = detector.analyze_entropy(message)
|
|
89
|
+
>>> # Find encrypted regions in mixed data
|
|
90
|
+
>>> windows = detector.sliding_window_entropy(mixed_data, window_size=256)
|
|
91
|
+
>>> # Detect fields across multiple messages
|
|
92
|
+
>>> fields = detector.detect_crypto_fields(messages, min_field_size=16)
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
# Entropy thresholds (bits per byte)
|
|
96
|
+
ENTROPY_THRESHOLD_ENCRYPTED = 7.5 # Strong encryption: AES, ChaCha20
|
|
97
|
+
ENTROPY_THRESHOLD_COMPRESSED = 6.5 # Compression: gzip, zlib, deflate
|
|
98
|
+
ENTROPY_THRESHOLD_STRUCTURED = 3.0 # Structured data: text, low-entropy
|
|
99
|
+
|
|
100
|
+
# Statistical test parameters
|
|
101
|
+
CHI_SQUARED_ALPHA = 0.05 # Significance level for randomness test
|
|
102
|
+
MIN_SAMPLE_SIZE = 32 # Minimum bytes for reliable entropy analysis
|
|
103
|
+
|
|
104
|
+
def analyze_entropy(self, data: bytes, window_size: int | None = None) -> EntropyResult:
|
|
105
|
+
"""Analyze entropy and randomness characteristics of data.
|
|
106
|
+
|
|
107
|
+
Performs comprehensive entropy analysis including Shannon entropy,
|
|
108
|
+
chi-squared test for randomness, and classification into
|
|
109
|
+
encrypted/compressed/structured categories.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
data: Binary data to analyze.
|
|
113
|
+
window_size: Optional window size for localized analysis.
|
|
114
|
+
If None, analyzes entire data block.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
EntropyResult with entropy metrics and classification.
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
ValueError: If data is empty or window_size is invalid.
|
|
121
|
+
|
|
122
|
+
Example:
|
|
123
|
+
>>> # Analyze encrypted data
|
|
124
|
+
>>> encrypted = os.urandom(256)
|
|
125
|
+
>>> result = detector.analyze_entropy(encrypted)
|
|
126
|
+
>>> assert result.is_high_entropy
|
|
127
|
+
>>> assert result.encryption_likelihood > 0.9
|
|
128
|
+
>>>
|
|
129
|
+
>>> # Analyze structured data
|
|
130
|
+
>>> text = b"Hello World" * 20
|
|
131
|
+
>>> result = detector.analyze_entropy(text)
|
|
132
|
+
>>> assert not result.is_high_entropy
|
|
133
|
+
>>> assert result.encryption_likelihood < 0.1
|
|
134
|
+
"""
|
|
135
|
+
if not data:
|
|
136
|
+
raise ValueError("Cannot analyze empty data")
|
|
137
|
+
|
|
138
|
+
if window_size is not None and window_size < 1:
|
|
139
|
+
raise ValueError(f"window_size must be positive, got {window_size}")
|
|
140
|
+
|
|
141
|
+
# Use full data if no window specified
|
|
142
|
+
if window_size is None or window_size >= len(data):
|
|
143
|
+
analysis_data = data
|
|
144
|
+
else:
|
|
145
|
+
# Take first window for analysis
|
|
146
|
+
analysis_data = data[:window_size]
|
|
147
|
+
|
|
148
|
+
# Calculate Shannon entropy
|
|
149
|
+
shannon_ent = self._shannon_entropy(analysis_data)
|
|
150
|
+
|
|
151
|
+
# Perform chi-squared test for randomness
|
|
152
|
+
chi_p_value = self._chi_squared_test(analysis_data)
|
|
153
|
+
is_random = chi_p_value > self.CHI_SQUARED_ALPHA
|
|
154
|
+
|
|
155
|
+
# Determine if high entropy
|
|
156
|
+
is_high_entropy = shannon_ent > self.ENTROPY_THRESHOLD_ENCRYPTED
|
|
157
|
+
|
|
158
|
+
# Calculate compression vs encryption likelihood
|
|
159
|
+
compression_likelihood = self._estimate_compression_likelihood(
|
|
160
|
+
shannon_ent, is_random, chi_p_value
|
|
161
|
+
)
|
|
162
|
+
encryption_likelihood = self._estimate_encryption_likelihood(
|
|
163
|
+
shannon_ent, is_random, chi_p_value
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Calculate confidence based on sample size
|
|
167
|
+
confidence = self._calculate_confidence(len(analysis_data), shannon_ent)
|
|
168
|
+
|
|
169
|
+
logger.debug(
|
|
170
|
+
f"Entropy analysis: {shannon_ent:.2f} bits/byte, "
|
|
171
|
+
f"chi-squared p={chi_p_value:.4f}, "
|
|
172
|
+
f"encrypted={encryption_likelihood:.2%}, "
|
|
173
|
+
f"compressed={compression_likelihood:.2%}"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
return EntropyResult(
|
|
177
|
+
shannon_entropy=shannon_ent,
|
|
178
|
+
is_high_entropy=is_high_entropy,
|
|
179
|
+
is_random=is_random,
|
|
180
|
+
compression_likelihood=compression_likelihood,
|
|
181
|
+
encryption_likelihood=encryption_likelihood,
|
|
182
|
+
confidence=confidence,
|
|
183
|
+
chi_squared_p_value=chi_p_value,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def sliding_window_entropy(
|
|
187
|
+
self, data: bytes, window_size: int = 256, stride: int = 64
|
|
188
|
+
) -> list[tuple[int, float]]:
|
|
189
|
+
"""Compute entropy across sliding windows to find regions of interest.
|
|
190
|
+
|
|
191
|
+
Useful for protocols with mixed plaintext/ciphertext regions:
|
|
192
|
+
- Header: low entropy, structured fields
|
|
193
|
+
- Payload: high entropy, encrypted data
|
|
194
|
+
- Footer: low entropy, checksums/padding
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
data: Binary data to analyze.
|
|
198
|
+
window_size: Size of sliding window in bytes. Default 256.
|
|
199
|
+
stride: Step size between windows in bytes. Default 64.
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
List of (offset, entropy) tuples for each window.
|
|
203
|
+
|
|
204
|
+
Raises:
|
|
205
|
+
ValueError: If data is too short, window_size or stride invalid.
|
|
206
|
+
|
|
207
|
+
Example:
|
|
208
|
+
>>> # Analyze message with mixed content
|
|
209
|
+
>>> header = b"PROTOCOL_HEADER"
|
|
210
|
+
>>> payload = os.urandom(200) # Encrypted
|
|
211
|
+
>>> footer = b"END"
|
|
212
|
+
>>> data = header + payload + footer
|
|
213
|
+
>>>
|
|
214
|
+
>>> windows = detector.sliding_window_entropy(data, window_size=64)
|
|
215
|
+
>>> for offset, ent in windows:
|
|
216
|
+
... if ent > 7.5:
|
|
217
|
+
... print(f"Encrypted region at offset {offset}")
|
|
218
|
+
"""
|
|
219
|
+
if not data:
|
|
220
|
+
raise ValueError("Cannot analyze empty data")
|
|
221
|
+
|
|
222
|
+
if window_size < 1:
|
|
223
|
+
raise ValueError(f"window_size must be positive, got {window_size}")
|
|
224
|
+
|
|
225
|
+
if stride < 1:
|
|
226
|
+
raise ValueError(f"stride must be positive, got {stride}")
|
|
227
|
+
|
|
228
|
+
if len(data) < window_size:
|
|
229
|
+
raise ValueError(f"Data length ({len(data)}) must be >= window_size ({window_size})")
|
|
230
|
+
|
|
231
|
+
results = []
|
|
232
|
+
for offset in range(0, len(data) - window_size + 1, stride):
|
|
233
|
+
window = data[offset : offset + window_size]
|
|
234
|
+
ent = self._shannon_entropy(window)
|
|
235
|
+
results.append((offset, ent))
|
|
236
|
+
|
|
237
|
+
logger.debug(f"Sliding window analysis: {len(results)} windows analyzed")
|
|
238
|
+
|
|
239
|
+
return results
|
|
240
|
+
|
|
241
|
+
def detect_crypto_fields(
|
|
242
|
+
self, messages: list[bytes], min_field_size: int = 8
|
|
243
|
+
) -> list[dict[str, Any]]:
|
|
244
|
+
"""Identify likely encrypted fields by analyzing multiple messages.
|
|
245
|
+
|
|
246
|
+
Strategy:
|
|
247
|
+
1. Group messages by length (same protocol message type)
|
|
248
|
+
2. Compute positional entropy (entropy at each byte offset)
|
|
249
|
+
3. Find consecutive high-entropy regions
|
|
250
|
+
4. Return field descriptors with offset, length, and characteristics
|
|
251
|
+
|
|
252
|
+
This works because:
|
|
253
|
+
- Plaintext fields vary between messages (low positional entropy)
|
|
254
|
+
- Encrypted fields appear random (high positional entropy)
|
|
255
|
+
- Field boundaries are consistent within a message type
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
messages: List of protocol messages to analyze.
|
|
259
|
+
min_field_size: Minimum field size in bytes to report. Default 8.
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
List of detected crypto field descriptors, each containing:
|
|
263
|
+
- offset: Field start offset in bytes
|
|
264
|
+
- length: Field length in bytes
|
|
265
|
+
- type: Classification (e.g., 'encrypted_payload')
|
|
266
|
+
- entropy: Average entropy across the field
|
|
267
|
+
- message_length: Length of messages containing this field
|
|
268
|
+
- sample_count: Number of messages analyzed
|
|
269
|
+
|
|
270
|
+
Raises:
|
|
271
|
+
ValueError: If messages list is empty or min_field_size invalid.
|
|
272
|
+
|
|
273
|
+
Example:
|
|
274
|
+
>>> # Capture multiple protocol messages
|
|
275
|
+
>>> messages = [...] # List of bytes objects
|
|
276
|
+
>>>
|
|
277
|
+
>>> # Detect encrypted fields
|
|
278
|
+
>>> fields = detector.detect_crypto_fields(messages, min_field_size=16)
|
|
279
|
+
>>>
|
|
280
|
+
>>> for field in fields:
|
|
281
|
+
... print(f"Encrypted field at offset {field['offset']}, "
|
|
282
|
+
... f"length {field['length']} bytes, "
|
|
283
|
+
... f"entropy {field['entropy']:.2f}")
|
|
284
|
+
"""
|
|
285
|
+
if not messages:
|
|
286
|
+
raise ValueError("Cannot analyze empty message list")
|
|
287
|
+
|
|
288
|
+
if min_field_size < 1:
|
|
289
|
+
raise ValueError(f"min_field_size must be positive, got {min_field_size}")
|
|
290
|
+
|
|
291
|
+
# Group messages by length (same message type)
|
|
292
|
+
length_groups: dict[int, list[bytes]] = {}
|
|
293
|
+
for msg in messages:
|
|
294
|
+
length_groups.setdefault(len(msg), []).append(msg)
|
|
295
|
+
|
|
296
|
+
crypto_fields = []
|
|
297
|
+
|
|
298
|
+
for msg_len, msg_group in length_groups.items():
|
|
299
|
+
if msg_len < min_field_size:
|
|
300
|
+
logger.debug(
|
|
301
|
+
f"Skipping message group with length {msg_len} < "
|
|
302
|
+
f"min_field_size {min_field_size}"
|
|
303
|
+
)
|
|
304
|
+
continue
|
|
305
|
+
|
|
306
|
+
# Compute entropy at each position
|
|
307
|
+
position_entropy = self._compute_positional_entropy(msg_group)
|
|
308
|
+
|
|
309
|
+
# Find high-entropy regions
|
|
310
|
+
fields = self._extract_high_entropy_regions(
|
|
311
|
+
position_entropy,
|
|
312
|
+
msg_len,
|
|
313
|
+
min_field_size,
|
|
314
|
+
len(msg_group),
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
crypto_fields.extend(fields)
|
|
318
|
+
|
|
319
|
+
logger.info(f"Detected {len(crypto_fields)} crypto fields across {len(messages)} messages")
|
|
320
|
+
|
|
321
|
+
return crypto_fields
|
|
322
|
+
|
|
323
|
+
# =========================================================================
|
|
324
|
+
# Internal Helper Methods
|
|
325
|
+
# =========================================================================
|
|
326
|
+
|
|
327
|
+
def _shannon_entropy(self, data: bytes) -> float:
|
|
328
|
+
"""Calculate Shannon entropy in bits per byte.
|
|
329
|
+
|
|
330
|
+
Shannon entropy measures the average information content per byte.
|
|
331
|
+
Formula: H = -sum(p(i) * log2(p(i))) for all byte values i
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
data: Binary data to analyze.
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
Entropy in bits per byte (0.0 to 8.0).
|
|
338
|
+
0.0: All bytes identical (no information)
|
|
339
|
+
8.0: Perfect randomness (maximum information)
|
|
340
|
+
|
|
341
|
+
Example:
|
|
342
|
+
>>> # All zeros - no entropy
|
|
343
|
+
>>> assert detector._shannon_entropy(b"\x00" * 100) == 0.0
|
|
344
|
+
>>> # Random data - high entropy
|
|
345
|
+
>>> random_data = os.urandom(1000)
|
|
346
|
+
>>> assert detector._shannon_entropy(random_data) > 7.5
|
|
347
|
+
"""
|
|
348
|
+
if not data:
|
|
349
|
+
return 0.0
|
|
350
|
+
|
|
351
|
+
# Count byte frequencies
|
|
352
|
+
byte_array = np.frombuffer(data, dtype=np.uint8)
|
|
353
|
+
byte_counts = np.bincount(byte_array, minlength=256)
|
|
354
|
+
|
|
355
|
+
# Calculate probabilities (exclude zero counts)
|
|
356
|
+
probabilities = byte_counts[byte_counts > 0] / len(data)
|
|
357
|
+
|
|
358
|
+
# Shannon entropy: -sum(p * log2(p))
|
|
359
|
+
entropy = float(-np.sum(probabilities * np.log2(probabilities)))
|
|
360
|
+
|
|
361
|
+
return entropy
|
|
362
|
+
|
|
363
|
+
def _chi_squared_test(self, data: bytes) -> float:
|
|
364
|
+
"""Perform chi-squared test for uniform distribution.
|
|
365
|
+
|
|
366
|
+
Tests the null hypothesis: data is uniformly distributed (random).
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
data: Binary data to test.
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
P-value from chi-squared test.
|
|
373
|
+
High p-value (>0.05): Data is likely random/uniform
|
|
374
|
+
Low p-value (<0.05): Data has structure/patterns
|
|
375
|
+
|
|
376
|
+
Example:
|
|
377
|
+
>>> # Random data passes test (high p-value)
|
|
378
|
+
>>> random_data = os.urandom(1000)
|
|
379
|
+
>>> p_value = detector._chi_squared_test(random_data)
|
|
380
|
+
>>> assert p_value > 0.05
|
|
381
|
+
>>>
|
|
382
|
+
>>> # Structured data fails test (low p-value)
|
|
383
|
+
>>> text = b"AAAA" * 100
|
|
384
|
+
>>> p_value = detector._chi_squared_test(text)
|
|
385
|
+
>>> assert p_value < 0.05
|
|
386
|
+
"""
|
|
387
|
+
if not data:
|
|
388
|
+
return 0.0
|
|
389
|
+
|
|
390
|
+
# Count observed byte frequencies
|
|
391
|
+
byte_array = np.frombuffer(data, dtype=np.uint8)
|
|
392
|
+
observed = np.bincount(byte_array, minlength=256)
|
|
393
|
+
|
|
394
|
+
# Expected frequencies for uniform distribution
|
|
395
|
+
expected = np.full(256, len(data) / 256.0)
|
|
396
|
+
|
|
397
|
+
# Chi-squared test
|
|
398
|
+
_, p_value = chisquare(observed, expected)
|
|
399
|
+
|
|
400
|
+
return float(p_value)
|
|
401
|
+
|
|
402
|
+
def _estimate_compression_likelihood(
|
|
403
|
+
self, entropy: float, is_random: bool, chi_p_value: float
|
|
404
|
+
) -> float:
|
|
405
|
+
"""Estimate likelihood that data is compressed.
|
|
406
|
+
|
|
407
|
+
Compressed data characteristics:
|
|
408
|
+
- Medium-high entropy (6.5-7.5 bits/byte)
|
|
409
|
+
- Less uniformly random than encryption
|
|
410
|
+
- Some residual structure from compression algorithm
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
entropy: Shannon entropy in bits/byte.
|
|
414
|
+
is_random: Result of chi-squared test.
|
|
415
|
+
chi_p_value: P-value from chi-squared test.
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
Compression likelihood (0.0-1.0).
|
|
419
|
+
"""
|
|
420
|
+
if entropy < self.ENTROPY_THRESHOLD_COMPRESSED:
|
|
421
|
+
# Too low entropy for compression
|
|
422
|
+
return 0.0
|
|
423
|
+
|
|
424
|
+
if entropy > self.ENTROPY_THRESHOLD_ENCRYPTED:
|
|
425
|
+
# Too high entropy - likely encryption, not compression
|
|
426
|
+
# Compression rarely achieves >7.5 bits/byte
|
|
427
|
+
return max(0.0, 1.0 - (entropy - self.ENTROPY_THRESHOLD_ENCRYPTED) * 2.0)
|
|
428
|
+
|
|
429
|
+
# Medium entropy range - likely compression
|
|
430
|
+
# Peak likelihood at 7.0 bits/byte
|
|
431
|
+
compression_score = (entropy - self.ENTROPY_THRESHOLD_COMPRESSED) / (
|
|
432
|
+
self.ENTROPY_THRESHOLD_ENCRYPTED - self.ENTROPY_THRESHOLD_COMPRESSED
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
# Reduce likelihood if data is too uniformly random
|
|
436
|
+
# (compression has some structure)
|
|
437
|
+
if is_random and chi_p_value > 0.5:
|
|
438
|
+
compression_score *= 0.5
|
|
439
|
+
|
|
440
|
+
return min(1.0, max(0.0, compression_score))
|
|
441
|
+
|
|
442
|
+
def _estimate_encryption_likelihood(
|
|
443
|
+
self, entropy: float, is_random: bool, chi_p_value: float
|
|
444
|
+
) -> float:
|
|
445
|
+
"""Estimate likelihood that data is encrypted.
|
|
446
|
+
|
|
447
|
+
Encrypted data characteristics:
|
|
448
|
+
- Very high entropy (>7.5 bits/byte)
|
|
449
|
+
- Uniformly random distribution
|
|
450
|
+
- High chi-squared p-value
|
|
451
|
+
|
|
452
|
+
Args:
|
|
453
|
+
entropy: Shannon entropy in bits/byte.
|
|
454
|
+
is_random: Result of chi-squared test.
|
|
455
|
+
chi_p_value: P-value from chi-squared test.
|
|
456
|
+
|
|
457
|
+
Returns:
|
|
458
|
+
Encryption likelihood (0.0-1.0).
|
|
459
|
+
"""
|
|
460
|
+
if entropy < self.ENTROPY_THRESHOLD_COMPRESSED:
|
|
461
|
+
# Too low for encryption
|
|
462
|
+
return 0.0
|
|
463
|
+
|
|
464
|
+
# Base score from entropy
|
|
465
|
+
if entropy >= self.ENTROPY_THRESHOLD_ENCRYPTED:
|
|
466
|
+
entropy_score = min(1.0, (entropy - 7.0) / 1.0) # Scale 7.0-8.0 to 0-1
|
|
467
|
+
else:
|
|
468
|
+
# Partial score for medium entropy
|
|
469
|
+
entropy_score = (entropy - self.ENTROPY_THRESHOLD_COMPRESSED) / (
|
|
470
|
+
self.ENTROPY_THRESHOLD_ENCRYPTED - self.ENTROPY_THRESHOLD_COMPRESSED
|
|
471
|
+
)
|
|
472
|
+
entropy_score *= 0.5 # Reduce confidence
|
|
473
|
+
|
|
474
|
+
# Boost score if uniformly random
|
|
475
|
+
if is_random:
|
|
476
|
+
# Very uniform = likely encryption
|
|
477
|
+
randomness_boost = min(0.5, chi_p_value)
|
|
478
|
+
encryption_score = min(1.0, entropy_score + randomness_boost)
|
|
479
|
+
else:
|
|
480
|
+
# Not uniform = less likely encryption
|
|
481
|
+
encryption_score = entropy_score * 0.7
|
|
482
|
+
|
|
483
|
+
return min(1.0, max(0.0, encryption_score))
|
|
484
|
+
|
|
485
|
+
def _calculate_confidence(self, sample_size: int, entropy: float) -> float:
|
|
486
|
+
"""Calculate confidence score based on sample size and entropy clarity.
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
sample_size: Number of bytes analyzed.
|
|
490
|
+
entropy: Shannon entropy value.
|
|
491
|
+
|
|
492
|
+
Returns:
|
|
493
|
+
Confidence score (0.0-1.0).
|
|
494
|
+
"""
|
|
495
|
+
# Sample size confidence
|
|
496
|
+
if sample_size < self.MIN_SAMPLE_SIZE:
|
|
497
|
+
size_confidence = sample_size / self.MIN_SAMPLE_SIZE
|
|
498
|
+
elif sample_size >= 256:
|
|
499
|
+
size_confidence = 1.0
|
|
500
|
+
else:
|
|
501
|
+
size_confidence = (
|
|
502
|
+
0.5 + (sample_size - self.MIN_SAMPLE_SIZE) / (256 - self.MIN_SAMPLE_SIZE) * 0.5
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
# Entropy clarity (distance from thresholds)
|
|
506
|
+
if entropy < self.ENTROPY_THRESHOLD_STRUCTURED:
|
|
507
|
+
# Clearly structured
|
|
508
|
+
clarity = 1.0
|
|
509
|
+
elif entropy > self.ENTROPY_THRESHOLD_ENCRYPTED:
|
|
510
|
+
# Clearly encrypted
|
|
511
|
+
clarity = 1.0
|
|
512
|
+
elif self.ENTROPY_THRESHOLD_COMPRESSED < entropy < self.ENTROPY_THRESHOLD_ENCRYPTED:
|
|
513
|
+
# Ambiguous range (compression vs encryption)
|
|
514
|
+
clarity = 0.6
|
|
515
|
+
else:
|
|
516
|
+
# Between structured and compressed
|
|
517
|
+
clarity = 0.8
|
|
518
|
+
|
|
519
|
+
return size_confidence * clarity
|
|
520
|
+
|
|
521
|
+
def _compute_positional_entropy(self, messages: list[bytes]) -> NDArray[np.float64]:
|
|
522
|
+
"""Compute entropy at each byte position across messages.
|
|
523
|
+
|
|
524
|
+
Args:
|
|
525
|
+
messages: List of messages (all same length).
|
|
526
|
+
|
|
527
|
+
Returns:
|
|
528
|
+
Array of entropy values, one per byte position.
|
|
529
|
+
"""
|
|
530
|
+
msg_len = len(messages[0])
|
|
531
|
+
position_entropy = np.zeros(msg_len, dtype=np.float64)
|
|
532
|
+
|
|
533
|
+
for pos in range(msg_len):
|
|
534
|
+
# Extract bytes at this position from all messages
|
|
535
|
+
position_bytes = bytes([msg[pos] for msg in messages])
|
|
536
|
+
position_entropy[pos] = self._shannon_entropy(position_bytes)
|
|
537
|
+
|
|
538
|
+
return position_entropy
|
|
539
|
+
|
|
540
|
+
def _extract_high_entropy_regions(
|
|
541
|
+
self,
|
|
542
|
+
position_entropy: NDArray[np.float64],
|
|
543
|
+
msg_len: int,
|
|
544
|
+
min_field_size: int,
|
|
545
|
+
sample_count: int,
|
|
546
|
+
) -> list[dict[str, Any]]:
|
|
547
|
+
"""Extract contiguous high-entropy regions as crypto field candidates.
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
position_entropy: Entropy at each position.
|
|
551
|
+
msg_len: Message length in bytes.
|
|
552
|
+
min_field_size: Minimum field size to report.
|
|
553
|
+
sample_count: Number of messages analyzed.
|
|
554
|
+
|
|
555
|
+
Returns:
|
|
556
|
+
List of field descriptors.
|
|
557
|
+
"""
|
|
558
|
+
fields = []
|
|
559
|
+
in_crypto = False
|
|
560
|
+
start = 0
|
|
561
|
+
|
|
562
|
+
# Use a lower threshold for positional entropy since we're analyzing
|
|
563
|
+
# entropy across limited samples (e.g., 20 messages = 20 bytes per position)
|
|
564
|
+
# Maximum positional entropy with N samples is log2(min(N, 256))
|
|
565
|
+
max_positional_entropy = min(np.log2(sample_count), 8.0) if sample_count > 1 else 0.0
|
|
566
|
+
# Use 70% of max as threshold for high positional entropy
|
|
567
|
+
positional_threshold = max_positional_entropy * 0.7
|
|
568
|
+
|
|
569
|
+
for pos in range(msg_len):
|
|
570
|
+
if position_entropy[pos] > positional_threshold:
|
|
571
|
+
if not in_crypto:
|
|
572
|
+
start = pos
|
|
573
|
+
in_crypto = True
|
|
574
|
+
else:
|
|
575
|
+
if in_crypto and (pos - start) >= min_field_size:
|
|
576
|
+
# Found a crypto field
|
|
577
|
+
fields.append(
|
|
578
|
+
{
|
|
579
|
+
"offset": start,
|
|
580
|
+
"length": pos - start,
|
|
581
|
+
"type": "encrypted_payload",
|
|
582
|
+
"entropy": float(np.mean(position_entropy[start:pos])),
|
|
583
|
+
"message_length": msg_len,
|
|
584
|
+
"sample_count": sample_count,
|
|
585
|
+
}
|
|
586
|
+
)
|
|
587
|
+
in_crypto = False
|
|
588
|
+
|
|
589
|
+
# Handle field at end of message
|
|
590
|
+
if in_crypto and (msg_len - start) >= min_field_size:
|
|
591
|
+
fields.append(
|
|
592
|
+
{
|
|
593
|
+
"offset": start,
|
|
594
|
+
"length": msg_len - start,
|
|
595
|
+
"type": "encrypted_payload",
|
|
596
|
+
"entropy": float(np.mean(position_entropy[start:])),
|
|
597
|
+
"message_length": msg_len,
|
|
598
|
+
"sample_count": sample_count,
|
|
599
|
+
}
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
return fields
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
__all__ = ["CryptoDetector", "EntropyResult"]
|