일단 bundle파일이 암호화가 되어있기 때문에, 그냥 해당 bundle을 가지고 그대로 압축해제를 하려고 하면 문제가 발생할거임.
무작위로 파일 하나를 예로 들자면
이런 식으로 파일이 되어있음.
일반적으로 유니티 번들 파일은 utf-8 인코딩으로 헤더 부분에 UnityFS와 해당 asset의 버전 정보로 시작하는데, 이 게임의 asset 파일은 암호화를 해놔서 UnityFS라는 글자가 보이지 않음. 대신 '0x65'라는 문자열이 엄청 나오는 것을 확인할 수 있는데, 이게 이 게임 암호화의 힌트임.
이 게임은 XOR 암호화를 사용해서, 원본 데이터에 어떤 키 값을 가지고 바이트 단위 XOR로 파일을 암호화함.
그래서 이 키 값만 알 수 있다면 다시 복호화할 수 있음.
이 게임은 XOR 암호화를 할 때, asset bundle의 파일명을 기반으로 어떤 해쉬값을 계산해서 "unity_asset_bundle_android"의 문자열 중 한 문자값에 매칭을 시킨 다음, 해당 문자값의 ASCII 코드 값에 해당하는 값을 XOR 암호키로 사용함.
제일 좋은건 이 해쉬 알고리즘을 파악하는거겠지만, 굳이 그렇게 하지 않더라도 암호화/복호화가 가능함.(해쉬 알고리즘은 dll 파일을 리버스 엔지니어링 해야하기 때문에 귀찮음)
"u, n, i, t, y, _, a, s, e, b, d, l, r, o" 이 암호키를 기반으로 XOR 복호화를 이것저것 브루트포스로 실행한 다음에, 헤더에서 UnityFS라는 문자열로 복호화를 하는 암호키를 사용하면 되는거임. (아니면 저기 0x65가 반복해서 등장하는 것을 바탕으로 0x65로 암호화되었다는 것을 알 수도 있음)
올바르게 복호화 된 bundle 파일의 예시
아무튼 그래서 asset을 수정하려면
asset 파일을 XOR 복호화
복호화된 파일은 이제 정규 unity bundle 파일이기 때문에 AssetStudio 및 UABEA를 사용할 수 있음.
UABEA를 통해서 이미지 texture 교체 후 저장, 그리고 LZ4로 bundle 파일 압축하여 다시 저장.
수정된 unity bundle 파일을 다시 XOR로 암호화
라는 과정을 거치면 됨.
import osimport jsonimport shutil # =========================================================# [설정] 작업할 폴더 경로를 여기에 입력하세요.# 예: r"C:\Users\Game\Bundles"TARGET_FOLDER = r"D:\Downloads\RJ01103194_MassageShop_ver_1.9.7_DLC_UNCENSORED_7차수정판\MassageShop_Data\StreamingAssets\Workspace" # 키를 저장할 데이터베이스 파일명KEY_DB_FILE = "key_database.json"# ========================================================= UNITY_MAGIC = 0x55 # 'U' def load_key_db(): """저장된 키 데이터베이스를 불러옵니다.""" db_path = os.path.join(TARGET_FOLDER, KEY_DB_FILE) if os.path.exists(db_path): with open(db_path, 'r', encoding='utf-8') as f: return json.load(f) return {} def save_key_db(db_data): """키 데이터베이스를 JSON 파일로 저장합니다.""" db_path = os.path.join(TARGET_FOLDER, KEY_DB_FILE) with open(db_path, 'w', encoding='utf-8') as f: json.dump(db_data, f, indent=4) print(f"💾 키 데이터베이스가 저장되었습니다: {db_path}") def backup_file(file_path, backup_dir): """파일을 백업 폴더로 복사합니다.""" if not os.path.exists(backup_dir): os.makedirs(backup_dir) filename = os.path.basename(file_path) shutil.copy2(file_path, os.path.join(backup_dir, filename)) def process_decrypt(): """복호화 모드: 키를 추출/저장하고 복호화 수행""" print(f"\n🔓 [복호화 모드] 시작: {TARGET_FOLDER}") key_db = load_key_db() backup_dir = os.path.join(TARGET_FOLDER, "backup_original") files = [f for f in os.listdir(TARGET_FOLDER) if os.path.isfile(os.path.join(TARGET_FOLDER, f)) and not f.endswith('.json')] count = 0 for filename in files: filepath = os.path.join(TARGET_FOLDER, filename) with open(filepath, 'rb') as f: data = bytearray(f.read()) # 이미 복호화된 파일인지 확인 (UnityFS로 시작하면 건너뜀) if data[:7] == b'UnityFS': print(f"⚠️ [Skip] 이미 복호화됨: {filename}") continue # 키 추출 로직: 첫 바이트 ^ 0x55 detected_key = data[0] ^ UNITY_MAGIC # 키 기록 key_db[filename] = detected_key # 백업 수행 backup_file(filepath, backup_dir) # 복호화 (전체 XOR) decrypted_data = bytearray([b ^ detected_key for b in data]) # 검증 if decrypted_data[:7] == b'UnityFS': with open(filepath, 'wb') as f: f.write(decrypted_data) print(f"✅ [Success] 복호화 & 키 저장 (0x{detected_key:02X}): {filename}") count += 1 else: print(f"❌ [Fail] 복호화 실패 (헤더 불일치): {filename}") # 변경된 키 DB 저장 save_key_db(key_db) print(f"\n✨ 총 {count}개 파일 복호화 완료. 원본은 'backup_original' 폴더에 있습니다.") def process_encrypt(): """암호화 모드: 저장된 키를 불러와서 암호화 수행""" print(f"\n🔒 [암호화 모드] 시작: {TARGET_FOLDER}") key_db = load_key_db() if not key_db: print("❌ 오류: 'key_database.json' 파일이 없습니다. 먼저 복호화를 진행하여 키를 생성하세요.") return files = [f for f in os.listdir(TARGET_FOLDER) if os.path.isfile(os.path.join(TARGET_FOLDER, f)) and not f.endswith('.json')] count = 0 for filename in files: filepath = os.path.join(TARGET_FOLDER, filename) # DB에서 키 조회 if filename not in key_db: print(f"⚠️ [Skip] 키 정보 없음 (DB에 없음): {filename}") continue xor_key = key_db[filename] with open(filepath, 'rb') as f: data = bytearray(f.read()) # 안전장치: 이미 암호화된 파일인지 확인 (UnityFS가 아니면 암호화된 것으로 간주하고 경고) if data[:7] != b'UnityFS': print(f"⚠️ [Skip] 이미 암호화된 것 같음 (UnityFS 헤더 없음): {filename}") continue # 암호화 (전체 XOR) encrypted_data = bytearray([b ^ xor_key for b in data]) # 덮어쓰기 with open(filepath, 'wb') as f: f.write(encrypted_data) print(f"✅ [Success] 암호화 완료 (Key: 0x{xor_key:02X}): {filename}") count += 1 print(f"\n✨ 총 {count}개 파일 암호화(리팩) 완료.") def main(): print("=== Unity Bundle XOR Tool ===") print(f"작업 폴더: {TARGET_FOLDER}") print("1. 복호화 (Decrypt) -> 키 추출 및 저장") print("2. 암호화 (Encrypt) -> 저장된 키 사용") choice = input("\n작업을 선택하세요 (1 또는 2): ").strip() if choice == '1': process_decrypt() elif choice == '2': process_encrypt() else: print("잘못된 선택입니다.") if __name__ == "__main__": if os.path.exists(TARGET_FOLDER): main() else: print(f"❌ 오류: 폴더를 찾을 수 없습니다. 경로를 확인하세요: {TARGET_FOLDER}")
|
위의 파이썬 코드를 사용하면 복호화(복호화 할 때 json 파일로 복호화 키를 저장함. 나중에 다시 암호화 할 때 복호화 키를 다시 사용해야함) 및 암호화를 수행할 수 있음. (그냥 넣으려니까 사이트가 이상해서 코드 자꾸 이상해지는데 아 몰라 대충 복사해서 알아서 수정해 쓰세요...)