Dynamic Code Loading in Android

 


안드로이드에서의 동적 코드 로딩(Dynamic code loading)은 악성 행위를 숨기기 위해 악성 코드가 사용하는 방법이었다. 하지만, 최근에는 일반적인 앱에서도 많이 사용된다. 앱에 대한 리버싱 및 디버깅을 방해하는데 매우 효과적이기 때문이다.

 

이 글에서는 런타임 시 안드로이드 애플리케이션에 코드를 동적으로 로드하는 데 사용할 수 있는 3가지 방법을 살펴볼 것이다. 우선, 이 글은 Play Feature Delivery1PFD는 Google Play 기능의 하나로, 앱의 기능을 여러 개의 동적 모듈로 나누고, 선택적으로 다운로드하고 업데이트할 수 있다.에 대한 내용이 아니다. 우리가 살펴볼 방법들은 BaseDexClassLoader의 직접적인 하위 클래스들이며, 악성 코드는 종종 이러한 방법들을 이용하여 자신의 실체를 숨기기 위해 사용한다.

그렇다면, 왜 이러한 것을 만드는 방법을 설명하고 있는지 궁금할 수도 있다.

답은 간단하다. 만들어봐야 어떻게 동작하는지 완전히 이해할 수 있다. 그리고, 완전히 이해한 후에야 탐지하는 방법을 찾을 수 있다.

모든 세 가지 방법을 사용한 예시를 여기에서 확인할 수 있다.

 


1. SETUP

 

세 가지 다른 방법은 로드할 수 있는 파일 형식들이 서로 다르다. 따라서, 일관성을 유지하기 위해 dex 파일을 로드하는데 사용할 것이다. 모든 방법들이 dex 파일은 로드할 수 있기 때문이다.

비어있는 액티비티와 RandomNumber라는 새로운 클래스를 가진 매우 간단한 안드로이드 애플리케이션을 만들었다. 다음은 해당 클래스에 있는 코드이다.

보다시피, 위의 코드는 많은 일을 하지 않는다. 랜덤한 숫자를 반환하는 함수만을 가지고 있다.

APK를 빌드한 후, 해당 APK를 추출하여 루트 폴더에 있는 classes.dex파일을 가져온다. 이 파일에는 우리가 작성한 클래스가 포함되어 있다. JEB 등의 도구로 해당 dex파일이 예상한 코드를 가지고 있는지 확인할 수 있다. 이제 목표는 이 dex파일을 새로운 앱에 애셋(asset)으로 추가하고, 런타임 시 동적으로 로드하여 getRandomNumber()함수를 사용하는 것이다.

 


2. DexClassLoader

 

DexClassLoaderapk 또는 jar파일에서도 dex파일을 로드할 수 있다. DexClassLoader의 생성자는 총 네 개의 매개변수를 요구하지만 그 중 두 개만 중요하다. 바로 dex파일의 경로와 부모 클래스로더이다. 따라서 작업은 간단하다. 먼저 애셋 폴더에서 dex파일을 가져와 내부 저장소에 넣은 다음 DexClassLoader의 생성자에 해당 경로를 지정하면 된다. 다음 코드를 통해 이 내용을 확인할 수 있다.

처음에는 createTempFile을 사용하여 임시 파일을 생성하고, 이 파일은 3번째 줄에서 로드되는 dex파일의 내용을 가진다. 마지막으로 9번째 줄에서 DexClassLoader를 호출하면서, 바로 위에서 생성한 임시 파일의 절대 경로와 부모 클래스로더를 제공한다.

다음 단계는 적절한 파일 이름으로 위의 함수를 호출한 다음 getRandomNumber함수를 호출하는 것입니다. 다음 코드는 이를 수행하는 방법을 보여준다.

 


3. PathClassLoader

 

PathClassLoader는 클래스로더를 매우 간단하게 구현한 것이다. PathClassLoader는 로컬 파일 시스템의 파일과 디렉토리 목록을 기반으로 동작합니다. 문서에 명시된 대로, PathClassLoader는 네트워크에서 클래스를 로드하지 않는다. DexClassLoader와 비교하여 구현 부분에는 차이가 적다. 이를 자세히 살펴보자.

중요한 차이점은 9번째 줄에서 PathClassLoader의 생성자가 두 개의 인수를 받는다는 것이다. 바로 dex 파일의 경로와 부모 클래스로더이다. 함수를 호출하고 메서드를 호출하는 부분은 이전에 살펴본 DexClassLoader와 동일하다.

 


4. InMemoryDexClassLoader

 

InMemoryDexClassLoader는 이름에서 알 수 있듯이 dex파일이 포함된 버퍼에서 클래스를 로드할 수 있는 클래스로더이다. 여기서 주목해야 할 중요한 점은 dex파일이 로컬 파일 시스템에 기록되지 않고 버퍼에만 있음을 의미한다. 이는 인터넷에서 dex파일을 다운로드하여 직접 로드해야 할 경우에 유용한 시나리오일 수 있다. 그러나 InMemoryDexClassLoader를 사용하여 구현한 코드 중에서 “로컬 파일 시스템에 접근하지 말라”는 원칙을 따르는 것을 찾지 못했다. 대신 파일을 로컬 파일 시스템에 다운로드한 다음 InMemoryDexClassLoader를 사용하여 로드하는 방식으로 구현되는 것을 찾을 수 있었다. 이는 사실상 처음부터 InMemoryDexClassLoader를 사용하는 의도를 무색하게 하는 것이므로 의미가 없다. 다음 예제는 dex파일을 다운로드하고 버퍼에 저장하여 클래스로더에 직접 공급하는 방법을 보여준다.

다음 코드는 이를 앱 내에서 사용하는 예제를 보여준다.

 


5. 예제

 

다음 애플리케이션은 세 가지 메서드가 사용된 예제이다. 세 개의 다른 dex파일이 생성되는데, 그들 사이의 유일한 차이점은 포함된 클래스의 이름(RandomNumber, RandomNumber2, RandomNumber3)이다. 각 클래스에 포함된 함수는 완전히 동일하다. 각 클래스의 이름이 다른 것은 올바른 dex파일이 각각 로드되었음을 증명한다. 이 애플리케이션은 Github에서 확인할 수 있으며, 위에서 설명한 단계를 더 자세히 확인할 수 있다.

InMemoryDexClassLoader를 사용하는 경우에는 버튼을 클릭할 때 해당 dex파일을 제공해야만 로드할 수 있다는 내용의 토스트 메시지가 나타나는 것을 주목하자. dex파일을 가져와 로드하기 위해 다운로드 버튼을 누른 후에야 버튼이 작동한다.