Primitive data types such as integers, floating-point numbers and characters are useful, but real software rarely operates on isolated values. Instead, programs often need to work with collections of data and more sophisticated structures.
Complex data types allow us to combine primitive values into larger logical units that are easier to manage and reason about.
In this section we will focus on three foundational concepts:
- Arrays
- Strings
- Structures ("Structs")
These concepts appear everywhere in software development, operating systems, networking, reverse engineering and exploit development.
Arrays
An array is a collection of elements of the same type stored sequentially in memory.
For example:
int numbers[5] = {10, 20, 30, 40, 50};
int a = numbers[0];
printf("%d", a); // prints the number "10"
From a programmer's perspective:
Index: 0 1 2 3 4
Value: 10 20 30 40 50
Although we access elements using indexes such as
numbers[0], internally the program is calculating the memory address of the requested element within the array.
From the machine's perspective:
Address Value
0x1000 10
0x1004 20
0x1008 30
0x100C 40
0x1010 50
Notice how each element is stored directly after the previous one. This is known as contiguous memory.
Because every element has the same size, the program can calculate the location of any element using only the base address (in this case 0x1000) and the index. This predictable memory layout is one of the reasons arrays appear so frequently in operating systems, networking, reverse engineering and exploit development.
A large percentage of classic software vulnerabilities originate from programs reading or writing beyond the intended boundaries of an array.
Strings
Strings are used to represent text.
Although many modern languages provide dedicated string types, at a low level a string is typically nothing more than an array of characters.
For example:
char name[] = "Operator";
In memory:
'O' 'p' 'e' 'r' 'a' 't' 'o' 'r' '\0'
Notice the final \0 character.
This is known as the null terminator and indicates where the string ends. Many operating system APIs and C standard library functions rely on this convention when processing text.
Because strings are simply arrays underneath, many vulnerabilities involving strings are actually array-related issues such as:
- Buffer overflows
- Out-of-bounds reads
- Memory corruption
- Information disclosure
Understanding that a string is ultimately just a sequence of bytes in memory is an important step toward understanding how software processes user input and how many vulnerabilities arise.
Structures (Structs)
A structure, commonly called a struct, allows multiple pieces of data to be grouped together under a single logical object.
For example, imagine we want to represent a user:
struct User {
int id;
char role;
int age;
};
Instead of storing each value independently, we can treat them as a single object:
struct User operator1;
Conceptually:
User
├── id
├── role
└── age
Structures are extremely common throughout software.
Operating systems, network protocols, executable formats, file formats and application data frequently use structures to organize information in memory.
For example:
- Process information
- Network packets
- PE/ELF headers
- Windows API objects
- Kernel data structures
As we progress through this course you will repeatedly encounter structures while debugging applications, analyzing memory, reversing binaries and studying operating system internals.
In many ways, structures are one of the most important building blocks of modern software because they allow complex information to be represented in a predictable memory layout.