Post

The Hidden Originals - How Protection Layers Conceal Apps

How SO Files Work in Software Protection (feat. Vermillion)

The Hidden Originals - How Protection Layers Conceal Apps

Introduction

현대 소프트웨어는 눈에 보이는 기능뿐 아니라, 그 이면에 숨겨진 수십 겹의 방어층을 가진다. 정교한 난독화, 런타임 변조, 무결성 검사, 라이선스 검증 — 그 모든 것은 ‘어떻게 보호해야 하는가’에 대한 설계자의 답이다. 하지만 답은 완전하지 않다. 악성코드와 공격자는 그 틈을 찾아내고, 우리는 그 흔적을 따라가며 설계의 한계를 드러낸다.

오늘은 악성코드를 해체하면서 보이는 보호 메커니즘의 의도와 작동 원리를 체계적으로 분석하고, 그 지식을 다시 제품 설계와 탐지 로직으로 재구성하는 과정을 기록한다. 리버싱은 혐오스러운 행위가 아니라, 더 강한 방어를 설계하기 위한 가장 현실적인 실험실이다. 이 글 끝에서는 실제 위협을 해부한 사례, 보호 메커니즘의 설계 철학을 엿볼 수 있을 것이다.

기술적 통찰과 윤리적 책임 사이의 균형을 잃지 않으며, 우리가 얻은 지식을 어떻게 ‘방어’로 돌려놓을지 함께 고민해본다.

최근 압축된 형태의 설치파일을 많이 보고 있는데 그중 또 재밌는 걸 길에서(?) 주워서 정리해보려고 한다.

파일은 6ded4cf6cc4f4735b954ec4a7becbb99f3d9654710ebc12ffe76c4e0ae396651.apk 이며 손쉽게 디컴파일이 가능하다.

앱의 패키지 이름은 com.tencent.mm 인데 찾아보면 WeChat 이다. WeChat 을 가장한 멀웨어로 보인다.

여기서는 멀웨어의 기능보다 어떻게 자신의 모습을 숨겼는지 그리고 어떤 방식으로 원본 코드를 로드하는지 간략하게 살펴볼 생각이다.

Reverse

자바 단을 먼저 들어간다.

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package arm;

import android.app.Application;
import android.content.Context;
import android.os.SystemClock;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class StubApp extends Application {
    public static String MAIN_APPLICATION;

    static {
        System.loadLibrary("arm_protect");
    }

    @Override  // android.content.ContextWrapper
    protected native void attachBaseContext(Context arg1) {
    }

    @Override  // android.content.ContextWrapper
    public native Context createPackageContext(String arg1, int arg2) {
    }

    @Override  // android.content.ContextWrapper
    public native String getPackageName() {
    }

    static void loadDex(List list0, Context context0) {
        try {
            long v = SystemClock.currentThreadTimeMillis();
            StubApp.MAIN_APPLICATION = StubApp.loadMainApplicationFromAssets(context0);
            Field field0 = ((Class)Objects.requireNonNull(context0.getClassLoader().getClass().getSuperclass())).getDeclaredField("pathList");
            if(!field0.isAccessible()) {
                field0.setAccessible(true);
            }

            Object object0 = field0.get(context0.getClassLoader());
            Field field1 = Objects.requireNonNull(object0).getClass().getDeclaredField("dexElements");
            if(!field1.isAccessible()) {
                field1.setAccessible(true);
            }

            Object[] arr_object = (Object[])field1.get(object0);
            Method method0 = object0.getClass().getDeclaredMethod("makePathElements", List.class, File.class, List.class);
            if(!method0.isAccessible()) {
                method0.setAccessible(true);
            }

            Object[] arr_object1 = (Object[])method0.invoke(object0, list0, context0.getDir("arm", 0), new ArrayList());
            Object[] arr_object2 = (Object[])Array.newInstance(((Class)Objects.requireNonNull(((Object[])Objects.requireNonNull(arr_object)).getClass().getComponentType())), ((Object[])Objects.requireNonNull(arr_object1)).length + arr_object.length);
            System.arraycopy(arr_object, 0, arr_object2, 0, arr_object.length);
            System.arraycopy(arr_object1, 0, arr_object2, arr_object.length, arr_object1.length);
            field1.set(object0, arr_object2);
            System.out.println("Time taken: " + (SystemClock.currentThreadTimeMillis() - v) + " ms");
        }
        catch(Exception exception0) {
            exception0.printStackTrace();
        }
    }

    private static String loadMainApplicationFromAssets(Context context0) {
        try {
            InputStream inputStream0 = context0.getAssets().open("config.so");
            byte[] arr_b = new byte[inputStream0.available()];
            inputStream0.read(arr_b);
            inputStream0.close();
            return new String(arr_b);
        }
        catch(IOException iOException0) {
            iOException0.printStackTrace();
            return null;
        }
    }

    @Override  // android.app.Application
    public native void onCreate() {
    }
}

위에 코드가 전부인데 흥미로운건 네이티브 라이브러리(arm_protect)를 로드한다는 것과 native 로 랩핑하여 onCreateattachBaseContext, createPackageContext 그리고 getPackageName 룰 구현하고 있다.

그러면 본격적으로 libarm_protec.so 를 열어본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
  jint v2; // w19
  __int64 v4; // x20
  __int64 v5; // x0
  _QWORD v6[2]; // [xsp+0h] [xbp-30h] BYREF

  v6[1] = *(_QWORD *)(_ReadStatusReg(TPIDR_EL0) + 40);
  v2 = 65542;
  if ( (*vm)->GetEnv(vm, (void **)v6, 65542) )
    return -1;
  v4 = v6[0];
  v5 = (*(__int64 (__fastcall **)(_QWORD, const char *))(*(_QWORD *)v6[0] + 48LL))(v6[0], "arm/StubApp");
  if ( !v5
    || ((*(__int64 (__fastcall **)(__int64, __int64, char **, __int64))(*(_QWORD *)v4 + 1720LL))(v4, v5, off_3B000, 4)
      & 0x80000000) != 0 )
  {
    return -1;
  }
  xhook_enable_debug();
  xhook_register(".*/libc.so$", "execv", my_execv, &org_execv);
  xhook_refresh(0);
  return v2;
}

위에 onLoad 코드를 보면 off_3B000 에 JNI 바인딩된 함수를 볼 수 있다.

1
2
3
4
native_attachContextBaseContext(_JNIEnv *,_jobject *,_jobject *)	.text	00000000000111F0	000003B0	00000190		R	.	.	.	.	.	B	T	.	.
native_onCreate(_JNIEnv *,_jobject *)	                            .text	000000000001180C	0000009C	00000030		R	.	.	.	.	.	B	.	.	.
native_getPackageName(_JNIEnv *,_jobject *)	                      .text	000000000001207C	00000014			        R	.	.	.	.	.	.	.	.	.
native_createPackageContext(_JNIEnv *,_jobject *,_jstring *,int)	.text	0000000000012090	000000C0	00000040		R	.	.	.	.	.	B	.	.	.

so 파일 위에서 위에 선언된 네이티브 함수들을 볼 수 있다.

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
__int64 __fastcall native_attachContextBaseContext(__int64 a1, __int64 a2, __int64 a3)
{
  __int64 v6; // x20
  __int64 v7; // x0
  __int64 v8; // x21
  __int64 v9; // x0
  __int64 v10; // x23
  __int64 v11; // x0
  __int64 v12; // x0
  __int64 v13; // x25
  __int64 v14; // x26
  __int64 v15; // x28
  __int64 v16; // x0
  __int64 v17; // x27
  __int64 v18; // x0
  __int64 v19; // x20
  __int64 v20; // x24
  __int64 v21; // x28
  __int64 i; // x1
  __int64 v23; // x0
  __int64 v24; // x0
  __int64 v25; // x20
  __int64 j; // x22
  const char *v27; // x0
  __int64 result; // x0
  __int64 v29; // [xsp+0h] [xbp-180h]
  __int64 v30; // [xsp+8h] [xbp-178h]
  __int64 v31; // [xsp+10h] [xbp-170h]
  char filename[256]; // [xsp+20h] [xbp-160h] BYREF
  __int64 v33; // [xsp+120h] [xbp-60h]

  v33 = *(_QWORD *)(_ReadStatusReg(TPIDR_EL0) + 40);
  v6 = (*(__int64 (__fastcall **)(__int64, const char *))(*(_QWORD *)a1 + 48LL))(a1, "android/content/ContextWrapper");
  v7 = (*(__int64 (__fastcall **)(__int64, __int64, const char *, const char *))(*(_QWORD *)a1 + 264LL))(
         a1,
         v6,
         "attachBaseContext",
         "(Landroid/content/Context;)V");
  v30 = a3;
  v31 = v6;
  _JNIEnv::CallNonvirtualVoidMethod(a1, a2, v6, v7, a3);
  v8 = (*(__int64 (__fastcall **)(__int64, __int64))(*(_QWORD *)a1 + 248LL))(a1, a2);
  v9 = (*(__int64 (__fastcall **)(__int64, __int64, const char *, const char *))(*(_QWORD *)a1 + 264LL))(
         a1,
         v8,
         "getFilesDir",
         "()Ljava/io/File;");
  v10 = _JNIEnv::CallObjectMethod(a1, a2, v9);
  v29 = (*(__int64 (__fastcall **)(__int64, __int64))(*(_QWORD *)a1 + 248LL))(a1, v10);
  v11 = (*(__int64 (__fastcall **)(__int64, __int64, const char *, const char *))(*(_QWORD *)a1 + 264LL))(
          a1,
          v29,
          "getAbsolutePath",
          "()Ljava/lang/String;");
  v12 = _JNIEnv::CallObjectMethod(a1, v10, v11);
  (*(void (__fastcall **)(__int64, __int64, _QWORD))(*(_QWORD *)a1 + 1352LL))(a1, v12, 0);
  VIBE_buildFilePath(filename);
  extractDex(a1, a2, filename);
  v13 = (*(__int64 (__fastcall **)(__int64, const char *))(*(_QWORD *)a1 + 48LL))(a1, "java/io/File");
  v14 = (*(__int64 (__fastcall **)(__int64, __int64, const char *, const char *))(*(_QWORD *)a1 + 264LL))(
          a1,
          v13,
          "<init>",
          "(Ljava/lang/String;)V");
  v15 = (*(__int64 (__fastcall **)(__int64, const char *))(*(_QWORD *)a1 + 48LL))(a1, "java/util/ArrayList");
  v16 = (*(__int64 (__fastcall **)(__int64, __int64, const char *, const char *))(*(_QWORD *)a1 + 264LL))(
          a1,
          v15,
          "<init>",
          "()V");
  v17 = _JNIEnv::NewObject(a1, v15, v16);
  v18 = (*(__int64 (__fastcall **)(__int64, __int64, const char *, const char *))(*(_QWORD *)a1 + 264LL))(
          a1,
          v15,
          "add",
          "(Ljava/lang/Object;)Z");
  v20 = dexList;
  v19 = qword_3B0C0;
  if ( dexList != qword_3B0C0 )
  {
    v21 = v18;
    if ( (*(_BYTE *)dexList & 1) != 0 )
      goto LABEL_6;
LABEL_3:
    for ( i = v20 + 1; ; i = *(_QWORD *)(v20 + 16) )
    {
      (*(void (__fastcall **)(__int64, __int64))(*(_QWORD *)a1 + 1336LL))(a1, i);
      v23 = _JNIEnv::NewObject(a1, v13, v14);
      _JNIEnv::CallBooleanMethod(a1, v17, v21, v23);
      v20 += 24;
      if ( v19 == v20 )
        break;
      if ( (*(_BYTE *)v20 & 1) == 0 )
        goto LABEL_3;
LABEL_6:
      ;
    }
  }
  v24 = (*(__int64 (__fastcall **)(__int64, __int64, const char *, const char *))(*(_QWORD *)a1 + 904LL))(
          a1,
          v8,
          "loadDex",
          "(Ljava/util/List;Landroid/content/Context;)V");
  _JNIEnv::CallStaticVoidMethod(a1, v8, v24, v17, v30);

extractDex 하고 자바 단에서 본 loadDex 를 호출하는 것을 볼 수 있다.

아래에서 extractDex 쪽을 봐본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  v7 = (__int64)(*a1)->GetMethodID(a1, (jclass)v6, "getAssets", "()Landroid/content/res/AssetManager;");
  v8 = (void *)_JNIEnv::CallObjectMethod(a1, a2, v7);
  result = AAssetManager_fromJava(a1, v8);
  if ( result )
  {
    v10 = result;
    v11 = AAssetManager_openDir(result, "");
    result = (AAssetManager *)AAssetDir_getNextFileName(v11);
    if ( result )
    {
      v12 = (const char *)result;
      do
      {
        if ( strstr(v12, "classes") && strstr(v12, ".dex") )
        {
          result = AAssetManager_open(v10, v12, 2);
          if ( !result )
            return result;

            { . . . }

assets 에 있는 classes.dex 를 읽어서 복호화하는 과정을 거친다.

그러면 자바 단에 loadDex 쪽으로 넘어간다.

1
StubApp.MAIN_APPLICATION = StubApp.loadMainApplicationFromAssets(context0);

loadDex 에서 위와 같이 다른 자바 메소드를 호출하는 것을 볼 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
    private static String loadMainApplicationFromAssets(Context context0) {
        try {
            InputStream inputStream0 = context0.getAssets().open("config.so");
            byte[] arr_b = new byte[inputStream0.available()];
            inputStream0.read(arr_b);
            inputStream0.close();
            return new String(arr_b);
        }
        catch(IOException iOException0) {
            iOException0.printStackTrace();
            return null;
        }
    }

해당 메소드는 assets 에 있는 config.so 를 읽어서 문자열로 변수에 저장한다.

1
2
3
00000000: 636f 6d2e 7379 7374 656d 2e6d 7961 7070  com.system.myapp
00000010: 6c69 6361 7469 6f6e 2e4a 766d 2e6a 766d  lication.Jvm.jvm
00000020: 6472 6564                                dred

해당 config.so 를 hex dump 에서 뽑아내면 위와 같은 문자열을 찾아볼 수 있다.

1
Stub.MAIN_APPLICATION = com.system.myapplication.Jvm.jvmdred

다시 정리하면 이렇게 볼 수 있다.

그 다음은 native_onCreate 쪽으로 넘어가본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__int64 __fastcall native_onCreate(__int64 a1, __int64 a2)
{
  __int64 v4; // x21
  __int64 v5; // x0
  __int64 v6; // x4
  __int64 result; // x0

  v4 = (*(__int64 (__fastcall **)(__int64, const char *))(*(_QWORD *)a1 + 48LL))(a1, "android/app/Application");
  v5 = (*(__int64 (__fastcall **)(__int64, __int64, const char *, const char *))(*(_QWORD *)a1 + 264LL))(
         a1,
         v4,
         "onCreate",
         "()V");
  result = _JNIEnv::CallNonvirtualVoidMethod(a1, a2, v4, v5, v6);
  if ( !isbindRealApplication )
    return bindRealApplication(a1);
  return result;
}

native_onCreate 를 보면 실제 앱으로 바인딩하는 부분을 볼 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
__int64 __fastcall bindRealApplication(__int64 a1)
{

  v4 = (*(__int64 (__fastcall **)(__int64, const char *))(*(_QWORD *)a1 + 48LL))(
         a1,
         "android/content/pm/ApplicationInfo");
  v5 = (*(__int64 (__fastcall **)(__int64, const char *))(*(_QWORD *)a1 + 48LL))(a1, "java/util/List");
  v54 = (*(__int64 (__fastcall **)(__int64, const char *))(*(_QWORD *)a1 + 48LL))(a1, "android/app/Application");
  v6 = (*(__int64 (__fastcall **)(__int64, const char *))(*(_QWORD *)a1 + 48LL))(a1, "android/app/LoadedApk");
  v7 = (*(__int64 (__fastcall **)(__int64, __int64, const char *, const char *))(*(_QWORD *)a1 + 904LL))(
         a1,
         v2,
         "currentActivityThread",
         "()Landroid/app/ActivityThread;");
  v11 = _JNIEnv::CallStaticObjectMethod(a1, v2, v7, v8, v9, v10);
  v12 = (*(__int64 (__fastcall **)(__int64, __int64, const char *, char *))(*(_QWORD *)a1 + 1152LL))(
          a1,
          v3,
          "MAIN_APPLICATION",
          "Ljava/lang/String;");
  v56 = v3;
  v60 = (*(__int64 (__fastcall **)(__int64, __int64, __int64))(*(_QWORD *)a1 + 1160LL))(a1, v3, v12);
  v13 = (*(__int64 (__fastcall **)(__int64, __int64, const char *, const char *))(*(_QWORD *)a1 + 752LL))(
    

위에서 보면 이전에 아펭서 받은 Stub.MAIN_APPLCIATION 을 호출하는 것을 볼 수 있다.

Call Stack

여기까지 본다면 순서는 아래와 같을 것이다.

native_attachContextBaseContext -> extractDex -> loadDex -> loadMainApplicationFromAssets -> native_onCreate -> MAIN_APPLICATION

그러나 이 파일의 내부 구동 방식은 정확하게 알 필요가 있어서 SO 파일이 로드되기 전에 코드 트래킹을 역으로 추적할 수 있는 Vermillion 을 구현했다.

해당 툴에 대해서는 다른 장에서 상세하게 다뤄볼 생각이다.

SO 파일이 로드되는 시점보다 훨씬 전에 코드 주입을 시작하면 모든 콜스택을 쫓아갈 수 있다.

android_dlopen_ext 를 주목하면 된다. 라이브러리 libdl.so 안에 0x1180 오프셋에 있다.

아래는 현재 보고있는 arm_protect 에 대한 콜스택이다.

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# 0x00001 => JNI_OnLoad
# 0x00002 => .xhook_enable_debug
# 0x00003 => xhook_enable_debug
# 0x00004 => .xh_core_enable_debug
# 0x00005 => xh_core_enable_debug
# 0x00006 => .xhook_register
# 0x00007 => xhook_register
# 0x00008 => .xh_core_register
# 0x00009 => xh_core_register
# 0x00010 => .regcomp
# 0x00011 => .malloc
# 0x00012 => .strdup
# 0x00013 => .pthread_mutex_lock
# 0x00014 => .pthread_mutex_unlock
# 0x00015 => .xhook_refresh
# 0x00016 => xhook_refresh
# 0x00017 => .xh_core_refresh
# 0x00018 => xh_core_refresh
# 0x00019 => .sigemptyset
# 0x00020 => .sigaction
# 0x00021 => sub_1394C
# 0x00022 => .fopen
# 0x00023 => .fgets
# 0x00024 => .sscanf
# 0x00025 => .isspace
# 0x00026 => .strlen
# 0x00027 => .regexec
# 0x00028 => sub_13D20
# 0x00029 => .sigsetjmp
# 0x00030 => .xh_elf_check_elfheader
# 0x00031 => xh_elf_check_elfheader
# 0x00032 => sub_13DC4
# 0x00033 => sub_14064
# 0x00034 => sub_140F8
# 0x00035 => .xh_elf_init
# 0x00036 => xh_elf_init
# 0x00037 => .__android_log_print
# 0x00038 => .strcmp
# 0x00039 => .free
# 0x00040 => .fclose
# 0x00041 => _Z31native_attachContextBaseContextP7_JNIEnvP8_jobjectS2_
# 0x00042 => ._ZN7_JNIEnv24CallNonvirtualVoidMethodEP8_jobjectP7_jclassP10_jmethodIDz
# 0x00043 => _ZN7_JNIEnv24CallNonvirtualVoidMethodEP8_jobjectP7_jclassP10_jmethodIDz
# 0x00044 => ._ZN7_JNIEnv16CallObjectMethodEP8_jobjectP10_jmethodIDz
# 0x00045 => _ZN7_JNIEnv16CallObjectMethodEP8_jobjectP10_jmethodIDz
# 0x00046 => sub_1114C
# 0x00047 => .__vsprintf_chk
# 0x00048 => ._Z10extractDexP7_JNIEnvP8_jobjectPKc
# 0x00049 => _Z10extractDexP7_JNIEnvP8_jobjectPKc
# 0x00050 => .access
# 0x00051 => .mkdir
# 0x00052 => .AAssetManager_fromJava
# 0x00053 => .AAssetManager_openDir
# 0x00054 => .AAssetDir_getNextFileName
# 0x00055 => .strstr
# 0x00056 => .AAssetManager_open
# 0x00057 => ._ZNSt6__ndk16vectorINS_12basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEENS4_IS6_EEE24__emplace_back_slow_pathIJRA256_cEEEvDpOT_
# 0x00058 => _ZNSt6__ndk16vectorINS_12basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEENS4_IS6_EEE24__emplace_back_slow_pathIJRA256_cEEEvDpOT_
# 0x00059 => ._Znwm
# 0x00060 => _Znwm
# 0x00061 => ._ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEC2IDnEEPKc
# 0x00062 => _ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEC2IDnEEPKc
# 0x00063 => .memcpy
# 0x00064 => .AAsset_read
# 0x00065 => .fwrite
# 0x00066 => .AAsset_close
# 0x00067 => ._ZN7_JNIEnv9NewObjectEP7_jclassP10_jmethodIDz
# 0x00068 => _ZN7_JNIEnv9NewObjectEP7_jclassP10_jmethodIDz
# 0x00069 => ._ZN7_JNIEnv17CallBooleanMethodEP8_jobjectP10_jmethodIDz
# 0x00070 => _ZN7_JNIEnv17CallBooleanMethodEP8_jobjectP10_jmethodIDz
# 0x00071 => ._ZN7_JNIEnv20CallStaticVoidMethodEP7_jclassP10_jmethodIDz
# 0x00072 => _ZN7_JNIEnv20CallStaticVoidMethodEP7_jclassP10_jmethodIDz
# 0x00073 => .remove
# 0x00074 => _Z21native_getPackageNameP7_JNIEnvP8_jobject
# 0x00075 => _Z27native_createPackageContextP7_JNIEnvP8_jobjectP8_jstringi
# 0x00076 => ._Z19bindRealApplicationP7_JNIEnvP8_jobject
# 0x00077 => _Z19bindRealApplicationP7_JNIEnvP8_jobject
# 0x00078 => ._ZN7_JNIEnv22CallStaticObjectMethodEP7_jclassP10_jmethodIDz
# 0x00079 => _ZN7_JNIEnv22CallStaticObjectMethodEP7_jclassP10_jmethodIDz
# 0x00080 => ._ZN7_JNIEnv14CallVoidMethodEP8_jobjectP10_jmethodIDz
# 0x00081 => _ZN7_JNIEnv14CallVoidMethodEP8_jobjectP10_jmethodIDz
# 0x00082 => _Z15native_onCreateP7_JNIEnvP8_jobject

얼추 비슷하게 맞아보인다. 추후에 dex 파일을 열어서 어떤 내용이 있는지 확인해보면 좋을 것 같다.

Conclusion

다음에는 패킹된 dex 파일을 해제하는 작업을 해보고 어떤 내용이 있는지 확인해보겠다.

This post is licensed under CC BY 4.0 by the author.
If you find any errors, please let me know by comment or email. Thank you.

© Ruffalo. Some rights reserved.

I'm

Using the Chirpy theme for Jekyll.