Sunday, September 6, 2026

SUDO INIT 6: Rebooting Your C Journey

 

                                                    Yeah, let's do just that. So grab some popcorn and lean back.

 

I've said it again and again: To teach is to lie.

 

I'll simplify things. A lot.

Not that the details aren't interesting, but if I start throwing around PTEs, PFNs, page faults, kernel internals and CPU privileges from the beginning, nobody is going to read this.

Worry slightly, though. I'll throw some stuff your way. End of warning.

 

I Wanted a C project


I've been learning C for a while and I genuinely like it. I like it a lot. But I don't like the "just build a project!" approach to learning.

Build an app.

A website.

A to-do list.

A game or something that simplifies your life.

 

Just do a project = me dead inside

 

Yeah, that's not me. I'm not a programmer. I don't want to build an app and spend months adding features, fixing bugs and maintaining it. That's not me. To me, these kinds of projects are mood-killers.
 

What I do enjoy is understanding what the machine is actually doing. I want to use C to ask questions about the stuff I really want to understand.

And I've been having some success learning C and Assembly while also playing around with microcontrollers and lower-level concepts: memory, pointers, addresses, system calls, how OSes work, and so on. 

 

A Memorable Idea?

 

 I knew that a process doesn't deal directly with physical RAM addresses. It works with virtual addresses.

 

The CPU and OS cooperate to translate those virtual addresses into physical addresses. The OS sets up the mappings; the CPU's memory-management hardware uses them.

The details are considerably more interesting, but let's keep this mental picture for now:

Process --> Virtual Address --> Translation --> Physical Address 

 

I also knew that, under certain conditions, the relationship between virtual and physical addresses could change.

The same virtual address could end up referring to a different physical page.

So, could I force those conditions? Can I make one of those addresses change?

Hey, isn't this a project of sorts?

 

I was hyped. And that's a good sign. If you are ecstatic about doing something that is useful for what you're studying, something you actually want to become good at, that's a good sign. 

 

The Plan

 

 I started by asking myself some questions and starting small:

- Can I get the PID of a process? Of course, easy.

- Can I allocate a page? Probably.

- Can I print the virtual address? Sure. 

- Can I figure out which page that address belongs to?

- Can I find the corresponding physical page?

- Do I have enough privileges for anything related to the physical address?

 

And finally, when I know I can access all of this and print it to stdout:

- Can I make the relationship change while the process is still alive? 

 

Oh, but for context mmap() in ASM was the touchstone that made me trip into this rabbit hole. I even considered building my whole project in ASM, but then briefly considered how much pain I was willing to accept. So C it was!

mmap()


If you haven't used it before, mmap() is a Linux/POSIX interface that lets a program create mappings in its virtual address space.

 

For my first experiment, I used it to create a mapping for a single 4096-byte page of memory. Remember that 4096 bytes in decimal is 0x1000 in hexadecimal. 

 

This was important because 4096 bytes is also the page size I'm working with here.

A 4096-byte page needs 12 bits to identify any byte within it, because:

4096 = 2¹²

So, the lowest 12 bits of a virtual address tell us the offset within the page. 

Everything above those 12 bits gives us the virtual page number.


Once we have the virtual page number, we can find the corresponding entry in pagemap

There is one 64-bit entry in pagemap for each virtual page. So, to find the entry for our virtual page, we multiply the virtual page number by 8. 

 

A pagemap entry consists of several pieces of information. I care here about the Page Frame Number (PFN), which tells me which physical page is backing the virtual page. 

Then I wrote a value into that memory:

42 

 

So far, nothing particularly exciting. Just moving along and gathering our LEGO bricks.

 

But now we had our program, a process, a virtual address, a page, and a value stored at that address.

I made the program request the user to press the Enter key. This way, it was kept alive.

 

But where is that page actually sitting in physical memory? 

Linux provides an interesting interface for this:

/proc/<PID>/pagemap

 

This is a kernel-provided interface that lets a userspace program inspect information about a process's page mappings.

In particular, a pagemap entry can contain the PFN, corresponding to a virtual page.

And, from here, the physical address is fairly straightforward:

Physical Address = PFN x page size + page offset

  

So, now I had all the bricks that I needed.

I also learned that you needed the appropriate privileges to actually access the pagemap

Root isn't necessarily equivalent to having every Linux capability. On my system, I needed the privileges associated with CAP_SYS_ADMIN to obtain the PFN information from pagemap.

And that gave me another little rabbit hole: being root and having a particular Linux capability aren't quite the same thing.


 

 

 

 

 

 

 

 

  

 

 

 

 

 


Experiment A (first steps) or, as  Ilike to call it: To Sudo or Not to Sudo 

 

The Experiments

 

I called these the experiments. Gathering all the tools I needed was Experiment A. I now wanted to jump into Experiment B, where I'd try to change the physical address.

I knew one cheaty way to do it.

I knew about Copy-On-Write (COW). I'd recently been reading about COW in another context, including some security research involving COW-related behaviour.

So this seemed like a natural place to start.

 

The basic idea behind COW is pretty simple to understand:

Suppose a process has a page containing some data. Now we create a child process with fork() - see here.
The child gets its own process and its own virtual address space. 

But, for the moment, the kernel has no need to immediately make a complete physical copy of every page.

Instead, parent and child can initially refer to the same physical page.

The mappings are made effectively read-only (this is important) and the system keeps track of the fact that the page can be copied if someone tries to modify it.

  

Experiment B

 

 

 Why 666? Because Gilfoyle is my spirit animal

 

Now, my child process did the following:

*(int *)addr = 666;


The CPU detects that the attempted write isn't permitted by the current page-table entry and raises a page fault.

Now, before you start thinking segfault, nope. 

A page fault does not automatically mean something has gone wrong.

A page fault is a mechanism, and the kernel can handle it.

In this case, it recognizes that this is a legitimate COW situation, creates a new physical page for the child, copies the contents, changes the child's mapping, and allows the write to continue.

 

And, voilĂ ! Same virtual address in two processes, different physical addresses.

So, slight cheating, but not entirely.

 

Also, see the weird thing up there? Maybe you don't. Compare that image of experiment B against my running that exact same program a second time:

 

 See it now? Both processes are writing to stdout and we cannot predict which one will print which line first. 

 

For a moment, I was baffled by this, but then I remembered what fork() actually does. It's not instructing the child to run after the parent runs. It means:

There are now two concurrent processes capable of running. Run them.

The scheduler decides when each gets CPU time.

So, voilĂ ! An accidental demonstration of concurrency. You're welcome. It wasn't planned.

As for concurrency, that's another rabbit hole. Rabbit holes everywhere you look. Reminds me of math classes and the Real line. My professor used to draw rabbit holes there all the time. Fun, fun, fun.


 Experiment C

 

 Single process, same physical page, two different virtual addresses.

I now wanted to remove fork() from the equation. 

 This required a different approach, and I used a POSIX shared-memory object and mapped it twice into the same process.


Think of the shared-memory object as the thing being mapped. The mapping itself isn't the memory.

I asked Linux to map the same object twice.

So, now Linux could give me two different virtual addresses while both mappings referred to the same physical page. 

 

  

 

 

 

 

 

 

 



Note: it is not a given that the two addresses will be adjacent. 

 

I could inspect both virtual pages through /proc/<PID>/pagemap, once more and check that the PFN was the same and, therefore, the same physical page.

 

The Issue

 

I had technically answered part of my original question, but I had used fork(). What if I didn't?

Could I get a single process, keep the same virtual address, and somehow make Linux replace the physical page underneath it?

That became the next question. 

And this is where the project is currently sitting.

I already received a very interesting suggestion for how to approach it, involving madvise() and memory pressure.

 

I haven't properly investigated it yet, so I'm not going to pretend I understand it well enough to explain that here.

That's the next rabbit hole, and one you might want to trip into yourself. All quite accidentally, of course.

 

 

 

 

 

 

 

 

  Yup, I'm a cute little sunrise.  

 

 

 

 

 

 

 

I started this little field trip because I wanted to learn C and understand virtual memory better. And, along the way, I got to touch on:

- mmap()

- pointers and pointer casts

- virtual pages and page offsets

- PFNs

- /proc/<PID>/pagemap

- Linux capabilities

- COW

- page faults

- shared memory

- multiple virtual mappings

- process IDs

- scheduling and concurrent processes

- ... 

 

If anyone reads this and is interested in its innards, here's my Github. Mind the step, not the mess.

 

The code is deliberately small, and certainly not a production-quality memory inspection tool.

It's an experiment. Or a series of them, actually.

Something you can compile yourself, run, modify, break, and have fun with. 

 

One last thing:

The little snippets of these experiments that I posted on LinkedIn were well received. People have suggested alternative experiments, pointed out things I wasn't considering, asked interesting questions which themselves gave me ideas of stuff to try.

That's been rather nice.

I don't pretend to be an expert in any of these things. Quite the contrary. So I'm humbled and grateful when those that know more than I do chime in and give their input.

 

I was going to say keep digging, but that's not necessary. Just accidentally trip into any of the rabbit holes that are all around us. And be perpetually amazed as you fall deeper and deeper. 

 

 

Thursday, May 28, 2026

We Failed the Turing Test Before Machines Did


 


We anthropomorphize everything.


Cars get names.

Roombas become pets.

Soldiers mourn bomb disposal robots.

Children punish tables after bumping against them.


We do this automatically. Against our will, sometimes.


Which is why the Turing Test was probably doomed from the beginning.


We treated it, in a way, as the summit of machine intelligence: “Can a machine fool us? Will it ever? Can we talk to a machine and not know it’s a machine?”


But that was never the test. As it has been revealed, it was mostly a test of human projection. And LLMs exposed this in an uncanny fashion. Once they became mundane, the game was up.

Not because they became conscious (more on that later) but because they became socially convincing enough.


Arguing otherwise is like trying to argue that a computer will never outmatch a human at chess while the Deep Blue vs Kasparov matches are happening. That argument is already over. What you are arguing for is now an engineering problem, not a mathematics or scientific problem.

The remaining objections are mostly engineering constraints: speed, memory, latence, cadency, fidelity, ...


That’s it. That’s done.


We humans infer agency extremely aggressively. We are meaning-generating machines.

We look for intention in weather, order in tea leaves, morality in chaos and consciousness in fluent language use.


And we mistake limitations in our conceptual frameworks as limitations in reality itself.

Reality is doing fine. Nature is under no obligation to conform to the limitations of our models.

We’re the ones that need to adapt.

Fortunately we do just that, and we do it fast.


An adaptation of Zeno's Paradox: the arrow cannot go from start to finish because it always needs to cross half the distance, and then another half of the missing half and so on and so forth?

Nope. Please get better math. Create the theory of limits, and you’ll understand why the arrow is always reaching its target.


The first principle is that you must not fool yourself – and you are the easiest person to fool

Richard Feynman


Of Consciousness

 

I always found Searle’s Chinese Room intellectually lazy.

Not because the intuition is wrong, but because the mechanism felt hand-waved into existence.




Peter Watts took that intuition seriously, though.

He weaponized Searle’s idea and made the Chinese room into something more than hypothetical. It became optimized and self-sustaining.


Watts posits in Blindsight not only that Consciousness isn’t necessary for intelligence, but that it can actually be detrimental, an evolutionary slip, a mishap.

 

That wonderful book does much more than that, but I don’t want to go too much into the nitty-gritty of it, because I think anyone who loves Sci Fi should absolutely read Peter Watts’ Blindsight.


Back to LLMs, the Turing test and of human inevitability.


The unsettling possibility is that the Turing test was never a test on machines but a demonstration on human limitations waiting to happen.

We require far less complexity and depth than we imagined before attributing understanding, intention and mind to language output systems.


What matters then? Should we just wave our arms in the air like we just don’t care?


I think that logic, rigor, curiosity and scientific method matter now more than ever.

Because, once more, we realize that humans remain deeply vulnerable to narrative, projection and linguistic fluency.


LLMs did not suddenly create self-deception. They industrialized it.


Many of the frictions that forced us to slow down, investigate, compare, doubt and eventually learn, have been bypassed.


The real question is whether our capacity to adapt can keep pace with the mirrors we are building, or whether we will simply become fascinated by our own reflection.


Sunday, October 19, 2025

Hashing Isn't Encryption — Explained Simply

 

                    I'm sure this image will be a total viewer-magnet, because Math = Sexeh

 

Everyone has heard of encryption, and most understand the basic idea. 
You have a message, apply an algorithm, and you get an encrypted version of that message. 

To go back to the original message, you use the same key used to encrypt it.

In other words, encryption allows you to move back and forth between the clear message and its encrypted form.

This is a one-to-one relation. 

 

                    Left: original message; Right: encrypted message (high-quality visuals)

---

Now, Let's Talk About Hashing, Shall We? 

Seems to be the same idea, really:

Put something in, hash it, get something out. But it behaves very differently.

Imagine: to your left, a world of infinite possible cleartext messages. In the middle, the hashing function. And to your right, the resulting hashes.


Here's the key difference:

The group to the right is smaller than the one of original messages.

Different messages can end up producing the same hash — this is called a collision.

  

In other words: there is information loss during hashing:

Your final group of hashed messages is "poorer" in terms of information than your original group.

 

                                         Left: original message; Right: hashed message (gorgeous)

 

---

A Simple Example

 

Let's clarify this with a toy example.

Imagine a small world where every message is made of 4 different digits, each one between 0 and 3.

Here's our original group of possible "cleartext" messages:

(0123)    (1023)    (2013)    (3012)

(0132)    (1032)    (2031)    (3021)

(0213)    (1203)    (2103)    (3102)

(0231)    (1230)    (2130)    (3120)

(0312)    (1302)    (2301)    (3201)

(0321)    (1320)    (2301)    (3210)

 

 And here's our toy hashing rule:

    Take the first two positions (the leftmost digits).

    Look at the indices that those digits represent.

    Swap the values at those two positions.

Example: 

(0213)

 First two digits are '0' and '2'.

(0213)

Now we swap the values at positions '0' and '2':

(1203)   

This final number is our hashed value.

---

What Happens When We Apply This To All Messages? 

- Some original values won't be found in the hashed group (information loss).

- Many of the original values will map to the same hashed result (collisions).

- Ergo, we can't always reverse the process and retrieve the original.

---

Let's Showcase This With a Particular Number

 

Suppose you pick this final (already hashed) value:

(2301)

There is only one way to get to this value, so we can actually reverse our number and get to the original

(2310) -----> (2301) 

 (check the top image or, even better, try to make the table yourself)

But now take this hashed value:

(3201) 

 

This one can actually be produced by three different original messages:

(0231) ----> (3201) 

(3102) ----> (3201) 

(3210) ----> (3201) 

Which one was the original one? Can we be sure?

 ---

Key Takeaway

 

Hashing means: 
- Information Loss

- Many-to-one relationship

- Reversibility not guaranteed

 

Even in this toy example, some hashes have several origin messages, leading to the same output. In fact, the original group has 24 unique values and the end group has only 12 unique values.

And real life examples are much, much more complex than this, making even finding collisions computationally impractical. But the principle remains.

 

---

 

And remember:

To teach is to lie (a little). 

Hashing (and encryption) can be way more complicated and interesting than this. 

For more information, check this link and this link

Also, this simple algorithm can be found in the fine (and free) "Reverse Engineering for Beginners", by Dennis Yurichev. 

Check that out too.

Have Fun!

Saturday, October 11, 2025

How Computers Think: A Liars Walk Through x86 Assembly

 

                                                                                    So, yeah. The artist found out about digital art.
 
 

"To teach is to lie."

 

A few days ago I promised to talk a bit about a simple Assembly program.

And talk I shall!

First off, let's just get out of the way what that code is doing:

It takes any number of integer positive values as parameters and adds them.

So, if you run the program with ./<program_name> 40 2 (which we shall in a bit) it will print out the answer:



If this were a Python program, it would be simple as can be. But Python is withholding from you a lot of what is happening behind the scenes. And we want none of that. We want to see stuff as it is happening.

 

First of all, a couple of disclaimers:

- This code isn't as simple as it could be. It's in fact part of a series of lessons I'm going through in order to review and solidify my ASM and debugging knowledge.  They are part of a NASM tutorial (ASMtutor), which I highly advise — it's quite carefully made, without major errors (very common in basic ASM stuff) and which guides you in a smooth manner towards greater knowledge. You can find it here.
- The code I'm using can be found here.

- To create a compiled binary of this code, in particular of the files functions_v1.asm and 13_atoi.asm, you need to download those two files and run: 

nasm -f elf 13_atoi.asm

ld -m elf_i386 13_atoi.o -o atoi

 This will create an executable named atoi, which you can then run as explained before.

- You should know a bit about binary and hexadecimal. Please take a quick gander at this. Knowing how to quickly translate between them will really help to inspect the debugger.
- And speaking of which, I'll be using GDB with GEF and I advise you to do so as well. If you already rock on GDB you probably don't need it. But then again, I highly doubt you'll be reading this if you already have mastered GDB. 

As for GEF, it will make the debugger much more pleasing to the eyes, and your code easier to follow.
- To finally start the debugger (GEF) with our program, we'll run:



- My focus isn't a 'deep dive' into Assembly. I want to give you the very basic and essential tools to be able to go through this code, instruction by instruction and be able to understand how the computer is 'thinking'. In order to do that, I'll give you some basic tools.

- Speaking of which, here is a pdf with the few commands and concepts you'll refer to in order to be able to follow through the code.
- Functions in Assembly are interesting. You can call a function or jump into a function (check the PDF), but also can just enter a function because you're going through the code flow and there is no call, no jump (or there isn't a reason to jump). So, for example, in this case:

You have here a comparison and you are checking if eax is equal to 0, when you do cmp eax, 0. Then you have a jump not zero. If eax is NOT zero, then you jump elsewhere to the function divideLoop. But if eax IS in fact zero, then you just move along that grocery shopping list to the next item on the list. Therefore, you enter the function printLoop and decrease ecx by 1. 

 

On the other hand if printLoop actually had a dot behind it (.printLoop) it would be a function inside of whatever function we are in, and couldn't be called from outside of that function. If in doubt, dig a little deeper into Assembly. 

- You might also need a quick contextual/theoretical rundown, so here it goes:

Assembly is the lowest level code you can have that is still human-readable. Below it is the world of ones and zeroes, and above it the world of different programming languages and their way to read and write programs.

In assembly, we're working in the so-called User Space, but sometimes we need to give control to the Operating System through system calls. To do that, we use interrupts (Here is a link to a nice Linux system call table. If you use Windows we can't be friends, sorry). You won't really need that table in order to follow along and read the code, but it sure helps. We only use two syscalls in this code — one for writing to the terminal, and another to exit cleanly. (look up: do the registers change after the OS is done with them?).

When the syscall happens, the OS takes control of the program, and uses the stack and your registers. After that, you cannot be 100% sure of how all registers will be, so if it matters to you, like what happens in other situations, make sure you save those registers into the stack and get them back after the OS lets go of control and returns it to User space.

 

There are a couple of bitwise operations - basically XOR ('exclusive or'). To learn more about bitwise operators, check this page. And if you like computer games and are even slightly interested in electronics or logic, then take a gander at this game. It's great fun.

But, again, you don't have to jump into bitwise operations to read this code. Just do a mental substitution. Whenever you see, for example xor eax, eax it's basically the same as taking zero and placing it in the register eax or, in other words, doing mov eax, 0

Learn boolean logic, bitwise operations, and everything else because you want to and because its fun. :) 

 

What is a register? What is the Stack? You probably heard about those two terms before. Here's a very quick and dirty analogy:

Imagine you're sitting at a desk and I'm telling you to remember things, like a name, then a number, then an address, then another name. Sure, you can do it for a while. Your short term memory will be able to memorize some stuff before it's too much stuff to handle. Those places where you're storing this easy-to-remember, short-term memory stuff are the registers. Registers are temporary containers for fast memory access. Incredibly fast memory access. A bit like short-term variables on steroids.

But what if I start giving you too much stuff to remember? Then you are allowed to write whatever information I tell you on small pieces of paper, as long as you store them properly on a receipt-spike (also known as an order-spike). You've seen them before in restaurants. Something like this:
 

                          Analogies R Us: each strip of paper a memory location, an integer, etc
 
This stack, or this order-spike, uses a LIFO system, meaning Last In First Out. Whatever last piece of paper with information you have pushed into that pile, is the last one you can pop out and examine.

Neat, no? So, you can store x amount of things for very quick access inside those registers, and you can push whatever you can't hold in quick access memory into that pile, and then you can take it our and again place it in a register if you so wish (actually, you push copies of those values — the originals remain in registers unless you clear them).

All that I've said so far is to be taken with a pinch of salt. Not only because these last bits are an analogy, but because, like I said in the beginning, to teach is to lie. I have to omit stuff in order to let you do some progress.

Imagine us teaching Math in class and instead of telling kids all over the world that you can't divide by zero, we instead said, there are several instances in mathematics where dividing by zero is perfectly sensible, and then described said instances. Can you imagine the faces of those 7th graders? Nothing would be retained.

By lying to them, they learn a useful rule and important aspect of mathematics, and when they advance in their mathematical careers, they get to learn at least of one way to actually divide by zero.

It's the same here. I'm skimming a lot of fat in order to draw an understandable picture, and to let you (if you so wish) take your first steps in Assembly, which I truly love.

Got any questions? Stuff you don't understand? Stuff you'd like to see? Shoot them in my general direction. Search on Google, use an LLM ("gulp, LLMs?! But aren't they the devil?". Look, I was here when Wikipedia first came up and academia was having a fit over it. I also survived using AltaVista back in the 90s. You'll do fine with LLMs and live to tell the tale).

As I've shown in one of the pictures above, we'll be using our program to add 40 to 2 and to find out the Answer to the Ultimate Question of Life, the Universe, and Everything.

I won't go through the whole code. It would serve little purpose and, besides, you need to walk the walk.


                                                     Ah, Jayne... you look young.
 

 So, back to business!

 

You need to:

- download the two .asm files;

- compile them into a binary file;

- install GEF (GDB probably already installed);

- download the PDF with some information;

- Run GDB and check what happens to the Registers and to the Stack as you step into the program.

 

And that's it! We got a party going.

 

Ok, let's pretend anyone is actually reading this and actually is inspecting the program with the debugger. You run the code in terminal to open the debugger, then when it opens, you write start and press enter.
What might you be seeing at this point in time? Something like this:


                                        Already regretting your very recent life decisions? Don't! This stuff is fascinating.


What a bloody mess! I know. Don't worry. The first time I started looking at this I was pretty confused as well. But let's first try to get some 'unhelpful' information out of the way (ahem, teaching is lying) and focus on the meat of this and at what we'll be focusing on exclusively. So, clean-up time!

 

                                       Some breathing room, at last!
 

Aha, so what do we have here?

We have 3 different screens:

- Registers

- Stack

- Code

 

Lucky us! Because that's exactly what we want to follow as we're moving along our code. Let's start with the registers. If you've looked at your PDF, you'll see the stars of our show there. 

Register Screen:

esp is the stack pointer and it is always pointing to the top of the stack. It's not holding the value at the top of the stack, which is the value 3, but it's holding the value 0xffffd0f0.

Huh.. interesting. If you look at the menu below, you'll see that that's the exact stack address of its topmost value. Memory position 0xffffd0f0 is the topmost piece of paper (like its identifier) and in it we have written the value 3 (more on why 3 later). Everything else we care about is (apparently) 0 for now, so let's move on.

Stack Screen:

Remember that pile of papers stuck at the order-spike? This is it.
 Here's an example of one of these lines:

0xffffd0f8│+0x0008: 0xffffd2e0  →  0x32003034 ("40"?)

Let me explain it to you. 

0xffffd0f8  memory location in the Stack. This is represented in 4 byte intervals, since we're in a 32 bit program

+0x0008  Memory offset from the top of the stack. This means that we're in a position 8 bytes above the position of the top of the stack (0xffffd0f0). As we add stuff to the stack the memory values go down. This is an important quirk that you would do good in remembering. Also note that while this has the stack facing upwards, sometimes you'll see it upside down (which makes the memory values 'growing' down appear to be a bit less strange).

0xffffd2e0  What is stored in that stack position (our piece of paper). In this case it is a pointer to a string. That string is '40'. So, in other words, there is a location in memory that is storing the string '40' and in this stack there is a pointer pointing to the first character of that string, or '4'. Read on, it will be made somewhat clearer. You don't need to know in depth what are pointers, etc to be able to read this program, but knowing these things de-mystifies them plenty and makes your life easier and more enjoyable. 

(bonus cookie points for you if you can explain why, as you go up in the stack you go from  0xffffd2e3 to 0xffffd2e0, a small 3 byte jump, and then follow with a big big jump from 0xffffd2e0 to 0xffffd2a5 a 59 byte-sized jump)

0x32003034  Well, I see a 40 and the beginning of a 2. :) Let me explain:

As 40 and 2 were used as arguments in our program, they were added to the stack. But they weren't added as integers. In fact, they were added as ASCII characters. 


 We're working with hexadecimal characters and we're looking for the value 40, right?

So, 0x32003034. You'd possibly expect to read from left to right, but we're working in little-endian here, so in fact our number will be presented right to left. And if we look at that table, we would be reading (now reversed) 4, 0, NUL, 2. That NUL is the null byte which marks the end of that string. And I'd bet you my breeches that after that 32 there will be immediately to its left another 00, since these strings are being stored next to each other. If you're paying attention, you'll know I was cheating and you won't want to bet against me as you read the next value in the stack 0x48530032 and see, in black and white that '00 32', which translates to the number 2 (in decimal). You might be tempted to continue translating that line and read 'SH..'. Interesting, no? But I'll leave it to you to find out more.

But you might be asking, why is the Stack in this initial state? And that is a perfectly valid question. As we load up our program and enter its two parameters 40 and 2, we have automatically added 4 things to the stack, in this order (from bottom to top):

- a pointer to the last parameter (our third argument, or argv[2]);

- a pointer to the first parameter (our second argument, or argv[1]);

- a pointer to the program itself, its full path, in fact (our first argument, or argv[0]);

- the number of arguments  (argc).

As you start reading the program and debugging it, you'll see the stack add more items and remove them. 

Code Screen:

This is where you follow your code. the green arrow and green letters shows you where you're at in the code, and the red dot tells you that there's a breakpoint here. If you're into learning more about GDB (please do) then you can try this link as well. This line of code is the next to be executed. And as you press si ('step into') you'll move along the code, Assembly line by Assembly line.

 

So, let's do just that and run si some 4 times, until we get to that compare (cmp). How will that code look like? That Stack, will it change? And the Registers?  Let's see:


 So, what happened? We started by popping the value on the top of the stack and placing it in ecx. So, in that next step you'll see that ecx = 3. Then we do the same and place that value, a pointer to the function name, in edx, removing it from the top of the stack. Finally, we decrease ecx, basically subtracting 1 to the value within and we xor edx, edx which is basically the same as doing mov edx, 0. ecx is holding the number of variables used in our Math and edx is emptied for future use. See how that works? If we push, we take the value in the register and copy it into the stack, and if we pop we remove the value from the stack and place it in the designated variable.

I'm stopping here with the guided code lesson. I'm leaving it up to you. You do have all the tools that you need to do so.

Remember tidbits like: if you call a function, you jump to that function, instead of continuing down the lines of code, but you place the line of code right after the call on top of the stack (the return address is pushed onto the stack). When you hit the instruction ret, on the other hand, you pop whatever value is on top of the stack and go directly to the line that is referenced in that value (a pointer to a memory value). 

GEF will be kind enough to tell you when a jump instruction isn't fulfilled (which means that instead of jumping into a function you continue reading the code below that jump).

Pay also attention to the name of the function and on how far you are away from its start. For example, in this case, I'm inside the inner function .multiplyLoop, inside the function atoi:

 


 

Here's some more stuff you might want to get into:
 

- what is the register eip doing?

-  What's with bl, BYTE PTR [esi+ecx*1]? Can you guess what's happening there?

- Pay attention to where you're going and what's happening when you run a ret instruction.

- Perhaps write down all the commands in a piece of paper and try to 'guess' how the flow should work until the very last operation. Then compare to what you see on GDB. Does it match?

And that's it! You're now ready to start discovering more and more about this wonderful world, or you're thinking that anyone that likes this must be a bit crazy.



Either way, you cannot unsee it now. 
Have fun and hack away!

SUDO INIT 6: Rebooting Your C Journey

                                                                  Yeah, let's do just that. So grab some popcorn and lean back.   I'...