Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 2개 있습니다.)

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

비밀번호

댓글 작성자
 




1  2  3  4  5  6  7  8  9  10  11  12  13  [14]  15  ...
NoWriterDateCnt.TitleFile(s)
13270정성태2/24/20233755.NET Framework: 2098. dotnet build에 /p 옵션을 적용 시 유의점
13269정성태2/23/20234290스크립트: 46. 파이썬 - uvicorn의 콘솔 출력을 UDP로 전송
13268정성태2/22/20234842개발 환경 구성: 667. WSL 2 내부에서 열고 있는 UDP 서버를 호스트 측에서 접속하는 방법
13267정성태2/21/20234770.NET Framework: 2097. C# - 비동기 소켓 사용 시 메모리 해제가 finalizer 단계에서 발생하는 사례파일 다운로드1
13266정성태2/20/20234371오류 유형: 848. .NET Core/5+ - Process terminated. Couldn't find a valid ICU package installed on the system
13265정성태2/18/20234285.NET Framework: 2096. .NET Core/5+ - PublishSingleFile 유형에 대한 runtimeconfig.json 설정
13264정성태2/17/20235769스크립트: 45. 파이썬 - uvicorn 사용자 정의 Logger 작성
13263정성태2/16/20233893개발 환경 구성: 666. 최신 버전의 ilasm.exe/ildasm.exe 사용하는 방법
13262정성태2/15/20234986디버깅 기술: 191. dnSpy를 이용한 (소스 코드가 없는) 닷넷 응용 프로그램 디버깅 방법 [1]
13261정성태2/15/20234279Windows: 224. Visual Studio - 영문 폰트가 Fullwidth Latin Character로 바뀌는 문제
13260정성태2/14/20234067오류 유형: 847. ilasm.exe 컴파일 오류 - error : syntax error at token '-' in ... -inf
13259정성태2/14/20234196.NET Framework: 2095. C# - .NET5부터 도입된 CollectionsMarshal
13258정성태2/13/20234104오류 유형: 846. .NET Framework 4.8 Developer Pack 설치 실패 - 0x81f40001
13257정성태2/13/20234196.NET Framework: 2094. C# - Job에 Process 포함하는 방법 [1]파일 다운로드1
13256정성태2/10/20235045개발 환경 구성: 665. WSL 2의 네트워크 통신 방법 - 두 번째 이야기
13255정성태2/10/20234343오류 유형: 845. gihub - windows2022 이미지에서 .NET Framework 4.5.2 미만의 프로젝트에 대한 빌드 오류
13254정성태2/10/20234251Windows: 223. (WMI 쿼리를 위한) PowerShell 문자열 escape 처리
13253정성태2/9/20234990Windows: 222. C# - 다른 윈도우 프로그램이 실행되었음을 인식하는 방법파일 다운로드1
13252정성태2/9/20233820오류 유형: 844. ssh로 명령어 수행 시 멈춤 현상
13251정성태2/8/20234298스크립트: 44. 파이썬의 3가지 스레드 ID
13250정성태2/8/20236100오류 유형: 843. System.InvalidOperationException - Unable to configure HTTPS endpoint
13249정성태2/7/20234906오류 유형: 842. 리눅스 - You must wait longer to change your password
13248정성태2/7/20234044오류 유형: 841. 리눅스 - [사용자 계정] is not in the sudoers file. This incident will be reported.
13247정성태2/7/20234961VS.NET IDE: 180. Visual Studio - 닷넷 소스 코드 디버깅 중 "Decompile source code"가 동작하는 않는 문제
13246정성태2/6/20234073개발 환경 구성: 664. Hyper-V에 설치한 리눅스 VM의 VHD 크기 늘리는 방법 - 두 번째 이야기
13245정성태2/6/20234618.NET Framework: 2093. C# - PEM 파일을 이용한 RSA 개인키/공개키 설정 방법파일 다운로드1
1  2  3  4  5  6  7  8  9  10  11  12  13  [14]  15  ...