天天看點

換運作中的檔案(深入淺出)

文章作者:Dancefire at 263 dot net

這是今天瞎琢磨的一點收獲。

一、引入

凡事總喜歡刨根問底,一直問自己為什麼,問到實在沒什麼可問了為止,當然也有問到自己是在懶得再問了為止的時候。因為一個軟體的自動更新老出問題,有時候出現無法自動更新,分析後發現原來是在替換正在運作的程式時出了問題。無知的我就開始對替換正在運作中的程式的方法進行分析。

雖大言不慚自稱深入淺出,其實隻不過對一個問題想深入進去,分析到自己因技術不佳無法繼續分析的時候,淺淺的出來而已。但是仍舊希望這篇文章能夠對和我一樣思考這個問題的人有一點點幫助。

言歸正傳,首先從xfocus的bgate的文章《在Win2000/XP上安靜地替換正在使用的系統檔案》中獲得啟發。

他對替換正在使用的系統檔案進行了研究,分析了微軟的一個工具zap,這個工具可以替換系統檔案。經過分析後,這個工具其實是先把正在使用的檔案移動到一個臨時目錄中去,然後再把這個檔案删除,但是标記為下次啟動的時候删除。此時系統檔案目錄已經騰出了空間,這樣再把新的檔案移動過來就可以了。

實作部分代碼示意如下:

  if(szFileToDel[1] == ':'){

    sprintf(cTempPathName, "%c://", szFileToDel[0]);

  }

  else{

    GetModuleFileName(NULL, cFileName, 0x100);

    sprintf(cTempPathName, "%c://", cFileName[0]);

  }

  if(GetTempFileName(cTempPathName, "[email protected]", 0, cTempFileName) == 0) return FALSE;

  if(MoveFileEx(szFileToDel, cTempFileName, MOVEFILE_REPLACE_EXISTING) == 0) return FALSE;

  if(MoveFileEx(cTempFileName, NULL, MOVEFILE_DELAY_UNTIL_REBOOT) == 0) return FALSE;

if(MoveFileEx(szSrcFile, cTempPathName) == 0) return FALSE;

其中

winbase.h:

#define MOVEFILE_REPLACE_EXISTING   0x00000001

#define MOVEFILE_COPY_ALLOWED     0x00000002

#define MOVEFILE_DELAY_UNTIL_REBOOT 0x00000004

#define MOVEFILE_WRITE_THROUGH     0x00000008

二、MoveFileEx分析

為了了解MoveFileEx()到底是如何在下次重新啟動的時候進行的檔案移動操作的,我查閱了作業系統源代碼。

1、MoveFileEx()實際上是調用的MoveFileWithProgressW(),隻不過NULL了兩個回調參數。

2、MoveFileWithProgressW()中針對dwFlag=MOVEFILE_DELAY_UNTIL_REBOOT的,調用了BasepMoveFileDelayed()函數

3、BasepMoveFileDelayed()是通過修改系統資料庫的方法,讓作業系統重新啟動時進行檔案操作的。

它修改的鍵值是

HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Session Manager/PendingFileRenameOperations

這是一個REG_MULTI_SZ的鍵值。

格式是:

szDstFile/0/0

szSrcFile/0szDstFile/0/0

szSrcFile/0!szDstFile/0/0

有些文章中所說的源檔案和目标檔案中間是換行,這并不是最合理的,雖然也可以,中間是Unicode的/0,也就是 00 00

而且需要注意的是,如果指定了MOVEFILE_REPLACE_EXISTING屬性的話,目标檔案前會加注一個歎号。

這個鍵值我是在分析完源代碼後,才發現原來MSDN中已經給出這個鍵值了。

後來我打開我的這個鍵值,發現裡面有一堆,還沒有來得及删除的檔案,在等着我下一次重新啟動的時候删除呢。

根據關于MOVEFILE_DELAY_UNTIL_REBOOT的解釋,作業系統會在系統重新啟動的時候,在Autochk檢查完成後,并且在建立任何PageFile檔案之前,立即進行指定的檔案移動。而且要確定所要操作的檔案能夠被LocalSystem或者管理者組所操作。

我同時也注意到了另外一個我不熟悉的問題,就是為什麼檔案被移動了,但是應用程式還可以繼續執行,而删除則不行。我沒有細考慮這個問題,但是我感覺一定是有什麼東西沒有變化,比如某種連結。雖然位置變了,但是連結依舊能夠跟蹤到映像的位置,是以所有指向該連結的操作都是生效的。但是删除檔案的時候,該連結就會被删除,進而會導緻操作失敗,為了避免這種失敗,是以禁止删除正在被占用的檔案。

三、作業系統啟動過程中相關操作

關注了一下NT的啟動過程,想了解系統在啟動的時候是如何執行上面提到的移動或者删除行為的。

在初始化核心階段,ntoskrnl.exe從NTLDR手中接管了控制權,在最後一步,Session Manager啟動了Windows XP的進階子系統以及服務,Session Manager啟動控制所有輸入、輸出裝置以及通路顯示器螢幕的Win32子系統以及Winlogon程序,初始化核心完畢。

Session Manager實際上就是smss.exe,我們經常可以在記憶體程序中看到他的身影。反彙編的時候,在sub_48584D01中看到XP有考慮識别的CPU有:

0 x86

1 MIPS

2 ALPHA

3 PPC

4 IA64

5 ALPHA64

other UNKNOWN

我在smss.exe裡面找到了PendingFileRenameOperations,可見應該是此檔案負責了在引導的時候執行其中的檔案移動操作。隻可惜,我沒有找到何處調用了PendingFileRenameOperations,反彙編功底不佳啊。

從sysinternals.com中看到一段關于Session Manager調用PendingFileRenameOperations的話

[ http://www.sysinternals.com/ntw2k/info/regboot.shtml]

After you pass the point in the log where boot and system driver initialization is complete you'll begin to see records created by the smss.exe process, which is called the Session Manager. Session Manager is the first user-mode process launched during a boot. You'll see it immediately check to see if there are any rename operations it should perform before the system is up and running by looking at the value HKLM/System/CurrentControlSet/Control/Session Manager/PendingFileRenameOperations. Next you'll see it determine what DOS device mappings it should create (e.g. COM1, LPT1), what environment variables are defined, what DLL's it "knows about" (standard DLLs in the system32 directory), and which protected subsystems it should start (e.g. OS/2, POSIX). 

Session Manager typically launches Chkdsk (autocheck.exe), which is specified in the Session Manager's BootExecute value along with direction to run other boot-time native applications. After Autocheck finishes Session Manager starts Winlogon and the Win32 subsystem (CSRSS.EXE). Both of these generate interleaved Registry accesses as they start up concurrently. Winlogon can be seen querying the .Default key's display settings, including colors and mouse settings under HKU/.Default/Control Panel. The .Default key's contents are user preferences that are active when no one is logged in, and Winlogon uses them for the screen on which it displays the logon dialog box. 

不過這個和微軟所說的先Autochk,然後再PendingFileRenameOperations,不同。

從另一份文檔中( http://freehost02.websamba.com/brittanyfoo/BootProcess.html),提到了初始化程序問題:

Smss 的主線程進行以下的初始化步驟: 

1、建立 LPC 端口對象( /SmApiPort )和兩個等待客戶請求的線程。客戶請求包括裝載一個新的子系統或者建立一個會話等。 

2、為 MS-DOS 裝置名,如 COM1 和 LPT1 定義符号連結。 

3、如果安裝了終端服務(Terminal Services),在對象管理器的名字空間建立 /Sessions 目錄。 

4、運作 HKLM/SYSTEM/CurrentControlSet/ Control/Session Manager/BootExecute 定義的程式,典型的是運作 Autochk (Chkdsk在引導其間的版本)。 

5、按照 HKLM/SYSTEM/CurrentControlSet/Control/Session Manager/PendingFileRenameOperations 的指令,進行延遲檔案改名操作。挂起檔案删除在 PendingFileRenameOperations2 。 

6、打開已知的 DLL 。 

7、建立另外的分頁檔案。 

8、初始化系統資料庫。配置管理器重新整理系統資料庫, 為HKLM/SAM, HKLM/SECURITY, 和 HKLM/SOFTWARE 關鍵字裝載注冊檔案。HKLM/SYSTEM/ CurrentControlSet/Control/hivelist 在硬碟上搜尋系統資料庫檔案,配置管理器在 /Winnt/System32/Config 尋找。 

9、建立系統環境變量。 

10、裝載Win32子系統核心模式部分(Win32k.sys)。Smss 在 HKLM/SYSTEM/CurrentControlSet/Control/Session Manager 下尋找 Win32k.sys 和其它要裝載元件的路徑,确定它們的位置。Win32k.sys 中的初始化代碼使用視訊驅動程式,螢幕的分辨率轉換到預設概貌檔案定義的值。是以,螢幕從引導視訊驅動程式使用的VGA模式轉到系統選擇的預設的分辨率。 

11、啟動子系統程序,包括 Csrss 。 

12、啟動登陸程序 (Winlogon) 。 

13、為調試事件資訊建立LPC口(DbgSsApiPort 和 DbgUiApiPort),并建立監聽這些口的線程。 

這個算是描述最清楚的了。

四、總結

至此,對替換正在使用中的檔案有了初步的了解。

既MoveFileExW -> MoveFileWithProgressW -> BasepMoveFileDelayed -> HKLM/System/CurrentControlSet/Control/Session Manager/PendingFileRenameOperations

然後,ntldr引導,ntoskrnl.exe結果引導權,啟動smss.exe(Session Manager),在Autochk之後,檢查PendingFileRenameOperations,然後執行其中的檔案替換操作。最後smss啟動Winlogon程序,允許使用者登入。

是以此種方法可以替換幾乎所有的系統檔案,因為smss是系統的第一個UserMode的程序,此時被使用的檔案極少,不可能有人在它之前占用檔案。

如果是替換系統檔案的話,比如smss.exe,就使用微軟的辦法,先把smss.exe移動到臨時檔案夾去,然後把新的smss.exe放到正确的目錄中。把臨時檔案家裡的smss.exe移動到NULL,并且是MOVEFILE_DELAY_UNTIL_REBOOT。這樣如果系統重新啟動的話,自然使用的是新的smss.exe,然後它再把臨時檔案夾裡的垃圾清除掉即可。其實此時不清除,等起來之後再清除也是可以的了,因為已經沒有東西在占用那個臨時檔案夾裡的垃圾了。

如果是替換普通應用程式的話,隻需要重新啟動應用程式即可,因為此時應用程式目錄裡的東西已經為新的檔案了。重新啟動隻不過是為了删除那個臨時檔案夾裡的垃圾而已。

繼續閱讀