[PYTHON] Créer un analyseur de données binaires à l'aide de Kaitai Struct

introduction

Calendrier de l'Avent du Département étudiant LOCAL Jour 11

J'avais peur que tout le monde soit têtu et "N'est-ce pas trop faible ...?", Alors j'ai décidé de lancer une balle changeante. Dans la mesure où j'ai cherché, il n'y a pas de littérature japonaise, donc je pense que les personnes qui recherchent ceci à l'avenir verront cet article presque automatiquement. Je voudrais vous demander à tous. Si vous pensez: "De quoi parlez-vous? N'est-ce pas plein d'informations incorrectes?", Veuillez commenter. Je ferai de mon mieux pour y remédier.

Qu'est-ce que Kaitai Struct?

Aperçu

Officiel: kaitai.io

Kaitai Struct est un langage déclaratif utilisé pour décrire des structures de données binaires.

Le code source de l'analyseur de données binaires peut être généré automatiquement en fonction de la structure de données écrite dans votre propre langue.

Langues prises en charge (à compter du 2 décembre 2019)
  • C++ / STL
  • C#
  • Go (entry-level support)
  • Java
  • JavaScript
  • Lua
  • Perl
  • PHP
  • Python
  • Ruby

license Le compilateur et le visualiseur décrits plus tard sont GPL v3 +, et la bibliothèque pour chaque langage est MIT (JS est Apache v2). Cela signifie-t-il que le code source généré à l'aide du compilateur infectera la GPL ...? Veuillez me dire une personne détaillée.

Installation

Kaitai Struct Compiler (KSC) Pour plus d'informations sur l'installation ici Mac est un coup avec brew install kaitai-struct-compiler. Pour Windows, suivez le lien ci-dessus pour télécharger le programme d'installation.

Les distributions basées sur Debian / Ubuntu peuvent installer des paquets à partir du dépôt officiel .deb.
# 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
Si vous utilisez un autre système d'exploitation, clonez-le depuis [ici](https://github.com/kaitai-io/kaitai_struct_compiler) et compilez-le.

Kaitai Struct Visualizer (KSV) Ceci est un visualiseur simple pour les fichiers .ksy. Écrit en «Ruby», il est disponible sous forme de paquet «gem».

gem install kaitai-struct-visualizer

(Dépôt Git)

Essayez d'utiliser

Pour les fichiers bien connus, il existe un fichier .ksy dans le dépôt officiel github (https://github.com/kaitai-io/kaitai_struct_formats). (Si vous souhaitez utiliser le fichier .ksy qui existe ici, veuillez vérifier la licence décrite dans meta / license dans le fichier.) Si vous écrivez un nouveau .ksy, envoyez une pull request. (kaitai_struct_formats/CONTRIBUTING.md)

Exemple) matrice

<détails>

Enregistrer dans un fichier (np.array) </ summary>

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))

Je vais utiliser KS pour charger le `test.matrix` généré par le code ci-dessus.

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  

La structure du fichier est du début

  1. b'MAT\x01\x02/'
  2. Nombre de matrices existantes (2 octets)
  3. forme et décalage pour chaque matrice ((8 * nombre de matrices) octets)
  4. Corps de la matrice Il est devenu.

Écrivons ceci dans matrix.ksy.

KSY (Kaitai Struct YAML) déclare un seul type défini par l'utilisateur (littéralement traduit de officiel). Type défini par l'utilisateur

  • meta
  • doc
  • seq
  • types
  • instances
  • enums Consiste en. Vous n'êtes pas obligé de tout avoir. Voir la référence officielle pour plus d'informations.

meta

meta


meta:
  id: matrix
  endian: le

Décrivez le nom du type défini par l'utilisateur à décrire dans meta / id. Il doit toujours être dans le fichier «.ksy». meta / endian décrit l'endian par défaut utilisé dans la structure ( 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

Décrivez la structure des données dans seq. ʻIdest le nom de la variable. Si les données sont une constante, écrivez la constante danscontents. Si vous voulez obtenir la valeur, décrivez le type de données dans type(voir [ici](https://doc.kaitai.io/ksy_reference.html#primitive-data-types) pour plus de détails). Vous pouvez également utiliser les types décrits danstypesdécrits plus loin. Ici, le type «header» est utilisé. repeat peut contenir n'importe lequel de ʻexpr, ʻeos, ʻuntil (voir ici pour plus de détails). ) Si vous entrez ʻexpr, entrez le nombre de répétitions dans repeat-expr`.

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

Les types définis par l'utilisateur peuvent être imbriqués dans types. Le type «en-tête» utilise des «instances», qui peuvent être utilisées pour lire des données autres que celles qui existent en séquence, telles que «seq».

header.instances


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

L'utilisation est très similaire à «seq». ʻId est le mat_body ici. ʻO est le flux IO à utiliser. pos est le nombre d'octets depuis le début de ʻio`. «type» est le même que pour «seq».

À propos des variables

Certains champs (dans ce cas, repeat-expr, pos, ʻio) peuvent faire référence à des variables ainsi qu'à des valeurs constantes. Vous ne pouvez pas voir les données qui n'ont pas encore été lues. Les données ont une structure arborescente (c'est facile à comprendre si vous utilisez ksv), et vous pouvez spécifier l'élément parent avec _parent. Vous pouvez également spécifier l'élément supérieur avec _root`.

Visualize

À ce stade, vous avez écrit le code suivant.

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

Visualisons cela en utilisant ksv (Kaitai Struct Visualizer). L'utilisation est 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

Il semble qu'il puisse être bien lu.

Décompression de fichier

C'est le sujet principal. J'ai fait un fichier compressé dans l'article ici. Cette fois, décompressez ce fichier compressé à l'aide de KS. Voir l'article pour la structure du fichier.

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 spécifie l'encodage par défaut à utiliser avec type: str. repeat: eos se répète jusqu'à la fin du flux. process: zlib répond aux données lues avec zlib. (Très pratique)

  • Pour plus d'informations sur process, cliquez ici](https://doc.kaitai.io/ksy_reference.html#spec-process)

Générez du code à partir de mcp.ksy en utilisant 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)

Le code généré par «mcp.py». Écrivons un script de décompression en utilisant ceci.

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)

Vous pouvez répondre avec python extract.py <target.mcp> [dossier_sortie]

Pour lire le fichier, utilisez KaitaiStruct.from_file (file_path). Si vous voulez lire la chaîne d'octets telle quelle, utilisez KaitaiStruct.from_bytes (bytes). Pour les flux IO, utilisez KaitaiStruct.from_io (io).

à la fin

Je pense que KS est assez pratique. C'est facile à écrire et vous pouvez l'utiliser dans votre langue préférée, de sorte que le coût d'apprentissage de nouvelles choses est très faible. La référence officielle est honnêtement difficile à lire, mais plus de gens comme moi écriront des articles sur KS à l'avenir (probablement).

Souhaitez-vous "démonter" avec KS?

Recommended Posts

Créer un analyseur de données binaires à l'aide de Kaitai Struct
Créer un bot de collecte de données en Python à l'aide de Selenium
Créer un fichier de données factice
Créez instantanément un diagramme de données 2D à l'aide de matplotlib de python
Créer une interface graphique python à l'aide de tkinter
Créer un dictionnaire imbriqué à l'aide de defaultdict
Créer un fichier binaire en Python
Créer une API CRUD à l'aide de l'API rapide
Créez un wrapper de langage C à l'aide de Boost.Python
Créer une API qui renvoie les données d'un modèle à l'aide de turicreate
Créer un graphique à l'aide du module Sympy
Création d'une application d'analyse de données à l'aide de Streamlit
Créez rapidement des données de classification de documents à l'aide de NLTK
Créer un bloc de données à partir d'Excel à l'aide de pandas
Créer un fichier GIF en utilisant Pillow en Python
Créons une API REST en utilisant SpringBoot + MongoDB
Créer un arbre phylogénétique à partir de Biopyton en utilisant ClustalW2
Une histoire sur la gestion des données binaires en Python
Créer des données d'imprimante 3D (fichier STL) à l'aide de CadQuery
Créer une carte Web en utilisant Python et GDAL
Créer un système de notification des visiteurs à l'aide de Raspberry Pi
Créez un fichier MIDI en Python en utilisant pretty_midi
J'ai écrit un analyseur japonais en japonais en utilisant pyparsing.
Créez une interface graphique sur le terminal à l'aide de curses
Créer une application de gestion de partition shogi à l'aide de Django 5 ~ Passer les données de la base de données au modèle ~
Lire la source Python-Markdown: Comment créer un analyseur
Créez un capteur de couleur à l'aide d'une tarte à la râpe et d'une caméra
[CRUD] [Django] Créer un site CRUD en utilisant le framework Python Django ~ 1 ~
Créez des données factices à l'aide des packages NumPy et Faker de Python
[Python] Générer ValueObject avec un constructeur complet à l'aide de classes de données
Créer un pseudo serveur d'API REST à l'aide de pages GitHub
[CRUD] [Django] Créer un site CRUD en utilisant le framework Python Django ~ 2 ~
J'ai essayé de lire les données d'un fichier en utilisant Node.js.
[CRUD] [Django] Créer un site CRUD en utilisant le framework Python Django ~ 3 ~
[CRUD] [Django] Créer un site CRUD en utilisant le framework Python Django ~ 4 ~
[CRUD] [Django] Créer un site CRUD en utilisant le framework Python Django ~ 5 ~
Rechercher la table à l'aide de sqlalchemy et créer un dictionnaire