Printing: The P Module

The Printing (p) Module is responsible for printing memory and data. This module prints the disassembly, the hexdump, and the strings. This module is used extensively in both static and dynamic analysis.

This section will print the contents of registers, the stack, and key addresses throughout execution. We can control the print formatting as hex, decimal, or ASCII.

The Help Page

[0x00000000]> p? Usage: p[=68abcdDfiImrstuxz] [arg|len] [@addr] | p[b|B|xb] [len] ([S]) bindump N bits skipping S bytes | p[iI][df] [len] print N ops/bytes (f=func) (see pi? and pdi) | p[kK] [len] print key in randomart (K is for mosaic) | p-[?][jh] [mode] bar|json|histogram blocks (mode: e?search.in) | p2 [len] 8x8 2bpp-tiles | p3 [file] print 3D stereogram image of current block | p6[de] [len] base64 decode/encode | p8[?][dfjx] [len] 8bit hexpair list of bytes | p=[?][bep] [N] [L] [b] show entropy/printable chars/chars bars | pa[edD] [arg] pa:assemble pa[dD]:disasm or pae: esil from hex | pA[n_ops] show n_ops address and type | pb[?] [n] bitstream of N bits | pB[?] [n] bitstream of N bytes | pc[?][p] [len] output C (or python) format | pC[aAcdDxw] [rows] print disassembly in columns (see hex.cols and pdi) | pd[?] [sz] [a] [b] disassemble N opcodes (pd) or N bytes (pD) | pf[?][.name] [fmt] print formatted data (pf.name, pf.name $<expr>) | pF[?][apx] print asn1, pkcs7 or x509 | pg[?][x y w h] [cmd] create new visual gadget or print it (see pg? for details) | ph[?][=|hash] ([len]) calculate hash for a block | pi[?][bdefrj] [num] print instructions | pI[?][iI][df] [len] print N instructions/bytes (f=func) | pj[?] [len] print as indented JSON | pk [len] print key in randomart mosaic | pK [len] print key in randomart mosaic | pm[?] [magic] print libmagic data (see pm? and /m?) | po[?] hex print operation applied to block (see po?) | pp[?][sz] [len] print patterns, see pp? for more help | pq[?][is] [len] print QR code with the first Nbytes | pr[?][glx] [len] print N raw bytes (in lines or hexblocks, 'g'unzip) | ps[?][pwz] [len] print pascal/wide/zero-terminated strings | pt[?][dn] [len] print different timestamps | pu[w] [len] print N url encoded bytes (w=wide) | pv[?][ejh] [mode] show value of given size (1, 2, 4, 8) | pwd display current working directory | px[?][owq] [len] hexdump of N bytes (o=octal, w=32bit, q=64bit) | py([-:file]) [expr] print clipboard (yp) run python script (py:file) oneliner `py print(1)` or stdin slurp `py-` | pz[?] [len] print zoom view (see pz? for help) | pkill [process-name] kill all processes with the given name | pushd [dir] cd to dir and push current directory to stack | popd[-a][-h] pop dir off top of stack and cd to it

Printing Data

The printing module is responsible for printing data in various ways. We can print raw data (in decimal, hex, or other basic types) or disassembly.

In Von Neumann architecture (the architecture used in most modern computing), the CPU cannot distinguish between data and instructions. This means we can print any data as raw data or as an instruction.

Printing Raw Data

This section explains how to print raw data from a binary, including hexadecimal, decimal, strings, and other basic data types.

Basic Data Types

There is a long list of basic data types. You can use pf?? for the format characters and pf??? for examples of these types. The most important types available are:

Format: | c char (signed byte) | f float value (4 bytes) | F double value (8 bytes) | G long double value (16 bytes (10 with padding)) | i signed integer value (4 bytes) (see 'd' and 'x') | p pointer reference (2, 4 or 8 bytes) | s 32bit pointer to string (4 bytes) | S 64bit pointer to string (8 bytes) | T show Ten first bytes of buffer | x 0xHEX value and flag (fd @ addr) (see 'd' and 'i') | X show formatted hexpairs | ? data structure `pf ? (struct_name)example_name`

Accompanying each type is a corresponding size. You can choose the size of the output. The available sizes are:

Sizes: | b byte (unsigned) | w word (2 bytes unsigned short in hex) | d dword (4 bytes in hex) (see 'i' and 'x') | q quadword (8 bytes) | Q uint128_t (16 bytes)

Let's look at some examples of this in action. We most commonly use hexadecimal output and then the size of an address in the binary. We can also specify the number of bytes to output.

Info
The default number of bytes is rather large. It's recommended to choose an output size. The default is configured so it's easier to examine the stack.
[0x0804923c]> pxw 4 0x0804923c 0x83e58955 U... [0x0804923c]> pi 1 push ebp [0x0804923c]> pd 1 @sym.win+60 │ sym.win + 60 0x080491e2 e899feffff call sym.imp.system ; int system(const char *string)

We can use pf to print format: this prints an address based on the data type. Here are some examples.

[0x0804923c]> pf i 0x0804923c = -2082109099 [0x0804923c]> pf D push ebp [0x0804923c]> pf x 0x0804923c = 0x83e58955 [0x0804923c]> pf D @ sym.win + 60 call sym.imp.system

Telescoping Output

The pxr command is used to print telescoping values. This resolves addresses to their values and prints them as well. This is useful for resolving return pointers, strings, and other data on the stack.

[0x0804923c]> pxr 16 @ esp 0xffa633fc 0xf7c21519 .... @ esp 0xffa63400 0x00000001 .... 1 .comment 0xffa63404 0xffa634b4 .4.. [stack] esi stack R W X 'push ebx' '[stack]' 0xffa63408 0xffa634bc .4.. [stack] stack R W X 'push 0x7effa649' '[stack]'

Printing Strings

The ps submodule handles the printing of strings. There are a few formats for printing strings. The most common is to print null-terminated strings. The ps or psz command handles this:

[0x0804923c]> ps @ 0x0804a008 You lose! [0x0804923c]> psz @ 0x0804a012 cat flag.txt

High-Level Language Views

radare2 supports a variety of high-level languages. These languages are used to print data in a more human-readable format. The most common languages are:

| pc C | pcd C dwords (8 byte) | pch C half-words (2 byte) | pcw C words (4 byte) | pcj json | pcs string | pcp python

Here are some examples of this in action:

[0x0804923c]> pc 4 #define _BUFFER_SIZE 4 const uint8_t buffer[_BUFFER_SIZE] = { 0x55, 0x89, 0xe5, 0x83 }; [0x0804923c]> pcs 4 "\x55\x89\xe5\x83" [0x0804923c]> pcp 4 import struct buf = struct.pack ("4B", *[ 0x55,0x89,0xe5,0x83])

Printing Disassembly

This section involves the printing of assembly code in the binary. The pd submodule is responsible for printing disassembly.

There are two pieces to the pd submodule:

  • pd: Disassemble N instructions
  • pD: Disassemble N bytes

We will primarily use pd since we are more often interested in disassembling entire instructions rather than bytes.

Disassembling Instructions

The pd function is used to print disassembly. It takes a number as an argument: the number of instructions to disassemble.

[0x0804923d]> pd 1 │ main + 1 0x0804923d 89e5 mov ebp, esp

If you're at the start of a function, it will also print the header:

[0x0804923c]> pd 1 main + 0 ; DATA XREFS from entry0 @ 0x80490b0(r), 0x80490b6(w) 24: int main (int argc, char **argv, char **envp); │ rg: 0 (vars 0, args 0) bp: 0 (vars 0, args 0) sp: 0 (vars 0, args 0) │ main + 0 0x0804923c 55 push ebp

By default, radare2 chooses to dissect 64 instructions. This bloats all output, so it is recommended to use a more meaningful number.

To disassemble a number of bytes, use pD. By default, this will continue disassembling until an invalid instruction is hit; therefore, you should always input a number of bytes.

[0x080491f0]> pD 1 │ sym.read_in + 1 0x080491f0 89 invalid [0x080491f0]> pD 2 │ sym.read_in + 1 0x080491f0 89e5 mov ebp, esp

The first output isn't helpful to us. This is why we use pd and not pD for most disassembling purposes.

Disassembling Functions

To disassemble an entire function, use pdf. This function finds the current function and disassembles the whole function, no matter where you are inside the function.

[0x080491f0]> pdf sym.read_in + 0 ; CALL XREF from main @ 0x804924c(x) 77: sym.read_in (); │ sym.read_in + 0 ; var int32_t var_4h @ ebp-0x4 │ sym.read_in + 0 ; var int32_t var_30h @ ebp-0x30 │ sym.read_in + 0 0x080491ef 55 push ebp │ sym.read_in + 1 0x080491f0 89e5 mov ebp, esp │ sym.read_in + 3 0x080491f2 53 push ebx │ sym.read_in + 4 0x080491f3 83ec34 sub esp, 0x34 │ sym.read_in + 7 0x080491f6 e8e5feffff call sym.__x86.get_pc_thunk.bx │ sym.read_in + 12 0x080491fb 81c3052e0000 add ebx, 0x2e05 │ sym.read_in + 18 0x08049201 83ec0c sub esp, 0xc │ sym.read_in + 21 0x08049204 8d831fe0ffff lea eax, [ebx - 0x1fe1] │ sym.read_in + 27 0x0804920a 50 push eax │ sym.read_in + 28 0x0804920b e860feffff call sym.imp.puts ; int puts(const char *s) │ sym.read_in + 33 0x08049210 83c410 add esp, 0x10 │ sym.read_in + 36 0x08049213 8b83fcffffff mov eax, dword [ebx - 4] │ sym.read_in + 42 0x08049219 8b00 mov eax, dword [eax] │ sym.read_in + 44 0x0804921b 83ec0c sub esp, 0xc │ sym.read_in + 47 0x0804921e 50 push eax │ sym.read_in + 48 0x0804921f e82cfeffff call sym.imp.fflush ; int fflush(FILE *stream) │ sym.read_in + 53 0x08049224 83c410 add esp, 0x10 │ sym.read_in + 56 0x08049227 83ec0c sub esp, 0xc │ sym.read_in + 59 0x0804922a 8d45d0 lea eax, [var_30h] │ sym.read_in + 62 0x0804922d 50 push eax │ sym.read_in + 63 0x0804922e e82dfeffff call sym.imp.gets ; char *gets(char *s) │ sym.read_in + 68 0x08049233 83c410 add esp, 0x10 │ sym.read_in + 71 0x08049236 90 nop │ sym.read_in + 72 0x08049237 8b5dfc mov ebx, dword [var_4h] │ sym.read_in + 75 0x0804923a c9 leave └ sym.read_in + 76 0x0804923b c3 ret

You can use the @ operator to choose a function to disassemble.

[0x080491f0]> pdf@main main + 0 ; DATA XREFS from entry0 @ 0x80490b0(r), 0x80490b6(w) 24: int main (int argc, char **argv, char **envp); │ main + 0 0x0804923c 55 push ebp │ main + 1 0x0804923d 89e5 mov ebp, esp │ main + 3 0x0804923f 83e4f0 and esp, 0xfffffff0 │ main + 6 0x08049242 e80d000000 call sym.__x86.get_pc_thunk.ax │ main + 11 0x08049247 05b92d0000 add eax, 0x2db9 │ main + 16 0x0804924c e89effffff call sym.read_in │ main + 21 0x08049251 90 nop │ main + 22 0x08049252 c9 leave └ main + 23 0x08049253 c3 ret

Disassembly Syntax

radare2 supports a variety of assembly syntax. It defaults to intel syntax, but you can change this based on your preference. Use e asm.syntax=? to see the list of options:

[0x00000000]> e asm.syntax=? att intel masm jz regnum

To switch between AT&T and Intel syntax:

[0x00000000]> e asm.syntax = intel [0x00000000]> e asm.syntax = att

Searching Data

The search module allows us to search for data in the binary. This is useful for finding strings, functions, regex, and other patterns.

There are several configuration options for searching. These are located in the search namespace of the e module. We can view these options by using e??search.

Info
The most common search command I change is search.in. It defaults to the current debug map, which is rather inconvenient. To search all maps (hence the entire file), change the configuration to:
[0x00000000]> e search.in = dbg.maps

There are other good locations you could use for searching. For example, searching dbg.maps.rw searches writeable memory, meaning you could find a string to overwrite.

Searching for Strings

radare2 supports a variety of string types. The most common searches will be ASCII strings.

ASCII Strings

To search for an ASCII string, use the / command. It will search all regions in e search.in and return the results.

[0xf7fc88a0]> / flag.txt Searching 8 bytes in [0x8048000-0x8049000] hits: 0 Searching 8 bytes in [0x8049000-0x804a000] hits: 0 Searching 8 bytes in [0x804a000-0x804b000] hits: 1 Searching 8 bytes in [0x804b000-0x804d000] hits: 1 Searching 8 bytes in [0xf7fa6000-0xf7faa000] hits: 0 Searching 8 bytes in [0xf7faa000-0xf7fac000] hits: 0 Searching 8 bytes in [0xf7fac000-0xf7fad000] hits: 0 Searching 8 bytes in [0xf7fad000-0xf7fd2000] hits: 0 Searching 8 bytes in [0xf7fd2000-0xf7fe1000] hits: 0 Searching 8 bytes in [0xf7fe1000-0xf7fe4000] hits: 0 Searching 8 bytes in [0xffc3d000-0xffc5e000] hits: 0 0x0804a016 hit2_0 .You lose!cat flag.txtGood luck winni. 0x0804b016 hit2_1 .You lose!cat flag.txtGood luck winni.
Success
In the radare2 output, the hits are color-coded, so they're easier to find.

Use the /i command to perform a case-insensitive search. This is a very useful tool to have.

[0x0804923c]> /i YoU LoSe 0x0804a008 hit7_0 .You lose!cat flag.txtG. 0x0804b008 hit7_1 .You lose!cat flag.txtG.

Hex Strings

Hex strings are useful for searching for specific bytes. This is useful for finding instructions, raw data, or other patterns. Use /x to search for hex strings.

The below command searches for a function call to sym.read_in.

[0x0804923c]> /x e89effffff 0x0804924c hit5_0 e89effffff

You can use .. to leave out data. For example, the below command searches for a function call to sym.read_in with any offset.

[0x0804923c]> /x e8..ffff 0x080490b7 hit8_0 e884ffff 0x08049183 hit8_1 e868ffff 0x0804924c hit8_2 e89effff 0xf7fad1bc hit8_3 e8bfffff 0xf7fae31a hit8_4 e8a1ffff 0xf7fae4ae hit8_5 e8fbffff 0xf7fb1986 hit8_6 e8fdffff 0xf7fb1fb3 hit8_7 e8feffff 0xf7fb2ca4 hit8_8 e8faffff ...

Wide Strings

A wide string is any string where each character is more than one byte. This is most common in Windows binaries. We can use /w to search for wide strings. In most Linux binaries, wide-string searches won't return any results.

[0x0804923c]> /w You lose ... hits: 0

The actual string that got searched is Y\0o\0u\0 \0l\0o\0s\0e\0 assuming it was encoded in UTF-16. This is why the search didn't return any results.

Searching for Regex

The /e command is used to match regular expressions (regex). This is useful for finding patterns that are too complex for the other search commands. An important note is that in radare2, the regex is surrounded by /. The wildcard character is . instead of *.

[0x0804923c]> /e /You lose/ 0x0804b008 hit1_0 .You lose!cat flag.txtG. 0x0804a008 hit1_1 .You lose!cat flag.txtG. [0x0804923c]> /e /You.../ 0xf7fe9a94 hit3_0 .FOR-PROGRAM...]You have invoked 'ld.s. 0xf7fe9b58 hit3_1 .le is started.You may invoke the pro. 0x0804b008 hit3_2 .You lose!cat flag.txt. 0x0804a008 hit3_3 .You lose!cat flag.txt.

Searching for ROP Gadgets

The /R submodule is used for searching for ROP gadgets. There are a few available modifiers:

[0x0804923c]> /R? Usage: /R search for ROP gadgets (see "? for escaping chars in the shell) | /R [string] show gadgets | /R/ [regexp] show gadgets [regular expression] | /R/j [regexp] json output [regular expression] | /R/q [regexp] show gadgets in a quiet manner [regular expression] | /Rj [string] json output | /Rk [ropklass] query stored ROP gadgets klass | /Rq [string] show gadgets in a quiet manner

The most common use case is the first. This command searches the file for an inputted string and returns all ROP gadgets that contain that string.

[0x0804923c]> /R pop esi ... 0xf7fd119f 2c5b sub al, 0x5b 0xf7fd11a1 5e pop esi 0xf7fd11a2 5f pop edi 0xf7fd11a3 5d pop ebp 0xf7fd11a4 c3 ret 0xf7fd11a0 5b pop ebx 0xf7fd11a1 5e pop esi 0xf7fd11a2 5f pop edi 0xf7fd11a3 5d pop ebp 0xf7fd11a4 c3 ret 0xf7fe111c 5e pop esi 0xf7fe111d c3 ret

Because of the nature of the command, this method often prints a lot of garbage. /Rq prints the same information but in a more compact format.

[0x0804923c]> /Rq pop esi ... 0xf7fd11a0: pop ebx; pop esi; pop edi; pop ebp; ret; 0xf7fdc1d4: add eax, 0x2200e43; pop esi; or cl, byte [esi]; adc al, 0x41; ret; 0xf7fdc1d7: and byte [edx], al; pop esi; or cl, byte [esi]; adc al, 0x41; ret; 0xf7fe011c: pop esi; ret; 0xf7fe111c: pop esi; ret;

I find that other gadgetfinders are more useful than this one. However, it is still a useful tool.

Searching for Patterns

The /p command allows you to search for patterns. This is useful for finding ROP gadgets, instructions, or other patterns. The /p command takes a number as an argument, which is the pattern size to search for.

[0x0804923c]> /p 50
Warning
This prints a ton of output. It is recommended to pipe the output to a file or use the grep module to filter it.

The Help Page

[0x00000000]> /? Usage: /[!bf] [arg] Search stuff (see 'e??search' for options) Use io.va for searching in non virtual addressing spaces | / foo\x00 search for string 'foo\0' | /j foo\x00 search for string 'foo\0' (json output) | /! ff search for first occurrence not matching, command modifier | /!x 00 inverse hexa search (find first byte != 0x00) | /+ /bin/sh construct the string with chunks | // repeat last search | /a[?][1aoditfmsltf] jmp eax find instructions by text or bytes (asm/disasm) | /b[?][p] search backwards, command modifier, followed by other command | /c[?][adr] search for crypto materials | /d 101112 search for a deltified sequence of bytes | /e /E.F/i match regular expression | /E esil-expr offset matching given esil expressions $$ = here | /f search forwards, (command modifier) | /F file [off] [sz] search contents of file with offset and size | /g[g] [from] find all graph paths A to B (/gg follow jumps, see search.count and anal.depth) | /h[t] [hash] [len] find block matching this hash. See ph | /i foo search for string 'foo' ignoring case | /k foo search for string 'foo' using Rabin Karp alg | /m[?][ebm] magicfile search for magic, filesystems or binary headers | /o [n] show offset of n instructions backward | /O [n] same as /o, but with a different fallback if anal cannot be used | /p[?][p] patternsize search for pattern of given size | /P patternsize search similar blocks | /s[*] [threshold] find sections by grouping blocks with similar entropy | /r[erwx][?] sym.printf analyze opcode reference an offset (/re for esil) | /R[?] [grepopcode] search for matching ROP gadgets, semicolon-separated | /v[1248] value look for an `cfg.bigendian` 32bit value | /V[1248] min max look for an `cfg.bigendian` 32bit value in range | /w foo search for wide string 'f\0o\0o\0' | /wi foo search for wide string ignoring case 'f\0o\0o\0' | /x ff..33 search for hex string ignoring some nibbles | /x ff0033 search for hex string | /x ff43:ffd0 search for hexpair with mask | /z min max search for strings of given size | /* [comment string] add multiline comment, end it with '*/'