성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
글쓰기
제목
이름
암호
전자우편
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'>커널 메모리를 읽고 쓰는 NT Legacy driver와 C# 클라이언트 프로그램</h1> <p> 아래의 글을 쓰다 보니,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > User 권한(Ring 3)의 프로그램에서 _ETHREAD 주소(및 커널 메모리를 읽을 수 있다면 _EPROCESS 주소) 구하는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/12102'>http://www.sysnet.pe.kr/2/0/12102</a> </pre> <br /> 커널 메모리 값을 읽기 위해 windbg의 로컬 커널 디버그 모드로 진입해야 하고, 다시 이것을 위해 UEFI Secure Boot도 꺼야 하고 "bcdedit -debug on"를 실행해 재부팅까지 해야 하는 불편함이 있습니다. 그래서 이런 불편함을 덜기 위해 ^^ 그냥 커널 메모리를 읽고 쓸 수 있는 device driver를 만들었습니다.<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 드라이버를 이용하여 C#에서 Port 입출력 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/932'>http://www.sysnet.pe.kr/2/0/932</a> </pre> <br /> 소스 코드와 별반 다르지 않게 구현할 수 있었는데요. 게다가 <a target='tab' href='http://www.sysnet.pe.kr/2/0/12103'>Visual Studio 2019 + WDK의 도움</a>으로 다음과 같이 금방 만들 수 있었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ...[생략]... NTSTATUS MajorDeviceControl(PDEVICE_OBJECT pDeviceObject, PIRP pIrp) { PIO_STACK_LOCATION irpSp; NTSTATUS ntStatus = STATUS_SUCCESS; ULONG inBufLength; /* Input buffer length */ ULONG outBufLength; /* Output buffer length */ PVOID ioBuffer; ULONG ptrSize = sizeof(void*); irpSp = IoGetCurrentIrpStackLocation(pIrp); inBufLength = irpSp->Parameters.DeviceIoControl.InputBufferLength; outBufLength = irpSp->Parameters.DeviceIoControl.OutputBufferLength; ioBuffer = pIrp->AssociatedIrp.SystemBuffer; pIrp->IoStatus.Information = 0; /* Output Buffer Size */ PMEMORYIO_DEVICE_EXTENSION deviceExtension = (PMEMORYIO_DEVICE_EXTENSION)pDeviceObject->DeviceExtension; switch (irpSp->Parameters.DeviceIoControl.IoControlCode) { <span style='color: blue; font-weight: bold'>case IOCTL_READ_MEMORY:</span> if (inBufLength != ptrSize) { ntStatus = STATUS_BUFFER_TOO_SMALL; } else { PUCHAR ptr = BytesToPtr(ioBuffer); deviceExtension->Position = ptr; /* PUCHAR outBuffer = (PUCHAR)ioBuffer; for (ULONG i = 0; i < outBufLength; i++) { outBuffer[i] = *(ptr + i); } */ MM_COPY_ADDRESS address; address.VirtualAddress = ptr; SIZE_T numberOfBytesTransferred = 0; MmCopyMemory(ioBuffer, address, outBufLength, MM_COPY_MEMORY_VIRTUAL, &numberOfBytesTransferred); pIrp->IoStatus.Information = numberOfBytesTransferred; ntStatus = STATUS_SUCCESS; } break; ...[생략]... <span style='color: blue; font-weight: bold'>case IOCTL_WRITE_MEMORY:</span> if (inBufLength == 0) { ntStatus = STATUS_BUFFER_TOO_SMALL; } else { PUCHAR ptr = deviceExtension->Position; /* PUCHAR inBuffer = (PUCHAR)ioBuffer; for (ULONG i = 0; i < inBufLength; i++) { *(ptr + i) = inBuffer[i]; } */ MM_COPY_ADDRESS address; address.VirtualAddress = ioBuffer; SIZE_T numberOfBytesTransferred = 0; MmCopyMemory(ptr, address, inBufLength, MM_COPY_MEMORY_VIRTUAL, &numberOfBytesTransferred); pIrp->IoStatus.Information = numberOfBytesTransferred; ntStatus = STATUS_SUCCESS; } break; default: ntStatus = STATUS_UNSUCCESSFUL; break; } pIrp->IoStatus.Status = ntStatus; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return ntStatus; } ...[생략]... </pre> <br /> 테스트도 해봐야겠지요. ^^ 클라이언트는 C#으로 DeviceIoControl을 DllImport로 연결하면 다음과 같이 쉽게 구현할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Collections.Generic; using System.Text; using Microsoft.Win32.SafeHandles; using System.Runtime.InteropServices; using System.Net; namespace MemoryIOLib { class MemoryIOLib : IDisposable { const uint IOCTL_READ_MEMORY = 0x9c402410; const uint IOCTL_WRITE_MEMORY = 0x9c402414; const uint IOCTL_GETPOS_MEMORY = 0x9c402418; const uint IOCTL_SETPOS_MEMORY = 0x9c40241c; SafeFileHandle fileHandle; public MemoryIOLib() { InitializeDevice(); } public bool InitializeDevice() { Dispose(); fileHandle = Kernel32.CreateFile(@"\\.\KernelMemoryIO", NativeFileAccess.FILE_GENERIC_READ, NativeFileShare.NONE, IntPtr.Zero, NativeFileMode.OPEN_EXISTING, NativeFileFlag.FILE_ATTRIBUTE_NORMAL, IntPtr.Zero); if (fileHandle.IsInvalid == true) { return false; } return true; } public void Dispose() { if (fileHandle != null) { fileHandle.Close(); fileHandle = null; } } public bool IsInitialized { get { return fileHandle != null && fileHandle.IsInvalid == false; } } public IntPtr Position { get { if (this.IsInitialized == false) { return IntPtr.Zero; } byte[] addressBytes = new byte[IntPtr.Size]; int pBytesReturned; if (Kernel32.DeviceIoControl(fileHandle, <span style='color: blue; font-weight: bold'>IOCTL_GETPOS_MEMORY</span>, null, 0, addressBytes, addressBytes.Length, out pBytesReturned, IntPtr.Zero) == true) { if (IntPtr.Size == 8) { return new IntPtr(BitConverter.ToInt64(addressBytes, 0)); } return new IntPtr(BitConverter.ToInt32(addressBytes, 0)); } return IntPtr.Zero; } set { if (this.IsInitialized == false) { return; } byte[] addressBytes = null; if (IntPtr.Size == 8) { addressBytes = BitConverter.GetBytes(value.ToInt64()); } else { addressBytes = BitConverter.GetBytes(value.ToInt32()); } int pBytesReturned; Kernel32.DeviceIoControl(fileHandle, <span style='color: blue; font-weight: bold'>IOCTL_SETPOS_MEMORY</span>, addressBytes, addressBytes.Length, null, 9, out pBytesReturned, IntPtr.Zero); } } internal int WriteMemory(IntPtr ptr, byte[] buffer) { if (this.IsInitialized == false) { return 0; } this.Position = ptr; int pBytesReturned; if (Kernel32.DeviceIoControl(fileHandle, <span style='color: blue; font-weight: bold'>IOCTL_WRITE_MEMORY</span>, buffer, buffer.Length, null, 0, out pBytesReturned, IntPtr.Zero) == true) { return pBytesReturned; } return 0; } public int ReadMemory(IntPtr position, byte [] buffer) { if (this.IsInitialized == false) { return 0; } byte[] addressBytes = null; if (IntPtr.Size == 8) { addressBytes = BitConverter.GetBytes(position.ToInt64()); } else { addressBytes = BitConverter.GetBytes(position.ToInt32()); } int pBytesReturned; if (Kernel32.DeviceIoControl(fileHandle, <span style='color: blue; font-weight: bold'>IOCTL_READ_MEMORY</span>, addressBytes, addressBytes.Length, buffer, buffer.Length, out pBytesReturned, IntPtr.Zero) == true) { return pBytesReturned; } return 0; } } } </pre> <br /> 자, 그럼 다시 Process Explorer를 실행시키고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Windows 10 - Process Explorer로 확인한 Handle 정보를 windbg에서 조회 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/12099'>http://www.sysnet.pe.kr/2/0/12099</a> </pre> <br /> 원하는 프로세스의 handle 목록 중에서 "Thread" 유형인 것을 찾아냅니다. 당연히 해당 핸들의 <a target='tab' href='http://www.sysnet.pe.kr/2/0/12102'>Object Address는 _ETHREAD 구조체</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 System; using KernelStructOffset; namespace MemoryIOLib { class Program { static void Main(string[] args) { using (KernelMemoryIO memoryIO = new KernelMemoryIO()) { if (memoryIO.IsInitialized == false) { Console.WriteLine("Failed to open device"); return; } // 0xFFFF878274668080 from process explorer (Handles view pane: Ctrl+H) // It must be a Handle of Thead type. <span style='color: blue; font-weight: bold'>IntPtr ethreadPtr = new IntPtr(unchecked((long)0xFFFF878274668080));</span> { IntPtr clientIdPtr = ethreadPtr + 0x648; byte[] buffer = new byte[16]; if (memoryIO.<span style='color: blue; font-weight: bold'>ReadMemory</span>(clientIdPtr, buffer) != buffer.Length) { Console.WriteLine("failed to read"); return; } long value = BitConverter.ToInt64(buffer, 0); Console.WriteLine("PID: " + value + "(" + value.ToString("x") + ")"); value = BitConverter.ToInt64(buffer, 8); Console.WriteLine("TID: " + value + "(" + value.ToString("x") + ")"); } } } } } /* 출력 결과 PID: 63740(f8fc) TID: 72284(11a5c) */ </pre> <br /> _ETHREAD 구조체에 보관된 _CLIENT_ID 타입의 Cid 구조체 값을 접근할 수 있습니다. 즉, 사용자 모드(Ring 3) 프로그램에서 원하는 커널 메모리의 내용을 자유롭게 확인할 수 있는 것입니다.<br /> <br /> <hr style='width: 50%' /><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;' > stjeong/KernelMemoryIO ; <a target='tab' href='https://github.com/stjeong/KernelMemoryIO'>https://github.com/stjeong/KernelMemoryIO</a> </pre> <br /> 이 프로그램을 사용할 때 주의할 점이 있다면, 절대로 이렇게 만든 device driver를 "서명"까지 해서 제품에 포함하거나 공식적으로 배포하면 안 됩니다. 커널 메모리를 자유롭게 읽을 수 있게 만드는 것은 시스템에 심각한 보안 위협을 초래할 수 있으며 더군다나 서명까지 해서 배포하면 전 세계의 수많은 해커들이 여러분들이 배포한 sys 파일을 기반으로 더욱 나쁜 해악을 끼칠 수 있도록 도와주게 되는 것입니다.<br /> <br /> 설마 소스 코드도 없이 몰래 구현한 sys의 기능을 누가 알까...라고 안심하는 분이 있다면, 과거 capcom에서 자사의 제품에 배포한 capcom.sys에 사용자 정의 코드를 실행할 수 있는 것을 넣어두었던 것이 역공학으로 널리 ^^; 알려져,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Double KO! Capcom's Street Fighter V installs hidden rootkit on PCs ; <a target='tab' href='https://www.theregister.co.uk/2016/09/23/capcom_street_fighter_v/'>https://www.theregister.co.uk/2016/09/23/capcom_street_fighter_v/</a> tandasat/ExploitCapcom ; <a target='tab' href='https://github.com/tandasat/ExploitCapcom'>https://github.com/tandasat/ExploitCapcom</a> </pre> <br /> 사용되었던 사례를 간과해서는 안 될 것입니다.<br /> <br /> 그러니까,,, KernelMemoryIO는 공부나 테스트 용도로만 사용해야 합니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
4502
(왼쪽의 숫자를 입력해야 합니다.)