간만에 툴 소개를 좀 할까 합니다. 오늘 이야기드릴 툴은 .. Frida 입니다.
파이썬 기반의 라이브러리 + Command로 구성되어 있고 Native App에 대한 후킹을 통해 분석에 도움을 줄 수 있는 프로그램이죠.
What is Frida?
위에서 설명드렸듯이 Frida는 JS Injection을 이용하여 Windows, macOS, Linux, iOS, Android, and QNX 기반의 네이티앱에 대해 후킹이 가능한 파이썬 라이브러리입니다. 대표적으론 iOS, Android 등 모바일 분석 때문에 알려져 있지만 다른 플랫폼에서도 사용이 가능하기 때문에 확장적인 면에서 좋습니다.Frida는 Python 기반의 프로그램입니다. 물론 Core 부분은 C와 Google V8 Engine으로 작성됬지만 대체로 Python library를 많이 사용하지요. 이친구는 JS, C, Swift 등 여러 API를 지원하니 입맛에 맞게 개발해서 사용하시면 좋습니다.
Install frida library
먼저 frida를 설치합니다. 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만 설치해서 되는 문제가 아닙니다. 각 플랫폼에 연결되는 Agent 설치가 필요하죠.Android 디바이스 기준으로 이야기드리겠습니다. 먼저 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
편의를 위해 이름을 바꾸겠습니다. (넘길어)
#> 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 &"
혹시라도.. adb root 시 에러가 발생한다면.. 아래 trouble shotting 쪽 참고해주세요.
(요약하면 adb shell로 직접 들어가서 su 이후 작업해주시면 됩니다)
잘 실행되었나 볼까요?
#(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
pip를 통해 frida를 설치하면 python 라이브러리도 생기지만 command line 기반 프로그램도 생성됩니다.옵션을 대충 보면..
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 , -R , -U , -H 옵션으로 타겟을 지정해주고, -P 옵션으로 원하는 pid를 후킹합니다.
#> frida -D 192.168.0.74:5555
Usage: frida [options] target
frida: error: target file, process name or pid must be specified
#> 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...
간단하죠? 여기서 -D 옵션을 준 이유는.. adb를 원격으로 붙여놨기 때문이죠.
아닌 경우에 보통 -U 옵션(usb)이나 -R 옵션(remote, ip)으로 연결합니다.
Attach된 이후부터는 JS 코드로 제어/후킹이 가능해집니다. (마치 irb로 보는 것 처럼)
다만, 매번 하나하나 코드를 써가면서 테스트하기엔 어렵기 때문에 미리 Js 코드를 만들어두고 로드해서 사용합니다. (function 으로 만들어 필요할 떄 불러쓰거나 익명함수로 바로 실행되도록 해서 결과를 확인한다는 둥.. 여러가지 방법이 있겠네요)
추가로 frida는 몇가지 명령을 별도로 지원합니다.
1. frida-ps
이 명령은 frida-server를 통해 process list를 확인합니다. 직접 안들어가도 되요. Attach 시 패키지 이름? 앱 이름이라고 해야하나..아무튼 그걸로도 잡을 수 있긴 하지만, pid가 가장 확실하기 떄문에 frida-ps로 pid 확인 후 frida로 attac합니다. 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
#> 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
2. frida-trace
frida-trace는 함수 호출에 대해서 동적으로 추적해줍니다. 예를들면 옵션을 주어 앱을 모니터링하고 있을 때 해당 앱에서 발생하는 function에 대해 기록하고 보여준다는 이야기죠.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 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를 애용하며, 프리다 실행 후 대화형쉘에서 직접 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 받아서 처리해주심되요. 프리다 공식홈에 잘 나와있으니 참고해주세요~
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를 보시는게 좋을 것 같습니다.https://www.frida.re/docs/javascript-api
Frida CodeShare
보통은 분석하는 대상 앱에 따라 frida 코드를 작성해서 테스트하는데요, 서로 작성한 코드를 공유하는 사이트가 있습니다. frida에서 공식적으로 지원하는 웹 페이지이고 이를 이용하면 필요한 테스트를 조금 더 편하게 할 수 있습니다.https://codeshare.frida.re/browse
Frida 코드 작성법(?)에 대해 감 잡기도 좋은 것 같구요.
Frida 많이 쓰는 이유 중 하나가 SSL Pinning 때문이기도 한데요, 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
설치 과정 중 몇가지 에러 포인트가 있어습니다.1. 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이네요. 아... type 관련 문제는 대체로 컴파일단에서 발생하지만 ,
이번 케이스는 초기에 x86으로 잘못 받아서 에러가 났던것입니다.
frida-server-10.5.8-android-x86_64
arm 버전으로 받아서 실행하면 잘 됩니다.
2. adb root 불가 케이스(adbd cannot run as root in production builds)
에러 내용과 같이 adbd에서 root run을 지원하지 않는 경우입니다. 이런 경우 adb를 root run이 가능하게 다시 build 하거나 그냥 직접 adb shell로 접근하여 su를 통해 root로 변경 후 작업하시면 됩니다.Document
문서화가 잘 되어있습니다. 굿https://www.frida.re/docs/examples/android/
Reference
https://www.frida.re/docs/examples/android/HAHWULSecurity engineer, Gopher and H4cker! |
안녕하세요. 하울님.
ReplyDeletejscode 내의 데이터를 파이썬코드에서 받아서 쓸려면 어떻게 해야할까요?
저에게 익명으로 질문이라뇨.. frida 전문가이시면서!(이미 바로 해답도 찾으셨으면서)
Delete암튼 공유를 위해 남겨둡니다 :)
send(message[, data]):
send the JavaScript object message to your Frida-based application (it must be serializable to JSON). If you also have some raw binary data that you’d like to send along with it, e.g. you dumped some memory using Memory#readByteArray, then you may pass this through the optional data argument. This requires it to either be an ArrayBuffer or an array of integers between 0 and 255.
Cydia 쓰는 경우엔 Cydia에서 firda-server 설치하는게 더 깔끔한듯 합니다.
ReplyDeleteCydia > Source > https://build.frida.re 추가 후 frida package 설치
안녕하세요 정리해 주신글 잘읽었습니다.
ReplyDelete혹시 frida를 이용하여 함수를 후킹할때 함수 내부에 있는 변수를 변조하는 방법은 없나요?
인자값이랑 리턴값은 변조가 가능한걸 알지만 변수도 가능한지 궁금하네요
직접 바꾸는건 어려운걸로 알고 있어요. 객체의 value면 생성자쪽에서 건드리면 바꿀 순 있는데, 코드 없이 하는 경우엔 좀 번거로운 작업이 될 것 같아요.
Delete제가 잘못 알 수도 있으니 frida쪽 문서랑 구글링해보시는게 좋을 듯 싶습니다 :)
혹시 에뮬레이터 NOX 같은경우에 프리다서버 열고 어태치할경우에 ARM라이브러리인 어플같은경우엔 어떤방식으로 후킹하나요?
ReplyDeleteNox가 지원하는 선에선 앱 구동은 가능할거고, 그후에 후킹하는 방식은 동일할텐데요. 혹시 후킹 과정에 문제가 있는건가요?
Delete보통 앱 구동에 문제가 있지, 후킹에는 크게 문제가 없었어서요..