Este documento técnico aborda o processo de engenharia reversa aplicado ao jogo Soul Knight, com foco em contornar mecanismos de proteção para realizar análise de memória e descriptografia de arquivos de salvamento. O objetivo é descrever as etapas para extrair e analisar dados do jogo, incluindo a utilização de ferramentas especializadas e técnicas de criptografia.
Reparo e Análise do Arquivo Binário
Para iniciar, é necessário reparar o arquivo SO do jogo utilizando a ferramenta SoFixer. Em seguida, o arquivo reparado é carregado no IDA Pro, onde scripts como il2cppdumper são empregados para mapear símbolos e estruturas de dados. Devido ao tamanho do binário do jogo, a análise pode ser intensiva em tempo e recursos.
Identificação do Arquivo de Salvamento
O arquivo principal de salvamento, denominado game.data, é localizado dentro dos recursos do jogo. No IDA Pro, a navegação para endereços específicos e a análise de referências cruzadas permitem identificar funções responsáveis pela leitura e escrita desses dados, como a rotina LoadGameData.
Algoritmo de Criptografia
A função de criptografia emprega uma combinação de operações XOR com uma chave secreta e um fator calculado com base no índice do byte. A implementação a seguir ilustra uma versão reescrita do algoritmo em Python, mantendo a lógica original mas com estrutura modificada:
def decifrar_xor(dados_cifrados: bytes, chave: str) -> bytes:
buffer_saida = bytearray(dados_cifrados)
tamanho_chave = len(chave)
for idx in range(len(buffer_saida)):
byte_chave = ord(chave[idx % tamanho_chave])
fator_indice = ((idx % 15) * (idx % 5)) % 92
buffer_saida[idx] = buffer_saida[idx] ^ byte_chave ^ fator_indice
return bytes(buffer_saida)
Técnica de Força Bruta para Descriptografia
Considerando que o arquivo descriptografado segue o formato JSON, é possível aplicar força bruta para determinar a chave. A abordagem baseia-se na detecção de padrões típicos de JSON, como sequências de caracteres comuns, para avaliar a probabilidade de uma chave correta. O script abaixo realiza essa análise:
import json
from collections import defaultdict
def encontrar_chave_bruta(dados_cifrados: bytes, limite_tamanho: int = 50):
padroes_json = [b'{"', b'":', b',"', b'"}', b':[', b']}']
candidatos = {}
for padrao in padroes_json:
lista_padrao = list(padrao)
for tam_chave in range(1, limite_tamanho + 1):
votos_por_posicao = [defaultdict(int) for _ in range(tam_chave)]
for pos_inicial in range(len(dados_cifrados) - len(lista_padrao) + 1):
chave_parcial = {}
invalido = False
for i, byte_esperado in enumerate(lista_padrao):
pos_absoluta = pos_inicial + i
fator = ((pos_absoluta % 15) * (pos_absoluta % 5)) % 92
byte_decifrado = dados_cifrados[pos_absoluta] ^ fator ^ byte_esperado
if not (32 <= byte_decifrado < 127):
invalido = True
break
pos_chave = pos_absoluta % tam_chave
if pos_chave in chave_parcial:
if chave_parcial[pos_chave] != byte_decifrado:
invalido = True
break
else:
chave_parcial[pos_chave] = byte_decifrado
if not invalido:
for pos_chave, valor in chave_parcial.items():
votos_por_posicao[pos_chave][valor] += 1
chave_candidata = bytearray(tam_chave)
votacao_minima = float('inf')
for i in range(tam_chave):
if not votos_por_posicao[i]:
chave_candidata = None
break
byte_mais_votado = max(votos_por_posicao[i].items(), key=lambda x: x[1])
chave_candidata[i] = byte_mais_votado[0]
votacao_minima = min(votacao_minima, byte_mais_votado[1])
if chave_candidata and votacao_minima >= 2:
try:
chave_texto = chave_candidata.decode('utf-8')
if chave_texto not in candidatos or votacao_minima > candidatos[chave_texto][1]:
candidatos[chave_texto] = (padrao.decode('utf-8'), votacao_minima)
except:
pass
return sorted(candidatos.items(), key=lambda x: -x[1][1])
# Processo principal de descriptografia
if __name__ == "__main__":
with open("game.data", "rb") as arquivo:
dados_cifrados = arquivo.read()
print(f"Tamanho dos dados cifrados: {len(dados_cifrados)} bytes")
resultados = encontrar_chave_bruta(dados_cifrados, limite_tamanho=50)
print(f"Candidatos de chave encontrados: {len(resultados)}")
for chave, (padrao, votos) in resultados[:20]:
print(f"Chave: '{chave}' (tamanho={len(chave)}, votos={votos}, padrão='{padrao}')")
try:
dados_decifrados = decifrar_xor(dados_cifrados, chave)
texto_saida = dados_decifrados.decode('utf-8', errors='replace')
json.loads(texto_saida)
print("✓✓ Sucesso: JSON válido identificado.")
with open("resultado_decifrado.txt", "wb") as saida:
saida.write(dados_decifrados)
print("Arquivo salvo como resultado_decifrado.txt")
break
except json.JSONDecodeError:
print("✗ Formato JSON inválido.")
except Exception as erro:
print(f"✗ Erro durante o processamento: {erro}")
Ao executar o script, a chave correta é determinada e o arquivo de salvamento é descriptografado, permitindo a inspeção de dados internos do jogo, como configurações de mascotes e habilidades.