Strona główna » Blog » Dekodowanie base64 o zmienionym alfabecie

Dekodowanie base64 o zmienionym alfabecie

Serwis pewnej znanej państwowej instytucji zwraca sam dla siebie dane za pomocą interfejsu typu API REST, ale zwraca jest w postaci kodowanej. Wynik zaczyna się mniej więcej tak:

{"d":"enc!331aa1bb!45!3!!49!32!50!381ab1a7!45y25!45yy91b9y23yy4y1y1a1y231a2!391a1y...."}

No i zagadaka – co to jest i jak to odczytać? Skoro przeglądarka potrafi, to znaczy to, że gdzieś w skryptach strony jest dekoder. No rzeczywiście znajdujemy skrypt, który jednakże zdaje się być sam sobie również zaszyfrowany, jego początek wygląda tak:

eval(function(p,a,c,k,e,d){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\b'+e(c)+'\b','g'),k[c])}}return p}('o a=["\b\d\b","\b\b\i","\b\b\j","\b\b\l","\b\b\s","\b\b\N","\b\b\m","\b\b\D","\b\b\z","\b\i\B","\b\i\b","\b\i\i","\b\i\j","\b\i\l","\b\i\s","\b\i\N","\b\i\m","\b\i\D","\b\i\z","\b\j\B","\e\j\e","\e\j\i","\e\j\j","\e\j\l","\e\j\s","\e\j\N","\e\j\m","\e\j\D","\e\j\z","\e\l\B","\e\l\e"

Początek „function packed” jest charakterystyczny dla standardowych zaciemniaczy kodu. Da się to względnie odszyfrować, jest wiele deszyfratorów online, np. https://freeseotool.org/javascript-unpacker.

Po deszyfracji wciąż nie jest łatwo. Zmienne mają dziwaczne nazwy, a tablice mają zwartości zapisane za pomocą kodów szesnastkowych. To ostatnie to drobiazg, bo jeżeli mam taką dziwną tablicę…

var _0xeae2=["\x79\x31\x79","\x79\x79\x32","\x79\x79\x33","\x79\x79\x34","\x79\x79\x35","\x79\x79\x36","\x79\x79\x37","\x79\x79\x38","\x79\x79\x39","\x79\x32\x30","\x79\x32\x79","\x79\x32\x32","\x79\x32\x33","\x79\x32\x34","\x79\x32\x35","\x79\x32\x36","\x79\x32\x37","\x79\x32\x38","\x79\x32\x39","\x79\x33\x30","\x21\x33\x21","\x21\x33\x32","\x21\x33\x33","\x21\x33\x34","\x21\x33\x35","\x21\x33\x36","\x21\x33\x37","\x21\x33\x38"

…to wystarczy są wrzucić w przeglądarkowy console.log i dostanę zwrot danych w ludzkiej postaci:

["y1y", "yy2", "yy3", "yy4", "yy5", "yy6", "yy7", "yy8", "yy9", "y20", "y2y", "y22", "y23", "y24", "y25", "y26", "y27", "y28", "y29", "y30", "!3!", "!32", "!33",

Hmm, znowu jakiś kod? Może, aczkolwiek jeżeli wrócimy do pierwotnej zaszyfrowanej wiadomości, to dostrzeżemy, że składała się właśnie z takich elementów. Wygląda więc to na jakiegoś rodzaju alfabet. W alfabecie nie ma jedynie początkowego trzyliterowego „enc”, które widać w danych zawracanych przez API, więc pewnie to jakiś taki rozpoznawczy ciąg znaków (zapewne pochodzący od „encoded” – zakodowany).

Wróćmy jednak na chwilę do nazw. Nazwy zmiennych są kodowane, ale nazwy funkcji nie – są takie jak kiedyś były. Znajdujemy w skrypcie cztery:

  • tajemnicza funkcja „bd”,
  • pospolicie brzmiąca „getIndex”,
  • pospolicie brzmiąca „addslashes”,
  • i pozornie pospolicie brzmiąca „_ut8_decode” – pozornie, bo ten underscore na początku jest nietypowy.

Wykorzystując ów nietypowy underscore możemy poszukać w Google, czy to nie jest jakaś znana funkcja. Jak się okazuje, istnieje tylko jeden skrypt, który zawiera tę funkcję oraz „addSlashes” i „getIndex” – to pochodzący z http://www.webtoolkit.info skrypt JS realizujący dekodowanie znaków „zakodowanych” algorytmem Base64.

No i faktycznie wydaje się, że to jest nasz skrypt, ale coś się nie zgadza. W oryginalnym skrypcie w jednym miejscu jest coś zupełnie inaczej. Zamiast tego dziwnego alfabetu wskazanego wcześniej jest zwyczajny alfabet o taki:

_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

Hmm, czyżby więc programiści z państwowej naszej instytucji jedynie podmienili alfabety?

Sprawdźmy! Fragment zakodowanego tekstu bez początkowego „enc” wrzucamy w zmienną „test”, podmieniamy alfabety i dekodujemy base64…

<?php
// fragment kodowanej wiadomości bez początkowego 'enc'
$test = '!331aa1bb!45!3!!49!32!50!381ab1a7!45y25!45yy91b9y23yy4y1y1a1y231a2!391a1y25y30!39!45y22yy3y20y29!36!33!401b8!38!471a0!52!38!491bb!45y25!45yy9171!35y29yy2!5!!39!49!32!49y26y29!40!53!35!34!36!44!391aby241a1!37!34yy21a3y25!49y28!44!38!49!32y27!381aby20!52!36!34y201a9!3!yy7!321bb!38!50!48y29!35!34yy21b8!39!50y27!5!!34yy3yy91b9y23yy4y1y1a1y231a2!391a1y25y30!40!39yy9!451a0!39yy9!47y28!44!38!49!32y29!35!34yy21b8!39!50y28y27!39!49yy61aa!38!49yy6y27!40!33y201bb!34yy3yy91bbyy9yy4y1y!52y251a1!39173y23yy4y1y1b9y23!46y231aay23!46!471aay26yy61b91b8!35y301a7!45y22yy3y20!3!!4!!34y1y!45y25!45y20y27yy9!451b9!45y30!49yy61a9!401abyy5!45y25!45y20y27!32!3!y20y23y29!3!y24!37y30!47yy5!43!3!1a4!52y22y30171!33yy2y27y29yy2y27y301a3y28y30!32yy5yy6!34y301a4!40yy2yy9yy51a8y29y22!46yy9!43y29!3!1a31b7y30!3!!48y22y30171!33yy2y27!3!!52yy2yy9yy51bay26!3!yy5!32y29y30!47!48y22y27y29yy2!34yy9yy6y24!37!33!3!y30yy6!43!3!172!34y271a4!3!!45y22yy3y20!34!381ab!52!48!401ab172!47!4!!50y281aa!381a1yy91a9yy9!471a4yy2!33!47172!34y29!3!!32yy4y291a3!48yy6yy9!451b9!45!3!yy71721aa!37!33yy61a3yy9!46!5!!45!391aa!521a8!36y23!33yy3!381aa!40!52!36!33y241b4!37y29yy91bbyy9!47!401ba!37!331a8!44yy9!46!5!!45!3!1aa!521a8!36y23!33yy3!381aa!40!52!36!33y23!45y22yy3y20y22!381aby28y27!381aby241a9!40yy71721aa!4!y29yy91a9yy9!46yy91aby22y30!3!1b9y23yy3yy91bbyy9';

$key = ["y1y", "yy2", "yy3", "yy4", "yy5", "yy6", "yy7", "yy8", "yy9", "y20", "y2y", "y22", "y23", "y24", "y25", "y26", "y27", "y28", "y29", "y30", "!3!", "!32", "!33", "!34", "!35", "!36", "!37", "!38", "!39", "!40", "!4!", "!42", "!43", "!44", "!45", "!46", "!47", "!48", "!49", "!50", "!5!", "!52", "!53", "1b4", "1bb", "1ba", "1b7", "1b8", "1b9", "1a0", "1a1", "1a2", "1a3", "1a4", "1ab", "1aa", "1a7", "1a8", "1a9", "170", "171", "172", "173", "174", "17b", "71a"];

$original = str_split("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=");

$pairs = [];

for ($i = 0; $i < 65; $i++) {
    $pairs[$key[$i]] = $original[$i];
}

echo base64_decode(strtr($test, $pairs));

…sprawdźcie sami, że wynik wygląda tak:

[{"Regon":"000237297","RegonLink":"<a href='javascript:danePobierzPelnyRaport(\"000237297\",\"DaneRaportPrawnaPubl\", 0);'>000237297<\/a>","Typ":"P","Nazwa":"PUBLICZNA SZKOŁA PODSTAWOWA NR.2 IM.MIKOŁAJA KOPERNIKA W SZYDŁOWCU","Wojewodztwo":"MAZOWIECKIE","Powiat":"szydłowiecki","Gmina":"Szydłowiec","KodPocztowy":"26-500",

Czyli sukces! 🙂

Przy okazji wyjaśnia się nazwa jednej z funkcji w skrypcie: „bd” to po prostu „base64_decode”.

Na koniec kilka pytań: po co właściwie te dane są kodowane? Przecież owa instytucja udostępnia informację publiczną. Jaki jest sens utrudniania korzystania z API? Tu nie ma żadnych tajemnic przecież. Mogą pojawić się dane osobowe, ale zwyczajnie podlegają pod RODO jeżeli ktoś chciałby na nich zbijać pieniądze.

Nie wiem po co takie dziwne utrudnienia. Dodatkowo, dane zwracane powyższym kodowaniem są objętościowo większe. Nie tylko dlatego, że base64 standardowo zwiększa objętość, ale i dlatego, że użyto alfabet o „literach” złożonych z wielu znaków.