Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 4개 있습니다.)
(시리즈 글이 3개 있습니다.)
디버깅 기술: 148. cdb.exe를 이용해 (ntdll.dll 등에 정의된) 커널 구조체 출력하는 방법
; https://www.sysnet.pe.kr/2/0/12092

.NET Framework: 874. C# - 커널 구조체의 Offset 값을 하드 코딩하지 않고 사용하는 방법
; https://www.sysnet.pe.kr/2/0/12098

디버깅 기술: 156. C# - PDB 파일로부터 심벌(Symbol) 및 타입(Type) 정보 열거
; https://www.sysnet.pe.kr/2/0/12114




C# - 커널 구조체의 Offset 값을 하드 코딩하지 않고 사용하는 방법

물론, 지난 글에 설명한 cdb.exe 등을 이용해 커널 구조체를 출력하면,

cdb.exe를 이용해 (ntdll.dll 등에 정의된) 커널 구조체 출력하는 방법
; https://www.sysnet.pe.kr/2/0/12092

해당 결과를 파싱해 해결할 수 있습니다. 또는, dbgeng.dll을 이용해 직접 제작한 디버거와 함께,

C# - DbgEng.dll을 이용한 간단한 디버거 제작
; https://www.sysnet.pe.kr/2/0/12096

PDB 심벌 파일을 다운로드하는 코드를 곁들이면,

C# - 코드를 통해 PDB 심벌 파일 다운로드 방법
; https://www.sysnet.pe.kr/2/0/12094

cdb.exe 없이도 이 문제를 해결할 수 있습니다. (필요한 라이브러리를 nuget에 올려두었으니) 대략 다음과 같은 식으로 작성하면 되겠지요. ^^

/*
Install-Package WindowsPE
Install-Package SimpleDebugger
*/

using Microsoft.Diagnostics.Runtime.Interop;
using SimpleDebugger;
using System;
using System.Diagnostics;
using System.IO;
using WindowsPE;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            // .NET Framework 4.5.2 이하라면 아래의 코드 추가
            // ServicePointManager.SecurityProtocol |= (SecurityProtocolType)3072;

            string rootPathToSave = Path.Combine(Environment.CurrentDirectory, "sym");

            using (UserDebugger debugger = new UserDebugger())
            {
                debugger.SetOutputText(false);

                ProcessStartInfo psi = new ProcessStartInfo();
                psi.FileName = "DummyApp.exe";
                psi.UseShellExecute = false;
                psi.WindowStyle = ProcessWindowStyle.Hidden;
                psi.CreateNoWindow = true;

                Process child = Process.Start(psi);
                bool attached = false;

                try
                {
                    DownloadPdb(child, rootPathToSave);

                    debugger.ModuleLoaded += (ModuleInfo modInfo) =>
                    {
                        RunDTSetCommand(debugger, rootPathToSave);

                        debugger.SetOutputText(false);
                        debugger.Execute(DEBUG_OUTCTL.IGNORE, "q", DEBUG_EXECUTE.NOT_LOGGED);
                    };

                    if (debugger.AttachTo(child.Id) == false)
                    {
                        Console.WriteLine("Failed to attach");
                        return;
                    }

                    attached = true;
                    debugger.WaitForEvent();
                }
                finally
                {
                    if (attached == true)
                    {
                        debugger.Detach();
                    }

                    try
                    {
                        child.Kill();
                    } catch { }
                }

            }
        }

        private static void RunDTSetCommand(UserDebugger debugger, string rootPathToSave)
        {
            {
                string cmd = ".sympath \"" + rootPathToSave + "\"";
                int result = debugger.Execute(DEBUG_OUTCTL.IGNORE, cmd, DEBUG_EXECUTE.NOT_LOGGED);
                if (result != (int)HResult.S_OK)
                {
                    Console.WriteLine("failed to run command: " + cmd);
                    return;
                }
            }

            {
                string cmd = ".reload /s /f ntdll.dll";
                int result = debugger.Execute(DEBUG_OUTCTL.IGNORE, cmd, DEBUG_EXECUTE.NOT_LOGGED);
                if (result != (int)HResult.S_OK)
                {
                    Console.WriteLine("failed to run command: " + cmd);
                    return;
                }
            }

            debugger.SetOutputText(true);
            {
                string cmd = "dt ntdll!_PEB";
                int result = debugger.Execute(cmd);
                if (result != (int)HResult.S_OK)
                {
                    Console.WriteLine("failed to run command: " + cmd);
                    return;
                }
            }
        }

        private static void DownloadPdb(Process child, string rootPathToSave)
        {
            foreach (ProcessModule module in child.Modules)
            {
                if (module.FileName.EndsWith("ntdll.dll", StringComparison.OrdinalIgnoreCase) == true)
                {
                    string modulePath = module.FileName;
                    PEImage pe = PEImage.ReadFromFile(modulePath);
                    DownloadPdb(modulePath, rootPathToSave);
                    return;
                }
            }
        }

        private static void DownloadPdb(string modulePath, string rootPathToSave)
        {
            if (File.Exists(modulePath) == false)
            {
                Console.WriteLine("NOT Found: " + modulePath);
                return;
            }

            PEImage pe = PEImage.ReadFromFile(modulePath);
            if (pe == null)
            {
                Console.WriteLine("Failed to read images");
                return;
            }

            Uri baseUri = new Uri("https://msdl.microsoft.com/download/symbols/");

            foreach (CodeViewRSDS codeView in pe.EnumerateCodeViewDebugInfo())
            {
                if (string.IsNullOrEmpty(codeView.PdbFileName) == true)
                {
                    continue;
                }

                string pdbFileName = codeView.PdbFileName;
                if (Path.IsPathRooted(codeView.PdbFileName) == true)
                {
                    pdbFileName = Path.GetFileName(codeView.PdbFileName);
                }

                string localPath = Path.Combine(rootPathToSave, pdbFileName);
                string localFolder = Path.GetDirectoryName(localPath);

                if (Directory.Exists(localFolder) == false)
                {
                    try
                    {
                        Directory.CreateDirectory(localFolder);
                    }
                    catch (DirectoryNotFoundException)
                    {
                        Console.WriteLine("NOT Found on local: " + codeView.PdbLocalPath);
                        continue;
                    }
                }

                if (File.Exists(localPath) == true)
                {
                    continue;
                }

                if (CopyPdbFromLocal(modulePath, codeView.PdbFileName, localPath) == true)
                {
                    continue;
                }

                Uri target = new Uri(baseUri, codeView.PdbUriPath);
                Uri pdbLocation = GetPdbLocation(target);

                if (pdbLocation == null)
                {
                    string underscorePath = ProbeWithUnderscore(target.AbsoluteUri);
                    pdbLocation = GetPdbLocation(new Uri(underscorePath));
                }

                if (pdbLocation != null)
                {
                    DownloadPdbFile(pdbLocation, localPath);
                }
                else
                {
                    Console.WriteLine("Not Found on symbol server: " + codeView.PdbFileName);
                }
            }
        }

        private static bool CopyPdbFromLocal(string modulePath, string pdbFileName, string localTargetPath)
        {
            if (File.Exists(pdbFileName) == true)
            {
                File.Copy(pdbFileName, localTargetPath);
                return File.Exists(localTargetPath);
            }

            string fileName = Path.GetFileName(pdbFileName);
            string pdbPath = Path.Combine(Environment.CurrentDirectory, fileName);

            if (File.Exists(pdbPath) == true)
            {
                File.Copy(pdbPath, localTargetPath);
                return File.Exists(localTargetPath);
            }

            pdbPath = Path.ChangeExtension(modulePath, ".pdb");
            if (File.Exists(pdbPath) == true)
            {
                File.Copy(pdbPath, localTargetPath);
                return File.Exists(localTargetPath);
            }

            return false;
        }

        private static string ProbeWithUnderscore(string path)
        {
            path = path.Remove(path.Length - 1);
            path = path.Insert(path.Length, "_");
            return path;
        }

        private static void DownloadPdbFile(Uri target, string pathToSave)
        {
            System.Net.HttpWebRequest req = System.Net.WebRequest.Create(target) as System.Net.HttpWebRequest;

            using (System.Net.HttpWebResponse resp = req.GetResponse() as System.Net.HttpWebResponse)
            using (FileStream fs = new FileStream(pathToSave, FileMode.CreateNew, FileAccess.Write, FileShare.None))
            using (BinaryWriter bw = new BinaryWriter(fs))
            {
                BinaryReader reader = new BinaryReader(resp.GetResponseStream());
                long contentLength = resp.ContentLength;

                while (contentLength > 0)
                {
                    byte[] buffer = new byte[4096];
                    int readBytes = reader.Read(buffer, 0, buffer.Length);
                    bw.Write(buffer, 0, readBytes);

                    contentLength -= readBytes;
                }
            }
        }

        private static Uri GetPdbLocation(Uri target)
        {
            System.Net.HttpWebRequest req = System.Net.WebRequest.Create(target) as System.Net.HttpWebRequest;
            req.Method = "HEAD";

            try
            {
                using (System.Net.HttpWebResponse resp = req.GetResponse() as System.Net.HttpWebResponse)
                {
                    return resp.ResponseUri;
                }
            }
            catch (System.Net.WebException)
            {
                return null;
            }
        }
    }
}

위의 코드를 Windows 10 1909 x64에서 실행하면 다음과 같은 출력을 얻을 수 있습니다.

C:\temp> ConsoleApp1.exe
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged    : UChar
+0x003 BitField         : UChar
...[생략]...
+0x7c0 LeapSecondFlags  : Uint4B
+0x7c0 SixtySecondEnabled : Pos 0, 1 Bit
+0x7c0 Reserved         : Pos 1, 31 Bits
+0x7c4 NtGlobalFlag2    : Uint4B

마찬가지로 Windows Server 2008 x86에서 실행하면 다음과 같이 결과가 바뀌므로,

C:\temp> ConsoleApp1.exe
   +0x000 InheritedAddressSpace : UChar
   +0x001 ReadImageFileExecOptions : UChar
   +0x002 BeingDebugged    : UChar
   +0x003 BitField         : UChar
...[생략]...
   +0x22c FlsHighIndex     : Uint4B
   +0x230 WerRegistrationData : Ptr32 Void
   +0x234 WerShipAssertPtr : Ptr32 Void

출력을 잘 이용하면 지겨운 offset 값 하드 코딩을 없앨 수 있겠습니다. ^^




기왕 하는 김에, 위의 결과를 종합해 간단하게 라이브러리로 만들어 nuget에 올렸습니다.

KernelStructOffset
; https://www.nuget.org/packages/KernelStructOffset/

소스 코드
; https://github.com/stjeong/DotNetSamples/tree/master/WinConsole/Debugger

따라서 패키지 추가 후,

Install-Package KernelStructOffset

다음과 같이 코딩을 완료할 수 있습니다.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

class Program
{
    static void Main(string[] args)
    {
        var dict = DbgOffset.Get("_PEB", "ntdll.dll");

        int offset = dict["Ldr"];
        Console.WriteLine("Ldr: " + offset + "(0x" + offset.ToString("x") + ")");
    }
}




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 1/15/2025]

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

비밀번호

댓글 작성자
 



2020-09-10 11시21분
[윈공부] 안녕하세요. KernelStructOffset 모듈을 다운로드받아서 테스트 해보고 있습니다.
Windows 10 Pro Build 190318에서는 정상 동작하는데요^^;
Windows 10 Pro Build 160715에서는 _ethreadOffset.GetPointer(ethreadPtr, "Cid"); 이 값이 0으로 나옵니다.
그래서... KernelStructOffset 소스를 다운로드 받아서 이것 저것 확인해보고 있었는데요.
XP때부터 fieldName은 Cid로 동일하더라구요. 필드 네임이 윈도우 버전에 따라 다른건 아닌거 같아요.
ethreadPtr 이 값은 정상적으로 처리가 되는데요. 특정 윈도우 버전에서 _ethreadOffset.GetPointer(ethreadPtr, "Cid"); 포인터 값을 가져올 수 있는 방법이 없을까요?
도움 부탁드립니다. 감사합니다^^;
[guest]
2020-09-10 01시01분
(소스 코드의) rootPathToSave 위치에 해당 pdb 파일이 정상적으로 다운로드되고 있는지 확인해 보세요. 사실 KernelStructOffset이 하는 역할은 pdb 파일을 이용해 Cid의 위치를 알아내는 것이 전부입니다. (참고로, 다른 머신에서 실행하던 디렉터리를 그대로 복사해서 실행하면 안 됩니다. 새로운 머신에서는 rootPathToSave가 비어 있어야 합니다.)
정성태
2020-09-10 01시30분
[윈공부] 감사합니다.ㅠㅠ
sym 폴더에 pdb 파일을 삭제하고 해보니 정상 동작합니다.
정말 감사드립니다. 진심으로 정말 감사드립니다.
[guest]

... 91  92  93  94  95  96  97  [98]  99  100  101  102  103  104  105  ...
NoWriterDateCnt.TitleFile(s)
11484정성태4/11/201824592.NET Framework: 737. C# - async를 Task 타입이 아닌 사용자 정의 타입에 적용하는 방법파일 다운로드1
11483정성태4/10/201827898개발 환경 구성: 358. "Let's Encrypt"에서 제공하는 무료 SSL 인증서를 IIS에 적용하는 방법 (2) [1]
11482정성태4/10/201820386VC++: 126. CUDA Core 수를 알아내는 방법
11481정성태4/10/201832004개발 환경 구성: 357. CUDA의 인덱싱 관련 용어 - blockIdx, threadIdx, blockDim, gridDim
11480정성태4/9/201822015.NET Framework: 736. C# - API를 사용해 Azure에 접근하는 방법 [2]파일 다운로드1
11479정성태4/9/201817708.NET Framework: 735. Azure - PowerShell로 Access control(IAM)에 새로운 계정 만드는 방법
11478정성태11/8/201919921디버깅 기술: 115. windbg - 덤프 파일로부터 PID와 환경변수 등의 정보를 구하는 방법 [1]
11477정성태4/8/201817405오류 유형: 460. windbg - sos 명령어 수행 시 c0000006 오류 발생
11476정성태4/8/201818928디버깅 기술: 114. windbg - !threads 출력 결과로부터 닷넷 관리 스레드(System.Threading.Thread) 객체를 구하는 방법
11475정성태3/28/201821208디버깅 기술: 113. windbg - Thread.Suspend 호출 시 응용 프로그램 hang 현상에 대한 덤프 분석
11474정성태3/27/201819342오류 유형: 459. xperf: error: TEST.Event: Invalid flags. (0x3ec).
11473정성태3/22/201824549.NET Framework: 734. C# - Thread.Suspend 호출 시 응용 프로그램 hang 현상파일 다운로드2
11472정성태3/22/201818491개발 환경 구성: 356. GTX 1070, GTX 960, GT 640M의 cudaGetDeviceProperties 출력 결과
11471정성태3/20/201821902VC++: 125. CUDA로 작성한 RGB2RGBA 성능 [1]파일 다운로드1
11470정성태3/20/201823918오류 유형: 458. Visual Studio - CUDA 프로젝트 빌드 시 오류 C1189, expression must have a constant value
11469정성태3/19/201816917오류 유형: 457. error MSB3103: Invalid Resx file. Could not load file or assembly 'System.Windows.Forms, ...' or one of its dependencies.
11468정성태3/19/201816473오류 유형: 456. 닷넷 응용 프로그램 실행 시 0x80131401 예외 발생
11467정성태3/19/201815997오류 유형: 455. Visual Studio Installer - 업데이트 실패
11466정성태3/18/201817104개발 환경 구성: 355. 한 대의 PC에서 2개 이상의 DirectX 게임을 실행하는 방법
11463정성태3/15/201819517.NET Framework: 733. 스레드 간의 read/write 시에도 lock이 필요 없는 경우파일 다운로드1
11462정성태3/14/201822342개발 환경 구성: 354. HTTPS 호출에 대한 TLS 설정 확인하는 방법 [1]
11461정성태3/13/201824954오류 유형: 454. 윈도우 업데이트 설치 오류 - 0x800705b4 [1]
11460정성태3/13/201817428디버깅 기술: 112. windbg - 닷넷 메모리 덤프에서 전역 객체의 내용을 조사하는 방법
11459정성태3/13/201818217오류 유형: 453. Debug Diagnostic Tool에서 mscordacwks.dll을 찾지 못하는 문제
11458정성태2/21/201819258오류 유형: 452. This share requires the obsolete SMB1 protocol, which is unsafe and could expose your system to attack. [1]
11457정성태2/17/201823962.NET Framework: 732. C# - Task.ContinueWith 설명 [1]파일 다운로드1
... 91  92  93  94  95  96  97  [98]  99  100  101  102  103  104  105  ...