Microsoft MVP성태의 닷넷 이야기
.NET Framework: 1053. C# - 특정 레지스트리 변경 시 알림을 받는 방법 [링크 복사], [링크+제목 복사],
조회: 18488
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)

C# - 특정 레지스트리 변경 시 알림을 받는 방법

레지스트리 변경에 대한 알림은 Win32 API도 제공하지만,

RegNotifyChangeKeyValue function
; https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regnotifychangekeyvalue

이럴 때는 간단하게 WMI 기능을 이용하면 됩니다.

Registry class
; https://learn.microsoft.com/en-us/windows/win32/etw/registry

RegistryKeyChangeEvent class
; https://learn.microsoft.com/en-us/previous-versions/windows/desktop/regprov/registrykeychangeevent

Registry Watcher C#
; https://stackoverflow.com/questions/826971/registry-watcher-c-sharp

그래서 대충 다음과 같은 식으로 코딩할 수 있습니다.

using Microsoft.Win32;
using System;
using System.Management;

public class Program
{
    public static void Main()
    {
        using (RegistryMonitor regMon = new RegistryMonitor(Registry.LocalMachine,
            @"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"))
        {
            regMon.Changed += registry_Changed;

            do
            {
                Console.WriteLine("Press 'q' key to exit...");
            } while (Console.ReadLine() != "q");
        }
    }

    private static void registry_Changed(object sender, EventArrivedEventArgs e)
    {
        Console.WriteLine(e.NewEvent.GetText(TextFormat.Mof));
    }
}

public class RegistryMonitor : IDisposable
{
    ManagementEventWatcher _watcher;
    string _wmiQuery;

    public RegistryMonitor(RegistryKey hiveRoot, string registryKeyPath)
    {
        try
        {
            _wmiQuery = "SELECT * FROM RegistryKeyChangeEvent WHERE " +
                     $" Hive='{hiveRoot.Name}'" +
                     $" AND KeyPath='{registryKeyPath}'";

            WqlEventQuery wqlEvent = new WqlEventQuery(_wmiQuery);

            _watcher = new ManagementEventWatcher(wqlEvent);
            _watcher.EventArrived += new EventArrivedEventHandler(HandleEvent);
            _watcher.Start();
        }
        catch (ManagementException e)
        {
            Console.WriteLine(_wmiQuery + Environment.NewLine + Environment.NewLine + e.ToString());
        }
    }

    private void HandleEvent(object sender, EventArrivedEventArgs e)
    {
        Changed?.Invoke(sender, e);
    }

    public void Dispose()
    {
        if (_watcher != null)
        {
            _watcher.Dispose();
            _watcher = null;
        }
    }

    public event EventHandler<EventArrivedEventArgs> Changed;
}

위에서는 시스템의 환경 변수가 변경되었을 때 알림을 받도록 설정했는데요, 그래서 regedit를 통해 "Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" 경로의 환경 변수를 변경하면 다음과 같은 식의 텍스트가 화면에 출력됩니다.

instance of RegistryKeyChangeEvent
{
        Hive = "HKEY_LOCAL_MACHINE";
        KeyPath = "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment";
        TIME_CREATED = "132643355350866404";
};

개별 값은 e.NewEvent.Properties에서 추출할 수 있는데, 예를 들어 TIME_CREATED는 다음과 같이 C#의 DateTime으로 변환할 수 있습니다.

PropertyData timeCreated = e.NewEvent.Properties["TIME_CREATED"];
ulong timeValue = (ulong)timeCreated.Value;

DateTime changed = DateTime.FromFileTime((long)timeValue);
Console.WriteLine(changed); // 2021-05-01 오후 7:23:56

그나저나, 시간 관련 값이 저렇게 정숫값으로 나올 때는 해석을 어떻게 해야 할지 늘 고민입니다. ^^; (windbg 내의 DateTime 메모리 값을 해석하는 방법)

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




간단하게 VBScript로 작성하는 것도 가능합니다. ^^

REM RegistryKeyChangeEvent class
REM https://learn.microsoft.com/en-us/previous-versions/windows/desktop/regprov/registrykeychangeevent

Set wmiServices = GetObject("winmgmts:root/default") 
Set wmiSink = WScript.CreateObject("WbemScripting.SWbemSink", "SINK_") 

wmiServices.ExecNotificationQueryAsync wmiSink, "SELECT * FROM RegistryKeyChangeEvent " _
    & "WHERE Hive='HKEY_LOCAL_MACHINE' AND KeyPath='SOFTWARE\\Microsoft\\WBEM\\Scripting'" 

WScript.Echo "Listening for Registry Key Change Events..." & vbCrLf 

While(True) 
    WScript.Sleep 1000 
Wend 

Sub SINK_OnObjectReady(wmiObject, wmiAsyncContext) 
    WScript.Echo "Received Registry Change Event" & vbCrLf & wmiObject.GetObjectText_() 
End Sub




그런데, 가끔은 레지스트리 변경 자체의 알림보다는 특정 레지스트리 값을 변경한 프로세스를 알고 싶을 때가 있습니다. 아쉽게도, Win32 API/WMI 수준에서는 이에 대한 어떠한 정보도 알려주지 않습니다.

이것을 알려면 ProcMon이나 레지스트리의 audit 기능을 사용해야 한다는데,

tinyapps.org / blog - Find out what process changed a registry key or value
; https://tinyapps.org/blog/201509170700_what_process_changed_registry.html

아무래도 ProcMon은 별도 다운로드를 해야 하니, 윈도우의 기본 기능만으로 가능한 auditpol 방법을 소개하겠습니다. 위의 글에 나온 방법에 따르면, ^^ 다음과 같이 레지스트리에 대한 감사 정책을 활성화시키고,

C:\Windows\System32> auditpol /set /subcategory:"Registry" /success:enable
The command was successfully executed.

이후, regedit.exe를 실행해 모니터링을 원하는 레지스트리 키에 대해 우 클릭, "Permissions..." 메뉴를 선택하고 다음과 같이 "Auditing" 탭에 새롭게 "Everyone"으로 "Full Control" / "Read" 권한을 추가합니다.

audit_registry_key_1.png

일단 저렇게 설정이 완료되면, 이벤트 로그의 "Windows Logs" / "Security" 범주에 다음과 같이 변경 이벤트가 남게 됩니다.

audit_registry_key_2.png

위의 예는, regedit.exe(PID == 0x2e68) 프로세스에서 "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" 하위의 환경 변숫값을 바꿨음을 알려주고 있습니다.

// 설정 해제

C:\Windows\System32>  auditpol /get /subcategory:"Registry"
System audit policy
Category/Subcategory                      Setting
Object Access
  Registry                                Success

C:\Windows\System32>  auditpol /set /subcategory:"Registry" /success:disable
The command was successfully executed.

C:\Windows\System32>auditpol /get /subcategory:"Registry"
System audit policy
Category/Subcategory                      Setting
Object Access
  Registry                                No Auditing





만약 ManagementEventWatcher.Start 메서드에서 다음과 같은 예외가 발생한다면?

System.Management.ManagementException: Unparsable query.
   at System.Management.ManagementException.ThrowWithExtendedInfo(ManagementStatus errorCode)
   at System.Management.ManagementEventWatcher.Start()
   at RegistryMonitor..ctor(RegistryKey hiveRoot, String registryKeyPath)

"WqlEventQuery wqlEvent = new WqlEventQuery(_wmiQuery);" 코드에 전달한 _wmiQuery의 텍스트 내용을 잘 살펴볼 필요가 있습니다. 이 글에서 든 예제의 경우 다음과 같은 경로의 Registry 키를 감시하는 건데,

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment

이 값을 넘겨줄 때, escape를 이중으로 고려해 넘겨야 합니다. 즉, C#의 "@" 마크로 감쌌다고 해도 다음과 같이 넘겨야 하고,

string txt = @"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment";

만약 "@" 마크를 사용하지 않았다면 이렇게 해야 합니다.

string txt = "SYSTEM\\\\CurrentControlSet\\\\Control\\\\Session Manager\\\\Environment";




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 8/28/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2024-04-19 09시51분
The Windows Registry Adventure #1: Introduction and research results
; https://googleprojectzero.blogspot.com/2024/04/the-windows-registry-adventure-1.html

The Windows Registry Adventure #2: A brief history of the feature
; https://googleprojectzero.blogspot.com/2024/04/the-windows-registry-adventure-2.html

The Windows Registry Adventure #3: Learning resources
; https://googleprojectzero.blogspot.com/2024/06/the-windows-registry-adventure-3.html

The Windows Registry Adventure #4: Hives and the registry layout
; https://googleprojectzero.blogspot.com/2024/10/the-windows-registry-adventure-4-hives.html

The Windows Registry Adventure #5: The regf file format
; https://googleprojectzero.blogspot.com/2024/12/the-windows-registry-adventure-5-regf.html

The Windows Registry Adventure #6: Kernel-mode objects
; https://googleprojectzero.blogspot.com/2025/04/the-windows-registry-adventure-6-kernel.html

----------------------------------------------

Misunderstanding the “Prevent access to registry editing tools” policy
; https://devblogs.microsoft.com/oldnewthing/20241001-22/?p=110330
정성태

... 76  77  78  79  80  81  82  83  [84]  85  86  87  88  89  90  ...
NoWriterDateCnt.TitleFile(s)
11837정성태3/6/201939810기타: 74. 도서: 시작하세요! C# 7.3 프로그래밍 [10]
11836정성태3/5/201923388오류 유형: 525. Visual Studio 2019 Preview 4/RC - C# 8.0 Missing compiler required member 'System.Range..ctor' [1]
11835정성태3/5/201921862.NET Framework: 810. C# 8.0의 Index/Range 연산자를 .NET Framework에서 사용하는 방법 및 비동기 스트림의 컴파일 방법 [3]파일 다운로드1
11834정성태3/4/201920655개발 환경 구성: 432. Visual Studio 없이 최신 C# (8.0) 컴파일러를 사용하는 방법
11833정성태3/4/201921208개발 환경 구성: 431. Visual Studio 2019 - CMake를 이용한 공유/실행(so/out) 리눅스 프로젝트 설정파일 다운로드1
11832정성태3/4/201917120오류 유형: 524. Visual Studio CMake - rsync: connection unexpectedly closed
11831정성태3/4/201916950오류 유형: 523. Visual Studio 2019 - 새 창으로 뜬 윈도우를 닫을 때 비정상 종료
11830정성태2/26/201916638오류 유형: 522. 이벤트 로그 - Error opening event log file State. Log will not be processed. Return code from OpenEventLog is 87.
11829정성태2/26/201918327개발 환경 구성: 430. 마이크로소프트의 CoreCLR 프로파일러 예제 빌드 방법 - 리눅스 환경 [1]
11828정성태2/26/201926248개발 환경 구성: 429. Component Services 관리자의 RuntimeBroker 설정이 2개 있는 경우 [8]
11827정성태2/26/201919185오류 유형: 521. Visual Studio - Could not start the 'rsync' command on the remote host, please install it using your system package manager.
11826정성태2/26/201919361오류 유형: 520. 우분투에 .NET Core SDK 설치 시 패키지 의존성 오류
11825정성태2/25/201924632개발 환경 구성: 428. Visual Studio 2019 - CMake를 이용한 리눅스 빌드 환경 설정 [1]
11824정성태2/25/201919062오류 유형: 519. The SNMP Service encountered an error while accessing the registry key SYSTEM\CurrentControlSet\Services\SNMP\Parameters\TrapConfiguration. [1]
11823정성태2/21/201920653오류 유형: 518. IIS 관리 콘솔이 뜨지 않는 문제
11822정성태2/20/201919031오류 유형: 517. docker에 설치한 MongoDB 서버로 연결이 안 되는 경우
11821정성태2/20/201919774오류 유형: 516. Visual Studio 2019 - This extension uses deprecated APIs and is at risk of not functioning in a future VS update. [1]
11820정성태2/20/201922831오류 유형: 515. 윈도우 10 1809 업데이트 후 "User Profiles Service" 1534 경고 발생
11819정성태2/20/201922102Windows: 158. 컴퓨터와 사용자의 SID(security identifier) 확인 방법
11818정성태2/20/201920188VS.NET IDE: 131. Visual Studio 2019 Preview의 닷넷 프로젝트 빌드가 20초 이상 걸리는 경우 [2]
11817정성태2/17/201916534오류 유형: 514. WinDbg Preview 실행 오류 - Error : DbgX.dll : WindowsDebugger.WindowsDebuggerException: Could not load dbgeng.dll
11816정성태2/17/201920009Windows: 157. 윈도우 스토어 앱(Microsoft Store App)을 명령행에서 직접 실행하는 방법
11815정성태2/14/201918250오류 유형: 513. Visual Studio 2019 - VSIX 설치 시 "The extension cannot be installed to this product due to prerequisites that cannot be resolved." 오류 발생
11814정성태2/12/201917074오류 유형: 512. VM(가상 머신)의 NT 서비스들이 자동 시작되지 않는 문제
11813정성태2/12/201918395.NET Framework: 809. C# - ("Save File Dialog" 등의) 대화 창에 확장 속성을 보이는 방법
11812정성태2/11/201915706오류 유형: 511. Windows Server 2003 VM 부팅 후 로그인 시점에 0xC0000005 BSOD 발생
... 76  77  78  79  80  81  82  83  [84]  85  86  87  88  89  90  ...