성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
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'>windbg - .NET x86 CLR2/CLR4 EXE의 EntryPoint</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;' > EXE를 LoadLibrary로 로딩해 PE 헤더에 있는 EntryPoint를 직접 호출하는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11858'>http://www.sysnet.pe.kr/2/0/11858</a> WinDbg로 EXE의 EntryPoint에서 BP 거는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11859'>http://www.sysnet.pe.kr/2/0/11859</a> </pre> <br /> 실습한 내용을 .NET x86 CLR2/CLR4 EXE에 적용해 보면 어떨까요? 32비트 윈도우 환경에서 x86 windbg로 로딩하면 Native EXE와 동일하게 EntryPoint 단계까지 BP를 걸어 진행할 수 있습니다. 다음은 Windows Server 2008 x86에서 실습한 결과입니다.<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'>bp $exentry</span> *** WARNING: Unable to verify checksum for ConsoleApp1.exe 0:000> <span style='color: blue; font-weight: bold'>g</span> 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 </pre> <br /> 재미있는 점은, EntryPoint가 ConsoleApp1.exe 모듈 내의 코드가 아닌, mscoree!ShellShim__CorExeMain로 되어 있습니다. 심지어 이때의 callstack을 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > mscoree!ShellShim__CorExeMain mscoree!_CorExeMain_Exported+0x8 KERNEL32!BaseThreadInitThunk+0xe ntdll!__RtlUserThreadStart+0x23 ntdll!_RtlUserThreadStart+0x1b </pre> <br /> ShellShim__CorExeMain보다 먼저 _CorExeMain_Exported 단계가 있는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > mscoree!_CorExeMain_Exported: 739d4ddb 8bff mov edi,edi 739d4ddd 56 push esi 739d4dde e80c2f0000 call mscoree!ShellShim__CorExeMain (739d7cef) 739d4de3 6a00 push 0 </pre> <br /> 그렇다면 _CorExeMain_Exported의 739d4ddb가 EntryPoint로써 더 적절할 텐데도 windbg는 ShellShim__CorExeMain의 739d7cef 주소를 가리킨다는 것입니다.<br /> <br /> 여기서 궁금해지는군요. ^^ windbg는 739d7cef 주소를 어떻게 계산한 것일까요? 일단, x86으로 빌드된 .NET EXE는 PE 헤더의 AddressOfEntryPoint 값이 기록되어 있으며 제가 실습한 EXE에는 AddressOfEntryPoint == 0x26ce 값이었습니다. 하지만 모듈의 로딩 주소를 보면,<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'>lm</span> start end module name <span style='color: blue; font-weight: bold'>00100000</span> 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 </pre> <br /> 어떻게 windbg가 (00100000 + 0x26ce = ) 001026ce 값이 아닌 739d7cef를 구한 것인지 감이 안 옵니다. 어쩌면, windbg가 EntryPoint를 .NET EXE에 한해 mscoree의 CorExeMain을 보여주는 것이 아닐까요? 확인을 해보면, AddressOfEntryPoint로 계산한 001026ce 주소에는 0xff, 0x25로 시작하는 JMP DWORD PTR 코드가 있는데,<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'>u 001026ce</span> ConsoleApp1!COM+_Entry_Point <PERF> (ConsoleApp1+0x26ce): 001026ce ff2500201000 jmp dword ptr [ConsoleApp1!COM+_Entry_Point <PERF> (ConsoleApp1+0x2000) (00102000)] 0:000> <span style='color: blue; font-weight: bold'>db 001026ce</span> 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 ................ </pre> <br /> 이때의 00102000 주소에 담긴 값이,<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'>dd 00102000 L4</span> 00102000 <span style='color: blue; font-weight: bold'>739d4ddb</span> 00000000 00000048 00050002 </pre> <br /> _CorExeMain_Exported에서 봤던 그 주소 값입니다. 그러니까, 결국 AddressOfEntryPoint로 계산한 그 JMP 문에서 시작하는 것이 맞습니다. 그런데, 여기서 재미있는 점이 있습니다. AddressOfEntryPoint로 계산한 주솟값을 bp로 설정해도,<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'>bp 001026ce</span> 0:000> <span style='color: blue; font-weight: bold'>bl</span> 0 e 001026ce 0001 (0001) 0:**** ConsoleApp1!COM+_Entry_Point <PERF> (ConsoleApp1+0x26ce) </pre> <br /> 막상 실행해 보면, windbg는 해당 주소에서 (BP로 인한) 정지를 하지 않습니다. 물론 bp 739d4ddb 명령어로 하면 정지하는데 windbg의 이런 동작을 이해할 수가 없군요. ^^; <br /> <br /> <hr style='width: 50%' /><br /> <br /> 참고로, 코드가 별로 없는 EXE라면 windbg에서 진입점 코드를 0xff, 0x25 (JMP DWORD PTR) 바이트를 검색하는 식으로 찾을 수도 있습니다. 예를 들어 이번 글의 예제 같은 경우에는 EXE가 00100000 ~ 00108000 주소에 로드되어 있으므로 s(earch) 명령을 이용해 찾을 수 있습니다.<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'>s 00100000 00108000 ff 25</span> 001026ce ff 25 00 20 10 00 00 00-00 00 00 00 00 00 00 00 .%. ............ </pre> <br /> <hr style='width: 50%' /><br /> <br /> 재미 삼아서 PE 헤더에 나온 AddressOfEntryPoint 값의 파일 위치를 한번 계산해 볼까요? ^^ 제가 만든 .NET EXE의 경우 AddressOfEntryPoint == 0x26ce였고, .text 섹션의 정보는 다음과 같았습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .text Virtual Address == 0x2000 Raw Address == 0x200 </pre> <br /> 예전에도 한번 설명했던 방법에 따라,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 닷넷의 어셈블리 서명 데이터 확인 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/10816'>http://www.sysnet.pe.kr/2/0/10816</a> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0x26ce - VA + RawAddr = 0x26ce - 0x2000 + 0x200 = 000008ce </pre> <br /> 이렇게 구한 000008ce 값의 EXE (로드된 위치가 아닌) 파일 위치를 hexa 덤프로 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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 <span style='color: blue; font-weight: bold'>FF 25</span> ee.dll.........% 000008D0 <span style='color: blue; font-weight: bold'>00 20 40 00</span> 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 ................ </pre> <br /> 결국 "JMP DWORD PTR [00402000]" 코드가 됩니다. 이에 대해서는 아래의 글에도 나오지만,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > What is the Entry Point RVA in a .Net PE file? ; <a target='tab' href='https://stackoverflow.com/questions/6101388/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</a> </pre> <br /> 마이크로소프트의 ECMA 제출 문서에 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > DzonnyDZ/Tools - Partition II Metadata.doc ; <a target='tab' href='https://github.com/DzonnyDZ/Tools/blob/master/Used%20doc/CLI/Partition%20II%20Metadata.doc'>https://github.com/DzonnyDZ/Tools/blob/master/Used%20doc/CLI/Partition%20II%20Metadata.doc</a> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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 </pre> <br /> 무조건 이를 따르는 것으로 나옵니다. 따라서 windbg가 진입점을 AddressOfEntryPoint가 아닌, 0xff, 0x25 이후의 코드로 계산한다는 것이 크게 무리는 없어 보입니다.<br /> <br /> 그런데, 어떻게 이 코드가 실행 시에는 "JMP DWORD PTR [<span style='color: blue; font-weight: bold'>00102000</span>]"으로 바뀌는 걸까요? 왜냐하면, EXE를 빌드할 때는 PE 헤더의 Optional Header에 있는 ImageBase를 기준으로 호출하는 코드를 컴파일러가 작성하기 때문입니다. 즉, PE의 ImageBase 값은 0x400000이고, 따라서 "JMP DWORD PTR [<span style='color: blue; font-weight: bold'>00402000</span>]" 코드는 현재 이미지가 0x400000 메모리에 로딩되었다는 것을 가정으로 0x2000 위치에 있는 주소를 참조하는 것입니다. <br /> <br /> 이 때문에 Image의 로딩 주소가 바뀌면, 즉 windbg에서 lm 명령으로 확인한 로딩 주소인 0x100000으로 바뀌면 저 주소는 "JMP DWORD PTR [00102000]"이 되는 것입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> x86 .NET EXE를 64비트 윈도우에서 x86 windbg로 로드하면 다음과 같은 식의 오류가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Could not create process 'c:\temp\ConsoleApplication1\Debug\ConsoleApp1.exe', Win32 error 0n50 The request is not supported. </pre> <br /> 반면 x86 .NET EXE임에도 x64 windbg로 로딩을 시도하면 정상적으로 ntdll Loader 단계까지의 진입이 됩니다. 하지만 이후 과정에서 정상적인 디버깅이 되지 않습니다.<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'>bp $exentry</span> *** WARNING: Unable to verify checksum for ConsoleApp1.exe 0:000> <span style='color: blue; font-weight: bold'>bl</span> 0 e Disable Clear x86 0000022c`a532275e 0001 (0001) 0:**** ConsoleApp1!COM+_Entry_Point (ConsoleApp1+0x275e) 0:000> <span style='color: blue; font-weight: bold'>g</span> Unable to insert breakpoint 0 at 0000022c`a532275e, <span style='color: blue; font-weight: bold'>Win32 error 0n998</span> "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) </pre> <br /> 아마도 windbg의 버그로 보이는데, 따라서 x86 .NET EXE 모듈은 64비트 환경에서 (현재는) 가능하지 않습니다. <br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
9801
(왼쪽의 숫자를 입력해야 합니다.)