[tool].astro (11161B)
1 --- 2 import Base from '../../layouts/Base.astro'; 3 import SlantTitle from '../../components/SlantTitle.astro'; 4 import allTechniques from '../../data/techniques.json'; 5 import allTools from '../../data/tools.json'; 6 import { toolSlug, routeSourceOf } from '../../lib/techniques'; 7 import type { Technique, Tool } from '../../lib/techniques'; 8 import { SOURCE_ROUTE, SOURCE_META, PLATFORM_META, CAPABILITY_META } from '../../lib/taxonomy'; 9 import { highlightCommand } from '../../lib/highlight'; 10 import { url } from '../../lib/url'; 11 import { techArticle, breadcrumbs } from '../../lib/jsonld'; 12 import '../../styles/catalog.css'; 13 14 export async function getStaticPaths() { 15 const tools = allTools as unknown as Tool[]; 16 const techs = allTechniques as unknown as Technique[]; 17 const byTool = new Map<string, Technique[]>(); 18 for (const t of techs) { 19 if (!byTool.has(t.toolId)) byTool.set(t.toolId, []); 20 byTool.get(t.toolId)!.push(t); 21 } 22 const seen = new Set<string>(); 23 const paths = []; 24 for (const tool of tools) { 25 const deck = SOURCE_ROUTE[routeSourceOf(tool.id)]; 26 const route = `${deck}/${toolSlug(tool.id)}`; 27 if (seen.has(route)) continue; // guard against slug collisions 28 seen.add(route); 29 paths.push({ 30 params: { source: deck, tool: toolSlug(tool.id) }, 31 props: { tool, techniques: byTool.get(tool.id) || [] }, 32 }); 33 } 34 return paths; 35 } 36 37 interface Props { tool: Tool; techniques: Technique[] } 38 const { tool, techniques } = Astro.props; 39 // Display deck comes from the toolId namespace, so a daemon-authored WADComs 40 // addition (wadcoms:Certipy) reads under the WADComs deck; per-technique NEW 41 // badges convey which rows are daemon-authored. 42 const src = SOURCE_META[routeSourceOf(tool.id)]; 43 const upstream = tool.references?.find((r) => /gtfobins|lolbas|wadcoms|loobins/i.test(r)) || src.homepage; 44 const deck = SOURCE_ROUTE[routeSourceOf(tool.id)]; 45 const path = `/${deck}/${toolSlug(tool.id)}`; 46 const jsonLd = [ 47 techArticle(Astro.site, path, tool, techniques, routeSourceOf(tool.id)), 48 breadcrumbs(Astro.site, [ 49 { name: 'Catalog', path: '/catalog' }, 50 { name: src.label, path: `/${deck}` }, 51 { name: tool.name, path }, 52 ]), 53 ]; 54 --- 55 <Base 56 title={`${tool.name} — ${src.label} — DÆMONBins`} 57 description={`${tool.name}: ${techniques.length} living-off-the-land technique${techniques.length === 1 ? '' : 's'} from ${src.label}. Commands, MITRE ATT&CK mapping, detection and references.`} 58 jsonLd={jsonLd} 59 > 60 <div class="wrap tool" style="padding-top:1.5rem;" data-pagefind-body> 61 <span class="sr-only" data-pagefind-meta={`source:${src.label}`}></span> 62 <p class="tool__crumb"> 63 <a href={url('catalog')}>Catalog</a> 64 <span aria-hidden="true">/</span> 65 <a href={url(SOURCE_ROUTE[routeSourceOf(tool.id)])}>{src.label}</a> 66 </p> 67 68 <SlantTitle eyebrow={`${src.tag} · ${tool.toolType || 'Tool'}`} title={tool.name} tone={src.accent}> 69 <Fragment slot="meta"> 70 <span>{techniques.length} technique{techniques.length === 1 ? '' : 's'}</span> 71 {tool.platform?.length ? <span>{tool.platform.join(' · ')}</span> : null} 72 <span>{src.label}</span> 73 <span>GPL-3.0</span> 74 </Fragment> 75 </SlantTitle> 76 77 <div class="tool__body"> 78 {(tool.aliases?.length || tool.fullPath?.length || tool.author || tool.contributors?.length) ? ( 79 <aside class="tool__facts slab corners"> 80 <p class="micro tool__facts-label">// {src.tag} · {tool.name}</p> 81 <dl> 82 {tool.aliases?.length ? (<><dt>Aliases</dt><dd>{tool.aliases.join(', ')}</dd></>) : null} 83 {tool.fullPath?.length ? (<><dt>Full path</dt><dd class="mono">{tool.fullPath.map((p) => <div>{p}</div>)}</dd></>) : null} 84 {tool.author ? (<><dt>Author</dt><dd>{tool.author}</dd></>) : null} 85 {tool.created ? (<><dt>Created</dt><dd>{tool.created}</dd></>) : null} 86 {tool.contributors?.length ? (<><dt>Credits</dt><dd>{tool.contributors.join(', ')}</dd></>) : null} 87 <dt>Upstream</dt> 88 <dd><a href={upstream} target="_blank" rel="noopener">{upstream.replace(/^https?:\/\//, '')}</a></dd> 89 </dl> 90 </aside> 91 ) : null} 92 93 <div class="tool__techs"> 94 {techniques.map((t) => ( 95 <article class="tech" id={t.id}> 96 <header class="tech__head"> 97 <div class="tech__title"> 98 <h2 class="tech__name">{t.name && t.name !== t.toolName ? t.name : t.capability[0]}</h2> 99 {t.added ? <span class="tbadge tbadge--new" style="--acc: var(--love);">DÆMON</span> : null} 100 </div> 101 <div class="tech__badges"> 102 {t.capability.map((c) => ( 103 <span class="tbadge" style={`--acc: var(--${CAPABILITY_META[c]?.accent || 'foam'});`}>{c}</span> 104 ))} 105 {t.platform.map((p) => ( 106 <span class="tbadge tbadge--soft" style={`--acc: var(--${PLATFORM_META[p]?.accent || 'foam'});`}>{p}</span> 107 ))} 108 </div> 109 </header> 110 111 {t.description ? <p class="tech__desc">{t.description}</p> : null} 112 {t.usecase ? <p class="tech__use"><span>Use</span> {t.usecase}</p> : null} 113 114 <div class="tech__cmdbar" data-cmdbar> 115 <pre class="cmd"><code set:html={highlightCommand(t.command)} /></pre> 116 <button class="trow__copy" type="button" data-copy aria-label="Copy command">copy</button> 117 </div> 118 119 <dl class="tech__meta"> 120 {t.verification ? (<div><dt>Evidence</dt><dd>{t.verification}{t.reviewedAt ? ` · reviewed ${t.reviewedAt}` : ''}</dd></div>) : null} 121 {t.environment?.length ? (<div><dt>Environment</dt><dd>{t.environment.join(', ')}</dd></div>) : null} 122 {t.compatibility ? (<div><dt>Compatibility</dt><dd>{t.compatibility}</dd></div>) : null} 123 {t.expected ? (<div><dt>Expected result</dt><dd>{t.expected}</dd></div>) : null} 124 {t.troubleshooting ? (<div><dt>Troubleshooting</dt><dd>{t.troubleshooting}</dd></div>) : null} 125 {t.sideEffects ? (<div><dt>Side effects</dt><dd>{t.sideEffects}</dd></div>) : null} 126 {t.restore ? (<div><dt>Restore</dt><dd>{t.restore}</dd></div>) : null} 127 {t.template ? (<div><dt>Template</dt><dd><a href={url(`catalog?id=${encodeURIComponent(t.id)}`)}>Configure this command in the catalog</a></dd></div>) : null} 128 {t.privilege ? (<div><dt>Context</dt><dd>{t.privilege}</dd></div>) : null} 129 {t.nativeCategory?.length ? (<div><dt>Native</dt><dd>{t.nativeCategory.join(', ')}</dd></div>) : null} 130 {t.mitre?.length ? (<div><dt>MITRE</dt><dd>{t.mitre.map((m) => <a class="tchip-link" href={`https://attack.mitre.org/techniques/${m.replace('.', '/')}/`} target="_blank" rel="noopener">{m}</a>)}</dd></div>) : null} 131 {t.requires?.length ? (<div><dt>Requires</dt><dd>{t.requires.join(', ')}</dd></div>) : null} 132 {t.services?.length ? (<div><dt>Services</dt><dd>{t.services.join(', ')}</dd></div>) : null} 133 {t.detection?.length ? (<div><dt>Detection</dt><dd>{t.detection.map((d) => <div>{d.type}: {/^https?:/.test(d.value) ? <a href={d.value} target="_blank" rel="noopener">{d.value.replace(/^https?:\/\//, '')}</a> : d.value}</div>)}</dd></div>) : null} 134 {t.verifyNote ? (<div><dt>Verified</dt><dd class="tech__note">✓ {t.verifyNote}</dd></div>) : null} 135 {t.references?.length ? (<div><dt>Refs</dt><dd>{t.references.map((r) => <div><a href={r} target="_blank" rel="noopener">{r.replace(/^https?:\/\//, '')}</a></div>)}</dd></div>) : null} 136 </dl> 137 </article> 138 ))} 139 </div> 140 </div> 141 </div> 142 143 <script>import '../../scripts/copy.ts';</script> 144 </Base> 145 146 <style> 147 .tool__crumb { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--fg-faint); margin: 0 0 0.5rem; } 148 .tool__crumb a { color: var(--fg-dim); text-decoration: none; } 149 .tool__crumb a:hover { color: var(--accent); } 150 .tool__body { margin-top: 2.5rem; display: grid; grid-template-columns: 1fr; gap: 1.5rem; } 151 .tool__facts { padding: 1.1rem 1.3rem; } 152 .tool__facts-label { color: var(--fg-faint); margin: 0 0 0.6rem; } 153 .tool__facts dl { display: grid; grid-template-columns: 120px 1fr; gap: 0.35rem 1rem; margin: 0; font-size: 13px; } 154 .tool__facts dt { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--fg-faint); } 155 .tool__facts dd { margin: 0; color: var(--fg-dim); } 156 .tool__facts dd.mono { font-family: var(--font-mono); font-size: 12px; } 157 .tool__facts a { color: var(--accent); text-decoration: none; } 158 .tool__techs { display: flex; flex-direction: column; gap: 1px; background: var(--rule); border: 1px solid var(--rule); } 159 .tech { background: var(--bg); padding: 1.1rem 1.2rem; scroll-margin-top: 5.5rem; } 160 /* The catalog's "full details" link lands here by #id — mark the row it meant. */ 161 .tech:target { box-shadow: inset 3px 0 0 var(--accent); } 162 .tech__head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.6rem; margin-bottom: 0.7rem; } 163 .tech__title { display: flex; align-items: center; gap: 0.5rem; } 164 /* An h2 so the techniques form a real outline under the tool's h1; styled as the label it always was. */ 165 .tech__name { margin: 0; font-family: var(--font-archivo); font-weight: 700; font-size: 16px; line-height: 1.3; letter-spacing: -0.01em; text-transform: none; color: var(--fg); } 166 .tech__badges { display: flex; flex-wrap: wrap; gap: 0.3rem; } 167 .tech__desc { margin: 0 0 0.7rem; font-size: 14px; line-height: 1.6; color: var(--fg-dim); max-width: 74ch; white-space: pre-wrap; } 168 .tech__use { margin: 0 0 0.7rem; font-size: 13px; color: var(--fg-dim); } 169 .tech__use span { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--fg-faint); } 170 .tech__cmdbar { position: relative; display: flex; align-items: stretch; margin: 0 0 0.8rem; } 171 .cmd { flex: 1; min-width: 0; margin: 0; padding: 0.6rem 0.8rem; background: var(--color-night-base, #191724); border: 1px solid color-mix(in srgb, #000 20%, var(--color-night-base, #191724)); overflow-x: auto; font-family: var(--font-mono); font-size: 12.5px; line-height: 1.6; } 172 .cmd code { white-space: pre; color: var(--color-night-text, #e0def4); } 173 .tech__meta { display: grid; gap: 0.35rem 1.5rem; margin: 0; } 174 .tech__meta > div { display: grid; grid-template-columns: 90px 1fr; gap: 0.8rem; font-size: 12.5px; line-height: 1.55; } 175 .tech__meta dt { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--fg-faint); } 176 .tech__meta dd { margin: 0; color: var(--fg-dim); word-break: break-word; } 177 .tech__meta a { color: var(--accent); text-decoration: none; } 178 .tech__meta a:hover { text-decoration: underline; text-underline-offset: 2px; } 179 .tech__note { color: var(--foam); font-family: var(--font-mono); font-size: 11.5px; } 180 </style>