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") + ")");
}
}
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]