Dynamic Code Loading in Android (feat.DexClassloader)
안드로이드 악성코드 분석 시, 원본 dex를 숨기기 위해 dex파일 동적로딩을 하는 경우가 많이 있습니다.
동적으로 Dex파일을 로드하기 위해선 파일 혹은 메모리에서 로드하는데 다음과 같은 함수를 사용하게 됩니다.
From file:
dalvik.system.DexFile.loadDex depreciated after API 26
dalvik.system.DexClassLoader
dalvik.system.PathClassLoader
From memory:
dalvik.system.InMemoryDexClassLoader (not common in malwares)
파일을 통한 동적로딩 중 DexClassLoader함수를 이용하여 동적로딩하는 코드 작성 및 분석 방법을 확인하겠습니다.
android developer를 통해 확인한 dexclassloader 함수 형식입니다.
아래는 dexclassloader를 이용하여 외부 apk의 dex를 로드하는 코드 예제입니다.
dexclassloader.java
public class MainActivity extends AppCompatActivity {
static final int BUF_SIZE = 8 * 1024;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button).setOnClickListener(
new Button.OnClickListener(){
public void onClick(View v){
String text = loadClass2();
Toast.makeText(getApplicationContext(),text,Toast.LENGTH_LONG).show();
}
});
}
private String loadClass2(){
String APPJS = "test.apk"; // 불러올 apk 이름 명시
File dexInternalStoragePath = new File(getDir("cache",Context.MODE_PRIVATE), APPJS);
BufferedInputStream bis = null;
OutputStream dexWriter = null;
try { // asset 폴더 내 저장된 apk파일을 앱 내 read가 가능한 영역으로 복사
// (/data/data/<app>/app_cache/)
bis = new BufferedInputStream(getAssets().open(APPJS));
dexWriter = new BufferedOutputStream(new FileOutputStream(dexInternalStoragePath));
byte[] buf = new byte[BUF_SIZE];
int len;
while ((len = bis.read(buf, 0, BUF_SIZE)) > 0) {
dexWriter.write(buf, 0, len);
}
dexWriter.close();
bis.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
final File optimizedDexOutputPath = getDir("cache",Context.MODE_PRIVATE); // 복사한 apk파일 경로 획득
DexClassLoader dexClassLoader = new DexClassLoader(dexInternalStoragePath.getAbsolutePath(),
optimizedDexOutputPath.getAbsolutePath(), null,getClassLoader()); //dex 파일 로드
try {
Class clazz = dexClassLoader.loadClass("com.example.user.test.test"); // dex 파일 내 class 로드
Object object = clazz.newInstance();
Method method = clazz.getMethod("get_Message"); //class 내 method 로드
String text = (String) method.invoke(object); //method 호출
return text;
}catch (Exception e){
e.printStackTrace();
}
return "Error..!!";
}
}
test.java(test.apk)
package com.example.user.flag;
public class test{
public String get_Message(){
return "Catch Me!";
}
}
test.java의 경우 앱 실행은 되지 않지만 빌드를 통해 apk파일 생성 및 dexclassloader.java가 있는 프로젝트 assets 파일 내 apk파일 복사
이렇게 작성된 앱의 경우, assets파일 내 apk파일이 존재하는 것을 모른다면 test.java의 내용을 알기 어렵습니다.
원본 Dex 추출을 위해 앱 실행 및 단말기 내 /proc/<pid>/maps를 확인하여 불러오는 dex파일의 위치를 확인합니다.
그 후 해당 위치(위 예제 앱의 경우 /data/data/<package_name>/app_cache/)에 복사한 dex파일이 존재합니다.
다만, 복사한 dex파일은 odex파일로 dex형식으로 변환하여 원본소스코드 확인이 가능합니다.