심플스 - 프로그램과 책 이야기로 가득한 곳, (Simples.Kr)

  

 
몇 년전부터 화면캡쳐방지를 해주는 솔루션들이 등장하기 시작했습니다.
어떻게보면 좀 오래된 분야지만 아직까지도 완벽하게 화면캡쳐방지를 막는 것은 사실상 불가능합니다.
그래도 어차피 100% 다 막을 수가 없기 때문에 적어도 99%정도는 막는다라는 취지에서 이러한 솔루션들이 등장하고 있습니다.

화면캡쳐방지에 대해 거론하기 전에 우선 화면캡쳐를 어떻게 하는지 당연히 알고 있어야 합니다.
방어를 할려면 공격방법을 먼저 알아야 되죠. 그래서 해킹보다 보안이라는것이 더 어려운 경우가 많습니다.

화면캡쳐에 기본적인 주요 API는 3가지로 구현됩니다.
GetDC, BitBlt, ReleaseDC

화면캡쳐 프로그램들은 보통 이 3가지 API를 이용하여 화면캡쳐를 합니다.
다음은 오픈캡쳐에서 사용하는 전체화면 캡쳐 방식입니다.

procedure TCaptureEngine.ScreenCapture;
const
  // ** 윈도우 2000 이상에서 반투명화 된 윈도우를 캡쳐 할 수 있는 비트 플래그 ** //
  // ** BitBlt 함수에서 OR 연산자를 통해서 사용한다 ** //
  CAPTUREBLT = $40000000;
var
  CursorInfo: TCursorInfo;
  piconinfo: TIconInfo;
  DC: HDC;
  scrRect: TRect;
  CAPTUREBIT: Integer;
begin
  DC := GetDC(0);

  scrRect := Screen.DesktopRect;
  FBackBuffer.SetSize(scrRect.Right - scrRect.Left, scrRect.Bottom - scrRect.Top);

  CAPTUREBIT := 0;

  if FTransparentCapture = True then
    CAPTUREBIT := CAPTUREBLT;

  with FBackBuffer do
    BitBlt(FBackBuffer.Canvas.Handle, 0, 0, FBackBuffer.Width,
      FBackBuffer.Height, DC, scrRect.Left, scrRect.Top,
      SRCCOPY or CAPTUREBIT);

  if FCaptureCursor = True then
  begin
    CursorInfo := CaptureCursor;
    GetIconInfo(CursorInfo.hCursor, piconinfo);

    CursorInfo.ptScreenPos := DesktopCoordinateToOriginCoordinate(CursorInfo.ptScreenPos);

    DrawIcon(FBackBuffer.Canvas.Handle,
      CursorInfo.ptScreenPos.X - piconinfo.xHotspot,
      CursorInfo.ptScreenPos.Y - piconinfo.yHotspot, CursorInfo.hCursor);
  end;

  FCaptureRect := Rect(0, 0, FBackBuffer.Width, FBackBuffer.Height);

  ReleaseDC(0, DC);
end;

GetDC(0);을 호출하게 되면 전체화면에 대한 디바이스 컨텍스트 핸들을 흭득하게 됩니다. ( 듀얼모니터 포함 )
그리고 BitBlt을 통해서 실제 화면DC에 있는 데이터를 비트맵인 FBackBuffer에 DC로 옮깁니다.
그리고 마지막으로 ReleaseDC(0, DC);를 호출하여 할당한 디바이스 컨텍스트 자원을 해제하게 됩니다.

위에서 보앗듯이 화면캡쳐는 이게 전부입니다. 추가적으로 구현되는 부분도 있겠지만 기본 베이스는 이게 끝입니다.
화면캡쳐하는 방법에 대해서 알았으니 이제 화면캡쳐방지는 어떻게 구현해야 할지 생각해볼 차례입니다.

우선적으로 떠오로는 것은 API Hooking이 필요하다라는것을 느낄것입니다.
그리고 화면캡쳐방지에 대한 3가지 주요함수에 대해서 후킹에 필요성이 느껴질것입니다.

그렇다면 어떠한 API를 후킹해야 할지 결정할 차례입니다.
일단 ReleaseDC는 제외됩니다. 단순히 자원을 해제하게 되기 때문입니다.

두 번째로 GetDC와 BitBlt이 있는데 이 함수를 다 후킹해야할지 하나만 후킹해야할지를 결정해야 합니다.
보통 화면캡쳐방지를 만들려고 하면 실수하는 부분이 GetDC를 고려한다는 점인데 실제로 GetDC는 고려할 필요조차
없습니다. 만약에 GetDC를 후킹한다면 결과적으로 그 DC를 얻을 때 마다 DC를 관리하는 리스트에 다 추가를 해줘야
되겠고 BitBlt이 호출되면 이 때 리스트에 대한 정보를 검색하여 이전에 GetDC를 이용하여 화면캡쳐를 시도하려는 DC였다면
BitBlt을 실패시키겠죠. 그리고 ReleaseDC도 덩달아 후킹을 해야 합니다. 왜냐하면 자원이 해제되는 것을 체크하여 DC를
관리하는 리스트에서 제거를 해주어야 하기 때문입니다.

우선 왜 실수로 GetDC를 고려할려고 하였는지 생각해봐야 합니다.
GetDC를 고려하는 이유는 BitBlt 함수가 호출 될 때 이게 화면캡쳐를 하기 위해 BitBlt를 하였는지 아니면 그냥 단순히
비트맵 복사를 할려고 하였는지 알수가 없기 때문입니다.

그렇다면 오로지 BitBlt만 후킹해서 함수를 호출한 프로그램이 화면을 캡쳐하려는것인지 비트맵을 복사하려고 하는것인지
알아낼 수 있는 방법이 있는지를 찾아내야 합니다.
그리고 실제로 상용 화면캡쳐 방지 솔루션에 경우에는 오로지 BitBlt 후킹만으로 화면캡쳐방지를 구현합니다.

이것을 체크하기 위해 윈도우즈 API인 GetObjectType 함수를 이용할수 있습니다.
이 함수는 인자로 받는 매개변수가 GDIObject로써 DC도 GDIObject에 포함되기 때문에
당연히 매개변수로 받을 수 있습니다. 그리고 이 함수가 리턴값들 중에 가장 중요한 리턴값은

OBJ_DC로 MSDN에 보면 이것은 Device context 를 의미합니다.


즉, BitBlt를 후킹한 루틴에서는 넘어온 srcDC ( SourceDC ) 에 대해 GetObjectType으로 해당 DC에 타입을
조사하여 OBJ_DC일 경우 원본 API를 호출하지 않으면 됩니다.

다음과 같이 소스로 구현될것입니다.

BOOL WINAPI MyBitBlt(
  HDC hdcDest, // handle to destination DC
  int nXDest,  // x-coord of destination upper-left corner
  int nYDest,  // y-coord of destination upper-left corner
  int nWidth,  // width of destination rectangle
  int nHeight, // height of destination rectangle
  HDC hdcSrc,  // handle to source DC
  int nXSrc,   // x-coordinate of source upper-left corner
  int nYSrc,   // y-coordinate of source upper-left corner
  DWORD dwRop  // raster operation code
)
{
    if (GetObjectType(hdcSrc) == OBJ_DC)
    {
        return TRUE;
    }
    BOOL Result = 0;

    RestoreAPI(g_hInst, "gdi32.dll", "BitBlt", OldBitBltCode);
 
    // ** 그리고 원래의 API 함수를 호출 ** //
    //if (hWnd == 0)
    Result = BitBlt(hdcDest, nXDest, nYDest, nWidth, nHeight, hdcSrc, nXSrc, nYSrc, dwRop);

    // ** 다시 코드를 변경한다. ** //
    InterceptAPI(g_hInst, "gdi32.dll", "BitBlt", (DWORD)MyBitBlt, OldBitBltCode);

    return Result;
}

위에서 RestoreAPI는 후킹된 함수를 원래대로 되돌려주며 InterceptAPI는 다시 후킹을 하는 함수입니다.
그리고 이러한 방식은 멀티 스레드에 매우 위험한 방법으로 실제 상용DRM에서는 이렇게 허접하게 만들면 안됩니다.-_-
지금은 단순히 예제를 보여주기 위해 이렇게 코드가 작성되었다라고 이해해주시면 감사하겠습니다.
( Detour 라이브러리를 쓰는것이 좋습니다. 그러나 API Hooking하는것 자체도 설명을 같이 할것이기 때문에 나중에
  이 부분은 다시 설명하겠습니다. )

후킹을 하고 해제하는 함수는 다음과 같이 구현되어 있습니다.

BOOL InterceptAPI(HMODULE hLocalModule, const char* c_szDllName, const char* c_szApiName, DWORD dwReplaced, char *OldCode)
{
    DWORD dwOldProtect;
    // 해당 함수의 시작 주소를 얻는다. ** //
    DWORD dwAddressToIntercept = (DWORD)GetProcAddress(
      GetModuleHandle((char*)c_szDllName), (char*)c_szApiName);

    if (dwAddressToIntercept == 0)
    {
    //    OutputDebugString("에러");
        return FALSE;
    }

    BYTE *pbTargetCode = (BYTE *) dwAddressToIntercept;

    BYTE *pbReplaced = (BYTE *) dwReplaced;

    // ** 메모리의 권한을 변경 ** //
    VirtualProtect((void *) dwAddressToIntercept, 5, PAGE_WRITECOPY, &dwOldProtect);

    // ** 원래의 코드를 백업한다. ** //
    memcpy(OldCode, pbTargetCode, 5);

    // ** 점프 코드를 삽입한다. ** //
    *pbTargetCode++ = 0xE9; // jump rel32

    // ** 주소는 내가 설정한 함수 주소 ** //
    *((unsigned int *)(pbTargetCode)) = pbReplaced - (pbTargetCode +4);
    VirtualProtect((void *) dwAddressToIntercept, 5, PAGE_EXECUTE, &dwOldProtect);
    FlushInstructionCache(GetCurrentProcess(), NULL, NULL);
   
    OutputDebugString("InterceptAPI 완료");

    return TRUE;
}

BOOL RestoreAPI(HMODULE hLocalModule, const char* c_szDllName, const char* c_szApiName, char *OldCode)
{
    DWORD dwOldProtect;
    DWORD dwAddressToIntercept = (DWORD)GetProcAddress(
      GetModuleHandle((char*)c_szDllName), (char*)c_szApiName);

    if (dwAddressToIntercept == 0)
    {
        return FALSE;
    }

    BYTE *pbTargetCode = (BYTE *) dwAddressToIntercept;

    VirtualProtect((void *) dwAddressToIntercept, 5, PAGE_WRITECOPY, &dwOldProtect);

    // ** 원래의 코드를 복구한다. ** //
    memcpy(pbTargetCode, OldCode, 5);

    VirtualProtect((void *) dwAddressToIntercept, 5, PAGE_EXECUTE, &dwOldProtect);
    FlushInstructionCache(GetCurrentProcess(), NULL, NULL);

    OutputDebugString("RestoreAPI 완료");;
    return TRUE;
}

그리고 후킹용 DLL이 로드 될 때
InterceptAPI(g_hInst, "gdi32.dll", "BitBlt",(DWORD)MyBitBlt, OldBitBltCode);
해제 될 때,
RestoreAPI(g_hInst, "gdi32.dll", "BitBlt", OldBitBltCode);
를 해주어야 합니다.

위와 같은 예제를 통해서 DLL을 만들어 인젝션을 시킨 후에 오픈캡쳐로 화면캡쳐를 시도하려고 하면
그냥 흰색으로 나오거나 검은색나올것입니다. BitBlt 함수자체가 동작하지 않기 때문에 아예 복사자체가 안되기 때문입니다.

이정도면 제가 보기엔 안정성을 제외하고서는 기능적으로 보았을때에는 거의 상용과 동일한 수준이라고 생각됩니다.
그러나 추가적으로 하나의 API를 더 후킹해야 할 필요가 있습니다.
일부 상용 DRM 솔루션은 바보같게도 WIndows XP에서 추가 된 새로운 API인 PrintWindow를 후킹하지 않는 바람에
이 함수를 써서 우회가 가능하더군요.

PrintWindow 함수는 화면상에 보이지 않고 가려져 있는 윈도우에 대해서도 화면을 캡쳐할 수 있는 매우 막강한 함수입니다.
그냥 후킹해서 return TRUE; 해버리면 이것도 당연히 막히겠죠.

추가적으로 상용 화면캡쳐방지 솔루션은 단순히 화면캡쳐만 막는게 아니라 화면캡쳐에 대한 영역을 보안하는 기능이 있습니다.
즉, 전체화면에 대한 캡쳐가 안되는것이 아니라 보호하고 있는 대상이 웹브라우저라면 그 웹브라우저에 대한 부분만 캡쳐가
안되도록 막는 기능입니다. 이거는 후킹하는 루틴에서 원본 함수를 호출 후 좌표계산을 통해 웹브라우저가 속한 영역에 대
해 화면캡쳐를 하지 말라는 경고그림이라던지(-_-) 그런거를 그려넣으면 되겠죠.

이전에도 말씀드렷다시피 지금 알려드린 예제는 안정성이 떨어지기 때문에 마이크로소프트사에서 만든
API Hooking용 라이브러리를 쓰는 것이 좋습니다.
http://research.microsoft.com/en-us/projects/detours/
만약에 델파이를 사용하신다면 매드훅 라이브러리를 쓰면 됩니다.
http://help.madshi.net/ApiCodeHooking.htm

Detour 라이브러리에 경우에는 인터넷에 관련 글들이 많이 있고 매드훅 라이브러리는 상용 라이브러리이기 때문에
제가 사용을 해보질 않아서 이 두 라이브러리에 대한 내용은 생략하겠습니다.

그렇다면 이걸로 끝일까요?
그렇지 않습니다. 단순히 API를 후킹했다고 해서 끝나지 않습니다.
화면캡쳐를 하는 방식은 무궁무진하기 때문에 저 두 API만 후킹한다고 해서 끝나지 않습니다.
다이렉트X를 이용한 화면캡쳐라던지 윈도우즈 미디어 API를 이용한 화면캡쳐등 여러가지가 있습니다.
이 모든 것을 막을 수 없기 때문에 추가적으로 요구되는 사항이 바로 블랙리스트 개념입니다.

블랙리스트 방식은 화면캡쳐와 관련 된 프로그램에 대한 식별할 수 있는 시그내쳐를 모두 모아서
이 시그내쳐와 맞는 프로세스가 발견되면 즉시 강제 종료시키는 방식입니다.

이러한 시그내쳐중에 단순히 윈도우에 타이틀바 캡션정보를 이용하여 식별하는 방법이 있는데 허접하게 보일지 몰라도
의외로 강력하며 최후에 수단이라고 보여집니다.-_-
왜냐하면 일반 사용자는 직접 소스코딩을 할 수가 없기 때문에 프로그래머가 아닌 일반 사용자에 대해서는 좋은 방어
수단이 될수가 있기 때문입니다. 물론 완벽하지는 않겠지만요.

여기까지 화면캡쳐방지에 대한 글은 마치겠습니다.
지금까지 설명한 내용은 실제로 상용으로 판매되고 있는 화면캡쳐방지 기법에 대한 기본 베이스입니다.

profile

esniper

2010.09.05 22:15:22

기존에 상용으로 판매되는 것들이 기본적인 것들만 후킹해서 막거나 윈도우 타이틀바 캡션값이나 classname으로 블랙리스트 만들어 제거 하는 형태로 작동하는 하더라

어짜피 일반 사용자이 퍼가는걸 막는 정도만 만들었겠지~

더 복잡하게 만들면 다른 보안솔루션들하고 충돌나는 일이 많아져서 아마 막는것보다 더 복잡한 일이 생길테니 얼마간은 그 정도선이 캡쳐 차단은 그정도로만 운영이 될것 같네~

프린트스크린

2011.05.10 20:51:53

그럼 캡쳐 프로그램들이 후킹을 또 하게 되면 어떻게 되나요? 후킹된걸 다시 후킹해서 되돌리면 무용지물이 되는 것은 아닌지...

profile

esniper

2011.05.10 22:49:34

캡쳐 방지 솔루션들은 대부분이 무용지물이 되는 솔루션일 것이라 생각됩니다.

그냥 일반인이 적당히 프로그램써서 캡쳐가 안되도록 막는것들이 솔루션으로 나오더라구요.

무용지물이 되지 않는걸 한번 만들어 보세요~

List of Articles
번호 제목 글쓴이 날짜 조회 수
163 Linux Tip 리눅스 기반 오픈 소스 사이트 모음 삽질신 2009-11-16 72864
162 Linux Tip VirtualBox에 우분투 설치 후에 내부 네트워크 접속 설정하기 file esniper 2010-09-10 35620
161 Windows Research IE Cache 경로 변경하기 lain 2011-06-09 30854
» Windows Research 화면캡쳐방지는 어떻게 구현될까? [3] lain 2010-09-04 29315
159 Windows Research VMWare 탐지 기법 우회 - 2 [2] lain 2011-04-04 29248
158 PHP Tip php 파싱 라이브러리 - snoopy esniper 2009-03-26 21026
157 Linux Tip Directory index forbidden by Options directive 삽질신 2009-11-16 20640
156 Linux Tip VirtualBox에서 오른쪽 CTRL키 사용하기(VitualBox 호스트키) file [2] esniper 2010-09-10 19875
155 Windows Tip SSH 터널링 - putty의 plink 사용예 esniper 2009-03-26 19822
154 문서자료 고품질의 무료 아이콘들 [3] esniper 2010-09-08 19659
153 문서자료 json 문법 체크 사이트 Lyn 2012-02-28 19193
152 PHP Tip mysql_insert_id() - 마지막 insert 된 아이디 값 esniper 2009-03-26 18728
151 Linux Tip 우분투 설치 후 putty에서 한글 안 깨지도록 설정하기 file esniper 2010-09-10 17185
150 Windows Research 커널레벨에서 IAT Hook lain 2011-06-11 16931
149 Linux Tip 우분투 ssh or mysql 서버 접속지연이 있는 경우 해결책 esniper 2010-09-10 16604
148 Windows Research SetEvent 함수는 언제 실패할까? lain 2010-09-04 16194
147 Linux Tip 우분투에 IRC서버 설치후 닉네임 길이와 동접자 조절. [1] 오랑캐꽃 2010-11-26 16146
146 Windows Research 유저 레벨에서 API Hook 1부 file [1] lain 2010-09-03 16074
145 Windows Research Sysinternals - Filemon source code file [5] lain 2010-09-04 15632
144 Linux Tip Wget 완벽 정리 esniper 2009-03-26 15160

  • 이용약관
  • 개인정보취급방침
  • 사이트맵