성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
[공진영] 안녕하세요 좋은글 감사합니다. 현재 제가 wpf로 관제 모...
[정성태] The Windows Registry Adventure #1: ...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# - 특정 레지스트리 변경 시 알림을 받는 방법</h1> <p> 레지스트리 변경에 대한 알림은 Win32 API도 제공하지만,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > RegNotifyChangeKeyValue function ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regnotifychangekeyvalue'>https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regnotifychangekeyvalue</a> </pre> <br /> 이럴 때는 간단하게 WMI 기능을 이용하면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Registry class ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/etw/registry'>https://learn.microsoft.com/en-us/windows/win32/etw/registry</a> RegistryKeyChangeEvent class ; <a target='tab' href='https://learn.microsoft.com/en-us/previous-versions/windows/desktop/regprov/registrykeychangeevent'>https://learn.microsoft.com/en-us/previous-versions/windows/desktop/regprov/registrykeychangeevent</a> Registry Watcher C# ; <a target='tab' href='https://stackoverflow.com/questions/826971/registry-watcher-c-sharp'>https://stackoverflow.com/questions/826971/registry-watcher-c-sharp</a> </pre> <br /> 그래서 대충 다음과 같은 식으로 코딩할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using Microsoft.Win32; using System; using System.Management; public class Program { public static void Main() { using (<span style='color: blue; font-weight: bold'>RegistryMonitor regMon = new RegistryMonitor(Registry.LocalMachine, @"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment")</span>) { <span style='color: blue; font-weight: bold'>regMon.Changed += registry_Changed;</span> do { Console.WriteLine("Press 'q' key to exit..."); } while (Console.ReadLine() != "q"); } } private static void registry_Changed(object sender, EventArrivedEventArgs e) { <span style='color: blue; font-weight: bold'>Console.WriteLine(e.NewEvent.GetText(TextFormat.Mof));</span> } } 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; } </pre> <br /> 위에서는 시스템의 환경 변수가 변경되었을 때 알림을 받도록 설정했는데요, 그래서 regedit를 통해 "Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" 경로의 환경 변수를 변경하면 다음과 같은 식의 텍스트가 화면에 출력됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > instance of RegistryKeyChangeEvent { Hive = "HKEY_LOCAL_MACHINE"; KeyPath = "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"; TIME_CREATED = "132643355350866404"; }; </pre> <br /> 개별 값은 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.management.managementbaseobject.properties'>e.NewEvent.Properties</a>에서 추출할 수 있는데, 예를 들어 TIME_CREATED는 다음과 같이 C#의 DateTime으로 변환할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > PropertyData timeCreated = <span style='color: blue; font-weight: bold'>e.NewEvent.Properties["TIME_CREATED"]</span>; ulong timeValue = (ulong)timeCreated.Value; DateTime changed = <span style='color: blue; font-weight: bold'>DateTime.FromFileTime((long)timeValue)</span>; Console.WriteLine(changed); // 2021-05-01 오후 7:23:56 </pre> <br /> 그나저나, 시간 관련 값이 저렇게 정숫값으로 나올 때는 해석을 어떻게 해야 할지 늘 고민입니다. ^^; (<a target='tab' href='https://www.sysnet.pe.kr/2/0/11313'>windbg 내의 DateTime 메모리 값을 해석하는 방법</a>)<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1760&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 간단하게 VBScript로 작성하는 것도 가능합니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > REM RegistryKeyChangeEvent class REM <a target='tab' href='https://learn.microsoft.com/en-us/previous-versions/windows/desktop/regprov/registrykeychangeevent'>https://learn.microsoft.com/en-us/previous-versions/windows/desktop/regprov/registrykeychangeevent</a> Set wmiServices = GetObject("winmgmts:root/default") Set wmiSink = WScript.CreateObject("WbemScripting.SWbemSink", "SINK_") <span style='color: blue; font-weight: bold'>wmiServices.ExecNotificationQueryAsync</span> 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 </pre> <br /> <hr style='width: 50%' /><br /> <br /> 그런데, 가끔은 레지스트리 변경 자체의 알림보다는 특정 레지스트리 값을 변경한 프로세스를 알고 싶을 때가 있습니다. 아쉽게도, Win32 API/WMI 수준에서는 이에 대한 어떠한 정보도 알려주지 않습니다.<br /> <br /> 이것을 알려면 <a target='tab' href='https://learn.microsoft.com/en-us/sysinternals/downloads/procmon'>ProcMon</a>이나 레지스트리의 audit 기능을 사용해야 한다는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > tinyapps.org / blog - Find out what process changed a registry key or value ; <a target='tab' href='https://tinyapps.org/blog/201509170700_what_process_changed_registry.html'>https://tinyapps.org/blog/201509170700_what_process_changed_registry.html</a> </pre> <br /> 아무래도 ProcMon은 별도 다운로드를 해야 하니, 윈도우의 기본 기능만으로 가능한 auditpol 방법을 소개하겠습니다. 위의 글에 나온 방법에 따르면, ^^ 다음과 같이 레지스트리에 대한 감사 정책을 활성화시키고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C:\Windows\System32> <span style='color: blue; font-weight: bold'>auditpol /set /subcategory:"Registry" /success:enable</span> The command was successfully executed. </pre> <br /> 이후, regedit.exe를 실행해 모니터링을 원하는 레지스트리 키에 대해 우 클릭, "Permissions..." 메뉴를 선택하고 다음과 같이 "Auditing" 탭에 새롭게 "Everyone"으로 "Full Control" / "Read" 권한을 추가합니다.<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='audit_registry_key_1.png' src='/SysWebRes/bbs/audit_registry_key_1.png' /><br /> <br /> 일단 저렇게 설정이 완료되면, 이벤트 로그의 "Windows Logs" / "Security" 범주에 다음과 같이 변경 이벤트가 남게 됩니다.<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='audit_registry_key_2.png' src='/SysWebRes/bbs/audit_registry_key_2.png' /><br /> <br /> 위의 예는, regedit.exe(PID == 0x2e68) 프로세스에서 "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" 하위의 환경 변숫값을 바꿨음을 알려주고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // 설정 해제 C:\Windows\System32> <span style='color: blue; font-weight: bold'> auditpol /get /subcategory:"Registry"</span> System audit policy Category/Subcategory Setting Object Access Registry Success C:\Windows\System32> <span style='color: blue; font-weight: bold'> auditpol /set /subcategory:"Registry" /success:disable</span> The command was successfully executed. C:\Windows\System32>auditpol /get /subcategory:"Registry" System audit policy Category/Subcategory Setting Object Access Registry No Auditing </pre> <br /> <br /> <hr style='width: 50%' /><br /> <br /> 만약 ManagementEventWatcher.Start 메서드에서 다음과 같은 예외가 발생한다면?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > System.Management.ManagementException: Unparsable query. at System.Management.ManagementException.ThrowWithExtendedInfo(ManagementStatus errorCode) at System.Management.ManagementEventWatcher.Start() at RegistryMonitor..ctor(RegistryKey hiveRoot, String registryKeyPath) </pre> <br /> "WqlEventQuery wqlEvent = new WqlEventQuery(_wmiQuery);" 코드에 전달한 _wmiQuery의 텍스트 내용을 잘 살펴볼 필요가 있습니다. 이 글에서 든 예제의 경우 다음과 같은 경로의 Registry 키를 감시하는 건데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment </pre> <br /> 이 값을 넘겨줄 때, escape를 이중으로 고려해 넘겨야 합니다. 즉, C#의 "@" 마크로 감쌌다고 해도 다음과 같이 넘겨야 하고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > string txt = @"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"; </pre> <br /> 만약 "@" 마크를 사용하지 않았다면 이렇게 해야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > string txt = "SYSTEM\\\\CurrentControlSet\\\\Control\\\\Session Manager\\\\Environment"; </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1016
(왼쪽의 숫자를 입력해야 합니다.)