OllyDbg Reverse Engineering Series - Part 6 of 60
OllyDbg Reverse Engineering Series
| Part | Title | Status |
|---|---|---|
| 1 | What is Reverse Engineering? And Why Am I Using OllyDbg? | |
| 2 | OllyDbg Interface Tour - Every Window Explained | |
| 3 | Number Systems - Hex, Decimal, Binary with Real Examples | |
| 4 | CPU Registers - What They Are and Why They Matter | |
| 5 | Your First Look at Assembly - Reading Code Like a Reverser | |
| 6 - You are here | MOV, PUSH, POP Moving Data Around | |
| 7–12 | Phase 2 continuing… | |
| 13–60 | More phases coming… |
Welcome to Phase 2
Phase 1 was setup. We got OllyDbg running, we understood number systems, registers, and got a first look at assembly.
Phase 2 is different. We load a real executable. We step through real instructions. We connect what we see in OllyDbg to what actually happens inside the CPU.
By the end of Phase 2 (Parts 6–12), you will be able to look at a disassembled function and understand what it is doing not guess, actually understand.
Let’s go.
Our Target Executable crackme.exe
For all of Phase 2, we are using a crackme we compiled ourselves. Here is why that is a good idea you know what the C code does before you look at the assembly. So when you see a MOV in OllyDbg, you already know what it is supposed to be doing. That connection is how you build pattern recognition fast.
I was confused about crackmes at first. I downloaded CrueHead’s crackme and it had anti-debug protection and encryption. Great for later. Not great for learning MOV. So we built our own.
Here is the C code. Save it as crackme.c:
#include <stdio.h>
#include <string.h>
int check_password(char *input) {
char *secret = "iotsec";
return strcmp(input, secret);
}
int main() {
char buf[64];
printf("Enter password: ");
scanf("%63s", buf);
if (check_password(buf) == 0) {
printf("Correct!\n");
} else {
printf("Wrong!\n");
}
return 0;
}
Simple. One function that checks a password. One main function that takes input and prints the result.
Compiling on Kali Linux (or any Ubuntu/Debian):
First install the cross-compiler:
sudo apt install gcc-mingw-w64-i686
Then compile:
i686-w64-mingw32-gcc -o crackme.exe crackme.c -m32
The i686 part is important. That gives you a 32-bit Windows PE executable. OllyDbg works with 32-bit. Copy crackme.exe to your Windows machine and load it in OllyDbg via File → Open.
Finding Our Code in OllyDbg
When OllyDbg loads the exe, it stops at the entry point. But that is not our main(). MinGW adds CRT startup code before main ignore that for now.
To find our actual code, right click in the disassembler → Search for → All referenced text strings. You will see strings like "iotsec", "Enter password:", "Correct!", "Wrong!".
Double click on "iotsec" and it jumps you straight into check_password. Double click on "Enter password:" and it jumps you into main.
Double Click On IotSec
This is how reversers navigate large binaries. You search for strings you expect to find, then work backwards from there.
Concept 1 - The Function Prologue (The Pattern You Will See Everywhere)
When I jumped to the "iotsec" string, the first thing I scrolled up to see was this:
00821558 PUSH EBP
00821559 MOV EBP, ESP
0082155B SUB ESP, 28
I had no idea what this was the first time. Now I cannot unsee it. This is the function prologue the 3-line setup that appears at the start of every single function. Every one. Without exception.
Here is what it means, in the way I understood it after thinking about it for a while.
Imagine two people sharing an office desk. Person A was working there. Their stuff is arranged a certain way. Now Person B needs to use the same desk.
Before Person B starts working, they:
- Take a photo of how the desk looked - so they can restore it later. That is
PUSH EBP. We save the previous function’s EBP onto the stack before we overwrite it. - Mark their own starting position - Person B puts their own reference marker down. That is
MOV EBP, ESP. EBP now points to the current stack position our fixed reference point for this function. - Clear some space on the desk - make room for their own papers. That is
SUB ESP, 28. ESP moves down by 28 bytes (hex), carving out space for local variables.
The key thing I had to wrap my head around: EBP does not move after this. ESP moves up and down constantly. EBP stays fixed. Think of EBP as a nail in the wall. You measure everything from that nail.
After the prologue:
High address
| |
| old EBP | ← PUSH EBP saved it here
| |
← EBP points HERE (fixed, never moves)
| |
| EBP-4 | local variable slot 1
| EBP-8 | local variable slot 2
| EBP-C | local variable slot 3
| |
← ESP points here (moved down by 28)
Low address
The gap between EBP and ESP that is where local variables live.
How to spot a function boundary in OllyDbg:
RET ← previous function ends here
PUSH EBP ← new function starts here
MOV EBP, ESP
SUB ESP, xx
RET = end. PUSH EBP + MOV EBP,ESP + SUB ESP = start. You will use this pattern thousands of times.

Concept 2 - MOV Deep Dive
Part 5 introduced MOV. Now we go deeper with real examples from our crackme.
MOV is the most common instruction in x86. It copies data from one place to another. The source never changes only the destination gets the new value.
The variants you need to know:
Register to register:
MOV EAX, EBX
Copy the value in EBX into EAX. EBX stays the same. Simple.
Immediate value to register:
MOV EAX, 5
Put the number 5 directly into EAX. No memory involved.
Memory to register:
MOV EAX, [405000]
Go to address 405000, fetch whatever 4 bytes are there, put them in EAX. The square brackets mean “go fetch from this address.” Like a house address — the brackets tell you to go inside and get what is there.
Register to memory (writing TO memory):
MOV [405000], EAX
Take the value in EAX and write it to address 405000. You are writing data into memory.
The illegal one — memory to memory:
MOV [EAX], [EBX] ← THIS IS ILLEGAL IN x86
x86 does not allow this. You cannot copy directly from one memory location to another in a single MOV. You always need a register in the middle. This trips up a lot of people.
Concept 3 - Reading DWORD PTR SS:[EBP-C]
The first real MOV I found in our crackme was this:
00B2155E MOV DWORD PTR SS:[EBP-C], crackme.00B30044 ; "iotsec"
This looks scary. It is not. Break it apart:
DWORD PTR - size specifier. Tells the CPU how many bytes to work with.
BYTE PTR= 1 byteWORD PTR= 2 bytesDWORD PTR= 4 bytes ← this one
SS: - segment register. SS means Stack Segment. The CPU has different memory regions SS is where the stack lives. DS is Data Segment. CS is Code Segment. In OllyDbg you will mostly see DS: and SS:. Just know that SS: means “we are talking about stack memory.”
[EBP-C] - EBP is our fixed reference point. C in hex = 12 in decimal. So EBP-C means “go 12 bytes below EBP.” That is one of our local variable slots from the gap we saw earlier.
Put it all together: “In stack memory, go to the slot 12 bytes below EBP, treat it as 4 bytes, and write the address of ‘iotsec’ there.”
In our C code this was just:
char *secret = "iotsec";
That one line of C became that one line of assembly. That is what reversing is - connecting assembly back to logic.
Concept 4 - MOVZX and MOVSX (Sign and Zero Extension)
These two come up when you move a smaller value into a bigger register. When you take an 8-bit value (like AL) and put it into a 32-bit register (like EAX), what happens to the other 24 bits?
Two options:
MOVZX - Zero Extension. Fills the remaining bits with zeros. Safe for unsigned values.
MOVZX EAX, AL
If AL = 0xFF (255), EAX becomes 0x000000FF.
MOVSX - Sign Extension. Looks at the sign bit of the source. If the highest bit is 1 (negative number), fills remaining bits with 1s. If 0, fills with 0s.
MOVSX EAX, AL
If AL = 0xFF (-1 in signed), EAX becomes 0xFFFFFFFF (still -1 in signed 32-bit).
If AL = 0x7F (127), EAX becomes 0x0000007F (still 127).
Why does this matter in reversing? If you assume a value is unsigned but it is actually signed, your analysis of what a comparison does will be wrong. MOVZX vs MOVSX tells you whether the programmer intended signed or unsigned arithmetic.
Concept 5 - PUSH Variants
Part 5 covered basic PUSH. Here is the full picture.
PUSH EAX -push the value in register EAX onto the stack. ESP decreases by 4.
PUSH 0 - push an immediate value directly. Common before function calls.
PUSH [405000] - go to address 405000, fetch the value there, push that value onto the stack. Not the address the value stored AT the address.
PUSH DWORD PTR [EAX] - EAX contains an address. Go to that address, fetch 4 bytes, push them.
The most important pattern PUSH before CALL means function arguments. In 32-bit x86, arguments are passed by pushing them onto the stack before calling the function. Reading the PUSHes before a CALL tells you what arguments are being passed.
In our crackme:
MOV DWORD PTR SS:[ESP], EAX ; push argument 1 (input string)
CALL JMP.&msvcrt.strcmp ; call strcmp
That MOV to [ESP] is another way to pass an argument — same idea as PUSH. The callee finds its arguments on the stack.
Concept 6 - POP
POP is the reverse of PUSH. Takes the value at the top of the stack and puts it somewhere. ESP increases by 4.
POP EAX - take the top of stack, put it in EAX.
POP [405000] - take the top of stack, write it to memory address 405000. Valid, you will see this sometimes.
Why do you rarely see POP in normal functions? Because most code uses LEAVE instead of manually POPping everything. But in exploits and shellcode, POP is everywhere — it is used to adjust the stack pointer and grab return addresses.
Concept 7 - Reading the Full check_password Function
Here is the full check_password function from our crackme in OllyDbg:
00821558 PUSH EBP ; save caller's EBP
00821559 MOV EBP, ESP ; set up our reference point
0082155B SUB ESP, 28 ; carve out local variable space
0082155E MOV DWORD PTR SS:[EBP-C], 00B30044 ; secret = "iotsec"
00821565 MOV EAX, DWORD PTR SS:[EBP-C] ; load secret into EAX
0082156C MOV EAX, DWORD PTR SS:[EBP+8] ; load input argument into EAX
0082156F MOV DWORD PTR SS:[ESP], EAX ; pass input as argument to strcmp
00821572 CALL JMP.&msvcrt.strcmp ; call strcmp(input, secret)
00821577 LEAVE ; restore stack frame
00821578 RET ; return to caller
Notice [EBP+8] on line 4. EBP+8 means 8 bytes ABOVE EBP. That is where function arguments live above EBP, not below. Local variables are below (EBP-x). Arguments from the caller are above (EBP+x).
And LEAVE at the end that is shorthand for the function epilogue:
MOV ESP, EBP ; restore stack pointer to where EBP is
POP EBP ; restore caller's EBP from stack
Then RET sends execution back to the caller.
So the full story of a function:
; Prologue - function starts
PUSH EBP ; save caller's EBP
MOV EBP, ESP ; set our reference point
SUB ESP, xx ; make room for locals
; ... function body ...
; Epilogue -function ends
LEAVE ; restore EBP and ESP
RET ; go back to caller
Memorize this. You will see it in every function for the rest of your reversing life.
Concept 8 — Reading main() in OllyDbg
Now let us look at main(). Jump to "Enter password:" string in OllyDbg. Scroll up to the prologue. You will see:
00821579 PUSH EBP
0082157A MOV EBP, ESP
0082157C AND ESP, FFFFFFF0 ; stack alignment (MinGW adds this, ignore it)
0082157F SUB ESP, 50
Then further down you can see your entire program logic:
; printf("Enter password: ")
00821587 MOV DWORD PTR SS:[ESP], crackme.0083004B ; "Enter password: "
0082158E CALL crackme.0082151C
; scanf("%63s", buf)
0082159B MOV DWORD PTR SS:[ESP], crackme.0083005C ; "%63s"
008215A2 CALL crackme.008214E0
; check_password(buf)
008215AB MOV DWORD PTR SS:[ESP], EAX
008215AE CALL crackme.00821558 ; our check_password function
; if (result == 0)
008215B3 TEST EAX, EAX
008215B5 JNZ SHORT crackme.008215C5 ; jump if NOT zero (wrong password)
; printf("Correct!")
008215B7 MOV DWORD PTR SS:[ESP], crackme.00830061 ; "Correct!"
008215BE CALL crackme.0082151C
; printf("Wrong!")
008215C5 MOV DWORD PTR SS:[ESP], crackme.0083006B ; "Wrong!"
008215CC CALL crackme.0082151C
Your entire C program is right there. Every function call, every if/else, every string. You wrote this code. Now you are reading it in assembly. That feeling of recognizing your own logic in disassembly that is what reversing is.
What I Found Confusing (And Now Don’t)
EBP vs ESP - I kept mixing these up.
ESP is the top of the stack. It moves constantly. EBP is a fixed reference point for the current function. It does not move after the prologue. Once I visualised EBP as a nail in the wall and ESP as something sliding along the wall, it clicked.
[EBP-C] - what is that C?
C is hex for 12. EBP-C means 12 bytes below EBP. It is just a local variable slot. The function has multiple slots EBP-4, EBP-8, EBP-C, EBP-10, etc. Each one is a local variable.
DWORD PTR SS: why is it so verbose?
x86 assembly needs to be explicit. DWORD PTR tells the CPU “4 bytes.” SS: tells it “stack segment memory.” [EBP-C] tells it the exact address. Every piece of that line is necessary information for the CPU. Once you know what each part means, it stops looking scary.
Memory to memory MOV is illegal.
You cannot do MOV [EAX], [EBX]. x86 simply does not support it. Always need a register in the middle. MOV ECX, [EBX] then MOV [EAX], ECX. This one confused me because logically it seems like it should work.
What We Learned
| Term | What It Means |
|---|---|
| MOV | Copies data from source to destination. Source is unchanged. |
| DWORD PTR | Size specifier - means 4 bytes |
| WORD PTR | Size specifier - means 2 bytes |
| BYTE PTR | Size specifier - means 1 byte |
| SS: | Stack Segment - the memory region where the stack lives |
| DS: | Data Segment - the memory region where global data lives |
| [EBP-C] | Address 12 bytes below EBP - a local variable slot |
| [EBP+8] | Address 8 bytes above EBP - a function argument from the caller |
| PUSH | Puts a value on top of the stack. ESP decreases by 4. |
| POP | Takes the top value off the stack. ESP increases by 4. |
| MOVZX | Move with zero extension - fills upper bits with zeros |
| MOVSX | Move with sign extension - fills upper bits based on sign bit |
| Function Prologue | PUSH EBP + MOV EBP,ESP + SUB ESP,xx - every function starts with this |
| Function Epilogue | LEAVE + RET - every function ends with this |
| LEAVE | Shorthand for MOV ESP,EBP then POP EBP |
| RET | Return to the caller function |





