Knobelaufgabe Deserialize Bond-Format [Microblog]: ein erster Ansatz

(Microblog-Post)

Hallo Mike @kuketzblog,

Also der Anschlag ist Dir gelungen :-). Nachfolgend mein (nicht abgeschlossener) „Forschungsstand“.

Zuerst einmal ein Paar Beobachtungen bzw. ein wenig Vorbereitung:

  • Die codierten Daten in dem curl-Request sind brauchbar, die in der XML nicht – als würde man sich den Textteil in einem Hex-Editor anschauen (nicht-druckbare Zeichen durch . ersetzt)
  • Man kann sich dann erstere für das weitere Vorgehen als Datei ablegen. Zum Beispiel interpretiert LuaJIT (aber Achtung, nicht Lua 5.1) Hex-Escapes in Strings genauso. Also: Den Teil in $'...' extrahieren und als s="..." in z. B. kuketz-bond-rq-raw.lua ablegen; dann z. B.
$ LUA_PATH=";;$PWD/?.lua" luajit -l kuketz-bond-rq-raw -e 'f=io.open("rq-raw.bin", "w"); f:write(s); f:close()'
  • Was dann auffällt: Die Datei ist 9804 Bytes groß, im Vergleich zu Content-Length: 9802

Ich hatte das allerdings erst im Nachhinein bewusst wahrgenommen und zuerst tatsächlich versucht, die Daten als Content-Type: application/bond-compact-binary zu interpretieren.

Bond geklont, Abhängigkeiten installiert (dabei „Stack“ das Haskell-Tool mit stack upgrade --force-download auf einen neueren Stand gebracht als aus Ubuntu 22.04, da sonst Compile-Fehler) und mithilfe des Protocol Transcoding-Beispiels aus der C+±API-Doku folgendes gebastelt (wobei einfach examples/cpp/core/serialization/serialization.cpp ersetzt wurde):

#include <bond/core/bond.h>
#include <bond/protocol/simple_json_writer.h>

#undef NDEBUG
#include <cassert>
#include <cstdio>

#include <array>
#include <iostream>

int main()
{
    FILE *f = std::fopen("rq-raw.bin", "r");
    assert(f);
    static std::array<uint8_t, 9804> request;
    const size_t bytesRead = fread(request.data(), 1, request.size(), f);
    assert(bytesRead == request.size());

    bond::blob data(request.data(), request.size());
    bond::CompactBinaryReader<bond::InputBuffer> reader(data);
    bond::bonded<bond::Unknown> payload(reader);

    bond::OutputBuffer json;
    bond::SimpleJsonWriter<bond::OutputBuffer> writer(json);

    Serialize(payload, writer);

    bond::blob jsonBlob = json.GetBuffer();
    std::cout << jsonBlob.content() << std::endl;

    return 0;
}

Außerdem habe ich

diff --git a/cmake/Config.cmake b/cmake/Config.cmake
index 3f284974..d86a6661 100644
--- a/cmake/Config.cmake
+++ b/cmake/Config.cmake
@@ -136,7 +136,9 @@ add_definitions (-D_ENABLE_ATOMIC_ALIGNMENT_FIX)
 cxx_add_compile_options(Clang
     -fPIC
     -fstrict-aliasing
-    --std=c++11
+    --std=c++17
+    -O0
+    -ggdb
     -Wall
     -Werror
     -Wno-null-dereference

Konfigurieren mache ich mit

$ cmake -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=True -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17 ..

(Ausgabe von compile_commands.json, um mithilfe von clangd den Quellcode navigieren zu können) und bauen dann mit

$ ninja serialization

Bei der Ausführung kommt man zu

cpp/inc/bond/core/detail/typeid_value.h:652: void bond::detail::MapByElement(T &, BondDataType, BondDataType, Reader &, uint32_t) [Protocols = bond::BuiltInProtocols, T = const bond::Serializer<bond::SimpleJsonWriter<bond::OutputMemoryStream<>>>, Reader = bond::CompactBinaryReader<bond::InputBuffer>]: Assertion `false' failed.

und man hat bei

(gdb) fr 8
(gdb) p/x type
$5 = {first = 0x8c, second = 0x24}

(also die Byte-Werte bei Offsets 3 und 4, die aber keinen Konstanten des Enums BondDataType entsprechen).

[Nebenbei ist anzumerken, dass es hier also keine Eingabevalidierung außer mit assert gibt. OK…]

==> Die naheliegende Folgerung ist dann, dass wir (noch) keine „Bond-Daten“ (naja, bond-compact-binary) vorliegen haben.

Und… Content-Encoding: deflate. Die sind also zuerst zu dekomprimieren. Also in das relevante RFC 1950 - ZLIB Compressed Data Format Specification version 3.3 geschaut. Unterabschnitt 2.2 sagt:

2.2. Data format

      A zlib stream has the following structure:

           0   1
         +---+---+
         |CMF|FLG|   (more-->)
         +---+---+

(...)

      CMF (Compression Method and flags)
         This byte is divided into a 4-bit compression method and a 4-
         bit information field depending on the compression method.

            bits 0 to 3  CM     Compression method
            bits 4 to 7  CINFO  Compression info

      CM (Compression method)
         This identifies the compression method used in the file. CM = 8
         denotes the "deflate" compression method with a window size up
         to 32K.  (...).  CM = 15 is reserved.  (...)

Wir aber haben CM = 0xd (dezimal 13), also einen Wert außerhalb der Spezifikation! Suchanfragen waren nicht ergiebig.

Ab dem Punkt bin ich raus. Man kann verschiedene Dekomprimierungsverfahren auf gut Glück (oder vielleicht auch systematischer) ausprobieren, die Daten statistischer Analyse unterziehen (als würde man eine historische Chiffre knacken wollen :-)) und so weiter und so fort. Wenn aber schon der Weg der Nichtkonformanz bestritten wurde… wer sagt dann, dass die Daten nicht vielleicht sogar verschlüsselt sind? Also: Viel Erfolg!

P. S. Es kann natürlich auch sein, dass meine Folgerung falsch ist. Die +2-Byte-Diskrepanz legt es nahe, dass man den zlib-Header vorliegen hat, aber wer weiß…

P. P. S. Ein kurzer Blick (keine Analyse!) in einen 256 Spalten breiten Plot der Daten mit ministat lässt mich denken: passt doch; Nullhypothese „vorliegende Daten sind komprimiert“ nicht verworfen.

2 „Gefällt mir“

Vielen Dank für deine Mühe und Einblicke in deine Erkenntnisse. Leider bin ich bis dato auch nicht weitergekommen.