L05 · Little-Endian, the Registers eip/ebp/esp, and Push/Pop

CS 161, Lesson 5, in 50 slides and code mode. It covers little-endian byte order, the three special 32-bit registers eip, ebp, and esp, and push and pop as decrement-then-write and read-then-increment, with a full esp trace table. It is anchored to textbook sections 2.4 to 2.6.

Subject: Computer Security · 43 slides · code lesson

Open the interactive version of this deck · Homework for this lesson

What this lesson covers

The lesson, slide by slide

1. Three Registers and a Stack

Title

CS 161 · Lesson 5 of 45

little-endian bytes · eip / ebp / esp · push and pop, mechanically

2. By the end of this lesson you can…

Objectives

  1. Read a 4-byte value out of a little-endian hex dump, and write a value back into bytes.
  2. State the job of eip, ebp, and esp and which memory region each points to.
  3. Explain why ebp is the high address of a frame and esp the low address.
  4. Trace push (decrement esp, then write) and pop (read, then increment esp) byte-exactly.
  5. Predict the final esp and register contents after a sequence of pushes and pops.

3. What survived from L04 · Numbers, the Toolchain, and 32-bit C Memory Layout?

Warm-up

Discussion prompt

Before we open L05 · Little-Endian, the Registers eip/ebp/esp, and Push/Pop: without looking back, what was the main idea of L04 · Numbers, the Toolchain, and 32-bit C Memory Layout, and what could you do by the end of it that you could not do before?

Hint: One sentence for the idea, one for the skill. If the second one is blank, that is the part to revisit.

Answer:

CS 161 Lesson 4 (50 slides, code mode): binary/hex/two's-complement, the source→process toolchain, ELF sections (⊕), and the four-section 32-bit C memory layout — code/static/heap/stack with grow directions. Anchored to textbook §2.1–2.3.

4. Little-Endian Bytes

Section

Part 1 · §2.4

5. Least-significant byte at the lowest address

Concept

x86 is little-endian: when a 4-byte word is stored, its least-significant byte goes to the lowest address and the most-significant byte to the highest.

word = 0x44332211   (stored at addresses 0x1000..0x1003)

 addr    byte
0x1000    0x11   <- least significant (lowest addr)
0x1001    0x22
0x1002    0x33
0x1003    0x44   <- most significant (highest addr)
AddressByteSignificance
0x10000x11least
0x10010x22—
0x10020x33—
0x10030x44most

6. Which is which, by Significance

Discrimination

Sort into buckets

Sort these by Significance, from memory, without looking back at Least-significant byte at the lowest address. Telling them apart on the spot is the skill; the table is only where the answer happens to be written down.

least
0x1000
—
0x1001; 0x1002
most
0x1003
g1
Significance is "least" for 0x1000 — that is what the table on "Least-significant byte at the lowest…" records, and it is the single property separating this group from the rest.
g2
Significance is "—" for 0x1001, 0x1002 — that is what the table on "Least-significant byte at the lowest…" records, and it is the single property separating this group from the rest.
g3
Significance is "most" for 0x1003 — that is what the table on "Least-significant byte at the lowest…" records, and it is the single property separating this group from the rest.

7. Why we still draw whole words on diagrams

Intuition

Bytes are physically little-endian, but on stack diagrams we usually write the whole word 0x44332211 in one 4-byte row to stay readable — the little-endianness is abstracted away.

Networking is the opposite: many protocols are big-endian ('network byte order'). When we reach the network unit, byte order flips — keep the distinction filed.

Ask yourself: when you craft exploit input later, will you type the target address most-significant-byte first, or least-significant-byte first?

8. Break it if you can: Why we still draw whole words on diagrams

Counterexample

Discussion prompt

Bytes are physically little-endian, but on stack diagrams we usually write the whole word 0x44332211 in one 4-byte row to stay readable — the little-endianness is abstracted away.

That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.

Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.

Answer:

Networking is the opposite: many protocols are big-endian ('network byte order'). When we reach the network unit, byte order flips — keep the distinction filed.

9. What has to happen first: Read a little-endian value from a dump

Ranking

Put in order

Put the moves of Read a little-endian value from a dump into the order they have to happen.

  1. Lowest address is the least-significant byte
  2. Assemble most-significant first to read the word
  3. Verify by storing it back

Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. 0xEF is at the lowest address, so it is the LSB; 0xDE at the highest is the MSB.

10. Read a little-endian value from a dump

Worked example

Memory dump (low -> high address):
0x2000:  EF  BE  AD  DE

Lowest address is the least-significant byte

Why: 0xEF is at the lowest address, so it is the LSB; 0xDE at the highest is the MSB.

Assemble most-significant first to read the word

Why: Reverse the byte order on read: DE | AD | BE | EF → 0xDEADBEEF.

AddressByteWord reading
0x2000EF(LSB)
0x2001BE
0x2002AD
0x2003DE(MSB) → 0xDEADBEEF

Verify by storing it back

Why: Storing 0xDEADBEEF little-endian puts EF at the lowest address — matches the dump. This is exactly how we'll write a return address in Lesson 8.

11. What each one costs: Read a little-endian value from a dump

Trade off

Comparison matrix

From Read a little-endian value from a dump: every row here is a choice with a cost. Fill the Byte column, then say which row you would actually pick and what you give up for it.

AddressByteWord reading
0x2000EF(LSB)
0x2001BE
0x2002AD
0x2003DE(MSB) → 0xDEADBEEF

12. Something is wrong here: typing an address big-endian into exploit input

Anomaly

Predict first

A student writes this, and it looks reasonable:

Goal: write 0xDEADBEEF over a 4-byte slot.

It is wrong. Say what breaks — and say it before you turn the page.

Correct: Types the value in 'reading' order — but memory is little-endian, so this stores the bytes reversed and the slot holds 0xEFBEADDE.

Goal: write 0xDEADBEEF over a 4-byte slot.

Why: Types the value in 'reading' order — but memory is little-endian, so this stores the bytes reversed and the slot holds 0xEFBEADDE.

13. Trap: typing an address big-endian into exploit input

Trap

The trap

Goal: write 0xDEADBEEF over a 4-byte slot.

Send bytes DE AD BE EF (as written, left to right)

Why: Types the value in 'reading' order — but memory is little-endian, so this stores the bytes reversed and the slot holds 0xEFBEADDE.

The fix

Goal: write 0xDEADBEEF over a 4-byte slot.

Send bytes EF BE AD DE (least-significant first)

Why: §2.4: the lowest address takes the LSB. Input bytes go to increasing addresses, so the LSB must come first to land 0xDEADBEEF correctly.

14. The Registers

Section

Part 2 · §2.5

15. Three special registers

Concept

Registers store a word on the CPU, have no memory address, and are named. Three matter for this unit:

RegisterNameHolds / points to
eipinstruction pointeraddress of the instruction now executing (code region)
ebpbase pointerTOP (highest addr) of the current stack frame
espstack pointerBOTTOM (lowest addr) of the current stack frame

The e means 'extended' — these are the 32-bit forms. On a 64-bit CPU they become rip/rbp/rsp, but we stay 32-bit.

16. Fill in: Name for Three special registers

Comparison

Comparison matrix

From Three special registers: refill the Name column from what you know. The rest of the table is as it appeared.

RegisterNameHolds / points to
eipinstruction pointeraddress of the instruction now executing (code region)
ebpbase pointerTOP (highest addr) of the current stack frame
espstack pointerBOTTOM (lowest addr) of the current stack frame

17. Top is high, bottom is low (yes, really)

Intuition

Because the stack grows down, the 'top of the frame' (where it began) is the highest address — that's ebp. The 'bottom' (the growing edge) is the lowest address — that's esp.

high addr
  |  ...caller...
  |  [ ebp ] ---> top of frame
  |   local
  |   local
  |  [ esp ] ---> bottom of frame
low addr
RegisterEdge of frameAddress
ebptophigher
espbottomlower

Sanity check from the book: which region do eip, ebp, esp point into? eip → code; ebp and esp → stack.

18. Break it if you can: Top is high, bottom is low (yes, really)

Counterexample

Discussion prompt

Because the stack grows down, the 'top of the frame' (where it began) is the highest address — that's ebp. The 'bottom' (the growing edge) is the lowest address — that's esp.

That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.

Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.

Answer:

Sanity check from the book: which region do eip, ebp, esp point into? eip → code; ebp and esp → stack.

19. The general-purpose registers

Concept

Six more general registers appear in our assembly: eax, ebx, ecx, edx, esi, edi. For this unit you mostly need to recognize them and know eax conventionally carries return values.

RegisterTypical use
eaxreturn value / accumulator
esi / edisource / destination index (string copies)
ebx, ecx, edxgeneral scratch

20. Fill in: Typical use for The general-purpose registers

Comparison

Comparison matrix

From The general-purpose registers: refill the Typical use column from what you know. The rest of the table is as it appeared.

RegisterTypical use
eaxreturn value / accumulator
esi / edisource / destination index (string copies)
ebx, ecx, edxgeneral scratch

21. Push & Pop

Section

Part 3 · §2.6

22. push = decrement, then write

Concept

Storing a value on the stack is two micro-steps: first allocate space by decrementing esp by 4, then write the value there.

push %eax   is equivalent to:
    esp = esp - 4     # allocate 4 bytes (stack grows down)
    *(esp) = eax      # write the value into the new slot
StepespAction
beforeS—
allocateS − 4make room
writeS − 4*(esp) = value

23. What each one costs: push = decrement, then write

Trade off

Comparison matrix

From push = decrement, then write: every row here is a choice with a cost. Fill the esp column, then say which row you would actually pick and what you give up for it.

StepespAction
beforeS—
allocateS − 4make room
writeS − 4*(esp) = value

24. pop = read, then increment

Concept

Removing a value reverses it: read the value at esp into a register, then deallocate by incrementing esp by 4.

pop %eax   is equivalent to:
    eax = *(esp)      # read the top value into a register
    esp = esp + 4     # deallocate (the value is now BELOW esp)
StepespAction
beforeSvalue at *(S)
readSeax = *(S)
deallocateS + 4old value now below esp = undefined

Note: pop does not erase the bytes — it just moves esp above them. Anything below esp is 'undefined' and may be overwritten by the next push.

25. Fill in: esp for pop = read, then increment

Comparison

Comparison matrix

From pop = read, then increment: refill the esp column from what you know. The rest of the table is as it appeared.

StepespAction
beforeSvalue at *(S)
readSeax = *(S)
deallocateS + 4old value now below esp = undefined

26. Plan first: Full trace: two pushes and a pop

Step zero

Discussion prompt

Full trace: two pushes and a pop — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.

Hint: It starts with: push $0xAA

Answer:

  1. push $0xAA
  2. push $0xBB
  3. Verify the end state

27. Full trace: two pushes and a pop

Worked example

; start with esp = 0xbffff020
push $0xAA
push $0xBB
pop  %eax

push $0xAA

Why: esp = 0xbffff020 − 4 = 0xbffff01c, then *(0xbffff01c) = 0xAA.

push $0xBB

Why: esp = 0xbffff01c − 4 = 0xbffff018, then *(0xbffff018) = 0xBB.

pop %eax

Why: eax = *(0xbffff018) = 0xBB, then esp = 0xbffff018 + 4 = 0xbffff01c.

After instructionespeaxmemory written
start0xbffff020——
push $0xAA0xbffff01c—*(0xbffff01c)=0xAA
push $0xBB0xbffff018—*(0xbffff018)=0xBB
pop %eax0xbffff01c0xBB(0xBB now below esp)

Verify the end state

Why: Final esp = 0xbffff01c, eax = 0xBB. The 0xAA we pushed first is still in memory at 0xbffff01c but is now the live top-of-stack slot again — confirm by tracing every 4-byte step.

28. Which is which, by esp

Discrimination

Sort into buckets

Sort these by esp, from memory, without looking back at Full trace: two pushes and a pop. Telling them apart on the spot is the skill; the table is only where the answer happens to be written down.

0xbffff020
start
0xbffff01c
push $0xAA; pop %eax
0xbffff018
push $0xBB
g1
esp is "0xbffff020" for start — that is what the table on "Full trace: two pushes and a pop" records, and it is the single property separating this group from the rest.
g2
esp is "0xbffff01c" for push $0xAA, pop %eax — that is what the table on "Full trace: two pushes and a pop" records, and it is the single property separating this group from the rest.
g3
esp is "0xbffff018" for push $0xBB — that is what the table on "Full trace: two pushes and a pop" records, and it is the single property separating this group from the rest.

29. Something is wrong here: push writes first, then moves esp

Anomaly

Predict first

A student writes this, and it looks reasonable:

Write *(0x1000) = eax, THEN set esp = 0x0FFC

It is wrong. Say what breaks — and say it before you turn the page.

Correct: Writes at the OLD esp, clobbering the current top-of-stack value before allocating.

push %eax with esp = 0x1000.

Why: Writes at the OLD esp, clobbering the current top-of-stack value before allocating.

30. Trap: push writes first, then moves esp

Trap

The trap

push %eax with esp = 0x1000.

Write *(0x1000) = eax, THEN set esp = 0x0FFC

Why: Writes at the OLD esp, clobbering the current top-of-stack value before allocating.

The fix

push %eax with esp = 0x1000.

Decrement first: esp = 0x0FFC, THEN write *(0x0FFC) = eax

Why: §2.6: allocate before you write. Order matters — decrement-then-write protects the existing top, write-then-decrement would corrupt it.

31. Which of these survive contact with L05 · Little-Endian, the Registers…?

Two truths and a lie

Sort into buckets

Some of these hold up and some are the exact mistakes this lesson is built to prevent. Sort them.

Holds up
Networking is the opposite: many protocols are big-endian ('network byte order'). When we reach the network unit, byte order flips — keep the distinction filed.; Registers store a word on the CPU, have no memory address, and are named. Three matter for this unit:; Sanity check from the book: which region do eip, ebp, esp point into? eip → code; ebp and esp → stack.
Breaks
Goal: write 0xDEADBEEF over a 4-byte slot.; Write *(0x1000) = eax, THEN set esp = 0x0FFC
sound
These are stated as this lesson states them — each one survives the edge cases L05 · Little-Endian, the Registers eip/ebp/esp, and Push/Pop puts it through.
flawed
Each of these is lifted from a trap in this deck: reasonable-sounding, and wrong in a way that only shows up once you rely on it.

32. Rebuild the recipe: The Lesson-5 recipe

Ranking

Put in order

These are the steps of The Lesson-5 recipe, scrambled. Put them back in order before the next slide shows you.

  1. Little-endian read: lowest address = LSB; reverse bytes to get the word.
  2. Little-endian write: emit the LSB first into increasing addresses.
  3. Registers: eip → code; ebp → top (high) of frame; esp → bottom (low) of frame.
  4. push: esp −= 4, then *(esp) = value.
  5. pop: reg = *(esp), then esp += 4 (old value now below esp = undefined).
  6. Trace discipline: every push/pop moves esp by exactly 4; write the value AND the new esp each step.

Why: This is the order the recipe itself gives. Recalling the sequence without the slide in front of you is the difference between recognising the method and being able to run it — most of what goes wrong in practice is a step done out of turn.

33. The Lesson-5 recipe

Pattern

  1. Little-endian read: lowest address = LSB; reverse bytes to get the word.
  2. Little-endian write: emit the LSB first into increasing addresses.
  3. Registers: eip → code; ebp → top (high) of frame; esp → bottom (low) of frame.
  4. push: esp −= 4, then *(esp) = value.
  5. pop: reg = *(esp), then esp += 4 (old value now below esp = undefined).
  6. Trace discipline: every push/pop moves esp by exactly 4; write the value AND the new esp each step.

34. Where does it stop working: The Lesson-5 recipe

Edge cases

Discussion prompt

The Lesson-5 recipe works on the cases you have just seen. Push it to the edge: what is the most degenerate input it still handles — empty, zero, one item, everything equal — and what is the first case where it stops being true? Name the case, not just "it breaks".

Hint: Try the smallest legal input, then the largest, then the one where two things collide. Methods are specified at their edges; the middle takes care of itself.

Answer:

  1. Little-endian read: lowest address = LSB; reverse bytes to get the word.
  2. Little-endian write: emit the LSB first into increasing addresses.
  3. Registers: eip → code; ebp → top (high) of frame; esp → bottom (low) of frame.
  4. push: esp −= 4, then *(esp) = value.
  5. pop: reg = *(esp), then esp += 4 (old value now below esp = undefined).
  6. Trace discipline: every push/pop moves esp by exactly 4; write the value AND the new esp each step.

35. Checkpoint 1 — read the word

Check

A 4-byte slot at 0x3000 contains the bytes (low→high): 00 10 04 08. Solve on paper.

Check your understanding

What 32-bit value is stored there?

  • A. 0x08041000 (correct)
  • B. 0x00100408
  • C. 0x10000804
  • D. 0x04081000

Answer: A

Why: Little-endian: the lowest address holds the least-significant byte. Bytes low→high are 00,10,04,08, so the MSB (0x08) is last. Reading most-significant first gives 08 04 10 00 = 0x08041000.

Why B tempts people
This reads the bytes in address order without reversing — that treats the dump as big-endian.
Why C tempts people
Bytes are shuffled arbitrarily; little-endian is a clean full reversal of byte order, not a swap of pairs.
Why D tempts people
This reverses only some bytes; the correct reading reverses all four to 08 04 10 00.

36. Checkpoint 2 — trace esp

Check

esp starts at 0xbffff100. Execute: push, push, push, pop. (Values don't matter here.)

Check your understanding

What is esp afterward?

  • A. 0xbffff0f8 (correct)
  • B. 0xbffff0f4
  • C. 0xbffff100
  • D. 0xbffff10c

Answer: A

Why: Three pushes lower esp by 3×4 = 12 (0xbffff100 → 0xbffff0f4), and one pop raises it by 4 (→ 0xbffff0f8). Net is −8 from the start.

Why B tempts people
This counts the three pushes (−12) but forgets the pop adds 4 back.
Why C tempts people
This assumes pushes and pops cancel one-for-one; there are three pushes and only one pop.
Why D tempts people
This moves esp the wrong direction — pushes DECREMENT esp (stack grows down), not increment.

37. Rule out three: Checkpoint 3 — which register, and why

Elimination

Eliminate the wrong options

Which register points to the HIGHEST address of the current frame, and what is it called?

3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.

  • A. ebp, the base pointer — the top (highest address) of the frame.
  • B. esp, the stack pointer — it always sits at the highest address.
  • C. eip, the instruction pointer — it tracks the top of the stack.
  • D. eax, the accumulator — it stores the frame base.

Survives elimination: A

Why: §2.5: ebp (base pointer) marks the top of the frame, which — because the stack grows down — is the highest address. esp marks the bottom (lowest address), and eip is the instruction pointer into the code region.

38. Checkpoint 3 — which register, and why

Check

You're reading a stack diagram drawn with high addresses at the top.

Check your understanding

Which register points to the HIGHEST address of the current frame, and what is it called?

  • A. ebp, the base pointer — the top (highest address) of the frame. (correct)
  • B. esp, the stack pointer — it always sits at the highest address.
  • C. eip, the instruction pointer — it tracks the top of the stack.
  • D. eax, the accumulator — it stores the frame base.

Answer: A

Why: §2.5: ebp (base pointer) marks the top of the frame, which — because the stack grows down — is the highest address. esp marks the bottom (lowest address), and eip is the instruction pointer into the code region.

Why B tempts people
esp points to the BOTTOM of the frame, the lowest address — it moves as you push/pop.
Why C tempts people
eip points into the code region at the current instruction, not into the stack at all.
Why D tempts people
eax is a general-purpose register for return values; it has no role marking the frame.

39. Misconceptions to drop now

Concept

40. Synthesis — the moving parts of a function call

Concept

41. Primary sources & where to read more

Concept

42. Connect it up: L05 · Little-Endian, the Registers eip/ebp/esp, and Push/Pop

Connect it up

Draw it

One page, no notation unless you need it: draw how these connect — Little-Endian Bytes · The Registers · Push & Pop. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.

43. Recap — Lesson 5

Recap

You can read and write little-endian words, name what eip/ebp/esp point to, and trace push (down-then-write) and pop (read-then-up) to an exact esp and register value.

Idea§Keep this
Little-endian2.4Lowest address = least-significant byte
eip2.5current instruction (code region)
ebp2.5top of frame = highest address
esp2.5bottom of frame = lowest address
push / pop2.6−4 then write / read then +4

Sources

  1. CS 161 Computer Security Textbook §2.4 (Little-endian words), §2.5 (Registers), §2.6 (Stack: pushing and popping) — Wagner, Weaver, Kao, Shakir, Law & Ngai, UC Berkeley
  2. Intel 64 and IA-32 Architectures Software Developer's Manual, Vol. 1 — Intel, §3.4 (registers) and §6.2 (stack, PUSH/POP semantics) — the authoritative spec for 32-bit register and stack behavior
  3. On Holy Wars and a Plea for Peace — D. Cohen, IEEE Computer, 1981 — coined 'little-endian' / 'big-endian'

Want this taught 1-on-1? Alexander tutors Computer Security — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108