Home How to Extract Dynamic DEX Loading
Post
Cancel

How to Extract Dynamic DEX Loading

최근 모바일 진단 중 루팅탐지를 하고 있지만 어디서 호출하는지를 찾을 수가 없었는데 어찌저찌 하다가 Dynamic Dex Loading이 적용된 것을 발견 하였습니다.
Dynamic Dex Loading을 하는 App에서 DEX를 추출하는 방법들에 대해서 알아봅시다.


Dynamic Dex Loading 🔵⚪️🔴

Dynamic Dex Loading 기법은 APP 분석을 어렵게 하기 위해 사용하는 난독화 기법입니다. 보통은 APK내 일반적인 경로(classes.dex)가 아닌 다른 경로(assets, App 내부 저장소)에 암호화되어 저장되어 있으며 앱이 실행될 때 복호화되어 메모리에 로드 됩니다. 복호화 과정을 거친 후 삭제 되기 때문에 일반적으로는 찾기가 어렵습니다.


How to Extract Dynamic DEX?

암호화된 DEX를 복호화 하는 방법은 Java Code 내에서 할 수도 있고 JNI Library 파일을 통한 복호화등이 있습니다. 난독화 된 APP에서 암호화 된 DEX 파일을 찾고 복호화 방법을 찾는 방법은 시간이 많이 소요됩니다.

빠른 시간 내 우회를 하기 위해서는 복호화된 DEX가 메모리에 로드되거나 삭제된다는 것으로 시작할 수 있습니다.

다음과 같은 우회 방법을 생각할 수 있습니다.

  1. Android 기기 내 폴더 복사
  2. 메모리에 올라간 복호화 된 DEX 추출
  3. JNI Library 함수 후킹

Find Dymanic DEX Loading

가장 먼저 Dynamic DEX Loading 기법을 사용하고 있는지 확인을 해야합니다. 확인하는 방법으로는 loadDEX(), openDexFile() 함수 후킹을 통해 인자 값에서 확인할 수 있습니다. 추가적으로 /proc/{pid}/maps | grep delete 명령어를 통해 메모리에서 삭제된 DEX를 확인할 수 있습니다.


Dynamic Dex Extract - Shell Script

Android 기기 내 폴더 복사를 통해 DEX 파일을 추출하는 방법입니다. Shell Scipt로 삭제되는 DEX 폴더의 위치를 알고 있을 때 0.1초마다 해당 폴더를 지워지지않도록 다른 폴더에 복사를 하는 방법입니다.

1
2
3
4
5
6
7
8
9
10
11
#!/system/bin/sh
cnt=0
while [ 1 ]
do
    if [ $cnt -ge 0]; then
        echo "Copy Dex -->>>>>>>";
        cp -r {DEX 폴더} /sdcard/test/DEX$cnt;
        fi
    cnt=$(($cnt+1))
    sleep 0.1
done

Dynamic Dex Extract - JNI Function

JNI Library 함수에서 파일을 삭제시키는 함수를 후킹하여 삭제하지 않도록 하는 방법입니다. Unlink(), Remove() 함수등이 있습니다.

unlink()함수가 실행될때 NativeCallback으로 함수를 재정의 하여 삭제가 아닌 console.warn() 만 실행되도록 합니다.

1
2
3
4
5
6
7
function unlink(){
    var unlink = Module.findExportByName(null, 'unlink');
    var open = new NativeFunction(unlink, 'void', ['int']);
    Interceptor.replace(open, new NativeCallback(function(){
    console.warn('[*] unlink Hook complete');  
    }, 'void', ['int']));
}

Dynamic Dex Extract - Memory Load DEX

복호화된 DEX는 메모리에 로드되기 때문에 메모리에서 복호화된 DEX를 추출하는 방법입니다.

❗️❗️원하는 DEX를 추출하여도 디컴파일이 되지 않는다면 바이너리 편집 도구(HxD)를 이용하여 DEX 시그니처(64 65 78 0a 30)를 찾아 불필요한 영역을 지워야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
Java.perform(function (){
    var ThreadDef = Java.use('java.lang.Thread');
    var ThreadObj = ThreadDef.$new();
    
    function stackTrace() {
        var stack = ThreadObj.currentThread().getStackTrace();
        for (var i = 0; i < stack.length; i++) {
            console.log(i + " => " + stack[i].toString());
        }
        console.log("-------------------------------------");
    }
       
        var DexFile = Java.use("dalvik.system.DexFile");
        DexFile.loadDex.overload('java.lang.String', 'java.lang.String', 'int', 'java.lang.ClassLoader', '[Ldalvik.system.DexPathList$Element;').implementation = function(sourcepath, outputPathName, flags,arg4,arg5){
            console.warn(sourcepath);
            if(outputPathName.indexOf("{찾는dex}")>-1){
               console.log("[*] arg1 : "+sourcepath);
            }
            ExtractDexFile();
            return this.loadDex(sourcepath, outputPathName, flags,arg4,arg5)
        }
    })

    function ExtractDexFile() {
        console.warn('Dex Extract');
        Process.enumerateRanges('r--').forEach(function (range) {
            try {
                if(range.file.path && (range.file.path.startsWith("/data/dalvik-cache/") || range.file.path.startsWith("/system/") || range.file.path.startsWith("/dev/"))) {
                    return;
                }
                else {
                    Memory.scanSync(range.base, range.size, "64 65 78 0a 30 ?? ?? 00").forEach(function (match) {
                        console.log('\x1b[32m[*] file_path : ' + range.file.path + '\x1b[0m');
                        var dex_addr = match.address;
                        var dex_size = dex_addr.add(0x20).readUInt();
                        console.log('\x1b[33m[*] dex_addr : ' + dex_addr + '\x1b[0m');
                        console.log('\x1b[36m[*] dex_size : ' + dex_size + '\x1b[0m');
                        if(range.file.path.indexOf("{찾는 DEX 이름}")>-1){
                            var file = new File("{생성할 DEX File Name}", "wb");
                            file.write(Memory.readByteArray(dex_addr, dex_size));
                            file.flush();
                            file.close();
                        }
                    });
                }
                
            } catch (e) {}
        })
        console.warn('[!] Extract Complete ! :)');
    }

//frida -U -f com.kakaobank.channel -l a.js --no-pause

Reference

https://blog.naver.com/gigs8041/222137154226 https://www.igloo.co.kr/security-information/악성-apk을-이용한-dynamic-dex-loading-분석/


후기

Android 여러 버전에서 시도를 해봤는데 모든 버전이 되지 않았습니다. 요건 되고 저건 안되고… 다른 버전에선 요게 되고 다른게 안되고…. 신기 했지만 왜 안되는지는 결국 못찾았습니다😱😱 아직 많이 부족하기에.. 더 열심히 해야겠습니다