13 Security Lab

DBI(Dynamic Binary Instrumentation) 겉핥기로 알아보기 본문

Computer Security/Analysis

DBI(Dynamic Binary Instrumentation) 겉핥기로 알아보기

Maj0r Tom 2021. 2. 23. 23:21

DBI (Dynamic Binary Instrumentation)


printf("test %s", buf); 등과 같이 특정 시점에서의 변수값 등을 확인하거나 프로그램의 행위를 조사하는 일을 Instrumentation 라고 부른다 바이너리 분석을 할때 각종 모니터링 툴들을 켜고 악성코드를 실행하여 행위를 분석하거나, 런타임에 코드를 후킹하여 데이터를 조사 또는 변경하는 방법으로 분석한다.


비교 DBA vs. DBI


DBA (Dynamic Binary Analysis) = Dynamic Program Analysis = Dynamic Analysis

프로그램 런타임에 바이너리를 분석한다.
소프트웨어를 실제 또는 가상프로세서에서 프로그램을 실행함으로서 바이너리를 분석하는 방법이다.
Dynamic Analysis 는 Static Analysis 에 비해 효과가 좋은편이나 테스트를 위한 충분한 테스트 커버리지가 가능해야한다.

Static binary instrumentation :  프로그램이 실행되기 전에 object code, executable code를 재작성하는 단계에서 수행
Dynamic binary instrumentation : 런타임 단계에서 수행

DBI (Dynamic Binary Instrumentation)

바이너리를 런타임(실행되고 있는동안) 코드를 삽입하여 동작 분석한다.
코드를 삽입하여 동작을 분석하는 행위가 instrumentation
DBA 의 방법 중 하나로서 DBI 가 있다고 이해하였다.


비교 SI vs. BI


SI (Source Instrumentation)

소스 코드 수준에서 하는 행위
소스 계측은 개발자와 도메인 전문가가 실행중인 응용 프로그램에 대한 테스트 시나리오를 작성하기 위해 테스트중인 소스를 선택적으로 계측하는 프로세스입니다. 소스 계측을 활용하는 테스트를 구현하는 것을 Expectation Testing이라고합니다.

BI (Binary Instrumentation)

바이너리 수준에서 하는 행위
BI 중에서도 바이너리 실행 중에 조사하는 것을 DBI 라 부른다. 바이너리가 어떤 함수들을 호출하는지, 또 인자값은 무엇인지 등을 조사하는 것.

바이너리 분석을 할때 각종 모니터링 툴들을 켜고 악성코드를 실행하여 행위를 분석하거나, 런타임에 코드를 후킹하여 데이터를 조사 또는 변경하는 방법으로 분석한다.


DBI의 프로젝트

Frida
PIN
DynamoRio
Dyninst
Bochs
Valgrind

DynamoRIO

 

 

 

Frida DBI


Frida가 중요한 이유는 IOS ANDROID 같은 환경에서는 로컬 디버깅이 제한되지만 Frida를 사용하면 로컬 디버깅하는 것 처럼 동적 분석이 가능

FRIDA 는 scriptable 한 DBI 프레임워크 (vala 라는 특이한 프로그래밍 언어로 작성)

DBI 를 위한 주 조작은 자바스크립트를 통해서 하며, C/S 구조로 동작하게 된다.
즉 처음에 바이너리에 프레임워크 라이브러리를 인젝션하여 파이프를 만들어 놓고, 그 파이프를 통해서 명령을 주고 받으면서 바이너리 조사를 할 수 있도록 되어 있다. 물론 C/S 를 사용하지 않고 그냥 PIN 처럼 내부적으로 동작하도록 할 수도 있다.

Frida는 Python 기반의 프로그램입니다. 물론 Core 부분은 C와 Google V8 Engine으로 작성됬지만 대체로 Python library를 많이 사용하고, 여러가지 언어를 이용하여 스크립트를 작성할 수 있으나(바이너리 조작은 자바스크립트로 해야 한다) Python 이 가장 단순하고 좋은 듯 하다.

하지만 제작자는 발표 등을 할 때 node.js 를 많이 사용하는 모습을 보여주고 있다. FRIDA 의 가장 큰 장점은 다양한 플랫폼을 지원한다는 것이다. PIN 과는 달리 ARM 아키텍쳐를 지원하므로 Android, IOS 앱에도 적용이 가능하다. 또한 64 bit 에서도 정상적으로 동작한다.

또한, 컴파일이 필요없는 스크립트 언어로 작성이 가능하다는 것도 매력적이다. 코드 수정이 빈번한 경우 매번 컴파일을 하는 것만큼 귀찮은 것은 없기 때문이다.    


Frida 제공기능

특정함수에 연결해 함수후킹
함수추적관점 실행중인 앱디버깅
실시간 트래픽 스니핑 및 암호해독

파이썬기반의 라이브러리 + 커맨드라인, 네이티브앱 후킹 통한 분석가능  
대상: 윈도우즈 맥, 리눅스 IOS 안드로이드 QNX 
다양한 플랫폼에서 후킹을 할수 있는 플랫폼 / 파이썬틀에 인젝션할 코드를 자바스크립트로 작성가능 파이썬으로 실행할수있음


 

Frida windows example) 

from __future__ import print_function
import frida
import sys

def on_message(message, data):
    print("[%s] => %s" % (message, data))

def main(target_process):
    session = frida.attach(target_process)

    script = session.create_script("""

    // Find base address of current imported jvm.dll by main process fledge.exe
    var baseAddr = Module.findBaseAddress('Jvm.dll');
    console.log('Jvm.dll baseAddr: ' + baseAddr);

    var SetAesDeCrypt0 = resolveAddress('0x1FF44870'); // Here we use the function address as seen in our disassembler

    Interceptor.attach(SetAesDeCrypt0, { // Intercept calls to our SetAesDecrypt function

        // When function is called, print out its parameters
        onEnter: function (args) {
            console.log('');
            console.log('[+] Called SetAesDeCrypt0' + SetAesDeCrypt0);
            console.log('[+] Ctx: ' + args[0]);
            console.log('[+] Input: ' + args[1]); // Plaintext
            console.log('[+] Output: ' + args[2]); // This pointer will store the de/encrypted data
            console.log('[+] Len: ' + args[3]); // Length of data to en/decrypt
            dumpAddr('Input', args[1], args[3].toInt32());
            this.outptr = args[2]; // Store arg2 and arg3 in order to see when we leave the function
            this.outsize = args[3].toInt32();
        },

        // When function is finished
        onLeave: function (retval) {
            dumpAddr('Output', this.outptr, this.outsize); // Print out data array, which will contain de/encrypted data as output
            console.log('[+] Returned from SetAesDeCrypt0: ' + retval);
        }
    });

    function dumpAddr(info, addr, size) {
        if (addr.isNull())
            return;

        console.log('Data dump ' + info + ' :');
        var buf = addr.readByteArray(size);

        // If you want color magic, set ansi to true
        console.log(hexdump(buf, { offset: 0, length: size, header: true, ansi: false }));
    }

    function resolveAddress(addr) {
        var idaBase = ptr('0x1FEE0000'); // Enter the base address of jvm.dll as seen in your favorite disassembler (here IDA)
        var offset = ptr(addr).sub(idaBase); // Calculate offset in memory from base address in IDA database
        var result = baseAddr.add(offset); // Add current memory base address to offset of function to monitor
        console.log('[+] New addr=' + result); // Write location of function in memory to console
        return result;
    }
""")
    script.on('message', on_message)
    script.load()
    print("[!] Ctrl+D on UNIX, Ctrl+Z on Windows/cmd.exe to detach from instrumented program.\n\n")
    sys.stdin.read()
    session.detach()

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print("Usage: %s <process name or PID>" % __file__)
        sys.exit(1)

    try:
        target_process = int(sys.argv[1])
    except ValueError:
        target_process = sys.argv[1]
    main(target_process)

 

 

Comments