读硬盘序列号
| ? | 直接用下列函數吧 function GetIdeDiskSerialNumber(ScsiID:integer): string; //硬盤ID號 typeTSrbIoControl = packed recordHeaderLength: ULONG;Signature: array[0..7] of Char;Timeout: ULONG;ControlCode: ULONG;ReturnCode: ULONG;Length: ULONG;end;SRB_IO_CONTROL = TSrbIoControl;PSrbIoControl = ^TSrbIoControl;TIDERegs = packed recordbFeaturesReg: Byte; // Used for specifying SMART "commands".bSectorCountReg: Byte; // IDE sector count registerbSectorNumberReg: Byte; // IDE sector number registerbCylLowReg: Byte; // IDE low order cylinder valuebCylHighReg: Byte; // IDE high order cylinder valuebDriveHeadReg: Byte; // IDE drive/head registerbCommandReg: Byte; // Actual IDE command.bReserved: Byte; // reserved for future use. Must be zero.end;IDEREGS = TIDERegs;PIDERegs = ^TIDERegs;TSendCmdInParams = packed recordcBufferSize: DWORD; // Buffer size in bytesirDriveRegs: TIDERegs; // Structure with drive register values.bDriveNumber: Byte; // Physical drive number to send command to (0,1,2,3).bReserved: array[0..2] of Byte; // Reserved for future expansion.dwReserved: array[0..3] of DWORD; // For future use.bBuffer: array[0..0] of Byte; // Input buffer.end;SENDCMDINPARAMS = TSendCmdInParams;PSendCmdInParams = ^TSendCmdInParams;TIdSector = packed recordwGenConfig: Word;wNumCyls: Word;wReserved: Word;wNumHeads: Word;wBytesPerTrack: Word;wBytesPerSector: Word;wSectorsPerTrack: Word;wVendorUnique: array[0..2] of Word;sSerialNumber: array[0..19] of Char;wBufferType: Word;wBufferSize: Word;wECCSize: Word;sFirmwareRev: array[0..7] of Char;sModelNumber: array[0..39] of Char;wMoreVendorUnique: Word;wDoubleWordIO: Word;wCapabilities: Word;wReserved1: Word;wPIOTiming: Word;wDMATiming: Word;wBS: Word;wNumCurrentCyls: Word;wNumCurrentHeads: Word;wNumCurrentSectorsPerTrack: Word;ulCurrentSectorCapacity: ULONG;wMultSectorStuff: Word;ulTotalAddressableSectors: ULONG;wSingleWordDMA: Word;wMultiWordDMA: Word;bReserved: array[0..127] of Byte;end;PIdSector = ^TIdSector; constIDE_ID_FUNCTION = $EC;IDENTIFY_BUFFER_SIZE = 512;DFP_RECEIVE_DRIVE_DATA = $0007C088;IOCTL_SCSI_MINIPORT = $0004D008;IOCTL_SCSI_MINIPORT_IDENTIFY = $001B0501;DataSize = sizeof(TSendCmdInParams) - 1 + IDENTIFY_BUFFER_SIZE;BufferSize = SizeOf(SRB_IO_CONTROL) + DataSize;W9xBufferSize = IDENTIFY_BUFFER_SIZE + 16; varhDevice: THandle;cbBytesReturned: DWORD;pInData: PSendCmdInParams;pOutData: Pointer; //PSendCmdInParams;Buffer: array[0..BufferSize - 1] of Byte;srbControl: TSrbIoControl absolute Buffer;procedure ChangeByteOrder(var Data; Size: Integer);var ptr: PChar;i: Integer;c: Char;beginptr := @Data;for i := 0 to (Size shr 1) - 1 do beginc := ptr^;ptr^ := (ptr + 1)^;(ptr + 1)^ := c;Inc(ptr, 2);end;end; beginResult := '';FillChar(Buffer, BufferSize, #0);if Win32Platform = VER_PLATFORM_WIN32_NT thenbegin // Windows NT, Windows 2000// Get SCSI port handlehDevice := CreateFile(pchar('//./Scsi'+inttostr(ScsiID)+':'), GENERIC_READ or GENERIC_WRITE,FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);if hDevice = INVALID_HANDLE_VALUE then Exit;trysrbControl.HeaderLength := SizeOf(SRB_IO_CONTROL);System.Move('SCSIDISK', srbControl.Signature, 8);srbControl.Timeout := 2;srbControl.Length := DataSize;srbControl.ControlCode := IOCTL_SCSI_MINIPORT_IDENTIFY;pInData := PSendCmdInParams(PChar(@Buffer) + SizeOf(SRB_IO_CONTROL));pOutData := pInData;with pInData^ do begincBufferSize := IDENTIFY_BUFFER_SIZE;bDriveNumber := 0;with irDriveRegs do beginbFeaturesReg := 0;bSectorCountReg := 1;bSectorNumberReg := 1;bCylLowReg := 0;bCylHighReg := 0;bDriveHeadReg := $A0;bCommandReg := IDE_ID_FUNCTION;end;end;if not DeviceIoControl(hDevice, IOCTL_SCSI_MINIPORT, @Buffer, BufferSize, @Buffer, BufferSize, cbBytesReturned, nil) then Exit;finallyCloseHandle(hDevice);end;endelse begin // Windows 95 OSR2, Windows 98hDevice := CreateFile('//./SMARTVSD', 0, 0, nil, CREATE_NEW, 0, 0);if hDevice = INVALID_HANDLE_VALUE then Exit;trypInData := PSendCmdInParams(@Buffer);pOutData := PChar(@pInData^.bBuffer);with pInData^ do begincBufferSize := IDENTIFY_BUFFER_SIZE;bDriveNumber := 0;with irDriveRegs do beginbFeaturesReg := 0;bSectorCountReg := 1;bSectorNumberReg := 1;bCylLowReg := 0;bCylHighReg := 0;bDriveHeadReg := $A0;bCommandReg := IDE_ID_FUNCTION;end;end;if not DeviceIoControl(hDevice, DFP_RECEIVE_DRIVE_DATA, pInData, SizeOf(TSendCmdInParams) - 1, pOutData, W9xBufferSize, cbBytesReturned, nil) then Exit;finallyCloseHandle(hDevice);end;end;with PIdSector(PChar(pOutData) + 16)^ do beginChangeByteOrder(sSerialNumber, SizeOf(sSerialNumber));SetString(Result, sSerialNumber, SizeOf(sSerialNumber));end;Result := Trim(Result); end;使用時 GetIdeDiskSerialNumber(0);假設硬盤接在0#IDE上。 | ||
| Top | |||
| ? | |||
| ? | 回復人: imageonline(不交房租) ( ) 信譽:100 | 2003-03-04 12:56:41Z | 得分:0 |
| ? | |||
| ? | |||
| ? | 一、什么是硬盤的序列號 ---- 硬盤的序列號是生產時由廠家設定的,存在于硬盤的控制芯片內,不隨硬盤的 分區 、格式化狀態而改變,象硬盤的物理柱面數、扇區數一樣,是一個與操作系統無關的 特 性。該序列號只能用硬盤控制器的I/O指令讀取.,并且不能用常規辦法修改。 ---- 需要注意的是,硬盤的序列號是物理存在的,這與將硬盤格式化成FAT或FAT32 后在 分區引導扇區自動生成的序列號有著根本的區別。格式化產生的序列號是一種邏輯上 的 號碼,每次格式化產生的序列號是不同的,并且可以手工修改。 二、為什么要讀取硬盤的序列號 ---- 可以想到的唯一原因就是軟件防拷貝保護。這是由硬盤序列號的唯一性和只讀 性所 決定的。一般的做法是在軟件安裝到硬盤時讀取該序列號,在做些適當的變化后保存 起 來,以后,安裝到硬盤的軟件可以根據當前的硬盤序列號和安裝時保存的序列號進行 比 較,如果發現二者不一致時,說明該軟件被非法拷貝到其實硬盤上運行。 ---- 因為該序列號已經由生產廠家保證了它的唯一性,并且用戶又不可能修改,所 以用 這種方法基本上保證了軟件的合法效益,國內已經有許多DOS下的軟件采用了這種方 式。 順便提一下,這些軟件還常利用主板的BIOS配合硬盤的序列號進行保護。究其原因, 一 方面是為了增大加密的強度,另一方面,也就是本方法的缺點,就是有些硬盤,如 SANS UNG 的某些型號,是沒有序列號的。 三、如何讀取硬盤的序列號 ---- 硬盤的序列號只能采用對硬盤控制器直接操作的方式進行讀取,也就是說只能 采用 CPU的I/O指令操作硬盤控制器,讀取的方法如下面的C語言程序所示: static int WaitIde() { int al; while ((al=inp(0x1F7)) >=0x80) ; return al; } static void ReadIDE() { int al; int i; WORD pw[256]; WaitIde(); outp(0x1F6,0xA0); al = WaitIde(); if ((al&0x50)!=0x50) return; outp(0x1F6,0xA0); outp(0x1F7,0xEC); al = WaitIde(); if ((al&0x58)!=0x58) return; for (i=0;i< 256;i++) pw[i] = inpw(0x1F0); } ---- 上面的程序實際上讀取了保存在硬盤控制器內的全部信息,而序列號只是其中 的一 部分,位于上面提到的 pw[] 數組的 10 至 20 元素內,即從 &pw[10] 開始的10個 WOR D內,每個WORD占兩個字節,共占用了20個字節。由于該序列號保存時每個WORD的 高、低 字節是非Intel順序,也就是說它的高字節在前,低字節在后,所以在使用時需要將 高、 低字節顛倒一下,這樣就能得到完整的序列號。 四、上面所說的讀取方法為什么不能在Windows 95下用 ---- 以前,在DOS時代,用這種方法完全可以讀到硬盤的序列號并利用它進行軟件防 拷 貝保護,即使在Windows 3.x下也沒有問題,但隨著Windows 95(及Windows 98,下簡 稱 Windows 9x)的普及,這種方法的局限性也顯露出來,因為在Windows 9x下用這種方 法根 本就讀不到任何信息。 ---- 原因是在Windows 9x下,上面代碼所用的I/O指令被作為特權指令限制起來了。 那 么,什么是特權指令呢? ---- 首先,簡單回顧一下Intel 80386以上CPU的保護機制,從80386以后,CPU分為 四個 特權級別(即Ring 0-3),供操作系統使用,其中Ring 0的級別最高,Ring 3的級別 最低 。所允許的CPU指令集從0到3有所減少。那些僅在Ring 0級別上使用的指令即為特別 指令 。在其它級別上執行特權指令會導致CPU異常的產生。 ---- 現在回到Windows 9x中,Windows 95/98在設計時只使用了CPU的兩個特權級 別,即 Ring 0和Ring 3。在這兩個級別中,Windows的虛擬機管理、各種驅動程序(VxD)運行 在 Ring 0級,其它應用程序,甚至包括KERNEL、GDI、USER三個主要模塊在內都運行在 Rin g 3級, 這個級別的程序通過CPU的異常從Ring 0模塊中取得所需要的服務。而 Windows 9x自己提供了異常處理程序,所以可以提供相應的服務。 ---- 在Ring 3級別上,操作硬盤控制器的I/O指令是不可使用的,所以第三節中所列 出 的代碼在執行到 WaitIde()時會陷到死循環中,原因就是Windows 9x的異常處理程序 總 是讓 IN 0x1F7返回 0xFF(事實上,即使跳過這個等待也不行)。 五、在Windows 9x下應該如何讀 ---- 那么,應該如何在Windows 9x下繞過這層保護來操作硬盤控制器從而達到讀取 序列 號的目的呢?容易想到的解決辦法是寫一個虛擬設備驅動程序,即VxD,因為VxD是運 行 在CPU的Ring 0最高特權級別上的。在該級別,所有的指令都是可用的。采用這種方 法是 可以直接讀取的。事實上我已經寫了這樣的 VxD,效果與在DOS下是一樣的。 ---- 然而,這種方法存在三個問題(或稱為缺點): ---- 第一, 寫VxD要比寫普通的Windows 9x程序要復雜得多,所需要的操作系統知 識也 多得多。需要編程人員熟悉Windows 9x的DDK及相關的內核技術。在可視化編程風行 一時 的今天,許多VB、Delphi的程序員幾乎連Windows 9x的SDK都有些生疏,就更別提用 DDK 進行編程了,從這個角度看,用編寫VxD的方法確實平空增加了許多難度。(實在有 興趣 的讀者可以參閱拙作“VxD入門教程”) ---- 第二, 由于VxD運行在CPU的Ring 0級,實際上也運行在Windows 9x操作系統的 核 心級,所以任何一個小小的疏忽都足以讓Windows 9x崩潰,毫無疑問,這將會增加系 統 的不穩定性,特別是經驗不足的程序員編寫的VxD,更容易出現問題。 ---- 第三, 讀硬盤序列號的主要目的是為了保護軟件,也就是防止非法拷貝,如果 帶 上一個VxD,很明顯會提示別人應該如何破解。 ---- 其實,我們完全可以在普通應用程序中采用VxD技術進行操作,這將會涉及到如 何 在應用程序級(Ring 3)執行Ring 0級代碼的技術。 六、如何從應用程序中執行特權0級代碼 ---- 采用這種方法唯一的一個技術難點就是利用 CPU 的異常從 Ring 3 直接切換到 R ing 0。如果解決了這個問題,那么剩下的工作就非常簡單了。值得一提的是,由于 該技 術將CPU切換到 Ring 0 級別運行,所以,可以進行的操作就不僅僅是讀取硬盤序列 號這 么簡單了。 ---- 為了解決這個問題,我們還需要再回頭看看CPU的保護模式和中斷處理方式。 ---- 在實模式下,中斷向量表位于內存地址的0:0處,每當中斷發生時,無論是硬中 斷 還是軟中斷,CPU都會從該表中查找中斷的入口位置,并執行相應的中斷處理程序。 ---- 在保護模式下(包括V86方式),中斷向量表不是必須放在物理內存0000:0000 處,事 實上,象“0000:0000”這種表示方式也發生了根本變化,原來意義上的的段址已經 不復 存在了,代之以段選擇器。相應地,中斷向量表也用中斷描述符表(IDT)取代了,這 意味 著當發生中斷或異常時(異常只在保護模式下存在,可以簡單地把它當作中斷看待), CP U查找的是IDT,然后再根據查到的入口地址執行相應的處理程序。 ---- 關于這部分內容的詳細情況請參閱80386 (及以上) CPU的技術手冊。 ---- 從上面可以看出,雖然查找的內容及方法發生了變化,但原理并沒有變,如果 要修 改中斷入口,所需要修改的地方無非是變成了IDT而已。更妙的是,中斷(或異常)處 理程 序是運行在CPU最高特權級Ring 0上,僅有這點還不夠,我們還需要對IDT具有寫的權 限 ,幸運的是,在Windows 95/98下,IDT位于內存的0C0000000以上的共享區域,而這 部分 區域是對所有進程都可見的,其中的IDT則對所有進程都是可寫的,這就使得我們從 Rin g 3執行Ring 0級別的代碼成為可能。 七、如何利用Ring 0代碼讀取硬盤的序列號 ---- 根據以上的分析,可以將具體的方法歸結如下: ---- 首先,需要取得系統 IDT,這可用 SIDT 指令一步到位,該指令不受特權級3的 限 制; ---- 然后,選定一個中斷(異常),修改其在 IDT 中的入口(修改的方法請參閱IDT 的 格式),使其指向我們自己的處理程序;雖然IDT表中的所有的項都可以使用,但我建 議 使用中斷 3, 這個中斷是給調試器用的,平常沒用。 ---- 最后,通常執行該中斷產生異常。方法是直接執行代碼 INT 3,當然,在 Ring 3 上執行該指令必然地導致CPU異常的發生,于是,我們的處理程序就這樣輕易而舉地 得 到了控制權。 ---- 一旦得到CPU最高特權級的控制權,中斷處理程序就可以進行任何平常在 Ring 3 級別上不能夠進行的操作了,包括讀取硬盤序列號。 ---- 說得再具體一些,就是將本文前面第三節中列出的程序代碼放到中斷處理程序 中即 可全部搞定了。 ---- 采用這種方法讀取硬盤序列號的完整C語言程序限于篇幅就不在文中列出。 八、小結 ---- 實際上我寫這篇文章的目的并不完全是為了讀取硬盤的序列號(這東東畢竟沒多 大 用途,有些硬盤,如三星的 32543A 根本就沒有序列號),主要的目的是想在從 Ring 3 獲取 Ring 0 特權這個問題上做些嘗試。 ---- 最后,需要說明的是,這種方法不可以在 Windows NT 下使用。原因是在 Windows NT下無法修改IDT。回復人: cobi(小新國際) (2001-8-2 9:47:39) 得0分 轉帖: 博士網(http://www.helpwork.net)問答區--Delphi版作者:kongfang(bnnay) 來源:210.28.70.67 標題:如何獲得硬盤的序列號-程序清單 聲明:不能用于NT unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; Label0: TLabel; Label1: TLabel; Label2: TLabel; Label3: TLabel; Label4: TLabel; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; const hookexceptionno = 5; ErrNo : integer =0; var pw : array [0..255] of WORD;// pw[256]; idtr_1 : array [0..5] of byte; //保存中斷描述符表寄存器 oldexceptionhook : dword; //保存原先的中斷入口地址 IdeBase : word; SelectDisk: integer; var Form1: TForm1; implementation | ||
| Top | |||
| ? | |||
| ? | 回復人: sysu(死樹) ( ) 信譽:198 | 2003-03-04 13:00:26Z | 得分:0 |
| ? | |||
| ? | |||
| ? | function GetHardDiskSerieNumber: string; varsysinfo:tsysteminfo;lpRootPathName : PChar; // address of root directory of the file systemlpVolumeNameBuffer : PChar; // address of name of the volumenVolumeNameSize : DWORD; // length of lpVolumeNameBufferlpVolumeSerialNumber : DWORD; // address of volume serial numberlpMaximumComponentLength : DWORD; // address of system's maximum filename lengthlpFileSystemFlags : DWORD; // address of file system flagslpFileSystemNameBuffer : PChar; // address of name of file systemnFileSystemNameSize : DWORD; // length of lpFileSystemNameBuffer beginlpRootPathName:=pchar('c:/');windows.GetSystemInfo(sysinfo);GetMem( lpVolumeNameBuffer, MAX_PATH + 1 );GetMem( lpFileSystemNameBuffer, MAX_PATH + 1 );nVolumeNameSize := MAX_PATH + 1;nFileSystemNameSize := MAX_PATH + 1;Windows.GetVolumeInformation( lpRootPathName,lpVolumeNameBuffer,nVolumeNameSize,@lpVolumeSerialNumber,lpMaximumComponentLength,lpFileSystemFlags,lpFileSystemNameBuffer,nFileSystemNameSize );Result := Copy( IntToHex( lpVolumeSerialNumber, 0 ), 1, 4 ) + '-' +Copy( IntToHex( lpVolumeSerialNumber, 0 ), 5, 4 ); end; | ||
| Top | |||
| ? | |||
| ? | 回復人: imageonline(不交房租) ( ) 信譽:100 | 2003-03-04 13:02:43Z | 得分:0 |
| ? | |||
| ? | |||
| ? | procedure TForm1.Button1Click(Sender: TObject); var SerialNum: DWord; A, B: DWord; VolumeSerialNumber: string ; Key: string ; begin Key:=''; if GetVolumeInformation(PChar('C:/'), Nil, 0, @SerialNum, A, B, Nil, 0) then VolumeSerialNumber := IntToHex(HiWord(SerialNum), 4) + IntToHex(LoWord(SerialNum), 4); Key := VolumeSerialNumber ; Label1.Caption := Key ; end; | ||
總結
- 上一篇: c语言指针向前移动i个位置,C语言指针
- 下一篇: 电阻(电位器)学习