DBI-Frida

» Hacking-Tips

프로그램을 분석하는 데 많은 도움이 되는 툴이다.

일명 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만 소개하겠다.

  1. 외부 모듈의 함수에 후킹하기
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로 후킹을 할 수 있다.

onEnteronLeave에는 함수에 진입할 때와 return할 때 주입할 코드를 작성할 수 있다.

위 코드는 단순히 onEnter에서 인자를 출력하고, onLeave에서 retval을 출력하는 예제이다.

  1. 바이너리 내부의 함수에 후킹하기
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 로 실행해야 한다.