监控进程创建,全部阻止的demo(使用MiniFilter)

85 篇文章 6 订阅
83 篇文章 9 订阅

使用wdk7600例子passthrough改写,监控IRPIRP_MJ_ACQUIRE_FOR_SECTION_SYNCHRONIZATION在
Data->Iopb->Parameters.AcquireForSectionSynchronization.PageProtection == PAGE_EXECUTE(等于这个就是创建进程)
在x64可以用这个监控进程 ,不会触发patchguard

这里只是粗略的静止掉所有的进程创建,

总结

刚开始把监控放在了post里,发现不成功,最后分析发现2了,post是结束后调用,都操作完了deny也没用了。

相关代码

/*++

Copyright (c) 1999 - 2002  Microsoft Corporation

Module Name:

	passThrough.c

Abstract:

	This is the main module of the passThrough miniFilter driver.
	This filter hooks all IO operations for both pre and post operation
	callbacks.  The filter passes through the operations.

Environment:

	Kernel mode

--*/

#include <fltKernel.h>
#include <dontuse.h>
#include <suppress.h>

#pragma prefast(disable:__WARNING_ENCODE_MEMBER_FUNCTION_POINTER, "Not valid for kernel mode drivers")


PFLT_FILTER gFilterHandle;
ULONG_PTR OperationStatusCtx = 1;

#define PTDBG_TRACE_ROUTINES            0x00000001
#define PTDBG_TRACE_OPERATION_STATUS    0x00000002

ULONG gTraceFlags = 0;


#define PT_DBG_PRINT( _dbgLevel, _string )          \
    (FlagOn(gTraceFlags,(_dbgLevel)) ?              \
        DbgPrint _string :                          \
        ((int)0))

/*************************************************************************
	Prototypes
*************************************************************************/

DRIVER_INITIALIZE DriverEntry;
NTSTATUS
DriverEntry(
	__in PDRIVER_OBJECT DriverObject,
	__in PUNICODE_STRING RegistryPath
);

NTSTATUS
PtInstanceSetup(
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__in FLT_INSTANCE_SETUP_FLAGS Flags,
	__in DEVICE_TYPE VolumeDeviceType,
	__in FLT_FILESYSTEM_TYPE VolumeFilesystemType
);

VOID
PtInstanceTeardownStart(
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__in FLT_INSTANCE_TEARDOWN_FLAGS Flags
);

VOID
PtInstanceTeardownComplete(
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__in FLT_INSTANCE_TEARDOWN_FLAGS Flags
);

NTSTATUS
PtUnload(
	__in FLT_FILTER_UNLOAD_FLAGS Flags
);

NTSTATUS
PtInstanceQueryTeardown(
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__in FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags
);

FLT_PREOP_CALLBACK_STATUS
PtPreOperationPassThrough(
	__inout PFLT_CALLBACK_DATA Data,
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__deref_out_opt PVOID *CompletionContext
);

FLT_PREOP_CALLBACK_STATUS
PtCreatePreOperationPassThrough(
	__inout PFLT_CALLBACK_DATA Data,
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__deref_out_opt PVOID *CompletionContext
);

VOID
PtOperationStatusCallback(
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__in PFLT_IO_PARAMETER_BLOCK ParameterSnapshot,
	__in NTSTATUS OperationStatus,
	__in PVOID RequesterContext
);

FLT_POSTOP_CALLBACK_STATUS
PtPostOperationPassThrough(
	__inout PFLT_CALLBACK_DATA Data,
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__in_opt PVOID CompletionContext,
	__in FLT_POST_OPERATION_FLAGS Flags
);

FLT_PREOP_CALLBACK_STATUS
PtPreOperationNoPostOperationPassThrough(
	__inout PFLT_CALLBACK_DATA Data,
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__deref_out_opt PVOID *CompletionContext
);

BOOLEAN
PtDoRequestOperationStatus(
	__in PFLT_CALLBACK_DATA Data
);

FLT_POSTOP_CALLBACK_STATUS
PtCreatePostOperationPassThrough(
	__inout PFLT_CALLBACK_DATA Data,
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__in_opt PVOID CompletionContext,
	__in FLT_POST_OPERATION_FLAGS Flags
);


FLT_POSTOP_CALLBACK_STATUS
PtProcessMonitorPostOperationPassThrough(
	__inout PFLT_CALLBACK_DATA Data,
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__in_opt PVOID CompletionContext,
	__in FLT_POST_OPERATION_FLAGS Flags
);

FLT_PREOP_CALLBACK_STATUS
PtProcessMonitorPreOperationPassThrough(
	__inout PFLT_CALLBACK_DATA Data,
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__deref_out_opt PVOID *CompletionContext
);



//
//  Assign text sections for each routine.
//

#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, DriverEntry)
#pragma alloc_text(PAGE, PtUnload)
#pragma alloc_text(PAGE, PtInstanceQueryTeardown)
#pragma alloc_text(PAGE, PtInstanceSetup)
#pragma alloc_text(PAGE, PtInstanceTeardownStart)
#pragma alloc_text(PAGE, PtInstanceTeardownComplete)
#endif

//
//  operation registration
//

CONST FLT_OPERATION_REGISTRATION Callbacks[] = {
	{ IRP_MJ_CREATE,
	  0,
	  PtCreatePreOperationPassThrough,
	  PtCreatePostOperationPassThrough },

	{ IRP_MJ_CREATE_NAMED_PIPE,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_CLOSE,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_READ,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_WRITE,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_QUERY_INFORMATION,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_SET_INFORMATION,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_QUERY_EA,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_SET_EA,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_FLUSH_BUFFERS,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_QUERY_VOLUME_INFORMATION,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_SET_VOLUME_INFORMATION,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_DIRECTORY_CONTROL,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_FILE_SYSTEM_CONTROL,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_DEVICE_CONTROL,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_INTERNAL_DEVICE_CONTROL,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_SHUTDOWN,
	  0,
	  PtPreOperationNoPostOperationPassThrough,
	  NULL },                               //post operations not supported

	{ IRP_MJ_LOCK_CONTROL,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_CLEANUP,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_CREATE_MAILSLOT,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_QUERY_SECURITY,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_SET_SECURITY,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_QUERY_QUOTA,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_SET_QUOTA,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_PNP,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_ACQUIRE_FOR_SECTION_SYNCHRONIZATION,
	  0,
	  PtProcessMonitorPreOperationPassThrough,
	  PtProcessMonitorPostOperationPassThrough },

	{ IRP_MJ_RELEASE_FOR_SECTION_SYNCHRONIZATION,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_ACQUIRE_FOR_MOD_WRITE,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_RELEASE_FOR_MOD_WRITE,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_ACQUIRE_FOR_CC_FLUSH,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_RELEASE_FOR_CC_FLUSH,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_FAST_IO_CHECK_IF_POSSIBLE,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_NETWORK_QUERY_OPEN,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_MDL_READ,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_MDL_READ_COMPLETE,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_PREPARE_MDL_WRITE,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_MDL_WRITE_COMPLETE,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_VOLUME_MOUNT,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_VOLUME_DISMOUNT,
	  0,
	  PtPreOperationPassThrough,
	  PtPostOperationPassThrough },

	{ IRP_MJ_OPERATION_END }
};

//
//  This defines what we want to filter with FltMgr
//

CONST FLT_REGISTRATION FilterRegistration = {

	sizeof(FLT_REGISTRATION),         //  Size
	FLT_REGISTRATION_VERSION,           //  Version
	0,                                  //  Flags

	NULL,                               //  Context
	Callbacks,                          //  Operation callbacks

	PtUnload,                           //  MiniFilterUnload

	PtInstanceSetup,                    //  InstanceSetup
	PtInstanceQueryTeardown,            //  InstanceQueryTeardown
	PtInstanceTeardownStart,            //  InstanceTeardownStart
	PtInstanceTeardownComplete,         //  InstanceTeardownComplete

	NULL,                               //  GenerateFileName
	NULL,                               //  GenerateDestinationFileName
	NULL                                //  NormalizeNameComponent

};



NTSTATUS
PtInstanceSetup(
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__in FLT_INSTANCE_SETUP_FLAGS Flags,
	__in DEVICE_TYPE VolumeDeviceType,
	__in FLT_FILESYSTEM_TYPE VolumeFilesystemType
)
/*++

Routine Description:

	This routine is called whenever a new instance is created on a volume. This
	gives us a chance to decide if we need to attach to this volume or not.

	If this routine is not defined in the registration structure, automatic
	instances are alwasys created.

Arguments:

	FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing
		opaque handles to this filter, instance and its associated volume.

	Flags - Flags describing the reason for this attach request.

Return Value:

	STATUS_SUCCESS - attach
	STATUS_FLT_DO_NOT_ATTACH - do not attach

--*/
{
	UNREFERENCED_PARAMETER(FltObjects);
	UNREFERENCED_PARAMETER(Flags);
	UNREFERENCED_PARAMETER(VolumeDeviceType);
	UNREFERENCED_PARAMETER(VolumeFilesystemType);

	PAGED_CODE();

	PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
		("PassThrough!PtInstanceSetup: Entered\n"));

	return STATUS_SUCCESS;
}


NTSTATUS
PtInstanceQueryTeardown(
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__in FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags
)
/*++

Routine Description:

	This is called when an instance is being manually deleted by a
	call to FltDetachVolume or FilterDetach thereby giving us a
	chance to fail that detach request.

	If this routine is not defined in the registration structure, explicit
	detach requests via FltDetachVolume or FilterDetach will always be
	failed.

Arguments:

	FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing
		opaque handles to this filter, instance and its associated volume.

	Flags - Indicating where this detach request came from.

Return Value:

	Returns the status of this operation.

--*/
{
	UNREFERENCED_PARAMETER(FltObjects);
	UNREFERENCED_PARAMETER(Flags);

	PAGED_CODE();

	PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
		("PassThrough!PtInstanceQueryTeardown: Entered\n"));

	return STATUS_SUCCESS;
}


VOID
PtInstanceTeardownStart(
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__in FLT_INSTANCE_TEARDOWN_FLAGS Flags
)
/*++

Routine Description:

	This routine is called at the start of instance teardown.

Arguments:

	FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing
		opaque handles to this filter, instance and its associated volume.

	Flags - Reason why this instance is been deleted.

Return Value:

	None.

--*/
{
	UNREFERENCED_PARAMETER(FltObjects);
	UNREFERENCED_PARAMETER(Flags);

	PAGED_CODE();

	PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
		("PassThrough!PtInstanceTeardownStart: Entered\n"));
}


VOID
PtInstanceTeardownComplete(
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__in FLT_INSTANCE_TEARDOWN_FLAGS Flags
)
/*++

Routine Description:

	This routine is called at the end of instance teardown.

Arguments:

	FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing
		opaque handles to this filter, instance and its associated volume.

	Flags - Reason why this instance is been deleted.

Return Value:

	None.

--*/
{
	UNREFERENCED_PARAMETER(FltObjects);
	UNREFERENCED_PARAMETER(Flags);

	PAGED_CODE();

	PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
		("PassThrough!PtInstanceTeardownComplete: Entered\n"));
}


/*************************************************************************
	MiniFilter initialization and unload routines.
*************************************************************************/

NTSTATUS
DriverEntry(
	__in PDRIVER_OBJECT DriverObject,
	__in PUNICODE_STRING RegistryPath
)
/*++

Routine Description:

	This is the initialization routine for this miniFilter driver.  This
	registers with FltMgr and initializes all global data structures.

Arguments:

	DriverObject - Pointer to driver object created by the system to
		represent this driver.

	RegistryPath - Unicode string identifying where the parameters for this
		driver are located in the registry.

Return Value:

	Returns STATUS_SUCCESS.

--*/
{
	NTSTATUS status;

	UNREFERENCED_PARAMETER(RegistryPath);

	PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
		("PassThrough!DriverEntry: Entered\n"));

	//
	//  Register with FltMgr to tell it our callback routines
	//

	status = FltRegisterFilter(DriverObject,
		&FilterRegistration,
		&gFilterHandle);

	ASSERT(NT_SUCCESS(status));

	if (NT_SUCCESS(status)) {

		//
		//  Start filtering i/o
		//

		status = FltStartFiltering(gFilterHandle);

		if (!NT_SUCCESS(status)) {

			FltUnregisterFilter(gFilterHandle);
		}
	}

	return status;
}

NTSTATUS
PtUnload(
	__in FLT_FILTER_UNLOAD_FLAGS Flags
)
/*++

Routine Description:

	This is the unload routine for this miniFilter driver. This is called
	when the minifilter is about to be unloaded. We can fail this unload
	request if this is not a mandatory unloaded indicated by the Flags
	parameter.

Arguments:

	Flags - Indicating if this is a mandatory unload.

Return Value:

	Returns the final status of this operation.

--*/
{
	UNREFERENCED_PARAMETER(Flags);

	PAGED_CODE();

	PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
		("PassThrough!PtUnload: Entered\n"));

	FltUnregisterFilter(gFilterHandle);

	return STATUS_SUCCESS;
}


/*************************************************************************
	MiniFilter callback routines.
*************************************************************************/


FLT_PREOP_CALLBACK_STATUS
PtCreatePreOperationPassThrough(
	__inout PFLT_CALLBACK_DATA Data,
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__deref_out_opt PVOID *CompletionContext
)
/*++

Routine Description:

	This routine is the main pre-operation dispatch routine for this
	miniFilter. Since this is just a simple passThrough miniFilter it
	does not do anything with the callbackData but rather return
	FLT_PREOP_SUCCESS_WITH_CALLBACK thereby passing it down to the next
	miniFilter in the chain.

	This is non-pageable because it could be called on the paging path

Arguments:

	Data - Pointer to the filter callbackData that is passed to us.

	FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing
		opaque handles to this filter, instance, its associated volume and
		file object.

	CompletionContext - The context for the completion routine for this
		operation.

Return Value:

	The return value is the status of the operation.

--*/
{


	UNREFERENCED_PARAMETER(FltObjects);
	UNREFERENCED_PARAMETER(CompletionContext);

	return FLT_PREOP_SUCCESS_WITH_CALLBACK;
}

FLT_PREOP_CALLBACK_STATUS
PtProcessMonitorPreOperationPassThrough(
	__inout PFLT_CALLBACK_DATA Data,
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__deref_out_opt PVOID *CompletionContext
)
/*++

Routine Description:

	This routine is the main pre-operation dispatch routine for this
	miniFilter. Since this is just a simple passThrough miniFilter it
	does not do anything with the callbackData but rather return
	FLT_PREOP_SUCCESS_WITH_CALLBACK thereby passing it down to the next
	miniFilter in the chain.

	This is non-pageable because it could be called on the paging path

Arguments:

	Data - Pointer to the filter callbackData that is passed to us.

	FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing
		opaque handles to this filter, instance, its associated volume and
		file object.

	CompletionContext - The context for the completion routine for this
		operation.

Return Value:

	The return value is the status of the operation.

--*/
{


	NTSTATUS ntStatus;
	PFLT_FILE_NAME_INFORMATION pNameInfo = NULL;

	//UNREFERENCED_PARAMETER(Data);
	UNREFERENCED_PARAMETER(FltObjects);
	UNREFERENCED_PARAMETER(CompletionContext);



	if (Data->Iopb->Parameters.AcquireForSectionSynchronization.PageProtection == PAGE_EXECUTE) {
		ntStatus = FltGetFileNameInformation(Data,
			FLT_FILE_NAME_NORMALIZED |
			FLT_FILE_NAME_QUERY_DEFAULT,
			&pNameInfo);
		if (NT_SUCCESS(ntStatus))
		{
			FltParseFileNameInformation(pNameInfo);
			DbgPrint("FileName:%wZ\n 正在创建进程,不让创建", &pNameInfo->Name);
			FltReleaseFileNameInformation(pNameInfo);
		}

		PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
			("PassThrough!PtPostOperationPassThrough: Entered\n"));
		return STATUS_ACCESS_DENIED;
	}
	return FLT_PREOP_SUCCESS_WITH_CALLBACK;

}





FLT_PREOP_CALLBACK_STATUS
PtPreOperationPassThrough(
	__inout PFLT_CALLBACK_DATA Data,
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__deref_out_opt PVOID *CompletionContext
)
/*++

Routine Description:

	This routine is the main pre-operation dispatch routine for this
	miniFilter. Since this is just a simple passThrough miniFilter it
	does not do anything with the callbackData but rather return
	FLT_PREOP_SUCCESS_WITH_CALLBACK thereby passing it down to the next
	miniFilter in the chain.

	This is non-pageable because it could be called on the paging path

Arguments:

	Data - Pointer to the filter callbackData that is passed to us.

	FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing
		opaque handles to this filter, instance, its associated volume and
		file object.

	CompletionContext - The context for the completion routine for this
		operation.

Return Value:

	The return value is the status of the operation.

--*/
{

	UNREFERENCED_PARAMETER(FltObjects);
	UNREFERENCED_PARAMETER(CompletionContext);


	return FLT_PREOP_SUCCESS_WITH_CALLBACK;
}



VOID
PtOperationStatusCallback(
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__in PFLT_IO_PARAMETER_BLOCK ParameterSnapshot,
	__in NTSTATUS OperationStatus,
	__in PVOID RequesterContext
)
/*++

Routine Description:

	This routine is called when the given operation returns from the call
	to IoCallDriver.  This is useful for operations where STATUS_PENDING
	means the operation was successfully queued.  This is useful for OpLocks
	and directory change notification operations.

	This callback is called in the context of the originating thread and will
	never be called at DPC level.  The file object has been correctly
	referenced so that you can access it.  It will be automatically
	dereferenced upon return.

	This is non-pageable because it could be called on the paging path

Arguments:

	FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing
		opaque handles to this filter, instance, its associated volume and
		file object.

	RequesterContext - The context for the completion routine for this
		operation.

	OperationStatus -

Return Value:

	The return value is the status of the operation.

--*/
{
	UNREFERENCED_PARAMETER(FltObjects);

	PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
		("PassThrough!PtOperationStatusCallback: Entered\n"));

	PT_DBG_PRINT(PTDBG_TRACE_OPERATION_STATUS,
		("PassThrough!PtOperationStatusCallback: Status=%08x ctx=%p IrpMj=%02x.%02x \"%s\"\n",
			OperationStatus,
			RequesterContext,
			ParameterSnapshot->MajorFunction,
			ParameterSnapshot->MinorFunction,
			FltGetIrpName(ParameterSnapshot->MajorFunction)));
}


FLT_POSTOP_CALLBACK_STATUS
PtPostOperationPassThrough(
	__inout PFLT_CALLBACK_DATA Data,
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__in_opt PVOID CompletionContext,
	__in FLT_POST_OPERATION_FLAGS Flags
)
/*++

Routine Description:

	This routine is the post-operation completion routine for this
	miniFilter.

	This is non-pageable because it may be called at DPC level.

Arguments:

	Data - Pointer to the filter callbackData that is passed to us.

	FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing
		opaque handles to this filter, instance, its associated volume and
		file object.

	CompletionContext - The completion context set in the pre-operation routine.

	Flags - Denotes whether the completion is successful or is being drained.

Return Value:

	The return value is the status of the operation.

--*/
{
	UNREFERENCED_PARAMETER(Data);
	UNREFERENCED_PARAMETER(FltObjects);
	UNREFERENCED_PARAMETER(CompletionContext);
	UNREFERENCED_PARAMETER(Flags);

	PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
		("PassThrough!PtPostOperationPassThrough: Entered\n"));

	return FLT_POSTOP_FINISHED_PROCESSING;
}

FLT_POSTOP_CALLBACK_STATUS
PtCreatePostOperationPassThrough(
	__inout PFLT_CALLBACK_DATA Data,
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__in_opt PVOID CompletionContext,
	__in FLT_POST_OPERATION_FLAGS Flags
)
/*++

Routine Description:

	This routine is the post-operation completion routine for this
	miniFilter.

	This is non-pageable because it may be called at DPC level.

Arguments:

	Data - Pointer to the filter callbackData that is passed to us.

	FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing
		opaque handles to this filter, instance, its associated volume and
		file object.

	CompletionContext - The completion context set in the pre-operation routine.

	Flags - Denotes whether the completion is successful or is being drained.

Return Value:

	The return value is the status of the operation.

--*/
{

	NTSTATUS ntStatus;
	PFLT_FILE_NAME_INFORMATION pNameInfo = NULL;

	UNREFERENCED_PARAMETER(Data);
	UNREFERENCED_PARAMETER(FltObjects);
	UNREFERENCED_PARAMETER(CompletionContext);
	UNREFERENCED_PARAMETER(Flags);



	ntStatus = FltGetFileNameInformation(Data,
		FLT_FILE_NAME_NORMALIZED |
		FLT_FILE_NAME_QUERY_DEFAULT,
		&pNameInfo);
	if (NT_SUCCESS(ntStatus))
	{
		FltParseFileNameInformation(pNameInfo);
		DbgPrint("FileName:%wZ\n", &pNameInfo->Name);
		FltReleaseFileNameInformation(pNameInfo);
	}

	PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
		("PassThrough!PtPostOperationPassThrough: Entered\n"));

	return FLT_POSTOP_FINISHED_PROCESSING;
}



FLT_POSTOP_CALLBACK_STATUS
PtProcessMonitorPostOperationPassThrough(
	__inout PFLT_CALLBACK_DATA Data,
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__in_opt PVOID CompletionContext,
	__in FLT_POST_OPERATION_FLAGS Flags
)
{

	UNREFERENCED_PARAMETER(Data);
	UNREFERENCED_PARAMETER(FltObjects);
	UNREFERENCED_PARAMETER(CompletionContext);
	UNREFERENCED_PARAMETER(Flags);

	PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
		("PassThrough!PtPostOperationPassThrough: Entered\n"));

	return FLT_POSTOP_FINISHED_PROCESSING;
}




FLT_PREOP_CALLBACK_STATUS
PtPreOperationNoPostOperationPassThrough(
	__inout PFLT_CALLBACK_DATA Data,
	__in PCFLT_RELATED_OBJECTS FltObjects,
	__deref_out_opt PVOID *CompletionContext
)
/*++

Routine Description:

	This routine is the main pre-operation dispatch routine for this
	miniFilter. Since this is just a simple passThrough miniFilter it
	does not do anything with the callbackData but rather return
	FLT_PREOP_SUCCESS_WITH_CALLBACK thereby passing it down to the next
	miniFilter in the chain.

	This is non-pageable because it could be called on the paging path

Arguments:

	Data - Pointer to the filter callbackData that is passed to us.

	FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing
		opaque handles to this filter, instance, its associated volume and
		file object.

	CompletionContext - The context for the completion routine for this
		operation.

Return Value:

	The return value is the status of the operation.

--*/
{
	UNREFERENCED_PARAMETER(Data);
	UNREFERENCED_PARAMETER(FltObjects);
	UNREFERENCED_PARAMETER(CompletionContext);

	PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
		("PassThrough!PtPreOperationNoPostOperationPassThrough: Entered\n"));

	return FLT_PREOP_SUCCESS_NO_CALLBACK;
}


BOOLEAN
PtDoRequestOperationStatus(
	__in PFLT_CALLBACK_DATA Data
)
/*++

Routine Description:

	This identifies those operations we want the operation status for.  These
	are typically operations that return STATUS_PENDING as a normal completion
	status.

Arguments:

Return Value:

	TRUE - If we want the operation status
	FALSE - If we don't

--*/
{
	PFLT_IO_PARAMETER_BLOCK iopb = Data->Iopb;

	//
	//  return boolean state based on which operations we are interested in
	//

	return (BOOLEAN)

		//
		//  Check for oplock operations
		//

		(((iopb->MajorFunction == IRP_MJ_FILE_SYSTEM_CONTROL) &&
		((iopb->Parameters.FileSystemControl.Common.FsControlCode == FSCTL_REQUEST_FILTER_OPLOCK) ||
			(iopb->Parameters.FileSystemControl.Common.FsControlCode == FSCTL_REQUEST_BATCH_OPLOCK) ||
			(iopb->Parameters.FileSystemControl.Common.FsControlCode == FSCTL_REQUEST_OPLOCK_LEVEL_1) ||
			(iopb->Parameters.FileSystemControl.Common.FsControlCode == FSCTL_REQUEST_OPLOCK_LEVEL_2)))

			||

			//
			//    Check for directy change notification
			//

			((iopb->MajorFunction == IRP_MJ_DIRECTORY_CONTROL) &&
			(iopb->MinorFunction == IRP_MN_NOTIFY_CHANGE_DIRECTORY))
			);
}

效果图

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很抱歉,我无法为您提供完整的代码。但是,我可以为您提供一个简单的示例,以说明如何使用minifilter双缓存机制实现透明加解密处理。 在此示例中,我们将使用minifilter驱动程序来拦截文件访问,并使用双缓存机制对文件进行加解密处理。我们将使用AES算法进行加解密,并使用Windows CryptoAPI来实现加解密过程。 以下是示例代码的主要部分: ```c // 定义双缓存结构体 typedef struct _DOUBLE_BUFFER { PVOID DataBuffer; // 数据缓存 ULONG DataLength; // 数据长度 PVOID AuxBuffer; // 辅助缓存 ULONG AuxLength; // 辅助缓存长度 ULONG TotalLength; // 数据总长度 CRITICAL_SECTION Lock; // 互斥锁 } DOUBLE_BUFFER, *PDOUBLE_BUFFER; // 初始化双缓存 NTSTATUS InitializeDoubleBuffer(PDOUBLE_BUFFER pBuffer, ULONG TotalLength) { NTSTATUS status = STATUS_SUCCESS; pBuffer->DataBuffer = ExAllocatePoolWithTag(NonPagedPool, TotalLength, 'Tag1'); if (pBuffer->DataBuffer == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } pBuffer->DataLength = 0; pBuffer->AuxBuffer = ExAllocatePoolWithTag(NonPagedPool, TotalLength, 'Tag2'); if (pBuffer->AuxBuffer == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } pBuffer->AuxLength = 0; pBuffer->TotalLength = TotalLength; InitializeCriticalSection(&pBuffer->Lock); Exit: if (!NT_SUCCESS(status)) { if (pBuffer->DataBuffer != NULL) { ExFreePoolWithTag(pBuffer->DataBuffer, 'Tag1'); } if (pBuffer->AuxBuffer != NULL) { ExFreePoolWithTag(pBuffer->AuxBuffer, 'Tag2'); } } return status; } // 销毁双缓存 VOID DestroyDoubleBuffer(PDOUBLE_BUFFER pBuffer) { if (pBuffer->DataBuffer != NULL) { ExFreePoolWithTag(pBuffer->DataBuffer, 'Tag1'); } if (pBuffer->AuxBuffer != NULL) { ExFreePoolWithTag(pBuffer->AuxBuffer, 'Tag2'); } DeleteCriticalSection(&pBuffer->Lock); } // 读取文件到缓存 NTSTATUS ReadFileToBuffer(PFLT_CALLBACK_DATA Data, PFLT_RELATED_OBJECTS FltObjects, PVOID* pBuffer, PULONG pLength) { NTSTATUS status = STATUS_SUCCESS; HANDLE hFile = NULL; OBJECT_ATTRIBUTES objAttr; IO_STATUS_BLOCK ioStatus; FILE_STANDARD_INFORMATION fileInfo; LARGE_INTEGER byteOffset; ULONG length = 0; ULONG bytesRead = 0; InitializeObjectAttributes(&objAttr, &Data->Iopb->TargetFileObject->FileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); status = FltCreateFile(FltObjects->Instance, FltObjects->FileObject, &hFile, FILE_READ_DATA, &objAttr, &ioStatus, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_NON_DIRECTORY_FILE, NULL, 0, IO_IGNORE_SHARE_ACCESS_CHECK); if (!NT_SUCCESS(status)) { goto Exit; } status = ZwQueryInformationFile(hFile, &ioStatus, &fileInfo, sizeof(fileInfo), FileStandardInformation); if (!NT_SUCCESS(status)) { goto Exit; } length = (ULONG)fileInfo.EndOfFile.QuadPart; *pBuffer = ExAllocatePoolWithTag(NonPagedPool, length, 'Tag3'); if (*pBuffer == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } byteOffset.QuadPart = 0; status = ZwReadFile(hFile, NULL, NULL, NULL, &ioStatus, *pBuffer, length, &byteOffset, NULL); if (!NT_SUCCESS(status)) { goto Exit; } bytesRead = (ULONG)ioStatus.Information; if (bytesRead != length) { status = STATUS_FILE_CORRUPT_ERROR; goto Exit; } *pLength = length; Exit: if (hFile != NULL) { ZwClose(hFile); } if (!NT_SUCCESS(status) && *pBuffer != NULL) { ExFreePoolWithTag(*pBuffer, 'Tag3'); *pBuffer = NULL; *pLength = 0; } return status; } // 写入缓存到文件 NTSTATUS WriteBufferToFile(PFLT_CALLBACK_DATA Data, PFLT_RELATED_OBJECTS FltObjects, PVOID pBuffer, ULONG Length) { NTSTATUS status = STATUS_SUCCESS; HANDLE hFile = NULL; OBJECT_ATTRIBUTES objAttr; IO_STATUS_BLOCK ioStatus; LARGE_INTEGER byteOffset; InitializeObjectAttributes(&objAttr, &Data->Iopb->TargetFileObject->FileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); status = FltCreateFile(FltObjects->Instance, FltObjects->FileObject, &hFile, FILE_WRITE_DATA, &objAttr, &ioStatus, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE, NULL, 0, IO_IGNORE_SHARE_ACCESS_CHECK); if (!NT_SUCCESS(status)) { goto Exit; } byteOffset.QuadPart = 0; status = ZwWriteFile(hFile, NULL, NULL, NULL, &ioStatus, pBuffer, Length, &byteOffset, NULL); if (!NT_SUCCESS(status)) { goto Exit; } Exit: if (hFile != NULL) { ZwClose(hFile); } return status; } // AES加密 NTSTATUS EncryptData(PVOID pData, ULONG Length, PVOID pKey, ULONG KeyLength) { NTSTATUS status = STATUS_SUCCESS; HCRYPTPROV hProv = 0; HCRYPTKEY hKey = 0; ULONG blockSize = 0; ULONG bufferLength = 0; PVOID pBuffer = NULL; // 获取加密算法的块大小 if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) { status = STATUS_INTERNAL_ERROR; goto Exit; } if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, NULL, NULL)) { status = STATUS_INTERNAL_ERROR; goto Exit; } if (!CryptDeriveKey(hProv, CALG_AES_256, NULL, 0, &hKey)) { status = STATUS_INTERNAL_ERROR; goto Exit; } if (!CryptGetKeyParam(hKey, KP_BLOCKLEN, (PBYTE)&blockSize, &bufferLength, 0)) { status = STATUS_INTERNAL_ERROR; goto Exit; } // 分配缓存 bufferLength = ROUND_UP(Length, blockSize); pBuffer = ExAllocatePoolWithTag(NonPagedPool, bufferLength, 'Tag4'); if (pBuffer == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } RtlZeroMemory(pBuffer, bufferLength); RtlCopyMemory(pBuffer, pData, Length); // 加密数据 if (!CryptEncrypt(hKey, NULL, TRUE, 0, (PBYTE)pBuffer, &Length, bufferLength)) { status = STATUS_INTERNAL_ERROR; goto Exit; } RtlCopyMemory(pData, pBuffer, Length); Exit: if (hKey != 0) { CryptDestroyKey(hKey); } if (hProv != 0) { CryptReleaseContext(hProv, 0); } if (pBuffer != NULL) { ExFreePoolWithTag(pBuffer, 'Tag4'); } return status; } // AES解密 NTSTATUS DecryptData(PVOID pData, ULONG Length, PVOID pKey, ULONG KeyLength) { NTSTATUS status = STATUS_SUCCESS; HCRYPTPROV hProv = 0; HCRYPTKEY hKey = 0; ULONG blockSize = 0; ULONG bufferLength = 0; PVOID pBuffer = NULL; // 获取解密算法的块大小 if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) { status = STATUS_INTERNAL_ERROR; goto Exit; } if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, NULL, NULL)) { status = STATUS_INTERNAL_ERROR; goto Exit; } if (!CryptDeriveKey(hProv, CALG_AES_256, NULL, 0, &hKey)) { status = STATUS_INTERNAL_ERROR; goto Exit; } if (!CryptGetKeyParam(hKey, KP_BLOCKLEN, (PBYTE)&blockSize, &bufferLength, 0)) { status = STATUS_INTERNAL_ERROR; goto Exit; } // 分配缓存 bufferLength = ROUND_UP(Length, blockSize); pBuffer = ExAllocatePoolWithTag(NonPagedPool, bufferLength, 'Tag5'); if (pBuffer == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } RtlZeroMemory(pBuffer, bufferLength); RtlCopyMemory(pBuffer, pData, Length); // 解密数据 if (!CryptDecrypt(hKey, NULL, TRUE, 0, (PBYTE)pBuffer, &Length)) { status = STATUS_INTERNAL_ERROR; goto Exit; } RtlCopyMemory(pData, pBuffer, Length); Exit: if (hKey != 0) { CryptDestroyKey(hKey); } if (hProv != 0) { CryptReleaseContext(hProv, 0); } if (pBuffer != NULL) { ExFreePoolWithTag(pBuffer, 'Tag5'); } return status; } // 处理文件读取操作 FLT_PREOP_CALLBACK_STATUS PreReadCallback(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext) { NTSTATUS status = STATUS_SUCCESS; PFILE_OBJECT pFileObject = FltObjects->FileObject; PVOID pBuffer = NULL; ULONG length = 0; PDOUBLE_BUFFER pDoubleBuffer = NULL; // 检查文件对象是否是普通文件 if ((pFileObject->Flags & FO_STREAM_FILE) != 0) { goto Exit; } // 检查文件大小是否超过双缓存的总长度 if (pFileObject->SectionObjectPointer->FileSize.QuadPart > MAX_DOUBLE_BUFFER_LENGTH) { goto Exit; } // 创建双缓存 pDoubleBuffer = ExAllocatePoolWithTag(NonPagedPool, sizeof(DOUBLE_BUFFER), 'Tag6'); if (pDoubleBuffer == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } status = InitializeDoubleBuffer(pDoubleBuffer, (ULONG)pFileObject->SectionObjectPointer->FileSize.QuadPart); if (!NT_SUCCESS(status)) { goto Exit; } // 读取文件到缓存 status = ReadFileToBuffer(Data, FltObjects, &pDoubleBuffer->DataBuffer, &pDoubleBuffer->DataLength); if (!NT_SUCCESS(status)) { goto Exit; } // 复制数据到辅助缓存 EnterCriticalSection(&pDoubleBuffer->Lock); RtlCopyMemory(pDoubleBuffer->AuxBuffer, pDoubleBuffer->DataBuffer, pDoubleBuffer->DataLength); pDoubleBuffer->AuxLength = pDoubleBuffer->DataLength; LeaveCriticalSection(&pDoubleBuffer->Lock); // 加密缓存中的数据 status = EncryptData(pDoubleBuffer->DataBuffer, pDoubleBuffer->DataLength, g_Key, g_KeyLength); if (!NT_SUCCESS(status)) { goto Exit; } // 将双缓存设置为完成上下文 *CompletionContext = pDoubleBuffer; Exit: if (!NT_SUCCESS(status)) { if (pDoubleBuffer != NULL) { DestroyDoubleBuffer(pDoubleBuffer); ExFreePoolWithTag(pDoubleBuffer, 'Tag6'); } Data->IoStatus.Status = status; return FLT_PREOP_COMPLETE; } return FLT_PREOP_SYNCHRONIZE; } // 处理文件写入操作 FLT_POSTOP_CALLBACK_STATUS PostWriteCallback(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID CompletionContext, FLT_POST_OPERATION_FLAGS Flags) { NTSTATUS status = STATUS_SUCCESS; PFILE_OBJECT pFileObject = FltObjects->FileObject; PDOUBLE_BUFFER pDoubleBuffer = (PDOUBLE_BUFFER)CompletionContext; // 检查文件对象是否是普通文件 if ((pFileObject->Flags & FO_STREAM_FILE) != 0) { goto Exit; } // 复制数据到辅助缓存 EnterCriticalSection(&pDoubleBuffer->Lock); RtlCopyMemory(pDoubleBuffer->AuxBuffer, pDoubleBuffer->DataBuffer, pDoubleBuffer->DataLength); pDoubleBuffer->AuxLength = pDoubleBuffer->DataLength; LeaveCriticalSection(&pDoubleBuffer->Lock); // 解密辅助缓存中的数据 status = DecryptData(pDoubleBuffer->AuxBuffer, pDoubleBuffer->AuxLength, g_Key, g_KeyLength); if (!NT_SUCCESS(status)) { goto Exit; } // 写入缓存到文件 status = WriteBufferToFile(Data, FltObjects, pDoubleBuffer->AuxBuffer, pDoubleBuffer->AuxLength); if (!NT_SUCCESS(status)) { goto Exit; } Exit: if (pDoubleBuffer != NULL) { DestroyDoubleBuffer(pDoubleBuffer); ExFreePoolWithTag(pDoubleBuffer, 'Tag6'); } Data->IoStatus.Status = status; return FLT_POSTOP_FINISHED_PROCESSING; } // 注册minifilter回调 FLT_PREOP_CALLBACK_STATUS PreOperationCallback(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext) { switch (Data->Iopb->MajorFunction) { case IRP_MJ_READ: return PreReadCallback(Data, FltObjects, CompletionContext); default: return FLT_PREOP_SUCCESS_WITH_CALLBACK; } } FLT_POSTOP_CALLBACK_STATUS PostOperationCallback(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID CompletionContext, FLT_POST_OPERATION_FLAGS Flags) { switch (Data->Iopb->MajorFunction) { case IRP_MJ_WRITE: return PostWriteCallback(Data, FltObjects, CompletionContext, Flags); default: return FLT_POSTOP_FINISHED_PROCESSING; } } // 注册minifilter驱动程序 NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { NTSTATUS status = STATUS_SUCCESS; PFLT_FILTER pFilter = NULL; OBJECT_ATTRIBUTES objAttr; UNICODE_STRING uniString; // 初始化互斥锁 InitializeCriticalSection(&g_Lock); // 初始化加密密钥 RtlInitUnicodeString(&uniString, L"Password"); status = BCryptGenerateSymmetricKey(&g_hKey, &g_Algorithm, NULL, 0, (PUCHAR)uniString.Buffer, uniString.Length, 0); if (!NT_SUCCESS(status)) { goto Exit; } // 创建过滤器 RtlInitUnicodeString(&uniString, L"FileEncryptionFilter"); InitializeObjectAttributes(&objAttr, NULL, OBJ_KERNEL_HANDLE, NULL, NULL); status = FltCreateFilter(&g_FilterHandle, FLT_REGISTRATION_VERSION, &g_FilterRegistration, &g_FilterContext, &pFilter); if (!NT_SUCCESS(status)) { goto Exit; } // 注册回调函数 status = FltRegisterFilter(pFilter, DriverObject, &uniString, &g_FilterHandle); if (!NT_SUCCESS(status)) { goto Exit; } status = FltStartFiltering(pFilter); if (!NT_SUCCESS(status)) { goto Exit; } Exit: if (!NT_SUCCESS(status)) { if (g_hKey != NULL) { BCryptDestroyKey(g_hKey); g_hKey = NULL; } if (pFilter != NULL) { FltUnregisterFilter(pFilter); } if (g_FilterHandle != NULL) { FltClose(g_FilterHandle); } DeleteCriticalSection(&g_Lock); } return status; } // 卸载minifilter驱动程序 VOID DriverUnload(PDRIVER_OBJECT DriverObject) { PFLT_FILTER pFilter = NULL; if (g_FilterHandle != NULL) { FltGetFilterFromInstance(g_FilterHandle, &pFilter); FltStopFiltering(pFilter); FltUnregisterFilter(pFilter); FltClose(g_FilterHandle); } if (g_hKey != NULL) { BCryptDestroyKey(g_hKey); g_hKey = NULL; } DeleteCriticalSection(&g_Lock); } ``` 这只是一个简单的示例,实际实现中可能需要更多的代码来处理各种情况和错误。此外,请注意,此示例仅用于演示目的,并且未经过完整测试或优化,因此可能存在问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值