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

비밀번호

댓글 작성자
 




... 46  47  48  49  50  51  52  53  54  55  56  57  [58]  59  60  ...
NoWriterDateCnt.TitleFile(s)
12179정성태3/10/202020046개발 환경 구성: 481. docker - PostgreSQL 컨테이너 실행
12178정성태3/10/202011549개발 환경 구성: 480. Linux 운영체제의 docker를 위한 tcp 바인딩 추가 [1]
12177정성태3/9/202011152개발 환경 구성: 479. docker - MySQL 컨테이너 실행
12176정성태3/9/202010571개발 환경 구성: 478. 파일의 (sha256 등의) 해시 값(checksum) 확인하는 방법
12175정성태3/8/202010689개발 환경 구성: 477. "Docker Desktop for Windows"의 "Linux Container" 모드를 위한 tcp 바인딩 추가
12174정성태3/7/202010222개발 환경 구성: 476. DockerDesktopVM의 파일 시스템 접근 [3]
12173정성태3/7/202011200개발 환경 구성: 475. docker - SQL Server 2019 컨테이너 실행 [1]
12172정성태3/7/202016112개발 환경 구성: 474. docker - container에서 root 권한 명령어 실행(sudo)
12171정성태3/6/202011069VS.NET IDE: 143. Visual Studio - ASP.NET Core Web Application의 "Enable Docker Support" 옵션으로 달라지는 점 [1]
12170정성태3/6/20209699오류 유형: 599. "Docker Desktop is switching..." 메시지와 DockerDesktopVM CPU 소비 현상
12169정성태3/5/202011709개발 환경 구성: 473. Windows nanoserver에 대한 docker pull의 태그 사용 [1]
12168정성태3/5/202012396개발 환경 구성: 472. 윈도우 환경에서의 dockerd.exe("Docker Engine" 서비스)가 Linux의 것과 다른 점
12167정성태3/5/202011653개발 환경 구성: 471. C# - 닷넷 응용 프로그램에서 DB2 Express-C 데이터베이스 사용 (3) - ibmcom/db2express-c 컨테이너 사용
12166정성태3/4/202011286개발 환경 구성: 470. Windows Server 컨테이너 - DockerMsftProvider 모듈을 이용한 docker 설치
12165정성태3/2/202010945.NET Framework: 900. 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 네 번째 이야기(Monitor.Enter 후킹)파일 다운로드1
12164정성태2/29/202011779오류 유형: 598. Surface Pro 6 - Windows Hello Face Software Device가 인식이 안 되는 문제
12163정성태2/27/202010241.NET Framework: 899. 익명 함수를 가리키는 delegate 필드에 대한 직렬화 문제
12162정성태2/26/202013045디버깅 기술: 166. C#에서 만든 COM 객체를 C/C++로 P/Invoke Interop 시 메모리 누수(Memory Leak) 발생 [6]파일 다운로드2
12161정성태2/26/20209678오류 유형: 597. manifest - The value "x64" of attribute "processorArchitecture" in element "assemblyIdentity" is invalid.
12160정성태2/26/202010359개발 환경 구성: 469. Reg-free COM 개체 사용을 위한 manifest 파일 생성 도구 - COMRegFreeManifest
12159정성태2/26/20208538오류 유형: 596. Visual Studio - The project needs to include ATL support
12158정성태2/25/202010346디버깅 기술: 165. C# - Marshal.GetIUnknownForObject/GetIDispatchForObject 사용 시 메모리 누수(Memory Leak) 발생파일 다운로드1
12157정성태2/25/202010259디버깅 기술: 164. C# - Marshal.GetNativeVariantForObject 사용 시 메모리 누수(Memory Leak) 발생 및 해결 방법파일 다운로드1
12156정성태2/25/20209603오류 유형: 595. LINK : warning LNK4098: defaultlib 'nafxcw.lib' conflicts with use of other libs; use /NODEFAULTLIB:library
12155정성태2/25/20208886오류 유형: 594. Warning NU1701 - This package may not be fully compatible with your project
12154정성태2/25/20208702오류 유형: 593. warning LNK4070: /OUT:... directive in .EXP differs from output filename
... 46  47  48  49  50  51  52  53  54  55  56  57  [58]  59  60  ...