5.1 Editing using Variables

Editing data with GNU poke mainly involves creating mapped values and storing them in Poke variables. For example, if we were interested in altering the fields of the header in an ELF file, we would map an Elf64_Ehdr struct at the beginning of the underlying IO space (the file), like in:

(poke) .file foo.o
(poke) load elf
(poke) var ehdr = Elf64_Ehdr @ 0#B

At this point the variable ehdr holds an Elf64_Ehdr structure, which is mapped. As such, altering any of the fields of the struct will update the corresponding bytes in foo.o. For example:

(poke) ehdr.e_entry = 0#B

A Poke value has three mapping related attributes: whether it is mapped, the offset at which it is mapped in an IO space, and in which IO space. This information is accessible for both the user and Poke programs using the following attributes:

(poke) ehdr'mapped
1
(poke) ehdr'offset
0UL#b
(poke) ehdr'ios
0

That’s it, ehdr is mapped at offset zero byte in the IO space #0, which corresponds to foo.o:

(poke) .info ios
  Id	Type	Mode	Bias		Size		Name
* #0	FILE	rw	0x00000000#B	0x000004c8#B   ./foo.o

Now that we have the ELF header, we may use it to get access to the ELF section header table in the file, that we will reference using another variable shdr:

(poke) var shdr = Elf64_Shdr[ehdr.e_shnum] @ ehdr.e_shoff
(poke) shdr[1]
Elf64_Shdr {
  sh_name=0x1bU#B,
  sh_type=0x1U,
  sh_flags=#<ALLOC,EXECINSTR>,
  sh_addr=0x0UL#B,
  sh_offset=0x40UL#B,
  sh_size=0xbUL#B,
  sh_link=0x0U,
  sh_info=0x0U,
  sh_addralign=0x1UL,
  sh_entsize=0x0UL#b
}

Variables are convenient entities to manipulate in Poke. Let’s suppose that the file has a lot of sections and we want to do some transformation in every section. It is a time consuming operation, and we may forget which sections we have already processed and which not. We could create an empty array to hold the sections already processed:

(poke) var processed = Elf64_Shdr[] ()

And then, once we have processed some given section, add it to the array:

... edit shdr[23] ...
(poke) processed += [shdr[23]]

Note how the array =processed= is not mapped, but the sections contained in it are mapped: Poke uses copy by shared value. So, after we spend the day carefully poking our ELF file, we can ask poke, are we done with all the sections in the file?

(poke) shdr'length == processed'length
1

Yes, we are. This can be made as sophisticated as desired. We could easily write a function that saves the contents of processed in files, so we can continue hacking tomorrow, for example.

We can then concluding that using mapped variables to edit data structures stored in IO spaces works well in common and simple cases like the above: we make our ways mapping here and there, defining variables to hold data that interests us, and it is easy to remember that the variables ehdr and shdr are mapped, where are they mapped, and that they are mapped in the file foo.o.

However, GNU poke allows to edit more than one IO space simultaneously. Let’s say we now want to poke the sections of another ELF file: bar.o. We would start by opening the file:

(poke) .file bar.o
(poke) .info ios
  Id	Type	Mode	Bias		Size		Name
* #1	FILE	rw	0x00000000#B	0x000004c8#B	./bar.o
  #0	FILE	rw	0x00000000#B	0x000004c8#B	./foo.o

Now that bar.o is the current IO space, we can map its header. But now, what variable to use? We would rather not redefine ehdr, because that is already holding the header of foo.o. We could adapt our naming schema on the fly:

(poke) var foo_ehdr = ehdr
(poke) var bar_ehdr = Elf64_Ehdr @ 0#B

But then we would need to do the same for the other variables too:

(poke) var foo_shdr = shdr
(poke) var bar_shdr = Elf64_Shdr[bar_ehdr.e_shnum] @ bar_ehdr.e_shoff

However, we can easily see how this can degenerate quickly: what about processed, for example? In general, as the number of IO spaces being edited increases it becomes more and more difficult to manage our mapped variables, which are associated to each IO space.