D2-ANTI-TRADE-COUNTER(7) Miscellaneous D2-ANTI-TRADE-COUNTER(7)

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:

  1. Prevent being crashed by the trade crash exploit.
  2. Print on-screen which player attempted to crash us.
  3. 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

AddressDescription
6FF75350Boolean — TRUE when trade crash detected
6FF75352Boolean — TRUE when counter-crash is armed
6FF75354DWORD — timestamp (ms since Windows boot) of last crash

Packet Buffers

AddressPacket
6FF753604F 03 00 00 00 00 00 — Accept trade / interrupt with no delay
6FF7537013 00 00 00 00 00 00 00 00 — Ask player to trade
6FF753804F 07 00 00 00 00 00 — Trade crash

String Buffers

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

TECHNOLOGIES

  • x86 Assembly
  • Packet Interception
  • Reverse Engineering
  • Diablo II
int03h.com 2001-11-05 D2-ANTI-TRADE-COUNTER(7)