Dekodowanie Aztec 2D z dowodu rejestracyjnego pojazdu

Dawid Farbaniec

W dowodzie rejestracyjnym pojazdu można zauważyć kod dwuwymiarowy. Jest to kod Aztec 2D i jak się można domyślić, w założeniach miał usprawnić odczyt danych z tego dokumentu.

***

Wstęp

W dowodzie rejestracyjnym pojazdu można zauważyć kod dwuwymiarowy. Jest to kod Aztec 2D i jak się można domyślić, w założeniach miał usprawnić odczyt danych z tego dokumentu.

Poszczególne etapy dekodowania kodu Aztec 2D z dowodu rejestracyjnego

  • Skanowanie: Aby zeskanować kod Aztec 2D z polskiego dowodu rejestracyjnego pojazdu można użyć gotowych skanerów kodów Aztec/2D. Po zeskanowaniu otrzymamy ciąg znaków.
  • Dekodowanie Base64: otrzymany ciąg znaków jest kodowany poprzez algorytm Base64. Dodatkowo można się spotkać z dodanym jednym dodatkowym znakiem na końcu. Należy to wziąć pod uwagę i pominąć ten znak, gdy zauważymy, że funkcja Base64 nie działa poprawnie.
  • Kompresja NRV2E: Na tym etapie dane są skompresowane i należy je zdekompresować algorytmem NRV2E, którego implementację można znaleźć m.in. w darmowej bibliotece UCL autorstwa Markus F.X.J. Oberhumer (http://www.oberhumer.com/opensource/ucl/). Funkcja ucl_nrv2e_decompress_8().
  • Dane czyste (plain): Po tych wszystkich etapach otrzymujemy dane tekstowe z kodowaniem UTF16-LE, gdzie poszczególne informacje są oddzielone znakiem pionowej kreski |, której kod to 0x7C.

Rozkodowanie przykładowych danych

dowod-dane-rozkodowane-3.png

Kod źródłowy (Visual C++)

/*
* C++ implementation of NRV2E decompression algorithm
* which was used in this project to decode
* Aztec 2D from Polish Vehicle Registration Documents
* - by http://haker.info A.D. 2019
*
* Based on original UCL library written by:
* Markus F.X.J. Oberhumer <[email protected]>
* http://www.oberhumer.com/opensource/ucl/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*/

#include <iostream>
#include <string>
#include <vector>
#include <fstream>

const int START_OFFSET = 4;
std::vector<unsigned char> src;
int ilen = START_OFFSET;
int currentByte;
int currentBit;

static unsigned char GetBit()
{
    if (ilen >= src.size())
        throw std::invalid_argument("Przesunięcie jest poza zakresem.");

    if (currentBit == 0)
    {
        currentByte = src[ilen++];
        currentBit = 8;
    }

    return (unsigned char)(((unsigned int)currentByte >> --currentBit) & 1);
}

static std::vector<unsigned char> DecompressNRV2E(std::vector<unsigned char> sourceData)
{
    src = sourceData;

    int destSize = src[0] | (int)src[1] << 8 | (int)src[2] << 16 | (int)src[3] << 24;
    std::vector<unsigned char> dst(destSize);

    unsigned int olen = 0, last_m_off = 1;

    while (ilen < src.size())
    {
        unsigned int m_off, m_len;

        while (GetBit() == 1)
        {
            dst[olen++] = src[ilen++];
        }

        m_off = 1;
        while (true)
        {
            m_off = m_off * 2 + GetBit();
            if (GetBit() == 1) break;
            m_off = (m_off - 1) * 2 + GetBit();
        }

        if (m_off == 2)
        {
            m_off = last_m_off;
            m_len = GetBit();
        }
        else
        {
            m_off = (m_off - 3) * 256 + src[ilen++];
            if (m_off == 0xffffffff)
                break;
            m_len = (m_off ^ 0xffffffff) & 1;
            m_off >>= 1;
            last_m_off = ++m_off;
        }
        if (m_len > 0)
            m_len = (unsigned int)1 + GetBit();
        else if (GetBit() == 1)
            m_len = (unsigned int)3 + GetBit();
        else
        {
            m_len++;
            do
            {
                m_len = m_len * 2 + GetBit();
            } while (GetBit() == 0);
            m_len += 3;
        }
        m_len += (unsigned int)(m_off > 0x500 ? 1 : 0);

        unsigned int m_pos;
        m_pos = olen - m_off;

        dst[olen++] = dst[m_pos++];
        do dst[olen++] = dst[m_pos++]; while (--m_len > 0);
    }
    return dst;
}

static std::string base64_decode(const std::string& in) {

    std::string out;

    std::vector<int> T(256, -1);
    for (int i = 0; i < 64; i++)
        T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i;

    int val = 0, valb = -8;
    for (unsigned char c : in)
    {
        if (T[c] == -1) break;
        val = (val << 6) + T[c];
        valb += 6;
        if (valb >= 0)
        {
            out.push_back(char((val >> valb) & 0xFF));
            valb -= 8;
        }
    }
    return out;
}

int main(int argc, char* argv[])
{
    std::string test1 = "BgQAANtYAAJDAPkxAHwAQXIw7zcGNN4ANiox+w81HrUGOP8eUABSAEUA+1oAWQBEDv9OAFQAIABN3wAuClMAvlQPV/eKUhq9Wg5X7k58UtcWSVq9TF5J79pBZ+5PAEsG12bTSm5GVQBM/ntSAEH7L1dj+0MAS1vvMvovewo3Ut4wDi39HjEAN6Pbl0FNe3YgPt5Q3kv3IlSevVnX1z9FMmuCShL2WgBaG9umKADvSAApJnx75k+itwZMAEx9X0rvbkSOTXtOOF/DRy0WOW53fPYLFoMzLr0xAi3DGnevLQOCfJ/vQZ5TcBZrN0oa9k4AfA82Q4QaDzj3q8deN6sN7zIE/1x8lbMnQdwBQi5ZT86jL2tqNAr2MwAw34xSH+uPSVPYFxZThBMzON8AMJM5wQA3MwRcMX7bNcET2jInwyedE01HZ4dlM94qKy0DL38fNgAqeBszSxOvNIeKfHM7fCLxNQAwVkMtdzl7Xiw/YMyrFzxQACBWw+Hza7c3C93/NWuHg1OWRquPQ5KP02K9IBZT4QZC9oNZU7aXFiOX83U4ADJFC7ADhrNVCyOW8w9qMbEnZhdHbHxjdjIT7E4DW0M3OQuGaxYmCSSSSSr/";

    if (test1.length() % 2 == 1)
    {
        test1[test1.length() - 1] = '\0';
    }

    std::string decoded = base64_decode(test1);
    std::vector<unsigned char> decodedVec = std::vector<unsigned char>(decoded.begin(), decoded.end());

    std::vector<unsigned char> decompressed = DecompressNRV2E(decodedVec);

    std::string plainData(decompressed.begin(), decompressed.end());

    //zapisz rozkodowane dane do pliku tekstowego
    //(zmień ścieżkę według swojego systemu)
    std::ofstream outfile("D:\\0001\\file1.txt", std::ofstream::binary);

    outfile.write(plainData.c_str(), plainData.length());

    outfile.close();

    return EXIT_SUCCESS;
}

Źródło: https://haker.info/blog/details/dekodowanie-kodu-aztec-2d-z-dowodu-rejestracyjnego

0 komentarzy