Skip to content

Commit

Permalink
continue trying to implement terminal support
Browse files Browse the repository at this point in the history
  • Loading branch information
goatshriek committed Jul 6, 2024
1 parent 6ea86df commit 76562e7
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 131 deletions.
28 changes: 26 additions & 2 deletions src/main/java/com/goatshriek/rubydragon/GhidraInterpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -255,13 +255,37 @@ public void initInteractiveInterpreterWithProgress(PrintWriter output, PrintWrit

/**
* Sets the input stream for this interpreter.
*
* @deprecated use setInputStream instead.
*
* @param input The new input stream to use for the interpreter.
*/
public abstract void setInput(InputStream input);
public void setInput(InputStream input) {
setInputStream(input);
}

/**
* Sets the input stream for this interpreter.
*
* @param input The new input stream to use for the interpreter.
*/
public void setInputStream(InputStream input) {
// TODO default implementation for testing, remove later
return;
}

/**
* Sets the input stream for this interpreter.
*
* @param input The new input stream to use for the interpreter.
*/
public void setOutputStream(OutputStream input) {
// TODO default implementation for testing, remove later
return;
}

/**
* Sets the output stream for this interpreter.
* Sets the output writer for this interpreter.
*
* @param output The new output stream to use for the interpreter.
*/
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/com/goatshriek/rubydragon/IrbTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.goatshriek.rubydragon;

import org.jruby.embed.LocalContextScope;
import org.jruby.embed.LocalVariableBehavior;
import org.jruby.embed.ScriptingContainer;

public class IrbTest {

public static void main(String[] args) {
ScriptingContainer container = new ScriptingContainer(LocalContextScope.SINGLETHREAD,
LocalVariableBehavior.PERSISTENT);
container.setInput(System.in);
container.setOutput(System.out);

System.out.println(container.getProperty("jruby.console"));
container.runScriptlet("puts 'start'");
container.runScriptlet("require 'irb'");
container.runScriptlet("require 'irb/completion'");
container.runScriptlet("IRB.conf[:USE_AUTOCOMPLETE] = true");
container.runScriptlet("IRB.conf[:USE_MULTILINE] = false");
container.runScriptlet("IRB.start");
container.runScriptlet("puts 'done'");
container.terminate();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@

package com.goatshriek.rubydragon.pty;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

import com.goatshriek.rubydragon.GhidraInterpreter;

Expand All @@ -31,29 +32,47 @@ public class GhidraInterpreterPty implements Pty {
private final GhidraInterpreter interpreter;
private final GhidraInterpreterPtyParent parent;
private final GhidraInterpreterPtyChild child;
private PipedOutputStream parentOut;
private PipedOutputStream childOut;
private PipedInputStream parentIn;
private PipedInputStream childIn;

public GhidraInterpreterPty(GhidraInterpreter interpreter) {
this.interpreter = interpreter;

parentOut = new PipedOutputStream();
childOut = new PipedOutputStream();
try {
childIn = new PipedInputStream(parentOut);
parentIn = new PipedInputStream(childOut);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

interpreter.setInputStream(childIn);
interpreter.setOutputStream(childOut);

out = interpreter.getOutputStream();
in = interpreter.getInputStream();

parent = new GhidraInterpreterPtyParent(interpreter, out, in);
child = new GhidraInterpreterPtyChild(interpreter, out, in);
parent = new GhidraInterpreterPtyParent(interpreter, parentOut, parentIn);
child = new GhidraInterpreterPtyChild(interpreter, childOut, childIn);
}

@Override
public PtyParent getParent() {
return parent;
public void close() {
return;
}

@Override
public PtyChild getChild() {
return child;
}

public GhidraInterpreter getInterpreter() {
return interpreter;
}

@Override
public void close() {
return;
public PtyParent getParent() {
return parent;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,87 +43,20 @@ public GhidraInterpreterPtyChild(GhidraInterpreter interpreter, OutputStream out
super(interpreter, outputStream, inputStream);
}

private String sttyString(Collection<TermMode> mode) {
StringBuilder sb = new StringBuilder();
if (mode.contains(Echo.OFF)) {
sb.append("-echo ");
} else if (mode.contains(Echo.ON)) {
sb.append("echo ");
}
if (sb.isEmpty()) {
return "";
}
return "stty " + sb + "&& ";
}

@Override
public GhidraInterpreterPtySession session(String[] args, Map<String, String> env, File workingDirectory,
Collection<TermMode> mode) throws IOException {
if (workingDirectory != null) {
throw new UnsupportedOperationException();
}
/**
* TODO: This syntax assumes a UNIX-style shell, and even among them, this may
* not be universal. This certainly works for my version of bash :)
*/
String envStr = env == null ? ""
: env.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining(" "))
+ " ";
String cmdStr = ShellUtils.generateLine(Arrays.asList(args));
channel.setCommand(sttyString(mode) + envStr + cmdStr);
try {
channel.connect();
} catch (JSchException e) {
throw new IOException("SSH error", e);
}
return new SshPtySession(channel);
}

private String getTtyNameAndStartNullSession(Collection<TermMode> mode) throws IOException {
// NB. UNIX sleep is only required to support integer durations
channel.setCommand(
sttyString(mode) + "sh -c 'tty && ctrlc() { echo; } && trap ctrlc INT && while true; do sleep "
+ Integer.MAX_VALUE + "; done'");
try {
channel.connect();
} catch (JSchException e) {
throw new IOException("SSH error", e);
}
byte[] buf = new byte[1024]; // Should be plenty
for (int i = 0; i < 1024; i++) {
int chr = inputStream.read();
if (chr == '\n' || chr == -1) {
return new String(buf, 0, i + 1, "UTF-8").trim();
}
buf[i] = (byte) chr;
}
throw new IOException("Expected pty name. Got " + new String(buf, 0, 1024, "UTF-8"));
return new GhidraInterpreterPtySession(interpreter);
}

@Override
public String nullSession(Collection<TermMode> mode) throws IOException {
if (name == null) {
this.name = getTtyNameAndStartNullSession(mode);
if ("".equals(name)) {
throw new IOException("Could not determine child remote tty name");
}
}
Msg.debug(this, "Remote SSH pty: " + name);
return name;
}

@Override
public InputStream getInputStream() {
throw new UnsupportedOperationException("The child is not local");
}

@Override
public OutputStream getOutputStream() {
throw new UnsupportedOperationException("The child is not local");
return "Ghidra interpreter null session";
}

@Override
public void setWindowSize(short cols, short rows) {
channel.setPtySize(Short.toUnsignedInt(cols), Short.toUnsignedInt(rows), 0, 0);
// TODO do we need to implement this?
return;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,12 @@

import java.io.IOException;

import com.goatshriek.rubydragon.GhidraInterpreter;
import com.goatshriek.rubydragon.ruby.RubyGhidraInterpreter;

import ghidra.pty.PtyFactory;
public class GhidraInterpreterPtyFactory implements PtyFactory {
private static final String TITLE = "Ruby Dragon Interpreter title";
private static final int WRAP_LEN = 80;
//private static final String TITLE = "Ruby Dragon Interpreter title";
//private static final int WRAP_LEN = 80;

@Override
public GhidraInterpreterPty openpty(short cols, short rows) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,51 +33,24 @@ public GhidraInterpreterPtySession(GhidraInterpreter interpreter) {
this.interpreter = interpreter;
}

protected int doWaitExited(Long millis) throws InterruptedException, TimeoutException {
long startMs = System.currentTimeMillis();
// Doesn't look like there's a clever way to wait. So do the spin sleep :(
while (!channel.isEOF()) {
Thread.sleep(100);
long elapsed = System.currentTimeMillis() - startMs;
if (millis != null && elapsed > millis) {
throw new TimeoutException();
}
}
// NB. May not be available
return channel.getExitStatus();
}

@Override
public int waitExited() throws InterruptedException {
try {
return doWaitExited(null);
}
catch (TimeoutException e) {
throw new AssertionError(e);
}
return 0;
}

@Override
public int waitExited(long timeout, TimeUnit unit)
throws InterruptedException, TimeoutException {
long millis = TimeUnit.MILLISECONDS.convert(timeout, unit);
return doWaitExited(millis);
public int waitExited(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {
return 0;
}

@Override
public void destroyForcibly() {
channel.disconnect();
return;
}

@Override
public String description() {
Session session;
try {
session = channel.getSession();
}
catch (JSchException e) {
return "ssh";
}
return "ssh " + session.getUserName() + "@" + session.getHost() + ":" + session.getPort();
// TODO make this meaningful
return "Ruby Dragon interpreter terminal session";
}
}
27 changes: 24 additions & 3 deletions src/main/java/com/goatshriek/rubydragon/ruby/RubyDragonPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

import com.goatshriek.rubydragon.DragonPlugin;
import com.goatshriek.rubydragon.GhidraInterpreter;
import com.goatshriek.rubydragon.pty.GhidraInterpreterPty;
import com.goatshriek.rubydragon.pty.GhidraInterpreterPtyFactory;

import ghidra.app.CorePluginPackage;
import ghidra.app.plugin.core.interpreter.InterpreterConnection;
Expand All @@ -37,6 +39,7 @@
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.pty.Pty;
import ghidra.pty.PtyFactory;
import ghidra.pty.PtyParent;
import ghidra.pty.PtySession;

/**
Expand Down Expand Up @@ -76,6 +79,8 @@ public class RubyDragonPlugin extends DragonPlugin implements InterpreterConnect
*/
public RubyDragonPlugin(PluginTool tool) {
super(tool, "Ruby");

terminal = null;

errIn = new PipedInputStream();
stdOutIn = new PipedInputStream();
Expand Down Expand Up @@ -120,9 +125,24 @@ public void init() {
super.init();

// console = getTool().getService(InterpreterPanelService.class).createInterpreterPanel(this, false);
terminal = getTool().getService(TerminalService.class).createWithStreams(StandardCharsets.UTF_8, stdOutIn,
stdInOut);
interpreter = new RubyGhidraInterpreter(stdInIn, stdOutOut, stdOutOut, this);
TerminalService terminalService = getTool().getService(TerminalService.class);
// .createWithStreams(StandardCharsets.UTF_8, stdOutIn,stdInOut);
GhidraInterpreterPtyFactory f = new GhidraInterpreterPtyFactory();
GhidraInterpreterPty pty;
PtySession session;
PtyParent parent;
try {
pty = (GhidraInterpreterPty) f.openpty();
interpreter = (RubyGhidraInterpreter) pty.getInterpreter();
session = pty.getChild().session(new String[] { "not-a-real-cmd" }, null);
parent = pty.getParent();
terminal = terminalService.createWithStreams(this, StandardCharsets.UTF_8, parent.getInputStream(),
parent.getOutputStream());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// interpreter = new RubyGhidraInterpreter(stdInIn, stdOutOut, stdOutOut, this);
// interpreter.setInput(stdInIn);
// interpreter.setOutput(stdOutOut);
// console.addFirstActivationCallback(() -> {
Expand All @@ -137,5 +157,6 @@ public void init() {
@Override
public void showConsole() {
// console.show();
terminal.toFront();
}
}
Loading

0 comments on commit 76562e7

Please sign in to comment.