NoAgent memory detection

Memory horse is a popular web layer permission maintenance method in China, and there are many research articles. After reading master rebeyond's Java Memory attack technology, I try to use the technology to develop a tool for memory horse detection.

testing

Firstly, there are two types of memory horses. One is to use the characteristics of Web middleware components or frameworks to embed malicious code in the web execution process to execute commands, such as tomcat's filter, servlet, spring MVC controller, etc. this kind of memory horse can also directly detect the corresponding components during detection.

The other is the Java agent memory horse, which directly modifies the code of its key classes by uploading jar packages, attaching web applications, calling instrument s, and using redefine or retransform. For example, the ice scorpion modifies javax servlet. http. Httpservlet class will be called in the general web access process. Java agent is also required to detect this kind of memory horse.

Memory horse defense detection

The above is a brief introduction to the memory horse. Now let's take a look at its advanced technology. In master rebbeyond's talk on Java Memory attack technology, we talked about how to block the attach process of Java agent, which makes it difficult to detect the use of agent.

The specific implementation can be seen

Topic analysis and reproduction -- Random Talk on Java Memory attack technology (I)

Let's take a look at the general implementation

The instrument mechanism implements the injection of agent-like memory horse, but it can also detect the memory horse.

The method given here is to destroy the instrument mechanism after injecting memory horse, so that it cannot detect the class bytecode of the process.

The following is the workflow of the instrument

1.Testing tools as Client,According to the specified PID,To target JVM launch attach Request;
2.JVM After receiving the request, do some verification (as mentioned above) jdk.attach.allowAttachSelf After the verification is passed, a IPC Access.
3.next Client Encapsulates a file named AttachOperation of C++Object, send to Server End;
4.Server End will put Client Sent it AttachOperation Put objects into a queue;
5.Server Another thread on the end will be taken out of the queue AttachOperation Object and parse it, then execute the corresponding operation, and pass the execution result IPC Channel return Client. 

The following is the anti detection of windows

Let's sort out the whole process of loadagent

Now it seems that just export the two functions of jvmLib to the JVM_EnqueueOperation and_ JVM_EnqueueOperation@20 The destruction of the instrument process can be completed by dropping the NOP.

Let's take a look at master rebeyond's handling method

use JNI,The core code is as follows:
 
unsigned char buf[]="\xc2\x14\x00"; //32,direct return enqueue function
HINSTANCE hModule = LoadLibrary(L"jvm.dll");
//LPVOID dst=GetProcAddress(hModule,"ConnectNamedPipe");
LPVOID dst=GetProcAddress(hModule,"_JVM_EnqueueOperation@20");
DWORD old;
if (VirtualProtectEx(GetCurrentProcess(),dst, 3, PAGE_EXECUTE_READWRITE, &old)){WriteProcessMemory(GetCurrentProcess(), dst, buf, 3, NULL);VirtualProtectEx(GetCurrentProcess(), dst, 3, old, &old);}
 
/*unsigned char buf[]="\xc3"; //64,direct return enqueue function
HINSTANCE hModule = LoadLibrary(L"jvm.dll");
//LPVOID dst=GetProcAddress(hModule,"ConnectNamedPipe");
LPVOIDdst=GetProcAddress(hModule,"JVM_EnqueueOperation");
//printf("ConnectNamedPipe:%p",dst);DWORD old;
if (VirtualProtectEx(GetCurrentProcess(),dst, 1, PAGE_EXECUTE_READWRITE, &old)){WriteProcessMemory(GetCurrentProcess(), dst, buf, 1, NULL);
VirtualProtectEx(GetCurrentProcess(), dst, 1, old, &old);
}*/

Although some masters have given us how to bypass this blocking, in master beyond's article, as long as any link in the instrument flow chart is blocked, there may be a variety of blocking methods, and each one needs targeted methods to bypass.

Therefore, I wonder whether I can completely bypass the blocking here, that is, I can call instrument without using external agent to attach. As it happens, master rebeyond's article mentioned how to carry out the agent memory horse attack without file landing. By constructing the instrument, you can call the instrument to modify the key classes without uploading the agent package.

NoAgent

How to construct the specific implementation of the instrument on the server can be seen
Topic analysis and reproduction -- Random Talk on Java Memory attack technology (II) no file landing Agent memory horse

Here is the general principle

First, let's take a look at the normal creation process of Java agent

  1. 1. On client and target JVM establish IPC After connecting, the client will encapsulate a to load agent.jar of AttachOperation Object, which contains three key data: actioName,libName and agentPath;
    2. Server received AttachOperation After the call enqueue Press in AttachOperation Queue waiting for processing;
    3. Server processing thread call dequeue Method take out AttachOperation;
    4. Server side analysis AttachOperation,Extract the three parameters mentioned in step 1 and call actionName by load The corresponding processing branch of, and then load libinstrument.so(stay windows Platform is instrument.dll),implement AttachOperation of On_Attach Function (as you can see, Java Layered instrument Mechanism, the bottom layer is through Native Layered Instrument To encapsulate);
    5. .ibinstrument.so Medium On_Attach Can resolve agentPath Specified in jar File, this jar Called in redefineClass The function of;
    6. Execution flow to Java Layer, JVM Will instantiate one InstrumentationImpl Class. When this class is constructed, it has a very important parameter mNativeAgent: This parameter is long Type whose value is a Native A pointer to a layer that points to a C++object JPLISAgent. 
    7. InstrumentationImpl After instantiation, continue to call InstrumentationImpl Class redefineClasses Method and continue calling after a little verification InstrumentationImpl of Native method redefineClasses0
    8. The execution flow continues to enter Native layer
    

It seems complicated. In fact, we only need to focus on what the server does

Let's take a look at the call stack on the server side. We drop the breakpoint at the agentmain on the server side. We can find that the call stack on the server side starts from the InstrumentationImpl class. This is the sixth step above, and the previous steps are the operations of the client or native layer. Therefore, in the java layer, we can construct malicious code directly from the InstrumentationImpl class.

In this way, the InstrumentationImpl class should be constructed first. Take a look at the constructor. Combined with the information generated by debug before, it is found that var3=true and var4=false. All that needs to be constructed is var1, that is, mnateagent. This parameter is of long type, and its value is a pointer to the native layer, pointing to a C + + object JPLISAgent. It shows that we need to construct a suitable C + + object JPLISAgent in the native layer.

private InstrumentationImpl(long var1, boolean var3, boolean var4) {
        this.mNativeAgent = var1;//This parameter needs to be constructed
        this.mEnvironmentSupportsRedefineClasses = var3;
        this.mEnvironmentSupportsRetransformClassesKnown = false;
        this.mEnvironmentSupportsRetransformClasses = false;
        this.mEnvironmentSupportsNativeMethodPrefix = var4;
    }

To construct parameters in the native layer, I used unsafe to allocate memory

Unsafe unsafe = null;
try {    Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");    field.setAccessible(true);    unsafe = (sun.misc.Unsafe) field.get(null);} catch (Exception e) {    throw new AssertionError(e);}

Next, let's take a look at the structure of JPLISAgent

struct _JPLISAgent {
    JavaVM *                mJVM;                   /* handle to the JVM */
    JPLISEnvironment        mNormalEnvironment;     /* for every thing but retransform stuff */
    JPLISEnvironment        mRetransformEnvironment;/* for retransform stuff only */
    jobject                 mInstrumentationImpl;   /* handle to the Instrumentation instance */
    jmethodID               mPremainCaller;         /* method on the InstrumentationImpl that does the premain stuff (cached to save lots of lookups) */
    jmethodID               mAgentmainCaller;       /* method on the InstrumentationImpl for agents loaded via attach mechanism */
    jmethodID               mTransform;             /* method on the InstrumentationImpl that does the class file transform */
    jboolean                mRedefineAvailable;     /* cached answer to "does this agent support redefine" */
    jboolean                mRedefineAdded;         /* indicates if can_redefine_classes capability has been added */
    jboolean                mNativeMethodPrefixAvailable; /* cached answer to "does this agent support prefixing" */
    jboolean                mNativeMethodPrefixAdded;     /* indicates if can_set_native_method_prefix capability has been added */
    char const *            mAgentClassName;        /* agent class name */
    char const *            mOptionsString;         /* -javaagent options string */
};

The structure of JPLISAgent is complex, so let's start with redefinclass to see which parameters need to be.

void
redefineClasses(JNIEnv * jnienv, JPLISAgent * agent, jobjectArray classDefinitions) {
    jvmtiEnv*   jvmtienv                        = jvmti(agent);
    jboolean    errorOccurred                   = JNI_FALSE;
    jclass      classDefClass                   = NULL;
    jmethodID   getDefinitionClassMethodID      = NULL;
    jmethodID   getDefinitionClassFileMethodID  = NULL;
    jvmtiClassDefinition* classDefs             = NULL;
    jbyteArray* targetFiles                     = NULL;
    jsize       numDefs                         = 0;
    ...

It can be seen from the usage here that JVM ti is a macro or function. A search shows that it is a macro

It can be determined that redefiniteclass requires the mNormalEnvironment parameter.

Let's look at the structure of this parameter.

struct _JPLISEnvironment {
    jvmtiEnv *              mJVMTIEnv;              /* the JVM TI environment */
    JPLISAgent *            mAgent;                 /* corresponding agent */
    jboolean                mIsRetransformer;       /* indicates if special environment */
};

You can see that there is a loopback pointer mAgent in this structure, which points to the JPLISAgent object. In addition, there is the most important pointer mJVMTIEnv, which points to the JVMTIEnv object in memory, which is the core object of the JVMTI mechanism. In addition, after analysis, there is another mredefiniteavailable member in the JPLISAgent object, which must be set to true.

In this way, we can complete the construction as long as we find a way to obtain mJVMTIEnv.

In the article on Java Memory attack technology, because we talk about attack technology, and there can be no file landing in the process, it is complex to obtain the mJVMTIEnv of the target machine, but what we do is a detection tool without so many restrictions. We can obtain the address directly by using JNI and dll.

The following is the code of dll

#include "pch.h"
#include "getAgent.h"
#include"getJPSAgent.h"
#include "jvmti.h"
JNIEXPORT void JNICALL Java_getJPSAgent_caloffset
(JNIEnv*, jobject) {
    struct JavaVM_* vm;
    jsize count;
    typedef jint(JNICALL* GetCreatedJavaVMs)(JavaVM**, jsize, jsize*);
    //Originally, I wanted to call the GetCreatedJavaVMs function directly, but the specific header file is missing, so only typedef can define another function with the same structure
    GetCreatedJavaVMs jni_GetCreatedJavaVMs;
    // ...
    jni_GetCreatedJavaVMs = (GetCreatedJavaVMs)GetProcAddress(GetModuleHandle(
        TEXT("jvm.dll")), "JNI_GetCreatedJavaVMs");
    //Due to JVM The dll has been loaded at the beginning of the java program, so you can directly obtain the JNI in the dll_ Address of getcreatedjavavms
    jni_GetCreatedJavaVMs(&vm, 1, &count);//Gets the address of the jvm object
    struct jvmtiEnv_* _jvmti_env;
    HMODULE jvm = GetModuleHandle(L"jvm.dll");//Get jvm base address
    vm->functions->GetEnv(vm, (void**)&_jvmti_env, JVMTI_VERSION_1_2);//Get_ jvmti_ The address of Env, that is, the pointer to the JVMTIEnv pointer.
    printf(" hModule jvm = 0x%llx\n", jvm);
    printf(" struct JavaVM_* vm = 0x%llx\n", vm);
    printf(" _jvmti_env = 0x%llx\n", _jvmti_env);
 ;
}

Then put the obtained address in the corresponding position to complete the construction.

The following is the code to get the instrument object

public Object genImp(String dlladdress,detect getJPSAgent) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        System.load(dlladdress);

        long native_jvmtienv = getJPSAgent.caloffset();
        
        Unsafe unsafe = null;
        try {    Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (sun.misc.Unsafe) field.get(null);}
        catch (Exception e) {
            throw new AssertionError(e);}
        long JPLISAgent = unsafe.allocateMemory(0x100000);
        //unsafe.putLong(jvmtiStackAddr,jvmtiAddress);
        unsafe.putLong(native_jvmtienv+8,0x30010100000071eel);
        unsafe.putLong(native_jvmtienv+0x168,0x9090909000000200l);//Implement redefiniteclass
        System.out.println("long:"+Long.toHexString(native_jvmtienv+0x168));
        unsafe.putLong(JPLISAgent,unsafe.getLong(native_jvmtienv) -0x9D6760);
    
        unsafe.putLong(JPLISAgent + 8, native_jvmtienv);//Implement retransform, mnormalenvironment mJVMTIEnv;
        unsafe.putLong(JPLISAgent + 0x10, JPLISAgent);// mNormalEnvironment.mAgent;
        unsafe.putLong(JPLISAgent + 0x18, 0x00730065006c0000l);//mNormalEnvironment.mIsRetransformer;  Decide whether to retransform
        //make retransform env
        unsafe.putLong(JPLISAgent + 0x20, native_jvmtienv);//mRetransformEnvironment.mJVMTIEnv
        unsafe.putLong(JPLISAgent + 0x28, JPLISAgent);//mRetransformEnvironment.mAgent
        unsafe.putLong(JPLISAgent + 0x30, 0x0038002e00310001l);//mRetransformEnvironment.mIsRetransformer
        unsafe.putLong(JPLISAgent + 0x38,  0);//jobject                 mInstrumentationImpl;
        unsafe.putLong(JPLISAgent + 0x40, 0);// jmethodID               mPremainCaller;
        unsafe.putLong(JPLISAgent + 0x48, 0);//jmethodID               mAgentmainCaller;
        unsafe.putLong(JPLISAgent + 0x50, 0);//jmethodID               mTransform;
        unsafe.putLong(JPLISAgent + 0x58, 0x0072007400010001l);
        /*    jboolean                mRedefineAvailable;     /* cached answer to "does this agent support redefine"
        jboolean                mRedefineAdded;         /* indicates if can_redefine_classes capability has been added
        jboolean                mNativeMethodPrefixAvailable; /* cached answer to "does this agent support prefixing"
        jboolean                mNativeMethodPrefixAdded;     /* indicates if can_set_native_method_prefix capability has been added */
        unsafe.putLong(JPLISAgent + 0x60, JPLISAgent + 0x68);// char const *            mAgentClassName;        /* agent class name */
        unsafe.putLong(JPLISAgent + 0x68, 0x0041414141414141l);// char const *            mOptionsString;         /* -javaagent options string */
        Class<?> instrument_clazz = Class.forName("sun.instrument.InstrumentationImpl");
        Constructor<?> constructor = instrument_clazz.getDeclaredConstructor(long.class, boolean.class, boolean.class);
        constructor.setAccessible(true);
        Object insn = constructor.newInstance(JPLISAgent, true, false);
        return  insn;//Return object
    }

The above process can realize the direct construction of Instrument on the server side, that is, the so-called NoAgent.

After that, it is actually the normal idea of agent to detect the memory horse, but it may be because it is a self constructed instrument. Some function calls will report errors, such as retransform. Therefore, it is not so convenient to directly restore the classes modified by the agent memory horse. Therefore, the deletion method of this kind of memory horse is still under consideration.

Thus, the idea of NoAgent memory horse detection was born.

The detection program mainly includes five files

  • NoAgent.jar is used to generate an instrument to detect the agent memory horse
  • NoAgent.dll is used to obtain the address and other data of the jvm and provide it to noagent jar
  • detect.jsp for noagent Jar makes external calls, detects the memory horse of components in frameworks such as filter, and provides a user interaction interface
  • dumpclass.jar exports the class in memory to disk for subsequent decompile code detection (decompile with cfr)
  • sa-jdi.jar as dumpclass The necessary components of jar are placed in% Java_ In home% / Lib

Advantages of the program

  • The blocking of attach can be bypassed because it is not used. Because it is not used, it should have no impact on the performance of some large web applications.

  • Using dumpclass and cfr, you can easily display the java code of all classes.

shortcoming

  • dumpclass can only be used in the environment of java8 due to the restriction of the use environment. When java11 uses the dump function, an error will be reported (to be solved)

  • java.lang.RuntimeException: can't determine target's VM version : field "_reserve_for_allocation_prefetch" not found in type Abstract_VM_Version sun.jvm.hotspot.debugger.DebuggerException: java.lang.RuntimeException: can't determine target's VM version : field "_reserve_for_allocation_prefetch" not found in type Abstract_VM_Version at 
    ...
    
  • The interactive interface is too simple and needs to be optimized

  • Detection involving complex code still needs to be viewed manually

  • The classes in the whitelist have not been carefully examined and do not know whether they can be used

  • The detection of decompiled code is too simple and prone to false positives

  • At present, only the dll on the windows side is made, and the so file on the linux side will be updated in the future

test

1.godzilla

A malicious class was detected at the instrument

Malicious servlet detected at Servlet

2. Java agent memory horse

Write an agent attach to tomcat and modify javax servlet. http. Httpservlet class

Through risk_ Implementation detection, listing the classes at risk. When using dumpclass, you can see that the code contains the code just added

3.attach block bypass

After the blocking code is turned on, other agent s cannot attach

However, the program can still detect normally.

Added by kiosklim on Wed, 19 Jan 2022 01:51:27 +0200