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]

... 16  17  18  19  20  21  22  23  24  25  26  27  28  29  [30]  ...
NoWriterDateCnt.TitleFile(s)
12871정성태12/12/20217461오류 유형: 771. docker: Error response from daemon: OCI runtime create failed
12870정성태12/9/20216067개발 환경 구성: 614. 파이썬 - PyPI 패키지 만들기 (4) package_data 옵션
12869정성태12/8/20218274개발 환경 구성: 613. git clone 실행 시 fingerprint 묻는 단계를 생략하는 방법
12868정성태12/7/20216842오류 유형: 770. twine 업로드 시 "HTTPError: 400 Bad Request ..." 오류 [1]
12867정성태12/7/20216550개발 환경 구성: 612. 파이썬 - PyPI 패키지 만들기 (3) entry_points 옵션
12866정성태12/7/202113921오류 유형: 769. "docker build ..." 시 "failed to solve with frontend dockerfile.v0: failed to read dockerfile ..." 오류
12865정성태12/6/20216616개발 환경 구성: 611. 파이썬 - PyPI 패키지 만들기 (2) long_description, cmdclass 옵션
12864정성태12/6/20215089Linux: 46. WSL 환경에서 find 명령을 사용해 파일을 찾는 방법
12863정성태12/4/20216990개발 환경 구성: 610. 파이썬 - PyPI 패키지 만들기
12862정성태12/3/20215732오류 유형: 768. Golang - 빌드 시 "cmd/go: unsupported GOOS/GOARCH pair linux /amd64" 오류
12861정성태12/3/20217955개발 환경 구성: 609. 파이썬 - "Windows embeddable package"로 개발 환경 구성하는 방법
12860정성태12/1/20216060오류 유형: 767. SQL Server - 127.0.0.1로 접속하는 경우 "Access is denied"가 발생한다면?
12859정성태12/1/202112213개발 환경 구성: 608. Hyper-V 가상 머신에 Console 모드로 로그인하는 방법
12858정성태11/30/20219466개발 환경 구성: 607. 로컬의 USB 장치를 원격 머신에 제공하는 방법 - usbip-win
12857정성태11/24/20216959개발 환경 구성: 606. WSL Ubuntu 20.04에서 파이썬을 위한 uwsgi 설치 방법
12856정성태11/23/20218744.NET Framework: 1121. C# - 동일한 IP:Port로 바인딩 가능한 서버 소켓 [2]
12855정성태11/13/20216140개발 환경 구성: 605. Azure App Service - Kudu SSH 환경에서 FTP를 이용한 파일 전송
12854정성태11/13/20217680개발 환경 구성: 604. Azure - 윈도우 VM에서 FTP 여는 방법
12853정성태11/10/20216060오류 유형: 766. Azure App Service - JBoss 호스팅 생성 시 "This region has quota of 0 PremiumV3 instances for your subscription. Try selecting different region or SKU."
12851정성태11/1/20217370스크립트: 34. 파이썬 - MySQLdb 기본 예제 코드
12850정성태10/27/20218521오류 유형: 765. 우분투에서 pip install mysqlclient 실행 시 "OSError: mysql_config not found" 오류
12849정성태10/17/20217690스크립트: 33. JavaScript와 C#의 시간 변환 [1]
12848정성태10/17/20218647스크립트: 32. 파이썬 - sqlite3 기본 예제 코드 [1]
12847정성태10/14/20218493스크립트: 31. 파이썬 gunicorn - WORKER TIMEOUT 오류 발생
12846정성태10/7/20218259스크립트: 30. 파이썬 __debug__ 플래그 변수에 따른 코드 실행 제어
12845정성태10/6/20218099.NET Framework: 1120. C# - BufferBlock<T> 사용 예제 [5]파일 다운로드1
... 16  17  18  19  20  21  22  23  24  25  26  27  28  29  [30]  ...