동적으로 로드되는 실행 파일에 대한 고찰 - 1
Malware Analysis - dex fileIntroduction
최근 여러 개의 Malware 를 포함해서 software-protection 이 적용된 앱들을 분석할 기회가 생겼다. Malware 라고 단정 지을 수 없지만 안티 리버싱이 적용된 앱들이 많아 평소보다 분석이 쉽지 않았던 케이스가 많았다. 이러한 목적은 소스코드 방어 목적도 있지만 어떤 의도를 가진 소프트웨어인지 비공개하기 위한 여러가지 제한을 하고자 적용한 앱들도 있다. 이러한 앱들을 분석해보면 메인 코드가 대부분 비어있었다. 그러나 앱을 실행하면 정상적으로 구동되는 앱들이었다.
디바이스에 존재하지 않는 코드가 어떻게 실행되는지 살펴본 결과 기상천외한 방법들이 있었고 그중 일반적인 것이 파일을 다운받는 형태를 볼 수 있었다. 과정을 복잡하게 해놓는 게 대부분이긴 하지만 네트워크 통신할 때 패킷을 살펴보면 실행파일을 가쟈오는 걸 알 수 있다.
해당 글에서는 동적으로 생기는 파일을 확인하고 메모리에 올라가는 실행 파일을 확인하는 것까지 진행해보겠다.
What is that
바이너리에 존재하지 않는 코드가 어떻게 실행 시 문제 없이 진행되는 걸 보면 Android 에서 동적으로 로드할 수 있는 몇가지 API 가 있다.
classDiagram
ClassLoader <|-- BaseDexClassLoader
BaseDexClassLoader <|-- DexClassLoader
BaseDexClassLoader <|-- PathClassLoader
class ClassLoader{
}
class DexClassLoader{
-load()
}
class PathClassLoader{
+load()
}
위와 같이 동적으로 로드할 수 있는 클래스가 있는데 이중에서 DexClassLoader
를 중점으로 바라보고자 한다. 또한 이중 빠진 API 가 있는데 메모리에서 로드하는 InMemoryDexClassLoader
API 가 누락되었다.
How We Got Here
assets/
에 저장되어있는 파일을 불러와 안드로이드 내부에 저장하는 걸 볼 수 있는데 이때 File
API 를 확인하면 실행 시 동적으로 내부에 파일을 저장하는 걸 볼 수 있다.
안드로이드 내부 디렉터리에 관한 내용은 다음과 같은 글에 따로 정리하였다.
1
2
3
4
5
[*] b'Hidden File opened for write /data/user/0/ww.rksr.jjiuuuu.lsls/nnn.dex'
[*] b'Got 503724 bytes!'
[*] b'Waiting..'
[*] b'Write..'
[*] Successfully dumped to nnn.dex
위와 같이 실행 시 파일을 생성한 후 assets 에 있는 내용을 로컬에 저장하는 걸 확인할 수 있다.
dex 를 로드하는 방법에는 몇년 전만해도 한 가지 방법 밖에 없었는데 최근 들어 디바이스 내에 그 어떤 파일을 만들지 않고 메모리 영역에 올려 코드를 로드하는 방법이 생겼다.
그래서 총 2가지 방법이 있는데 여기서 2가지 방법 다 다뤄볼 생각이다.
A Critical Breakthrough
File 로 로드하는 방법
assets 에 있는 파일을 안드로이드 내부 디렉터리에 저장한 후 동적으로 해당 파일을 로드하는 방법이다.
1
2
3
4
public DexClassLoader (String dexPath,
, String optimizedDirectory
, String librarySearchPath
ClassLoader parent)
파일을 로드할 때는 위와 같은 API 를 사용하여 진행한다.
동적으로 로드된 dex 파일을 확인하면 아래와 같이 apk 바이너리 안에 있는 dex 파일을 포함하여 실행 시 로드되는 dex 들을 함께 확인할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
[Dex1] : /data/user/0/ww.rksr.jjiuuuu.lsls/classes1.dex
[Dex2] : /data/user/0/ww.rksr.jjiuuuu.lsls/classes2.dex
[Dex3] : /data/user/0/ww.rksr.jjiuuuu.lsls/classes3.dex
[Dex4] : /data/user/0/ww.rksr.jjiuuuu.lsls/classes4.dex
[Dex5] : /data/user/0/ww.rksr.jjiuuuu.lsls/classes5.dex
[Dex6] : /data/user/0/ww.rksr.jjiuuuu.lsls/classes6.dex
[Dex7] : /data/user/0/ww.rksr.jjiuuuu.lsls/classes7.dex
[Dex8] : /data/user/0/ww.rksr.jjiuuuu.lsls/classes8.dex
[Dex9] : /data/user/0/ww.rksr.jjiuuuu.lsls/classes9.dex
[Dex10] : /data/user/0/ww.rksr.jjiuuuu.lsls/classes10.dex
[Dex11] : /data/user/0/ww.rksr.jjiuuuu.lsls/classes11.dex
[Dex12] : /data/user/0/ww.rksr.jjiuuuu.lsls/classes12.dex
[Dex13] : /data/user/0/ww.rksr.jjiuuuu.lsls/classes13.dex
여기서 일반 dex file 도 볼 수 있고 실행 중 올라간 dex file 도 확인할 수 있다.
Memory 에서 로드하는 방법
로컬 내부 디렉터리에 파일을 따로 생성하지 않고 assets/
에서 파일을 Byte 로 읽은 후 실행 중 바로 메모리 상에서 로드하는 방법이다.
1
2
public InMemoryDexClassLoader (ByteBuffer dexBuffer
, ClassLoader parent)
해당 API 는 디렉터리에 파일을 따로 만들지 않고 실행 시 메모리에 로드되는 형태이다.
실행 시 확인하면 아래와 같이 메모리 상에 올라간 dex 를 로컬 디렉터리에 파일로 만들어서 dump 를 뜰 수 있다.
1
2
3
4
5
6
[+] Dex dumped successfully !
=> /data/user/0/ww.rksr.jjiuuuu.lsls/2025_03_07_a_dump.dex
[*] Opening file name
=> /data/user/0/ww.rksr.jjiuuuu.lsls/2025_03_07_a_dump.dex to write 503724 bytes
[*] Writing size
=> 503724 bytes...
Conclusion
assets/
에 리소스처럼 위장해있는 파일을 후킹하여 그리고 그 파일이 메모리에 올라가는 것까지 진행해보았다.
고이신 여러 리버서들의 의견을 들어보면 정말 흥미로운 패턴을 가진 소프트웨어가 많다고 한다.
그중 난독화와 더불어 파일을 숨기거나 조합하거나 끼워맞추는 게 많고 이리갔다 저리갔다 하는 패턴도 있다고 한다. 알게 모르게 피곤하고 성가신 기술들로 분석이 난해한 경우가 다반사라고 한다. 또한 난독화와 더불어 바이너리 수준으로 안티 리버싱이 적용된 케이스도 적지 않다고 하여 해당 케이스도 추후 정리해볼까 한다.
다음 글에 작성할 내용이지만 꽤 재밌었던 사례 중 하나는 모든 호출부를 네이티브로 코드를 내린 어플리케이션이다. 네이티브 라이브러리는 사실 암호화나 난독화를 하지 않아도 이미 컴파일된 바이너리로 디컴파일을 진행해도 기존 원본 코드를 유지할 수 없으며 이해하는 게 어렵기 때문에 대부분 네이티브로 내리는데 몇몇 리버서들이 이또한 분석하는 방법과 툴을 개발하여 네이티브 라이브러리 또한 암호화하는 도구들이 등장하였다.
당연히 그에 걸맞게 해당 암호화 방법들을 또한 역공학할 수 방법들이 때에 맞춰 여러 사람들이 글을 정리하여 세상에 나타났다.
다른 흥미로운 사례는 실행 중 분석할 수 있는 도구들을 탐지하는 케이스도 있고 이러한 분석 도구들을 농락이라도 하듯이 dummy call 로 유도하는 케이스도 굉장히 많았다. 실제로 이와 비슷한 케이스를 확인하면서 앱 동작에는 영향은 없지만 분석 도구를 붙이는 순간 포착하려는 기능이 발견되지 않는 재밌는 상황이 있었다.
그러나 결과적으로 빌드된 Android OS 에서 앱이 성공적으로 실행된다면 가장 최상위 부모 프로세스인 Zygote 부터 정적 분석과 동적 분석으로 하나씩 살펴보면서 Application 으로 들어가고 class call, method call, native call 쪽으로 파고들면 된다.
불가능한건 아니다. 새벽과 길고 긴 밤이 있으면 결국엔 알아낼 수 있는 것 같다, 이 글처럼 :)
Reference
https://source.android.com/docs/core/runtime/dex-format https://developer.android.com/reference/java/lang/ClassLoader https://developer.android.com/reference/dalvik/system/DexClassLoader https://developer.android.com/reference/dalvik/system/InMemoryDexClassLoader
If you find any errors, please let me know by comment or email. Thank you.