성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; 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'>.NET AnyCPU로 빌드된 PE 헤더의 로딩 전/후 차이점</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;' > 해당 DLL이 Managed인지 / Unmanaged인지 확인하는 방법 - 두 번째 이야기 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1296'>http://www.sysnet.pe.kr/2/0/1296</a> </pre> <br /> AnyCPU의 PE 헤더가 로딩 전/후에 따라 달라지는 것을 알게 되었습니다.<br /> <br /> 예를 들어, AnyCPU로 빌드한 콘솔 프로그램의 경우 디스크 상의 파일을 기반으로 "<a target='tab' href='https://ntcore.com/?tag=cff-explorer'>CFF Explorer</a>"를 이용해 로딩해 보면 다음과 같은 PE Header 정보를 볼 수 있습니다.<br /> <br /> <img alt='anycpu_pe_header_1.png' src='/SysWebRes/bbs/anycpu_pe_header_1.png' /><br /> <br /> <img alt='anycpu_pe_header_2.png' src='/SysWebRes/bbs/anycpu_pe_header_2.png' /><br /> <br /> 즉, 완벽하게 32bit 규격의 PE header를 가지고 있습니다. 하지만 이것을 windbg로 로딩해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Microsoft (R) Windows Debugger Version 10.0.17763.132 AMD64 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: C:\ConsoleApplication1\ConsoleApplication1\bin\Debug\ConsoleApplication1.exe ************* Path validation summary ************** Response Time (ms) Location Deferred SRV*c:\Symbols*http://msdl.microsoft.com/download/symbols Symbol search path is: SRV*c:\Symbols*http://msdl.microsoft.com/download/symbols Executable search path is: ModLoad: <span style='color: blue; font-weight: bold'>00000000`00ef0000</span> 00000000`00ef8000 ConsoleApplication1.exe ModLoad: 00007ffe`19c60000 00007ffe`19e50000 ntdll.dll <span style='color: blue; font-weight: bold'>ModLoad: 00007ffe`022f0000 00007ffe`02354000 C:\WINDOWS\SYSTEM32\MSCOREE.DLL</span> ModLoad: 00007ffe`18e20000 00007ffe`18ed2000 C:\WINDOWS\System32\KERNEL32.dll ModLoad: 00007ffe`16e70000 00007ffe`17113000 C:\WINDOWS\System32\KERNELBASE.dll (5abc.5564): Break instruction exception - code 80000003 (first chance) ntdll!LdrpDoDebuggerBreak+0x30: 00007ffe`19d311dc cc int 3 </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>dt _IMAGE_DOS_HEADER 00000000`00ef0000</span> ntdll!_IMAGE_DOS_HEADER +0x000 e_magic : 0x5a4d +0x002 e_cblp : 0x90 +0x004 e_cp : 3 +0x006 e_crlc : 0 +0x008 e_cparhdr : 4 +0x00a e_minalloc : 0 +0x00c e_maxalloc : 0xffff +0x00e e_ss : 0 +0x010 e_sp : 0xb8 +0x012 e_csum : 0 +0x014 e_ip : 0 +0x016 e_cs : 0 +0x018 e_lfarlc : 0x40 +0x01a e_ovno : 0 +0x01c e_res : [4] 0 +0x024 e_oemid : 0 +0x026 e_oeminfo : 0 +0x028 e_res2 : [10] 0 <span style='color: blue; font-weight: bold'>+0x03c e_lfanew : 0n128</span> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>? 00000000`00ef0000 + 0n128 + 0n4</span> Evaluate expression: 15663236 = 00000000`00ef0084 0:000> <span style='color: blue; font-weight: bold'>dt _IMAGE_FILE_HEADER 00000000`00ef0084</span> ntdll!_IMAGE_FILE_HEADER <span style='color: blue; font-weight: bold'>+0x000 Machine : 0x14c</span> +0x002 NumberOfSections : 3 +0x004 TimeDateStamp : 0x5e022dd0 +0x008 PointerToSymbolTable : 0 +0x00c NumberOfSymbols : 0 <span style='color: blue; font-weight: bold'>+0x010 SizeOfOptionalHeader : 0xf0</span> +0x012 Characteristics : 0x22 </pre> <br /> 보는 바와 같이 _IMAGE_FILE_HEADER의 Machine 값이 (현재 x64 프로세스 공간임에도 불구하고) 0x14c(IMAGE_FILE_MACHINE_I386) 값을 갖고 있기 때문에 AnyCPU 이미지인 경우에는 Machine 필드의 값으로 타겟 플랫폼을 판단해서는 안 됩니다.<br /> <br /> 그런데, 재미있는 변화가 있습니다. 바로 SizeOfOptionalHeader의 값이 디스크 상에서는 0xe0이었던 것과는 달리 DLL 메모리 매핑이 이뤄진 시점에는 0xf0으로 변경되었다는 점입니다. 따라서 이 값을 기반으로 현재 로딩된 이미지를 x86 또는 x64 중 어떤 것으로 다뤄야 하는지 판단할 수 있습니다.<br /> <br /> 혹은, IMAGE_FILE_HEADER의 바로 다음 int16 값을 읽어보는 것도 방법일 수 있습니다. 즉, IMAGE_OPTIONAL_HEADER 영역의 Magic 필드 값을 읽어보는 건데요. 이 값도 디스크 상에 있을 때는 0x010B로 "PE32" 유형을 의미하지만 DLL 메모리 매핑이 이뤄진 시점에는,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>dw 00000000`00ef0084+0x14 L1</span> 00000000`00ef0098 <span style='color: blue; font-weight: bold'>020b</span> </pre> <br /> 0x020b로 PE64를 의미하는 값으로 바뀌어 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그런데 좀 미심쩍은 면이 있습니다. windbg에 로딩하자마자, 다음과 같이 mscoree.dll이 로드된 것을 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ModLoad: 00000000`00ef0000 00000000`00ef8000 ConsoleApplication1.exe ModLoad: 00007ffe`19c60000 00007ffe`19e50000 ntdll.dll <span style='color: blue; font-weight: bold'>ModLoad: 00007ffe`022f0000 00007ffe`02354000 C:\WINDOWS\SYSTEM32\MSCOREE.DLL</span> ModLoad: 00007ffe`18e20000 00007ffe`18ed2000 C:\WINDOWS\System32\KERNEL32.dll ModLoad: 00007ffe`16e70000 00007ffe`17113000 C:\WINDOWS\System32\KERNELBASE.dll </pre> <br /> 혹시 저 DLL에서 PE 헤더의 값을 동적으로 바꾸는 것일까요? 이를 확인하기 위해 windbg의 "Debug" / "Event Filters..." 메뉴를 선택하고, "Load module" 이벤트 항목에 대해 "Execution"을 "Enabled"로, "Continue"를 "Not Handled"로 바꿔준 다음,<br /> <br /> <img alt='anycpu_pe_header_3.png' src='/SysWebRes/bbs/anycpu_pe_header_3.png' /><br /> <br /> 다시 AnyCPU 실행 파일을 로드해 보면 이렇게 2개만 로드된 것을 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ModLoad: <span style='color: blue; font-weight: bold'>00000000`00380000</span> 00000000`00388000 ConsoleApplication1.exe ModLoad: 00007ffe`19c60000 00007ffe`19e50000 ntdll.dll </pre> <br /> 이제 다시 PE Header 관련 값을 덤프해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>? 00000000`00380000 + 0n128 + 0n4</span> Evaluate expression: 3670148 = 00000000`00380084 0:000> <span style='color: blue; font-weight: bold'>dt _IMAGE_FILE_HEADER 00000000`00380084</span> ntdll!_IMAGE_FILE_HEADER +0x000 Machine : 0x14c +0x002 NumberOfSections : 3 +0x004 TimeDateStamp : 0x5e022dd0 +0x008 PointerToSymbolTable : 0 +0x00c NumberOfSymbols : 0 <span style='color: blue; font-weight: bold'>+0x010 SizeOfOptionalHeader : 0xe0</span> +0x012 Characteristics : 0x22 0:000> <span style='color: blue; font-weight: bold'>dw 00000000`00380084+0x14 L1</span> 00000000`00380098 <span style='color: blue; font-weight: bold'>010b</span> </pre> <br /> 오호... 놀랍군요. ^^ 이 시점까지는 디스크 상의 PE 파일 내용과 동일하게 값을 유지하고 있습니다. 그렇다면 이러한 PE Header를 동적으로 변경하는 것은 운영체제가 아니고 mscoree.dll일 수 있다는 것입니다. 실제로 위의 상태에서 디버깅을 진행해 "mscoree.dll"을 로딩한 다음,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>g</span> ModLoad: 00007ffe`022f0000 00007ffe`02354000 C:\WINDOWS\SYSTEM32\MSCOREE.DLL ntdll!NtMapViewOfSection+0x14: 00007ffe`19cfc5c4 c3 ret 0:000> </pre> <br /> 다시 PE Header 값을 확인해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>dt _IMAGE_FILE_HEADER 00000000`00380084</span> ntdll!_IMAGE_FILE_HEADER +0x000 Machine : 0x14c +0x002 NumberOfSections : 3 +0x004 TimeDateStamp : 0x5e022dd0 +0x008 PointerToSymbolTable : 0 +0x00c NumberOfSymbols : 0 <span style='color: blue; font-weight: bold'>+0x010 SizeOfOptionalHeader : 0xf0</span> +0x012 Characteristics : 0x22 0:000> <span style='color: blue; font-weight: bold'>dw 00000000`00380084+0x14 L1</span> 00000000`00380098 <span style='color: blue; font-weight: bold'>020b</span> </pre> <br /> 값이 바뀌어 있습니다. (DllMain 함수 내에서 바꾸는 코드가 호출되는 걸까요? ^^)<br /> <br /> <hr style='width: 50%' /><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;' > The Relationship Between .NET And The Windows Kernel ; <a target='tab' href='http://debugandconquer.blogspot.com/2015/04/the-relationship-between-net-and.html'>http://debugandconquer.blogspot.com/2015/04/the-relationship-between-net-and.html</a> </pre> <br /> 다음의 내용을 볼 수 있습니다.<br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> AnyCPU Executables - How Does It Work?<br /> <br /> <span style='color: blue; font-weight: bold'><a target='tab' href='https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/corvalidateimage-function'>The documentation of mscoree’s _CorValidateImage</a> refers to changing 32-bit (PE32) executable image to 64-bit (PE32+) executable image in memory</span> (an executable in memory is called an Image) on 64-bit Windows, and you might think that it's all to it, However that's a user mode function that is supposed to be called in the newly created process by the NTDLL Loader, the decision about the architecture of a process happens earlier, and in kernel mode (kernel structures like EPROCESS depend on it), so there has to be some kernel mode code knowing about .NET which decides which architecture to use. <br /> <br /> Note: I debugged and _CorValidateImage isn't getting called on Windows 8.1 (on Windows 7 it does), its responsibility seems to have moved to the Loader's LdrpCorValidateImage and LdrpCorFixupImage functions.<br /> </div><br /> <br /> 여기서 재미있는 점이 있습니다. 운영체제는 PE32 header를 갖는 AnyCPU 이미지를 x64 시스템에서는 64bit 프로세스로 실행해 주는데 결국 위의 링크에서도 언급하고 있지만 EPROCESS 커널 자료 구조를 위해서라도 Kernel 레벨에서 32비트 이미지와 동일하게 빌드되어 있는 AnyCPU 이미지를 판단해야 한다는 것입니다.<br /> <br /> 아마도, Kernel 측의 Loader에서는 PE32 header만으로는 판단하지 못하고 "Data Directory"까지 내려가 <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/debug/pe-format'>CLR Runtime Header</a>를 가지고 있는지, 만약 가지고 있다면 그 안에 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/framework/tools/corflags-exe-corflags-conversion-tool'>"ILONLY", "32BITPREF"</a>가 설정되었는지를 보고 AnyCPU로서의 적절한 프로세스 구성을 하게 될 것입니다.<br /> <br /> 참고로 _CorValidateImage 함수에 대한 마이크로소프트의 공식 문서를 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > _CorValidateImage Function ; <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/corvalidateimage-function'>https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/corvalidateimage-function</a> </pre> <br /> 웬만큼 궁금했던 내용들을 다 설명하고 있군요. ^^<br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> In Windows XP and later versions, the operating system loader checks for managed modules by examining the COM Descriptor Directory bit in the common object file format (COFF) header. A set bit indicates a managed module. If the loader detects a managed module, it loads MsCorEE.dll and calls _CorValidateImage, which performs the following actions:<br /> <br /> <ul> <li>Confirms that the image is a valid managed module.</li> <li>Changes the entry point in the image to an entry point in the common language runtime (CLR).</li> <li>For 64-bit versions of Windows, modifies the image that is in memory by transforming it from PE32 to PE32+ format.</li> <li>Returns to the loader when the managed module images are loaded.</li> </ul> <br /> For executable images, the operating system loader then calls the _CorExeMain function, regardless of the entry point specified in the executable. For DLL assembly images, the loader calls the _CorDllMain function.<br /> <br /> _CorExeMain or _CorDllMain performs the following actions:<br /> <br /> <ul> <li>Initializes the CLR.</li> <li>Locates the managed entry point from the assembly's CLR header.</li> <li>Begins execution.</li> </ul> <br /> The loader calls the _CorImageUnloading function when managed module images are unloaded. However, this function does not perform any action; it just returns.<br /> </div><br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1025
(왼쪽의 숫자를 입력해야 합니다.)