在Win2000/XP上安静地替换正在使用的系统文件

作者:Bgate

总是索而不敷总有些过意不去.另外在安焦上灌了两年水竟然安焦文档还找不到一个我的名字. 灌不出篇精华帖子还回复不到别人灌的精华贴. 也算得上是个奇迹了.

  要安静地替换正在使用的系统文件要解决两个问题:
  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(NULLcFileName0x100);
    
sprintf(cTempPathName"%c:\\"cFileName[0]);
}

if(
GetTempFileName(cTempPathName"_@"0cTempFileName) == 0){
    return 
FALSE;
}

if(
MoveFileEx(szFileToDelcTempFileName1) == 0){
    return 
FALSE;
}

if(
MoveFileEx(cTempFileNameNULL4) == 0){
    return 
FALSE;
}

return 
TRUE;
}

void usage(char *n) {
printf("usage: %s fileNeedToDel\n"n);
exit(
0);
}

int main(int argccharargv[])
{

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 _SFCDebugeax
.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 _SFCDisableeax
.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 _SFCScaneax

其中_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 _SFCDisable3
.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 eaxeax
.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]; // image name (not full path)
ANTISFC_PROCESS, *PANTISFC_PROCESS;

__inline void ErrorMessageBox(char *szAdditionInfo)
{
printf("error on %s, error code %d. \n"szAdditionInfoGetLastError());
}

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(NULLSE_DEBUG_NAME, &sedebugnameValue)) {
    
ErrorMessageBox("LookupPrivilegeValue");
    } else {

    
tkp.PrivilegeCount 1;
    
tkp.Privileges[0].Luid sedebugnameValue;
    
tkp.Privileges[0].Attributes SE_PRIVILEGE_ENABLED;

    if (!
AdjustTokenPrivileges(hTokenFALSE, &tkpsizeof tkpNULLNULL)) {
    
ErrorMessageBox("AdjustTokenPrivileges");
    } else {
    
Ret 1;
    }
    }
    
CloseHandle(hToken);
}

return(
Ret);
}

DWORD GetPidEx(char *proc_namechar *full_path) {
DWORD dwPid=0;
HANDLE hSnapshot;
PROCESSENTRY32 pe;
BOOL Ret;
    
if (
isdigit(proc_name[0]))
    
dwPid strtoul(proc_nameNULL0);
else
    
dwPid = -1;
    
hSnapshot CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS0);
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_pathpe.szExeFile);
    break;
    }
    
pe.dwSize sizeof(PROCESSENTRY32);
    
Ret Process32Next(hSnapshot, &pe);
}

CloseHandle(hSnapshot);
if (
dwPid == -1)
    
dwPid 0;
return(
dwPid);
}

DWORD InitProcess(PANTISFC_PROCESS Processchar *proc_nameANTISFC_ACCESS access) {
DWORD Ret=0;

Process->Pid GetPidEx(proc_nameProcess->ImageName);
if (
Process->Pid != && Process->ImageName[0] != 0) {
    
Process->ProcessHandle OpenProcess(accessFALSEProcess->Pid);
    if (
Process->ProcessHandle == NULL)
    
ErrorMessageBox("OpenProcess");
    else
    
Ret 1;
}

return(
Ret);
}

DWORD InjectThread(PANTISFC_PROCESS Process,
    
PVOID function) {
HANDLE hThread;
DWORD dwThreadPid 0dwState;

hThread CreateRemoteThread(Process->ProcessHandle,
    
NULL,
    
0,
    (
DWORD (__stdcall *) (void *)) function,
    
NULL,
    
0,
    &
dwThreadPid);
if (
hThread == NULL) {
    
ErrorMessageBox("CreateRemoteThread");
    
goto cleanup;
}

dwState WaitForSingleObject(hThread4000); // 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 argccharargv[])
{
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搭配使用。

你可能感兴趣的:(在Win2000/XP上安静地替换正在使用的系统文件)