内核上项目【读内存的几种方式】

概述

在内核编程有时为了能够读取目标三环进程的内存,主要有以下几种不错的方式
1.附加后通过直接memcpy复制目标内存进行读取,但是这种方式非常不稳定,不推荐
2.使用MmCopyVirtualMemory进行读取,因为这是微软写好的一个内核函数,所以异常分发等都完善可以直接使用
3.切换CR3读取,当切换了CR3时,访问的地址都将以CR3为目录项基址,所以可以直接访问到目标进程的内存
4.使用MDL方式,附加进程然后将物理页上的内容映射一份到了内核,进而通过memcpy对映射后的地址进行拷贝即可

代码演示

三环测试代码

#include "stdio.h"
#include <windows.h>
#define DEVICE_LINK_NAME    L"\\\\.\\BufferedIODevcieLinkName"


#define CTL_SYS \
    CTL_CODE(FILE_DEVICE_UNKNOWN,0x830,METHOD_BUFFERED,FILE_ANY_ACCESS)
int main()
{
    HANDLE DeviceHandle = CreateFile(DEVICE_LINK_NAME,
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL);
    if (DeviceHandle == INVALID_HANDLE_VALUE)
    {
        return 0;
    }
    char BufferData = NULL;
    DWORD ReturnLength = 0;
    char pid[] = "2828";
    BOOL IsOk = DeviceIoControl(DeviceHandle, CTL_SYS,
        pid,
        strlen(pid) + 1,
        (LPVOID)BufferData,
        0,
        &ReturnLength,
        NULL);
    if (IsOk == FALSE)
    {
        int LastError = GetLastError();

        if (LastError == ERROR_NO_SYSTEM_RESOURCES)
        {
            char BufferData[MAX_PATH] = { 0 };
            IsOk = DeviceIoControl(DeviceHandle, CTL_SYS,
                pid,
                strlen(pid) + 1,
                (LPVOID)BufferData,
                MAX_PATH,
                &ReturnLength,
                NULL);

            if (IsOk == TRUE)
            {
                printf("%s\r\n", BufferData);
            }
        }
    }
    if (DeviceHandle != NULL)
    {
        CloseHandle(DeviceHandle);
        DeviceHandle = NULL;
    }
    printf("Input AnyKey To Exit\r\n");

    getchar();
    return 0;
}

内核function.c文件

#include"GetModule.h"
#include<ntifs.h>
#include<ntstrsafe.h>


NTSTATUS ConvertCharToUnicodeString(char* charString, UNICODE_STRING* unicodeString)
{
	ANSI_STRING ansiString = { 0 };
	RtlInitAnsiString(&ansiString, charString);

	return RtlAnsiStringToUnicodeString(unicodeString, &ansiString, TRUE);
}

/*
功能:获取目标进程的模块的DllBase
参数一:目标进程的PID
参数二:需要寻找的模块名称
返回:寻找到的模块DllBase
*/
HANDLE GetObjectModule(HANDLE pid, CHAR* ModuleName)
{
	PEPROCESS Process = 0;
	PLDR_DATA_TABLE_ENTRY32 pBase32, pNext32;
	PLDR_DATA_TABLE_ENTRY64 pBase64, pNext64;
	UNICODE_STRING CompareModuleName = { 0 };
	BOOLEAN FindIs = FALSE;
	HANDLE return_handle = 0;
	UNICODE_STRING uniFunctionName;
	ConvertCharToUnicodeString(ModuleName, &CompareModuleName);
	PFNPsGetProcessPeb  PsGetProcessPeb = NULL;

	NTSTATUS is = PsLookupProcessByProcessId(pid, &Process);
	if (!NT_SUCCESS(is))
	{
		return 0;
	}
	KAPC_STATE stack = { 0 };
	KeStackAttachProcess(Process, &stack);

	PPEB32 peb32 = PsGetProcessWow64Process(Process);

	if (peb32 == NULL) //如果为64位进程
	{
		RtlInitUnicodeString(&uniFunctionName, L"PsGetProcessPeb");
		PsGetProcessPeb = (PFNPsGetProcessPeb)MmGetSystemRoutineAddress(&uniFunctionName);
		PPEB64 peb64 = PsGetProcessPeb(Process);

		PPEB_LDR_DATA64 peb64ldr = (PPEB_LDR_DATA64)peb64->Ldr;
		pBase64 = (PLDR_DATA_TABLE_ENTRY64)(peb64ldr->InLoadOrderModuleList.Flink);
		pNext64 = pBase64;

		do
		{
			UNICODE_STRING UnicodeString = { 0 };
			RtlUnicodeStringInit(&UnicodeString, pNext64->BaseDllName.Buffer);
			if (!RtlCompareUnicodeString(&UnicodeString, &CompareModuleName, TRUE))
			{
				FindIs = TRUE;
				return_handle = pNext64->DllBase;
				break;
			}
			else
			{
				pNext64 = pNext64->InLoadOrderLinks.Flink;
			}
		} while (pNext64 != pBase64);

		KeUnstackDetachProcess(&stack);
		ObDereferenceObject(Process);
		RtlFreeUnicodeString(&CompareModuleName);
	}
	else //如果为32位进程
	{
		PPEB_LDR_DATA32 peb32ldr = (PPEB_LDR_DATA32)peb32->Ldr;

		pBase32 = (PLDR_DATA_TABLE_ENTRY32)(peb32ldr->InLoadOrderModuleList.Flink);
		pNext32 = pBase32;

		do
		{
			UNICODE_STRING UnicodeString = { 0 };
			RtlUnicodeStringInit(&UnicodeString, (PWCH)pNext32->BaseDllName.Buffer);
			if (!RtlCompareUnicodeString(&UnicodeString, &CompareModuleName, TRUE))
			{
				FindIs = TRUE;
				return_handle = pNext32->DllBase;
				break;
			}
			else
			{
				pNext32 = pNext32->InLoadOrderLinks.Flink;
			}
		} while (pNext32 != pBase32);

		KeUnstackDetachProcess(&stack);
		ObDereferenceObject(Process);
		RtlFreeUnicodeString(&CompareModuleName);
	}

	if (FindIs == TRUE)
	{
		return return_handle;
	}
	return 0;
}

内核driver.c文件

#include "BufferIO.h"
#include "GetModule.h"
#include <Intrin.h>

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegisterPath)
{
    NTSTATUS Status = STATUS_SUCCESS;
    PDEVICE_OBJECT  DeviceObject = NULL;
    UNICODE_STRING  DeviceObjectName;
    UNICODE_STRING  DeviceLinkName;
    ULONG           i;
    //   栈
    //   堆
    //   全局(global Static Const)
    DriverObject->DriverUnload = DriverUnload;

    //创建设备对象名称
    RtlInitUnicodeString(&DeviceObjectName, DEVICE_OBJECT_NAME);

    //创建设备对象
    Status = IoCreateDevice(DriverObject, NULL,&DeviceObjectName,FILE_DEVICE_UNKNOWN,0, FALSE,&DeviceObject);
    if (!NT_SUCCESS(Status))
    {
        return Status;
    }
    //创建设备连接名称
    RtlInitUnicodeString(&DeviceLinkName, DEVICE_LINK_NAME);

    //将设备连接名称与设备名称关联
    Status = IoCreateSymbolicLink(&DeviceLinkName, &DeviceObjectName);

    if (!NT_SUCCESS(Status))
    {
        IoDeleteDevice(DeviceObject);
        return Status;
    }
    //设计符合我们代码的派遣历程
    for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
    {
        DriverObject->MajorFunction[i] = PassThroughDispatch;   //函数指针
    }
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ControlThroughDispatch;
    return Status;
}
//派遣历程
NTSTATUS PassThroughDispatch(PDEVICE_OBJECT  DeviceObject, PIRP Irp)
{
    Irp->IoStatus.Status = STATUS_SUCCESS;     //LastError()
    Irp->IoStatus.Information = 0;             //ReturnLength
    IoCompleteRequest(Irp, IO_NO_INCREMENT);   //将Irp返回给Io管理器
    return STATUS_SUCCESS;
}

PVOID MdlMapMemory(PMDL* mdl, PVOID TargetAddress, SIZE_T size, MODE preMode)
{
    PMDL pMdl = IoAllocateMdl(TargetAddress, size, FALSE, FALSE, NULL);
    PVOID mapAddr = NULL;
    if (!pMdl)
    {
        return NULL;
    }

    BOOLEAN isLock = FALSE;
    __try
    {
        MmProbeAndLockPages(pMdl, preMode, IoReadAccess);
        isLock = TRUE;
        mapAddr = MmMapLockedPagesSpecifyCache(pMdl, KernelMode, MmCached, NULL, FALSE, NormalPagePriority);
    }
    __except (1)
    {
        IoFreeMdl(pMdl);
        if (isLock)
        {
            MmUnlockPages(pMdl);
        }

        return;
    }

    *mdl = pMdl;

    return mapAddr;

}

VOID MdlUnMapMemory(PMDL mdl, PVOID mapBase)
{
    __try
    {
        MmUnmapLockedPages(mapBase, mdl);

        MmUnlockPages(mdl);

        IoFreeMdl(mdl);
    }
    __except (1)
    {
        return;
    }
}



NTSTATUS ControlThroughDispatch(PDEVICE_OBJECT  DeviceObject, PIRP Irp)
{
    NTSTATUS Status;
    ULONG_PTR Informaiton = 0;
    PVOID InputData = NULL;
    ULONG InputDataLength = 0;
    PVOID OutputData = NULL;
    ULONG OutputDataLength = 0;
    ULONG IoControlCode = 0;
    PIO_STACK_LOCATION  IoStackLocation = IoGetCurrentIrpStackLocation(Irp);  //Irp堆栈  
    IoControlCode = IoStackLocation->Parameters.DeviceIoControl.IoControlCode;
    InputData = Irp->AssociatedIrp.SystemBuffer;
    OutputData = Irp->AssociatedIrp.SystemBuffer;
    InputDataLength = IoStackLocation->Parameters.DeviceIoControl.InputBufferLength;
    OutputDataLength = IoStackLocation->Parameters.DeviceIoControl.OutputBufferLength;
    switch (IoControlCode)
    {
    case CTL_SYS:
    {
        if (InputData != NULL && InputDataLength > 0)
        {
            ANSI_STRING PID ;
            //获取我们的PID
            
            RtlInitAnsiString(&PID,InputData);
            UNICODE_STRING PID_U = {0};
            
            RtlAnsiStringToUnicodeString(&PID_U,&PID,TRUE);
            ULONG number;
            RtlUnicodeStringToInteger(&PID_U,10,&number);
            DbgPrintEx(77, 0, "PID == %d\n", number);
            PEPROCESS Global_Peprocess;
            ULONG ref_value = 0;
            KAPC_STATE KAPC = { 0 };
            //第一种 直接使用MmCopyVirtualMemory读取
            PsLookupProcessByProcessId((HANDLE)number, &Global_Peprocess);
            HANDLE dllbase = GetObjectModule(number, "NOTEPAD.EXE");
            SIZE_T Result;
            DbgBreakPoint();
            Status = MmCopyVirtualMemory(Global_Peprocess, dllbase,IoGetCurrentProcess(), &ref_value,4, KernelMode,&Result);
            DbgPrintEx(77, 0, "ref_value == %x\n", ref_value);
            
            //第二种 MDL读取方式
            ref_value = 0;
            PMDL mdl = NULL;
            KeStackAttachProcess(Global_Peprocess, &KAPC);
            PVOID map = MdlMapMemory(&mdl, dllbase, 4, UserMode);
            if (map)
            {
                memcpy(&ref_value, map, 4);
                MdlUnMapMemory(mdl, map);
                KeUnstackDetachProcess(&KAPC);
            }
            DbgPrintEx(77, 0, "ref_value == %x\n", ref_value);

            //第三种方式 切换CR3读取
            ref_value = 0;
            ULONG64 newCr3 = *(PULONG64)((PUCHAR)Global_Peprocess + 0x28);

            ULONG64 oldCr3 = __readcr3();
            KeEnterCriticalRegion(); //关闭APC
            _disable(); //关中断
            __writecr3(newCr3);

            if (MmIsAddressValid(dllbase))
            {
                memcpy(&ref_value, dllbase, 4);
            }
            //恢复环境
            __writecr3(oldCr3);
            _enable();
            KeLeaveCriticalRegion();
            DbgPrintEx(77, 0, "ref_value == %x\n", ref_value);


        }   
        if (OutputData != NULL && OutputDataLength >= strlen("Ring0->Ring3") + 1)
        {
            memcpy(OutputData, "Ring0->Ring3", strlen("Ring0->Ring3") + 1);
            Status = STATUS_SUCCESS;
            Informaiton = strlen("Ring0->Ring3") + 1;
        }
        else
        {
            Status = STATUS_INSUFFICIENT_RESOURCES;   //内存不够
            Informaiton = 0;
        }
        break;
    }
    default:
        break;
    }
    Irp->IoStatus.Status = Status;             //Ring3 GetLastError();
    Irp->IoStatus.Information = Informaiton;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);  //将Irp返回给Io管理器
    return Status;                            //Ring3 DeviceIoControl()返回值
}
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
    UNICODE_STRING  DeviceLinkName;
    PDEVICE_OBJECT  v1 = NULL;
    PDEVICE_OBJECT  DeleteDeviceObject = NULL;

    RtlInitUnicodeString(&DeviceLinkName, DEVICE_LINK_NAME);
    IoDeleteSymbolicLink(&DeviceLinkName);

    DeleteDeviceObject = DriverObject->DeviceObject;
    while (DeleteDeviceObject != NULL)
    {
        v1 = DeleteDeviceObject->NextDevice;
        IoDeleteDevice(DeleteDeviceObject);
        DeleteDeviceObject = v1;
    }
}

内核struct.h

#pragma once
#include <ntifs.h>

typedef enum _KAPC_ENVIRONMENT {
	OriginalApcEnvironment,
	AttachedApcEnvironment,
	CurrentApcEnvironment,
	InsertApcEnvironment
} KAPC_ENVIRONMENT;

typedef VOID(*PKNORMAL_ROUTINE) (
	IN PVOID NormalContext,
	IN PVOID SystemArgument1,
	IN PVOID SystemArgument2
	);

typedef VOID(*PKKERNEL_ROUTINE) (
	IN struct _KAPC* Apc,
	IN OUT PKNORMAL_ROUTINE* NormalRoutine,
	IN OUT PVOID* NormalContext,
	IN OUT PVOID* SystemArgument1,
	IN OUT PVOID* SystemArgument2
	);

typedef VOID(*PKRUNDOWN_ROUTINE) (
	IN struct _KAPC* Apc
	);

VOID KeInitializeApc(
	__out PRKAPC Apc,
	__in PRKTHREAD Thread,
	__in KAPC_ENVIRONMENT Environment,
	__in PKKERNEL_ROUTINE KernelRoutine,
	__in_opt PKRUNDOWN_ROUTINE RundownRoutine,
	__in_opt PKNORMAL_ROUTINE NormalRoutine,
	__in_opt KPROCESSOR_MODE ApcMode,
	__in_opt PVOID NormalContext
);

BOOLEAN KeInsertQueueApc(
	__inout PRKAPC Apc,
	__in_opt PVOID SystemArgument1,
	__in_opt PVOID SystemArgument2,
	__in KPRIORITY Increment
);

BOOLEAN
KeAlertThread(
	__inout PKTHREAD Thread,
	__in KPROCESSOR_MODE AlertMode
);

EXTERN_C NTSTATUS PsWrapApcWow64Thread(PVOID* ApcContext, PVOID* ApcRoutine);

内核GetMoudule.h

#pragma once
#include<ntifs.h>
HANDLE GetObjectModule(HANDLE pid, CHAR* ModuleName);
EXTERN_C PVOID NTAPI PsGetProcessWow64Process(PEPROCESS Process);
typedef PPEB(__stdcall* PFNPsGetProcessPeb)(PEPROCESS pEProcess);

typedef struct _PEB32 {
    UCHAR InheritedAddressSpace;
    UCHAR ReadImageFileExecOptions;
    UCHAR BeingDebugged;
    UCHAR Spare;
    ULONG Mutant;
    ULONG ImageBaseAddress;
    ULONG/*PPEB_LDR_DATA32*/ Ldr;
} PEB32, * PPEB32;

typedef struct _PEB64
{
    UCHAR InheritedAddressSpace;                                            //0x0
    UCHAR ReadImageFileExecOptions;                                         //0x1
    UCHAR BeingDebugged;                                                    //0x2
    union
    {
        UCHAR BitField;                                                     //0x3
        struct
        {
            UCHAR ImageUsesLargePages : 1;                                    //0x3
            UCHAR IsProtectedProcess : 1;                                     //0x3
            UCHAR IsLegacyProcess : 1;                                        //0x3
            UCHAR IsImageDynamicallyRelocated : 1;                            //0x3
            UCHAR SkipPatchingUser32Forwarders : 1;                           //0x3
            UCHAR SpareBits : 3;                                              //0x3
        };
    };
    ULONGLONG Mutant;                                                       //0x8
    ULONGLONG ImageBaseAddress;                                             //0x10
    ULONGLONG Ldr;                                                          //0x18
}PEB64, * PPEB64;

//0x58 bytes (sizeof)
typedef struct _PEB_LDR_DATA64
{
    ULONG Length;                                                           //0x0
    UCHAR Initialized;                                                      //0x4
    VOID* SsHandle;                                                         //0x8
    struct _LIST_ENTRY InLoadOrderModuleList;                               //0x10
    struct _LIST_ENTRY InMemoryOrderModuleList;                             //0x20
    struct _LIST_ENTRY InInitializationOrderModuleList;                     //0x30
    VOID* EntryInProgress;                                                  //0x40
    UCHAR ShutdownInProgress;                                               //0x48
    VOID* ShutdownThreadId;                                                 //0x50
}PEB_LDR_DATA64, * PPEB_LDR_DATA64;

typedef struct _PEB_LDR_DATA32
{
    ULONG Length;                                                           //0x0
    UCHAR Initialized;                                                      //0x4
    ULONG SsHandle;                                                         //0x8
    LIST_ENTRY32 InLoadOrderModuleList;                               //0xc
    LIST_ENTRY32 InMemoryOrderModuleList;                             //0x14
    LIST_ENTRY32 InInitializationOrderModuleList;                     //0x1c
    ULONG EntryInProgress;                                                  //0x24
    UCHAR ShutdownInProgress;                                               //0x28
    ULONG ShutdownThreadId;                                                 //0x2c
}PEB_LDR_DATA32, * PPEB_LDR_DATA32;

//0x78 bytes (sizeof)
typedef struct _LDR_DATA_TABLE_ENTRY32
{
    LIST_ENTRY32 InLoadOrderLinks;                                    //0x0
    LIST_ENTRY32 InMemoryOrderLinks;                                  //0x8
    LIST_ENTRY32 InInitializationOrderLinks;                          //0x10
    ULONG DllBase;                                                          //0x18
    ULONG EntryPoint;                                                       //0x1c
    ULONG SizeOfImage;                                                      //0x20
    UNICODE_STRING32 FullDllName;                                     //0x24
    UNICODE_STRING32 BaseDllName;                                     //0x2c
    ULONG Flags;                                                            //0x34
    USHORT LoadCount;                                                       //0x38
    USHORT TlsIndex;                                                        //0x3a
}LDR_DATA_TABLE_ENTRY32, * PLDR_DATA_TABLE_ENTRY32;

//0xe0 bytes (sizeof)
typedef struct _LDR_DATA_TABLE_ENTRY64
{
    struct _LIST_ENTRY InLoadOrderLinks;                                    //0x0
    struct _LIST_ENTRY InMemoryOrderLinks;                                  //0x10
    struct _LIST_ENTRY InInitializationOrderLinks;                          //0x20
    VOID* DllBase;                                                          //0x30
    VOID* EntryPoint;                                                       //0x38
    LONGLONG SizeOfImage;                                                      //0x40
    struct _UNICODE_STRING FullDllName;                                     //0x48
    struct _UNICODE_STRING BaseDllName;                                     //0x58
}LDR_DATA_TABLE_ENTRY64, * PLDR_DATA_TABLE_ENTRY64;

内核BufferIO.h

#pragma once
#include <ntifs.h>


#define CTL_SYS \
    CTL_CODE(FILE_DEVICE_UNKNOWN,0x830,METHOD_BUFFERED,FILE_ANY_ACCESS)


#define DEVICE_OBJECT_NAME  L"\\Device\\BufferedIODeviceObjectName"
//设备与设备之间通信
#define DEVICE_LINK_NAME    L"\\DosDevices\\BufferedIODevcieLinkName"
//设备与Ring3之间通信
VOID DriverUnload(PDRIVER_OBJECT DriverObject);
NTSTATUS PassThroughDispatch(PDEVICE_OBJECT  DeviceObject, PIRP Irp);
NTSTATUS ControlThroughDispatch(PDEVICE_OBJECT  DeviceObject, PIRP Irp);

NTSTATUS NTAPI MmCopyVirtualMemory(PEPROCESS SourceProcess, PVOID SourceAddress, PEPROCESS TargetProcess, PVOID TargetAddress, SIZE_T BufferSize, KPROCESSOR_MODE PreviousMode, PSIZE_T ReturnSize);

代码讲解

程序通信框架借用的网上IRP通信框架,主要读取内存功能在函数ControlThroughDispatch,里面介绍了三种读取内存的方式,其中第一种直接附加memcpy不太稳定并没有演示,可以参考MDL读写

参考资料

火哥五期学习资料
应用程序与驱动程序通信

### 配置 CentOS 7 中的 CPU 核心数和内存大小 在 CentOS 7 系统中,配置 CPU 核心数和内存大小通常涉及虚拟化环境中的设置(如 KVM、VirtualBox 或 VMware),因为物理服务器上的资源由硬件决定,无法通过软件直接修改。 #### 查看当前系统的 CPU 和内存信息 在开始配置前,可以使用以下命令来确认当前系统的核心数和内存情况: ```bash lscpu ``` 此命令会提供详细的 CPU 架构信息,包括逻辑处理器数量和其他细节[^1]。 对于内存信息,可运行以下命令: ```bash free -h ``` 该命令将以人类易的形式展示可用内存总量及其分布情况。 #### 虚拟机环境下调整 CPU 核心数与内存大小的方法 如果是在虚拟机上部署的 CentOS 7,则可以根据所使用的虚拟化平台执行相应的操作来进行资源配置。以下是几种常见虚拟化技术下的具体方法: ##### **KVM** 当利用 KVM 创建虚拟机时,可通过 XML 定义文件或者 `virsh` 工具动态改变分配给 VM 的计算能力参数。编辑目标域(domain)对应的XML描述文档, 修改 `<vcpu>` 元素数值设定期望的最大线程数目;同样地,在同一份定义里找到存储器标签(`<memory>`) 并更新其值表示新的RAM容量(单位为KiB)[^4]: 示例代码片段如下所示: ```xml <domain> ... <vcpu placement='static'>8</vcpu> <!-- 设置CPU核数 --> <memory unit='MiB'>16384</memory> <!-- 设置内存大小为16GB --> ... </domain> ``` 保存更改之后重启关联的服务使改动生效即可(`service libvirt-bin restart`)。 另外一种方式就是借助于交互式的 shell session 来即时完成上述任务而无需手动干预底层数据结构——只需简单输入几条指令便能达成目的: ```bash virsh setvcpus DOMAIN_ID COUNT --config --live # 更改正在运行实例的VCPU计数 virsh setmaxmem DOMAIN_ID MEMORY_IN_KIB --config # 更新最大允许使用的内存空间 virsh setmem DOMAIN_ID MEMORY_IN_KIB --live # 实时调节实际占用量至指定水平 ``` ##### **VMware Workstation/ESXi** 针对 VMware 用户来说,进入特定 guest OS 的属性界面后定位到 “Processors and Cores” 分区下拉菜单选项卡处重新规定希望启用几个处理单元以及它们之间如何分组协作模式等等; 同样道理,“Memory” 字段旁边也有专门用于定制 RAM 数额的位置供挑选适合自己的方案应用上去就可以了. ##### **Oracle VirtualBox** 打开管理控制台窗口选中待优化的目标项目右键点击弹出快捷菜单列表里的 'Settings' 图标链接跳转过去接着切换到 "System" -> "Motherboard & Processor Features" 子栏目下面分别勾选开启多路支持checkboxes的同时填写预期达到的效果数字进去提交保存退出即完成了整个流程的操作步骤说明啦! 需要注意的是无论采用哪种途径都务必提前做好充分准备比如备份重要资料以防万一发生意外状况造成不可挽回损失哦~ --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值