干货|权限维持之开机自启动

VSole2021-11-27 07:16:51

注册表

在我们的计算机里面,有一些程序是可以设置成开机自启的,这种程序一般都是采用往注册表里面添加键值指向自己的程序路径来实现开机自启

在windows里面开机自启的注册表路径如下

//用户级
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce

//管理员权限
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run

那么我们知道了计算机系统启动程序时会自动加载注册表里面的路径,那么我们实现的思路就是打开Run这个目录的注册表,然后修改注册表数据这个项目指向的路径即可

User

首先使用RegOpenKeyEx打开Software\\Microsoft\\Windows\\CurrentVersion\\Run这个注册表

::RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_WRITE, &hKey)

这里作一个判断,如果不存在这个路径则用RegCreateKeyW这个api创建注册表路径

::RegCreateKeyW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", &hKey)

然后使用RegSetValueEx这个api来修改注册表值,完成后关闭句柄即可

::RegSetValueEx(hKey, lpszValueName, 0, REG_SZ, (BYTE*)lpszFileName, (::lstrlenW(lpszFileName) + 16))
::RegCloseKey(hKey)

完整代码如下

DWORD REUserRegedit(LPWSTR lpszValueName, LPWSTR lpszFileName)
{
    HKEY hKey;
if(ERROR_SUCCESS != ::RegOpenKeyEx(HKEY_CURRENT_USER,
        L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_WRITE, &hKey))
{
        printf("[!] RegOpenKeyEx not found, try create keyvalue");
if(ERROR_SUCCESS != ::RegCreateKeyW(HKEY_CURRENT_USER,
            L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", &hKey))
{
            printf("[!] RegCreateKeyW failed, error is : %d", GetLastError());
return FALSE;
}
else
{
            printf("[*] RegCreateKeyW successfully!");
}
}
else
{
        printf("[*] RegOpenKeyEx successfully!");
}
if(ERROR_SUCCESS != ::RegSetValueEx
(hKey, lpszValueName, 0, REG_SZ, (BYTE*)lpszFileName, (::lstrlenW(lpszFileName) + 16)))
{
::RegCloseKey(hKey);
        printf("[!] RegSetValueEx failed, error is : %d", GetLastError());
return FALSE;
}
else
{
        printf("[*] RegSetValueEx successfully!");
}
::RegCloseKey(hKey);
return TRUE;
}

这里我带参数进去调试一下

可以看到注册表已经添加成功,重启之后就会自动去运行这个路径的exe

Administrator

这里我本来以为把RegOpenKeyEx改成HKEY_LOCAL_MACHINE就可以了

但是报错如下,说不存在这个路径,但是我去注册表里面看却是存在这个路径的

这里百度过后发现在64位系统里面关键的注册表被重定位了,修改后的路径为

HKEY_LOCAL_MACHINE\\Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Run

修改一下即可,这列重启后计算机就会自动去启动

之前我们提到有一个HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce这个路径,也是能够添加指向程序的路径来实现自启动的,这里跟之前有一点不同的就是这个路径下的key在重启后执行一次就会自动删除,而Run目录下的注册表如果不去管他他会一直存在

快速启动目录

快速启动目录是一种不用修改任何系统数据,并且实现起来最为简单的开机自启动方法。只要把程序放入快速启动文件夹中,系统在启动时就会自动加载并运行相应的程序,实现开机自启动功能。

不过对于不同的计算机启动目录都是不一样的,原因是因为进入Users这个目录之后,账户的名称都不相同,默认情况下快速启动的路径在C:\Users\用户名\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup,如图所示,我们将自己的exe放到这个目录下即可

因为这里每台计算机的目录都不同,所以需要用到api来获取路径,使用到的是SHGetSpecialFolderPath

BOOL SHGetSpecialFolderPath(
    HWND hwndOwner,
    LPTSTR lpszPath,
int nFolder,
    BOOL fCreate)

首先获取当前计算机的快速启动目录

::SHGetSpecialFolderPath(NULL, szStartupPath, CSIDL_STARTUP, TRUE)

使用wsprintf放入缓冲区

::wsprintf(szDestFilePath, (LPCWSTR)"%s\\%s", szStartupPath, lpszDestFileName);

然后使用CopyFile拷贝到快速启动路径

::CopyFile(lpszSrcFilePath, szDestFilePath, FALSE)::CopyFile(lpszSrcFilePath, szDestFilePath, FALSE)

完整代码如下

DWORD AutoSetup(char* lpszSrcFilePath, char* lpszDestFileName)
{
    CHAR szStartupPath[MAX_PATH] = { 0};
    CHAR szDestFilePath[MAX_PATH] = { 0};
if(FALSE == ::SHGetSpecialFolderPathA(NULL, szStartupPath, CSIDL_STARTUP, TRUE))
{
        printf("[!] Get szStartupPath failed, error is : %d", GetLastError());
return FALSE;
}
else
{
        printf("[*] The szStartupPath is : %s", szStartupPath);
}
::wsprintfA(szDestFilePath, "%s\\%s", szStartupPath, lpszDestFileName);
if(FALSE == ::CopyFileA(lpszSrcFilePath, szDestFilePath, FALSE))
{
        printf("[!] CopyFile failed, error is : %d", GetLastError());
return FALSE;
}
else
{
        printf("[*] CopyFile successfully!");
}
return TRUE;
}

实现效果如下,可以看到已经被复制到了启动项,重启后即会自动启动

计划任务

这部分在进行代码实现的过程中会用到COM组件的相关知识,因为知识储备的原因,这里我就不展开细说这部分的具体操作,只能大概说一下实现的流程

使用COM组件之前需要调用CoInitialize来初始化COM接口环境,再使用CoCreateInstance创建服务对象ITaskService并连接到任务服务上,再从ITaskService对象中获取根任务Root Task Folder的指针对象ITaskFolder,指向的是新注册的任务,到这里初始化操作就已经完成,代码实现如下

CMyTaskSchedule::CMyTaskSchedule(void)
{
    m_lpITS = NULL;
    m_lpRootFolder = NULL;
// 初始化COM
    HRESULT hr = ::CoInitialize(NULL);
if(FAILED(hr))
{
        printf("[!] CoInitialize failed", hr);
return FALSE;
}
// 创建一个任务服务(Task Service)实例
    hr = ::CoCreateInstance(CLSID_TaskScheduler,
        NULL,
        CLSCTX_INPROC_SERVER,
        IID_ITaskService,
(LPVOID*)(&m_lpITS));
if(FAILED(hr))
{
        printf("[!] CoCreateInstance failed", hr);
return FALSE;
}
// 连接到任务服务(Task Service)
    hr = m_lpITS->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t());
if(FAILED(hr))
{
        printf("[!] ITaskService Connect failed", hr);
return FALSE;
}
// 获取Root Task Folder的指针,这个指针指向的是新注册的任务
    hr = m_lpITS->GetFolder(_bstr_t("\\"), &m_lpRootFolder);
if(FAILED(hr))
{
        printf("[!] ITaskService GetFolder failed", hr);
return FALSE;
}
}

然后是创建计划任务的操作,首先从ITaskService创建一个任务定义对象ITaskDefinition

ITaskDefinition*pTaskDefinition = NULL;
HRESULT hr = m_lpITS->NewTask(0, &pTaskDefinition);

设置计划任务的注册信息&作者信息

IRegistrationInfo*pRegInfo = NULL;
CComVariant variantAuthor(NULL);
    variantAuthor = lpszAuthor;
    hr = pTaskDefinition->get_RegistrationInfo(&pRegInfo);
if(FAILED(hr))
{
        printf("pTaskDefinition::get_RegistrationInfo failed", hr);
return FALSE;
}
    hr = pRegInfo->put_Author(variantAuthor.bstrVal);
    pRegInfo->Release();

再设置主题信息,包括登陆类型与运行权限

IPrincipal*pPrincipal = NULL;
    hr = pTaskDefinition->get_Principal(&pPrincipal);
if(FAILED(hr))
{
        printf("pTaskDefinition->get_Principal failed", hr);
return FALSE;
}
    hr = pPrincipal->put_LogonType(TASK_LOGON_INTERACTIVE_TOKEN);
    hr = pPrincipal->put_RunLevel(TASK_RUNLEVEL_HIGHEST);  
    pPrincipal->Release();

设置其他信息

ITaskSettings*pSettting = NULL;
    hr = pTaskDefinition->get_Settings(&pSettting);
if(FAILED(hr))
{
        printf("pTaskDefinition->get_Settings failed", hr);
return FALSE;
}
    hr = pSettting->put_StopIfGoingOnBatteries(VARIANT_FALSE);
    hr = pSettting->put_DisallowStartIfOnBatteries(VARIANT_FALSE);
    hr = pSettting->put_AllowDemandStart(VARIANT_TRUE);
    hr = pSettting->put_StartWhenAvailable(VARIANT_FALSE);
    hr = pSettting->put_MultipleInstances(TASK_INSTANCES_PARALLEL);
    pSettting->Release();

创建执行动作,包括设置执行程序的路径和参数

IActionCollection*pActionCollect = NULL;
    hr = pTaskDefinition->get_Actions(&pActionCollect);
if(FAILED(hr))
{
ShowError("pTaskDefinition::get_Actions", hr);
return FALSE;
}
IAction*pAction = NULL;
    hr = pActionCollect->Create(TASK_ACTION_EXEC, &pAction);
    pActionCollect->Release();

再设置执行程序路径和参数

CComVariant variantProgramPath(NULL);
CComVariant variantParameters(NULL);
IExecAction*pExecAction = NULL;
    hr = pAction->QueryInterface(IID_IExecAction, (PVOID *)(&pExecAction));
if(FAILED(hr))
{
        pAction->Release();
ShowError("IAction::QueryInterface", hr);
return FALSE;
}
    pAction->Release();
    variantProgramPath = lpszProgramPath;
    variantParameters = lpszParameters;
    pExecAction->put_Path(variantProgramPath.bstrVal);
    pExecAction->put_Arguments(variantParameters.bstrVal);
    pExecAction->Release();

创建触发器,实现用户登录的自启动

ITriggerCollection*pTriggers = NULL;
    hr = pTaskDefinition->get_Triggers(&pTriggers);
if(FAILED(hr))
{
ShowError("pTaskDefinition::get_Triggers", hr);
return FALSE;
}
ITrigger*pTrigger = NULL;
    hr = pTriggers->Create(TASK_TRIGGER_LOGON, &pTrigger);
if(FAILED(hr))
{
ShowError("ITriggerCollection::Create", hr);
return FALSE;
}

注册计划任务

IRegisteredTask*pRegisteredTask = NULL;
CComVariant variantTaskName(NULL);
    variantTaskName = lpszTaskName;
    hr = m_lpRootFolder->RegisterTaskDefinition(variantTaskName.bstrVal,
        pTaskDefinition,
        TASK_CREATE_OR_UPDATE,
_variant_t(),
_variant_t(),
        TASK_LOGON_INTERACTIVE_TOKEN,
_variant_t(""),
&pRegisteredTask);
if(FAILED(hr))
{
        pTaskDefinition->Release();
ShowError("ITaskFolder::RegisterTaskDefinition", hr);
return FALSE;
}
    pTaskDefinition->Release();
    pRegisteredTask->Release();
return TRUE;

这里总代码有点长,就不贴了,然后就是计划任务的删除就简单许多,只需要调用ITaskFolderDeleteTask接口函数即可,代码如下

BOOL CMyTaskSchedule::Delete(char*lpszTaskName)
{
if(NULL == m_lpRootFolder)
{
return FALSE;
}
CComVariant variantTaskName(NULL);
    variantTaskName = lpszTaskName;
    HRESULT hr = m_lpRootFolder->DeleteTask(variantTaskName.bstrVal, 0);
if(FAILED(hr))
{
return FALSE;
}
return TRUE;
}

这里尝试一下创建名为cs的计划任务成功,如下所示

系统服务

在任务管理器里面可以发现有许多系统服务进程在后台运行,而且有很多应用事随着系统的启动而启动的,如下图所示

系统服务运行在session0,有点基础的同学都有一个0环、3环的概念,在0环里面各个对话是相互独立的,在不同的会话中相互之间是不能够进行通信的,这也是为什么在系统服务中不能显示程序界面的原因

这里使用系统服务创建自启动服务的思路应该是打开句柄判断操作,若为StartService则启动服务,若为ControlService则停止服务,若为DeleteService则删除服务。

服务程序主函数(main)

调用系统函数StartServiceCtrlDispatcher连接程序主线程到服务控制管理程序(其中定义了服务入口点函数是ServiceMain)。服务控制管理程序启动服务程序后,等待服务程序主函数调用StartServiceCtrlDispatcher。如果没有调用该函数,设置服务入口点,则会报错。

执行服务初始化任务(同时执行多个服务的服务有多个入口点函数),首先调用RegisterServiceCtrlHandler定义控制处理程序函数(本例中是ServiceCtrlHandle),初始化后通过SetServiceStatus设定进行运行状态,然后运行服务代码。

控制处理程序(Handler)

在服务收到控制请求时由控制分发线程引用(最少要有停止服务的能力)。

我们首先创建启动系统服务,首先是获取文件名的操作

::lstrcpyA(szName, lpszDriverPath);
::PathStripPathA(szName);

然后使用OpenSCManager打开服务控制管理器数据库

OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

打开数据库后我们打开一个已经存在的服务

OpenServiceA(shOSCM, szName, SERVICE_ALL_ACCESS);

这里写一个switch...case...的分支,0为加载服务,1为启动服务,2为停止服务,3为删除服务,这里CreateServiceA的第四个参数有两种方式,一种是SERVICE_AUTO_START开机自启,另外一种是SERVICE_DEMAND_START手动启动

// 创建服务
shCS = ::CreateServiceA(shOSCM, szName, szName,SERVICE_ALL_ACCESS,SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS,
SERVICE_AUTO_START,SERVICE_ERROR_NORMAL,lpszDriverPath, NULL, NULL, NULL, NULL, NULL);
// 启动服务
::StartService(shCS, 0, NULL);
// 停止服务
::ControlService(shCS, SERVICE_CONTROL_STOP, &ss);
// 删除服务
::DeleteService(shCS);

最后关闭句柄

::CloseServiceHandle(shCS);
::CloseServiceHandle(shOSCM);

我们再进行系统服务程序的编写,自启动服务程序要求程序创建服务入口点函数,否则就不能创建系统服务。

首先我们注册服务入口函数

SERVICE_TABLE_ENTRY stDispatchTable[] = { { g_szServiceName, (LPSERVICE_MAIN_FUNCTION)ServiceMain}, { NULL, NULL } };
::StartServiceCtrlDispatcher(stDispatchTable);

然后就是ServiceMain入口函数如下所示

void __stdcall ServiceMain(DWORD dwArgc, char*lpszArgv)
{
    g_ServiceStatus.dwServiceType = SERVICE_WIN32;
    g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
    g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
    g_ServiceStatus.dwWin32ExitCode = 0;
    g_ServiceStatus.dwServiceSpecificExitCode = 0;
    g_ServiceStatus.dwCheckPoint = 0;
    g_ServiceStatus.dwWaitHint = 0;
    g_ServiceStatusHandle = ::RegisterServiceCtrlHandler(g_szServiceName, ServiceCtrlHandle);
    g_ServiceStatus.dwCurrentState = SERVICE_RUNNING;
    g_ServiceStatus.dwCheckPoint = 0;
    g_ServiceStatus.dwWaitHint = 0;
::SetServiceStatus(g_ServiceStatusHandle, &g_ServiceStatus);
while(TRUE)
{
Sleep(5000);
DoTask();
}
}
void __stdcall ServiceCtrlHandle(DWORD dwOperateCode)
{
switch(dwOperateCode)
{
case SERVICE_CONTROL_PAUSE:
{
// 暂停
        g_ServiceStatus.dwCurrentState = SERVICE_PAUSED;
break;
}
case SERVICE_CONTROL_CONTINUE:
{
// 继续
        g_ServiceStatus.dwCurrentState = SERVICE_RUNNING;
break;
}
case SERVICE_CONTROL_STOP:
{
// 停止
        g_ServiceStatus.dwWin32ExitCode = 0;
        g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
        g_ServiceStatus.dwCheckPoint = 0;
        g_ServiceStatus.dwWaitHint = 0;
::SetServiceStatus(g_ServiceStatusHandle, &g_ServiceStatus);
break;
}
case SERVICE_CONTROL_INTERROGATE:
{
// 询问
break;
}
default:
break;
}
}

首先编译生成ServiceTest.exe

这里运行主程序之后发现ServiceTest.exe已经启动

打开服务管理,发现ServiceTest.exe已经正常运行

printf开机启动项
本作品采用《CC 协议》,转载必须注明作者和本文链接
注册表在我们的计算机里面,有一些程序是可以设置成开机自启的,这种程序一般都是采用往注册表里面添加键值指向自己的程序路径来实现开机自启在windows里面开机自启的注册表路径如下//用户级。
mysql提权总结
2021-09-17 15:04:08
使用过MySQL的人都知道,MySQL有很多内置函数提供给使用者,包括字符串函数、数值函数、日期和时间函数等,给开发人员和使用者带来了很多方便。
一、前言 二、Windows入侵排查 检查系统账号安全 检查异常端口、进程 检查启动项、计划任务、服务 检查系统相关信息 自动化查杀 日志分析 三、Linux入侵排查 账号安全 历史命令 检查异常端口 检查异常进程 检查开机启动项 检查定时任务 检查服务 检查异常文件 检查系统日志
systeminfo | findstr /B /C:"OS Name" /C:"OS Version". systeminfo | findstr /B /C:"OS 名称" /C:"OS 版本" #中文操作系统。schtasks /query /fo LIST /v #query显示所有计划任务。
入侵者在入侵成功后,往往会留下后门以便再次访问被入侵的系统,而创建系统账号是一种比较常见的后门方式。在做入侵排查的时候,用户配置文件/etc/passwd和密码配置文件/etc/shadow是需要去重点关注的地方。查询特权用户特权用户> awk -F: '$3==0{print $1}' /etc/passwd. 查找远程可以登录的账户> awk '/\$1|\$5|\$6/{print $1}' /etc/shadow. $1:MD5$5:SHA-256$6:SHA-512检查sudo权限> cat /etc/sudoers | grep -v "^#\|^$" | grep "ALL=(ALL". 检查计划任务利用计划任务进行权限维持,可作为一种持久性机制被入侵者利用。
用户名:加密密码:密码最后一次修改日期:两次密码的修改时间间隔:密码有效期:密码修改到期到的警告天数:密码过期之后的宽限天数:账号失效时间:保留。查看下pid所对应的进程文件路径,
应急响应之入侵排查
2022-07-19 17:10:59
常见的应急响应事件分类: Web入侵:网页挂马、主页篡改、Webshell 系统入侵:病毒木马、勒索软件、远控后门 网络攻击:DDOS攻击、DNS劫持、ARP欺骗
当企业发生网络安全事件时,急需第一时间进行处理,使企业的网络信息系统在最短时间内恢复正常工作,同时还需进一步查找入侵来源,还原入侵事故过程,给出解决方案与防范措施,为企业挽回或减少经济损失。 常见的网络安全事件:
VSole
网络安全专家