An introductory session on stack smashing and some of the content in the seminal Aleph One paper. Pairs with interactive content which can be found on GitHub here: https://github.com/Dylnuge/stack-smashing.
@dylnuge Image Credits: Super Mario Wiki, Nintendo What is Stack Smashing? A form of buffer overflow exploit—we are going to write to memory we’re not supposed to (don’t worry if this doesn’t make sense yet) Modern computers try to protect against this, but it’s far from useless knowledge, even today We’ll come back to Mario at the end (no, seriously)
@dylnuge Resources We’re going to do some real exploits! Download the ZIP and follow along! All the source for this is in my stack-smashing repo: https:/ /github.com/Dylnuge/stack- smashing The Aleph1 paper this is about (in part) was published in Phrack. A copy of the text can be found here: https:/ /insecure.org/stf/ smashstack.html
@dylnuge Clobbering Variables Where do variables go? Each square here is 1 byte (8 bits). Each row has 4 squares, so 4 bytes (32 bits) 0x1c 0x00 0x1f 0x03
@dylnuge Clobbering Variables Where do variables go? Let’s push a value on this stack: int myInt = 0; int secondInt = 42; 0x1c 0x00 0x1f 0x03 00 00 00 00 2A 00 00 00
@dylnuge Clobbering Variables Where do variables go? Let’s push a value on this stack: int myInt = 0; int secondInt = 42; Note that as we add to the stack, we move “up” in memory, to lower addresses 0x1c 0x00 0x1f 0x03 00 00 00 00 2A 00 00 00
@dylnuge Clobbering Variables Let’s read the string from the user instead char username[8]; gets(username); I enter “dylnuge” and it’s copied into the variable username But that fits. What if I did “dylnuge evil”? 0x1c 0x00 0x1f 0x03 00 00 00 00 2A 00 00 00 00 00 00 00 00 00 00 00 64 79 6C 6E 75 67 65 00
@dylnuge Clobbering Variables Let’s read the string from the user instead char username[8]; gets(username); I enter “dylnuge” and it’s copied into the variable username But that fits. What if I did “dylnuge evil”? 0x1c 0x00 0x1f 0x03 00 00 00 00 65 76 69 6C 00 00 00 00 00 00 00 00 64 79 6C 6E 75 67 65 20
@dylnuge Side Note: Endianness int myInt = 0x01020304; int secondInt = 42; Why do these values look weird? We can choose to write a number from most to least significant byte (big-endian) or the other way around (little- endian) Intel (x86) uses little-endian 0x1c 0x00 0x1f 0x03 04 03 02 01 2A 00 00 00
@dylnuge Arguments Abusing Function Calls What happens when we call a function? What info does our function need? Arguments to the function 0x1c 0x00 0x1f 0x03
@dylnuge Return Pointer Arguments Abusing Function Calls What happens when we call a function? What info does our function need? Arguments to the function Where to come back to 0x1c 0x00 0x1f 0x03
@dylnuge Frame Pointer Return Pointer Arguments Abusing Function Calls What happens when we call a function? What info does our function need? Arguments to the function Where to come back to What the old stack was 0x1c 0x00 0x1f 0x03
@dylnuge Frame Pointer Return Pointer Arguments Abusing Function Calls This is “C Calling Convention” There’s no rules things go in this order (so long as they’re consistent and the program knows where to find them) 0x1c 0x00 0x1f 0x03
@dylnuge Frame Pointer Return Pointer Arguments Abusing Function Calls This is “C Calling Convention” There’s no rules things go in this order (so long as they’re consistent and the program knows where to find them) As our new function runs, it may put new things on the stack 0x1c 0x00 0x1f 0x03
@dylnuge 2A 00 00 00 Frame Pointer Return Pointer Arguments Abusing Function Calls int myVar = 42; char username[8]; gets(username); You can probably see some things we could do with this… 0x1c 0x00 0x1f 0x03 00 00 00 00 00 00 00 00
@dylnuge Process Memory All programs like to think they have all the system memory, so they get their own isolated address space. High Low 0x00000000 0xffffffff For right now, we’re changing the scale here. We’ll change it back after this word from our sponsors.
@dylnuge Process Memory They need to put things in this space: A stack The code itself (text) Dynamic data (heap) Note that the “text” segment includes all libraries the code uses High Low 0x00000000 0xffffffff
@dylnuge Process Memory Function calls point to the same, consistent memory address in the text segment High Low 0x00000000 0xffffffff Image Credit: Celeste (Matt Makes Games)
@dylnuge Process Memory Function calls point to the same, consistent memory address in the text segment The objdump command will be incredibly useful here. Example time! High Low 0x00000000 0xffffffff
@dylnuge 2A 00 00 00 Frame Pointer Return Pointer Arguments Abusing Function Calls int myVar = 42; char username[8]; gets(username); You can probably see some things we could do with this… 0x1c 0x00 0x1f 0x03 00 00 00 00 00 00 00 00
@dylnuge 2A 00 00 00 Frame Pointer Return Pointer Arguments Abusing Function Calls int myVar = 42; char username[8]; gets(username); You can probably see some things we could do with this… Like return to an arbitrary function 0x1c 0x00 0x1f 0x03 00 00 00 00 00 00 00 00
@dylnuge 2A 00 00 00 Frame Pointer Return Pointer Arguments Abusing Function Calls int myVar = 42; char username[8]; gets(username); You can probably see some things we could do with this… Like return to an arbitrary function The frame pointer will die, but we don’t care 0x1c 0x00 0x1f 0x03 00 00 00 00 00 00 00 00
@dylnuge Side Note: x86 32-bit Intel (and AMD) processors use a machine language called “x86” (or “i386”) 64-bit ones use a similar language called “x64” (or “x86_64” or “amd_64”) Instructions in x86 (and x64) are variable length.
@dylnuge Side Note: x86 Here’s code to subtract 57286 from 58623 b8 ff e4 00 00 mov eax,0xe4ff 2d c6 df 00 00 sub eax,0xdfc6 The result of this is stored in the register %eax
@dylnuge Side Note: x86 Here’s code to subtract 57286 from 58623 b8 ff e4 00 00 mov eax,0xe4ff 2d c6 df 00 00 sub eax,0xdfc6 The result of this is stored in the register %eax Registers are storage spaces on the processor itself—they’re like variables, but there’s not that many of them, so we’re constantly copying them back and forth from memory
@dylnuge Other Victims Variables Buffer Return Pointer Frame Pointer Writing Our Own Code Here’s a stack where we know the address that our buffer is at High 0x00aabbcc
@dylnuge Shell code can continue through these (clobbered) Shell code (assembly code is just numbers) 0x00aabbcc Who Cares? Writing Our Own Code Here’s a stack where we know the address that our buffer is at High 0x00aabbcc
@dylnuge Shell code can continue through these (clobbered) Shell code (assembly code is just numbers) 0x00aabbcc Who Cares? Writing Our Own Code Here’s a stack where we know the address that our buffer is at Imagine that we put the assembly equivalent of execv(“/bin/bash”) High 0x00aabbcc
@dylnuge Shell code can continue through these (clobbered) Shell code (assembly code is just numbers) 0x00aabbcc Who Cares? Writing Our Own Code Here’s a stack where we know the address that our buffer is at Imagine that we put the assembly equivalent of execv(“/bin/bash”) Now we have a shell (hence “shell code”) High 0x00aabbcc
@dylnuge Shell code can continue through these (clobbered) Shell code (assembly code is just numbers) 0x00aabbcc Who Cares? NOP NOP, Who’s There? Even if we can execute on the stack, we might not know the exact stack pointer at the time we’re read in High 0x00aabb??
@dylnuge Shell code can start here 90 90 90 90 90 90 90 90 90 90 90 90 0x00aabbcc Who Cares? NOP NOP, Who’s There? NOP (0x90 in 32-bit x86) - Do nothing When we run, we jump somewhere into the NOP “sled” (hopefully) High 0x00aabb??
@dylnuge Shell code can start here 90 90 90 90 90 90 90
0x00aabbcc Who Cares? NOP NOP, Who’s There? NOP (0x90 in 32-bit x86) - Do nothing When we run, we jump somewhere into the NOP “sled” (hopefully) High 0x00aabb?? Image Credit: Mercury Productions
@dylnuge Other Variables Buffer Return Pointer Frame Pointer ROP ROP Till You Drop OK…what if we don’t have even a guess? Or what if our shell code is already in the program’s text? High Low
@dylnuge Other Variables Buffer Return Pointer Frame Pointer ROP ROP Till You Drop OK…what if we don’t have even a guess? Or what if our shell code is already in the program’s text? With tons of assembly in libraries, we can jump to a part of the code that wasn’t intended High Low
@dylnuge ROP ROP Till You Drop Recall this (32-bit, x86) assembly code: b8 ff e4 00 00 mov eax,0xe4ff This is innocuous code that could appear in any library. It moves the number 58,623 into a register
@dylnuge ROP ROP Till You Drop Recall this (32-bit, x86) assembly code: b8 ff e4 00 00 mov eax,0xe4ff This is innocuous code that could appear in any library. It moves the number 58,623 into a register But if we jump to the second byte of it, it does something very different…
@dylnuge ROP ROP Till You Drop Recall this (32-bit, x86) assembly code: b8 ff e4 00 00 mov eax,0xe4ff This is innocuous code that could appear in any library. It moves the number 58,623 into a register But if we jump to the second byte of it, it does something very different… ff e4 jmp esp
@dylnuge ROP ROP Till You Drop This is called “return oriented programming” Segments of code in a library that can be interpreted differently when read from an offset that starts in the middle of an instruction are called “gadgets”
@dylnuge ROP ROP Till You Drop This is called “return oriented programming” Segments of code in a library that can be interpreted differently when read from an offset that starts in the middle of an instruction are called “gadgets” The example I gave no longer works very well
@dylnuge ROP ROP Till You Drop This is called “return oriented programming” Segments of code in a library that can be interpreted differently when read from an offset that starts in the middle of an instruction are called “gadgets” The example I gave no longer works very well But other gadgets can still be found—the sheer quantity of assembly code makes the odds high.
@dylnuge Modern Prevention Modern compilers, OSes, and processors do try to stop you from making this mistake Some of the ways they do this include Memory Segmentation and DEP: Preventing data sections from being executed as code
@dylnuge Modern Prevention Modern compilers, OSes, and processors do try to stop you from making this mistake Some of the ways they do this include Memory Segmentation and DEP: Preventing data sections from being executed as code ASLR and PIE: Randomizing the memory space of the OS and the executable on load
@dylnuge Modern Prevention Modern compilers, OSes, and processors do try to stop you from making this mistake Some of the ways they do this include Memory Segmentation and DEP: Preventing data sections from being executed as code ASLR and PIE: Randomizing the memory space of the OS and the executable on load Stack canaries: Detecting stack smashing when it happens
@dylnuge Modern Examples But stack smashing and other buffer overflow attacks still come up all the time Kind of like code injections (e.g. SQL injection), they’re hard to kill
@dylnuge Modern Examples But stack smashing and other buffer overflow attacks still come up all the time Kind of like code injections (e.g. SQL injection), they’re hard to kill Image Credit: xkcd 1354
@dylnuge Modern Examples But stack smashing and other buffer overflow attacks still come up all the time Kind of like code injections (e.g. SQL injection), they’re hard to kill Image Credits: xkcd 1354, Nintendo
@dylnuge Follow Ups If you enjoyed this talk: Phrak, PoC||GTFO, Academic Papers DEFCON, BSides, Tech Talks, Meetups CTFs and Coding Challenges (dcdark.net, cryptopals.com, hackthebox.eu, etc) Teach Someone This! (Or something else!) Happy to take questions and discuss further any time!