안드로이드 악성코드 분석 시, 원본 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형식으로 변환하여 원본소스코드 확인이 가능합니다.
첫 화면이고, 여러 문제를 볼 수 있는데 Login Method 1 / Check For Jailbreak / Show alert / Kill Application이 있지만 Login Method 1만 풀이를 올리도록 하겠습니다. 사실 하나만 풀면 나머지도 다 풀 수 있는거라.. ;)
IDA를 통해 login 문자열이 들어간 함수를 확인한 결과 ApplicationPatchingDetailsVC loginMethod1Tapped:를 확인할 수 있었습니다.
사실 소스코드를 좀만 살펴보면 소스에 ID/PW가 노출되어 있지만, 목적은 이게 아니니까 넘어가서 비교 후 분기문이나 인증 성공 함수 호출하는 부분을 찾아보겠습니다.
해당 함수의 끝 부분에 분기문을 통해 인증 결과값으로 성공 혹은 실패 로직을 실행하고 있습니다.
그럼 분기문(B.EQ loc_100170C58)을 변조하여 인증실패일 경우 SuccessPage를 호출하는 것으로 Binary를 패치하겠습니다.
이 페이지에서 opcode의 hex값을 확인 할 수 있는데, BEQ는 000100, BNE는 000101인 것을 확인 할 수 있습니다.