Extract and Verify Text Hidden in Linux Binaries with strings
You will finish with a small, repeatable workflow for finding readable text in an executable, shared library, object file or other binary. You will also know when an apparent absence of text is just a filtering choice, and how to record offsets so another tool can inspect the same bytes.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes. You need a shell and GNU strings from the binutils package. The commands below are read-only against the file being examined and do not require elevated privileges. Use a copy of an untrusted sample if you are not comfortable exposing its contents to your terminal or log collector.
1. Confirm the installed command
There are several names for the same utility on this machine. The unqualified command is the normal choice; the architecture-prefixed names are useful when a toolchain script calls them explicitly. Check the package and version before relying on an option:
$ command -v /usr/bin/strings
/usr/bin/strings
$ /usr/bin/strings --version
GNU strings (GNU Binutils for Ubuntu) 2.42
$ dpkg-query -W -f='${Package} ${Version}\n' binutils-common:amd64
binutils-common 2.42-4ubuntu2.10
The installed strings manpages for strings, aarch64-linux-gnu-strings and x86_64-linux-gnu-strings are identical here. The examples therefore apply to all three command names. If your PATH selects another implementation, run command -v and check that implementation's help first.
Checkpoint
You have confirmed GNU binutils 2.42 and know which executable your shell will run.
2. Scan a binary with the default filter
Start with an explicit file path. This example uses the installed command itself, which is a binary and is safe to read:
$ /usr/bin/strings /usr/bin/strings | head -n 12
/lib64/ld-linux-x86-64.so.2
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
__libc_start_main
__cxa_finalize
setlocale
bindtextdomain
xmalloc_set_program_name
bfd_set_error_program_name
stat
fopen
By default, strings prints runs of at least four displayable characters, ending at an unprintable character. Newline and carriage return end a run, while spaces and tabs can remain inside it. The exact output depends on the file and the build configuration.
Do not treat every line as a fact about the program. Binaries contain error messages, library names, debug data, unused constants and sometimes secrets left by mistake. The command extracts byte patterns; it does not decide whether a string is live code, configuration or evidence of a feature.
3. Make short strings visible
The default minimum length of four avoids a great deal of noise. Lower it only when you have a reason, such as looking for short format markers or two-character protocol values:
$ /usr/bin/strings -n 6 /usr/bin/strings | head -n 8
/lib64/ld-linux-x86-64.so.2
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
__libc_start_main
__cxa_finalize
setlocale
bindtextdomain
-n 6 means six or more characters, not exactly six. The long equivalent is --bytes=6. A compact spelling such as -6 is also documented, but -n is easier to recognise in a script and less likely to be confused with another option.
When the output is large, narrow it after extraction with a separate tool:
$ strings -n 8 /path/to/sample.bin | grep -Ei 'https?://|token|version' | head -n 20
This changes only what you display. It does not prove that a matching string is used, and it can miss encoded, split or compressed data.
4. Record offsets for follow-up inspection
Use -t when a line needs to be located in the file. Select octal, decimal or hexadecimal with o, d or x:
$ /usr/bin/strings -t x /usr/bin/strings | head -n 5
318 /lib64/ld-linux-x86-64.so.2
979 _ITM_deregisterTMCloneTable
995 __gmon_start__
9a4 _ITM_registerTMCloneTable
9be __libc_start_main
The offsets are byte positions in hexadecimal for this command. They are useful with tools such as od, xxd or a hex editor. Keep the radix in your notes: an offset printed by -t d is not the same text as one printed by -t x. The alias -o means octal in GNU strings, even though some other strings implementations use that option differently.
5. Search for wide-character text
Ordinary strings looks for single 7-bit bytes. Text stored as UTF-16 or another wide format may appear as separated letters or not appear at all. Choose the encoding with -e:
$ strings -e l -n 8 /path/to/sample.bin
$ strings -e b -n 8 /path/to/sample.bin
l scans 16-bit little-endian values and b scans 16-bit big-endian values. GNU strings also documents B and L for 32-bit big-endian and little-endian values, plus S for single 8-bit bytes. Select the form that matches the file's representation; trying all forms can produce plausible-looking noise.
UTF-8 handling is separate. The --unicode=hex, --unicode=escape and related modes control how UTF-8 multibyte characters are treated and automatically select 8-bit scanning. If the output contains unexpected non-ASCII text, record the mode used before comparing results from another machine.
6. Decide whether to scan the whole file
GNU strings can scan every byte with -a or --all, or restrict output to initialised, loaded data sections with -d or --data. The compiled default can vary, so do not assume that a normal scan covers the whole file:
$ /usr/bin/strings -a /path/to/sample.bin > /tmp/sample.strings
$ wc -l /tmp/sample.strings
<number of extracted lines> /tmp/sample.strings
$ /usr/bin/strings -d /path/to/sample.bin | head -n 10
Compare the two line counts when absence matters. The manpage warns that -d uses the BFD library and can expose strings to vulnerabilities in that library, while -a avoids BFD and performs a raw full-file scan. For an unknown or hostile file, prefer -a first and keep the output local.
Safety boundary
Extracting strings does not execute the input, but opening an untrusted object through format-aware tooling can still expand attack surface. Do not run the result as a command, source it as shell code or feed it into a privileged workflow without review.
7. Handle multiple files and stdin
Pass more than one file when you need a quick comparison. -f prefixes each result with its file name, which prevents lines from being mixed together:
$ /usr/bin/strings -f /usr/bin/strings /usr/bin/true | head -n 6
/usr/bin/strings: /lib64/ld-linux-x86-64.so.2
/usr/bin/strings: _ITM_deregisterTMCloneTable
/usr/bin/strings: __gmon_start__
/usr/bin/strings: _ITM_registerTMCloneTable
/usr/bin/strings: __libc_start_main
/usr/bin/strings: __cxa_finalize
Output wording varies by build, so use the filename prefix rather than matching the sample lines. To scan standard input, omit the file argument:
$ printf 'binary-noise\000visible-marker\000' | /usr/bin/strings -n 6
binary-noise
visible-marker
Standard input is always scanned in full. Be careful with pipelines: a producer can block or fail, and a command receiving sensitive input may leave it in shell history, terminal scrollback or a temporary output file.
Done means
- You checked the installed GNU strings version and command path.
- You know the default minimum length is four and can raise it with
-n. - You can record offsets with
-t xand keep the radix with the result. - You can select little-endian or big-endian wide-character encodings deliberately.
- You understand the difference between a raw full scan with
-aand a section-aware scan with-d. - You treated extracted text as untrusted evidence, not executable instructions.