안드로이드 유니티 게임 엔진 MONO 해킹
유니티로 제작된 안드로이드 게임은 원하는 컴파일 방식(Mono, IL2CPP)을 선택하여 빌드를 할 수 있습니다.
가장 많이 사용하는 Mono의 경우 JIT 컴파일을 사용하여 IL(중간언어)로 작성된 dll 파일을 읽어들여 런타임으로 코드실행을 하게됩니다. 자바의 JVM과 유사하다고 보시면 됩니다.
또한 빠른 빌드속도와 닷넷 프레임워크를 기반하여 여러 플랫폼(리눅스, 윈도우, 맥 등)에서 구동이 가능하다는 점등의 여러 큰 장점들이 있지만, 가장 큰 단점은 보안에 매우 취약하다는 점입니다.
코드 난독화 및 보호솔루션이 없는 일반 mono기반의 게임의 경우 마치 오픈된 소스코드처럼, 게임 내 모든 스크립트를 담고 있는 DLL 파일을 통해 아주 쉽게 디컴파일이 가능합니다. 분석하기에 앞서 가장 많이 사용하는 툴은 다음과 같습니다.
- https://github.com/icsharpcode/ILSpy
- https://github.com/0xd4d/dnSpy
필자는 코드 삽입 및 디버깅등의 여러가지 장점이 있는 dnSpy를 자주 사용하기에 해당 툴을 사용하는 방법을 간단히 알려드리려고 합니다.
유니티 게임을 변조하기에 앞서 테스트용 게임앱(apk)에서 "assets\bin\Data\Managed\Assembly-CSharp.dll" 파일을 우선 추출합니다.
dnspy
dnSpy를 실행하고 추출한Assembly-CSharp.dll을 드래그하여 오픈하면 다음 그림과 같이 실제 게임내에서 사용하는 스크립트의 디컴파일된 모습을 볼 수 있습니다.
보시다시피 원본 소스코드와 거의 유사한 게임 내 스크립트를 얻을 수 있습니다.
Edit IL Code
코드를 변조하는 방법은 해당 메소드를 오른쪽 마우스로 클릭하고 EditMethod(C#))기능을 통해 아주 쉽게 코드변조가 가능합니다.
그림과 같이 로그 출력 코드를 삽입하거나 특정 코드내 인자값 조작이 가능합니다. 간혹 컴파일에 실패하는 경우가 있는데 이때에는 Edit IL Instructions 기능을 통해서도 코드 변조가 가능하기 때문에 IL 인스트럭션을 변조해주는 방법을 사용하기도 합니다.
코드를 모두 수정하였다면 File -> Save Module을 클릭하여 저장하고, 수정된Assembly-CSharp.dll 원본 APK 파일 내 dll 파일과 교체한 뒤 리패키징 작업을 하면 코드가 변조된 APK를 얻을 수 있습니다.
간단하고 쉽게 리패키징하는 방법은 apktool 툴을 사용하거나, apk파일을 직접 열어 dll 교체 후 서명만 다시 해주면 됩니다.
만약, Assembly-CSharp.dll을 열었을때 DnSpy가 인식을 못할경우 PE구조 또는 CLR 메타데이터 헤더를 깨놨거나 파일 암호화가 되있을 확률이 큽니다.
파일을 헥스에디터로 열었을때 기존 PE구조와 비슷하다면 정상적인 dll파일과 비교하여 헤더를 복구하거나 PE구조 분석, 메타데이터 헤더 분석하여 복구하는 방법이 있습니다.
파일암호화의 경우 게임이 정상실행된다음 메모리 덤프를 통해 실제 사용되는 원본 dll 파일을 얻는 방법이 있으며, libmono.so 파일 내 mono_image_open_from_data_with_name심볼을 찾아가면 실제 원본 코드와 달리 복호화하는 코드가 포함된 변조된 함수일 수도 있기때문에 직접 libmono.so를 분석해보는것도 하나의 방법입니다.
실제로 blackmod 사이트에서 배포되는 모드앱을 분석해보면 복호화 코드가 포함된 변조 libmono.so로 교체하여 배포하는것을 확인할 수 있습니다.
이외에도 GOT Overwrite, 인라인후킹등의 방법을 사용하여 mono의 가상머신을 조작하는 경우도 존재하며,
덤프 및 원본 Assembly-CSharp.dll 얻었는데도 불구하고 특정 코드부분이 비어있거나 깨져있는 경우에는 가상화 코드 또는 메소드 교체등의 방법을 사용하고 있는 경우일 확률이 크며, 직접 동적분석을 통해 libmono.so 동작로직을 따라가 보는것을 추천합니다.