[WPF] 캡처 방지 기능을 만들어 보자 - SetWindowDisplayAffinity

안녕하세요. 괴발자 데브봉입니다.
 
오늘의 주제는 바로바로바로
 
" 캡처 방지 "
 
입니다 ㅎㅎ
 
그럼 일단 서론으로 넘어갈께요~


1. 숨바꼭질의 달인: 불펌을 막아라!

여러분들과 제가 정말 많이 사용하는 프로그램 중 하나인 "Snipping Tool" 이라는 윈도우 앱이 있습니다.
 
바로 "캡쳐 도구" 라는 프로그램인데요? 보통 웹 사이트에서 이미지 소스를 따오거나, 아님 PDF의 사진이라던지, 글 단락이라던지, 윈도우 내에 여러가지 화면을 여러 사이즈의 사각형으로 딸 수 있게 만든 마이크로소프트가 개발한 캡처 툴이죠.
 
 

근데 이것을 잘못 까딱 사용하다간 우리 모두 쇠고랑 찰 수 있다는거 아시죠?
 


 
한번은 제가 독서를 좀 할까 하고 밀리의 서재라는 독서 플랫폼 앱을 윈도우 PC 버전으로 설치하였습니다. 근데 굉장히 궁금했어요. 
 

 

과연 불법 스크린샷을 막을 수 있을 것인가?


 
저는 보안과 관련된 개발자는 아니지만, 충분히 이런 기능은 보안 개발자가 하지 않는 일이라는 것은 알고 있었습니다. 그래서 실험을 한번 해보기로 했어요.

 

보셨나요? 밀리의 서재에서 스크린샷을 하면 사라지게 되는거? 오른쪽은 바로 그 결과 사진이예요.
 
아예 저 창 자체가 숨겨져 버리네요? 저는 이 기능이 참 신기하고 밀리의 서재가 보안에 공을 많이 들였다라는 생각이 들었어요.
 
그래서 이번에 완전히 똑같은 기능은 아니지만 비슷한 기능을 한번 여러분들과 공유해보고자 해요.

 


2. SetWindowDisplayAffinity 너는 누구니?

일단 프로그램을 숨기든, 쳐박든, 없애든 뭐든 그 이전에 해야될 일은 캡처를 감지하는 것일 겁니다. 캡쳐를 감지하는 기능을 가지려면, 일반적인 방법으로는 보통 2가지가 있어요.
 
1) 키보드/마우스 후킹(hooking)
- 사용자의 입력을 모니터링하는 방법이며, 일반적으로 특정 키 조합(예: Win + Shift + S)을 감지하면 자동으로 스스로를 숨길 수 있습니다.
 
2) 시스템 이벤트 감지
- 이 방법은 특정 시스템 이벤트나 행동을 모니터링하는 방법입니다. 예를 들어, 스크린샷 프로그램이 활성화되면 일반적으로 화면의 일부 또는 전체가 가려지게 합니다.

 

결국 두가지의 차이는 사용자를 감시할거냐? 프로그램을 감시 할거냐? 의 차이이지 않을까 해요.
 
 
 

그렇다면
SetWindowDisplayAffinity 는 뭐하는 놈일 까요?
 
 
 

"SetWindowDisplayAffinity" 는 Windows API 함수의 일부로, 창의 내용이 외부 프로그램에 의해 캡처되는 것을 방지하는 데 사용됩니다. 이 함수는 특히 스크린샷 도구 또는 화면 캡처 소프트웨어가 창의 내용을 캡처하는 것을 방지할 때 유용합니다.
 
또한, 이 녀석은 사용자의 입력을 감지하는 것이 아니라, 시스템 레벨에서 창의 표시를 관리하고, 지정된 창의 내용이 화면 캡처 도구에 의해 캡처되는 것을 방지하기 위해 사용되며, 일종의 시스템 이벤트를 제어하는 것으로 볼 수 있습니다.
 
즉, 2) 시스템 이벤트 감지에 가까운 녀석이죠.
 
그럼 추가적으로 Display Affinity(화면 호환성)는 무엇일까요?

Display Affinity는 윈도우즈 운영체제에서 창의 내용이 어디에 표시되고, 어떤 프로세스가 그 내용을 볼 수 있는지를 결정하는 속성입니다. 특히 보안에 민감한 애플리케이션이나 스크린샷 도구 혹은 화면 녹화 도구 등에 창의 내용을 캡처하는 것을 제한하는 데 사용될 수 있습니다.

 


3. 스크린샷을 막아보자: SetWindowDisplayAffinity 실습

뭐 거창하게 준비 할 것 없이, 바로 가지고 있는 테스트 프로그램 위에 몇 줄만 추가하면 됩니다. 이미 아시는 분들께서는 WinAPI 이기에 당연히 dll을 추가 해야겠죠? 보통 껍데기 창이 있으시면 거기의 코드비하인드에서 작업하시면 좋을 것 같아요.
 
먼저 코드를 각 부분별로 설명드리고 그 다음 전체 풀코드를 보여드릴께요.
 


 
1)

private const uint WDA_MONITOR = 1;:

SetWindowDisplayAffinity 함수에서 사용하는 매개변수 중 하나인 dwAffinity에 대한 값을 설정합니다. WDA_MONITOR는 화면 캡처에서 창의 내용을 숨기는 옵션을 지정합니다.
 
이 함수의 두 번째 매개변수로는 

WDA_NONE 또는 WDA_MONITOR
 

둘 중 하나로 설정할 수 있는데, 각각의 값은 다음과 같은 의미를 가집니다:

1. WDA_NONE (0): 이 설정을 사용하면 창의 모든 내용이 화면 캡처에 노출됩니다. 이것이 기본 설정입니다.
2. WDA_MONITOR (1): 이 설정을 사용하면 창의 내용이 모니터에만 노출되고, 화면 캡처로는 노출되지 않습니다. 스크린샷 도구나 화면 녹화 도구 등이 창의 내용을 캡처하는 것을 방지할 수 있습니다.

 
 
2)

[DllImport("user32.dll")]
private static extern bool SetWindowDisplayAffinity(IntPtr hWnd, uint dwAffinity);

DllImport 속성을 사용하여 user32.dll 라이브러리에서 SetWindowDisplayAffinity 함수를 가져옵니다. 이 함수는 윈도우의 디스플레이 어피니티(WDA_NONE 또는 WDA_MONITOR)를 설정합니다.

 
 
3)

public AppShellView()
{
    InitializeComponent();
    this.Loaded += OnLoaded;
}

MainWindow 클래스의 생성자입니다. InitializeComponent는 WPF 애플리케이션의 자동 생성된 코드를 호출하여 사용자 인터페이스 요소를 초기화합니다. 
 
또한 이 생성자에서 Loaded 이벤트 핸들러로 OnLoaded 메서드를 등록합니다. OnLoaded 매서드는 창이 완전히 로드된 후에 실행될 코드를 발생시키는 함수입니다.

 

 
4)

private void OnLoaded(object sender, RoutedEventArgs e)
{
    var windowInteropHelper = new WindowInteropHelper(this);
    SetWindowDisplayAffinity(windowInteropHelper.Handle, WDA_MONITOR);
}

이 메서드는 창이 완전히 로드된 후에 호출되며, SetWindowDisplayAffinity 함수를 호출하여 창의 디스플레이 어피니티를 설정합니다. 
 
WindowInteropHelper 클래스를 사용하여 WPF 창의 핸들을 얻어, SetWindowDisplayAffinity 함수에 전달되어 해당 창의 디스플레이 어피니티를 설정합니다. (여기서 말하는 핸들은 윈도우에서 각 창에게 부여한 고유 식별값입니다. 이 식별값으로 어떤 창인지를 구별 할 수 있습니다.)


4. 마무리

이렇게 하면 해당 창의 내용이 화면 캡처 도구에 의해 캡처되는 것을 방지할 수 있습니다. 하지만 완전한 보안을 보장하지는 못한다는 점....
 
아시다시피 PC 소프트웨어를 떠나 물리적인 카메라를 사용하면 방지를 막을 수는 없습니다마는.... 그래도 1차로 불펌은 막을 수 있기에 일단 한번 만들어 보았습니다.
 
결과물 한번 보시죠~
 

 

왼쪽은 제 핸드폰으로 찍었고요. 오른쪽은 반디캠으로 찍었을때 입니다.
 
자 확연히 차이가 나오나요? 바로 캡처 이벤트를 잡아서 동영상도 막아버리는 방지 기능 ㅎㄷㄷㄷ;;; (사실... 동영상도 될지는 몰랐어요...)
 
제꺼는 저렇게 까만색으로 나오네요? 밀리의 서재는 아예 투명이던데... 그 방법은 좀 찾아봐야 될 듯해요.

 
 
 

아래는 MainWindow의 코드 비하인드입니다

 

MainWindow.xaml.cs

더보기
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;

public partial class MainWindow : Window
{
    private const uint WDA_MONITOR = 1;

    [DllImport("user32.dll")]
    private static extern bool SetWindowDisplayAffinity(IntPtr hWnd, uint dwAffinity);

    public MainWindow()
    {
        InitializeComponent();
        this.Loaded += OnLoaded;
    }

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        var windowInteropHelper = new WindowInteropHelper(this);
        SetWindowDisplayAffinity(windowInteropHelper.Handle, WDA_MONITOR);
    }
}

오늘은 정말 유익한 기능??을 만들어 보았습니다.
 
저는 개인적으로 이런 쪽에 좀 관심이 가더라고요 ㅋㅋ
 
앞으로도 제가 모르는 기술 중 신기하거나 재밌는 기능들이 있다면
 
또 글 올리도록 할께요.
 
그럼 모두 안녕~~