Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 4개 있습니다.)

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)
        {
            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") + ")");
    }
}




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 9/10/2020]

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]

1  2  3  4  5  6  7  8  9  10  11  12  13  [14]  15  ...
NoWriterDateCnt.TitleFile(s)
13278정성태3/8/20234116개발 환경 구성: 669. WSL 2의 (init이 아닌) systemd 지원 [1]
13277정성태3/6/20234742개발 환경 구성: 668. 코드 사인용 인증서 신청 및 적용 방법(예: Digicert)
13276정성태3/5/20234425.NET Framework: 2102. C# 11 - ref struct/ref field를 위해 새롭게 도입된 scoped 예약어
13275정성태3/3/20234754.NET Framework: 2101. C# 11의 ref 필드 설명
13274정성태3/2/20234322.NET Framework: 2100. C# - ref 필드로 ref struct 타입을 허용하지 않는 이유
13273정성태2/28/20234053.NET Framework: 2099. C# - 관리 포인터로서의 ref 예약어 의미
13272정성태2/27/20234320오류 유형: 850. SSMS - mdf 파일을 Attach 시킬 때 Operating system error 5: "5(Access is denied.)" 에러
13271정성태2/25/20234222오류 유형: 849. Sql Server Configuration Manager가 시작 메뉴에 없는 경우
13270정성태2/24/20233840.NET Framework: 2098. dotnet build에 /p 옵션을 적용 시 유의점
13269정성태2/23/20234404스크립트: 46. 파이썬 - uvicorn의 콘솔 출력을 UDP로 전송
13268정성태2/22/20234960개발 환경 구성: 667. WSL 2 내부에서 열고 있는 UDP 서버를 호스트 측에서 접속하는 방법
13267정성태2/21/20234858.NET Framework: 2097. C# - 비동기 소켓 사용 시 메모리 해제가 finalizer 단계에서 발생하는 사례파일 다운로드1
13266정성태2/20/20234479오류 유형: 848. .NET Core/5+ - Process terminated. Couldn't find a valid ICU package installed on the system
13265정성태2/18/20234390.NET Framework: 2096. .NET Core/5+ - PublishSingleFile 유형에 대한 runtimeconfig.json 설정
13264정성태2/17/20235887스크립트: 45. 파이썬 - uvicorn 사용자 정의 Logger 작성
13263정성태2/16/20234047개발 환경 구성: 666. 최신 버전의 ilasm.exe/ildasm.exe 사용하는 방법
13262정성태2/15/20235100디버깅 기술: 191. dnSpy를 이용한 (소스 코드가 없는) 닷넷 응용 프로그램 디버깅 방법 [1]
13261정성태2/15/20234379Windows: 224. Visual Studio - 영문 폰트가 Fullwidth Latin Character로 바뀌는 문제
13260정성태2/14/20234179오류 유형: 847. ilasm.exe 컴파일 오류 - error : syntax error at token '-' in ... -inf
13259정성태2/14/20234344.NET Framework: 2095. C# - .NET5부터 도입된 CollectionsMarshal
13258정성태2/13/20234216오류 유형: 846. .NET Framework 4.8 Developer Pack 설치 실패 - 0x81f40001
13257정성태2/13/20234310.NET Framework: 2094. C# - Job에 Process 포함하는 방법 [1]파일 다운로드1
13256정성태2/10/20235157개발 환경 구성: 665. WSL 2의 네트워크 통신 방법 - 두 번째 이야기
13255정성태2/10/20234478오류 유형: 845. gihub - windows2022 이미지에서 .NET Framework 4.5.2 미만의 프로젝트에 대한 빌드 오류
13254정성태2/10/20234365Windows: 223. (WMI 쿼리를 위한) PowerShell 문자열 escape 처리
13253정성태2/9/20235148Windows: 222. C# - 다른 윈도우 프로그램이 실행되었음을 인식하는 방법파일 다운로드1
1  2  3  4  5  6  7  8  9  10  11  12  13  [14]  15  ...