Signatures

Radare2 has its own format of the signatures, allowing to both load/apply and create them on the fly. They are available under the z command namespace:

  1. [0x00000000]> z?
  2. Usage: z[*j-aof/cs] [args] # Manage zignatures
  3. | z show zignatures
  4. | z. find matching zignatures in current offset
  5. | zb[?][n=5] search for best match
  6. | z* show zignatures in radare format
  7. | zq show zignatures in quiet mode
  8. | zj show zignatures in json format
  9. | zk show zignatures in sdb format
  10. | z-zignature delete zignature
  11. | z-* delete all zignatures
  12. | za[?] add zignature
  13. | zg generate zignatures (alias for zaF)
  14. | zo[?] manage zignature files
  15. | zf[?] manage FLIRT signatures
  16. | z/[?] search zignatures
  17. | zc[?] compare current zignspace zignatures with another one
  18. | zs[?] manage zignspaces
  19. | zi show zignatures matching information

To load the created signature file you need to load it from SDB file using zo command or from the compressed SDB file using zoz command.

To create signature you need to make function first, then you can create it from the function:

  1. r2 /bin/ls
  2. [0x000051c0]> aaa # this creates functions, including 'entry0'
  3. [0x000051c0]> zaf entry0 entry
  4. [0x000051c0]> z
  5. entry:
  6. bytes: 31ed4989d15e4889e24883e4f050544c............48............48............ff..........f4
  7. graph: cc=1 nbbs=1 edges=0 ebbs=1
  8. offset: 0x000051c0
  9. [0x000051c0]>

As you can see it made a new signature with a name entry from a function entry0. You can show it in JSON format too, which can be useful for scripting:

  1. [0x000051c0]> zj~{}
  2. [
  3. {
  4. "name": "entry",
  5. "bytes": "31ed4989d15e4889e24883e4f050544c............48............48............ff..........f4",
  6. "graph": {
  7. "cc": "1",
  8. "nbbs": "1",
  9. "edges": "0",
  10. "ebbs": "1"
  11. },
  12. "offset": 20928,
  13. "refs": [
  14. ]
  15. }
  16. ]
  17. [0x000051c0]>

To remove it just run z-entry.

If you want, instead, to save all created signatures, you need to save it into the SDB file using command zos myentry.

Then we can apply them. Lets open a file again:

  1. r2 /bin/ls
  2. -- Log On. Hack In. Go Anywhere. Get Everything.
  3. [0x000051c0]> zo myentry
  4. [0x000051c0]> z
  5. entry:
  6. bytes: 31ed4989d15e4889e24883e4f050544c............48............48............ff..........f4
  7. graph: cc=1 nbbs=1 edges=0 ebbs=1
  8. offset: 0x000051c0
  9. [0x000051c0]>

This means that the signatures were successfully loaded from the file myentry and now we can search matching functions:

  1. [0x000051c0]> z.
  2. [+] searching 0x000051c0 - 0x000052c0
  3. [+] searching function metrics
  4. hits: 1
  5. [0x000051c0]>

Note that z. command just checks the signatures against the current address. To search signatures across the all file we need to do a bit different thing. There is an important moment though, if we just run it “as is” - it wont find anything:

  1. [0x000051c0]> z/
  2. [+] searching 0x0021dfd0 - 0x002203e8
  3. [+] searching function metrics
  4. hits: 0
  5. [0x000051c0]>

Note the searching address - this is because we need to adjust the searching range first:

  1. [0x000051c0]> e search.in=io.section
  2. [0x000051c0]> z/
  3. [+] searching 0x000038b0 - 0x00015898
  4. [+] searching function metrics
  5. hits: 1
  6. [0x000051c0]>

We are setting the search mode to io.section (it was file by default) to search in the current section (assuming we are currently in the .text section of course). Now we can check, what radare2 found for us:

  1. [0x000051c0]> pd 5
  2. ;-- entry0:
  3. ;-- sign.bytes.entry_0:
  4. 0x000051c0 31ed xor ebp, ebp
  5. 0x000051c2 4989d1 mov r9, rdx
  6. 0x000051c5 5e pop rsi
  7. 0x000051c6 4889e2 mov rdx, rsp
  8. 0x000051c9 4883e4f0 and rsp, 0xfffffffffffffff0
  9. [0x000051c0]>

Here we can see the comment of entry0, which is taken from the ELF parsing, but also the sign.bytes.entry_0, which is exactly the result of matching signature.

Signatures configuration stored in the zign. config vars’ namespace:

  1. [0x000051c0]> e? zign.
  2. zign.autoload: Autoload all zignatures located in ~/.local/share/radare2/zigns
  3. zign.bytes: Use bytes patterns for matching
  4. zign.diff.bthresh: Threshold for diffing zign bytes [0, 1] (see zc?)
  5. zign.diff.gthresh: Threshold for diffing zign graphs [0, 1] (see zc?)
  6. zign.graph: Use graph metrics for matching
  7. zign.hash: Use Hash for matching
  8. zign.maxsz: Maximum zignature length
  9. zign.mincc: Minimum cyclomatic complexity for matching
  10. zign.minsz: Minimum zignature length for matching
  11. zign.offset: Use original offset for matching
  12. zign.prefix: Default prefix for zignatures matches
  13. zign.refs: Use references for matching
  14. zign.threshold: Minimum similarity required for inclusion in zb output
  15. zign.types: Use types for matching
  16. [0x000051c0]>

Finding Best Matches zb

Often you know the signature should exist somewhere in a binary but z/ and z. still fail. This is often due to very minor differences between the signature and the function. Maybe the compiler switched two instructions, or your signature is not for the correct function version. In these situations the zb commands can still help point you in the right direction by listing near matches.

  1. [0x000040a0]> zb?
  2. Usage: zb[r?] [args] # search for closest matching signatures
  3. | zb [n] find n closest matching zignatures to function at current offset
  4. | zbr zigname [n] search for n most similar functions to zigname

The zb (zign best) command will show the top 5 closest signatures to a function. Each will contain a score between 1.0 and 0.0.

  1. [0x0041e390]> s sym.fclose
  2. [0x0040fc10]> zb
  3. 0.96032 0.92400 B 0.99664 G sym.fclose
  4. 0.65971 0.35600 B 0.96342 G sym._nl_expand_alias
  5. 0.65770 0.37800 B 0.93740 G sym.fdopen
  6. 0.65112 0.35000 B 0.95225 G sym.__run_exit_handlers
  7. 0.62532 0.34800 B 0.90264 G sym.__cxa_finalize

In the above example, zb correctly associated the sym.fclose signature to the current function. The z/ and z. command would have failed to match here since both the Byte and Graph scores are less then 1.0. A 30% separation between the first and second place results is also a good indication of a correct match.

The zbr (zign best reverse) accepts a zignature name and attempts to find the closet matching functions. Use an analysis command, like aa to find functions first.

  1. [0x00401b20]> aa
  2. [x] Analyze all flags starting with sym. and entry0 (aa)
  3. [0x00401b20]> zo ./libc.sdb
  4. [0x00401b20]> zbr sym.__libc_malloc 10
  5. 0.94873 0.89800 B 0.99946 G sym.malloc
  6. 0.65245 0.40600 B 0.89891 G sym._mid_memalign
  7. 0.59470 0.38600 B 0.80341 G sym._IO_flush_all_lockp
  8. 0.59200 0.28200 B 0.90201 G sym._IO_file_underflow
  9. 0.57802 0.30400 B 0.85204 G sym.__libc_realloc
  10. 0.57094 0.35200 B 0.78988 G sym.__calloc
  11. 0.56785 0.34000 B 0.79570 G sym._IO_un_link.part.0
  12. 0.56358 0.36200 B 0.76516 G sym._IO_cleanup
  13. 0.56064 0.26000 B 0.86127 G sym.intel_check_word.constprop.0
  14. 0.55726 0.28400 B 0.83051 G sym.linear_search_fdes