30.06.2015 | 11:52

Adam Haertle

Jak sprytny gracz okradł bitcoinowe kasyno internetowe na milion dolarów

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.

Ekran z zakładem

Ekran z zakładem

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…

Powrót

Komentarze

  • 2015.06.30 12:13 Sebastian

    Jaki znowu oszust?
    Udostępniając skrypt z błędem trzeba się liczyć z konsekwencjami, a nie później szukać kozła ofiarnego ;]

    Odpowiedz
    • 2015.06.30 15:36 Artur

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

      Odpowiedz
    • 2015.06.30 18:51 Adam

      @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…

      Odpowiedz
      • 2015.07.04 09:28 kez87

        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.

        Odpowiedz
    • 2015.07.02 20:22 mr_ty

      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

      Odpowiedz
  • 2015.06.30 12:21 Kuba

    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.

    Odpowiedz
  • 2015.06.30 12:57 józek

    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 ;)

    Odpowiedz
    • 2015.06.30 13:54 d46e

      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.

      Odpowiedz
      • 2015.06.30 16:25 józek

        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 ;)

        Odpowiedz
        • 2015.07.01 11:06 Heniek

          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ę :)

          Odpowiedz
        • 2015.07.01 19:03 mlen

          FYI /dev/random == /dev/urandom (nie blokuje) na *BSD i pochodnych. /dev/urandom istnieje na tych systemach tylko dla kompatybilności.

          Odpowiedz
    • 2015.06.30 14:02 Krzysiek

      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.

      Odpowiedz
    • 2015.06.30 14:18 zdzisio

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

      Odpowiedz
      • 2015.06.30 16:16 józek

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

        Odpowiedz
    • 2015.06.30 17:31 Kamil Dzióbek

      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.

      Odpowiedz
      • 2015.06.30 20:06 józek

        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 :)

        Odpowiedz
        • 2015.06.30 20:11 józek

          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 ;)

          Odpowiedz
          • 2015.07.01 00:04 Adam

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

          • 2015.07.01 00:11 Adam

            Poprawka:
            Wtedy liczby: 10101, 12 i 3 będą miały ten sam hash wynoszący 3.

          • 2015.07.02 00:07 józek

            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?

          • 2015.07.03 08:26 Adam

            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… ;)

        • 2015.06.30 23:54 darek

          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.

          Odpowiedz
  • 2015.06.30 20:16 f

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

    Odpowiedz
  • 2015.07.01 12:22 kot

    Kleptowaluta! :D

    Odpowiedz
  • 2015.07.01 12:42 hffufufu

    śmiesznie, że polska nazwa na race condition to hazard

    Odpowiedz
  • 2015.07.16 17:21 lukasz

    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 :)

    Odpowiedz
  • 2018.05.18 19:58 zenka

    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 ;)

    Odpowiedz

Zostaw odpowiedź do mlen

Jeśli chcesz zwrócić uwagę na literówkę lub inny błąd techniczny, zapraszamy do formularza kontaktowego. Reagujemy równie szybko.

Jak sprytny gracz okradł bitcoinowe kasyno internetowe na milion dolarów

Komentarze