I’ve decided to do something a little different with this series. It’s not just going to be a bunch of blog posts. Nope! I’ve decided to turn this into a full course—complete with videos, blogs, projects, and challenges.
Honestly, it will challenge me more than it offers something to you. I’m trying to learn by doing what I love. But the goal isn’t just to read a book and blog about it. I want to be practical and output as much as I take in.
Hopefully, you’ll enjoy the journey… I mean, at the end of it, you’ll see me hoping that you enjoyed it. I’m not assuming you’ll read all of this, am I? You might identify as not reading this blog—just as I identify as a penguin 🐧.
I’m taking a different approach here. As my name suggests, I’m a Muslim. Actually, a Muslim who supports Palestine, and I’ll weave that into this series. You can expect a combination of Islamic stories, History, and related projects alongside the usual technical topics I dive into it.
Throughout this whole journey, I’ll be building projects I genuinely enjoy. I’ll also be solving CTFs and replicating them, hosting the source code on each blog so you can understand it. And of course, I’ll be recording YouTube videos and embedding them in each blog post.
Before diving into anything, I like to study what it actually is. What does it imply to me? What kind of hat am I putting on here (at least, it’s not a kippah 😄), and what does it mean to others?
When I first started, I asked myself: “What does hack even mean?” As someone who doesn’t trust history much, I found one story I actually liked:
“A person who enjoys exploring the details of programmable systems and > how to stretch their capabilities, as opposed to most users, who prefer to > learn only the minimum necessary.”
That’s how the Jargon File defines it, and honestly, I like it. It really describes me.
That’s what I aim to do—learn, apply, fail, and apply again after failing (probably a couple more times). So, this blog may be just another failed success.
Apart from the title of this blog which implies that it’s going to be about hardware security and hacking, which actually it is to some extent.
I can’t just learn how to hack something without learning what is it that I am trying to hack, in this case, the hardware.
Actually, it’s a pretty easy task depending on what level of abstraction you are looking from, for example, creating it from the very bottom up including a ton of knowledge and working hours to make the smallest set of transistors, busses, and each logical gate which is way out of our scope, my scope is to understand how the hardware works so I can build up a computer from whatever components available given they’re usable for such a task
so to build one we need first to understand what makes a computer and what actually defines one to be
“A computer is a machine that can be programmed to automatically carry out sequences of arithmetic or logical operations.”
and that’s actually true if we disregard the scale, the power and speed of the operations everything around us becomes a computer in one sense or another and that might include but is not limited to, of course, your TV, smartwatch, toothbrush if you care so much to get a programmable one per se, or even a toaster.
but that’s the normal perspective of what makes a computer to the normal user or consumer, which we are not in this case, what makes a computer at least for me, is anything that YOU use because if it exists it means it’s hackable and if you use it, it means you leave a part of your personality in your preferences and daily routine using that device which means more fun for me or Us for that matter.
but all of these perspectives doesn’t shadow the full truth of what a computer actually is under, it actually consists of 5 basic components that make up any computer if applied to its design
See, all electronic devices kind of have these 5 pillars, let’s say your earbuds or airpods whatever air pollutes your ear, includes input in the shape of microphone, output as for the speaker, and working memory which might be very tiny as it only saves some data to be output by the speaker and permanent which holds its core instructions.
The CPU or in other words, the brain of every device, this is where all the calculations happen, not really, but mostly it is. and actually, it doesn’t do the calculations as we perceive them in our real world of maths of physics, it actually uses a different simpler system simpler for the computers not us actually which is binary that is zeros and ones
it is the one that computes the arithmetic, logic, control, and input/output operations as instructed by the programs that are on the computer.
and the components that make it up consist of the Control Unit (CU), The Arithmetic Logic Unit (ALU), and some registers that enable it to process data instructions.
Actually, let’s talk about some components that are either in the CPU or is closely interfering with it so we kind of build a cloud of knowledge around the current topic
Name | Description |
---|---|
Random Access Memory (RAM) | This is the main memory where The CPU fetches instructions and data it is a volatile memory meaning that it loses the data in it when the power is off, that’s why automatic save was invented so whenever the power goes out you don’t actually lose everything |
Control Unit (CU) | This is the component that controls the flow of data in the CPU. it interprets the instructions from the program and signal to each specific part of the CPU to do its part of the instruction |
Memory Management Unit | This unit manages the translation of the virtual memory addresses to physical memory addresses and ensure that Its process is separated into a contained and different memory space |
Cache |
This includes the L1, L2, L3 Cache which are the smallest and fastest memory in your computer, they are located near the CPU cores only differ in size and speed.
|
Registers |
These are small, fast storages located within the CPU used to hold data
temporarily during execution and includes
|
the CPU goes through a simple loop that is referred to as the fetch-decode-execute cycle to process instruction and this can be broken down into many sub-steps that would help to enhance the processing of a certain task. This cycle includes the following stages: searching for the instruction within the memory, interpreting the instruction to know what needs to be done with it, performing the necessary action and, if necessary, storing the results back in memory.
At the fetch stage of the cycle the Program Counter (PC) contains the address of the next instruction to be executed. Instruction is also fetched from the memory location referenced by the content of the PC and after that, the fetched instruction is located in the IR. Afterwards, the PC is incremented to the next instruction to enable the CPU to prepare for the next cycle’s operation.
In the decode stage, the CU takes the meaning of an instruction that is stored in the IR. The CU produces control signals that enable the other parts of the CPU to perform the required activities like loading data from memory to perform ARITHMETIC LOGIC UNIT ALU operations by the instructions to be executed.
In the execute stage, the arithmetic logic unit ALU carries out an arithmetic or logical operation according to the decoded instruction. If the instruction is a data transfer type, the CPU loads from or stores to the main memory. In control-type operations including the jump or branch instructions, the PC is updated to correspond to the new instructional sequence.
In the store stage, the completion of the executed instruction is written back to the memory or registers based on the CPU; this is important to keep the outcome available for other instructions or processing.
This numbering system was meant to make it easier on the computer hardware to do its calculations, it is as fast as the transistor could change it’s state from one state to another and actually that MHZ that you find next to each processor is kind of a unit that measures this speed but on an upper level of abstraction
and as an example to further understand this, the letter “A” that you see everywhere and can recognize it is actually saved in the computer as 01000001
but why exactly do we need it to be only 0’s and 1’s, it’s because each and the single component in the computer actually consists of millions of transistors that work by either holding current or not holding it, full or empty, on or off, true or false. whatever you like to call it and that was the reason for calling it bi in binary.
it means that it takes 8 transistors to process that one single uppercase letter in your computer
A ”speed” which is used to indicate clock frequency. It really means: million cycles per second. The more MHZ, the more data operations can be performed per second.
it really measures the number of operations that can be done per given unit of time, which is if we break it down
this is actually a very top-level view of what’s happening inside, but it works, for now until we know what we don’t later.
and to make it even more engaging, we’ll create a very minimal emulation of a CPU using the C programming language.
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#define MEMORY_SIZE 8
#define REGISTER_NUMBER 4
#include <stdint.h>
: This header provides
definitions for fixed-width integer types, such as uint8_t
,
which is an unsigned 8-bit integer.#include <stdio.h>
: This is the standard
input/output library, allowing the program to use functions like
printf
and fopen
.#include <stdlib.h>
: This includes standard
library functions, such as exit()
for terminating the
program.#define MEMORY_SIZE 8
: In this line we define what will
be the maximum memory size.#define REGISTER_NUMBER 4
: Here we define the number of
registers we require for our emulator to work.// Define a type alias 'CPU' for the struct
typedef struct {
uint8_t memory[MEMORY_SIZE]; // Memory array of 8 bytes
uint8_t registers[REGISTER_NUMBER]; // Array of 4 registers
uint8_t register_number; // Register number to use in operations
uint8_t opcode; // Current instruction opcode
uint8_t memory_operand; // Operand specifying a memory address
uint8_t immediate_value; // Immediate value for operations
uint8_t program_counter; // Program counter to track the current instruction
uint8_t destination_register; // Destination register for certain instructions
} CPU;
CPU
struct and
implement all the parts that actually should be in a real
CPU
:
memory
: This is an array that mimics the memory and is
set to be max of MEMORY_SIZE
.registers
: An array that holds all the register values,
limited by REGISTER_NUMBER
.register_number
: Indicates the current register that it
is being operated on.opcode
: Stores the current instruction’s
(operand).memory_operand
: Holds the address of the operand that
will or is being accessed.immediate_value
: Represents an immediate value used in
some operations.program_counter
: Tracks the current instruction’s
position in the program.destination_register
: Specifies the register where a
value may be moved or stored./*
* reset_cpu - Resets the CPU state to its initial values
* @cpu: Pointer to the CPU struct
*/
void reset_cpu(CPU *cpu) {
for (int i = 0; i < MEMORY_SIZE; i++) {
->memory[i] = 0;
cpu}
for (int j = 0; j < REGISTER_NUMBER; j++) {
->registers[j] = 0;
cpu}
->program_counter = 0;
cpu->destination_register = 0;
cpu
// Output the reset status with improved formatting
("\n[INFO] CPU has been reset successfully."
printf"\n");
}
reset_cpu
function resets all the values of both the MEMORY_SIZE
and
REGISTER_NUMBER
to zero so that it is cleared./*
* print_cpu_state - Prints the current state of the CPU, including memory,
* registers, and program counter
* @cpu: Pointer to the CPU struct
*/
void print_cpu_state(const CPU *cpu) {
("\n──── CPU STATE ────\n");
printf("Memory State:\n");
printffor (int i = 0; i < MEMORY_SIZE; i++) {
(" Memory[%d]: %d\n", i, cpu->memory[i]);
printf}
("\nRegister State:\n");
printffor (int j = 0; j < REGISTER_NUMBER; j++) {
(" Register[%d]: %d\n", j, cpu->registers[j]);
printf}
("\nProgram Counter: %d\n", cpu->program_counter);
printf("─────────────────────\n");
printf}
print_cpu_state
function displays the current state of the CPU, including its memory,
registers, and program counter./*
* execute_instruction - Executes the current instruction based on the opcode
* @cpu: Pointer to the CPU struct
*/
void execute_instruction(CPU *cpu) {
("[EXEC] Executing Opcode: 0x%02X\n", cpu->opcode);
printfswitch (cpu->opcode) {
case 0x00: // LDR - Load value from memory into a register
->registers[cpu->register_number] = cpu->memory[cpu->memory_operand];
cpu("[LDR] Loaded Memory[%d] into Register[%d]: %d\n",
printf->memory_operand, cpu->register_number,
cpu->registers[cpu->register_number]);
cpu->program_counter++;
cpubreak;
case 0x01: // ADD - Add value from memory to a register
("[ADD] Adding Memory[%d] (%d) to Register[%d] (%d)\n",
printf->memory_operand, cpu->memory[cpu->memory_operand],
cpu->register_number, cpu->registers[cpu->register_number]);
cpu->registers[cpu->register_number] += cpu->memory[cpu->memory_operand];
cpu->program_counter++;
cpubreak;
// Additional cases...
case 0x06: // HLT - Halt the CPU
("[HALT] CPU halted.\n");
printf(0);
exitbreak;
default: // Unknown opcode
("[ERROR] Unknown Instruction Opcode: 0x%02X\n", cpu->opcode);
printfbreak;
}
}
execute_instruction
function executes the instruction based
on the current operand code in the asm.b
file.case
in the switch
statement
corresponds to a specific instruction:
/*
* main - Main function to run the CPU simulation
*/
int main(void) {
// Declare and initialize the CPU
;
CPU cpu(&cpu); // Reset the CPU state at the start
reset_cpu
FILE *file;
= fopen("asm.b", "rb");
file
uint8_t command[MAX_COMMAND_LENGTH];
if (file == NULL) {
("[ERROR] Could not open assembly file.\n");
printfreturn 1;
}
while (fread(command, sizeof(uint8_t), 1, file) == 1) {
.opcode = command[0];
cpu
if (cpu.opcode == 0x05 || cpu.opcode == 0x09) {
(&cpu);
execute_instruction} else if (cpu.opcode == 0x0A || cpu.opcode == 0x0B) {
(&cpu.register_number, sizeof(uint8_t), 1, file);
fread(&cpu);
execute_instruction} else if (cpu.opcode == 0x0C) {
(&cpu.register_number, sizeof(uint8_t), 1, file);
fread(&cpu.destination_register, sizeof(uint8_t), 1, file);
fread(&cpu);
execute_instruction} else if (cpu.opcode == 0x06) {
(&cpu);
execute_instruction} else if (cpu.opcode == 0x07) {
(&cpu.memory_operand, sizeof(uint8_t), 1, file);
fread(&cpu.immediate_value, sizeof(uint8_t), 1, file);
fread(&cpu);
execute_instruction} else {
(&cpu.register_number, sizeof(uint8_t), 1, file);
fread(&cpu.memory_operand, sizeof(uint8_t), 1, file);
fread(&cpu);
execute_instruction}
}
(file);
fclose(&cpu); // Print the final CPU state
print_cpu_state
return 0;
}
main
function
runs the CPU simulation.CPU
struct
instance is created and reset to its initial state.