干貨|權限維持之開機自啟動
注冊表
在我們的計算機里面,有一些程序是可以設置成開機自啟的,這種程序一般都是采用往注冊表里面添加鍵值指向自己的程序路徑來實現開機自啟
在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;
這里總代碼有點長,就不貼了,然后就是計劃任務的刪除就簡單許多,只需要調用ITaskFolder的DeleteTask接口函數即可,代碼如下
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已經正常運行
