윈도우NT Version 6 이상의 커널부터 UAC가 도입되었고, 이때부터 모든 실행파일은 실행할 때 권한체크를 거치게 된다.

일반적으로 EXE파일이 실행될 때 요구되는 권한은 3가지가 존재한다.

 

 

asInvoker : Parent Process의 토큰을 그대로 따라간다. 빌드할 때 기본적으로 설정되어져 있는 UAC Manifest 값이다. 보통 일반적인 어플리케이션의 평범한 실행을 할때 요구되는 권한이다.

 

highestAvailable : 현재 로그인 된 유저의 권한을 따라간다. 예를 들어 현재 로그인 된 유저가 슈퍼관리자라면, 슈퍼관리자의 권한으로 실행하고, 일반 사용자라면 일반 사용자의 권한으로 실행한다.

 

requireAdministrator : 실행되기 위해서는 무조건 관리자 권한이 필요하다.

 

 

오늘 삽질을 좀 하다가, asInvoker UAC Manifest를 가진 프로세스에서 requireAdministrator UAC Manifest를 가진 프로세스를 실행을 해보려고 했다.

CreateProcess를 이용해서 실행하려고 했는데 실패하면서 GetLastError로  에러코드 740을 내뱉는다.

에러코드 740의 내용을 참조해보았더니 다음과 같았다.

 

 

ERROR_ELEVATION_REQUIRED
740 (0x2E4)

The requested operation requires elevation.

권한 상승이 필요하단다.

생각을 해보니 asInvoker 권한만을 가진 프로세스가 requireAdmin 권한을 필요로 하는 프로세스를 정상적으로 실행시키는것이 말이 안되었다. 졸개가 왕에게 명령하는꼴 아닌가?

 

그런데 어디선가 찾아보니 CreateProcess 대신에 Shell API인 ShellExecute를 사용해서 실행시키면 된다고 한다.

그래서 코드를 좀 고쳤다.

 

 

 /*
*
*
*
*   asInvoker.c
*
*
*/

#include <stdio.h>
#include <windows.h>
#include <shellapi.h>
#include <stdlib.h>

int main(void)
{
 STARTUPINFO si;
 PROCESS_INFORMATION pi;

 ZeroMemory(&si, sizeof(si));
 ZeroMemory(&pi, sizeof(pi));
 si.cb = sizeof(si);

 printf("This is a asInvoker process.\n");

 if ( !CreateProcess (
    NULL,
    "requireAdmin.exe",
    NULL,
    NULL,
    FALSE,
    0,
    NULL,
    NULL,
    &si,
    &pi) )
 {

  printf("CreateProcess failed. Error code : %d\n", GetLastError());
  printf("Try to execute using ShellExecute...\n");
  system("pause");

  if ( ShellExecute(NULL, "open", "requireAdmin.exe", NULL, NULL, SW_SHOW) <= (HINSTANCE)32 )
  {
   printf("ShellExecute failed.\n");
   system("pause");
   return EXIT_FAILURE;
  }

  system("pause");
  return EXIT_SUCCESS;
 }

 CloseHandle(pi.hThread);
 CloseHandle(pi.hProcess);
 system("pause");

 return EXIT_SUCCESS;
}

 

 

 

 /*
*
*
*
*   requireAdmin.c
*
*
*/


#include <stdio.h>
#include <stdlib.h>

int main(void)
{
 printf("This is a requiredAdmin process.\n");
 system("pause");

 return 0;
}

 

 

 

 

실행을 해보니 asInvoker.exe에서 requireAdmin.exe 프로세스를 정상적으로 실행할 수 있었다.

물론 ShellExecute를 이용해서다.

 

 

 

 

 이때 권한 상태를 보니 아래와 같았다.

 

 asInvoker.exe는  Elevated 되지 않은 상태이다.

 

 

requireAdmin.exe는  Elevated 된 상태이다.

Non-elevated Process에서 실행을 하는데, 자식이 Elevated 될수가 있단 말인가? OS자체에서 그냥 권한을 띄워주는건가?

 

 

부모 자식 관계를 살펴보니 아래와 같았다.

 

ShellAPI로 실행했기 때문에, 부모 자식 관계가 없을줄 알았는데 requireAdmin은 asInvoker의 자식 관계로 생성되었다.

ShellExecute는 내부적으로 어떻게 구현되어져 있길래, 이러한 실행이 가능해지는거지?

ShellAPI에 대해서 좀 조사해봐야 될것 같다.

 

 

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

 얼마전, 윈도우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
  종종 프로세스의 메모리를 덤프해야될때가 있는데, 이 프로그램은 특정 프로세스의 COMMIT && READ 가능한 영역은 모두다 찾아서 덤프해줍니다. 참고로 유저모드 프로그램이며, 커널 영역으로는 내려가지 않습니다.
(커널에서 후킹이 이루어지는 프로세스인 경우, (ex. 자가보호)  덤프가 안될수 있음)
잘못된 점이 있으면 알려주세요 !





#include <stdio.h>
#include <Windows.h>
#include <Tlhelp32.h>
#include <tchar.h>


//to set super privilege for current process
VOID SetSuperPrivilege(IN LPCTSTR PrivilegeName)
{
HANDLE hToken = NULL;
TOKEN_PRIVILEGES tp;

//get token handle from current process
if ( !OpenProcessToken( 
GetCurrentProcess(), 
TOKEN_ADJUST_PRIVILEGES, 
&hToken)
) {
_tprintf(_T("OPToken error. %d\n"), GetLastError());
return;
}
__try {


//query debug privilege
if ( !LookupPrivilegeValue(
NULL, 
PrivilegeName, 
&tp.Privileges[0].Luid)
) {
_tprintf(_T("LUPPrivilege error. %d\n"), GetLastError());
return;
}


tp.PrivilegeCount = 1; //must be 1
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; //privilege enabled


//adjust token privilege
if ( !AdjustTokenPrivileges(
hToken, 
FALSE, 
&tp, 
sizeof(TOKEN_PRIVILEGES), 
NULL, 
NULL)
) {
_tprintf(_T("ADJUPrivilege error. %d\n"), GetLastError());
return;
}

}
__finally {
CloseHandle(hToken);
}
}


//Enum process list on display
BOOL EnumerateProcessList(VOID)
{
HANDLE hSnapShot = INVALID_HANDLE_VALUE;
PROCESSENTRY32 pe32;

//get a snapshot handle
hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if ( hSnapShot == INVALID_HANDLE_VALUE ) {
_tprintf(_T("CTHelp32 error. %d\n"), GetLastError());
return FALSE;
}

__try {

//initialize PROCESSENTRY structure
ZeroMemory(&pe32, sizeof(PROCESSENTRY32));
pe32.dwSize = sizeof(PROCESSENTRY32);

//call first
if ( !Process32First(hSnapShot, &pe32) ) {
_tprintf(_T("PE32First error. %d\n"), GetLastError());
return FALSE;
}

//call next until ERROR_FILE_NOT_FOUND or failed to call next
while( Process32Next(hSnapShot, &pe32) ) {

if ( GetLastError() == ERROR_FILE_NOT_FOUND ) {
break;
}

//Enum process list on display
_tprintf(
_T("%-30s\t\t%-4d\n"), 
pe32.szExeFile, 
pe32.th32ProcessID
);

}

return TRUE;

}
__finally {
CloseHandle(hSnapShot);
}

}

//dump memory which is on target process
VOID DumpAllMemory(IN HANDLE hProcess)
{
HANDLE hFile;
LPVOID lpMem, lpBuffer;
DWORD NumberofBytesWritten;
TCHAR szFileName[MAX_PATH];
SYSTEM_INFO si;
MEMORY_BASIC_INFORMATION mbi;

if ( hProcess == NULL ) {
_tprintf(_T("hProcess is an invalid value.\n"));
return;
}


/* Get maximum address range from system info */
GetSystemInfo(&si);
//initialize MBI
ZeroMemory(&mbi, sizeof(MEMORY_BASIC_INFORMATION));
lpMem = NULL;

while (lpMem < si.lpMaximumApplicationAddress) {
if ( !VirtualQueryEx(
hProcess, 
lpMem, 
&mbi, 
sizeof(MEMORY_BASIC_INFORMATION)) 
) {

_tprintf(_T("VQuery failed. %d\n"), GetLastError());
CloseHandle(hProcess);
return;

}

/* Increase lpMem to next region of memory */
lpMem = (PVOID)( ( (DWORD_PTR)mbi.BaseAddress + 
(DWORD_PTR)mbi.RegionSize) );


if ( (mbi.BaseAddress != NULL) &&
(mbi.State == MEM_COMMIT) ) {


switch (mbi.Protect)
{
case PAGE_EXECUTE_READ:
case PAGE_EXECUTE_READWRITE:
case PAGE_EXECUTE_WRITECOPY:
case PAGE_READONLY:
case PAGE_READWRITE:
case PAGE_WRITECOPY:

//allocate temp buffer to save data
lpBuffer = VirtualAlloc(
NULL, 
mbi.RegionSize, 
MEM_COMMIT, 
PAGE_READWRITE
);

//read data from target process's memory, data is gonna be saved into lpBuffer
if ( !ReadProcessMemory(
hProcess, 
mbi.BaseAddress, 
lpBuffer, 
mbi.RegionSize, 
NULL) 
) {
_tprintf(_T("ReadMemory failed. %d\n"), GetLastError());
VirtualFree(lpBuffer, mbi.RegionSize, MEM_FREE);
break;
}
//generate file name
_stprintf_s(
szFileName, 
sizeof(szFileName), 
_T("%X - %X.dmp"), 
mbi.BaseAddress, 
((DWORD_PTR)mbi.BaseAddress + (DWORD_PTR)mbi.RegionSize)
);

//create file
hFile = CreateFile(
szFileName, 
GENERIC_WRITE, 
0, 
NULL, 
CREATE_ALWAYS, 
FILE_ATTRIBUTE_NORMAL, 
NULL
);

//write data into file
if ( WriteFile(
hFile, 
lpBuffer, 
(DWORD_PTR)mbi.RegionSize, 
&NumberofBytesWritten, 
NULL) 
) {
_tprintf(_T("Dumped successfully : %s\n"), szFileName);
} else _tprintf(_T("failed to dump : %s,  %x\n"), szFileName, GetLastError());

//return resources
VirtualFree(lpBuffer, mbi.RegionSize, MEM_FREE);
CloseHandle(hFile);
break;

default:
break;
}


}


}

CloseHandle(hProcess);
return;
}


int _tmain( int argc, TCHAR* argv[] )
{
DWORD target_pid;

//obtain super privilege
SetSuperPrivilege(SE_DEBUG_NAME);

//enum process list on display
if ( !EnumerateProcessList() ) {
return EXIT_FAILURE;
}

//input target PID
_tprintf(_T("\n\n ### Choose target PID : "));
_tscanf(_T("%d"), &target_pid);

//dump
DumpAllMemory ( 
OpenProcess(
PROCESS_ALL_ACCESS, 
FALSE, 
target_pid
);

//end
system("pause");
return EXIT_SUCCESS;
}
저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2012.01.19 02:44
  다들 잘 아시다시피, 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

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

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
x64기반 어셈블리 프로그래밍에 대해서 소개와 함께 간단히 다루는 문서입니다.
x64 어셈블리 프로그래밍 입문자를 대상으로 하는 문서입니다.


출처 : 인텔
저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2010.09.24 09:49
 요즘에는 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
심심해서 게임가드 역할을 하는 솔루션을 만들어보고 있는데,
그 기능중에서 당연히 HWBP를 탐지하는 로직도 있어야할것이므로,,,

GetThreadContext를 이용해서 레지스터 정보를 얻어온 뒤, Dr0~Dr3 값을 체크해서
HWBP를 탐지한다는건 전부터 이미 알고있었는데,,,

결과적으로 GetThreadContext 의 사용법을 올바르게 알지 못하고 , 삽질을 해버린꼴이 되었다.


#include <windows.h>
#include <stdio.h>

DWORD __stdcall ThreadFunction(LPVOID lparam)
{
while(1)
__asm rdtsc
return 0;
}
int main(void)
{
HANDLE hThread = CreateThread(NULL,0,ThreadFunction,NULL,0,NULL);

CONTEXT mt;
memset(&mt, 0, sizeof(mt));

mt.ContextFlags = CONTEXT_DEBUG_REGISTERS;
while(1)
{
Sleep(500);
SuspendThread(hThread);
GetThreadContext(hThread,&mt);
if(mt.Dr0 || mt.Dr1 || mt.Dr2 || mt.Dr3)
printf("HWBP detected!\n");
else
printf("HWBP not detected!\n");
ResumeThread(hThread);
}
CloseHandle(hThread);

return 0;
}

망할 저기서 , ContextFlags 라는 값을 지정해줘야 제대로 된 값이 나오는것이었는데,
그것도 모르고 값 지정도 해주지 않은채 ,  왜 컨텍스트가 리턴이 되지가 않는겅미??   하고 있었다.


VS2010  MSDN을 찾아보니 , CONTEXT 관련 정의된 매크로에 관해서는 별 언급이 없고,
그저 Flags 라는 값에 대해서 설명만 주구장창 늘어놓고 있다.
아래는 WinNT.h 파일에  이미 정의된 매크로들을 나열해놓은 것이다.

#define CONTEXT_i386    0x00010000    // this assumes that i386 and
#define CONTEXT_i486    0x00010000    // i486 have identical context records

// end_wx86

#define CONTEXT_CONTROL         (CONTEXT_i386 | 0x00000001L) // SS:SP, CS:IP, FLAGS, BP
#define CONTEXT_INTEGER         (CONTEXT_i386 | 0x00000002L) // AX, BX, CX, DX, SI, DI
#define CONTEXT_SEGMENTS        (CONTEXT_i386 | 0x00000004L) // DS, ES, FS, GS
#define CONTEXT_FLOATING_POINT  (CONTEXT_i386 | 0x00000008L) // 387 state
#define CONTEXT_DEBUG_REGISTERS (CONTEXT_i386 | 0x00000010L) // DB 0-3,6,7
#define CONTEXT_EXTENDED_REGISTERS  (CONTEXT_i386 | 0x00000020L) // cpu specific extensions

#define CONTEXT_FULL (CONTEXT_CONTROL | CONTEXT_INTEGER |\
                      CONTEXT_SEGMENTS)

#define CONTEXT_ALL             (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | \
                                 CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | \
                                 CONTEXT_EXTENDED_REGISTERS)

#define CONTEXT_XSTATE          (CONTEXT_i386 | 0x00000040L)


그냥 간단하게 CONTEXT_ALL 매크로를 지정해주면  모든 컨텍스트 정보가 리턴이 된다.
이거 정말 별거 아닌데 ,  Flags 값 하나 때문에 삽질해버린 꼴....
저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2010.05.23 13:09
pediy 에서 제공한 New Present 2010 크랙 패키지에 있길래 냅다 올려봅니다.
Windows 2K 기준으로 작성된  Native API Reference Manual 입니다.



Reference Manual 이라함은 아래와 같은 형식이라고 볼수 있겠지요. (MSDN같은)


ZwOpenProcess

ZwOpenProcess opens a process object.
NTSYSAPI
NTSTATUS
NTAPI
ZwOpenProcess(
OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PCLIENT_ID ClientId OPTIONAL
);

Parameters

ProcessHandle
Points to a variable that will receive the process object handle if the call is successful.

DesiredAccess
Specifies the type of access that the caller requires to the process object.This parameter
can be zero, or any combination of the following flags:
PROCESS_TERMINATE Terminate process
PROCESS_CREATE_THREAD Create threads in process
PROCESS_SET_SESSIONID Set process session id
PROCESS_VM_OPERATION Protect and lock memory of process
PROCESS_VM_READ Read memory of process
PROCESS_VM_WRITE Write memory of process
PROCESS_DUP_HANDLE Duplicate handles of process
PROCESS_CREATE_PROCESS Bequeath address space and handles to new process
PROCESS_SET_QUOTA Set process quotas
PROCESS_SET_INFORMATION Set information about process
PROCESS_QUERY_INFORMATION Query information about process
PROCESS_SET_PORT Set process exception or debug port
PROCESS_ALL_ACCESS All of the preceding + STANDARD_RIGHTS_ALL


ObjectAttributes
Points to a structure that specifies the object’s attributes. OBJ_PERMANENT, OBJ_EXCLUSIVE,
and OBJ_OPENIF are not valid attributes for a process object.

ClientId
Optionally points to a structure that contains the process id (UniqueProcess) and
optionally the id of a thread in the process (UniqueThread).

Return Value
Returns STATUS_SUCCESS or an error status, such as STATUS_ACCESS_DENIED,
STATUS_OBJECT_NAME_NOT_FOUND, STATUS_INVALID_PARAMETER_MIX, or
STATUS_INVALID_PARAMETER.

Related Win32 Functions
OpenProcess.

Remarks
Process objects can be given names in the same way as other objects.This name is different
from what is commonly referred to as the process name, which is actually the
name of the executable file from which the initial section object of the process was
created.
The process to be opened is identified either by ObjectAttributes.ObjectName or
ClientId; it is an error to specify both.
If ClientId.UniqueThread is not zero, it must be the identifier of a thread in the process
identified by ClientId.UniqueProcess.
If the caller has SeDebugPrivilege, the check of whether the caller is granted access to
the process by its ACL is bypassed. (This behavior can be disabled under Windows NT
4.0 by setting the NtGlobalFlag FLG_IGNORE_DEBUG_PRIV.)



함수에 대한 매뉴얼 말고도 , 구조체 타입이라던가  Enum 열거형 자료에 대한 설명도 들어있습니다.
모르는 함수가 있다면 Ctrl + F를 눌러서 서치해보면 됩니다.

이제 다들 감이 오셨을거라 봅니다.
보안 개발자분들께선 이미 다들 가지고 계실겁니다~

아쉬운점은 WIN2K 기반이라서  최근의 NT6.0이나 NT6.1 기반과는 다를수가 있다는것...


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

Windows Native API 의 이름을 잘 살펴보면,  Ps , Ke,  Ki , Ob 등이 붙는것을 알수있다.
커널 API를 처음 접해보는사람은 도대체 무슨 의미일까...한번쯤은 생각해보기 마련인데,
MSDN에서 이에 관한 정보를 찾아볼 수 있었다.

아래는 MSDN에서 퍼온 내용이다.

Windows Driver Kit: Kernel-Mode Driver Architecture
What Does the Zw Prefix Mean? (Zw 접두어의 의미는 무엇인가?)

The Windows native system services routines have names that begin with the prefixes Nt and Zw. The Nt prefix is an abbreviation of Windows NT, but the Zw prefix has no meaning. Zw was selected partly to avoid potential naming conflicts with other APIs, and partly to avoid using any potentially useful two-letter prefixes that might be needed in the future.

> 윈도우 네이티브 시스템 서비스 루틴은  Nt , Zw  로 시작하는 이름을 가지고있다.
Nt 접두어는 Windows NT를 간단히 줄여서 말하는것이다.  그러나 Zw 접두어는  의미를 가지지 않는다.
Zw는 단지  다른 API들과의 잠재적인 Naming 혼란을 피하기 위하기 위해서 선택된것이다.
그리고 한편으로는 , 훗날 잠재적으로 유용한 2글자 접두어의 사용을 피하기 위해서(?)......

마지막 번역이 뭔가 좀 이상하긴 한데 , 어쨌든 여기까지의 결론은  Zw는  의미가 없다   라고 볼수있겠다.



Many of the Windows driver support routines have names that begin with two- or three-letter prefixes. These prefixes indicate which kernel-mode system components implement the routines. The following table contains some examples.

윈도우 드라이버를 지원하는 여러 함수들은  2글자 혹은 3글자의 접두어를 가지고 있다.
이러한 접두어들은  커널모드의 여러가지 시스템 구성요소들을 의미하는 용도로 사용된다.
다음은 여러가지 예이다.

Prefix Kernel component Example routine
Cm Configuration manager CmRegisterCallbackEx
Ex Executive ExAllocatePool
Hal Hardware abstraction layer HalGetAdapter
Io I/O manager IoAllocateIrp
Ke Kernel core KeSetEvent
Mm Memory manager MmCreateMdl
Ob Object manager ObReferenceObject
Po Power manager PoSetPowerState
Tm Transaction manager TmCommitTransaction
Nt and Zw Native system services NtCreateFile and ZwCreateFile

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

디버깅이라함은 , 이제 막 프로그램 개발에 입문하였거나, 현직 개발자 직업으로 일하고 계신분이나,
혹은  의도치않게 상용바이너리의 버그를 발견하고 이에 대해 분석할때,  자주 이용되는 기술이라고 생각된다.

디버깅툴은 이미 많이들 알고계신대로 , 가장 많이 대중적으로 쓰여지는 대표적인 것들을 나열해보자면, 

유저모드 디버깅/
MS Visual Studio , Olly Debugger, IDA  , WinDBG

커널모드 디버깅/
WinDBG , SoftIce (최근의 운영체제에서는 동작하지 않는다는것이 아쉬울뿐이다.)

요즘 대부분의 디버거들은 개발자에게 보다 친숙하게 느껴질수 있도록 하기위하여 , 
GUI 기반의 인터페이스를 지원하고 , 접근이 쉽게쉽게 이루어질수 있도록 구성되어져 있다.

WinDBG는  GUI 환경으로 개발되어졌다고는 하지만,  90%는 콘솔 인터페이스 기반이라고 봐도 무관한데,
이는 대부분 개발자들에게 접근성이 떨어지는 결과를 낳게되었고, 개발자들 사이에서 기피하는 디버거가 되버렸다.
위에서 확인할 수 있다시피 , WinDBG는  유저와 커널 모두 접근할 수 있고 ,  다양한 기능들을 제공해주지만
사용법이 어렵다는 이유만으로 기피되고 있는것이다.

그러던 와중에!  국내 개발자분들께서 WinDBG 사용법을 널리 전파하고자, WinDBG 사용법을 초보자부터 봐도 자세하고 쉽게 알수있게끔 아주 좋은 책을 내주셨다.

SAMSUNG TECHWIN | NV24HD, VLUU NV24HD, LANDIAO NV106HD | Normal program | Pattern | 1/30sec | F/2.8 | 0.00 EV | 4.3mm | ISO-120 | Off Compulsory | 2009:09:21 18:10:56
SAMSUNG TECHWIN | NV24HD, VLUU NV24HD, LANDIAO NV106HD | Normal program | Pattern | 1/30sec | F/2.8 | 0.00 EV | 4.3mm | ISO-120 | Off Compulsory | 2009:09:21 18:11:26



개인적으로 WinDBG 사용법을 너무도 모르고 있던터라 바로 질렀다!
(가끔 드라이버 엔트리포인트 앞단에 __asm int 3h(0xCC)  를 걸고  디버깅을 하곤하는데, 기본적인 bp  ba  bl bc  kb  uf  dt  process 등 이정도밖에 모르고 있던터라...)

이번에도 에이콘 출판사인데 , 에이콘 출판사 책이 지금 몇개나 있는지 궁금하구랴..
어쨌든  이게 책 홍보하는 글인가,  개인적인 생각을 쓰는건가는 잘 모르겠는데 

초보자도 매우 쉽게 접근할수있도록,  툴의 설치법 , 세팅법,  크래시파일 확보방법부터 시작해서
고급적인 내용까지  순차적으로  자세하게 다루고있는것 같다.(WinDBG의 또다른 장점이라함은 , 크래시파일 분석!)

블루스크린 케이스별  디버깅방법도 다루고있는것도 흥미로운 부분이라고 생각된다.

구매전에 주의할점은 ,  이 책의 저자는  C/C++ 언어와  기초적인 디버깅 지식(Visual Studio로 한번쯤 해봤다! 하는정도)
,  운영체제의 지식을 알고있다고 가정하고 설명을 진행한다는 것이다.
운영체제를 알아야 하는 이유는 , 아무래도 커널 디버깅또한 다루기때문에 , 프로세스 구조체 , 쓰레드 구조체  , 핸들 , Usage Count  등등등  윈도우 운영체제에 대한 용어와 개념들이 많이 등장하기때문에 요구되는것으로 보인다.

시간날때마다 틈틈히 책을 보면서 즐거운 삽질(?) 을 해봐야겠다.
(사실 디버깅도 개발경험에서 중요한 부분이기 때문에 삽질이라는 용어는 적절치 않은듯
근데 어찌보면 삽질이라는 의미가 맞는것 같기도??  뭥미 )
저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2009.09.21 18:33
API

스크롤 압박의 관계로  보실분만 클릭.

더보기

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

XP 서비스 번호별 API



더보기

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2009.09.09 23:57
사실 거창하게 입문이라고 하기보다는 , MFC 공부할 생각도 없었는데,  어찌저찌 하다가 이렇게 만들고 싶은 생각이 들어서,
하루종일 책보고 삽질하면서 만든 프로그램...(사실 틀 잡는것은 빠른시간에 다 완료했지만 , 기능 구현 문제때문에 삽질을 오래한듯)

덕분에 뭐 의도하지않게 MFC에 대해서 조금이나마 맛을 볼수 있었다.




이름하여  테일즈위버 멀티로더 !
사실 이 프로그램 만들기전에는 ,  Windows API만 무식하게 때려박아서 , 각종 리턴값으로 흐름을 제어하는식으로  로더를 제작했었지만,
본인이 봐도 너무 불편한것 같아서.. 마음 먹고 책 뒤져가면서 만들어보았다.

아주 잘 작동한다!  (렌더링타입 빼고)
렌더링타입이 적용되지 않는 문제는 .....
사실 렌더링타입은  Config.DAT의 내용을 바꿔준다음  인자로 전달되는 숫자만 적절히 잘 주면 될줄 알았는데,
아직 분석하지않은 그 무언가가 또 있는듯 하다...

뭐 렌더링타입 설정은 솔직히 있으나마나... 필요없으므로..


**************** 수정 *****************

렌더링 타입 변경도 아주 잘 작동한다......
역시나 리버스 엔지니어링을 통한 분석 완료.



추가적으로 ? 를 체크하면 비밀 기능 작동이........
저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2009.08.29 02:29


#include <ntddk.h>

VOID Unload(IN PDRIVER_OBJECT DriverObject)
{
 __asm{
  cli           //Clear Interrupt
  push eax                  //save eax
  mov eax, cr0
  and eax,0xfffeffff  //11111111111111101111111111111111  즉 , WP 비트를 의도적으로 0으로 세트함.
  mov cr0, eax
  pop eax                   //restore eax
 }
 DbgPrint("Control Reg 0 WP Clear");



//  훅을  원래대로 돌려놓는 코드를 작성



 __asm{
  push eax           //save eax
  mov eax, cr0
  or eax, NOT 0xfffeffff     //00000000000000010000000000000000 WP 비트를 1로 세팅함.
  mov cr0, eax
  pop eax             //restore eax
  sti               //Set Interrupt
 }
 DbgPrint("Control Reg 0  WP Set");
}



NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
      IN PUNICODE_STRING RegistryPath)
{
 DriverObject->DriverUnload=Unload;
 __asm{
  cli
  push eax                  //save eax
  mov eax, cr0
  and eax,0xfffeffff  //11111111111111101111111111111111  즉 , WP 비트를 의도적으로 0으로 세트함.
  mov cr0, eax
  pop eax                   //restore eax
 }
 DbgPrint("Control Reg 0 WP Clear");



//원하는 Hooking Code 작성



 __asm{
  push eax           //save eax
  mov eax, cr0
  or eax, NOT 0xfffeffff     //00000000000000010000000000000000 WP 비트를 1로 세팅함.
  mov cr0, eax
  pop eax             //restore eax
  sti
 }
DbgPrint("Control Reg 0  WP Set");
 return STATUS_SUCCESS;
}


위 코드는 각종 블로그  , 인터넷 사이트 등에 알려진지 꽤 오래 된 코드이다.


보통 SSDT, SSDTShadow, KiFastCallEntry 등 에 접근하여 쓰기작업을 수행하고자 할때 ,  (후킹할 때)
먼저 CR0 레지스터의 Write Protection 비트에 접근하여, WP를 해제시켜주어야 한다.
해제시켜주지 않으면 , 공포의 Blue Screen Of Death 를 맛보게 될것이니...
왜냐면 SSDT 영역은 기본적으로 읽기전용 영역으로 지정되어져 있다.
그런데  WP비트를 해제시켜주지않고  , 거기에 쓰기 작업을 시도하게 되면 , 당연히 예외가 발생하는것이다.

cr0에 값을 쓰기전에 인터럽트를 해제시켜주는 이유는 ,   인터럽트 서비스 루틴에 의해서 CR0값이 변경될 가능성이 있기 때문이다.

보통 인터럽트의 IRQL은  일반 커널 쓰레드의 PASSIVE_LEVEL보다 높은 값을 지니기때문에 , 언제든지 CPU를 선점할수 있게된다.

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

#include <ntddk.h>

 

typedef struct ServiceDescriptorEntry
{
 unsigned int* ServiceTableBase;
 unsigned int* ServiceCounterTableBase;
 unsigned int NumberOfServices;
 unsigned char* ParamTableBase;
} ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;

__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;

typedef NTSTATUS (*NTOPENPROCESS)(PHANDLE ProcessHandle,
          ACCESS_MASK DesiredAccess,
          POBJECT_ATTRIBUTES ObjectAttributes,
          PCLIENT_ID ClientId OPTIONAL);

NTOPENPROCESS OrigNtOpenProcess = NULL;

NTSTATUS HookNtOpenProcess (PHANDLE ProcessHandle,
       ACCESS_MASK DesiredAccess,
       POBJECT_ATTRIBUTES ObjectAttributes,
       PCLIENT_ID ClientId OPTIONAL)
{
 NTSTATUS ntReturn = STATUS_SUCCESS;
 DbgPrint("HookNTOpenProcess\n");

 ntReturn = OrigNtOpenProcess(ProcessHandle,
  DesiredAccess,
  ObjectAttributes,
  ClientId);

 return ntReturn;
}

VOID HookServiceTable()
{
 OrigNtOpenProcess = (PVOID) InterlockedExchangePointer(
  (PVOID*) &KeServiceDescriptorTable.ServiceTableBase[0x7A], HookNtOpenProcess);
}


VOID DRIVERUnload(IN PDRIVER_OBJECT DriverObject)
{
 __asm{
  cli
  push eax
  mov eax, cr0
  and eax,0xfffeffff
  mov cr0, eax
  pop eax
 }
 DbgPrint("CR0 WP bit disabled\n");

 InterlockedExchangePointer(
  (PVOID*) &KeServiceDescriptorTable.ServiceTableBase[0x7A], OrigNtOpenProcess);

 __asm{
  push eax
  mov eax, cr0
  or eax, NOT 0xfffeffff
  mov cr0, eax
  pop eax
  sti
 }
 DbgPrint("CR0 WP bit enabled\n");

 DbgPrint("Hook Unloaded\n");


}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject ,
      IN PUNICODE_STRING RegistryPath)
{
 DriverObject->DriverUnload=DRIVERUnload;

 __asm{
  cli
  push eax
  mov eax, cr0
  and eax,0xfffeffff
  mov cr0, eax
  pop eax
 }
 DbgPrint("CR0 WP bit disabled\n");

 HookServiceTable();

 __asm{
  push eax
  mov eax, cr0
  or eax, NOT 0xfffeffff
  mov cr0, eax
  pop eax
  sti
 }
 DbgPrint("CR0 WP bit enabled\n");
 return STATUS_SUCCESS;
}



사실 이것을  드라이버 작성법도 올바르게 모르는 내가 혼자 다했다고 하면 , 뻥인게 확실할것이고..
각종 서적에 나와있는 방법들과  ,인터넷 사이트들을 뒤져가면서..(물론 이런 종류의 코드는 널리 알려진지 꽤나 오래됐지만)
초보의 입장에서 직접 쳐보면서 원리를 이해하고자 나름대로 노력하였다. (다행히도 나름대로 90%정도는 이해한것 같다!)

C언어를 처음배울 때 ,  뭣도 모르고  printf ,` #include ,` int main(void)  등을 사용하지 않았는가?
그와 비슷한 것이라고 보면 될듯...

드라이버가 로드되면,
ntoskrnl 이 export 하고있는   SDT 테이블을 얻어와서 , InterLockedExchangePointer 함수를 이용해서 포인터를 교환해버리는 방식이다. 즉 , Hook된 NtOpenProcess가 호출되게끔 되는것이다. 위 코드 상에서는 , 특별히 다른 작업은 수행안하고 , 오리지널 NtOpenProcess를 호출하고 있다.
물론 , 언로드되면  기존의 백업해두었던 포인터를 다시 원래대로 돌려놓는 방식..

그런데 , 요즘 이 방법이 먹힐거라고 보면 큰 오산이다.
이유인 즉슨, 근래 대부분의 보안프로그램들은 ,  API 내부적으로 호출하는 함수들 또한 후킹을 하고있기 때문이다.

NtOpenProcess가  내부적으로  호출하고있는 함수들의 목록은 다음과 같다.

0: kd> uf -c nt!NtOpenProcess
nt!NtOpenProcess (805cd408)
  nt!NtOpenProcess+0xa (805cd412):
    call to nt!_SEH_prolog (8053db90)
  nt!NtOpenProcess+0x4e (805cd456):
    call to nt!ExRaiseDatatypeMisalignment (80616090)
  nt!NtOpenProcess+0x7c (805cd484):
    call to nt!ExRaiseDatatypeMisalignment (80616090)
  nt!NtOpenProcess+0x116 (805cd51e):
    call to nt!SeCreateAccessState (805f2dc4)
  nt!NtOpenProcess+0x132 (805cd53a):
    call to nt!SeSinglePrivilegeCheck (805f9cde)
  nt!NtOpenProcess+0x17d (805cd585):
    call to nt!ObOpenObjectByName (805bd8f2)
  nt!NtOpenProcess+0x18b (805cd593):
    call to nt!SeDeleteAccessState (805f2b86)
  nt!NtOpenProcess+0x1e2 (805cd5ea):
    call to nt!PsLookupProcessThreadByCid (805d502e)
  nt!NtOpenProcess+0x1f4 (805cd5fc):
    call to nt!SeDeleteAccessState (805f2b86)
  nt!NtOpenProcess+0x202 (805cd60a):
    call to nt!PsLookupProcessByProcessId (805d50ea)
  nt!NtOpenProcess+0x224 (805cd62c):
    call to nt!ObOpenObjectByPointer (805bdc78)
  nt!NtOpenProcess+0x232 (805cd63a):
    call to nt!SeDeleteAccessState (805f2b86)
  nt!NtOpenProcess+0x23e (805cd646):
    call to nt!ObfDereferenceObject (8052868e)
  nt!NtOpenProcess+0x246 (805cd64e):
    call to nt!ObfDereferenceObject (8052868e)
  nt!NtOpenProcess+0x27e (805cd686):
    call to nt!_SEH_epilog (8053dbcb)


만약 보안솔루션이나 기타 루트킷등에서 내부 함수들까지 싹다 후킹해버리면 , 여간 귀찮은게 한두가지가 아닐듯 하다..

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

그냥 어셈블리 언어 책을 보다가...잠시 딴짓좀 할겸 N사의 모 게임 프로그램 바이너리를 열어서 사용된 API를  봤는데
timeGetTime  API가 보여서  ,  갑자기 삘 받아서 카운터를 만들어볼까~?  하는 생각이 들었다.

(원래 시계 만드려고 했는데 ,  하다보니 이상하게 카운터로 변질된...)

MASM으로 해도 되지만 , 
VC컴파일러에서 __asm 디렉티브를 이용한 인라인 어셈블리로 구현해도 별 지장 없기에..
(오히려 더 편한듯)



일단 아래 코드는  서브루프가 계속되는 구조로 이루어져 있기때문에  , CPU사용율이 높아진다는 점 주의하자.
(CPU 사용율을 낮출수는 없을까?  
무슨 카운터 하나 구현하는데 CPU사용율이 50% ㅡㅡ;;; 
루프를 돌리지않으면 될텐데..그럼 어떠한 방법이 있을까? 
Sleep 함수 이런거 쓰면 좀 유치할것 같은데.. )


원래 변수 안쓰고 레지스터만을 이용해서 구현해보려고 했는데...
함수마다 리턴되는 위치와 값이 제각각 다르고 , 자꾸 삑사리 나서 , 결국 변수 3개 간단히 선언해서 구현을 해보았다..


** 주의 ** 
필자는 실력이 미흡하므로  주의 요망...언제 어디서 에러가 발생할지 모름.
(사실 시 단위는 테스트 안해봄 -_-;;  시 단위까지 기다리기 귀찮고 ,  물론 디버거로 일부러 분에다가 60 잡아넣어서 해볼수도 있지만
귀차니즘때문에 패스.)



아 ,  또 다른 주의할점은 , timeGetTime API를 이용하려면 ,  MMSystem.h 헤더파일을 Include 해주어야하고,
프로젝트  링커 명령줄에다가  Winmm.lib  정적 라이브러리 파일을  명령줄에다가 추가시켜주어야 한다.


//---------------------------inline_assembly.cpp------------------------//
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <MMSystem.h>  //timeGetTime API 이용

int main(void)
{
 DWORD Hour=0,Min=0,Sec=0;

 __asm{
   XOR EDI,EDI //카운트 0으로 초기화 (EDI 레지스터는 카운터 목적으로 이용)
 }

EntireLoop:
 timeGetTime();

 __asm{
   MOV ESI,EAX //1차 시간이 EAX에 반환되고, 그것을 ESI에 집어넣음
 }
SubLoop: timeGetTime(); //2차 시간이 EAX에 반환됨
 __asm{
   SUB EAX,ESI  //2차시간 - 1차시간
   MOV EBX,0x3E8 //10진수 1000을 EBX에 넣는다.
   DIV EBX  //EAX에 들어있는 값과 EBX에 있는 10진수 1000으로 나눈다.
   CMP EAX,0x01 //EAX와 1초를 비교한다.
   JB SubLoop  //1초보다 작다면 SubLoop를 계속 돌면서 연산 수행(CPU 사용율 높아지는 주범)
   ADD EAX,EDI  //EAX에는 1이 있는 상태
   INC EDI   //카운트 증가

   MOV DWORD PTR DS:[Sec],EAX //초 단위 시간을 Sec 변수에 넣음
   CMP EAX,060d //60초 비교
   JNE ShowTime
   INC DWORD PTR DS:[Min] //분 단위 1 증가
   MOV DWORD PTR DS:[Sec],0 //초 단위 0으로 초기화
   XOR EDI,EDI //카운트 초기화

   MOV EDX,DWORD PTR DS:[Min]
   CMP EDX,060d //60분 비교
   JNE ShowTime
   INC DWORD PTR DS:[Hour]  //시 단위 1 증가
   MOV DWORD PTR DS:[Min],0 //분 단위 0으로 초기화

   MOV ECX,DWORD PTR DS:[Hour]
   CMP ECX,024d //24시간 비교
   JNE ShowTime
   MOV DWORD PTR DS:[Hour],0 //0 시로 초기화
   MOV DWORD PTR DS:[Min],0 //0 분으로 초기화
   MOV DWORD PTR DS:[Sec],0 //0 초로 초기화
 }
ShowTime:
   system("cls");
   printf("%02d : %02d : %02d\n",Hour,Min,Sec);
   goto EntireLoop; //무한 루프
}

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

간단히 프로세스 열어서  , 읽고 쓰고  ,닫고 하는것과
기타 다른 명령어 기능들을 구현중에 있습니다.

사실 이 명령프롬프트 프로젝트를 시작하게 된 계기는 , 모 출판사의 윈도우 시스템 프로그래밍 책과 관련 깊지만,
책 내용과는 관계없이 그냥 제 마음대로 명령어들을 구현해보고 있습니다.

간단한 작업도 디버거로 건드려야하는게 귀찮은데  , 그때 좋을것 같네요.

프로세스와 관련된 명령어들을 추가 예정에 있습니다.
이제 메모리 write 커맨드를 구현해야 할듯..




간단한  하드웨어 정보



프로세스 목록 출력


프로세스 권한 획득 후, 특정 주소의 메모리 읽기




핸들 닫기 기능과  , 프로세스 강제 종료




간단한 프로세스 메모리 정보와  , 특정 주소의 정보




프로세스 특정 주소에   바이트단위로  메모리 쓰기 기능
저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2009.07.21 18:19
| 1 |

티스토리 툴바