Customizing your Cowrie honeypot

7 min readNov 3, 2020
Future logo for Cowrie? :)

Cowrie is an open source SSH/Telnet honeypot. It is particularly suited for IoT (possibility to mimic various architectures + Mirai-like malware target SSH/Telnet). The project is documented here and you can find lots of articles on Internet which explain how to install it and quickly set it up (e.g here). But if you are after information to help you customize Cowrie to your needs, apart from the project’s documentation and GitHub issues/history, you are (mostly) going to be on your own. Let’s try and remedy — at least a bit — the situation 😉.

Update Nov 5, 2020 from feedback from Cowrie Slack community: how to customize uname + /root + adding image of a cowrie :)

Overall, there are 4 places for configuration:

  1. The Cowrie config file ./etc/cowrie.cfg. This is the main config file of the honeypot. You can configure path names (and hence modify names below), login options, prompts, architecture etc.
  2. Fake commands in ./share/cowrie/txtcmds. It would take too much time to emulate all commands. Some of them can be very basically emulated by always returning the same text, such as command usage or error. This is the directory for such commands.
  3. The honeypot filesystem ./honeyfs. This is where you place a tree of files you want the attacker to see.
  4. The pickle filesystem ./share/cowrie/fs.pickle. This is a virtual filesystem: files in there won’t really exist (you can’t cat them for example) but they get listed in the tree of files. This helps make it look real, while not copying each file (would take space).

Creating a pickle filesystem

Cowrie uses a packed data structure to simulate a filesystem on the honeypot: ./share/cowrie/fs.pickle. Files present in the pickle file will be displayed when listing directories, but they don’t really exist: e.g you cannot read or execute them (if you want to do that, the files must be present in the honeyfs, or their output simulated in ./share/cowrie/txtcmds).

First, we need to create a tree of files which match the system we want to build a honeypot for. If you don’t have an example at hand for that system, I suggest using docker containers, e.g docker run — rm -it openwrtorg/rootfs:x86–64. Then, copy the directories you want to map:

for i in proc usr sbin sys lib etc bin
docker cp DOCKER_ID:$i /tmp/picklefs

Then, create the Pickle file: ./bin/createfs -l /tmp/picklefs -d 6 -o ./honeypot.pickle.

Note that createfs is provided by the cowrie project in the bin directory. -dis the depth of the filesystem. Specify the output file with -o (the default name for the Pickle filesystem in Cowrie is fs.pickle, but this can be customized).

An excerpt of the filesystem I am mimicking on my honeypot
Pickle filesystem viewed by the attacker. See how s/he can go in the libubox directory and list files, and has a valid size, but it’s all fake and in reality you cannot read the file. If you want the file to readable, you need to use Cowrie’s honeyfs feature.

Once we have created a pickle filesystem, we may want to fix ownership and permissions. This can be done interactively with using fsctl tool (in Cowrie’s bin directory).

./fsctl ./fs.pickle
fs.pickle:/$ cd ffs
fs.pickle:/ffs$ ls -l
-rw-rw-r — 1 1 1 0 2020–03–24 15:28 CONFIG.XML
fs.pickle:/ffs$ chown 0 CONFIG.XML
former UID: 1. New UID: 0
.pickle:/ffs$ chgrp 0 CONFIG.XML
former GID: 1. New GID: 0

Adding custom files attackers can access

While it’s probably okay not to have all files, we do want some files to be accessible to attackers. For example,/etc/passwd. It’s simple to do that: put the file in the pickle filesystem (e.g /tmp/picklefs/etc/passwd) AND in the honeyfs dir (e.g ./honeyfs/etc/passwd). Yes, you must put it in both places.

The attacker can list /etc and read /etc/passwd

Note that Cowrie’s default passwd file has a user named richard or now phil. You might want to customize this in honeyfs/etc/passwd. Same, depending on your system, you might want to remove some accounts or groups like games or irc.

Typical files you might want to customize for your honeypot:

  • honeyfs/etc/hostname
  • honeyfs/proc/cpuinfo: customize this for your system’s architecture (e.g. ARM)
  • honeyfs/proc/mounts: typically customize this if you are imitating an embedded device: they frequently use JFFS2, tmpfs etc.
  • honeyfs/proc/meminfo: customize with a plausible amount of memory for your system.
  • honeyfs/root directory.

Prompts and banners

To be more realistic, we absolutely need to customize the honeypot’s banners. Most of this can be done from Cowrie’s config file /etc/cowrie.cfg. For example:

# telnet prompt
telnet_username_prompt_regex = (\n|^) login: .*
# SSH banner
ssh_version = OpenSSH_7.9p1, OpenSSL 1.1.1a 20 Nov 2018
version = SSH-2.0-OpenSSH_6.0p1 Debian-4+deb7u2
# Modify the response of '/bin/uname'
# Default (uname -a): Linux <hostname> <kernel_version>
# <kernel_build_string> <hardware_platform> <operating system>
kernel_version = 3.10.27
kernel_build_string = #781 SMP
hardware_platform = armv7l
operating_system = GNU/Linux

A shell prompt containing the current directory is implemented by Cowrie. As this didn’t match the prompt for the system I wanted to mimic, I added a new setting to cowrie.cfg: a static prompt entry. This has now been integrated to Cowrie, and is available by default.


The feature was easy to code (Cowrie’s source code is quite easy to understand). It consisted in reading the prompt for the config file and using that prompt when it exists (./src/cowrie/shell/

Adding the prompt feature to Cowrie — now integrated to Cowrie’s repository

Some other banners are configured from files:

  • honeyfs/etc/issue
  • honeyfs/etc/ customize your Telnet banner in here
  • honeyfs/etc/motd: customize your login banner here
  • honeyfs/proc/version: Unix version

Creating a [fake] command

Let’s imagine we want to fake iwconfig. Our fake command will be very limited and won’t do anything, apart display something plausible for an attacker. For example, something like this:

Interface: wl0
WEP Hex KEY / WPA-PSK Passphrase:
Encryption: none
Authentication: open
EAP: none
Association Status:UNASSOCIATED
Access Point MAC Address:00:00:00:00:00:00
Signal Strength: 0%
Signal Strength: 0dBm (-100dBm Min, -40dBm Max)
Pump Channel: 0
Access Point Channel: 0

Yes, it is true that an attentive attacker will notice something is wrong (the command will always return the same text!), but it is difficult to notice everything, and sometimes attackers need to hurry 😉.

iwconfig is normally found in /sbin. So, in Cowrie’s ./share/cowrie/txtcmds, create a sbin subdirectory, and add a text file iwconfig with the corresponding content. Cowrie does the rest 😃.

Cowrie comes with several default fake commands such as df, dmesg, mount.

Creating simple operational commands

It’s unlikely an attacker will want to use dmesg with specific options, and therefore appropriate as a “text command” (txtcmds), but what about cp, rm, mv? We probably want those to work for real on the honeypot.

The solution is to add those Unix commands to ./bin directory of the Pickle filesystem, and to use busybox (indeed, on embedded systems, cp, rm etc are often a redirection to busybox).

Unfortunately, createfs does not follow links, so we must duplicate busybox over each command: see below how cat, chgrp, chmod, chown, cp (etc) are actually copies of busybox.

$ ls -al 
drwxr-xr-x 2 axelle axelle 4096 Apr 2 2020 .
drwxrwxr-x 14 axelle axelle 4096 Apr 27 2020 ..
-rwxr-xr-x 1 axelle axelle 372766 Apr 2 2020 ash
-rwxr-xr-x 1 axelle axelle 204 Mar 21 2020 board_detect
-rwxr-xr-x 1 axelle axelle 372766 Mar 21 2020 busybox
-rwxr-xr-x 1 axelle axelle 372766 Apr 2 2020 cat
-rwxr-xr-x 1 axelle axelle 372766 Apr 2 2020 chgrp
-rwxr-xr-x 1 axelle axelle 372766 Apr 2 2020 chmod
-rwxr-xr-x 1 axelle axelle 372766 Apr 2 2020 chown
-rwxr-xr-x 1 axelle axelle 372766 Apr 2 2020 cp

That’s it! Oh? But how does it work? We’ve just mentioned busybox in the pickle filesystem, but it’s not present in the honeyfs or elsewhere! That’s because busybox is part of Cowrie’s implementation: see ./src/cowrie/commands/ A few commands are implemented by Cowrie, and make attacker’s experience more realistic: cat, ls, ping, dd etc.

Listing process

Cowrie also implements a feature to list (fake) process on the honeypot.

4000> ps
7807 pts/0 0:00 -bash
7809 pts/0 0:00 ps
4000> ps aex
1 ? Ss 0.48 /lib/systemd/systemd --system --deserialize 20
2 ? S< 0.0 [kthreadd]

The list of process is detailed in a JSON file ./share/cowrie/cmdoutput.json. You can remove process which are unlikely to appear on your honeypot. Reciprocally, I personally wanted to add a mds process, where mds is a specific executable for “Medical Device Server” on my honeypot.

It’s very simple to add to the JSON file:

"COMMAND": "/usr/bin/mds --daemon",
"CPU": 0.0,
"MEM": 0.0,
"PID": 28002,
"RSS": 0,
"START": "Apr6",
"STAT": "Ss",
"TIME": 0.0,
"TTY": "?",
"USER": "mds",
"VSZ": 0

And my honeypot displays it:

4000> ps aex
1 ? Ss 0.48 /lib/systemd/systemd --system --deserialize 20
28002 ? Ss 0.0 /usr/bin/mds --daemon


The credentials an attacker can use to log on the honeypot is something else we must configure. Cowrie currently supports 2 different modes:

  1. UserDB. The honeypot has a list of valid credentials (./etc/userdb.txt). The attacker is granted access if s/he uses one of those. The list is little more complicated than pure id/pwd couples: you can specify wildcards for example, or deny particular words.
  2. AuthRandom. Attackers are granted access after a random number of logins (see ./src/cowrie/core/

In my case, I wanted to create a medical device honeypot, so using UserDB with a list of credentials typically found on medical devices helped me (1) behave like a real medical device, and (2) rule out basic attacks which weren’t targeting the medical sector.

— Cryptax




Mobile and IoT malware researcher. The postings on this account are solely my own opinion and do not represent my employer.