- Die Lua-Addon-Umgebung von World of Warcraft ermöglichte es, die UI leicht zu verändern, trennte aber den Informationszugriff von Ausführungsrechten, um die Automatisierung von Spielverhalten zu verhindern
- Blizzards Sicherheitsmodell teilte sich in eine unsichere Umgebung, die viele Informationen lesen konnte, und eine sichere Umgebung, die Aktionen ausführen konnte; diese Grenze war der Kern der Automatisierungsabwehr
- Das in der sicheren Umgebung erlaubte
random war ein dünner Wrapper um MSVC-C-rand und damit ein schwacher linearer Kongruenzgenerator mit gemeinsam genutztem globalem Zustand, der als Informationskanal dienen konnte
- Mit nur zwei RNG-Ausgaben ließ sich die Zahl möglicher interner Zustände auf höchstens 8 eingrenzen; mit weiteren Ausgaben konnte man einen einzelnen Kandidaten auswählen und den RNG so lange weiterschalten, bis der gewünschte Aktionsindex erschien
- Nach außen sah es wie eine zufällige Aktionsauswahl aus, tatsächlich ließ sich aber eine gewünschte Aktion wie etwa ein bestimmter Zauber auswählen; wie Blizzard das später behoben hat, ist nicht sicher
World-of-Warcraft-Addons als Einstieg ins Programmieren
- World-of-Warcraft-Addons bestanden aus einigen in Lua geschriebenen
.lua-Quelldateien, die man in einen Ordner legte und die das Spiel direkt lud
- Nach dem Ändern und Speichern der Dateien konnte man die Oberfläche neu laden und das Ergebnis sofort sehen, wodurch die Einstiegshürde sehr niedrig war
- Anfangs entstanden mehrere Addons, indem Code anderer kopiert, refaktoriert oder neu kombiniert wurde, um die gewünschte Funktion zu bauen
- Mit der Zeit floss mehr Zeit in das Schreiben von Addons als ins eigentliche Spielen, und World of Warcraft wurde so zum eigentlichen Einstieg ins Programmieren
Blizzards Sicherheitsmodell für Addons
- Wenn Nutzer vollständig programmierbare Addons erstellen können, entsteht ein Bot-Problem; deshalb schuf Blizzard eine Struktur, die die für normale Spieler mögliche Automatisierung begrenzte
- Die meisten UI-Elemente und APIs zum Sammeln von Informationen konnten ohne Einschränkung genutzt werden
- So ließ sich etwa eine Lebensleiste bauen, die die Größe eines Vordergrund-Frames anhand der ausgelesenen Charaktergesundheit anpasste
- APIs, die tatsächliche Spielaktionen ausführten, etwa Charakterbewegung, Zauberwirken oder Gegenstandsbenutzung, waren geschützt und konnten nur aus offiziellem Blizzard-Code aufgerufen werden
- Auch APIs zum Abrufen der exakten Weltposition und Kamerarichtung wurden ab einem bestimmten Zeitpunkt geschützt
- Das war eine Reaktion auf Addons, die 3D-Elemente über die Spielwelt zeichneten, um Bosskämpfe leichter zu machen
Trennung von sicherer und unsicherer Umgebung
- Einige UI-Elemente mussten mit echten Spielaktionen interagieren, daher konnte man spezielle Buttons erstellen, die beim Klicken Code in einer sicheren Umgebung ausführten
- Solche Buttons konnten im Kampf weder erzeugt, gelöscht noch verschoben werden
- Damit wurde verhindert, dass man im Kampf bedingt Buttons unter dem Cursor platzierte, um Aktionen zu automatisieren
- In der sicheren Umgebung ließ sich programmatisch festlegen, welcher Zauber gewirkt werden sollte, aber der Zugriff auf externe Zustände, die für beliebige Automatisierung nötig wären, war blockiert
- Erlaubt war nur ein Teil der APIs zur Informationsabfrage auf Makro-System-Ebene; Informationen wie Cooldowns von Fähigkeiten oder die Gesundheit von Einheiten, die für die Auswahl des optimalen Zaubers nötig wären, wurden nicht bereitgestellt
- Dadurch hatten die beiden Umgebungen unterschiedliche Rechte
- Unsichere Umgebung: kann viele Informationen erhalten, aber nicht handeln
- Sichere Umgebung: kann handeln, aber nicht die für Automatisierung nötigen Informationen erhalten
random als Hintertür-Kanal
- Ziel war nicht, die Sicherheitsbeschränkungen mit Drittsoftware abzuschalten, sondern zu prüfen, ob sich die sichere Umgebung allein mit technisch erlaubten Mitteln umgehen ließ
- Beim Durchsehen der in der sicheren Umgebung erlaubten Funktionen fiel
random auf
- Da Zufallszahlengeneratoren in Computern meist Pseudozufallszahlengeneratoren mit internem Zustand sind, lag die Vermutung nahe, dass sich durch Manipulation dieses Zustands externe Informationen in die sichere Umgebung übertragen lassen
- Tatsächlich war
random nur ein kleiner Wrapper um C-rand
- Da World of Warcraft mit MSVC kompiliert wurde, basierte die
rand-Implementierung auf einem Generator mit globalem Zustand
uint32_t state;
int rand() {
state = state * 214013 + 2531011;
return (state >> 16) & 0x7fff;
}
- Diese Implementierung teilte sich einen einzigen globalen Zufallszustand im gesamten Prozess und war ein schwacher linearer Kongruenzgenerator
Wiederherstellung des MSVC-rand-Zustands
- Die
rand-Ausgabe legt direkt 15 Bits des Zustands offen
- Da der Zustandsraum klein ist und ein Teil der Ausgabe sichtbar wird, kann man mögliche Zustandskandidaten verfolgen und mit weiteren Ausgaben Kandidaten ausschließen
- Effizienter ist es, zwei RNG-Ausgaben
r0, r1 zu beobachten und daraus Kandidaten für den verborgenen 16-Bit-Zustandsteil zu berechnen
- Die Konstanten von MSVC-
rand sind wie folgt
- Bezeichnet man die verborgenen 16-Bit-Teile nach dem ersten und zweiten Aufruf jeweils mit
h0 und h1, lassen sich aus der Zustandsgleichung Kandidaten für h0 herleiten
- Im Ergebnis muss man nur Werte von
k im Bereich -1 <= k < 7 betrachten, sodass höchstens 8 Kandidaten für den verborgenen Zustand übrig bleiben
- Anschließend entfernt man falsche Kandidaten, indem man Ergebnisse weiterer RNG-Aufrufe mit den Ausgaben der Kandidaten-RNGs vergleicht
- In der beispielhaften Python-Implementierung wurde eine
while-Schleife verwendet, praktisch schien aber schon die dritte Ausgabe zur Eingrenzung auf einen einzelnen Kandidaten zu genügen
- Damals war es schwierig, diese diskret-mathematische Herleitung selbst auszuarbeiten, daher wurde auf crypto.SE eine Frage gestellt – mit der Begründung, man wolle einem Kollegen zeigen, wie schwach dieser RNG sei
So wurde in der sicheren Umgebung die gewünschte Aktion ausgewählt
- Sobald sich der interne RNG-Zustand wiederherstellen ließ, konnten beliebige Automatisierungsentscheidungen in die sichere Umgebung übertragen werden
- Der Ablauf war wie folgt
- Es wurde ein unsicherer Hook registriert, der unmittelbar vor der Ausführung des Codes der sicheren Umgebung lief
- Innerhalb des Hooks war Zugriff auf alle Informationen möglich, daher wurde dort entschieden, welche Aktion ausgeführt werden sollte
- Die gewählte Aktion wurde in einer fest codierten Aktionsliste gesucht und in einen Index umgewandelt
- Mit dem oben beschriebenen Verfahren wurde der aktuelle RNG-Zustand rekonstruiert
- Ergab die nächste RNG-Ausgabe modulo der Länge der Aktionsliste nicht den gewünschten Index, wurde der RNG weitergeschaltet und erneut geprüft
- Sobald die gewünschte Ausgabe an der Reihe war, kehrte der Hook zurück, und die sichere Umgebung rief
random auf, um die Aktion an diesem Index auszuführen
- In der sicheren Umgebung sah es so aus, als würde die Aktionsliste mit einer Zufallszahl indiziert, tatsächlich wurde aber der Zeitpunkt des nächsten
random-Aufrufs so gesteuert, dass die gewünschte Aktion ausgewählt wurde
- Hatte die Aktionsliste die Größe
n, musste der RNG im Mittel um n Schritte weitergeschaltet werden, um den gewünschten Wert zu übertragen; praktisch war das jedoch kein Problem
Späterer Stand
- Einige Jahre später funktionierte diese Methode bei einem erneuten Versuch nicht mehr
- Wann Blizzard das Problem des schwachen und gemeinsam genutzten RNG-Zustands behoben hat oder ob es überhaupt als Problem erkannt wurde, ist unklar
- Mögliche Änderungen wären ein Wechsel auf einen anderen Algorithmus oder ein sauber getrennter RNG-Zustand für die sichere Umgebung
- Es war kein Code, der tatsächlich eingesetzt werden sollte, aber es war ein eng umrissener Fall einer Umgehung von Spielesicherheit, bei dem etwas scheinbar Zufälliges erfolgreich auf ein gewünschtes Ergebnis gelenkt wurde
1 Kommentare
Hacker-News-Kommentare
Vor langer Zeit, als ich als WoW-GM arbeitete, gab es in einer bestimmten Version des beliebten Add-ons Titan Bar einen Bug, durch den ständig die API aufgerufen wurde, die die UI zum „Ticket an einen GM schreiben“ öffnete.
Selbst wenn man abbrach, erschien sie wieder; außer das problematische Add-on zu löschen, konnte man das Fenster nur loswerden, indem man irgendetwas ins Textfeld schrieb und absendete.
Für uns GMs waren das ein, zwei wirklich harte Tage, und viele Spieler wussten weder, was ein GM ist, noch dass man überhaupt ein Ticket schicken kann.
Zu erklären, dass „Sie vermutlich wegen eines Add-ons ein Ticket eingereicht haben und ich mich als Kundendienstmitarbeiter deshalb melde“, war meistens auch nicht einfach.
Da die normalen Antwortzeiten damit praktisch ruiniert wurden, brachten die Entwickler einen Hotfix heraus und setzten diesen API-Aufruf hinter eine Schutzschicht, sodass er nur noch aus Blizzards eigenem LUA-Code möglich war.
Es war ein Add-on, das verschiedene UI-Elemente wie Gesamtgold und Reparaturprozentsatz in einer einzelnen Leiste zusammenfasste.
Seltsamerweise hatte ich auf StackOverflow einige Monate bevor die Frage auf Crypto.SE auftauchte eine nahezu identische Frage zum „Zurückrechnen eines Zufallszahlengenerators“ beantwortet: https://stackoverflow.com/a/15237585/1204143
Allerdings griff ich statt des MSVC-Zufallszahlengenerators den Java-Zufallszahlengenerator an.
Beide sind einfache lineare Kongruenzgeneratoren (LCG), die abgeschnittene Werte ihres internen Zustands ausgeben, daher sind auch die Angriffsansätze sehr ähnlich.
import java.util.Random;
public class Hayley {
public static void main(String... args) {
byte[] b1 = new byte[4], b2 = new byte[2];
(new Random(0x2effe2140e00L)).nextBytes(b1);
(new Random(0xc2f0097)).nextBytes(b2);
System.out.println(new String(b1) + new String(b2));
}
}
Ich frage mich, ob auf dem Server möglicherweise derselbe Zufallszahlengenerator verwendet wurde.
Falls ja, scheint das theoretisch ausnutzbar, wenn man einen Server mit sehr geringer Nutzung, niedriger Latenz, die Fähigkeit, den aktuellen Zustand des Zufallszahlengenerators ziemlich genau einzugrenzen, und die Fähigkeit hat, ein vom Zufall abhängiges Ereignis mit großer wirtschaftlicher Streuung schnell erneut auszulösen.
Blizzard veröffentlichte in einem größtenteils erfolglosen Versuch, Gerüchte unter abergläubischen Spielern auszuräumen, die fest an Dinge wie „Loot-Seeds“ glaubten, ziemlich viele Details darüber, wie serverseitige Zufallszahlen erzeugt wurden.
Die Ökonomie und die Anreize von Zufallszahlengeneratoren sind interessant.
Wenn praktisch nichts auf dem Spiel steht und die Leute es nicht leicht bemerken können, können auch unvollkommene oder sogar schlechte Zufallszahlengeneratoren wie in Spielen ausreichen.
Sobald aber wie bei Kryptowährungs-Wallets oder Online-Casinos Geld oder Sicherheit daran hängt, wird es plötzlich enorm wichtig.
Noch gravierender ist, dass der gezeigte lineare Kongruenz-Zufallszahlengenerator nur 12.445 unterschiedliche Zahlen erzeugt, bevor er sich wiederholt.
Allein die Verwendung besserer Parameter für diesen Algorithmus wäre schon deutlich vernünftiger gewesen.
Für einfache Aufgaben wie Partikelbewegungen in Rauchspuren braucht man einen einfachen und schnellen Zufallszahlengenerator.
In der Kryptografie oder bei Casinospielen verwendet man nicht nur bessere Zufallszahlengeneratoren mit mindestens 32 Byte Zustand, sondern lässt die Engine in zufälligen Abständen zusätzlich weiterlaufen, um Vorhersagen der Ergebnisse weiter zu erschweren.
World of Warcraft war der Anlass, weshalb ich mich fürs Programmieren interessierte.
Als es erschien, war ich sechs Jahre alt, und als private Server aufkamen, wollte ich wissen, wie man eigene NPCs erstellt, also lernte ich Lua.
Viele der Beiträge aus dem MMOwned-Forum, in dem ich damals aktiv war, existieren noch, und eine der Personen, die mir damals beim Lernen half, postet auch heute noch gelegentlich.
Beim Kompilieren eigener Private-Server-Cores lernte ich Kompilieren, SVN und das Anwenden von Patches; ich glaube, es gab einen Bug im Zusammenhang mit dem Fireball des Magiers, aber genau erinnere ich mich nicht.
Heute fühlt es sich so an, als sei ein großer Teil der guten Seiten des Internets durch dopamintreibende Social-Media-Apps und geschlossene Foren wie Reddit ersetzt worden.
Vermutlich bleibe ich auch deshalb noch auf HN, weil es mich an die alten Foren erinnert.
„Wer mit arithmetischen Methoden Zufallszahlen erzeugen will, befindet sich natürlich im Zustand der Sünde. Wie mehrfach angemerkt wurde, gibt es so etwas wie eine Zufallszahl nicht. Es gibt nur Methoden, Zufallszahlen zu erzeugen, und ein strenges arithmetisches Verfahren ist selbstverständlich keine solche Methode.“ — John von Neumann
Ich habe nichts gegen Neumann, aber die Leute, die es wie eine weise Maxime verbreiten, transportieren meist nur Fehlinformationen.
Im Kontext ist es eher ein unnötiger philosophischer Witz.
Wir haben seit Jahrzehnten schnelle und verlässliche kryptografische Zufallszahlengeneratoren.
Sie funktionieren mit einem sehr kleinen Seed und arithmetischen Operationen korrekt, die erzeugten Zahlen sind von anderen korrekten Zufallsquellen nicht zu unterscheiden und für jeden Zweck verwendbar.
Zu viele Menschen verstehen das nicht, und es gibt sogar Firmen, die Schlangenöl in Form von „echtem Zufall“ verkaufen.
Ein gutes Beispiel für einen wirklich schrecklichen Zufallszahlengenerator gibt es in Monster Hunter 3 Ultimate, und vermutlich auch in anderen Teilen.
Er war nicht nur vollständig ausnutzbar, um guten Loot zu bekommen, sondern führte auch zu „Charm Tables“.
Einige Items werden mit zufälligen Werten erzeugt, aber der anfängliche Seed wird in der Speicherdatei abgelegt, wodurch der Bereich der Items eingeschränkt ist, die man erhalten kann.
Mit Pech landet man auf einer „verfluchten Tabelle“, also faktisch einem Zufallszahlengenerator mit sehr kurzer Periode, und kann nie die Höchstwerte bekommen.
Es zerstört das Spiel nicht, ist aber schlecht fürs High-Level-Spiel.
Ich schrieb ein Auktionshaus-Skript.
Es scannte alle Auktionen, kaufte unterbewertete Items und stellte sie wieder ein.
Wenn jemand sie günstiger einstellte, konnte es die Auktion abbrechen und sie knapp darunter erneut einstellen, wodurch man praktisch unbegrenzt Gold hatte.
„Ähm, eigentlich ist das eine ziemlich gute Analogie. Der Unterschied ist … ähm … ziemlich gute Analogie!“
„Wenn Variablen nicht variieren und Konstanten nicht konstant sind“
https://www.theregister.com/2006/07/26/constants_are_not/
„Die Klage über die Wankelmütigkeit der Programmierung im Titel dieses Artikels, ‘Variables Won't Constants Aren't’, las ich erstmals in Creative Computing, mehrere Jahre bevor C entstand.“
Wenn es ein bestimmtes Dokument gibt, das sich wie die kanonische Fassung dieses Memes anfühlt, würde ich es gern lesen.