8

I looked at the basics of buffer overflow vulnerabilities and tried to understand how the stack is working. For that I wanted to write a simple program which changes the address of the return address to some value. Can anybody help me with figuring out the size of the base pointer to get the offset from the first argument?

void foo(void)
{
    char ret;
    char *ptr;

    ptr = &ret; //add some offset value here 
    *ptr = 0x00;
}

int main(int argc, char **argv)
{
    foo();

    return 1;
}

The generated assembler code looks as follows:

    .file   "test.c"
    .text
    .globl  foo 
    .type   foo, @function
foo:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16 
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    leaq    -9(%rbp), %rax
    movq    %rax, -8(%rbp)
    movq    -8(%rbp), %rax
    movb    $0, (%rax)
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret 
    .cfi_endproc
.LFE0:
    .size   foo, .-foo
    .globl  main
    .type   main, @function
main:
.LFB1:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16 
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movl    %edi, -4(%rbp)
    movq    %rsi, -16(%rbp)
    call    foo 
    movl    $1, %eax
    leave
    .cfi_def_cfa 7, 8
    ret 
    .cfi_endproc
.LFE1:
    .size   main, .-main
    .ident  "GCC: (GNU) 4.7.1 20120721 (prerelease)"
    .section    .note.GNU-stack,"",@progbits

The relevant part of the foo frame segment should look like this:

[char ret] [base pointer] [return address]

I have the position of the first one which is only 1 byte in size. Is it only 1 byte further to the base pointer or the size of a word as mentioned in http://insecure.org/stf/smashstack.html? And how do I get to know the size of the base pointer?

Heisenbug
  • 38,762
  • 28
  • 132
  • 190
fliX
  • 773
  • 8
  • 24

3 Answers3

1

You're not going to be able to do this in vanilla C, you don't have control of how the compiler lays out the stack frame.

In x86-64, the return address should be at %rbp + 8. You could use some inline assembly to get that (gcc syntax):

uint64_t returnaddr;
asm("mov 8(%%rbp),%0" : "=r"(returnaddr) : : );

Similarly for setting it.

Even that is a bit sketchy, as you don't know whether the compiler is going to set up %rbp or not. YMMV.

Keith Randall
  • 22,985
  • 2
  • 35
  • 54
  • 1
    +1 A recompile could always move variables. Just for the OP's testing, in this particular case the return address is at `&ret + 17`, which is unlikely to change as long as the local variables in that function don't change. – ughoavgfhw Sep 27 '12 at 21:54
  • @fliX In the assembly, the instruction `leaq -9(%rbp), %rax` is getting an address on the stack. Since it is byte aligned, and the only address calculation, that must be where `ret` is. Taking that 9, and adding 8 for the previous 64-bit base pointer, you get 17. – ughoavgfhw Sep 27 '12 at 23:04
  • Why using `char`s? Aren't `int`s better for pointer addresses? – Quoting Eddie Oct 27 '16 at 10:35
1

Your basepointer is most likely just a pointer, so it has the size sizeof(int*). But there is also another value in between your variable ret and the base pointer. I would assume its value of a register (eax?). This would lead to something like the following, if you want an endless loop:

void foo(void)
{
    char ret;
    char *ptr;

    ptr = (char*)(&ret) + (sizeof(ret)  + 2*sizeof(int*)) ;
    *(int*)ptr -= 0x0c;
}

The return target which is modified assuming it has the size of a pointer (could differ for other instruction sets). By decrementing it, the return target is set to a point before the calling point of foo.

Uli Klank
  • 199
  • 10
0

It seems you're using a 64-bit architecture, since RBP and RSP registers are 64-bit long. If you declare ptr as char* you will have to increment it 8 times to move through the stack. Instead you could declare it as uint64_t *. This data type is normally available in <stdint.h>.

However the stack frame definition varies depending on the target architecture and even on the compiler behaviour and optimizations. It's fine if you are experimenting, though.

Claudi
  • 5,224
  • 17
  • 30