모바일 앱의 분석을 저해하기 위한 목적으로 중요 로직이나 소스코드를 보호하기 위해 사용하는 난독화 기술이다. 소스코드가 컴파일된 Original Dex 파일을 일반적인 경로(APK 구조의 루트 디렉터리에 존재하는 Classes.dex)가 아닌 다른 경로(App 내부 저장소, assets Dir등)에 저장해두고 앱이 실행될 때 Original Dex가 메모리에 로드되고 원본앱이 실행된다.
시스템 호출을 사용하여 파일에 대한 디렉터리 항목을 지우고 링크 개수를 감소시키는 함수
#include <unistd.h>
int unlink(const char *pathname);
이와 비슷한 함수로 remove가 있으며 remove 함수의 삭제 대상이 파일이면 unlink 함수와 동일하고 삭제 대상이 디렉터리면 rmdir과 동일하다.
/proc 디렉터리는 리눅스에서 사용되는 디렉토리이며 시스템의 프로세스 정보를 담고 있다.
따라서, 이 명령어를 통해 dex 파일이 어디에서 삭제 되었는지 경로를 확인할 수 있다.
cat /proc/[pid]/maps | grep appname
unlink 함수는 pathname 이름을 가진 파일을 지운다. 즉, 앱에서 동적으로 로딩되는 Dex를 삭제할때 사용하면 unlink 함수를 재정의해 frida로 후킹하면 동적으로 로딩된 dex 파일이 app 내부 저장소에 남게 된다. 따라서, 이를 통해 동적으로 로딩된 dex 파일을 추출해 분석을 할 수 있다.
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']));
}
코드 설명 :
(1) NativeFunction의 프로토타입은 NativeFunction(address, returnType, argTypes[, abi])로 address에서 함수를 호출할 새 NativeFunction을 재정의 한다. returnType은 반환유형을, argTypes는 인수 유형을 지정한다. 여기서는 unlink 함수 주소에 returnType은 void로 인수 유형은 int로 재정의하고 있다.
(2) Interceptor.replace(target, replacement[, data])은 open으로 재정의된 함수를 구현으로 대체하기 위해 사용한 함수이다. 즉, unlink의 함수를 기능을 콘솔에 문자열을 출력하는 기능으로 대체한다는 것이다.
vdex 파일로 추출된 경우 dex 파일로 추가적인 변환이 필요하다. 아래 github에서 소스코드를 다운받은 뒤 tools → deodex에 들어가 run.sh를 실행시키면 된다. 그러면 dex 파일을 추출할 수 있다.
dex 파일 추출 소스코드 : https://github.com/anestisb/vdexExtractor
참고자료) https://www.igloo.co.kr/security-information/%EC%95%85%EC%84%B1-apk%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-dynamic-dex-loading-%EB%B6%84%EC%84%9D/
https://www.hahwul.com/2015/09/08/system-hacking-procselfmaps/
https://jdh5202.tistory.com/900