Microsoft MVP성태의 닷넷 이야기
디버깅 기술: 126. windbg - .NET x86 CLR2/CLR4 EXE의 EntryPoint [링크 복사], [링크+제목 복사],
조회: 12074
글쓴 사람
정성태 (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

비밀번호

댓글 작성자
 




... 31  32  33  34  35  36  37  38  39  40  41  42  [43]  44  45  ...
NoWriterDateCnt.TitleFile(s)
12560정성태3/11/202110146개발 환경 구성: 549. ssh-keygen으로 생성한 개인키/공개키 파일을 각각 PKCS8/PEM 형식으로 변환하는 방법
12559정성태3/11/20219583.NET Framework: 1028. 닷넷 5 환경의 Web API에 OpenAPI 적용을 위한 NSwag 또는 Swashbuckle 패키지 사용 [2]파일 다운로드1
12558정성태3/10/20219057Windows: 192. Power Automate Desktop (Preview) 소개 - Bitvise SSH Client 제어 [1]
12557정성태3/10/20217735Windows: 191. 탐색기의 보안 탭에 있는 "Object name" 경로에 LEFT-TO-RIGHT EMBEDDING 제어 문자가 포함되는 문제
12556정성태3/9/20216989오류 유형: 703. PowerShell ISE의 Debug / Toggle Breakpoint 메뉴가 비활성 상태인 경우
12555정성태3/8/20219049Windows: 190. C# - 레지스트리에 등록된 DigitalProductId로부터 라이선스 키(Product Key)를 알아내는 방법파일 다운로드2
12554정성태3/8/20218849.NET Framework: 1027. 닷넷 응용 프로그램을 위한 PDB 옵션 - full, pdbonly, portable, embedded
12553정성태3/5/20219310개발 환경 구성: 548. 기존 .NET Framework 프로젝트를 .NET Core/5+ 용으로 변환해 주는 upgrade-assistant, try-convert 도구 소개 [4]
12552정성태3/5/20218582개발 환경 구성: 547. github workflow/actions에서 Visual Studio Marketplace 패키지 등록하는 방법
12551정성태3/5/20217488오류 유형: 702. 비주얼 스튜디오 - The 'CascadePackage' package did not load correctly. (2)
12550정성태3/5/20217149오류 유형: 701. Live Share 1.0.3713.0 버전을 1.0.3884.0으로 업데이트 이후 ContactServiceModelPackage 오류 발생하는 문제
12549정성태3/4/20217664오류 유형: 700. VsixPublisher를 이용한 등록 시 다양한 오류 유형 해결책
12548정성태3/4/20218419개발 환경 구성: 546. github workflow/actions에서 nuget 패키지 등록하는 방법
12547정성태3/3/20218957오류 유형: 699. 비주얼 스튜디오 - The 'CascadePackage' package did not load correctly.
12546정성태3/3/20218576개발 환경 구성: 545. github workflow/actions에서 빌드시 snk 파일 다루는 방법 - Encrypted secrets
12545정성태3/2/202111296.NET Framework: 1026. 닷넷 5에 추가된 POH (Pinned Object Heap) [10]
12544정성태2/26/202111520.NET Framework: 1025. C# - Control의 Invalidate, Update, Refresh 차이점 [2]
12543정성태2/26/20219861VS.NET IDE: 158. C# - 디자인 타임(design-time)과 런타임(runtime)의 코드 실행 구분
12542정성태2/20/202112210개발 환경 구성: 544. github repo의 Release 활성화 및 Actions를 이용한 자동화 방법 [1]
12541정성태2/18/20219422개발 환경 구성: 543. 애저듣보잡 - Github Workflow/Actions 소개
12540정성태2/17/20219737.NET Framework: 1024. C# - Win32 API에 대한 P/Invoke를 대신하는 Microsoft.Windows.CsWin32 패키지
12539정성태2/16/20219665Windows: 189. WM_TIMER의 동작 방식 개요파일 다운로드1
12538정성태2/15/202110084.NET Framework: 1023. C# - GC 힙이 아닌 Native 힙에 인스턴스 생성 - 0SuperComicLib.LowLevel 라이브러리 소개 [2]
12537정성태2/11/202111115.NET Framework: 1022. UI 요소의 접근은 반드시 그 UI를 만든 스레드에서! - 두 번째 이야기 [2]
12536정성태2/9/202110105개발 환경 구성: 542. BDP(Bandwidth-delay product)와 TCP Receive Window
12535정성태2/9/20219215개발 환경 구성: 541. Wireshark로 확인하는 LSO(Large Send Offload), RSC(Receive Segment Coalescing) 옵션
... 31  32  33  34  35  36  37  38  39  40  41  42  [43]  44  45  ...