Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Shiina Rio .exe decoder
#!/bin/python3
import struct
import argparse
# tested on:
# - Sorcery Jokers (works)
# - Tekoire Princess (needs manual tweaks to work)
# so it's only semi-automatic.
def read_u16(data, offset):
return struct.unpack('<H', data[offset:offset + 2])[0]
def read_s32(data, offset):
return struct.unpack('<l', data[offset:offset + 4])[0]
def read_u32(data, offset):
return struct.unpack('<L', data[offset:offset + 4])[0]
def write_u16(data, offset, what):
data[offset:offset + 2] = struct.pack('<H', what)
def write_u32(data, offset, what):
data[offset:offset + 4] = struct.pack('<L', what)
def decrypt(data, key, section):
source_offset = section['rva']
for i in range(min(section['virt-size'], section['size'])):
key = (key * 5 + 0x3711) & 0xFFFFFFFF
data[section['rva'] + i] ^= key & 0xFF
return key
def decode(input_path, output_path):
with open(input_path, 'rb') as ifh:
data = bytearray(ifh.read())
file_header_size = 0x14
optional_header_size = 0x60
section_size = 0x28
lfa_new = struct.unpack('<L', data[0x3C:0x40])[0]
file_header_offset = lfa_new + 4
optional_header_offset = lfa_new + 4 + file_header_size
number_of_sections = read_u16(data, file_header_offset + 2)
image_base = read_u32(data, optional_header_offset + 28)
number_of_rva_and_sizes = read_u32(data, optional_header_offset + 0x5C)
rva_and_sizes_offset = optional_header_offset + optional_header_size
sections_offset = rva_and_sizes_offset + number_of_rva_and_sizes * 8
sections = {}
for i in range(number_of_sections):
section_offset = sections_offset + section_size * i
name = (data[section_offset:section_offset+8]
.decode('ascii').rstrip('\x00'))
section = \
{
'name': name,
'offset': image_base + read_u32(data, section_offset + 12),
'rva': read_u32(data, section_offset + 20),
'virt-size': read_u32(data, section_offset + 8),
'size': read_u32(data, section_offset + 16),
}
sections[name] = section
print('%+8s rva:%8x offset:%8x size:%8x(real:%8x)' % (
section['name'],
section['rva'],
section['offset'],
section['virt-size'],
section['size']))
i = 0
key = None
while i < sections['.riox']['size'] - 4:
if read_u32(data, sections['.riox']['rva'] + i) == sections['.text']['virt-size']:
key = read_u32(data, sections['.riox']['rva'] + i + 5)
break
i += 1
program_start_proc = None
while i < sections['.riox']['size'] - 4:
if read_u32(data, sections['.riox']['rva'] + i) == 0xDDB8BCD6:
program_start_proc = sections['.riox']['offset'] + i + 8 + 4 + read_s32(data, sections['.riox']['rva'] + i + 8)
break
i += 1
if not key:
raise RuntimeError('Failed to guess the key, proceed manually')
print('Key: %08x' % key)
print('OEP: %08x' % program_start_proc)
key = decrypt(data, key, sections['.text'])
decrypt(data, key, sections['.data'])
decrypt(data, key, sections['.riox'])
decryptor_rva = sections['.riox']['rva']
data[decryptor_rva] = 0x68 # x86 "push"
write_u32(data, decryptor_rva + 1, program_start_proc)
data[decryptor_rva + 5] = 0xC3 # x86 "ret"
with open(output_path, 'wb') as ofh:
ofh.write(data)
def main():
parser = argparse.ArgumentParser(
description='Decode ShiinaRio encoded executables')
parser.add_argument('INPUT_FILE', help='Path to the input .exe file')
parser.add_argument('OUTPUT_FILE', help='Path where to save output .exe')
args = parser.parse_args()
decode(args.INPUT_FILE, args.OUTPUT_FILE)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment