[Case Study2] 프로세스 정보 추출에 유용한 ‘2가지’ 방법은?
메모리 덤프에서 프로세스 정보 추출 방법
1부에서 HBGary Responder와 Volatility Framework라는 분석 도구를 사용하여 나타나는 결과를 바탕으로 분석을 수행하였다. 툴을 이용하여 분석하는 경우, 툴이 어떠한 원리로 결과를 표시했는지 이해하지 못한다면 결과에 대해서 객관성을 보장할 수 없다. 프로세스 목록을 추출하는 데에는 여러 방법이 존재하지만, 효과적인 두 가지 방법에 대해서 알아보자.
1. 프로세스 카빙(Process Carving)
윈도우에서 프로세스와 관련된 정보는 EPROCESS라는 구조체를 사용하여 관리한다. 메모리 덤프에서 이 EPROCESS 구조체와 EPROCESS 구조체 앞에 나오는 헤더 정보를 추출하여, 프로세스에 대한 구조체가 맞는지 여부를 판단한다. 프로세스에 대한 구조체가 맞는 경우 이 구조체는 EPROCESS 구조체로, 자세한 내용은 아래 예제에서 디버거를 통해 확인해보자. 이 작업을 통해 공격자가 수행했던 프로세스에 대한 자세한 정보를 알아낼 수 있다.
[그림 1] 프로세스 검증 확인 내역
메모리 덤프에서 프로세스 관련 정보를 뽑아내기 위해서는 총 3단계의 검증 과정을 거친다. [그림 1]에서 나열한 3개의 항목(POOL_HEADER, OBJECT_HEADER, EPROCESS 중 KPROCESS)에서 각각 값들을 확인한다. 이 검증 과정에 대해 다루기 전에 먼저 EPROCESS 구조체에 대해서 알아보고, EPROCESS의 시작 부분인 KPROCESS에 대해 다루면서 프로세스를 나타내는 구조체를 추출하기 위해서 어떤 조건을 검사하는지 알아보겠다.
[표 1]은 WinDBG를 이용하여 확인한 Windows XP SP3에서의 EPROCESS 구조체 내용이다. 각각의 프로세스는 EPROCESS 구조체에 직접 값을 저장하거나, 다른 객체로의 포인터를 저장하고 있다.
|
0: kd> dt nt!_EPROCESS +0x000 Pcb : _KPROCESS +0x06c ProcessLock : _EX_PUSH_LOCK +0x070 CreateTime : _LARGE_INTEGER +0x078 ExitTime : _LARGE_INTEGER +0x080 RundownProtect : _EX_RUNDOWN_REF +0x084 UniqueProcessId : Ptr32 Void +0x088 ActiveProcessLinks : _LIST_ENTRY +0x090 QuotaUsage : [3] Uint4B +0x09c QuotaPeak : [3] Uint4B +0x0a8 CommitCharge : Uint4B +0x0ac PeakVirtualSize : Uint4B +0x0b0 VirtualSize : Uint4B +0x0b4 SessionProcessLinks : _LIST_ENTRY +0x0bc DebugPort : Ptr32 Void +0x0c0 ExceptionPort : Ptr32 Void +0x0c4 ObjectTable : Ptr32 _HANDLE_TABLE +0x0c8 Token : _EX_FAST_REF +0x0cc WorkingSetLock : _FAST_MUTEX +0x0ec WorkingSetPage : Uint4B +0x0f0 AddressCreationLock : _FAST_MUTEX +0x110 HyperSpaceLock : Uint4B +0x114 ForkInProgress : Ptr32 _ETHREAD +0x118 HardwareTrigger : Uint4B +0x11c VadRoot : Ptr32 Void +0x120 VadHint : Ptr32 Void +0x124 CloneRoot : Ptr32 Void +0x128 NumberOfPrivatePages : Uint4B +0x12c NumberOfLockedPages : Uint4B +0x130 Win32Process : Ptr32 Void +0x134 Job : Ptr32 _EJOB +0x138 SectionObject : Ptr32 Void +0x13c SectionBaseAddress : Ptr32 Void +0x140 QuotaBlock : Ptr32 _EPROCESS_QUOTA_BLOCK +0x144 WorkingSetWatch : Ptr32 _PAGEFAULT_HISTORY +0x148 Win32WindowStation : Ptr32 Void +0x14c InheritedFromUniqueProcessId : Ptr32 Void +0x150 LdtInformation : Ptr32 Void +0x154 VadFreeHint : Ptr32 Void +0x158 VdmObjects : Ptr32 Void +0x15c DeviceMap : Ptr32 Void +0x160 PhysicalVadList : _LIST_ENTRY +0x168 PageDirectoryPte : _HARDWARE_PTE +0x168 Filler : Uint8B +0x170 Session : Ptr32 Void +0x174 ImageFileName : [16] UChar +0x184 JobLinks : _LIST_ENTRY +0x18c LockedPagesList : Ptr32 Void +0x190 ThreadListHead : _LIST_ENTRY +0x198 SecurityPort : Ptr32 Void +0x19c PaeTop : Ptr32 Void +0x1a0 ActiveThreads : Uint4B +0x1a4 GrantedAccess : Uint4B +0x1a8 DefaultHardErrorProcessing : Uint4B +0x1ac LastThreadExitStatus : Int4B +0x1b0 Peb : Ptr32 _PEB +0x1b4 PrefetchTrace : _EX_FAST_REF +0x1b8 ReadOperationCount : _LARGE_INTEGER +0x1c0 WriteOperationCount : _LARGE_INTEGER +0x1c8 OtherOperationCount : _LARGE_INTEGER +0x1d0 ReadTransferCount : _LARGE_INTEGER +0x1d8 WriteTransferCount : _LARGE_INTEGER +0x1e0 OtherTransferCount : _LARGE_INTEGER +0x1e8 CommitChargeLimit : Uint4B +0x1ec CommitChargePeak : Uint4B +0x1f0 AweInfo : Ptr32 Void +0x1f4 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO +0x1f8 Vm : _MMSUPPORT +0x238 LastFaultCount : Uint4B +0x23c ModifiedPageCount : Uint4B +0x240 NumberOfVads : Uint4B +0x244 JobStatus : Uint4B +0x248 Flags : Uint4B +0x248 CreateReported : Pos 0, 1 Bit +0x248 NoDebugInherit : Pos 1, 1 Bit +0x248 ProcessExiting : Pos 2, 1 Bit +0x248 ProcessDelete : Pos 3, 1 Bit +0x248 Wow64SplitPages : Pos 4, 1 Bit +0x248 VmDeleted : Pos 5, 1 Bit +0x248 OutswapEnabled : Pos 6, 1 Bit +0x248 Outswapped : Pos 7, 1 Bit +0x248 ForkFailed : Pos 8, 1 Bit +0x248 HasPhysicalVad : Pos 9, 1 Bit +0x248 AddressSpaceInitialized : Pos 10, 2 Bits +0x248 SetTimerResolution : Pos 12, 1 Bit +0x248 BreakOnTermination : Pos 13, 1 Bit +0x248 SessionCreationUnderway : Pos 14, 1 Bit +0x248 WriteWatch : Pos 15, 1 Bit +0x248 ProcessInSession : Pos 16, 1 Bit +0x248 OverrideAddressSpace : Pos 17, 1 Bit +0x248 HasAddressSpace : Pos 18, 1 Bit +0x248 LaunchPrefetched : Pos 19, 1 Bit +0x248 InjectInpageErrors : Pos 20, 1 Bit +0x248 VmTopDown : Pos 21, 1 Bit +0x248 Unused3 : Pos 22, 1 Bit +0x248 Unused4 : Pos 23, 1 Bit +0x248 VdmAllowed : Pos 24, 1 Bit +0x248 Unused : Pos 25, 5 Bits +0x248 Unused1 : Pos 30, 1 Bit +0x248 Unused2 : Pos 31, 1 Bit +0x24c ExitStatus : Int4B +0x250 NextPageColor : Uint2B +0x252 SubSystemMinorVersion : UChar +0x253 SubSystemMajorVersion : UChar +0x252 SubSystemVersion : Uint2B +0x254 PriorityClass : UChar +0x255 WorkingSetAcquiredUnsafe : UChar +0x258 Cookie : Uint4B |
[표 1]의 항목 중에서 볼드(Bold)체로 표시한 항목에 대해서 자세히 살펴보자. 첫 번째로 나오는 Pcb는 프로세스 컨트롤 블록(Process Control Block)으로 스케줄링 관련 정보를 가지고 있으며, 메모리 덤프에서 프로세스 정보를 추출하는데 많이 사용된다. 바로 이 PCB가 KPROCESS라는 구조체로 되어 있으며, 검증 과정에서 KPROCESS의 Type 값과 Size 값을 확인한다.
두 번째, 세 번째 볼드체로 표시된 CreateTime과 ExitTime은 각각 프로세스가 실행되고, 종료된 시간을 나타낸다. UniqueProcessId는 프로세스를 식별하는 고유한 PID 값을 저장하고 있다. ActiveProcessLinks는 이중 환형 링크드 리스트로 두 개의 값이 각각 이전 프로세스와 이후 프로세스를 가리키고 있으며, 이 구조체를 사용하여 메모리 덤프에서 프로세스 내역 추출이 가능하다.
이어 PEB는 메모리에 올라가 있는 모듈 및 실행했을 때의 커맨드 명령어 등 프로세스와 관련된 상세한 정보를 담고 있다. 이 PEB(Process Environment Block)는 침해사고 조사시 필요한 내역 들을 담고 있다. EPROCESS 구조체에서는 PEB의 모든 내용을 저장하지 않고, 사용자 영역에 있는 PEB에 대한 포인터만 저장되어 있다. [표 2]는 이 PEB의 내용을 출력한 것이다.
|
0: kd> !PEB PEB at 7ffde000 InheritedAddressSpace: No ReadImageFileExecOptions: No BeingDebugged: No ImageBaseAddress: 01000000 Ldr 00191e90 Ldr.Initialized: Yes Ldr.InInitializationOrderModuleList: 00191f28 . 00193048 Ldr.InLoadOrderModuleList: 00191ec0 . 00193038 Ldr.InMemoryOrderModuleList: 00191ec8 . 00193040 Base TimeStamp Module 1000000 49a5f6a7 Feb 26 10:55:51 2009 C:\program files\Debugging Tools for Windows (x86)\windbg.exe 7c930000 4802c0ce Apr 14 11:26:22 2008 C:\WINDOWS\system32\ntdll.dll 77e20000 4802c07d Apr 14 11:25:01 2008 C:\WINDOWS\system32\GDI32.dll ---- < 중 략 > ---- 76f60000 4802c0cb Apr 14 11:26:19 2008 C:\WINDOWS\System32\winrnr.dll 76f10000 4802c0da Apr 14 11:26:34 2008 C:\WINDOWS\system32\WLDAP32.dll SubSystemData: 00000000 ProcessHeap: 00090000 ProcessParameters: 00020000 WindowTitle: 'livekd.exe -w' ImageFile: 'C:\program files\Debugging Tools for Windows (x86)\windbg.exe' CommandLine: 'windbg.exe -z C:\WINDOWS\livekd.dmp' DllPath: 'C:\program files\Debugging Tools for Windows (x86);C:\WINDOWS\system32;C:\WINDOWS\system;C:\WINDOWS;.;C:\WINDOWS\system32; C:\WINDOWS;C:\WINDOWS\System32\Wbem' Environment: 00010000 =::=::\ =C:=C:\LiveKd =ExitCode=00000000 ALLUSERSPROFILE=C:\Documents and Settings\All Users APPDATA=C:\Documents and Settings\Administrator\Application Data CLIENTNAME=Console CommonProgramFiles=C:\Program Files\Common Files COMPUTERNAME=KARA-FE21EACFF3 ComSpec=C:\WINDOWS\system32\cmd.exe DBGENG_NO_BUGCHECK_ANALYSIS=1 FP_NO_HOST_CHECK=NO HOMEDRIVE=C: HOMEPATH=\Documents and Settings\Administrator LOGONSERVER=\\KARA-FE21EACFF3 NUMBER_OF_PROCESSORS=2 OS=Windows_NT Path=C:\program files\Debugging Tools for Windows (x86)\winext\arcade;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH PROCESSOR_ARCHITECTURE=x86 PROCESSOR_IDENTIFIER=x86 Family 6 Model 23 Stepping 6, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=1706 ProgramFiles=C:\Program Files PROMPT=$P$G SESSIONNAME=Console SystemDrive=C: SystemRoot=C:\WINDOWS TEMP=C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp TMP=C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp USERDOMAIN=KARA-FE21EACFF3 USERNAME=Administrator USERPROFILE=C:\Documents and Settings\Administrator WINDBG_DIR=C:\program files\Debugging Tools for Windows (x86) windir=C:\WINDOWS _NT_SYMBOL_PATH=srv*c:\Symbols*http://msdl.microsoft.com/download/symbols |
PEB의 내용은 직관적으로 확인이 가능하다. 우선 로드된 DLL 파일들의 리스트가 표시되어 악성 DLL이 포함되었는지 확인할 수 있다. 또한 윈도우 이름, 이미지 파일의 경로, 커맨드 라인 명령어 및 사용자 환경 변수들을 확인할 수 있다. 이 내용들을 사용하여 프로세스에 대한 상세 정보 확인이 가능하다.
EPROCESS 구조체에서 확인 가능한 정보에 대해서 살펴보았고, 이제는 실제로 어떻게 메모리 덤프에서 특정 부분이 프로세스와 관련된 정보라는 것을 검증하는지에 대해서 살펴보겠다. PCB는 앞서 다뤘듯이 EPROCESS에서 가장 먼저 나오는 값으로, KPROCESS라는 구조체를 사용하여 저장되고 있으며 [표 3]의 값들로 구성된다.
|
0: kd> dt nt!_KPROCESS +0x000 Header : _DISPATCHER_HEADER +0x010 ProfileListHead : _LIST_ENTRY +0x018 DirectoryTableBase : [2] Uint4B +0x020 LdtDescriptor : _KGDTENTRY +0x028 Int21Descriptor : _KIDTENTRY +0x030 IopmOffset : Uint2B +0x032 Iopl : UChar +0x033 Unused : UChar +0x034 ActiveProcessors : Uint4B +0x038 KernelTime : Uint4B +0x03c UserTime : Uint4B +0x040 ReadyListHead : _LIST_ENTRY +0x048 SwapListEntry : _SINGLE_LIST_ENTRY +0x04c VdmTrapcHandler : Ptr32 Void +0x050 ThreadListHead : _LIST_ENTRY +0x058 ProcessLock : Uint4B +0x05c Affinity : Uint4B +0x060 StackCount : Uint2B +0x062 BasePriority : Char +0x063 ThreadQuantum : Char +0x064 AutoAlignment : UChar +0x065 State : UChar +0x066 ThreadSeed : UChar +0x067 DisableBoost : UChar +0x068 PowerState : UChar +0x069 DisableQuantum : UChar +0x06a IdealNode : UChar +0x06b Flags : _KEXECUTE_OPTIONS +0x06b ExecuteOptions : UChar |
KPROCESS의 시작 부분은 DISPATCHER_HEADER로 시작하는데 바로 이 내용을 이용하여 프로세스를 나타내는 구조체가 맞는지 검증하게 된다. [표 4]는 실행 중인 프로세스의 DISPATCHER_HEADER를 출력한 것으로 이중 Type 값이 0x3 이며, Size 값이 0x1b인 경우, 해당 객체가 프로세스 관련 구조체가 맞다고 판단하게 된다.
|
0: kd> dt _DISPATCHER_HEADER 82283370 ntdll!_DISPATCHER_HEADER +0x000 Type : 0x3 '' +0x001 Absolute : 0 '' +0x002 Size : 0x1b '' +0x003 Inserted : 0 '' +0x004 SignalState : 0 +0x008 WaitListHead : _LIST_ENTRY [ 0x82283378 - 0x82283378 ] |
[표 4]의 조건이 메모리 덤프에서 EPROCESS 구조체를 찾아낼 때 확인하는 항목이며, 그 외 나머지 두 조건을 먼저 만족해야 한다. 이 내용은 [그림 1]에서 EPROCESS 구조체 앞에 있는 두 개의 헤더와 관련이 있다. 이 헤더들은 EPROCESS 구조체 앞에 나오는 POOL_HEADER와 OBJECT_HEADER이며, 각각 8Byte와 24Byte 값을 가지고 있다.
현재 실행 중인 메모장(notepad.exe)을 사용하여 이 조건을 확인하는 방법에 대해서 살펴보자. [표 5]에서 볼 수 있듯이 현재 메모장을 나타내는 EPROCESS 구조체는 82283370위치에 존재하고 있다.
|
PROCESS 82283370 SessionId: 0 Cid: 0574 Peb: 7ffde000 ParentCid: 0638 DirBase: 02b403a0 ObjectTable: e1624420 HandleCount: 56. Image: notepad.exe |
해당 객체의 정보를 출력한 결과는 [표 6]과 같다. 즉, Process인 경우 825b7e70 값을 가지고 있으며, 이 값은 PsProcesssType이라는 값과 동일하다.
|
0: kd> !object 82283370 Object: 82283370 Type: (825b7e70) Process ObjectHeader: 82283358 (old version) HandleCount: 2 PointerCount: 17 0: kd> dd PsProcessType L1 805659b8 825b7e70 |
OBJECT_HEADER가 Type 필드를 가지고 있는 경우 오브젝트에 대한 일반적인 속성을 담고 있다. OBJECT_HEADER 뒤에 EPROCESS 구조체가 나오는 경우 이 Type 필드의 값은 PsProcessType(825b7e70) 값과 같거나 0xbad040이라는 값을 갖게 된다. 이 OBJECT_HEADER의 Type 값 확인이 메모리에서 프로세스 정보임을 검증하는 두 번째 항목이다.
이제, 마지막 조건인 POOL_HEADER 검증 과정에 대해 살펴보자. 앞선 예제에서 EPROCESS 구조체가 앞서 82283370 번지에 위치함을 확인했다. 또한 POOL_HEADER는 [그림 1]에서 확인할 수 있듯이, EPROCESS 구조체로부터 32 Byte(0x18 + 0x8)만큼 떨어져 있어 [표 7]의 명령어를 통해 해당 프로세스의 POOL_HEADER 정보를 확인할 수 있다.
|
0: kd> dt _POOL_HEADER 82283370-0x18-0x8 nt!_POOL_HEADER +0x000 PreviousSize : 0y000000001 (0x1) +0x000 PoolIndex : 0y0000000 (0) +0x002 BlockSize : 0y001010000 (0x50) +0x002 PoolType : 0y0000101 (0x5) +0x000 Ulong1 : 0xa500001 +0x004 ProcessBilled : 0xe36f7250 _EPROCESS +0x004 PoolTag : 0xe36f7250 +0x004 AllocatorBackTraceIndex : 0x7250 +0x006 PoolTagHash : 0xe36f |
POOL_HEADER에서는 PoolTag 값을 확인 하는 데, 뒤에 따라오는 내용이 프로세스와 쓰레드인 경우 각각 0xe36f7250, 0xe5726854값을 가지게 된다. PoolTag 값이 0xe36f7250인지 확인하는 것이 메모리 덤프에서 프로세스를 추출할 때 확인하는 마지막 조건이다.
위의 예제에서는 디버거를 이용하여 각 조건을 확인하느라, KPROCESS, OBJECT_HEADER, POOL_HEADER 순으로 확인하였다. 실제로 메모리 덤프 파일에서 프로세스를 추출하기 위해서는 반대의 순서로 확인하게 된다. 다시 한번 정리해 보면, 메모리 덤프에서 프로세스 관련 정보를 추출할 때 다음의 세 가지 조건을 확인하게 된다.
POOL_HEADER의 PoolTag 값이 36f7250
OBJECT_HEADER의 Type 값이 PsProcessType(825b7e70) 또는 0xbad040
EPROCESS의 PCB(KPROCESS)에서 Type값이 0x3, Size값이 0x1b
해당 내역을 메모리에서 모두 추출하여 나열하는 경우 루트킷이나 DKOM(Direct Kernel Object Manipulation) 등에 의해 감춰진 프로세스 정보도 모두 출력이 가능하다.
2. ActiveProcessLink를 이용한 프로세스 리스팅
메모리에서 프로세스 정보를 추출하는 방법은 여러 가지가 존재한다. 그 방법 중에 가장 기본적으로 사용되고 쉽게 찾을 수 있는 방법을 소개하고자 한다. 이 방법은 EPROCESS의 값인 ActiveProcessLinks를 사용한 방법으로, 이중 링크드 리스트로 구성되어 있다.
[그림 2] ActiveProcessLink 구조
ActiveProcessLink는 FLink라는 값과 BLink라는 값을 가지고 있다. ActiveProcesssLink의 Flink값은 바로 다음 프로세스의 ActiveProcessLink를 가리키며, Blink 값은 이전 프로세스의 ActiveProcessLink의 시작점을 가리킨다. PSActiveProcessHead라는 전역 변수가 존재하여 FLink, Blink 값을 가지고 있으며, 이 변수로 인해 환형 구조가 완성된다. 최초 PSActiveProcessHead의 FLink가 가리키는 값을 따라가면, 바로 첫 번째 프로세스의 PSActiveProcessLink의 시점에 접근할 수 있다. PSActiveProcessHead의 FLink는 항상 첫 번째 프로세스를 가리키고 있으며, 모든 시스템에서 이 첫 번째 프로세스는 시스템(System) 프로세스이다.
그럼 이제 실제 시스템에서 어떻게 해당 링크드 리스트를 따라가 볼 수 있는지 알아보자.
우선 시스템에서 실행 중인 모든 프로세스 목록을 보기 위해, !process 0 0 명령어를 입력한다. 앞서 언급했듯이 첫 번째 프로세스가 시스템인 것을 확인할 수 있다. 이번 예제에서는 ActiveProcessLink의 값을 이용하여 cmd.exe 이전 프로세스가 무엇인지 찾아보자.
|
0: kd> !process 0 0 **** NT ACTIVE PROCESS DUMP **** PROCESS 825b7830 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000 DirBase: 02b40020 ObjectTable: e1002e40 HandleCount: 264. Image: System PROCESS 82429948 SessionId: none Cid: 0220 Peb: 7ffd8000 ParentCid: 0004 DirBase: 02b40040 ObjectTable: e14558d8 HandleCount: 19. Image: smss.exe PROCESS 82446da0 SessionId: 0 Cid: 0260 Peb: 7ffd7000 ParentCid: 0220 DirBase: 02b40060 ObjectTable: e15e3858 HandleCount: 378. Image: csrss.exe PROCESS 82344020 SessionId: 0 Cid: 0278 Peb: 7ffd8000 ParentCid: 0220 DirBase: 02b40080 ObjectTable: e14f2590 HandleCount: 460. Image: winlogon.exe PROCESS 81f035d0 SessionId: 0 Cid: 0194 Peb: 7ffde000 ParentCid: 02a4 DirBase: 02b40320 ObjectTable: e1981680 HandleCount: 140. Image: AYServiceNT.aye PROCESS 8238ea00 SessionId: 0 Cid: 0640 Peb: 7ffda000 ParentCid: 0638 DirBase: 02b40300 ObjectTable: e16626c0 HandleCount: 33. Image: cmd.exe PROCESS 821dc020 SessionId: 0 Cid: 048c Peb: 7ffde000 ParentCid: 0640 DirBase: 02b40340 ObjectTable: e25c5bb8 HandleCount: 33. Image: livekd.exe PROCESS 820c8120 SessionId: 0 Cid: 0770 Peb: 7ffd6000 ParentCid: 048c DirBase: 02b40240 ObjectTable: e2249ee8 HandleCount: 197. Image: windbg.exe |
[표 8]에서 cmd.exe가 실행 중인 프로세스는 8238ea00 번지에 EPROCESS 정보가 저장되어 있음을 확인할 수 있다. 해당 EPROCESS는 [표 9]의 내용을 담고 있으며, 이전 프로세스를 찾기 위해 확인해야 할 정보는 바로 ActiveProcessLinks에 저장된 값이다. 내용을 살펴보면 다음 프로세스가 0x821dc0a8, 이전 프로세스가 0x81f03658에 저장되어 있음을 알 수 있다. 하지만 이 값들은 각 프로세스의 EPROCESS를 가리키고 있는 것이 아니고, EPROCESS로부터 0x088 Byte만큼 떨어져 있는 ActiveProcessLink의 시작점을 가리키고 있다.
|
0: kd> dt _EPROCESS 8238ea00 ntdll!_EPROCESS +0x000 Pcb : _KPROCESS +0x06c ProcessLock : _EX_PUSH_LOCK +0x070 CreateTime : _LARGE_INTEGER 0x1cbdf8f`90aaef70 +0x078 ExitTime : _LARGE_INTEGER 0x0 +0x080 RundownProtect : _EX_RUNDOWN_REF +0x084 UniqueProcessId : 0x00000640 +0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x821dc0a8 - 0x81f03658 ] +0x090 QuotaUsage : [3] 0x988 +0x09c QuotaPeak : [3] 0xd60 ---- 이하 생략 ---- |
[표 9] 8238ea00 번지의 EPROCESS 정보
따라서, 이전 프로세스의 정보를 출력하기 위해서는 EPROCESS 구조체의 시작점을 확인해야 하므로, 앞의 예제에서 확인한 BLink의 값보다 0x88 byte만큼 이전을 확인해야 한다.
|
0: kd> dt _EPROCESS 81f03658-0x88 ntdll!_EPROCESS +0x000 Pcb : _KPROCESS +0x06c ProcessLock : _EX_PUSH_LOCK +0x070 CreateTime : _LARGE_INTEGER 0x1cbdf8b`7ad92d14 +0x078 ExitTime : _LARGE_INTEGER 0x0 +0x080 RundownProtect : _EX_RUNDOWN_REF +0x084 UniqueProcessId : 0x00000194 +0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x8238ea88 - 0x82366280 ] +0x090 QuotaUsage : [3] 0x3a20 ---- 중략 ---- +0x170 Session : 0xf8bb6000 +0x174 ImageFileName : [16] "AYServiceNT.aye" +0x184 JobLinks : _LIST_ENTRY [ 0x0 - 0x0 ] |
[표 10] 0x81f03658보다 0x88 byte만큼 이전 프로세스 찾기 예제
명령어 실행 결과, 해당 프로세스의 이름은 AYServiceNT.aye이고, ActiveProcessLink의 Flink는 앞서 예제에서 확인한 cmd.exe의 ActiveProcessLink의 시작점인 0x8238ea88을 가리키고 있는 것을 확인할 수 있다.
메모리 덤프에서 한 개의 프로세스를 찾는다면, 이 ActiveProcessLink의 값을 확인하면서 연결된 모든 프로세스의 정보를 획득할 수 있다. 하지만, 이 방법으로는 DKOM으로 숨겨진 프로세스는 찾지 못하는데, 이는 숨기고자 하는 프로세스의 앞, 뒤 프로세스의 FLink, Blink를 조작하여 ActiveProcessLink를 사용하여 따라가지 못하도록 조작하기 문이다. 이러한 문제를 피하기 위해 앞에서 알아본 POOL_HEADER의 pooltag 및 검증 과정을 이용하여 메모리 상에 존재하는 모든 프로세스를 추출함으로써 이러한 문제를 피할 수 있다.
지금까지 총 2부에 걸쳐 메모리 분석을 통한 침해 사고 대응에 대해 소개했다. 이 글이 침해 사고 분석에 관심 있는 분들에게 도움이 될 수 있기를 기대해본다@
- 안철수연구소안철수연구소 CERT팀 주임 김창엽