성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
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# - KernelMemoryIO 드라이버를 이용해 실행 프로그램을 숨기는 방법(DKOM: Direct Kernel Object Modification)</h1> <p> Patch Guard가 보호한다는,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Patch Guard로 인해 블루 스크린(BSOD)가 발생하는 사례 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/12110'>http://www.sysnet.pe.kr/2/0/12110</a> </pre> <br /> ActiveProcessLinks를 변조해 실제로 프로세스를 숨겨보겠습니다. ^^ 사실 방법은 이미 대충 설명했습니다.<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;' > 커널 메모리를 읽고 쓰는 NT Legacy driver와 C# 클라이언트 프로그램 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/12104'>http://www.sysnet.pe.kr/2/0/12104</a> </pre> <br /> KernelMemoryIO 드라이버를 이용해 _ETHREAD와 _EPROCESS에 접근하는 방법을 다뤘습니다. 물론, _ETHREAD의 값을 알기 위해 Process Explorer로 Object Address를 구하긴 했지만, 사실 이것도 핸들을 열람하기만 하면 되므로,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - 프로세스의 모든 핸들을 열람 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/12080'>http://www.sysnet.pe.kr/2/0/12080</a> </pre> <br /> 위에서 만들어 둔 WindowsHandleInfo 타입을 이용해 다음과 같이 현재 프로세스의 스레드로부터 _ETHREAD 주소를 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // Install-Package KernelStructOffset using (WindowsHandleInfo whi = new WindowsHandleInfo()) { for (int i = 0; i < whi.HandleCount; i++) { var she = whi[i]; if (she.OwnerPid != processId) { continue; } string objName = she.GetName(out string handleTypeName); if (handleTypeName == "Thread") { <span style='color: blue; font-weight: bold'>// she.ObjectPointer</span> } } } </pre> <br /> 따라서, 이런 것들을 종합해 지난 "<a target='tab' href='http://www.sysnet.pe.kr/2/0/12104'>커널 메모리를 읽고 쓰는 NT Legacy driver와 C# 클라이언트 프로그램</a>" 글의 예제를 다음과 같이 구현할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // Install-Package KernelStructOffset using KernelStructOffset; using System; using System.Diagnostics; namespace HideProcess { class Program { // Prerequisite: // Register and start "KernelMemoryIO" kernel driver // <a target='tab' href='https://github.com/stjeong/KernelMemoryIO/tree/master/KernelMemoryIO'>https://github.com/stjeong/KernelMemoryIO/tree/master/KernelMemoryIO</a> // // sc create "KernelMemoryIO" binPath= "D:\Debug\KernelMemoryIO.sys" type= kernel start= demand // sc delete "KernelMemoryIO" // net start KernelMemoryIO // net stop KernelMemoryIO static void Main(string[] args) { int processId = Process.GetCurrentProcess().Id; Console.WriteLine($"ThisPID: {processId}"); <span style='color: blue; font-weight: bold'>IntPtr ethreadPtr = GetEThread();</span> if (ethreadPtr == IntPtr.Zero) { Console.WriteLine("THREAD handle not found"); return; } Console.WriteLine($"_ETHREAD address: {ethreadPtr.ToInt64():x}"); Console.WriteLine(); <span style='color: blue; font-weight: bold'>var ethreadOffset = DbgOffset.Get("_ETHREAD");</span> using (KernelMemoryIO memoryIO = new KernelMemoryIO()) { if (memoryIO.IsInitialized == false) { Console.WriteLine("Failed to open device"); return; } { /* 2.2.2.16.2.1 CLIENT_ID ; <a target='tab' href='https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsts/a11e7129-685b-4535-8d37-21d4596ac057'>https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsts/a11e7129-685b-4535-8d37-21d4596ac057</a> typedef struct _CLIENT_ID { HANDLE UniqueProcess; HANDLE UniqueThread; } CLIENT_ID; */ // +0x648 Cid : _CLIENT_ID IntPtr clientIdPtr = ethreadOffset.GetPointer(ethreadPtr, "Cid"); // windows 10 1909 == 0x648; <span style='color: blue; font-weight: bold'>_CLIENT_ID cid = memoryIO.ReadMemory<_CLIENT_ID>(clientIdPtr);</span> Console.WriteLine($"PID: {cid.Pid} ({cid.Pid:x})"); Console.WriteLine($"TID: {cid.Tid} ({cid.Tid:x})"); } } } private static IntPtr GetEThread() { int processId = Process.GetCurrentProcess().Id; using (WindowsHandleInfo whi = new WindowsHandleInfo()) { for (int i = 0; i < whi.HandleCount; i++) { var she = whi[i]; if (she.OwnerPid != processId) { continue; } string objName = she.GetName(out string handleTypeName); if (handleTypeName == "Thread") { return she.ObjectPointer; } } } return IntPtr.Zero; } } } </pre> <br /> <hr style='width: 50%' /><br /> <br /> 본론으로 돌아와서, 프로세스를 숨기는 것도 지난번에 소개한 DLL 숨기는 방법과 유사합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - PEB를 조작해 로드된 DLL을 숨기는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/12105'>http://www.sysnet.pe.kr/2/0/12105</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;' > ActiveProcessLinks---hide/src/hide.cpp ; <a target='tab' href='https://github.com/irp/ActiveProcessLinks---hide/blob/master/src/hide.cpp'>https://github.com/irp/ActiveProcessLinks---hide/blob/master/src/hide.cpp</a> AntiForensics techniques : Process hiding in Kernel Mod ; <a target='tab' href='https://www.cert-devoteam.fr/en/antiforensics-techniques-process-hiding-in-kernel-mode/'>https://www.cert-devoteam.fr/en/antiforensics-techniques-process-hiding-in-kernel-mode/</a> </pre> <br /> 여기서 우리가 다뤄야 할 연결 리스트는 _EPROCESS의 ActiveProcessLinks이므로,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:007> <span style='color: blue; font-weight: bold'>dt _EPROCESS</span> ntdll!_EPROCESS +0x000 Pcb : _KPROCESS +0x2e0 ProcessLock : _EX_PUSH_LOCK +0x2e8 UniqueProcessId : Ptr64 Void <span style='color: blue; font-weight: bold'>+0x2f0 ActiveProcessLinks : _LIST_ENTRY</span> ...[생략]... </pre> <br /> <a target='tab' href='http://www.sysnet.pe.kr/2/0/12098'>DbgOffset</a>을 이용해 다음과 같이 제법 우아하게 ^^ 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > { // +0x220 Process : Ptr64 _KPROCESS IntPtr processPtr = kthreadOffset.GetPointer(ethreadPtr, "Process"); IntPtr eprocessPtr = memoryIO.ReadMemory<IntPtr>(processPtr); IntPtr activeProcessLinksPtr = eprocessOffset.GetPointer(eprocessPtr, "ActiveProcessLinks"); <span style='color: blue; font-weight: bold'>_LIST_ENTRY entry = memoryIO.ReadMemory<_LIST_ENTRY>(activeProcessLinksPtr);</span> Console.WriteLine($"entry.Flink: {entry.Flink.ToInt64():x}"); Console.WriteLine($"entry.Blink: {entry.Blink.ToInt64():x}"); } /* 출력 결과 dll.Flink: ffff850956caa370 dll.Blink: ffff850953e46370 */ </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;' > private unsafe static IntPtr Unlink(KernelMemoryIO memoryIO, IntPtr linkPtr) { _LIST_ENTRY entry = memoryIO.ReadMemory<_LIST_ENTRY>(linkPtr); IntPtr pNext = entry.Flink; IntPtr pPrev = entry.Blink; memoryIO.WriteMemory<IntPtr>(pNext + sizeof(IntPtr) /* pNext.Blink */, pPrev); memoryIO.WriteMemory<IntPtr>(pPrev + 0 /* pPrev.Flink */, pNext); memoryIO.WriteMemory<IntPtr>(linkPtr + sizeof(IntPtr) /* linkPtr.Blink */, linkPtr); memoryIO.WriteMemory<IntPtr>(linkPtr + 0 /* linkPtr.Flink */, linkPtr); return linkPtr; } <span style='color: blue; font-weight: bold'>IntPtr deletedEntry = Unlink(memoryIO, activeProcessLinksPtr);</span> </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;' > <span style='color: blue; font-weight: bold'>RestoreLink(memoryIO, deletedEntry);</span> private unsafe static void RestoreLink(KernelMemoryIO memoryIO, IntPtr deletedLink) { if (deletedLink == IntPtr.Zero) { return; } // WindowsHandleInfo를 이용해 다른 프로세스의 ethreadPtr을 찾아 ActiveProcessLinks를 반환 IntPtr baseLink = GetActiveProcessLinksFromAnotherProcess(memoryIO); if (baseLink == IntPtr.Zero) { Console.WriteLine("Can't find an appropriate ActiveProcessLinks"); return; } Console.WriteLine($"Restore {deletedLink.ToInt64():x} to {baseLink.ToInt64():x}"); _LIST_ENTRY baseEntry = memoryIO.ReadMemory<_LIST_ENTRY>(baseLink); // 다른 프로세스의 ActiveProcessLinks에 숨겨두었던 프로세스의 ActiveProcessLinks를 연결 IntPtr nextItem = baseEntry.Flink; memoryIO.WriteMemory<IntPtr>(deletedLink + 0 /* deletedLink.Flink */, nextItem); memoryIO.WriteMemory<IntPtr>(deletedLink + sizeof(IntPtr) /* deletedLink.Blink */, baseLink); memoryIO.WriteMemory<IntPtr>(baseLink + 0 /* baseLink.Flink */, deletedLink); memoryIO.WriteMemory<IntPtr>(nextItem + sizeof(IntPtr) /* nextItem.Blink */, deletedLink); } </pre> <br /> 와~~~ ^^ 이제 Unlink 호출 후 Console.ReadLine()을 찍어 프로세스가 없어진 것을 (작업 관리자에서) 확인하고, <br /> <br /> <img alt='hide_process_2.png' src='/SysWebRes/bbs/hide_process_2.png' /><br /> <br /> 다시 RestoreLink를 호출해 복원할 수 있습니다.<br /> <br /> 참고로, 이 글의 완전한 소스 코드는 다음의 github에 올려 두었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > DotNetSamples/WinConsole/Debugger/HideProcess/ ; <a target='tab' href='https://github.com/stjeong/DotNetSamples/tree/master/WinConsole/Debugger/HideProcess'>https://github.com/stjeong/DotNetSamples/tree/master/WinConsole/Debugger/HideProcess</a> </pre> <br /> <hr style='width: 50%' /><br /> <br /> Process Explorer로 HideProcess.exe를 테스트해 보면 재미있는 사실을 하나 알 수 있습니다. 이 프로그램을 cmd.exe를 통해 실행하는 경우, 자식 프로세스인 conhost.exe에는 HideProcess.exe에 대해 Process 핸들을 하나 열게 됩니다. 그렇다면 그 Process 핸들은 해당 프로세스가 사라진 다음 어떻게 되는 걸까요?<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='hide_process_1.png' src='/SysWebRes/bbs/hide_process_1.png' /><br /> <br /> 보는 바와 같이 "<Non-existent Process>"로 나옵니다.<br /> <br /> 하지만, Object Address도 있고 Pid도 구해 오는 걸로 봐서 EPROCESS 자료 구조에 대한 접근은 여전히 할 수 있는 걸로 나옵니다. 사실 EPROCESS에도 파일명을 구할 수 있는 필드가 있는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > lkd> <span style='color: blue; font-weight: bold'>dt _EPROCESS</span> ...[생략]... +0x448 ImageFilePointer : Ptr64 _FILE_OBJECT +0x450 ImageFileName : [15] UChar ...[생략]... </pre> <br /> Process Explorer는 저 필드를 사용해 프로세스 이름을 보여주지는 않는 것입니다.<br /> <br /> 그리고 프로세스를 숨겨 둔 상태를 일정 시간 지속하면 "<a target='tab' href='http://www.sysnet.pe.kr/2/0/12110'>Patch Guard로 인해 블루 스크린(BSOD)가 발생하는 사례</a>" 글에 설명한 대로 BSOD가 발생하게 됩니다. 반면, 숨겨두었다가 다시 복원을 하는 경우 ActiveProcessLinks의 순서가 바뀌었지만 BSOD는 발생하지 않는 걸로 봐서 순서까지는 체크하지 않는 것으로 보입니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1527
(왼쪽의 숫자를 입력해야 합니다.)