5

I am trying to port a C program to android using JNI. I have been able to set up the program and get java and c working fine together. The problem is I need to be able to use STDIN since the C program reads input from STDIN and returns a response through STDOUT (C program is a server-client application). I don't know if it is worth mentioning but the C program uses STDIN_FILENO file descriptor for reading input from STDIN. How do I read from STDOUT and WRITE to STDIN using Java?

I did some research and found some vague explanation at the following link: https://groups.google.com/forum/#!topic/android-ndk/Brm6jPr4C0Y that I don't understand.

Here's is the C code https://github.com/unekwu/kemi/blob/master/jni/dnscat/dnscat.c#L1270

More Details

The C program is usually run from command line like dnscat --dns <ip> <port> After which it starts listening for messages from the user. Normally entered from stdin.

Now in my Android app, I'm able to run it with JNI by calling main and different name and passing ann array of strings to it. I'v verify the program starts up correcting. The problem is how I'll send the messages to the program since there's no stdin on android.

Emmanuel John
  • 2,296
  • 1
  • 23
  • 30
  • Well, you can change signature of your C method and pass byte arrays from java code part. To do this, you can split incoming data in java part (transforming stream to chunks of bytes and passing it to C part). – str14821 Apr 28 '14 at 23:19
  • I've thought That won't work in my case since the C program is not modifiable that way (and I didn't write the program). I have included a link with the C program I'm working with. redirecting stdin and out would be the best solution for me. Just not sure how to do it. – Emmanuel John Apr 28 '14 at 23:50
  • 1
    [Process Class Overview](https://developer.android.com/reference/java/lang/Process.html) and [Sharing file descriptors between Java and Native code](http://www.kfu.com/~nsayer/Java/jni-filedesc.html). – jww May 01 '14 at 11:44
  • Your second link gave me some idea. Would try it and get back to you. – Emmanuel John May 01 '14 at 21:50

2 Answers2

8

I've created a project in github, which you can download from here.

It creates 2 named pipes (FIFO), one for input and other for output.

It opens up one end of the pipe in write only mode in native code and other end of the pipe in read only mode in Java code. The file descriptor in native code is mapped to STDOUT i.e. 1, thereafter any writes to STDOUT in native code, will be redirected to other end of pipe which can read in Java code.

It opens up one end of the pipe in read only mode in native code and other end of the pipe in write only mode in Java code. The file descriptor in native code is mapped to STDIN i.e. 0, thereafter any writes to other end of pipe in Java code, will be read by native code using STDIN.

To achieve STDOUT redirection:

Native Code:

    /*
     * Step 1: Make a named pipe
     * Step 2: Open the pipe in Write only mode. Java code will open it in Read only mode.
     * Step 3: Make STDOUT i.e. 1, a duplicate of opened pipe file descriptor.
     * Step 4: Any writes from now on to STDOUT will be redirected to the the pipe and can be read by Java code.
     */
    int out = mkfifo(outfile, 0664);
    int fdo = open(outfile, O_WRONLY);
    dup2(fdo, 1);
    setbuf(stdout, NULL);
    fprintf(stdout, "This string will be written to %s", outfile);
    fprintf(stdout, "\n");
    fflush(stdout);
    close(fdo);

Java Code:

/*
 * This thread is used for reading content which will be written by native code using STDOUT.
 */

new Thread(new Runnable() {

    @Override
    public void run() {
        BufferedReader in = null;
        try {
            in = new BufferedReader(new FileReader(mOutfile));
            while(in.ready()) {
                final String str = in.readLine();
                mHandler.post(new Runnable() {

                    @Override
                    public void run() {

                        Toast.makeText(RedirectionJni.this, str, Toast.LENGTH_LONG).show();
                    }

                });
            }
            in.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}).start();

To achieve STDIN redirection:

Native Code:

    /*
     * Step 1: Make a named pipe
     * Step 2: Open the pipe in Read only mode. Java code will open it in Write only mode.
     * Step 3: Make STDIN i.e. 0, a duplicate of opened pipe file descriptor.
     * Step 4: Any reads from STDIN, will be actually read from the pipe and JAVA code will perform write operations.
     */
    int in = mkfifo(infile, 0664);
    int fdi = open(infile, O_RDONLY);
    dup2(fdi, 0);
    char buf[256] = "";
    fscanf(stdin, "%*s %99[^\n]", buf); // Use this format to read white spaces.
    close(fdi);

Java Code:

/*
 * This thread is used for writing content which will be read by native code using STDIN.
 */
new Thread(new Runnable() {

    @Override
    public void run() {
        BufferedWriter out = null;
        try {
                out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(mInfile)));
            String content = "This content is written to " + mInfile;
            out.write(content.toCharArray(), 0, content.toCharArray().length);
            out.flush();
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}).start();

Let me know if you need any help.

Manish Mulimani
  • 17,535
  • 2
  • 41
  • 60
  • STDOUT works but stdin doesn't. It blocks when it gets to this line in the JNI code `int fdi = open(infile, O_RDONLY);`. not sure why. Do I need to change any permissions for android? – Emmanuel John May 03 '14 at 15:20
  • open blocks until other end of pipe is opened for writing. Can you debug and check whether pipe is opened in JAVA code for writing. – Manish Mulimani May 05 '14 at 07:18
  • This code sample is a little bit confusing to me in terms of the timing. Does it close the redirection after just writing the one line? Or is it leaving stdin/stdout running for the interface? – RoundSparrow hilltx Nov 25 '16 at 14:06
  • Note to anyone referencing this code: mkfifo on NDK compile APP_PLATFORM 21 and higher will likely have problems. see here: http://stackoverflow.com/questions/27091001/how-to-use-mkfifo-using-androids-ndk Overall, I've found the actual on-device runtime to be troublesome with this technique. The 'heads up' message is to be aware that stdin may close unexpectedly or other issues on some devices. – RoundSparrow hilltx Dec 01 '16 at 14:40
  • Another heads-up. Some devices have pollution of 'referenceTable ' messages in the stdout from Android framework: https://code.google.com/p/android/issues/detail?id=167715 - code you use may need to filter these out. – RoundSparrow hilltx Dec 01 '16 at 16:06
1

In the Java Code, get a command line by

Process p;
p = Runtime.getRuntime().exec("su"); // or execute something else, su is just to get root
DataOutputStream dos = new DataOutputStream(p.getOutputStream());
dos.writeBytes(ForExampleABinaryPath+" &\n");
dos.flush();

// to read (might has to run parallel in different thread)
InputStream is = p.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
is.readLine() // will get you a line o null

using write && flush and read in parallel, you might be able to achieve your goals. good luck

user3387542
  • 611
  • 1
  • 8
  • 28
  • My program is running with JNI. Its not a binary and can't be started this way. – Emmanuel John May 01 '14 at 11:40
  • In case you are only starting the main method, you can easily create an android binary. All you would need is to call ndk-build after putting an apropriate Android.mk file there. In case you are calling more (different) methods through jni, you might be able to send your inputs down to the C code by calling certain methods you create for this purpose. – user3387542 May 01 '14 at 12:59
  • The executable created by ndk-build is non-static. It produces a .so file. Not sure how to run this – Emmanuel John May 01 '14 at 22:04
  • You might have to slightly adapt your Android.mk - the Android Makefile, so it produces a binary. The binary usually has no special ending behind the file name. Using adb push you can put it on your device, adb shell, chmod to make it executable and then you should be able to start it from commandline. – user3387542 May 02 '14 at 07:02