00 윈도우 DLL 분석

원문 :  http://kkamagui.springnote.com/pages/388634

 

들어가기 전에...

 

개요

 보안 프로그램들이 실행될 때, 실행 중인 프로세스에 DLL을 밀어 넣는 것은 알만한 사람들은 다 아는 비밀(??)이다. DLL을 밀어넣어서 각종 보안에 필요한 API를 후킹한다던지 하는 작업을 하게되는데... 순간 의문점이 생겼다. DLL이 삽입(Injection)되어서 Kernel32.dll에 포함된 코드들을 변경할 경우, 코드의 영향력은 어디까지 일까? 해당 프로세스에만 영향을 미치는 걸까? 아니면 Kernel32.dll을 사용하는 프로그램 전체에 영향을 미치는 것일까?

 지금까지 여기저기 작업하면서 어렴풋이 해당 프로세스에만 영향을 미칠 것이라고 생각하고 있었는데, 오늘 제대로 한번 분석해 보았다. 일단 대상은 LoadLibrary로 정하였고 내가 Python으로 만든 디스어셈블러를 이용하여 분석하였다.

 

Kernel32.dll 정보 분석

 일단 Kernel32.dll에 있는 LoadLibrary() 함수를 찾아서 그 함수가 존재하는 영역의 메모리 정보를 얻어야 했다. 그래서 아래와 같은 테스트 프로그램을 작성하였다. 아래 코드는 VirtualQuery()와 VirtualProtect() 함수를 이용해서 간단히 메모리 영역에 대한 정보를 보고 변경하는 코드이다.

  1. #include "stdafx.h"
    #include <windows.h>
  2. /**
        Protect String을 표시한다.
    */
    void PrintProtectString( DWORD dwProtect )
    {
        if( dwProtect & PAGE_READONLY )
        {
            printf( "PAGE_READONLY " );
        }
       
        if( dwProtect & PAGE_READWRITE )
        {
            printf( "PAGE_READWRITE " );
        }
       
        if( dwProtect & PAGE_WRITECOPY )
        {
            printf( "PAGE_WRITECOPY " );
        }
       
        if( dwProtect & PAGE_EXECUTE )
        {
            printf( "PAGE_EXECUTE " );
        }
       
        if( dwProtect & PAGE_EXECUTE_READ )
        {
            printf( "PAGE_EXECUTE_READ " );
        }
       
        if( dwProtect & PAGE_EXECUTE_READWRITE )
        {
            printf( "PAGE_EXECUTE_READWRITE " );
        }
  3.     if( dwProtect & PAGE_EXECUTE_WRITECOPY )
        {
            printf( "PAGE_EXECUTE_WRITECOPY " );
        }
  4.     if( dwProtect & PAGE_GUARD )
        {
            printf( "PAGE_GUARD " );
        }
  5.     if( dwProtect & PAGE_NOACCESS )
        {
            printf( "PAGE_NOACCESS " );
        }
  6.     if( dwProtect & PAGE_NOCACHE )
        {
            printf( "PAGE_NOCACHE " );
        }
    }
  7. /**
        State를 출력한다.
    */
    void PrintState( DWORD dwState )
    {
        if( dwState & MEM_COMMIT )
        {
            printf( "MEM_COMMIT " );
        }
  8.     if( dwState & MEM_FREE )
        {
            printf( "MEM_FREE " );
        }
  9.     if( dwState & MEM_RESERVE )
        {
            printf( "MEM_RESERVE " );
        }
    }

  10. /**
        Type을 표시한다.
    */
    void PrintType( DWORD dwType )
    {
        if( dwType & MEM_IMAGE )
        {
            printf( "MEM_IMAGE " );
        }
  11.     if( dwType & MEM_MAPPED )
        {
            printf( "MEM_MAPPED " );
        }
  12.     if( dwType & MEM_PRIVATE )
        {
            printf( "MEM_PRIVATE " );
        }
    }
  13. /**
        Memory Information을 출력한다.
    */
    void DumpMemoryInformation( MEMORY_BASIC_INFORMATION* pstMbi )
    {
        printf( "Type : " );
        PrintType( pstMbi->Type );
        printf( "\n" );
  14.     printf( "State : " );
        PrintState( pstMbi->State );
        printf( "\n" );
  15.     printf( "Base Address : 0x%X\n", pstMbi->BaseAddress );
  16.     printf( "Allocation Base : 0x%X\n", pstMbi->AllocationBase );
  17.     printf( "Region Size : %d\n", pstMbi->RegionSize );
  18.     printf( "Allocation Protect : " );
        PrintProtectString( pstMbi->AllocationProtect );
        printf( "\n" );
  19.     printf( "Protect : " );
        PrintProtectString( pstMbi->Protect );
        printf( "\n\n" );
    }
  20. int main(int argc, char* argv[])
    {
        HMODULE hModule;
        BYTE* pbOpenProcess;
        MEMORY_BASIC_INFORMATION stMbi;
        DWORD dwWriteEnable;
        DWORD dwOldProtect;
  21.     hModule = LoadLibrary( "Kernel32.dll" );
        pbOpenProcess = ( BYTE* ) GetProcAddress( hModule, "LoadLibraryA" );
  22.     // OpenProcess가 있는 영역의 속성을 본다.
        if( VirtualQuery( pbOpenProcess, &stMbi, sizeof( stMbi ) ) == 0 )
        {
            printf( "Virtual Query Error\n" );
            return 0;
        }
  23.     // 일단 막무가네 쓰기 테스트
        // 여기서 이렇게 하면 에러난다.
        //pbOpenProcess[ 0 ] = 0xFF;
  24.     // 정보 출력
        printf( "Befere Protect Change=================\n" );
        DumpMemoryInformation( &stMbi );
  25.     // Write Enable을 하고나면
        dwWriteEnable = stMbi.Protect;
        dwWriteEnable &= ~( PAGE_READONLY | PAGE_EXECUTE_READ );
        dwWriteEnable |= ( PAGE_READWRITE );
        if( VirtualProtect( pbOpenProcess, stMbi.RegionSize, dwWriteEnable,
            &dwOldProtect ) == FALSE )
        {
            printf( "Write Protect Error\n" );
            return 0;
        }
  26.     // 막무가네 쓰기
        // 이렇게 하면 성공한다.
        pbOpenProcess[ 0 ] = 0xFF;
  27.     // OpenProcess가 있는 영역의 속성을 본다.
        if( VirtualQuery( pbOpenProcess, &stMbi, sizeof( stMbi ) ) == 0 )
        {
            printf( "Virtual Query Error\n" );
            return 0;
        }
  28.     // 정보 출력
        printf( "After Protect Change=================\n" );
        DumpMemoryInformation( &stMbi );
  29.     return 0;
    }

 

 위의 코드를 Console로 만들어서 실행하면 아래와 같이 LoadLibrary() 함수 영역에 대한 정보를 뽑을 수 있다.

MemoryInfo.PNG

 일단 메모리의 STATE가 MEM_COMMIT인 것을 보면 물리 메모리가 맵핑된 상태의 가상메모리 주소임을 알 수 있고, Allocation Protect가 PAGE_EXECUTE_WIRTECOPY 인 것을 보아 쓰기가 되면 메모리가 새로 복사되어 맵핑될 것이라는 것을 알 수 있다.

 실제 위의 코드에서 보면 Before Protect Change와 After Protect Chage 사이에서 메모리에 보호 설정을 변경하여 제일 첫바이트를 0xFF로 변경한 것을 볼 수 있는데, 이때 쓰기와 동시에 Page 한개의 크기(4096Byte)만큼 새로 할당되어 맵핑된 것으로 추측된다. 이것은 위에 Region Size가 4096으로 변경된 것을 보고 추측한 것이다.

 

변경 영향력 검사

 위의 포로그램을 실행한 상태에서 디스어셈블리어를 실행시켜 LoadLibrary()를 디스어셈블리시켜 보았다. 만약 저 변경이 글로벌한 것이라면 디스어셈블리의 결과 첫바이트가 0xFF로 변경되어 나와야 한다. 일단 아래 결과를 보자.

0x7c801d77 (02) 8BFF                 MOV EDI, EDI
0x7c801d79 (01) 55                   PUSH EBP
0x7c801d7a (02) 8BEC                 MOV EBP, ESP

 위 결과에서 보는 것과 같이 0x8B로 변하지 않았다는 것을 알 수 있다. 이것은 변화가 해당 프로세스에 국한 된다는 것을 말한다.

 

결론

 위의 결과로 보아 VirtualProtect를 이용함 메모리 수정은 해당 프로세스에 종속되므로 결국 모든 프로세스에 DLL을 삽입해야 할 것 같다. 어렴풋이 알고 있는 것을 제대로 알게 되어 다행이다. 역시 직접 해보는 것이 최고 @0@)/~!!

 

ps)

만약 kernel32.dll에 실제 물리주소를 얻어서 직접 수정을 가한다면 모든 프로세스에 DLL을 삽입하지 않아도 될 것 같은데... 상당히 위험한 방법일 것 같기도 하고 ㅡ_ㅡ;;;;(많이 위험할래나...)

 

이 글은 스프링노트에서 작성되었습니다.

+ Recent posts