성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
[공진영] 안녕하세요 좋은글 감사합니다. 현재 제가 wpf로 관제 모...
글쓰기
제목
이름
암호
전자우편
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# - USB 관련 ETW(Event Tracing for Windows)를 이용한 키보드 입력을 감지하는 방법</h1> <p> 전에 USB 패킷 모니터링을,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - (Wireshark의) USBPcap을 이용한 USB 패킷 모니터링 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12215'>https://www.sysnet.pe.kr/2/0/12215</a> C# - DirectX 게임 클라이언트 실행 중 키보드 입력을 감지하는 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12218'>https://www.sysnet.pe.kr/2/0/12218</a> </pre> <br /> 이용해 키보드 입력을 감지하는 것을 다뤘는데요. 이를 위해 USBPcap의 device driver를 이용했는데 가만 보니 ETW를 이용해서도,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ETW(Event Tracing for Windows)를 이용한 닷넷 프로그램의 내부 이벤트 활용 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12244'>https://www.sysnet.pe.kr/2/0/12244</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;' > Logging Keystrokes with Event Tracing for Windows (ETW) ; <a target='tab' href='https://www.cyberpointllc.com/posts/cp-logging-keystrokes-with-event-tracing-for-windows-etw.html'>https://www.cyberpointllc.com/posts/cp-logging-keystrokes-with-event-tracing-for-windows-etw.html</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;' > CyberPoint / Ruxcon2016ETW ; <a target='tab' href='https://github.com/CyberPoint/Ruxcon2016ETW'>https://github.com/CyberPoint/Ruxcon2016ETW</a> MSDN-WhiteKnight / HidLogger ; <a target='tab' href='https://github.com/MSDN-WhiteKnight/HidLogger'>https://github.com/MSDN-WhiteKnight/HidLogger</a> </pre> <br /> 숟가락만 얹으면 될 것... 같다고 생각했는데, 결정적으로 동작을 안 하는군요. ^^;<br /> <br /> <hr style='width: 50%' /><br /> <br /> 일단, 해당 소스 코드의 ETW 이벤트는,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > session.Source.Dynamic.All += EventCallback; </pre> <br /> 잘 발생합니다. 문제는 EventCallback에서 키보드 관련 데이터를 가져오는 부분인데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > private static void EventCallback(TraceEvent eventData) { ulong hndl = 0; if (eventData.EventDataLength <= 0) return; if (eventData.PayloadNames.Contains("fid_USBPORT_URB_BULK_OR_INTERRUPT_TRANSFER")) hndl = FilterUsb2(eventData); else if (eventData.PayloadNames.Contains("fid_UCX_URB_BULK_OR_INTERRUPT_TRANSFER")) hndl = FilterUsb3(eventData); else return; if (hndl == 0) return; byte[] xferData = new byte[8]; <span style='color: blue; font-weight: bold'>Array.Copy(eventData.EventData(), eventData.EventDataLength - 8, xferData, 0, 8);</span> // ...[생략]... } /* eventData { <Event MSec="2596.9958" PID="19672" PName="" TID="14640" EventName="URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER/Stop" ProviderName="Microsoft-Windows-USB-UCX" FormattedMessage="Complete URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER " fid_UcxController="0x767b6c00ab58" fid_UsbDevice="0x767b690184f8" fid_PipeHandle="0xffff898498178c40" fid_IRP_Ptr="0xffff8984981875a0" fid_URB_Ptr="0xffff8984b1a7c300" fid_UCX_URB_BULK_OR_INTERRUPT_TRANSFER="{ fid_URB_Hdr_Length:128, fid_URB_Hdr_Function:9, fid_URB_Hdr_Status:0, fid_URB_Hdr_UsbdDeviceHandle:&quot;130272414762232&quot;, fid_URB_Hdr_UsbdFlags:&quot;0&quot;, fid_URB_PipeHandle:&quot;18446613801313209408&quot;, fid_URB_TransferFlags:3, fid_URB_TransferBufferLength:8, fid_URB_TransferBuffer:&quot;18446613801312713072&quot;, fid_URB_TransferBufferMDL:&quot;0&quot;, fid_URB_ReservedMBZ:&quot;0&quot;, fid_URB_ReservedHcd:&quot;0&quot; }" fid_IRP_NtStatus="0"/> } Microsoft.Diagnostics.Tracing.TraceEvent */ </pre> <br /> 아쉽게도 eventData.EventData()는 ETW 이벤트 관련 정보들이 반환될 뿐,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 58 ab 00 6c 7b 76 00 00 f8 84 01 69 7b 76 00 00 40 8c 17 98 84 89 ff ff 10 80 18 98 84 89 ff ff 90 62 3f ac 84 89 ff ff 80 00 09 00 00 00 00 00 f8 84 01 69 7b 76 00 00 00 00 00 00 00 00 00 00 40 8c 17 98 84 89 ff ff 03 00 00 00 08 00 00 00 d0 f9 0f 98 84 89 ff ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 </pre> <br /> 저 버퍼의 마지막 8바이트가 키보드로부터 전송한 데이터가 실려온 경우는 없었습니다. (아마도, 저 POC 프로젝트가 실행되던 당시에는 키보드 정보를 반환한 것 같습니다.)<br /> <br /> 그런데, 방법이 아주 없는 것은 아닙니다. FilterUsb3 메서드를 보면, "fid_URB_TransferBufferLength"를 키로 해서 8바이트 데이터를 구해 오는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > private static ulong FilterUsb3(TraceEvent eventData, out byte[] urbBuffer) { urbBuffer = null; ulong hndl = (ulong)GetItem(eventData, "fid_PipeHandle"); if (hndl <= 0) return 0; // retrieve raw urb data object field = GetItem(eventData, "fid_UCX_URB_BULK_OR_INTERRUPT_TRANSFER"); Dictionary<string, string> urb = _expose(field); // xfer buffer length is last n-bytes in eventData int xferDataSize = 0; if (!int.TryParse(<span style='color: blue; font-weight: bold'>urb["fid_URB_TransferBufferLength"]</span>, out xferDataSize)) return 0; // usb keyboard xfer data is 8 bytes if (xferDataSize != 8) return 0; return hndl; } </pre> <br /> 왠지 8바이트가 낯이 익습니다. ^^ 이름도 URB 데이터라는 걸로 봐서 "<a target='tab' href='https://www.sysnet.pe.kr/2/0/12215'>C# - (Wireshark의) USBPcap을 이용한 USB 패킷 모니터링</a>" 글의 ReadFunction에서 봤던 바로 그 8바이트를 의미하는 듯합니다.<br /> <br /> 그렇다면 일단 키보드 데이터의 길이가 8이라는 것은 구했지만, 정작 원래의 버퍼를 구할 방법이 없습니다. 왜냐하면, urb["fid_URB_TransferBuffer"] 값이 제공되긴 하지만 그것은 버퍼 자체의 데이터가 아니라 버퍼의 포인터인데다, 사용자 영역이 아닌 커널 영역의 URB 버퍼 포인터(예를 들어 0xffff8984b1a7c300), 즉 커널 측 device driver가 다루던 URB 패킷 버퍼의 포인터이기 때문입니다.<br /> <br /> 불행 중 다행이라면 예전에 만들어 둔 커널 driver를 이용하는 경우,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 커널 메모리를 읽고 쓰는 NT Legacy driver와 C# 클라이언트 프로그램 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12104'>https://www.sysnet.pe.kr/2/0/12104</a> </pre> <br /> 다음과 같이 FilterUsb3 메서드를 확장해 키보드 데이터를 구할 수는 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > private static ulong FilterUsb3(TraceEvent eventData, out byte[] urbBuffer) { // ...[생략]... if (<span style='color: blue; font-weight: bold'>ulong.TryParse(urb["fid_URB_TransferBuffer"], out ulong bufferAddress</span>) == false) { return 0; } if (_memoryIO.IsInitialized == false) { return 0; } IntPtr ptr = new IntPtr((long)bufferAddress); urbBuffer = new byte[xferDataSize]; <span style='color: blue; font-weight: bold'>_memoryIO.ReadMemory(ptr, urbBuffer);</span> return hndl; } static KernelMemoryIO _memoryIO; public static void StartCapture(string newSessionName = null) { if (newSessionName != null) sessionName = newSessionName; using (var session = new TraceEventSession(sessionName, null)) { if (TraceEventSession.IsElevated() != true) { Console.Out.WriteLine("[!] run as admin"); return; } _memoryIO = new KernelMemoryIO(); if (_memoryIO.IsInitialized == false) { Console.Out.WriteLine("[!] Failed to load KernelMemoryIO device driver"); } SetupCtrlCHandler(() => { if (session != null) session.Stop(); }); session.Source.Dynamic.All += EventCallback; session.EnableProvider(UsbUcx); session.EnableProvider(UsbPort); Console.WriteLine("starting capture ..."); session.Source.Process(); } } </pre> <br /> 저렇게 구한 8바이트 데이터를 이제 EventCallback 메서드의 나머지 코드에 태우면 실행 시 다음과 같은 식의 출력을 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > starting capture ... 20200625 11:44:49.143 00 00 07 00 00 00 00 00 d 20200625 11:44:49.287 00 00 00 00 00 00 00 00 20200625 11:44:56.759 00 00 00 00 00 00 00 00 20200625 11:44:56.863 00 00 17 00 00 00 00 00 t 20200625 11:44:57.031 00 00 17 00 00 00 00 00 t 20200625 11:45:49.015 00 00 00 00 00 00 00 00 20200625 11:45:49.143 00 00 28 00 00 00 00 00 [RET] </pre> <br /> 이제야 뭔가 좀 동작하는군요. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 하지만, 아직도 여전히 문제가 있습니다. 지난 글에서도,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ETW(Event Tracing for Windows)를 이용한 닷넷 프로그램의 내부 이벤트 활용 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12244'>https://www.sysnet.pe.kr/2/0/12244</a> </pre> <br /> ETW의 이벤트 발생에 다소 지연이 있다고 했는데, 이 때문에 키보드를 연타하게 되면 - 예를 들어 "test"라고 입력하면 "t", "t"와 같은 식으로 인식이 됩니다. 원인은, urb["fid_URB_TransferBuffer"]로 구하는 URB 버퍼가 재사용되고 있기 때문에 뒤늦게 해당 버퍼를 읽는 경우 마지막에 발생한 키보드 눌림이 들어가 있어 그런 현상이 발생하는 것 같습니다.<br /> <br /> 정리해 보면, 이런저런 이유로 인해 "키보드 눌림"이라는 이벤트는 (지연 시간과 함께) 수신할 수는 있지만 어떤 키인지에 대한 ETW를 통해서는 가져올 수 없다는 한계가 있습니다.<br /> <br /> (<a target='tab' href='https://github.com/stjeong/Ruxcon2016ETW/tree/master/KeyloggerPOC'>위의 변형된 소스 코드는 github에 올려</a>두었습니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
5094
(왼쪽의 숫자를 입력해야 합니다.)