3. Frida-gum - binding

3. Frida-gum - binding

2024년 12월 24일

참고 #


frida-gum/bindings #

Repo: https://github.dev/frida/frida-gum/tree/16.5.6
Version: 16.5.6

네이티브 후킹을 할 수 있는 API를 구현한 것이 frida-gum이며, c로 구현되어 있는 frida-gum 함수들을 cpp나 js 코드로 호출할 수 있도록 바인딩한 것이 frida-gum/bindings 폴더의 gumjs, gumpp 이다.

devkit? #

아래에서 gumjs, gumpp 를 빌드하게 될건데, gumpp에는 devkit이 없기도 하고 devkit과 일반 라이브러리의 사이즈도 아주많이 차이나서 확실히 하기위해 정리한다.

옵션 없이 빌드한 frida-gum 라이브러리는 실제 frida-gum에 해당하는 코드만 포함되어 있는 라이브러리이고, devkit/frida-gum 라이브러리는 frida-gum을 사용하기 위한 모든 라이브러리가 정적으로 링크되어 있다.

그렇기 때문에 frida-gum/devkit 라이브러리는 그 자체만으로도 사용이 가능하고, libfrida-gum-1.0.a 라이브러리는 직접 필요한 라이브러리들을 포함시켜줘야 한다.

빌드된 파일 #

devkit이 훨씬 용량이 큰 것을 알 수 있다.

1-rw-r--r-- 1 kdh kdh 6265834 Feb 11 13:40 ./build/gum/libfrida-gum-1.0.a
2-rw-r--r-- 1 kdh kdh 93054678 Feb 11 13:40 ./build/gum/devkit/libfrida-gum.a
3-rw-r--r-- 1 kdh kdh 493228 Feb 11 19:00 ./build/libs/gum/heap/libfrida-gum-heap-1.0.a
4-rw-r--r-- 1 kdh kdh 246206 Feb 11 19:00 ./build/libs/gum/prof/libfrida-gum-prof-1.0.a
5
6-rwxr-xr-x 1 kdh kdh 18539312 Feb 11 19:00 ./build/bindings/gumpp/libfrida-gumpp-1.0.so
7
8-rw-r--r-- 1 kdh kdh 26806536 Feb 11 19:03 ./build/bindings/gumjs/libfrida-gumjs-1.0.a
9-rw-r--r-- 1 kdh kdh 204100202 Feb 11 19:03 ./build/bindings/gumjs/devkit/libfrida-gumjs.a

왜 libfrida-gumpp-1.0.so 만 동적 라이브러리인지 모르겠지만 devkit 라이브러리처럼 이 gumpp 라이브러리만 사용해서 컴파일할 수 있다. fef3ed6a-35f6-4e41-80bc-ce35eaa1513e


gumpp #

원래 c로만 작성되어 있는 frida-gum 을 c++ 코드를 이용해서 호출할 수 있도록 래핑한 코드들이다.
c++로 래핑하기 위해 여러 클래스를 정의해서 메모리 자동 관리(RAII)를 지원하거나 객체 단위의 직관적인 코드를 작성할 수 있게 해서 코드의 가독성을 향상시켰다.

간단한 사용 예제 #

예시의 코드는 main 에서 자기 자신의 함수를 후킹하도록 구현했지만, 동적 라이브러리로 빌드하고 Injection 하는게 가장 많이 사용되는 패턴일 것이다.

gumpp는 frida-gum/bindings/gumpp 의 소스코드를 가져와서 gumpp.hpp 를 include 후 원하는 소스코드를 함께 작성하고 frida-gum devkit과 함께 빌드하는 식으로 사용할 수 있고,
frida-gum을 빌드할때 --enable-gumpp 옵션을 주면 build/bindings/gumpp/libfrida-gumpp-1.0.so 파일이 빌드되는데, frida-gum + gumpp 이 포함된 동적 라이브러리이기 때문에 header만 추가해서 추가해서 사용하면 된다.

 1$ ./configure --enable-gumpp
 2$ make
 3
 4$ c++ gumpp_test.cpp -I./frida-gum/bindings/gumpp/ -L./frida-gum/build/bindings/gumpp/ -lfrida-gumpp-1.0
 5$ export LD_LIBRARY_PATH=~/frida-gum/build/bindings/gumpp/:$LD_LIBRARY_PATH
 6$ ./a.out
 7[test_native] test execve
 8[before call] 3
 9[test_native] on_enter
10123
11[test_native] on_leave
12[after call]  0

참고 코드: actboy168/lua-debug/test/interceptor.cpp

 1#include <gumpp.hpp>
 2#include <iostream>
 3
 4#define LOG(fmt, ...) printf("[test_native] " fmt "\n", ##__VA_ARGS__)
 5
 6void test_add(int a, int b) {
 7    std::cout << a << b << a + b << std::endl;
 8}
 9
10// 후킹이 잘 됐는지 확인하기 위한 값
11static int on_enter = 1;
12static int on_leave = 2;
13
14// Gum::InvocationListener 를 상속받아서 on_enter, on_leave 를 구현한다. 
15class MyInvocationListener : public Gum::InvocationListener {
16public:
17    ~MyInvocationListener () {}
18
19    void on_enter (Gum::InvocationContext * context) override {
20        LOG("on_enter");
21        ::on_enter = 0;    // 호출됐으면 정적 변수를 0으로 변경
22    };
23    void on_leave (Gum::InvocationContext * context) override {
24        LOG("on_leave");
25        ::on_leave = 0;
26    };
27};
28
29int main(int argc, char *argv[]) {
30    LOG("test execve");
31    Gum::RefPtr<Gum::Interceptor> interceptor (Gum::Interceptor_obtain ());
32    MyInvocationListener listener;
33
34    // 테스트 함수에 attach
35    interceptor->attach((void*)&test_add, &listener, 0);
36
37    std::cout << "[before call] " << ::on_enter + ::on_leave << std::endl;
38    test_add(1, 2);
39    std::cout << "[after call]  " << ::on_enter + ::on_leave << std::endl;
40    return 0;
41}

gumpp.hpp (주요 추상클래스들) #

참조 카운트를 관리할 수 있도록 Object 클래스를 만들어서 모든 오브젝트가 상속받는 구조로 구현되고, RefPtr 템플릿 클래스로 스마트포인터를 구현했다. (일부 메서드만 가져옴)

 1namespace Gum
 2{
 3  struct InvocationContext;
 4  struct InvocationListener;
 5  struct CpuContext;
 6  struct ReturnAddressArray;
 7
 8  struct Object
 9  {
10    virtual ~Object () {}
11
12    virtual void ref () = 0;     // 참조카운트 증가
13    virtual void unref () = 0;   // 참조카운트 감소
14    virtual void * get_handle () const = 0;
15  };
16
17  template <typename T> class RefPtr
18  {
19  public:
20    explicit RefPtr (T * ptr_) : ptr (ptr_) {}
21    explicit RefPtr (const RefPtr<T> & other) : ptr (other.ptr)
22    {
23      if (ptr)
24        ptr->ref ();
25    }
26
27    template <class U> RefPtr (const RefPtr<U> & other) : ptr (other.operator->())
28    {
29      if (ptr)
30        ptr->ref ();
31    }
32
33    RefPtr () : ptr (0) {}
34
35    ~RefPtr ()
36    { if (ptr) ptr->unref (); }
37
38// 일반 포인터처럼 사용할 수 있도록 오버로딩 
39    T * operator-> () const
40    { return ptr; }
41
42    T & operator* () const
43    { return *ptr; }
44// 형변환
45    operator T * ()
46    { return ptr; }
47
48  private:
49    T * ptr;
50  };

Interceptor를 바인딩한 클래스이며, 함수 후킹 attach, detach, replace 를 c++ 형태로 호출할 수 있게 해준다.

 1  struct Interceptor : public Object
 2  {
 3    virtual bool attach (void * function_address, InvocationListener * listener, void * listener_function_data = 0) = 0;
 4    virtual void detach (InvocationListener * listener) = 0;
 5
 6    virtual void replace (void * function_address, void * replacement_address, void * replacement_data = 0) = 0;
 7    virtual void revert (void * function_address) = 0;
 8
 9    virtual void begin_transaction () = 0;
10    virtual void end_transaction () = 0;
11
12    virtual InvocationContext * get_current_invocation () = 0;
13
14    virtual void ignore_current_thread () = 0;
15    virtual void unignore_current_thread () = 0;
16
17    virtual void ignore_other_threads () = 0;
18    virtual void unignore_other_threads () = 0;
19  };
20
21// Interceptor를 생성하고 리턴하는 함수이다.
22  GUMPP_CAPI Interceptor * Interceptor_obtain (void);
23// 실제 구현. Interceptor 구현체를 생성 후 리턴한다. 
24// extern "C" Interceptor * Interceptor_obtain (void) { return new InterceptorImpl; }

후킹 컨텍스트 클래스이며, 리스너의 인자로 전달되고 c++ 코드로 인자나 리턴값을 얻어오거나 변경하며 cpu 컨텍스트 등을 가져올 수 있는 메서드를 제공한다.

 1  struct InvocationContext
 2  {
 3    virtual ~InvocationContext () {}
 4
 5    virtual void * get_function () const = 0;
 6
 7    template <typename T>
 8    T get_nth_argument (unsigned int n) const
 9    {
10      return static_cast<T> (get_nth_argument_ptr (n));
11    }
12    virtual void * get_nth_argument_ptr (unsigned int n) const = 0;
13    virtual void replace_nth_argument (unsigned int n, void * value) = 0;
14    template <typename T>
15    T get_return_value () const
16    {
17      return static_cast<T> (get_return_value_ptr ());
18    }
19    virtual void * get_return_value_ptr () const = 0;
20
21    virtual unsigned int get_thread_id () const = 0;
22
23    template <typename T>
24    T * get_listener_thread_data () const
25    {
26      return static_cast<T *> (get_listener_thread_data_ptr (sizeof (T)));
27    }
28    virtual void * get_listener_thread_data_ptr (size_t required_size) const = 0;
29    template <typename T>
30    T * get_listener_function_data () const
31    {
32      return static_cast<T *> (get_listener_function_data_ptr ());
33    }
34    virtual void * get_listener_function_data_ptr () const = 0;
35    template <typename T>
36    T * get_listener_invocation_data () const
37    {
38      return static_cast<T *> (get_listener_invocation_data_ptr (sizeof (T)));
39    }
40    virtual void * get_listener_invocation_data_ptr (size_t required_size) const = 0;
41
42    template <typename T>
43    T * get_replacement_data () const
44    {
45      return static_cast<T *> (get_replacement_data_ptr ());
46    }
47    virtual void * get_replacement_data_ptr () const = 0;
48
49    virtual CpuContext * get_cpu_context () const = 0;
50  };

리스너 한쌍을 구현할 수 있는 클래스이다.

1  struct InvocationListener
2  {
3    virtual ~InvocationListener () {}
4
5    virtual void on_enter (InvocationContext * context) = 0;
6    virtual void on_leave (InvocationContext * context) = 0;
7  };

현재 함수의 호출 스택을 생성하는 Backtracer 클래스이다. make 함수들은 그냥 frida-gum c api의 gum_backtracer_make_ 함수들을 호출하면서 객체만 관리해준다.

 1  struct Backtracer : public Object
 2  {
 3    virtual void generate (const CpuContext * cpu_context, ReturnAddressArray & return_addresses) const = 0;
 4  };
 5
 6  GUMPP_CAPI Backtracer * Backtracer_make_accurate ();
 7  GUMPP_CAPI Backtracer * Backtracer_make_fuzzy ();
 8
 9  typedef void * ReturnAddress;
10
11  struct ReturnAddressArray
12  {
13    unsigned int len;
14    ReturnAddress items[GUMPP_MAX_BACKTRACE_DEPTH];
15  };

특정 리턴주소(void*) 에 대해 상세 디버그 정보를 제공해주는 함수이다.

 1  struct ReturnAddressDetails
 2  {
 3    ReturnAddress address;
 4    char module_name[GUMPP_MAX_PATH + 1];
 5    char function_name[GUMPP_MAX_SYMBOL_NAME + 1];
 6    char file_name[GUMPP_MAX_PATH + 1];
 7    unsigned int line_number;
 8    unsigned int column;
 9  };
10
11  GUMPP_CAPI bool ReturnAddressDetails_from_address (ReturnAddress address, ReturnAddressDetails & details);

심볼 관련 유틸함수도 있다. 이름 기반으로 함수 포인터를 검색하거나 문자열 패턴에 매칭되는 함수들을 찾아서 벡터에 담아준다.

 1  class SymbolUtil
 2  {
 3  public:
 4    static void * find_function (const char * name)
 5    {
 6      return find_function_ptr (name);
 7    }
 8
 9    static std::vector<void *> find_matching_functions (const char * str)
10    {
11      RefPtr<PtrArray> functions = RefPtr<PtrArray> (find_matching_functions_array (str));
12      std::vector<void *> result;
13      for (int i = functions->length () - 1; i >= 0; i--)
14        result.push_back (functions->nth (i));
15      return result;
16    }
17  };
18}

gumjs #

gumjs는 js 런타임인 v8과 quickjs 를 사용하여 frida-gum을 사용할 수 있도록 도와주는 js 바인딩 소스코드이다.

gumjs 프로젝트에 js 런타임 코드까지 포함된게 아니라 gumjs 라이브러리를 빌드할때 시스템에 이미 설치된 js 런타임을 가져와서 라이브러리에 포함시키고 인자로 전달받은 js 스크립트를 라이브러리에 내장된 js 런타임으로 실행시키는 구조로 되어있다.

nodejs가 설치되지 않았다면 configure 명령을 실행시킬 때 에러가 발생한다. b461fa80-2166-4ad4-a090-17e9f3e457cb


간단한 사용 예제 #

gumjs 바인딩은 많이 사용해서 그런지 devkit 옵션도 제공되기 때문에 쉽게 사용할 수 있다. node를 설치해야 js 런타임이 설치되기 때문에 필수적으로 설치해야한다.

 1# node 설치: nodejs 공식적으로는 nvm을 사용해서 설치하는 것을 권장 
 2# nvm 다운로드 및 설치:
 3$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
 4$ nvm install 20
 5$ node -v
 6$ find /lib/ -name "*v8*"
 7/lib/x86_64-linux-gnu/libv8.so
 8
 9# gumjs 설치
10$ ./configure --enable-gumjs --with-devkits=gumjs
11$ make
12$ cd ./build/bindings/gumjs/devkit
13$ g++ -ffunction-sections -fdata-sections frida-gumjs-example.c -o frida-gumjs-example -L. -lfrida-gumjs -ldl -lrt -lm -lresolv -pthread -static-libstdc++
14$ ./frida-gumjs-example
15[*] open("/etc/hosts")
16[*] close(5)
17[*] open("/etc/fstab")
18[*] close(5)

#

comments powered by Disqus