描述
- 通过CreateProcessAsUser函数,在一个服务进程中创建用户进程,实现ui交互
准备知识
- CFF Explorer:PE查看器,可查看PE文件的导入导出表等信息
- Windows窗口回调
- Windows操作系统向应用程序发送一系列消息,如左键按下和左键抬起,应用程序将通过GetMessage等方法,从消息队列中取出消息,将其提交到窗口过程(WndProc)指向一个应用程序定义的窗口过程的指针
- win32 api编写窗口程序例程:链接
- Qt自己有一套消息处理机制(信号槽),但是也可以使用Windows原生的消息机制,链接
代码
注册服务程序入口函数
int _tmain(int argc, _TCHAR* argv[])
{
SERVICE_TABLE_ENTRY stDispatchTable[] = { {g_szServiceName, (LPSERVICE_MAIN_FUNCTION)ServiceMain}, {NULL, NULL} };
::StartServiceCtrlDispatcher(stDispatchTable);
return 0;
}
服务入口函数
- 注册服务控制处理函数ServiceCtrlHandle
- 完成服务主体工作
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);
DoTask();
}
服务控制处理函数
- 接收来自ControlService发出的服务控制请求,按照基本控制码进行状态判断,通过SetServiceStatus通知SCM对服务状态进行更新
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;
}
}
服务主体工作
- 调用CreateProcessAsUser函数,创建用户态进程
- 通过WTSSendMessage弹出一个用户态提示窗口
void DoTask()
{
ShowMessage("Hello Z5onk0\nThis Message Is From Session 0 Service!\n", "Session 0");
CreateUserProcess("E:\\Project\\Cpp\\BlackHat\\ResourceFree\\result\\ResourceFree.exe");
}
void ShowMessage(TCHAR* lpszMessage, TCHAR* lpszTitle)
{
DWORD dwSessionId = ::WTSGetActiveConsoleSessionId();
DWORD dwResponse = 0;
::WTSSendMessage(WTS_CURRENT_SERVER_HANDLE, dwSessionId,
lpszTitle, (1 + ::lstrlen(lpszTitle)),
lpszMessage, (1 + ::lstrlen(lpszMessage)),
0, 0, &dwResponse, FALSE);
}
BOOL CreateUserProcess(char* lpszFileName)
{
BOOL bRet = TRUE;
DWORD dwSessionID = 0;
HANDLE hToken = NULL;
HANDLE hDuplicatedToken = NULL;
LPVOID lpEnvironment = NULL;
STARTUPINFO si = { 0 };
PROCESS_INFORMATION pi = { 0 };
si.cb = sizeof(si);
do
{
dwSessionID = ::WTSGetActiveConsoleSessionId();
if (FALSE == ::WTSQueryUserToken(dwSessionID, &hToken))
{
ShowMessage("WTSQueryUserToken", "ERROR");
bRet = FALSE;
break;
}
if (FALSE == ::DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL,
SecurityIdentification, TokenPrimary, &hDuplicatedToken))
{
ShowMessage("DuplicateTokenEx", "ERROR");
bRet = FALSE;
break;
}
if (FALSE == ::CreateEnvironmentBlock(&lpEnvironment,
hDuplicatedToken, FALSE))
{
ShowMessage("CreateEnvironmentBlock", "ERROR");
bRet = FALSE;
break;
}
if (FALSE == ::CreateProcessAsUser(hDuplicatedToken,
lpszFileName, NULL, NULL, NULL, FALSE,
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,
lpEnvironment, NULL, &si, &pi))
{
ShowMessage("CreateProcessAsUser", "ERROR");
bRet = FALSE;
break;
}
} while (FALSE);
if (lpEnvironment)
{
::DestroyEnvironmentBlock(lpEnvironment);
}
if (hDuplicatedToken)
{
::CloseHandle(hDuplicatedToken);
}
if (hToken)
{
::CloseHandle(hToken);
}
return bRet;
}
结果
- 用服务加载器来加载和启动服务
- 服务一启动后,立马弹出用户窗口,并且成功运行带ui的用户进程

- 查看ProcMon,可以看到位于session 0的服务进程成功创建了位于session 9的用户进程

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库