드라이버 개발의 넓고 얕은 지식 03. ECP 첫 번째 이야기 - 어디에서 내 공유 파일에 접근한거야!?

( 원본 글 )

오랜만의 '넓고 얕은 지식' 시리즈입니다. ㅎㅎ

파일 I/O 를 다루는 개발자라면 반드시 알아야 하지만, 의외로 많은 분들이 대충 넘기시는 ECP 에 관해 몇 가지 끄적여 보려고 합니다.

일단 ECP 가 뭔지 가물가물하신 분은 아래 링크를 살포시 훑어 보세요~

Using ECPs to Process IRPMJCREATE Operations in a File System...

오늘은 ECP 에 대한 첫 번째 이야기로 미니필터 드라이버를 기준으로 어디에서 내 공유 파일에 접근을 한 것인지 찾는 과정을 가볍게 써보겠습니다.

당연한 얘기지만 이런 정보는 드라이버에서 간단한 API 호출로 알 수 있습니다.

오늘은 API 얘기가 아닌, 메모리 덤프나 라이브 커널 디버깅에서 이런 정보를 확인하는 방법을 간단히 소개해드리려고 합니다.

일단 우리 미니필터의 IRPMJCREATE Pre Callback 이 호출된 시점부터 시작해보겠습니다.

콜스택을 확인해보죠.

1: kd> kvn
ChildEBP RetAddr Args to Child
00 8beff3fc 89693aeb 858fd6f0 8beff41c 8beff448 MifiTest!FileFilterPreCreate+0x115
01 8beff468 896969f0 8beff4ac 859a73b0 00000000 fltmgr!FltpPerformPreCallbacks+0x34d
02 8beff480 896aa1fe 8beff4ac 896adf3c 00000000 fltmgr!FltpPassThroughInternal+0x40
03 8beff494 896aa8b7 8beff4ac 859a73b0 87025470 fltmgr!FltpCreateInternal+0x24
04 8beff4d8 82e89593 85ff0ed8 85fef008 870254cc fltmgr!FltpCreate+0x2c9
05 8beff4f0 830992a9 f438c0d8 858fe530 8595e7f8 nt!IofCallDriver+0x63
06 8beff5c8 830d40ae 85ff0ed8 855f38f0 858db008 nt!IopParseDevice+0xed7
07 8beff608 83078ac5 858fe530 855f38f0 858db008 nt!IopParseFile+0x51
08 8beff684 83088ed6 800008a4 8beff6d8 00000240 nt!ObpLookupObjectName+0x4fa
09 8beff6e4 8307f9b4 8beff880 855f38f0 8beffb00 nt!ObOpenObjectByName+0x165
0a 8beff760 830c0eca 8beff8bc 00120089 8beff880 nt!IopCreateFile+0x673
0b 8beff7bc 94504fd0 8beff8bc 00120089 8beff880 nt!IoCreateFileEx+0x9e
0c 859a73b0 00000000 00000884 00000000 86c286b4 srv2!SrvCreateFile+0x5c5

맨 아래 srv2 드라이버의 프레임을 보면 네트워크를 통한 파일 I/O 처리 과정임을 유추할 수 있습니다. (참고로 srv2 는 SMB 서버 드라이버입니다.)

자 그럼 우선 접근 대상 파일부터 확인해 볼까요?

미니 필터이므로 간단하게 콜백에 전달된 FLTCALLBACKDATA 를 확인해보죠.

fltkd 확장 명령을 쓰면 간단하지만 무식하게 dt로 확인해봅시다.

1: kd> dt fltmgr!_FLT_CALLBACK_DATA 858fd6f0
+0x000 Flags : 9
+0x004 Thread : 0x86c28468 _KTHREAD
+0x008 Iopb : 0x858fd71c _FLT_IO_PARAMETER_BLOCK
+0x00c IoStatus : _IO_STATUS_BLOCK
+0x014 TagData : (null)
+0x018 QueueLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x020 QueueContext : [2] (null)
+0x018 FilterContext : [4] (null)
+0x028 RequestorMode : 0 ''

1: kd> dt fltmgr!_FLT_IO_PARAMETER_BLOCK 0x858fd71c
+0x000 IrpFlags : 0x884
+0x004 MajorFunction : 0 ''
+0x005 MinorFunction : 0 ''
+0x006 OperationFlags : 0x9 ''
+0x007 Reserved : 0 ''
+0x008 TargetFileObject : 0x87025470 _FILE_OBJECT
+0x00c TargetInstance : 0x858be008 _FLT_INSTANCE
+0x010 Parameters : _FLT_PARAMETERS

1: kd> !fileobj 0x87025470
desktop.ini
Related File Object: 0x858fe530
Device Object: 0x85ff1e20 \Driver\volmgr
Vpb is NULL
Flags: 0x20
Sequential Only
CurrentByteOffset: 0

1: kd> !fileobj 0x858fe530
\share
Device Object: 0x85ff1e20 \Driver\volmgr
Vpb: 0x85ff1218
Event signalled
Access: Read SharedRead SharedWrite
Flags: 0x40000
Handle Created
FsContext: 0xa259d500 FsContext2: 0xa259d6a0
CurrentByteOffset: 0

아하~ \share\desktop.ini 파일에 접근하고 있군요~!

자 그럼 오늘의 주제인... 대체 어디에서 접근하는 것인지 알아 내는 방법에 대해서 시작해보죠.

Windows 7 부터는 SRV 관련 정보를 다음과 같은 ECP Context 로 확인할 수 있습니다.

typedef struct _SRV_OPEN_ECP_CONTEXT {
//
// Share name for the create with type PUNICODE_STRING
//
PUNICODE_STRING ShareName;
//
// Socket address of client
//
PSOCKADDR_STORAGE_NFS SocketAddress;
//
// Oplock state of open (for SMB/SMB2 oplock breaking logic)
//
BOOLEAN OplockBlockState;
BOOLEAN OplockAppState;
BOOLEAN OplockFinalState;
} SRV_OPEN_ECP_CONTEXT, * PSRV_OPEN_ECP_CONTEXT;

공유 이름과 클라이언트 주소 정보, oplock 상태 등을 저장하고 있습니다.

그럼 이 ECP Context를 찾아보겠습니다.

ECP는 다음과 같은 리스트로 관리됩니다.

1: kd> dt nt!_ECP_LIST
+0x000 Signature : Uint4B
+0x004 Flags : Uint4B
+0x008 EcpList : _LIST_ENTRY

그리고 ECP 리스트는 IRP 에서 찾을 수 있습니다.

그러니 먼저 IRP를 찾아야겠군요.

!thread 확장 명령을 통해 확인해도 되지만, IRP 가 여러개 달려있으면 번거로워지니 !fltkd 확장 명령으로 확인해보겠습니다.

참고로 FltpCreateInternal, FltpPerformPreCallbacks 등 특정 함수의 파라미터를 체크해도 됩니다.

그리고 문서화되지 않은 콜백데이터를 포함한 내부 구조체를 사용해도 되는데 이 얘기는 다음에..

(참고로 fltkd 도 결국 이 정보를 사용합니다 :)

아무튼 fltkd의 cbd 명령으로 콜백데이터 주소를 던져줍시다.

1: kd> !fltkd.cbd 858fd6f0
IRP_CTRL: 858fd690 CREATE (0) [00000009] Irp SystemBuffer
Flags : [1000000c] DontCopyParms Synchronize FixedAlloc
Irp : 859a73b0
DeviceObject : 85ff0ed8 "\Device\HarddiskVolume2"
FileObject : 87025470
CompletionNodeStack : 858fd748 Size=5 Next=1
SyncEvent : (858fd6a0)
InitiatingInstance : 00000000
Icc : 8beff4ac
CreateIrp.NameCacheCtrl : 858eac80
CreateIrp.SavedFsContext : 00000000
CallbackData : (858fd6f0)
Flags : [00000009] Irp SystemBuffer
Thread : 86c28468
Iopb : 858fd71c
RequestorMode : [00] KernelMode
IoStatus.Status : 0x00000000
IoStatus.Information : 00000000
TagData : 00000000
FilterContext[0] : 00000000
FilterContext[1] : 00000000
FilterContext[2] : 00000000
FilterContext[3] : 00000000
Cmd IrpFl OpFl CmpFl Instance FileObjt Completion-Context Node Adr
--------- -------- ----- ----- -------- -------- ------------------ --------
[0,0] 00000000 00 0000 00000000 00000000 00000000-00000000 858fd868
Args: 00000000 00000000 00000000 00000000 00000000 0000000000000000
[0,0] 00000000 00 0000 00000000 00000000 00000000-00000000 858fd820
Args: 00000000 00000000 00000000 00000000 00000000 0000000000000000
[0,0] 00000000 00 0000 00000000 00000000 00000000-00000000 858fd7d8
Args: 00000000 00000000 00000000 00000000 00000000 0000000000000000
[0,0] 00000000 00 0000 00000000 00000000 00000000-00000000 858fd790
Args: 00000000 00000000 00000000 00000000 00000000 0000000000000000
[0,0] 00000884 09 0002 85ffd748 87025470 896ccd6c-449a2877 858fd748
("FileInfo","FileInfo") fileinfo!FIPostCreateCallback
Args: 8beff514 01000144 00070000 00000000 00000000 0000000000000000
Working IOPB:
>[0,0] 00000884 09 858be008 87025470 858fd71c
("MifiTest","AltitudeAndFlags")
Args: 8beff514 01000144 00070000 00000000 00000000 0000000000000000

IRP 를 찾았습니다. 이제 찾은 IRP를 확인해볼까요.

1: kd> dt nt!_IRP 859a73b0
+0x000 Type : 0n6
+0x002 Size : 0x1d8
+0x004 MdlAddress : (null)
+0x008 Flags : 0x884
+0x00c AssociatedIrp : <unnamed-tag>
+0x010 ThreadListEntry : _LIST_ENTRY [ 0x86c286b4 - 0x86c286b4 ]
+0x018 IoStatus : _IO_STATUS_BLOCK
+0x020 RequestorMode : 0 ''
+0x021 PendingReturned : 0 ''
+0x022 StackCount : 9 ''
+0x023 CurrentLocation : 9 ''
+0x024 Cancel : 0 ''
+0x025 CancelIrql : 0 ''
+0x026 ApcEnvironment : 0 ''
+0x027 AllocationFlags : 0x4 ''
+0x028 UserIosb : 0x8beff578 _IO_STATUS_BLOCK
+0x02c UserEvent : (null)
+0x030 Overlay : <unnamed-tag>
+0x038 CancelRoutine : (null)
+0x03c UserBuffer : 0xa2a50e38 Void
+0x040 Tail : <unnamed-tag>

위에서 확인해야 할 부분은 UserBuffer 필드입니다.

(왜 UserBuffer를 봐야 하는지 혹시 모르시는 분이 계실지 모르지만 설명이 길어지니 일단 넘어갑시다 ㅎㅎ)

바로 이 UserBuffer 에 ECP List Head 가 위치하고 있습니다.

한번 볼까요?

1: kd> dt nt!_ecp_list 0xa2a50e38
+0x000 Signature : 0x4c706345
+0x004 Flags : 7
+0x008 EcpList : _LIST_ENTRY [ 0xa2a28378 - 0xa2abec50 ]

1: kd> dc 0xa2a50e38
a2a50e38 4c706345 00000007 a2a28378 a2abec50 EcpL....x...P...
a2a50e48 06210603 6e664d46 00fcf204 00000000 ..!.FMfn........
a2a50e58 00005371 00000000 a2a50e60 00000000 qS......`.......
a2a50e68 00000000 8593ab2c 00000000 00000000 ....,...........
a2a50e78 00018000 00000040 00000001 008c008a ....@...........
a2a50e88 a2a50ec0 002e002e a2a50ec0 00000000 ................
a2a50e98 00000000 00000000 00000000 00000000 ................
a2a50ea8 00000000 00000000 00000000 00000000 ................

'EcpL' (ECP List 란 말이겠죠? ㅎㅎ) 시그니쳐를 가지고 있는 헤드입니다.

Flink 를 수동으로 따라가면서 확인해보겠습니다.

1: kd> dc a2a28378 - 8
a2a28370 48706345 00000000 a2abec50 a2a50e40 EcpH....P...@...
a2a28380 bebfaebc 489daabf e3e92c9d 53281061 .......H.,..a.(S
a2a28390 00000000 00000007 00000040 00000000 ........@.......
a2a283a0 00000000 94865c30 859a0240 00000100 ....0\..@.......
a2a283b0 00150209 e56b6f54 8553e320 8553e320 ....Tok. .S. .S.
a2a283c0 0a860202 64536553 00000000 05000000 ....SeSd........
a2a283d0 00000013 00000101 10000000 00004000 .............@..
a2a283e0 00000101 05000000 0a810207 6e664d46 ............FMfn

1: kd> dc a2abec50 - 8
a2abec48 48706345 00000000 a2a50e40 a2a28378 EcpH....@...x...
a2abec58 48850596 4be73050 c3fe6398 7f8dce50 ...HP0.K.c..P...
a2abec68 00000000 00000003 00000048 00000000 ........H.......
a2abec78 00000000 efd01506 11e5e67d 0c00cbad ........}.......
a2abec88 c5623f29 00000067 060a040a 6d4d6956 )?b.g.......ViMm
a2abec98 a2b2aa18 9499c008 087cf000 a2acfd20 ..........|. ...
a2abeca8 00000000 00000001 85996e70 85996e70 ........pn..pn..
a2abecb8 a2b2aad8 a2b2aad8 00000000 00000000 ................

마지막 엔트리의 Flink는 다시 Head 이므로 총 2개의 ECP 정보가 있네요.

이 ECP_LIST 는 문서화되지 않은 ECP 헤더의 일부분이고, ECP 헤더 뒤에 바로 Context 정보가 위치하게 됩니다.

ECP 헤더에서 중요한 부분은 ECP_LIST 바로 뒤에 위치한 ECP 타입에 대한 GUID 정보입니다.

위 리스트 엔트리 정보들 중 첫 번째 엔트리 정보에서 GUID 는 a2a28380 에 위치하게 되겠죠.

확인해볼까요?

1: kd> dt nt!_GUID a2a28380
{bebfaebc-aabf-489d-9d2c-e9e361102853}
+0x000 Data1 : 0xbebfaebc
+0x004 Data2 : 0xaabf
+0x006 Data3 : 0x489d
+0x008 Data4 : [8] "???"

ntifs.h 파일을 보면 이 GUID 를 찾으실 수 있습니다.

//
// The GUID used for the SRV_OPEN_ECP_CONTEXT structure
// {BEBFAEBC-AABF-489d-9D2C-E9E361102853}
//
//typedef struct sockaddr_storage *PSOCKADDR_STORAGE_SMB;
DEFINE_GUID( GUID_ECP_SRV_OPEN,
0xbebfaebc,
0xaabf,
0x489d,
0x9d, 0x2c, 0xe9, 0xe3, 0x61, 0x10, 0x28, 0x53 );

첫 번째 엔트리가 우리가 원하는 정보인 SRVOPENECPCONTEXT 정보를 가진 녀석임을 알 수 있습니다.

이제 ECP 헤더 뒤에 붙어 있는 컨텍스트 정보를 확인하면 됩니다.

ECP 헤더의 크기는 x86 에서는 0x34 바이트입니다. (x64 에서는 0x48 바이트입니다.)

(위에서도 언급했듯이 ECP 헤더는 문서화되지 않았습니다.)

0x34 오프셋 이후 정보를 SRVOPENECPCONTEXT 로 확인해보면 요렇습니다.

1: kd> !knext.dt _SRV_OPEN_ECP_CONTEXT (a2a28370+34)
_SRV_OPEN_ECP_CONTEXT
+0x000 ShareName : 0x94865c30 _UNICODE_STRING "share"
+0x004 SocketAddress : 0x859a0240 sockaddr_storage
+0x008 OplockBlockState : 0 ''
+0x009 OplockAppState : 0x1 ''
+0x00a OplockFinalState : 0 ''

공유 이름인 share가 잘 보이네요^^

이름은 이미 위에서 확인해서 알고 있었고...

두 번째 필드인 SocketAddress 정보가 우리가 찾는 녀석입니다!!!

(참고로 sockaddr_storage 는 프로토콜에 따라 변환해서 사용해야 하는 녀석입니다.)

먼저 SocketAddress 정보를 sockaddr 로 변환해서 확인해봅니다.

1: kd> !knext.dt sockaddr 0x859a0240
sockaddr
+0x000 sa_family : 2
+0x002 sa_data : [14] "???"

safamily 가 2 이므로, AFINET 즉 IPv4 를 의미하고 있습니다.

그럼 SOCKADDR_IN 정보로 확인하면 되겠죠?

1: kd> !knext.dt sockaddr_in 0x859a0240
sockaddr_in
+0x000 sin_family : 2
+0x002 sin_port : 0x65c5
+0x004 sin_addr : in_addr
+0x008 sin_zero : [8] ""

우선 port 부터 확인해볼까요? 바이트 오더를 주의해서 변환해보면...

1: kd> ? c565
Evaluate expression: 50533 = 0000c565

50533 포트에서 접근했습니다^^

그럼 IP를 확인해볼까요. sin_addr 정보를 보면 되겠죠?

1: kd> db 0x859a0240 + 4 L4
859a0244 c0 a8 3a 01

10 진수로 변환해봅시다.

1: kd> ? c0
Evaluate expression: 192 = 000000c0

1: kd> ? a8
Evaluate expression: 168 = 000000a8

1: kd> ? 3a
Evaluate expression: 58 = 0000003a

1: kd> ? 1
Evaluate expression: 1 = 00000001

찾았습니다~~!

192.168.58.1 IP 에서 접근하고 있네요~~!

지금까지 확인한 정보들을 조합해보면,

192.168.58.1:50533 에서 \share\desktop.ini 파일에 접근한 것을 알 수 있습니다^^

다음에는 (언제가 될지 모르지만.. ) ECP 에 관한 두 번째 이야기이면서 중요한 이야기인 oplock 에 대해 끄적여보겠습니다.

그럼 즐프하세요 :)

Written on March 16, 2016