Managing variables

Radare2 allows managing local variables, no matter their location, stack or registers. The variables’ auto analysis is enabled by default but can be disabled with anal.vars configuration option.

The main variables commands are located in afv namespace:

  1. Usage: afv [rbs]
  2. | afv* output r2 command to add args/locals to flagspace
  3. | afv-([name]) remove all or given var
  4. | afv= list function variables and arguments with disasm refs
  5. | afva analyze function arguments/locals
  6. | afvb[?] manipulate bp based arguments/locals
  7. | afvd name output r2 command for displaying the value of args/locals in the debugger
  8. | afvf show BP relative stackframe variables
  9. | afvn [new_name] ([old_name]) rename argument/local
  10. | afvr[?] manipulate register based arguments
  11. | afvR [varname] list addresses where vars are accessed (READ)
  12. | afvs[?] manipulate sp based arguments/locals
  13. | afvt [name] [new_type] change type for given argument/local
  14. | afvW [varname] list addresses where vars are accessed (WRITE)
  15. | afvx show function variable xrefs (same as afvR+afvW)

afvr, afvb and afvs commands are uniform but allow manipulation of register-based arguments and variables, BP/FP-based arguments and variables, and SP-based arguments and variables respectively. If we check the help for afvr we will get the way two others commands works too:

  1. |Usage: afvr [reg] [type] [name]
  2. | afvr list register based arguments
  3. | afvr* same as afvr but in r2 commands
  4. | afvr [reg] [name] ([type]) define register arguments
  5. | afvrj return list of register arguments in JSON format
  6. | afvr- [name] delete register arguments at the given index
  7. | afvrg [reg] [addr] define argument get reference
  8. | afvrs [reg] [addr] define argument set reference

Like many other things variables detection is performed by radare2 automatically, but results can be changed with those arguments/variables control commands. This kind of analysis relies heavily on preloaded function prototypes and the calling-convention, thus loading symbols can improve it. Moreover, after changing something we can rerun variables analysis with afva command. Quite often variables analysis is accompanied with types analysis, see afta command.

The most important aspect of reverse engineering - naming things. Of course, you can rename variable too, affecting all places it was referenced. This can be achieved with afvn for any type of argument or variable. Or you can simply remove the variable or argument with afv- command.

As mentioned before the analysis loop relies heavily on types information while performing variables analysis stages. Thus comes next very important command - afvt, which allows you to change the type of variable:

  1. [0x00003b92]> afvs
  2. var int local_8h @ rsp+0x8
  3. var int local_10h @ rsp+0x10
  4. var int local_28h @ rsp+0x28
  5. var int local_30h @ rsp+0x30
  6. var int local_32h @ rsp+0x32
  7. var int local_38h @ rsp+0x38
  8. var int local_45h @ rsp+0x45
  9. var int local_46h @ rsp+0x46
  10. var int local_47h @ rsp+0x47
  11. var int local_48h @ rsp+0x48
  12. [0x00003b92]> afvt local_10h char*
  13. [0x00003b92]> afvs
  14. var int local_8h @ rsp+0x8
  15. var char* local_10h @ rsp+0x10
  16. var int local_28h @ rsp+0x28
  17. var int local_30h @ rsp+0x30
  18. var int local_32h @ rsp+0x32
  19. var int local_38h @ rsp+0x38
  20. var int local_45h @ rsp+0x45
  21. var int local_46h @ rsp+0x46
  22. var int local_47h @ rsp+0x47
  23. var int local_48h @ rsp+0x48

Less commonly used feature, which is still under heavy development - distinction between variables being read and written. You can list those being read with afvR command and those being written with afvW command. Both commands provide a list of the places those operations are performed:

  1. [0x00003b92]> afvR
  2. local_48h 0x48ee
  3. local_30h 0x3c93,0x520b,0x52ea,0x532c,0x5400,0x3cfb
  4. local_10h 0x4b53,0x5225,0x53bd,0x50cc
  5. local_8h 0x4d40,0x4d99,0x5221,0x53b9,0x50c8,0x4620
  6. local_28h 0x503a,0x51d8,0x51fa,0x52d3,0x531b
  7. local_38h
  8. local_45h 0x50a1
  9. local_47h
  10. local_46h
  11. local_32h 0x3cb1
  12. [0x00003b92]> afvW
  13. local_48h 0x3adf
  14. local_30h 0x3d3e,0x4868,0x5030
  15. local_10h 0x3d0e,0x5035
  16. local_8h 0x3d13,0x4d39,0x5025
  17. local_28h 0x4d00,0x52dc,0x53af,0x5060,0x507a,0x508b
  18. local_38h 0x486d
  19. local_45h 0x5014,0x5068
  20. local_47h 0x501b
  21. local_46h 0x5083
  22. local_32h
  23. [0x00003b92]>

Type inference

The type inference for local variables and arguments is well integrated with the command afta.

Let’s see an example of this with a simple hello_world binary

  1. [0x000007aa]> pdf
  2. | ;-- main:
  3. / (fcn) sym.main 157
  4. | sym.main ();
  5. | ; var int local_20h @ rbp-0x20
  6. | ; var int local_1ch @ rbp-0x1c
  7. | ; var int local_18h @ rbp-0x18
  8. | ; var int local_10h @ rbp-0x10
  9. | ; var int local_8h @ rbp-0x8
  10. | ; DATA XREF from entry0 (0x6bd)
  11. | 0x000007aa push rbp
  12. | 0x000007ab mov rbp, rsp
  13. | 0x000007ae sub rsp, 0x20
  14. | 0x000007b2 lea rax, str.Hello ; 0x8d4 ; "Hello"
  15. | 0x000007b9 mov qword [local_18h], rax
  16. | 0x000007bd lea rax, str.r2_folks ; 0x8da ; " r2-folks"
  17. | 0x000007c4 mov qword [local_10h], rax
  18. | 0x000007c8 mov rax, qword [local_18h]
  19. | 0x000007cc mov rdi, rax
  20. | 0x000007cf call sym.imp.strlen ; size_t strlen(const char *s)
  • After applying afta
  1. [0x000007aa]> afta
  2. [0x000007aa]> pdf
  3. | ;-- main:
  4. | ;-- rip:
  5. / (fcn) sym.main 157
  6. | sym.main ();
  7. | ; var size_t local_20h @ rbp-0x20
  8. | ; var size_t size @ rbp-0x1c
  9. | ; var char *src @ rbp-0x18
  10. | ; var char *s2 @ rbp-0x10
  11. | ; var char *dest @ rbp-0x8
  12. | ; DATA XREF from entry0 (0x6bd)
  13. | 0x000007aa push rbp
  14. | 0x000007ab mov rbp, rsp
  15. | 0x000007ae sub rsp, 0x20
  16. | 0x000007b2 lea rax, str.Hello ; 0x8d4 ; "Hello"
  17. | 0x000007b9 mov qword [src], rax
  18. | 0x000007bd lea rax, str.r2_folks ; 0x8da ; " r2-folks"
  19. | 0x000007c4 mov qword [s2], rax
  20. | 0x000007c8 mov rax, qword [src]
  21. | 0x000007cc mov rdi, rax ; const char *s
  22. | 0x000007cf call sym.imp.strlen ; size_t strlen(const char *s)

It also extracts type information from format strings like printf ("fmt : %s , %u , %d", ...), the format specifications are extracted from anal/d/spec.sdb

You could create a new profile for specifying a set of format chars depending on different libraries/operating systems/programming languages like this :

  1. win=spec
  2. spec.win.u32=unsigned int

Then change your default specification to newly created one using this config variable e anal.spec = win

For more information about primitive and user-defined types support in radare2 refer to types chapter.