iOS
frida-gum을 활용한 iOS 앱 Hook
2022년 10월 11일
원문에서 보기 ↗
1. 개요
Frida(https://github.com/frida/frida)의 핵심 로직은 모듈화되어 있습니다. 그래서 후킹(Hooking) 모듈만 가져다 프로그램에 포함시켜 개발할 수 있으며, Frida JavaScript API 외에 C/C++ API를 이용해 Frida의 강력한 후킹 기능을 사용할 수 있습니다. 따라서 좀 더 유연하게 iOS 앱 분석이 가능하다는 이점이 있습니다.
frida-gum(https://github.com/frida/frida-gum)이 바로 이 후킹 모듈입니다. 이 글에서는 해당 모듈을 이용해 기본적인 method trace 트윅(Tweak)을 구현해 보려 합니다.
2. 환경 구성
트윅 작성을 위해 THEOS를 설치해야 합니다. https://theos.dev/docs/installation-macos
THEOS를 설치한 뒤에는 frida 공식 깃허브에서 frida-gum-devkit을 다운로드합니다. 
압축을 풀면 예시 소스 코드(frida-gum-example.c), 헤더 파일(frida-gum.h) 및 static library 파일(libfrida-gum.a)이 들어 있습니다. 
이 중 헤더 파일 및 static library 파일을 THEOS 설치 경로 하위로 이동합니다.
cp libfrida-gum.a <THEOS 설치 경로>/lib/
cp frida-gum.h <THEOS 설치 경로>/include/
3. 테스트
DVIA-v2(https://github.com/prateek147/DVIA-v2) 앱에 대해서 Objective-C 메서드 콜을 추적하는 트윅을 작성해 보려 합니다. THEOS 프로젝트를 하나 생성하고 Makefile을 다음과 같이 작성합니다.
- Makefile
FINALPACKAGE := 1
ARCHS := arm64
TARGET := iphone:clang:latest:12.4
INSTALL_TARGET_PROCESSES = SpringBoard
include $(THEOS)/makefiles/common.mk
TWEAK_NAME = fridagumtest
$(TWEAK_NAME)_FILES = Tweak.xm
$(TWEAK_NAME)_CFLAGS = -fobjc-arc -w
$(TWEAK_NAME)_LDFLAGS = -lm -lresolv
$(TWEAK_NAME)_LIBRARIES = frida-gum
include $(THEOS_MAKE_PATH)/tweak.mk
frida-gum API를 사용하기 위한 헤더 파일을 하나 생성합니다. frida-gum example(https://gist.github.com/oleavr/3edc47c9f69eb048de9d70ed45998f9c)을 참고하여 작성하였습니다.
- fridagumlistener.h
#import "frida-gum.h"
#include <fcntl.h>
#include <unistd.h>
#include <Foundation/Foundation.h>
enum _FridaHookId
{
FRIDA_HOOK_OBJC
};
typedef enum _FridaHookId FridaHookId;
struct _FridaListener
{
GObject parent;
FridaHookId hookid;
NSString* fullname;
guint num_calls;
guint paramscount;
};
typedef struct _FridaListener FridaListener;
static void frida_listener_iface_init (gpointer g_iface, gpointer iface_data);
#define FRIDA_TYPE_LISTENER (frida_listener_get_type ())
G_DECLARE_FINAL_TYPE (FridaListener, frida_listener, FRIDA, LISTENER, GObject)
G_DEFINE_TYPE_EXTENDED (FridaListener,
frida_listener,
G_TYPE_OBJECT,
0,
G_IMPLEMENT_INTERFACE (GUM_TYPE_INVOCATION_LISTENER,
frida_listener_iface_init))
// Interceptor onEnter
static void
frida_listener_on_enter (GumInvocationListener * listener,
GumInvocationContext * ic);
// Interceptor onLeave
static void
frida_listener_on_leave (GumInvocationListener * listener,
GumInvocationContext * ic);
static void
frida_listener_class_init (FridaListenerClass * klass)
{
(void) FRIDA_IS_LISTENER;
(void) glib_autoptr_cleanup_FridaListener;
}
static void
frida_listener_iface_init (gpointer g_iface,
gpointer iface_data)
{
GumInvocationListenerInterface * iface = static_cast<GumInvocationListenerInterface *>(g_iface);
iface->on_enter = frida_listener_on_enter;
iface->on_leave = NULL;
}
static void
frida_listener_init (FridaListener * self)
{
}
다음으로 frida-gum을 이용한 메인 로직을 Tweak.xm 파일에 작성합니다. 대소문자 구분 없이 *[dvia* *] regex 패턴을 가진 Objective-C 메서드를 추적하려 합니다.
- Tweak.xm
#import "fridagumlistener.h"
#include <mach-o/dyld.h>
#include <Foundation/Foundation.h>
static BOOL imageLoaded = NO;
static char* loadedImagename;
static uint64_t image_base = 0x0;
// Interceptor onEnter
static void
frida_listener_on_enter (GumInvocationListener * listener,
GumInvocationContext * ic)
{
FridaListener * self = FRIDA_LISTENER (listener);
FridaHookId hook_id = self->hookid;
NSString* full_name = self->fullname;
gpointer inptr;
switch (hook_id)
{
case FRIDA_HOOK_OBJC:
// print objc method arguments
NSLog(@"[nhncloud] [+] %@", full_name);
if(self->paramscount > 0){
for(int i = 0; i < self->paramscount; i++){
inptr = gum_invocation_context_get_nth_argument (ic, i + 2);
NSLog(@"[nhncloud] \t\targs%d: %p", (i + 2), inptr);
}
}
break;
}
self->num_calls++;
}
static gboolean
instrument_objc_method (const GumApiDetails *details,
gpointer user_data)
{
// gum interceptor set up
GumInterceptor * interceptor;
GumInvocationListener * listener;
interceptor = gum_interceptor_obtain ();
listener = static_cast<GumInvocationListener *>(g_object_new (FRIDA_TYPE_LISTENER, NULL));
const char* name = details->name;
GumAddress address = details->address;
FRIDA_LISTENER(listener)->hookid = FRIDA_HOOK_OBJC;
FRIDA_LISTENER(listener)->fullname = [NSString stringWithUTF8String:name];
NSString *fullname = FRIDA_LISTENER(listener)->fullname;
// check if objc method has arguments. then how many params it has
if([fullname containsString:@":"]){
NSArray *arrString= [[[fullname componentsSeparatedByString: @" "][1] componentsSeparatedByString:@"]"][0] componentsSeparatedByString:@":"];
FRIDA_LISTENER(listener)->paramscount = (int) ([arrString count] - 1);
}
gum_interceptor_begin_transaction (interceptor);
// gum interceptor attach to OBJC method
gum_interceptor_attach (interceptor,
GSIZE_TO_POINTER (address),
listener,
NULL);
gum_interceptor_end_transaction (interceptor);
return TRUE; // keep enumerating
}
void objcTrace(const char* objc_pattern){
// find objc methods with pattern using "gum_api_resolver_enumerate_matches". then instrument it
if(image_base != NULL){
g_autoptr(GumApiResolver) resolver = gum_api_resolver_make("objc");
gum_api_resolver_enumerate_matches (resolver,
objc_pattern,
instrument_objc_method,
NULL,
NULL);
}
}
// check if wanted image loaded. then trace objc
void checkImageLoaded(){
char* target_img = "DVIA-v2";
while(true){
image_base = gum_module_find_base_address(target_img);
if(!image_base){
continue;
}
else if(image_base){
loadedImagename = target_img;
NSLog(@"[nhncloud] wanted image %s loaded!", loadedImagename);
objcTrace("*[dvia* *]/i");
break;
}
}
}
%ctor {
// initialize frida-gum
gum_init_embedded ();
NSLog(@"[nhncloud] frida-gum initialized!");
// check image loaded asynchronously
dispatch_async(dispatch_get_main_queue(), ^{
checkImageLoaded();
});
}
이제 트윅을 빌드하여 단말기에 설치하고, 타깃 앱인 DVIA-v2를 실행해 보면 Objective-C 메서드 콜 추적 로그가 잘 기록되는 것을 확인할 수 있습니다. 
4. 느낀점
iOS 앱 분석을 하다 보면 기본 frida만으로는 한계에 부딪히는 경우가 많았습니다. 예컨대 구조체 접근이나 각종 Objective-C 문법을 Frida의 JavaScript API로 구현하기 힘든 경우가 있습니다.
frida-gum을 활용하여 트윅을 작성하면 C/C++/Objective-C 문법 사용이 가능해 이와 같은 한계 극복할 수 있었습니다. 기본 frida에서 제공하는 후킹 기능도 frida-gum을 이용해 대부분 구현이 가능했습니다. 다만 소스 코드 작성에 시간이 오래 걸린다는 점과, 매번 빌드가 필요하다는 단점도 있습니다.
그래서 기본 frida와 frida-gum을 활용한 프로그램을 동시에 사용한다면 iOS 앱 분석에 있어 상호 보완이 가능할 것으로 보입니다.
참고
- Frida Github, Frida, https://github.com/frida/frida
- Frida Github, Frida-gum, https://github.com/frida/frida-gum
- Theos, installation, https://theos.dev/docs/installation-macos
- DVIA-v2, https://github.com/prateek147/DVIA-v2
- Frida-gum example, https://gist.github.com/oleavr/3edc47c9f69eb048de9d70ed45998f9c