직접 주입한 frida-gum 사용하기
2024년 11월 12일
서론 #
출처: Frida-gum을 이용한 Android Hook - 라온
출처의 글이 4~5년 정도 되기도 했고 지금까지는 frida를 이렇게까지 쓴적이 없었지만, 이해도와 숙련도를 높이기 위해 정리해보려고 한다.
frida-gum 라이브러리 #
일반적인 프리다 사용 방식은 frida 프로젝트를 빌드하면 생성되는 frida-server 를 실행하고 cli와의 통신으로 frida-agent가 라이브러리 형태로 앱에 주입되어 내부의 frida-gum 모듈을 통해 C/C++ 함수의 후킹이 가능하게 된다.
그래서 이런 방법이 있는지도 모르고 사용하고 있었는데, frida-gum을 라이브러리 형태로 빌드하고 이걸 주입해서 후킹코드를 작성하여 클라이언트처럼 사용하는 방식이 있었다.
한번 해보자
예제 테스트 #
테스트 환경 #
- wsl ubuntu 24.04
- Android 13 (sdk 33) arm64
- android-ndk-r25c (현재 지원하는 최신버전)
- frida-gum 16.5.6
libfrida-gum.a 라이브러리 빌드 #
devkit으로 빌드하면 라이브러리와 예제 파일을 얻을 수 있다.
meson 빌드 시스템을 사용하며, configure -> releng/meson_configure.py -> meson setup 로 연결되고 기본적으로 meson.options 파일에 정의된 기본 옵션들과 인자로 전달한 추가 옵션을 파싱해서 빌드 디렉터리를 생성하게 된다.
이후 make -> Makefile -> releng/meson_make.py -> meson compile 순서로 호출되어 빌드되며 빌드디렉터리 내에 정의된 옵션과 meson.build 에 정의된 규칙에 따라 frida가 빌드되는 구조를 갖고 있다.
1git clone --recursive https://github.com/frida/frida-gum.git
2git checkout tags/16.5.6
3make
4mkdir android-arm64
5cd android-arm64
6../configure --host=android-arm64 --with-devkits=gum
7make
8cd ./gum/devkit
예제 파일 빌드 및 실행 #
예제 코드를 보면 맨위에 주석으로 어떻게 빌드하는지도 적혀있다. 하지만 나는 크로스플랫폼 타겟(안드로이드)으로 작성할 것이기 때문에 clang을 ndk에 있는걸로 써야한다.
1~/android-ndk-r25c/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android33-clang -DANDROID -ffunction-sections -fdata-sections frida-gum-example.c -o frida-gum-example -L. -lfrida-gum -llog -ldl -lm -pthread
빌드된 파일을 실행하면 이렇게 보인다.
구조 분석 #
example 코드는 간단하다. 그냥 리스너 attch 후 open, close를 후킹하고 호출하고, 리스너 detach 후 호출하기만 한다.
1int
2main (int argc,
3 char * argv[])
4{
5 // ... 인터셉터를 생성하고 리스너를 opem, close 함수에 attach 한다.
6 interceptor = gum_interceptor_obtain ();
7 gum_interceptor_begin_transaction (interceptor);
8 gum_interceptor_attach (interceptor,
9 GSIZE_TO_POINTER (gum_module_find_export_by_name (NULL, "open")),
10 listener,
11 GSIZE_TO_POINTER (EXAMPLE_HOOK_OPEN));
12 gum_interceptor_attach (interceptor,
13 GSIZE_TO_POINTER (gum_module_find_export_by_name (NULL, "close")),
14 listener,
15 GSIZE_TO_POINTER (EXAMPLE_HOOK_CLOSE));
16 gum_interceptor_end_transaction (interceptor);
17
18 // 테스트 1. 오픈, 클로즈 테스트
19 close (open ("/etc/hosts", O_RDONLY));
20 close (open ("/etc/fstab", O_RDONLY));
21
22 // 리스너를 detach 한다.
23 gum_interceptor_detach (interceptor, listener);
24
25 // 잘 detach 됐는지 확인. 이때는 로그출력이 안된다.
26 close (open ("/etc/hosts", O_RDONLY));
27 close (open ("/etc/fstab", O_RDONLY));
28
29 // detach 이후에 리스너가 호출됐는지 확인하는 코드.
30 // 위에서 볼 수 있듯 여전히 4 call 이다.
31 g_print ("[*] listener still has %u calls\n", EXAMPLE_LISTENER (listener)->num_calls);
32 // ...
33}
34
35// on_enter 리스너이다.
36static void
37example_listener_on_enter (GumInvocationListener * listener,
38 GumInvocationContext * ic)
39{
40 ExampleListener * self = EXAMPLE_LISTENER (listener);
41 ExampleHookId hook_id = GUM_IC_GET_FUNC_DATA (ic, ExampleHookId);
42
43 switch (hook_id)
44 {
45 case EXAMPLE_HOOK_OPEN:
46 g_print ("[*] open(\"%s\")\n", (const gchar *) gum_invocation_context_get_nth_argument (ic, 0));
47 break;
48 case EXAMPLE_HOOK_CLOSE:
49 g_print ("[*] close(%d)\n", GPOINTER_TO_INT (gum_invocation_context_get_nth_argument (ic, 0)));
50 break;
51 }
52 // 호출될 때마다 호출 카운트 증가
53 self->num_calls++;
54}
직접 인젝트해서 사용 #
예제코드는 자기 자신을 후킹하는 것이기 때문에 api 테스트는 가능하지만 의미 없는 코드이다.
테스트 #
공유 라이브러리 형태로 빌드 #
main 함수를 라이브러리 로드하면서 실행할 수 있도록 .init_array 등록 코드로 변경한다.
1- int main(int argc, char * argv[])
2
3+ __attribute__((constructor))
4+ int init(void *arg)
빌드할때는 공유 라이브러리 형태로 빌드되도록 -shared 옵션을 추가하면 된다.
1~/android-ndk-r25c/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android33-clang -DANDROID -ffunction-sections -fdata-sections frida-gum-example.c -o frida-gum-example -L. -lfrida-gum -llog -ldl -lm -pthread -shared
인젝터 빌드 #
~ 이 깃허브에서 빌드해서 사용하면 된다. ~ 방식으로 ~ 에러가 발생하기 때문에 이동시켜서 인젝트 한다.