The complete, in-depth guide to C and C++ programming — from syntax basics to advanced systems programming. The languages that power exploits, malware analysis, and low-level security tools.
C was created by Dennis Ritchie at Bell Labs in 1972. It was designed for system programming — specifically, to rewrite the UNIX operating system. Nearly every modern operating system, including Linux, Windows, macOS, and Android, is written largely in C. It's the closest thing to a universal assembly language.
C++ was developed by Bjarne Stroustrup in 1979 as an extension of C. It added object-oriented programming, templates, exceptions, and a huge standard library. C++ powers everything from game engines (Unreal) to browsers (Chrome) to databases (MySQL) to embedded systems.
For ethical hackers and security researchers, C and C++ are essential because they're the languages of exploit development, malware analysis, reverse engineering, and low-level systems work. If you want to understand buffer overflows, write shellcode, or analyze malware, you need C.
gcc or g++, and get a binary executable. This is different from Python or Ruby, which are interpreted.
Python and Ruby are great for automation, but when you need raw power, direct memory access, and speed, nothing beats C and C++. Here's why they're essential for serious security work:
Buffer overflows, ROP chains, and shellcode are written in C/Assembly.
Most malware is written in C/C++ — you need to read and understand it.
Write high-performance security tools, rootkits, and implants.
Understanding C teaches you how compilers generate assembly.
Learn how memory works — critical for exploitation and debugging.
C runs 50-100x faster than Python. Essential for brute-forcing at scale.
On Kali Linux, you'll need gcc (C compiler) and g++ (C++ compiler). Both come pre-installed on most systems, but you can install them if missing.
# Install compilers on Kali/Debian
sudo apt update
sudo apt install build-essential gcc g++ gdb
# Check versions
gcc --version
g++ --version
# Compile and run
gcc hello.c -o hello
./hello
# Compile with debug symbols (for gdb)
gcc -g hello.c -o hello
# Disable protections (for exploit dev practice)
gcc -fno-stack-protector -z execstack -no-pie hello.c -o hello
# Compile C++
g++ hello.cpp -o hello
#include <stdio.h>
int main() {
printf("Hello, Ethical Hacker!\n");
return 0;
}
Every C program has a main function where execution begins. You must #include the libraries you use and declare the return type of every function.
// Preprocessor directive — includes the standard I/O library
#include <stdio.h>
// main() is the entry point. Returns int (exit code).
int main(int argc, char *argv[]) {
printf("Hello, Ethical Hacker!\n");
printf("Program name: %s\n", argv[0]);
return 0; // 0 = success, non-zero = error
}
int port = 22; // Integer (4 bytes typically)
char grade = 'A'; // Single character (1 byte)
float ratio = 3.14f; // Single-precision float
double pi = 3.14159265; // Double-precision float
unsigned int count = 100; // No negative values
long long big = 1234567890; // Larger integer
// Constants
const int MAX = 100;
| Type | Size (typical) | Range |
|---|---|---|
char | 1 byte | -128 to 127 |
short | 2 bytes | -32,768 to 32,767 |
int | 4 bytes | -2.1B to 2.1B |
long | 4-8 bytes | Platform dependent |
float | 4 bytes | ~7 decimal digits |
double | 8 bytes | ~15 decimal digits |
void* | 8 bytes | Any pointer (64-bit) |
int port;
char host[64];
printf("Enter host: ");
scanf("%s", host);
printf("Enter port: ");
scanf("%d", &port);
printf("Target: %s:%d\n", host, port);
// Format specifiers
// %d = int, %s = string, %c = char, %f = float, %p = pointer, %x = hex
scanf("%s", buf) is dangerous — it doesn't check buffer size and causes buffer overflows. Use fgets() instead, or specify width: scanf("%63s", buf).
signed int a = -10; // Can be negative (default)
unsigned int b = 10; // Only positive
short int c = 100; // Smaller range
long int d = 100000L; // Larger range
long long e = 1000000LL;
int a = 10;
int b = 3;
// Integer division — loses decimal
float result1 = a / b; // 3.0 (integer division)
float result2 = (float)a / b; // 3.333... (cast to float)
printf("%.2f\n", result2);
enum Status { CLOSED, OPEN, FILTERED };
enum Status port_status = OPEN;
if (port_status == OPEN) {
printf("Port is open!\n");
}
// Custom values
enum Color { RED = 1, GREEN = 2, BLUE = 4 };
int port = 443;
if (port == 22) {
printf("SSH\n");
} else if (port == 80) {
printf("HTTP\n");
} else if (port == 443) {
printf("HTTPS\n");
} else {
printf("Unknown\n");
}
switch (port) {
case 22:
printf("SSH\n");
break;
case 80:
case 443:
printf("Web\n");
break;
default:
printf("Unknown\n");
}
// for loop
for (int i = 0; i < 10; i++) {
printf("%d\n", i);
}
// while loop
int n = 0;
while (n < 10) {
printf("%d\n", n);
n++;
}
// do-while (runs at least once)
do {
printf("Runs once at minimum\n");
} while (0);
// break & continue
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) continue; // Skip evens
if (i > 20) break; // Stop at 20
printf("%d\n", i);
}
// Function prototype (declaration)
int add(int a, int b);
// Function definition
int add(int a, int b) {
return a + b;
}
// Function that takes a pointer (modifies original)
void increment(int *n) {
(*n)++;
}
int main() {
printf("%d\n", add(5, 3)); // 8
int x = 10;
increment(&x);
printf("%d\n", x); // 11
return 0;
}
int global_counter = 0; // Accessible everywhere
void counter() {
static int count = 0; // Persists across calls
count++;
printf("Called %d times\n", count);
}
int main() {
counter(); // 1
counter(); // 2
counter(); // 3
return 0;
}
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
printf("%d\n", factorial(5)); // 120
Pointers are the most important and most dangerous feature of C. They store memory addresses and give you direct control over memory — which is why C is used for exploits and malware.
int x = 42;
int *p = &x; // p holds the address of x
printf("x = %d\n", x); // 42
printf("&x = %p\n", &x); // address
printf("p = %p\n", p); // same address
printf("*p = %d\n", *p); // 42 (dereferencing)
*p = 100; // Modify x through pointer
printf("x = %d\n", x); // 100
#include <stdlib.h>
// Allocate memory for 10 ints
int *arr = (int *)malloc(10 * sizeof(int));
// Check if allocation succeeded
if (arr == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return 1;
}
// Use the array
for (int i = 0; i < 10; i++) {
arr[i] = i * i;
}
// Free the memory when done
free(arr);
// Other allocation functions
calloc(10, sizeof(int)); // Zero-initialized
realloc(arr, 20 * sizeof(int)); // Resize
free() memory you malloc(). Always check for NULL. Never use memory after freeing. These bugs are the source of countless exploits.
int arr[] = {10, 20, 30, 40};
int *p = arr;
printf("%d\n", *p); // 10
printf("%d\n", *(p+1)); // 20
printf("%d\n", *(p+2)); // 30
p++; // Move pointer forward
printf("%d\n", *p); // 20
// Declare and initialize
int ports[5] = {21, 22, 80, 443, 3306};
// Access and modify
printf("%d\n", ports[0]); // 21
ports[1] = 2222;
// Iterate
for (int i = 0; i < 5; i++) {
printf("Port: %d\n", ports[i]);
}
// 2D array
int matrix[3][3] = {{1,2,3}, {4,5,6}, {7,8,9}};
#include <string.h>
// C strings are null-terminated char arrays
char name[20] = "Hacker";
char *host = "example.com";
// Common string functions
strlen(name); // Length: 6
strcpy(name, "NewName"); // Copy (unsafe!)
strncpy(name, "Safe", 19); // Copy with bound
strcat(name, "!"); // Concatenate
strcmp(name, "Other"); // Compare
strstr(name, "ack"); // Find substring
// Safe input
fgets(name, sizeof(name), stdin);
strcpy() and strcat() don't check buffer sizes — they cause buffer overflows. Use strncpy() and strncat() with explicit bounds. This is one of the most common vulnerability classes.
struct Target {
char ip[16];
int port;
char os[32];
int vulnerable;
};
// Using a struct
struct Target t1;
strcpy(t1.ip, "192.168.1.1");
t1.port = 22;
strcpy(t1.os, "Linux");
t1.vulnerable = 1;
printf("Target: %s:%d (%s)\n", t1.ip, t1.port, t1.os);
// Pointer to struct
struct Target *ptr = &t1;
printf("Port: %d\n", ptr->port); // Arrow operator
typedef struct {
char ip[16];
int port;
} Target;
// Now use without "struct" keyword
Target t1;
t1.port = 80;
union Data {
int i;
float f;
char str[4];
};
union Data d;
d.i = 0x41424344;
// Access same bytes as different types
printf("%c%c%c%c\n", d.str[3], d.str[2], d.str[1], d.str[0]);
C++ extends C with object-oriented features: classes, inheritance, polymorphism, templates, and exceptions. It's a superset of C — almost all C code compiles as C++.
#include <iostream>
int main() {
std::cout << "Hello, Hacker!" << std::endl;
return 0;
}
class Target {
private:
std::string ip;
int port;
std::vector<int> open_ports;
public:
// Constructor
Target(std::string ip, int port) : ip(ip), port(port) {}
// Destructor
~Target() {}
// Methods
void addPort(int p) {
open_ports.push_back(p);
}
void display() const {
std::cout << "Target: " << ip << ":" << port << std::endl;
}
// Getter
std::string getIp() const { return ip; }
};
int main() {
Target t("192.168.1.1", 22);
t.addPort(80);
t.addPort(443);
t.display();
return 0;
}
class WebTarget : public Target {
private:
std::string domain;
public:
WebTarget(std::string ip, int port, std::string domain)
: Target(ip, port), domain(domain) {}
void display() const {
Target::display();
std::cout << "Domain: " << domain << std::endl;
}
};
class Scanner {
public:
virtual void scan() {
std::cout << "Generic scan" << std::endl;
}
virtual ~Scanner() {}
};
class NmapScanner : public Scanner {
public:
void scan() override {
std::cout << "Running Nmap scan" << std::endl;
}
};
The STL is C++'s killer feature — a rich collection of data structures and algorithms ready to use.
#include <vector>
#include <map>
#include <set>
#include <string>
// Vector — dynamic array
std::vector<int> ports = {22, 80, 443};
ports.push_back(8080);
for (int p : ports) {
std::cout << p << std::endl;
}
// Map — key-value pairs (sorted)
std::map<std::string, int> services;
services["ssh"] = 22;
services["http"] = 80;
services["https"] = 443;
for (auto &pair : services) {
std::cout << pair.first << " → " << pair.second << std::endl;
}
// Set — unique elements
std::set<std::string> unique_ips = {"10.0.0.1", "10.0.0.2"};
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
std::cout << max(5, 10) << std::endl; // int
std::cout << max(3.14, 2.71) << std::endl; // double
std::cout << max(std::string("a"), std::string("b"));
#include <memory>
// Unique pointer — exclusive ownership
std::unique_ptr<int> p1 = std::make_unique<int>(42);
// Shared pointer — reference counted
std::shared_ptr<int> p2 = std::make_shared<int>(100);
// No manual delete needed — automatic cleanup
Understanding C at a low level is essential for exploitation. Here are the classic vulnerability classes you'll encounter.
// VULNERABLE CODE — DO NOT USE IN PRODUCTION
void vulnerable(char *input) {
char buffer[64];
strcpy(buffer, input); // No bounds check!
}
// If input is longer than 64 bytes, it overwrites
// the return address on the stack → code execution
// SAFE version
void safe(char *input) {
char buffer[64];
strncpy(buffer, input, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';
}
// VULNERABLE
printf(user_input); // If user_input contains %x, %s, %n...
// SAFE
printf("%s", user_input);
unsigned int a = 4294967295; // Max uint32
a = a + 1; // Wraps to 0!
// Common in size calculations:
int size = user_len + 1; // Can overflow
char *buf = malloc(size); // Too small allocation
char *ptr = malloc(100);
free(ptr);
// ptr is now dangling
strcpy(ptr, "data"); // Use-after-free vulnerability!
| Protection | Flag | Purpose |
|---|---|---|
| Stack Canary | -fstack-protector | Detects stack smashing |
| NX / DEP | -z noexecstack | Prevents code on stack |
| PIE / ASLR | -pie -fPIE | Randomizes memory layout |
| RELRO | -Wl,-z,relro | Protects GOT |
| FORTIFY | -D_FORTIFY_SOURCE=2 | Checks buffer sizes |
# Compile with all protections enabled (production)
gcc -fstack-protector-strong -D_FORTIFY_SOURCE=2 -pie -fPIE -Wl,-z,relro,-z,now secure.c -o secure
# Compile with NO protections (for exploit dev practice)
gcc -fno-stack-protector -z execstack -no-pie -m32 vulnerable.c -o vulnerable
GNU Debugger — the essential tool for reverse engineering and exploit development.
gdb plugins that supercharge debugging with heap analysis, ROP gadgets, and more.
Disassemble binaries. See what a compiled program actually does at assembly level.
Trace library and system calls — see exactly what a binary does at runtime.
Inspect binary protections (ASLR, NX, canary, PIE, RELRO).
Find ROP gadgets in binaries for return-oriented programming attacks.
Reverse engineering framework. Disassemble, debug, patch binaries.
NSA's open-source reverse engineering suite. Decompiles binaries to C.
Memory error detector. Finds leaks, overflows, and uninitialized reads.
Fuzzing tools — feed random input to find crashes and vulnerabilities.
# Start debugger
gdb ./program
# Inside gdb:
(gdb) break main # Set breakpoint
(gdb) run # Run program
(gdb) next # Step over
(gdb) step # Step into
(gdb) info registers # Show all registers
(gdb) x/20x $rsp # Examine stack memory
(gdb) disassemble main # Show assembly
(gdb) continue # Resume
(gdb) quit # Exit
Buffer overflows, ROP chains, heap exploitation, shellcode.
Most malware is written in C/C++ — read, understand, and analyze it.
Low-level kernel modules and persistent backdoors.
Understand how compilers generate assembly and how to reverse it.
Implement crypto primitives, custom encryption, and bypasses.
High-performance packet sniffers, packet crafters, and scanners.
Write custom fuzzers and analyze crashes for vulnerabilities.
Nmap, Metasploit, Wireshark, John — all use C/C++ internally.
malloc(), fopen(), etc.strncpy(), snprintf(), fgets().valgrind to check.gcc -Wall -Wextra -Werror.-fstack-protector-strong -D_FORTIFY_SOURCE=2 -pie -fPIE.argv, stdin, or network data.const where possible — Documents intent and helps the compiler.-fsanitize=address,undefined during development.# Development (with sanitizers)
gcc -g -Wall -Wextra -fsanitize=address,undefined program.c -o program
# Production (with hardening)
gcc -O2 -fstack-protector-strong -D_FORTIFY_SOURCE=2 \
-fPIE -pie -Wl,-z,relro,-z,now program.c -o program
# Check for memory errors and leaks
valgrind --leak-check=full --show-leak-kinds=all ./program
Free, world-class binary exploitation training from ASU.
Beginner-friendly CTF with excellent binary exploitation challenges.
Learn return-oriented programming step by step.
Free courses on Linux and Windows exploitation.
Free CTF-style binary exploitation course on GitHub.
Structured learning paths for binary exploitation.
C and C++ are the foundational languages of computing. Every operating system, every browser, every database, and virtually every security tool is built on them. For ethical hackers, they are indispensable — the key to understanding how systems actually work at the lowest level.
Python and Bash make you productive. C and C++ make you dangerous. They let you:
The journey follows a clear path:
gcc and gdb on Kali. Write a simple C program. Compile it, run it, then disassemble it. Then open gdb and inspect it. Within weeks, you'll understand exploitation at a level most hackers never reach.
Happy hacking — ethically. ⚙️