NAME
Anti Trade Crash & Counter Crash — How to detect, block, and counter-crash the Diablo II trade crash exploit — design walkthrough and annotated ASM source.
METADATA
OBJECTIVE
We want this hack to do three things:
- Prevent being crashed by the trade crash exploit.
- Print on-screen which player attempted to crash us.
- Counter-crash that very player.
THE PROBLEMS
Diablo II does not make this easy:
- You cannot know which player is currently asking you to trade.
- There is a ~5 second server-imposed delay after a trade request before either player can send another.
- Sending all packets simultaneously won’t work — the server ignores the extras.
STEP 1 — GETTING THE ATTACKER’S NAME
When another player requests a trade, the server only sends a generic “Trade request” packet — no player identity included. However, if you accept the trade, the server follows up with a packet containing the other player’s info, used by the client to display their name in the trade screen UI.
Packet format (post-descramble):
78 <Name> <ID>
Where Name is 15 bytes and ID is a DWORD. That’s everything we need: the name to display on screen, and the player ID to target with a counter-crash.
The solution: intercept this 0x78 packet in memory and extract both values before the client uses them normally.
STEP 2 — BYPASSING THE TRADE DELAY
A direct counter-crash would require initiating a new trade request immediately — but the server’s delay blocks this.
The workaround: send packet 4F 03 00 00 00 00 00 while inside the trade screen. This causes the trade to close silently (as if the other player cancelled), without triggering the cooldown timer. We can then immediately send a new trade request.
STEP 3 — PACKET TIMING
The server discards packets sent too close together. We need to hook the incoming packet stream and send our response packets at exactly the right moment — specifically when we receive the server acknowledgement that our trade request went through.
SEQUENCE OF OPERATIONS
1. Detect the trade crash packet (EAX == 2 in crash handler)
2. Send "Accept trade" packet (0x4F 03...) to unblock cooldown
3. Intercept 0x78 packet → extract attacker name + ID
4. Print "[name] has tried to crash you" on screen
5. Send "Accept trade" packet again to close the trade cleanly
6. Wait for 0x7700 (character initiating trade) → send trade crash packet
Sanity check: a 2.5-second debounce prevents an infinite mutual counter-crash loop when two players both have this program running.
MEMORY MAP
Variables
| Address | Description |
|---|---|
6FF75350 | Boolean — TRUE when trade crash detected |
6FF75352 | Boolean — TRUE when counter-crash is armed |
6FF75354 | DWORD — timestamp (ms since Windows boot) of last crash |
Packet Buffers
| Address | Packet |
|---|---|
6FF75360 | 4F 03 00 00 00 00 00 — Accept trade / interrupt with no delay |
6FF75370 | 13 00 00 00 00 00 00 00 00 — Ask player to trade |
6FF75380 | 4F 07 00 00 00 00 00 — Trade crash |
String Buffers
| Address | Content |
|---|---|
6FC0675D | " has tried to crash you" |
6FC0677D | "Attempting to counter crash..." |
ANNOTATED SOURCE
Detection — Trade Crash Hook
; Called from 6FF64A60
:6FF75300 cmp eax, 02 ; EAX == 2 when trade crash fires
:6FF75305 jnz 6FF7534A
:6FF75307 mov eax, 6FF7534B ; Point EAX at safe address (prevent client crash)
:6FF7530C pushad
; Sanity check: skip if last crash was < 2.5 seconds ago
:6FF7530D call 6FAB1C33 ; EAX = GetTickCount equivalent
:6FF75312 mov edx, [6FF75354] ; EDX = last recorded timestamp
:6FF75318 mov ecx, eax
:6FF7531A sub ecx, edx
:6FF7531C cmp ecx, 09c4 ; 0x9C4 = 2500 ms
:6FF75322 jb 6FF75349 ; Bail if too recent
:6FF75324 mov [6FF75354], eax ; Update timestamp
; Flag the crash and send "accept trade" twice to unblock cooldown
:6FF75329 mov byte ptr [6FF75350], 01
:6FF75330 mov si, 02
:6FF75334 push 07
:6FF75336 push 6FF75360 ; Accept-trade packet
:6FF7533B push 01
:6FF7533D call 6FC01780 ; D2 send packet routine
:6FF75342 dec si
:6FF75344 test si, si
:6FF75347 jnz 6FF75334 ; Repeat twice
:6FF75349 popad
:6FF7534A ret
Incoming Packet Intercept
; Hooked at 6FC0166F
:6FC065FD cmp byte ptr [6FF75350], 01 ; Crash flag set?
:6FC06604 jnz 6FC06643 ; No — fall through normally
:6FC06606 pushad
; 0xC77 = trade screen just closed → send "ask to trade" packet
:6FC06607 cmp word ptr [ebx], 0c77
:6FC0660C jnz 6FC0661C
:6FC0660E push 09
:6FC06610 push 6FC0685D
:6FC06615 push 01
:6FC06617 call 6FC01780
; 0x0077 = we just asked someone to trade → send crash packet
:6FC0661C cmp word ptr [ebx], 0077
:6FC06621 jnz 6FC06638
:6FC06623 push 07
:6FC06625 push 6FF75380 ; Trade crash packet
:6FC0662A push 01
:6FC0662C call 6FC01780
:6FC06631 mov byte ptr [6FF75350], 00 ; Reset flag — done
; 0x78 = received other player's info packet → extract name + ID
:6FC06638 cmp byte ptr [ebx], 78
:6FC0663B jnz 6FC06642
:6FC0663D call 6FC0665D ; Get player info subroutine
:6FC06642 popad
:6FC06643 mov edi, ebp ; Restore overwritten registers
:6FC06645 shr ecx, 02
:6FC06648 ret
Extract Player Name & ID
:6FC0665D pushad
; Measure name length (scan for null terminator)
:6FC0665E xor ecx, ecx
:6FC06660 inc ecx
:6FC06661 cmp byte ptr [ecx+ebx], 00
:6FC06665 jnz 6FC06660
; Copy name into temp buffer
:6FC06667 dec ecx
:6FC06668 lea esi, [ebx+01]
:6FC0666B mov edi, 6FC0685D
:6FC06670 repz movsb
; Append " has tried to crash you"
:6FC06672 push 18
:6FC06674 pop ecx
:6FC06675 mov esi, 6FC0675D
:6FC0667A repz movsb
; Print message in red (color 0x13)
:6FC0667C push 13
:6FC0667E push 6FC0685D
:6FC06683 call 6FC06700
; If counter-crash armed: copy player ID into trade request packet
:6FC06688 cmp byte ptr [6FF75352], 01
:6FC0668F jnz 6FC066B8
:6FC06691 push 0F
:6FC06693 push 6FC0677D ; "Attempting to counter crash..."
:6FC06698 call 6FC06700
:6FC0669D push 09
:6FC0669F pop ecx
:6FC066A0 mov esi, 6FF75370 ; "Ask to trade" packet template
:6FC066A5 mov edi, 6FC0685D
:6FC066AA repz movsb
:6FC066AC lea eax, [ebx+11] ; Player ID offset in 0x78 packet
:6FC066AF mov eax, [eax]
:6FC066B1 mov [6FC06862], eax ; Patch ID into packet buffer
:6FC066B6 jmp 6FC066BF
:6FC066B8 mov byte ptr [6FF75350], 00 ; Counter-crash off — reset flag
:6FC066BF push 07
:6FC066C1 push 6FF75360 ; Interrupt current trade (no delay)
:6FC066C6 push 01
:6FC066C8 call 6FC01780
:6FC066CD popad
:6FC066CE ret
Print Message to Screen
; Parameters: [esp+4] = message offset, [esp+8] = color byte
:6FC06700 mov ebp, esp
:6FC06702 push 00000100 ; Max string length 0x100
:6FC06707 mov edx, [ebp+04] ; Message pointer
:6FC0670A mov ecx, 6FC068AD ; Destination buffer
:6FC0670F call [6FB6CB6C] ; D2 wide-string conversion
:6FC06715 mov dl, [ebp+08] ; Color
:6FC06718 mov ecx, 6FC068AD
:6FC0671D call 6FB21C20 ; D2 in-game message print routine
:6FC06722 ret 0008
SEE ALSO
- d2-trade-crash(6) — the exploit this defends against
TECHNOLOGIES
- x86 Assembly
- Packet Interception
- Reverse Engineering
- Diablo II