Detect SAM File Corruption under Windows 7 (Part 1)





5.00/5 (3 votes)
This first part of the article presents how to inject a DLL inside lsass process by using Windows service
Introduction
This article presents and explains how to gain access to SAM file while Windows 7 system is running. The goal here is not to give a method to hack Windows systems, but this solution is designed to help system administrators in detecting the SAM file corruption.
Several approaches exist to extract user credentials from SAM file either under a running system or when the system is turned off. In this article, we have opted for the first approach. Indeed, for our purpose, we need the most recent credentials in order to compare them to the previously saved one and emit an alert if the comparison fails.
This approach works by performing DLL injection inside the Local Security Authority Subsystem Service (LSASS) process (better known as lsass.exe). It is a way that malware uses to run a DLL inside another process, thereby providing that DLL with all of the privileges of that process. Hash dumping tools often target lsass.exe because it has the necessary privilege level as well as access to many useful API functions.
When the DLL was injected, it uses undocumented API functions like SamIConnect
, SamQueryInformationUser
and SamIGetPrivateData
to extract hashes from SAM file.
Once the hashes were dumped, they are compared to the previously saved ones. If the comparison fails, the system emits an alert.
Background
Our application, developed under Visual Studio 2013 is composed of three programs:
- CheckSam.exe: contains a Windows service that injects a DLL inside the
lsass
process. Our choice of using a Windows service is motivated by the introduction since Vista of 'Session Separation' protection. ‘Session Separation' ensures that core system processes including services always run in session 0 while all user process's run in different sessions. As a result, any process running in user session fails to inject DLL into system process as "CreateRemoteThread
" did not work across session boundaries. In addition, services run in the security context of the local system account. This account is the same account in which core Windows user-mode operating system components run, including Smss.exe, Csrss.exe, Lsass.exe, and so on. - ChkSamConfig.exe: This program proposes a user console interface to start, stop and delete a CheckSam service.
- The dump.dll DLL: This DLL contains a code responsible for extracting the credential hashes from a SAM file.
ChkSamConfig.exe
Writing a Service Program's Main Function
The main function of CheckSam.exe calls the "StartServiceCtrlDispatcher
" function to connect to the service control manager (SCM) and start the control dispatcher thread. The dispatcher thread loops, waiting for incoming control requests for the services specified in the dispatch table, namely "SvcMain
".
// Communication API with SCM
#pragma comment(lib, "advapi32.lib")
// The service name
#define SVCNAME TEXT("checkSam")
void __cdecl _tmain(int argc, TCHAR *argv[])
{
// if the first argument is "install", then install SVCNAME service
if (lstrcmpi(argv[1], TEXT("install")) == 0)
{
InstallSvc();
return;
}
// add SVCNAME into the DispatchTable
SERVICE_TABLE_ENTRY DispatchTable[] =
{
{ SVCNAME, (LPSERVICE_MAIN_FUNCTION)SvcMain },
{ NULL, NULL }
};
// This call returns when the service has stopped.
if (!StartServiceCtrlDispatcher(DispatchTable))
{
// logging To Do
return;
}
}
Writing a ServiceMain Function
The "SvcMain
" function first calls the "RegisterServiceCtrlHandler
" function to register the "CtrlHandler
" function as the service's Handler
function and begin initialization.
Next, the "SvcMain
" function calls the "ReportSvcStatus
" function to indicate that its initial status is SERVICE_START_PENDING
. While the service is in this state, no controls are accepted. To simplify the logic of the service, it is recommended that the service doesn't accept any controls while it is performing its initialization.
Finally, the "SvcMain
" function calls the "SvcInit
" function to perform the service-specific initialization and begin the work to be performed by the service.
SERVICE_STATUS gSvcStatus;
SERVICE_STATUS_HANDLE gSvcStatusHandle;
VOID WINAPI SvcMain(DWORD dwArgc, LPTSTR *lpszArgv)
{
// Register the handler function for the service
gSvcStatusHandle = RegisterServiceCtrlHandler( SVCNAME, CtrlHandler);
if (!gSvcStatusHandle)
{
// logging To-DO
return;
}
// Set the service type
gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
// Set the error code to return
//when an error occurs while the service is starting or stopping
gSvcStatus.dwServiceSpecificExitCode = 0;
// Report initial status to the SCM
ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000);
// Perform service-specific initialization and work.
SvcInit(dwArgc, lpszArgv);
}
Writing a SvcInit Function
This function reports the running state to SCM, launches a thread that executes the "Thread
" function, waits for its end thanks to "WaitForSingleObject
" function and finally calls "ReportSvcStatus
" function to indicate that the service has entered the SERVICE_STOPPED
state, and returns.
VOID SvcInit(DWORD dwArgc, LPTSTR *lpszArgv)
{
// Report running status when initialization is complete.
ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
// Start the thread that will perform the main task of the service
HANDLE hThread = CreateThread(NULL, 0, Thread, NULL, 0, NULL);
// Check whether to stop the service.
WaitForSingleObject(hThread, INFINITE);
ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
}
Once the thread was launched, it executes the "Thread
" function that calls, in its turn, the injection code.
The "injection( )
" function starts by calling "LsassId()
" function in order to get the PID
of lsass process. The PID
is passed to "OpenProcess
" to obtain the handle of lsass process. Using this handle, "VirtualAllocEx
" and "WriteProcessMemory
" then respectively allocate space and write the name of the DLL (the string
"dump.dll") into the victim process. Next, "GetProcAddress
" is used to get the address to "LoadLibrary
". Finally, "CreateRemoteThread
" is called with three important parameters: the handle to lsass
process (hp
), the address of LoadLibrary
(loadLibAddr
), and a pointer to the DLL name previously written inside lsass
process (baseAdd
). "CreateRemoteThread
" allows a process to execute a thread inside a remote process when it has the appropriate privileges.
// The name of the DLL to inject
#define DLLTOINJECT "C:\\dump.dll"
// The logging file name
#define LOGFILE "C:\\checkSam.log
VOID injection()
{
// create a log file
FILE * log;
DWORD pid; // pid of lsass process
HANDLE hp = NULL; // a handle to lsass process
LPSTR baseAdd = NULL; // base address of the allocated pages inside lsass process
// open logging file
fopen_s(&log, LOGFILE, "w");
// get pid of lsass process
pid = LsassId();
if (pid == 0){
fprintf(log, "Can't find the pid of lsass process\n");
fclose(log);
return;
}
// try to open lsass process with all privileges
hp = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (hp == NULL){
fprintf(log, "ERROR: (%d) while trying to
open lsass process (PID: %d)\n", GetLastError(), pid);
fclose(log);
return;
}
// create
baseAdd = (LPSTR)VirtualAllocEx(hp, NULL,
strlen(DLLTOINJECT), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (baseAdd == NULL)
{
fprintf(log, "ERROR: (%d) while creating a vitual memory address
space inside lsass process (PID: %d)\n", GetLastError(), pid);
fclose(log);
return;
}
fprintf(log, "The base address inside lsass of %s is : %p\n", DLLTOINJECT, baseAdd);
if (!WriteProcessMemory(hp, baseAdd, DLLTOINJECT, strlen(DLLTOINJECT), NULL)){
fprintf(log, "ERROR : (%d) while trying to write inside
the committed address space in lsass process (PID: %d)\n", GetLastError(), pid);
fclose(log);
return;
};
// Get the real address of LoadLibraryW in Kernel32.dll
PTHREAD_START_ROUTINE loadLibAddr = (PTHREAD_START_ROUTINE)GetProcAddress
(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA");
if (loadLibAddr == NULL) {
fprintf(log, "ERROR : (%d) while loading the loadLibrary function \n", GetLastError());
fclose(log);
return;
}
fprintf(log, "loadLibrary function is loaded at %p\n", loadLibAddr);
if (!CreateRemoteThread(hp, NULL, 0, loadLibAddr, baseAdd, 0, NULL)){
fprintf(log, "ERROR: (%d) occurred when attempting
to launch a thread in lsass process", GetLastError());
fclose(log);
return;
}
fprintf(log, "The DLL has been successfully injected in the lsass process...\n");
//close the logging file
fclose(log);
return;
}
DWORD LsassId(){
// Array that receives the list of process identifiers.
DWORD ProcessList[1024];
// The size of the ProcessList, in bytes
DWORD cbNeeded;
// The number of bytes returned in the ProcessList array
DWORD cProcesses;
TCHAR szProcessName[MAX_PATH] = TEXT("<unknown>");
if (!EnumProcesses(ProcessList, sizeof(ProcessList), &cbNeeded))
{
return 0;
}
// Calculate how many process identifiers were returned.
cProcesses = cbNeeded / sizeof(DWORD);
// looking for the PID of lsass process
for (unsigned int i = 0; i < cProcesses; i++)
{
if (ProcessList[i] != 0)
{
// Get a handle to the process.
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
FALSE,
ProcessList[i]);
// Get the process name.
if (NULL != hProcess)
{
HMODULE hMod;
DWORD cbNeeded;
if (EnumProcessModules(hProcess, &hMod, sizeof(hMod),
&cbNeeded))
{
GetModuleBaseName(hProcess, hMod, szProcessName,
sizeof(szProcessName) / sizeof(TCHAR));
}
}
// get the pid of lsass process if exists
if (lstrcmp(szProcessName, TEXT("lsass.exe")) == 0)
{
CloseHandle(hProcess);
return ProcessList[i];
}
}
}
return 0;
}
ChkSamConfig.exe
This console program allows a user to start and delete the checkSam
service.
The install
, start
and delete
functions share the same starting code. They first call "OpenSCManager
" to open and get a handle to SCM, then they get a handle to the desired service.
SC_HANDLE schSCManager;
SC_HANDLE schService;
SERVICE_STATUS ssStatus;
// Get a handle to the SCM database.
schSCManager = OpenSCManager(
NULL, // local computer
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (NULL == schSCManager)
{
printf("OpenSCManager failed (%d)\n", GetLastError());
return;
}
// Get a handle to the service.
schService = OpenService(
schSCManager, // SCM database
SvcName, // name of service
DELETE); // need delete access
if (schService == NULL)
{
printf("OpenService failed (%d)\n", GetLastError());
CloseServiceHandle(schSCManager);
return;
}
// start, stop, delete, ... code
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
//...
Install Service Function
It starts by obtaining the path to the service image. For this purpose, we use "GetModuleFileName
" function with NULL
as first parameter. This indicates that "GetModuleFileName
" retrieves the path of the executable file of the current process. Indeed, To keep the code as simple as possible, we have included the code of install
function in the checkSam
program.
...
TCHAR ServicePath[MAX_PATH];
if (!GetModuleFileName(NULL, ServicePath, MAX_PATH))
{
printf("Cannot install service (%d)\n", GetLastError());
return;
}
// open SCM database as presented above
...
// Create the service
schService = CreateService(
schSCManager, // SCM database
SVCNAME, // name of service
SVCNAME, // service name to display
SERVICE_ALL_ACCESS, // desired access
SERVICE_WIN32_OWN_PROCESS, // service type
SERVICE_DEMAND_START, // start type
SERVICE_ERROR_NORMAL, // error control type
ServicePath, // path to service's binary
NULL, // no load ordering group
NULL, // no tag identifier
NULL, // no dependencies
NULL, // LocalSystem account
NULL); // no password
if (schService == NULL)
{
printf("CreateService failed (%d)\n", GetLastError());
CloseServiceHandle(schSCManager);
return;
}
else printf("Service installed successfully\n");
// close handles
...
Start Service Function
After getting a handle to the service, we check the status of the service. If the service is in running state, the program returns. Otherwise, we wait until the service exits its "SERVICE_STOP_PENDING
state" and then we call the "StartService
" function to start the service.
// ... see the ChkSamConfig source code
// Attempt to start the service.
if (!StartService(
schService, // handle to service
0, // number of arguments
NULL)) // here dll name (LPCWSTR *)DllName
{
printf("StartService failed (%d)\n", GetLastError());
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
return;
}
else printf("Service start pending...\n");
//... see the ChkSamConfig source code
Delete Service Function
To delete a service, we just need to obtain a handle to a concerned service and call the "DeleteService
" Windows function.
//...
// Delete the service.
if (!DeleteService(schService))
{
printf("DeleteService failed (%d)\n", GetLastError());
}
else printf("Service deleted successfully\n");
//...
Conclusion
In this first part of the article, we showed how to inject a DLL to lsass
process. In the second part of the article, we will present how to create dump.dll, how to extract the hashes from SAM file and how the detection works.
References
-
Windows Internals, Sixth Edition, Part 1, Mark Russinovich, David A. Solomon, Alex Ionescu, Microsoft Press, 2012
-
Practical Malware Analysis, The Hands-On Guide to Dissecting Malicious Software, Michael Sikorski and Andrew Honig, William Pollock, 2012
-
The Art of Memory Forensics Detecting Malware and Threats in Windows, Linux, and Mac Memory, Michael Hale Ligh Andrew Case Jamie Levy AAron Walters, Wiley, 2014
- https://developer.microsoft.com/fr-fr/windows/desktop/develop