[PYTHON] Erstellen Sie mit Kaitai Struct einen Binärdatenparser

Einführung

LOKAL Studentenabteilung Adventskalender Tag 11

Ich war besorgt, dass alle Leute stur waren und "Bin ich nicht zu schwach ...?", Also beschloss ich, einen wechselnden Ball zu werfen. Soweit ich gesucht habe, gibt es keine japanische Literatur, daher denke ich, dass Leute, die dies in Zukunft erforschen, diesen Artikel fast automatisch sehen werden. Ich möchte euch alle fragen. Wenn Sie denken: "Worüber sprechen Sie? Ist es nicht voller falscher Informationen?", Bitte kommentieren Sie. Ich werde mein Bestes tun, um das Problem zu beheben.

Was ist Kaitai Struct?

Überblick

Beamter: kaitai.io

Kaitai Struct ist eine deklarative Sprache zur Beschreibung von binären Datenstrukturen.

Der Quellcode des Binärdatenparsers kann automatisch basierend auf der in Ihrer eigenen Sprache geschriebenen Datenstruktur generiert werden.

Unterstützte Sprachen (Stand 2. Dezember 2019)
  • C++ / STL
  • C#
  • Go (entry-level support)
  • Java
  • JavaScript
  • Lua
  • Perl
  • PHP
  • Python
  • Ruby

license Der später beschriebene Compiler und Visualizer sind GPL v3 + und die Bibliothek für jede Sprache ist MIT (JS ist Apache v2). Bedeutet dies, dass der mit Compiler generierte Quellcode die GPL infiziert ...? Bitte sagen Sie mir eine detaillierte Person.

Installation

Kaitai Struct Compiler (KSC) Weitere Informationen zur Installation hier Mac ist ein Schuss mit Brew Install Kaitai-Struct-Compiler. Folgen Sie für Windows dem obigen Link, um das Installationsprogramm herunterzuladen.

Debian / Ubuntu-basierte Distributionen können Pakete aus dem offiziellen .deb-Repository installieren.
# Import GPG key, if you never used any BinTray repos before
sudo apt-key adv --keyserver hkp://pool.sks-keyservers.net --recv 379CE192D401AB61

# Add stable repository
echo "deb https://dl.bintray.com/kaitai-io/debian jessie main" | sudo tee /etc/apt/sources.list.d/kaitai.list
# ... or unstable repository
echo "deb https://dl.bintray.com/kaitai-io/debian_unstable jessie main" | sudo tee /etc/apt/sources.list.d/kaitai.list

sudo apt-get update
sudo apt-get install kaitai-struct-compiler
Wenn Sie ein anderes Betriebssystem verwenden, klonen Sie es von [hier](https://github.com/kaitai-io/kaitai_struct_compiler) und erstellen Sie es.

Kaitai Struct Visualizer (KSV) Dies ist ein einfacher Visualisierer für .ksy-Dateien. Geschrieben in "Ruby", ist es als "Edelstein" -Paket erhältlich.

gem install kaitai-struct-visualizer

(Git-Repository)

Versuchen Sie es mit

Für bekannte Dateien befindet sich im offiziellen Github-Repository eine .ksy-Datei (https://github.com/kaitai-io/kaitai_struct_formats). (Wenn Sie die hier vorhandene ".ksy" -Datei verwenden möchten, überprüfen Sie bitte die in "Meta / Lizenz" in der Datei beschriebene Lizenz.) Wenn Sie eine neue .ksy schreiben, senden Sie eine Pull-Anfrage. (kaitai_struct_formats/CONTRIBUTING.md)

Beispiel) Matrix

In Datei speichern (np.array)

matrix.py


import numpy as np
import struct


def create_header(*mats: [np.ndarray], magic: bytes = None) -> bytes:
    header = magic
    header += struct.pack('<H', len(mats))
    length = len(header) + 8 * len(mats)
    for mat in mats:
        header += struct.pack('<HH', mat.shape[0], mat.shape[1])
        header += struct.pack('<I', length)
        length += 4 * mat.shape[0] * mat.shape[1]
    return header


mat1 = np.random.randint(-1024, 1024, [3, 3], dtype=np.int32)
mat2 = np.random.randint(-1024, 1024, [5, 9], dtype=np.int32)
mat3 = np.random.randint(-1024, 1024, [2, 2], dtype=np.int32)

with open('test.matrix', 'wb') as o:
    magic = b'THIS IS MAT FILE.\x01\x02'
    o.write(create_header(mat1, mat2, mat3, magic=magic))
    for mat in [mat1, mat2, mat3]:
        for y in mat:
            for x in y:
                o.write(struct.pack('<i', x))

Ich werde KS verwenden, um die durch den obigen Code erzeugte `test.matrix` zu laden.

test.matrix


  Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 	
00000000: 4D 41 54 01 02 2F 03 00 03 00 03 00 20 00 00 00    MAT../..........
00000010: 05 00 09 00 44 00 00 00 02 00 02 00 F8 00 00 00    ....D.......x...
00000020: DC FE FF FF 49 01 00 00 A7 FF FF FF 17 02 00 00    \~..I...'.......
00000030: 25 FC FF FF 35 FF FF FF B5 00 00 00 CF FE FF FF    %|..5...5...O~..
00000040: E2 FF FF FF 5D 00 00 00 15 FE FF FF 30 FC FF FF    b...]....~..0|..
00000050: 4C 03 00 00 C1 FF FF FF B0 FD FF FF 31 02 00 00    L...A...0}..1...
00000060: 54 03 00 00 C4 FF FF FF 65 FF FF FF D0 FE FF FF    T...D...e...P~..
00000070: 75 01 00 00 DE FE FF FF ED 00 00 00 ED FC FF FF    u...^~..m...m|..
00000080: BE FD FF FF E5 02 00 00 EC FE FF FF 22 FE FF FF    >}..e...l~.."~..
00000090: C3 02 00 00 11 00 00 00 29 03 00 00 00 01 00 00    C.......).......
000000a0: 78 00 00 00 C4 FC FF FF 4C 02 00 00 88 00 00 00    x...D|..L.......
000000b0: 43 FF FF FF 35 FF FF FF A4 00 00 00 CF 02 00 00    C...5...$...O...
000000c0: 3A FF FF FF 33 FF FF FF BD FE FF FF F9 01 00 00    :...3...=~..y...
000000d0: 22 FF FF FF 3A 02 00 00 7C 00 00 00 15 FF FF FF    "...:...|.......
000000e0: D8 FE FF FF 42 00 00 00 82 02 00 00 24 02 00 00    X~..B.......$...
000000f0: 8A FE FF FF AF FF FF FF EF 02 00 00 96 01 00 00    .~../...o.......
00000100: 83 01 00 00 2F 02 00 00  

Die Struktur der Datei ist von Anfang an

  1. b'MAT\x01\x02/'
  2. Anzahl der vorhandenen Matrizen (2 Bytes)
  3. Form und Versatz für jede Matrix ((8 * Anzahl der Matrizen) Bytes)
  4. Matrixkörper Es ist geworden.

Schreiben wir dies in matrix.ksy.

KSY (Kaitai Struct YAML) deklariert einen einzelnen benutzerdefinierten Typ (wörtlich übersetzt vom offiziellen). Benutzerdefinierter Typ

meta

meta


meta:
  id: matrix
  endian: le

Beschreiben Sie den Namen des benutzerdefinierten Typs, der in "meta / id" beschrieben werden soll. Es muss immer in der .ksy-Datei sein. meta / endian beschreibt den in der Struktur verwendeten Standard-Endian ( le / be)

seq

seq


seq:
  - id: magic
    contents: ['MAT', 1, 0x2, '/']
  - id: header_num
    type: u2
  - id: headers
    repeat: expr
    repeat-expr: header_num
    type: header

Beschreiben Sie die Datenstruktur in seq. id ist der Variablenname. Wenn die Daten eine Konstante sind, schreiben Sie die Konstante in "Inhalt". Wenn Sie den Wert erhalten möchten, beschreiben Sie den Datentyp in type (siehe hier für Details). Sie können auch die unter "Typen" beschriebenen Typen verwenden, die später beschrieben werden. Hier wird der Typ "Header" verwendet. repeat kann expr, eos oder till sein (siehe hier für Details). ) Wenn Sie "Ausdruck" eingeben, geben Sie die Anzahl der Wiederholungen in "Wiederholung-Ausdruck" ein.

types

types


types:
  header:
    seq:
      - id: shape0
        type: u2
      - id: shape1
        type: u2
      - id: offset
        type: u4
    instances: 
      mat_body:
        pos: offset
        io: _root._io
        type: matrix

  matrix:
    seq:
      - id: dim0
        repeat: expr
        repeat-expr: _parent.shape0
        type: dim1
    types:
      dim1:
        seq:
          - id: dim1
            repeat: expr
            repeat-expr: _parent._parent.shape0
            type: s4

Benutzerdefinierte Typen können in "Typen" verschachtelt werden. Ich verwende "Instanzen" mit dem Typ "Header", mit denen andere als die nacheinander vorhandenen Daten gelesen werden können, z. B. "seq".

header.instances


instances: 
  mat_body:
    pos: offset
    io: _root._io
    type: matrix

Die Verwendung ist der von "seq" sehr ähnlich. id ist hier der mat_body. io ist der zu verwendende IO-Stream. pos ist die Anzahl der Bytes ab dem Beginn von io. type ist das gleiche wie für seq.

Über Variablen

Einige Felder (in diesem Fall "repeat-expr", "pos", "io") können sich sowohl auf Variablen als auch auf konstante Werte beziehen. Sie können keine Daten sehen, die noch nicht gelesen wurden. Die Daten haben eine Baumstruktur (es ist leicht zu verstehen, wenn Sie ksv verwenden), und Sie können das übergeordnete Element mit "_parent" angeben. Sie können das oberste Element auch mit _root angeben.

Visualize

Zu diesem Zeitpunkt haben Sie den folgenden Code geschrieben.

matrix.ksy


meta:
  id: matrix
  endian: le

seq:
  - id: magic
    contents: ['MAT', 1, 0x2, '/']
  - id: header_num
    type: u2
  - id: headers
    repeat: expr
    repeat-expr: header_num
    type: header

types:
  header:
    seq:
      - id: shape0
        type: u2
      - id: shape1
        type: u2
      - id: offset
        type: u4
    instances: 
      mat_body:
        pos: offset
        io: _root._io
        type: matrix

  matrix:
    seq:
      - id: dim0
        repeat: expr
        repeat-expr: _parent.shape0
        type: dim1
    types:
      dim1:
        seq:
          - id: dim1
            repeat: expr
            repeat-expr: _parent._parent.shape0
            type: s4

Lassen Sie uns dies mit ksv (Kaitai Struct Visualizer) visualisieren. Die Verwendung ist ksv <file_to_parse.bin> <format.ksy>.

shell


$ ksv test.matrix matrix.ksy

ksv


[-] [root]                              00000000: 4d 41 54 01 02 2f 03 00 03 00 03 00 20 00 00 00 | MAT../...... ...
  [.] magic = 4d 41 54 01 02 2f         00000010: 05 00 09 00 44 00 00 00 02 00 02 00 f8 00 00 00 | ....D...........
  [.] header_num = 3                    00000020: dc fe ff ff 49 01 00 00 a7 ff ff ff 17 02 00 00 | ....I...........
  [-] headers (3 = 0x3 entries)         00000030: 25 fc ff ff 35 ff ff ff b5 00 00 00 cf fe ff ff | %...5...........
    [-] 0                               00000040: e2 ff ff ff 5d 00 00 00 15 fe ff ff 30 fc ff ff | ....].......0...
      [.] shape0 = 3                    00000050: 4c 03 00 00 c1 ff ff ff b0 fd ff ff 31 02 00 00 | L...........1...
      [.] shape1 = 3                    00000060: 54 03 00 00 c4 ff ff ff 65 ff ff ff d0 fe ff ff | T.......e.......
      [.] offset = 32                   00000070: 75 01 00 00 de fe ff ff ed 00 00 00 ed fc ff ff | u...............
      [-] mat_body                      00000080: be fd ff ff e5 02 00 00 ec fe ff ff 22 fe ff ff | ............"...
        [-] dim0 (3 = 0x3 entries)      00000090: c3 02 00 00 11 00 00 00 29 03 00 00 00 01 00 00 | ........).......
          [-] 0                         000000a0: 78 00 00 00 c4 fc ff ff 4c 02 00 00 88 00 00 00 | x.......L.......
            [-] dim1 (3 = 0x3 entries)  000000b0: 43 ff ff ff 35 ff ff ff a4 00 00 00 cf 02 00 00 | C...5...........
              [.] 0 = -292              000000c0: 3a ff ff ff 33 ff ff ff bd fe ff ff f9 01 00 00 | :...3...........
              [.] 1 = 329               000000d0: 22 ff ff ff 3a 02 00 00 7c 00 00 00 15 ff ff ff | "...:...|.......
              [.] 2 = -89               000000e0: d8 fe ff ff 42 00 00 00 82 02 00 00 24 02 00 00 | ....B.......$...
          [-] 1                         000000f0: 8a fe ff ff af ff ff ff ef 02 00 00 96 01 00 00 | ................
            [-] dim1 (3 = 0x3 entries)  00000100: 83 01 00 00 2f 02 00 00                         | ..../...        
              [.] 0 = 535
              [.] 1 = -987
              [.] 2 = -203
          [-] 2
            [+] dim1
    [-] 1
      [.] shape0 = 5
      [.] shape1 = 9
      [.] offset = 68
      [-] mat_body
        [+] dim0
    [+] 2

Es scheint, dass es gut gelesen werden kann.

Dateikomprimierung

Dies ist das Hauptthema. Ich habe im Artikel [hier] eine komprimierte Datei erstellt (https://qiita.com/mueru/items/2e99832304cf8b89d3ec). Entpacken Sie dieses Mal diese komprimierte Datei mit KS. Die Struktur der Datei finden Sie im Artikel.

mcp.ksy


meta:
    id: mcp
    encoding: UTF-8
    endian: le

seq:
  - id: file
    type: file
    repeat: eos
types:
    file:
        seq:
          - id: filename_len
            type: u4
          - id: filebody_len
            type: u4
          - id: filename
            type: str
            size: filename_len
          - id: filebody
            size: filebody_len
            process: zlib

meta / encoding gibt die Standardcodierung an, die mit type: str verwendet werden soll. repeat: eos wiederholt sich bis zum Ende des Streams. process: zlib beantwortet die gelesenen Daten mit zlib. (Sehr angenehm)

Generieren Sie Code aus mcp.ksy mit ksc (Kaitai Struct Compiler).

usage


Usage: kaitai-struct-compiler [options] <file>...

  <file>...                source files (.ksy)
  -t, --target <language>  target languages (graphviz, csharp, all, perl, java, go, cpp_stl, php, lua, python, ruby, javascript)
  -d, --outdir <directory>
                           output directory (filenames will be auto-generated)
  -I, --import-path <directory>:<directory>:...
                           .ksy library search path(s) for imports (see also KSPATH env variable)
  --go-package <package>   Go package (Go only, default: none)
  --java-package <package>
                           Java package (Java only, default: root package)
  --java-from-file-class <class>
                           Java class to be invoked in fromFile() helper (default: io.kaitai.struct.ByteBufferKaitaiStream)
  --dotnet-namespace <namespace>
                           .NET Namespace (.NET only, default: Kaitai)
  --php-namespace <namespace>
                           PHP Namespace (PHP only, default: root package)
  --python-package <package>
                           Python package (Python only, default: root package)
  --opaque-types <value>   opaque types allowed, default: false
  --ksc-exceptions         ksc throws exceptions instead of human-readable error messages
  --ksc-json-output        output compilation results as JSON to stdout
  --verbose <value>        verbose output
  --debug                  enable debugging helpers (mostly used by visualization tools)
  --help                   display this help and exit
  --version                output version information and exit

shell


$ ksc -t python mcp.ksy

mcp.py


# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

from pkg_resources import parse_version
from kaitaistruct import __version__ as ks_version, KaitaiStruct, KaitaiStream, BytesIO
import zlib


if parse_version(ks_version) < parse_version('0.7'):
    raise Exception("Incompatible Kaitai Struct Python API: 0.7 or later is required, but you have %s" % (ks_version))

class Mcp(KaitaiStruct):
    def __init__(self, _io, _parent=None, _root=None):
        self._io = _io
        self._parent = _parent
        self._root = _root if _root else self
        self._read()

    def _read(self):
        self.file = []
        i = 0
        while not self._io.is_eof():
            self.file.append(self._root.File(self._io, self, self._root))
            i += 1


    class File(KaitaiStruct):
        def __init__(self, _io, _parent=None, _root=None):
            self._io = _io
            self._parent = _parent
            self._root = _root if _root else self
            self._read()

        def _read(self):
            self.filename_len = self._io.read_u4le()
            self.filebody_len = self._io.read_u4le()
            self.filename = (self._io.read_bytes(self.filename_len)).decode(u"UTF-8")
            self._raw_filebody = self._io.read_bytes(self.filebody_len)
            self.filebody = zlib.decompress(self._raw_filebody)

Der Code, der "mcp.py" generiert. Schreiben wir damit ein Dekomprimierungsskript.

extract.py


from mcp import Mcp
import os
import sys

mcps = Mcp.from_file(sys.argv[1])
out = 'output/'
if len(sys.argv) >= 3:
    out = sys.argv[2]

for f in mcps.file:
    if os.path.dirname(f.filename):
        os.makedirs(os.path.join(out, os.path.dirname(f.filename)), exist_ok=True)
    with open(os.path.join(out, f.filename), 'wb') as o:
        o.write(f.filebody)

Sie können mit "python extract.py <target.mcp> [output_folder]" antworten

Verwenden Sie zum Lesen der Datei "KaitaiStruct.from_file (file_path)". Wenn Sie die Byte-Zeichenfolge so lesen möchten, wie sie ist, verwenden Sie "KaitaiStruct.from_bytes (Bytes)". Verwenden Sie für E / A-Streams "KaitaiStruct.from_io (io)".

schließlich

Ich denke, KS ist sehr praktisch. Es ist einfach zu schreiben und Sie können es in Ihrer Lieblingssprache verwenden, sodass die Kosten für das Erlernen neuer Dinge sehr gering sind. Die offizielle Referenz ist ehrlich gesagt schwer zu lesen, aber ich bin sicher, dass in Zukunft mehr Leute wie ich Artikel über KS schreiben werden (wahrscheinlich).

Möchten Sie mit KS "zerlegen"?

Recommended Posts

Erstellen Sie mit Kaitai Struct einen Binärdatenparser
Erstellen Sie mit Selenium einen Datenerfassungsbot in Python
Erstellen Sie eine Dummy-Datendatei
Erstellen Sie sofort ein Diagramm mit 2D-Daten mit der matplotlib von Python
Erstellen Sie mit tkinter eine Python-GUI
Erstellen Sie ein verschachteltes Wörterbuch mit defaultdict
Erstellen Sie eine Binärdatei in Python
Erstellen Sie die CRUD-API mit der Fast API
Erstellen Sie mit Boost.Python einen C-Sprach-Wrapper
Erstellen Sie mit turicreate eine API, die Daten aus einem Modell zurückgibt
Erstellen Sie mit dem Sympy-Modul ein Diagramm
Erstellen einer Datenanalyseanwendung mit Streamlit
Erstellen Sie Dokumentklassifizierungsdaten schnell mit NLTK
Erstellen Sie mit Pandas einen Datenrahmen aus Excel
Erstellen Sie eine GIF-Datei mit Pillow in Python
Erstellen wir eine REST-API mit SpringBoot + MongoDB
Erstellen Sie mit ClustalW2 einen phylogenetischen Baum aus Biopyton
Eine Geschichte über den Umgang mit Binärdaten in Python
Erstellen Sie mit CadQuery 3D-Druckerdaten (STL-Datei)
Erstellen Sie eine Webmap mit Python und GDAL
Erstellen Sie ein Besuchermeldungssystem mit Raspberry Pi
Erstellen Sie eine MIDI-Datei in Python mit pretty_midi
Ich habe einen japanischen Parser auf Japanisch mit Pyparsing geschrieben.
Erstellen Sie eine GUI auf dem Terminal mit Flüchen
Erstellen Sie eine Shogi-Score-Management-Anwendung mit Django 5 ~ DB-Daten an Vorlage übergeben ~
Lesen Sie die Python-Markdown-Quelle: So erstellen Sie einen Parser
Erstellen Sie einen Farbsensor mit einem Raspeltorte und einer Kamera
[CRUD] [Django] Erstellen Sie eine CRUD-Site mit dem Python-Framework Django ~ 1 ~
Erstellen Sie Dummy-Daten mit den NumPy- und Faker-Paketen von Python
[Python] Generieren Sie ValueObject mit dem vollständigen Konstruktor mithilfe von Datenklassen
Erstellen Sie mit GitHub Pages einen Pseudo-REST-API-Server
[CRUD] [Django] Erstellen Sie eine CRUD-Site mit dem Python-Framework Django ~ 2 ~
Ich habe versucht, Daten aus einer Datei mit Node.js zu lesen.
[CRUD] [Django] Erstellen Sie eine CRUD-Site mit dem Python-Framework Django ~ 3 ~
[CRUD] [Django] Erstellen Sie eine CRUD-Site mit dem Python-Framework Django ~ 4 ~
[CRUD] [Django] Erstellen Sie eine CRUD-Site mit dem Python-Framework Django ~ 5 ~
Durchsuchen Sie die Tabelle mit sqlalchemy und erstellen Sie ein Wörterbuch