The reason the you are unable to locate text1
on the program runtime stack is that during runtime text1
is in the data
segment of the process running in virtual memory, not the stack. In order for a reference to text1
to be written to the stack text1
must be passed as an argument to a function which is called.
When a function is called and a new stack frame is created on the runtime stack for that function, memory is allocated on the stack for any local variables declared in that function as well. However, text1
, text2
and target
are global variables declared outside of any function. A direct consequence of this is that memory will not be allocated for text1
, text2
and target
on the stack. Instead, text1
and text2
will be in the process's data
segment and target
will be in the bss
segment. In order to understand why, familiarity with the ELF and the System V Application Binary Interface is essential.
x86 Linux Process Layout in Virtual Memory
For some context, here is a diagram of a process's layout in virtual memory on an x86 Linux system from Gustavo Duarte's article titled "Anatomy of a Program in Memory":
A look at this diagram will help clarify the significance of the memory addresses you are seeing. On an x86 Linux system, the stack is high in virtual memory and grows downward. This is why when the stack is examined one sees memory addresses such as 0xffffcc10
and 0xffffccb0
. The location in virtual memory of global variables text1
, text2
and target
will be more proximate to the program entry point since the data
and bss
segments are adjacent to the text
segment, which is low in memory. In light of this, a memory address of 0x804a01c
for text1
makes sense.
ELF and the System V ABI
From Section 4 (Object Files) of the ABI:
An executable file holds a program suitable for execution; the file specifies how the function exec
creates a program's process image.
and
Created by an assembler and link editor, object files are binary representations of programs intended to execute directly on a processor.
Once the program is compiled, assembled and linked, it is essentially a description of what it will look like as a process. This means that an executable binary can be statically analyzed to get an idea of how things will look when the program is running. When the binary constructed from the source code provided above is analyzed it is observed that the values text1
and text2
are in the .rodata
section:
$ readelf -x .rodata <ELF BINARY NAME>
Hex dump of section '.rodata':
0x08048538 03000000 01000200 41414141 41414141 ........AAAAAAAA
0x08048548 41414141 41004242 42424242 42424242 AAAAA.BBBBBBBBBB
0x08048558 42424200 796f7520 68617665 206d6f64 BBB.you have mod
0x08048568 69666965 64207468 65207461 72676574 ified the target
0x08048578 203a2900 50726f67 72616d20 6e616d65 :).Program name
0x08048588 3a202573 00 : %s.
According to the ABI (4-19), the .rodata
section holds read-only data that typically contributes to a non-writable segment in the process image. Examples of non-writable process image segments are the text
and data
segments mentioned above. The implication of this is that text1
and text2
will be located near where the program instructions are, namely the text
segment, when the program is loaded into memory. The instruction memory addresses will look much more similar to the memory addresses of text1
and text2
than memory addresses on the stack.
The target
variable is an uninitialized global variable, so its data will be held in the bss
segment.
Arguments to Functions are Written to the Stack
If you want pointers to these variables to appear on the runtime stack they must be passed as arguments to a function that is called at some point throughout the course of process execution, as a function's arguments are typically written to the stack in the caller's argument build area, as seen in the diagram below.
Stack layout with multiple frames (from CSAPP):
For example, instead of passing argv[1]
as an argument to the vuln
function, the global variable text1
can be passed instead. A pointer to text1
would then be saved on the stack prior to vuln
being called.
Alternatively, instead of hardcoding 'A' as a value for a global variable, you can pass an arbitrary number of 'A's (or any other ASCII characters) as an argument on the command line when executing your program in the shell. This will result in whatever values you pass being stored in argv[1]
which is the argument to vuln
.
It should be noted that due to their global scope, text1
, text2
and data
can be referenced in any function without being passed as an argument, but in the context of format string vulnerabilities and printf
that is not particularly useful to know.
For More Information
For more information on how printf
behaves in a x86 Linux environment, one can take a look at the answer to the following question on stackoverflow in which a user is calling printf
in a non-standard fashion: "ELF32 binary, little endian or not?"
Section 3.7 (titled "Procedures") in "Computer Systems: A Programmer's Perspective" covers function calls and the stack on an assembly level and has several helpful diagrams.