Frida
Index
개요
https://frida.re/docs/home/
기본적인 정보들은 FRIDA의 공식 사이트에서 얻을 수 있습니다.
FRIDA는 다양한 환경에서 네이티브 앱에 Javascript 스니펫이나 자체 라이브러리를 삽입할 수 있습니다. 주로 code injecting의 용도로 자주 사용하게 됩니다.
하지만, 통신 채널을 생성할 수 없는 jail형태의 환경을 위한 Embedded 모드가 있고 그 외에 Preloaded 모드도 존재합니다.
Frida가 실행되면 대상 프로세스에 QuickJS를 주입하게 됩니다. 그렇게 되면 JS가 프로세스 메모리에 대한 전체 접근 권한으로 실행되게 되며, 프로세스와 JS 간에 양방향 통신 채널이 생기게 됩니다.
그리고 frida를 제어하는 API로는 Python API가 대표적으로 제공됩니다.
이번 course에서는 hooking에 사용되는 python script를 제공합니다.
-
FRIDA hooking skeleton script (hook.py)
#!/usr/bin/env python3 import sys import frida import signal SPAWN = False SPAWNED_PID = None TARGET_PROC = None SCRIPT_PATH = None def cleanup(): global SPAWNED_PID if SPAWNED_PID is not None: try: print(f"[*] Killing spawned process (PID: {SPAWNED_PID})") frida.kill(SPAWNED_PID) except: print(f"[*] spawned process already killed") print("[*] Cleanup complete.") def signal_handler(sig, frame): print("\n[*] Detected termination signal (SIGINT or SIGTERM)") cleanup() sys.exit(0) def on_message(message, data): if message["type"] == "send": print(f"[JS] {message['payload']}") elif message["type"] == "error": print(f"[JS Error] {message['stack']}") def main(target_process, script_text): global SPAWN, SPAWNED_PID print("[*] Inspecting file operations") if SPAWN: SPAWNED_PID = frida.spawn(target_process) session = frida.attach(SPAWNED_PID) print(f"[*] Spawned and attached to process: {target_process} (PID): {SPAWNED_PID}") script = session.create_script(script_text) script.on("message", on_message) script.load() print("[*] Script loaded. Press Enter to exit.") frida.resume(SPAWNED_PID) else: session = frida.attach(target_process) print(f"[*] Attached to process: {target_process}") script = session.create_script(script_text) script.on("message", on_message) script.load() print("[*] Script loaded. Press Enter to exit.") input() cleanup() def parse_parm(): global SPAWN, TARGET_PROC, SCRIPT_PATH if (len(sys.argv) != 3 and len(sys.argv) != 4): print("How to use:") print("Basic format './hook.py <binary path> <js script>'") print("Using PID instead binary path is ok. './hook.py <pid> <js script>'") print("use '-spawn' option if you want to generate new process") print("'-spawn' option must use process path, not pid") exit() for idx in range(len(sys.argv)): if idx == 0: continue arg = sys.argv[idx] if arg == "-spawn": SPAWN = True elif arg[-3:] == ".js": SCRIPT_PATH = arg else: try: TARGET_PROC = int(arg) if SPAWN == True: print("'-spawn' option must use process path, not pid") except ValueError: TARGET_PROC = arg return if __name__ == '__main__': parse_parm() script_text = '' with open(SCRIPT_PATH, 'r') as fd: script_text += fd.read() + '\n' signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) main(TARGET_PROC, script_text)
FRIDA 시작하기
간단한 frida script를 작성하는 것으로 시작해 봅시다.
// test.c
// gcc -Wall test.c -o test
#include <stdio.h>
#include <unistd.h>
void f(int n) {
printf("Number: %d\n", n);
}
int main(int argc, char* argv[]) {
int i = 0;
printf("f() is at %p\n", f);
while (1) {
f(i++);
sleep(1);
}
return 0;
}
위 코드를 컴파일합니다.
그 다음 inject에 사용되는 js코드를 작성합니다. f함수에 후킹하여 인자를 받아 오는 스크립트입니다.
var f_addr = 0x1169;
var target_proc = Process.mainModule;
if (target_proc) {
console.log("Image Base: " + target_proc.base);
var f_func = ptr(parseInt(target_proc.base, 16) + f_addr);
Interceptor.attach(f_func, {
onEnter: function(args) {
send(args[0].toInt32());
}
});
}
else {
console.log("Module not found: " + target_proc_name);
}
이 때 첫 번째 line의 f_addr에는 f함수의 가상 주소를 넣습니다.
그 뒤 위에서 제공한 hook.py
를 실행시켜 봅시다.
아무런 인자 없이 실행하면 기본적인 사용 방법이 나옵니다.
How to use:
Basic format './hook.py <proc name> <js script>'
Using PID instead binary path is ok. './hook.py <pid> <js script>'
use '-spawn' option if you want to generate new process
`./hook.py <binary path> <js script> -spawn`
'-spawn' option must use process path, not pid
기존에 실행되는 process에 inject하는 경우 프로세스명 혹은 pid를 넣어주고, -spawn
옵션을 사용하여 새롭게 프로세스를 실행하며 inject하는 경우에는 -spawn
과 함께 바이너리 경로를 넣어주면 됩니다.
다음과 같이 실행해 보겠습니다.
후킹한 결과가 출력 되는 모습입니다.
자세한 사용 방법은 차차 익혀 가도록 하죠!
Tutorial
본격적으로 기능 하나 하나를 공부하기 전에, Frida로 간단하게 할 수 있는 작업들을 경험해보고자 합니다.
Frida에서는 대상 프로세스에 Read, Write, Execute를 모두 할 수 있습니다. Tutorial에서 다룰 3가지 작업에서는 Read, Write, Execute에서 가장 간단한 예제들을 다뤄보고자 합니다.
따라서 Tutorial은 다음의 목차로 진행됩니다.
- 함수 인자 읽기
- 함수 인자 수정하기
- 함수 call하기
Read Function Argument
위의 Frida 시작하기에서 했던 스크립트가 함수 인자를 얻어 오는 스크립트였습니다.
한 줄 한 줄 해석해 봅시다.
var f_addr = 0x1169;
var target_proc = Process.mainModule;
if (target_proc) {
console.log("Image Base: " + target_proc.base);
var f_func = ptr(parseInt(target_proc.base, 16) + f_addr);
Interceptor.attach(f_func, {
onEnter: function(args) {
send(args[0].toInt32());
}
});
}
else {
console.log("Module not found: " + target_proc_name);
}
먼저 Process
class를 이용하면 프로세스와 관련된 작업을 하거나 정보들을 얻을 수 있습니다.
Process.mainModule
의 경우 프로세스의 main 모듈을 불러 옵니다.
여기서 Module이란
여기서는 test를 포함해서 libc와 ld 이렇게 3개의 모듈이 프로세스에 로드 되어 있는 셈입니다.
여기서 main 모듈이라 함은 원본 바이너리에 해당합니다.
그리고 이 모듈 class에서 base 메소드에 접근하면 매핑된 image base를 얻을 수 있습니다.
그 다음 Interceptor
를 이용하면 기본적으로 함수에 코드를 inject하는 기능을 제공합니다.
첫 번째 인자에는 inject할 함수의 주소가 들어가고 두 번째 인자로는 onEnter와 onLeave가 들어갈 수 있습니다.
onEnter에는 함수에 진입할 때 실행되고, onLeave는 함수가 return할 때 실행됩니다.
그럼 위 스크립트에서는 이제 첫 번째 인자를 frida에 보내주게 되고, hook.py
는 이를 출력해줍니다.
Modify Function Argument
이번에는 함수 인자를 바꿔 보겠습니다.
var f_addr = 0x1169;
var target_proc = Process.mainModule;
if (target_proc) {
console.log("Image Base: " + target_proc.base);
var f_func = ptr(parseInt(target_proc.base, 16) + 0x1169);
Interceptor.attach(f_func, {
onEnter: function(args) {
args[0] = ptr("1337");
}
});
}
else {
console.log("Module not found: " + target_proc_name);
}
onEnter
에서 args[0]을 수정합니다.
이렇게 되면 1337이 출력 되어야 합니다.
잘 되는 모습입니다.
Call Function
이번에는 함수를 call해보겠습니다.
var f_addr = 0x1169;
var target_proc = Process.mainModule;
const f = new NativeFunction(ptr(parseInt(target_proc.base, 16) + f_addr), 'void', ['int']);
f(1911);
f(1911);
f(1911);
NativeFunction함수를 이용하면 프로세스 내부의 Function을 선언하고 call할 수 있게 됩니다.
이번에는 Interceptor 기반이 아니므로 frida로 후킹하는 즉시 함수가 실행되겠죠.
spawn 옵션 없이 다른 터미널에서 test 바이너리를 실행하고, 도중에 후킹을 시도해보면 다음과 같은 출력이 나옵니다.
후킹이 정상적으로 된 모습입니다.
Tutorial은 여기서 끝입니다.
Frida로 할 수 있는 기본적인 동작들을 수행해 봤는데, 이로써 세부적인 기능들을 배울 때 해당 기능들을 사용하여 어떤 것을 할 수 있을 지 상상할 수 있으리라 생각됩니다.
JavaScript API
이제 본격적으로 Frida의 기능들을 탐구해 봅시다. 그 중 이제 후킹했을 때 실행되는 JavaScript 코드에 대해서 자세히 알아보겠습니다.
https://frida.re/docs/javascript-api/
위 링크 들어가면 세세하고 친절하게 설명되어 있어요.
주요 기능들은 아래에 정리되어 있습니다.