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

» Lecture video (Dark-brown ID required)
» Lecture code
» Post-Lecture Quiz (due 6pm Monday, February 24).

Associates, continued

Final time, we looked at assembly code and adult an intuition for how to read assembly linguistic communication instructions. But all programs we looked at independent just direct control flow, significant that the assembly instructions simply execute one after some other until the processor hits the ret education. Real programs contain provisional (if) statements, loops (for, while), and function calls. Today, we will understand how those concepts in the C language translate into assembly, and then build upwards an understanding of the resulting retention layout that reveals how a unsafe course of reckoner security attacks is enabled past seemingly innocuous C programs.

Command Flow

Your computer's processor is incredibly dumb: given the retentiveness accost of an instruction, it goes and executes that instruction, then executes the side by side instruction in retention, and so the adjacent, etc., until either there are no more than instructions to run. Control flow instructions alter that default behavior past changing where in memory the processor gets its adjacent teaching from.

The role of the %rip register

The %rip register on x86-64 is a special-purpose annals that always holds the retentivity address of the next instruction to execute in the program's code segment. The processor increments %rip automatically subsequently each instruction, and control catamenia instructions like branches set the value of %rip to modify the side by side education.
Perhaps surprisingly, %rip also shows up when an assembly program refers to a global variable. See the sidebar nether "Addressing modes" beneath to sympathize how %rip-relative addressing works.

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

A branch instruction jumps to the instruction following a label in the assembly programme. Retrieve that labels are lines that terminate with a colon (due east.g., .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 disassemble such a file (objdump -d FILE), you volition see memory addresses as the branch target instead.

Here is an case 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 3rd and eighth (last) lines both contain branch instructions.

At that place are two kinds of branches: unconditional and conditional. The jmp or j instruction (line 8) executes an unconditional branch and control menses e'er jumps to the branch target (hither, .L1). All other branch instructions are conditional: they simply co-operative if some condition holds. That status is represented by condition flags that are prepare as a side effect of every arithmetic operation the processor runs. In the example program above, the pedagogy that sets the flags is cmpl, which is a "compare" instruction that the processor internally executes as a subtraction of its starting time argument from its second argument, setting the flags and throwing away the effect.

Arithmetics instructions modify part of the %rflags register. The almost commonly used flags are:

  • ZF (cypher flag): set iff the issue was zero.
  • SF (sign flag): set iff the result, when considered as a signed integer, was negative, i.eastward., iff most significant bit (the sign fleck) of the upshot was one.
  • CF (carry flag): set iff the result overflowed when considered an unsigned value (i.e., the result was greater than iiWestward-1 for a value of width W bytes).
  • OF (overflow flag): fix iff the consequence overflowed when considered a signed value (i.east., the issue was greater than 2W-1-1 or less than –2W-one for a value of width W bytes).
Although a few instructions let you load specific flags into the flag register, code usually accesses flags via a conditional jump or a provisional move pedagogy.

You volition often meet the test and cmp instructions earlier a conditional branch. As mentioned in a higher place, these operations perform arithmetic just throw abroad the effect (rather than storing it in the destination annals), but prepare the flags. test performs binary AND, while cmp performs subtraction, and both set the flags according to the result.

Beneath is a table of all co-operative instructions on the x86-64 compages and the flags they look at to decide whether to branch and execute the next didactics at the branch target, or whether to continue execution with the side by side sequential pedagogy after the branch.

Instruction Mnemonic C example Flags
j (jmp) Jump break; (Unconditional)
je (jz) Jump if equal (zero) if (10 == y) ZF
jne (jnz) Jump if non equal (nonzero) if (ten != y) !ZF
jg (jnle) Leap if greater if (x > y), signed !ZF && !(SF ^ OF)
jge (jnl) Spring if greater or equal if (10 >= 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 (10 > y), unsigned !CF && !ZF
jae (jnb) Bound if above or equal if (ten >= y), unsigned !CF
jb (jnae) Jump if below if (10 < y), unsigned CF
jbe (jna) Leap if below or equal if (x <= y), unsigned CF || ZF
js Jump if sign bit if (x < 0), signed SF
jns Leap if not sign bit if (x >= 0), signed !SF
jc Jump if conduct bit North/A CF
jnc Jump if non comport bit Due north/A !CF
jo Jump if overflow scrap N/A OF
jno Jump if non overflow bit N/A !OF
Loops

Conditional branch instructions and flags are sufficient to back up both conditional statements (if (...) { ... } else { ... } blocks in C) and loops (for (...) { ... }, while (...) { ... }, and practice { ... } while (...)). For a conditional, the branch either jumps if the condition is true (or simulated, depending on how the compiler lays out the assembly) and continues execution otherwise. For a loop, the associates volition comprise a provisional branch at the stop of the loop body that checks the loop condition; if it is even so satisfied, the branch jumps dorsum to a characterization (or address) at the top of the loop.

When y'all see a conditional branch in associates code whose target is a characterization or accost above the branching pedagogy, it is nearly always a loop.

Consider the instance in controlflow02.due south, and the corresponding program in controlflow02.c. Let'south focus on the associates code following the characterization:

          .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 tin infer this from the fact that these registers are the only ones that appear in a comparison.) The pedagogy above cmpq increments the loop variable by iv every time the loop executes. Finally, loop'due south body consists of the 2 instructions above the addq $iv, %rdx instruction: the commencement dereferences a arrow in %rdx and puts the value at the memory accost it points to into register %rcx, and the second adds that value to the contents of %rax. Since %rax does not change before the conditional co-operative, 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

Nosotros have seen a few means in which assembly instruction'south operands can exist written already. In particular, the loop example contains (%rdx), which dereferences the address stored in register %rdx.

The total, general course of a memory operand is outset(base, index, scale), which refers to the address offset + base of operations + index*scale. In 0x18(%rax, %rbx, iv), %rax is the base, 0x18 the offset, %rbx the alphabetize, and 4 the scale. The get-go (if used) must be a constant and the base and index (if used) must exist registers; the scale must exist either 1, 2, 4, or eight. In other words, if we write this as N(%reg1, %reg2, M), the address computed is %reg1 + Due north + %reg2 * M.

The default offset, base, and alphabetize are 0, and the default scale is 1, and instructions omit these parts if they have their default values. You lot will near often encounter instructions of the course offset(%register), which perform uncomplicated add-on to the address in the register and then dereference the effect. Only occasionally, you may come up across instructions that apply both base and index registers, or which use the full general class.

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

Type Example syntax Value used
Annals %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 proper name to an accost when creating the executable)
symbol_name(%rip) %rip-relative addressing for global (run across below)
symbol_name+4(%rip) Unproblematic computations on symbols are immune
(the compiler resolves the computation when creating the executable)
(%rax) Value stored at address in %rax
0x4(%rax) Value stored at address %rax + 4
(%rax,%rbx) Value stored at address %rax + %rbx
(%rax,%rbx,iv) Value stored at address %rax + %rbx*4
0x18(%rax,%rbx,4) Value stored at accost %rax + 0x18 + %rbx*four
%rip-relative addressing for global variables

x86-64 code ofttimes refers to globals using %rip-relative addressing: a global variable named a is referenced every bit a(%rip). This style of reference supports position-independent code (PIC), a security characteristic. 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 betoken and loads all instructions and globals relative to that starting betoken. The PIE'due south instructions never refer to global variables using straight 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 point! For case, consider an instruction located at (starting-signal + 0x80) that loads a variable g located at (starting-point + 0x1000) into %rax. In a not-PIE, the instruction might be written as movq m, %rax; but this relies on thousand having a fixed accost. In a PIE, the instruction might be written movq chiliad(%rip), %rax, which works out without having to know the starting address of the program'due south code in retention at compile fourth dimension (instead, %rip contains a number some known number of bytes autonomously from the starting point, so whatsoever accost relative to %rip is too relative to the starting bespeak).

At starting betoken… The mov educational activity is at… The adjacent teaching is at… And g 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

Nosotros discussed conditionals and loops, but in that location is a third blazon of command flow: function calls. Assembly linguistic communication has no functions, just sequences of instructions. Function calls therefore translate into command catamenia involving branches, but we need a bit more than that: functions can accept arguments, and the compiler better make sure that the argument are available later on it jumps to a function's instructions!

Defining how function calls and returns work, where a function can await to notice its arguments, and where it must place its return value is the business concern of a calling convention. A calling convention governs how functions on a particular architecture and operating system interact in assembly lawmaking. This includes rules on how function arguments are placed, where return values get, what registers functions may employ, how they may classify local variables, and others.

Why exercise we demand calling conventions?

Calling conventions ensure that functions compiled by different compilers tin can interoperate, and they ensure that operating systems can run code from different programming languages and compilers. For example, you lot 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 lawmaking understand its calling convention, and because the gcc and clang compilers' authors concur on the calling convention to employ.

Some aspects of a calling convention are derived from the instruction set itself and embedded into the compages (east.g., via special-purpose registers modified as a side-result of certain instructions), but some are conventional, pregnant they wre decided upon by people (for example, at a convention), and may differ across operating systems and compilers.

Programs call01.c to call06.c and their corresponding assembly in call01.s to call06.south assistance us effigy out the calling convention for x86-64 on the Linux operating system!

Some bones rules are:

  • The get-go half-dozen function arguments are passed in registers %rdi, %rsi, %rdx, %rcx, %r8, and %r9 (in this guild; see the annals listing from last lecture).
  • The seventh and subsequent arguments are passed on the stack (encounter more beneath).
  • The render value is passed in register %rax.
There are actually several other rules, which govern things like how to pass information structures that are larger than a register (eastward.yard., a struct), floating point numbers, etc. If you're interested, you can find all the details in the AMD64 ABI, section 3.2.3.

call04.southward illustrates the dominion about the beginning six arguments all-time: they are passed straight in registers. Other examples (e.thousand., call01 to call03) are compiled without optimizations and have somewhat more than circuitous 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 retentiveness 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 it tin can just skip actually storing them, and so information technology just uses the registers containing the arguments directly.

The Stack

You volition call back the stack segment of retention from earlier lectures: information technology is where all variables with automated lifetime are stored. These include local variables declared within functions, simply importantly as well function arguments.

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

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

The %rsp register is called the stack pointer. It always points to the "height" of the stack, which is at the everyman (leftmost) accost current used in the stack segment. At the get-go of the function, any memory to the left of where %rsp points is therefore unused; any retentiveness to the right of where it points is used. This explains why the code stores the argument at addresss %rsp - four: it'due south the first 4-byte slot available on the stack, to the left of the currently used memory.

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

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

The memory on the correct of the %rsp address at the bespeak of f() being called (nosotros refer to this equally "entry %rsp") is the caller'due south stack frame (ruddy below), and the memory to its left is the callee's stack frame.

The arguments and local variables of f() live within f()'s stack frame. Subsequent arguments (second, third, fourth, etc.) are stored at afterward lower addresses below %rsp (run across call02.s and call03.s for examples with more arguments), followed eventually by whatsoever local variables in the caller.

How does %rsp modify?

The convention is that %rsp e'er points to the lowest (leftmost) stack address that is currently used. This means that when a office declares a new local variable, %rsp has to move down (left) and if a role returns, %rsp has to move up (right) and dorsum to where information technology was when the role was originally called.

Moving %rsp happens in two ways: explicit modification via arithmetics instructions, and implicit modification as a side upshot of special instructions. The onetime happens when the compiler knows exactly how many bytes a function requires %rsp to motility by, and involves instructions like subq $0x10, %rsp, which moves the stack arrow down by xvi bytes. The latter, side-result modification happens when instruction push and pop run. These instructions write the contents of a register onto the stack retentivity immediately to the left of the electric current %rsp and besides modify %rsp to indicate to the outset of this new data. For example, pushq %rax would write the eight bytes from register %rax at accost %rsp - 8 and set %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.s 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 you lot can write functions in C that take whatsoever number of arguments! The calling convention says that the beginning six arguments max exist passed in registers, but that the viith and in a higher place arguments are e'er passed in memory on the stack. Specifically, these arguments go into the caller'due south stack frame, so they are stored above the entry %rsp at the point where the role is called (come across call05.{c,s} and call06.{c,s}).

Return Address

Equally a function executes, it eventually reaches a ret instruction in its assembly. The consequence of ret is to render to the caller (a grade a command flow, as the next educational activity needs to alter). Only how does the processor know what educational activity to execute adjacent, and what to set up %rip to?

It turns out that the stack plays a part here, also. In a nutshell, each function call stores the return accost every bit the very first (i.east., rightmost) data in the callee'southward stack frame. (If the part called takes more than half-dozen arguments, the return address is to the left of the 7th argument in the caller's stack frame.)

The stored return address makes information technology possible for each role to know exactly where to continue execution once information technology returns to its caller. (Yet, storing the return address on the stack also has some dangerous consequences, as we will come across shortly.)

Nosotros tin now define the full function entry and exit sequence. Both the caller and the callee accept responsibilities in this sequence.

To prepare for a office telephone call, the caller performs the post-obit tasks:

  1. The caller stores the kickoff six arguments in the corresponding registers.

  2. If the callee takes more than vi arguments, or if some of its arguments are big, the caller must store the surplus arguments on its stack frame (in increasing guild). The viithursday argument must be stored at (%rsp) (that is, the top of the stack) when the caller executes its callq education.

  3. The caller saves whatsoever caller-saved registers (see last lecture'due south listing). These are registers whose values the callee might overwrite, simply which the caller needs to retain for later on utilise.

  4. The caller executes callq FUNCTION. This has an issue like pushq $NEXT_INSTRUCTION; jmp FUNCTION (or, equivalently, subq $viii, %rsp; movq $NEXT_INSTRUCTION, (%rsp); jmp FUNCTION), where NEXT_INSTRUCTION is the address of the instruction immediately following callq.

To return 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 accost from the stack and jumps to that accost (because the teaching writes it into the special %rip register).

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

Base Pointers and the %rbp Register

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

%rbp holds the address of the base of the current stack frame: that is, the address of the rightmost (highest) address that points to a value withal part of the current stack frame. This corresponds the rightmost address of an object in the callee's stack, and to the first address that isn't part of an statement to the callee or one of its local variables. It 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 of operations" (= bottom). The %rbp register maintains this value for the whole execution of the function (i.eastward., the office may not overwrite the value in that annals), even as %rsp changes.

This scheme has the advantage that when the role exits, it can restore its original entry %rsp past loading it from %rbp. In addition, it also facilitates debugging because each role stores the old value of %rbp to the stack at its signal of entry. The 8 bytes holding the caller'due south %rbp are the very first thing stored inside the callee's stack frame, and they are right beneath the render address in the caller's stack frame. This mean that the saved %rbps form a concatenation that allows each office to locate the base of its caller's stack frame, where it will notice the %rbp of the "yard-caller's" stack frame, etc. The backtraces you see in GDB and in Accost Sanitizer error messages are generated precisely using this concatenation!

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

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

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

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

    Note, also, that the value stored at (%rbp) is the caller'southward %rbp, and the value stored at 8(%rbp) is the render address. This information tin be used to trace backwards by debuggers (a process called "stack unwinding").

  3. The function ends with movq %rbp, %rsp; popq %rbp; retq, or, equivalently, leave; retq. This sequence is the last thing the callee does, and it restores the caller'due south %rbp and entry %rsp before returning.

You can notice an example of this in call07.south. Lab 3 also uses the %rbp-based calling convention, so make certain you go on the extra 8 bytes for storing the caller's %rbp on the stack in mind!

Buffer overflow attacks

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

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

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

Consider the lawmaking in attackme.cc. This program computes checksums of strings provided to it as command line arguments. You don't demand to understand in deep detail what it does, but observe that the checksum() part uses a 100-byte stack-allocated buffer (as part of the buf union) to hold the input cord, which it copies into that buffer.

A sane execution of attackme might await like this:

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

Only what if the user provides an input string longer than 99 characters (remember that we likewise need the zero terminator in the buffer)? The office just keeps writing, and it will write over whatever is side by side to buf on the stack.

From our prior pictures, nosotros know that buf will be in checksum's stack frame, below the entry %rsp. Moreover, directly above the entry %rsp is the render address! In this case, that is an address in main(). So, if checksum writes beyond the cease of buf, will overwrite the render address on the stack; if it keeps going further, information technology will overwrite data in chief's stack frame.

Why is overwriting the return address dangerous? It means that a clever attacker can direct the program to execute whatever function within the program. In the case of attackme.cc, note the run_shell() part, which runs a string as a shell command. This has a lot of nefarious potential – what if nosotros could cause that role to execute with a user-provided string? We could print a lot of sad confront emojis to the shell, or, more dangerously, run a control similar rm -rf /, which deletes all information on the user's computer!

If we run ./attackme.unsafe (a variant of attackme with condom features added by mondern compilers to combat these attacks disabled), information technology 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 chip more unusual:
          $ ./attackme.dangerous sghfkhgkfshgksdhrehugresizqaugerhgjkfdhgkjdhgukhsukgrzufaofuoewugurezgureszgukskgreukfzreskugzurksgzukrestgkurzesi Segmentation error (core dumped)                  
The crash happens because the return address for checksum() was overwritten by garbage from our cord, which isn't a valid address. But what if we figure out a valid address and put it in exactly the right place in our string?

This is what the input in attack.txt does. Specifically, using GDB, I figured out that the accost 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 advisedly crafted "payload" that puts the value 0x400734 into the right bytes on the stack. The assault payload is 115 characters long considering we need 100 characters to overrun buf, 3 bytes for the malicious render accost, and 12 bytes of actress payload because stack frames on x86-64 Linux are aligned to 16-byte boundaries.

Executing this assault works as follows:

          $ ./attackme.unsafe "$(cat attack.txt)" Owned Owned OWNED Owned OWNED Endemic sh: 7: ��5��: non plant Segmentation mistake (cadre dumped)                  
The cat attack.txt beat control simple pastes the contents of the attack.txt file into the string we're passing to the program. (The quotes are required to make sure our assail payload is candy as a single string even if it contains spaces.)

Summary

Today, we concluded our brief tour of associates linguistic communication and the low-level concepts of programme execution.

We beginning looked at control flow in associates, where instructions change what other instructions the processor executes next. In many cases, control flow offset involves a flag-setting instruction and and then a conditional branch based on the values of the flags register. This allows for conditional statements and loops.

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

We also understood in more item how the stack segment of memory is structured and managed, and discussed how it grows and shrinks. Finally, nosotros looked into how the very well-defined retention layout of the stack can become a danger if a program is compromised through a malicious input: by advisedly crafting inputs that overwrite function of the stack retentiveness via a buffer overflow, we tin change important data and cause a program to execute arbitrary lawmaking.

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