3

我一直在研究一种方法来防止我的用户意外进入我的应用程序的数据目录。

我的应用程序使用文件夹来存储结构化项目。文件夹内部结构是批评性的,不应该搞砸。我希望我的用户看到这个文件夹作为一个整体并且不能打开它(就像一个 Mac 包)。

有没有办法在 Windows 上做到这一点?

从当前答案编辑

当然,我并不是要阻止我的用户访问他们的数据,只是保护他们免于意外破坏数据完整性。因此不需要加密或密码保护。

感谢大家对 .Net 的回答,但不幸的是,这主要是一个 C++ 项目,对 .Net 框架没有任何依赖。

我提到的数据不是光,它们是从电子显微镜获得的图像。这些数据可能很大(~100 MiB 到~1 GiB),因此不能将所有内容加载到内存中。这些是巨大的图像,因此存储必须提供一种通过一次访问一个文件而不将整个存档加载到内存中来增量读取数据的方法。

此外,该应用程序主要是遗留的,一些我们甚至不负责的组件。允许我保留当前 IO 代码的解决方案是可取的。

Shell Extension 看起来很有趣,我将进一步研究解决方案。

LarryF,您能详细说明 Filter Driver 或 DefineDOSDevice 吗?我不熟悉这些概念。

4

10 回答 10

1

在您的程序内部还是外部?

有办法,但没有一个是容易的。您可能会查看文件系统上的过滤器驱动程序。

于 2008-12-12T18:56:58.837 回答
1

您可以做几件事:

一件事是您可以创建一个 FolderView Windows Shell 扩展,该扩展将为您的关键文件夹创建自定义视图。通过创建一个自定义的 FolderView,您可以使用一行文本“Nothing to see here”将文件夹设置为空白,或者您可以执行更复杂的操作,例如使用相同方法的 GAC 查看器。这种方法会相当复杂,但是可以通过使用类似CodeProject文章的库之类的东西作为基础来减轻这种复杂性。

另一种解决方案是使用 ZIP 虚拟文件系统,这将要求您替换任何直接使用 System.IO 的代码来使用其他东西。ASP.NET 2.0 正是出于这个原因,您可以很容易地在此基础上进行构建,请查看这篇关于实现 VirtualPathProvider 的MSDN 文章。

于 2008-12-12T19:16:52.650 回答
1

如果您采用 ZIP 文件方法(我为您考虑过,但没有提及),我建议您使用 deflate 算法,但使用您自己的文件系统...查看类似 TAR 格式的内容。然后,只需编写代码以在将所有 I/O 写入磁盘时通过 Inflate/Deflate 算法。我不会使用 ZIP“FORMAT”,因为它太容易查看文件,找到 PK 作为前两个字节,然后解压缩文件....

我最喜欢 Joshperry 的建议。

当然,您也可以编写一个将所有数据存储在一个文件中的设备驱动程序,但同样,我们正在研究一个驱动程序。(我不确定您是否可以在驱动程序之外实现它。您可能可以,并且在您的程序内部调用 DefineDOSDevice,给它一个只有您的代码可以访问的名称,它将被视为一个普通的文件系统。 )。我会尝试一些想法,如果它们有效,我会给你一个样本。现在你让我感兴趣了。

于 2008-12-12T20:34:42.617 回答
1

结构化存储专为您描述的场景而设计:

结构化存储通过将单个文件作为称为存储和流的对象的结构化集合处理,在 COM 中提供文件和数据持久性。

“存储”类似于文件夹,“流”类似于文件。基本上,您有一个文件,当使用结构化存储 API 访问它时,它的行为和外观就像一个完整的、自包含的文件系统。

但请注意:

对 COM 技术的深刻理解是结构化存储的开发使用的先决条件。

于 2010-02-08T00:07:20.900 回答
0

您可以将项目目录包装到 .zip 文件中并将数据存储在那里,就像使用 .jar 一样(我知道 .jar 几乎是只读的,这是为了示例)。做一个非标准的扩展,这样双击就不会立即生效,完成。;-)

当然,这意味着您必须包装所有文件 IO 才能使用 .zip,这取决于您的程序是如何构建的,这可能很乏味。Java 已经完成了:TrueZip。也许你可以以此为灵感?

如果您受到诱惑 - 我不建议摆弄文件夹权限,原因很明显,这无济于事。

于 2008-12-12T18:59:35.307 回答
0

您可以使用隔离存储。

http://www.ondotnet.com/pub/a/dotnet/2003/04/21/isolatedstorage.html

它并不能解决所有问题,但它确实使应用程序数据免受伤害。

于 2008-12-12T19:18:30.773 回答
0

请记住:如果您将其存储在文件系统中,用户将始终能够看到它。篡改资源管理器,我改用 cmd.exe。或总司令。或者别的什么。

如果您不希望人们弄乱您的文件,我建议您

  • 加密它们以防止篡改文件
  • 将它们放入存档(即 ZIP)中,可能对其进行密码保护,然后在运行时压缩/解压缩(我会查找快速修改存档的算法)

这当然不是完全保护,但它的实现相当简单,不需要您在操作系统中安装时髦的东西,并且应该远离大多数好奇的用户。

当然,如果不控制计算机本身,您将永远无法完全控制用户计算机上的文件。

于 2008-12-12T19:22:24.963 回答
0

我见过使用 Tomalak 建议的 zip 存档作为“项目文件”的软件(Visual Paradigm 的 Agilian)。Zip 文件很好理解,并且使用非标准文件扩展名确实可以防止临时用户弄乱“文件”。这样做的一大优势是,在发生损坏的情况下,可以使用标准工具来解决问题,您不必担心创建特殊工具来支持您的主应用程序。

于 2008-12-12T19:26:12.990 回答
0

我很高兴听到您在 C++ 中执行此操作。似乎没有人认为 C++ 是“必要的”了。这都是 C# 和 ASP.NET ......即使我在一个全 C# 的房子里工作,当我发誓我永远不会切换时,因为 C++ 做了我曾经需要做的所有事情,然后是一些。我已经成年了,可以清理自己的记忆了!呵呵..无论如何,回到手头的问题...

DefineDOSDevice()是一种用于分配驱动器号、端口名称(LPT1、COM1 等)的方法。你给它一个名字,一些标志和一个处理这个设备的“路径”。但是,不要让它愚弄你。它不是文件系统路径,而是 NT 对象路径。我确定您已经将它们视为“\Device\HardDisk0”等。您可以使用 sysinternals 中的 WinObj.exe 来了解我的意思。无论如何,您可以创建一个设备驱动程序,然后将 MSDOS 符号链接指向它,然后您就可以关闭并运行了。但是,对于最初的问题,这似乎需要做很多工作。

一个典型的目录中有多少这些兆到千兆字节的文件?您最好将所有文件粘贴在一个巨型文件中,并在其旁边存储一个索引文件(或每个文件的标题),该文件指向“虚拟文件系统”文件中的下一个“文件”。

一个很好的例子可能是查看 Microsoft MSN 存档格式。当我在一家 AV 公司工作时,我颠倒了这种存档格式,它实际上非常有创意,但非常简单。可以在一个文件中完成所有操作,如果您想要花哨,您可以数据存储在 RAID 5 类型配置中的 3 个文件中,因此如果 3 个文件中的任何一个被冲洗掉,您可以重建其他文件。另外,用户只会在一个目录中看到 3 个非常大的文件,并且无法访问单个(内部)文件。

我为您提供了解压缩这些 MSN 存档格式之一的代码。我没有创建一个的代码,但是从提取源中,您可以毫无问题地构建/编写一个。如果文件被删除和/或经常重命名,则可能会导致文件中的已用空间出现问题,必须不时进行修剪。

这种格式甚至支持 CRC 字段,因此您可以测试文件是否正常。我永远无法完全逆转微软用来对数据进行 CRC 校验的算法,但我有一个很好的主意。

您将无法保留当前的 ​​I/O 例程,这意味着 CreateFile() 不仅能够打开存档中的任何文件,但是,凭借 C++ 的超酷,您可以覆盖 CreateFile 调用来实现您的存档格式。

如果您需要他的帮助,并且这是一个足够大的问题,也许我们可以离线讨论并为您找到解决方案。

我不反对给你写一个 FileSystemDriver,但为此,我们必须开始谈论补偿。就像我现在所做的那样,我非常乐意免费为您提供指导和想法。

我不确定在这里给你我的电子邮件地址是否符合犹太教规,我不确定 SO 在这方面的政策,因为我们可能会谈论潜在的工作/征集,但这不是我唯一的意图。我宁愿先帮你找到自己的解决方案。

在查看设备驱动程序之前,请下载 WinDDK。它到处都是驱动程序示例。

如果你想知道我为什么这么关心这个,那是因为我多年来一直在写一个类似于这个的驱动程序,它必须是 Windows与 OSX 兼容,这将允许用户保护驱动器卷(USB 密钥、可移动卷),而无需安装任何驱动程序或复杂(而且体积庞大,有时令人讨厌)的软件。近年来,很多硬件制造商都在做类似的事情,但我认为安全性并不是那么安全。我正在研究使用 RSA 和 AES,与 GPG 和 PGP 的工作方式完全相同。最初,我被告知(我相信,但没有证据)将用于保护 MP3 文件的内容。由于它们将以加密格式存储,因此如果没有正确的密码短语,它们根本无法工作。但是,我也看到了它的其他用途。(当一个 16 兆(是 MEG)USB 密钥的成本超过 100 美元左右时,这又回来了)。

该项目还与我的石油和天然气行业 PC 安全系统一起使用,该系统使用类似于智能卡的东西,只是更容易使用、重复使用/重新发行、不可能(阅读:非常困难和不太可能)破解,并且我可以在家里自己的孩子身上使用它!(因为总是争论谁在电脑上的时间,谁得到的最多,等等,等等,等等……)

呼……我想我在这里跑题了。无论如何,这里是 Microsoft MSN 存档格式的示例。看看你是否可以使用这样的东西,知道你总是可以通过在主文件中解析/搜索请求的文件时跟踪文件中的偏移量来“跳过”文件;或在内存中保存的预解析数据中。而且由于您不会在内存中加载原始二进制文件数据,因此您唯一的限制可能是 32 位机器上的 4gb 文件限制。

MARC(Microsoft MSN 存档)格式的布局(松散)如下:

  • 12字节头(只有一个)
    • 文件魔术
    • 马克版
    • 文件数(在下表中)
  • 68 字节文件表头(其中 1 到 Header.NumFiles)
    • 文件名
    • 文件大小
    • 校验和
    • 原始文件数据的偏移量

现在,在 12 字节文件表条目中,32 位用于文件长度和偏移量。对于非常大的文件,您可能必须将其增加到 48 或 64 位整数。

这是我为处理这些而编写的一些代码。

#define MARC_FILE_MAGIC         0x4352414D // In Little Endian
#define MARC_FILENAME_LEN       56 //(You'll notice this is rather small)
#define MARC_HEADER_SIZE        12
#define MARC_FILE_ENT_SIZE      68

#define MARC_DATA_SIZE          1024 * 128 // 128k Read Buffer should be enough.

#define MARC_ERR_OK              0      // No error
#define MARC_ERR_OOD             314    // Out of data error
#define MARC_ERR_OS              315    // Error returned by the OS
#define MARC_ERR_CRC             316    // CRC error

struct marc_file_hdr
{
    ULONG            h_magic;
    ULONG            h_version;
    ULONG            h_files;
    int              h_fd;
    struct marc_dir *h_dir;
};

struct marc_file
{
    char            f_filename[MARC_FILENAME_LEN];
    long            f_filesize;
    unsigned long   f_checksum;
    long            f_offset;
};

struct marc_dir
{
    struct marc_file       *dir_file;
    ULONG                   dir_filenum;
    struct marc_dir        *dir_next;
};

这让您了解我为它们编写的标题,这里是 open 函数。是的,它缺少所有的支持电话、错误例程等,但你明白了。请原谅 C 和 C++ 代码风格的混合。我们的扫描仪是由许多类似这样的问题组成的集群……我使用了诸如 open()、fopen() 之类的古老调用,以与代码库的其余部分保持标准。

struct marc_file_hdr *marc_open(char *filename)
{
    struct marc_file_hdr *fhdr  = (struct marc_file_hdr*)malloc(sizeof(marc_file_hdr));
    fhdr->h_dir = NULL;

#if defined(_sopen_s)
    int errno = _sopen_s(fhdr->h_fd, filename, _O_BINARY | _O_RDONLY, _SH_DENYWR, _S_IREAD | _S_IWRITE);
#else
    fhdr->h_fd = open(filename, _O_BINARY | _O_RDONLY);
#endif
    if(fhdr->h_fd < 0)
    {
        marc_close(fhdr);
        return NULL;
    }

    //Once we have the file open, read all the file headers, and populate our main headers linked list.
    if(read(fhdr->h_fd, fhdr, MARC_HEADER_SIZE) != MARC_HEADER_SIZE)
    {
        errmsg("MARC: Could not read MARC header from file %s.\n", filename);
        marc_close(fhdr);
        return NULL;
    }

    // Verify the file magic
    if(fhdr->h_magic != MARC_FILE_MAGIC)
    {
        errmsg("MARC: Incorrect file magic %x found in MARC file.", fhdr->h_magic);
        marc_close(fhdr);
        return NULL;
    }

    if(fhdr->h_files <= 0)
    {
        errmsg("MARC: No files found in archive.\n");
        marc_close(fhdr);
        return NULL;
    }

    // Get all the file headers from this archive, and link them to the main header.
    struct marc_dir *lastdir = NULL, *curdir = NULL;
    curdir = (struct marc_dir*)malloc(sizeof(marc_dir));
    fhdr->h_dir = curdir;

    for(int x = 0;x < fhdr->h_files;x++)
    {
        if(lastdir)
        {
            lastdir->dir_next = (struct marc_dir*)malloc(sizeof(marc_dir));
            lastdir->dir_next->dir_next = NULL;
            curdir = lastdir->dir_next;
        }

        curdir->dir_file = (struct marc_file*)malloc(sizeof(marc_file));
        curdir->dir_filenum = x + 1;

        if(read(fhdr->h_fd, curdir->dir_file, MARC_FILE_ENT_SIZE) != MARC_FILE_ENT_SIZE)
        {
            errmsg("MARC: Could not read file header for file %d\n", x);
            marc_close(fhdr);
            return NULL;
        }
        // LEF: Just a little extra insurance...
        curdir->dir_file->f_filename[MARC_FILENAME_LEN] = NULL;

        lastdir = curdir;
    }
    lastdir->dir_next = NULL;

    return fhdr;
}

然后,您有简单的提取方法。请记住,这仅用于病毒扫描,因此没有搜索例程等。这旨在简单地转储文件,对其进行扫描,然后继续。下面是我相信 Microsoft 使用的 CRC 代码例程,但我不确定他们 CRC 到底是什么。它可能包括标题数据+文件数据等。我只是没有足够的心思回去尝试扭转它。无论如何,如您所见,这种存档格式没有压缩,但添加起来非常容易。如果您愿意,可以提供完整的源代码。(我认为剩下的就是 close() 例程,以及调用和提取每个文件的代码等!!)

bool marc_extract(struct marc_file_hdr *marc, struct marc_file *marcfile, char *file, int &err)
{
    // Create the file from marcfile, in *file's location, return any errors.
    int ofd = 0;
#if defined(_sopen_s)
     err = _sopen_s(ofd, filename, _O_CREAT | _O_SHORT_LIVED | _O_BINARY | _O_RDWR, _SH_DENYNO, _S_IREAD | _S_IWRITE);
#else
    ofd = open(file, _O_CREAT | _O_SHORT_LIVED | _O_BINARY | _O_RDWR);
#endif

    // Seek to the offset of the file to extract
    if(lseek(marc->h_fd, marcfile->f_offset, SEEK_SET) != marcfile->f_offset)
    {
        errmsg("MARC: Could not seek to offset 0x%04x for file %s.\n", marcfile->f_offset, marcfile->f_filename);
        close(ofd);
        err = MARC_ERR_OS; // Get the last error from the OS.
        return false;
    }

    unsigned char *buffer = (unsigned char*)malloc(MARC_DATA_SIZE);

    long bytesleft = marcfile->f_filesize;
    long readsize = MARC_DATA_SIZE >= marcfile->f_filesize ? marcfile->f_filesize : MARC_DATA_SIZE;
    unsigned long crc = 0;

    while(bytesleft)
    {
        if(read(marc->h_fd, buffer, readsize) != readsize)
        {
            errmsg("MARC: Failed to extract data from MARC archive.\n");
            free(buffer);
            close(ofd);
            err = MARC_ERR_OOD;
            return false;
        }

        crc = marc_checksum(buffer, readsize, crc);

        if(write(ofd, buffer, readsize) != readsize)
        {
            errmsg("MARC: Failed to write data to file.\n");
            free(buffer);
            close(ofd);
            err = MARC_ERR_OS; // Get the last error from the OS.
            return false;
        }
        bytesleft -= readsize;
        readsize = MARC_DATA_SIZE >= bytesleft ? bytesleft : MARC_DATA_SIZE;
    }

    // LEF:  I can't quite figure out how the checksum is computed, but I think it has to do with the file header, PLUS the data in the file, or it's checked on the data in the file
    //       minus any BOM's at the start...  So, we'll just rem out this code for now, but I wrote it anyways.
    //if(crc != marcfile->f_checksum)
    //{
    //    warningmsg("MARC: File CRC does not match.  File could be corrupt, or altered.  CRC=0x%08X, expected 0x%08X\n", crc, marcfile->f_checksum);
    //    err = MARC_ERR_CRC;
    //}

    free(buffer);
    close(ofd);

    return true;
}

这是我假设的 CRC 例程(我可能从 Stuart Caie 和libmspack偷了这个,我不记得了):

static unsigned long marc_checksum(void *pv, UINT cb, unsigned long seed)
{
    int count = cb / 4;
    unsigned long csum = seed;
    BYTE *p = (BYTE*)pv;
    unsigned long ul;

    while(count-- > 0)
    {
        ul = *p++;
        ul |= (((unsigned long)(*p++)) <<  8);
        ul |= (((unsigned long)(*p++)) << 16);
        ul |= (((unsigned long)(*p++)) << 24);
        csum ^= ul;
    }

    ul = 0;
    switch(cb % 4)
    {
        case 3: ul |= (((unsigned long)(*p++)) << 16);
        case 2: ul |= (((unsigned long)(*p++)) <<  8);
        case 1: ul |= *p++;
        default: break;
    }
    csum ^= ul;

    return csum;
}                                                                                     

好吧,我认为这篇文章现在已经够长了……如果您需要帮助或有任何问题,请联系我。

于 2008-12-15T23:18:49.007 回答
0

看起来FUSE 的一些 Windows 端口开始出现。我认为这将是最好的解决方案,因为它可以让我保持遗留代码(相当大)不变。

于 2010-02-07T23:17:52.713 回答