
1. 동적 로딩(DYNAMIC DEX LOADING)
동적 로딩(Dynamic Dex Loading)은 앱이 실행되는 동안 서버 측에서 동적으로 파일(DEX)을 로드하여 실행하는 기법입니다. 모바일 앱에는 루팅 탐지, 안티 디버깅, 프리다 탐지, SSL Pinning, 무결성 검사 등 많은 보안기법이 적용되어 있는데요. 이러한 방어 기법들은 공격자들이 APK를 디컴파일 하여 탐지 로직을 분석하면 쉽게 우회될 수 있습니다. 때문에 이러한 방어 기법(루팅, 안티 디버깅, 프리다, 무결성, SSL Pinning, etc.)들을 APK 파일에 노출시키지 않는 것이 좋은데요. 이러한 상황에서 동적 로딩은 유용하게 활용될 수 있습니다. 앱 실행 시에 서버 측에서 방어 로직이 담긴 파일(DEX)을 불러와 사용하고 기능(탐지)을 사용한 후에는 해당 파일을 삭제하여 분석이 어렵도록 하는 것이죠. 실제로 여러 금융 앱에서는 Rooting 탐지, SSL Pinning, 무결성 검사 기능을 따로 DEX 파일로 빼놓고, 실행 시점에서만 로드하는 방식을 사용하여 공격자가 APK만 가지고는 보안 기능을 분석하기 어렵도록 하고 있습니다.
1-1. BYPASS DYNAMIC CODE : Dex 파일 동적 로딩 탐지 기법(파일 삭제X)
- 안드로이드 단말기의 애플리케이션 캐시 정보가 저장되는
cache_code경로에dynamic파일 생성 - APK의
assets폴더에dynamic이름의 파일 생성 - (1)에서 읽은
/assets/dynamic파일의 내용을/data/data/com.playground.anditer/code_cache/dynamic파일에 저장 - DexClassLoader를 활용하여
cache_code경로의dynmaic파일을 열람 후getResult함수 호출 - 함수(getResult) 호출이 종료되면 사용된 DEX 파일을 삭제 및 리턴 값(z) 반환

(FRIDA) DexClasssLoader 함수 후킹 후 파일(DEX) 경로 확인
/data/user/0/com.playground.anditer/code_cache

DEX 파일(dynamic) 디컴파일 후 확인
PS C:\study\android\Anditer> adb pull /data/local/tmp/dynamic .
/data/local/tmp/dynamic: 1 file pulled, 0 skipped. 0.0 MB/s (640 bytes in 0.015s)
PS C:\study\android\Anditer>dynamic DEX 파일 디컴파일 후 코드를 확인하면 항상 참 값을 반환하는 getResult() 함수를 확인할 수 있습니다. 따라서, isCheckDynamic 함수의 z = ((Boolean) invoke).booleanValue(); 값은 항상 참(true)를 반환하므로 Bypass Dynamic Code 탐지 로직에 걸리게 됐던거죠.

(FRIDA) 동적 로딩 우회 방법
Class loadClass = new DexClassLoader(file.getAbsolutePath(), codeCacheDir.getAbsolutePath(), null, this.mContext.getClassLoader()).loadClass("com.playground.anditer.Dynamic");코드에서 DEX 파일 경로에 유효하지 않은 값을 삽입하여 탐지 로직이 적용된 파일을 불러오지 못하도록 우회isCheckDynamic함수의 리턴 값 변조DEX 파일이 메모리에 적재되었을 때getResult()` 함수의 리턴 값 변조
(1) .../code_cache/dynamic 파일 호출 방지를 통한 우회
DexClassLoader 인스턴스가 생성되지 않도록하면 덱스 파일 호출 방지가 가능합니다.
Java.perform(function () {
var DexClassLoader = Java.use("dalvik.system.DexClassLoader");
DexClassLoader.$init.overload(
"java.lang.String",
"java.lang.String",
"java.lang.String",
"java.lang.ClassLoader"
).implementation = function (absPath, cacheDir, temp, classLoader) {
console.log(`\n${this.absPath}\n${cacheDir}\n${temp}\n${classLoader}`);
return;
};
});(2) (DEX File) getResult() 리턴 값 변조를 통한 우회
DEX 파일에서 동적으로 가져온 클래스는 Java.use()를 통해 활용하는 것이 불가능합니다. 해당 클래스를 사용하기 위해서는 Java.classFactory.loader로 지정하거나 Java.classFactory.use('{CLASS_NAME}')과 같이 사용하면 됩니다.
Java.perform(function () {
var DexClassLoader = Java.use("dalvik.system.DexClassLoader");
DexClassLoader.$init.overload(
"java.lang.String",
"java.lang.String",
"java.lang.String",
"java.lang.ClassLoader"
).implementation = function (absPath, cacheDir, tmp, classLoader) {
this.$init(absPath, cacheDir, tmp, classLoader);
Java.classFactory.loader = this;
var Dynamic = Java.use("com.playground.anditer.Dynamic");
Dynamic.getResult.implementation = function () {
console.warn("[*] getResult function has been called");
console.log("Dynamic DEX File => Class: Dynamic => Method: getResult()");
console.log("\tOriginal getResult() Return Value: ", this.getResult());
return false;
};
return this.$init(absPath, cacheDir, tmp, classLoader);
};
});
1-2. BYPASS HIDE CODE : Dex 파일 동적 로딩 탐지 기법(로딩 후 파일 삭제)
Bypass Hide Code 버튼 클릭 시 로드된 DEX 파일을 삭제처리 하는 코드를 확인할 수 있습니다. DEX 파일이 삭제되면 해당 파일을 추출하여 코드를 분석하는 것이 불가능하기 때문에 삭제되지 않도록 조치하는 것이 필요한데요. 이를 위한 우회 방법은 아래와 같습니다.
- (1)
deleteFile함수 재정의 (파일 삭제 우회) - (2)
dexFile.exists()리턴 값 변조 (분기문 우회) - (3)
dexFile.delete()함수 재정의 (파일 삭제 로직 우회)

(FRIDA) deleteFile 함수 재정의를 통한 우회
/proc/self/maps 경로의 파일 확인 시 .../code_cache/dynamic 파일이 삭제(deleted)되도록 설정되어 있으나, 아래 스크립트를 실행하여 이를 우회하면 삭제되지 않고 해당 경로에서 발견할 수 있습니다.
setImmediate(function () {
Java.perform(function () {
var Dynamic = Java.use("com.playground.anditer.DynamicDetector");
Dynamic.deleteFile.implementation = function (dexFile) {
console.warn("[*] deleteFile() has been intercepted!");
console.log("\tPreventing deletion of: " + dexFile.getAbsolutePath());
return; // 항상 실패하도록 설정
};
});
});
(FRIDA) frida-dexdump 도구를 활용한 DEX 파일 추출(메모리 덤프)
Install frida-dexdump
PS C:\study\android\Anditer> pip install frida-dexdump
Collecting frida-dexdump
Downloading frida_dexdump-2.0.1-py3-none-any.whl.metadata (1.0 kB)
[ . . . ]
Successfully installed frida-dexdump-2.0.1 wallbreaker-1.0.3이후 아래 명령어를 입력하여 애플리케이션 스폰(spawn)/어태치(attach) 후 버튼 클릭 후 대기하면 메모리에 적재된 DEX 파일들을 덤프하여 저장합니다.
# Spawn (-f) , Attach (-F)
frida-dexdump -U --sleep 10 -f {PACKAGE_NAME}
frida-dexdump -U --sleep 10 -F {PID}
이 때, 일반적으로 동적으로 로드되는 DEX 파일들은 용량이 작은 경우가 대부분이라고 하는데요. 앱의 주요 기능을 담당하는 파일이 아니라 단순히 보안 솔루션 기능을 구현하기 위한 코드이기 때문일 것으로 보입니다. 가장 용량이 작은 파일을 디컴파일하여 확인해보면 이전과 동일하게 Dynamic 클래스와 getResult 함수가 존재하는 것을 볼 수 있습니다. 코드가 동일하니 이전과 동일하게 우회도 가능하겠죠!?

몇몇 금융권 앱의 경우에는 이처럼 DEX 파일을 동적으로 불러와 보안 솔루션 기능을 적용시킨 후 분석이 힘들도록 삭제 처리하기도 합니다. 이러한 경우에 직접 코드를 작성해도 좋겠지만 frida-dexdump와 같은 도구를 사용하면 진단을 더 수월하게 할 수 있을 것 같네요. 다음 포스팅에서는 네이티브 함수를 통한 탐지 방법들과 이를 우회하는 방법에 대한 실습을 진행해보겠습니다.
REFERENCE
- https://github.com/naroSEC/Anditer?tab=readme-ov-file
- https://www.igloo.co.kr/security-information/anditer%eb%a5%bc-%ed%99%9c%ec%9a%a9%ed%95%9c-%ec%95%88%eb%93%9c%eb%a1%9c%ec%9d%b4%eb%93%9c-%ec%9c%84%ed%98%91-%ed%83%90%ec%a7%80-%eb%b0%8f-%ec%9a%b0%ed%9a%8c-%eb%b0%a9%ec%95%88-part-4-1-%eb%ac%b4/
- How to Hook Dynamic Loaded DEX Files