fix login issue*
This commit is contained in:
60
test/crypto_simple_test.exs
Normal file
60
test/crypto_simple_test.exs
Normal file
@@ -0,0 +1,60 @@
|
||||
defmodule Odinsea.CryptoSimpleTest do
|
||||
@moduledoc """
|
||||
Simple test module for MapleStory crypto implementation.
|
||||
These tests don't require the full application to start.
|
||||
"""
|
||||
|
||||
use ExUnit.Case
|
||||
|
||||
alias Odinsea.Net.Cipher.{IGCipher, ShandaCipher, AESCipher}
|
||||
|
||||
describe "IGCipher (IV transformation)" do
|
||||
test "inno_hash transforms IV correctly" do
|
||||
iv = <<0x34, 0x9A, 0x0F, 0x0C>>
|
||||
result = IGCipher.inno_hash(iv)
|
||||
|
||||
# Result should be 4 bytes
|
||||
assert byte_size(result) == 4
|
||||
|
||||
# Result should be different from input (usually)
|
||||
assert result != iv
|
||||
end
|
||||
|
||||
test "inno_hash produces deterministic results" do
|
||||
iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
|
||||
result1 = IGCipher.inno_hash(iv)
|
||||
result2 = IGCipher.inno_hash(iv)
|
||||
|
||||
assert result1 == result2
|
||||
end
|
||||
end
|
||||
|
||||
describe "ShandaCipher" do
|
||||
test "encrypt and decrypt are inverse operations" do
|
||||
original = <<1, 0, 7, 112, 0, 4, 0>> # CP_PermissionRequest payload
|
||||
encrypted = ShandaCipher.encrypt(original)
|
||||
decrypted = ShandaCipher.decrypt(encrypted)
|
||||
|
||||
assert decrypted == original
|
||||
end
|
||||
|
||||
test "encrypt produces different output" do
|
||||
original = <<1, 0, 7, 112, 0, 4, 0>>
|
||||
encrypted = ShandaCipher.encrypt(original)
|
||||
|
||||
assert encrypted != original
|
||||
end
|
||||
end
|
||||
|
||||
describe "AESCipher" do
|
||||
test "crypt is self-inverse (XOR property)" do
|
||||
data = <<1, 0, 7, 112, 0, 4, 0>>
|
||||
iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
|
||||
|
||||
encrypted = AESCipher.crypt(data, iv)
|
||||
decrypted = AESCipher.crypt(encrypted, iv)
|
||||
|
||||
assert decrypted == data
|
||||
end
|
||||
end
|
||||
end
|
||||
185
test/crypto_test.exs
Normal file
185
test/crypto_test.exs
Normal file
@@ -0,0 +1,185 @@
|
||||
defmodule Odinsea.CryptoTest do
|
||||
@moduledoc """
|
||||
Test module for MapleStory crypto implementation.
|
||||
Run with: mix test test/crypto_test.exs
|
||||
"""
|
||||
|
||||
use ExUnit.Case
|
||||
|
||||
alias Odinsea.Net.Cipher.{ClientCrypto, IGCipher, ShandaCipher, AESCipher}
|
||||
alias Odinsea.Util.BitTools
|
||||
|
||||
describe "IGCipher (IV transformation)" do
|
||||
test "inno_hash transforms IV correctly" do
|
||||
iv = <<0x34, 0x9A, 0x0F, 0x0C>>
|
||||
result = IGCipher.inno_hash(iv)
|
||||
|
||||
# Result should be 4 bytes
|
||||
assert byte_size(result) == 4
|
||||
|
||||
# Result should be different from input (usually)
|
||||
assert result != iv
|
||||
end
|
||||
|
||||
test "inno_hash produces deterministic results" do
|
||||
iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
|
||||
result1 = IGCipher.inno_hash(iv)
|
||||
result2 = IGCipher.inno_hash(iv)
|
||||
|
||||
assert result1 == result2
|
||||
end
|
||||
end
|
||||
|
||||
describe "ShandaCipher" do
|
||||
test "encrypt and decrypt are inverse operations" do
|
||||
original = <<1, 0, 7, 112, 0, 4, 0>> # CP_PermissionRequest payload
|
||||
encrypted = ShandaCipher.encrypt(original)
|
||||
decrypted = ShandaCipher.decrypt(encrypted)
|
||||
|
||||
assert decrypted == original
|
||||
end
|
||||
|
||||
test "encrypt produces different output" do
|
||||
original = <<1, 0, 7, 112, 0, 4, 0>>
|
||||
encrypted = ShandaCipher.encrypt(original)
|
||||
|
||||
assert encrypted != original
|
||||
end
|
||||
end
|
||||
|
||||
describe "AESCipher" do
|
||||
test "crypt is self-inverse (XOR property)" do
|
||||
data = <<1, 0, 7, 112, 0, 4, 0>>
|
||||
iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
|
||||
|
||||
encrypted = AESCipher.crypt(data, iv)
|
||||
decrypted = AESCipher.crypt(encrypted, iv)
|
||||
|
||||
assert decrypted == data
|
||||
end
|
||||
end
|
||||
|
||||
describe "ClientCrypto" do
|
||||
test "new creates crypto with random IVs" do
|
||||
crypto = ClientCrypto.new(112)
|
||||
|
||||
assert byte_size(crypto.send_iv) == 4
|
||||
assert byte_size(crypto.recv_iv) == 4
|
||||
assert crypto.version == 112
|
||||
end
|
||||
|
||||
test "new_from_ivs creates crypto with specified IVs" do
|
||||
send_iv = <<0x34, 0x9A, 0x0F, 0x0C>>
|
||||
recv_iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
|
||||
|
||||
crypto = ClientCrypto.new_from_ivs(112, send_iv, recv_iv)
|
||||
|
||||
assert crypto.send_iv == send_iv
|
||||
assert crypto.recv_iv == recv_iv
|
||||
end
|
||||
|
||||
test "new_from_client_ivs swaps IVs correctly" do
|
||||
# Client's perspective
|
||||
client_send_iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
|
||||
client_recv_iv = <<0x34, 0x9A, 0x0F, 0x0C>>
|
||||
|
||||
# Server's perspective (should be swapped)
|
||||
crypto = ClientCrypto.new_from_client_ivs(112, client_send_iv, client_recv_iv)
|
||||
|
||||
# Server's send = client's recv
|
||||
assert crypto.send_iv == client_recv_iv
|
||||
# Server's recv = client's send
|
||||
assert crypto.recv_iv == client_send_iv
|
||||
end
|
||||
|
||||
test "decrypt updates recv_iv" do
|
||||
crypto = ClientCrypto.new(112)
|
||||
original_recv_iv = crypto.recv_iv
|
||||
|
||||
encrypted_data = <<0x7C, 0xA8, 0x7B, 0xA8, 0xBF, 0x0A, 0xCD, 0xDE>>
|
||||
{new_crypto, _decrypted} = ClientCrypto.decrypt(crypto, encrypted_data)
|
||||
|
||||
# IV should be updated after decryption
|
||||
assert new_crypto.recv_iv != original_recv_iv
|
||||
end
|
||||
|
||||
test "encrypt updates send_iv" do
|
||||
crypto = ClientCrypto.new(112)
|
||||
original_send_iv = crypto.send_iv
|
||||
|
||||
data = <<1, 0, 7, 112, 0, 4, 0>>
|
||||
{new_crypto, _encrypted, _header} = ClientCrypto.encrypt(crypto, data)
|
||||
|
||||
# IV should be updated after encryption
|
||||
assert new_crypto.send_iv != original_send_iv
|
||||
end
|
||||
|
||||
test "header encoding produces 4 bytes" do
|
||||
crypto = ClientCrypto.new(112)
|
||||
data = <<1, 0, 7, 112, 0, 4, 0>>
|
||||
|
||||
{_new_crypto, _encrypted, header} = ClientCrypto.encrypt(crypto, data)
|
||||
|
||||
assert byte_size(header) == 4
|
||||
end
|
||||
|
||||
test "header validation works correctly" do
|
||||
crypto = ClientCrypto.new(112)
|
||||
data = <<1, 0, 7, 112, 0, 4, 0>>
|
||||
|
||||
{new_crypto, encrypted, header} = ClientCrypto.encrypt(crypto, data)
|
||||
|
||||
# Extract raw_seq from header
|
||||
<<raw_seq::little-16, _raw_len::little-16>> = header
|
||||
|
||||
# Validation should pass
|
||||
assert ClientCrypto.decode_header_valid?(new_crypto, raw_seq)
|
||||
end
|
||||
|
||||
test "full encrypt/decrypt roundtrip" do
|
||||
# Create crypto instances with same IVs
|
||||
send_iv = <<0x34, 0x9A, 0x0F, 0x0C>>
|
||||
recv_iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
|
||||
|
||||
# Server's crypto (for sending)
|
||||
server_crypto = ClientCrypto.new_from_ivs(112, send_iv, recv_iv)
|
||||
|
||||
# Client's crypto would have swapped IVs
|
||||
# For testing, we'll use the same crypto to verify roundtrip
|
||||
original_data = <<1, 0, 7, 112, 0, 4, 0>>
|
||||
|
||||
# Encrypt
|
||||
{server_crypto_after, encrypted, header} = ClientCrypto.encrypt(server_crypto, original_data)
|
||||
|
||||
# Decrypt (using recv_iv which should match server's send_iv after swap)
|
||||
# For this test, we'll create a matching client crypto
|
||||
client_crypto = ClientCrypto.new_from_ivs(112, recv_iv, send_iv)
|
||||
{client_crypto_after, decrypted} = ClientCrypto.decrypt(client_crypto, encrypted)
|
||||
|
||||
assert decrypted == original_data
|
||||
end
|
||||
end
|
||||
|
||||
describe "BitTools" do
|
||||
test "roll_left rotates bits correctly" do
|
||||
# 0b11001101 = 205
|
||||
# rotate left by 3: 0b01101101 = 109 (wrapping around)
|
||||
result = BitTools.roll_left(205, 3)
|
||||
assert result == 109
|
||||
end
|
||||
|
||||
test "roll_right rotates bits correctly" do
|
||||
# Test that roll_right is inverse of roll_left
|
||||
original = 205
|
||||
rotated = BitTools.roll_left(original, 3)
|
||||
back = BitTools.roll_right(rotated, 3)
|
||||
assert back == original
|
||||
end
|
||||
|
||||
test "multiply_bytes repeats correctly" do
|
||||
input = <<1, 2, 3, 4>>
|
||||
result = BitTools.multiply_bytes(input, 4, 2)
|
||||
assert result == <<1, 2, 3, 4, 1, 2, 3, 4>>
|
||||
end
|
||||
end
|
||||
end
|
||||
80
test/debug_crypto.exs
Normal file
80
test/debug_crypto.exs
Normal file
@@ -0,0 +1,80 @@
|
||||
# Debug script to trace crypto operations
|
||||
# Run with: cd /home/ra/odinsea-elixir && elixir test/debug_crypto.exs
|
||||
|
||||
Code.require_file("lib/odinsea/util/bit_tools.ex", ".")
|
||||
Code.require_file("lib/odinsea/net/cipher/ig_cipher.ex", ".")
|
||||
Code.require_file("lib/odinsea/net/cipher/aes_cipher.ex", ".")
|
||||
Code.require_file("lib/odinsea/net/cipher/shanda_cipher.ex", ".")
|
||||
|
||||
alias Odinsea.Net.Cipher.{IGCipher, AESCipher, ShandaCipher}
|
||||
alias Odinsea.Util.BitTools
|
||||
|
||||
import Bitwise
|
||||
|
||||
IO.puts("=== Debugging Crypto ===\n")
|
||||
|
||||
# From the packet log:
|
||||
# Server's recv_iv = <<0xA8, 0xBC, 0x0D, 0xB3>> (client's send_iv)
|
||||
recv_iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
|
||||
|
||||
# First encrypted packet from client:
|
||||
# [Data] 7C A8 7B A8 BF 0A CD DE C7 71 AC
|
||||
packet = <<0x7C, 0xA8, 0x7B, 0xA8, 0xBF, 0x0A, 0xCD, 0xDE, 0xC7, 0x71, 0xAC>>
|
||||
|
||||
IO.puts("Raw packet: #{Base.encode16(packet)}")
|
||||
IO.puts("Server recv_iv: #{Base.encode16(recv_iv)}")
|
||||
|
||||
# Extract header
|
||||
<<raw_seq::little-16, raw_len::little-16, payload::binary>> = packet
|
||||
IO.puts("\nHeader:")
|
||||
IO.puts(" raw_seq: 0x#{Integer.to_string(raw_seq, 16)} (#{raw_seq})")
|
||||
IO.puts(" raw_len: 0x#{Integer.to_string(raw_len, 16)} (#{raw_len})")
|
||||
IO.puts(" payload: #{Base.encode16(payload)} (#{byte_size(payload)} bytes)")
|
||||
|
||||
# Validate header
|
||||
<<_r0, _r1, r2, r3>> = recv_iv
|
||||
enc_version = 112
|
||||
seq_base = ((r2 &&& 0xFF) ||| ((r3 <<< 8) &&& 0xFF00)) &&& 0xFFFF
|
||||
calculated_version = Bitwise.bxor(raw_seq &&& 0xFFFF, seq_base)
|
||||
|
||||
IO.puts("\nHeader validation:")
|
||||
IO.puts(" r2: 0x#{Integer.to_string(r2, 16)} (#{r2})")
|
||||
IO.puts(" r3: 0x#{Integer.to_string(r3, 16)} (#{r3})")
|
||||
IO.puts(" seq_base: 0x#{Integer.to_string(seq_base, 16)} (#{seq_base})")
|
||||
IO.puts(" calculated_version: #{calculated_version}")
|
||||
IO.puts(" expected_version: #{enc_version}")
|
||||
IO.puts(" valid: #{calculated_version == enc_version}")
|
||||
|
||||
# Get packet length
|
||||
packet_len = Bitwise.bxor(raw_seq, raw_len) &&& 0xFFFF
|
||||
IO.puts("\nPacket length: #{packet_len}")
|
||||
|
||||
# Decrypt with AES
|
||||
IO.puts("\n=== AES Decryption ===")
|
||||
aes_decrypted = AESCipher.crypt(payload, recv_iv)
|
||||
IO.puts("After AES: #{Base.encode16(aes_decrypted)}")
|
||||
|
||||
# Decrypt with Shanda
|
||||
IO.puts("\n=== Shanda Decryption ===")
|
||||
shanda_decrypted = ShandaCipher.decrypt(aes_decrypted)
|
||||
IO.puts("After Shanda: #{Base.encode16(shanda_decrypted)}")
|
||||
|
||||
# Expected opcode 0x01 = 1
|
||||
if byte_size(shanda_decrypted) >= 2 do
|
||||
<<opcode::little-16, rest::binary>> = shanda_decrypted
|
||||
IO.puts("\n=== Result ===")
|
||||
IO.puts("Opcode: 0x#{Integer.to_string(opcode, 16)} (#{opcode})")
|
||||
IO.puts("Rest: #{Base.encode16(rest)}")
|
||||
|
||||
if opcode == 1 do
|
||||
IO.puts("\n✓ SUCCESS! This is CP_PermissionRequest (0x01)")
|
||||
else
|
||||
IO.puts("\n✗ FAILED! Expected opcode 0x01, got 0x#{Integer.to_string(opcode, 16)}")
|
||||
end
|
||||
end
|
||||
|
||||
# Update IV
|
||||
new_recv_iv = IGCipher.inno_hash(recv_iv)
|
||||
IO.puts("\nUpdated recv_iv: #{Base.encode16(new_recv_iv)}")
|
||||
|
||||
IO.puts("\n=== Done ===")
|
||||
101
test/debug_crypto2.exs
Normal file
101
test/debug_crypto2.exs
Normal file
@@ -0,0 +1,101 @@
|
||||
# Debug script to trace crypto operations with CORRECT IV
|
||||
# Run with: cd /home/ra/odinsea-elixir && elixir test/debug_crypto2.exs
|
||||
|
||||
Code.require_file("lib/odinsea/util/bit_tools.ex", ".")
|
||||
Code.require_file("lib/odinsea/net/cipher/ig_cipher.ex", ".")
|
||||
Code.require_file("lib/odinsea/net/cipher/aes_cipher.ex", ".")
|
||||
Code.require_file("lib/odinsea/net/cipher/shanda_cipher.ex", ".")
|
||||
|
||||
alias Odinsea.Net.Cipher.{IGCipher, AESCipher, ShandaCipher}
|
||||
|
||||
import Bitwise
|
||||
|
||||
IO.puts("=== Debugging Crypto (Corrected IV) ===\n")
|
||||
|
||||
# From the hello packet sent by server:
|
||||
# [Data] 0E 00 70 00 01 00 34 9A 0F 0C A8 BC 0D B3 E6 07
|
||||
# Server sends: recv_iv=34 9A 0F 0C, send_iv=A8 BC 0D B3
|
||||
#
|
||||
# Client SWAPS these:
|
||||
# Client's send_iv = server's recv_iv = 34 9A 0F 0C
|
||||
# Client's recv_iv = server's send_iv = A8 BC 0D B3
|
||||
#
|
||||
# Client encrypts with its send_iv (34 9A 0F 0C)
|
||||
# Server must decrypt with its recv_iv = 34 9A 0F 0C
|
||||
|
||||
server_send_iv = <<0xA8, 0xBC, 0x0D, 0xB3>> # Server sends this, client uses as recv
|
||||
server_recv_iv = <<0x34, 0x9A, 0x0F, 0x0C>> # Server sends this, client uses as send
|
||||
|
||||
IO.puts("Server perspective:")
|
||||
IO.puts(" server_send_iv: #{Base.encode16(server_send_iv)}")
|
||||
IO.puts(" server_recv_iv: #{Base.encode16(server_recv_iv)}")
|
||||
|
||||
IO.puts("\nClient perspective (after swapping):")
|
||||
IO.puts(" client_send_iv = server_recv_iv: #{Base.encode16(server_recv_iv)}")
|
||||
IO.puts(" client_recv_iv = server_send_iv: #{Base.encode16(server_send_iv)}")
|
||||
|
||||
# First encrypted packet from client:
|
||||
# [Data] 7C A8 7B A8 BF 0A CD DE C7 71 AC
|
||||
packet = <<0x7C, 0xA8, 0x7B, 0xA8, 0xBF, 0x0A, 0xCD, 0xDE, 0xC7, 0x71, 0xAC>>
|
||||
|
||||
IO.puts("\nRaw packet: #{Base.encode16(packet)}")
|
||||
|
||||
# Server decrypts with server_recv_iv
|
||||
recv_iv = server_recv_iv
|
||||
IO.puts("Server decrypts with recv_iv: #{Base.encode16(recv_iv)}")
|
||||
|
||||
# Extract header
|
||||
<<raw_seq::little-16, raw_len::little-16, payload::binary>> = packet
|
||||
IO.puts("\nHeader:")
|
||||
IO.puts(" raw_seq: 0x#{Integer.to_string(raw_seq, 16)} (#{raw_seq})")
|
||||
IO.puts(" raw_len: 0x#{Integer.to_string(raw_len, 16)} (#{raw_len})")
|
||||
IO.puts(" payload: #{Base.encode16(payload)} (#{byte_size(payload)} bytes)")
|
||||
|
||||
# Validate header
|
||||
<<_r0, _r1, r2, r3>> = recv_iv
|
||||
enc_version = 112
|
||||
seq_base = ((r2 &&& 0xFF) ||| ((r3 <<< 8) &&& 0xFF00)) &&& 0xffff
|
||||
calculated_version = bxor(raw_seq &&& 0xFFFF, seq_base)
|
||||
|
||||
IO.puts("\nHeader validation:")
|
||||
IO.puts(" r2: 0x#{Integer.to_string(r2, 16)} (#{r2})")
|
||||
IO.puts(" r3: 0x#{Integer.to_string(r3, 16)} (#{r3})")
|
||||
IO.puts(" seq_base: 0x#{Integer.to_string(seq_base, 16)} (#{seq_base})")
|
||||
IO.puts(" raw_seq ^ seq_base: #{calculated_version}")
|
||||
IO.puts(" expected_version: #{enc_version}")
|
||||
IO.puts(" valid: #{calculated_version == enc_version}")
|
||||
|
||||
# Get packet length
|
||||
packet_len = bxor(raw_seq, raw_len) &&& 0xFFFF
|
||||
IO.puts("\nPacket length: #{packet_len}")
|
||||
|
||||
# Decrypt with AES
|
||||
IO.puts("\n=== AES Decryption ===")
|
||||
IO.puts("Input: #{Base.encode16(payload)}")
|
||||
aes_decrypted = AESCipher.crypt(payload, recv_iv)
|
||||
IO.puts("After AES: #{Base.encode16(aes_decrypted)}")
|
||||
|
||||
# Decrypt with Shanda
|
||||
IO.puts("\n=== Shanda Decryption ===")
|
||||
shanda_decrypted = ShandaCipher.decrypt(aes_decrypted)
|
||||
IO.puts("After Shanda: #{Base.encode16(shanda_decrypted)}")
|
||||
|
||||
# Expected opcode 0x01 = 1
|
||||
if byte_size(shanda_decrypted) >= 2 do
|
||||
<<opcode::little-16, rest::binary>> = shanda_decrypted
|
||||
IO.puts("\n=== Result ===")
|
||||
IO.puts("Opcode: 0x#{Integer.to_string(opcode, 16)} (#{opcode})")
|
||||
IO.puts("Rest: #{Base.encode16(rest)}")
|
||||
|
||||
if opcode == 1 do
|
||||
IO.puts("\n✓ SUCCESS! This is CP_PermissionRequest (0x01)")
|
||||
else
|
||||
IO.puts("\n✗ FAILED! Expected opcode 0x01, got 0x#{Integer.to_string(opcode, 16)}")
|
||||
end
|
||||
end
|
||||
|
||||
# Update IV
|
||||
new_recv_iv = IGCipher.inno_hash(recv_iv)
|
||||
IO.puts("\nUpdated recv_iv: #{Base.encode16(new_recv_iv)}")
|
||||
|
||||
IO.puts("\n=== Done ===")
|
||||
53
test/find_iv.exs
Normal file
53
test/find_iv.exs
Normal file
@@ -0,0 +1,53 @@
|
||||
# Find the correct IV by trying all combinations from hello packet
|
||||
# Hello packet: 0E 00 70 00 01 00 34 9A 0F 0C A8 BC 0D B3 E6 07
|
||||
|
||||
import Bitwise
|
||||
|
||||
# Raw packet from client
|
||||
packet = <<0x7C, 0xA8, 0x7B, 0xA8, 0xBF, 0x0A, 0xCD, 0xDE, 0xC7, 0x71, 0xAC>>
|
||||
<<raw_seq::little-16, _raw_len::little-16, _payload::binary>> = packet
|
||||
|
||||
IO.puts("Raw packet: #{Base.encode16(packet)}")
|
||||
IO.puts("raw_seq: 0x#{Integer.to_string(raw_seq, 16)} (#{raw_seq})")
|
||||
IO.puts("")
|
||||
|
||||
# For header validation to pass: raw_seq ^ seq_base == 112
|
||||
target_seq_base = bxor(raw_seq, 112)
|
||||
IO.puts("Need seq_base: 0x#{Integer.to_string(target_seq_base, 16)} (#{target_seq_base})")
|
||||
IO.puts("")
|
||||
|
||||
# seq_base = (r2 & 0xFF) | ((r3 << 8) & 0xFF00)
|
||||
# So: r2 = lower byte, r3 = upper byte
|
||||
target_r2 = target_seq_base &&& 0xFF
|
||||
target_r3 = (target_seq_base >>> 8) &&& 0xFF
|
||||
IO.puts("Need recv_iv[2] = 0x#{Integer.to_string(target_r2, 16)} (#{target_r2})")
|
||||
IO.puts("Need recv_iv[3] = 0x#{Integer.to_string(target_r3, 16)} (#{target_r3})")
|
||||
IO.puts("")
|
||||
|
||||
# Bytes available in hello packet (positions 6-13):
|
||||
# 34 9A 0F 0C A8 BC 0D B3
|
||||
bytes = [0x34, 0x9A, 0x0F, 0x0C, 0xA8, 0xBC, 0x0D, 0xB3]
|
||||
IO.puts("Available bytes from hello packet:")
|
||||
Enum.each(Enum.with_index(bytes), fn {b, i} ->
|
||||
IO.puts(" [#{i}]: 0x#{Integer.to_string(b, 16)}")
|
||||
end)
|
||||
IO.puts("")
|
||||
|
||||
# Find matching bytes
|
||||
IO.puts("Looking for matches...")
|
||||
Enum.each(Enum.with_index(bytes), fn {b2, i2} ->
|
||||
Enum.each(Enum.with_index(bytes), fn {b3, i3} ->
|
||||
if b2 == target_r2 and b3 == target_r3 do
|
||||
IO.puts("Found match! recv_iv[2]=0x#{Integer.to_string(b2, 16)} at [#{i2}], recv_iv[3]=0x#{Integer.to_string(b3, 16)} at [#{i3}]")
|
||||
|
||||
# Construct full IV (need to determine r0 and r1 too)
|
||||
# Try different combinations for r0 and r1
|
||||
Enum.each(Enum.with_index(bytes), fn {b0, i0} ->
|
||||
Enum.each(Enum.with_index(bytes), fn {b1, i1} ->
|
||||
iv = <<b0, b1, b2, b3>>
|
||||
IO.puts(" Possible IV: #{Base.encode16(iv)} (bytes[#{i0}][#{i1}][#{i2}][#{i3}])")
|
||||
end)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
48
test/test_aes.exs
Normal file
48
test/test_aes.exs
Normal file
@@ -0,0 +1,48 @@
|
||||
# Test AES cipher with known values
|
||||
|
||||
Code.require_file("lib/odinsea/util/bit_tools.ex", ".")
|
||||
Code.require_file("lib/odinsea/net/cipher/ig_cipher.ex", ".")
|
||||
Code.require_file("lib/odinsea/net/cipher/aes_cipher.ex", ".")
|
||||
|
||||
alias Odinsea.Net.Cipher.AESCipher
|
||||
|
||||
import Bitwise
|
||||
|
||||
# Test: AES crypt should be self-inverse (since it's just XOR)
|
||||
iv = <<0x0F, 0x0C, 0x0C, 0xA8>>
|
||||
data = <<0xBF, 0x0A, 0xCD, 0xDE, 0xC7, 0x71, 0xAC>>
|
||||
|
||||
IO.puts("Testing AES cipher:")
|
||||
IO.puts("IV: #{Base.encode16(iv)}")
|
||||
IO.puts("Data: #{Base.encode16(data)}")
|
||||
IO.puts("")
|
||||
|
||||
# Expand IV to 16 bytes
|
||||
expanded_iv = :binary.copy(iv, 4)
|
||||
IO.puts("Expanded IV (16 bytes): #{Base.encode16(expanded_iv)}")
|
||||
|
||||
# The AES key (16 bytes)
|
||||
aes_key = <<
|
||||
0x13, 0x00, 0x00, 0x00,
|
||||
0x08, 0x00, 0x00, 0x00,
|
||||
0x06, 0x00, 0x00, 0x00,
|
||||
0xB4, 0x00, 0x00, 0x00
|
||||
>>
|
||||
|
||||
# Encrypt the expanded IV to get keystream
|
||||
keystream = :crypto.crypto_one_time(:aes_128_ecb, aes_key, expanded_iv, true)
|
||||
IO.puts("Keystream: #{Base.encode16(keystream)}")
|
||||
|
||||
# XOR data with keystream (only use as many bytes as data)
|
||||
keystream_bytes = :binary.bin_to_list(keystream) |> Enum.take(byte_size(data))
|
||||
data_bytes = :binary.bin_to_list(data)
|
||||
result_bytes = Enum.zip_with(data_bytes, keystream_bytes, fn x, y -> bxor(x, y) end)
|
||||
result = :binary.list_to_bin(result_bytes)
|
||||
|
||||
IO.puts("XOR result: #{Base.encode16(result)}")
|
||||
IO.puts("")
|
||||
|
||||
# Compare with AESCipher.crypt
|
||||
aes_result = AESCipher.crypt(data, iv)
|
||||
IO.puts("AESCipher.crypt result: #{Base.encode16(aes_result)}")
|
||||
IO.puts("Match: #{result == aes_result}")
|
||||
55
test/test_ivs.exs
Normal file
55
test/test_ivs.exs
Normal file
@@ -0,0 +1,55 @@
|
||||
# Test specific IV candidates
|
||||
|
||||
Code.require_file("lib/odinsea/util/bit_tools.ex", ".")
|
||||
Code.require_file("lib/odinsea/net/cipher/ig_cipher.ex", ".")
|
||||
Code.require_file("lib/odinsea/net/cipher/aes_cipher.ex", ".")
|
||||
Code.require_file("lib/odinsea/net/cipher/shanda_cipher.ex", ".")
|
||||
|
||||
alias Odinsea.Net.Cipher.{IGCipher, AESCipher, ShandaCipher}
|
||||
|
||||
import Bitwise
|
||||
|
||||
packet = <<0x7C, 0xA8, 0x7B, 0xA8, 0xBF, 0x0A, 0xCD, 0xDE, 0xC7, 0x71, 0xAC>>
|
||||
<<raw_seq::little-16, raw_len::little-16, payload::binary>> = packet
|
||||
|
||||
IO.puts("Testing IV candidates for packet: #{Base.encode16(packet)}")
|
||||
IO.puts("raw_seq: 0x#{Integer.to_string(raw_seq, 16)}, raw_len: 0x#{Integer.to_string(raw_len, 16)}")
|
||||
IO.puts("")
|
||||
|
||||
# IV candidates based on bytes [2]=0x0F, [3]=0x0C, [4]=0xA8, [5]=0xBC
|
||||
candidates = [
|
||||
<<0x0F, 0x0C, 0x0C, 0xA8>>, # [2][3][3][4] - overlapping
|
||||
<<0x0C, 0x0C, 0x0C, 0xA8>>, # [3][3][3][4]
|
||||
<<0x0C, 0xA8, 0x0C, 0xA8>>, # [3][4][3][4] - repeating pattern
|
||||
<<0x0F, 0x0C, 0xA8, 0xBC>>, # [2][3][4][5] - recv_iv from hello?
|
||||
<<0x34, 0x9A, 0x0C, 0xA8>>, # [0][1][3][4]
|
||||
<<0x9A, 0x0F, 0x0C, 0xA8>>, # [1][2][3][4]
|
||||
<<0x0C, 0xA8, 0xBC, 0x0D>>, # [3][4][5][6]
|
||||
<<0xA8, 0xBC, 0x0C, 0xA8>>, # [4][5][3][4]
|
||||
]
|
||||
|
||||
Enum.each(candidates, fn iv ->
|
||||
<<_r0, _r1, r2, r3>> = iv
|
||||
seq_base = ((r2 &&& 0xFF) ||| ((r3 <<< 8) &&& 0xFF00)) &&& 0xFFFF
|
||||
valid = bxor(raw_seq &&& 0xFFFF, seq_base) == 112
|
||||
|
||||
IO.puts("IV: #{Base.encode16(iv)}")
|
||||
IO.puts(" seq_base: 0x#{Integer.to_string(seq_base, 16)}, valid: #{valid}")
|
||||
|
||||
if valid do
|
||||
# Try full decryption
|
||||
aes_result = AESCipher.crypt(payload, iv)
|
||||
shanda_result = ShandaCipher.decrypt(aes_result)
|
||||
|
||||
if byte_size(shanda_result) >= 2 do
|
||||
<<opcode::little-16, _rest::binary>> = shanda_result
|
||||
IO.puts(" *** VALID IV! ***")
|
||||
IO.puts(" Decrypted opcode: 0x#{Integer.to_string(opcode, 16)} (#{opcode})")
|
||||
if opcode == 1 do
|
||||
IO.puts(" *** CORRECT OPCODE! ***")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
IO.puts("")
|
||||
end)
|
||||
54
test/test_shanda.exs
Normal file
54
test/test_shanda.exs
Normal file
@@ -0,0 +1,54 @@
|
||||
# Test Shanda cipher - encrypt then decrypt should give original
|
||||
|
||||
Code.require_file("lib/odinsea/util/bit_tools.ex", ".")
|
||||
Code.require_file("lib/odinsea/net/cipher/shanda_cipher.ex", ".")
|
||||
|
||||
alias Odinsea.Net.Cipher.ShandaCipher
|
||||
alias Odinsea.Util.BitTools
|
||||
|
||||
import Bitwise
|
||||
|
||||
# Test with simple data
|
||||
data = <<0x01, 0x00, 0x07, 0x70, 0x00, 0x04, 0x00>>
|
||||
IO.puts("Original data: #{Base.encode16(data)}")
|
||||
|
||||
# Encrypt
|
||||
encrypted = ShandaCipher.encrypt(data)
|
||||
IO.puts("Encrypted: #{Base.encode16(encrypted)}")
|
||||
|
||||
# Decrypt
|
||||
decrypted = ShandaCipher.decrypt(encrypted)
|
||||
IO.puts("Decrypted: #{Base.encode16(decrypted)}")
|
||||
|
||||
IO.puts("Match: #{data == decrypted}")
|
||||
IO.puts("")
|
||||
|
||||
# If they don't match, let's debug step by step
|
||||
if data != decrypted do
|
||||
IO.puts("MISMATCH! Let's debug...")
|
||||
IO.puts("")
|
||||
|
||||
# Manual encrypt - first pass only
|
||||
bytes = :binary.bin_to_list(data)
|
||||
data_len = length(bytes)
|
||||
data_length = data_len &&& 0xFF
|
||||
|
||||
IO.puts("Data length: #{data_len}")
|
||||
IO.puts("Initial bytes: #{inspect(bytes)}")
|
||||
IO.puts("")
|
||||
|
||||
# First pass (j=0, forward)
|
||||
IO.puts("=== Pass 0 (forward) ===")
|
||||
{result0, remember0} = Enum.reduce(Enum.with_index(bytes), {[], 0}, fn {cur, i}, {acc, remember} ->
|
||||
cur = BitTools.roll_left(cur, 3)
|
||||
cur = (cur + data_length) &&& 0xFF
|
||||
cur = bxor(cur, remember)
|
||||
new_remember = cur
|
||||
cur = BitTools.roll_right(cur, data_length &&& 0xFF)
|
||||
cur = bxor(cur, 0xFF)
|
||||
cur = (cur + 0x48) &&& 0xFF
|
||||
{[cur | acc], new_remember}
|
||||
end)
|
||||
result0 = Enum.reverse(result0)
|
||||
IO.puts("After pass 0: #{inspect(result0)}, remember: #{remember0}")
|
||||
end
|
||||
Reference in New Issue
Block a user