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.
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)
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
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
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
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
= 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
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
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
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
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
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.
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
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
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
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
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.
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
(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
(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
(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
(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??
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??
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
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
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…
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
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”
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
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.
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
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
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
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!