Preorder drugiego tomu książki sekuraka: Wprowadzenie do bezpieczeństwa IT. -15% z kodem: sekurak-book
Zhackował Hearthstone. Banalny sposób na rozłączenie gry przeciwnika [historia ciekawego buga, fixed]
Kliiiiikbait – bo sposób nie był banalny! A nie, czekaj, sposób był banalny, jak już się wiedziało co i jak. No dobra, nie taki banalny.
Ech… zobaczmy o co chodziło w bugu, który został wykorzystany bojowo 3 lata temu i nieco później załatany.
Idąc od końca, efekt na koncie gracza-hackera był taki:
Idąc od początku:
1. Analiza binarki gry:
- Zdeszyfrowanie pliku Assembly-CSharp.dll
- Użycie dnSpy do dekomplikacji dll-ki
2. Można zacząć szukać podatności.
- Próba dobrania się do szczegółów decku należącego do innego gracza? Nie działa (tzn. Blizzard robi odpowiednie sprawdzenie po stronie serwerowej).
- Używanie niedozwolonego na arenie decku? Nie działa.
- Używanie innych kart niż dozwolone na arenie. Nieee ;(
- Stworzenie decku z kartami, których się nie ma, dodawanie dzikich kart do decku? Taaaaaaak! Ale system matchujący traktuje taki deck niestandardowy i nie znajduje żadnego oponenta. Zatem: nope.
- Podatności klasy integer overflow. Nic.
- Nic. Nic Nic!
3. Może coś w FindGame() ?
byte[] array = Guid.NewGuid().ToByteArray();
long currentFsgId = FiresideGatheringManager.Get().CurrentFsgId;
bnet.protocol.game_master.Player player = new bnet.protocol.game_master.Player();
Identity identity = new Identity();
identity.SetGameAccountId(gameAccountId);
player.SetIdentity(identity);
player.AddAttribute(ProtocolHelper.CreateAttribute("type", (long)bnetGameType));
player.AddAttribute(ProtocolHelper.CreateAttribute("scenario", (long)scenarioId));
player.AddAttribute(ProtocolHelper.CreateAttribute("brawl_library_item_id", (long)brawlLibraryItemId));
player.AddAttribute(ProtocolHelper.CreateAttribute("deck", deckId));
player.AddAttribute(ProtocolHelper.CreateAttribute("aideck", aiDeckId));
player.AddAttribute(ProtocolHelper.CreateAttribute("request_guid", array));
player.AddAttribute(ProtocolHelper.CreateAttribute("fsg_id", currentFsgId));
...
BattleNet.FindGame(gameProperties, new bnet.protocol.game_master.Player[] { player });
Odpalenie FindGame() już po dołączeniu do gry wywala (samego siebie) z gry. Tj. restart klienta i ponowne podłączenie. Nic wielkiego. Ale co by się stało gdyby gameAccountId podmienić na ID innego gracza? Jeśli dodatkowo ustawię dobry deckId (docelowego gracza), to klient gry tego gracza rozłącza się, restartuje i ponownie podłącza do gry.
Skąd poznać gameAccountID kogoś innego? To gra nam prezentuje w standardzie. Co z deckId? Można użyć tutaj wartości 0 (zero) – odpowiadającej temu deckowi.
4. Do boju.
- Prosty „mod” ;-) który odpala co 5 sekund funkcję findGame(), feedując ją parametrami:
gameAccountId = GameState.Get().GetOpposingSidePlayer().GetGameAccountId(); bnetGameType = BnetGameType.BGT_VS_AI; scenarioId = 0xBA2 /Boom Labs/; deckId = 0; - Stworzenie nowego konta w grze
- Odpalenie „moda”
- Czekanie kilkanaście godzin. Ranga #1 w legendarnym tierze:
Ciężko powiedzieć czy Blizzard (oraz gracze) byli zachwyceni takim obrotem sprawy. No dobra, Blizzard chyba finalnie tak, bo błąd został zgłoszony w ramach programu bug bounty i została wypłacona nagroda $2500.
PS
Pamiętajcie żeby takie „zabawy” wykonywać zawsze etycznie i przypadkiem nie rozwalić całej gry.
~Michał Sajdak
Michale Sajdaku, kiedy wyniki konkursu na najlepszy artykuł? Ludzie wciąż czekają.
Odpowiedź jest prosta: najlepszy artykuł nie został jeszcze nadesłany. Konkurs nadal trwa ;)