지금껏 시스템 프로그래밍을 많이 해왔지만 프로세스를 찾는 프로그래밍은.. 안해본것 같군요.
기껏해봐야
CreateToolhelp32Snapshot()
Process32First()
Process32Next()
세 함수를 사용한 유저모드에서 프로세스 찾는...;;
커널에서도 찾는 방법이 여러가지가 있을 껀데
ZwQuerySystemInformation함수를 사용하는 방법..
또한 System 프로세스의 EPROCESS 구조체 주소를 구해 링크드 리스트로 연결된 EPROCESS를 쭉 따라가면서 찾는 방법..
등등 여러가지가 있었습니다. 하지만 API함수를 사용하는 방법은 간단히 후킹해 내 프로세스 정보를 없애버리면 되는것이고
EPROCESS 구조체를 쭉 따라가면서 탐지하는 방법은 내 프로세스의 링크를 끊어버리면 탐지가 어렵게 되는.. ;;
그래서 루트킷 책을 보니깐 nt!SwapContext 함수를 후킹해 탐지하는 방법이 있더군요.
해당 함수는 현재 실행 중인 스레드의 컨텍스트가 바로 다음에 실행될 스레드의 컨텍스트로 스왑될 때 호출되는 함수입니다. 고로.... IRQL은..?! PASSIVE_LEVEL이 아닌 DISPATCH_LEVEL에서 이루어지겠죠. 무튼무튼
해당 함수에 들어오게되면
EDI 레지스터는 다음에 실행될 스레드(ETHREAD)를 가리키며 ESI는 현재 실행 중이며 곧 스왑될 스레드(ETHREAD)를 가리킵니다. 실제로 그런지 봐볼까요?
아 ~ 그리고 이 모든 테스트는 Windows XP SP3에서 테스트 됬습니다. 그렇다고 Windows 7이라고 뭐 다를건 없습니다. -_-;
현재 SwapContext 함수 처음 부분에 멈춰있는 상태입니다. EDI 레지스터를 확인하고 ETHREAD 구조체에 맵핑 시켜보죠.
저기서
+0x220 ThreadsProcess : 0x825b9830 _EPROCESS
이 부분이 바로 해당 스레드를 가지고 있는 프로세스의 EPROCESS 구조체 주소 정보가 있는 곳입니다.
해당 EPROCESS를 따라가보죠.
이부분
+0x174 ImageFileName : [16] "System"
이 부분에 바로 해당 프로세스의 이름이 담겨 있는 곳입니다.
이 부분만 후킹해서 슬쩍슬쩍 빼오면 되는 것이지요. EDI, ESI 두 레지스터에서 전부 말이죠.
방법은 간단합니다.
끝입니다. 구현하면서 중요시 해야될 부분은 보통 API 함수 후킹할 때는
후킹 -> My 함수 호출되면 -> 인자값 조작 -> 언후킹 -> 오리지널 함수 호출 -> 후킹 -> 리턴 값 조작....
이런식으로 진행이 되는데 이 함수는 그렇게 하면 안됩니다.
일단 문서화된 함수가 아니고.. 찾아보니까 함수의 원형을 도저히 모르겠더군요. 또한 이건 함수라고 하기도 뭐한게 [ebp+8].. 이런식으로 인자 값을 가져와서 하는게 아닌 단순 EDI, ESI 에 있는 값을 모니터링 하기 때문이죠. 일반적인 API함수가 아닌..
또한 안다 하더라도 위와같은 방식을 사용하면 스레드가 스왑될 때마다 언후킹, 후킹을 하게 되는데 이러면 상당히 오버헤드가 발생하게 됩니다. Windows 같은 경우 하나의 스레드가 사용할 수 있는 퀀텀을 다 사용하면서 스왑된다 하더라도 스왑되는 주기가 매우 짧기 때문이죠. 고로 이런 방식으로 진행됩니다.
후킹 -> (naked)My 함수 호출 -> EDI, ESI로부터 프로세스 이름 얻음(함수로 처리) -> nt!SwapContext 함수로 점프
이런식입니다. 즉 언후킹은 해당 후킹 드라이버가 언로드 될 때만 해주는 것입니다.
그리고 언후킹을 하지 않기 때문에 저 같은 경우는 아래와 같이 구현했습니다;;
Windows XP SP3 SwapContext 처음 부분
My 함수 끝 부분
or cl,cl
기껏해봐야
CreateToolhelp32Snapshot()
Process32First()
Process32Next()
세 함수를 사용한 유저모드에서 프로세스 찾는...;;
커널에서도 찾는 방법이 여러가지가 있을 껀데
ZwQuerySystemInformation함수를 사용하는 방법..
또한 System 프로세스의 EPROCESS 구조체 주소를 구해 링크드 리스트로 연결된 EPROCESS를 쭉 따라가면서 찾는 방법..
등등 여러가지가 있었습니다. 하지만 API함수를 사용하는 방법은 간단히 후킹해 내 프로세스 정보를 없애버리면 되는것이고
EPROCESS 구조체를 쭉 따라가면서 탐지하는 방법은 내 프로세스의 링크를 끊어버리면 탐지가 어렵게 되는.. ;;
그래서 루트킷 책을 보니깐 nt!SwapContext 함수를 후킹해 탐지하는 방법이 있더군요.
해당 함수는 현재 실행 중인 스레드의 컨텍스트가 바로 다음에 실행될 스레드의 컨텍스트로 스왑될 때 호출되는 함수입니다. 고로.... IRQL은..?! PASSIVE_LEVEL이 아닌 DISPATCH_LEVEL에서 이루어지겠죠. 무튼무튼
해당 함수에 들어오게되면
EDI 레지스터는 다음에 실행될 스레드(ETHREAD)를 가리키며 ESI는 현재 실행 중이며 곧 스왑될 스레드(ETHREAD)를 가리킵니다. 실제로 그런지 봐볼까요?
아 ~ 그리고 이 모든 테스트는 Windows XP SP3에서 테스트 됬습니다. 그렇다고 Windows 7이라고 뭐 다를건 없습니다. -_-;
kd> ln nt!SwapContext
(805438e0) nt!SwapContext | (80543a38) nt!KeInterlockedSwapPte
Exact matches:
nt!SwapContext = <no type information>
kd> bp 805438e0
kd> g
Breakpoint 0 hit
nt!SwapContext:
805438e0 0ac9 or cl,cl
현재 SwapContext 함수 처음 부분에 멈춰있는 상태입니다. EDI 레지스터를 확인하고 ETHREAD 구조체에 맵핑 시켜보죠.
kd> r
eax=00001000 ebx=ffdff000 ecx=00000001 edx=80555880 esi=825b2b30 edi=825b6020
eip=805438e0 esp=f8afdce0 ebp=f8afdcf4 iopl=0 nv up ei pl nz na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000206
nt!SwapContext:
805438e0 0ac9 or cl,cl
kd> dt _ETHREAD 825b6020
ntdll!_ETHREAD
+0x000 Tcb : _KTHREAD
+0x1c0 CreateTime : _LARGE_INTEGER 0xe6654fd`d4418200
+0x1c0 NestedFaultCount : 0y00
+0x1c0 ApcNeeded : 0y0
+0x1c8 ExitTime : _LARGE_INTEGER 0x825b61e8`825b61e8
+0x1c8 LpcReplyChain : _LIST_ENTRY [ 0x825b61e8 - 0x825b61e8 ]
+0x1c8 KeyedWaitChain : _LIST_ENTRY [ 0x825b61e8 - 0x825b61e8 ]
+0x1d0 ExitStatus : 0n0
+0x1d0 OfsChain : (null)
+0x1d4 PostBlockList : _LIST_ENTRY [ 0x825b61f4 - 0x825b61f4 ]
+0x1dc TerminationPort : (null)
+0x1dc ReaperLink : (null)
+0x1dc KeyedWaitValue : (null)
+0x1e0 ActiveTimerListLock : 0
+0x1e4 ActiveTimerListHead : _LIST_ENTRY [ 0x825b6204 - 0x825b6204 ]
+0x1ec Cid : _CLIENT_ID
+0x1f4 LpcReplySemaphore : _KSEMAPHORE
+0x1f4 KeyedWaitSemaphore : _KSEMAPHORE
+0x208 LpcReplyMessage : (null)
+0x208 LpcWaitingOnPort : (null)
+0x20c ImpersonationInfo : 0xe163d390 _PS_IMPERSONATION_INFORMATION
+0x210 IrpList : _LIST_ENTRY [ 0x824541e8 - 0x824541e8 ]
+0x218 TopLevelIrp : 0
+0x21c DeviceToVerify : (null)
+0x220 ThreadsProcess : 0x825b9830 _EPROCESS
+0x224 StartAddress : 0x80536b02 Void
+0x228 Win32StartAddress : (null)
+0x228 LpcReceivedMessageId : 0
+0x22c ThreadListEntry : _LIST_ENTRY [ 0x825b6fd4 - 0x825b75f4 ]
+0x234 RundownProtect : _EX_RUNDOWN_REF
+0x238 ThreadLock : _EX_PUSH_LOCK
+0x23c LpcReplyMessageId : 0
+0x240 ReadClusterSize : 7
+0x244 GrantedAccess : 0x1f03ff
+0x248 CrossThreadFlags : 0x10
+0x248 Terminated : 0y0
+0x248 DeadThread : 0y0
+0x248 HideFromDebugger : 0y0
+0x248 ActiveImpersonationInfo : 0y0
+0x248 SystemThread : 0y1
+0x248 HardErrorsAreDisabled : 0y0
+0x248 BreakOnTermination : 0y0
+0x248 SkipCreationMsg : 0y0
+0x248 SkipTerminationMsg : 0y0
+0x24c SameThreadPassiveFlags : 3
+0x24c ActiveExWorker : 0y1
+0x24c ExWorkerCanWaitUser : 0y1
+0x24c MemoryMaker : 0y0
+0x250 SameThreadApcFlags : 0
+0x250 LpcReceivedMsgIdValid : 0y0
+0x250 LpcExitThreadCalled : 0y0
+0x250 AddressSpaceOwner : 0y0
+0x254 ForwardClusterOnly : 0 ''
+0x255 DisablePageFaultClustering : 0 '' +0x220 ThreadsProcess : 0x825b9830 _EPROCESS
이 부분이 바로 해당 스레드를 가지고 있는 프로세스의 EPROCESS 구조체 주소 정보가 있는 곳입니다.
해당 EPROCESS를 따라가보죠.
kd> dt _EPROCESS 0x825b9830
ntdll!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x06c ProcessLock : _EX_PUSH_LOCK
+0x070 CreateTime : _LARGE_INTEGER 0x0
+0x078 ExitTime : _LARGE_INTEGER 0x0
+0x080 RundownProtect : _EX_RUNDOWN_REF
+0x084 UniqueProcessId : 0x00000004 Void
+0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x821a3310 - 0x8055c158 ]
+0x090 QuotaUsage : [3] 0
+0x09c QuotaPeak : [3] 0
+0x0a8 CommitCharge : 7
+0x0ac PeakVirtualSize : 0x293000
+0x0b0 VirtualSize : 0x1cf000
+0x0b4 SessionProcessLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x0bc DebugPort : (null)
+0x0c0 ExceptionPort : (null)
+0x0c4 ObjectTable : 0xe1000cc0 _HANDLE_TABLE
+0x0c8 Token : _EX_FAST_REF
+0x0cc WorkingSetLock : _FAST_MUTEX
+0x0ec WorkingSetPage : 0
+0x0f0 AddressCreationLock : _FAST_MUTEX
+0x110 HyperSpaceLock : 0
+0x114 ForkInProgress : (null)
+0x118 HardwareTrigger : 0
+0x11c VadRoot : 0x825b5200 Void
+0x120 VadHint : 0x825b5200 Void
+0x124 CloneRoot : (null)
+0x128 NumberOfPrivatePages : 3
+0x12c NumberOfLockedPages : 0
+0x130 Win32Process : (null)
+0x134 Job : (null)
+0x138 SectionObject : (null)
+0x13c SectionBaseAddress : (null)
+0x140 QuotaBlock : 0x8055c200 _EPROCESS_QUOTA_BLOCK
+0x144 WorkingSetWatch : (null)
+0x148 Win32WindowStation : (null)
+0x14c InheritedFromUniqueProcessId : (null)
+0x150 LdtInformation : (null)
+0x154 VadFreeHint : (null)
+0x158 VdmObjects : (null)
+0x15c DeviceMap : 0xe1004440 Void
+0x160 PhysicalVadList : _LIST_ENTRY [ 0x825b9990 - 0x825b9990 ]
+0x168 PageDirectoryPte : _HARDWARE_PTE_X86
+0x168 Filler : 0
+0x170 Session : (null)
+0x174 ImageFileName : [16] "System"
+0x184 JobLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x18c LockedPagesList : (null)
+0x190 ThreadListHead : _LIST_ENTRY [ 0x825b97e4 - 0x8246fbb4 ]
+0x198 SecurityPort : 0xe19c27b0 Void
+0x19c PaeTop : (null)
+0x1a0 ActiveThreads : 0x39
+0x1a4 GrantedAccess : 0x1f0fff
+0x1a8 DefaultHardErrorProcessing : 1
+0x1ac LastThreadExitStatus : 0n0
+0x1b0 Peb : (null)
+0x1b4 PrefetchTrace : _EX_FAST_REF
+0x1b8 ReadOperationCount : _LARGE_INTEGER 0x65
+0x1c0 WriteOperationCount : _LARGE_INTEGER 0x1c7
+0x1c8 OtherOperationCount : _LARGE_INTEGER 0x12e5
+0x1d0 ReadTransferCount : _LARGE_INTEGER 0x9a53c
+0x1d8 WriteTransferCount : _LARGE_INTEGER 0x2c1cd6
+0x1e0 OtherTransferCount : _LARGE_INTEGER 0x76c46
+0x1e8 CommitChargeLimit : 0
+0x1ec CommitChargePeak : 0x1cd
+0x1f0 AweInfo : (null)
+0x1f4 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
+0x1f8 Vm : _MMSUPPORT
+0x238 LastFaultCount : 0
+0x23c ModifiedPageCount : 0xcc3
+0x240 NumberOfVads : 4
+0x244 JobStatus : 0
+0x248 Flags : 0x40000
+0x248 CreateReported : 0y0
+0x248 NoDebugInherit : 0y0
+0x248 ProcessExiting : 0y0
+0x248 ProcessDelete : 0y0
+0x248 Wow64SplitPages : 0y0
+0x248 VmDeleted : 0y0
+0x248 OutswapEnabled : 0y0
+0x248 Outswapped : 0y0
+0x248 ForkFailed : 0y0
+0x248 HasPhysicalVad : 0y0
+0x248 AddressSpaceInitialized : 0y00
+0x248 SetTimerResolution : 0y0
+0x248 BreakOnTermination : 0y0
+0x248 SessionCreationUnderway : 0y0
+0x248 WriteWatch : 0y0
+0x248 ProcessInSession : 0y0
+0x248 OverrideAddressSpace : 0y0
+0x248 HasAddressSpace : 0y1
+0x248 LaunchPrefetched : 0y0
+0x248 InjectInpageErrors : 0y0
+0x248 VmTopDown : 0y0
+0x248 Unused3 : 0y0
+0x248 Unused4 : 0y0
+0x248 VdmAllowed : 0y0
+0x248 Unused : 0y00000 (0)
+0x248 Unused1 : 0y0
+0x248 Unused2 : 0y0
+0x24c ExitStatus : 0n259
+0x250 NextPageColor : 0x5bf6
+0x252 SubSystemMinorVersion : 0 ''
+0x253 SubSystemMajorVersion : 0 ''
+0x252 SubSystemVersion : 0
+0x254 PriorityClass : 0x2 ''
+0x255 WorkingSetAcquiredUnsafe : 0 ''
+0x258 Cookie : 0 +0x174 ImageFileName : [16] "System"
이 부분에 바로 해당 프로세스의 이름이 담겨 있는 곳입니다.
이 부분만 후킹해서 슬쩍슬쩍 빼오면 되는 것이지요. EDI, ESI 두 레지스터에서 전부 말이죠.
방법은 간단합니다.
1. nt!SwapContext 함수를 인라인 후킹을 한다. 처음 5Byte를 MyRoutine() 함수로 Jmp하게끔
2. MyRoutine() 함수에서는 EDI, ESI 레지스터에서 ETHREAD -> EPROCESS -> ImageFileName 주소를 구한다.
3. nt!SwapContext 함수로 다시 점프한다.
끝입니다. 구현하면서 중요시 해야될 부분은 보통 API 함수 후킹할 때는
후킹 -> My 함수 호출되면 -> 인자값 조작 -> 언후킹 -> 오리지널 함수 호출 -> 후킹 -> 리턴 값 조작....
이런식으로 진행이 되는데 이 함수는 그렇게 하면 안됩니다.
일단 문서화된 함수가 아니고.. 찾아보니까 함수의 원형을 도저히 모르겠더군요. 또한 이건 함수라고 하기도 뭐한게 [ebp+8].. 이런식으로 인자 값을 가져와서 하는게 아닌 단순 EDI, ESI 에 있는 값을 모니터링 하기 때문이죠. 일반적인 API함수가 아닌..
또한 안다 하더라도 위와같은 방식을 사용하면 스레드가 스왑될 때마다 언후킹, 후킹을 하게 되는데 이러면 상당히 오버헤드가 발생하게 됩니다. Windows 같은 경우 하나의 스레드가 사용할 수 있는 퀀텀을 다 사용하면서 스왑된다 하더라도 스왑되는 주기가 매우 짧기 때문이죠. 고로 이런 방식으로 진행됩니다.
후킹 -> (naked)My 함수 호출 -> EDI, ESI로부터 프로세스 이름 얻음(함수로 처리) -> nt!SwapContext 함수로 점프
이런식입니다. 즉 언후킹은 해당 후킹 드라이버가 언로드 될 때만 해주는 것입니다.
그리고 언후킹을 하지 않기 때문에 저 같은 경우는 아래와 같이 구현했습니다;;
Windows XP SP3 SwapContext 처음 부분
805438e0 0ac9 or cl,cl
805438e2 26c6462d02 mov byte ptr es:[esi+2Dh],2
805438e7 9c pushfd My 함수 끝 부분
or cl,cl
mov byte ptr [esi+0x2D],2
jmp swapContext_Trampoline ; SwapContext_Address + 7
이런식으로 5Byte가 jmp문으로 바뀐것에 대해 내부적으로 수행해주고 SwapContext 주소에서 +7 자리로 점프를 해서 실행하는 것입니다. 이러면 언후킹할 필요가 없더군요. 오버헤드 막기 성공.. ;;
아무튼 구현하니 잘 작동은 합니다.
사실 이렇게 끝내면 안되고 당연히 프로세스 이름을 담을 테이블을 만들고 중복 처리도 해주고
프로세스가 종료되면 해당 프로세스를 테이블에서 빼주고도 하고 유저와 통신을 하면서
사용자에게 보여주기도 해야하는 과정이 남았지만 이건 나중에 실질적으로 필요할 때가 생기면 구현을 해봐야겠습니다.
아래는 그냥 DbgPrint를 사용해 프로세스 이름을 출력하는 과정만 보여주었습니다.
또한 DKOM방법으로 숨긴 프로세스 또한 찾아지는지 보겠습니다.
이제 이렇게 SwapContext를 후킹해 프로세스를 찾아내는 방법 우회해 프로세스를 숨길 수 있는 방법은 무엇이 있을까요?
무조건 프로그램을 사용하게 되면 해당 프로세스 내부에 있는 스레드는 걸리게 될 것이고.. 걸리면 탐지가 되는거고..
안걸리게 해야될탠데.. 안걸리려면 프로세스가 작동을 안해야한다는.. 작동 안할꺼면 필요도 없는 거고..;
우회할 수 있는 방법엔 무엇이 있을까요?? 좋은 방법 아시는 분은 댓글로 저에게 조언 부탁드려요! 막상 바로 떠오르진 않군요.
이런식으로 5Byte가 jmp문으로 바뀐것에 대해 내부적으로 수행해주고 SwapContext 주소에서 +7 자리로 점프를 해서 실행하는 것입니다. 이러면 언후킹할 필요가 없더군요. 오버헤드 막기 성공.. ;;
아무튼 구현하니 잘 작동은 합니다.
사실 이렇게 끝내면 안되고 당연히 프로세스 이름을 담을 테이블을 만들고 중복 처리도 해주고
프로세스가 종료되면 해당 프로세스를 테이블에서 빼주고도 하고 유저와 통신을 하면서
사용자에게 보여주기도 해야하는 과정이 남았지만 이건 나중에 실질적으로 필요할 때가 생기면 구현을 해봐야겠습니다.
아래는 그냥 DbgPrint를 사용해 프로세스 이름을 출력하는 과정만 보여주었습니다.
또한 DKOM방법으로 숨긴 프로세스 또한 찾아지는지 보겠습니다.
이제 이렇게 SwapContext를 후킹해 프로세스를 찾아내는 방법 우회해 프로세스를 숨길 수 있는 방법은 무엇이 있을까요?
무조건 프로그램을 사용하게 되면 해당 프로세스 내부에 있는 스레드는 걸리게 될 것이고.. 걸리면 탐지가 되는거고..
안걸리게 해야될탠데.. 안걸리려면 프로세스가 작동을 안해야한다는.. 작동 안할꺼면 필요도 없는 거고..;
우회할 수 있는 방법엔 무엇이 있을까요?? 좋은 방법 아시는 분은 댓글로 저에게 조언 부탁드려요! 막상 바로 떠오르진 않군요.
'My Study > Programming&Theory' 카테고리의 다른 글
BlockInput Function (3) | 2012.01.20 |
---|---|
My BSOD (2) | 2012.01.08 |
Remote Library Injection (4) | 2011.12.29 |
OpenProcess를 막는 방법은?? (11) | 2011.12.20 |
Immunity Debugger Hard Hooking (2) | 2011.12.08 |