在Win2000/XP上安靜地替換正在使用的系統文件

總是索而不敷總有些過意不去.另外在安焦上灌了兩年水竟然安焦文檔還找不到一個我的名字. 灌不出篇精華帖子還回復不到別人灌的精華貼. 也算得上是個奇蹟了.
  
  要安靜地替換正在使用的系統文件要解決兩個問題:
  1. 替換正在使用的文件.
  2. 在替換系統文件時不顯示插CD的對話框.
  
  微軟有兩個工具可以替換正在使用的文件,zap和inuse. 不過都沒有源代碼, 只好逆向分析了. inuse比較大40K, zap很小7K. 就分析zap了.
  
  用ida打開zap. 就有一個核心函數, 原來它的工作原理是把這個文件移了下位置, 因爲比較簡單就直接貼上代碼.
  
  -------------------cut zap.c---------
  #include <Windows.h>
  
  BOOL ZapDelFile(char *szFileToDel)
  {
  char cTempFileName[0x80];
  char cTempPathName[0x100];
  char cFileName[0x100];
  
  if(szFileToDel[1] == ':'){
  sprintf(cTempPathName, "%c:\\", szFileToDel[0]);
  }
  else{
  GetModuleFileName(NULL, cFileName, 0x100);
  sprintf(cTempPathName, "%c:\\", cFileName[0]);
  }
  
  if(GetTempFileName(cTempPathName, "_@", 0, cTempFileName) == 0){
  return FALSE;
  }
  
  if(MoveFileEx(szFileToDel, cTempFileName, 1) == 0){
  return FALSE;
  }
  
  if(MoveFileEx(cTempFileName, NULL, 4) == 0){
  return FALSE;
  }
  
  return TRUE;
  }
  
  void usage(char *n) {
  printf("usage: %s fileNeedToDel\n", n);
  exit(0);
  }
  
  int main(int argc, char* argv[])
  {
  
  printf("Zap programed by bgate. :) *\n\n");
  
  if (argc != 2)
  usage(argv[0]);
  
  if(ZapDelFile(argv[1]) == TRUE){
  printf("OK");
  }
  else{
  printf("error %d", GetLastError());
  }
  return 0;
  }
  
  -------------------end cat-----------
  
  現在你已經可以用它去刪除正在使用的系統文件了, 不過刪除之後會彈出讓你插入Windows CD對話框.
  注意: 刪系統文件前做好備份, 在重啓前恢復, 另外刪系統文件前還需要把dllcache中相應的備份刪除. 否則系統會自動恢復.
  
  接下來就想辦法去掉這個對話框, 拿出我的法寶--google. 胡亂地搜了一氣. 搜到兩條有用信息.
  1.Windows 2000下執行系統文件保護的代碼在sfc.dll中, Xp系統下在sfc_os.dll中.
  2.註冊表中把一個叫SfcDisable的鍵設爲FFFFFF9D能在下次啓動時讓文件保護功能失效.
  
  下面的分析是在Win2K sp4+上進行的. 其中分析的sfc.dll版本是5.0.2195.6673
  
  用ida打開sfc.dll在string中找sfcdisable, 沒找到! 讓string顯示Unicode. 這下看到了. 找到對SfcDisable引用的一個地方.代碼如下
  .text:769269F9 call _SfcQueryRegDwordWithAlternate@16 ; SfcQueryRegDwordWithAlternate(x,x,x,x)
  .text:769269FE push ebx
  .text:769269FF push offset ??_C@_1BG@HOGG@?$AAS?$AAf?$AAc?$AAD?; "SfcDisable"
  .text:76926A04 push edi
  .text:76926A05 push esi
  .text:76926A06 mov _SFCDebug, eax
  .text:76926A0B call _SfcQueryRegDwordWithAlternate@16 ; SfcQueryRegDwordWithAlternate(x,x,x,x)
  .text:76926A10 push ebx
  .text:76926A11 push offset ??_C@_1BA@HLJH@?$AAS?$AAf?$AAc?$AAS?$AAc?$AAa?$AAn?$AA?$AA@ ; "SfcScan"
  .text:76926A16 push edi
  .text:76926A17 push esi
  .text:76926A18 mov _SFCDisable, eax
  .text:76926A1D call _SfcQueryRegDwordWithAlternate@16 ; SfcQueryRegDwordWithAlternate(x,x,x,x)
  .text:76926A22 push ebx
  .text:76926A23 push offset ??_C@_1BC@KFAJ@?$AAS?$AAf?$AAc?$AAQ?$AAu?$AAo?$AAt?$AAa?$AA?$AA@ ; "SfcQuota"
  .text:76926A28 push edi
  .text:76926A29 push esi
  .text:76926A2A mov _SFCScan, eax
  
  其中_SfcQueryRegDwordWithAlternate@16是讀註冊表的函數. 很明顯, 它把註冊表中SfcDisable的值讀到了_SFCDisable中. 好, 調出softice. 在_SFCDisable上設斷點. 我們又用剛寫的zap去刪系統文件, softice彈出來了. 斷到了下面這個地方, eip爲7692A326, _SFCDisable爲2.
  .text:7692A319 push ecx
  .text:7692A31A and [esp+4+var_4], 0
  .text:7692A31F cmp _SFCDisable, 3
  .text:7692A326 push ebx
  .text:7692A327 push ebp
  .text:7692A328 push esi
  .text:7692A329 push edi
  .text:7692A32A jnz short loc_7692A333
  .text:7692A32C xor eax, eax
  .text:7692A32E jmp loc_7692A459
  F5退出, 一會兒對話框彈了出來, 就對這兒引用了一次. 很好, 看看上面這段代碼"cmp _SFCDisable, 3". 此時_SFCDisable爲2彈出了對話框, 那麼我就把它改爲3又用zap刪系統文件試試. 哈, 運氣很好, 這次沒出現讓插CD的對話框了. 也就是說只要我們把_SFCDisable改爲3就能偷偷地替換系統文件了. 不過不同版本這個地址是不一樣的, 用switch來做這個活總是不好. 得寫個有通用性的代碼.
  
  開始我想它的工作原理大概是Winlogon發現了有對系統文件進行操作. 便調用sfc.dll中的輸出函數進行檢查. 我們就只需得到這個輸出函數入口然後把這個函數"註釋"掉就可以了.跟着上面這段代碼逆流而上, 找到最後由76924544輸出, 又在76924544上加個斷點, 繼續去刪文件. softice跳出來了, 不過不在函數的入口, 反倒在剛纔設置的對_SFCDisable的讀取上, 沒運行函數的入口就運行了函數體中的代碼, 看來遇到高人了. 非得逼我出必殺技, 打開2000源代碼 : ). 找了半天沒找到相應代碼又只得退回來看彙編, 最後發現了這個函數NtWaitForMultipleObjects. 呵, 難怪沒中斷在函數的入口上, 原來早運行了函數的入口然後在函數體裏一直沒退出. 註釋函數的方法不行了.
  
  這時我想它的工作原理大概是winlogon調用sfc.dll中的輸出函數在系統啓動時創建了一系列事件. 既然winlogon創建了, 那麼它也應該得撤銷. 用depends打開winlogon. 果然從sfc.dll中輸入了兩個函數. 一個是剛纔分析的那個, 創建了一系列事件. 看看另一個, 輸出地址是76926869, 不出所料, 關閉了一系列事件. 現在我們只要向winlogon中注入代碼調用"另一個"函數就能取消文件保護功能了. 不過winlogon不能隨便注入代碼. 26A雜誌第六期上有篇文章提到了注入方法:"adjust debugger access rightz to our process". 那也是一篇SFCDisable的文章, 他用的方法是在內存中搜索特徵碼, 然後修改. 通用性應該沒這麼好.
  
  下面的注入方法是從crazylord的代碼中拷過來的, 不過方法不是. :), 寫完後就懶得檢查了, 加之水平有限, 寫的不過優雅的地方就將就着看.
  
  -----------------cut antisfc.c-----------
  
  #include <stdlib.h>
  #include "Windows.h"
  #include "Tlhelp32.h"
  #pragma comment( lib, "Advapi32.lib" )
  
  typedef void (_stdcall * CLOSEEVENTS)(void);
  typedef unsigned long DWORD;
  typedef DWORD ANTISFC_ACCESS;
  
  /*
  * ANTISFC structures
  */
  
  typedef struct _ANTISFC_PROCESS {
  DWORD Pid; // process pid
  HANDLE ProcessHandle; // process handle
  char ImageName[MAX_PATH]; // p_w_picpath name (not full path)
  } ANTISFC_PROCESS, *PANTISFC_PROCESS;
  
  __inline void ErrorMessageBox(char *szAdditionInfo)
  {
  printf("error on %s, error code %d. \n", szAdditionInfo, GetLastError());
  }
  
  void usage(char *n) {
  printf("usage: %s [/d]\n", n);
  printf("\t/d: disable sfc file protecte fuction.\n");
  exit(0);
  }
  
  DWORD Init() {
  DWORD Ret = 0;
  HANDLE hToken;
  LUID sedebugnameValue;
  TOKEN_PRIVILEGES tkp;
  
  if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {
  ErrorMessageBox("OpenProcessToken");
  } else {
  
  if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &sedebugnameValue)) {
  ErrorMessageBox("LookupPrivilegeValue");
  } else {
  
  tkp.PrivilegeCount = 1;
  tkp.Privileges[0].Luid = sedebugnameValue;
  tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
  
  if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof tkp, NULL, NULL)) {
  ErrorMessageBox("AdjustTokenPrivileges");
  } else {
  Ret = 1;
  }
  }
  CloseHandle(hToken);
  }
  
  return(Ret);
  }
  
  DWORD GetPidEx(char *proc_name, char *full_path) {
  DWORD dwPid=0;
  HANDLE hSnapshot;
  PROCESSENTRY32 pe;
  BOOL Ret;
  
  if (isdigit(proc_name[0]))
  dwPid = strtoul(proc_name, NULL, 0);
  else
  dwPid = -1;
  
  hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if (hSnapshot == (HANDLE) -1){
  ErrorMessageBox("CreateToolhelp32Snapshot");
  return(0);
  }
  
  pe.dwSize = sizeof(PROCESSENTRY32);
  Ret = Process32First(hSnapshot, &pe);
  
  while (Ret) {
  if((strncmp(strlwr(pe.szExeFile), strlwr(proc_name), strlen(proc_name)) == 0)
  || (pe.th32ProcessID == dwPid)) {
  dwPid = pe.th32ProcessID;
  strcpy(full_path, pe.szExeFile);
  break;
  }
  pe.dwSize = sizeof(PROCESSENTRY32);
  Ret = Process32Next(hSnapshot, &pe);
  }
  
  CloseHandle(hSnapshot);
  if (dwPid == -1)
  dwPid = 0;
  return(dwPid);
  }
  
  DWORD InitProcess(PANTISFC_PROCESS Process, char *proc_name, ANTISFC_ACCESS access) {
  DWORD Ret=0;
  
  Process->Pid = GetPidEx(proc_name, Process->ImageName);
  if (Process->Pid != 0 && Process->ImageName[0] != 0) {
  Process->ProcessHandle = OpenProcess(access, FALSE, Process->Pid);
  if (Process->ProcessHandle == NULL)
  ErrorMessageBox("OpenProcess");
  else
  Ret = 1;
  }
  
  return(Ret);
  }
  
  DWORD InjectThread(PANTISFC_PROCESS Process,
  PVOID function) {
  HANDLE hThread;
  DWORD dwThreadPid = 0, dwState;
  
  hThread = CreateRemoteThread(Process->ProcessHandle,
  NULL,
  0,
  (DWORD (__stdcall *) (void *)) function,
  NULL,
  0,
  &dwThreadPid);
  if (hThread == NULL) {
  ErrorMessageBox("CreateRemoteThread");
  goto cleanup;
  }
  
  dwState = WaitForSingleObject(hThread, 4000); // attends 4 secondes
  
  switch (dwState) {
  case WAIT_TIMEOUT:
  case WAIT_FAILED:
  ErrorMessageBox("WaitForSingleObject");
  goto cleanup;
  
  case WAIT_OBJECT_0:
  break;
  
  default:
  ErrorMessageBox("WaitForSingleObject");
  goto cleanup;
  }
  
  CloseHandle(hThread);
  return dwThreadPid;
  
  cleanup:
  CloseHandle(hThread);
  
  return 0;
  }
  
  int main(int argc, char* argv[])
  {
  ANTISFC_PROCESS Process;
  HMODULE hSfc;
  DWORD dwThread;
  CLOSEEVENTS pfnCloseEvents;
  DWORD dwVersion;
  
  printf("AntiSfc programed by bgate. :) *\n\n");
  
  if (argc != 2)
  usage(argv[0]);
  
  if (strcmp(argv[1], "/d") != 0) {
  usage(argv[0]);
  }
  
  if (Init()) {
  printf("debug privilege set\n");
  } else {
  printf("error on get debug privilege\n");
  return(0);
  }
  
  if(InitProcess(&Process, "winlogon.exe", PROCESS_ALL_ACCESS) == 0) {
  printf("error on get process info. \n");
  return(0);
  }
  
  dwVersion = GetVersion();
  if ((DWORD)(LOBYTE(LOWORD(dwVersion))) == 5){ // Windows 2000/XP
  if((DWORD)(HIBYTE(LOWORD(dwVersion))) == 0){ //Windows 2000
  hSfc = LoadLibrary("sfc.dll");
  printf("Win2000\n");
  }
  else {//if((DWORD)(HIBYTE(LOWORD(dwVersion))) = 1) //Windows XP
  hSfc = LoadLibrary("sfc_os.dll");
  printf("Windows XP\n");
  }
  }
  //else if () //2003?
  else {
  printf("unsupported version\n");
  }
  
  pfnCloseEvents = (CLOSEEVENTS)GetProcAddress(hSfc,
  MAKEINTRESOURCE(2));
  if(pfnCloseEvents == NULL){
  printf("Load the sfc fuction failed\n");
  FreeLibrary(hSfc);
  return(0);
  }
  
  FreeLibrary(hSfc);
  
  dwThread = InjectThread(&Process,
  pfnCloseEvents);
  
  if(dwThread == 0){
  printf("failed\n");
  }
  else{
  printf("OK\n");
  }
  
  CloseHandle(Process.ProcessHandle);
  return(0);
  
  }
 
------------------end cut---------  
在運行zap替換系統文件前運行一下antisfc就行了, 你也可以把它們寫到一起. 理論上他能在2000, xp, 2003?的任何版本上使用. 不過我只在Win2K sp4+, WinXP sp1+上測試過.
  本文的缺點是替換的系統文件只能在重啓後生效, 寫完了.

附轉:在Win 98/Me下輕鬆替換使用着的文件
大家都知道,在Windows 98/Me下,有一個“系統文件查看器”,使用該工具我們可以輕鬆地查找出系統裏的哪些文件被損壞了,並可以從安裝盤中將對應的文件提取出來,替換已經損壞的文件。在Windows XP中,這個工具不見了,其實啊,Windows XP也有能實現這個工具功能的命令,它就是Replace.exe。該命令在Windows安裝目錄的System32文件夾下,它的主要功能,就是替換文件,與Windows 98/Me中的“系統文件查看器”不一樣的是,它能夠替換正在使用中的文件!

  例如,我們用Windows Media Player播放一首路徑爲“g:\temp\source.mp3”的歌曲,然後在命令提示符窗口下鍵入下列命令:

  replace.exe d:\windows\temp\source.mp3 g:\temp

  這裏的“d:\windows\temp\source.mp3”是準備用來替換的另一首MP3歌曲,注意兩個文件的文件名必須保持一致,很快我們就會在看到替換成功的提示,接下來Windows Media Player窗口正在播放的歌曲也會自動變爲另一首歌曲,呵呵,夠神奇的吧?當然,replace命令的最大好處是用來替換系統文件,這樣就不需要重新啓動到安全模式下了!

  Replace.exe命令的使用參數如下,有興趣的朋友可以深入研究一下:

  /A——把新文件加入目標目錄,注意不能與/S或/U搭配使用。

  /P——替換文件或加入源文件之前提醒用戶進行確認。

  /R——替換隻讀文件以及未受保護的文件。

  /S——替換目標目錄中所有子目錄中的文件。

  /W——等待用戶插入磁盤後再運行。

  /U——只替換或更新比源文件日期早的文件,不能與/A搭配使用。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章