DBI-Frida
프로그램을 분석하는 데 많은 도움이 되는 툴이다.
일명 hooking이라고 해서 특정 함수나 코드가 실행될 때 실행 흐름을 가져와서 내가 원하는 임의의 코드를 실행시키게 할 수 있게 해주는 툴이다.
hooking을 이용해서 원하는 함수나 원하는 특정 코드가 실행 될 때 프로그램의 상황을 살펴보는 디버깅의 목적으로도 사용될 수 있고, 메모리에 값을 쓸 수도 있어서 여러모로 사용처가 많다.
gdb script와 유사한 행위들을 할 수 있지만 원리는 조금 다른 느낌. 대신 gdb script보다 더 빠른 속도를 가지고 있다.
Frida 설치
Window, Linux 등 여러 OS를 지원한다. macOS는 물론 iOS 및 Android도 지원하기 때문에 모바일 프로그램을 분석할 때 필수적인 툴 중 하나로 알고 있다.
Window와 Linux만 소개하겠다. 두 OS에서 설치 방법은 동일하다.
pip install frida
pip install frida-tools
frida의 스크립트는 여러 언어를 지원하지만 그 중 js와 python이 대표적이다. 지금 알 수 없는 이유로… 본인 환경에서 python이 안 돌아가므로.. js만 소개하겠다.
- 외부 모듈의 함수에 후킹하기
var createFile = Module.findExportByName("kernel32.dll", "CreateFileW");
Interceptor.attach(createFile, {
onEnter: function (args)
{
// HANDLE CreateFile(
// [in] LPCSTR lpFileName,
// [in] DWORD dwDesiredAccess,
// [in] DWORD dwShareMode,
// [in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
// [in] DWORD dwCreationDisposition,
// [in] DWORD dwFlagsAndAttributes,
// [in, optional] HANDLE hTemplateFile
// );
console.log("Program Counter before function call:");
console.log("RIP:", this.context.rip);
console.log("=== CreateFile's lpFileName ===");
console.log(Memory.readUtf16String(args[0]));
console.log("=== CreateFile's dwCreationDisposition ===");
console.log(args[4]);
console.log("=== CreateFile's dwFlagsAndAttributes ===");
console.log(args[5]);
//console.log("")
},
onLeave: function (retval) {
console.log("=== Created File handle ===");
console.log(retval);
console.log("")
}
});
예를 들어서 winAPI의 CreateFileW 함수에 후킹하고 싶을 수 있다.
dll은 기본적으로 함수 명이 다 살아 있기 때문에, 함수 명으로 쉽게 후킹을 할 수 있다.
Module.findExportByName
으로 쉽게 원하는 모듈 함수의 주소를 얻어 오고
Interceptor.attach
로 후킹을 할 수 있다.
onEnter
와 onLeave
에는 함수에 진입할 때와 return할 때 주입할 코드를 작성할 수 있다.
위 코드는 단순히 onEnter
에서 인자를 출력하고, onLeave
에서 retval을 출력하는 예제이다.
- 바이너리 내부의 함수에 후킹하기
var targetModuleName = "WindowsProject1.exe";
var targetModule = Process.getModuleByName(targetModuleName);
if (targetModule) {
console.log("Image Base: " + targetModule.base);
var WinProc = ptr(parseInt(targetModule.base, 16) + 0x1520);
var addr0 = ptr(parseInt(targetModule.base, 16) + 0x20694);
var addr1 = ptr(parseInt(targetModule.base, 16) + 0x20698);
Interceptor.attach(WinProc, {
onEnter: function (args) {
console.log("Image Base: " + targetModule.base);
console.log(Memory.readS32(addr0, 4));
console.log(Memory.readS32(addr1, 4));
var data = [0x0e, 0x69, 0xf6, 0x43];
Memory.writeByteArray(addr1, data);
console.log("\n");
}
})
}
else {
console.log("Module not found: " + targetModuleName);
}
stripped 되어 있는 바이너리의 경우에는 함수 명이 아니라 해당 함수가 있는 주소를 알아내야 한다.
함수의 상대 주소는 정적 분석 도구를 이용하여 알아내고 Image Base의 경우에는 Process.getModuleByName
으로 프로세스를 가져오고 .base
에 접근하면 Image Base를 얻을 수 있다.
여기서 정적 분석 툴에서 구한 상대 주소를 더하여 후킹할 함수의 주소를 구해 준다. 이 때 .base
는 int형이 아니므로 parseInt
로 int형으로 바꿔준 뒤 주소를 더해주고 더한 뒤에는 다시 ptr()
로 씌워 줘야 한다.
메모리 값 읽어오기 및 쓰기
//register값 읽기
this.context.rax
this.context.rip
//메모리에 있는 값 string으로 읽기
Memory.readUtf16String(ptr);
Memory.readUtf8String(ptr);
//4byte값, 8byte값 읽기
Memory.readS32(ptr);
Memory.readS64(ptr);
//raw한 값으로 쓰기
Memory.writeByteArray(ptr, data);
frida를 실행하여 후킹하기
frida -l hook.js -f program.exe
쉘에서 위 코드를 실행하면 program.exe
를 새로 실행하면서 해당 프로세스에 hook.js
에 적혀 있는 대로 후킹하게 된다. frida의 경우에는 관리자 권한이 반드시 필요하므로, window에서는 관리자 권한의 shell에서 실행을 해야 하고, Linux에서는 sudo
로 실행해야 한다.