Microsoft MVP성태의 닷넷 이야기
디버깅 기술: 126. windbg - .NET x86 CLR2/CLR4 EXE의 EntryPoint [링크 복사], [링크+제목 복사],
조회: 19513
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 1개 있습니다.)
(시리즈 글이 5개 있습니다.)
.NET Framework: 431. .NET EXE 파일을 닷넷 프레임워크 버전에 상관없이 실행할 수 있을까요?
; https://www.sysnet.pe.kr/2/0/1659

.NET Framework: 461. .NET EXE 파일을 닷넷 프레임워크 버전에 상관없이 실행할 수 있을까요? - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/1746

VC++: 129. EXE를 LoadLibrary로 로딩해 PE 헤더에 있는 EntryPoint를 직접 호출하는 방법
; https://www.sysnet.pe.kr/2/0/11858

디버깅 기술: 126. windbg - .NET x86 CLR2/CLR4 EXE의 EntryPoint
; https://www.sysnet.pe.kr/2/0/11861

디버깅 기술: 127. windbg - .NET x64 EXE의 EntryPoint
; https://www.sysnet.pe.kr/2/0/11863




windbg - .NET x86 CLR2/CLR4 EXE의 EntryPoint

지난 글에서,

EXE를 LoadLibrary로 로딩해 PE 헤더에 있는 EntryPoint를 직접 호출하는 방법
; https://www.sysnet.pe.kr/2/0/11858

WinDbg로 EXE의 EntryPoint에서 BP 거는 방법
; https://www.sysnet.pe.kr/2/0/11859

실습한 내용을 .NET x86 CLR2/CLR4 EXE에 적용해 보면 어떨까요? 32비트 윈도우 환경에서 x86 windbg로 로딩하면 Native EXE와 동일하게 EntryPoint 단계까지 BP를 걸어 진행할 수 있습니다. 다음은 Windows Server 2008 x86에서 실습한 결과입니다.

0:000> bp $exentry
*** WARNING: Unable to verify checksum for ConsoleApp1.exe

0:000> g
Breakpoint 0 hit
eax=75ead3ff ebx=7ffff000 ecx=00000000 edx=739d4ddb esi=00000000 edi=00000000
eip=739d7cef esp=002cfd28 ebp=002cfd38 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
mscoree!ShellShim__CorExeMain:
739d7cef 8bff            mov     edi,edi

재미있는 점은, EntryPoint가 ConsoleApp1.exe 모듈 내의 코드가 아닌, mscoree!ShellShim__CorExeMain로 되어 있습니다. 심지어 이때의 callstack을 보면,

mscoree!ShellShim__CorExeMain
mscoree!_CorExeMain_Exported+0x8
KERNEL32!BaseThreadInitThunk+0xe
ntdll!__RtlUserThreadStart+0x23
ntdll!_RtlUserThreadStart+0x1b

ShellShim__CorExeMain보다 먼저 _CorExeMain_Exported 단계가 있는데,

mscoree!_CorExeMain_Exported:
739d4ddb 8bff            mov     edi,edi
739d4ddd 56              push    esi
739d4dde e80c2f0000      call    mscoree!ShellShim__CorExeMain (739d7cef)
739d4de3 6a00            push    0

그렇다면 _CorExeMain_Exported의 739d4ddb가 EntryPoint로써 더 적절할 텐데도 windbg는 ShellShim__CorExeMain의 739d7cef 주소를 가리킨다는 것입니다.

여기서 궁금해지는군요. ^^ windbg는 739d7cef 주소를 어떻게 계산한 것일까요? 일단, x86으로 빌드된 .NET EXE는 PE 헤더의 AddressOfEntryPoint 값이 기록되어 있으며 제가 실습한 EXE에는 AddressOfEntryPoint == 0x26ce 값이었습니다. 하지만 모듈의 로딩 주소를 보면,

0:000> lm
start    end        module name
00100000 00108000   ConsoleApp1 C (private pdb symbols)  C:\temp\ConsoleApp1.pdb
739d0000 73a1a000   mscoree    (pdb symbols)          d:\symbols\mscoree.pdb\DBEE0410B7644D97A9BFD1DB523618F02\mscoree.pdb
75e60000 75f3e000   KERNEL32   (pdb symbols)          d:\symbols\kernel32.pdb\6D189265A3F542139F85B845237355A92\kernel32.pdb
774c0000 775e9000   ntdll      (pdb symbols)          d:\symbols\ntdll.pdb\9AD18D20D0F24B41B6F15F4E91A9D47E2\ntdll.pdb

어떻게 windbg가 (00100000 + 0x26ce = ) 001026ce 값이 아닌 739d7cef를 구한 것인지 감이 안 옵니다. 어쩌면, windbg가 EntryPoint를 .NET EXE에 한해 mscoree의 CorExeMain을 보여주는 것이 아닐까요? 확인을 해보면, AddressOfEntryPoint로 계산한 001026ce 주소에는 0xff, 0x25로 시작하는 JMP DWORD PTR 코드가 있는데,

0:000> u 001026ce
ConsoleApp1!COM+_Entry_Point <PERF> (ConsoleApp1+0x26ce):
001026ce ff2500201000    jmp     dword ptr [ConsoleApp1!COM+_Entry_Point <PERF> (ConsoleApp1+0x2000) (00102000)]

0:000> db 001026ce
001026ce  ff 25 00 20 10 00 00 00-00 00 00 00 00 00 00 00  .%. ............
001026de  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

이때의 00102000 주소에 담긴 값이,

0:000> dd 00102000 L4
00102000  739d4ddb 00000000 00000048 00050002

_CorExeMain_Exported에서 봤던 그 주소 값입니다. 그러니까, 결국 AddressOfEntryPoint로 계산한 그 JMP 문에서 시작하는 것이 맞습니다. 그런데, 여기서 재미있는 점이 있습니다. AddressOfEntryPoint로 계산한 주솟값을 bp로 설정해도,

0:000> bp 001026ce

0:000> bl
 0 e 001026ce     0001 (0001)  0:**** ConsoleApp1!COM+_Entry_Point <PERF> (ConsoleApp1+0x26ce)

막상 실행해 보면, windbg는 해당 주소에서 (BP로 인한) 정지를 하지 않습니다. 물론 bp 739d4ddb 명령어로 하면 정지하는데 windbg의 이런 동작을 이해할 수가 없군요. ^^;




참고로, 코드가 별로 없는 EXE라면 windbg에서 진입점 코드를 0xff, 0x25 (JMP DWORD PTR) 바이트를 검색하는 식으로 찾을 수도 있습니다. 예를 들어 이번 글의 예제 같은 경우에는 EXE가 00100000 ~ 00108000 주소에 로드되어 있으므로 s(earch) 명령을 이용해 찾을 수 있습니다.

0:000> s 00100000 00108000 ff 25
001026ce  ff 25 00 20 10 00 00 00-00 00 00 00 00 00 00 00  .%. ............




재미 삼아서 PE 헤더에 나온 AddressOfEntryPoint 값의 파일 위치를 한번 계산해 볼까요? ^^ 제가 만든 .NET EXE의 경우 AddressOfEntryPoint == 0x26ce였고, .text 섹션의 정보는 다음과 같았습니다.

.text
    Virtual Address == 0x2000
    Raw Address == 0x200

예전에도 한번 설명했던 방법에 따라,

닷넷의 어셈블리 서명 데이터 확인 방법
; https://www.sysnet.pe.kr/2/0/10816

0x26ce - VA + RawAddr
= 0x26ce - 0x2000 + 0x200 = 000008ce

이렇게 구한 000008ce 값의 EXE (로드된 위치가 아닌) 파일 위치를 hexa 덤프로 보면,

 Offset    0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F   Ascii

000008A0  00 AD 26 00 00 00 00 00 00 00 00 00 00 00 00 5F  .?............_
000008B0  43 6F 72 45 78 65 4D 61 69 6E 00 6D 73 63 6F 72  CorExeMain.mscor
000008C0  65 65 2E 64 6C 6C 00 00 00 00 00 00 00 00 FF 25  ee.dll.........%
000008D0  00 20 40 00 00 00 00 00 00 00 00 00 00 00 00 00  ..@.............
000008E0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

결국 "JMP DWORD PTR [00402000]" 코드가 됩니다. 이에 대해서는 아래의 글에도 나오지만,

What is the Entry Point RVA in a .Net PE file?
; https://stackoverflow.com/questions/6101388/what-is-the-entry-point-rva-in-a-net-pe-file

마이크로소프트의 ECMA 제출 문서에 보면,

DzonnyDZ/Tools - Partition II Metadata.doc
; https://github.com/DzonnyDZ/Tools/blob/master/Used%20doc/CLI/Partition%20II%20Metadata.doc

Entry Point RVA	
	RVA of entry point , needs to point to bytes 0xFF 0x25 followed by the RVA in a section marked execute/read for EXEs or 0 for DLLs

무조건 이를 따르는 것으로 나옵니다. 따라서 windbg가 진입점을 AddressOfEntryPoint가 아닌, 0xff, 0x25 이후의 코드로 계산한다는 것이 크게 무리는 없어 보입니다.

그런데, 어떻게 이 코드가 실행 시에는 "JMP DWORD PTR [00102000]"으로 바뀌는 걸까요? 왜냐하면, EXE를 빌드할 때는 PE 헤더의 Optional Header에 있는 ImageBase를 기준으로 호출하는 코드를 컴파일러가 작성하기 때문입니다. 즉, PE의 ImageBase 값은 0x400000이고, 따라서 "JMP DWORD PTR [00402000]" 코드는 현재 이미지가 0x400000 메모리에 로딩되었다는 것을 가정으로 0x2000 위치에 있는 주소를 참조하는 것입니다.

이 때문에 Image의 로딩 주소가 바뀌면, 즉 windbg에서 lm 명령으로 확인한 로딩 주소인 0x100000으로 바뀌면 저 주소는 "JMP DWORD PTR [00102000]"이 되는 것입니다.




x86 .NET EXE를 64비트 윈도우에서 x86 windbg로 로드하면 다음과 같은 식의 오류가 발생합니다.

Could not create process 'c:\temp\ConsoleApplication1\Debug\ConsoleApp1.exe', Win32 error 0n50

The request is not supported.

반면 x86 .NET EXE임에도 x64 windbg로 로딩을 시도하면 정상적으로 ntdll Loader 단계까지의 진입이 됩니다. 하지만 이후 과정에서 정상적인 디버깅이 되지 않습니다.

0:000>  bp $exentry
*** WARNING: Unable to verify checksum for ConsoleApp1.exe

0:000> bl
     0 e Disable Clear  x86 0000022c`a532275e     0001 (0001)  0:**** ConsoleApp1!COM+_Entry_Point  (ConsoleApp1+0x275e)

0:000> g
Unable to insert breakpoint 0 at 0000022c`a532275e, Win32 error 0n998
    "Invalid access to memory location."
The breakpoint was set with BP.  If you want breakpoints
to track module load/unload state you must use BU.
bp0 at 0000022c`a532275e failed
WaitForEvent failed
ntdll!LdrpDoDebuggerBreak+0x31:
00007ff8`970e2cbd eb00            jmp     ntdll!LdrpDoDebuggerBreak+0x33 (00007ff8`970e2cbf)

아마도 windbg의 버그로 보이는데, 따라서 x86 .NET EXE 모듈은 64비트 환경에서 (현재는) 가능하지 않습니다.




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 4/6/2019]

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

비밀번호

댓글 작성자
 




... [106]  107  108  109  110  111  112  113  114  115  116  117  118  119  120  ...
NoWriterDateCnt.TitleFile(s)
11274정성태8/22/201719337.NET Framework: 674. Thread 타입의 Suspend/Resume/Join 사용 관련 예외 처리
11273정성태8/22/201721625오류 유형: 415. 윈도우 업데이트 에러 Error 0x80070643
11272정성태8/21/201724761VS.NET IDE: 120. 비주얼 스튜디오 2017 버전 15.3.1 - C# 7.1 공개 [2]
11271정성태8/19/201719190VS.NET IDE: 119. Visual Studio 2017에서 .NET Core 2.0 프로젝트 환경 구성하는 방법
11270정성태8/17/201730653.NET Framework: 673. C#에서 enum을 boxing 없이 int로 변환하기 [2]
11269정성태8/17/201721446디버깅 기술: 93. windbg - 풀 덤프에서 .NET 스레드의 상태를 알아내는 방법
11268정성태8/14/201721018디버깅 기술: 92. windbg - C# Monitor Lock을 획득하고 있는 스레드 찾는 방법
11267정성태8/10/201725088.NET Framework: 672. 모노 개발 환경
11266정성태8/10/201724893.NET Framework: 671. C# 6.0 이상의 소스 코드를 Visual Studio 설치 없이 명령행에서 컴파일하는 방법
11265정성태8/10/201753130기타: 66. 도서: 시작하세요! C# 7.1 프로그래밍: 기본 문법부터 실전 예제까지 [11]
11264정성태8/9/201724024오류 유형: 414. UWP app을 signtool.exe로 서명 시 0x8007000b 오류 발생
11263정성태8/9/201719505오류 유형: 413. The C# project "..." is targeting ".NETFramework, Version=v4.0", which is not installed on this machine. [3]
11262정성태8/5/201718213오류 유형: 412. windbg - SOS does not support the current target architecture. [3]
11261정성태8/4/201720799디버깅 기술: 91. windbg - 풀 덤프 파일로부터 강력한 이름의 어셈블리 추출 후 사용하는 방법
11260정성태8/3/201718887.NET Framework: 670. C# - 실행 파일로부터 공개키를 추출하는 방법
11259정성태8/2/201718130.NET Framework: 669. 지연 서명된 어셈블리를 sn.exe -Vr 등록 없이 사용하는 방법
11258정성태8/1/201718912.NET Framework: 668. 지연 서명된 DLL과 서명된 DLL의 차이점파일 다운로드1
11257정성태7/31/201719135.NET Framework: 667. bypassTrustedAppStrongNames 옵션 설명파일 다운로드1
11256정성태7/25/201720607디버깅 기술: 90. windbg의 lm 명령으로 보이지 않는 .NET 4.0 ClassLibrary를 명시적으로 로드하는 방법 [1]
11255정성태7/18/201723174디버깅 기술: 89. Win32 Debug CRT Heap Internals의 0xBAADF00D 표시 재현 [1]파일 다운로드3
11254정성태7/17/201719501개발 환경 구성: 322. "Visual Studio Emulator for Android" 에뮬레이터를 "Android Studio"와 함께 쓰는 방법
11253정성태7/17/201719801Math: 21. "Coding the Matrix" 문제 2.5.1 풀이 [1]파일 다운로드1
11252정성태7/13/201718421오류 유형: 411. RTVS 또는 PTVS 실행 시 Could not load type 'Microsoft.VisualStudio.InteractiveWindow.Shell.IVsInteractiveWindowFactory2'
11251정성태7/13/201717079디버깅 기술: 88. windbg 분석 - webengine4.dll의 MgdExplicitFlush에서 발생한 System.AccessViolationException의 crash 문제 (2)
11250정성태7/13/201720666디버깅 기술: 87. windbg 분석 - webengine4.dll의 MgdExplicitFlush에서 발생한 System.AccessViolationException의 crash 문제 [1]
11249정성태7/12/201718476오류 유형: 410. LoadLibrary("[...].dll") failed - The specified procedure could not be found.
... [106]  107  108  109  110  111  112  113  114  115  116  117  118  119  120  ...