解决PPL注入技术的bug

admin 2023年8月5日20:18:21评论14 views字数 3219阅读10分43秒阅读模式

0. 前言

上篇文章中我使用PPLDump在services.exe这个PPL级别为WinTcb-Light的进程中进行shellcode注入,并成功在诸如wininit.exe或MsMpEng.exe等进程中上线CS

但上线后却无法创建进程,CS中一切需要启动新进程的操作都无法完成 (诸如shell / execute / execute-assembly等等) ,但不需要创建进程的操作如文件系统访问 / 进程注入都正常

解决PPL注入技术的bug

经过昨天的研究我找到了原因并写了一个BOF来解决这个问题,本文将阐述产生这个问题的原因

1. 从MSDN入手

创建进程时返回Error Code 87,为Invalid Parameters。所以我仔细查看了MSDN文档中kernel32!CreateProcess每个参数的说明,发现了bInheritHandles参数有下述说明

解决PPL注入技术的bug

也就是说,一个PPL进程中启动非PPL进程时是不能要求子进程继承句柄的。因为services.exe是PPL进程,而cmd.exe不是,所以在调用kernel32!CreateProcess时该参数必须设置为FALSE,否则就会返回Error Code 87


2. beacon中的CreateProcess

我测试了beacon中所有的创建进程的操作,都是将该参数设置为TRUE的。至于为什么这样做,是因为像shell命令需要这样来获取命令执行的输出

beacon获取命令执行输出是通过kernel32!CreatePipe创建匿名管道,然后将子进程的stdout设置为该匿名管道,父进程从管道中读取子进程,但这样做的前提是,bInheritHandles参数需要为TRUE,这样子进程才能继承到匿名管道的句柄。这种利用匿名管道在父子进程间通信的例子借用之前写的一个项目https://github.com/EddieIvan01/win32api-practice/blob/master/PrintSpoofer-webshell/PrintSpoofer/PrintSpoofer.cpp#L443


BOOL CreateProcessWithOutput(HANDLE hToken) {    BOOL result;    DWORD SessionId;    PROCESS_INFORMATION pi;    STARTUPINFO si;    SECURITY_ATTRIBUTES sa = { 0 };
sa.bInheritHandle = TRUE; sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = NULL;
ZeroMemory(&si, sizeof(STARTUPINFO)); ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
DWORD sessionId = WTSGetActiveConsoleSessionId();
HANDLE hReadPipe = NULL; HANDLE hWritePipe = NULL;
result = CreatePipe(&hReadPipe, &hWritePipe, &sa, 0); if (!result) { printf("n[-] CreatePipe error: %dn", GetLastError()); return result; }
si.cb = sizeof(si); si.hStdError = hWritePipe; si.hStdOutput = hWritePipe; si.wShowWindow = SW_HIDE; si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; si.lpDesktop = (LPWSTR)L"winsta0\default";
fflush(stdout); wchar_t command[256]; wcscpy_s(command, L"cmd.exe /c "); wcsncat_s(command, g_pwszCommandLine, wcslen(g_pwszCommandLine));
LPWSTR pwszCurrentDirectory = (LPWSTR)malloc(sizeof(WCHAR) * 512); GetCurrentDirectoryW(512, pwszCurrentDirectory); if (!CreateProcessWithTokenW(hToken, LOGON_WITH_PROFILE, NULL, command, 0, NULL, pwszCurrentDirectory, &si, &pi)) { wprintf(L"CreateProcessWithTokenW() failed. Error: %dn", GetLastError()); goto CLEANUP; } else { wprintf(L"[+] CreateProcessWithTokenW() OKn"); goto RECV_OUTPUT; }
CLEANUP: CloseHandle(pi.hThread); CloseHandle(pi.hProcess); CloseHandle(hReadPipe); free(pwszCurrentDirectory); return result;
RECV_OUTPUT: const int dwResultBufferSize = 1024; // const DWORD timeout = 1000 * 10; char pszResultBuffer[dwResultBufferSize];
// WaitForSingleObject(pi.hProcess, timeout); CloseHandle(hWritePipe);
printf("n====================CMD===================n%wsn", command); printf("n==================OUTPUT==================n");
DWORD n = 0; do { RtlZeroMemory(pszResultBuffer, dwResultBufferSize); if (!ReadFile(hReadPipe, pszResultBuffer, dwResultBufferSize, &n, NULL)) break; printf("%s", pszResultBuffer); // if (pszResultBuffer[n] == EOF) break; } while (n);
printf("n=========================================="); goto CLEANUP;};


3. 解决方法

解决方法无非有两种:

第一是魔改CS的代码,把继承句柄的参数给改为FALSE,但这样会影响像shell这种命令正常获取输出

第二是通过BOF来实现创建进程并获取输出,因为BOF是在当前进程空间加载执行,所以可以完美解决

这里有一个问题,如何获取进程输出?因为前面提到子进程无法继承父进程句柄,所以最优解匿名管道无法使用。那么还有写文件 / 命名管道 / 共享内存 / 套接字等方法,我暂时使用了写文件这个最简单的办法,后续再根据实际情况进行优化

4. 最终效果

我注册了一个tshell命令来调用BOF,用法和shell一样

注入到高PPL级别的进程例如WinTcb-Light级别的wininit.exe可以防止被内存扫描,因为像Windows Defender / 腾讯管家等的服务进程都是Antimalware-Light级别,是无法打开WinTcb-Light级别进程句柄进行扫描的

另外注入到系统关键进程和AV自身的服务进程的效果也是不言自明

注入到wininit.exe

解决PPL注入技术的bug

注入到Windows Defender服务进程MsMpEng.exe

解决PPL注入技术的bug


原文始发于微信公众号(0x4d5a):解决PPL注入技术的bug

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年8月5日20:18:21
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   解决PPL注入技术的bughttps://cn-sec.com/archives/961265.html

发表评论

匿名网友 填写信息