{"id":101,"date":"2024-02-11T13:27:58","date_gmt":"2024-02-11T13:27:58","guid":{"rendered":"https:\/\/tolva.fr\/?p=101"},"modified":"2024-02-11T13:29:54","modified_gmt":"2024-02-11T13:29:54","slug":"how-ssh-client-and-server-handle-port-forwarding","status":"publish","type":"post","link":"https:\/\/tolva.fr\/index.php\/2024\/02\/11\/how-ssh-client-and-server-handle-port-forwarding\/","title":{"rendered":"How SSH client and server handle port forwarding ?"},"content":{"rendered":"\n<p>In the previous article, we saw how an application can use a SOCKS client (such as proxychains) to contact a SOCKS server (the SSH client),<\/p>\n\n\n\n<p>allowing it to reach an address who cannot be contacted directly.<\/p>\n\n\n\n<p>Here we will see:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>what happens when the SSH client is launched with a dynamic port forwarding string (through the <code>-D<\/code> option)<\/li>\n\n\n\n<li>what the SSH client does when proxychains (or any other SOCKS client !) connects to it, and how it forwards traffic to the SSH server<\/li>\n\n\n\n<li>how the SSH server receives the forwarded traffic and resends it to its final destination<\/li>\n<\/ul>\n\n\n\n<p><strong>Which source ?<\/strong><\/p>\n\n\n\n<p>We get the portable version of OpenSSH from github:<\/p>\n\n\n\n<p><code>git clone https:\/\/github.com\/openssh\/openssh-portable.git<\/code><\/p>\n\n\n\n<p>Then we checkout the last tag:<\/p>\n\n\n\n<p><code>git checkout V_9_6_P1<\/code><\/p>\n\n\n\n<p>We follow the steps described in <code>INSTALL<\/code> file to compile SSH.<\/p>\n\n\n\n<p class=\"has-medium-font-size\"><strong>What happens when we launch <code>ssh -D 9999 user@host<\/code><\/strong><\/p>\n\n\n\n<p><strong>How port forwarding string is parsed ?<\/strong><\/p>\n\n\n\n<p>When we launch the command <code>ssh -D 9999 user@host<\/code>, the piece of code (it&rsquo;s part of <code>main()<\/code> function in ssh.c) below is executed:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>case 'D':\n    if (parse_forward(&amp;fwd, optarg, 1, 0)) {\n        add_local_forward(&amp;options, &amp;fwd);\n    } else {\n        fprintf(stderr,\n            \"Bad dynamic forwarding specification \"\n            \"'%s'\\n\", optarg);\n        exit(255);\n    }\n    break;<\/code><\/pre>\n\n\n\n<p>Two functions are called:<\/p>\n\n\n\n<p>The first one is <code>parse_forward()<\/code>: Implemented in readconf.c, this function parses the port forwarding string into a <code>struct Forward *<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>\/*\n * parse_forward\n * parses a string containing a port forwarding specification of the form:\n *   dynamicfwd == 0\n *    &#91;listenhost:]listenport|listenpath:connecthost:connectport|connectpath\n *    listenpath:connectpath\n *   dynamicfwd == 1\n *    &#91;listenhost:]listenport\n * returns number of arguments parsed or zero on error\n *\/\nint\nparse_forward(struct Forward *fwd, const char *fwdspec, int dynamicfwd, int remotefwd)<\/code><\/pre>\n\n\n\n<p>Here, <code>fwdspec<\/code> is <code>optarg<\/code>, that is to say <code>9999<\/code>, <code>dynamicfwd<\/code> is set to 1, and <code>remotefwd<\/code> is 0.<\/p>\n\n\n\n<p>The function will populate a <code>struct Forward *<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>\/* Data structure for representing a forwarding request. *\/\nstruct Forward {\n    char     *listen_host;      \/* Host (address) to listen on. *\/\n    int   listen_port;      \/* Port to forward. *\/\n    char     *listen_path;      \/* Path to bind domain socket. *\/\n    char     *connect_host;     \/* Host to connect. *\/\n    int   connect_port;     \/* Port to connect on connect_host. *\/\n    char     *connect_path;     \/* Path to connect domain socket. *\/\n    int   allocated_port;   \/* Dynamically allocated listen port *\/\n    int   handle;       \/* Handle for dynamic listen ports *\/\n};<\/code><\/pre>\n\n\n\n<p>The <code>parse_forward()<\/code> function parses the port forwarding argument (which here is \u00ab\u00a09999\u00a0\u00bb) into <code>&amp;fwd<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>switch (i) {\ncase 1:\n    if (fwdargs&#91;0].ispath) {\n        fwd-&gt;listen_path = xstrdup(fwdargs&#91;0].arg);\n        fwd-&gt;listen_port = PORT_STREAMLOCAL;\n    } else {\n        fwd-&gt;listen_host = NULL;\n        fwd-&gt;listen_port = a2port(fwdargs&#91;0].arg);\n    }\n    fwd-&gt;connect_host = xstrdup(\"socks\");\n    break;<\/code><\/pre>\n\n\n\n<p>Let&rsquo;s try to figure out the content of <code>fwd<\/code> with gdb:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>(gdb) p i\n$4 = 1\n(gdb) p *((struct Forward *)fwd)\n$5 = \n{\n    listen_host = 0x0,\n    listen_port = 9999,\n    listen_path = 0x0,\n    connect_host = 0x555555641a30 \"socks\",\n    connect_port = 0,\n    connect_path = 0x0,\n    allocated_port = 0,\n    handle = 0\n}<\/code><\/pre>\n\n\n\n<p>Then comes a block how depends on <code>dynamicfwd<\/code> (which is set to 1 here, as we want to perform <em>dynamic<\/em> forwarding):<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>if (dynamicfwd) {\n    if (!(i == 1 || i == 2))\n        goto fail_free;\n} else {\n    if (!(i == 3 || i == 4)) {\n        if (fwd-&gt;connect_path == NULL &amp;&amp;\n            fwd-&gt;listen_path == NULL)\n            goto fail_free;\n    }\n    if (fwd-&gt;connect_port &lt;= 0 &amp;&amp; fwd-&gt;connect_path == NULL)\n        goto fail_free;\n}<\/code><\/pre>\n\n\n\n<p>Here the value of <code>i<\/code> (which is the number of arguments in the port forwarding string) is 1, so nothing is executed.<\/p>\n\n\n\n<p>The next block depends on a condition which is true only if the configuration put into <code>fwd<\/code> is not correct:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>    if ((fwd-&gt;listen_port &lt; 0 &amp;&amp; fwd-&gt;listen_path == NULL) ||\n        (!remotefwd &amp;&amp; fwd-&gt;listen_port == 0))\n        goto fail_free;<\/code><\/pre>\n\n\n\n<p>Then comes another block who depends on a condition true only if the configuration put into <code>fwd<\/code> is not correct:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    if (fwd-&gt;connect_host != NULL &amp;&amp;\n        strlen(fwd-&gt;connect_host) &gt;= NI_MAXHOST)\n        goto fail_free;<\/code><\/pre>\n\n\n\n<p>After come several other sanity checks. All of them depends on conditions who are not satisfied in our situation, and we get out of the <code>parse_forward()<\/code> function.<\/p>\n\n\n\n<p>The second function is <code>add_local_forward()<\/code>: <\/p>\n\n\n\n<p>This function is defined in readconf.c too.<\/p>\n\n\n\n<p>It copies the content of <code>fwd<\/code> (obtained by parsing the port forwarding string with <code>parse_forward()<\/code>) into the <code>local_forwards<\/code> substructure of <code>options<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>\/*\n * Adds a local TCP\/IP port forward to options.  Never returns if there is an\n * error.\n *\/\n\nvoid\nadd_local_forward(Options *options, const struct Forward *newfwd)\n{\n    struct Forward *fwd;\n    int i;\n\n    \/* Don't add duplicates *\/\n    for (i = 0; i &lt; options-&gt;num_local_forwards; i++) {\n        if (forward_equals(newfwd, options-&gt;local_forwards + i))\n            return;\n    }\n    options-&gt;local_forwards = xreallocarray(options-&gt;local_forwards,\n        options-&gt;num_local_forwards + 1,\n        sizeof(*options-&gt;local_forwards));\n    fwd = &amp;options-&gt;local_forwards&#91;options-&gt;num_local_forwards++];\n\n    fwd-&gt;listen_host = newfwd-&gt;listen_host;\n    fwd-&gt;listen_port = newfwd-&gt;listen_port;\n    fwd-&gt;listen_path = newfwd-&gt;listen_path;\n    fwd-&gt;connect_host = newfwd-&gt;connect_host;\n    fwd-&gt;connect_port = newfwd-&gt;connect_port;\n    fwd-&gt;connect_path = newfwd-&gt;connect_path;\n}<\/code><\/pre>\n\n\n\n<p>By the way, <\/p>\n\n\n\n<p class=\"has-text-align-left\">the line <code>fwd = &amp;options->local_forwards[options->num_local_forwards++];<\/code> increments <code>options->num_local_forwards<\/code>.<\/p>\n\n\n\n<p><strong>Where does the SSH calls <code>bind()<\/code> ?<\/strong><\/p>\n\n\n\n<p>The SSH client will necessarily start to listen on the port specified in the <code>listen_port<\/code> of the <code>Forward<\/code> structure. Let&rsquo;s look for calls to the <code>bind()<\/code> function.<\/p>\n\n\n\n<p>Several candidates are present:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>$ grep -nre \"bind(\" .\/ --exclude-dir={regress,autom4te.cache}\n(...)\n.\/sshconnect.c:417:    if (bind(sock, (struct sockaddr *)&amp;bindaddr, bindaddrlen) != 0) {\n.\/misc.c:1926:    if (bind(sock, (struct sockaddr *)&amp;sunaddr, sizeof(sunaddr)) == -1) {\n.\/channels.c:3858:        if (bind(sock, ai-&gt;ai_addr, ai-&gt;ai_addrlen) == -1) {\n.\/channels.c:5028:            if (bind(sock, ai-&gt;ai_addr, ai-&gt;ai_addrlen) == -1) {\n.\/sshd.c:1067:        if (bind(listen_sock, ai-&gt;ai_addr, ai-&gt;ai_addrlen) == -1) {\n(...)\n.\/openbsd-compat\/bindresvport.c:102:        error = bind(sd, sa, salen);\n.\/openbsd-compat\/rresvport.c:90:        if (bind(s, sa, salen) &gt;= 0)<\/code><\/pre>\n\n\n\n<p>Moreover, at this stage we do not know yet whether the SSH client starts to listen before or after connecting to the SSH server.<\/p>\n\n\n\n<p>We therefore launch the SSH client with an high verbosity into gdb, after placing a breakpoint on the <code>bind()<\/code> function:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>$ gdb .\/ssh\n(...)\n(gdb) start -D 9999 thomas@1.2.3.4 -vvv\nTemporary breakpoint 1 at 0xb770: file ssh.c, line 669.\nStarting program: \/home\/thomas\/bordels\/openssh-portable\/ssh -D 9999 thomas@1.2.3.4 -vvv\n&#91;Thread debugging using libthread_db enabled]\nUsing host libthread_db library \"\/lib\/x86_64-linux-gnu\/libthread_db.so.1\".\n\nTemporary breakpoint 1, main (ac=5, av=0x7fffffffe248) at ssh.c:669\n669    {\n(gdb) break bind\nBreakpoint 2 at 0x7ffff7b9da80<\/code><\/pre>\n\n\n\n<p>When the secure channel with the SSH server is established, the user authenticates:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>debug1: Authenticating to 1.2.3.4:22 as 'thomas'\n(...)\ndebug1: Next authentication method: password\nthomas@1.2.3.4's password: \ndebug3: send packet: type 50\ndebug2: we sent a password packet, wait for reply\ndebug3: receive packet: type 52\nAuthenticated to 1.2.3.4 (&#91;1.2.3.4]:22) using \"password\".<\/code><\/pre>\n\n\n\n<p>and <code>bind()<\/code> is called for the first time:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>debug1: Local connections to LOCALHOST:9999 forwarded to remote address socks:0\ndebug3: channel_setup_fwd_listener_tcpip: type 2 wildcard 0 addr NULL\n\nBreakpoint 2, 0x00007ffff7b9da80 in bind () from \/lib\/x86_64-linux-gnu\/libc.so.6<\/code><\/pre>\n\n\n\n<p>We have a look at the backtrace:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>(gdb) bt\n#0  0x00007ffff7b9da80 in bind () from \/lib\/x86_64-linux-gnu\/libc.so.6\n#1  0x00007ffff7bb8ec2 in ?? () from \/lib\/x86_64-linux-gnu\/libc.so.6\n#2  0x00007ffff7b863d1 in getaddrinfo () from \/lib\/x86_64-linux-gnu\/libc.so.6\n#3  0x000055555559ae31 in channel_setup_fwd_listener_tcpip (ssh=0x5555556406b0, type=2, fwd=0x555555642a50, allocated_listen_port=0x0, fwd_opts=0x7fffffffc330) at channels.c:3800\n#4  0x00005555555622fb in ssh_init_forwarding (ifname=&lt;synthetic pointer&gt;, ssh=0x5555556406b0) at ssh.c:2045\n#5  ssh_session2 (cinfo=0x555555644410, ssh=0x5555556406b0) at ssh.c:2207\n#6  main (ac=&lt;optimized out&gt;, av=&lt;optimized out&gt;) at ssh.c:1787<\/code><\/pre>\n\n\n\n<p>The <code>bind()<\/code> function has the following signature:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);<\/code><\/pre>\n\n\n\n<p>The most interesting argument is probably the second one. As we are studying a x64 binary, the address of this argument is stored in <code>$rsi<\/code> register.<\/p>\n\n\n\n<p>We therefore print it as a <code>const struct sockaddr *<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>(gdb) p *((const struct sockaddr *)$rsi)\n$1 = {sa_family = 16, sa_data = \"\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\062\\062) \"}<\/code><\/pre>\n\n\n\n<p>The first field of this structure identifies the address family. To see what means the value <code>16<\/code>, we have a look at socket.h:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>#define PF_INET        2   \/* IP protocol family.  *\/\n#define PF_AX25        3   \/* Amateur Radio AX.25.  *\/\n#define PF_IPX        4   \/* Novell Internet Protocol.  *\/\n#define PF_APPLETALK    5   \/* Appletalk DDP.  *\/\n#define PF_NETROM    6   \/* Amateur radio NetROM.  *\/\n#define PF_BRIDGE    7   \/* Multiprotocol bridge.  *\/\n#define PF_ATMPVC    8   \/* ATM PVCs.  *\/\n#define PF_X25        9   \/* Reserved for X.25 project.  *\/\n#define PF_INET6    10  \/* IP version 6.  *\/\n#define PF_ROSE        11  \/* Amateur Radio X.25 PLP.  *\/\n#define PF_DECnet    12  \/* Reserved for DECnet project.  *\/\n#define PF_NETBEUI    13  \/* Reserved for 802.2LLC project.  *\/\n#define PF_SECURITY    14  \/* Security callback pseudo AF.  *\/\n#define PF_KEY        15  \/* PF_KEY key management API.  *\/\n#define PF_NETLINK    16<\/code><\/pre>\n\n\n\n<p>This is therefore a <code>PF_NETLINK<\/code>. According to the call stack, it seems that bind() was called by <code>getaddrinfo()<\/code>, which is called in <code>channel_setup_fwd_listener_tcpip()<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>if ((r = getaddrinfo(addr, strport, &amp;hints, &amp;aitop)) != 0) {<\/code><\/pre>\n\n\n\n<p>Then we break a second time:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>(gdb) c\nContinuing.\ndebug3: sock_set_v6only: set socket 4 IPV6_V6ONLY\ndebug1: Local forwarding listening on ::1 port 9999.\n\nBreakpoint 2, 0x00007ffff7b9da80 in bind () from \/lib\/x86_64-linux-gnu\/libc.so.6<\/code><\/pre>\n\n\n\n<p>This time, <code>bind()<\/code> was explicitely called by <code>channel_setup_fwd_listener_tcpip()<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>(gdb) bt\n#0  0x00007ffff7b9da80 in bind () from \/lib\/x86_64-linux-gnu\/libc.so.6\n#1  0x000055555559af38 in channel_setup_fwd_listener_tcpip (ssh=0x5555556406b0, type=2, fwd=0x555555642a50, allocated_listen_port=0x0, fwd_opts=0x7fffffffc330) at channels.c:3858\n#2  0x00005555555622fb in ssh_init_forwarding (ifname=&lt;synthetic pointer&gt;, ssh=0x5555556406b0) at ssh.c:2045\n#3  ssh_session2 (cinfo=0x555555644410, ssh=0x5555556406b0) at ssh.c:2207\n#4  main (ac=&lt;optimized out&gt;, av=&lt;optimized out&gt;) at ssh.c:1787<\/code><\/pre>\n\n\n\n<p>We dump the <code>addr<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>(gdb) p *((const struct sockaddr *)$rsi)\n$2 = {sa_family = 10, sa_data = \"'\\017\", '\\000' &lt;repeats 11 times&gt;}<\/code><\/pre>\n\n\n\n<p>This time, the <code>sa_family<\/code> is <code>10<\/code>, which stands for <code>PF_INET6<\/code>. This is consistent with the \u00ab\u00a0debug1: Local forwarding listening on ::1 port 9999.\u00a0\u00bb debug message.<\/p>\n\n\n\n<p>Moreover, if we list the connections when reaching the breakpoint:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>$ netstat -tplen | grep ssh\n(Tous les processus ne peuvent \u00eatre identifi\u00e9s, les infos sur les processus\nnon poss\u00e9d\u00e9s ne seront pas affich\u00e9es, vous devez \u00eatre root pour les voir toutes.)<\/code><\/pre>\n\n\n\n<p>and just after:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>$ netstat -tplen | grep ssh\n(Tous les processus ne peuvent \u00eatre identifi\u00e9s, les infos sur les processus\nnon poss\u00e9d\u00e9s ne seront pas affich\u00e9es, vous devez \u00eatre root pour les voir toutes.)\ntcp6       0      0 ::1:9999                :::*                    LISTEN      1000       9142268    1420192\/ssh<\/code><\/pre>\n\n\n\n<p>We see that SSH client is now listening on 9999 TCP\/IPv6 port.<\/p>\n\n\n\n<p>We reach the breakpoint a third time:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>debug1: channel 0: new port-listener &#91;port listener] (inactive timeout: 0)\ndebug1: Local forwarding listening on 127.0.0.1 port 9999.\n\nBreakpoint 2, 0x00007ffff7b9da80 in bind () from \/lib\/x86_64-linux-gnu\/libc.so.6\n(gdb) bt\n#0  0x00007ffff7b9da80 in bind () from \/lib\/x86_64-linux-gnu\/libc.so.6\n#1  0x000055555559af38 in channel_setup_fwd_listener_tcpip (ssh=0x5555556406b0, type=2, fwd=0x555555642a50, allocated_listen_port=0x0, fwd_opts=0x7fffffffc330) at channels.c:3858\n#2  0x00005555555622fb in ssh_init_forwarding (ifname=&lt;synthetic pointer&gt;, ssh=0x5555556406b0) at ssh.c:2045\n#3  ssh_session2 (cinfo=0x555555644410, ssh=0x5555556406b0) at ssh.c:2207\n#4  main (ac=&lt;optimized out&gt;, av=&lt;optimized out&gt;) at ssh.c:1787<\/code><\/pre>\n\n\n\n<p>As we can see, the callstack is still the same, and the debug message \u00ab\u00a0debug1: Local forwarding listening on 127.0.0.1 port 9999.\u00a0\u00bb leads to think SSH client is starting to listen on the 9999 TCP\/IPv4 port.<\/p>\n\n\n\n<p>This is consistent with the <code>addr<\/code> argument:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>(gdb) p *((const struct sockaddr *)$rsi)\n$3 = {sa_family = 2, sa_data = \"'\\017\\177\\000\\000\\001\\000\\000\\000\\000\\000\\000\\000\"}<\/code><\/pre>\n\n\n\n<p>Indeed, <code>2<\/code> stands for <code>PF_INET<\/code>.<\/p>\n\n\n\n<p>And if we <code>netstat<\/code> just after entering <code>continue<\/code> in gdb, we can see that SSH client is now listening on the 9999 TCP\/IPv4 port:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>$ netstat -tplen | grep ssh\n(Tous les processus ne peuvent \u00eatre identifi\u00e9s, les infos sur les processus\nnon poss\u00e9d\u00e9s ne seront pas affich\u00e9es, vous devez \u00eatre root pour les voir toutes.)\ntcp        0      0 127.0.0.1:9999          0.0.0.0:*               LISTEN      1000       9160728    1420192\/ssh         \ntcp6       0      0 ::1:9999                :::*                    LISTEN      1000       9142268    1420192\/ssh<\/code><\/pre>\n\n\n\n<p>So let&rsquo;s sum up:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The <code>ssh_session2()<\/code> is called at the very end of the <code>main()<\/code> function of the client:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code> skip_connect:\n    exit_status = ssh_session2(ssh, cinfo);\n    ssh_conn_info_free(cinfo);\n    ssh_packet_close(ssh);\n\n    if (options.control_path != NULL &amp;&amp; muxserver_sock != -1)\n        unlink(options.control_path);\n\n    \/* Kill ProxyCommand if it is running. *\/\n    ssh_kill_proxy_command();\n\n    return exit_status;\n}<\/code><\/pre>\n\n\n\n<p>At this point, the port forwarding string has been handled far more earlier in the beginning of the <code>main()<\/code> function.<\/p>\n\n\n\n<p>Calling <code>ssh_init_forwarding()<\/code> is one of the first things done in <code>ssh_session2()<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>static int\nssh_session2(struct ssh *ssh, const struct ssh_conn_info *cinfo)\n{\n    int r, interactive, id = -1;\n    char *cp, *tun_fwd_ifname = NULL;\n\n    \/* XXX should be pre-session *\/\n    if (!options.control_persist)\n        ssh_init_stdio_forwarding(ssh);\n\n    ssh_init_forwarding(ssh, &amp;tun_fwd_ifname);<\/code><\/pre>\n\n\n\n<p>The <code>ssh_init_forwarding()<\/code> function initializes both TC\/IP local and remote forwarding:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>static void\nssh_init_forwarding(struct ssh *ssh, char **ifname)\n{\n    int success = 0;\n    int i;\n\n    ssh_init_forward_permissions(ssh, \"permitremoteopen\",\n        options.permitted_remote_opens,\n        options.num_permitted_remote_opens);\n\n    if (options.exit_on_forward_failure)\n        forward_confirms_pending = 0; \/* track pending requests *\/\n    \/* Initiate local TCP\/IP port forwardings. *\/\n    for (i = 0; i &lt; options.num_local_forwards; i++) {\n        debug(\"Local connections to %.200s:%d forwarded to remote \"\n            \"address %.200s:%d\",\n            (options.local_forwards&#91;i].listen_path != NULL) ?\n            options.local_forwards&#91;i].listen_path :\n            (options.local_forwards&#91;i].listen_host == NULL) ?\n            (options.fwd_opts.gateway_ports ? \"*\" : \"LOCALHOST\") :\n            options.local_forwards&#91;i].listen_host,\n            options.local_forwards&#91;i].listen_port,\n            (options.local_forwards&#91;i].connect_path != NULL) ?\n            options.local_forwards&#91;i].connect_path :\n            options.local_forwards&#91;i].connect_host,\n            options.local_forwards&#91;i].connect_port);\n        success += channel_setup_local_fwd_listener(ssh,\n            &amp;options.local_forwards&#91;i], &amp;options.fwd_opts);\n    }\n    if (i &gt; 0 &amp;&amp; success != i &amp;&amp; options.exit_on_forward_failure)\n        fatal(\"Could not request local forwarding.\");\n    if (i &gt; 0 &amp;&amp; success == 0)\n        error(\"Could not request local forwarding.\");\n\n    \/* Initiate remote TCP\/IP port forwardings. *\/\n    (...)<\/code><\/pre>\n\n\n\n<p>Here <code>options.num_local_forwards<\/code> is set 1 (it is initially set to 0 and incremented in <code>add_local_forward<\/code>), <\/p>\n\n\n\n<p>and <code>channel_setup_local_fwd_listener<\/code> is therefore called once in our context.<\/p>\n\n\n\n<p>This function essentially calls <code>channel_setup_fwd_listener_streamlocal<\/code> or <code>channel_setup_fwd_listener_tcpip<\/code> on whether <code>fwd-&gt;listen_path<\/code> is <code>NULL<\/code> or not:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>\/* protocol local port fwd, used by ssh *\/\nint\nchannel_setup_local_fwd_listener(struct ssh *ssh,\n    struct Forward *fwd, struct ForwardOptions *fwd_opts)\n{\n    if (fwd-&gt;listen_path != NULL) {\n        return channel_setup_fwd_listener_streamlocal(ssh,\n            SSH_CHANNEL_UNIX_LISTENER, fwd, fwd_opts);\n    } else {\n        return channel_setup_fwd_listener_tcpip(ssh,\n            SSH_CHANNEL_PORT_LISTENER, fwd, NULL, fwd_opts);\n    }\n}<\/code><\/pre>\n\n\n\n<p>In our context, <code>fwd-&gt;listen_path<\/code> is <code>NULL<\/code> so <code>channel_setup_fwd_listener_tcpip<\/code> is called.<\/p>\n\n\n\n<p>In <code>channel_setup_fwd_listener_tcpip<\/code>, the first call to <code>bind()<\/code> is done by <code>getaddrinfo()<\/code>.<\/p>\n\n\n\n<p>The <code>getaddrinfo()<\/code> function is used to enumerate the network addresses of the SSH client.<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>    \/*\n     * getaddrinfo returns a loopback address if the hostname is\n     * set to NULL and hints.ai_flags is not AI_PASSIVE\n     *\/\n    memset(&amp;hints, 0, sizeof(hints));\n    hints.ai_family = ssh-&gt;chanctxt-&gt;IPv4or6;\n    hints.ai_flags = wildcard ? AI_PASSIVE : 0;\n    hints.ai_socktype = SOCK_STREAM;\n    snprintf(strport, sizeof strport, \"%d\", fwd-&gt;listen_port);\n    if ((r = getaddrinfo(addr, strport, &amp;hints, &amp;aitop)) != 0) {\n        if (addr == NULL) {\n            \/* This really shouldn't happen *\/\n            ssh_packet_disconnect(ssh, \"getaddrinfo: fatal error: %s\",\n                ssh_gai_strerror(r));\n        } else {\n            error_f(\"getaddrinfo(%.64s): %s\", addr,\n                ssh_gai_strerror(r));\n        }\n        return 0;\n    }<\/code><\/pre>\n\n\n\n<p>The <code>aitop<\/code> is a chained-list of <code>struct addrinfo<\/code>.<\/p>\n\n\n\n<p>The <code>channel_setup_fwd_listener_tcpip<\/code> function then iterates over this list.<\/p>\n\n\n\n<p>Each turn of the loop calls:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>socket()<\/code> to create a new socket<\/li>\n\n\n\n<li><code>bind()<\/code> on the address stored in <code>ai<\/code>, and<\/li>\n\n\n\n<li><code>listen()<\/code> to start listening for connection on the new socket bind and the specified address:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>    for (ai = aitop; ai; ai = ai-&gt;ai_next) {\n(...)\n        \/* Create a port to listen for the host. *\/\n        sock = socket(ai-&gt;ai_family, ai-&gt;ai_socktype, ai-&gt;ai_protocol);\n        if (sock == -1) {\n            \/* this is no error since kernel may not support ipv6 *\/\n            verbose(\"socket &#91;%s]:%s: %.100s\", ntop, strport,\n                strerror(errno));\n            continue;\n        }\n(...)\n        debug(\"Local forwarding listening on %s port %s.\",\n            ntop, strport);\n\n        \/* Bind the socket to the address. *\/\n        if (bind(sock, ai-&gt;ai_addr, ai-&gt;ai_addrlen) == -1) {\n(...)        \n        \/* Start listening for connections on the socket. *\/\n        if (listen(sock, SSH_LISTEN_BACKLOG) == -1) {\n            error(\"listen &#91;%s]:%s: %.100s\", ntop, strport,\n                strerror(errno));\n            close(sock);\n            continue;\n        }       \n(...)        \n    }<\/code><\/pre>\n\n\n\n<p>This concretely explains why the SSH client starts to listen on ::1:9999 (tcp6) <\/p>\n\n\n\n<p>and on 127.0.0.1:9999 (tcp).<\/p>\n\n\n\n<p><strong>What happens when proxychains connects to the SSH client ?<\/strong><\/p>\n\n\n\n<p>As we&rsquo;ve seen, the parsing of dynamic port forwarding string happens in the beginning of the <code>main()<\/code> function,<\/p>\n\n\n\n<p>and one of the last thing done in <code>main()<\/code> is calling <code>ssh_session2()<\/code> function:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code> skip_connect:\n    exit_status = ssh_session2(ssh, cinfo);\n    ssh_conn_info_free(cinfo);\n    ssh_packet_close(ssh);\n\n    if (options.control_path != NULL &amp;&amp; muxserver_sock != -1)\n        unlink(options.control_path);\n\n    \/* Kill ProxyCommand if it is running. *\/\n    ssh_kill_proxy_command();\n\n    return exit_status;\n}<\/code><\/pre>\n\n\n\n<p>The <code>ssh_init_forwarding()<\/code> function who indirectly <code>bind()<\/code> and starts to <code>listen()<\/code> as a SOCKS server is called at the beginning of the <code>ssh_session2()<\/code> function.<\/p>\n\n\n\n<p>At its end, this function then calls the <code>client_loop()<\/code> function, which is the SSH client endless loop.<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>    return client_loop(ssh, tty_flag, tty_flag ?\n        options.escape_char : SSH_ESCAPECHAR_NONE, id);\n}<\/code><\/pre>\n\n\n\n<p>Every further interaction with the SSH client will therefore be handled by the <code>client_loop()<\/code> function, at least indirectly.<\/p>\n\n\n\n<p>According to the chapter 7.2 of RFC 4524 (who describes the SSH connection protocol),<br>a client who wants to forward a local port to the other side will send a <code>SSH_MSG_CHANNEL_OPEN<\/code> packet, whose string will be \u00ab\u00a0direct-tcpip\u00a0\u00bb:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>byte      SSH_MSG_CHANNEL_OPEN\nstring    \"direct-tcpip\"\nuint32    sender channel\nuint32    initial window size\nuint32    maximum packet size\nstring    host to connect\nuint32    port to connect\nstring    originator IP address\nuint32    originator port<\/code><\/pre>\n\n\n\n<p>Moreover, we also know that data transfer from client to server will imply sending of <code>SSH_MSG_CHANNEL_DATA<\/code> packets.<\/p>\n\n\n\n<p>To understand what happens, we can therefore set a breakpoint on the function dedicated to sending SSH packets, and have a look at the backtrace.<\/p>\n\n\n\n<p>Digging a little bit in the source code leads to the sshpkt_send() function:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/* send it *\/\nint\nsshpkt_send(struct ssh *ssh)\n{\n    if (ssh-&gt;state &amp;&amp; ssh-&gt;state-&gt;mux)\n        return ssh_packet_send_mux(ssh);\n    return ssh_packet_send2(ssh);\n}<\/code><\/pre>\n\n\n\n<p>We could do this manually but let&rsquo;s simplify our life and make a gdb script instead:<\/p>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>file .\/ssh\n\nstart thomas@1.2.3.4 -D 9999\n\nbreak sshpkt_send\n\nset var $sshpkt_send_addr = sshpkt_send\n\ncontinue\n\nwhile 1\n\n    if $rip == $sshpkt_send_addr\n        print \"i have reached sshpkt_send function !\"\n        print \"backtrace:\"\n        bt\n\n        print \"outgoing packet:\"\n\n        set $state = (*(struct ssh *)$rdi)->state\n        set $pkt = (*(struct session_state *)$state)->outgoing_packet\n        set $buf = (*(struct sshbuf *)$pkt)->d\n        x \/64c $buf\n\n        continue\n    end\nend<\/code><\/pre>\n\n\n\n<p>This script will:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>break on every <code>sshpkt_send()<\/code> call;<\/li>\n\n\n\n<li>print the backtrace;<\/li>\n\n\n\n<li>dump the content plaintext content of the SSH packet.<\/li>\n<\/ul>\n\n\n\n<p>It leads to the following observations:<\/p>\n\n\n\n<p><strong>Opening of \u00ab\u00a0direct-tcpip\u00a0\u00bb channel<\/strong><\/p>\n\n\n\n<p>Sending the <code>SSH_MSG_CHANNEL_OPEN<\/code> involves the following sequence of function calls:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>client_loop()\n |\n +--&gt; client_wait_until_can_do_something()\n       |\n       +--&gt; channel_prepare_poll()\n             |\n             +--&gt; channel_handler()\n                   |\n                   +--&gt; port_open_helper()\n                         |\n                         +--&gt; sshpkt_send()<\/code><\/pre>\n\n\n\n<p><strong>Sending of forwarded data<\/strong><\/p>\n\n\n\n<p>Sending the <code>SSH_MSG_CHANNEL_DATA<\/code> involves the following sequence of function calls:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>client_loop()\n |\n +--&gt; channel_output_poll()\n       |\n       +--&gt; channel_output_poll_input_open()\n             |\n             +--&gt; sshpkt_send()<\/code><\/pre>\n\n\n\n<p><strong>End of sending<\/strong><\/p>\n\n\n\n<p>Sending the <code>SSH_MSG_CHANNEL_EOF<\/code> involves the following sequence of function calls:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>client_loop()\n |\n +--&gt; channel_output_poll()\n       |\n       +--&gt; channel_output_poll_input_open()\n             |\n             +--&gt; chan_ibuf_empty()\n                   |\n                   +--&gt; chan_send_eof2()\n                         |\n                         +--&gt; sshpkt_send()<\/code><\/pre>\n\n\n\n<p><strong>Channel closure<\/strong><\/p>\n\n\n\n<p>Sending the <code>SSH_MSG_CHANNEL_CLOSE<\/code> involves the following sequence of function calls:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>client_loop()\n |\n +--&gt; client_wait_until_can_do_something()\n       |\n       +--&gt; channel_prepare_poll()\n             |\n             +--&gt; channel_handler()\n                   |\n                   +---&gt; channel_garbage_collect()\n                          |\n                          +--&gt; chan_is_dead()\n                                |\n                                +--&gt; chan_send_close2()\n                                      |\n                                      +-&gt; sshpkt_send()`<\/code><\/pre>\n\n\n\n<p><strong>Handling of client connection<\/strong><\/p>\n\n\n\n<p>The proxychains connection to SSH acting as a SOCKS server is done via the following function calls:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>client_loop()\n |\n +--&gt; client_wait_until_can_do_something()\n       |\n       +--&gt; channel_prepare_poll()\n             |\n             +--&gt; channel_handler()\n                   |\n                   +--&gt; channel_pre_dynamic()\n                         |\n                         +--&gt; channel_decode_socks5()<\/code><\/pre>\n\n\n\n<p><strong>What data is exchanged ?<\/strong><\/p>\n\n\n\n<p>When we <code>proxychains wget http:\/\/www.somewhere.com<\/code>, <\/p>\n\n\n\n<p>we see two sequences of <\/p>\n\n\n\n<p><code>SSH_MSG_CHANNEL_OPEN\/SSH_MSG_CHANNEL_DATA\/SSH_MSG_CHANNEL_EOF\/SSH_MSG_CHANNEL_CLOSE<\/code>.<\/p>\n\n\n\n<p>The first one is made to perform a DNS request: what is the IP address of www.somewhere.com ?<\/p>\n\n\n\n<p>The second one is dedicated to the HTTP exchange properly speaking.<\/p>\n\n\n\n<p><strong>How does the SSH server forwards the proxychains traffic to the destination ?<\/strong><\/p>\n\n\n\n<p>We use the same method as on the client side: We recompile the server from the github source, we put breakpoints with gdb and <em>voil\u00e0<\/em> !<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Forwarding the DNS request from the client:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>server_loop2()\n|\n+-&gt; process_buffered_input_packets()\n    |\n    +-&gt; ssh_dispatch_run_fatal()\n        |\n        +-&gt; ssh_dispatch_run()\n            |\n            +-&gt; server_input_channel_open()\n                |\n                +-&gt; server_request_direct_tcpip()\n                    |\n                    +-&gt; channel_connect_to_port(ssh, \"4.2.2.2\", 53, \"direct-tcpip\", ...)\n                        |\n                        +-&gt; connect_to_helper(ssh, \"4.2.2.2\", 53, ...)\n                            |\n                            +-&gt; connect_next()\n                                |\n                                +-&gt; __libc_connect()<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Forwarding the HTTPS traffic from the client:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code alignwide\"><code>server_loop2()\n |\n +-> process_buffered_input_packets()\n      |\n      +-> ssh_dispatch_run_fatal()\n           |\n           +-> ssh_dispatch_run()\n                |\n                +-> server_input_channel_open()\n                     |\n                     +-> server_request_direct_tcpip()\n                          |\n                          +-> channel_connect_to_port(..., \"80.77.95.49\", 443, ...)\n                               |\n                               +-> connect_to_helper(ssh, \"80.77.95.49\", 443, ...)\n                                    |\n                                    +-> connect_next()\n                                         |\n                                         +-> __libc_connect()<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>In the previous article, we saw how an application can use a SOCKS client (such as proxychains) to contact a SOCKS server (the SSH client), allowing it to reach an address who cannot be contacted directly. Here we will see: Which source ? We get the portable version of OpenSSH from github: git clone https:\/\/github.com\/openssh\/openssh-portable.git [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-101","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/tolva.fr\/index.php\/wp-json\/wp\/v2\/posts\/101","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tolva.fr\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tolva.fr\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tolva.fr\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tolva.fr\/index.php\/wp-json\/wp\/v2\/comments?post=101"}],"version-history":[{"count":3,"href":"https:\/\/tolva.fr\/index.php\/wp-json\/wp\/v2\/posts\/101\/revisions"}],"predecessor-version":[{"id":105,"href":"https:\/\/tolva.fr\/index.php\/wp-json\/wp\/v2\/posts\/101\/revisions\/105"}],"wp:attachment":[{"href":"https:\/\/tolva.fr\/index.php\/wp-json\/wp\/v2\/media?parent=101"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tolva.fr\/index.php\/wp-json\/wp\/v2\/categories?post=101"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tolva.fr\/index.php\/wp-json\/wp\/v2\/tags?post=101"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}