This repository has been archived by the owner on Apr 25, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 83
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added Fate/Hollow Ataraxia decryption (closes #1)
- Loading branch information
Marcin Kurczewski
committed
Dec 6, 2014
1 parent
6d853bd
commit 2027f16
Showing
5 changed files
with
583 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.