Graliście kiedyś w internetowym kasynie? Stara zasada mówi, że kasyno – internetowe czy zwykłe – zawsze wygrywa. Ten gracz znalazł jednak sposób na pokonanie kasyna – a wszystko przez dość prosty błąd programistów.
Primedice to jedno z największych i najpopularniejszych internetowych kasyn, w których można grać za bitcoiny. Obdarzone zaufaniem przez tysiące użytkowników, korzysta z przejrzystego mechanizmu zapewniającego uczciwość kasyna i możliwość jej weryfikacji przez użytkownika. Jednemu z graczy udało się jednak oszukać mechanizm losowania i ograć kasyno na około milion dolarów.
Skąd wiem, że kasyno mnie nie oszukało
By opisać mechanizm oszustwa gracza trzeba zacząć od opisania sposobu rozgrywki w internetowych kasynach. Jeśli wiecie, jak generowane są losowe wartości i skąd gracz wie, że kasyno go nie oszukało – możecie przejść do kolejnego rozdziału. Pozostałych Czytelników zapraszamy na krótką wycieczkę do świata hazardu.
Gracze w ruletkę widzą, jak kulka przeskakuje po kolejnych polach. Gracze w karty widzą, jak krupier otwiera i rozdaje świeżą talię. Gracze w kasynach internetowych, gdzie o wyniku gry decyduje komputer, też potrzebują możliwości weryfikacji uczciwości gry. Z pomocą przychodzi prosty, ale sprytny algorytm.
Dla potrzeb wyjaśnienia mechanizmu weryfikacji uczciwości gry w kasynie przyjmijmy założenie, że gracz stawia jednego bitcoina w podstawowej grze 2x. Polega ona na tym, że komputer wylosuje wartość z przedziału od 0,00 do 99,99 i jeśli ta wartość będzie mniejsza od 49,50, to gracz wygra bitcoina, a jeśli wartość będzie równa lub większa 49,50, to gracz traci bitcoina.
Próg wygranej na poziomie 49,50 oznacza, że kasyno zawsze wygrywa w dłuższym terminie – 1% brakujący do 100 to przychód Primedice. Jak jednak obliczana jest najważniejsza wartość, czyli wynik losowania? Poniższe wyjaśnienie oparte jest na publikacji serwisu dicesites.com.
Proces obliczeń opiera się na trzech wartościach – losowym ciągu znaków dostarczonym przez serwer, ciągu znaków dostarczonym przez użytkownika oraz liczniku kolejnych gier. Na przykład mogą one wyglądać tak:
Losowy ciąg serwera: 293d5d2ddd365f54759283a8097ab2640cbe6f8864adc2b1b31e65c14c999f04 Ciąg użytkownika: ClientSeedForDiceSites.com Licznik: 0 (to pierwsza gra z tym zestawem danych)
Następnie serwer oblicza kod uwierzytelniający wiadomość z wmieszanym kluczem tajnym (HMAC, w uproszczeniu rodzaj rozszerzonej funkcji skrótu)
hmac-sha512(293d5d2ddd365f54759283a8097ab2640cbe6f8864adc2b1b31e65c14c999f04, ClientSeedForDiceSites.com-0)
a w wyniku tej operacji otrzymuje długi ciąg znaków
aa671aad5e4565ebffb8dc5c185e4d[.....]8f6ca2
W kolejnym kroku serwer bierze pierwsze 5 znaków i przelicza je z zapisu szesnastkowego na dziesiętny
aa671 HEX = 697969 DEC
Jeśli otrzymana wartość przekracza 999 999, to algorytm bierze kolejne pięć znaków. Jeśli wartość jest mniejsza lub równa 999 999, to serwer bierze 4 cyfry z prawej i powstałą liczbę dzieli przez 100 – i jest to wynik losowania.
(697169 modulo 10 000) / 100 = 71,69
Wartość przekracza 49,5, zatem gracz przegrał. W kolejnym losowaniu zmianie ulega wartość licznika, co powoduje całkowitą zmianę wylosowanej wartości.
Jak ten algorytm gwarantuje uczciwość? Podczas rozgrywki gracz nie może znać wartości wylosowanej przez serwer, ponieważ mógłby wtedy szybko obliczyć czy wygra, czy przegra i na tej podstawie pokonać kasyno. Zamiast zatem ogłaszać wartość serwera dla tego gracza, kasyno podaje mu jedynie wynik funkcji skrótu (hash) dla tej wartości. Gdy gracz postanawia wygenerować nową wartość po stronie serwera, serwer pokazuje mu wartość poprzednią. Wtedy gracz może sprawdzić, czy hash, który otrzymał, zgadza się z samą wartością a także znając wartość serwera oraz swoją policzyć, czy wyniki wcześniejszych losowań były prawidłowe.
Gra pozostaje sprawiedliwa i weryfikowalna post factum tak długo, jak gracz nie zna wartości przypisanej do danej sesji przez serwer. Jak zatem wyglądał atak, który przyniósł tajemniczemu graczowi milion dolarów?
Najszczęśliwszy gracz świata
W sierpniu 2014 serwis Primedice uruchomił nową, trzecią już wersję swojej platformy. Ze względu na presję czasu platforma była testowana zaledwie przez tydzień zanim trafiła w ręce użytkowników. Natychmiast po jej uruchomieniu dwóch graczy, pod pseudonimami Nappa oraz Kane, zaczęło regularnie wygrywać zakłady z kasynem. Kane wypłacił swoje zyski automatycznie, z kolei sprawa Nappy była już analizowana przez zespół serwisu. Programiści nie dopatrzyli się jednak żadnych oznak nieprawidłowości i choć fala zwycięstw wyglądała podejrzanie, to wygrane również wypłacono.
Po kilku tygodniach przerwy pojawił się gracz Hufflepuff, który obstawiał zakłady w ilościach i wartościach jakich serwis wcześniej nie widział. Gracz przez wiele godzin stawiał co sekundę 12 bitcoinów w zakładach x3 (jeśli system wylosuje wartość poniżej 0,33 nagroda wynosi dwukrotność zakładu) i choć regularnie przegrywał, to jeszcze bardziej regularnie wygrywał, z poziomem szczęścia o ok. 4% większym, niż zakładał to algorytm serwisu.
Obsługa serwisu cały czas szukała problemu, lecz nie była go w stanie zlokalizować. Początkowo wstrzymała wypłaty, jednak wierząc, że system działa prawidłowo i lada moment szczęśliwy gracz zacznie tracić pieniądze, odblokowała je i czekała na rozwój wypadków. Tymczasem Hufflepuff zarobił ponad 2000 bitcoinów i nie zanosiło się na to, że je wkrótce straci. Dwa dni później główny programista odkrył sztuczkę złodzieja.
Wyścig po bitcoiny
Hufflepuff użył prostej sztuczki – tzw. race condition, czyli sytuacji, w której serwer nie potrafi prawidłowo obsłużyć wielu żądań od klienta otrzymanych w krótkim przedziale czasu. W ten sposób można np. wysyłając setki lub tysiące żądań do serwera w ciągu sekundy 10 razy zrealizować ten sam kupon rabatowy. W przypadku Primedice gracz tak bombardował żądaniami serwer gry, że w bazie jednocześnie potrafiły znajdować się przypisane do jego konta te same losowe ciągi uznawane za aktywne i uznawane za nieaktywne. Dzięki temu gracz mógł poznać w trakcie gry losowy ciąg wygenerowany przez serwer i dodając do niego swój własny ciąg oraz numer losowania obliczyć szanse na wygraną. Był to ewidentny błąd programisty – system nie powinien nigdy pozwolić na to, by gracz w trakcie gry mógł poznać losowy ciąg serwera.
Gdy obsługa serwisu skontaktowała się z graczem z prośbą, by ten oddał ponad 2400 zarobionych nieuczciwie bitcoinów (wartych wówczas około miliona dolarów), ten założył konto Robbinhood i wykorzystując fakt, że programiści serwisu nieudolnie załatali problem, wygrał jeszcze 2000 bitcoinów, lecz zdążył wypłacić 60, ponieważ tylko tyle było w gorącym portfelu. Następnie w wiadomości do serwisu wyśmiał jego obsługę i zaproponował, by każdy udał się w swoją stronę i nie wracał więcej do tematu. Primedice podało do publicznej wiadomości adresy IP, adresy email oraz adresy portfeli BTC oszusta, jednak raczej nic nie wskazuje na to, by zanosiło się na jego złapanie.
Największym naszym zaskoczenie jest to, że prowadzący kasyno uwierzył w to, że jeden z jego klientów ma o 4% więcej szczęścia niż pozostałe tysiące. A można było zainwestować ułamek straconej kwoty w profesjonalny audyt serwisu…
Komentarze
Jaki znowu oszust?
Udostępniając skrypt z błędem trzeba się liczyć z konsekwencjami, a nie później szukać kozła ofiarnego ;]
@Sebastian:
Zgodnie z tokiem twojego rozumowania – jeśli zobaczysz otwarty samochód to zawiniesz z niego radio, jeśli wejdziesz do sklepu i nikogo nie ma na kasie, to zawiniesz utarg, itp?
Normalny człowiek o znalezieniu błędu powiadamia administrację serwisu.
W jednym masz rację: nie tyle oszust a złodziej.
@Sebastian: Idąc twoim tokiem rozumowania… Jak ci pedofil porwie dzieciaka spod bloku, zgwałci i zamorduje, to też będziemy mówić, że to nie był pedofil, tylko że to ty nie upilnowałeś dzieciaka, że jesteś sam winny i tylko szukasz kozła ofiarnego…
Jaki tam pedofil od razu ?! Kasyno oszukał,a nie dziecko porwał.
Okradnięcie banku na 10 groszy jest znacznie większą zbrodnią.Gry hazardowe natomiast to naciąganie ludzi i tyle. Niektórzy przez hazard tracą mieszkanie a nawet życie.Typek był cwańszy od kasyna i ich trochę oskubał,oni za to oskubią o wiele więcej ofiar na o wiele większe sumy.
kolega ma czesciowo racje – gra nastepowala na platformie i wg zasad kasyna
kasyno zaklada ze w odpowiednio dlugim okresie czasu ZAWSZE zarobi
ich reguly sa ustalone w ten sposob
gdyby facet wlamal im sie na serwer i przelal sobie BTC to bylaby zupelnie inna sprawa
jednak cala jego komunikacja byla 'legalna’ i akceptowana przez kasyno, wiec nie widze tu zadnego problemu – spieprzyli i bedzie ich to kosztowalo
Kiedyś już była podobna sprawa z just-dice.com tylko o ile pamietam to tam nikt do końca nie rozgryzł sposobu w jaki kasyno straciło 11k btc.
No dobra, ale jednego tylko nie rozumiem – gram sobie w ruletkę, kasyno zbiera zakłady od graczy, ruletka ma pola od 1-100 (bez kolorów, to czysta teoria), po co implementować tutaj caaaały sposób hashowania, sprawdzania hashów itp. jak /dev/urandom załatwia wszystkie problemy mogąc zwracać wartości dziesiętne i potwierdza uczciwość? Rozumiem, że kasyno musi mieć „swój” sposób, na częstsze wygrywanie, ale przy takich zakładach jak ruletki czy kości szanse, że ktoś wygra są i tak małe, a wynikają z samej mechaniki gry. Jakbym był graczem, to właśnie osobny mechanizm generowania hashy i sposóbu na obliczanie wyników byłby dla mnie dowodem na nieuczciwość kasyna ;)
Urandom jest bardzo mało losowe – poobserwuj sobie patterny tego co tam jest serwowane (przemielone /dev/random).
Kasyna i inne pokery wykorzystują często zewnętrzne masynki – szum termiczny, szum radiowy (atmosferyczny) i inne zjawiska prawdziwie losowe.
Jest znacznie bardziej losowe, niż rand(), to na pewno. Kwestia jest tylko taka, czy stopnień przewidywalności i losowości jest na tyle duży, aby uniemożliwić przewidzenie kolejnych wartości i losowy na tyle, żeby faktycznie były różne od poprzednich bez konkretnego schematu, w np. losowaniu liczb od 1-1000 (bo wątpię, że jakiekolwiek kasyno losuje większe wartości)? Moim skromnym zdaniem tak. Dane które pobiera /dev/urandom (a więc jak dobrze zauważyłeś „przemielony /dev/random”), zwłaszcza na serwerze, zmieniają się bardzo radykalnie i właściwie bez dostępu do serwera przewidywanie kolejnego stanu jest całkiem nie możliwe do wykonania, a przynajmniej nie mam żadnego innego pomysłu jak to zrobić i google też nie bardzo pomaga ;)
Nie ma znaczenia, czy losujesz wartości od 0 do 1 czy od 0 do 1000000000, jeśli generator nie ma odpowiednich parametrów, to wystarczająca liczba obserwacji pozwoli z dużą dozą prawdopodobieństwa stwierdzić, czy masz jakieś odchyły od normy w jedną lub drugą stronę :)
FYI /dev/random == /dev/urandom (nie blokuje) na *BSD i pochodnych. /dev/urandom istnieje na tych systemach tylko dla kompatybilności.
urandom wcale nie jest losowe przeciez to juz szybciej random, ktore uzywa sie do generowania kluczy, urandom ma mniejsza przypadkowość danych oraz odporność na przewidywalność kolejnych danych.
Komputer to maszyna nie moze byc przypadkowa, dlatego zwykly random stara sie korzystac z bufora gdzie powinny byc przypadkowe znaki jak punkt wyjscia funkcji.
http://security.stackexchange.com/questions/3936/is-a-rand-from-dev-urandom-secure-for-a-login-key – w odpowiedzi jest link do innego artykułu, który pokazuje, że się mylisz. Nie jestem ekspertem, ale obie strony przemawiają do mnie.
Gdyby wygenerować czystego randoma i przesłać do klienta to klient widzi czy wygrywa, czy przegrywa – co za tym idzie – na wygrane może stawiać 10 jednostek, a na przegrane 1 jednostkę.
„na wygrane może stawiać 10 jednostek, a na przegrane 1 jednostkę”
Que? Obstawianie w godzinie 16:05-16:10, losowanie o 16:10:01, podanie wyniku o 16:10:02 – w jaki sposób ktoś mógłby obstawiać jednostki na wygrane bądź przegrane? Mówisz tutaj o przewidzeniu przez użytkownika, jaką wartość zwróciłby /dev/urandom przed losowaniem – czyli w sumie zupełnie nie na temat.
Hash ma 'udowodnić’ uczciwość kasyna. Zabezpieczyć przed tym ze ruletka nie wybiera specjalnie koloru przeciwnego niż człowiek obstawił.
Bo najpierw jest obstawianie, a później gracz dowiaduje się o wyniku.
W tym rozwiązaniu. Jest losowany ciąg losowy, liczony jest jego hash i przesyłany do gracza. Gracz widzi hash i może się zdecydować na co obstawiać. Jak obstawi to dostanie wylosowany ciąg losowy i będzie mógł sprawdzić na podstawie wcześniej otrzymanego hasha czy ruletką nie oszukuje poprzez specjalne wygenerowanie wartości przeciwnej do przez niego obstawianej.
O widzisz, to brzmi logicznie. Jednak oznacza to, że wynik (liczba losowa) i jej hash oraz metoda otrzymywania tego hasha – muszą być znane przed obstawieniem przez gracza wyniku. W jaki sposób to zabezpiecza kasyno i uczciwość rozgrywki? Przecież skoro mamy metodę i mamy listę możliwych wyników – to możemy wygenerować sobie kombinacje tysięcy, jak nie milionów czy miliardów odpowiednich par wynik-hash (za pomocą chociażby chmury amazonu). Np. w ruletce wynikiem jest liczbą 100, zakładając nawet, że ta liczba „losowa” to wartość kombinacji 2^20, albo że używa się w metodzie 3 różnych liczb losowych 2^20, można obliczyć wszystkie możliwe hashe z danej metody w kilka dni/tygodni i grać do woli :)
I nawet nie chodzi o to, żeby mieć WSZYSTKIE kombinacje wynik-hash, ale chociażby 30% i czekać, aż w grze pojawi się znany hash, a wtedy wpakować w tę grę dom, samochód, żonę, dziecko i 1 000 000euro kredu ;)
@józek: Ale jeśli hash jest skrótem, to wtedy (teoretycznie) nie niesie on żadnej sensownej informacji o samej pierwotnej wartości.
–
Ustalmy przykładowo, że hash będzie sumą cyfr.
Wtedy liczby: 10001, 12 i 3 będą miały ten sam hash wynoszący 3.
–
Co z tego, że jest to nasz ulubiony hash, skoro nie mamy nawet pojęcia, czy liczba z niego się biorąca jest duża (10001), czy mała (3)?
Miałoby to sens w przypadku bardzo słabego i „obcykanego” generatora pseudolosowego. Pomijam „race condition”, itp.
Poprawka:
Wtedy liczby: 10101, 12 i 3 będą miały ten sam hash wynoszący 3.
Twój komentarz trochę nie ma sensu. „Wtedy liczby: 10001, 12 i 3 będą miały ten sam hash wynoszący 3.” Obstawiam zatem liczbę 10101, a wygrywa liczba 12, w jaki sposób ten mechanizm hashowy udowadnia mi, że system nie zmienił wyniku z 10101 na 12, skoro obie mają ten sam hash wynoszący 3?
Sens z hashem ma mój komentarz po poprawce w moim drugim komentarzu, z tą odgadniętą liczbą 10101. ;)
Natomiast zdolność udowadniania takiej, a nie innej wartości zależy od kolizyjności funkcji skrótu. Funkcja skrótu typu suma cyfr jest mocno kolizyjna. Dużo mniej kolizyjna jest np. funkcja MD5, choć nie wyklucza ona w 100% manipulacji.
Ale ogólnie pomysł ze znanymi parami liczba-cache jest OK. Nie daje 100% pewności, ale statystycznie pewnie byłoby się mocno na plusie… ;)
Wynik nie jest znany do czasu, aż gracz nie wyśle swojego seeda (razem z zakładem). Dopiero wtedy za pomocą hmac wyliczany jest wynik.
http://2.bp.blogspot.com/-M17njgFU3cE/UwHvo0TyiVI/AAAAAAAAscg/cbkcqEGQg4A/s1600/2318.strip.gif
A nie można odseparować maszyny przyjmującej zakłady i maszyny losującej? Wtedy gracz obstawia zakład na jednej maszynie, a na innej jest losowanie. Potem wynik losowania konfrontuje się z zakładem i podaje wynik. Problemem może być rozwiązanie jak te maszyny odseparować. Jeśli obie miały by być w kasynie, to jest podejrzenie, że się komunikują.
Kleptowaluta! :D
śmiesznie, że polska nazwa na race condition to hazard
Szukam spamu w ktorym oferowana jest praca, chodzi oczywiscie o role slupa. Jesli ktos posiada takie maile prosze o podeslanie na adres: [email protected] Z gory dziekuje :)
No cóż… musieli się z tym liczyć jeśli rzeczywiście programiści popełnili błąd… ja gram na coinbids.io i jak na razie wszystko wydaje się uczciwe zarówno po stronie graczy jak i samego portalu, chociaż jak to się zawsze mówi… kasyno zawsze wygrywa ;)