Skip to content

Commit c95a702

Browse files
oleglazariclaude
andcommitted
feat: add 58 exercises across 8 new curriculum units
New units: - Intro to C (8 exercises): variables, pointers, arrays, functions, structs, malloc, pitfalls - Heap Internals Linux (6): ptmalloc2, chunk lifecycle, bins, tcache, arenas, coalescing - Windows Heap Internals (4): NT heap, LFH, Segment Heap, metadata encoding - ARM (8): registers, data processing, memory, conditionals, calling convention, stack, overflow, ROP - MIPS (8): registers, arithmetic, memory, branching/delay slots, calling convention, stack, overflow, ROP - Mitigations (8): ASLR, DEP/NX, canaries, RELRO, PIE, CFI, CET, combined analysis - Advanced I (8): vtable hijacking, type confusion, UAF+vtable, COOP, ret2dlresolve, FILE/FSOP - Advanced II (8): JIT spray, kernel pool, Spectre, BROP, heap feng shui, sandbox, data-only, DOP Registry updated with new UNITS, TRACKS (Architectures, Defense & Theory), and 8 new badges. Total exercises: 110. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4de7e2a commit c95a702

67 files changed

Lines changed: 3857 additions & 10 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/exercises/registry.ts

Lines changed: 113 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Exercise, Unit, Badge, Track } from './types';
2+
import { unitIntroCExercises } from './unit-intro-c';
23
import { unit0Exercises } from './unit0-asm';
34
import { unit1Exercises } from './unit1-stack';
45
import { unit2Exercises } from './unit2-logic';
@@ -10,9 +11,16 @@ import { unit7Exercises } from './unit7-x64';
1011
import { unit8Exercises } from './unit8-win-stack';
1112
import { unit9Exercises } from './unit9-win-heap';
1213
import { unit10Exercises } from './unit10-challenges';
14+
import { unit11Exercises } from './unit11-heap-internals';
15+
import { unit12Exercises } from './unit12-win-heap-internals';
16+
import { unit13Exercises } from './unit13-arm';
17+
import { unit14Exercises } from './unit14-mips';
18+
import { unit15Exercises } from './unit15-mitigations';
19+
import { unit16Exercises } from './unit16-advanced';
20+
import { unit17Exercises } from './unit17-advanced-ii';
1321

14-
// All exercises from all units, flattened
1522
const ALL_EXERCISES: Exercise[] = [
23+
...unitIntroCExercises,
1624
...unit0Exercises,
1725
...unit1Exercises,
1826
...unit2Exercises,
@@ -24,15 +32,22 @@ const ALL_EXERCISES: Exercise[] = [
2432
...unit8Exercises,
2533
...unit9Exercises,
2634
...unit10Exercises,
35+
...unit11Exercises,
36+
...unit12Exercises,
37+
...unit13Exercises,
38+
...unit14Exercises,
39+
...unit15Exercises,
40+
...unit16Exercises,
41+
...unit17Exercises,
2742
];
2843

29-
// Exercise lookup map
3044
const exerciseMap = new Map<string, Exercise>();
3145
for (const ex of ALL_EXERCISES) {
3246
exerciseMap.set(ex.id, ex);
3347
}
3448

3549
export const UNITS: Unit[] = [
50+
{ id: 'unit-intro-c', name: 'Intro to C', exerciseIds: ['c-01', 'c-02', 'c-03', 'c-04', 'c-05', 'c-06', 'c-07', 'c-08'] },
3651
{ id: 'unit0-asm', name: 'ASM', exerciseIds: ['asm-01', 'asm-02', 'asm-03', 'asm-04', 'asm-05', 'asm-06', 'asm-07', 'asm-08', 'asm-09', 'asm-10'] },
3752
{ id: 'unit1-stack', name: 'STACK', exerciseIds: ['stack-01', 'stack-02', 'stack-03', 'stack-04', 'stack-05'] },
3853
{ id: 'unit2-logic', name: 'Logic & Input Mismatches', exerciseIds: ['logic-06', 'logic-07', 'logic-08', 'logic-09', 'logic-10'] },
@@ -44,18 +59,36 @@ export const UNITS: Unit[] = [
4459
{ id: 'unit8-win-stack', name: 'WIN STACK', exerciseIds: ['win-37', 'win-38', 'win-39', 'win-40', 'win-41', 'win-42'], platform: 'windows' },
4560
{ id: 'unit9-win-heap', name: 'WIN HEAP', exerciseIds: ['win-43', 'win-44', 'win-45', 'win-46'], platform: 'windows' },
4661
{ id: 'unit10-challenges', name: 'CTF Lab', exerciseIds: ['ctf-47', 'ctf-48', 'ctf-49', 'ctf-50', 'ctf-51', 'ctf-52'] },
62+
{ id: 'unit11-heap-internals', name: 'Heap Internals', exerciseIds: ['hint-53', 'hint-54', 'hint-55', 'hint-56', 'hint-57', 'hint-58'] },
63+
{ id: 'unit12-win-heap-internals', name: 'Win Heap Internals', exerciseIds: ['whint-59', 'whint-60', 'whint-61', 'whint-62'], platform: 'windows' },
64+
{ id: 'unit13-arm', name: 'ARM', exerciseIds: ['arm-63', 'arm-64', 'arm-65', 'arm-66', 'arm-67', 'arm-68', 'arm-69', 'arm-70'] },
65+
{ id: 'unit14-mips', name: 'MIPS', exerciseIds: ['mips-71', 'mips-72', 'mips-73', 'mips-74', 'mips-75', 'mips-76', 'mips-77', 'mips-78'] },
66+
{ id: 'unit15-mitigations', name: 'Mitigations', exerciseIds: ['mit-79', 'mit-80', 'mit-81', 'mit-82', 'mit-83', 'mit-84', 'mit-85', 'mit-86'] },
67+
{ id: 'unit16-advanced', name: 'Advanced I', exerciseIds: ['adv-87', 'adv-88', 'adv-89', 'adv-90', 'adv-91', 'adv-92', 'adv-93', 'adv-94'] },
68+
{ id: 'unit17-advanced-ii', name: 'Advanced II', exerciseIds: ['adv2-95', 'adv2-96', 'adv2-97', 'adv2-98', 'adv2-99', 'adv2-100', 'adv2-101', 'adv2-102'] },
4769
];
4870

4971
export const TRACKS: Track[] = [
50-
{ id: 'foundations', name: 'Foundations', unitIds: ['unit0-asm', 'unit7-x64', 'unit1-stack', 'unit3-stack-ii'] },
72+
{ id: 'foundations', name: 'Foundations', unitIds: ['unit-intro-c', 'unit0-asm', 'unit7-x64', 'unit1-stack', 'unit3-stack-ii'] },
5173
{ id: 'logic-input', name: 'Logic & Input', unitIds: ['unit2-logic'] },
52-
{ id: 'linux-heap', name: 'Linux Heap', unitIds: ['unit4-heap', 'unit5-heap-ii'] },
53-
{ id: 'windows', name: 'Windows', unitIds: ['unit8-win-stack', 'unit9-win-heap'] },
54-
{ id: 'advanced', name: 'Advanced', unitIds: ['unit6-final'] },
74+
{ id: 'linux-heap', name: 'Linux Heap', unitIds: ['unit11-heap-internals', 'unit4-heap', 'unit5-heap-ii'] },
75+
{ id: 'windows', name: 'Windows', unitIds: ['unit8-win-stack', 'unit12-win-heap-internals', 'unit9-win-heap'] },
76+
{ id: 'architectures', name: 'Architectures', unitIds: ['unit13-arm', 'unit14-mips'] },
77+
{ id: 'defense', name: 'Defense & Theory', unitIds: ['unit15-mitigations'] },
78+
{ id: 'advanced', name: 'Advanced', unitIds: ['unit6-final', 'unit16-advanced', 'unit17-advanced-ii'] },
5579
{ id: 'challenges', name: 'Challenges', unitIds: ['unit10-challenges'] },
5680
];
5781

5882
export const BADGES: Badge[] = [
83+
{
84+
id: 'c-beginner',
85+
name: 'C Beginner',
86+
icon: '\u{1F4DA}',
87+
condition: (completed) => {
88+
const unit = UNITS.find(u => u.id === 'unit-intro-c');
89+
return !!unit && unit.exerciseIds.length > 0 && unit.exerciseIds.every(id => completed.has(id));
90+
},
91+
},
5992
{
6093
id: 'asm-apprentice',
6194
name: 'ASM Apprentice',
@@ -110,12 +143,30 @@ export const BADGES: Badge[] = [
110143
return !!unit && unit.exerciseIds.length > 0 && unit.exerciseIds.every(id => completed.has(id));
111144
},
112145
},
146+
{
147+
id: 'heap-internist',
148+
name: 'Heap Internist',
149+
icon: '\u{1F9EC}',
150+
condition: (completed) => {
151+
const unit = UNITS.find(u => u.id === 'unit11-heap-internals');
152+
return !!unit && unit.exerciseIds.length > 0 && unit.exerciseIds.every(id => completed.has(id));
153+
},
154+
},
113155
{
114156
id: 'full-chain',
115157
name: 'Full Chain',
116158
icon: '\u{1F517}',
117159
condition: (completed) => completed.has('final-27'),
118160
},
161+
{
162+
id: 'x64-master',
163+
name: 'x64 Master',
164+
icon: '\u{1F4BB}',
165+
condition: (completed) => {
166+
const unit = UNITS.find(u => u.id === 'unit7-x64');
167+
return !!unit && unit.exerciseIds.length > 0 && unit.exerciseIds.every(id => completed.has(id));
168+
},
169+
},
119170
{
120171
id: 'windows-warrior',
121172
name: 'Windows Warrior',
@@ -129,14 +180,66 @@ export const BADGES: Badge[] = [
129180
},
130181
},
131182
{
132-
id: 'x64-master',
133-
name: 'x64 Master',
134-
icon: '\u{1F4BB}',
183+
id: 'arm-exploiter',
184+
name: 'ARM Exploiter',
185+
icon: '\u{1F4AA}',
135186
condition: (completed) => {
136-
const unit = UNITS.find(u => u.id === 'unit7-x64');
187+
const unit = UNITS.find(u => u.id === 'unit13-arm');
137188
return !!unit && unit.exerciseIds.length > 0 && unit.exerciseIds.every(id => completed.has(id));
138189
},
139190
},
191+
{
192+
id: 'mips-exploiter',
193+
name: 'MIPS Exploiter',
194+
icon: '\u{1F680}',
195+
condition: (completed) => {
196+
const unit = UNITS.find(u => u.id === 'unit14-mips');
197+
return !!unit && unit.exerciseIds.length > 0 && unit.exerciseIds.every(id => completed.has(id));
198+
},
199+
},
200+
{
201+
id: 'mitigation-master',
202+
name: 'Mitigation Master',
203+
icon: '\u{1F6E1}',
204+
condition: (completed) => {
205+
const unit = UNITS.find(u => u.id === 'unit15-mitigations');
206+
return !!unit && unit.exerciseIds.length > 0 && unit.exerciseIds.every(id => completed.has(id));
207+
},
208+
},
209+
{
210+
id: 'advanced-attacker',
211+
name: 'Advanced Attacker',
212+
icon: '\u{1F525}',
213+
condition: (completed) => {
214+
const unit = UNITS.find(u => u.id === 'unit16-advanced');
215+
return !!unit && unit.exerciseIds.length > 0 && unit.exerciseIds.every(id => completed.has(id));
216+
},
217+
},
218+
{
219+
id: 'modern-threat',
220+
name: 'Modern Threat',
221+
icon: '\u{1F47E}',
222+
condition: (completed) => {
223+
const unit = UNITS.find(u => u.id === 'unit17-advanced-ii');
224+
return !!unit && unit.exerciseIds.length > 0 && unit.exerciseIds.every(id => completed.has(id));
225+
},
226+
},
227+
{
228+
id: 'multi-arch',
229+
name: 'Multi-Arch',
230+
icon: '\u{1F30D}',
231+
condition: (completed) => {
232+
const x86 = UNITS.find(u => u.id === 'unit0-asm');
233+
const x64 = UNITS.find(u => u.id === 'unit7-x64');
234+
const arm = UNITS.find(u => u.id === 'unit13-arm');
235+
const mips = UNITS.find(u => u.id === 'unit14-mips');
236+
return !!(x86 && x64 && arm && mips) &&
237+
x86.exerciseIds.every(id => completed.has(id)) &&
238+
x64.exerciseIds.every(id => completed.has(id)) &&
239+
arm.exerciseIds.every(id => completed.has(id)) &&
240+
mips.exerciseIds.every(id => completed.has(id));
241+
},
242+
},
140243
{
141244
id: 'certified-hacker',
142245
name: 'Certified Hacker',

src/exercises/unit-intro-c/c-01.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { Exercise } from '../types';
2+
3+
const c01: Exercise = {
4+
id: 'c-01',
5+
unitId: 'unit-intro-c',
6+
title: '01: Variables & Memory',
7+
desc: '<b>Goal:</b> Understand how C stores variables in memory as raw bytes.',
8+
source: {
9+
c: [
10+
{ text: '#include <stdio.h>', cls: '' },
11+
{ text: '', cls: '' },
12+
{ text: 'int main() {', cls: '' },
13+
{ text: ' int x = 42;', cls: 'highlight' },
14+
{ text: ' char c = \'A\';', cls: 'highlight' },
15+
{ text: ' printf("%d %c\\n", x, c);', cls: '' },
16+
{ text: ' return 0;', cls: '' },
17+
{ text: '}', cls: '' },
18+
],
19+
},
20+
mode: 'step',
21+
vizMode: 'stack',
22+
bufSize: 16,
23+
steps: [
24+
{
25+
action: 'init',
26+
log: ['info', 'In C, every variable is a named slot in memory. Unlike Python or JavaScript, you must tell the compiler how big each slot is by choosing a "type." Let\'s see how two different types get stored.'],
27+
},
28+
{
29+
action: 'init',
30+
srcLine: 3,
31+
log: ['info', 'int x = 42 -- An "int" occupies 4 bytes (32 bits). The computer stores the value 42 in hexadecimal as 0x0000002A. Those 4 bytes sit at a specific address in memory.'],
32+
},
33+
{
34+
action: 'init',
35+
srcLine: 4,
36+
log: ['info', 'char c = \'A\' -- A "char" occupies just 1 byte (8 bits). Characters are stored as numbers using the ASCII code. The letter A is 65 in decimal, or 0x41 in hex.'],
37+
},
38+
{
39+
action: 'init',
40+
log: ['info', 'The sizeof operator tells you how many bytes a type uses: sizeof(int) is 4, sizeof(char) is 1. Other common types: short (2 bytes), long (4-8 bytes), double (8 bytes).'],
41+
},
42+
{
43+
action: 'done',
44+
log: ['success', 'Variables in C map directly to bytes in memory. There is no hidden layer of abstraction. Understanding this is the foundation of everything that follows -- exploits work because attackers can read and manipulate these raw bytes.'],
45+
},
46+
],
47+
check() { return false; },
48+
winTitle: 'Variables & Memory!',
49+
winMsg: 'You learned how C stores data as raw bytes in memory.',
50+
};
51+
52+
export default c01;

src/exercises/unit-intro-c/c-02.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Exercise } from '../types';
2+
3+
const c02: Exercise = {
4+
id: 'c-02',
5+
unitId: 'unit-intro-c',
6+
title: '02: Pointers',
7+
desc: '<b>Goal:</b> Learn what pointers are -- they are just numbers that hold memory addresses.',
8+
source: {
9+
c: [
10+
{ text: '#include <stdio.h>', cls: '' },
11+
{ text: '', cls: '' },
12+
{ text: 'int main() {', cls: '' },
13+
{ text: ' int x = 10;', cls: 'highlight' },
14+
{ text: ' int *p = &x;', cls: 'highlight' },
15+
{ text: ' printf("x = %d\\n", x);', cls: '' },
16+
{ text: ' *p = 20;', cls: 'highlight' },
17+
{ text: ' printf("x = %d\\n", x);', cls: '' },
18+
{ text: ' return 0;', cls: '' },
19+
{ text: '}', cls: '' },
20+
],
21+
},
22+
mode: 'step',
23+
vizMode: 'stack',
24+
bufSize: 16,
25+
steps: [
26+
{
27+
action: 'init',
28+
srcLine: 3,
29+
log: ['info', 'First we create an integer variable x with the value 10. It lives at some address in memory -- let\'s say address 0xBFFF0010. The 4 bytes at that address hold the value 10.'],
30+
},
31+
{
32+
action: 'init',
33+
srcLine: 4,
34+
log: ['info', 'int *p = &x -- The & operator ("address-of") gives us the memory address where x lives. The variable p is a "pointer" -- it stores that address (0xBFFF0010). A pointer is just a number that happens to be an address.'],
35+
},
36+
{
37+
action: 'init',
38+
log: ['info', 'A pointer itself takes up 4 bytes on a 32-bit system (or 8 bytes on 64-bit). It holds the address of another variable. Think of it as a slip of paper with a house number written on it.'],
39+
},
40+
{
41+
action: 'init',
42+
srcLine: 6,
43+
log: ['info', '*p = 20 -- The * operator ("dereference") follows the address stored in p and modifies the value at that location. Since p points to x, this changes x from 10 to 20.'],
44+
},
45+
{
46+
action: 'init',
47+
log: ['warn', 'Here is the key insight: modifying *p and modifying x are the same operation. Both change the exact same bytes in memory. The pointer just gives us another way to reach them.'],
48+
},
49+
{
50+
action: 'done',
51+
log: ['success', 'Pointers are central to C -- and central to exploitation. If an attacker can control a pointer, they can read or write anywhere in memory. That ability to reach arbitrary addresses is what makes many attacks possible.'],
52+
},
53+
],
54+
check() { return false; },
55+
winTitle: 'Pointers Demystified!',
56+
winMsg: 'You learned that pointers are just addresses stored as numbers.',
57+
};
58+
59+
export default c02;

src/exercises/unit-intro-c/c-03.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { Exercise } from '../types';
2+
3+
const c03: Exercise = {
4+
id: 'c-03',
5+
unitId: 'unit-intro-c',
6+
title: '03: Arrays & Strings',
7+
desc: '<b>Goal:</b> See how arrays and strings are stored in memory, and why C does not prevent you from going past the end.',
8+
source: {
9+
c: [
10+
{ text: '#include <stdio.h>', cls: '' },
11+
{ text: '#include <string.h>', cls: '' },
12+
{ text: '', cls: '' },
13+
{ text: 'int main() {', cls: '' },
14+
{ text: ' char name[8] = "Alice";', cls: 'highlight' },
15+
{ text: ' char buf[8];', cls: 'highlight' },
16+
{ text: ' // Memory layout:', cls: 'cmt' },
17+
{ text: ' // name: [A][l][i][c][e][\\0][ ][ ]', cls: 'cmt' },
18+
{ text: ' // buf: [ ][ ][ ][ ][ ][ ][ ][ ]', cls: 'cmt' },
19+
{ text: ' name[7] = \'Z\';', cls: '' },
20+
{ text: ' name[10] = \'!!\'; // out of bounds!', cls: 'highlight vuln' },
21+
{ text: ' return 0;', cls: '' },
22+
{ text: '}', cls: '' },
23+
],
24+
},
25+
mode: 'step',
26+
vizMode: 'stack',
27+
bufSize: 32,
28+
steps: [
29+
{
30+
action: 'init',
31+
srcLine: 4,
32+
log: ['info', 'char name[8] declares an array of 8 bytes. The string "Alice" fills the first 5 bytes with the ASCII values for A, l, i, c, e. The 6th byte is automatically set to 0x00 -- the "null terminator" -- which marks the end of the string.'],
33+
},
34+
{
35+
action: 'init',
36+
srcLine: 5,
37+
log: ['info', 'char buf[8] creates another 8-byte block right next to name in memory. Arrays in C are laid out contiguously -- each element sits right after the previous one, with no gaps or fences between different arrays.'],
38+
},
39+
{
40+
action: 'init',
41+
log: ['info', 'The null terminator (0x00) is how C knows where a string ends. Functions like printf and strlen scan forward byte by byte until they hit 0x00. If that byte is missing or overwritten, the function will keep reading into whatever memory comes next.'],
42+
},
43+
{
44+
action: 'init',
45+
srcLine: 9,
46+
log: ['info', 'name[7] = \'Z\' is fine -- index 7 is the last valid position in an 8-element array (indices 0 through 7). This writes one byte within bounds.'],
47+
},
48+
{
49+
action: 'init',
50+
srcLine: 10,
51+
log: ['warn', 'name[10] = \'!\' is OUT OF BOUNDS. Index 10 is past the end of the 8-byte array. But C does not check! It happily writes to whatever memory is 10 bytes past the start of name. This could overwrite data belonging to buf or other variables.'],
52+
},
53+
{
54+
action: 'done',
55+
log: ['success', 'C arrays have no bounds checking. Writing past the end of an array overwrites whatever happens to be next in memory. This is the fundamental reason buffer overflows exist -- and it is exactly what attackers exploit.'],
56+
},
57+
],
58+
check() { return false; },
59+
winTitle: 'Arrays & Strings!',
60+
winMsg: 'You learned how C arrays work and why they are vulnerable to out-of-bounds access.',
61+
};
62+
63+
export default c03;

0 commit comments

Comments
 (0)