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

» Lecture video (Brown ID required)
» Lecture code
» Post-Lecture Quiz (due 6pm Mon, February 24).

Assembly, continued

Concluding time, we looked at associates code and developed an intuition for how to read associates linguistic communication instructions. But all programs we looked at contained only direct control flow, meaning that the assembly instructions simply execute one afterwards another until the processor hits the ret education. Real programs contain conditional (if) statements, loops (for, while), and function calls. Today, we volition understand how those concepts in the C linguistic communication interpret into associates, and and then build up an agreement of the resulting memory layout that reveals how a unsafe form of calculator security attacks is enabled by seemingly innocuous C programs.

Control Flow

Your computer's processor is incredibly dumb: given the memory accost of an instruction, it goes and executes that instruction, then executes the adjacent instruction in memory, then the adjacent, etc., until either in that location are no more instructions to run. Control flow instructions modify that default behavior by irresolute where in retention the processor gets its next instruction from.

The role of the %rip register

The %rip annals on x86-64 is a special-purpose register that e'er holds the retentiveness accost of the adjacent instruction to execute in the program'southward code segment. The processor increments %rip automatically subsequently each educational activity, and control flow instructions like branches set the value of %rip to change the next instruction.
Perchance surprisingly, %rip also shows up when an assembly program refers to a global variable. See the sidebar under "Addressing modes" beneath to understand how %rip-relative addressing works.

Deviations from sequential education execution, such as function calls, loops, and conditionals, are chosen control period transfers.

A branch pedagogy jumps to the education following a label in the assembly program. Call back that labels are lines that end with a colon (eastward.thousand., .L3:) in the assembly generated from the compiler. In an executable or object file, the labels are replaced by actual memory addresses, so if you detach such a file (objdump -d FILE), you will see memory addresses as the co-operative target instead.

Hither 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 tertiary and eighth (final) lines both comprise branch instructions.

There are ii kinds of branches: unconditional and conditional. The jmp or j teaching (line 8) executes an unconditional co-operative and control flow ever jumps to the co-operative target (here, .L1). All other branch instructions are conditional: they just branch if some status holds. That condition is represented by condition flags that are set as a side issue of every arithmetic operation the processor runs. In the example program in a higher place, the instruction that sets the flags is cmpl, which is a "compare" instruction that the processor internally executes as a subtraction of its showtime argument from its second statement, setting the flags and throwing away the result.

Arithmetic instructions change office of the %rflags register. The virtually commonly used flags are:

  • ZF (nada flag): set iff the outcome was zippo.
  • SF (sign flag): set up iff the result, when considered as a signed integer, was negative, i.e., iff most significant flake (the sign chip) of the result was one.
  • CF (carry flag): set iff the issue overflowed when considered an unsigned value (i.east., the issue was greater than twoW-1 for a value of width W bytes).
  • OF (overflow flag): set iff the result overflowed when considered a signed value (i.e., the consequence was greater than 2W-1-1 or less than –2Due west-1 for a value of width W bytes).
Although a few instructions allow yous load specific flags into the flag annals, code ordinarily accesses flags via a provisional jump or a conditional motility instruction.

You will frequently come across the test and cmp instructions before a provisional branch. Equally mentioned above, these operations perform arithmetic but throw away the result (rather than storing it in the destination register), but set the flags. test performs binary AND, while cmp performs subtraction, and both prepare the flags according to the issue.

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

Instruction Mnemonic C instance Flags
j (jmp) Bound interruption; (Unconditional)
je (jz) Jump if equal (cypher) if (x == y) ZF
jne (jnz) Bound if not equal (nonzero) if (x != y) !ZF
jg (jnle) Jump if greater if (x > y), signed !ZF && !(SF ^ OF)
jge (jnl) Jump if greater or equal if (x >= y), signed !(SF ^ OF)
jl (jnge) Jump if less if (x < y), signed SF ^ OF
jle (jng) Jump if less or equal if (x <= 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) Bound if below if (ten < y), unsigned CF
jbe (jna) Jump if below or equal if (x <= y), unsigned CF || ZF
js Jump if sign chip if (x < 0), signed SF
jns Jump if non sign bit if (x >= 0), signed !SF
jc Jump if carry bit Due north/A CF
jnc Jump if not carry fleck N/A !CF
jo Bound if overflow bit North/A OF
jno Jump if not overflow bit North/A !OF
Loops

Conditional co-operative instructions and flags are sufficient to support both conditional statements (if (...) { ... } else { ... } blocks in C) and loops (for (...) { ... }, while (...) { ... }, and do { ... } while (...)). For a provisional, the branch either jumps if the condition is truthful (or simulated, depending on how the compiler lays out the associates) and continues execution otherwise. For a loop, the assembly will contain a conditional branch at the end of the loop torso that checks the loop condition; if it is still satisfied, the branch jumps back to a characterization (or address) at the elevation of the loop.

When you come across a conditional co-operative in assembly lawmaking whose target is a label or address above the branching teaching, it is almost e'er a loop.

Consider the example in controlflow02.south, and the respective program in controlflow02.c. Let's focus on the assembly lawmaking following 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. (You can infer this from the fact that these registers are the simply ones that appear in a comparison.) The didactics in a higher place cmpq increments the loop variable by 4 every fourth dimension the loop executes. Finally, loop's trunk consists of the two instructions above the addq $4, %rdx instruction: the offset dereferences a pointer in %rdx and puts the value at the memory address it points to into register %rcx, and the 2nd adds that value to the contents of %rax. Since %rax does non change before the conditional branch, it will be incremented past the value pointed to by %rdx on every iteration: this loop iterates over integers in memory via pointer arithmetic.
Adressing Modes

We take seen a few ways in which assembly instruction'due south operands can be written already. In particular, the loop instance contains (%rdx), which dereferences the address stored in annals %rdx.

The full, general class of a memory operand is offset(base, alphabetize, scale), which refers to the address first + base + index*scale. In 0x18(%rax, %rbx, iv), %rax is the base, 0x18 the offset, %rbx the index, and 4 the calibration. The commencement (if used) must be a constant and the base and index (if used) must exist registers; the calibration must exist either 1, two, 4, or viii. In other words, if nosotros write this equally N(%reg1, %reg2, Grand), the address computed is %reg1 + North + %reg2 * M.

The default get-go, base, and index are 0, and the default scale is 1, and instructions omit these parts if they have their default values. You will most often run into instructions of the grade starting time(%register), which perform simple improver to the address in the register and and so dereference the result. Just occasionally, y'all may come across instructions that utilize both base and index registers, or which use the general form.

Below is a handy overview table containing all the possible ways of writing operands to assembly instructions.

Type Example syntax Value used
Register %rbp Contents of %rbp
Firsthand $0x4 0x4
Retention 0x4 Value stored at address 0x4
symbol_name Value stored in global symbol_name
(the compiler resolves the symbol proper noun to an address when creating the executable)
symbol_name(%rip) %rip-relative addressing for global (see below)
symbol_name+iv(%rip) Simple computations on symbols are allowed
(the compiler resolves the computation when creating the executable)
(%rax) Value stored at address in %rax
0x4(%rax) Value stored at address %rax + iv
(%rax,%rbx) Value stored at address %rax + %rbx
(%rax,%rbx,4) Value stored at accost %rax + %rbx*4
0x18(%rax,%rbx,4) Value stored at address %rax + 0x18 + %rbx*4
%rip-relative addressing for global variables

x86-64 code 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 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 point. The PIE's instructions never refer to global variables using direct 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 independent of the starting indicate! For example, consider an instruction located at (starting-point + 0x80) that loads a variable thousand located at (starting-point + 0x1000) into %rax. In a non-PIE, the instruction might be written every bit movq 1000, %rax; but this relies on g having a fixed address. In a PIE, the educational activity might be written movq g(%rip), %rax, which works out without having to know the starting address of the plan'due south code in memory at compile time (instead, %rip contains a number some known number of bytes apart from the starting point, so any accost relative to %rip is also relative to the starting betoken).

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

Calling Convention

We discussed conditionals and loops, simply there is a third type of command period: office calls. Assembly language has no functions, just sequences of instructions. Function calls therefore translate into control flow involving branches, but we need a bit more than that: functions can take arguments, and the compiler better make sure that the argument are available afterwards it jumps to a function's instructions!

Defining how part calls and returns work, where a office can expect to notice its arguments, and where it must identify its render value is the business of a calling convention. A calling convention governs how functions on a item architecture and operating system interact in assembly code. This includes rules on how office arguments are placed, where return values become, what registers functions may employ, how they may allocate local variables, and others.

Why exercise we need calling conventions?

Calling conventions ensure that functions compiled past unlike compilers tin can interoperate, and they ensure that operating systems can run code from dissimilar programming languages and compilers. For example, y'all can call into C code from Python, or link C lawmaking compiled with gcc and lawmaking compiled with clang. This is possible only because the Python libraries that call into C code empathize its calling convention, and considering the gcc and clang compilers' authors agree on the calling convention to use.

Some aspects of a calling convention are derived from the instruction gear up itself and embedded into the architecture (e.thou., via special-purpose registers modified as a side-effect of certain instructions), but some are conventional, meaning they wre decided upon by people (for example, at a convention), and may differ beyond operating systems and compilers.

Programs call01.c to call06.c and their respective assembly in call01.s to call06.southward help usa figure out the calling convention for x86-64 on the Linux operating system!

Some basic rules are:

  • The first six function arguments are passed in registers %rdi, %rsi, %rdx, %rcx, %r8, and %r9 (in this order; run across the register listing from final lecture).
  • The seventh and subsequent arguments are passed on the stack (see more below).
  • The render value is passed in register %rax.
There are actually several other rules, which govern things similar how to pass data structures that are larger than a register (e.thou., a struct), floating betoken numbers, etc. If you lot're interested, you can find all the details in the AMD64 ABI, department 3.2.three.

call04.south illustrates the rule virtually the start six arguments all-time: they are passed straight in registers. Other examples (e.g., call01 to call03) are compiled without optimizations and have somewhat more complex assembly code, 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 memory in the stack segment is that arguments are local variables of a function, and since local variables have automated lifetime, they're technically stored in the stack segment. With optimizations, the compiler is smart enough to realize that information technology can just skip actually storing them, so it just uses the registers containing the arguments directly.

The Stack

You will recall the stack segment of retention from earlier lectures: information technology is where all variables with automatic lifetime are stored. These include local variables declared inside functions, just chiefly also function arguments.

Recall that in call01.due south to call03.s independent a bunch of instructions referring to %rsp, such as this implementation of the function f() (from call01.s):

                      movl    %edi, -4(%rsp)         movl    -4(%rsp), %eax         ret                  
The outset movl stores the commencement argument (a iv-byte integer, passed in %edi) at an address four bytes below the address stored in register %rsp; the second movl instruction takes that value in memory and loads it into register %eax.

The %rsp register is called the stack pointer. Information technology always points to the "acme" of the stack, which is at the lowest (leftmost) accost current used in the stack segment. At the start of the function, any retentiveness to the left of where %rsp points is therefore unused; any retention to the right of where it points is used. This explains why the code stores the argument at addresss %rsp - 4: it's the first 4-byte slot bachelor on the stack, to the left of the currently used retention.

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

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

The memory on the correct of the %rsp accost at the point of f() existence called (we refer to this every bit "entry %rsp") is the caller's stack frame (cerise below), and the memory to its left is the callee'southward stack frame.

The arguments and local variables of f() live within f()'s stack frame. Subsequent arguments (2d, third, quaternary, etc.) are stored at subsequently lower addresses below %rsp (see call02.due south and call03.s for examples with more than arguments), followed eventually by any local variables in the caller.

How does %rsp change?

The convention is that %rsp e'er points to the lowest (leftmost) stack accost that is currently used. This means that when a function declares a new local variable, %rsp has to movement down (left) and if a function returns, %rsp has to move up (right) and back to where it was when the function was originally called.

Moving %rsp happens in two ways: explicit modification via arithmetic instructions, and implicit modification equally a side effect of special instructions. The sometime happens when the compiler knows exactly how many bytes a function requires %rsp to movement by, and involves instructions like subq $0x10, %rsp, which moves the stack pointer downwards past xvi bytes. The latter, side-upshot modification happens when instruction push and popular run. These instructions write the contents of a register onto the stack memory immediately to the left of the current %rsp and also modify %rsp to point to the offset of this new data. For example, pushq %rax would write the 8 bytes from register %rax at address %rsp - 8 and set up %rsp to that address; it is equivalent to movq %rax, -eight(%rsp); subq $viii, %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 up to six arguments, which per calling convention are held in specific registers. call04.south shows this: the C code we compile it from (call04.c) is identical to the code in call03.c.

But there is a limited number of registers in the x86-64 architecture, and y'all tin can write functions in C that take any number of arguments! The calling convention says that the kickoff six arguments max be passed in registers, but that the 7th and in a higher place arguments are ever passed in retentivity on the stack. Specifically, these arguments go into the caller'south stack frame, so they are stored above the entry %rsp at the point where the role is called (come across call05.{c,south} and call06.{c,s}).

Render Accost

As a part executes, it somewhen reaches a ret instruction in its assembly. The effect of ret is to return to the caller (a form a command flow, equally the next pedagogy needs to change). But how does the processor know what pedagogy to execute side by side, and what to fix %rip to?

Information technology turns out that the stack plays a part here, besides. In a nutshell, each function phone call stores the render accost equally the very first (i.e., rightmost) data in the callee's stack frame. (If the function called takes more than than six arguments, the return accost is to the left of the 7th argument in the caller's stack frame.)

The stored return address makes information technology possible for each function to know exactly where to continue execution one time it returns to its caller. (However, storing the return address on the stack also has some dangerous consequences, as we will see shortly.)

We tin can now define the full function entry and get out sequence. Both the caller and the callee accept responsibilities in this sequence.

To prepare for a function call, the caller performs the following tasks:

  1. The caller stores the first vi arguments in the corresponding registers.

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

  3. The caller saves any caller-saved registers (run into terminal lecture's listing). These are registers whose values the callee might overwrite, but which the caller needs to retain for later use.

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

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

  1. The callee places its return 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 return address from the stack and jumps to that address (considering the instruction writes it into the special %rip register).

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

Base Pointers and the %rbp Register

Keeping rail of the entry %rsp tin can be tricky with more 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 utilise of another annals, %rbp equally a special-purpose register.

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

This scheme has the advantage that when the function exits, it tin restore its original entry %rsp by loading it from %rbp. In improver, it also facilitates debugging because each function stores the old value of %rbp to the stack at its bespeak of entry. The eight bytes property the caller'south %rbp are the very offset thing stored inside the callee'south stack frame, and they are right below the return accost in the caller'southward stack frame. This mean that the saved %rbps form a chain that allows each function to locate the base of its caller'due south stack frame, where it will detect the %rbp of the "1000-caller's" stack frame, etc. The backtraces you see in GDB and in Address Sanitizer error messages are generated precisely using this chain!

Therefore, with a base pointer, the function entry sequence becomes:

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

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

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

    Note, too, that the value stored at (%rbp) is the caller'due south %rbp, and the value stored at 8(%rbp) is the return address. This information tin can be used to trace backwards by debuggers (a procedure called "stack unwinding").

  3. The role ends with movq %rbp, %rsp; popq %rbp; retq, or, equivalently, leave; retq. This sequence is the final thing the callee does, and information technology restores the caller's %rbp and entry %rsp before returning.

Yous can discover an case of this in call07.s. Lab three also uses the %rbp-based calling convention, and so make sure you lot go along the extra 8 bytes for storing the caller's %rbp on the stack in mind!

Buffer overflow attacks

Now that nosotros empathize the calling convention and the stack, let'due south accept a step dorsum and think of some of the consequences of this well-defined retention layout. While a callee is not supposed to admission its caller'south stack frame (unless information technology's explicitly passed a pointer to an object within information technology), there is no principled mechanism in the x86-64 architecture that prevents such access.

In detail, if you can judge the address of a variable on the stack (either a local inside the current function or a local/argument in a caller of the electric current function), your program tin just write data to that address and overwrite whatever is there.

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

Consider the code in attackme.cc. This program computes checksums of strings provided to information technology every bit command line arguments. You don't need to understand in deep item what information technology does, merely observe that the checksum() function uses a 100-byte stack-allocated buffer (as function of the buf spousal relationship) to hold the input string, which it copies into that buffer.

A sane execution of attackme might look similar this:

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

But what if the user provides an input string longer than 99 characters (call up that nosotros as well need the nix terminator in the buffer)? The office just keeps writing, and it volition write over whatever is adjacent to buf on the stack.

From our prior pictures, we know that buf will be in checksum's stack frame, beneath the entry %rsp. Moreover, directly above the entry %rsp is the return accost! In this case, that is an address in master(). So, if checksum writes beyond the cease of buf, will overwrite the return address on the stack; if information technology keeps going farther, it will overwrite information in primary'southward stack frame.

Why is overwriting the render address dangerous? It means that a clever attacker tin can directly the program to execute any function inside the programme. In the case of attackme.cc, note the run_shell() role, which runs a string as a crush command. This has a lot of nefarious potential – what if we could cause that function to execute with a user-provided string? We could impress a lot of sad confront emojis to the shell, or, more than dangerously, run a command like rm -rf /, which deletes all data on the user'due south computer!

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

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

This is what the input in set on.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 code/text segment of the executable). assail.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 16-byte boundaries.

Executing this assault works as follows:

          $ ./attackme.unsafe "$(true cat attack.txt)" OWNED OWNED OWNED Endemic OWNED Owned sh: 7: ��5��: not constitute Segmentation fault (core dumped)                  
The cat assault.txt crush command elementary pastes the contents of the attack.txt file into the cord we're passing to the program. (The quotes are required to make sure our attack payload is processed as a single string even if it contains spaces.)

Summary

Today, nosotros concluded our brief tour of associates language and the depression-level concepts of plan execution.

We get-go looked at control menses in assembly, where instructions change what other instructions the processor executes next. In many cases, control flow starting time involves a flag-setting instruction so a conditional branch based on the values of the flags register. This allows for conditional statements and loops.

Function calls in assembly are governed by the calling convention of the architecture and operating system used: information technology determines which registers agree specific values such as arguments and return values, which registers a role may modify, and where on the stack sure information (such every bit the return address) is stored.

Nosotros also understood in more detail how the stack segment of retention is structured and managed, and discussed how it grows and shrinks. Finally, nosotros looked into how the very well-defined memory layout of the stack tin get a danger if a program is compromised through a malicious input: by carefully crafting inputs that overwrite office of the stack memory via a buffer overflow, nosotros tin change important data and cause a plan to execute arbitrary code.

In Lab three, you will craft and execute buffer overflow attacks on a plan yourself!