I have a simple program that initializes a c style string and then initializes a character. I then use the function strcpy
to cause a buffer overflow situation which would seemingly overwrite the memory content of the character variable x
(assuming it is stored in adjacent memory).
char str[] = "Testt";
char x = 'X';
// print address and value of str
printf("%p: ", &str);
printf("%s\n", str);
// print value of x
printf("%c\n", x);
// cause buffer overflow
strcpy(str, "Hello world");
// print address and value of str
printf("%p: ", &str);
printf("%s\n", str);
// print address and value of x
// printf("%p: ", &x);
printf("%c\n", x);
return 0;
When run, this code produces output that looks like
0061FF29: Testt
X
0061FF29: Hello world
w
This situation shows that the buffer overflow did occur, and it caused the value of the x
variable to change from 'X'
to 'w'
.
However, if I remove the commented // printf("%p: ", &x);
on the third to last line, the buffer overflow does not cause the x
variable to be overwritten.
For clarity here is that code (notice the change on the third to last line)
char str[] = "Testt";
char x = 'X';
// print address and value of str
printf("%p: ", &str);
printf("%s\n", str);
// print value of x
printf("%c\n", x);
// cause buffer overflow
strcpy(str, "Hello world");
// print address and value of str
printf("%p: ", &str);
printf("%s\n", str);
// print address and value of x
printf("%p: ", &x);
printf("%c\n", x);
return 0;
This causes the output to be:
0061FF2A: Testt
X
0061FF2A: Hello world
0061FF29: X
So in this situation, the buffer overflow did not overwrite the x
variable.
Why does simply printing the memory address of the x
variable have this affect on the buffer overflow situation?
edit: added in assembly for the two situations The generated assembly for the first case (no printf):
.file "hello.c"
.def ___main; .scl 2; .type 32; .endef
.section .rdata,"dr"
LC0:
.ascii "%p: \0"
LC1:
.ascii "%c\12\0"
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
LFB17:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $32, %esp
call ___main
movl $1953719636, 25(%esp)
movw $116, 29(%esp)
movb $88, 31(%esp)
leal 25(%esp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
leal 25(%esp), %eax
movl %eax, (%esp)
call _puts
movsbl 31(%esp), %eax
movl %eax, 4(%esp)
movl $LC1, (%esp)
call _printf
leal 25(%esp), %eax
movl $1819043144, (%eax)
movl $1870078063, 4(%eax)
movl $6581362, 8(%eax)
leal 25(%esp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
leal 25(%esp), %eax
movl %eax, (%esp)
call _puts
movsbl 31(%esp), %eax
movl %eax, 4(%esp)
movl $LC1, (%esp)
call _printf
movl $0, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE17:
.ident "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"
.def _printf; .scl 2; .type 32; .endef
.def _puts; .scl 2; .type 32; .endef
and for the second situation
.file "hello.c"
.def ___main; .scl 2; .type 32; .endef
.section .rdata,"dr"
LC0:
.ascii "%p: \0"
LC1:
.ascii "%c\12\0"
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
LFB17:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $32, %esp
call ___main
movl $1953719636, 26(%esp)
movw $116, 30(%esp)
movb $88, 25(%esp)
leal 26(%esp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
leal 26(%esp), %eax
movl %eax, (%esp)
call _puts
movzbl 25(%esp), %eax
movsbl %al, %eax
movl %eax, 4(%esp)
movl $LC1, (%esp)
call _printf
leal 26(%esp), %eax
movl $1819043144, (%eax)
movl $1870078063, 4(%eax)
movl $6581362, 8(%eax)
leal 26(%esp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
leal 26(%esp), %eax
movl %eax, (%esp)
call _puts
leal 25(%esp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
movzbl 25(%esp), %eax
movsbl %al, %eax
movl %eax, 4(%esp)
movl $LC1, (%esp)
call _printf
movl $0, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE17:
.ident "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"
.def _printf; .scl 2; .type 32; .endef
.def _puts; .scl 2; .type 32; .endef
x
andstr
differs in the two programs. The compiler can place local variables in any arbitrary order, and the algorithm it uses can be totally opaque. But my guess is that the difference here has more to do with the fact that you take the address ofx
in the second one, thereby forcing it to have an address. If you compiled with optimisation, you'd probably find thatx
in the first program doesn't actually exist in memory; it xan simply live in a register (or be eliminated altogether). – rici