Microsoft MVP성태의 닷넷 이야기
디버깅 기술: 126. windbg - .NET x86 CLR2/CLR4 EXE의 EntryPoint [링크 복사], [링크+제목 복사]
조회: 785
글쓴 사람
홈페이지
첨부 파일
 

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@outlook.com

비밀번호

댓글 쓴 사람
 




1  2  3  4  5  6  7  [8]  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
11883정성태5/3/2019964.NET Framework: 827. C# - 인터넷 시간 서버로부터 받은 시간을 윈도우에 적용하는 방법파일 다운로드1
11882정성태5/9/2019661.NET Framework: 826. (번역글) .NET Internals Cookbook Part 11 - Various C# riddles파일 다운로드1
11881정성태4/28/2019890오류 유형: 532. .NET Core 프로젝트로 마이그레이션 시 "CS0579 Duplicate 'System.Reflection.AssemblyCompanyAttribute' attribute" 오류 발생
11880정성태4/25/2019568오류 유형: 531. 이벤트 로그 오류 - Task Scheduling Error: m->NextScheduledSPRetry 1547, m->NextScheduledEvent 1547
11879정성태10/21/2019736.NET Framework: 825. (번역글) .NET Internals Cookbook Part 10 - Threads, Tasks, asynchronous code and others파일 다운로드1
11878정성태5/9/2019738.NET Framework: 824. (번역글) .NET Internals Cookbook Part 9 - Finalizers, queues, card tables and other GC stuff파일 다운로드1
11877정성태5/9/2019739.NET Framework: 823. (번역글) .NET Internals Cookbook Part 8 - C# gotchas파일 다운로드1
11876정성태5/9/2019644.NET Framework: 822. (번역글) .NET Internals Cookbook Part 7 - Word tearing, locking and others파일 다운로드1
11875정성태4/21/2019527오류 유형: 530. Visual Studo에서 .NET Core 프로젝트를 열 때 "One or more errors occurred." 오류 발생
11874정성태5/9/2019651.NET Framework: 821. (번역글) .NET Internals Cookbook Part 6 - Object internals파일 다운로드1
11873정성태5/9/2019609.NET Framework: 820. (번역글) .NET Internals Cookbook Part 5 - Methods, parameters, modifiers파일 다운로드1
11872정성태5/9/2019776.NET Framework: 819. (번역글) .NET Internals Cookbook Part 4 - Type members파일 다운로드1
11871정성태5/9/2019835.NET Framework: 818. (번역글) .NET Internals Cookbook Part 3 - Initialization tricks [3]파일 다운로드1
11870정성태4/16/2019699.NET Framework: 817. Process.Start로 실행한 콘솔 프로그램의 출력 결과를 얻는 방법파일 다운로드1
11869정성태5/9/2019712.NET Framework: 816. (번역글) .NET Internals Cookbook Part 2 - GC-related things파일 다운로드1
11868정성태4/15/2019689.NET Framework: 815. CER(Constrained Execution Region)이란?파일 다운로드1
11867정성태4/15/2019648.NET Framework: 814. Critical Finalizer와 SafeHandle의 사용 의미파일 다운로드1
11866정성태4/9/20191170Windows: 159. 네트워크 공유 폴더(net use)에 대한 인증 정보는 언제까지 유효할까요?
11865정성태4/9/2019625오류 유형: 529. 제어판 - C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Administrative Tools is not accessible.
11864정성태4/9/2019656오류 유형: 528. '...' could be '0': this does not adhere to the specification for the function '...'
11863정성태4/9/2019809디버깅 기술: 127. windbg - .NET x64 EXE의 EntryPoint
11862정성태4/7/2019686개발 환경 구성: 437. .NET EXE의 ASLR 기능을 끄는 방법
11861정성태4/6/2019785디버깅 기술: 126. windbg - .NET x86 CLR2/CLR4 EXE의 EntryPoint
11860정성태4/5/20191190오류 유형: 527. Visual C++ 컴파일 오류 - error C2220: warning treated as error - no 'object' file generated
11859정성태4/4/2019882디버깅 기술: 125. WinDbg로 EXE의 EntryPoint에서 BP 거는 방법
11858정성태3/27/2019842VC++: 129. EXE를 LoadLibrary로 로딩해 PE 헤더에 있는 EntryPoint를 직접 호출하는 방법파일 다운로드1
1  2  3  4  5  6  7  [8]  9  10  11  12  13  14  15  ...