在 notepad.exe 中挂钩 CreateFile 不会捕获 API 调用

2024-01-07

我的最终目标是通过在 kernel32.dll 中挂钩文件 api 来跟踪 explorer.exe 完成的文件操作,但是我尚未实现该操作(explorer.exe 没有调用 API,或者我这边出了问题)。为了弄清楚发生了什么,我设置了一个目标来跟踪 notepad.exe 创建文件的时间,但是由于某种原因这也失败了!

我有 3 个 Visual Studio 2012 C++ 项目:我的 DLL、一个 DLL 注入器,它将强制任何可执行文件加载我的 dll(尽管如果 Unicode/多字节和 32/64 位设置不匹配,它可能会失败),以及一个测试程序调用API。我有一个批处理文件,它将启动我的测试程序,然后使用注入器程序将我的 DLL 加载到测试程序中。奇怪的部分是输出do显示正在测试程序上跟踪 API(生成控制台并打印所有内容!),但 notepad.exe 没有任何反应(没有控制台 = 没有捕获任何操作)。

这是挂钩 API 的 DLL(使用 mhook 库)。格式和概念取自this http://www.codeproject.com/Articles/49319/Easy-way-to-set-up-global-API-hooks指导。需要注意的重要事项是,我挂钩 CreateFile(A/W),并在第一次操作发生时生成一个控制台来打印文件 I/O 日志。

#include "stdafx.h"
#include "mhook/mhook-lib/mhook.h"

//////////////////////////////////////////////////////////////////////////
// Defines and typedefs
typedef HANDLE (WINAPI *CreateFileWFP)(
    _In_ LPCWSTR lpFileName,
    _In_ DWORD dwDesiredAccess,
    _In_ DWORD dwShareMode,
    _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    _In_ DWORD dwCreationDisposition,
    _In_ DWORD dwFlagsAndAttributes,
    _In_opt_ HANDLE hTemplateFile
    );
typedef HANDLE (WINAPI *CreateFileAFP)(
    _In_ LPCSTR lpFileName,
    _In_ DWORD dwDesiredAccess,
    _In_ DWORD dwShareMode,
    _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    _In_ DWORD dwCreationDisposition,
    _In_ DWORD dwFlagsAndAttributes,
    _In_opt_ HANDLE hTemplateFile
    );

//////////////////////////////////////////////////////////////////////////
// Original function
CreateFileWFP OriginalCreateFileW = (CreateFileWFP)::GetProcAddress(::GetModuleHandle(TEXT("kernel32")), "CreateFileW");
CreateFileAFP OriginalCreateFileA = (CreateFileAFP)::GetProcAddress(::GetModuleHandle(TEXT("kernel32")), "CreateFileA");

//////////////////////////////////////////////////////////////////////////
// Some Helper Stuff

struct Console{
    HANDLE handle;

    Console(){ handle = INVALID_HANDLE_VALUE; }

    void write(LPCWSTR text){
        if(handle==INVALID_HANDLE_VALUE){
            AllocConsole();
            handle = GetStdHandle(STD_OUTPUT_HANDLE);
        }
        DWORD numCharsWritten = 0;
        WriteConsoleW(handle, text, (DWORD)wcslen(text), &numCharsWritten,NULL);
    }
    void write(LPCSTR text){
        if(handle==INVALID_HANDLE_VALUE){
            AllocConsole();
            handle = GetStdHandle(STD_OUTPUT_HANDLE);
        }
        DWORD numCharsWritten = 0;
        WriteConsoleA(handle, text, (DWORD)strlen(text), &numCharsWritten,NULL);
    }
} console;

void operationPrint(LPCWSTR left, LPCWSTR middle, LPCWSTR right){
    console.write(left);
    console.write(middle);
    console.write(right);
    console.write(L"\n");
}
void operationPrint(LPCSTR left, LPCSTR middle, LPCSTR right){
    console.write(left);
    console.write(middle);
    console.write(right);
    console.write(L"\n");
}

//////////////////////////////////////////////////////////////////////////
// Hooked function

HANDLE HookedCreateFileW(
    _In_ LPCWSTR lpFileName,
    _In_ DWORD dwDesiredAccess,
    _In_ DWORD dwShareMode,
    _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    _In_ DWORD dwCreationDisposition,
    _In_ DWORD dwFlagsAndAttributes,
    _In_opt_ HANDLE hTemplateFile
    ){
        HANDLE out = OriginalCreateFileW(
                        lpFileName,
                        dwDesiredAccess,
                        dwShareMode,
                        lpSecurityAttributes,
                        dwCreationDisposition,
                        dwFlagsAndAttributes,
                        hTemplateFile);
        if(out == INVALID_HANDLE_VALUE) return out; //ignore failiures
        operationPrint(L"CreatedW file",L" at ",lpFileName);
        return out;
}
HANDLE HookedCreateFileA(
    _In_ LPCSTR lpFileName,
    _In_ DWORD dwDesiredAccess,
    _In_ DWORD dwShareMode,
    _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    _In_ DWORD dwCreationDisposition,
    _In_ DWORD dwFlagsAndAttributes,
    _In_opt_ HANDLE hTemplateFile
    ){
        HANDLE out = OriginalCreateFileA(
                        lpFileName,
                        dwDesiredAccess,
                        dwShareMode,
                        lpSecurityAttributes,
                        dwCreationDisposition,
                        dwFlagsAndAttributes,
                        hTemplateFile);
        if(out == INVALID_HANDLE_VALUE) return out; //ignore failiures
        operationPrint("CreatedA file"," at ",lpFileName);
        return out;
}

//////////////////////////////////////////////////////////////////////////
// Entry point

BOOL WINAPI DllMain(
    __in HINSTANCE  hInstance,
    __in DWORD      Reason,
    __in LPVOID     Reserved
    )
{        
    switch (Reason)
    {
    case DLL_PROCESS_ATTACH:
        Mhook_SetHook((PVOID*)&OriginalCreateFileW, HookedCreateFileW);
        Mhook_SetHook((PVOID*)&OriginalCreateFileA, HookedCreateFileA);
        break;

    case DLL_PROCESS_DETACH:
        FreeConsole();
        Mhook_Unhook((PVOID*)&OriginalCreateFileW);
        Mhook_Unhook((PVOID*)&OriginalCreateFileA);
        break;
    }

    return TRUE;
}

我无法准确找到注入器程序的位置,但它是nearly相同this http://www.mpgh.net/forum/290-crossfire-spammers-injectors-multi-tools/332194-how-make-dll-injection-c.html指导。有些评论甚至是相同的,所以我很确定其中一个来自另一个(但不确定哪个来自谁)。无论如何,我只是做了一些小的修改,以便它在 Unicode 或多字节下编译时可以工作。除非有要求,否则我不会在这里发布完整的代码,因为我认为这是浪费空间,但这是重要的部分。

#include "Injector.h"
#include <windows.h>
#include <tlhelp32.h>
#include <shlwapi.h>
#include <conio.h>
#include <stdio.h> 
#include "DebugPrint.h"
#include <atlbase.h>

using namespace std;

Injector::Injector(void)
{
}


Injector::~Injector(void)
{
} 

#define CREATE_THREAD_ACCESS (PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ) 

bool Injector::Inject(string procName, string dllName){
   DWORD pID = GetTargetThreadIDFromProcName(procName.c_str()); 
   return Inject(pID,dllName);
}

bool Injector::Inject(DWORD pID, string dllName){
   const char* DLL_NAME = dllName.c_str();

   HANDLE Proc = 0; 
   HMODULE hLib = 0; 
   LPVOID RemoteString, LoadLibAddy; 

   if(!pID) 
      return false; 

   Proc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pID); 
   if(!Proc) 
   { 
      DEBUG_PRINT("OpenProcess() failed: %d", GetLastError()); 
      return false; 
   } 

   LoadLibAddy = (LPVOID)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "LoadLibraryA"); 

   // Allocate space in the process for our <strong class="highlight">DLL</strong> 
   RemoteString = (LPVOID)VirtualAllocEx(Proc, NULL, strlen(DLL_NAME), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); 

   // Write the string name of our <strong class="highlight">DLL</strong> in the memory allocated 
   WriteProcessMemory(Proc, (LPVOID)RemoteString, DLL_NAME, strlen(DLL_NAME), NULL); 

   // Load our <strong class="highlight">DLL</strong> 
   CreateRemoteThread(Proc, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibAddy, (LPVOID)RemoteString, NULL, NULL); 

   CloseHandle(Proc); 
   return true; 
}

DWORD Injector::GetTargetThreadIDFromProcName(const char* ProcName)
{
    PROCESSENTRY32 pe;
    HANDLE thSnapShot;
    BOOL retval, ProcFound = false;

    thSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if(thSnapShot == INVALID_HANDLE_VALUE)
    {
        //MessageBox(NULL, "Error: Unable to create toolhelp snapshot!", "2MLoader", MB_OK);
        DEBUG_PRINT("Error: Unable to create toolhelp snapshot!");
        return false;
    }

    pe.dwSize = sizeof(PROCESSENTRY32);

    retval = Process32First(thSnapShot, &pe);
    while(retval)
    {
#ifdef _UNICODE
        char peSzExeFile[MAX_PATH];
        wcstombs_s(NULL,peSzExeFile,MAX_PATH,pe.szExeFile,MAX_PATH);
#else
        const char* peSzExeFile = pe.szExeFile;
#endif
        DEBUG_PRINT("\nSearching for process: %s ",peSzExeFile);
        if(!strcmp(peSzExeFile, ProcName))
        {
            DEBUG_PRINT(" Found!\n\n");
            return pe.th32ProcessID;
        }
        retval = Process32Next(thSnapShot, &pe);
    }
    return 0;
}

最后,我的测试程序只是调用一些文件 API 来查看注入的 DLL 是否可以捕获它们。如前所述,它确实非常成功地捕获了呼叫。

#include <windows.h>
#include <tchar.h>

using namespace std;

#ifdef _UNICODE
#define printf(X,...) wprintf(TEXT(X),__VA_ARGS__); //I don't want to have to keep replacing printf whenever I switch to Unicode or Multibyte
#endif

#ifdef _DEBUG
int _tmain(int argc, TCHAR* argv[]){ //makes a console.  printf() will have a place to go in this case
#else
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){ //no console
#endif
    Sleep(2000); //let DLL finish loading

    LPCTSTR fileSrc = TEXT("C:\\Users\\jajoach\\Desktop\\hi.txt");
    LPCTSTR fileDst = TEXT("C:\\Users\\jajoach\\Desktop\\hi\\hi.txt");

    printf("Moving file from %s to %s\n",fileSrc,fileDst);
    MoveFile(fileSrc,fileDst);
    Sleep(1000);

    printf("Moving file from %s to %s\n",fileSrc,fileDst);
    MoveFile(fileDst,fileSrc);
    Sleep(1000);

    printf("Copying file from %s to %s\n",fileSrc,fileDst);
    CopyFile(fileSrc,fileDst,true);
    Sleep(1000);

    printf("Deleting file %s\n",fileDst);
    DeleteFile(fileDst);
    Sleep(1000);

    printf("Creating file %s\n",fileDst);
    HANDLE h=CreateFile(fileDst,0,0,NULL,CREATE_NEW,FILE_ATTRIBUTE_NORMAL,NULL);
    Sleep(1000);

    printf("Deleting file %s\n",fileDst);
    CloseHandle(h);
    DeleteFile(fileDst);

    Sleep(5000);

    return 0;
}

Here is the confirmed output for Unicode (as noted by the 'W's) and Release mode, and what I was expecting (but didn't get) from both notepad.exe and explorer.exe. For the record, the test also works for Multibyte, but instead gives 'A's as expected. enter image description here

I had thought that maybe explorer.exe and notepad.exe don't use these API for their file I/O, but my research says otherwise. This https://stackoverflow.com/questions/14235482/ms-detours-express-3-0-is-not-hooking-createfile-win32-api-function-properly post hooks CreateFile in notepad.exe using Detours and reports success for that application. Additionally, ProgramMonitor clearly shows notepad.exe calling CreateFile during a Saveas operation (after many failed requests with different parameters...): enter image description here

暂时不用管explorer.exe;当我执行 Saveas 时,为什么我的挂钩对 notepad.exe 不起作用?

EDIT:我忘了提及,我还将 MoveFile(A/W) 和 CopyFile(A/W) 与测试程序挂钩,但为了简洁起见,我从这篇文章的 DLL 中删除了该代码。


正如OP评论中指出的那样,似乎notepad.exe uses ASLR http://en.wikipedia.org/wiki/Address_space_layout_randomization而你的测试程序却没有。这样,LoadLibraryA 的地址在每个进程中都会不同,并且您的注入代码会失败。

情况是您正在获取的地址LoadLibraryA并假设它与目标进程中的地址空间相同。这通常是正确的,但 ASLR 是专门为使该假设失败而设计的。确实如此......您创建的线程获得了“很可能”无效的地址,并且失败了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 notepad.exe 中挂钩 CreateFile 不会捕获 API 调用 的相关文章

  • 分层架构中的异常处理

    我们正在分层设计中重构 当然还有重新设计 我们的服务 我们有服务操作层 BLL 网络抽象层 gt 处理网络代理 数据抽象层 但我们对我们的异常处理策略有点困惑 我们不想向外界透露太多 BLL 的信息 从其他层到bll就可以了 我们不想让 t
  • 如何动态加载包含非托管代码的原始程序集?(绕过“无法验证的代码失败策略检查”异常)

    我将举一个使用的例子系统 Data SQLite DLL http sqlite phxsoftware com 这是一个包含非托管代码的混合程序集 如果我执行这个 var assembly Assembly LoadFrom System
  • WP8.1 C# 绑定联系人图像

    信息很简单 我正在尝试创建一个可以显示用户联系人的应用程序 我也是一名自学成才的程序员 所以我在某些方面有编程经验 但总体来说我对数据绑定相对较新 首先 我有一个 ListView 控件 其中包含图像绑定
  • StreamReader,C#,peek

    我有一个 StreamReader 它偶尔会检查它是否有更多内容可以从简单的文本文件中读取 它使用 peek 属性 问题是 当我使用 peek 时 位置发生了变化 尽管不应该发生 FileStream m fsReader new File
  • 求一个数的因数。无法得到准确的结果

    有人可以帮助纠正我的算法吗 我已经对几个数字进行了测试 但它没有输出完整的因式分解 对于具有大量因子的数字 它完全失败 int num 20 for int i 2 i lt num i if num i 0 cout lt lt i lt
  • 在 C++ 中使用表达式模板进行符号微分

    如何在 C 中使用表达式模板实现符号微分 一般来说 您需要一种表示符号的方法 即编码的表达式模板 例如3 x x 42 以及一个可以计算导数的元函数 希望您对 C 中的元编程足够熟悉 知道这意味着什么和需要什么 但可以给您一个想法 This
  • 读取STM32 MCU SPI数据寄存器的值

    有很多类似的问题 但似乎没有一个问题完全相同 我正在将 STML4 MCU 连接到 6 轴传感器 LSM6DS3 我已经成功地在 I2C 中实现了所有内容 但想要 SPI 的额外速度 和 DMA 如果我能让这些第一步工作起来的话 因此 第一
  • 是否有一种算法可以在线性时间内计算数组反转?

    我知道有多少倒转 en wikipedia org wiki Inversion 28discrete mathematics 29 in an n 元素数组可以在 O n log n 操作使用增强型归并排序 http www geeksf
  • Qt QML 数据模型似乎不适用于 C++

    我一直在使用中的示例http doc qt digia com 4 7 qdeclarativemodels html http doc qt digia com 4 7 qdeclarativemodels html这是 QML 声明性数
  • 命名空间“Microsoft”中不存在类型或命名空间名称“Practices”

    我正在使用 Microsoft Visual Studio 2005 for c 我的代码中有以下命名空间 using Microsoft Practices EnterpriseLibrary using Microsoft Practi
  • 函数参数评估顺序[重复]

    这个问题在这里已经有答案了 在 C 和 C 中 函数参数的求值是否有固定的顺序 我的意思是 标准怎么说 是吗left to right or right to left 我从书中得到的信息令人困惑 是否有必要function call应该使
  • 带双重检查锁的单例设计模式

    假设您有以下代码 1 为什么我们使用双重检查锁 为什么单锁不够好 请提供详细的例子 2 这种实施方式的主要缺点是什么 我该如何证明呢 Thanks public sealed class SomeSingleton5 private sta
  • 使用 QGraphicsScene 实现流畅的动画

    我希望我的问题并不总是同样的问题 我有一个 QGraphicsScene 它的项目是一些 QGraphicsPixmap 我用一个计时器来移动它们 每秒 SetX 10 我设置 10是因为窗口大100 使用这个解决方案我的动画不流畅 我想我
  • 按值返回的函数的返回语句中的初始化

    我的问题源于深入研究std move in return语句 例如以下示例 struct A A std cout lt lt Constructed lt lt this lt lt std endl A A noexcept std c
  • SQL参数化查询不显示结果

    我的 DataAcess 类中有以下函数 但它没有显示任何结果 我的代码如下 public List
  • C# Julian 日期解析器

    我在电子表格中有一个单元格 它是 Excel 中的日期对象 但当它来自 C1 的 xls 类时 它会变成双精度型 类似于 2009 年 1 月 7 日的 39820 0 我读到这是儒略日期格式 有人可以告诉我如何在 C 中将其解析回 Dat
  • 字符串 c 的二叉树

    我正在尝试实现一个能够在 c 中保存字符串的二叉树 在让代码适用于整数之后 我尝试稍微修改它以处理字符数组 现在我似乎完全破解了代码 但不知道如何破解 任何帮助表示赞赏 include
  • 为什么调试器只显示数组指针中的一个元素?

    首先 我知道new是执行此操作的 C 方法 我只是表明有不止一种方法可以重现此错误 而且两种方法都令人难以置信的令人沮丧 我有两种形式的源文件 我正在尝试调试另一个编程作业 但我并没有寻求帮助 基本上 我正在尝试重新实施set作为一个类 具
  • 如何从尖点库矩阵格式获取原始指针

    我需要从尖点库矩阵格式获取原始指针 例如 cusp coo matrix
  • 类模板的 C++ 静态成员 - 链接器警告“多重定义”[重复]

    这个问题在这里已经有答案了 假设出于某种原因 我想要一个类模板 MyTemp 和一些静态数据成员 smDummyVar Mytemp h ifndef MY TEMP H define MY TEMP H template

随机推荐

  • 渲染画布时单词重叠

    我有一个很长的文本要渲染到画布上 并且单词在 Safari 中重叠且混乱 但在 Chrome 中完美运行 我正在使用最新版本的 html2canvas 库 v1 0 0 rc 3 这是 safari 中的输出 https i stack i
  • 错误消息:看起来您缺少一些运行 Electron 所需的依赖项

    全局安装 electro forge 后npm install g electron forge我在与 electro forge 相关的终端中输入的任何命令都会显示以下消息 它似乎缺少运行 Electron 所需的一些依赖项 确保您安装了
  • 根据国家地理 IP 将观众重定向到其他网站

    假设我有一个网站 www abc com featured abc 并且想将除马来西亚以外其他国家 地区的观众重定向 仅该链接 到 YouTube 视频 但 www abc com 仍然是全球性的 是的 我的服务器已经使用 geoip 模块
  • 使用httpwebrequest从网站获取图像到byte[]

    我想读取网站上 PNG 文件的原始二进制文件并将其存储到 byte 中 到目前为止我有这样的内容 Uri imageUri new Uri http www example com image png Create a HttpWebreq
  • 使用 java processbuilder 运行 bat 文件

    我正在尝试使用 java 进程生成器执行 bat 文件 但它不会启动该进程 请告诉我我在这里做错了什么 当我用 file sh 替换 file bat 时 这段代码在 Linux 环境下工作得很好 final ArrayList
  • Python 随机列表理解

    我有一个类似于以下内容的列表 1 2 1 4 5 2 3 2 4 5 3 1 4 2 我想从此列表中创建 x 个随机元素的列表 其中所选元素都不相同 困难的部分是我想通过使用列表理解来做到这一点 如果 x 3 可能的结果是 1 2 3 2
  • Rails 模型回调(创建/更新后)attribute_was 不起作用

    我正在将 Rails 5 1 应用程序迁移到 Rails 5 2 1 在我的模型中 我在创建或更新模型后使用回调来创建活动日志 很遗憾todo name and todo name was始终相同 当前值 这适用于每个属性和每个模型 还ch
  • 如何为 JMenu 子菜单设置加速器?

    我有一个用户请求将加速器添加到子菜单 JMenu 这将允许用户按下快捷键并使相应的子菜单 折叠 显示其包含的菜单项 我不记得每个人都见过这样的东西 无论是在 Java 还是任何其他语言中 我们的应用程序是使用 Swing 用 Ja va 编
  • 了解 TypeError:获得关键字参数的多个值

    我尝试了不同的方法来使用 args 调用该方法 下面是我的代码片段 def total name args if args print s has total money of Rs d name sum args else print s
  • 如何使用 Linq 的聚合函数 C# 添加到列表

    我有一个类型的对象集合 我想将其转换为不同类型 这可以使用 foreach 轻松完成 但我想弄清楚如何使用 Linq 的聚合函数来完成此操作 问题是所有聚合示例都使用行字符串或 int 类型 它们支持 运算符 我希望累加器类型是一个列表 它
  • 如何从 Java 向 sudo 提供 root 密码?

    我正在尝试编写一个小型 Java 应用程序来覆盖我的 etc resolv conf文件 我使用的是 Ubuntu 12 04 为此 我需要提供我的root密码 myUser myMachine sudo vim etc resolv co
  • Android:如何测量ListView的总高度[重复]

    这个问题在这里已经有答案了 我需要测量 ListView 的总高度 但似乎我不断得到错误的值 我正在使用这段代码 public static void setListViewHeightBasedOnChildren ListView li
  • 如何配置 karma-runner(也称为睾丸)与closure-library 一起使用

    我正在尝试将 karma runner 与 mocha 测试框架结合使用来测试使用closure library 和 angularjs 构建的应用程序 我总是得到namespace Application is not defined 提
  • 如何将动画视图捕获为视频

    我正在使用反应本机图像来显示网络上某些网址的一些图像 现在 根据某些条件 我以不同的时间间隔更新图像网址 我想将整个过渡捕获到视频文件中 但是 我找不到合适的 android API 用于本机端 或react native 包来实现这一点
  • 如何测量函数运行的时间?

    我想查看一个函数运行了多长时间 所以我在表单上添加了一个计时器对象 并得到了以下代码 private int counter 0 Inside button click I have timer new Timer timer Tick n
  • AWS S3 ACL 公共读写:安全问题

    我想要一些关于 AWS S3 ACL 公共读写的解释 来自docs http docs aws amazon com AmazonS3 latest dev acl overview html 所有者获得 FULL CONTROL AllU
  • R 图 - 具有颜色渐变的正态曲线

    How can I make curves with a color gradient in R Take a look at this flame 它应该看起来像那样 我尝试制作一条法线曲线 然后制作另一条法线曲线 但从技术上讲 你无法用
  • 等待文件上传

    我遇到了困难 我正在尝试使用 WatiN 上传文件 我可以加载上传框 但它很快就消失了 目前我的代码的最后一行是 ie FileUpload Find ById profile file Click 它加载对话框以选择图片但消失 是否可以自
  • Fiware-Orion:订阅所有实体

    在 Orion 1 4 0 中我使用以下 JSON 来订阅对于所有实体 description Update average rating subject entities idPattern type condition attrs no
  • 在 notepad.exe 中挂钩 CreateFile 不会捕获 API 调用

    我的最终目标是通过在 kernel32 dll 中挂钩文件 api 来跟踪 explorer exe 完成的文件操作 但是我尚未实现该操作 explorer exe 没有调用 API 或者我这边出了问题 为了弄清楚发生了什么 我设置了一个目