OBJECT_METHOD初視
10511 ワード
一、背景:Windows NTのオブジェクトメカニズム
Windows NTシステムは、さまざまなリソースをオブジェクトとして組織および管理します.Windows NTカーネルはC言語とアセンブリ言語で記述されているが、それ自体はC++のオブジェクト向けメカニズムには使用されていない.しかし、依然として抽象化された対象概念を通じて各種資源を管理している.
オブジェクトはオブジェクトヘッダとオブジェクトボディの2つの部分に分けられ、オブジェクトヘッダは標準のオブジェクトヘッダとオプションヘッダに分けられます.後者はここでは説明しません.標準ヘッダの定義は次のとおりです.
コード:
標準的なオブジェクトヘッダの前面部分の長さは固定されており、長さはちょうど0 x 18であり、その後がオブジェクトであり、オブジェクトボディの長さは不定であることがわかる.
で、OBJECT_HEADER構造では、POBJECT_TYPE Typeはオブジェクトタイプのポインタです.Windowsでは、基本的なオブジェクトタイプがあらかじめ定義されています.例えばProcessType.(プロセスオブジェクト)、そのオブジェクトボディがEPROCESS構造体(KPROCESSを含む)です.カーネルがPsCreatProcess()で新しいプロセスを確立すると、同時に新しいオブジェクトが作成されます.
次に、オブジェクトタイプに関するOBJECTを見てみましょう.TYPE構造体:
コード:
特殊構造体OBJECT_を含むTYPE_INITIALIZER:
コード:
多くのOB_が含まれていることがわかりますXXXX_METHODタイプのメンバーで、いくつかの関数ポインタです.潘愛民先生は「Windowsカーネルの原理と実現」という本で、ここで定義したのはいくつかのオブジェクト操作の方法の基本的な方法だと話しています.もしかしてプロセスを開くときに使うのがOB_OPEN_METHOD OpenProcedure指定の方法(ルーチン)ですか?次はExplorer.exeで検証します.
二、実戦
まずWinDBGでパス!procress 0 0コマンドはExplorerを得る.exeのPEPROCESS:0 x 8 ad 84720、これもプロセスのオブジェクトの開始です.すると、隣接するオブジェクトヘッダの大きさ0 x 18を減算すると、オブジェクトヘッダの先頭アドレス:0 x 8 ad 84708が得られる.その後dt_を実行OBJECT_HEADER 0 x 8 ad 84708命令観察explorer.Exceプロセスのオブジェクトヘッダ:
コード:
プロセスタイプのポインタ:0 x 8 af 7 cca 0を直接取得し、次に、
コード:
TypeInfoを展開:
コード:
明らかに、この山のProcedureの中で、DeleteProcedureとSecurityProcedureだけが非ゼロ値で、明らかにシステムはカーネルでcall 0 x 00000000を除去しません.システムはこれらのProcedureに対して特別な処理方法がありますか?
ここまで来ると、WRKソースコードをひっくり返してこれらのメンバーのすべての参照を探さなければならないようです.しかし、これは仕事の量が少なくないようで、ここで私たちは比較的に怠ける方法を取っています:ブレークポイント.ここではOpenProcedureとSecurityProcedureの2つしか選択されていません.これらの1つはNULLであり、1つは非NULLであり、名前から言えばオブジェクトを開くときに実行され、1つはセキュリティチェック時に実行されます.
コード:
ログイン後、NtOpenProcess()を呼び出してプロセスを繰り返し、ブレークポイントのヒットを静かに待つようにします.
すぐに:
コード:
対応するソース:
コード:
ここで、ObpCentralizedSecurity(ObjectType)はマクロです.
コード:
面白くないように見えますが、検査をしただけで成功しました.
そのままGOして待ち続けたいと思っていたのですが、次に下を見るとコードが見つかります.
コード:
ObjectType->TypeInfo.SecurityProcedure!=SeDefaultObjectMethodの場合、カーネルは最終的にSecurityProcedureで指定されたインスタンスを呼び出します.また、カーネルはSecurityProcedureで指定されたアドレスをチェックしていないため、修正する場合は有効性を保証する必要があります.
では、このNTSTATUS ObGetObjectSecurity()はいつ呼び出されますか?NtDesignedBookをめくってみませんか?呼び出し階層を確認しましょう.
コード:
このうちObCheckCreateObjectAccess()のみを深く追跡し、いくつかのシェルをすり抜けた後、ObOpenObjectByName()というよく知られた関数を見ることができます.呼び出す関数が多すぎて、ここでは列を作りません.NtOpenProcess、NtOpenThreadはすべてその中にあります.ここは一発で全身を動かすと言える.
追跡を続け、GO後にブレークポイント0がヒット:
コード:
ここは簡潔明瞭です.ObjectType->Type Infoをチェックしてください.OpenProcedureはNULLではなく直接OpenProcedureを呼び出します(これではまだ厳密ではありませんが、カーネルアドレスの範囲内かどうかを確認しましょう).ObpIncrementHandleCount()は、ハンドルのカウントを増やしたように見えますか?それとも、最終的に誰がそれを使用するかを確認します.
コード:
ここでは、ObpCreateHandle()を深く追跡した結果のみをリストします.ObOpenObjectByName()とObOpenObjectByPointer()の2つのよく知られている関数が表示されます.その意味は、前のObCheckCreateObjectAccess()と完全に同じレベルであり、ここでは再利用されません.
三、まとめ
小さなOBJECT_METHOD暗蔵玄機、分析を通じて私達は発見することができます:これらのOBJECT_METHODカードは「オプション」であり、デフォルト値がNULLの場合もあるが、これは重要ではないという意味ではない.特殊な関数ポインタに設定されると、呼び出される機会がかなり高く、HOOKに使用できることも多い.本稿では,具体的な応用については,興味のある子供靴が呼び出される可能性のあるすべての場所を見つけて利用できることを初めて見た.
Windows NTシステムは、さまざまなリソースをオブジェクトとして組織および管理します.Windows NTカーネルはC言語とアセンブリ言語で記述されているが、それ自体はC++のオブジェクト向けメカニズムには使用されていない.しかし、依然として抽象化された対象概念を通じて各種資源を管理している.
オブジェクトはオブジェクトヘッダとオブジェクトボディの2つの部分に分けられ、オブジェクトヘッダは標準のオブジェクトヘッダとオプションヘッダに分けられます.後者はここでは説明しません.標準ヘッダの定義は次のとおりです.
コード:
typedef struct _OBJECT_HEADER { LONG_PTR PointerCount; union { LONG_PTR HandleCount; PVOID NextToFree; }; POBJECT_TYPE Type; UCHAR NameInfoOffset; UCHAR HandleInfoOffset; UCHAR QuotaInfoOffset; UCHAR Flags; union { POBJECT_CREATE_INFORMATION ObjectCreateInfo; PVOID QuotaBlockCharged; }; PSECURITY_DESCRIPTOR SecurityDescriptor; QUAD Body; } OBJECT_HEADER, *POBJECT_HEADE
標準的なオブジェクトヘッダの前面部分の長さは固定されており、長さはちょうど0 x 18であり、その後がオブジェクトであり、オブジェクトボディの長さは不定であることがわかる.
で、OBJECT_HEADER構造では、POBJECT_TYPE Typeはオブジェクトタイプのポインタです.Windowsでは、基本的なオブジェクトタイプがあらかじめ定義されています.例えばProcessType.(プロセスオブジェクト)、そのオブジェクトボディがEPROCESS構造体(KPROCESSを含む)です.カーネルがPsCreatProcess()で新しいプロセスを確立すると、同時に新しいオブジェクトが作成されます.
次に、オブジェクトタイプに関するOBJECTを見てみましょう.TYPE構造体:
コード:
typedef struct _OBJECT_TYPE { ERESOURCE Mutex; LIST_ENTRY TypeList; UNICODE_STRING Name; // Copy from object header for convenience PVOID DefaultObject; ULONG Index; ULONG TotalNumberOfObjects; ULONG TotalNumberOfHandles; ULONG HighWaterNumberOfObjects; ULONG HighWaterNumberOfHandles; OBJECT_TYPE_INITIALIZER TypeInfo; #ifdef POOL_TAGGING ULONG Key; #endif //POOL_TAGGING ERESOURCE ObjectLocks[ OBJECT_LOCK_COUNT ]; } OBJECT_TYPE, *POBJECT_TYPE;
特殊構造体OBJECT_を含むTYPE_INITIALIZER:
コード:
typedef struct _OBJECT_TYPE_INITIALIZER { USHORT Length; BOOLEAN UseDefaultObject; BOOLEAN CaseInsensitive; ULONG InvalidAttributes; GENERIC_MAPPING GenericMapping; ULONG ValidAccessMask; BOOLEAN SecurityRequired; BOOLEAN MaintainHandleCount; BOOLEAN MaintainTypeList; POOL_TYPE PoolType; ULONG DefaultPagedPoolCharge; ULONG DefaultNonPagedPoolCharge; OB_DUMP_METHOD DumpProcedure; OB_OPEN_METHOD OpenProcedure; OB_CLOSE_METHOD CloseProcedure; OB_DELETE_METHOD DeleteProcedure; OB_PARSE_METHOD ParseProcedure; OB_SECURITY_METHOD SecurityProcedure; OB_QUERYNAME_METHOD QueryNameProcedure; OB_OKAYTOCLOSE_METHOD OkayToCloseProcedure; } OBJECT_TYPE_INITIALIZER, *POBJECT_TYPE_INITIALIZER;
多くのOB_が含まれていることがわかりますXXXX_METHODタイプのメンバーで、いくつかの関数ポインタです.潘愛民先生は「Windowsカーネルの原理と実現」という本で、ここで定義したのはいくつかのオブジェクト操作の方法の基本的な方法だと話しています.もしかしてプロセスを開くときに使うのがOB_OPEN_METHOD OpenProcedure指定の方法(ルーチン)ですか?次はExplorer.exeで検証します.
二、実戦
まずWinDBGでパス!procress 0 0コマンドはExplorerを得る.exeのPEPROCESS:0 x 8 ad 84720、これもプロセスのオブジェクトの開始です.すると、隣接するオブジェクトヘッダの大きさ0 x 18を減算すると、オブジェクトヘッダの先頭アドレス:0 x 8 ad 84708が得られる.その後dt_を実行OBJECT_HEADER 0 x 8 ad 84708命令観察explorer.Exceプロセスのオブジェクトヘッダ:
コード:
0: kd> dt_OBJECT_HEADER 0x8ad84708 nt!_OBJECT_HEADER +0x000 PointerCount : 0n108 +0x004 HandleCount : 0n4 +0x004 NextToFree : 0x00000004 Void +0x008 Type : 0x8af7cca0 _OBJECT_TYPE +0x00c NameInfoOffset : 0 '' +0x00d HandleInfoOffset : 0 '' +0x00e QuotaInfoOffset : 0 '' +0x00f Flags : 0x20 ' ' +0x010 ObjectCreateInfo : 0x8a5a7008 _OBJECT_CREATE_INFORMATION +0x010 QuotaBlockCharged : 0x8a5a7008 Void +0x014 SecurityDescriptor : 0xe16cd8c5 Void +0x018 Body : _QUAD
プロセスタイプのポインタ:0 x 8 af 7 cca 0を直接取得し、次に、
コード:
0: kd> dt_OBJECT_TYPE 0x8af7cca0 nt!_OBJECT_TYPE +0x000 Mutex : _ERESOURCE +0x038 TypeList : _LIST_ENTRY [ 0x8af7ccd8 - 0x8af7ccd8 ] +0x040 Name : _UNICODE_STRING "Process" +0x048 DefaultObject : (null) +0x04c Index : 5 +0x050 TotalNumberOfObjects : 0x1c +0x054 TotalNumberOfHandles : 0x7d +0x058 HighWaterNumberOfObjects : 0x20 +0x05c HighWaterNumberOfHandles : 0x82 +0x060 TypeInfo : _OBJECT_TYPE_INITIALIZER +0x0ac Key : 0x636f7250 +0x0b0 ObjectLocks : [4] _ERESOURCE
TypeInfoを展開:
コード:
0: kd> dt _OBJECT_TYPE_INITIALIZER 0x8af7cd00 nt!_OBJECT_TYPE_INITIALIZER +0x000 Length : 0x4c +0x002 UseDefaultObject : 0 '' +0x003 CaseInsensitive : 0 '' +0x004 InvalidAttributes : 0xb0 +0x008 GenericMapping : _GENERIC_MAPPING +0x018 ValidAccessMask : 0x1f0fff +0x01c SecurityRequired : 0x1 '' +0x01d MaintainHandleCount : 0 '' +0x01e MaintainTypeList : 0 '' +0x020 PoolType : 0 ( NonPagedPool ) +0x024 DefaultPagedPoolCharge : 0x1000 +0x028 DefaultNonPagedPoolCharge : 0x2a8 +0x02c DumpProcedure : (null) +0x030 OpenProcedure : (null) +0x034 CloseProcedure : (null) +0x038 DeleteProcedure : 0x80932460 void nt!PspProcessDelete+0 +0x03c ParseProcedure : (null) +0x040 SecurityProcedure : 0x8091ce64 long nt!SeDefaultObjectMethod+0 +0x044 QueryNameProcedure : (null) +0x048 OkayToCloseProcedure : (null)
明らかに、この山のProcedureの中で、DeleteProcedureとSecurityProcedureだけが非ゼロ値で、明らかにシステムはカーネルでcall 0 x 00000000を除去しません.システムはこれらのProcedureに対して特別な処理方法がありますか?
ここまで来ると、WRKソースコードをひっくり返してこれらのメンバーのすべての参照を探さなければならないようです.しかし、これは仕事の量が少なくないようで、ここで私たちは比較的に怠ける方法を取っています:ブレークポイント.ここではOpenProcedureとSecurityProcedureの2つしか選択されていません.これらの1つはNULLであり、1つは非NULLであり、名前から言えばオブジェクトを開くときに実行され、1つはセキュリティチェック時に実行されます.
コード:
0: kd> ba r4 0x8af7cd30 0: kd> ba r4 0x8af7cd40 3: kd> bl 0 e 8af7cd30 r 4 0001 (0001) 1 e 8af7cd40 r 4 0001 (0001)
ログイン後、NtOpenProcess()を呼び出してプロセスを繰り返し、ブレークポイントのヒットを静かに待つようにします.
すぐに:
コード:
Breakpoint 1 hit nt!ObGetObjectSecurity+0x22: 80924f0a 7518 jne nt!ObGetObjectSecurity+0x3c (80924f24)
対応するソース:
コード:
if (ObpCentralizedSecurity(ObjectType)) { *SecurityDescriptor = ObpReferenceSecurityDescriptor( ObjectHeader ); *MemoryAllocated = FALSE; return( STATUS_SUCCESS ); }
ここで、ObpCentralizedSecurity(ObjectType)はマクロです.
コード:
#define ObpCentralizedSecurity(_ObjectType) \ ((_ObjectType)->TypeInfo.SecurityProcedure == SeDefaultObjectMethod) // // Declare a global table of object types. //
面白くないように見えますが、検査をしただけで成功しました.
そのままGOして待ち続けたいと思っていたのですが、次に下を見るとコードが見つかります.
コード:
// // The security method will return an absolute format // security descriptor that just happens to be in a self // contained buffer (not to be confused with a self-relative // security descriptor). // ObpBeginTypeSpecificCallOut( SaveIrql ); Status = (*ObjectType->TypeInfo.SecurityProcedure)( Object, QuerySecurityDescriptor, &SecurityInformation, *SecurityDescriptor, &Length, &ObjectHeader->SecurityDescriptor, ObjectType->TypeInfo.PoolType, &ObjectType->TypeInfo.GenericMapping ); ObpEndTypeSpecificCallOut( SaveIrql, "Security", ObjectType, Object );
ObjectType->TypeInfo.SecurityProcedure!=SeDefaultObjectMethodの場合、カーネルは最終的にSecurityProcedureで指定されたインスタンスを呼び出します.また、カーネルはSecurityProcedureで指定されたアドレスをチェックしていないため、修正する場合は有効性を保証する必要があります.
では、このNTSTATUS ObGetObjectSecurity()はいつ呼び出されますか?NtDesignedBookをめくってみませんか?呼び出し階層を確認しましょう.
コード:
ObCheckCreateObjectAccess(); - ObpLookupObjectName(); - ObInsertObject(); - ObOpenObjectByName(); -ObReferenceObjectByName(); ObCheckObjectAccess(); ObInsertObject(); ObpCheckObjectReference(); ObpCheckTraverseAccess(); PsCreateProcess() PsCreateThread(); PspSetPrimaryToken();
このうちObCheckCreateObjectAccess()のみを深く追跡し、いくつかのシェルをすり抜けた後、ObOpenObjectByName()というよく知られた関数を見ることができます.呼び出す関数が多すぎて、ここでは列を作りません.NtOpenProcess、NtOpenThreadはすべてその中にあります.ここは一発で全身を動かすと言える.
追跡を続け、GO後にブレークポイント0がヒット:
コード:
Breakpoint 0 hit nt!ObpIncrementHandleCount+0x2b9: 80921d3f 85c0 test eax,eax : if (ObjectType->TypeInfo.OpenProcedure != NULL) { #if DBG KIRQL SaveIrql; #endif // // Leave the object type mutex when call the OpenProcedure. If an exception // while OpenProcedure the HoldObjectTypeMutex disable leaving the mutex // on finally block // ObpBeginTypeSpecificCallOut( SaveIrql ); Status = (*ObjectType->TypeInfo.OpenProcedure)( OpenReason, Process, Object, AccessState ? AccessState->PreviouslyGrantedAccess : 0, ProcessHandleCount ); ObpEndTypeSpecificCallOut( SaveIrql, "Open", ObjectType, Object );
ここは簡潔明瞭です.ObjectType->Type Infoをチェックしてください.OpenProcedureはNULLではなく直接OpenProcedureを呼び出します(これではまだ厳密ではありませんが、カーネルアドレスの範囲内かどうかを確認しましょう).ObpIncrementHandleCount()は、ハンドルのカウントを増やしたように見えますか?それとも、最終的に誰がそれを使用するかを確認します.
コード:
ObDupHandleProcedure(); ObDuplicateObject(); ObpCreateHandle(); - ObInsertObject(); - ObOpenObjectByName(); -ObOpenObjectByPointer();
ここでは、ObpCreateHandle()を深く追跡した結果のみをリストします.ObOpenObjectByName()とObOpenObjectByPointer()の2つのよく知られている関数が表示されます.その意味は、前のObCheckCreateObjectAccess()と完全に同じレベルであり、ここでは再利用されません.
三、まとめ
小さなOBJECT_METHOD暗蔵玄機、分析を通じて私達は発見することができます:これらのOBJECT_METHODカードは「オプション」であり、デフォルト値がNULLの場合もあるが、これは重要ではないという意味ではない.特殊な関数ポインタに設定されると、呼び出される機会がかなり高く、HOOKに使用できることも多い.本稿では,具体的な応用については,興味のある子供靴が呼び出される可能性のあるすべての場所を見つけて利用できることを初めて見た.