Online: 0x02D (45)
haker.iиfø  — Etyczny hacking_
Spreading knowledge like a virus.

Dekodowanie kodu Aztec 2D z dowodu rejestracyjnego

   Dawid Farbaniec    2290 słów

1. Kod Aztec 2D z dowodu rejestracyjnego

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.

Mało by mnie to interesowało, gdyby nie fakt, że sposób kodowania w nim danych - do niedawna - znany był tylko wąskiej grupie ludzi. Taka się wydaje powszechna rzecz ten kod i takie ograniczenia dostępu do informacji. Zgodnie z moją pamięcią temat został podjęty pierwszy raz na wykop.pl (podziękowania i pozdrowienia!), dalej temat poruszały kolejne witryny (np. zaufanatrzeciastrona.pl), osoby etc.

Kod Aztec 2D na dowodzie rejestracyjnym
Rysunek 1.1. Przykładowy kod Aztec 2D na dowodzie rejestracyjnym

2. Metoda kodowania danych w dowodzie rej. pojazdu

Na rysunku 2.1. poniżej przedstawiono schemat poszczególnych kroków jakie należy podjąć, aby móc odczytać dane zeskanowane z kodu Aztec 2D dowodu rejestracyjnego pojazdu.

Dekodowanie kodu Aztec 2D z dowodu rejestracyjnego
Rysunek 2.1. Metoda dekodowania znaku Aztec 2D z dowodu rejestracyjnego

Postaram się poniżej rozwinąć trochę opis poszczególnych etapów:

  • 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.

3. Dekodowanie Aztec 2D z dowodu rej. pojazdu — kody źródłowe


3.1. C#.NET — kod źródłowy (License)


VehicleDocumentAztecDecoder.cs

/*  * C#.NET implementation of NRV2E decompression algorithm  * - 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. */ using System; using System.Text; namespace FreeAztecVehicleDocDecoder { public class VehicleDocumentAztecDecoder { private const int START_OFFSET = 4; private byte[] src; private int ilen = START_OFFSET; private int currentByte; private int currentBit; private byte[] dst; public string Decode(string text) { byte[] decoded = Base64Decode(text); byte[] decompressed = DecompressNRV2E(decoded); return Encoding.Unicode.GetString(decompressed); } private byte[] DecompressNRV2E(byte[] sourceData) { src = sourceData; uint olen = 0, last_m_off = 1; dst = new byte[BitConverter.ToInt32(src, 0)]; while (ilen < src.Length) { uint 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 = (uint)1 + GetBit(); else if (GetBit() == 1) m_len = (uint)3 + GetBit(); else { m_len++; do { m_len = m_len * 2 + GetBit(); } while (GetBit() == 0); m_len += 3; } m_len += (uint)(m_off > 0x500 ? 1 : 0); uint m_pos; m_pos = olen - m_off; dst[olen++] = dst[m_pos++]; do dst[olen++] = dst[m_pos++]; while (--m_len > 0); } return dst; } private byte GetBit() { if (ilen >= src.Length) throw new Exception("Przesunięcie jest poza zakresem."); if (currentBit == 0) { currentByte = src[ilen++]; currentBit = 8; } return (byte)(((uint)currentByte >> --currentBit) & 1); } private byte[] Base64Decode(string textToDecode) { if (string.IsNullOrWhiteSpace(textToDecode)) return new byte[0]; if (textToDecode.Length % 2 == 1) { textToDecode = textToDecode.Substring(0, textToDecode.Length - 1); } return Convert.FromBase64String(textToDecode); } } }

Program.cs

/*  * C#.NET decoder of Aztec 2D used on 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. */ using System; using System.Collections.Generic; using System.Text; namespace FreeAztecVehicleDocDecoder { class Program { static void Main(string[] args) { //Przykładowy ciąg Aztec 2D string test1 = "BgQAANtYAAJDAPkxAHwAQXIw7zcGNN4ANiox+w81HrUGOP8eUABSAEUA+1oAWQBEDv9OAFQAIABN3wAuClMAvlQPV/eKUhq9Wg5X7k58UtcWSVq9TF5J79pBZ+5PAEsG12bTSm5GVQBM/ntSAEH7L1dj+0MAS1vvMvovewo3Ut4wDi39HjEAN6Pbl0FNe3YgPt5Q3kv3IlSevVnX1z9FMmuCShL2WgBaG9umKADvSAApJnx75k+itwZMAEx9X0rvbkSOTXtOOF/DRy0WOW53fPYLFoMzLr0xAi3DGnevLQOCfJ/vQZ5TcBZrN0oa9k4AfA82Q4QaDzj3q8deN6sN7zIE/1x8lbMnQdwBQi5ZT86jL2tqNAr2MwAw34xSH+uPSVPYFxZThBMzON8AMJM5wQA3MwRcMX7bNcET2jInwyedE01HZ4dlM94qKy0DL38fNgAqeBszSxOvNIeKfHM7fCLxNQAwVkMtdzl7Xiw/YMyrFzxQACBWw+Hza7c3C93/NWuHg1OWRquPQ5KP02K9IBZT4QZC9oNZU7aXFiOX83U4ADJFC7ADhrNVCyOW8w9qMbEnZhdHbHxjdjIT7E4DW0M3OQuGaxYmCSSSSSr/"; VehicleDocumentAztecDecoder dec = new VehicleDocumentAztecDecoder(); string result = dec.Decode(test1); Console.OutputEncoding = Encoding.Unicode; //Wyświetl rozkodowane dane na konsoli tekstowej Console.WriteLine(result); #if DEBUG Console.ReadLine(); #endif } } }

3.2. C++ — kod źródłowy (License)


/* * 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; }

03 stycznia 2020 r. — poprawiono kodowanie polskich znaków w przykładzie dla Visual C++


Rysunek 3.1. Przykład dla Visual C++ zapisuje rozkodowane dane do pliku tekstowego


4. Opis poszczególnych pól z danymi

Przykładowe rozkodowane dane w stanie surowym przedstawiono na rysunku 4.1 poniżej.

Przykładowe rozkodowane dane
Rysunek 4.1. Przykład rozkodowania kodu Aztec 2D z dow. rej. - dane surowe

W przypadku pisania własnego oprogramowania bazującego na powyższym kodzie, na pewno będzie przydatny opis poszczególnych pól.

Pozycja (indeks) Opis
0Wersja protokołu
1Seria i numer dowodu
2Kod terytorialny urzędu rejestrującego
3Organ wydający - Nazwa
4Organ wydający - Gmina
5Organ wydający - Ulica i nr
6Organ wydający - Kod i miejscowość
7Numer rejestracyjny pojazdu
8Marka
9Typ homologacji
10Wariant homologacji
11Wersja homologacji
12Model
13Numer VIN
14Data wydania dowodu rejestracyjnego
15Okres ważności dowodu
16Nazwa właściciela
17Imiona
18Nazwisko
19?
20PESEL
21Kod pocztowy
22Gmina
23Miejscowość
24Ulica
25Nr domu
26Nr mieszkania
27Nazwa właściciela
28Imiona
29Nazwisko
30?
31PESEL
32Kod pocztowy
33Gmina
34Miejscowość
35Ulica
36Nr domu
37Nr mieszkania
38Maksymalna masa całkowita [kg]
39Maksymalna masa całkowita pojazdu [kg]
40Maksymalna masa całkowita zespołu pojazdów [kg]
41Masa własna
42Kategoria pojazdu
43Numer świadectwa homologacji typu pojazdu
44Liczba osi
45Maksymalna masa całkowita przyczepy z hamulcem
46Maksymalna mas całkowita przyczepy bez hamulca
47Stosunek mocy do masy (w KW/kg)
48Pojemność silnika [cm3]
49Moc silnika [kW]
50Rodzaj paliwa
51Data pierwszej rejestracji pojazdu
52Liczba miejsc siedzących
53Liczba miejsc stojących
54Rodzaj pojazdu
55Przeznaczenie
56Rok produkcji
57Dopuszczalna ładowność
58Największy dopuszczalny nacisk osi
59Numer karty pojazdu
60Kod ITS
61?
62?
63?
64?
65?

5. Podsumowanie

Dzięki za przeczytanie. Jeśli są jakieś pytania lub sugestie dot. tego wpisu to proszę pisać.

Dawid Farbaniec


Tagi:  reverse-engineering  visual-cpp  c-sharp 

Komentarze czytających

Czytelnik napisał:
Rewelacja, super robota. Mam pytanie, jak wykonać to w drugą stronę tzn. aby otrzymać kod Aztec obrazkowy z posiadanych danych.
 rok temu (13 lutego 2019 godz. 18:55)
Dawid (haker.info) napisał:
Ja próbowałbym wykonać poszczególne kroki dekodowania w odwrotnej kolejności :)

1. Zapisać dane tekstem w kodowaniu UTF-16LE oddzielone znakiem pionowej linii | (0x7C) w odpowiedniej kolejności
2. Skompresować algorytmem NRV2E (wariant 8-bitowy)
3. Zakodować za pomocą algorytmu Base64
4. No i o ten ostatni krok pewnie jest pytanie. Trzeba znaleźć coś co wygeneruje Aztec 2D. Nie pomogę w wyborze, bo nigdy takich bibliotek nie szukałem. Ale powinno być coś takiego, gdyż ten cały Aztec 2D to dość popularny barcode.

Dla wielu programistów problemem było to, że do niedawna algorytm nie był jawny dla wszystkich. Nie było wiadomo, że to kompresja NRV2E w wariancie 8-bitowym. Teraz wiadomo czego należy użyć i pozostaje tylko odpowiednie zaprogramowanie tego.

Pozdrawiam!
 rok temu (13 lutego 2019 godz. 20:02)
Czytelnik napisał:
No właśnie z kompresją algorytmem NRV2E mam problem.
 rok temu (13 lutego 2019 godz. 20:24)
Dawid (haker.info) napisał:
Włączyłem wyszukiwanie w źródłach biblioteki UCL i oprócz dekompresji są tam też funkcje kompresujące np. ucl_nrv2e_99_compress (nazwa pliku n2_99.ch. Trzeba eksperymentować. Ja bibliotekę UCL używałem tylko na potrzeby tego artykułu. I to tylko funkcję dekompresującą.
 rok temu (13 lutego 2019 godz. 21:03)
Krzysiek napisał:
Wszystko super opisane. Mam pytanie dotyczące implementacji w C++.
Jak rozwiązać problem polskich znaków.
Zamiast litery Ł otrzymuje dwuznak A? (ten znak zapytania jest w takim jakby kwadracie).
Czy mógłbyś mi napisać w jaki sposób wyświetlić te polskie znaki?
 rok temu (26 lipca 2019 godz. 13:42)
Dawid (haker.info) napisał:
Myślę, że w napisie (zmiennej) znaki są poprawne, a jedynie konsola tekstowa Windows je źle wyświetla.
Dodaj na początku funkcji głównej main wywołanie: setlocale(LC_ALL, "Polish");
Powinno to wyglądać:
//(...)
int main(int argc, char *argv[])
{
    setlocale(LC_ALL, "Polish");
    //(...)
Dziękuję za zainteresowanie artykułem :)
 rok temu (26 lipca 2019 godz. 23:05)
Krzysiek napisał:
Po dodaniu setlocale(LC_ALL, "Polish"); litera Ó wyświetla się poprawnie ale litera Ł w dalszym ciągu wyświetla się błędnie (nawet przy zapisie do pliku).
Czy dysponujesz może jakimś opisem algorytmu NRV2E?
 rok temu (27 lipca 2019 godz. 13:15)
Dawid (haker.info) napisał:
Uruchomiłem teraz jeszcze raz kod źródłowy w C# i tam polskie znaki diakrytyczne wyświetlają się poprawnie. Coś widocznie jest nie tak z kodowaniem znaków w przykładzie dla C++. Nie znalazłem dokumentów, które by opisywały algorytm NRV2E. Przeglądałem tylko jego oryginalną implementację jako kod z biblioteki UCL.
 rok temu (28 lipca 2019 godz. 01:20)
Jacek napisał:
"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."

znasz jakąś bibliotekę która pozwala np w xamarin zeskanować taki kod ? Oczywiście nie pytam o bibliotekę od peloka.
 rok temu (18 września 2019 godz. 14:53)
Dawid (haker.info) napisał:
Jest dużo profesjonalnych bibliotek, ale płatnych. Warto pomyśleć czy nie kupić profesjonalnego SDK, który odczyta kod obrazkowy (zaoszczędzi to czas na własne rozpoznawanie obrazów), a resztą aplikacji zająć się samodzielnie skoro algorytm jest już jawny.
 rok temu (19 września 2019 godz. 19:48)
Klausvonbrown napisał:
Hej! Czy masz może w zanadrzu wersje dekodera dla php lub javascript aby dekodować to na stronie www (html) ? Probowałem przekształcić kod dex4er z github'a, ale nie najlepiej mi to wychodzi :(
 ponad 10 miesięcy temu (03 listopada 2019 godz. 12:17)
Dawid (haker.info) napisał:
Będę miał na uwadze rozbudowanie tego artykułu o kolejne implementacje dla różnych języków programowania. Jednak nie mogę obiecać kiedy dokładnie.

Na ten czas można stworzyć witrynę w ASP.NET MVC i użyć implementacji w C#, która wykona się po stronie serwera.

Natomiast dla JavaScript to może Bridge.NET (https://bridge.net) — nie używałem, ale zgodnie z opisem na stronie projektu:
Open Source C# to JavaScript Compiler and Frameworks.
Run your C# Apps on the web.
 ponad 10 miesięcy temu (04 listopada 2019 godz. 14:17)
Łukasz :) napisał:
Też chętnie przygarnąłbym dekoder AZTEC dla PHP
 ponad 9 miesięcy temu (18 grudnia 2019 godz. 12:21)
jas napisał:
Super sprawa z tym algorytmem. Zupełnie nie rozumiem dlaczego było to okryte taką tajemnicą skoro zawiera jawne informacje wydrukowane w dowodzie. Gratuluję rozwiązania problemu i ułatwienia życia innym :)
 ponad 3 tygodnie temu (03 września 2020 godz. 18:41)



Wszystkie treści umieszczone na tej witrynie są chronione prawem autorskim. Surowo zabronione jest kopiowanie i rozpowszechnianie zawartości tej witryny bez zgody autora. Wszelkie opublikowane tutaj treści (w tym kody źródłowe i inne) służą wyłącznie celom informacyjnym oraz edukacyjnym. Właściciele tej witryny nie ponoszą odpowiedzialności za ewentualne niezgodne z prawem wykorzystanie zasobów dostępnych w witrynie. Użytkownik tej witryny oświadcza, że z zamieszczonych tutaj danych korzysta na własną odpowiedzialność. Wszelkie znaki towarowe i nazwy zastrzeżone zostały użyte jedynie w celach informacyjnych i należą wyłącznie do ich prawnych właścicieli. Korzystając z zasobów witryny haker.info oświadczasz, że akceptujesz powyższe warunki oraz politykę prywatności.