최근 들어 프리다를 탐지하여 앱을 종료시키는 솔루션들이 꽤 많습니다. 이렇게 되면, 진단하는 입장에서는 애를 먹을 수 밖에 없습니다.
그래서, AOS에서 몇몇 기본적인 프리다 탐지원리 및 우회방안을 정리해봤습니다.
(1) /proc/<pid>/maps
프리다가 앱 프로세스에 attach 하게 되면, 다음 그림과 같이 /proc/<pid>/maps 파일에 frida-agent native library 정보가 표시됩니다.
/proc/<pid>/maps 파일을 읽어들이기 위해서는 파일 오픈이 먼저 필요하므로, "fopen" 함수를 후킹하여 "/proc/self/maps" 문자열을 "/proc/self/statm" 등으로 바꿔치기하여 우회가 가능합니다.
(2) /proc/<pid>/fd/<No>
"/proc/<pid>/fd/" 디렉터리에서는 심볼릭링크에 대한 정보를 볼 수 있습니다.
프리다가 앱 프로세스에 attach 하게 되면, "linjector" 라는 파일로 연결되는 특이한 심볼릭링크가 하나 생성되는데, 이는 프리다 탐지의 실마리를 제공합니다.
lstat 함수는 심볼릭링크 파일의 원본파일에 대한 정보를 구조체에 저장합니다. 따라서, 위 그림의 경우 lstat 함수가 원본파일이 "linjector-1121"인 심볼릭링크 파일("/proc/31302/fd/557")을 읽지 못하도록 하면 우회가 가능합니다.
다음은 "/proc/<pid>/maps" 파일 및 "/proc/<pid>/fd/<No>" 파일을 읽어들여 프리다를 탐지하는 로직을 우회하는 예시 스크립트입니다.
function nativeTrace(nativefunc) {
var readlinkPtr = Module.getExportByName("libc.so", "readlink");
var readlink = new NativeFunction(readlinkPtr, 'int', ['pointer','pointer','int']);
var nativefunc_addr = Module.getExportByName(null,nativefunc)
var func = ptr(nativefunc_addr);
Interceptor.attach(func, { // set hook
onEnter: function (args) {
console.warn("\n[+] " + nativefunc + " called"); // before call
// /proc/self/fd/<NO> 파일들은 모두 symlink 파일들인데, readlink 함수를 이용해 original file path를 가져올 수 있고, 그 중에 frida attach 로 인한 "linjector" 문자열이 포함되어 있는지 확인하고, 포함되어 있다면 해당 인자를 변경
if(nativefunc == "lstat" && args[0].readUtf8String().match(/\/proc.*fd.*/)) {
// readlink 함수를 이용한 original file path 가져오기
var buf = Memory.alloc(64);
readlink(args[0], buf, 64);
console.log(ptr(buf).readUtf8String());
// original file path에 "linjector" 문자열이 포함되어 있는 경우 args[0] 인자를 "/proc/self/fd/1"로 변경
if(ptr(buf).readUtf8String().indexOf("linjector") > -1){
args[0] = args[0].writeUtf8String("/proc/self/fd/1");
console.log(args[0].readUtf8String());
}
}
else if(nativefunc == "fopen"){
// fopen 함수가 "/proc/self/maps" 파일을 열려고 한다면, args[0]인자를 "proc/self/statm"으로 변경
if(args[0].readUtf8String() == "/proc/self/maps"){
args[0] = args[0].writeUtf8String("/proc/self/statm");
}
console.log("\n\x1b[31margs[0]:\x1b[0m \x1b[34m" + args[0].readUtf8String() + ", \x1b[32mType: ");
}
},
onLeave: function (retval) {
console.warn("[-] " + nativefunc + " Exiting");
}
});
}
nativeTrace("lstat");
nativeTrace("fopen");
(3) /proc/<pid>/task/<thread id>/status
"/proc/<pid>/task/<thread id>/status" 파일에서는 특정 thread 에 대한 정보를 볼 수 있습니다.
프리다가 앱 프로세스에 attach 하게 되면, 다음과 같이 "gmain" 또는 "gum-js-loop" 문자열을 포함하는 프리다 특유의 thread가 생성됩니다.
해당 로직은 아직 접해보지는 않았지만 이 역시 fopen 함수 후킹을 통한 인자 바꿔치기로 우회가 가능할 것으로 생각됩니다.
(4) Iterate through all the TCP open ports and pass D-Bus AUTH message to see if frida-server responds
모든 열린 TCP 포트에 D-Bus AUTH 메세지를 보내서 응답값을 관찰하여 프리다 서버 구동여부를 탐지하는 로직입니다. 해당 탐지로직 예시 코드는 "https://github.com/b-mueller/frida-detection-demo/blob/master/AntiFrida/app/src/main/cpp/native-lib.cpp" 에서 확인하실 수 있습니다.
다음은 위 깃헙의 예시코드를 aarch64로 빌드한 프리다 탐지 실행파일입니다.
frida server를 구동한 상태에서 위 실행파일을 실행하게 되면 다음과 같이 "REJECT" 메세지를 볼 수 있습니다.
해당 로직은 아직 접해보지는 않았지만 "connect" 또는 "send" 또는 "recv" 메서드를 후킹해서 우회 가능할 것으로 판단됩니다.
※ 참고
https://darvincitech.wordpress.com/2019/12/23/detect-frida-for-android/
https://www.romainthomas.fr/post/20-09-r2con-obfuscated-whitebox-part1/
'Information Security > Android' 카테고리의 다른 글
Android 10 SSH for Magisk (0) | 2021.05.19 |
---|---|
gdb 단말기에서 실행 (0) | 2021.05.10 |
Index was outside the bounds of the array 에러 해결(frida-il2cpp-bridge) (3) | 2021.04.23 |
삼성 펌웨어 다운로드 (1) | 2021.02.02 |
Android SSL Pinning Bypass (0) | 2020.12.31 |