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

渗透技巧 2年前 (2021) admin
1,196 0 0

文章首发于奇安信攻防社区

原文链接:https://forum.butian.net/share/834

注册表

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

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

//用户级HKEY_CURRENT_USERSoftwareMicrosoftWindowsCurrentVersionRunHKEY_CURRENT_USERSoftwareMicrosoftWindowsCurrentVersionRunOnce
干货|权限维持之开机自启动
//管理员权限HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionRunHKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionRunOnceHKEY_LOCAL_MACHINESoftwareMicrosoftWindowsCurrentVersionPoliciesExplorerRun
干货|权限维持之开机自启动

那么我们知道了计算机系统启动程序时会自动加载注册表里面的路径,那么我们实现的思路就是打开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 : %dnn", GetLastError());return FALSE;}else{            printf("[*] RegCreateKeyW successfully!nn");}}else{        printf("[*] RegOpenKeyEx successfully!nn");}if(ERROR_SUCCESS != ::RegSetValueEx(hKey, lpszValueName, 0, REG_SZ, (BYTE*)lpszFileName, (::lstrlenW(lpszFileName) + 16))){::RegCloseKey(hKey);        printf("[!] RegSetValueEx failed, error is : %dnn", GetLastError());return FALSE;}else{        printf("[*] RegSetValueEx successfully!nn");}::RegCloseKey(hKey);return TRUE;}

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

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

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

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

Administrator

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

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

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

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

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

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

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

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

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

快速启动目录

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

不过对于不同的计算机启动目录都是不一样的,原因是因为进入Users这个目录之后,账户的名称都不相同,默认情况下快速启动的路径在C:Users用户名AppDataRoamingMicrosoftWindowsStart MenuProgramsStartup,如图所示,我们将自己的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 : %dnn", GetLastError());return FALSE;}else{        printf("[*] The szStartupPath is : %snn", szStartupPath);}::wsprintfA(szDestFilePath, "%s\%s", szStartupPath, lpszDestFileName);if(FALSE == ::CopyFileA(lpszSrcFilePath, szDestFilePath, FALSE)){        printf("[!] CopyFile failed, error is : %dnn", GetLastError());return FALSE;}else{        printf("[*] CopyFile successfully!nn");}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 failednn", hr);return FALSE;}// 创建一个任务服务(Task Service)实例    hr = ::CoCreateInstance(CLSID_TaskScheduler,        NULL,        CLSCTX_INPROC_SERVER,        IID_ITaskService,(LPVOID*)(&m_lpITS));if(FAILED(hr)){        printf("[!] CoCreateInstance failednn", hr);return FALSE;}// 连接到任务服务(Task Service)    hr = m_lpITS->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t());if(FAILED(hr)){        printf("[!] ITaskService Connect failednn", hr);return FALSE;}// 获取Root Task Folder的指针,这个指针指向的是新注册的任务    hr = m_lpITS->GetFolder(_bstr_t("\"), &m_lpRootFolder);if(FAILED(hr)){        printf("[!] ITaskService GetFolder failednn", 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 failednn", 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 failednn", 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 failednn", 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已经正常运行

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

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


推荐阅读


实战 | 记一次攻防演练


干货 | 绕过AMSI实现免杀的研究和思路


实战 | C++ Socket详解与研究


实战 | 进程启动技术的思路和研究


点赞 在看 转发


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

原文始发于微信公众号(HACK学习呀):干货|权限维持之开机自启动

版权声明:admin 发表于 2021年11月27日 上午1:04。
转载请注明:干货|权限维持之开机自启动 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...