Frida를 소개합니다! 멀티 플랫폼 후킹을 위한 가장 강력한 도구 😎
간만에 툴 소개를 좀 할까 합니다. 오늘 이야기드릴 툴은 Frida 입니다. 파이썬 기반의 라이브러리와 Command로 구성되어 있고 Native App에 대한 후킹을 통해 동적 분석을 진행할 수 있는 도구이죠.
What is Frida?
Frida는 JS Injection을 이용하여 Windows, macOS, Linux, iOS, Android, and QNX 기반의 네이티앱에 대해 후킹이 가능한 파이썬 라이브러리입니다. 대표적으로 iOS, Android 등 모바일 분석 때문에 알려져 있지만 다른 플랫폼에서도 사용이 가능하기 때문에 확장적인 면에서 좋습니다.
물론 Core 부분은 C와 Google V8 Engine으로 작성 되었지만 대다수 구현체는 python입니다. 또한 js, c, swift 등 여러 언어에 대한 API를 지원하니 입맛에 맞게 추가로 개발해서 사용하시면 좋습니다.
Installation
python package로 제공되고 있어 pip를 통해 설치가 가능합니다.
pip install frida
Collecting frida
Downloading frida-10.5.8.tar.gz
Requirement already satisfied: colorama>=0.2.7 in /usr/local/lib/python2.7/dist-packages/colorama-0.3.9-py2.7.egg (from frida)
Collecting prompt-toolkit>=0.57 (from frida)
Downloading prompt_toolkit-1.0.15-py2-none-any.whl (247kB)
100% |████████████████████████████████| 256kB 1.2MB/s
Requirement already satisfied: pygments>=2.0.2 in /usr/lib/python2.7/dist-packages (from frida)
Requirement already satisfied: six>=1.9.0 in /usr/lib/python2.7/dist-packages (from prompt-toolkit>=0.57->frida)
Requirement already satisfied: wcwidth in /usr/local/lib/python2.7/dist-packages/wcwidth-0.1.7-py2.7.egg (from prompt-toolkit>=0.57->frida)
Building wheels for collected packages: frida
Running setup.py bdist_wheel for frida ... \
Download & Setting frida server(android)
실제 사용을 위해선 frida를 분석할 pc에 설치한 이후에 각 디바이스에도 firda-server 설치가 필요합니다.
- https://github.com/frida/frida/releases
릴리즈 페이지에서 frida server를 다운로드합니다. 여기서 frida-server는 각 플랫폼과 비트별로 버전이 있고 환경에 맞게 받아서 사용하시면 됩니다.
안드로이드의 경우 .xz
로 묶여있습니다. 풀어주세요!
ls
frida-server-10.5.8-android-arm.xz
unxz frida-server-10.5.8-android-arm.xz
ll
합계 45560
drwxr-xr-x 2 hahwul hahwul 4096 8월 31 22:08 ./
drwxr-xr-x 67 hahwul hahwul 4096 8월 31 21:33 ../
-rw-rw-r-- 1 hahwul hahwul 46650120 8월 31 22:08 frida-server-10.5.8-android-arm
저는 편의를 위해 이름을 바꿨습니다. 다만 시간이 지나면 분명 frida 버전을 계속 올리시야 할테고 이 때 버전명이 없으면 보기 불편하기 떄문에 가급적이면 그냥 사용하시는 것을 더 추천드려요!
cp frida-server-10.5.8-android-arm frida-server
adb로 안드로이드 폰에 연결한 후 frida-server를 폰에 넣어 실행해줍니다.
adb connect 192.168.0.74
* daemon not running. starting it now on port 5037 *
* daemon started successfully *
connected to 192.168.0.74:5555
push로 넣어주고, 권한 설정 후. 실행해 줍니다. 물론 이 과정은 root 권한으로 진행이 필요합니다.
adb root
adb push frida-server /data/local/tmp
855 KB/s (46650120 bytes in 53.268s)
adb shell "chmod 777 /data/local/tmp/frida-server"
adb shell "/data/local/tmp/frida-server &"
이제 frida-server가 실행된걸 확인해보면..
#(android) ps | grep server
drm 331 1 29452 1660 ffffffff b6f0b090 S /system/bin/drmserver
media 332 1 199940 4884 ffffffff b6eb6090 S /system/bin/mediaserver
media 370 1 42412 1380 ffffffff b6f79090 S /system/bin/dmbserver
system 881 366 2226688 110888 ffffffff b6ef79c8 S system_server
system 1315 1 7192 668 ffffffff b6ef0090 S /system/bin/tlc_server
system 1316 1 7192 664 ffffffff b6eec090 S /system/bin/tlc_server
radio 1683 366 1832928 16752 ffffffff b6ef79c8 S com.android.server.telecom
root 20932 20777 39852 28996 ffffffff b5dcff84 S ./frida-server
맨 아래 20932번으로 잘 돌아가고 있네요.
Frida command
frida cli는 frida-server와 연결하여 앱을 후킹하고 원하는 동작을 수행하는데 있어서 필수인 도구입니다. 옵션을 보면 아래와 같습니다.
Usage: frida [options] target
Options:
--version show program's version number and exit
-h, --help show this help message and exit
-D ID, --device=ID connect to device with the given ID
-U, --usb connect to USB device
-R, --remote connect to remote frida-server
-H HOST, --host=HOST connect to remote frida-server on HOST
-f FILE, --file=FILE spawn FILE
-n NAME, --attach-name=NAME
attach to NAME
-p PID, --attach-pid=PID
attach to PID
--debug enable the Node.js compatible script debugger
--enable-jit enable JIT
-l SCRIPT, --load=SCRIPT
load SCRIPT
-c CODESHARE_URI, --codeshare=CODESHARE_URI
load CODESHARE_URI
-e CODE, --eval=CODE evaluate CODE
-q quiet mode (no prompt) and quit after -l and -e
--no-pause automatically start main thread after startup
-o LOGFILE, --output=LOGFILE
output to log file
-D
(Device) , -R
(Remote) , -U
(USB) , -H
(Host) 옵션으로 타겟을 지정해주고, -P
옵션으로 원하는 pid를 후킹합니다.
frida -D 192.168.0.74:5555 -p 2011
____
/ _ | Frida 10.5.8 - A world-class dynamic instrumentation framework
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at http://www.frida.re/docs/home/
Attaching...
Attach된 이후부터는 JS 코드로 후킹이 가능해집니다. 다만, 매번 하나하나 코드를 써가면서 테스트 하기엔 어렵기 때문에 미리 Js 코드를 만들어두고 로드하시는게 편합니다. (function 으로 만들어 필요할 떄 불러쓰거나 익명함수로 바로 실행되도록 해서 결과를 확인한다는 둥.. 여러가지 방법이 있겠네요)
추가로 frida는 몇가지 명령을 별도로 지원합니다.
frida-ps
frida-ps는 frida-server를 통해 process list를 확인합니다. 직접 디바이스에 접근하지 않아도 pid를 미리 확인할 수 있어 빠르게 대상을 식별할 수 있습니다.
Usage
Usage: frida-ps [options]
Options:
--version show program's version number and exit
-h, --help show this help message and exit
-D ID, --device=ID connect to device with the given ID
-U, --usb connect to USB device
-R, --remote connect to remote frida-server
-H HOST, --host=HOST connect to remote frida-server on HOST
-a, --applications list only applications
-i, --installed include all installed applications
Example
frida-ps -D "192.168.0.74:5555"
PID Name
----- --------------------------------------------------
23814 Com.sktelecom.minit
8586 adbd
363 adsprpcd
1813 android.process.acore
621 androidshmservice
333 apaservice
373 at_distributor
2391 auditd
365 bintvoutservice
376 cnd
380 cnss-daemon
6669 com.ahnlab.v3mobilesecurity.soda
3942 com.android.bluetooth
frida-trace
frida-trace는 함수 호출에 대해서 동적으로 추적해 줍니다. 예를들면 옵션을 주어 앱을 모니터링하고 있을 때 해당 앱에서 발생하는 function에 대해 기록하고 사용자에게 제공합니다.
Usage
Usage: frida-trace [options] target
Options:
--version show program's version number and exit
-h, --help show this help message and exit
-D ID, --device=ID connect to device with the given ID
-U, --usb connect to USB device
-R, --remote connect to remote frida-server
-H HOST, --host=HOST connect to remote frida-server on HOST
-f FILE, --file=FILE spawn FILE
-n NAME, --attach-name=NAME
attach to NAME
-p PID, --attach-pid=PID
attach to PID
--debug enable the Node.js compatible script debugger
--enable-jit enable JIT
-I MODULE, --include-module=MODULE
include MODULE
-X MODULE, --exclude-module=MODULE
exclude MODULE
-i FUNCTION, --include=FUNCTION
include FUNCTION
-x FUNCTION, --exclude=FUNCTION
exclude FUNCTION
-a MODULE!OFFSET, --add=MODULE!OFFSET
add MODULE!OFFSET
-T, --include-imports
include program's imports
-t MODULE, --include-module-imports=MODULE
include MODULE imports
-m OBJC_METHOD, --include-objc-method=OBJC_METHOD
include OBJC_METHOD
Example
frida-trace -D "192.168.0.74:5555" -i "Func 패턴"
Uploading data...
open: Auto-generated handler …/linker/open.js
open: Auto-generated handler …/libc.so/open.js
Frida in Console & Code
Frida를 사용하는 방법은 크게 2가지가 있습니다. 하나는 Console mode, 하나는 Code에서 라이브러리르 불러서 사용하는 형태입니다. 개인의 취향에 따라 다르지만 저의 경우는 Console를 애용하며, 프리다 실행 후 Interactive Shell에서 직접 Javascript 구문으로 후킹을 진행합니다.
#> frida -U "앱"
Android / iOS, PC 들 공통적으로 훅을 걸 포인트를 찾는게 가장 중요합니다. 코드를 볼 수 있는 환경이면 코드로 걸어서 바로 진입하고, 블랙박스 테스팅의 경우 리버싱이나 추가적인 분석으로 봐야할 함수의 위치를 찾아야하죠.
var hook = ObjC.classes.YourClass["- yourFunction"]
Interceptor.attach(hook.implementation, {onload(args){ console.log('gogogogo') }});
그다음 Interceptor로 attach 하거나 replace 등으로 로직을 바꿔주시면 기존 동작과 다르게 앱을 동작시킬 수 있습니다. 여기서 발생하는 이벤트는 javascript의 이벤트와 동일하므로 onload, onleave 받아서 처리해주심되요. 프리다 공식홈에 잘 나와있으니 참고해주세요 :D
python에서 로드하는 경우
import frida, sys
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
jscode = """
Java.perform(function () {
var MainActivity = Java.use('com.yourapp.MainActivity');
MainActivity.onClick.implementation = function (v) {
send('onClick');
this.onClick(v);
this.m.value = 0;
this.n.value = 1;
this.cnt.value = 999;
// Log to the console that it's done, and we should have the flag!
console.log('Done:' + JSON.stringify(this.cnt));
};
});
"""
process = frida.get_usb_device().attach('com.your.app')
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
sys.stdin.read()
자세한 내용은 API Reference를 보시는게 좋을 것 같습니다.
Frida CodeShare
보통은 분석하는 대상 앱에 따라 frida 코드를 작성해서 테스트 하는데요, 서로 작성한 코드를 공유하는 사이트가 있습니다. frida에서 공식적으로 지원하는 웹 서비스고 이를 이용하면 미리 만들어진 코드를 통해 필요한 테스트를 조금 더 편하게 할 수 있습니다.
- https://codeshare.frida.re/browse
Frida 코드 작성법(?)에 대해 감 잡기도 좋은 것 같구요. Frida 많이 쓰는 이유 중 하나가 SSL Unpinning 때문이기도 한데요, codeshare에 Pinning 우회 코드가 있습니다. 참고하셔서 필요한 부분은 수정해서 쓰시면 됩니다. (동작 방식은 키 스토어를 디바이스껄로 바라보도록 로직을 처리하는 형태입니다)
- https://codeshare.frida.re/@pcipolloni/universal-android-ssl-pinning-bypass-with-frida/
setTimeout(function(){
Java.perform(function (){
console.log("");
console.log("[.] Cert Pinning Bypass/Re-Pinning");
var CertificateFactory = Java.use("java.security.cert.CertificateFactory");
var FileInputStream = Java.use("java.io.FileInputStream");
var BufferedInputStream = Java.use("java.io.BufferedInputStream");
var X509Certificate = Java.use("java.security.cert.X509Certificate");
var KeyStore = Java.use("java.security.KeyStore");
var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory");
var SSLContext = Java.use("javax.net.ssl.SSLContext");
// Load CAs from an InputStream
console.log("[+] Loading our CA...")
cf = CertificateFactory.getInstance("X.509");
try {
var fileInputStream = FileInputStream.$new("/data/local/tmp/cert-der.crt");
}
catch(err) {
console.log("[o] " + err);
}
var bufferedInputStream = BufferedInputStream.$new(fileInputStream);
var ca = cf.generateCertificate(bufferedInputStream);
bufferedInputStream.close();
var certInfo = Java.cast(ca, X509Certificate);
console.log("[o] Our CA Info: " + certInfo.getSubjectDN());
// Create a KeyStore containing our trusted CAs
console.log("[+] Creating a KeyStore for our CA...");
var keyStoreType = KeyStore.getDefaultType();
var keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
console.log("[+] Creating a TrustManager that trusts the CA in our KeyStore...");
var tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
var tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
console.log("[+] Our TrustManager is ready...");
console.log("[+] Hijacking SSLContext methods now...")
console.log("[-] Waiting for the app to invoke SSLContext.init()...")
SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").implementation = function(a,b,c) {
console.log("[o] App invoked javax.net.ssl.SSLContext.init...");
SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").call(this, a, tmf.getTrustManagers(), c);
console.log("[+] SSLContext initialized with our custom TrustManager!");
}
});
},0);
기능에 따라 코드가 많이질수도 있는데, 다행히 Frida에서 codeshare를 바로 불러와 사용하는 기능을 제공합니다. 바로 --codeshare
옵션으로 업로더/이름 형태로 불러와 사용할 수 있습니다. 위의 코드로 예를들면 아래 형태로 바로 로드하여 실행이 가능하겠죠.
frida -U --codeshare pcipolloni/universal-android-ssl-pinning-bypass-with-frida
Troubleshot
설치 과정 중 몇가지 에러 포인트가 있어서 메모해둡니다.
Error - not executable: magic 7F4
Android에서 frida-server실행 시 “not executable: magic 7F4” 메시지 발생하는 경우 보통 아키텍쳐 문제입니다. readelf 등으로 파일 헤더를 확인해보시면 왜 실행할 수 없는 파일인지 아실 수 있을겁니다.
readelf --file-header --arch-specific frida-server
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x7beb0
Start of program headers: 64 (bytes into file)
Start of section headers: 46648520 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 25
Section header string table index: 24
제 케이스를 보면 type이 DYN이네요. 초기에 x86으로 잘못 받아서 에러가 났던것입니다. frida-server-10.5.8-android-x86_64
arm 버전으로 받아서 실행하면 잘 됩니다.
adbd cannot run as root in production builds
에러 내용과 같이 adbd에서 root run을 지원하지 않는 경우입니다. 이런 경우 adb를 root run이 가능하게 다시 build 하거나 그냥 직접 adb shell로 접근하여 su를 통해 root로 변경 후 작업하시면 됩니다.
Reference
- https://www.frida.re/docs/examples/android/