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
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 <markus@oberhumer.com>
* 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