Skip to content
This repository has been archived by the owner on Apr 25, 2022. It is now read-only.

Commit

Permalink
Added Fate/Hollow Ataraxia decryption (closes #1)
Browse files Browse the repository at this point in the history
  • Loading branch information
Marcin Kurczewski committed Dec 6, 2014
1 parent 6d853bd commit 2027f16
Show file tree
Hide file tree
Showing 5 changed files with 583 additions and 7 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Currently supported decryption routines:

- Noop (no encryption)
- Fate/Stay Night
- CXDEC-based:
- Fate/Hollow Ataraxia

Usage:

Expand Down
62 changes: 62 additions & 0 deletions lib/decryption/cxdec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
require_relative 'cxdec_key_deriver'

# Decryption routine for CXDEC-protected games
class CxdecDecryptor
def initialize(plugin)
@plugin = plugin
@key_deriver = CxdecKeyDeriver.new(plugin)
end

def filter(data, file_entry)
bytes = data.unpack('C*')

begin
hash = file_entry.adlr_chunk.encryption_key[0]
key = (hash & @plugin.key[0]) + @plugin.key[1]

if bytes.length > key
dec_len = key
else
dec_len = data.length
end

decrypt_chunk(hash, 0, bytes, dec_len)
offset = dec_len
dec_len = data.length - offset

decrypt_chunk((hash >> 16) ^ hash, offset, bytes, dec_len) if dec_len > 0
rescue StandardError => e
puts e.message
puts e.backtrace
end

bytes.pack('C*')
end

private

def decrypt_chunk(hash, base_offset, bytes, length)
seed = hash & 0x7f
hash >>= 7
ret0 = @key_deriver.derive(seed, hash)
ret1 = @key_deriver.derive(seed, hash ^ 0xffff_ffff)

xor0 = (ret0 >> 8) & 0xff
xor1 = (ret0 >> 16) & 0xff
xor2 = ret0 & 0xff
xor2 = 1 if xor2 == 0

offset0 = ret1 >> 16
offset1 = ret1 & 0xffff

if offset0.between?(base_offset, base_offset + length)
bytes[offset0] ^= xor0
end

if offset1.between?(base_offset, base_offset + length)
bytes[offset1] ^= xor1
end

(base_offset..(base_offset + length - 1)).each { |i| bytes[i] ^= xor2 }
end
end
232 changes: 232 additions & 0 deletions lib/decryption/cxdec_key_deriver.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
# A class used to derive the nontrivial key in CXDEC based encryption.
class CxdecKeyDeriver
def initialize(plugin)
@plugin = plugin
@pos = 0
@seed = 0
@parameter = 0
end

def derive(seed, parameter)
@seed = seed
@parameter = parameter

# What we do: we try to run a code a few times for different "stages".
# The first one to succeed yields the key.

# This mechanism of figuring out the valid stage number is really crappy,
# but it's important we do it this way. This is because we initialize the
# seed only once, and even if we fail to get a number from the given stage,
# the internal state of randomizer is preserved to the next iteration.

# Maintaining the randomizer state is essential for the decryption to work.

5.downto(1).each do |stage|
begin
return derive_for_stage(stage) & 0xffff_ffff
rescue MemoryException
next
end
end

fail 'Fatal: failed to derive the key from the parameter!'
end

private

class MemoryException < StandardError
end

# The execution for current stage must fail when we run code for too long.
# CRASS tracked executed opcode count for this purpose, and we use this
# approach here as well.
def advance(count = 1)
@pos += count
fail MemoryException if @pos > 128
end

def reset
@pos = 0
end

# This is a modified glibc LCG randomization routine. It is used to make the
# key as random as possible for each file, which is supposed to maximize
# confusion.
def rand
old_seed = @seed
@seed = ((1_103_515_245 * old_seed) + 12_345) & 0xffff_ffff
(@seed ^ (old_seed << 16) ^ (old_seed >> 16)) & 0xffff_ffff
end

def derive_for_stage(stage)
reset

advance(5) # push edi, push esi, push ebx, push ecx, push edx
advance(4) # mov edi, dword ptr ss:[esp+18] (esp+18 == @parameter)

eax = run_stage_strategy_1(stage)

advance(5) # pop edx, pop ecx, pop ebx, pop esi, pop edi
advance # retn

eax
end

def run_first_stage
routine_number = @plugin.key_derivation_order1[rand % 3]

if routine_number == 0
advance(1) # mov eax, ...
eax = rand
advance(4) # ...rand()
elsif routine_number == 1
advance(2) # mov eax, edi
eax = @parameter # edi = stage
elsif routine_number == 2
advance(1) # mov esi, ...
advance(4) # ...&encryption_block
advance(2) # mov eax, ...
pos = (rand & 0x3ff) * 4
advance(4) # ...dword ptr ds:[esi+((rand & 0x3ff)*4)]
eax = @plugin.encryption_block[pos..(pos + 3)].unpack('L')[0]
end

eax & 0xffff_ffff
end

def run_stage_strategy_0(stage)
return run_first_stage if stage == 1

if rand & 1 == 1
eax = run_stage_strategy_1(stage - 1)
else
eax = run_stage_strategy_0(stage - 1)
end

routine_number = @plugin.key_derivation_order2[rand % 8]

if routine_number == 0
advance(2) # not eax
eax ^= 0xffff_ffff

elsif routine_number == 1
advance # dec eax
eax -= 1

elsif routine_number == 2
advance(2) # neg eax
eax = -eax

elsif routine_number == 3
advance # inc eax
eax += 1

elsif routine_number == 4
advance(1) # mov esi, ...
advance(4) # ...&encryption_block
advance(5) # and eax, 3ff
advance(3) # mov eax, dword ptr ds:[esi+eax*4]
pos = (eax & 0x3ff) * 4
eax = @plugin.encryption_block[pos..(pos + 3)].unpack('L')[0]

elsif routine_number == 5
advance(1) # push ebx
advance(2) # mov ebx, eax
advance(6) # and ebx, aaaaaaaa
advance(5) # and eax, 55555555
advance(2) # shr ebx, 1
advance(2) # shl eax, 1
advance(2) # or eax, ebx
advance(1) # pop ebx

ebx = eax
ebx &= 0xaaaa_aaaa
eax &= 0x5555_5555
ebx >>= 1
eax <<= 1
eax |= ebx

elsif routine_number == 6
advance(1) # xor eax, ...
eax ^= rand
advance(4) # ...rand()

elsif routine_number == 7
if rand & 1 == 1
advance(1) # add eax, ...
eax += rand
advance(4) # ...rand()
else
advance(1) # sub eax, ...
eax -= rand
advance(4) # ...rand()
end
end

eax & 0xffff_ffff
end

def run_stage_strategy_1(stage)
return run_first_stage if stage == 1

advance(1) # push ebx

if rand & 1 == 1
eax = run_stage_strategy_1(stage - 1)
else
eax = run_stage_strategy_0(stage - 1)
end

advance(2) # mov ebx, eax
ebx = eax

if rand & 1 == 1
eax = run_stage_strategy_1(stage - 1)
else
eax = run_stage_strategy_0(stage - 1)
end

routine_number = @plugin.key_derivation_order3[rand % 6]
if routine_number == 0
advance # push ecx
advance(2) # mov ecx, ebx
advance(3) # and ecx, 0f
advance(2) # shr eax, cl
advance # pop ecx
ecx = ebx & 0x0f
eax >>= (ecx & 0xff)

elsif routine_number == 1
advance # push ecx
advance(2) # mov ecx, ebx
advance(3) # and ecx, 0f
advance(2) # shl eax, cl
advance # pop ecx
ecx = ebx & 0x0f
eax <<= (ecx & 0xff)

elsif routine_number == 2
advance(2) # add eax, ebx
eax += ebx

elsif routine_number == 3
advance(2) # neg eax
advance(2) # add eax, ebx
eax = ebx - eax

elsif routine_number == 4
advance(3) # imul eax, ebx
eax *= ebx

elsif routine_number == 5
advance(2) # sub eax, ebx
eax -= ebx

end

# pop ebx
advance

eax & 0xffff_ffff
end
end
Loading

0 comments on commit 2027f16

Please sign in to comment.