65.9K
CodeProject 正在变化。 阅读更多。
Home

检测 Windows 7 下的 SAM 文件损坏(第 1 部分)

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2016年4月11日

CPOL

5分钟阅读

viewsIcon

11826

downloadIcon

74

本文第一部分介绍了如何通过 Windows 服务将 DLL 注入 lsass 进程。

引言

本文介绍并解释了如何在 Windows 7 系统运行时访问 SAM 文件。这里的目标不是提供一种破解 Windows 系统的方法,而是为系统管理员检测 SAM 文件损坏提供解决方案。

有几种方法可以在系统运行时或关闭时从 SAM 文件中提取用户凭据。在本文中,我们选择了第一种方法。事实上,为了我们的目的,我们需要最新的凭据,以便将它们与之前保存的凭据进行比较,并在比较失败时发出警报。

此方法通过在本地安全机构子系统服务 (LSASS) 进程(俗称 lsass.exe)中执行 DLL 注入来工作。这是一种恶意软件用于在另一个进程中运行 DLL 的方式,从而为该 DLL 提供该进程的所有权限。哈希转储工具通常以 lsass.exe 为目标,因为它具有必要的权限级别以及对许多有用 API 函数的访问权限。

DLL 注入后,它使用未记录的 API 函数,如 SamIConnectSamQueryInformationUserSamIGetPrivateData,从 SAM 文件中提取哈希。

一旦哈希被转储,它们就会与之前保存的哈希进行比较。如果比较失败,系统会发出警报。

背景

我们在 Visual Studio 2013 下开发的应用程序由三个程序组成

  • CheckSam.exe:包含一个 Windows 服务,该服务将 DLL 注入到 lsass 进程中。我们选择使用 Windows 服务是受到 Vista 以来引入的“会话分离”保护的驱动。‘会话分离’确保包括服务在内的核心系统进程始终在会话 0 中运行,而所有用户进程都在不同的会话中运行。因此,在用户会话中运行的任何进程都无法将 DLL 注入到系统进程中,因为“CreateRemoteThread”无法跨会话边界工作。此外,服务在本地系统帐户的安全上下文中运行。此帐户与运行核心 Windows 用户模式操作系统组件的帐户相同,包括 Smss.exeCsrss.exeLsass.exe 等。
  • ChkSamConfig.exe:此程序提供了一个用户控制台界面来启动、停止和删除 CheckSam 服务
  • dump.dll DLL:此 DLL 包含负责从 SAM 文件中提取凭据哈希的代码。

ChkSamConfig.exe

编写服务程序的 Main 函数

CheckSam.exe 的主函数调用“StartServiceCtrlDispatcher”函数连接到服务控制管理器 (SCM) 并启动控制调度器线程。调度器线程循环,等待服务控制管理器中的传入控制请求,这些请求针对调度表(即“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;
	}
}

编写 ServiceMain 函数

SvcMain”函数首先调用“RegisterServiceCtrlHandler”函数,将“CtrlHandler”函数注册为服务的 Handler 函数,并开始初始化。

接下来,“SvcMain”函数调用“ReportSvcStatus”函数来指示其初始状态为 SERVICE_START_PENDING。当服务处于此状态时,不接受任何控件。为了简化服务逻辑,建议服务在执行初始化时不要接受任何控件。

最后,“SvcMain”函数调用“SvcInit”函数来执行特定于服务的初始化并开始服务要执行的工作。

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);
}

编写 SvcInit 函数

此函数向 SCM 报告运行状态,启动一个执行“Thread”函数的线程,使用“WaitForSingleObject”函数等待其结束,最后调用“ReportSvcStatus”函数指示服务已进入 SERVICE_STOPPED 状态,然后返回。

    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);
}

一旦线程启动,它就执行“Thread”函数,该函数又调用注入代码。

injection( )”函数首先调用“LsassId()”函数以获取 lsass 进程的 PIDPID 被传递给“OpenProcess”以获取 lsass 进程的句柄。使用此句柄,“VirtualAllocEx”和“WriteProcessMemory”分别分配空间并将 DLL 的名称(字符串“dump.dll”)写入目标进程。接下来,使用“GetProcAddress”获取指向“LoadLibrary”的地址。最后,使用三个重要参数调用“CreateRemoteThread”:到 lsass 进程的句柄(hp)、LoadLibrary 的地址(loadLibAddr)以及指向先前写入 lsass 进程的 DLL 名称的指针(baseAdd)。“CreateRemoteThread”允许具有适当权限的进程在远程进程中执行线程。

// 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

此控制台程序允许用户启动和删除 checkSam 服务。

installstartdelete 函数共享相同的起始代码。它们首先调用“OpenSCManager”来打开 SCM 并获取句柄,然后它们获取所需服务的句柄。

    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);
	
//...

安装服务函数

它首先获取服务的映像路径。为此,我们使用“GetModuleFileName”函数,并将第一个参数设置为 NULL。这表明“GetModuleFileName”检索当前进程可执行文件的路径。事实上,为了使代码尽可能简单,我们将 install 函数的代码包含在 checkSam 程序中。

...
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
...

启动服务函数

在获取服务句柄后,我们检查服务的状态。如果服务正在运行,程序将返回。否则,我们等待服务退出“SERVICE_STOP_PENDING”状态,然后调用“StartService”函数启动服务。

    // ... 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

删除服务函数

要删除服务,我们只需要获取对相关服务的句柄并调用“DeleteService”Windows 函数。

//...

// Delete the service.

	if (!DeleteService(schService))
	{
		printf("DeleteService failed (%d)\n", GetLastError());
	}
	else printf("Service deleted successfully\n");
//...

结论

在本文的第一部分,我们展示了如何将 DLL 注入到 lsass 进程。在本文的第二部分,我们将介绍如何创建 dump.dll、如何从 SAM 文件中提取哈希以及检测工作原理。

参考文献

  1. Windows Internals, Sixth Edition, Part 1, Mark Russinovich, David A. Solomon, Alex Ionescu, Microsoft Press, 2012

  2. Practical Malware Analysis, The Hands-On Guide to Dissecting Malicious Software, Michael Sikorski and Andrew Honig, William Pollock, 2012

  3. 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

  4. https://developer.microsoft.com/fr-fr/windows/desktop/develop
© . All rights reserved.