8/31/2017

[HACKING] Frida를 이용한 멀티 플랫폼 후킹(Hooking to multi platform with Frida / Android / iOS / Other..)


간만에 툴 소개를 좀 할까 합니다. 오늘 이야기드릴 툴은 .. 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/


HAHWUL

Security engineer, Gopher and H4cker!

Share: | Coffee Me:

7 comments:

  1. 안녕하세요. 하울님.
    jscode 내의 데이터를 파이썬코드에서 받아서 쓸려면 어떻게 해야할까요?

    ReplyDelete
    Replies
    1. 저에게 익명으로 질문이라뇨.. frida 전문가이시면서!(이미 바로 해답도 찾으셨으면서)
      암튼 공유를 위해 남겨둡니다 :)

      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.

      Delete
  2. Cydia 쓰는 경우엔 Cydia에서 firda-server 설치하는게 더 깔끔한듯 합니다.

    Cydia > Source > https://build.frida.re 추가 후 frida package 설치

    ReplyDelete
  3. 안녕하세요 정리해 주신글 잘읽었습니다.
    혹시 frida를 이용하여 함수를 후킹할때 함수 내부에 있는 변수를 변조하는 방법은 없나요?
    인자값이랑 리턴값은 변조가 가능한걸 알지만 변수도 가능한지 궁금하네요

    ReplyDelete
    Replies
    1. 직접 바꾸는건 어려운걸로 알고 있어요. 객체의 value면 생성자쪽에서 건드리면 바꿀 순 있는데, 코드 없이 하는 경우엔 좀 번거로운 작업이 될 것 같아요.
      제가 잘못 알 수도 있으니 frida쪽 문서랑 구글링해보시는게 좋을 듯 싶습니다 :)

      Delete
  4. 혹시 에뮬레이터 NOX 같은경우에 프리다서버 열고 어태치할경우에 ARM라이브러리인 어플같은경우엔 어떤방식으로 후킹하나요?

    ReplyDelete
    Replies
    1. Nox가 지원하는 선에선 앱 구동은 가능할거고, 그후에 후킹하는 방식은 동일할텐데요. 혹시 후킹 과정에 문제가 있는건가요?
      보통 앱 구동에 문제가 있지, 후킹에는 크게 문제가 없었어서요..

      Delete