LongCut logo

#1 لماذا 98% من المبرمجين مخطئون؟ تشريح الذاكرة، ROP، ومحاذاة المكدس في x64 | سلسلة التقنيات المحرمة

By سلطان الرفاعي | PhysiHack

Summary

## Key takeaways - **Constants Aren't Unchangeable**: 98% of programmers believe constants cannot be changed during runtime, but we can break this rule with a single command to control the processor and launch Calculator. [00:16], [00:38] - **ROP Mimics Ransom Note**: Hackers use ROP Chain like a criminal's paper cutout technique, extracting 'gadgets'—small instruction fragments ending in RET—from existing code to form exploits without injecting new code. [02:07], [02:57] - **Buffer Overflow Hijacks Return**: Stack grows downward but writing goes upward, so overflowing a buffer overwrites the saved RBP and Return Address, letting us control RIP and redirect execution. [31:02], [31:48] - **Gadgets Hide in Data**: A harmless int score = 50009 stores as 59 C3 in Little Endian memory, which the processor executes as POP RCX; RET if RIP jumps to it, turning data into a gadget. [01:42:07], [01:46:22] - **Stack Alignment Needs RET Fix**: x64 system() uses movaps requiring 16-byte RSP alignment; add an extra RET gadget after POP RCX to pop 8 bytes and align the stack before calling system. [02:28:19], [02:53:38] - **ABI Dictates Register Args**: Windows x64 ABI requires system()'s first argument in RCX, Linux in RDI, so chain POP RCX; RET gadgets to load the 'cmd' string address from stack into the correct register. [01:12:42], [01:13:25]

Topics Covered

  • Constants Hackable at Runtime
  • Data Variables Become Gadgets
  • ROP Chains Bypass NX Bit
  • Stack Overflow Controls Return Address
  • RET Chains Reuse System Code

Full Transcript

Imagine a system being compromised by a single command. Of course, you might look at it and say, "It's just defining a numeric variable and passing it a value."

How can a single command and one line hack a system? This is what I want to talk about regarding Concealing Abstraction versus Fundamental Abstraction, and how we can hack a system using only one command. I am sure...

98% of programmers will tell you, "This is a constant, and a constant cannot be changed." Unfortunately, this is a widespread belief among programmers that constants cannot be changed during program runtime. Today, we want to break this fact, change the "unchangeable" constant, and break the rules of the language. Now, we simply execute this command, press Enter, and notice that we launched the Calculator. We have now, in this way, controlled the processor completely

and directed it the way we wanted. Furthermore, we executed commands the programmer never wrote, nor do they exist in the system essentially. That is the idea.

Previously, things were simple. Just inject code, which is Shellcode, into memory, execute it, and the system is hacked quite simply. But the topic has changed now with modern systems. The idea that you run code directly from memory has become almost impossible, especially with new technologies like the NX Bit, or simply "No-Execute".

The idea simply is that memory that accepts writing prevents execution, and memory that accepts execution prevents writing. This is the law present in modern systems, which prevents us from injecting programming codes or external codes that we could later exploit to hack systems. So, the simple question, Sultan: How can we hack systems,

and how can we create, for example, a payload through which we can hack systems? The answer to this question is simple. We will talk about the "Forbidden Techniques" that I promised you in the previous videos.

In the world of crime, the criminal tries to hide their identity in every possible way. One of these ways is the "paper cutout" technique (Ransom Note). The idea, quite simply, is that the criminal takes a normal newspaper containing natural text and starts cutting words from the newspaper intentionally. Meaning, they cut a specific word from the first page, a specific word from the second page, and form a message or sentence, for example, a threat letter, etc.

Why does the criminal do this? To avoid tracking and avoid handwriting analysis, so that special agencies cannot recognize their specific handwriting. At the same time, they use something natural and legal, which is the newspaper. The newspaper, written by journalists, contains text that is very natural. But they extract the words from specific places and specific areas so that

very natural. But they extract the words from specific places and specific areas so that they form a threat message or coordinates, etc., and avoid tracking directly. This is

the method hackers use in the ROP Chain technique (Return-Oriented Programming) which we will talk about, God willing, today. It relies on the idea of cutouts or fragments, which we call "Gadgets".

today. It relies on the idea of cutouts or fragments, which we call "Gadgets".

Of course, what I want to say first of all is that we must understand the memory structure before we move on to the rest of the details.

Peace be upon you and the mercy of Allah and His blessings. This is Sultan Al-Rifai, or PhysiHack. We will talk about Memory Layout theoretically, and then we will enter the practical part. I know you love the practical part, but understanding the topic is important. When we double-click the program to run it, quite simply, the system Loader comes and takes the raw data from the hard disk, whether it is

EXE in Windows or ELF in Linux. This raw data is loaded, and the Loader divides and adjusts it. Of course, the system gives the illusion to the program that it took the entire memory, while in reality, it took Virtual Addresses. But basically, there is a unit (MMU) that translates virtual addresses into random physical addresses. However, the program

sees that it took virtual addresses, and these are arranged, organized, and divided. This is the program's view. What are the existing divisions of the program's memory when it transforms from mere raw data,

program's view. What are the existing divisions of the program's memory when it transforms from mere raw data, dead on the hard disk, to a live process running completely? Quite simply, it is divided into many sections. The first section is Text. This section, which is in red down here, is where

many sections. The first section is Text. This section, which is in red down here, is where the program code lives. You know when we write code, commands, the main function, etc.?

After the compilation process, it turns into what? Into Machine Code or operations. It is stored or placed in this section.

This section is only for execution and reading, RX, which is short for Read and Execution.

So here the processor comes to execute the commands found here.

The important point in this thing is that in this area, we cannot inject codes. We cannot modify this area because its permission is only execution and reading. After that,

inject codes. We cannot modify this area because its permission is only execution and reading. After that,

we come to the second section, or the second area, which is R-Data. What does R-Data mean? R

is short for Read. Meaning simply Read-Only Data. Data that is readable only.

So what is stored in this section? What is stored here is data that you cannot modify, like texts (Strings). Strings in general are considered constants. For example, "Hello World" inside a print function.

When you write "Hello World", or Constants in general. Of course, provided that the constants are Global, meaning global constants outside functions, not inside functions.

Those inside functions are stored in the Stack; we will get to that in a bit. So here, data is only readable.

The permission is only Read. You cannot modify nor execute in this area. After that,

we have the Data section. The Data section is simply variables that have been initialized, meaning a value was placed in them. Like, for example, here we have int x = 10. This value 10 is stored in the initialized Data section where the programmer set its values. After that, we have the last section which is BSS. This section is for uninitialized data. For example, if we

defined a variable but didn't give it a value, like int x. Notice there is no equals sign nor any value.

Since we didn't initialize it, it will be placed in the BSS section, and a default value will be assigned to it, which is zeroing out (a set of zeros), and it reserves a slot only in memory. Next comes

this dynamic area between the Stack and the Heap. First, we have the Heap.

This area is the programmer's playground. When the programmer needs a specific space they want to reserve, they use a function like malloc in C. So that they reserve a specific space from the Heap to use for specific purposes. And of course, the growth direction when you reserve space in the Heap area, it grows upwards. The growth direction is up. After that, we have this area, you see the gray lines?

This is the Shared Libraries. Here, quite simply, the libraries that the program needs to run properly are present. Like system libraries, C libraries, etc. This point is very important for us to understand regarding ROP attacks and the point of attack which is Return-to-Libc or "Return to C Library" because we will use C libraries. And this is what matters to us

in the ROP attack; we will explain it later. After that, we have the Stack. In the Stack, functions are processed.

When a function is called, the function frame, its information, the internal variables that are defined inside the function are all stored in the Stack. Any variable you define inside the function, whatever the function is, gets stored in the Stack. Plus the Return Addresses, etc., of these details are all stored in the Stack. Finally, we have the last section where the Arguments passed to the program through the terminal or command prompt are stored,

as well as the Environment Variables which contain system paths, etc. This is simply the Memory Layout generally and theoretically. And of course, what I want to say is to clarify a very important picture. Here

we have C code. Let's see where each value will go. We have, as we mentioned, int x.

Where will it be stored? It will be stored in this area, in the area or space of uninitialized variables because it has no value. After that, we have y because it has a value. It is a variable, of course.

Focus, these are global variables. Global variables because they are outside the function. They were defined outside the function.

So here, because it has a value, where is it stored? Stored in Data. Then we have const.

Const, which is a constant. So this const will be stored—and it is also global, meaning global outside the function—it is stored in R-Data or Read-Only Data (RO-Data) in Linux. After that, here inside the main function, we have this variable c. Where will it be stored? It will be stored in the Stack. Why?

Because any variables defined inside functions, whatever the function, are stored inside the Stack. And here we have this command. This command has two parts. The first part is the variable ptr.

the Stack. And here we have this command. This command has two parts. The first part is the variable ptr.

The value of ptr, which will contain a memory address, where will it be stored? It will be stored in the Stack. But the malloc function, the space that was reserved through this function malloc, where will the area we reserved be? It will be in the Heap. Because we said when the programmer uses malloc, they reserve in the Heap memory. The memory address or location—meaning it's like we bought a land,

and this land has an address. So its address, which is the location of the space we reserved, will be stored in the variable ptr. The value of the variable ptr, which will be the address of the space we reserved in the Heap, where will its value go? It will go into the Stack. Because this is a variable we defined inside the function. So we said variables inside the function always have values in the Stack.

The space we reserved, what did we reserve? Four bytes. Why? Because we said sizeof(int).

int, which is Integer, a data type for whole numbers, reserves how much? Four bytes in memory.

So we simply reserved a space of 4 bytes in the Heap memory, our dynamic memory.

And then we saved its address inside the Stack. After that, we have the printf function. This function in C, the text here, because we said texts in general are considered constants, where will it be stored? It will be stored in R-Data. And we will see these details actually in the part after this. And we will also explain Assembly

and other details. I hope the picture regarding this diagram is clear. And as we mentioned, the lower addresses down here start from Text. The Text section is present in lower addresses. And as we go up to the higher addresses, the Stack exists in the higher addresses. Of course, we will talk about ASLR (Address Space Layout Randomization), which regards address randomization, God willing, in future videos and the series, and how to solve its problems.

Wait, wait. Just a few theoretical points left I want to say. The Stack grows downwards,

Wait, wait. Just a few theoretical points left I want to say. The Stack grows downwards, unlike the Heap. The Heap grows upwards. Whenever we create a space in the Heap, it grows upwards, from lower addresses going up to higher addresses. The Stack is the opposite; it grows downwards. Whenever we add a value to it, it grows downwards. Whenever we push a value, it grows downwards. Stack permissions are RW.

What does RW mean? It means Read and Write. We cannot execute programming codes.

We cannot inject Shellcode into the Stack and execute it because its permissions are read and write, and we talked about NX Bit prevention.

There is also a point I want to mention. The R-Data section in Windows is named .rdata, but in Linux, you will notice a section named .rodata. RO is short for Read-Only. A section readable only.

The last point is regarding this area. We said ptr. When we reserve space in Heap memory, for example, as we said 4 bytes, we store the pointer in the variable ptr. And we said the value of ptr, which is the address, is stored in the Stack.

What I want to say is that this pointer or variable, if we did free to this malloc—meaning we freed the space we reserved— and the pointer still points to it, a vulnerability or exploitation might occur: Use-After-Free, which we will talk about later in the series. This is what I wanted to say regarding the Stack.

Why did I mention it grows downwards? Because this is a very important point regarding ROP and the Buffer Overflow problem.

And how through it we can write over Return Addresses and these things. We will mention the details.

Let's see where variables are actually stored in memory. First, we will open Code::Blocks and define a variable.

Write int (short for Integer, whole number) and variable name x, then equals 30.

Press Ctrl+S for save, then Build (compile). We compiled our program.

Now we come to the program "Die" (Detect It Easy). Of course, we will download all programs and these details, so don't worry.

Now press... open the compiled program which is this. Now we want to go to Sections.

Remember in the memory structure part, we explained Data and R-Data sections, etc.? And we said variables that have been assigned a value like x = 10 are stored in the Data section. Now go to File Info, then go to Sections.

Of course, an important point: This memory is on the hard disk, unlike the memory we explained which is in RAM (Process Memory) when we run the program.

Now click on Data section. What do we notice? Do you see 1E? This is 30 but in Hex.

Let's verify in the browser and convert. Write here number 30 and convert it to Hexadecimal.

Notice it is 1E as you see here in the Data section. Meaning because we defined a variable of type Integer, it naturally reserved 4 bytes in memory. Of course, you ask, "Sultan, how do we know it reserved 4 bytes?" The zeros in front of it.

This is the 1st byte, 2nd byte, 3rd byte, and this is the 4th byte. If I defined another variable, we would find its value here after the fourth byte. To believe me, let me define another variable. Write int y = 10.

10 will be 0A. Let's compile again. Come again, close our program, press Exit.

Open it again, choose our program. Now come again to File Info, Sections, and come to Data section. Notice, you see this? These 4 bytes are for what? For, as we said, the Integer reserves 4 bytes in memory.

These 4 bytes are for the first variable x. And here, notice the value of the second variable y just arrived, which is 10.

Notice it is 0A as I mentioned. Let's see, come here write 10 and see its value in Hexadecimal.

Press Convert. Notice it is A. So quite simply, now we know where variables are actually stored as I mentioned.

Initialized variables are stored in the Data section. Well, for example texts (Strings), like the word here.

Let's open our program. "Hello World". Where is it? We said it's in R-Data. Let's search for it.

Go to R-Data section. This is R-Data section. Where is "Hello World"? Look, this is "Hello World". You see it?

Of course, here is the Zero Terminator. Every text or string in memory ends with zeros so the processor knows "Okay, this is the end of the string" in a loop. We will talk about it, God willing, in the Assembly part.

Notice that indeed in R-Data, the text "Hello World" exists. Well, what if we defined a constant variable?

Let's define a constant variable. Come here write const int (constant integer) c = 30.

Write 30 here and compile the program again. Close our program, press Exit, open it again, choose it.

We will find now what? Because this is a constant, as we said, where are constants stored? In R-Data.

Texts and constants are in R-Data if the constants were Global (outside the function). So press File Info, come to Sections section, come to R-Data. Notice, you see a variable before "Hello World"? And it reserved how much? Reserved 4 bytes.

This is the idea quite simply. Now we are seeing this data realistically. This is of course what is called a "Hex Dump".

It is raw data of the file, the EXE (Executable File) or ELF as we mentioned in Linux. So simply,

as you see, the same theoretical division we explained and the same details exist in reality.

Meaning when you come to do reverse engineering, every theoretical part matters to you. So don't skip any theoretical information I will mention in this video because it will matter in the applied information, God willing, or in practical methods we will execute. Let's move to the rest of the parts and explain Assembly.

The Stack. This is the area where we will fight in the entire part of the video. And we must understand it.

It is very simple. The Stack is simply a reserved area of memory where data is stored temporarily for the processor to use. That's it simply. It works on the LIFO system: Last In, First Out.

What does it mean? It means the last value to enter the Stack is the first value to come out of the Stack.

Last In, First Out. You know the system of dishes? Now, for example, I have plates. I go and put a plate or dish.

Okay, now I came with a new plate. Quite simply, what do I do? I put it on top of the old plate, right? Like that.

I came again with a plate, what do I do? I put it on top of the two old plates. And if I needed to wash a plate and use it, what will I do? I will take the last one, right? So what happened is the last one put in is the first one to be taken. This is the dishes system. The Stack's job and function is the same: a dishes system.

When we do PUSH (adding a value), we added a value inside the Stack. After that, we added another value inside the Stack.

The second value became above the previous value. Now if we want to do POP (withdrawal), what will happen? We pull the last value we added. Last In, First Out. Meaning the last value we added is the first value we pull.

Of course, someone might say, "No, in reality with plates, I go pull the plate at the bottom, not the top." Because you are weird. There is no other explanation because you are weird. Okay, let's continue our topic. So quite simply: Last In. Last value enters, first value is pulled. That's it simply. Two commands. This is the Stack. There is nothing terrifying about it.

But it is very important regarding understanding Buffer Overflow later. It is very important to understand the Stack so we can execute an attack like ROP later. So this is the idea of the Stack.

PUSH adds a value inside the Stack. POP pulls the last value inside the Stack. Simply.

Let's take a simple example here. We have two values. If I pushed the value 10 inside the Stack, I added it now inside the Stack.

Okay, I pushed again the value 5. We added it. Nice. Now if I did a POP command, what will happen? Which value will be pulled? The last value we added. POP this one.

After that, if we did a pull again, POP, what will be pulled? 10. Simply, this is the Stack. So we use the Stack for what?

We use it as a temporary and very fast storage unit. The processor uses it in operations. And we will come to its basic role regarding functions, especially in the second part regarding the Stack Frame. Always remember,

Stack = dishes. If you haven't washed dishes, go wash dishes now to understand the idea. Let's move on.

Registers. You heard me say "Registers" very frequently in the video. These are considered storage units, very fast and very small, located inside the processor. We see this CPU unit.

Registers are inside the processing unit itself. Why inside? Because the processor needs them very quickly in its arithmetic operations. Meaning simply... imagine RAM is like a refrigerator. The processor takes ingredients from it.

Registers are literally like the table where the processor works, cuts, etc. Meaning no one opens the fridge and starts cutting the chicken inside the fridge. Surely they take the chicken out and put it on the table and start working on it.

So Registers are used by the processor in its operations. That's why it needs them to be close to it.

The processor, by the way, you see it as a square, but inside it are many units. Like the Arithmetic Logic Unit (ALU).

This unit simply, as its name implies, performs arithmetic and logical operations. Like OR, AND, XOR, etc. These are logical operations.

Also arithmetic operations like ADD and SUB (addition and subtraction) are done in this unit. And we also have the Control Unit (CU).

It runs the show. It organizes. It directs data from Registers to the ALU and from/to. Also, it takes instructions (commands) from the Cache, L1 specifically.

and from/to. Also, it takes instructions (commands) from the Cache, L1 specifically.

Of course, L1 Cache is divided into two parts. A part where Instructions are stored (commands executed by the processor), and a second part where Data is stored. This data might be needed by Registers or the ALU.

And we have here the Memory Management Unit (MMU). All this stuff is inside the processor. The MMU is what we talked about when we discussed memory structure and Virtual Memory. And we said it translates from Virtual Addresses to Physical Addresses inside RAM. This unit's role is translation inside the processor. Why is the unit that translates addresses from virtual to physical inside the processor? For speed. Because every operation in the computer happens on a virtual address.

So the processor needs to translate it. So literally this unit must be next to it to quickly translate addresses to physical addresses and reach their location in RAM. Before, in the 80s, old days, this unit was outside the processor. But then they integrated it. So all devices now—computers, phones, laptops—

the processor. But then they integrated it. So all devices now—computers, phones, laptops— the MMU is inside the processor. Of course, MMU is not RAM. This is separate. So these are simply the units inside the processor. As I said, the Control Unit does multiple operations. Inside there are units like Fetch (fetch instructions from Cache),

the processor. As I said, the Control Unit does multiple operations. Inside there are units like Fetch (fetch instructions from Cache), Decode unit (decoding), etc. What matters to us are the Registers. Because they are what we will use very often. And we will understand their idea and roles in the next part.

often. And we will understand their idea and roles in the next part.

Registers. Of course, we talked in the previous part that they are very fast storage units, small, and inside the processor.

When we talk about speed, we are talking about 0.3 nanoseconds. Very super speed. So the processor uses them in its arithmetic operations.

Registers have two categories or types. The first type is General Purpose Registers. The second type is Special Purpose Registers.

Meaning those having a role and function, and we shouldn't manipulate their values. What does "General" mean? Simply like EAX, EBX, ECX. These are General Registers. Meaning we can add values to them, whether numbers,

EBX, ECX. These are General Registers. Meaning we can add values to them, whether numbers, addresses, whatever. So we can change their values, do whatever we want without problems. But there are Registers (storage units)

addresses, whatever. So we can change their values, do whatever we want without problems. But there are Registers (storage units) that are special, having specific functions and tasks. Like for example, EIP. EIP is short for Instruction Pointer.

This is the "Processor's Finger". What does "Processor's Finger" mean? It means the processor knows which instruction it will execute through this register. The address of the next instruction will be stored in it. Because when it executes the program,

this register. The address of the next instruction will be stored in it. Because when it executes the program, literally it's like a finger walking step by step. So this register (EIP) stores the address of the instruction the processor will execute next. We also have a register with a specific function, ESP.

ESP is short for Stack Pointer. This points to the last value added. Remember the Stack in the previous part when we talked about dishes? I always use kitchen examples. I don't know what's the story with the kitchen.

Maybe bachelor problems. It doesn't matter. The pointer always points to the last value we added. You see this last plate we added?

ESP or RSP points to the last value. Why is there a letter 'E' at the beginning? 'E' means this is 32-bit architecture.

Or 'R' for 64-bit. You will find the same registers with same functions but with letter 'R'. Instead of EAX, you find RAX.

ESP, you find it RSP. This is in case of 64-bit. 'R' stands for Register (Extended).

So we will find later when we analyze 64-bit, we will find RSP not ESP. This is an important point to understand so you don't get confused.

So simply, registers are like what? Like variables. You know programming variables where we store temporary data and change values?

Same thing. Registers, despite the name difference, are registers. There we call them variables. It's the same idea.

But these belong to the processor. The processor uses them in its recipe, in arithmetic operations. Now let's start using these registers in our operations.

The first command we will write in Assembly is MOV. What does MOV mean? Short for Move.

But an important point to clarify: The function of this command effectively does not move the value from place to place by removing it from the first place. No. It almost does a Copy operation. Literally copies the value from the first place and puts it in the second place, and the value remains in the first place. For example, let's write MOV EAX.

This is a register name. So we will move a value to it, which is 5. Now look at this register.

We said these are just storage units. I transferred a number, stored it in EAX. Now if we do Step, notice as soon as I do Step, its value became 5. The command was executed. The processor took value 5 and transferred it directly to our register EAX.

Well, for example, if we wrote MOV ECX then for example 3. Do Reset, press Step. It will add 5 to this register.

After that, if we press Step, notice it added 3 to the second register ECX. Important point to clarify: These are General Registers, but every register has a default function. EAX (or RAX) usually stores function return values. ECX (or RCX in 64-bit architecture) is usually used as a Counter for loops. But generally, it depends on the compiler and programmer how to use these registers.

But these can be controlled. Of course, EIP, the instruction pointer, we will come to it in the ROP attack and control it. Meaning simply we will start manipulating the program path. And this is the strongest point we will reach, God willing.

Now we understood that MOV command copies a value to another register. What if we said ADD EAX, ECX?

Now ADD command, as the name implies, Addition. It will perform addition. It will use the unit we mentioned in the processor, the ALU.

The ADD command will use it. So the value in ECX will be added to EAX, and stored where?

In the register on the left, EAX. Meaning the result of addition. So if we do Reset, notice EAX took 5.

Okay, sweet. Now this took 3 (ECX). Okay, notice EAX is 5 + 3 will be what? Let's press Step. Notice it became 8.

Simply this is addition. Subtraction? Write SUB. Meaning Subtraction. So we can say SUB EAX, 2.

Do Reset. Step (5), then (3). We moved 3 to it. Now addition became 8 (EAX). Now subtract 2 from EAX (our register).

Press Step. Notice it became 6 because we subtracted 2. These are simple operations, but these arithmetic operations matter to us very much.

A point when I said MOV is a Copy operation not actual Move. Let me clarify. See this register EDX?

Now it is zero. Let's do MOV EDX, ECX. We want to move the value of ECX which will be 3 to EDX. Do Reset. Notice what will happen to EDX? Value 3 will be moved from ECX to EDX.

I said Copy operation meaning ECX will remain 3 and EDX will become 3. Let's press. Indeed EDX became 3 and ECX became 3.

So it is a Move operation, but technically it is Copy. Not transferring the value entirely making ECX zero.

It is almost a text copy. I wanted to clarify this from an engineering perspective so you don't have confusion.

Registers are storage units having two types: General and Special (specific functions). We shouldn't manipulate their values.

In the next part, we will talk about Stack Frame because it is the most important point after explaining Registers and these simple commands.

Stack Frame. This is very important, God bless you. Simply, Stack Frame is the workspace specific to every called function.

When a function is called, a workspace is reserved for it. This workspace is called Stack Frame. It's like an employee when they come to work.

They tell them, "Listen, you just arrived. This is your desk. You can work here, put your files, papers. This is your workspace."

So simply functions, whenever a function is called—whether the main function main which is called via C-Runtime (Start function)— first main is called and a frame is reserved for it in the Stack. The frame starts with Register RBP and RSP. We will explain now. Then comes our specific function, for example, called from main.

and RSP. We will explain now. Then comes our specific function, for example, called from main.

This function also gets PUSHed to Stack and a frame is created for it. Let's talk about the components of this frame.

Every function without exception consists of the same components. First, values in higher addresses which are Arguments (Parameters).

We pass values to functions. These values are pushed at the beginning before reserving space for the function.

After that is the Return Address. What is the Return Address? The processor executed MyFunction.

It executed it and finished. Then the processor asks, "Where do I go now? I finished my job. I executed the code inside the function. I must Return."

From here, the processor knows where to return. The Return Address is stored in this area. So the processor, after finishing work from the function, jumps to the code following the executed function. This point is very important. Focus on it because we will manipulate this area specifically.

We will change the Return Address to make the processor jump to another area. This way we control a register called RIP or Instruction Pointer. So we can control the Processor's Finger and tell it, "Listen, go to this area, then jump to that area."

We control the processor completely and control the program path. After that, we have the saved value of Base Pointer (RBP).

RBP is short for Base Pointer. This points to the start of the Stack Frame, which is the called function.

For example, MyFunction frame starts from here, from RBP. This address points to a value called Saved Base Pointer.

What is this Saved RBP? It is the Base value of the previous function that called this function (which is main).

Why do we save the Base Address of the previous function? To create what is called the Call Stack (Chain of calls). So we know who called whom.

And more importantly, after we finish the first function and return to the second, we must restore its workspace.

How do we know its workspace if we don't have the Base Address of its Stack Frame? So we store the Base Address of the previous function in this area. Consequently, the current RBP of this function points to the Base Pointer of the previous function.

So we know this function is called from that function, creating a sequence. This is a very important point.

Especially when we start manually creating our Payload. In 64-bit architecture, addresses are 8 bytes long.

In 32-bit, addresses are 4 bytes long. This is very important regarding Padding. When we insert bytes manually after the RBP address.

Values of Local Variables. Remember in Memory Structure we said: Local Variables are stored in the Stack?

Here is their place, below the RBP pointer. Always an important point: Base Pointer is in higher addresses. Below it are addresses for Local Variables values. Under this address. So if we want to reach this variable. Assuming this variable is Integer.

We said Integer equals 4 bytes. So simply we write RBP - 4.

What does it mean? We said: Listen, take the current RBP address which points to the start of the Base of the current function frame, minus 4 to reach the value of the variable existing in the Stack (the first variable, x). What if we want to reach the second variable which is also Integer (4 bytes)? It becomes minus 8. RBP - 8. This takes us to this variable.

So we can reach Local Variables inside the Stack through the Base Pointer (RBP). Why don't we rely on RSP (Stack Pointer) to calculate Local Variable locations? Because it changes with every PUSH and POP. As we mentioned, RSP always points to the last value pushed.

If there was a PUSH in the function, RSP location changes. So our calculation of addresses will get messed up.

So we rely on RBP because its address is constant as long as the function is still running and hasn't finished.

Based on this, we can reach local variables from RBP. We can also reach Arguments if they exist (values passed to the function) but here the operation becomes addition instead of subtraction. Why? Because we go up to higher addresses. If we want to reach what's above (Arguments), we use addition. But because we want to reach lower addresses (Local Variables), we do subtraction.

Subtract from Base Pointer the number we want to reach. If we want the first variable, minus 4.

This is simply the idea of Stack Frame. It saves Local Variables. Above it is the Return Address. This is the most important point: Control the Return Address to later control the Processor Pointer (RIP). After that, Arguments. What I want to say is the point we want to understand: The Stack grows downwards. Notice RSP if we added a value, it goes down because Stack growth is downwards. But writing values is upwards.

For example, in Strings, Arrays, etc. If we wrote a value in an Array, its data when stored starts going up from lower addresses to higher addresses.

Why? Because an Array starts from lower addresses (Index 0). Then Index 1, 2, 3 starts going up to higher addresses.

This point is very important: Writing direction is upwards, Stack growth is downwards. The point we want to clarify: If this variable, for example, is an Array of 4 bytes. If we exceeded the capacity of this Array (Buffer Overflow) from 4 bytes to 5 bytes, we will write over this variable. Assuming this variable is Integer (4 bytes).

So here total is 8 bytes. If I wrote 8 bytes, I will overwrite (crush) the first variable and the second variable.

which is 8 bytes. So now total is: 4 + 4 + 8 = 16 bytes. If I wrote 16 bytes (Payload/Padding), we managed to step over all this area. Consequently, we reached the Return Address. Now we can put an extra 8 bytes address and control our Return Address. This is the idea of Buffer Overflow. We exploited capacity overflow to write on higher addresses

because writing is ascending. So the value starts from here (Array 4 bytes). We exceeded capacity, wrote 16 bytes.

We crushed the first variable, second variable, and the Saved Base Pointer area. After that, we can write 8 extra bytes for an address we want to tell the processor to go to. So the processor, instead of jumping to the next code in this natural program, we modify the Return code/address and make it jump to a completely different place. Here comes the ROP idea.

How through RET (Return) command, we can change the program path and make it jump to different places.

We exploited the overflow in this point and the idea that Stack grows downwards but writing is reverse (upwards).

Simply, the address added to Stack above, we will come from below and go up and write on the address existing above.

Consequently, we control the processor finger and tell it: "Processor, move. Forget the next code in this program.

Go to X place, execute X code." I hope the picture is clear So, simply, the address added to the Stack above, we come from below, go up, and overwrite it.

Consequently, we control the processor's finger and tell it: "Processor, move. Forget the next code in this program."

"Go to location X, and execute code X." I hope the picture is clear.

We talked in the previous part about the Stack Frame, and said the Stack grows from higher to lower addresses.

But the writing direction is from lower to higher. Opposite to each other.

Consequently, if we execute a Buffer Overflow exploit in one of the local variables, we will overwrite the rest of the variables, the Saved Base Pointer, and most importantly...

We write over the Return Address. So, we control the Instruction Pointer.

Now, if I came in the C code and defined a variable char buffer[5].

Then int x = 30. Now, what did I do?

I defined an array reserving 5 bytes, and an Integer with value 30.

The important point is that an Integer reserves 4 bytes.

So the total for the Buffer and Integer is 9 bytes. We said Local Variables inside a function...

like main, will be placed within the Stack Frame of main. This is very important.

Now, if we compile our program, we want to go to the Debugger and analyze.

Before analyzing, let's add the gets function to take input from the client/user.

Build again. Before going to the debugger code...

We said Local Variables are always below the Base Address (RBP).

This is the RBP. Local variables are below it. So if I calculate variable x...

It will be RBP - 4. Why? Because the variable is 4 bytes.

So if we subtract 4 bytes from RBP, we reach x address which is directly under RBP.

The Buffer will be RBP - 9. Why? Because Buffer is 5 bytes + Integer 4 bytes = 9.

So to reach the first byte in our Buffer, it will be minus 9.

Of course, you ask why 9? Why not Integer minus 9? Because compiler settings differ.

But generally, space for the Buffer is reserved first, then space for variable x.

So variable x will be after the Buffer in memory. Therefore, the Buffer will be 9 away from RBP.

Let's see this. Refresh, go to main. This is main in Assembly.

PUSH RBP: Pushes the Base Pointer of the previous function. MOV RBP, RSP: Moves Stack Pointer to Base Pointer.

Here SUB reserves space. What matters here: Variable x is minus 4.

Notice 1E (Hex) equals 30 (Decimal). So it moves value 30 directly to SS:[RBP-4].

Now let's walk through the code and go to RBP - 9. This is our Buffer.

Count the bytes: 1, 2, 3, 4, 5 bytes exactly. And notice directly after it is the Integer 1E.

This is the Integer space. If we look closely, notice the address here is 57.

As we walk with the bytes, this number increases. Meaning the writing direction is upwards.

So if I filled the Buffer with 5 bytes and added a byte, I will write into the Integer x space.

If I added 4 extra bytes, I will start entering the RBP space.

If we come to the first address in the Buffer, 57, then 58, 59, going up.

So writing direction is really upwards. If we wrote bytes, we enter the RBP address.

See RBP here 27 62. And notice after it is the Return Address starting with 7F.

This is the Return Address. So simply, if we overflowed our Buffer...

and kept writing until we reach the Return Address. If we wrote bytes (Padding/Filling)...

on all this area, then added our address that we want the code to jump to...

We control the Return Address and control the processor (RIP).

This is simply the Buffer Overflow idea. Because writing starts from lower addresses...

and goes up. So we write over pointers in front of it: second variable, RBP, then Return Address.

This way we control the Processor's Finger and say: "Processor, move to location X."

"I want you to execute code X." And so on with ROP...

and how through the RET command we change the program path.

We exploited the overflow at this point and the idea that Stack grows downwards...

but writing is reverse (upwards). Simply, the address added to the Stack above...

we will come from below and go up, and write on the address existing above.

Let's write Assembly code to see how the Stack works with the commands we mentioned (PUSH, POP).

Open FASM (Flat Assembler). First write format PE64 console.

PE means Portable Executable. 64 for 64-bit architecture.

So PUSH will be 8 bytes. console type. Then write entry start.

entry is the Entry Point where code execution starts. From label start.

Then write section .text code readable executable. Text section stores executable code.

Permissions: readable and executable. Press Tab, write start:. This is the label.

The processor (RIP) will come here directly. Write mov rax, 1. Why RAX not EAX?

Because 64-bit. Registers start with 'R'. Pass value 1 (Hex) to RAX.

Then mov rbx, 2. Then push rax. Push RAX value (1) to Stack.

Then push rbx (2). Then pop rcx.

pop pulls the last value added to Stack (top of Stack) and puts it in RCX.

Then pop rdx. Pulls value from Stack to RDX.

Now compile (save as test). Go to Debugger (x64dbg).

This interface: Here are Assembly commands (x86-64). This is the CPU.

Here commands are executed by the processor line by line. Here is the Address of commands.

Notice RIP (Instruction Pointer). It points to the address the processor will execute now.

Press F8 to step over commands. Before that, close debugger, I want to add ret.

ret (Return). Run debugger again. Notice ret added.

Execute first command: Move 1 to RAX. These are Registers. This is Stack location.

This is Memory Dump. This window monitors the Stack only.

Here is RSP (Stack Pointer) pointing to value F8. Press F8. RAX became 1.

RBX became 2. push rax (1). Notice Stack: Value 1 added.

push rbx (2). Added (2, 1). Values came from registers.

pop rcx. pop takes last value pointed by RSP (2). Puts it in RCX.

Notice RCX became 2. Value removed from Stack.

PUSH operation is RSP - 8 (grows down). POP is RSP + 8 (shrinks up).

pop rdx. Takes last value (1) puts in RDX. ret.

Takes last value in Stack (Return Address) and puts it in RIP.

ret is actually pop rip. Pulls value from Stack to Instruction Pointer.

Execute ret: We returned back. Exited our function.

We saw that RET took the value from the Stack and placed it directly into the RIP.

This means RET is practically POP RIP. This is the most critical equation to understand.

Why is this important? Because if we can control the Stack...

and we can write values onto the Stack, we effectively control the RIP.

But remember, we have a problem. The NX Bit (No-Execute) prevents us.

We cannot just put our Shellcode in the Stack and tell the processor to jump to it.

The processor will refuse to execute code located in a writable section (Stack).

So, what is the solution? We must use code that already exists.

Code that is present in the .text section. Because that section is marked "Executable".

But we cannot write our own code there. So, we use the "Ransom Note" technique.

Recall the criminal who cuts words from a newspaper to form a sentence?

The newspaper is the system memory. The words are the "Gadgets".

A "Gadget" is simply a small sequence of instructions that ends with a RET command.

Why must it end with RET? Because RET is the "glue".

It allows us to chain these fragments together. Imagine we find a POP RCX; RET gadget.

The processor executes POP RCX. It takes a value from our Stack into RCX.

Then it hits RET. RET looks at the Stack for the next address.

So it jumps to the second Gadget. For example, POP RDX; RET.

This way, we execute a chain of commands: Move value to RCX -> Move value to RDX...

And finally, jump to a function like system. All without injecting a single new instruction.

We are just reusing the bytes that are already sitting in memory.

This is why it's called Return-Oriented Programming. Everything revolves around the RET instruction.

Now, let's talk about how to find these Gadgets. We don't need the source code.

We look at the binary. The raw bytes. Remember, the processor just sees numbers.

A value like 0xC3 is interpreted as RET. A value like 0x59 is POP RCX.

Sometimes, these bytes are hidden inside other instructions or even data variables.

This is the concept of "Concealing Abstraction". A programmer defines a large number...

but that number, in Hex, matches valid machine code instructions.

If we jump into the middle of that number, the processor will execute it as code.

This is how we can find Gadgets even if they weren't intentionally written.

So, our plan is clear.

2. Overwrite the Return Address. 3. Place the address of our first Gadget.

4. Place the values we want to pop into registers right after the Gadget address.

Because POP will take them from the Stack. 5. Chain them until we call system().

But wait, calling system() isn't random. We must respect the ABI (Calling Convention).

In Windows x64, the first argument must be in the RCX register.

In Linux x64, it must be in RDI. So we need specific Gadgets.

If we are on Windows, we look for POP RCX; RET. If on Linux, we look for POP RDI; RET.

This is why knowing the Operating System and its ABI is crucial before constructing the chain.

Remember the Ransom Note analogy I mentioned? This is exactly what we are doing now.

We are cutting the POP RCX from one place. Cutting the RET from another place.

And taping them together on the Stack. The Stack is our canvas.

And the RET instruction is the tape that holds this malicious letter together.

Modern systems thought they beat us with NX. But they provided us with the library (libc).

A library full of powerful functions like system. And full of Gadgets to reach them.

We are essentially using the system's own weapons against itself. It's like Judo.

We don't bring force. We redirect the opponent's force. We redirect the execution flow.

So, where do we find these "newspapers"? Where do we find the text to cut from?

We find them in the Shared Libraries. Like kernel32.dll or msvcrt.dll in Windows.

Or libc in Linux systems. These libraries are loaded into memory...

and they contain thousands of instructions. Thousands of "Gadgets" waiting for us.

We don't need to inject anything. We just need to know where they are.

Now, let's establish our Action Plan. First, we need a Buffer Overflow.

We need to exploit a vulnerability in the Stack. Why? To control the memory layout.

The Stack permissions are Read/Write (RW). But execution is forbidden (NX Bit).

So, we use the Buffer Overflow to overwrite the Return Address.

If we control the Return Address, we control the Instruction Pointer (RIP).

We will direct the RIP to our first Gadget. This Gadget is located in the Text section.

The Text section is executable (RX). So the processor will happily run it.

Our ultimate goal involves the system() function. This function exists in the C Runtime Library.

system() allows us to execute OS commands. If we can call system("cmd")...

or system("/bin/sh") on Linux, we effectively gain control of the machine.

This is what we call "Return-to-Libc". We return to the C library to do our dirty work.

But wait, it's not that simple. We need to understand the difference...

between API and ABI. This is where many beginners get stuck.

API (Application Programming Interface) is the contract between you and the compiler.

The API says: "Just write system("cmd")." It's simple. It's high-level.

The API is like a manager. "You want 'system'? Just call it. I handle the rest."

But as Reverse Engineers, we don't deal with the manager.

We deal with the low-level mechanics. We deal with the ABI (Application Binary Interface).

The ABI is the contract between the Executable, the Processor, the Registers, and the OS.

It defines how data is passed. And this differs between Operating Systems. In Windows x64 Architecture, the ABI dictates a specific "Calling Convention".

The first argument of a function MUST be placed in the RCX register.

In Linux x64, it's different. The first argument MUST be in the RDI register.

In the old 32-bit days, arguments were just pushed onto the Stack.

But in 64-bit, we use Registers for speed. This changes everything for our exploit.

So, to call system("cmd") manually on Windows... We cannot just push "cmd" to the Stack.

We must find a way to move the address of the string "cmd" into the RCX register.

And how do we do that if we don't control RCX directly? We use a Gadget.

We need a POP RCX; RET gadget. Why POP?

Because POP takes a value from the Stack (which we control via Buffer Overflow)...

and puts it into the register. So the chain becomes: 3. The address of "cmd" string (on the Stack). 4. Address of system function.

3. The address of "cmd" string (on the Stack). 4. Address of system function.

When the processor returns, it jumps to POP RCX. It pops "cmd" into RCX.

Then it returns again. And jumps to system.

system looks at RCX, sees "cmd", and executes it. Game over.

Let's verify the API vs ABI concept practically. Look at the code for system("cmd").

In the Debugger (x64dbg), notice this instruction: LEA RCX, [address_of_cmd_string].

The compiler loaded the address of "cmd" into RCX. Why RCX? Because it's the First Argument.

Then immediately after, it calls system. This setup is done automatically by the Compiler.

But we are the ones compiling the exploit now. We must manually replicate this behavior.

We control the Stack (RW). We cannot execute there. So we need Gadgets in the Text section (RX).

We need a Gadget to pull data from our Stack and place it into the Registers.

To enable the Shell via system() properly: (Or POP RDI if you are on Linux). 2. We need a RET instruction immediately after it.

POP RCX pulls the value (pointer) from the Stack and stores it in the RCX register.

RET then pulls the next address from the Stack and jumps to it (which will be system).

But where do we get the string "cmd"? Do we need to write it to the Stack?

We could, but there is a smarter way. We use "Ready-made Code" (Libraries).

The system() function itself uses "cmd.exe" or "/bin/sh" internally to work.

This means the string "cmd" already exists in the library's memory.

It resides in the .rdata section of the library. We just need to find its address.

This is Abstraction. Hiding complexity behind simple functions.

But we break this abstraction. We look directly at the memory dump.

We verify the address in the Memory Dump. It is there. We don't need to write it manually.

This saves us space in the Buffer and avoids corrupting the Stack unnecessarily.

Now, we have the Argument (the string "cmd"). We need the function itself (system).

We go to the "Symbols" tab in x64dbg. We select msvcrt.dll.

We search for "system". And there it is. We copy its address.

Now we have all the ingredients for the spell: Now, let's talk about "Concealing Abstraction". This is where the magic happens.

Look at this line of code: int score = 50009; To a normal programmer, this is a variable. A "Score". A number. harmless.

But I am not a programmer. I am a Reverse Engineer. I see the physics of it.

I convert 50009 to Hexadecimal. It becomes 0xC359.

But wait. In memory (Little Endian), it is stored as 59 C3.

What does 59 mean to the processor? It means POP RCX.

What does C3 mean? It means RET.

Do you see what happened? The programmer created a variable score.

But unintentionally, he created a "Gadget". He created a weapon inside his own code.

POP RCX; RET. This is exactly the Gadget we were looking for!

This is "Concealing Abstraction". Hiding the fundamental reality (Opcodes)...

behind a high-level abstraction (Variables). We strip away the abstraction.

We must strip away the laws of the language. Forget "Integer". Forget "String".

Go back to the origins. The physics of the processor.

The processor does not distinguish between Data and Instructions.

It blindly executes whatever the RIP points to. If we point RIP to the score variable...

It will execute it as code. This is the "Forbidden Technique".

We are using the data segment as a code segment. Technically, we are executing inside the Text section...

because the variable definition is part of the compiled instructions (MOV).

Let me explain how this works with "Misaligned Execution".

In x86 Architecture, instructions have variable lengths. Unlike other architectures like ARM.

An instruction can be 1 byte, 2 bytes, or up to 15 bytes long.

This means the processor does not enforce strict alignment. It just reads from wherever the RIP points.

If we jump into the middle of an instruction, the processor will interpret the bytes differently.

Let's apply this to our variable score. In Assembly, it looks like this: MOV DWORD PTR [RBP-4], 50009 This instruction moves the value to the Stack.

Now, let's look at the Machine Code (The Hex). It looks like this: C7 45 FC 59 C3 00 00.

C7 is the Opcode for MOV. 45 FC specifies the location [RBP-4].

And 59 C3 00 00 is the value 50009 stored in Little Endian format.

If the RIP points to C7 (the start), the CPU executes a MOV. Harmless.

But what if we direct the RIP to Address + 3? We skip C7, 45, and FC.

The RIP lands directly on 59. And what is 59? It is POP RCX.

Then it reads C3. Which is RET.

We just hijacked the instruction! We ignored the MOV and executed the data inside it.

This is Misaligned Execution. We found a Gadget where none was supposed to exist.

This brings me to a crucial advice. Don't learn Assembly as a programmer.

Programmers learn Mnemonics: MOV, CMP, ADD. They learn the logic.

But you must learn it as a Reverse Engineer. You must understand the Physics.

For example, take the CMP (Compare) instruction. A programmer thinks: "It compares two numbers."

But physically? It is exactly a SUB (Subtraction) instruction.

It subtracts the second number from the first. But it discards the result.

It only updates the EFLAGS register (Zero Flag, etc.). This deep understanding allows you to see things others miss.

When you see MOV, don't just see "Move". See "Copy". See the bytes.

See the potential Gadgets hiding inside the operands. This is the difference between a Script Kiddie and a Hacker.

This deep understanding allows you to see things others miss. When you see MOV, don't just see "Move".

See "Copy". See the bytes. See the potential Gadgets hiding inside the operands.

Now, let's address a confusing topic for many: Little Endian.

You noticed that 0xC359 was stored as 59 C3. Why did the processor reverse the bytes?

This is not random. It is an architectural choice. Intel x86 uses Little Endian.

It means the "Least Significant Byte" (LSB) is stored at the "Lowest Address".

Imagine the number 50009. In Hex, it is C3 59.

59 is the smaller part (LSB). C3 is the larger part (MSB).

The processor places 59 first in memory. Why? For arithmetic efficiency.

When the processor performs addition, it starts from the right (the ones place), just like us.

By having the LSB first in memory, it can start calculating immediately without reading the whole number.

It handles the "Carry" bit more efficiently. This is pure physics and electrical engineering.

As a Reverse Engineer, you must memorize this. What you see in the register (Big Endian)...

is reversed in memory (Little Endian). So 0xC359 becomes 59 C3.

Let's look at the code again. MOV [RBP-4], 50009.

The Machine Code is: C7 45 FC 59 C3 00 00.

C7 is the Instruction Opcode. We want to skip it.

We want to land on 59. So we need to calculate the Offset.

The address of the instruction plus 3 bytes. This new address is our "Gadget Address".

When RIP hits this address, it sees 59 (POP RCX) and C3 (RET).

We have successfully turned a data variable into executable code.

Now, let's start the "Gathering Phase". We need to collect our addresses.

We need 3 critical pieces of information: 2. The address of the string "cmd" (The Argument). 3. The address of the system function.

2. The address of the string "cmd" (The Argument). 3. The address of the system function.

We are doing "Static Analysis" right now. We are assuming ASLR is disabled for demonstration.

In a real scenario, addresses change every reboot. But the offsets between them remain constant.

For now, we take the absolute addresses. We go to x64dbg memory map.

We find the address of "cmd" in msvcrt.dll. Write it down.

We find the address of system in msvcrt.dll. Write it down.

And we calculate the address of our score variable. And add 3 to it. Write it down.

Now we have our "Ransom Note" fragments ready. We just need to tape them together on the Stack.

Now we have the addresses. Let's construct the Payload mentally first.

3. We overwrite the Return Address with the address of our Gadget (POP RCX).

4. Immediately after, we place the Argument. The address of the "cmd" string.

5. Finally, we place the address of system. It looks perfect, right?

But in the world of x64 exploitation, there is a hidden trap.

It is called "Stack Alignment". If you run this payload, it might crash.

Why? Because of the system function. Inside system, there are SIMD instructions.

Specifically, an instruction called movaps. Move Aligned Packed Single-Precision.

This instruction is very strict. It demands that the memory address be 16-byte aligned.

It means the address of the Stack Pointer (RSP) must end in 0. (e.g., ...00, ...10, ...20).

If the RSP ends in 8 (e.g., ...08, ...18), movaps will trigger a Segmentation Fault.

When we perform a CALL instruction normally, it pushes the Return Address (8 bytes).

This naturally misaligns the stack by 8 bytes. The compiler usually fixes this inside the function.

But here, we are manually messing with the Stack. We might inadvertently leave the RSP misaligned.

If our chain jumps to system and the Stack is not aligned... Crash.

So, what is the solution? The solution is surprisingly simple.

We add a "Do-Nothing" Gadget. We add an extra RET instruction into the chain.

What does RET do? It pops 8 bytes off the stack.

If the stack ends in 8, popping 8 bytes will make it end in 0 (modulo 16).

It fixes the alignment! So the final chain becomes: POP RCX -> Argument -> RET (The Fix) -> System. The RET gadget simply jumps to the next item (System).

But crucially, it shifts the RSP by 8 bytes, satisfying the hunger of movaps.

So, the extra RET instruction acts as a bridge. It consumes 8 bytes from the Stack.

It re-aligns the Stack Pointer to a 16-byte boundary. And then it passes control to system.

This is the hidden art of exploitation. It's not just about logic; it's about architecture.

Now, let's look at the Final Payload Structure. This is what we will send to the program.

2. The POP RCX; RET Gadget Address. To prepare the register.

3. The pointer to the string "calc" (or "cmd"). This is the argument for our function.

4. The RET Gadget Address. The "Stack Alignment" fix.

5. The system Function Address. The final destination.

We press Enter. And there it is! The Calculator launches.

We have successfully bypassed NX and ASLR (conceptually) using only the system's own resources.

This brings us to the conclusion. Your knowledge is your only true weapon.

If you don't have an IDE, or Debugger, or powerful tools in a real engagement...

You will be helpless if you only rely on them. You won't be able to analyze or execute.

But if you have the knowledge, you become self-sufficient.

No matter how variables change, or if tools are unavailable.

We know reality is different from a test environment. Here, tools were available, and we used Debugger Mode.

But in the real world, you might only have a terminal. And your understanding of the bits.

"You are crazy if you think it's finished." "I've just started."

"You are crazy if you think it's finished." "I've just started."

Loading...

Loading video analysis...