NTFS是Windows NT以及之后的Windows 2000、Windows XP、Windows Server 2003、Windows Server 2008、Windows Vista和Windows 7的标准文件系统。NTFS取代了文件分配表(FAT)文件系统,为Microsoft的Windows系列操作系统提供文件系统。NTFS对FAT和HPFS(高性能文件系统)作了若干改进,例如,支持元数据,并且使用了高级数据结构,以便于改善性能、可靠性和磁盘空间利用率,并提供了若干附加扩展功能,如访问控制列表(ACL)和文件系统日志。该文件系统的详细定义属于商业秘密 ,但 Microsoft 已经将其注册为 知识产权产品。


NTFS 提供长文件名、数据保护和恢复,并通过目录和文件许可实现安全性。NTFS 支持大硬盘和在多个硬盘上存储文件(称为卷)。例如,一个大公司的数据库可能大得必须跨越不同的硬盘。NTFS 提供内置安全性特征,它控制文件的隶属关系和访问。从DOS或其他操作系统上不能直接访问 NTFS 分区上的文件。如果要在DOS下读写NTFS分区文件的话可以借助第三方软件;现如今,Linux系统上已可以使用 NTFS-3G进行对 NTFS 分区的完美读写,不必担心数据丢失。


  Win 2000采用了更新版本的NTFS文件系统NTFS 5.0,它的推出使得用户不但可以像Win 9X那样方便快捷地操作和管理计算机,同时也可享受到NTFS所带来的系统安全性。 NTFS 允许文件名的长度可达 256 个字符。虽然 DOS 用户不能访问 NTFS 分区,但是 NTFS 文件可以拷贝到 DOS 分区。每个 NTFS 文件包含一个可被 DOS 文件名格式认可的 DOS 可读文件名。这个文件名是 NTFS 从长文件名的开始字符中产生的。

 

我们来实现在VC++下面实现读取MBR。

C++内联汇编

在C++代码中插入__asm {}即可

 

我们亲自来分析实现NTFS文件恢复。

 

 

 

;******************************************************
.386
.model flat, stdcall
option casemap :none
;******************************************************
; Include 文件定义
;******************************************************
include	        \masm32\include\windows.inc
include	        \masm32\include\user32.inc
includelib      \masm32\lib\user32.lib
include         \masm32\include\kernel32.inc
includelib      \masm32\lib\kernel32.lib
;*******************************************************
; Equ 等值定义
;*******************************************************
ICO_MAIN	equ		1000h  ;图标ID
DLG_MAIN	equ		1      ;对话框ID
IDC_PARTITION   equ             101h    ;盘符名输入框ID
IDC_FILENAME    equ             102h   ;文件名ID
;*******************************************************
; 数据段
;*******************************************************
.data
hFile_Disk     dd   0               ;磁盘文件号
hFile          dd   0               ;文件号 
FileName       db   '\\.\'          ;打开文件名为\\.\X:形式的文件则打开了X分区
PARTITION      db   3  dup (0)      ;请注意FileName和等待用户输入的PARTITION共
                                    ;同组成了要打开的文件名(分区)
FILENAMEA      db   25 dup (0)      ;等待用户输入的字符缓存
FILENAMEU      db   50 dup (0)      ;转换成Unicode字符串的缓存
ErrCap         db   '失败',00       ;错误对话框的Caption
ErrorInfo1     db   '可能的原因是:',0dh
               db   '1.该程序不能在除NT以外的系统中执行;',0dh
               db   '2.您输入的盘符无效!',00h
ErrorInfo2     db   '读盘错误!',00
ErrorInfo3     db   '该程序只能恢复NTFS文件系统中的数据!',0dh
               db   '您打开的磁盘不是NTFS文件系统!',00h
ErrorInfo4     db   '移动文件指针错误!',00h
Info1          db   '请在当前文件夹下查看已恢复的文件!如果文件扩展名不对,请自行',0dh
               db   '修改扩展名,如果没有文件,则说明您输入的文件没有找到!',00h
Recoveried     dd   30h               ;用来存放已经恢复的文件个数0~z
_0             db   00               ;字符串结束
Readed         dd   0
System_Id      db   'NTFS'           ;DBR中NTFS卷的标志
MFT_Flag       db   'FILE'           ;MFT的标志
StateFindFile  dd   0                ;该数据是FindFile过程的返回值
StringLength   dd   0                ;该地址用于存放用户输入的文件名的输入长度
MFTTime        dd   1024
Temp           db   'Text',00
EdiOffset      dd   0                ;
IndicEdi       dd   0                ;用来在比较字符串中存放Edi所指向的字符串的指针
FileNameOffset dd   0                ;用来存放文件名偏移
FileSize       dd   0                ;用来存放常驻80H属性的文件大小
FileSize1      dq   0                ;用来存放系统分配给非常驻80H属性的大小
FileSize2      dq   0                ;用来存放非常驻80H属性的文件真实大小
Edi80          dd   0                ;用来保存非常驻80H属性的运行列表偏移
CInfo1         db   0                ;用来保存运行列表的第一个字节的低4位
CInfo2         db   0                ;用来保存运行列表的第一个字节的高4位
ResidentFlag   dd   0ffh             ;80H属性常驻与非常驻标志,为0表示常驻,为1表示非常驻
ByeofOneC      dd   0                ;每簇字节数
RunC           dd   0                ;运行相对起始簇号
RunC2          dd   0                ;运行绝对起始簇号
RunByte        dd   0                ;运行字节数
RunCN          dd   0                ;运行起始簇号
RunFirstAddr   dq   0                ;运行起始偏移字节
;__________________________________________________________________
.data?
hInstance      dd   ?
DBR            db   512 dup (?)      ;512字节作为DBR的缓冲
MFTFirstSector dd   ?                ;MFT首扇区存放的缓冲
MFTFirstOffset dd   2 dup (?)
Filling        db   8 dup (?)        ;这个是没有用的数据,只是为了让后面的MFT在
                                     ;程序执行时起始偏移在XXXXXXX0上,方便调试
MFT            db   1024*1024 dup (?);1M作为MFT的缓冲
DataBuffer     db   1024*1024 dup (?);1M字节做为文件数据缓冲
FileNameLong   dd   ?
FileNameBuffer dw   260 dup (?)
;*******************************************************
; 代码段
;*******************************************************
.code
;*******************************************************
_ProcDlgMain	proc	uses ebx edi esi hWnd,wMsg,wParam,lParam
    mov      eax,wMsg
.if	eax == WM_CLOSE         ;如果消息为WM_CLOSE,当按下右上角的关闭按钮
    invoke    EndDialog,hWnd,NULL        ;hWnd为对话框窗口句柄,结束对话框
    ret
.elseif  eax == WM_INITDIALOG             ;初始化代码
    invoke    GetDlgItem,hWnd,IDOK         ;取IDOK句柄 
    invoke    EnableWindow,eax,FALSE      ;IDOK显示为灰色(确定按钮)
    invoke    LoadIcon,hInstance,ICO_MAIN     ;设置标题栏图标
    invoke    SendMessage,hWnd,WM_SETICON,ICON_BIG,eax   
.elseif  eax == WM_COMMAND
    mov       eax,wParam
     ;_____________________________________________________________________
      .if  ax == IDCANCEL                         ;如果用户点击“取消”按钮
          invoke    EndDialog,hWnd,NULL           ;关闭对话框
          mov       eax,TRUE
          ret
      .elseif  ax== IDOK                           ;如果用户点击“确定”按钮
          invoke    CreateFileA,offset FileName,\  ;打开用户输入的盘符
                    GENERIC_READ OR GENERIC_WRITE,\
                    FILE_SHARE_READ OR FILE_SHARE_WRITE,\
                    NULL,OPEN_EXISTING,NULL,NULL
	  mov       [hFile_Disk],eax               ;保存该分区的文件号
	  cmp       eax,INVALID_HANDLE_VALUE       ;判断其是否成功(为-1失败)
	  jz        _98CODE                        ;失败则转
          invoke    ReadFile,[hFile_Disk],\
                    offset DBR,512,\
                    offset Readed,NULL
	  cmp       eax,0
          jz        ReadFail
          mov       edx,dword ptr [DBR+3]          ;将该分区的分区标志送edx
          mov       ebx,dword ptr System_Id      
          cmp       edx,ebx                        ;是NTFS分区吗?
          jnz       P_Err                          ;不是则转
          CALL      FirstMFTOffset                 ;定位MFT的起始偏移
          invoke    SetFilePointer,[hFile_Disk],\
                    dword ptr [MFTFirstOffset],\   ;移动文件指针的低32位
                    offset MFTFirstOffset+4,\      ;移动文件指针的高32位
                    FILE_BEGIN                     ;从文件(分区)开始处计算
          cmp       eax,-1
          jz        MoveFail
       ReadMFT:
          mov       dword ptr [MFTTime],1024       ;用于计算指针是否指到了内存中的MFT尾
          invoke    ReadFile,[hFile_Disk],\        ;读1兆MFT
                    offset MFT,1024*1024,\
                    offset Readed,NULL
          add       dword ptr [MFTFirstOffset],1024*1024  ;保存当前磁盘指针
          adc       dword ptr [MFTFirstOffset+4],0 
          cmp       eax,0
          jz        ReadFail
          
          mov       edi,offset MFT
       CallFindFile:                       
          call      FindFile             ;查找符合用户输入的已经删除的文件
              ;((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((
              .if     [StateFindFile]==0 ;FindFile过程返回状态数据[StateFindFile]为0
                                         ;表示所有的MFT都找完了
                   invoke    MessageBoxA,NULL,offset Info1,offset Temp,MB_OK 
                   ret
              ;((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((        

              .elseif [StateFindFile]==1 ;FindFile过程返回状态数据[StateFindFile]为1
                                         ;表示该文件未被删除或这不是用户要恢复的文件
                ReadToo:
                  dec dword ptr [MFTTime] 
                  cmp dword ptr [MFTTime],0
                  jz  ReadMFT           ;结果为0则表示要读下1兆MFT了
                  add edi,1024          ;edi向后1K,指向下一个MFT
                  jmp CallFindFile      ;继续查找符合用户输入的已经删除的文件
              ;((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((                                       
              .elseif [StateFindFile]==2 ;FindFile过程返回状态数据[StateFindFile]为2
                                         ;表示这是一个用户要恢复的文件(文件名包含用户输入)
                  call ReadData           ;这个过程将要恢复文件的数据读入内存中
                  ;创建要恢复的文件,文件名为原来的文件名
                 ;______________________________________________________
                     .if   dword ptr [ResidentFlag]==0       ;如果为常驻属性 
                        invoke    CreateFileW,offset FileNameBuffer,\  ;创建要恢复的文件
                            GENERIC_READ OR GENERIC_WRITE,\ ;为读和写打开
                            0,NULL,\                        ;不允许文件再被打开
                            CREATE_NEW,\                 ;创建新文件,如果文件已经存在则返回失败代码
                            NULL,NULL


                        mov       [hFile],eax                  ;保存文件号
                        invoke    WriteFile,[hFile],\          ;写文件
                                  offset DataBuffer,[FileSize],\      
                                  offset Readed,NULL
                        invoke    CloseHandle,[hFile]           ;关闭文件
                        jmp       ReadToo                       ;继续找是否还有包含用户输入的文件
                  ;_____________________________________________________
                      .elseif  dword ptr [ResidentFlag]==1    ;如果为非常驻属性 
                        mov       dword ptr [RunC2],0    ;绝对起始簇号清零
                        jmp       ReadToo                  
                       ;invoke    MessageBoxA,NULL,offset ErrorInfo1,offset Temp,MB_OK
                      .endif
                  ;_______________________________________________________
                  ret
              .endif
              ;((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((          




        _98CODE:          
          invoke    MessageBoxA,NULL,offset ErrorInfo1,offset ErrCap,MB_OK
          mov	    eax,TRUE
          ret
        ReadFail:
          invoke    CloseHandle,[hFile_Disk]        ;关闭文件(关闭分区)
          invoke    MessageBoxA,NULL,offset ErrorInfo2,offset ErrCap,MB_OK
          mov	    eax,TRUE
          ret
        P_Err:
          invoke    CloseHandle,[hFile_Disk]        ;关闭文件(关闭分区)
          invoke    MessageBoxA,NULL,offset ErrorInfo3,offset ErrCap,MB_OK
          mov	    eax,TRUE
          ret
        MoveFail:
          invoke    CloseHandle,[hFile_Disk]        ;关闭文件(关闭分区)
          invoke    MessageBoxA,NULL,offset ErrorInfo4,offset ErrCap,MB_OK
          mov	    eax,TRUE
          ret       
      ;_____________________________________________________________________
      .elseif  ax == IDC_PARTITION                     ;如果用户在盘符文本框中输入
          invoke    SendDlgItemMessage,hWnd,\          ;限制文本输入为2个字符
                    IDC_PARTITION,EM_LIMITTEXT,2,NULL   
          invoke    GetDlgItemText,hWnd,IDC_PARTITION,\ 
                    addr PARTITION,sizeof PARTITION    ;取用户输入文本到PARTITION处
          invoke    GetDlgItem,hWnd,IDOK               ;取IDOK句柄(确定按钮) 
          invoke    EnableWindow,eax,TRUE              ;置确定按钮为可用
          mov       eax,TRUE
          ret
      ;_____________________________________________________________________
      .elseif  ax == IDC_FILENAME                   ;如果用在文件名文本框中输入
          invoke    SendDlgItemMessage,hWnd,\       ;限制文本输入为24个字符
                    IDC_FILENAME,EM_LIMITTEXT,24,NULL
          invoke    GetDlgItemText,hWnd,IDC_FILENAME,\  ;取用户输入到FILENAMEA处
                    addr FILENAMEA,sizeof FILENAMEA
          call      CompString                     ;计算用户输入的字符的长度,并保存在StringLength处
          ;将ANSI字符串转换为Unicode字符串
          invoke    MultiByteToWideChar,1,0,addr FILENAMEA,\ ;AUSI地址FILENAMEA首地址
                    -1,addr FILENAMEU,\          ;-1为自动大小(为0结束)
                    sizeof FILENAMEU             ;转换到FILENAMEU,大小为FILENAMEU的大小
          mov       edi,offset  FILENAMEU
          call      ToCaps          ;将FILENAMEU中的Unicode字符串中的小写字母转换为大写
          mov       eax,TRUE 
          ret
      .endif
      ;_____________________________________________________________________
.else
    mov      eax,FALSE
    ret
.endif
    mov      eax,TRUE
    ret
_ProcDlgMain	endp
;*******************************************************
;该过程用于计算用户输入的文件名的长度(Unicode的长度),并保存在StringLength处
CompString    proc
    mov  eax,00h
    mov  edi,offset FILENAMEU
  CmpString:
    cmp  byte ptr [edi],00      ;字符串是否结束
    jz   ExitProc
    inc  eax
    inc  edi
    inc  edi
    jmp  CmpString
  ExitProc:
    mov  [StringLength],eax
    ret
CompString    endp
;*******************************************************
;小写的Unicode字母转大写
ToCaps	proc
  Char:
    mov  bx,word ptr [edi]       ;将字符串中的一个字送BX
.while   bx>=61h && bx<=7ah       ;这个字是UNICODE小写字母么,是则执行循环体
    sub  bx,20h                  ;将其转变为大小的Unicode字符
    mov  word ptr [edi],bx       ;再将这个转变后的字符送回字符串中
    inc  edi                     ;指针加2
    inc  edi                     
    mov  bx,word ptr [edi]       ;继续将下一个字符串中的一个字送BX
.break .if bx==00h                ;如果是00则表示已经到了字符串尾,则退出循环
.endw
    cmp  bx,00                   ;bx为0表示已经到了字符串的尾部
    jz   Okchar                  ;到了字符串的尾部转返回处 
    inc  edi                     ;指针加2(因为是Unicode字符)
    inc  edi                              
    jnz  Char                    ;不是小写Unicode字母则跳过这个字符继续做下一个字符的判断    
  Okchar:
    ret
ToCaps    endp
;*******************************************************
;计算$MFT起始偏移,并存放在MFTFirstOffset
FirstMFTOffset  proc
    mov  edi,offset DBR
    mov  eax,dword ptr [edi+30h]  ;MFT起始簇号送eax
    mov  cl,[edi+0dh]
    movzx ebx,cl       ;每簇扇区数送ebx
    mul  ebx           ;MFT起始簇号*每簇扇区数=MFT起始扇区号,高位保存在edx中
                       ;低位保存在eax中
    mov  edi,offset MFTFirstSector
    mov  [edi],eax     ;将MFT起始扇区号保存
    mov  ebx,512       ;每扇区字节数送ebx
    mul  ebx           ;每扇区字节数*MFT起始扇区号=MFT起始偏移
    mov  edi,offset MFTFirstOffset ;放MFT起始偏移的地址送edi
    mov  [edi],eax     ;保存MFT起始偏移(低位)
    mov  [edi+4],edx     ;保存MFT起始偏移(高位)
    ;经过以上算法后,MFTFirstOffset保存的就是MFT起始偏移了
    ret
FirstMFTOffset  endp
;*******************************************************
;判断当前MFT是否是用户要恢复的文件
FindFile  proc
    push  edi
NextFindFile:
  ;_____________________________________________________
  .if     dword ptr [edi] != 454c4946h ;如果这不是一个合法的MFT,则表明已经读完了卷中所有的MFT
    cmp   dword ptr [edi],44414142h    ;在windows 2K中,有可能出现标志为“BAAD”的空MFT
    jnz   NoMft
    mov   [StateFindFile],1            ;[StateFindFile]赋1后退出,跳过这个MFT
    jmp   ExitProc  
  NoMft:
    mov   [StateFindFile],0       ;[StateFindFile]赋0后退出
    jmp   ExitProc
  ;_____________________________________________________
  .elseif word ptr [edi+16h] != 0                        ;如果这个文件是未被删除的
    mov   [StateFindFile],1                                ;[StateFindFile]赋1后退出
    jmp   ExitProc
  ;_____________________________________________________
  .else
    add   di,word ptr [edi+14h]
    mov   dword ptr [EdiOffset],edi                            ;保存属性头开始偏移
      ;((((((((((((((((((((((((((((((((((((((((((((((((((((((
        FindAttribute:
      .if     dword ptr [edi]==30h             ;是30h属性的话
        CmpFileNameLeng:                       ;比较文件名长度
            push  edx
            pop   edx
            add   di,word ptr [edi+14h]        ;将30H属性头后的属性偏移送edi
                                               ;(属性头14H处为该属性开始的相对偏移)
            mov   edx,dword ptr [edi+30h]      ;保存文件大小,为后面恢复文件做准备                                               
            mov   dword ptr [FileSize2],edx    ;30H属性偏移30H后的8个字节是文件的实际大小
            mov   edx,dword ptr [edi+34h]      
            mov   dword ptr [FileSize2+4],edx
            movzx eax,byte ptr [edi+40h]       ;edi+40中存放的是30H属性的文件名Unicode长度
            mov   dword ptr [FileNameLong],eax ;保存文件名长度
            cmp   eax,dword ptr [StringLength] ;该长度和用户输入的文件名长度进行比较
            jc    Next                         ;如果CF=1(有借位)则转,有借位则表示用户输
                                               ;入的字符串的长度大于该文件的文件名长度,
                                               ;这肯定不是用户要恢复的文件了
            jmp   CmpFileName
        Next:
            mov   edi,dword ptr [EdiOffset]    ;将属性头开始偏移送回edi
            add   edi,dword ptr [edi+4h]       ;edi为下一个属性的偏移(属性头04~07为该属性的长度)
            mov   dword ptr [EdiOffset],edi    ;保存下一个属性头开始偏移     
            jmp   FindAttribute                ;继续找下一个属性
        CmpFileName:
            movzx eax,byte ptr [edi+40h]       ;该文件30H属性的文件名长度送eax
            add   edi,42h                      ;30H属性开始的偏移+42后为该文件文件名的开始偏移送edi(源串地址)
            mov   dword ptr [IndicEdi],edi     ;保存文件名的开始偏移
            mov   dword ptr [FileNameOffset],edi ;保存文件名开始偏移!!!!!!!!!!!
            sub   eax,dword ptr [StringLength] ;eax=文件名长度-用户输入的串长度
            inc   eax                          ;eax加1,结果为串比较时移动源串指针的次数
            push  edi
            call  ToCaps                       ;将小写的Unicode码转为大写,为比较串做准备
            pop   edi
            mov   ecx,dword ptr [StringLength] ;计数器=用户输入的串的长度
            mov   ebx,ecx                      ;保存用户输入串长度到ebx    
            mov   esi,offset [FILENAMEU]       ;目的串地址
        Compare:
            repz cmpsw                        ;比较源串和目的串
            jz    Alike                        ;一样则说明这就是用户要恢复的文件,一样则转
            dec   eax
            cmp   eax,0                        ;为0表示比较完毕都没有发现文件名中包含了用户输入的字符串                       
            jz    Next                         ;继续找下一个属性
            mov   esi,offset [FILENAMEU]       ;重新指向用户输入串的首地址目的串地址
            add   dword ptr [IndicEdi],2       ;文件名字符串向后移动两个单位,指向下一个字符
            mov   edi,dword ptr [IndicEdi]
            mov   ecx,ebx                      ;比较的次数=用户输入的串的长度 
            jmp   Compare                      ;转比较处                       
        Alike:
            xor   eax,eax
            mov   edi,offset FileNameBuffer
            mov   ecx,128                      ;循环128次,将文件名缓冲区清零
        ClearBuffer:
            mov   dword ptr [edi],eax  
            add   edi,4                          
            loop  ClearBuffer                    ;清除文件名缓冲区
            mov   esi,dword ptr [FileNameOffset] ;源地址为30H属性中文件名的开始
            mov   edi,offset FileNameBuffer      ;目标地址为FileNameBuffer
            mov   ecx,dword ptr [FileNameLong]   ;传送次数为文件名长度
            rep   movsw                          ;拷贝文件名到FileNameBuffer中
            mov   [StateFindFile],2            ;这就是用户要恢复的文件[StateFindFile]赋值2后退出
            jmp   ExitProc   
      ;(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((      
      .elseif dword ptr [edi]==0ffffffffh      ;是属性结束的话,说明这个文件不是用户要恢复的
            mov   [StateFindFile],1            ;[StateFindFile]赋1后退出
            jmp   ExitProc
      ;(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((
      .else                                     ;是其他属性的话直接将指针加上属性长度得到后面的属性开始,重新判断
            jmp   Next
           
      .endif
      ;(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((
  .endif
  ExitProc:
    pop  edi                        ;将该MFT的起始偏移写回edi
    ret      
FindFile  endp
;*******************************************************
ReadData  proc
    push  edi                       ;保存要恢复文件的MFT的开始地址
    add   di,word ptr [edi+14h]
  FindNext:
    mov   dword ptr [EdiOffset],edi   ;保存属性头开始偏移
 .if      dword ptr [edi]==80h        ;是80h属性的话
    cmp   byte  ptr [edi+9h],0        ;该80H属性是否有属性名[edi+09H]为0表示没有属性名
    jnz   Next                        ;有属性名的80H属性不是用户数据属性,跳过这个属性,比较下一个
        .if   byte ptr [edi+08]==0        ;[edi+08]==0(一个字节) 表示该属性为常驻属性
            movzx  eax,word ptr [edi+14h]   ;属性开始的偏移(属性头的长度)送eax
            mov    ebx,dword ptr [edi+4h]   ;包括属性头长度在内的属性长度
            sub    ebx,eax                  ;ebsx=包括属性头长度在内的属性长度 - 属性头的长度=用户数据的长度(文件大小)
            mov    dword ptr [FileSize],ebx ;文件大小
            mov    ecx,ebx                  ;循环次数为文件大小
            mov    esi,edi                  ;把80H属性头开始的偏移送esi
            add    si,word ptr [edi+14h]    ;把80H属性开始的偏移送esi(源地址)
            mov    edi,offset DataBuffer    ;把数据缓存区的地址送edi(目的地址)
            rep    movsb                    ;将要恢复的用户数据传到DataBuffer中
            inc    dword ptr [Recoveried]   ;每恢复一个文件将[Recoveried]加1
            mov    dword ptr [ResidentFlag],0 ;给常驻与非常驻标志置0,表示常驻 
            jmp    ExitProc     
        .elseif byte ptr [edi+08]==1      ;[edi+08]==1(一个字节)表示该属性为非常驻属性
            ;获取文件的大小
            mov    edx,dword ptr [edi+30h]      ;保存文件大小,为后面恢复文件做准备                                               
            mov    dword ptr [FileSize1],edx    ;80H属性头偏移30H后的8个字节是文件的真实大小
            mov    edx,dword ptr [edi+34h]      
            mov    dword ptr [FileSize1+4],edx
            invoke    CreateFileW,offset FileNameBuffer,\  ;创建要恢复的文件
                      GENERIC_READ OR GENERIC_WRITE,\ ;为读和写打开
                      0,NULL,\                        ;不允许文件再被打开
                      CREATE_NEW,\                 ;创建新文件,如果文件已经存在则返回失败代码
                      NULL,NULL
            mov       [hFile],eax                  ;保存文件号
            
            push   edi                          ;保存edi
            push   eax                          ;保存eax            
            mov    edi,offset DBR
            mov    cl,byte ptr [edi+0dh]
            movzx  ebx,cl       ;每簇扇区数送ebx
            mov    eax,512
            mul    ebx           ;每簇扇区数乘以每扇区字节数=每簇字节数
            mov    dword ptr [ByeofOneC],eax ;每簇字节数保存到ByeofOneC
            pop    eax
            pop    edi
            xor    ebx,ebx
            mov    bx,word ptr [edi+20h]
            add    edi,ebx    ;把80H属性运行列表开始的偏移送edi

         Recover:
            call   RunInfo   ;获取运行起始字节偏移(在RunFirstAddr处),和该运行占有的字节数(在RunByte处)
            invoke    SetFilePointer,[hFile_Disk],\ ;移动文件指针到运行数据开始处
                      dword ptr [RunFirstAddr],\   ;移动文件指针的低32位
                      offset RunFirstAddr+4,\      ;移动文件指针的高32位
                      FILE_BEGIN                     ;从文件(分区)开始处计算                 

         ReadWrite:
            .if     dword ptr [RunByte]>100000H ;如果运行大小大于1M字节
                 invoke    ReadFile,[hFile_Disk],\        ;读1兆字节的运行数据
                           offset DataBuffer,1024*1024,\
                           offset Readed,NULL                 
                 invoke    WriteFile,[hFile],\          ;写1M数据到文件
                           offset DataBuffer,1024*1024,\      
                           offset Readed,NULL
                 sub    dword ptr [RunByte],1024*1024  ;RunByte减1M字节
                 sub    dword ptr [FileSize1],1024*1024 ;文件大小减1M字节
                 jmp    ReadWrite                      ;继续读数据写进文件
            .elseif dword ptr [RunByte]<=100000H ;如果运行大小小于或等于1M字节
                 invoke    ReadFile,[hFile_Disk],\        ;读运行大小字节的运行数据
                           offset DataBuffer,[RunByte],\
                           offset Readed,NULL                 
                 invoke    WriteFile,[hFile],\          ;写运行最后剩下的实际文件大小字节数据到文件
                           offset DataBuffer,dword ptr [FileSize1],\; [FileSize1]总是小于[RunByte]的  
                           offset Readed,NULL
                 mov       al,byte ptr [CInfo1]
                 movzx     ecx,al
                 add       edi,ecx
                 mov       al,byte ptr [CInfo2]
                 movzx     ecx,al
                 add       edi,ecx
                 inc       edi               ;这样edi就指向了下一个运行列表的开始
                 cmp       byte ptr [edi],0 ;有下一个运行列表吗?
                 jnz       Recover    ;不是0表示还有下一个运行,继续处理下一个运行
                 invoke    CloseHandle,[hFile] ;没有下一个运行则关闭文件
                 invoke    SetFilePointer,[hFile_Disk],\  ;还原磁盘指针
                           dword ptr [MFTFirstOffset],\   ;移动文件指针的低32位
                           offset MFTFirstOffset+4,\      ;移动文件指针的高32位
                           FILE_BEGIN                     ;从文件(分区)开始处计算
                  
            .endif
          ExitReadWrite:
            mov    dword ptr [ResidentFlag],1 ;给常驻与非常驻标志置1,表示非常驻 
            jmp    ExitProc   
        .endif
 .elseif  dword ptr [edi]==0ffffffffh ;是属性结束的话,就退出
    jmp   ExitProc
 .else                                ;是其他属性的话
  Next:
    mov   edi,dword ptr [EdiOffset]   ;将属性头开始偏移送回edi
    add   edi,dword ptr [edi+4h]      ;edi为下一个属性的偏移(属性头04~07为该属性的长度)
    jmp   FindNext                    ;继续找下一个属性
 .endif
  ExitProc:
    pop  edi                          ;将该MFT的起始偏移写回edi
    ret
ReadData  endp
;*******************************************************
RunInfo   proc    
    ;获取文件起始字节偏移,和该运行占有的字节数
    mov    dword ptr [Edi80],edi    ;将属性运行列表偏移保存到Edi80 
    mov    al,byte ptr [edi]        ;把运行列表的第一个字节送al,其高
                                            ;4位表示多少运行列表中多少个字节为起始簇
                                            ;低4位表示多少个字节表示簇大小
    push   eax
    mov    cl,4
    shr    al,cl                    ;将al逻辑右移4位,结果al为该运行起始的簇号所占字节数
    mov    byte ptr [CInfo1],al 
    pop    eax
    and    al,0fh                   ;清al高4位后al为该运行的簇数占有的字节
    mov    byte ptr [CInfo2],al 
    ;计算运行的总共的字节数
    push   edi
    mov    esi,edi
    inc    esi                      ;源地址为运行所占簇数偏移
    mov    edi,offset RunCN         ;目标地址
    xor    ecx,ecx
    mov    cl,byte ptr [CInfo2]
    rep    movsb                    ;将运行簇数保存到RunCN处
    mov    eax,dword ptr [RunCN]     ;将运行簇数送eax        
    mov    ebx,dword ptr [ByeofOneC] ;将每簇字节数送ebx
    mul    ebx                      
    mov    dword ptr [RunByte],eax  ;该运行所占字节数偏移送RunByte处
    pop    edi
    ;计算运行的起始偏移字节
    push   edi
    mov    esi,edi
    inc    esi
    xor    ecx,ecx
    mov    cl,byte ptr [CInfo2]
    add    esi,ecx                 ;esi指向运行起始簇偏移(源地址)
    mov    edi,offset RunC         ;目标地址
    mov    cl,byte ptr [CInfo1]    ;循环次数为运行列表中起始簇所占字节数
    rep    movsb                   ;将运行的起始簇号保存到RunC处
    mov    eax,dword ptr [RunC]     ;将相对运行起始簇号送eax
    add    dword ptr [RunC2],eax    ;计算绝对起始簇号
    mov    eax, dword ptr [RunC2]    ;绝对起始簇号送eax   
    mov    ebx,dword ptr [ByeofOneC] ;将每簇字节数送ebx
    mul    ebx                      
    mov    dword ptr [RunFirstAddr],eax
    mov    dword ptr [RunFirstAddr+4],edx ;保存运行起始偏移到RunFirstAddr处
    pop    edi

    ret

RunInfo   endp
;*******************************************************
start:
    invoke    GetModuleHandle,NULL    ;得到模块句柄(NULL为本模块)
    mov       hInstance,eax           ;保存模块句柄
    invoke    DialogBoxParam,hInstance,\ ;创建模块对话框,从hInstance指定模块装入
              DLG_MAIN,NULL,\            ;装入DLG_MAIN参数指定的对话框,父对话框为NULL
              offset _ProcDlgMain,NULL  ;过程地址为_ProcDlgMain的首地址,
                                        ;当作WM_INITDIALOG消息的lParam传给过程对话框定义为NULL(未定义)
    invoke    ExitProcess,NULL          ;退出程序
end    start