4

I am unable to locate a variable on the stack. I'm using Fedora 25 x64 but with a 32 bit program, btw.

C program:

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

char* text1 = "AAAAAAAAAAAAA";
int target;
char* text2 = "BBBBBBBBBBBBB";
void vuln(char *string)
{

  printf(string);

  if(target) {
      printf("you have modified the target :)\n");
  }
}

int main(int argc, char **argv)
{
  vuln(argv[1]);
  printf("Program name: %s", argv[0]);
}

/*After stepping into main and doing x/500wx $esp to examine the stack in gdb, here's a piece:

0xffffcc00: 0x00000000  0x00000000  0x00000000  0xf7fe0ca0
0xffffcc10: 0xf7df12af  0xf7fd765f  0x00000000  0x00000000
0xffffcc20: 0x00000000  0x00000000  0x00000000  0x0d696910
0xffffcc30: 0x00000000  0x00000000  0xf7fe0b79  0xf7de1ef0
0xffffcc40: 0x00000961  0xf7fd13d8  0x7c96f087  0xf7fe1399
0xffffcc50: 0x00000001  0x00000004  0xf7deb534  0x00000961
0xffffcc60: 0xf7deb604  0xf7fd13d8  0xffffccbc  0xffffccb8
0xffffcc70: 0x00000003  0x00000000  0xf7ffcfcc  0xf7fd764c
0xffffcc80: 0xf7deb534  0x7c96f087  0xf7deb604  0xf7de1f12
0xffffcc90: 0x03e4b784  0xffffccb8  0xffffcd48  0x00000961
0xffffcca0: 0x00000000  0x00000000  0x00000000  0x00000000
0xffffccb0: 0x00000000  0x00000000  0x00000000  0x00000000
(gdb) print &text1
$2 = (char **) 0x804a01c <text1>
(gdb) print text1
$3 = 0x8048544 'A' <repeats 13 times>
(gdb) 

As you can see, my text1 variable's address is in the 0x804 area, whereas my stack is in 0xffffcc area which is why I am completely lost. You can probably see what I'm trying to do, but I'm trying to locate 0x414141's followed by the target, followed by 0x42424242's but there are no 41s or 42s anywhere in the stack area near esp. I am currently educating myself on format string vulnerabilities, but at this point, I can't even locate the variables on the stack. Is there something I'm missing? Thanks.

7

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":

Process Layout in Virtual 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):

http://csapp.cs.cmu.edu/2e/ics2/asm/frame.ppt

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.

-1

Where in main are you when you dump the stack? Those variables are global initialised variables, they won't be on the stack until they are pushed there before the function call. Until then they will be in the bss or data section of your program. Print the disassembly of main and you should see a reference to your string being pushed onto the stack just before the call to your function. This could be a push op or a mov op btw, depending what your compiler settings are.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.