Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 

windbg - 파일 열기 시점에 bp를 걸어 파일명 알아내는 방법(Managed/Unmanaged)

프로그램에서 파일을 열었을 때, BP를 걸어 어떤 파일을 열려고 하는지 알고 싶을 때가 있습니다. 실습을 위해 다음과 같이 간단한 프로그램을 하나 만들고,

class Program
{
    static void Main(string[] args)
    {
        File.WriteAllText("test.txt", "test is good");
    }
}

64비트로 빌드해 windbg에서 BP를 걸어보겠습니다.




파일을 여는 시점을 알아내려면, 우선 그에 따른 적당한 메서드를 찾는 것이 중요합니다. 닷넷의 경우, 모든 파일 열기는 FileStream을 통해 이뤄지고, 그중에서도 Init 메서드가 통합해서 담당을 합니다.

namespace System.IO
{
    //
    // Summary:
    //     Provides a System.IO.Stream for a file, supporting both synchronous and asynchronous
    //     read and write operations.
    [ComVisible(true)]
    public class FileStream : Stream
    {
        // ...[생략]...

        [SecuritySafeCritical]
        private unsafe void Init(string path, FileMode mode, FileAccess access, int rights, bool useRights, FileShare share, int bufferSize, FileOptions options, Win32Native.SECURITY_ATTRIBUTES secAttrs, string msgPath, bool bFromProxy, bool useLongPath, bool checkHost)
        {
            // ...[생략]...
            _handle = Win32Native.SafeCreateFile(text3, dwDesiredAccess, share, secAttrs, mode, num2, IntPtr.Zero);
            // ...[생략]...
        }

        // ...[생략]...
    }
}

따라서, windbg로 exe 파일을 실행한 후 (위의 프로그램에서는 초기에 File을 사용하므로) clrjit.dll 로딩 시점에 bp를 걸어둔 후,

0:000> sxe ld:clrjit

실행을 계속해,

0:000> g
ModLoad: 00007ff8`6eb10000 00007ff8`6ecb0000   C:\Windows\System32\USER32.dll
ModLoad: 00007ff8`6d140000 00007ff8`6d162000   C:\Windows\System32\win32u.dll
ModLoad: 00007ff8`57060000 00007ff8`5711d000   C:\Windows\SYSTEM32\ucrtbase_clr0400.dll
ModLoad: 00007ff8`57040000 00007ff8`57056000   C:\Windows\SYSTEM32\VCRUNTIME140_CLR0400.dll
ModLoad: 00007ff8`6d680000 00007ff8`6d6aa000   C:\Windows\System32\GDI32.dll
ModLoad: 00007ff8`6cfe0000 00007ff8`6d0e9000   C:\Windows\System32\gdi32full.dll
ModLoad: 00007ff8`6ce40000 00007ff8`6cedd000   C:\Windows\System32\msvcp_win.dll
ModLoad: 00007ff8`6cee0000 00007ff8`6cfe0000   C:\Windows\System32\ucrtbase.dll
ModLoad: 00007ff8`6e770000 00007ff8`6e7a0000   C:\Windows\System32\IMM32.DLL
ModLoad: 00007ff8`6d6b0000 00007ff8`6da05000   C:\Windows\System32\combase.dll
(2ef8.d40): Unknown exception - code 04242420 (first chance)
ModLoad: 00007ff8`6e3d0000 00007ff8`6e3d8000   C:\Windows\System32\psapi.dll
ModLoad: 00007ff8`54ad0000 00007ff8`560d0000   C:\Windows\assembly\NativeImages_v4.0.30319_64\mscorlib\52ce2de93895cec3507794c893974551\mscorlib.ni.dll
ModLoad: 00007ff8`6e8c0000 00007ff8`6e9ea000   C:\Windows\System32\ole32.dll
ModLoad: 00007ff8`6d6b0000 00007ff8`6da05000   C:\Windows\System32\combase.dll
ModLoad: 00007ff8`6d5a0000 00007ff8`6d61f000   C:\Windows\System32\bcryptPrimitives.dll
ModLoad: 00007ff8`54840000 00007ff8`5498f000   C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clrjit.dll
ntdll!NtMapViewOfSection+0x14:
00007ff8`6f6ac294 c3              ret

bp가 걸리면 sos.dll 확장을 통해,

0:000> .loadby sos clr

FileStream.Init 메서드에,

0:000> !name2ee mscorlib.dll!System.IO.FileStream.Init
Module:      00007ff854ad1000
Assembly:    mscorlib.dll
Token:       000000000600184e
MethodDesc:  00007ff854cbcf30
Name:        System.IO.FileStream.Init(System.String, System.IO.FileMode, System.IO.FileAccess, Int32, Boolean, System.IO.FileShare, Int32, System.IO.FileOptions, SECURITY_ATTRIBUTES, System.String, Boolean, Boolean, Boolean)
JITTED Code Address: 00007ff855056b40

BP를 걸어둡니다.

0:000> !bpmd -md 00007ff854cbcf30
MethodDesc = 00007ff854cbcf30
Setting breakpoint: bp 00007FF855056B40 [System.IO.FileStream.Init(System.String, System.IO.FileMode, System.IO.FileAccess, Int32, Boolean, System.IO.FileShare, Int32, System.IO.FileOptions, SECURITY_ATTRIBUTES, System.String, Boolean, Boolean, Boolean)]

이후 실행을 재개하면,

0:000> g
Breakpoint 0 hit
mscorlib_ni+0x586b40:
00007ff8`55056b40 55              push    rbp

FileStream.Init 메서드가 실행되는 시점에 windbg에 의해 실행이 멈추게 되고,

0:000> !clrstack
OS Thread Id: 0x22d0 (0)
        Child SP               IP Call Site
0000009924afec18 00007ff855056b40 System.IO.FileStream.Init(System.String, System.IO.FileMode, System.IO.FileAccess, Int32, Boolean, System.IO.FileShare, Int32, System.IO.FileOptions, SECURITY_ATTRIBUTES, System.String, Boolean, Boolean, Boolean)
0000009924afec20 00007ff855027c12 System.IO.FileStream..ctor(System.String, System.IO.FileMode, System.IO.FileAccess, System.IO.FileShare, Int32, System.IO.FileOptions, System.String, Boolean, Boolean, Boolean)
0000009924afecd0 00007ff85502169e System.IO.StreamWriter.CreateFile(System.String, Boolean, Boolean)
0000009924afed60 00007ff8550215e9 System.IO.StreamWriter..ctor(System.String, Boolean, System.Text.Encoding, Int32, Boolean)
0000009924afedc0 00007ff8558be533 System.IO.File.InternalWriteAllText(System.String, System.String, System.Text.Encoding, Boolean)
0000009924afee40 00007ff7f7d208cc ConsoleApp1.Program.Main(System.String[]) [C:\temp\ConsoleApp1\ConsoleApp1\Program.cs @ 14]
0000009924aff058 00007ff857246913 [GCFrame: 0000009924aff058] 

스레드의 스택에 있는 의미 있는 변수들을 !dso 확장 명령어를 통해 덤프해 보면 쉽게 FileStream.Init에 전달된 "파일의 경로"를 알아낼 수 있습니다.

0:000> !dso
OS Thread Id: 0x22d0 (0)
RSP/REG          Object           Name
rcx              000002431b543478 System.IO.FileStream
rdx              000002431b542e20 System.String    test.txt
rsi              000002431b543478 System.IO.FileStream
rdi              000002431b542e20 System.String    test.txt
0000009924AFEC20 000002431b543478 System.IO.FileStream
0000009924AFEC28 000002431b542e20 System.String    test.txt
0000009924AFEC70 000002431b542e20 System.String    test.txt
0000009924AFEC90 000002431b542e20 System.String    test.txt
0000009924AFECA0 000002431b542e20 System.String    test.txt
...[생략]...
0000009924AFF1E0 000002431b542e08 System.String[]
0000009924AFF1E8 000002431b542e08 System.String[]

혹은, 정확하게 알아내고 싶다면, FileStream.Init이 instance 멤버 메서드이므로 rcx에 전달되는 첫 번째 인자는 this 포인터의 값이고, rdx에 전달되는 두 번째 인자가 FileStream.Init 메서드의 첫 번째 인자에 해당하는 "string path"에 해당하므로 다음과 같이 !do 명령어를 통해 콕 집어 확인할 수 있습니다.

0:000> !do @rdx
Name:        System.String
MethodTable: 00007ff854af59c0
EEClass:     00007ff854ad2ec0
Size:        42(0x2a) bytes
File:        C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String:      test.txt
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ff854af85a0  4000283        8         System.Int32  1 instance                8 m_stringLength
00007ff854af6838  4000284        c          System.Char  1 instance               74 m_firstChar
00007ff854af59c0  4000288       e0        System.String  0   shared           static Empty
                                 >> Domain:Value  00000250f19f07f0:NotInit  <<




알아보는 김에, Native 프로그램 기준으로도 살펴보겠습니다. 모든 파일 호출은 kernel32.dll의 CreateFileW Win32 API에서 이뤄지므로 bp를 다음과 같이 걸어,

0:000> bu kernel32!CreateFileW

0:000> bl
     0 e Disable Clear  00007ff8`6ed94b60     0001 (0001)  0:**** KERNEL32!CreateFileW

실행해 보면,

0:000> g
ModLoad: 00007ff8`6e3e0000 00007ff8`6e48a000   C:\Windows\System32\ADVAPI32.dll
ModLoad: 00007ff8`6da10000 00007ff8`6daae000   C:\Windows\System32\msvcrt.dll
ModLoad: 00007ff8`6dab0000 00007ff8`6db4b000   C:\Windows\System32\sechost.dll
ModLoad: 00007ff8`6db50000 00007ff8`6dc74000   C:\Windows\System32\RPCRT4.dll
ModLoad: 00007ff8`58a70000 00007ff8`58b1a000   C:\Windows\Microsoft.NET\Framework64\v4.0.30319\mscoreei.dll
Breakpoint 0 hit
KERNEL32!CreateFileW:
00007ff8`6ed94b60 ff25eac50500 jmp qword ptr [KERNEL32!_imp_CreateFileW (00007ff8`6edf1150)] 
                                             ds:00007ff8`6edf1150={KERNELBASE!CreateFileW (00007ff8`6d197860)}

파일이 열리는 시점에 "0번 BP" 설정에 의해 실행이 멈췄다는 메시지와 함께 현재 RIP 주소에 해당하는 명령을 하나 보여줍니다. (위의 경우에는 jmp)

Native에 익숙한 분들이라면 이것이 IAT의 점프 구문임을 알 수 있고,

0:000> u
KERNEL32!CreateFileW:
00007ff8`6ed94b60 ff25eac50500    jmp     qword ptr [KERNEL32!_imp_CreateFileW (00007ff8`6edf1150)]
00007ff8`6ed94b66 cc              int     3
00007ff8`6ed94b67 cc              int     3
00007ff8`6ed94b68 cc              int     3
00007ff8`6ed94b69 cc              int     3
00007ff8`6ed94b6a cc              int     3
00007ff8`6ed94b6b cc              int     3
00007ff8`6ed94b6c cc              int     3

따라서 그냥 bp를 _imp_CreateFileW로 걸어두는 것이 더 편합니다.

0:000> bp 00007ff8`6d197860

만약 기존 bp를 지우지 않고 그대로 g를 눌러 실행을 계속하면 파일 열기 하나에 다음과 같이 두 번의 bp가 걸리는 것을 볼 수 있습니다.

0:000> g
Breakpoint 0 hit
KERNEL32!CreateFileW:
00007ff8`6ed94b60 ff25eac50500    jmp     qword ptr [KERNEL32!_imp_CreateFileW (00007ff8`6edf1150)] ds:00007ff8`6edf1150={KERNELBASE!CreateFileW (00007ff8`6d197860)}

0:000> g
Breakpoint 1 hit
KERNELBASE!CreateFileW:
00007ff8`6d197860 4883ec58        sub     rsp,58h

첫 번째는 "0번 bp"로 걸린 것이고, 두 번째는 "1번 bp"로 걸린 것을 의미합니다. (이쯤에서, bl 또는 bc 명령어를 통해 0번 bp는 삭제해 줍니다.)

참고로, KERNELBASE!CreateFileW의 전체 코드는 uf 명령어로 역어셈블할 수 있습니다.

0:000> uf 00007ff8`6d197860
KERNELBASE!CreateFileW:
00007ff8`6d197860 4883ec58        sub     rsp,58h
00007ff8`6d197864 448b942488000000 mov     r10d,dword ptr [rsp+88h]
00007ff8`6d19786c 418bc2          mov     eax,r10d
00007ff8`6d19786f 25b77f0000      and     eax,7FB7h
00007ff8`6d197874 c744243020000000 mov     dword ptr [rsp+30h],20h
00007ff8`6d19787c 89442434        mov     dword ptr [rsp+34h],eax
00007ff8`6d197880 418bc2          mov     eax,r10d
00007ff8`6d197883 250000f0ff      and     eax,0FFF00000h
00007ff8`6d197888 89442438        mov     dword ptr [rsp+38h],eax
00007ff8`6d19788c 410fbae214      bt      r10d,14h
00007ff8`6d197891 7239            jb      KERNELBASE!CreateFileW+0x6c (00007ff8`6d1978cc)  Branch

KERNELBASE!CreateFileW+0x33:
00007ff8`6d197893 8364243c00      and     dword ptr [rsp+3Ch],0

KERNELBASE!CreateFileW+0x38:
00007ff8`6d197898 488b842490000000 mov     rax,qword ptr [rsp+90h]
00007ff8`6d1978a0 8364242800      and     dword ptr [rsp+28h],0
00007ff8`6d1978a5 4889442448      mov     qword ptr [rsp+48h],rax
00007ff8`6d1978aa 488d442430      lea     rax,[rsp+30h]
00007ff8`6d1978af 4c894c2440      mov     qword ptr [rsp+40h],r9
00007ff8`6d1978b4 448b8c2480000000 mov     r9d,dword ptr [rsp+80h]
00007ff8`6d1978bc 4889442420      mov     qword ptr [rsp+20h],rax
00007ff8`6d1978c1 e81a000000      call    KERNELBASE!CreateFileInternal (00007ff8`6d1978e0)
00007ff8`6d1978c6 4883c458        add     rsp,58h
00007ff8`6d1978ca c3              ret

KERNELBASE!CreateFileW+0x6c:
00007ff8`6d1978cc 4181e200001f00  and     r10d,1F0000h
00007ff8`6d1978d3 448954243c      mov     dword ptr [rsp+3Ch],r10d
00007ff8`6d1978d8 ebbe            jmp     KERNELBASE!CreateFileW+0x38 (00007ff8`6d197898)  Branch

우리는 그저, CreateFileW에 전달된 파일 경로만을 알고 싶은 것이므로 역시 이번에도 닷넷의 "!dso"와 유사한 스레드 콜 스택의 의미 있는 정보를 덤프해 볼 수 있습니다.

0:000> dqs @rsp
000000f1`492fecb8  00007ff8`58a79164 mscoreei!RuntimeDesc::VerifyMainRuntimeModule+0x2c
000000f1`492fecc0  00000000`00000000
000000f1`492fecc8  00000219`6806e550
000000f1`492fecd0  000000f1`492feec8
000000f1`492fecd8  00007ff8`58a7575e mscoreei!SString::Set+0x4e
000000f1`492fece0  ffffffff`00000003
000000f1`492fece8  00000012`10000000
000000f1`492fecf0  00000000`00000000
000000f1`492fecf8  00007ff8`58ae2290 mscoreei!`string'
000000f1`492fed00  000000f1`492fee10
000000f1`492fed08  00007ff8`58a761b5 mscoreei!FindRuntimesInInstallRoot+0x2f2
000000f1`492fed10  00000000`00000000
000000f1`492fed18  00000000`00000000
000000f1`492fed20  00000219`6806e490
000000f1`492fed28  00000000`00000001
000000f1`492fed30  00000219`6806e510

운이 좋다면 위의 주솟값들 중에 함수에 전달된 인자의 값이 있겠지만, x64의 특성상 처음 4개의 인자는 레지스터를 통해 전달되었을 것이므로 다행히 함수의 진입 초기에 bp가 걸렸다면 인자에 따라 rcx, rdx, r8, r9로 값을 알아낼 수 있습니다. 그리고 CreateFileW의 경우 첫 번째 인자가 파일 경로이므로 다음과 같이 @rcx가 그 값을 담고 있습니다.

0:000> du @rcx
000000f1`492feda8  "C:\Windows\Microsoft.NET\Framewo"
000000f1`492fede8  "rk64\\v4.0.30319\clr.dll"

위의 경우에는 닷넷 프로그램이 실행되면서 clr.dll을 여는 것까지 bp에 걸렸기 때문에 저런 경로가 나온 것이고, 적절한 시점에 이 글의 닷넷 예제 코드를 테스트해 보면 "test.txt"에 대한 경로도 보게 될 것입니다.

0:000> du @rcx
00000230`551a69ac  "C:\Users\testuser\testdir\"
00000230`551a69ec  "bp_createfile_test\ConsoleApp1\Consol"
00000230`551a6a2c  "eApp1\bin\Debug\test.txt"




시간 되시면, 다음의 글도 한 번 읽어보시고. ^^

windbg - x64 덤프 분석 시 메서드의 인자 또는 로컬 변수의 값을 확인하는 방법
; https://www.sysnet.pe.kr/2/0/12069

windbg의 콜스택 함수 인자를 쉽게 확인하는 방법
; https://www.sysnet.pe.kr/2/0/10934

x64 콜스택 인자 추적과 windbg의 Child-SP, RetAddr, Args to Child 값 확인
; https://www.sysnet.pe.kr/2/0/10832

windbg의 콜스택 함수 인자를 쉽게 확인하는 방법
; https://www.sysnet.pe.kr/2/0/10934




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

[연관 글]





[최초 등록일: ]
[최종 수정일: 10/29/2020 ]

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)
12433정성태11/29/202014Windows: 179. 윈도우 환경에서 클라이언트 소켓의 최대 접속 수 (3) - SO_PORT_SCALABILITY파일 다운로드1
12432정성태11/29/202055Windows: 178. 윈도우 환경에서 클라이언트 소켓의 최대 접속 수 (2) - SO_REUSEADDR [1]파일 다운로드1
12431정성태11/27/202058.NET Framework: 976. UnmanagedCallersOnly + C# 9.0 함수 포인터 사용 시 x86 빌드에서 오동작하는 문제파일 다운로드1
12430정성태11/27/202014오류 유형: 686. Ubuntu - E: The repository 'cdrom://...' does not have a Release file.
12429정성태11/25/202052디버깅 기술: 175. windbg - 특정 Win32 API에서 BP가 안 걸리는 경우
12428정성태11/25/202033VS.NET IDE: 154. Visual Studio - .NET Core App 실행 시 dotnet.exe 실행 화면만 나오는 문제
12427정성태11/25/2020108.NET Framework: 975. .NET Core를 직접 호스팅해 (runtimeconfig.json 없이) EXE만 배포해 실행파일 다운로드1
12426정성태11/24/202024오류 유형: 685. WinDbg Preview - error InitTypeRead
12425정성태11/24/202022VC++: 141. Visual C++ - "Treat Warnings As Errors" 옵션이 꺼져 있는데도 일부 경고가 에러 처리되는 경우
12424정성태11/24/202055VC++: 140. C++의 연산자 동의어(operator synonyms), 대체 토큰
12423정성태11/22/2020101.NET Framework: 974. C# 9.0 - (16) 제약 조건이 없는 형식 매개변수 주석(Unconstrained type parameter annotations)파일 다운로드1
12422정성태11/21/202070.NET Framework: 973. .NET 5, .NET Framework에서만 허용하는 UnmanagedCallersOnly 사용예파일 다운로드1
12421정성태11/23/202073.NET Framework: 972. DNNE가 출력한 NE DLL을 직접 생성하는 방법파일 다운로드1
12420정성태11/19/202034오류 유형: 684. Visual C++ - MSIL .netmodule or module compiled with /GL found; restarting link with /LTCG; add /LTCG to the link command line to improve linker performance
12419정성태11/23/202092VC++: 139. Visual C++ - .NET Core의 nethost.lib와 정적 링크파일 다운로드1
12418정성태11/19/202032오류 유형: 683. Visual C++ - error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MT_StaticRelease' doesn't match value 'MDd_DynamicDebug'파일 다운로드1
12417정성태11/19/202035오류 유형: 682. Visual C++ - warning LNK4099: PDB '...pdb' was not found with '...lib(pch.obj)' or at '...pdb'; linking object as if no debug info
12416정성태11/19/202023오류 유형: 681. Visual C++ - error LNK2001: unresolved external symbol _CrtDbgReport
12415정성태11/19/202081.NET Framework: 971. UnmanagedCallersOnly 특성과 DNNE 사용파일 다운로드1
12414정성태11/20/2020112VC++: 138. x64 빌드에서 extern "C"가 아닌 경우 ___cdecl name mangling 적용 [4]파일 다운로드1
12413정성태11/17/2020115.NET Framework: 970. .NET 5 / .NET Core - UnmanagedCallersOnly 특성을 사용한 함수 내보내기파일 다운로드1
12412정성태11/21/202081.NET Framework: 969. .NET Framework 및 .NET 5 - UnmanagedCallersOnly 특성 사용파일 다운로드1
12411정성태11/12/202071오류 유형: 680. C# 9.0 - Error CS8889 The target runtime doesn't support extensible or runtime-environment default calling conventions.
12410정성태11/12/202096디버깅 기술: 174. windbg - System.TypeLoadException 예외 분석 사례
12409정성태11/12/2020122.NET Framework: 968. C# 9.0의 Function pointer를 이용한 함수 주소 구하는 방법파일 다운로드1
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...