Lecture 8: Associates Language, Calling Convention, and the Stack

» Lecture video (Brown ID required)
» Lecture lawmaking
» Post-Lecture Quiz (due 6pm Monday, Feb 24).

Associates, continued

Final time, we looked at assembly code and developed an intuition for how to read assembly language instructions. Simply all programs we looked at contained only straight control flow, meaning that the assembly instructions merely execute one after another until the processor hits the ret instruction. Real programs contain conditional (if) statements, loops (for, while), and office calls. Today, we will understand how those concepts in the C language translate into associates, and then build up an understanding of the resulting retentivity layout that reveals how a dangerous class of computer security attacks is enabled past seemingly innocuous C programs.

Control Flow

Your estimator'southward processor is incredibly impaired: given the retentiveness address of an instruction, it goes and executes that didactics, and then executes the next instruction in memory, then the next, etc., until either at that place are no more than instructions to run. Control flow instructions change that default beliefs by irresolute where in memory the processor gets its next pedagogy from.

The role of the %rip register

The %rip annals on x86-64 is a special-purpose register that always holds the retentivity address of the adjacent teaching to execute in the program'south code segment. The processor increments %rip automatically later on each instruction, and control menses instructions like branches set the value of %rip to change the next instruction.
Perhaps surprisingly, %rip also shows upwardly when an assembly program refers to a global variable. Meet the sidebar under "Addressing modes" below to understand how %rip-relative addressing works.

Deviations from sequential instruction execution, such as role calls, loops, and conditionals, are called command flow transfers.

A branch instruction jumps to the didactics following a label in the assembly programme. Recall that labels are lines that stop with a colon (e.k., .L3:) in the assembly generated from the compiler. In an executable or object file, the labels are replaced by actual memory addresses, and then if you disassemble such a file (objdump -d FILE), you will encounter memory addresses as the branch target instead.

Here is an example of the assembly generated by a program that contains an if statement (controlflow01.c):

          .LFB0:         movl    a(%rip), %eax         cmpl    b(%rip), %eax         jl      .L4 .L1:         rep ret .L4:         movl    $0, %eax         jmp     .L1                  
The third and eighth (concluding) lines both contain branch instructions.

In that location are ii kinds of branches: unconditional and conditional. The jmp or j education (line viii) executes an unconditional branch and control catamenia always jumps to the branch target (here, .L1). All other branch instructions are conditional: they only co-operative if some status holds. That condition is represented by condition flags that are ready as a side effect of every arithmetic operation the processor runs. In the example programme to a higher place, the instruction that sets the flags is cmpl, which is a "compare" instruction that the processor internally executes every bit a subtraction of its starting time argument from its 2d argument, setting the flags and throwing abroad the result.

Arithmetic instructions modify function of the %rflags register. The most unremarkably used flags are:

  • ZF (zilch flag): set up iff the result was null.
  • SF (sign flag): gear up iff the result, when considered as a signed integer, was negative, i.e., iff most meaning chip (the sign bit) of the upshot was one.
  • CF (comport flag): set iff the upshot overflowed when considered an unsigned value (i.e., the result was greater than 2West-1 for a value of width W bytes).
  • OF (overflow flag): set up iff the result overflowed when considered a signed value (i.e., the effect was greater than iiWest-i-1 or less than –2W-1 for a value of width W bytes).
Although a few instructions let y'all load specific flags into the flag annals, code usually accesses flags via a conditional jump or a provisional move instruction.

You will often see the test and cmp instructions earlier a provisional co-operative. As mentioned above, these operations perform arithmetic but throw away the result (rather than storing it in the destination register), but set up the flags. test performs binary AND, while cmp performs subtraction, and both set the flags according to the outcome.

Below is a table of all branch instructions on the x86-64 architecture and the flags they look at to determine whether to branch and execute the next instruction at the co-operative target, or whether to continue execution with the next sequential instruction after the co-operative.

Instruction Mnemonic C instance Flags
j (jmp) Bound interruption; (Unconditional)
je (jz) Jump if equal (zero) if (x == y) ZF
jne (jnz) Jump if not equal (nonzero) if (x != y) !ZF
jg (jnle) Spring if greater if (x > y), signed !ZF && !(SF ^ OF)
jge (jnl) Jump if greater or equal if (x >= y), signed !(SF ^ OF)
jl (jnge) Leap if less if (ten < y), signed SF ^ OF
jle (jng) Jump if less or equal if (ten <= y), signed (SF ^ OF) || ZF
ja (jnbe) Bound if above if (x > y), unsigned !CF && !ZF
jae (jnb) Jump if above or equal if (x >= y), unsigned !CF
jb (jnae) Jump if below if (x < y), unsigned CF
jbe (jna) Jump if below or equal if (x <= y), unsigned CF || ZF
js Bound if sign bit if (10 < 0), signed SF
jns Jump if not sign bit if (x >= 0), signed !SF
jc Bound if carry bit N/A CF
jnc Jump if non behave scrap North/A !CF
jo Bound if overflow chip N/A OF
jno Spring if not overflow bit N/A !OF
Loops

Conditional branch instructions and flags are sufficient to support both conditional statements (if (...) { ... } else { ... } blocks in C) and loops (for (...) { ... }, while (...) { ... }, and do { ... } while (...)). For a conditional, the branch either jumps if the condition is true (or false, depending on how the compiler lays out the assembly) and continues execution otherwise. For a loop, the associates will comprise a conditional branch at the stop of the loop torso that checks the loop condition; if it is still satisfied, the branch jumps back to a label (or address) at the top of the loop.

When you see a provisional branch in assembly code whose target is a label or address to a higher place the branching education, information technology is about always a loop.

Consider the instance in controlflow02.s, and the corresponding programme in controlflow02.c. Let'southward focus on the assembly lawmaking post-obit the label:

          .L3:         movslq  (%rdx), %rcx         addq    %rcx, %rax         addq    $4, %rdx         cmpq    %rsi, %rdx         jne     .L3         rep ret [...]                  
Here, the loop variable is held in register %rdx, and the value that the loop variable is compared to on each iteration is in %rsi. (Yous can infer this from the fact that these registers are the simply ones that appear in a comparison.) The instruction above cmpq increments the loop variable by 4 every fourth dimension the loop executes. Finally, loop's torso consists of the two instructions above the addq $iv, %rdx educational activity: the first dereferences a pointer in %rdx and puts the value at the memory address it points to into register %rcx, and the 2d adds that value to the contents of %rax. Since %rax does not change before the conditional branch, it will exist incremented past the value pointed to by %rdx on every iteration: this loop iterates over integers in retentivity via pointer arithmetic.
Adressing Modes

Nosotros have seen a few ways in which associates instruction's operands can exist written already. In detail, the loop example contains (%rdx), which dereferences the address stored in register %rdx.

The total, full general form of a memory operand is commencement(base, index, scale), which refers to the address commencement + base of operations + alphabetize*calibration. In 0x18(%rax, %rbx, 4), %rax is the base, 0x18 the offset, %rbx the index, and four the scale. The starting time (if used) must exist a constant and the base and alphabetize (if used) must be registers; the scale must be either 1, 2, iv, or eight. In other words, if we write this as North(%reg1, %reg2, Chiliad), the accost computed is %reg1 + N + %reg2 * Thou.

The default offset, base, and index are 0, and the default calibration is 1, and instructions omit these parts if they take their default values. You will nearly often see instructions of the form kickoff(%annals), which perform simple add-on to the address in the annals and then dereference the effect. But occasionally, you may come across instructions that use both base of operations and index registers, or which use the total full general form.

Below is a handy overview tabular array containing all the possible ways of writing operands to associates instructions.

Type Example syntax Value used
Register %rbp Contents of %rbp
Immediate $0x4 0x4
Memory 0x4 Value stored at address 0x4
symbol_name Value stored in global symbol_name
(the compiler resolves the symbol name to an accost when creating the executable)
symbol_name(%rip) %rip-relative addressing for global (run into beneath)
symbol_name+4(%rip) Simple computations on symbols are allowed
(the compiler resolves the computation when creating the executable)
(%rax) Value stored at accost in %rax
0x4(%rax) Value stored at address %rax + 4
(%rax,%rbx) Value stored at accost %rax + %rbx
(%rax,%rbx,4) Value stored at accost %rax + %rbx*four
0x18(%rax,%rbx,4) Value stored at address %rax + 0x18 + %rbx*4
%rip-relative addressing for global variables

x86-64 lawmaking often refers to globals using %rip-relative addressing: a global variable named a is referenced as a(%rip). This style of reference supports position-independent code (PIC), a security feature. It specifically supports position-independent executables (PIEs), which are programs that piece of work independently of where their code is loaded into memory.

When the operating system loads a PIE, it picks a random starting point and loads all instructions and globals relative to that starting betoken. The PIE's instructions never refer to global variables using directly addressing: there is no movl global_int, %eax. Globals are referenced relatively instead, using deltas relative to the next %rip: to load a global variable into a register, the compiler emits movl global_int(%rip), %eax. These relative addresses work contained of the starting bespeak! For instance, consider an teaching located at (starting-point + 0x80) that loads a variable g located at (starting-point + 0x1000) into %rax. In a non-PIE, the didactics might be written as movq thousand, %rax; but this relies on chiliad having a fixed address. In a PIE, the instruction might be written movq g(%rip), %rax, which works out without having to know the starting address of the program'south code in memory at compile time (instead, %rip contains a number some known number of bytes apart from the starting point, and then any address relative to %rip is also relative to the starting signal).

At starting point… The mov instruction is at… The next pedagogy is at… And g is at… So the delta (grand - adjacent %rip) is…
0x400000 0x400080 0x400087 0x401000 0xF79
0x404000 0x404080 0x404087 0x405000 0xF79
0x4003F0 0x400470 0x400477 0x4013F0 0xF79

Calling Convention

Nosotros discussed conditionals and loops, but there is a 3rd blazon of control flow: function calls. Assembly language has no functions, simply sequences of instructions. Function calls therefore translate into command flow involving branches, just we need a bit more than that: functions can accept arguments, and the compiler better brand certain that the argument are available later it jumps to a role's instructions!

Defining how function calls and returns piece of work, where a function tin expect to discover its arguments, and where it must place its return value is the business of a calling convention. A calling convention governs how functions on a particular architecture and operating arrangement interact in assembly code. This includes rules on how function arguments are placed, where return values go, what registers functions may employ, how they may allocate local variables, and others.

Why practice we need calling conventions?

Calling conventions ensure that functions compiled by unlike compilers can interoperate, and they ensure that operating systems can run code from unlike programming languages and compilers. For instance, you can telephone call into C code from Python, or link C code compiled with gcc and code compiled with clang. This is possible merely because the Python libraries that call into C code understand its calling convention, and because the gcc and clang compilers' authors hold on the calling convention to use.

Some aspects of a calling convention are derived from the education set itself and embedded into the compages (east.g., via special-purpose registers modified every bit a side-effect of sure instructions), just some are conventional, meaning they wre decided upon by people (for instance, at a convention), and may differ across operating systems and compilers.

Programs call01.c to call06.c and their corresponding associates in call01.s to call06.s help us figure out the calling convention for x86-64 on the Linux operating system!

Some basic rules are:

  • The commencement six function arguments are passed in registers %rdi, %rsi, %rdx, %rcx, %r8, and %r9 (in this order; see the register list from last lecture).
  • The 7th and subsequent arguments are passed on the stack (see more below).
  • The return value is passed in register %rax.
There are really several other rules, which govern things like how to pass data structures that are larger than a register (e.thou., a struct), floating point numbers, etc. If you lot're interested, y'all can detect all the details in the AMD64 ABI, section 3.2.3.

call04.southward illustrates the rule about the first six arguments best: they are passed direct in registers. Other examples (east.thou., call01 to call03) are compiled without optimizations and accept somewhat more than circuitous associates lawmaking, which takes the values from registers, writes them onto the stack (more on that below), and then moves them into registers again. The reason why the unoptimized programs seemingly pointlessly write all their arguments to retentiveness in the stack segment is that arguments are local variables of a function, and since local variables accept automated lifetime, they're technically stored in the stack segment. With optimizations, the compiler is smart plenty to realize that it can only skip really storing them, so it but uses the registers containing the arguments directly.

The Stack

You will recall the stack segment of memory from earlier lectures: it is where all variables with automatic lifetime are stored. These include local variables alleged inside functions, but importantly also part arguments.

Recall that in call01.s to call03.s contained a agglomeration of instructions referring to %rsp, such every bit this implementation of the function f() (from call01.south):

                      movl    %edi, -4(%rsp)         movl    -four(%rsp), %eax         ret                  
The first movl stores the start statement (a 4-byte integer, passed in %edi) at an address four bytes below the address stored in register %rsp; the 2d movl education takes that value in memory and loads it into register %eax.

The %rsp register is called the stack pointer. It e'er points to the "top" of the stack, which is at the lowest (leftmost) accost current used in the stack segment. At the starting time of the office, any retention to the left of where %rsp points is therefore unused; whatever retention to the right of where information technology points is used. This explains why the lawmaking stores the argument at addresss %rsp - 4: information technology's the get-go 4-byte slot available on the stack, to the left of the currently used retentivity.

In other words, the what happened with these instructions is that the blue parts of the picture show below were added to the stack memory.

Nosotros can give names to the memory on the left and right of the address where %rsp points in the stack. The are chosen stack frames, where each stack frame corresponds to the data associated with one role phone call. The memory on the right of the address pointed to exist %rsp at the point f() gets called is the stack frame of any function calls f(). This part is named the caller (the role that calls), while f() is the callee (the role being called).

The memory on the right of the %rsp address at the point of f() being called (we refer to this as "entry %rsp") is the caller's stack frame (ruby beneath), and the retention to its left is the callee'southward stack frame.

The arguments and local variables of f() live inside f()'s stack frame. Subsequent arguments (second, tertiary, 4th, etc.) are stored at subsequently lower addresses below %rsp (see call02.s and call03.south for examples with more arguments), followed somewhen by whatever local variables in the caller.

How does %rsp modify?

The convention is that %rsp ever points to the lowest (leftmost) stack address that is currently used. This ways that when a function declares a new local variable, %rsp has to move down (left) and if a role returns, %rsp has to move upwardly (correct) and back to where it was when the role was originally called.

Moving %rsp happens in two ways: explicit modification via arithmetics instructions, and implicit modification as a side effect of special instructions. The former happens when the compiler knows exactly how many bytes a function requires %rsp to move by, and involves instructions like subq $0x10, %rsp, which moves the stack pointer downwardly by 16 bytes. The latter, side-effect modification happens when instruction push and popular run. These instructions write the contents of a annals onto the stack memory immediately to the left of the electric current %rsp and likewise change %rsp to signal to the beginning of this new data. For example, pushq %rax would write the 8 bytes from annals %rax at address %rsp - 8 and gear up %rsp to that address; it is equivalent to movq %rax, -8(%rsp); subq $8, %rsp or subq $8, %rsp; movq %rax, (%rsp).

As an optimization, the compiler may choose to avoid writing arguments onto the stack. Information technology does this for upward to six arguments, which per calling convention are held in specific registers. call04.due south shows this: the C code we compile it from (call04.c) is identical to the lawmaking in call03.c.

Merely in that location is a limited number of registers in the x86-64 architecture, and y'all can write functions in C that take whatever number of arguments! The calling convention says that the first 6 arguments max exist passed in registers, but that the 7th and above arguments are always passed in memory on the stack. Specifically, these arguments go into the caller's stack frame, and so they are stored above the entry %rsp at the point where the office is called (meet call05.{c,s} and call06.{c,s}).

Return Accost

As a role executes, it somewhen reaches a ret teaching in its assembly. The outcome of ret is to return to the caller (a grade a control menses, as the side by side teaching needs to change). Simply how does the processor know what instruction to execute next, and what to set %rip to?

It turns out that the stack plays a office here, too. In a nutshell, each function call stores the return address every bit the very first (i.due east., rightmost) data in the callee's stack frame. (If the function called takes more than six arguments, the return address is to the left of the seventh argument in the caller's stack frame.)

The stored return address makes information technology possible for each function to know exactly where to go along execution once it returns to its caller. (Even so, storing the return address on the stack likewise has some unsafe consequences, as nosotros volition see presently.)

We can now define the total function entry and go out sequence. Both the caller and the callee have responsibilities in this sequence.

To set up for a role call, the caller performs the following tasks:

  1. The caller stores the first half-dozen arguments in the respective registers.

  2. If the callee takes more than six arguments, or if some of its arguments are large, the caller must store the surplus arguments on its stack frame (in increasing order). The 7th argument must be stored at (%rsp) (that is, the summit of the stack) when the caller executes its callq instruction.

  3. The caller saves any caller-saved registers (see last lecture's list). These are registers whose values the callee might overwrite, but which the caller needs to retain for after employ.

  4. The caller executes callq Office. This has an result similar pushq $NEXT_INSTRUCTION; jmp Office (or, equivalently, subq $8, %rsp; movq $NEXT_INSTRUCTION, (%rsp); jmp Office), where NEXT_INSTRUCTION is the address of the education immediately following callq.

To render from a office, the callee does the following:

  1. The callee places its render value in %rax.

  2. The callee restores the stack pointer to its value at entry ("entry %rsp"), if necessary.

  3. The callee executes the retq instruction. This has an effect like popq %rip, which removes the render accost from the stack and jumps to that address (because the instruction writes information technology into the special %rip register).

  4. Finally, the caller then cleans up any infinite information technology prepared for arguments and restores caller-saved registers if necessary.

Base of operations Pointers and the %rbp Register

Keeping track of the entry %rsp can exist tricky with more than complex functions that allocate lots of local variables and modify the stack in complex ways. For these cases, the x86-64 Linux calling convention allows for the utilize of another register, %rbp as a special-purpose register.

%rbp holds the address of the base of the electric current stack frame: that is, the address of the rightmost (highest) address that points to a value still part of the current stack frame. This corresponds the rightmost address of an object in the callee's stack, and to the commencement accost that isn't part of an argument to the callee or one of its local variables. Information technology is called the base pointer, since the address points at the "base" of the callee's stack frame (if %rsp points to the "acme", %rbp points to the "base" (= bottom). The %rbp register maintains this value for the whole execution of the function (i.e., the function may not overwrite the value in that register), even as %rsp changes.

This scheme has the advantage that when the part exits, information technology can restore its original entry %rsp by loading it from %rbp. In add-on, it too facilitates debugging because each part stores the erstwhile value of %rbp to the stack at its point of entry. The 8 bytes belongings the caller's %rbp are the very first thing stored within the callee's stack frame, and they are right beneath the return address in the caller'southward stack frame. This hateful that the saved %rbps grade a chain that allows each function to locate the base of its caller'southward stack frame, where it will find the %rbp of the "thou-caller's" stack frame, etc. The backtraces you lot run across in GDB and in Address Sanitizer error messages are generated precisely using this chain!

Therefore, with a base arrow, the role entry sequence becomes:

  1. The first instruction executed by the callee on function entry is pushq %rbp. This saves the caller's value for %rbp into the callee'southward stack. (Since %rbp is callee-saved, the callee is responsible for saving it.)

  2. The second instruction is movq %rsp, %rbp. This saves the current stack pointer in %rbp (so %rbp = entry %rsp - 8).

    This adjusted value of %rbp is the callee'southward "frame pointer" or base arrow. The callee will non change this value until it returns. The frame pointer provides a stable reference point for local variables and caller arguments. (Circuitous functions may need a stable reference indicate because they reserve varying amounts of space.)

    Note, likewise, that the value stored at (%rbp) is the caller's %rbp, and the value stored at 8(%rbp) is the render accost. This information can be used to trace backwards by debuggers (a procedure called "stack unwinding").

  3. The part ends with movq %rbp, %rsp; popq %rbp; retq, or, equivalently, leave; retq. This sequence is the last matter the callee does, and information technology restores the caller'due south %rbp and entry %rsp earlier returning.

You tin can find an example of this in call07.s. Lab 3 also uses the %rbp-based calling convention, so make sure yous go along the extra 8 bytes for storing the caller'southward %rbp on the stack in mind!

Buffer overflow attacks

Now that we understand the calling convention and the stack, let's take a step back and think of some of the consequences of this well-defined retentiveness layout. While a callee is not supposed to access its caller'south stack frame (unless it'due south explicitly passed a arrow to an object within it), there is no principled machinery in the x86-64 architecture that prevents such access.

In particular, if you can guess the address of a variable on the stack (either a local within the electric current office or a local/argument in a caller of the electric current role), your program tin can simply write data to that address and overwrite whatsoever is there.

This can happen accidentally (due to bugs), but it becomes a much bigger trouble if done deliberately by malicious actors: a user might provide input that causes a program to overwrite important data on the stack. This kind of attack is called a buffer overflow attack.

Consider the lawmaking in attackme.cc. This plan computes checksums of strings provided to it as command line arguments. Yous don't need to understand in deep particular what information technology does, but discover that the checksum() function uses a 100-byte stack-allocated buffer (as part of the buf matrimony) to concord the input string, which it copies into that buffer.

A sane execution of attackme might look like this:

          $ ./attackme hey yo CS131 hey: checksum 00796568, sha1 7aea02175315cd3541b03ffe78aa1ccc40d2e98a  - yo: checksum 00006f79, sha1 dcdc24e139db869eb059c9355c89c382de15b987  - CS131: checksum 33315374, sha1 05ab4d9aea4f9f0605dc4703ae8cfc44aab7a5ef  -                  

Merely what if the user provides an input string longer than 99 characters (remember that we also need the zero terminator in the buffer)? The part merely keeps writing, and it will write over any is adjacent to buf on the stack.

From our prior pictures, we know that buf will be in checksum's stack frame, below the entry %rsp. Moreover, direct higher up the entry %rsp is the return address! In this case, that is an address in master(). So, if checksum writes beyond the stop of buf, will overwrite the return address on the stack; if information technology keeps going farther, information technology will overwrite data in main's stack frame.

Why is overwriting the return address dangerous? It means that a clever attacker can directly the program to execute whatsoever function within the program. In the instance of attackme.cc, notation the run_shell() function, which runs a string every bit a shell control. This has a lot of nefarious potential – what if we could cause that function to execute with a user-provided string? Nosotros could print a lot of sad face up emojis to the vanquish, or, more than dangerously, run a command like rm -rf /, which deletes all data on the user's computer!

If we run ./attackme.unsafe (a variant of attackme with safety features added by mondern compilers to combat these attacks disabled), it behaves equally normal with sane strings:

          $ ./attackme.unsafe hey yo CS131 hey: checksum 00796568, sha1 7aea02175315cd3541b03ffe78aa1ccc40d2e98a  - yo: checksum 00006f79, sha1 dcdc24e139db869eb059c9355c89c382de15b987  - CS131: checksum 33315374, sha1 05ab4d9aea4f9f0605dc4703ae8cfc44aab7a5ef  -                  
But if we pass a very long string with more than 100 characters, things get a flake more unusual:
          $ ./attackme.unsafe sghfkhgkfshgksdhrehugresizqaugerhgjkfdhgkjdhgukhsukgrzufaofuoewugurezgureszgukskgreukfzreskugzurksgzukrestgkurzesi Segmentation fault (core dumped)                  
The crash happens because the return address for checksum() was overwritten by garbage from our string, which isn't a valid address. But what if nosotros figure out a valid accost and put it in exactly the right identify in our string?

This is what the input in attack.txt does. Specifically, using GDB, I figured out that the address of run_shell in my compiled version of the code is 0x400734 (an address in the lawmaking/text segment of the executable). attack.txt contains a carefully crafted "payload" that puts the value 0x400734 into the correct bytes on the stack. The attack payload is 115 characters long because we need 100 characters to overrun buf, iii bytes for the malicious return accost, and 12 bytes of extra payload because stack frames on x86-64 Linux are aligned to xvi-byte boundaries.

Executing this attack works equally follows:

          $ ./attackme.dangerous "$(cat attack.txt)" Owned Endemic Owned OWNED OWNED OWNED sh: 7: ��5��: non plant Segmentation mistake (cadre dumped)                  
The true cat attack.txt shell command simple pastes the contents of the attack.txt file into the string we're passing to the program. (The quotes are required to make certain our assail payload is processed equally a unmarried cord even if it contains spaces.)

Summary

Today, nosotros concluded our brief bout of associates linguistic communication and the low-level concepts of program execution.

We outset looked at command period in assembly, where instructions change what other instructions the processor executes next. In many cases, command flow starting time involves a flag-setting educational activity and so a conditional branch based on the values of the flags annals. This allows for provisional statements and loops.

Function calls in associates are governed past the calling convention of the architecture and operating system used: it determines which registers hold specific values such as arguments and return values, which registers a function may modify, and where on the stack certain information (such every bit the return accost) is stored.

We also understood in more than particular how the stack segment of memory is structured and managed, and discussed how it grows and shrinks. Finally, we looked into how the very well-defined memory layout of the stack can go a danger if a program is compromised through a malicious input: by advisedly crafting inputs that overwrite role of the stack retentiveness via a buffer overflow, we can change of import data and cause a program to execute capricious code.

In Lab 3, y'all volition craft and execute buffer overflow attacks on a program yourself!