Hacking a telnetd sensor node on the back of busybox telnetd

Telnetd sensor node what?!

I call it a sensor node (more on that in a later post), you might call it a telnetd stub. We are essentially talking about a telnet service which looks like a telnet service, is fingerprinted as a telnet service, replies like a telnet service, but is limited to bare minimal functionality and just meant as a sensor to gather information.

In this case, we are looking for the folks who are looking for us - or, who are looking for exposed services on the interwebs (or maybe your corp network?).

You were talking about busybox?

Yes. Instead of implementing our own telnet service (this RFC and especially how clients implement it, is a mess!) we will just use the busybox telnetd and hack it so that it hands us the host which requests a login to our own login application.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
--- busybox-1.30.1/networking/telnetd.c 2019-02-14 13:31:15.000000000 +0000
+++ busybox_telnetd_patched.c   2019-05-03 10:50:04.000000000 +0000
@@ -413,7 +413,7 @@
 #if !ENABLE_FEATURE_TELNETD_STANDALONE
        enum { sock = 0 };
 #endif
-       const char *login_argv[2];
+       const char *login_argv[3];
        struct termios termbuf;
        int fd, pid;
        char tty_name[GETPTY_BUFSIZE];
@@ -541,15 +541,24 @@
         * for vforked child to exec!) */
        print_login_issue(G.issuefile, tty_name);
 
+       len_and_sockaddr *lsa = get_peer_lsa(sock);
+       char *hostname = NULL;
+       if (lsa) {
+         hostname = xmalloc_sockaddr2dotted(&lsa->u.sa);
+         free(lsa);
+       }
+
        /* Exec shell / login / whatever */
        login_argv[0] = G.loginpath;
-       login_argv[1] = NULL;
+       login_argv[1] = hostname;
+       login_argv[2] = NULL;
        /* exec busybox applet (if PREFER_APPLETS=y), if that fails,
         * exec external program.
         * NB: sock is either 0 or has CLOEXEC set on it.
         * fd has CLOEXEC set on it too. These two fds will be closed here.
         */
        BB_EXECVP(G.loginpath, (char **)login_argv);
+       free(hostname);
        /* _exit is safer with vfork, and we shouldn't send message
         * to remote clients anyway */
        _exit(EXIT_FAILURE); /*bb_perror_msg_and_die("execv %s", G.loginpath);*/

Thank’s to the busybox team - you rock!

The only thing left to do now (besides building busybox with the patched telnetd service) is to start the busybox telnetd with telnetd -l YOUR_LOGIN_APPLICATION.

Give me a hand with this login application!

Sure thing. Here’s a simple implementation of login which does nothing but “handle” the telnetd login process and take the host information handed over as an argument from the busybox telnetd. And when I say “handle” I mean reads the credentials and logs them together with the remote host information.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import (
  "bufio"
  "fmt"
  "os"
  "time"
  "strings"
  "net"
)

func main() {

  f, err := os.OpenFile("/log", os.O_RDWR|os.O_APPEND, os.ModeNamedPipe)
  if err != nil {
    fmt.Fprintf(os.Stderr, "error opening file: %v", err)
  }
  defer f.Close()

  args := os.Args

  host, port, _ := net.SplitHostPort(args[1])
  ip := net.ParseIP(host)
  reader := bufio.NewReader(os.Stdin)

  attempts := 0
  for {
    fmt.Printf("localhost login: ")
    user, _ := reader.ReadString('\n')
    time.Sleep(250 * time.Millisecond)

    fmt.Printf("Password: ")
    pass, _ := reader.ReadString('\n')

    fmt.Fprintf(f, "Username: %s Password: %s From: %s:%s\n", strings.Trim(user, "\r\n\t "), strings.Trim(pass, "\r\n\t "), ip, port)

    time.Sleep(1000 * time.Millisecond)
    fmt.Printf("Login incorrect\n")
    time.Sleep(500 * time.Millisecond)
    if attempts >= 3 { os.Exit(1) }
  }

}

As you might have guessed from the code above, I chose a fifo (mkfifo /log) to log to. It will all make sense soon, I promise ;)

Can you just give me a recipe?!

Indeed I can. Here you go:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
FROM alpine:latest AS build

RUN apk update && \
    apk add build-base perl linux-headers libc-dev go patch

WORKDIR /

RUN wget https://gist.githubusercontent.com/RichardSammet/380ae4e694d786c2ff2b2c680589e057/raw/e24ec1658e9811939735f49797683773e3e1ccbf/loginprinter.go

RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-extldflags "-static" -s -w' -o loginprinter loginprinter.go

RUN wget https://busybox.net/downloads/busybox-1.30.1.tar.bz2 && \
    tar xpf busybox-*.tar.bz2 && \
    rm busybox-*.tar.bz2 && \
    mv busybox-*/ busybox
    
RUN wget https://gist.githubusercontent.com/RichardSammet/5a4a49fdf293cc1502556a50d1fa941b/raw/a88e20571ee5c40848199df5935a0edfaad2f5cd/busybox_telnetd.patch.b64 && \
    base64 -d /busybox_telnetd.patch.b64 > /busybox_telnetd.patch

WORKDIR /busybox/

RUN patch networking/telnetd.c /busybox_telnetd.patch && \
    make defconfig && \
    make -j 4

# --- end of build ---

FROM alpine:latest

COPY --from=build /busybox/busybox /busybox
COPY --from=build /loginprinter /loginprinter

WORKDIR /

RUN wget https://gist.githubusercontent.com/RichardSammet/02b84f9b4fe80d67d150fb72e1676619/raw/2af92312b4b6df3e6628d6c04add4cbeaef7d91b/entrypoint.sh

RUN adduser -D -H app && \
    mkfifo /log && \
    chown app /busybox /loginprinter /log /entrypoint.sh && \
    chmod 750 /entrypoint.sh /loginprinter /busybox

USER app

ENTRYPOINT ["/entrypoint.sh"]

Build and run it like this:

 --- Terminal 1 ---
e-axe@little0ne $ docker build -t telnet-sensor .
e-axe@little0ne $ docker run -p 2323:2323 telnet-sensor

Once it’s up you can test it with a telnet client or netcat:

 --- Terminal 2 ---
nc -v localhost 2323
localhost [127.0.0.1] 2323 (3d-nfsd) open

localhost login: bob
Password: theBuilder
Login incorrect
localhost login: foo
Password: bar
Login incorrect
localhost login: lol
Password: rofl
Login incorrect

 --- Terminal 1 ---
Username: bob Password: theBuilder From: 172.17.0.1:54368
Username: foo Password: bar From: 172.17.0.1:54368
Username: lol Password: rofl From: 172.17.0.1:54368

I hope you have fun with it. And if you are interested to contribute to this little sensor feel free to optimize the busybox telnetd build. Currently it’s build with make defconfig which makes it a pretty big busybox binary. Generally only the telnetd applet and the required dependencies should be needed. I gave up aftert the third attempt to get the dependencies right. Getting it to build with minimal footprint is easy, but it also needs to run properly. Give it your best shot and report back :)

This is just one of dozens of sensors I’m working on for a project I call palpari. I will share more about it in the next few weeks and months.

Let me know if you have any questions, feedback or general comments in the respective twitter thread over here:
https://twitter.com/mytty_project/status/1124351935463739392