Skip to content

Instantly share code, notes, and snippets.

@amtal
Created January 17, 2017 08:28
Show Gist options
  • Save amtal/bf941bde443eefc7d4626fd439d7f480 to your computer and use it in GitHub Desktop.
Save amtal/bf941bde443eefc7d4626fd439d7f480 to your computer and use it in GitHub Desktop.
Walkthrough of two dupes and an item corruption exploit for Diablo 2 in layman's terms

This is a common-jargon walkthrough of an interesting Diablo 2 exploit. It provides the necessary background information (network protocol and game mechanics) to gain some understanding of the primitives from which it's constructed. Since the exploit is against a black-box network service with no available code, exact details and subtleties remain a mystery. :)

Exploit effects

Diablo 2 items can have a list of properties with various effects. The most common items (normal or "white" ones) have very few possible effects; however, all items can have sockets. Rune and gem-type items can be inserted into sockets. Some sequences of runes are special - inserting them into a white item makes a runeword item with predictable special properties.

Here's an example runeword "Peace" created by inserting Shael, Thul, and Amn runes into a 3-socket Light Plate:

Peace + Enigma hybrid

Wait. Something's fucky. If you've played Diablo 2 you may recognize that as "Enigma", a high-level runeword created with Jah, Ith, Ber and highly sought-after due to granting all classes the ability to teleport like a Sorceress.

Visual

It would be nice to have the item on-hand as proof, and for packet inspection. However, it's not uncommon for MMO exploits to propagate first as rumours, then eventually as screenshots.

Let's dissect the effects visible in the screenshot by looking up items on Blizzard's Arreat Summit reference site. We can look at the properties of runes, Peace, and Enigma. The hybrid in the screenshot can be broken down as follows:

B   base item (perfect-roll 15/15 3OS Light Plate)
 E  Enigma
  R Shael-Thul-Amn runes
--- -------------------------------------------
  R Required Level: 29
 E  2 to All Skills
 E  45% Faster Run/Walk
 E  20% Faster Hit Recovery
 E  1 to Teleport
B   15% Enhanced Defense
 E  775 Defense
 E  0 to Strength (Based on Character Level)
  R Cold Resist 30%
 E  14 Life After Each Kill
  R Attacker Takes Damage of 14
 E  1% Better Chance of Getting Magic Items (Based on Character Level)
B   Increase Maximum Durability 15%
B   Socketed (3)

We can learn some interesting things:

  • All Enigma effects are present. No Peace effects are present. Name says Peace - it's a bug.
  • The runes that make up Peace are present, setting the level requirement to 29 rather than Enigma's 65. This makes it a very desireable item for the level 30 dueling bracket.
  • The 15%ED/15%durability effects are a little rare - but take a look at the total defense! (899 - 775) // 1.15 = 107, it's both a perfect Enigma (775 out of 750-775) roll and Light Plate (107 out of 90-107) roll!
    • Finding the base item would've taken a significant logistics base, either their own (via a bot farm) or an economic one (via a trader that can overpay for extremely niche perfect items until someone else bots one up.)
    • Rolling a perfect runeword would either take many attempts or a rollback exploit granting free re-tries. It's tempting to suspect the second, since item sellers moving imperfect rolls in volume generally use Dusk Shrouds or Archon Plates. (High-defense Elite Armor with relatively low strength requirements and the "light" type.)
  • Despite being an improbable (and thus, very high effort) perfect roll, I suspect whoever made this wasn't part of the LLD scene. (Low Level Dueling began as a way to escape the hacking rampant at high levels.) As far as I can tell, the base item choice is just dumb.
    • At level 29, if you're maximizing defense (and pumping strength to wear high-armor items and wield non-caster weapon) the Mage Plate is accessible as a base and gives significantly more defense. It does need 55 strength rather than 41, but:
    • If you're making use of the ~21 strength Enigma bonus to wear typical caster items, you'd want a Breastplate base.
    • I can see the Light Plate as a middle ground between the two approaches, but the LLD community was obsessed with deep optimizations. Whoever chose the base item was not.

Protocol

MMO exploits are best understood at the network level. For this, we'll reference three public packet lists:

  • Blizzhackers, good overview and some interesting details
  • newd2event, copy of something ancient
  • BNETDocs, mostly focused outside game-server but occasionally deep
  • I can't find Ringo's list - might be down, might be I forgot where it was. It was the most detailed.

The item packets of interest are Server->Client 0x9C (world items) and 0x9D. (0x9D shares most of its structure with 9C, but has some extra stuff for handling movements on/off the cursor, socketed items, and other weirdness.)

These are a bit-packed hellscape, with client-side parsing making heavy use of game data tables. Only the Blizzhackers source has detailed information for the S->C packets, but we can observe some high level properties.

  • Items have a 32-bit (DWORD) "id". This identifier is used to manipulate them with various C->S packets such as the 0x16-0x2A range, as well as other more specialized packets. Manipulation results in 0x9C/0x9D S->C packets being broadcast to all connected player clients to update item states. There are comparatively few S->C item-related packets, with 0x9C/0x9D doing most of the lifting.
  • Unique packet types are used for unique actions. That is, the protocol encoding isn't very "orthogonal" - if it was an instruction set, it would definitely be CISC not RISC. In LangSec terms, there are many invalid states and actions that the protocol can encode. Consider the packets listed below:
    • XY-indexed buffers (inventory, stash, cube, player trade window) can be handled with 0x18-0x1f, but the belt gets its own actions in 0x23-0x26. The belt should only hold potions and scrolls, but is special! The trade buffer, used to perform atomic item transactions between consenting parties, is very special!
    • Shops aren't XY-indexed. All actions are done using item IDs, which raises questions about what happens if you try to sell things you don't own. Or potions from a belt. Or sell things that are locked by a trade transaction. Or a leap/whirlwind spell. Or...
    • There's some weird quest-specific packets. Here you'll find the one-off hacks written in a single night and forgotten. Consider the end of the comment for 0x44 on newd2event:
      • "Used to activate questobjects with the questitems u have to get, if u'll change a objects with state 00 00 -> graphical error(drophack) + 1mana->charcorrupt".
  • The game's been through a pile of patches, dev turnover, and a major expansion. Many protocol additions are hacked-on. (For example, switching to a secondary set of equipped weapons was added during the expansion. Triggering it and detecting that it correctly occurred during latency spikes has been a rite of passage for packet-based bot writers since.)
18   17   Insert item in buffer      18 [DWORD id] [DWORD xpos] [DWORD ypos] [DWORD buffer]
19   5   Remove item from buffer      19 [DWORD id]
1f   17   Swap cursor/buffer items   1f [DWORD cursor item id] [DWORD buffer item id] [DWORD xpos] [DWORD ypos]
23   9   Item to belt         23 [DWORD id] [WORD x] [WORD y]
24   5   Item from belt         24 [DWORD id]
26   13   Use belt item         26 [DWORD id] [DWORD shift key stats] [DWORD unknown]
32   17   Buy item from NPC buffer   32 [DWORD entity id] [DWORD item id] [DWORD tab] [DWORD cost]
33   17   Sell item to NPC buffer      33 [DWORD entity id] [DWORD item id] [DWORD tab] [DWORD cost]
44   17   Staff in orifice      44 [DWORD orifice entity kind] [DWORD orifice id] [DWORD staff id] [DWORD entity state]

History

Hold on, you said this game isn't new. What similar exploits were there in the past?

Glad you asked - there's been some weird stuff. Particularly relevant are "Ith" items - the method for which went public ages ago.

Ith item

The instructions (which complain about non-determinism, the bane of all interesting exploits) describe sniffing the rune items socketed into a runeword item (0x9D S->C packet) then using the 0x33 C->S packet to sell the runes. The result above is a glitched Silence runeword with open sockets, which some enterprising individual then Zod-bugged and filled with attack speed and life leech runes and some jewels.

Admittedly, the past is full of lies bugs and trolling - and some of the bugged items that used to float around must have been direct item imports:

Constricting ring

White ring

So; we know an exploit with similar effects existed in the past. It was patched, and an "Ith scanner" was added to detect and remove some types of impossible items. Experience says blacklist-based detection is never perfect, and in some systems bugs will always be dense.

This should be enough background to examine the recently-released exploits.

Step 1: Jerhyn Trade Dupe

Let's start with an elegant dupe published on Jun11-2014. Skim through the first post, and I'll provide some background.

  • The "Game maker" is used to place quest NPCs in particular positions, based on their progress through the game. Jerhyn is an NPC that initially welcomes you to the city on its south side, but gets moved to the palace in the west side once you leave the city.
  • Talking to NPCs (to open trade, or advance quests, or listen to painstakingly-voiced lore) affects their behavior in a way that's visible to other players. The state is entered on the server by sending 0x2f, and exited by sending 0x30. Since this dupe is done from a (mostly) unmodified client, blocking 0x30 lets you exit the client from the NPC transaction while retaining it server-side.
  • Similarly, trading with other players is a transaction. Spoofing receipt of 0x70 with the 0x0c button ID exits the trade window, allowing the client to perform some actions. However, unlike the NPC state, years of abuse-patch-abuse-abuse cycles have greatly reduced the actions available from a trade state.
  • The reason trades are special is that to ensure an atomic transaction did not leave room for scamming, and that canceling a trade would return your inventory to exactly how it began, actions while in the trade state are performed on a duplicate of your inventory. (Oops.)

In short, the packet blocking/spoofing is there to manipulate the client into ignoring transactions while the server maintains them. Otherwise the exploit is simple:

  1. A transaction is opened with an NPC and never closed.
  2. During this transaction, a second transaction is opened with a player.
  3. Items are traded, and the NPC transaction is force-closed by moving to a different zone, triggering a quest event on that NPC. Presumably, a transaction cleanup step in the event handler triggers a server-side type confusion on the player trade. This closes the trade for one player without cleaning up the duplicate inventory - leaving them free to drop the duplicate items in the trade buffer.
 C->S
2f   9   Initiate entity chat      2f [DWORD entity kind] [DWORD id]
30   9   Terminate entity chat      30 [DWORD entity kind] [DWORD id]
49   9   Take WP/Close WP      49 [DWORD wp id] [BYTE destination] 00 00 00
 S->C
77   2   Button Actions      77 [BYTE Action]

A remaining question is the waypoint packet. A destination parameter of 0 just closes the waypoint selection window, but it's a necessary step for the dupe. I assume it's needed to advance the server-side quest state to the point where Jerhyn's movement to the palace can be triggered. This is supported by some careful instructions for setting up repeated dupes in one game, centering around putting the "game maker" into a very specific quest state.

Worth mentioning is the "clash check" mechanic, explained in glorious rambling infodump detail by Dark Mage. At some point Blizzard realized fixing all dupe methods was beyond their grasp, and instead implemented unique item tags that triggered deletion if identical. However, this generic solution had serious resource and implementation constraints:

  • The scans were periodic, running across an entire game server, each gameserver hosting about a hundred games.
  • Deletion could only be done during a save checkpoint, and saves only happen when characters disconnect from the game.
  • Most importantly, the trade implementation dupes items by design. It plays merry hell with the dupe scan by invalidating the "duped item, delete on exit!" flags on trade exit. This serves as an idiotproof "perm" method for reliable scan evasion.

Step 2: Staff in Orifice Dupe

Let's walk through the Dec31-2016 dupe method. It's a little weirder, and requires 3 game clients. They're referred to as A-B-C in the post, with A being the "game creator"/"trader" in the previous dupe and B being the "mule". C in this case is a necessary wildcard, but can be highly useful since this dupe is also a controlled rollback.

This time I'll annotate each step with comments:

    1. This exploit interacts with Act 5 quests - in this case, the Duper has not yet saved Anya or killed Nihlathak. This ensures Nihlathak ("Nick" for short) is present and can still be interacted with as a friendly NPC, instead of a murderous necromancer.
    1. All clients block 0x51 (object updates) and 0x5E (some kind of large quest-related updates) receives. The comment for 0x44 on one of the packet lists mentions a "graphical error" drophack; blocking these packets likely maintains client stability during further steps, as the server starts sending packets not normally seen.
    1. Player A unblocks their client from the NPC transaction, as before.
  • 4-5. Larzuk is a shopkeeper in Act 5. He has a quest that allows a single item to be imbued with sockets. 0x44 is a quest-interaction packet involving items, such as inserting the Horadric Staff into the Orifice in the Tomb of Tal Rasha. It's a good bet that it's the same one used for Larzuk's socket quest. This step also increments your mana by 1 - forgotten quest effect, or memory corruption?
  • 6-7. Nihlathak is another Act 5 quest NPC. The overall sequence opens a transaction with Larzuk, opens a transaction with Nihlathak, then closes the transaction with Larzuk. While the previous dupe triggered a type confusion between an NPC transaction and a player trade, this one uses two NPCs.
 C->S
31   9   Quest message         31 [DWORD id] [DWORD message]
44   17   Staff in orifice      44 [DWORD orifice entity kind] [DWORD orifice id] [DWORD staff id] [DWORD entity state]
 S->C
51   14   Assign Object      51 [BYTE Object Type] [DWORD Object Id]  [WORD Object Code] [WORD X] [WORD Y] [BYTE State] [BYTE Interaction Type]
5d   5   Quest Item State   5d [BYTE Unknown (Id?)]  [DWORD State?]
5e   38   <Unknown>      ----------

At this point, whatever happened server-side, things are about to be rightly hooped for character A. (See packet list comment about incrementing mana - at least it wasn't a pointer!)

    1. A and B trade, items go into the trade buffer. However, this time, note that items go from the "trader" to the "mule". In the last dupe, the "mule" duped their own items - this time, they're duping the other party's items!
    1. The Crystalline Passage is a path to the area where Anya is held captive by Nihlathak. Holding a waypoint to it on the 3rd character is necessary, since the other two are locked in a trade. A quest trigger occurs when the area is entered - this is a common behavior, but I'd need to check in-game to see whether it's visible in the quest log. Whatever that trigger does works on the type confusion set up in steps 3-7, and quite possibly crashes the client with a malformed packet if you don't perform step 2.
    1. "Connection interrupted" is Blizzard-talk for being booted from the game server, and "realm down" is what they call temporary IP bans. As you can guess, this confuses innocent players far more than it does hackers.

At this point something is seriously wrong on character A - the type confusion triggered by step 9 resulted in them being saved with character B's items in the trade buffer.

    1. Moving items from a trade buffer directly to your inventory, while not trading, is clearly shenanigans. Selling them to the store via their IDs, then buying them back, is fine!

Phew. That was a little convoluted. Arguably public dupes get more complex with time as the low-hanging fruit is patched out, but these instructions do assume a pile of community/game knowledge and are often kept obtuse to keep the rabble out. Executing exploits manually is also great for initial research, but quickly gets replaced by simple tools like the crude but effective JS bot script linked here. The same post mentions using a Gull dagger's -5 mana bonus to avoid character corruption from excess mana during the 0x44 step.

The rollback mechanism relies on character B still being in trade with a character that's no longer in-game. I don't know whether or not it crashes the server; many rollbacks are due to a full server-crash, which sucks for everyone else on the server. It could be that whatever null pointer or user-after-free that occurs is cleaned up for just the specific game, without affecting others.

Curiously, there's a little-viewed Polish video posted Aug31-2015 showing a variant of this dupe. The same Nihlathak and Crystalline Passage waypoint quest type confusion is triggered. However, packet 0x44 is used on a shrine instead of Larzuk, during the transaction with Nihlathak. In addition, it appears to dupe the mule's items rather than the trader's, as in the first dupe. The video lists it as "patched" - or was it? :)

Step 3: Prof^H^H^H^HHybrids

The hybrid method posted on Aug14-2015 makes use of the items-in-tradebuffer dupe state. It also mentions an alternate method for triggering the Nihlathak type confusion. The steps are as follows:

  1. Run through the previous dupe, or a variant thereof, with the first character.
  2. Have a second character fill their inventory so that trading them an item fails. The rune items socketed into a valid runeword appear to be lost in this step, reducing it to the historic "Ith". This will not survive the on-save invalid item scan.
  3. The second character now triggers the out-of-trade bugged state, and sells the item they sniffed in the trade buffer in step 2.
  4. The item is bought back, and filled with runes for a valid runeword. This generates a new unique tag and permanently evades both the periodic dupe scanner and the on-save "Ith" scan.

We can now see the full chain for creating items like the Peace-Enigma in the first screenshot:

  1. Acquire perfect base item from bots or trades.
  2. Dupe piles of runes. While doing so, use the final rollback to re-try Enigma rolls until you get a perfect one.
  3. Dupe it twice, losing the Enigma runes. Fill with Peace runes to perm it and grant it a new name.

Closing Thoughts

We saw some weird machines that were discovered black-box through a little bit of tooling, and lots of trial and error. They likely match common memory corruption primitives, but even without reliable code to examine (open Battle.net and singleplayer count only sometimes) going off intuition at the packet layer was still effective.

We saw two variations of the same bug class (trade buffer dupes) along with hints that there's more code paths to reach the same effect. The second variation was used to resurrect a third bug class (Ith method) that was previously patched.

The bugs themselves made use of common building blocks (character save control, server rollbacks, blacklist cache invalidation) to evade anti-cheat measures. The bugs were grouped in known problem areas (transactions, obscure one-off code) in the protocol, but were still diverse due to the complexity of the distributed many-clients many-servers system.

@yaezah
Copy link

yaezah commented Feb 2, 2021

Do additional packets need to be blocked/received for #1? I created my own dll to do this on a 1.13d mod-- after speaking with an NPC and blocking 0x30, you're unable to interact with any other players (i.e. click another player to trade).

I did the same, and can't interact with waypoints or walk out of town either (I get lag walled).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment