외규장각 도서 환수 모금 캠페인

Search Results for '분류 전체보기'

279 POSTS

  1. 2007.06.19 Function Interception
  2. 2007.06.19 Controlling Memory Space of Other Process
  3. 2007.06.19 Detecting Application Debugger
  4. 2007.06.19 DLL 관련 MSDN 문서
  5. 2007.06.19 Calling Conventions in Win32 System

Function Interception

Posted 2007. 6. 19. 15:15, Filed under: Study/Computer Science

Function Interception

Notice the DLL project in the zip file. This function is in HookApi.h.

// Macro for adding pointers/DWORDs together without C arithmetic interfering 
// -- Taken from Matt Pietrek's book
// Thought it'd be great to use..
#define MakePtr( cast, ptr, addValue ) (cast)( (DWORD)(ptr)+(DWORD)(addValue))
//This code is very similar to Matt Pietrek's, except that it is written 
//according to my understanding...
//And Matt Pietrek's also handles Win32s 
//--(Because they it has some sort of a problem)
PROC WINAPI HookImportedFunction(HMODULE hModule,
			         //Module to intercept calls from
     PSTR FunctionModule, //The dll file that contains the function you want to 
			  //hook(ex: "USER32.dll")
     PSTR FunctionName,   //The function that you want to hook 
			  //(ex: "MessageBoxA")
     PROC pfnNewProc)     //New function, this gets called instead
{
    PROC pfnOriginalProc; //The intercepted function's original location
    IMAGE_DOS_HEADER *pDosHeader; 
    IMAGE_NT_HEADERS *pNTHeader;
    IMAGE_IMPORT_DESCRIPTOR *pImportDesc;
    IMAGE_THUNK_DATA *pThunk;
    // Verify that a valid pfn was passed

    if ( IsBadCodePtr(pfnNewProc) ) return 0; 

    pfnOriginalProc = GetProcAddress(GetModuleHandle(FunctionModule), 
                                                         FunctionName);
    if(!pfnOriginalProc) return 0;
    pDosHeader = (PIMAGE_DOS_HEADER)hModule; 
    //kindly read the ImgHelp function reference 
    //in the Image Help Library section in MSDN
    //hModule is the Process's Base address  (GetModuleHandle(0)) 
    //even if called in the dll, it still gets the hModule of the calling process
    //---That's you should save the hInstance of the DLL as a global variable, 
    //in DllMain(), because it's the only way to get it(I think)
    // Tests to make sure we're looking at a module image (the 'MZ' header)
    if ( IsBadReadPtr(pDosHeader, sizeof(IMAGE_DOS_HEADER)) )
        return 0;
    if ( pDosHeader->e_magic != IMAGE_DOS_SIGNATURE ) 
	//Image_DOS_SIGNATURE is a WORD (2bytes, 'M', 'Z' 's values)
        return 0;
    // The MZ header has a pointer to the PE header
    pNTHeader = MakePtr(PIMAGE_NT_HEADERS, pDosHeader, pDosHeader->e_lfanew); 
    //it's like doing pDosHeader + pDosHeader->e_lfanew
    // e_lfanew contains a RVA to the 'PE\0\0' Header...An rva means, offset,
    // relative to the BaseAddress of module 
    // -pDosHeader is the base address..and e_lfanew is the RVA, 
    // so summing them, will give you the Virtual Address..
    // More tests to make sure we're looking at a "PE" image
    if ( IsBadReadPtr(pNTHeader, sizeof(IMAGE_NT_HEADERS)) )
        return 0;
    if ( pNTHeader->Signature != IMAGE_NT_SIGNATURE ) 
	//IMAGE_NT_SIGNATURE is a DWORD (4bytes, 'P', 'E', '\0', '\0' 's values)
        return 0;
    // We now have a valid pointer to the module's PE header. 
    // Now get a pointer to its imports section
    pImportDesc = MakePtr(PIMAGE_IMPORT_DESCRIPTOR, 
      pDosHeader, //IMAGE_IMPORT_DESCRIPTOR *pImportDesc;
      pNTHeader->OptionalHeader.DataDirectory
       [IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

    //What i just did was get the imports section by getting the RVA of it
    //(like i did above), then adding the base addr to it.
    //// pNTHeader->OptionalHeader.DataDirectory
    ///     [IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress
    //// IMAGE_DIRECTORY_ENTRY_IMPORT==1 -- Look at that PE documentation. 
    //// Pietrek's articles in MSJ and MSDN Magazine will be real helpful!
    //Go out if imports table doesn't exist
    if ( pImportDesc == (PIMAGE_IMPORT_DESCRIPTOR)pNTHeader )
        return 0; //pImportDesc will ==pNTHeader. 
	//if the RVA==0, cause pNTHeader+0==pNTHeader -> stored in pImportDesc
	//Therefore, pImportDesc==pNTHeader
    // Iterate through the array of imported module descriptors, looking
    // for the module whose name matches the FunctionModule parameter
    while ( pImportDesc->Name ) //Name is a DWORD (RVA, to a DLL name)
    {
        PSTR pszModName = MakePtr(PSTR, pDosHeader, pImportDesc->Name);

        if ( stricmp(pszModName, FunctionModule) == 0 ) 
	    //str"i"cmp,,, suggest you to ignore cases when comparing,
            break; //or strcmpi() in some compilers
        pImportDesc++;  // Advance to next imported module descriptor
    }
    // Get out if we didn't find the Dll name. 
    // pImportDesc->Name will be non-zero if we found it.
    if ( pImportDesc->Name == 0 )
        return 0;
 // Get a pointer to the found module's import address table (IAT)
 //           =====IMAGE_THUNK_DATA *pThunk;
    pThunk = MakePtr(PIMAGE_THUNK_DATA, pDosHeader, pImportDesc->FirstThunk);
 //This is what i was talkin about earlier...
 //In pThunk, if it was image loaded in memory, you'll get the address to 
 //entry point of functions
 //but in a disk file, It's a function name

 // Look through the table of import addresses, of the found 
 // DLL, looking for the function's entry point that matches 
 // the address we got back from GetProcAddress above.
    while ( pThunk->u1.Function )
    {
       if ( (DWORD)pThunk->u1.Function == (DWORD)pfnOriginalProc )
        {
       // We found it!  Overwrite the original address with the
       // address of the interception function.  Return the original
       // address to the caller so that they can chain on to it.
            pThunk->u1.Function = (PDWORD)pfnNewProc; 
	    // pfnNewProc is in the parameters of the function
	    //pfnOriginalProc = (PROC)(DWORD)pdw1;
            return pfnOriginalProc;
        }

        pThunk++;   // Advance to next imported function address
    }
    return 0; //function not found!!!!!
}
















PE 파일의 import address table을 직접 수정하는 방식으로 로드된 라이브러리가 호출하는 함수를 변경하고 있다.
자신이 작성한 코드를 수행한 후에 원래의 함수로 제어를 돌려주는 식으로 코드를 작성한 후에  원래 함수를 대체하는 방식으로 사용할 수 있겠다.
Response : ,

Controlling Memory Space of Other Process

Posted 2007. 6. 19. 14:55, Filed under: Study/Computer Science
================================================================================
Title : Controlling Memory Space of Other Process

Author : proXima
Contact : proxima ]at[ postech.ac.kr
Date : 2006/11/15 - 2006/11/15
================================================================================

요즘의 OS들은 대부분 보호모드(Protected Mode)를 지원한다.
보호모드라는 것은, 프로세스 내에서 메모리를 액세스 할 때에 자신의 가상 메모리 공간을 자신의 영역이라고 간주하도록 하는 것이다. 무슨 이야기냐 하면, OS는 프로세스 간에 메모리를 건드리지 못하게 보호해 준다는 것이다.
하지만 MS Windows에서는 디버깅을 위해 다른 프로세스의 메모리를 건드릴 수 있게 해 주는 API를 제공해 주는데, 그것들은 다음과 같다.



BOOL __stdcall ReadProcessMemory(
 HANDLE hProcess,
 LPCVOID lpBaseAddress,
 LPVOID lpBuffer,
 SIZE_T nSize,
 SIZE_T* lpNumberOfBytesRead
);


BOOL __stdcall WriteProcessMemory(
 HANDLE hProcess,
 LPVOID lpBaseAddress,
 LPCVOID lpBuffer,
 SIZE_T nSize,
 SIZE_T* lpNumberOfBytesWritten
);


 
함수의 이름만 봐도, 뭐 하는 함수인지를 뻔히 알 수가 있다.
이 두 함수는 다른 프로세스의 메모리 공간에 있는 데이터를 읽어오거나(ReadProcessMemory) 혹은 다른 프로세스 메모리 공간에 데이터를 쓸 수 있게(WriteProcessMemory) 해 준다.

위에서 사용되는 인자 중 hProcess는 OpenProcess 혹은 CreateProcess를 통해 얻어온 타겟 프로세스의 핸들값이어야 한다. 그리고 lpBaseAddress는 타겟 프로세스의 가상메모리 주소여야 한다.
뭐 나머지는 별로 문제 될만한 건 없어 보인다.

그러면 간단하게 남의 프로세스 메모리를 읽어오는 프로그램을 만들어 보자.


HANDLE hTargetProcess = OpenProcess(PROCESS_VM_READ, FALSE, pid);
ReadProcessMemory(hTargetProcess, (LPCVOID)BaseAddr, (LPVOID)buf, \
                  sizeof(buf), (SIZE_T*)&nWritten);
CloseHandle(hTargetProcess);


 
간단하지만, 실수의 여지가 조금 있다. OpenProcess로부터 받아온 HANDLE값을 CloseHandle로 닫아주지 않을 경우가 생길 수 있다. 그러면 핸들이 계속 남아있게 된다.
항상 저렇게 3줄을 세트로 사용한다면 문제 없겠지만, 실수를 원천봉쇄하는 방법이 있다.
바로 Toolhelp 함수를 사용하는 것인데, 다음과 같다.


BOOL __stdcall Toolhelp32ReadProcessMemory(
  DWORD th32ProcessID,
  LPCVOID lpBaseAddress,
  LPVOID lpBuffer,
  SIZE_T cbRead,
  SIZE_T lpNumberOfBytesRead
);
 
아쉽게도, ReadProcessMemory만 된다. WriteProcessMemory에 대한 것은 없지만, 직접 만들어서 사용하여도 무방할 듯 하다.
아주 간단하게 Toolhelp32ReadProcessMemory처럼 동작하는 WriteProcessMemory에 대한 함수도 만들 수 있는데, 일단 Toolhelp32ReadProcessMemory 함수의 내부를 좀 보자.


MOV EDI,EDI
PUSH EBP
MOV EBP,ESP
PUSH ESI
PUSH DWORD PTR SS:[EBP+8]
PUSH 0
PUSH 10
CALL kernel32.OpenProcess
MOV ESI,EAX
TEST ESI,ESI
JE SHORT kernel32.7C863D5A
PUSH EDI
PUSH DWORD PTR SS:[EBP+18]
PUSH DWORD PTR SS:[EBP+14]
PUSH DWORD PTR SS:[EBP+10]
PUSH DWORD PTR SS:[EBP+C]
PUSH ESI
CALL kernel32.ReadProcessMemory
PUSH ESI
MOV EDI,EAX
CALL kernel32.CloseHandle
MOV EAX,EDI
POP EDI
POP ESI
POP EBP
RETN 14


 
디버거에서 코드를 가져다가 붙이는 바람에, JE SHORT kernel32.7C863D5A 라는 코드가 그대로 들어갔는데, 실제로 7C863D5A의 주소에 있는 명령은 밑에서 세 번째 줄에 있는 POP ESI이다.
이 코드가 조금 복잡해 보이더라도, 실제로 보면 굉장히 간단하다. 이 코드는 위에 C코드로 설명된 OpenProcess -> ReadProcessMemory -> CloseHandle 의 과정을 하나의 함수에 담아 실행되도록 만들어 둔 것을 뜻한다.
OpenProcess를 실행하기 전에 PUSH 10 을 하는 이유는, 그것이 PROCESS_VM_READ 플래그이기 때문이다.

아래에 MyWriteProcessMemory를 한 번 만들어 보도록 하겠다.
다른 부분은 다 같지만, PROCESS_VM_READ 대신 PROCESS_VM_WRITE를 통해 프로세스를 열어야 한다는 점이 다를 수 있다.


BOOL __stdcall MyWriteProcessMemory(
 DWORD pid,
 LPVOID lpBaseAddress,
 LPCVOID lpBuffer,
 SIZE_T cbWritten,
 SIZE_T* lpNumberOfBytesWritten)
{
    BOOL bRet = FALSE;

    HANDLE hTargetProcess = OpenProcess(PROCESS_VM_WRITE, FALSE, pid);
    if(hTargetProcess == NULL)
    {
        return bRet;
    }

    bRet = WriteProcessMemory(hTargetProcess, lpBaseAddress, lpBuffer, \
                              cbWritten, lpNumberOfBytesWritten);
    CloseHandle(hTargetProcess);

    return bRet;
}
 
이런 API들을 이용하여 게임핵처럼 다른 프로세스의 메모리를 읽고 쓰고 하는 프로그램들을 만들 수 있다. 물론 그런 프로그램들을 만드는 것이 다는 아니다.
총 여섯 편으로 계획한 연재 내용에는 없지만, 나중에 시간이 허락한다면 다른 프로세스의 메모리를 읽어서 그 프로세스의 메모리 공간에 떠 있는 모듈들이 어떤 함수들을 import하고 export하는 지를 알아내는 과정에 대해 조금 더 설명하도록 하겠다.
또한, 이와 같이 다른 프로세스의 메모리에 쓰기가 가능하기 때문에 'DLL Injection' 이라는 매우 강력한 무엇인가를 할 수 있게 되는데, DLL Injection에 대해서는 다음 글에서 자세히 다루도록 하겠다.

------------
dll 인젝션 찾다가 같이 찾은 자료들.
이런 내용들을 나도 모르는 것은 아니지만 이렇게 체계적으로 정리하고 깊이 파고드는 자세가 부족했던 것 같다.

상당히 자극 받았음 ㅋ
Response : ,

Detecting Application Debugger

Posted 2007. 6. 19. 14:51, Filed under: Study/Computer Science
================================================================================
Title : Detecting Application Debugger

Author : proXima
Contact : proxima ]at[ postech.ac.kr
Date : 2006/11/09 - 2006/11/15
================================================================================

보통 reverse engineering을 하기 위해서는 debugger와 disassembler를 사용한다.
하지만 어떤 프로그램들에는 anti-debug 루틴이 들어있어, debugger를 사용하기 어렵게 하기도 한다.
이 글에서는 간단하게 사용할 수 있는 몇 가지 방법을 소개하도록 하겠다.

PLUS 06학번들을 뽑을 때 냈던 문제에도 간단한 anti-debug 루틴이 들어있다.


1. IsDebuggerPresent
Win32 API 중에 IsDebuggerPresent 라는 함수가 있다. 만약 debug가 attach 된 프로세스에서 이 함수를 실행하게 되면 1이 리턴된다. WinXP에서 제공하는 IsDebuggerPresent 함수의 내부를 살펴보자.

앞으로 나오는 모든 코드에 포함된 숫자들은 16진수 값이다.


mov eax, dword ptr fs:[18]
mov eax, dword ptr ds:[eax+30]
mov eax, dword ptr ds:[eax+2]
retn
 
위에서 보는 것과 같이 매우 간단하다.
이렇게 간단한 코드가 어떻게 debugger를 디텍트할 수 있는지 살펴보자.

코드의 첫 줄을 보자. fs:[18] 을 볼 수 있다.
fs는 file segment로, fs:[0]에는 SEH(Structured Exception Handler)가 위치한다.
SEH 구조체의 내용 중에 TEB(Thread Environment Block)의 포인터가 존재하는데,
그 포인터의 위치가 바로 fs:[18] 이다.
첫 줄의 의미는, eax에 TEB의 스타트 포인트를 가져온다는 것이다.

두 번째 줄을 보자. ds:[eax+30] 에는 무엇이 있을까?
TEB 구조체에서 0x30 오프셋에 있는 내용은 PEB(Process Environment Block)의 포인터이다.
PEB에는 프로세스의 컨텍스트나 쓰레드 목록, 핸들 테이블 등, 프로세스에 specific한 정보들이 들어있다.

마지막줄을 보자. ds:[eax+2] 에는 무엇이 있을까?
결국 PEB의 0x02 오프셋에 있는 내용을 확인하겠다는 것인데,
PEB의 0x02 오프셋에는 BeingDebugged 라는 1바이트짜리 플래그가 들어있다.
디버거가 attach될 경우에는 이 BeingDebugged 플래그가 1이 되는데,
IsDebuggerPresent는 바로 이 플래그 값을 확인하여 자신이 디버그 당하고 있는지를 확인하는 것이다.

IsDebuggerPresent 함수를 직접 사용하는 경우는 함수에 BreakPoint를 걸어 건너뛰기 쉬우므로,
인라인 어셈을 이용하여 직접 어셈 코드를 적어주는 것도 하나의 방법이 될 수 있다.


2. Using Exception
디버거로 프로그램을 디버깅 하다가
데이터 혹은 코드를 잘못 건드리는 바람에 exception이 발생한 적 없는가?
보통은 디버거로 실행하는 중에 exception이 발생하게 되면,
프로그램 내부에 있는 exception handler보다 디버거가 먼저 exception을 캐치한다.
그것을 이용하여 디버깅을 조금 곤란하게 할 수 있다.

하지만 이 방법은 위험하고 또 오히려 프로그램을 짜는 입장에서 스스로 디버깅을 하다가
곤란해지는 경우가 생길 수 있으므로 별로 추천하고 싶지는 않다.

간단하게 소개만 하도록 하겠다.


int SEH1(unsigned int code, LPEXCEPTION_POINTERS ep)
{
  if(code == EXCEPTION_ILLEGAL_INSTRUCTION)
  {
    // (2)
    return EXCEPTION_EXECUTE_HANDLER;
  } else {
    // not expected!!!
    return EXCEPTION_CONTINUE_SEARCH;
  }
}

:
:
:

{
  :
  :
  :
  __try {
    // (1)
    __asm __emit 0x8f // intentional illegal instruction
  } __except {
    // (2)
  }
}

 
연속으로 실행되어야 하는 코드들이 있다고 하면,
그것을 위와 같은 코드에 두 개 혹은 세 개로 나누어 쓸 수 있게 된다.
(1) 이라고 된 부분에 첫 부분을 쓰고,
(2) 라고 된 부분에 두번째 부분을 쓰고,
(3) 라고 된 부분에 세번째 부분을 쓴다.

__asm __emit 0x8f 는 일부러 없는 인스트럭션을 실행하도록 하여 EXCEPTION_ILLEGAL_INSTRUCTION 예외를 발생시키는 역할을 한다.

그러면 실행하는 도중에 (1)의 코드가 실행되고 illegal instruction의 예외가 발생한다.
예외가 발생한 경우에 만약 디버거가 붙어있다면, 디버거가 예외를 잡아내고
더 이상 진행할 수 없는 상태가 된다. (물론 상태를 변경한다면 계속 진행할 수 있다.)

디버거가 붙어있지 않다면 SEH1의 함수 내부로 들어가 (2)가 실행된 뒤 리턴되어 (3)이 실행된다.
__try - __except 구문을 사용하는 함수에 넘어오는 인자는 (2)에서는 사용할 수가 없다.



3. NtGlobalFlag


mov eax, dword ptr fs:[30]
mov eax, dword ptr ds:[eax+68]
 

fs:[30]이 다시 등장했다.
위에서도 설명했듯이 fs:[30]에는 PEB가 존재하는데, 그 PEB의 0x68 오프셋에는 NtGlobalFlag라는 값이 들어있다.
1번에서 설명한 IsDebuggerPresent와 비슷하지만, 다른 점이라면 IsDebuggerPresent는 Windows에서 공식적으로 제공해 주는 함수인 반면, NtGlobalFlag는 그렇지 않다는 점이다.
만약 위의 코드가 실행된 뒤에 eax에 0x70의 값이 들어있다면, being debugged이다.


위에서 세 가지의 debugger detect 방법을 설명하였다.
실제로 위의 방법들이 전부는 아니다.
debugger를 찾아내는 방법들은 더 많이 있으며, 이런 방법으로도 찾아낼 수 있다는 것을 소개한 정도로 마치도록 하겠다.
Response : ,

DLL 관련 MSDN 문서

Posted 2007. 6. 19. 14:46, Filed under: Study/Computer Science
http://msdn2.microsoft.com/en-us/library/ms682592.aspx

Dynamic-Link Library Creation

To create a Dynamic-Link Library (DLL), you must create one or more source code files, and possibly a linker file for exporting the functions. If you plan to allow applications that use your DLL to use load-time dynamic linking, you must also create an import library.

Creating Source Files

The source files for a DLL contain exported functions and data, internal functions and data, and an optional entry-point function for the DLL. You may use any development tools that support the creation of Windows-based DLLs.

If your DLL may be used by a multithreaded application, you should make your DLL "thread-safe". You must synchronize access to all of the DLL's global data to avoid data corruption. You must also ensure that you link only with libraries that are thread-safe as well. For example, Microsoft Visual C++ contains multiple versions of the C Run-time Library, one that is not thread-safe and two that are.

Exporting Functions

How you specify which functions in a DLL should be exported depends on the tools that you are using for development. Some compilers allow you to export a function directly in the source code by using a modifier in the function declaration. Other times, you must specify exports in a file that you pass to the linker.

For example, using Visual C++, there are two possible ways to export DLL functions: with _declspec modifier or with a .def file. If you use the _declspec modifier, it is not necessary to use a .def file. For more information, see Exporting from a DLL.

Creating an Import Library

An import library (.lib) file contains information the linker needs to resolve external references to exported DLL functions, so the system can locate the specified DLL and exported DLL functions at run time.

For example, to call the CreateWindow function, you must link your code with the import library User32.lib. The reason is that CreateWindow resides in a system DLL named User32.dll, and User32.lib is the import library used to resolve the calls to exported functions in User32.lib in your code. The linker creates a table that contains the address of each function call. Calls to functions in a DLL will be fixed up when the DLL is loaded. While the system is initializing the process, it loads User32.dll because the process depends on exported functions in that DLL, and it updates the entries in the function address table. All calls to CreateWindow invoke the function exported from User32.dll.

For information, see Linking Implicitly with a DLL.

Warning  Calling the ExitProcess function in a DLL can lead to unexpected application or system errors. Be sure to call ExitProcess from a DLL only if you know which applications or system components will load the DLL and that it is safe to call ExitProcess in this context.

See Also

Creating a Simple Dynamic-link Library
DLLs (Visual C++)

Response : ,

Calling Conventions in Win32 System

Posted 2007. 6. 19. 14:25, Filed under: Study/Computer Science
================================================================================
Title : Calling Conventions in Win32 System

Author : proXima
Contact : proxima ]at[ postech.ac.kr
Date : 2006/10/27
================================================================================

calling convention에 대해 잘 알아두면,
프로그래밍할 때 도움이 될 수 있다.

그래서 간단하게 정리해 보았다.
읽다가 잘 이해가 안 되는 부분이 있으면, 끝까지 다 읽은 다음에 다시 한 번 보면 좋다.
왜냐면 이게 내용 비교가 서로 얽히는 부분이 있기 때문인데,
처음 읽고 개략적으로 어떤 것인지에 대해 알고 나면 한결 이해하기가 쉬울 것이라고 생각한다.
아래에는 __cdecl, __stdcall, __fastcall, __thiscall, naked 의 다섯 가지를 설명해 두었다.

1. __cdecl


int __cdecl functionA(int a, int b);
 

와 같은 형식으로 선언한다.
뭐라고 읽어야 할 지는 잘 모르겠지만, C declare 인 것 같다.
VC에서는 함수를 선언할 때, calling convention을 따로 지정해 주지 않으면 기본적으로 __cdecl로 선언된다. 물론 컴파일 설정에서 바꿀 수도 있다. (VS.NET이라면 구성 속성->C/C++->고급 에서 '호출 규칙'을 변경하면 된다.)

만약 main 함수에서 functionA를 호출할 경우, main 함수에서 functionA를 호출하는 부분은 다음과 같다.


push 2
push 1
call functionA
add esp, 8
 

그리고 functionA 함수가 끝나는 부분은 다음과 같다.

:
retn
 

functionA 함수를 호출한 주체(여기서는 main)가 스택을 정리해 준다.
(add esp, 8이 스택을 정리한다.)
때문에 함수 호출이 많은 경우 스택을 정리하는 코드가 여러 번 들어가야 하기 때문에 코드의 용량이 커질 수 있다. 하지만 그렇다고 무조건 쓰지 말라는 건 아니다. 이것도 필요할 때가 있다. 아래의 __stdcall에서 설명하도록 하겠다.



2. __stdcall


int __stdcall functionB(int a, int b);
 

와 같은 형식으로 선언한다.
standard call 이라는 뜻이며, __cdecl과는 스택을 정리하는 주체가 다르다는 차이점이 있다.

만약 위에서와 같이 main 함수에서 functionB를 호출한다고 하면, main은 __cdecl과 같다. 그러니 이번에는 functionB의 끝나는 부분만 보자.

:
retn 8
 

__stdcall 의 경우에는 functionB가 실행을 완료한 뒤, 자신이 필요로 했던 parameter들의 크기만큼 스택을 정리하며 종료한다. retn 8 에서 숫자 8이 의미하는 것은 8바이트를 정리하고 돌아가겠다는 의미이다.
이런 경우는 코드의 용량은 적게 할 수 있을지 모르지만, 제약이 있다. 함수가 받아들이는 parameter의 크기가 항상 static해야 한다는 점이다.
위의 __cdecl의 설명 중에, __cdecl이 필요할 때가 있다고 했는데, 바로 ... 이라는 parameter를 가지는 함수들을 만들 때이다. 예를 들면 printf가 있는데, printf의 경우는 그 안에 들어오는 format이 어떻게 되느냐에 따라 스택을 얼마나 탐색할 것인가를 런타임에 결정한다. 이렇게 함수에 넘기는 parameter의 크기가 정해지지 않는 경우는 함수의 내부에서 사용하는 스택의 크기가 static하지 않기 때문에 이런 함수들은 __stdcall 로 만들 수 없다.

Windows에서 제공하는 API들은 대부분 __stdcall로 선언되어 있다.
API의 선언부에 WINAPI나 CALLBACK 같은 것들이 실제로는 __stdcall을 define한 것이다.



3. __fastcall

int __fastcall functionC(int a, int b, int c, int d);
 

와 같은 형식으로 선언한다.
말 그대로 fast call 이며, __stdcall과 거의 비슷하지만, 함수의 실행이 조금 빠르다.
왜 조금 빠를 수 있냐면, parameter를 레지스터로 넘기기 때문이다. 하지만 모든 parameter를 레지스터로 넘기는 것은 아니다. 첫번째 parameter와 두번째 parameter를 각각 ecx, edx를 통해 넘기게 되고, 세번째 parameter부터는 스택에 쌓도록 되어 있다.

main 함수에서 functionC를 호출한다고 하면, main 함수는 다음과 같이 된다.

push 4
push 3
mov edx, 2
mov ecx, 1
call functionC
 

그리고 functionC의 마지막 부분은 다음과 같이 된다.

:
retn 8
 

이번에는 parameter를 네 개나 넘겼는데도 실제로 functionC가 정리하고 나오는 스택의 크기는 8바이트밖에 되지 않는다. 그 이유는 앞에서도 설명했듯이 parameter 두 개는 레지스터를 통해 전달되어 스택에는 두 개의 parameter밖에 들어가지 않았기 때문이다.

사족(蛇足) : 사실 __fastcall을 사용해 보지 않았기 때문에 어떤 문제가 생길 수 있는지에 대해서는 잘 모르겠지만, 아마 연산에 사용할 수 있는 레지스터가 두 개 줄어들기 때문에, 복잡한 연산을 해야 하는 경우는 오히려 속도의 저하가 있을 가능성도 있지 않을까 하는 생각이 든다.



4. __thiscall
__thiscall은 따로 선언하는 방법이 없다. 단, class의 method의 경우 자동으로 __thiscall로 선언이 된다. __thiscall의 특징은, C++에서 사용하는 this 포인터가 ecx에 들어간다는 것이다. ecx에 있는 주소값을 이용하여 그 클래스의 member variable이나 method 들을 사용할 수 있게 된다.
그 외 parameter를 넘기는 방식이나 스택을 정리하는 주체는 __stdcall과 같다.



5. naked

int __cdecl functionD(int a, int b, int c, int d);
:
:
int __declspec(naked) __cdecl functionD(int a, int b, int c, int d)
{
   // contents
}
 

와 같은 형태로 선언한다.
위에 선언하는 형태가 조금 이상하다고 느꼈을지도 모르겠다. 그렇다. 실제로 naked는 __cdecl, __stdcall, __fastcall 등과 조금 스타일이 다르다. 실제로 다른 calling convention들과 함께 선언할 수도 있다. 단, 각각의 calling convention에 따라 retn이나 스택 및 레지스터의 상태를 고려하여 내부를 작성해야 한다. 뭐 그래도 다행스럽게 넘어오는 parameter들은 그 변수명을 그대로 사용할 수 있다. 실제로 함수 내부에서 a, b가 필요하다면, 굳이 스택에서 어느 정도 거리에 있는지를 계산할 필요까지는 없다는 것이다. 그냥 a, b라고 쓰면 된다.

그리고 naked는 함수 prototype으로는 선언할 수 없도록 되어 있다. 무슨 말이냐 하면, 함수 prototype을 선언할 때에는 __declspec(naked)를 붙일 수 없다는 것이다. 함수를 작성하는 부분에서만 사용할 수 있다. 만약 prototype에 선언하게 되면 에러를 만나게 될 것이다.

naked는 사전적으로 벌거벗었다는 뜻이다. 그러면 대체 왜 함수가 벌거벗었는가... 다음과 같다.
naked로 함수를 선언하게 되면, 함수를 작성할 때 기본적으로 작성되는 다음의 코드들이 전혀 존재하지 않는다.

함수 시작시

push ebp
mov ebp, esp
 


함수 종료시

retn
 

필요하게 되면, 이것들을 직접 inline assembly로 입력해 주어야 한다.
그리고 중요한 점 하나는, 반드시 함수를 종료하는 부분은 선언한 다른 convention에 맞춰주어야 한다는 것이다.
__cdecl로 선언하고서 retn 8 과 같이 스스로 스택을 정리하고 나오면 안 된다.
왜냐하면 naked로 선언하면 이것은 __cdecl에서와 같이, 호출하는 쪽에서 스택을 정리해 주는 형태로 컴파일이 되기 때문에, 만약 함수 내부에서 스택을 정리하고 나오도록 작성했다면, 호출이 종료된 뒤에 스택에서 local variable이나 return address 등이 날아가는 상황을 볼 수 있다.

이런 것들이 프로그래밍을 할 때 무슨 도움이 될 수 있겠냐고 질문할 수도 있겠지만, 뭐 그래도 알아두면 언제 써먹을 기회가 생길 지도 모르지 않을까 하고 생각하고 있다. 가끔 reverse engineering을 할 때, 어셈 코드에서 리턴부분을 보면서 '아 이건 __cdecl로 선언한 거로군' 뭐 이러고 혼자 생각할 때가 있다. 하지만 바람직하진 않은 것 같다.

-----------
웹에서 이것저것 찾아보다가 발견한 글.
내가 지난 번에 올렸던 파스칼 방식, C 방식의 함수 호출 방식 이외에도 여러가지 방식들을 잘 설명해 놓았다.

자료의 출처는  PLUS at POSTECH 인듯. (학부생)
역시 대단한 사람들이 많구나.

나랑 비슷한 나이또래 ( 동갑 아니면 한살 많거나.) 같은데 정말 대단한 듯.
능력의 차이라기보다는 경험과 환경의 차이라고 믿고 싶다.
그런만큼 나도 열심히 하련다.
Response : ,

« Previous : 1 : ··· : 26 : 27 : 28 : 29 : 30 : 31 : 32 : ··· : 56 : Next »

Recent Posts

Recent Comments

Recent Trackbacks

Total hit (Today , Yesterday )

Admin Write Post