Skip to content

Commit e665983

Browse files
committed
bug fixes from claude + ci/cd fixes
1 parent 07cf817 commit e665983

12 files changed

Lines changed: 125 additions & 69 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ jobs:
7979
fi
8080
- name: Count exercises
8181
run: |
82-
COUNT=$(find out/exercise -mindepth 1 -maxdepth 1 -type d | wc -l)
82+
COUNT=$(find out/exercise -mindepth 1 -maxdepth 1 -name '*.html' | wc -l)
8383
echo "Exercise pages: $COUNT"
8484
if [ "$COUNT" -lt 100 ]; then
8585
echo "::error::Expected 100+ exercise pages, got $COUNT"

.github/workflows/deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ jobs:
5151
- name: Verify output
5252
run: |
5353
echo "Build size: $(du -sh out/ | cut -f1)"
54-
COUNT=$(find out/exercise -mindepth 1 -maxdepth 1 -type d | wc -l)
54+
COUNT=$(find out/exercise -mindepth 1 -maxdepth 1 -name '*.html' | wc -l)
5555
echo "Exercises: $COUNT"
5656
5757
- uses: actions/upload-pages-artifact@v3

src/app/exercise/[id]/ExercisePageClient.tsx

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -73,29 +73,25 @@ export default function ExercisePageClient({ params }: { params: Promise<{ id: s
7373

7474
// Initialize StackSim
7575
if (needsStack || exercise.mode.startsWith('input-')) {
76+
let extraSize = 0;
77+
if (exercise.ret2libc) extraSize = 8;
78+
else if (exercise.rop) extraSize = 40;
79+
else if (exercise.srop) extraSize = 28;
80+
else if (exercise.pivot) extraSize = 40;
81+
7682
const sim = new StackSim({
7783
bufSize: exercise.bufSize ?? 16,
7884
retAddr: retAddrInMain(symbols),
7985
savedEbp: 0xbfff0200,
8086
useCanary: exercise.canary,
87+
extraSize,
8188
});
8289

8390
if (exercise.mode === 'step') {
8491
sim.clearBlank();
8592
}
8693

87-
if (exercise.srop || exercise.ret2libc) {
88-
const bigSim = new StackSim(
89-
exercise.bufSize ?? 16,
90-
retAddrInMain(symbols),
91-
0xbfff0200,
92-
undefined,
93-
exercise.canary,
94-
);
95-
stackSim.current = bigSim;
96-
} else {
97-
stackSim.current = sim;
98-
}
94+
stackSim.current = sim;
9995
} else {
10096
stackSim.current = null;
10197
}
@@ -119,6 +115,7 @@ export default function ExercisePageClient({ params }: { params: Promise<{ id: s
119115
} else if (exercise.mode === 'heap-double-free') {
120116
const aResult = heap.malloc(16);
121117
if (aResult) {
118+
heap.funcPtrs['handler'] = { original: symbols.normal, current: symbols.normal } as any;
122119
setTimeout(() => {
123120
dispatch({ type: 'SET_HEAP_NAME', name: 'A', addr: aResult.addr });
124121
dispatch({ type: 'BUMP_VIZ' });

src/app/globals.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ main {
153153
.src-fn { color: var(--yellow); }
154154
.src-str { color: var(--amber); }
155155
.src-cmt { color: var(--comment); font-style: italic; }
156+
.src-num { color: var(--purple); }
156157
.src-vuln { color: var(--red); text-decoration: underline wavy var(--red); }
157158

158159
/* Stack panel */

src/components/panels/SourcePanel/SourcePanel.tsx

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,32 @@ const KW = new Set([
2121
]);
2222
const FN = new Set(['main','vuln','win','normal','exploit','shellcode','gadget','pivot','target','handler','callback']);
2323

24+
const ASM_MNEMONICS = new Set([
25+
'mov','add','sub','push','pop','ret','call','jmp','je','jne','jz','jnz',
26+
'jg','jge','jl','jle','ja','jae','jb','jbe','xor','and','or','not',
27+
'shl','shr','sar','sal','cmp','test','lea','nop','hlt','int','syscall',
28+
'sysenter','leave','enter','inc','dec','neg','mul','imul','div','idiv',
29+
'cdq','cbw','cwde','rep','movs','stos','cmps','lods','bswap',
30+
'ldr','str','bl','bx','blx','svc','swi','stmfd','ldmfd','b',
31+
'lw','sw','li','la','addiu','addi','lui','ori','beq','bne','jr','jal',
32+
'section','global','extern','db','dw','dd','dq','resb','resw','resd',
33+
]);
34+
const ASM_REGS = new Set([
35+
'eax','ebx','ecx','edx','esi','edi','esp','ebp','eip',
36+
'rax','rbx','rcx','rdx','rsi','rdi','rsp','rbp','rip',
37+
'r8','r9','r10','r11','r12','r13','r14','r15',
38+
'al','ah','bl','bh','cl','ch','dl','dh',
39+
'ax','bx','cx','dx','si','di','sp','bp',
40+
'r0','r1','r2','r3','r4','r5','r6','r7','r8','r9','r10','r11','r12','r13','r14','r15',
41+
'lr','pc','fp','ip','sl',
42+
'sp','ra','gp','at','v0','v1','a0','a1','a2','a3',
43+
't0','t1','t2','t3','t4','t5','t6','t7','t8','t9',
44+
's0','s1','s2','s3','s4','s5','s6','s7','s8',
45+
'cs','ds','es','fs','gs','ss',
46+
]);
47+
2448
const TOKEN_RE = /\/\/.*$|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|#\w+|<[\w./]+>|\b\w+\b/gm;
49+
const ASM_TOKEN_RE = /;.*$|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|0x[\da-fA-F]+|\b\w+\b/gm;
2550

2651
function escHtml(s: string): string {
2752
return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
@@ -39,6 +64,19 @@ function highlightSyntax(text: string): string {
3964
});
4065
}
4166

67+
function highlightAsm(text: string): string {
68+
return text.replace(ASM_TOKEN_RE, (tok) => {
69+
if (tok.startsWith(';')) return `<span class="src-cmt">${escHtml(tok)}</span>`;
70+
if (tok.startsWith('"') || tok.startsWith("'")) return `<span class="src-str">${escHtml(tok)}</span>`;
71+
if (tok.startsWith('0x')) return `<span class="src-num">${tok}</span>`;
72+
const lower = tok.toLowerCase();
73+
if (ASM_MNEMONICS.has(lower)) return `<span class="src-kw">${tok}</span>`;
74+
if (ASM_REGS.has(lower)) return `<span class="src-fn">${tok}</span>`;
75+
if (FN.has(tok)) return `<span class="src-fn">${tok}</span>`;
76+
return tok;
77+
});
78+
}
79+
4280
export default function SourcePanel() {
4381
const { currentExercise, state } = useExerciseContext();
4482

@@ -54,11 +92,14 @@ export default function SourcePanel() {
5492
);
5593
}
5694

57-
const lines = currentExercise.source.c;
95+
const isAsmExercise = currentExercise.mode.startsWith('asm-') || currentExercise.vizMode === 'asm';
96+
const hasAsmSource = currentExercise.source.asm && currentExercise.source.asm.length > 0;
97+
const lines = hasAsmSource && isAsmExercise ? currentExercise.source.asm! : currentExercise.source.c;
98+
const fileName = isAsmExercise ? 'source.asm' : 'source.c';
5899

59100
return (
60101
<div className="panel" id="source-panel">
61-
<div className="panel-hdr">source.c</div>
102+
<div className="panel-hdr">{fileName}</div>
62103
<div
63104
id="exercise-desc"
64105
dangerouslySetInnerHTML={{ __html: currentExercise.desc }}
@@ -73,7 +114,8 @@ export default function SourcePanel() {
73114
}
74115
}
75116
if (i === state.execLine) classes.push('executing');
76-
const html = line.text ? highlightSyntax(line.text) : '\u00A0';
117+
const highlight = isAsmExercise ? highlightAsm : highlightSyntax;
118+
const html = line.text ? highlight(line.text) : '\u00A0';
77119
return (
78120
<span key={i} className={classes.join(' ')}>
79121
<span className="ln">{i + 1}</span>

src/components/panels/VizPanel/HeapViz.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,10 @@ export default function HeapViz() {
9797
<div style={{ color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '0.25rem', fontSize: '10px' }}>
9898
{isWindows ? 'NT Heap Free Lists' : 'Free Lists'}
9999
</div>
100-
<FreeListChain label={tcacheLabel} color="var(--purple)" bins={freeLists.tcache} baseAddr={heap.baseAddr} />
101-
<FreeListChain label={fastbinLabel} color="var(--blue)" bins={freeLists.fastbins} baseAddr={heap.baseAddr} />
102-
<UnsortedChain addrs={freeLists.unsorted} baseAddr={heap.baseAddr} label={unsortedLabel} />
100+
{freeLists.tcache && <FreeListChain label={tcacheLabel} color="var(--purple)" bins={freeLists.tcache} baseAddr={heap.baseAddr} />}
101+
{freeLists.fastbins && <FreeListChain label={fastbinLabel} color="var(--blue)" bins={freeLists.fastbins} baseAddr={heap.baseAddr} />}
102+
{freeLists.unsorted && <UnsortedChain addrs={freeLists.unsorted} baseAddr={heap.baseAddr} label={unsortedLabel} />}
103+
{freeLists.listHints && <FreeListChain label="ListHints" color="var(--cyan)" bins={freeLists.listHints} baseAddr={heap.baseAddr} />}
103104
</div>
104105
)}
105106

src/components/panels/VizPanel/StackViz.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ export default function StackViz() {
8686
labelHtml = `\u2190 <span style="color:var(--green)">buf[0]</span> (write starts here)`;
8787
} else if (offset < sim.bufSize) {
8888
labelHtml = `<span style="color:var(--text-dim)">buf[${offset}..${Math.min(offset + 3, sim.bufSize - 1)}]</span>`;
89+
} else if (offset > retStart + 3) {
90+
const extraOff = offset - (retStart + sim.retSize);
91+
labelHtml = `<span style="color:var(--text-dim)">payload[+${extraOff}]</span>`;
8992
}
9093

9194
rows.push(

src/engine/simulators/StackSim.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export interface StackSimConfig {
55
baseAddr?: number;
66
useCanary?: boolean;
77
wordSize?: 4 | 8;
8+
extraSize?: number;
89
}
910

1011
export class StackSim {
@@ -27,14 +28,15 @@ export class StackSim {
2728
wordSize: 4 | 8;
2829

2930
constructor(config: StackSimConfig);
30-
constructor(bufSize: number, retAddr: number, savedEbp: number, baseAddr?: number, useCanary?: boolean, wordSize?: 4 | 8);
31+
constructor(bufSize: number, retAddr: number, savedEbp: number, baseAddr?: number, useCanary?: boolean, wordSize?: 4 | 8, extraSize?: number);
3132
constructor(
3233
bufSizeOrConfig: number | StackSimConfig,
3334
retAddr?: number,
3435
savedEbp?: number,
3536
baseAddr?: number,
3637
useCanary?: boolean,
3738
wordSize?: 4 | 8,
39+
extraSize?: number,
3840
) {
3941
let bufSize: number;
4042
if (typeof bufSizeOrConfig === 'object') {
@@ -44,6 +46,7 @@ export class StackSim {
4446
baseAddr = bufSizeOrConfig.baseAddr;
4547
useCanary = bufSizeOrConfig.useCanary;
4648
wordSize = bufSizeOrConfig.wordSize;
49+
extraSize = bufSizeOrConfig.extraSize;
4750
} else {
4851
bufSize = bufSizeOrConfig;
4952
}
@@ -53,7 +56,7 @@ export class StackSim {
5356
this.canarySize = useCanary ? this.wordSize : 0;
5457
this.ebpSize = this.wordSize;
5558
this.retSize = this.wordSize;
56-
this.totalSize = bufSize + this.canarySize + this.ebpSize + this.retSize;
59+
this.totalSize = bufSize + this.canarySize + this.ebpSize + this.retSize + (extraSize ?? 0);
5760
this.baseAddr = baseAddr ?? 0xbfff0100;
5861
this.memory = new Uint8Array(this.totalSize);
5962
this.written = new Uint8Array(this.totalSize);

src/exercises/unit19-glibc-bypass/glibc-108.ts

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,21 @@ const exercise: Exercise = {
4242
},
4343
{
4444
log: ['action', 'Allocating chunk A (16 bytes) and chunk B (16 bytes).'],
45-
vizAction: (sim: any) => {
46-
const a = sim.malloc(16);
47-
const b = sim.malloc(16);
48-
if (a) sim._nameMap = { ...sim._nameMap, A: a.addr };
49-
if (b) sim._nameMap = { ...sim._nameMap, B: b.addr };
45+
vizAction: (_sim: any, heap: any) => {
46+
if (!heap) return;
47+
const a = heap.malloc(16);
48+
const b = heap.malloc(16);
49+
if (a) heap._nameMap = { ...heap._nameMap, A: a.addr };
50+
if (b) heap._nameMap = { ...heap._nameMap, B: b.addr };
5051
},
5152
srcLine: 5,
5253
},
5354
{
5455
log: ['action', 'free(a) — A goes to tcache. The <strong>tcache key</strong> is written at A+0x10 (data+8).'],
55-
vizAction: (sim: any) => {
56-
const addr = sim._nameMap?.A;
57-
if (addr !== undefined) sim.free(addr);
56+
vizAction: (_sim: any, heap: any) => {
57+
if (!heap) return;
58+
const addr = heap._nameMap?.A;
59+
if (addr !== undefined) heap.free(addr);
5860
},
5961
srcLine: 7,
6062
},
@@ -64,25 +66,27 @@ const exercise: Exercise = {
6466
},
6567
{
6668
log: ['info', 'But if we have a UAF write primitive, we can <strong>clear the key</strong> at data+8. Setting it to 0 makes glibc think this chunk was never freed into tcache.'],
67-
vizAction: (sim: any) => {
68-
const addr = sim._nameMap?.A;
69+
vizAction: (_sim: any, heap: any) => {
70+
if (!heap) return;
71+
const addr = heap._nameMap?.A;
6972
if (addr !== undefined) {
70-
const chunk = sim.chunks.get(addr);
73+
const chunk = heap.chunks.get(addr);
7174
if (chunk) {
72-
sim._writeLE(chunk.dataStart + 4, 0, 4);
75+
heap._writeLE(chunk.dataStart + 4, 0, 4);
7376
}
7477
}
7578
},
7679
srcLine: 12,
7780
},
7881
{
7982
log: ['success', 'free(a) succeeds! Key was 0, so glibc doesn\'t detect the double-free. A is now in tcache <strong>twice</strong>. From here: malloc returns A → write fd → malloc again → malloc returns arbitrary address. Classic tcache poison, enabled by clearing the key.'],
80-
vizAction: (sim: any) => {
81-
const addr = sim._nameMap?.A;
83+
vizAction: (_sim: any, heap: any) => {
84+
if (!heap) return;
85+
const addr = heap._nameMap?.A;
8286
if (addr !== undefined) {
83-
const chunk = sim.chunks.get(addr);
87+
const chunk = heap.chunks.get(addr);
8488
if (chunk) chunk.allocated = true;
85-
sim.free(addr);
89+
heap.free(addr);
8690
}
8791
},
8892
srcLine: 13,

src/exercises/unit19-glibc-bypass/glibc-109.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,21 +42,23 @@ const exercise: Exercise = {
4242
},
4343
{
4444
log: ['action', 'Allocating A (16 bytes) and B (16 bytes).'],
45-
vizAction: (sim: any) => {
46-
const a = sim.malloc(16);
47-
const b = sim.malloc(16);
48-
if (a) sim._nameMap = { A: a.addr };
49-
if (b) sim._nameMap = { ...sim._nameMap, B: b.addr };
45+
vizAction: (_sim: any, heap: any) => {
46+
if (!heap) return;
47+
const a = heap.malloc(16);
48+
const b = heap.malloc(16);
49+
if (a) heap._nameMap = { A: a.addr };
50+
if (b) heap._nameMap = { ...heap._nameMap, B: b.addr };
5051
},
5152
srcLine: 5,
5253
},
5354
{
5455
log: ['action', 'free(a), then free(b). B\'s fd now points to A, but <strong>mangled</strong>: fd = ((B.data >> 12) ^ A.data).'],
55-
vizAction: (sim: any) => {
56-
const aAddr = sim._nameMap?.A;
57-
const bAddr = sim._nameMap?.B;
58-
if (aAddr !== undefined) sim.free(aAddr);
59-
if (bAddr !== undefined) sim.free(bAddr);
56+
vizAction: (_sim: any, heap: any) => {
57+
if (!heap) return;
58+
const aAddr = heap._nameMap?.A;
59+
const bAddr = heap._nameMap?.B;
60+
if (aAddr !== undefined) heap.free(aAddr);
61+
if (bAddr !== undefined) heap.free(bAddr);
6062
},
6163
srcLine: 8,
6264
},

0 commit comments

Comments
 (0)