(Właściwie nie mam pomysłu do jakiego działu to wrzucić)
Kilka osób było zainteresowanych, więc bardzo proszę.
Disclaimer: przedstawiam rozwiązania z punktu widzenia kogoś, kto zna się na kryptografii, trochę zna się na ogólnie rozumianym bezpieczeństwie, natomiast na pewno nie zna się na javascript. Dla mnie właśnie to stanowiło największy problem, większość czasu spędziłem grzebiąc w dokumentacji js. Smutne. Nie odpowiadam zatem za błędy merytoryczne związane z js. Mimo wszystko uważam, że moje rozwiązania pokazują ciekawe podejście, tzn okazuje się, że bez znajomości języka można zrobić dość zaawansowaną analize kodu w tym języku i coś w stylu reverse engineer, co na pewno łatwe nie jest.
Zachęcam do zrobienia tego samodzielnie, dużo funu jest moim zdaniem w takich zagadkach, więc jeśli ktoś w ogóle tego nie ruszał, to niech chociaż spróbuje.
CZĘŚĆ 1: http://gynvael.coldwind.pl/xacrackme/
Można to rozwiązać co najmniej na 3 sposoby (a przynajmniej ja na 3 sposoby wpadłem). Przedstawię rozwiązanie najszybsze, najsprytniejsze.
Zobaczmy co dzieje się od samego początku, tj. od momentu kliknięcia przycisku Submit
/Prześlij
1. form.addEventListener('submit', function (e) {
2. e.preventDefault();
3. password = getPass().toLowerCase();
4. if (sha256(password) === HASH) {
5. console.info('Valid password: %s', password);
6. alert('Success!');
7. location.href = password + "k.html";
8. } else {
9. error.classList.add('visible');
10. clearTimeout(tid);
11. tid = setTimeout(function () {
12. error.classList.remove('visible');
13. }, 1500);
14. console.warn('Wrong password: %s', password);
15. }
16. }
Widzimy, że wielkość liter w haśle prawdopodobnie nie ma znaczenia. Nie wiemy jeszcze co robi funkcja getPass
, nie jest to natomiast nam potrzebne, zresztą tak samo jak ta uwaga (co okaże się poźniej), ale jest ona potencjalnie bardzo ważna, dlatego o tym wspominam.
Można przypuszczać, że stroną docelową, naszym celem jest location.href
. Hasło sprawdzane jest w sposób tradycyjny: przechowywany jest hasz hasła, poźniej haszowane jest wpisane hasło (w tym wypadku wynik funkcji getPass
) i owe hashe są porównywane. Hash jednak jest zazwyczaj problemem nie do przeskoczenia.
const HASH = "aaff6f579e5125ce8c8824b9dbc4c2bec20727f0558b914bd6ee910e1b3348ce";
Mimo wszystko spróbujmy szczęścia. Oczywiście istnieją przecież jakies "bazy danych" dla sha256
...
No i okazuje się, że https://crackstation.net/ jest przekonany, że odpowiedzią jest basilis
.
location.href = password + "k.html";
Sprawdźmy zatem: http://gynvael.coldwind.pl/xacrackme/basilisk.html
Bingo. Jeśli ktoś w tym momencie zwątpił w bezpieczeństwo sha256
i swoich haseł, to uspokajam.
Trochę o samych funkcjach haszujących:
funkcja haszująca to najczęsciej funkcja z jakiegoś zbioru X
w skończony podzbiór liczb naturalnych P
z założeniem, że moc zbioru X
jest sporo większa od mocy zbioru P
. X
'em może być cokolwiek - inne liczby, stringi, hasła. Proste funkcje hashujące są używane w tablicach haszujących (std::unordered_map
w C++), natomiast sha2
jest przykładem funkcji haszującej kryptograficznie bezpiecznej. Co to znaczy? To, że funkcja ta może być uważana za "jednokierunkową", tzn łatwo jest obliczyć sha(jakis_string)
, natomiast mając wynik tej operacji odzyskanie jakis_string
jest praktycznie niemożliwe. Dlatego jest to bezpieczne. Matematycznie ściśle mówiąc, nie wiemy, czy jest to funkcja jednokierunkowa, nie wiemy w ogóle, czy takowe istnieją. W praktyce były przypadki wykazania słabości funkcji kiedyś uważanych za kryptograficznie bezpieczne (md2
, md4
, md5
, sha1
). sha2
(sha256
i sha512
) jednak trzyma się dobrze.
Mamy 3 podstawowe sposoby przechowywania haseł użytkowników.
- sposób bardzo głupi: plaintext. Niestety część serwisów dalej z tego korzysta (raz miałem sytuację, w której zapomniałem hasła i wysłali mi je na maila Oo)
- sposób głupi: trzymanie haszy haseł. Jasne jest jak weryfikujemy poprawność hasła, którym próbuje uwierzytelnić się użytkownik. Nie jest to jednak najlepszy sposób z dwóch powodów (a przynajmniej dwa w tym momencie przychodzą mi na myśl). Po pierwsze - słabe, krótkie hasła można złamać korzystając chociażby z tej bazy, którą wykorzystałem do złamania etapu pierwszego tego konkursu. Po drugie - mając identyczne hasze wiemy, że hasła również są identyczne. Tutaj dorzucamy troche heurystyki, jakieś statystyki najpopularniejszych haseł i już.
- sposób mądry (trochę w uproszczeniu): dla każdego hasła
p
trzymamy unikalny ciąg "losowych" znakóws
. Tak zwyczajnie, w plaintexcie. Obok tego trzymamy hasz zh + s
. Teraz kiedy użytkownik się loguje, doklejamys
do jego hasła i sprawdzamy się się zgadza. Bardzo proste, a eliminuje dwa najpoważniejsze słabości metody powyżej.
Każdy poważny serwis / os / program / cokolwiek używa trzeciego sposobu.
Wracając do konkursu.
CZĘŚĆ 2:
Tak jak wcześniej, spróbujmy analizować wykonanie kodu od momentu wciśnięcia "Decode".
var decodingFn = function () {};
1. document.querySelector('form').addEventListener('submit', function (e) {
2. e.preventDefault();
3. runDecoding();
4. }
1. function runDecoding() {
2. createDecodingFn();
3. decodingFn();
4. }
1. function createDecodingFn() {
2. try {
3. eval(XORCipher.decode(document.querySelector(p2.value).innerHTML.substr(0,13) + '...', SECRET1));
4. } catch(e) {
5. window.decodingFn = function () {}
6. }
7. }
Widzimy ciekawą rzecz. Funkcja decodingFn
nie jest explicite podana, jest tworzona dynamicznie (createDecodingFn
). Najbardziej interesuje nas zatem:
eval(XORCipher.decode(document.querySelector(p2.value).innerHTML.substr(0,13) + '...', SECRET1))
Funkcja eval
wykonuje kod, jaki dostała jako argument. Można zatem przypuszczać, że będzie to coś w stylu window.decodingFn = function () {[...]
. Okej, wiemy mniej więcej co ma być argumentem. Lecimy dalej.
http://gynvael.coldwind.pl/xacrackme/XORCipher.js
Z tego potrzebne będzie nam jedynie to, że pierwszym argumentem funkcji z XORCipher
jest klucz do szyfrowania / odszyfrowania.
XORCipher.decode(document.querySelector(p2.value).innerHTML.substr(0,13) + '...', SECRET1)
Z tego co zrozumiałem, funkcja querySelector
"chwyta" obiekt, którego id, nazwe, cośtam podaliśmy jako argument i nam go oddaje. Mały research i mamy http://www.w3schools.com/jquery/jquery_ref_selectors.asp
W polu p2
(pass 2) musi być zatem jakiegoś typu selector. Można strzelać, że chodzi o wybranie konkretnego elementu po jego id, sensownie to brzmi. Kiedy już mamy nasz obiekt, robimy na nim innerHTML
. Działa to chyba troche tak, jakbyśmy brali kod HTML, który jest pod spodem naszego obiektu (który tworzy, definiuje ten obiekt). To ma sens. Próbujemy, odpalamy konsole w przeglądarce, w pass 2 wklepujemy jakiś id-selector, strzelamy i dla wartości "#legend": bingo2. Mamy jedno hasło i funkcję dekodującą.
1. window.decodingFn = function() {
2. try { decodedData = self[p4.value](basilisk, p3.value) || []; }
3. catch(e) { decodedData = []; }
4.
5. ctx2.clearRect(0, 0, C_SIZE, C_SIZE);
6.
7. if (XORCipher.encode(document.title, genKey()) === SECRET2) { yaaay(); }
8. else { boooo(); }
9. }
W sumie dalej nie rozumiem linii 2,3, tego self
, skąd ono się wzięło i dlaczego jest tablicą(?), więc uznałem, że zajmę się linią 7. A ta jest wyjątkowo prosta i niesie dużo informacji.
1. function genKey() {
2. if (!arguments.length) {
3. return [arguments.length === 0, p2.value, p3.value, p4.value].join(':');
4. }
5. }
arguments
też nie rozumiem, ale ważne jest to, że wynikiem wywołania tej funkcji jest coś, z czego łatwo odzyskamy p3.value
, p4.value
. Tutaj wystarczy nie być łomem z matematyki, żeby zauważyć, że wystarczy po prostu wykonać komende
XORCipher.decode(document.title, SECRET2)
żeby odzyskać zakodowany "poprawny" wynik funkcji genKey
. No i mamy:
"true:#legend:btoa:decodeImage"
Mamy hasło trzecie i hasło czwarte. Prawie koniec. Poszukajmy gdzie używane jest hasło 1.
ctx2.globalAlpha = +p1.value || 0;
strzelam, że chodzi o jakąś transparencje, czysto techniczna rzecz związana z rysowaniem obrazu. Ustawiam na 1
. Bo tak mi się wydaję.
Działa? Działa. Mamy bazyliszka. Rzeczywiście najładniejszy to on nie jest ;D
wołam @Shalom @Rev @msm @Gynvael Coldwind @Hepek @TLesiu @Sh4rk1 bo chyba zainteresowani.