程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> 用Delphi編寫U盤整盤數據清除程序

用Delphi編寫U盤整盤數據清除程序

編輯:Delphi

由於U盤小巧易攜、使用方便、容量適度,使用頻度較其他移動存儲介質要高,信息安全隱患也要大得多。黑客、病毒和木馬都將目標瞄准了移動存儲介質特有的“信息安全軟肋”,目前移動存儲介質的擺渡、木馬、遠程控制程序多達百種,很多還采用了隱藏技術,防護的難度越來越大。一般的刪除不能使原文檔徹底清除,有時甚至是“文件粉碎”這樣的工具也不能保證萬無一失,因為像很多Word這樣的應用軟件在打開文檔時,會生成一個臨時文件,所有操作都在臨時文件上進行,只在關閉時才將臨時文件拷回到原文件的名錄上。這樣,在一個存儲介質上會存在多個“有實無名”的隱身文檔副本,沒有名錄信息對操作系統而言是不可見的,一般方法訪問不到,但是通過特殊的技術可以將“實體”的大部分內容恢復。因此,針對U盤流動性大、數據易洩漏的特點,在保護隱私、跨網使用和清除擺渡洩密的需求下,開發出對U盤數據安全可靠清除的程序很有必要。目前市場上的數據清除設備動辄上萬元,基本上是使用嵌入式系統開發的專用產品,本文介紹的方法和程序在原理和實際效果上與其沒有差別,在某些方面甚至有更好的拓展性、適應性和靈活性。當然,技術到產品的躍升還需做很多艱苦的工作,希望此文能拋磚引玉、與大家共享技術帶來的便利。
 
2 文件系統的原理
無論是FAT還是NTFS文件系統,盡管安全機制和存取的效率差別很大,但是原理基本相同,都是由索引部分和文件數據實體部分組成,索引提供名錄的有效存取、檢索,數據實體存放文件的真正內容。為了提高文件系統對文件處理的效率,在刪除文件時並不是將上述的兩部分全部消除,只是在索引部分標記為“已刪除”,而文件的實體數據依然原封不動地存在介質上,從表面上已經查不到這些刪除文件的存在,但是通過一定的方法,還是可以一定程度上進行恢復,這是多種文件恢復工具的工作原理。
即使采用高級格式化,也只是將文件系統的索引部分清除,而數據實體部分基本上沒有涉及,仍可進行數據恢復和提取。
 
3 U盤的特點
U盤與磁介質移動盤不同,U盤是內部的FLASH(電可擦除芯片組)的電荷來維持電位高低,從而進行二進制數據存儲的,一旦電荷發生變化,則二進制數據也會發生根本性的改變,或者說物理上不太可能恢復和還原。但是磁介質不同,由於每次的磁頭位置不可能完全重合,可能會有微弱的磁化殘留,也就是上一次寫的數據雖然被覆蓋,在磁力顯微鏡下仍可看出其殘留的痕跡,至於是否能將被覆蓋的上一次信息完整地恢復出來,學術界目前爭論不休,鮮見實測檢驗的權威報告。光介質存儲也存在類似的問題,只是使用頻度遠低於前兩者,還未引起重視。

U盤因為電荷清除後信息就消失了,不會有類似弱磁殘留的問題,可以保證清除效果。
 
4 關鍵技術
4.1 U盤容量的獲取
獲取U盤容量有兩個API函數,第一個是:
GetDiskFreeSpaceEx(Directory:PChar;var FreeAvailable,TotalSpace:TLargeInteger;TotalFree: PLargeInteger): Bool;//此函數在SysUtils單元
這個函數返回的容量(int64類型變量TotalSpace所返回的值)就是U盤標寫的容量,但是這個函數必須是在U盤已經格式化後才能正常工作,否則返回的容量是錯誤值。
另一個函數是:
DeviceIoControl(hDevice: THandle; dwIoControlCode: DWORD; lpInBuffer: Pointer;nInBufferSize: DWORD;lpOutBuffer: Pointer; nOutBufferSize: DWORD;var lpBytesReturned: DWORD; lpOverlapped: POverlapped): BOOL;//這個函數在Windows單元。
這個函數是向設備級發送請求,返回的是底層設備的原始值。在使用這個函數時,不要求U盤設備已經格式化,在“屬性”裡的“文件系統”一欄可以顯示為“RAW”,即沒有任何格式的移動盤。筆者在實驗中發現,這個函數是在設備層進行請求,有時在資源管理器中不顯示該盤符時,使用物理硬設備符(如用文件方式打開設備生成句柄hDevice時,用'\\.\PHYSICALDRIVE1'而不是'\\.\u:'),仍然可以得到U盤的容量,這對U盤數據恢復很有意義。但是這個函數與前一個函數相比,其返回的容量總是小於前函數的結果,並且沒有規律。
4.2 真實容量分析
要對U盤進行完全徹底的數據清除,首先必須要知道它的容量。由於U盤本身的特性,一般在商標上標寫的容量並不一定是真正的容量。原因有三:1、正規品牌的U盤標寫的是正常使用的容量,在其內部還有一部分是作為壞區補償的備用容量,當檢測有壞片時,U盤內部的電路會將壞區與補償部分進行互相置換,所以其真正容量會大於標寫的容量;2、某些不良商家會將小容量的U盤Mask成大容量的盤,這時所注的容量也不正確;3、目前很多黑客、木馬會將大容量的U盤改寫成小容量,使一部分存儲區成為一般手段無法訪問的隱蔽區,作為惡意信息的真正藏身地。表面上似乎如何檢測U盤真正的容量是關鍵,實質上能否完全清除U盤上的所有存儲信息才是安全保障的根本所在。
4.3 覆蓋U盤所有存儲空間
由上可知,無論哪個函數,包括系統顯示的的容量,都不是U盤真正的容量,而且多出來的容量不同廠家的產品各不相同。筆者通過測試,發現多出來的容量和額定的容量在連續的地址空間,即多出來的部分緊接在額定的後面。
因此,有效的解決方法是:當清除到額定容量時,並不停止,繼續清除,直到發生“寫錯誤”時才退出清除過程。(筆者曾對一枚128M的U盤清除到135M時才顯示清除完畢)
看到這裡讀者可能會問:如果用這種解決方法,獲取U盤容量有什麼意義?實際上獲取容量是為了大概估算清除時間,在程序設計時進度條控件需要總量來計算進度的百分比,前述介紹的U盤容量獲取的結果與真實容量差別不大,可以作為參考值。
 
5 主要代碼實現
5.1 程序界面與控件布局\
      

                                                                          圖5-1
 
 
5.2 搜索當前在線的U盤
因為U盤的數據清除與移動硬盤不同,U盤可以從邏輯的0扇區開始一直清除下去,
 
 
 
 
 
也就是從活動分區DBR開始清除下去,U盤一般沒有硬盤引導扇區MBR,即使有也可以進行清除,而移動硬盤不能從從MBR開始清除,因為MBR扇區裡存有劃分的多個分區的信息和整個硬盤的容量信息。從設備檢測來說,U盤屬於移動設備(DRIVE_REMOVABLE),而移動硬盤屬性仍然是固定設備(DRIVE_FIXED),關於移動硬盤的數據清除將另文介紹。
搜索當前在線的U盤列表的程序代碼如下:
procedure TForm1.Button1Click(Sender: TObject);
var
   buf:array [0..MAX_PATH-1] of char;
   m_Result:Integer;
   i:Integer;
   str_temp:string;
begin
 m_Result:=GetLogicalDriveStrings(MAX_PATH,buf);
//獲取當前邏輯驅動器的排列和數量
 ListBox1.Items.Clear;
 for i:=0 to (m_Result div 4) do
 begin
   str_temp:=string(buf[i*4]+buf[i*4+1]+buf[i*4+2]);
if GetDriveType(pchar(str_temp)) = DRIVE_REMOVABLE then
//這裡確定是U盤驅動器符
    begin
      //ShowMessage(str_temp+'盤為U盤');
      ListBox1.Items.Add(str_temp);
    end;
 end;
end;
 
5.3 容量估算與顯示
對ListBox1的OnClick事件進行處理,一方面保證所處理的盤肯定是U盤,另一方面可以確認清除的目標盤無誤,因為數據是永久性的清除,無法恢復。
事件處理程序如下:
procedure TForm1.ListBox1Click(Sender: TObject);
var
  dr:string;
  i,j,k:Integer;
 c: Char;
 totalsize:int64;
 userFreeBytes,totalBytes,freeBytes:Int64;
 nOutput:ULONG;
 actget:DWORD;
 getbuff:DISK_GEOMETRY;
 fbuf:pchar;
begin
  i:=ListBox1.ItemIndex;//當前U盤列表 www.2cto.com
  dr:=copy(ListBox1.Items[i],1,1);//查找邏輯盤符第一個字母
  driver:=pchar('\\.\'+dr+':'); //這裡也可以用硬件設備名
//如'\\.\PHYSICALDRIVE1'
  hDeviceHandle := CreateFile(driver, GENERIC_ALL, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING,0, 0);
  if (hDeviceHandle<> INVALID_HANDLE_VALUE) then
  begin
   DeviceIoControl(hDeviceHandle,
                   IOCTL_DISK_GET_DRIVE_GEOMETRY,
                   nil,
                   0,
                   @getbuff,sizeof(DISK_GEOMETRY),
                   actget,
                   nil);
 
totalSize:=int64(getbuff.Cylinders.QuadPart)  *  int64(getbuff.TracksPerCylinder) *int64(getbuff.SectorsPerTrack)*int64(getbuff.BytesPerSector);
   Label2.Caption:=Formatfloat('###,##0',totalSize);//從底層硬設備得到的容量
  SectorWithBytes:=int64(getbuff.BytesPerSector);// SectorWithBytes是個全局變量,記錄每個扇區有多少字節
  closehandle(hDeviceHandle);
  end;
  if GetDiskFreeSpaceEx(PChar(dr+':\'),userFreeBytes,totalBytes,@freeBytes)  then  //如果已經格式化,可以得到容量。
  begin
   Label8.Caption:=Formatfloat('###,##0',totalBytes);
 
  end else
  begin
   Label11.Caption:='未格式化的盤(RAW)     ';
//沒有格式化過的盤,不能得到容量信息
  end;
end;
  使用DeviceIoControl(…)時,輸入參數裡需要DISK_GEOMETRY類型的變量,在Delphi中沒有現成的引用單元,必須自建一個“unit IOctl”,源代碼如下:
unit IOctl;
interface
uses
 Windows;
type
  MEDIA_TYPE=(
 Unknown,
 F5_1Pt2_512,
 F3_1Pt44_512,
 F3_2Pt88_512,
 F3_20Pt8_512,
 F3_720_512,
 F5_360_512,
 F5_320_512,
 F5_320_1024,
 F5_180_512,
 F5_160_512,
 RemovableMedia,
 FixedMedia,
 F3_120M_512,
 F3_640_512,
 F5_640_512,
 F5_720_512,
 F3_1Pt2_512,
 F3_1Pt23_1024,
 F5_1Pt23_1024,
 F3_128Mb_512,
  F3_230Mb_512,
  F8_256_128
  );
 
  DISK_GEOMETRY = record
   Cylinders:LARGE_INTEGER;
   MediaType:MEDIA_TYPE;
   TracksPerCylinder:Ulong;
   SectorsPerTrack:Ulong;
   BytesPerSector:Ulong;
 end;
 
const
 FILE_DEVICE_DISK    = 00000007;
 IOCTL_DISK_GET_DRIVE_GEOMETRY =(FILE_DEVICE_DISK shl 16) + (0 shl 14) + (0 shl 2)+ (0);
implementation
 
end.
 
5.4 數據清除
我們使用最為簡單但是可靠有效的清除方式,就是從U盤的第一個扇區(邏輯序號為0的扇區),覆蓋無效的數據一直到U盤的最後尾部。確定尾部的方法前面已經介紹過,一種是根據容量來確定覆蓋的長度,另一種是根據覆蓋的“寫錯誤”發生時為止,這是清除U盤最干淨的方式。
無論是按檢測的U盤容量進行清除,還是進行超容量的試探性清除,最好是分段進行,以方便控制和進度顯示。下面是一個通用數據清除的子程序,源代碼如下:
Function TForm1.CleanUSBDisk(dri:char;FromWhere,CleanSize:int64):int64;
//參數分別是:驅動器盤符,起始清除的位置,總共清除的數量
Var
p:pchar;
i,j,EachClean:integer;
writesec:string;
driver:pchar;
fillbyte:Byte;
ActuralCleanSize:int64;
begin
result:=0;
driver:=pchar('\\.\'+dri+':');
   p:=allocmem(CleanSize);
   setlength(writesec, CleanSize);
writesec:='';
fillbyte:=0;//這是填充數據,可以根據需要填充任何數據
   for i:=0 to CleanSize -1 do
     writesec := writesec + chr(fillbyte);
    p:=pchar(writesec);
   hDeviceHandle := CreateFile(driver, GENERIC_ALL,
   FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING,0, 0);
   if (hDeviceHandle <> INVALID_HANDLE_VALUE) then
   begin
    FileSeek(hDevicehandle,FromWhere,0);//指向數據覆蓋的開始位置
    ActuralCleanSize := FileWrite(hDevicehandle,p[0], CleanSize);
 result:= ActuralCleanSize;
   if ActuralCleanSize <> CleanSize then
      raise exception.create('Write錯誤%d');//發生寫異常,可能是超過了盤的真實容量,這是判斷真實容量和全覆蓋的技術實現方法
   closehandle(hDeviceHandle);
   end;
end;
說明:1、輸入參數FromWhere和CleanSize必須是“每扇區字節數”(一般是512B)的整數倍;
2、填充的數據可以根據用戶自己的喜好定義,上述程序因篇幅只是簡單地填充00;
3、返回值是正確填充的容量,與輸入參數CleanSize相比可判斷該子程序執行的情況。
 
5.5可靠性和穩定性考慮
前述基本上解決了功能問題,在實際發布過程中,還要考慮很多可靠性和穩定性方面的問題,筆者例出幾條供參考:
1、清除的過程最好使用線程。這樣可以避免界面不響應的問題,線程中的完成情況,通過Delphi的Sychrinize過程傳遞到主界面上,不會發生死鎖沖突;
2、如果不使用線程,在調用CleanUSBDisk函數的循環間隙調用Application.ProcessMessages,可以讓主界面有機會響應一些正常的消息;
3、要考慮在程序非正常退出時還原工作環境。如打開的驅動器句柄要關閉、申請的內存要釋放等。
 
6 結語
本文從U盤的結構和內部工作機制上對其進行了詳細的剖析,從硬設備層和邏輯分區層對U盤進行完整的數據清除,原理上也基本適用於移動硬盤。對於固定硬盤,不包含系統工作文檔的邏輯分區也適用。其實際效果與專用清除設備沒有差別,可擴展性上具有更大的靈活性,如對冗余容量的清除、用戶自定義填充數據、清除效果掃描回顯等。此外,有時U盤插入後,在資源管理器中並不顯示盤符,但是在“控制面板”--“計算機管理”裡可以檢測到U盤,這時用硬設備名,如“\\.\PHYSICALDRIVE1”(表示第二個存儲設備),進行U盤數據清除,同時還可以修復U盤的異常故障。
本文的程序只完成了最基本的實用功能,如圖5-1所示,在Windows XP+Delphi 7下調試通過,清除效果通過專業測定。



摘自 fjwolf的專欄

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved