본문 바로가기

project/Vulnerability analysis Automation

[pin] 기본 구조

pin tool은 dynamic binary instrumentation (줄여서 DBI) 라는 기술을 사용하기 위한 intel에서 개발한 툴로서 대표적인 DBI Tool은 Pin Tool뿐만 아니라 DynamoRIO와 리눅스 기반의 Valgrind 가 있다.


DBI는 run time시에 코드삽입이 가능하게 만들어 주는 기술을 말하며, 이를 통해 얻을 수 있는 장점은 Recomplie과 Relink가 필요없기 때문에 작업의 효율성이 높고, 실행중에 발생하는 특정 동작이나 코드를 처리할 수 있는 장점이 있다.


pin은 그중에서도 다양한 기능을 지원하고, ARM과 x86에서도 사용가능하고, 리눅스와 윈도우에서도 사용이 가능하기 때문에 그중 가장 사랑받는 툴이라 할 수 있겟다.


나는 이번 프로젝트에서 pin을 이용하여 taint analysis engine을 구현할 것이다.  taint란 user input에 영향받는 레지스터, 메모리를 이야기 하며, 이를 구현하기 위해서 pin의 기능을 사용할 것이다.


pin은 기본적으로 제공하는 함수들을 이용해서 콜백함수를 지정해 해당 조건에 만족하는 콜백함수를 통해서 runtime시의 값들을 처리 할 수가 있다.


먼저 ManualExamples안에 예제코드를 분석하면서 한번 차례차례 익혀 보겠다.

처음은 가장 간단한 코드 inscount0.cpp를 먼저 해보겠다.


pin의 환경구성은 간단하다. cygwin을 설치하거나, visual studio를 이용하면 된다.


그럼 나는 visul studio를 이용해서 하겠다.


pin설치 디렉토리에서 

source\tool\MyPintool 의  .sln파일을 키면 mypintool프로젝트가 켜지고, MyPinTool.cpp소스에 

source\tool\ManualExample\insconut0.cpp소스를 가져와서 소스를 복사하고 넣으면 된다.


빌드를 하면 Debug 폴더에 MyPinTool.dll파일이 생기며, pin에 해당 dll을 인자로 주고, 타겟프로그램을 실행시키면 DBI가 되는 것이다.


일단 쉽게 실행시키기 위해 .bat파일을 만들었다.

@echo off
setlocal
set CMDLINE=notepad.exe
echo start
C:\pin\pin.exe -t C:\pin\source\tools\MyPinTool\Debug\MyPinTool.dll -- %CMDLINE%
echo end
endlocal

그리고 실행시키면 notepad를 타겟으로 해당 DBI를 실시한다.


그럼 이제 소스코드 분석을 해보겠다.


insconut0.cpp

/*BEGIN_LEGAL 
Intel Open Source License 

Copyright (c) 2002-2014 Intel Corporation. All rights reserved.
 
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.  Redistributions
in binary form must reproduce the above copyright notice, this list of
conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.  Neither the name of
the Intel Corporation nor the names of its contributors may be used to
endorse or promote products derived from this software without
specific prior written permission.
 
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR
ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
END_LEGAL */
#include <iostream>
#include <fstream>
#include "pin.H"

ofstream OutFile;

// The running count of instructions is kept here
// make it static to help the compiler optimize docount
static UINT64 icount = 0;

// This function is called before every instruction is executed
VOID docount() { icount++; }
    
// Pin calls this function every time a new instruction is encountered
VOID Instruction(INS ins, VOID *v)
{
    // Insert a call to docount before every instruction, no arguments are passed
    INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_END);
}

KNOB<string> KnobOutputFile(KNOB_MODE_WRITEONCE, "pintool",
    "o", "inscount.out", "specify output file name");

// This function is called when the application exits
VOID Fini(INT32 code, VOID *v)
{
    // Write to a file since cout and cerr maybe closed by the application
    OutFile.setf(ios::showbase);
    OutFile << "Count " << icount << endl;
    OutFile.close();
}

/* ===================================================================== */
/* Print Help Message                                                    */
/* ===================================================================== */

INT32 Usage()
{
    cerr << "This tool counts the number of dynamic instructions executed" << endl;
    cerr << endl << KNOB_BASE::StringKnobSummary() << endl;
    return -1;
}

/* ===================================================================== */
/* Main                                                                  */
/* ===================================================================== */
/*   argc, argv are the entire command line: pin -t <toolname> -- ...    */
/* ===================================================================== */

int main(int argc, char * argv[])
{
    // Initialize pin
    if (PIN_Init(argc, argv)) return Usage();

    OutFile.open(KnobOutputFile.Value().c_str());

    // Register Instruction to be called to instrument instructions
    INS_AddInstrumentFunction(Instruction, 0);

    // Register Fini to be called when the application exits
    PIN_AddFiniFunction(Fini, 0);
    
    // Start the program, never returns
    PIN_StartProgram();
    
    return 0;
}

pin소스코드를 해석할때는 main을 먼저보면서 해당 콜백 등록 함수를 보면 된다. 


물론 인클루드된, 아래의 헤더에 정의된 API들을 사용해서 DBI를 구현을 하는데, 

#include "pin.H"

궁금한 API는 여기서 찾아보면 된다.

https://software.intel.com/sites/landingpage/pintool/docs/67254/Pin/html/group__API__REF.html



다음의 규칙을 통해 API들을 명명한다.



소스를 한줄한줄 분석해보면,

    if (PIN_Init(argc, argv)) return Usage();

이것은 PIN tool을 사용하기 위한 Init함수이고, 무조건 들어가야 한다.


OutFile.open(KnobOutputFile.Value().c_str());

로그를 기록하기 위한 파일 오픈을 하고있다. KnobOutputFile 객체에다가 로그파일의 이름을 지정할 수 있다.

INS_AddInstrumentFunction(Instruction, 0);

INS는 각각의 인스트럭션 마다 콜백 함수를 지정한다. 콜백함수인 Instruction 을 보면,

// Pin calls this function every time a new instruction is encountered
VOID Instruction(INS ins, VOID *v)
{
    // Insert a call to docount before every instruction, no arguments are passed
    INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_END);
}

내부에서 InsertCall을 호출하는데 2번째 인자에서 BEFORE로 지정했으므로 ,  인스트럭션 앞에 docount라는 함수를 call하는 명령을 삽입해주는 역할을 한다.

docount함수를 한 번 보자.

// The running count of instructions is kept here
// make it static to help the compiler optimize docount
static UINT64 icount = 0;

// This function is called before every instruction is executed
VOID docount() { icount++; }

전역변수로 선언된 icount를 1증가시킨다. 


PIN_AddFiniFunction(Fini, 0);

위의 함수는 해당 프로그램이 종료될때 Fini라는 콜백을 등록해준다.


VOID Fini(INT32 code, VOID *v)
{
    // Write to a file since cout and cerr maybe closed by the application
    OutFile.setf(ios::showbase);
    OutFile << "Count " << icount << endl;
    OutFile.close();
}

Fini함수는 로그파일에 현재 icount를 write하고 스트림을 닫아준다.


이로써 해석된 내용을 보자면, 인스트럭션 마다 갯수를 세어서 로그파일에 기록해주는 프로그램이다.