Skip to content

How to build a trojan protocol #711

@mohuangNPC

Description

@mohuangNPC

I know this may not be within the scope of your answer, but I still hope you can help me out.
I want to use Java to construct a Trojan request, and it does follow the data in the document, but it never works. This is my code.
I'm sorry, I don't know where the problem is.

package org.example;

import org.example.entity.TrojanWrapperRequest;
import org.example.util.Sha224Util;
import javax.net.ssl.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

/**
 * @author mohuangNPC
 * @version 1.0
 * @date 2024/11/4 9:19
 */
public class TrojanMainTest {
    private static final int LISTEN_PORT = 7890;
    private static final String TROJAN_HOST = "xxx.xxx.xxx";
    private static final int TROJAN_PORT = 443;
    private static final String password = "password1";


    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(LISTEN_PORT)) {
            System.out.println("Trojan proxy server listening on port " + LISTEN_PORT);
            while (true) {
                Socket clientSocket = serverSocket.accept();
                new Thread(new ClientHandler(clientSocket)).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * The specific thread that accepts the request
     * +-----------------------+---------+----------------+---------+----------+
     * | hex(SHA224(password)) |  CRLF   | Trojan Request |  CRLF   | Payload  |
     * +-----------------------+---------+----------------+---------+----------+
     * |          56           | X'0D0A' |    Variable    | X'0D0A' | Variable |
     * +-----------------------+---------+----------------+---------+----------+
     *
     * where Trojan Request is a SOCKS5-like request:
     *
     * +-----+------+----------+----------+
     * | CMD | ATYP | DST.ADDR | DST.PORT |
     * +-----+------+----------+----------+
     * |  1  |  1   | Variable |    2     |
     * +-----+------+----------+----------+
     */
    static class ClientHandler implements Runnable {
        private final Socket clientSocket;

        public ClientHandler(Socket clientSocket) {
            this.clientSocket = clientSocket;
        }

        @Override
        public void run() {
            long threadId = Thread.currentThread().getId();
            String requestLine = "";
            try (InputStream clientInput = clientSocket.getInputStream();
                 OutputStream clientOutput = clientSocket.getOutputStream()) {
                TrojanWrapperRequest trojanWrapperRequest = new TrojanWrapperRequest();
                TrojanWrapperRequest.TrojanRequest trojanRequest = new TrojanWrapperRequest.TrojanRequest();
                // Set password and Request
                {
                    trojanRequest.setCmd(0X01);
                    String dstAddr = "";
                    int dstPort = 443;
                    int atyp = 1;
                    {
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        int byteRead;
                        while ((byteRead = clientInput.read()) != -1) {
                            if (byteRead == '\n') {
                                break;
                            }
                            baos.write(byteRead);
                        }
                        requestLine = baos.toString(StandardCharsets.UTF_8.name());
                        System.err.println(threadId+"----Received line: " + requestLine);
                        if (requestLine != null) {
                            String[] parts = requestLine.split(" ");
                            if (parts.length > 1) {
                                String url = parts[1];
                                String host = extractHostDomain(url);
                                int port = extractPort(url);
                                System.err.println(threadId+"----Host: " + host);
                                dstAddr = host;
                                System.err.println(threadId+"----Port: " + port);
                                dstPort = port;
                                System.err.println(threadId+"----Is IP: " + isValidIPAddress(host));
                                if (isValidIPAddress(host)) {
                                    atyp = 1;
                                } else {
                                    atyp = 3;
                                }
                            }
                        }
                    }
                    trojanRequest.setAtyp(atyp);
                    trojanRequest.setDstPort(dstPort);
                    trojanRequest.setDstAddr(dstAddr);
                }
                trojanWrapperRequest.setPassword(Sha224Util.encryptThisString(password));
                trojanWrapperRequest.setTrojanRequest(trojanRequest);
                // Do not verify certificates
                {
                    TrustManager[] trustAllCerts = new TrustManager[]{
                            new X509TrustManager() {
                                @Override
                                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                                    return null;
                                }

                                @Override
                                public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
                                }

                                @Override
                                public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
                                }
                            }
                    };
                    SSLContext sc = SSLContext.getInstance("SSL");
                    sc.init(null, trustAllCerts, new java.security.SecureRandom());
                    SSLSocketFactory factory = sc.getSocketFactory();
                    try (SSLSocket trojanSocket = (SSLSocket) factory.createSocket(TROJAN_HOST, TROJAN_PORT);
                         InputStream trojanInput = trojanSocket.getInputStream();
                         OutputStream trojanOutput = trojanSocket.getOutputStream()) {
                        // begin handshake
                        trojanSocket.startHandshake();
                        System.err.println(threadId+"----trojan connect success");
                        ByteArrayOutputStream out = new ByteArrayOutputStream();
                        // Assemble the data according to the Trojan protocol
                        {
                            out.write(trojanWrapperRequest.getPassword().getBytes(StandardCharsets.UTF_8));
                            out.write(0X0D);
                            out.write(0X0A);
                            out.write(trojanRequest.getCmd());
                            out.write(trojanRequest.getAtyp());
                            encodeAddress(trojanRequest.getAtyp(), out, trojanRequest.getDstAddr());
                            out.write((trojanRequest.getDstPort() >> 8) & 0xFF);
                            out.write(trojanRequest.getDstPort() & 0xFF);
                            out.write(0X0D);
                            out.write(0X0A);
                            // The domain name port has been read once when obtaining it, so it needs to be added here
                            out.write(requestLine.getBytes(StandardCharsets.UTF_8));
                        }
                        byte[] byteArray = out.toByteArray();
                        trojanOutput.write(byteArray);
                        Thread thread = new Thread(() -> {
                            clientToProxy(threadId, clientInput, trojanOutput);
                        });
                        thread.start();
                        clientToProxy(threadId,clientInput, trojanOutput);
                        proxyToClient(threadId,trojanInput, clientOutput);
                        // Waiting for the client to proxy forwarding to complete
                        try {
                            thread.join();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    clientSocket.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * Data from the client to the server
     * @param threadId
     * @param input
     * @param output
     */
    private static void clientToProxy(long threadId,InputStream input, OutputStream output) {
        byte[] buffer = new byte[4096];
        int bytesRead;
        try {
            while ((bytesRead = input.read(buffer)) != -1) {
                output.write(buffer, 0, bytesRead);
                output.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Response from the proxy server
     * @param threadId
     * @param input
     * @param output
     */
    private static void proxyToClient(long threadId,InputStream input, OutputStream output) {
        byte[] buffer = new byte[4096];
        int bytesRead;
        try {
            while ((bytesRead = input.read(buffer)) != -1) {
                output.write(buffer, 0, bytesRead);
                output.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static String extractHost(String url) {
        if (url.startsWith("http://")) {
            url = url.substring(7);
        } else if (url.startsWith("https://")) {
            url = url.substring(8);
        }
        String s = url.split("/")[0];
        return url.split("/")[0];
    }

    /**
     * Get the domain name or IP
     * @param url
     * @return
     */
    private static String extractHostDomain(String url) {
        if (url.startsWith("http://")) {
            url = url.substring(7);
        } else if (url.startsWith("https://")) {
            url = url.substring(8);
        }
        String s = url.split("/")[0];
        return s.split(":")[0];
    }

    /**
     * Get Port
     * @param url
     * @return
     */
    private static int extractPort(String url) {
        int defaultPort = url.startsWith("https://") ? 443 : 80;
        String host = extractHost(url);
        if (host.contains(":")) {
            String[] parts = host.split(":");
            return Integer.parseInt(parts[1]);
        }
        return defaultPort;
    }

    /**
     * Determine whether it is an IP or a domain name
     * @param ip
     * @return
     */
    private static boolean isValidIPAddress(String ip) {
        String ipPattern =
                "^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\." +
                        "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\." +
                        "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\." +
                        "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
        return ip.matches(ipPattern);
    }

    /**
     * Processing IP or domain name
     * @param addressType
     * @param out
     * @param dstAddr
     * @throws IOException
     */
    private static void encodeAddress(int addressType, ByteArrayOutputStream out, String dstAddr) throws IOException {
        if (addressType == 1) {
            String[] split = dstAddr.split("\\.");
            for (String item : split) {
                int b = Integer.parseInt(item);
                out.write(b);
            }
        } else if (addressType == 3) {
            out.write(dstAddr.length());
            out.write(dstAddr.getBytes(StandardCharsets.UTF_8));
        } else {
            throw new RuntimeException("error address");
        }
    }
}

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions