hkx3upper / FOKS-TROT

It's a minifilter used for transparent-encrypting.
GNU General Public License v3.0
286 stars 143 forks source link

特殊读写序列下文件无法写入,数据全部丢失 #44

Closed wangzhankun closed 2 years ago

wangzhankun commented 2 years ago

环境

bug复现步骤

  1. 运行驱动
  2. 运行TEST.exe向机密文件夹下写入文件,运行完毕后关闭该程序
  3. 使用机密进程notepad++.exe打开该文件,并删除全部内容,保存。关闭程序
  4. 再次运行TEST.exe向同一个文件写入数据,运行完毕后关闭该程序
  5. 使用机密进程notepad++.exe打开该文件,发现无任何数据
  6. 关闭驱动
  7. 使用notepad++.exe打开该文件,无任何数据且没有文件标识尾

日志

DESKTOP-RPF0QIQ.LOG

测试程序

#include <Windows.h>
#include <cstdio>
using namespace std;
int main(int argc, char const* argv[])
{
    // CreateFile 共享打开文件
    HANDLE hFile = CreateFileW(
        L"C:\\Users\\wangzhankun\\Desktop\\testdata\\hello.txt",
        //L"C:\\Users\\wangzhankun\\Desktop\\123src\\hello.txt",
        GENERIC_WRITE,
        FILE_SHARE_READ,
        NULL,
        OPEN_ALWAYS,
        FILE_FLAG_WRITE_THROUGH,
        NULL);

    // 判断文件是否打开成功
    if (hFile == INVALID_HANDLE_VALUE)
    {
        printf("CreateFile failed with error %d\n", GetLastError());
        goto EXIT;
    }

    // ReadFile 读取文件 
    for (int i = 0; i < 30; i++)
    {

        DWORD dwBytesWrite = 0;
        WCHAR szBuffer[100] = { L"1\n" };
        BOOL bWrite = WriteFile(hFile, szBuffer, wcslen(szBuffer) * sizeof(WCHAR), &dwBytesWrite, NULL);
        if (bWrite == FALSE)
        {
            printf("WriteFile failed with error %d\n", GetLastError());
            goto EXIT;
        }

        printf("%dth\n", i);
        Sleep(1000); // 每隔1秒读取一次
    }

EXIT:
    // CloseHandle 关闭文件
    if (hFile != INVALID_HANDLE_VALUE)
        CloseHandle(hFile);

    system("pause");

    return 0;
}
hkx3upper commented 2 years ago

可是日志里显示,你并没有把Test.exe设置为授权进程,所以之后用notepad++删除以后,非授权进程Test无法写入文件。 不过我还是更新了一下,文件的EOF==0,默认它是明文了。

wangzhankun commented 2 years ago

可是日志里显示,你并没有把Test.exe设置为授权进程,所以之后用notepad++删除以后,非授权进程Test无法写入文件。 不过我还是更新了一下,文件的EOF==0,默认它是明文了。

问题还是没解决。可能是文档上传错了。你那边可以调试一下。

DESKTOP-RPF0QIQ.LOG

wangzhankun commented 2 years ago

而且比较奇怪的是,在打开文件时,将FILE_FLAG_WRITE_THROUGH用0替换,缓冲写的时候,也是有问题。而且我在驱动里面看是确确实实有数据被写入的: image 可是打开文件数据就是不对。

hkx3upper commented 2 years ago

现在打开的数据是怎么不对?只有4个字节? 我没有复现出来,不过我改了俩地方,你再试试?把完整的日志传上来吧,我看看。

wangzhankun commented 2 years ago

最新的代码仍然有问题。录屏和日志见百度网盘。(你有阿里云盘吗?百度的你那边下载的时候太慢了) 链接:https://pan.baidu.com/s/1u9d2q6wqoHjElE96e4fadw 提取码:s5ql

hkx3upper commented 2 years ago

视频里对应的Test.exe也发给我,没事,我还有一个周的百度网盘会员

wangzhankun commented 2 years ago

test.zip

wangzhankun commented 2 years ago

test.zip

另外用这个程序做测试的时候还会有一个bug。我在录屏里面运行了两次TEST.exe。两次运行之间把生成的hello.txt文件删除了,如果这个文件不删除,连续运行两次TEST.exe,也会导致文件损坏。

hkx3upper commented 2 years ago

只能解决一部分问题,现在更新了CurrentByteOffset,不会有一块乱码了。但还是会有丢掉最后一块数据的情况。 不知为什么,这个WRITE_THROUGH,并没有在CacheManager写入文件之前更新Fcb里面的FileSize,这就导致Write中取到的FileSize是个过时的值,所以最后一块数据并没有正常加密写入。反而在PagingIo写完,返回FSD,然后FSD返回CachedIo时更新了FileSize。 我没找到原因......这可能取决于ntfs对于FileSize的更新方式。 不改了不改了,就这样吧。

wangzhankun commented 2 years ago

只能解决一部分问题,现在更新了CurrentByteOffset,不会有一块乱码了。但还是会有丢掉最后一块数据的情况。 不知为什么,这个WRITE_THROUGH,并没有在CacheManager写入文件之前更新Fcb里面的FileSize,这就导致Write中取到的FileSize是个过时的值,所以最后一块数据并没有正常加密写入。反而在PagingIo写完,返回FSD,然后FSD返回CachedIo时更新了FileSize。 我没找到原因......这可能取决于ntfs对于FileSize的更新方式。 不改了不改了,就这样吧。

我这边测试好像没有数据丢失的情况了。但是连续两次运行TEST.exe导致文件损坏的问题还是存在。

第一次运行

image

第二次运行

image

BUG复现步骤

  1. 删除hello.txt
  2. 运行TEST.exe
  3. 再次运行TEST.exe

(如果在2、3步之间使用机密进程打开该文件,或者删除hello.txt,第二次运行TEST.EXE并不会导致文件损坏)

wangzhankun commented 2 years ago

bug日志: DESKTOP-RPF0QIQ.LOG

wangzhankun commented 2 years ago

test.zip

另外用这个程序做测试的时候还会有一个bug。我在录屏里面运行了两次TEST.exe。两次运行之间把生成的hello.txt文件删除了,如果这个文件不删除,连续运行两次TEST.exe,也会导致文件损坏。

感觉是文件标识尾写入太慢的问题。

image

我建议是把这一行的代码Status = KeDelayExecutionThread(KernelMode, FALSE, &Interval);放到下面去。就是先判断是否有进程正在读写。把代码放到下面就没问题了。

hkx3upper commented 2 years ago

再试试吧,问题处在了Write中的LengthReturned值不准确上,因为WRITE_THROUGH的原因。 设置标识WRITE_THROUGH,ntfs并不会在PagingIo之前更新Fcb->FileSize,这导致我们的Write中LengthReturned取了一个过时的值。 它使用TopLevelIrpContext+184截断PagingIo的,如下图 Ntfs-Truncate1 这个数据就是TopLevelIrpContext+184的值,0x10是个准确的值 Ntfs-Truncate 我在StreamContext中加了一个变量WriteThroughFileSize,用来在这种情况下替代Fcb->FileSize做判断。

hkx3upper commented 2 years ago

数据丢失是指写入5次 1th\n 2th\n ......5th\n 第五次的5th\n丢失了

wangzhankun commented 2 years ago

我用beyond compare做了对比,丢失的数据还挺多的。

image
wangzhankun commented 2 years ago

有没有可能是valid_data_length的问题呢?

wangzhankun commented 2 years ago

我发现即使是不指定FILE_FLAG_WRITE_THROUGH,也是会出现数据丢失的问题。而且比指定了FILE_FLAG_WRITE_THROUGH要丢失的数据更多。指定了FILE_FLAG_WRITE_THROUGH之后,丢失的数据是最后一次写入的数据,而不指定该标志,丢失的数据是随机的。

    HANDLE hFile = CreateFileA(
        file_path.c_str(),
        GENERIC_WRITE,
        FILE_SHARE_READ,
        NULL,
        OPEN_ALWAYS,
        //FILE_FLAG_WRITE_THROUGH,
        0,
        NULL);
wangzhankun commented 2 years ago

我发现即使是不指定FILE_FLAG_WRITE_THROUGH,也是会出现数据丢失的问题。而且比指定了FILE_FLAG_WRITE_THROUGH要丢失的数据更多。指定了FILE_FLAG_WRITE_THROUGH之后,丢失的数据是最后一次写入的数据,而不指定该标志,丢失的数据是随机的。

  HANDLE hFile = CreateFileA(
      file_path.c_str(),
      GENERIC_WRITE,
      FILE_SHARE_READ,
      NULL,
      OPEN_ALWAYS,
      //FILE_FLAG_WRITE_THROUGH,
      0,
      NULL);

对于这个问题,在PreWrite中添加以下代码就能解决了。 image

hkx3upper commented 2 years ago

ok,加上了,虽然我没太搞懂这么做的原因。

hkx3upper commented 2 years ago

wangzhankun同学,我开了一个分支"wangzhankun",并且重新把你加上了Co,你可以把之前PR的代码提交到该分支。 最近有点忙,你自己提交或修改即可。 @wangzhankun

hkx3upper commented 2 years ago

除了修复目前还存在的各种bug之外,(可以参考Readme中的Unfixed), 如果你感兴趣,想添加一些新的功能, 可以做一下权限的细分,比如增加用户权限,用户组权限的控制,这里要注意用户组>用户>进程的权限顺序, 做一下驱动的保护,比如防止别的调试软件获取明文,以及防止其他恶意minifilter驱动加载, 做驱动对进程的注入,实现防截屏之类的功能等等, 这些都是可以参考的方向。 另外,我写代码很大程度参考Windows-driver-samples\filesys\fastfat的源码实现,以及Windows内核情景分析、Windows内核原理与实现和Windows NT File System Internals - A Developer's 三本书,再就是Winnt4和WRK泄露的Windows源码,你要是觉得有用可以看看。

wangzhankun commented 2 years ago

https://github.com/hkx3upper/FOKS-TROT/issues/25#issuecomment-1220696824