Find Hidden Listening Ports with unhide-tcp
A rootkit that opens a backdoor port rarely bothers hiding it from itself, only from ss and netstat, and that is the gap unhide-tcp is built to catch. It brute-forces the whole TCP and UDP port range and compares what it finds against the normal listing, then hands you a plain exit status to act on.
The route
Jump straight to the step you need, or tick off Done means at the end.
- Time: 10 to 20 minutes for a first check.
- You need: the
unhidepackage, a root shell, and a maintenance window if the host has strict network monitoring. - Versions used here: package
20220611-1ubuntu1, reporting itself asUnhide-tcp 20211016. Other releases may alter wording or implementation details.
Safety boundary
This is a forensic port check, not a firewall or service-management command. It does not close a port or stop a daemon. Its probing can still create network and audit events, so do not run it blindly on a production host or across a network you do not administer.
1. Confirm the installed tool
Check the package and binary before you interpret any result. These are ordinary, read-only commands and do not need elevated privileges:
$ dpkg-query -W -f='${Package} ${Version}\n' unhide
unhide 20220611-1ubuntu1
$ command -v unhide-tcp
/usr/sbin/unhide-tcp
$ unhide-tcp --version
Unhide-tcp 20211016
Copyright © 2013-2021 Yago Jesus & Patrick Gouin
Your version line may differ. If the package query says unhide is not installed, stop here and use your normal package-management process. Do not substitute a similarly named port-scanning tool: this command is specifically comparing hidden-port detection paths.
2. Take a normal, quiet baseline
Run the default check with root privileges. Root is required because the command reads and probes low-level network state:
$ sudo unhide-tcp --brief
$ status=$?
$ printf 'unhide-tcp exit status: %s\n' "$status"
unhide-tcp exit status: 0
The default mode uses /sbin/ss as its comparison view and prints no warning messages. A status of 0 means this run found no hidden port.
Checkpoint
Capture $? immediately, as above. Running another command first replaces the status you need.
3. Read the exit status by protocol
Do not treat every non-zero result as the same problem. The documented statuses are:
4: one or more hidden TCP ports were found.8: one or more hidden UDP ports were found.12: hidden TCP and UDP ports were found.
Use a small case statement when another script or alert will consume the result:
sudo unhide-tcp --brief
status=$?
case "$status" in
0) printf '%s\n' 'No hidden TCP or UDP port found' ;;
4) printf '%s\n' 'Hidden TCP port found' >&2 ;;
8) printf '%s\n' 'Hidden UDP port found' >&2 ;;
12) printf '%s\n' 'Hidden TCP and UDP ports found' >&2 ;;
*) printf 'unhide-tcp failed with unexpected status %s\n' "$status" >&2
exit 1 ;;
esac
exit "$status"
A finding is evidence for investigation, not proof of a rootkit. Record the host, time, package version and command line, then compare the result with expected services, container networking and local security telemetry. Do not restart services just to make a finding disappear.
4. Add process evidence to a finding
Repeat the check with --fuser or --lsof when those utilities are installed. These options ask unhide-tcp to display process information for a hidden port:
$ sudo unhide-tcp --brief --fuser
$ printf 'exit status: %s\n' "$?"
exit status: 4
The port list and process output are host-specific, so do not bake a fixed expected listing into an automated test. Try --lsof separately if you need lsof's view:
$ sudo unhide-tcp --brief --lsof
$ printf 'exit status: %s\n' "$?"
exit status: 0
Those example statuses illustrate the documented meanings, not a claim about your host. If a helper is unavailable, the main check and its status still matter. Check availability first with command -v fuser lsof.
5. Choose the scan strategy deliberately
Use --server when you want the quick strategy and accept that you are changing how the check runs. The manual says it is much faster on systems with many open ports:
$ sudo unhide-tcp --brief --server
$ printf 'server-strategy exit status: %s\n' "$?"
server-strategy exit status: 0
Keep the default strategy for a first forensic baseline unless elapsed time is the real constraint. Faster does not mean more authoritative: compare a surprising result with a second method and with host telemetry.
If iproute2, and therefore /sbin/ss, is not available, use --netstat instead:
$ command -v ss netstat
$ sudo unhide-tcp --brief --netstat
$ printf 'netstat-strategy exit status: %s\n' "$?"
netstat-strategy exit status: 0
The netstat method can be dramatically slower on a host with many open ports. Add --verbose when diagnosing an unexpected result, which prints warnings the brief mode suppresses:
$ sudo unhide-tcp --verbose
$ printf 'verbose exit status: %s\n' "$?"
verbose exit status: 0
6. Save a log without overwriting useful evidence
The --log option writes a dated unhide-tcp log in the current directory. Choose a fresh evidence directory first and check it is writable:
$ evidence_dir="/var/tmp/unhide-tcp-$(date +%Y%m%d-%H%M%S)"
$ mkdir "$evidence_dir"
$ cd "$evidence_dir"
$ sudo unhide-tcp --brief --log
$ status=$?
$ printf 'log directory: %s\nexit status: %s\n' "$PWD" "$status"
log directory: /var/tmp/unhide-tcp-20260927-120000
exit status: 0
$ find . -maxdepth 1 -type f -name 'unhide-tcp-*.log' -print
The timestamp above is a placeholder for the current time. Inspect the generated file before sharing it, because it may contain host-specific port and process details. Keep it alongside the command, version and exit status that produced it.
Warning
Do not point --log at a directory containing irreplaceable evidence without checking the existing filename first. If the command creates an unwanted new log, remove only that known file after recording its path; do not use a broad wildcard cleanup.
7. Recover from common mistakes
If the program says you must be root, rerun the same command with sudo. Root is a prerequisite, not a diagnosis of hidden ports. If sudo is not available, use a controlled root shell under your normal access policy:
$ su -
# unhide-tcp --brief
# printf 'exit status: %s\n' "$?"
# exit
If the default run is unexpectedly slow, check command -v ss, then consider --server or, where necessary, --netstat. Do not add both strategy options to the same command. If the result changes between strategies, preserve both logs and investigate rather than picking the more convenient answer.
If you ran --log from the wrong directory, find the exact newly created file by its timestamp and move it to your evidence directory with an explicit path. There is no service rollback needed: unhide-tcp does not change service configuration or firewall state.
Done means
- Confirmed the tool. You recorded the installed package and reported program version.
- Ran it as root. You captured the exit status immediately after the check.
- Read the status correctly. You distinguish 0, 4, 8 and 12 instead of collapsing them into pass or fail.
- Picked a strategy for a reason. You chose
--server,--netstat,--fuseror--lsofdeliberately. - Logged with care. Any log is stored in a deliberate directory with its version and command recorded.
- Left the system alone. No service, firewall rule or persistent setting was changed.