feat: sticky table of contents
This commit is contained in:
@@ -124,7 +124,7 @@ export default async function Page({ params }: { params: { slug: string[] } }) {
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ReportView slug={slug} />
|
||||
<Layout content={mainContent} authorDetails={authorDetails} next={next} prev={prev}>
|
||||
<Layout content={mainContent} authorDetails={authorDetails} toc={post.toc} next={next} prev={prev}>
|
||||
<MDXLayoutRenderer code={post.body.code} components={components} toc={post.toc} />
|
||||
</Layout>
|
||||
</>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { genPageMetadata } from 'app/seo'
|
||||
import { allBlogs } from 'contentlayer/generated'
|
||||
import { allCoreContent, sortPosts } from 'pliny/utils/contentlayer'
|
||||
|
||||
const POSTS_PER_PAGE = 5
|
||||
const POSTS_PER_PAGE = 100
|
||||
|
||||
export const metadata = genPageMetadata({ title: 'Blog' })
|
||||
|
||||
|
||||
@@ -187,3 +187,28 @@ svg[width='1372'] {
|
||||
.my-6.rounded-lg.p-4.bg-secondary.text-center ul {
|
||||
@apply m-0;
|
||||
}
|
||||
|
||||
/* Table of Contents styling */
|
||||
/* Courtesy Gilles Castel (castel.dev) */
|
||||
.toc {
|
||||
@apply hidden xl:block sticky top-16 h-0 ml-4 text-xs;
|
||||
transform: translateX(calc(-100% - 2em)) translateY(2em);
|
||||
width: calc(-4em - 340px + 50vw);
|
||||
}
|
||||
|
||||
.toc ul {
|
||||
list-style-type: none;
|
||||
@apply mt-0;
|
||||
}
|
||||
|
||||
.toc ul li a {
|
||||
@apply !no-underline !text-muted-foreground;
|
||||
}
|
||||
|
||||
.active-header {
|
||||
@apply brightness-200 font-bold;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
scroll-margin-top: 6rem;
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { Check, File, PenTool, Star, Tag, User, Users } from 'lucide-react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import rehypeRaw from 'rehype-raw'
|
||||
import slugify from 'slugify'
|
||||
|
||||
import Image from './Image'
|
||||
import Link from './Link'
|
||||
@@ -12,12 +13,17 @@ interface TitleProps {
|
||||
level: number
|
||||
className?: string
|
||||
children: React.ReactNode
|
||||
id?: string
|
||||
}
|
||||
|
||||
const Title = ({ level, className, children }: TitleProps) => {
|
||||
const Title = ({ level, className, children, id }: TitleProps) => {
|
||||
const Tag = `h${level}` as keyof JSX.IntrinsicElements
|
||||
|
||||
return <Tag className={className}>{children}</Tag>
|
||||
return (
|
||||
<Tag id={id} className={className}>
|
||||
{children}
|
||||
</Tag>
|
||||
)
|
||||
}
|
||||
|
||||
const Challenge = ({
|
||||
@@ -38,19 +44,14 @@ const Challenge = ({
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUserAvatars = async () => {
|
||||
let usernames
|
||||
if (Array.isArray(solvers)) {
|
||||
usernames = Array.from(new Set([...solvers]))
|
||||
} else {
|
||||
usernames = [solvers]
|
||||
}
|
||||
const usernames = Array.isArray(solvers) ? [...new Set(solvers)] : [solvers]
|
||||
|
||||
try {
|
||||
const avatarResponses = await Promise.all(
|
||||
usernames.map((username) => fetch(`https://api.github.com/users/${username}`))
|
||||
)
|
||||
const avatarData = await Promise.all(
|
||||
avatarResponses.map((response) => response.json())
|
||||
usernames.map(async (username) => {
|
||||
const response = await fetch(`https://api.github.com/users/${username}`)
|
||||
return response.json()
|
||||
})
|
||||
)
|
||||
const avatars = avatarData.reduce((accumulator, current) => {
|
||||
accumulator[current.login] = current.avatar_url
|
||||
@@ -65,51 +66,70 @@ const Challenge = ({
|
||||
fetchUserAvatars()
|
||||
}, [authors, solvers])
|
||||
|
||||
const renderSolvers = (solverList) => (
|
||||
<>
|
||||
<span className="flex items-center space-x-2">
|
||||
<Users size={14} strokeWidth={3} /> <b>solvers</b>:
|
||||
</span>
|
||||
{solverList.map((solver, index) => (
|
||||
<span key={index} className="flex items-center space-x-2">
|
||||
-
|
||||
{userAvatars[solver] ? (
|
||||
<Image
|
||||
src={userAvatars[solver]}
|
||||
alt={`${solver}'s avatar`}
|
||||
className="!m-0 inline-block h-4 w-4 rounded-full align-middle"
|
||||
skeletonClassName="rounded-full"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
) : (
|
||||
<User size={16} strokeWidth={3} />
|
||||
)}
|
||||
{userAvatars[solver] ? (
|
||||
<Link href={`https://github.com/${solver}`}>
|
||||
{usernameMapping[solver] || solver}
|
||||
</Link>
|
||||
) : (
|
||||
<span>{usernameMapping[solver] || solver}</span>
|
||||
)}
|
||||
{index !== solverList.length - 1 && <br />}
|
||||
</span>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
|
||||
const renderAuthors = (authorList) => (
|
||||
<>
|
||||
<span className="flex items-center space-x-2">
|
||||
<PenTool size={14} strokeWidth={3} /> <b>authors</b>:
|
||||
</span>
|
||||
{authorList.map((author, index) => (
|
||||
<span key={index}>
|
||||
- {author}
|
||||
{index !== authorList.length - 1 && <br />}
|
||||
</span>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
|
||||
const titleId = slugify(title, { lower: true })
|
||||
|
||||
return (
|
||||
<div className="my-6 overflow-hidden rounded-lg border border-border">
|
||||
<div className="px-6 py-3">
|
||||
<Title level={level} className="!m-0 text-xl">
|
||||
<Title level={level} className="!m-0 text-xl" id={titleId}>
|
||||
{title}
|
||||
</Title>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<div className="flex flex-col justify-center gap-1 bg-secondary px-6 py-4 text-xs">
|
||||
{solvers && Array.isArray(solvers) ? (
|
||||
<>
|
||||
<span className="flex items-center space-x-2">
|
||||
<Users size={14} strokeWidth={3} /> <b>solvers</b>:
|
||||
</span>
|
||||
{solvers.map((solver, index) => (
|
||||
<span key={index} className="flex items-center space-x-2">
|
||||
-
|
||||
{userAvatars[solver] ? (
|
||||
<Image
|
||||
src={userAvatars[solver]}
|
||||
alt={`${solver}'s avatar`}
|
||||
className="!m-0 inline-block h-4 w-4 rounded-full align-middle"
|
||||
skeletonClassName="rounded-full"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
) : (
|
||||
<User size={16} strokeWidth={3} />
|
||||
)}
|
||||
{userAvatars[solver] ? (
|
||||
<Link href={`https://github.com/${solver}`}>
|
||||
{usernameMapping[solver] || solver}
|
||||
</Link>
|
||||
) : (
|
||||
<span>{usernameMapping[solver] || solver}</span>
|
||||
)}
|
||||
{index !== solvers.length - 1 && <br />}
|
||||
</span>
|
||||
))}
|
||||
</>
|
||||
{Array.isArray(solvers) ? (
|
||||
renderSolvers(solvers)
|
||||
) : (
|
||||
<span className="flex items-center">
|
||||
<span className="flex items-center space-x-2">
|
||||
<User size={14} strokeWidth={3} /> <b>solver</b>
|
||||
:
|
||||
<User size={14} strokeWidth={3} /> <b>solver</b>:
|
||||
</span>
|
||||
<span className="flex items-center space-x-1">
|
||||
{userAvatars[solvers] ? (
|
||||
@@ -135,26 +155,14 @@ const Challenge = ({
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
{authors && Array.isArray(authors) ? (
|
||||
<>
|
||||
<span className="flex items-center space-x-2">
|
||||
<PenTool size={14} strokeWidth={3} /> <b>authors</b>:
|
||||
</span>
|
||||
{authors.map((author, index) => (
|
||||
<span key={index}>
|
||||
- {author}
|
||||
{index !== authors.length - 1 && <br />}
|
||||
</span>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
authors && (
|
||||
<span className="flex items-center space-x-2">
|
||||
<PenTool size={14} strokeWidth={3} /> <b>author</b>: {authors}
|
||||
<br />
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
{Array.isArray(authors)
|
||||
? renderAuthors(authors)
|
||||
: authors && (
|
||||
<span className="flex items-center space-x-2">
|
||||
<PenTool size={14} strokeWidth={3} /> <b>author</b>: {authors}
|
||||
<br />
|
||||
</span>
|
||||
)}
|
||||
{points && (
|
||||
<span className="flex items-center space-x-2">
|
||||
<Star size={14} strokeWidth={3} /> <b>points</b>: {points}
|
||||
|
||||
135
components/TOCInline.tsx
Normal file
135
components/TOCInline.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
'use client'
|
||||
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Toc } from 'pliny/mdx-plugins/remark-toc-headings'
|
||||
|
||||
type TocItem = {
|
||||
value: string
|
||||
url: string
|
||||
depth: number
|
||||
}
|
||||
|
||||
export interface TOCInlineProps {
|
||||
toc: Toc
|
||||
fromHeading?: number
|
||||
toHeading?: number
|
||||
asDisclosure?: boolean
|
||||
exclude?: string | string[]
|
||||
collapse?: boolean
|
||||
ulClassName?: string
|
||||
liClassName?: string
|
||||
}
|
||||
|
||||
export interface NestedTocItem extends TocItem {
|
||||
children?: NestedTocItem[]
|
||||
}
|
||||
|
||||
const createNestedList = (items: TocItem[]): NestedTocItem[] => {
|
||||
const nestedList: NestedTocItem[] = []
|
||||
const stack: NestedTocItem[] = []
|
||||
|
||||
items.forEach((item) => {
|
||||
const newItem: NestedTocItem = { ...item }
|
||||
while (stack.length > 0 && stack[stack.length - 1].depth >= newItem.depth) {
|
||||
stack.pop()
|
||||
}
|
||||
const parent = stack.length > 0 ? stack[stack.length - 1] : null
|
||||
if (parent) {
|
||||
parent.children = parent.children || []
|
||||
parent.children.push(newItem)
|
||||
} else {
|
||||
nestedList.push(newItem)
|
||||
}
|
||||
stack.push(newItem)
|
||||
})
|
||||
|
||||
return nestedList
|
||||
}
|
||||
|
||||
const TOCInline = ({
|
||||
toc,
|
||||
fromHeading = 1,
|
||||
toHeading = 6,
|
||||
asDisclosure = false,
|
||||
exclude = '',
|
||||
collapse = false,
|
||||
ulClassName = '',
|
||||
liClassName = '',
|
||||
}: TOCInlineProps) => {
|
||||
const [activeId, setActiveId] = useState<string | null>(null)
|
||||
|
||||
const re = Array.isArray(exclude)
|
||||
? new RegExp('^(' + exclude.join('|') + ')$', 'i')
|
||||
: new RegExp('^(' + exclude + ')$', 'i')
|
||||
|
||||
const filteredToc = toc.filter(
|
||||
(heading) =>
|
||||
heading.depth >= fromHeading && heading.depth <= toHeading && !re.test(heading.value)
|
||||
)
|
||||
|
||||
const handleScroll = () => {
|
||||
const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6')
|
||||
const scrollPosition = window.pageYOffset || document.documentElement.scrollTop
|
||||
|
||||
let currentId = ''
|
||||
headings.forEach((heading) => {
|
||||
const id = heading.getAttribute('id')
|
||||
if (id) {
|
||||
const rect = heading.getBoundingClientRect()
|
||||
if (rect.top + window.pageYOffset - 300 <= scrollPosition) {
|
||||
currentId = id
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
setActiveId(currentId)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const createList = (items: NestedTocItem[] | undefined) => {
|
||||
if (!items || items.length === 0) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<ul className={ulClassName}>
|
||||
{items.map((item, index) => (
|
||||
<li key={index} className={liClassName}>
|
||||
<a
|
||||
href={item.url}
|
||||
className={item.url === `#${activeId}` ? 'active-header' : ''}
|
||||
>
|
||||
{item.value}
|
||||
</a>
|
||||
{createList(item.children)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
const nestedList = createNestedList(filteredToc)
|
||||
|
||||
return (
|
||||
<>
|
||||
{!asDisclosure && (
|
||||
<div className="ml-6 pb-2 pt-2 text-base font-bold">Table of Contents</div>
|
||||
)}
|
||||
{asDisclosure ? (
|
||||
<details open={!collapse}>
|
||||
<summary className="ml-6 pb-2 pt-2 text-xl font-bold">Table of Contents</summary>
|
||||
<div className="ml-6">{createList(nestedList)}</div>
|
||||
</details>
|
||||
) : (
|
||||
createList(nestedList)
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TOCInline
|
||||
@@ -10,12 +10,18 @@ images: ['/static/images/actf-2023/banner.webp']
|
||||
layout: PostSimple
|
||||
---
|
||||
|
||||
### Intro
|
||||
## Intro
|
||||
|
||||
High school CTF team [View Source](https://ctftime.org/team/175828) and I participated in [AmateursCTF 2023](https://ctftime.org/event/1983), placing 2nd both overall and in the student division. Although there were over 64 challenges to tackle throughout the four-day submission period, I personally only put emphasis on the OSINT and algorithm categories. Within these categories lay an interesting challenge: the `gcd-query` series, which I solved with an implementation of a very special algorithm. This was my process (paired alongside a lengthy analogy)!
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## gcd-query-v1
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="gcd-query-v1"
|
||||
authors="skittles1412"
|
||||
@@ -233,6 +239,12 @@ We've solved `gcd-query-v1`!
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## gcd-query-v2
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="gcd-query-v2"
|
||||
authors="hellopir"
|
||||
@@ -310,7 +322,7 @@ We've managed to solve the entire challenge with only 16 queries!
|
||||
|
||||
---
|
||||
|
||||
### Afterword
|
||||
## Afterword
|
||||
|
||||
Thanks to everyone from les amateurs for hosting this CTF! I had a lot of fun solving these challenges and I hope to see more from you guys in the future. I'd also like to credit Quasar, [SuperBeetleGamer](https://www.cryptohack.org/user/SuperBeetleGamer/), and [flocto](https://ctftime.org/user/121085) for helping me wrap my head around CRT in general throughout the process of writing this (because I almost always learn along the way). I hope you learned something like I did!
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ images: ['/static/images/byuctf-2022/banner.webp']
|
||||
layout: PostSimple
|
||||
---
|
||||
|
||||
### Intro
|
||||
## Intro
|
||||
|
||||
So, we played BYUCTF 2022. There were **9 OSINT challenges**. 9. It was absolutely party-time for a CTF player w/ OSINT-emphasis like me, and a tragedy for people who dislike the inherently guessy nature behind the genre. Our team managed to solve them all, so here was our (albeit flawed) thought process behind it.
|
||||
|
||||
@@ -33,6 +33,12 @@ Thank you, and enjoy.
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## I don't dream about noodles, dad
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="I don't dream about noodles, dad"
|
||||
solvers="jktrn"
|
||||
@@ -56,6 +62,12 @@ Since the tribute is for Jason Turner, we can assume the signature is below his
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Oh The Vanity
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Oh The Vanity"
|
||||
solvers="sahuang"
|
||||
@@ -77,6 +89,12 @@ The [Vanity URL on darkreading.com](https://www.darkreading.com/cloud/vanity-url
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## B0uld3r1ng
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="B0uld3r1ng"
|
||||
solvers={['sahuang', 'jktrn', 'Battlemonger']}
|
||||
@@ -124,6 +142,12 @@ The flag is `byuctf{ju5t_5end_1t_br0_v8bLDrg}`.
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Squatter's Rights
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Squatter's Rights"
|
||||
solvers={['jktrn', 'sahuang']}
|
||||
@@ -186,6 +210,12 @@ Apparently for whatever stupid, scatter-brained, vapid, moronic reason this "Fro
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Okta? More like OhNah
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Okta? More like OhNah"
|
||||
solvers={['Battlemonger', 'jktrn']}
|
||||
@@ -216,6 +246,12 @@ It would have been much easier with this information... love you, John Hammond.
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Murder Mystery
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Murder Mystery"
|
||||
solvers={['Battlemonger', 'jktrn']}
|
||||
@@ -244,6 +280,12 @@ Removing the dates and names as the description specifies, the flag is `byuctf{m
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Buckeye Billy Birthday
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Buckeye Billy Birthday"
|
||||
solvers={['Battlemonger', 'sahuang', 'jktrn']}
|
||||
@@ -277,6 +319,12 @@ The flag is `byuctf{graeters}`. This was a guessy challenge, so don't feel dumb.
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Buckeye Billy Blabbin'
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Buckeye Billy Blabbin'"
|
||||
solvers={['Battlemonger', 'jktrn']}
|
||||
@@ -371,6 +419,12 @@ The flag is `byuctf{t@lk_0sinty_t0_m3}`. Also an extremely guessy challenge. Scr
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## 43
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="43"
|
||||
solvers={['Battlemonger', 'jktrn', 'neil-vs']}
|
||||
|
||||
@@ -10,7 +10,7 @@ images: ['/static/images/dhhutc-2022/banner.webp']
|
||||
layout: PostSimple
|
||||
---
|
||||
|
||||
### Intro
|
||||
## Intro
|
||||
|
||||
This challenge was part of the Deloitte Hackazon Hacky Holidays "Unlock the City" 2022 CTF (yeah, what a name!). Labeled under the `#ppc` category, which apparently stands for "professional programming challenge", it was the final challenge under the "District 1" segment of the CTF and categorized under the Hard difficulty.
|
||||
|
||||
@@ -18,6 +18,12 @@ This was the first CTF problem which didn't just challenge my ability to critica
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Port Authority
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Port Authority"
|
||||
authors={['Luuk Hofman', 'Diederik Bakker']}
|
||||
@@ -121,6 +127,12 @@ Let's finally get to solving the challenge.
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
### Level 1
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Level 1"
|
||||
solvers={['sahuang', 'blueset']}
|
||||
@@ -222,6 +234,12 @@ We've succesfully completed Level 1!
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
### Level 2
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Level 2"
|
||||
solvers={['sahuang', 'blueset']}
|
||||
@@ -274,6 +292,12 @@ Although we've solved level 2 manually, I have a gut feeling the next few ones w
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
### Level 3
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Level 3"
|
||||
solvers="sahuang"
|
||||
@@ -327,6 +351,12 @@ We've managed to stabilize the playing field for a manual solve! Let's flag the
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
### Level 4
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Level 4"
|
||||
solvers="sahuang"
|
||||
@@ -349,6 +379,12 @@ This means I can flag this level without needing to code at all!:
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
### Level 5
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Level 5"
|
||||
solvers={['jktrn', 'sahuang']}
|
||||
@@ -516,7 +552,7 @@ Here is the final script:
|
||||
|
||||
---
|
||||
|
||||
### Afterword
|
||||
## Afterword
|
||||
|
||||
If you made it to this point of the writeup, I want to sincerely thank you for reading. This writeup genuinely took longer to create than it took to solve the challenge (about 30 hours across two weeks), as I had to recreate, record, crop, and optimize every aspect of the solve. I had to create my own multi-hundred-line plugins to implement custom code blocks specifically for this writeup. Everything from the line numbers in highlighted diffs of code to the diagrams were hand-done, as this is my passion: to create for people to learn in a concise, aesthetically pleasing manner. This is also an entry for the Hacky Holidays writeup competition, so wish me luck! 🤞
|
||||
|
||||
|
||||
@@ -12,12 +12,18 @@ layout: PostSimple
|
||||
|
||||

|
||||
|
||||
### Intro
|
||||
## Intro
|
||||
|
||||
Recently my team ([Project Sekai](https://sekai.team/)) and I played [idekCTF 2022\*](https://ctftime.org/event/1839) (with an asterisk... because it's 2023), which was an extraordinarily "race against the clock"-esque CTF with a ridiculously large pool of challenges - 58 of them, over a 48-hour runtime. We managed to snag a 1st place finish after countless hours of _not_ touching grass (despite analyzing it throughout this challenge), and I would like to share my personal favorite OSINT challenge of the competition - "NMPZ", an acronym in the [GeoGuessr](https://geoguessr.com/) community for "no **moving**, **panning**, or **zooming**." Although my team hadn't 100% correctly solved the challenge (we inferred part of the flag), here was our thought process tackling it. Enjoy!
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## NMPZ
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="NMPZ"
|
||||
solvers="jktrn"
|
||||
@@ -389,7 +395,7 @@ Here’s the solution: simply Google “Elektro Weißensteiner” and you’ll f
|
||||
|
||||
---
|
||||
|
||||
## Pit Stop
|
||||
#### Pit Stop
|
||||
|
||||
We’ve now come to a completely arbitrary stopping point—from here on out, each `.png` will become exponentially harder... so let’s just recap what we’ve gotten so far. Note that incorrect countries will be _italicized_:
|
||||
|
||||
@@ -609,7 +615,7 @@ Here is a final table of all the countries (and what I guessed incorrectly):
|
||||
| [16.png](#16-png) | <CountryFlag country="al" /> [Albania](https://en.wikipedia.org/wiki/Albania) | 2,829,741 ([2021](https://en.wikipedia.org/wiki/Demographics_of_Albania)) | `a` | |
|
||||
| [17.png](#17-png) | <CountryFlag country="ru" /> [Russia](https://en.wikipedia.org/wiki/Russia) | 146,980,061 ([2022](https://en.wikipedia.org/wiki/Demographics_of_Russia)) | `R` | |
|
||||
|
||||
## Resources
|
||||
### Resources
|
||||
|
||||
Here are some of the websites I used throughout the challenge-solving process:
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ Since I'll of course be redacting me and my family's faces, at least these cute
|
||||
|
||||
---
|
||||
|
||||
### Day 0: Blunder Dominos
|
||||
### Day 0: Arrival at KIX
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -10,12 +10,18 @@ images: ['/static/images/mhsctf-2023/banner.png']
|
||||
layout: PostSimple
|
||||
---
|
||||
|
||||
### Intro
|
||||
## Intro
|
||||
|
||||
I was recently invited by the academic team "DN" (the name, surprisingly, has no inappropriate connotation) to compete in Mentor High School's second CTF iteration, [MHSCTF 2023](https://ctftime.org/event/1861). Although the competition ran for 15 days, we maxed out their 11 challenges in **just under 16 hours** (ignoring solve resets) and managed to take first place. This writeup was part of the verification process, which came with prize-receiving — enjoy!
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Matchmaker
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Matchmaker"
|
||||
solvers={['flocto', 'jktrn']}
|
||||
@@ -81,12 +87,20 @@ We can do a bit of analysis on what we've received so far.
|
||||
|
||||
<div className="flex">
|
||||
<div className="flex-1">
|
||||
The first line, `86 60 67...`, can be translated into: - Student 0 -> Student 1 = 86 -
|
||||
Student 0 -> Student 2 = 60 - Student 0 -> Student 3 = 67
|
||||
The first line, `86 60 67...`, can be translated into:
|
||||
<ul>
|
||||
<li>Student 0 -> Student 1 = 86</li>
|
||||
<li>Student 0 -> Student 2 = 60</li>
|
||||
<li>Student 0 -> Student 3 = 67</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
Let's do the same thing for the second line, `76 0 74...`: - Student 1 -> Student 0 = 76
|
||||
- Student 1 -> Student 2 = 0 - Student 1 -> Student 3 = 74
|
||||
Let's do the same thing for the second line, `76 0 74...`:
|
||||
<ul>
|
||||
<li>Student 1 -> Student 0 = 76</li>
|
||||
<li>Student 1 -> Student 2 = 0</li>
|
||||
<li>Student 1 -> Student 3 = 74</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -250,7 +264,7 @@ Like any other reasonable person would do if they were stuck, I asked ChatGPT-4
|
||||
<div className="bg-green-700/25 float-right rounded p-4 text-sm relative max-h-[500px] overflow-auto">
|
||||
<p className="m-0">
|
||||
|
||||
<p className="mt-0">
|
||||
<p className="-mt-4">
|
||||
Alright! Let's start with a simple analogy before diving into the specifics of the primal-dual
|
||||
method:
|
||||
</p>
|
||||
@@ -448,7 +462,7 @@ Running the script:
|
||||
|
||||
We've successfully performed a maximum weight matching utilizing the blossom algorithm and the primal-dual method!
|
||||
|
||||
### Afterword
|
||||
## Afterword
|
||||
|
||||
Wow, this challenge was definitely a rabbit hole. Even though the author never actually intended for us to go this deep into the math behind it (and for me to skip out on my Calculus classes to learn graph theory and discrete math), I really don't like ignoring concepts (or in this case, a wrapper function) simply because their prerequisite knowledge floors are either too high or too intimidating. Obviously I was a lost sheep when I was initially researching the blossom algorithm (as this is my first algo challenge, ever), but I just love the feeling when you tear through all the layers abstractions and finally get to the juicy, low-level bits.
|
||||
|
||||
|
||||
@@ -19,7 +19,13 @@ This is a series of selected challenges from the [picoCTF 2022](https://picoctf.
|
||||
|
||||
---
|
||||
|
||||
### Binary Exploitation
|
||||
## Binary Exploitation
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
### basic-file-exploit
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="basic-file-exploit"
|
||||
@@ -67,6 +73,12 @@ After we write some data with the command `1`, We should be pressing the command
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
### CVE-XXXX-XXXX
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="CVE-XXXX-XXXX"
|
||||
authors="Mubarak Mikail"
|
||||
@@ -84,6 +96,12 @@ This is a really trivial challenge. You can actually google "first recorded remo
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
### ropfu
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="ropfu"
|
||||
authors={['Sanjay C.', 'Lt. "Syreal" Jones']}
|
||||
@@ -130,6 +148,12 @@ I know the way of ROP-fu, old man. Your shell has been snatched.
|
||||
|
||||
## Cryptography
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
### basic-mod1
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="basic-mod1"
|
||||
authors="Will Hong"
|
||||
@@ -162,6 +186,12 @@ Running the scripts:
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
### basic-mod2
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="basic-mod2"
|
||||
authors="Will Hong"
|
||||
@@ -189,6 +219,12 @@ Running the script:
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
### credstuff
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="credstuff"
|
||||
authors={['Will Hong', "Lt. 'Syreal' Jones"]}
|
||||
@@ -226,6 +262,12 @@ On line 378 it looks like there's a flag obfuscated with shift cipher. Let's bru
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
### morse-code
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="morse-code"
|
||||
authors="Will Hong"
|
||||
@@ -264,6 +306,12 @@ The program outputs `WH47 H47H 90D W20U9H7`. Following the conversion instructio
|
||||
|
||||
## Forensics
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
### Enhance!
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Enhance!"
|
||||
authors="Lt. 'Syreal' Jones"
|
||||
|
||||
@@ -14,12 +14,18 @@ images: ['/static/images/picoctf-2022/buffer-overflow/banner.webp']
|
||||
layout: PostSimple
|
||||
---
|
||||
|
||||
### Intro
|
||||
## Intro
|
||||
|
||||
This is a writeup for the buffer overflow series during the **picoCTF 2022** competition. This was arguably my favorite set of challenges, as beforehand I'd never stepped into the realm of binary exploitation/pwn. I learned a lot from this, so I highly recommend solving it by yourself before referencing this document. Cheers!
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Buffer overflow 0
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Buffer overflow 0"
|
||||
authors={['Alex Fulton', 'Palash Oswal']}
|
||||
@@ -72,7 +78,7 @@ Let's check out our source code:
|
||||
|
||||
In the `vuln()` function, we see that once again, the `gets()` function is being used. However, instead of triggering a segmentation fault like Buffer overflow 0, we will instead utilize its vulnerability to write our own addresses onto the stack, changing the return address to `win()` instead.
|
||||
|
||||
### I: Explaining the Stack 💬
|
||||
### I: Explaining the Stack
|
||||
|
||||
Before we get into the code, we need to figure out how to write our own addresses to the stack. Let's start with a visual:
|
||||
|
||||
@@ -86,7 +92,7 @@ We can "smash the stack" by exploiting the `gets()` function. If we pass in a la
|
||||
|
||||
If we are deliberate of the characters we pass into `gets()`, we will be able to insert a new address to overwrite the return address to `win()`. Let's try!
|
||||
|
||||
### II: Smashing the Stack 🔨
|
||||
### II: Smashing the Stack
|
||||
|
||||
To start, we first need to figure out our "offset". The offset is the distance, in characters, between the beginning of the buffer and the position of the `$eip`. This can be visualized with the `gdb-gef` utility by setting a breakpoint (a place to pause the runtime) in the `main()` function:
|
||||
|
||||
@@ -98,7 +104,7 @@ Analyzing this breakpoint, if we look at the arrow on the assembly code, we can
|
||||
|
||||
Look what happened: our program threw a SIGSEGV (segmentation) fault, as it is trying to reference the address `0x41414141`, which doesn't exist! This is because our `$eip` was overwritten by all our `A`s (`0x41` in hex = `A` in ASCII).
|
||||
|
||||
### III: Finessing the Stack 🛠️
|
||||
### III: Finessing the Stack
|
||||
|
||||
Although we've managed to smash the stack, we still don't know the offset (**how many** `A`s we need to pass in order to reach the `$eip`). To solve this problem, we can use the pwntools `cyclic` command, which creates a string with a recognizable cycling pattern for it to identify:
|
||||
|
||||
@@ -131,7 +137,7 @@ Let's try running the script on the server:
|
||||
|
||||
We have completed our first `ret2win` buffer overflow on a x32 binary! Yet, this is just the beginning. How about we spice things up a little bit?
|
||||
|
||||
### IV: Automating the Stack 🔧
|
||||
### IV: Automating the Stack
|
||||
|
||||
Although the concept of buffer overflows can seem daunting to newcomers, experienced pwners will often find these sorts of challenges trivial, and don't want to spend the effort manually finding offsets and addresses just to send the same type of payload. This is where our best friend comes in: **pwntools** helper functions and automation! Let's start with the first part - the `$eip` offset for x32 binaries.
|
||||
|
||||
@@ -168,6 +174,12 @@ We've successfully automated a solve on a simple x32 buffer overflow!
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Buffer overflow 2
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Buffer overflow 2"
|
||||
authors={['Sanjay C.', 'Palash Oswal']}
|
||||
@@ -197,7 +209,7 @@ Looking at the `win()` function, we can see that two arguments are required that
|
||||
|
||||
The goal is simple: call `win(0xCAFEF00D, 0xF00DF00D)`! We'll be doing it the hard way (for a learning experience), in addition to a more advanced easy way. Let's get started.
|
||||
|
||||
### I: The Hard Way 🐢
|
||||
### I: The Hard Way
|
||||
|
||||
We can apply a lot from what we learned in Buffer overflow 1. The first thing we should do is find the offset, which requires no hassle with pwntools helpers! Although we'll get actual number here, I won't include it in the final script for the sake of not leaving out any steps. Simply segfault the process with a cyclic string, read the core dump's fault address (`$eip`) and throw it into `cyclic_find()`:
|
||||
|
||||
@@ -215,7 +227,7 @@ Let's run it on the remote server:
|
||||
|
||||
<CodeBlock src="picoctf-2022/buffer-overflow/bo2-run" rawHTML terminal />
|
||||
|
||||
### II: The Easy Way 🐇
|
||||
### II: The Easy Way
|
||||
|
||||
But... what if you wanted to be an even **more** lazy pwner? Well, you're in luck, because I present to you: the **[pwntools ROP object](https://docs.pwntools.com/en/stable/rop/rop.html)**! By throwing our elf object into `ROP()` it transforms, and we can use it to automatically call functions and build chains! Here it is in action:
|
||||
|
||||
@@ -229,6 +241,12 @@ We've successfully called a function with arguments through buffer overflow!
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Buffer overflow 3
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Buffer overflow 3"
|
||||
authors={['Sanjay C.', 'Palash Oswal']}
|
||||
@@ -246,7 +264,7 @@ We've successfully called a function with arguments through buffer overflow!
|
||||
|
||||
<CodeBlock src="picoctf-2022/buffer-overflow/bo3-checksec" rawHTML terminal />
|
||||
|
||||
### I: Finding the Canary 🐦
|
||||
### I: Finding the Canary
|
||||
|
||||
So, Dr. Oswal apparently implemented a [stack canary](https://www.sans.org/blog/stack-canaries-gingerly-sidestepping-the-cage/), which is just a **dynamic value** appended to binaries during compilation. It helps detect and mitigate stack smashing attacks, and programs can terminate if they detect the canary being overwritten. Yet, `checksec` didn't find a canary. That's a bit suspicious... but let's check out our source code first:
|
||||
|
||||
@@ -266,7 +284,7 @@ However, if we theoretically overwrite the canary with a single correct byte, `m
|
||||
|
||||

|
||||
|
||||
### II: Bypassing the Canary 💨
|
||||
### II: Bypassing the Canary
|
||||
|
||||
We can now start writing our script! My plan is to loop through all printable characters for each canary byte, which can be imported from `string`. Let's include that in our pwn boilerplate alongside a simple function that allows us to swap between a local and remote instance:
|
||||
|
||||
|
||||
@@ -10,12 +10,18 @@ authors: ['enscribe']
|
||||
layout: PostSimple
|
||||
---
|
||||
|
||||
### Intro
|
||||
## Intro
|
||||
|
||||
[SekaiCTF 2022](http://2022.ctf.sekai.team/) — my first capture-the-flag which I've had the honor of organizing alongside fellow members of [Project Sekai CTF](https://sekai.team). One of the aspects of its administration was the challenge verification process; as part of it, I've ended up authoring a bunch of forensics-based writeups which I'm really proud of. They're also available on the [GitHub](https://github.com/project-sekai-ctf/sekaictf-2022) repository if you'd like to check out the rest of the challenges — I've simply ported them over here for my fancy website formatting. Enjoy!
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Blind Infection 1
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Blind Infection 1"
|
||||
solvers="jktrn"
|
||||
@@ -95,6 +101,12 @@ Running the script:
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Blind Infection 2
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Blind Infection 2"
|
||||
solvers="jktrn"
|
||||
@@ -187,6 +199,12 @@ Use the `strings` command on `flag.png` to flag the challenge.
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Broken Converter
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Broken Converter"
|
||||
solvers="jktrn"
|
||||
@@ -236,6 +254,12 @@ However, opening the `.ttf` file in programs that sort by ASCII, such as [FontFo
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## flag Mono
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="flag Mono"
|
||||
solvers="jktrn"
|
||||
@@ -275,6 +299,12 @@ Let's test out typing `flag` on [FontDrop!](https://fontdrop.info/) and changing
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Symbolic Needs 1
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Symbolic Needs 1"
|
||||
solvers="jktrn"
|
||||
@@ -328,6 +358,12 @@ Those are easily identifiable as ASCII codes. Convert `72 48 117 53 84 48 110 95
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Symbolic Needs 2
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Symbolic Needs 2"
|
||||
solvers="jktrn"
|
||||
|
||||
@@ -10,7 +10,7 @@ authors: ['enscribe']
|
||||
layout: PostSimple
|
||||
---
|
||||
|
||||
### Intro
|
||||
## Intro
|
||||
|
||||
This blog post details my design process for a beginner-friendly reverse-engineering challenge that I authored this year for [SekaiCTF 2023](https://ctftime.org/event/1923/)——a fully functional, hackable replica of Project Sekai's "gacha" system. Here is a snippet of the challenge, featured in the CTF's first teaser:
|
||||
|
||||
@@ -71,7 +71,7 @@ Learning from feedback in our survey last year, we ([Project SEKAI CTF](https://
|
||||
|
||||
Combined with the fact we occasionally attract players from the Project Sekai community (a community which has absolutely nothing to do with cybersecurity), we decided that it would be best to design a beginner-friendly, eye-candy challenge that would attract a newer playerbase to register for our CTF and try the introductory challenges. This was a perfect opportunity for us to clone an aspect of the game that inspired our entire CTF theme, and to implement it in a way that would be engaging and educational for newer players. A Unity reverse engineering challenge would be the perfect fit, as plenty of Unity game-hacking resources exist online, and compiled Unity bundles are trivial to reverse-engineer and modify.
|
||||
|
||||
### Designing the Challenge: Brainstorming
|
||||
## Designing the Challenge: Brainstorming
|
||||
|
||||
Although "cloning" the game and its functionality might not be too brain-rotting of a process, we still have to design the challenge to be reverse-engineerable. Me and [@sahuang](https://twitter.com/sahuang97) ended up creating a design document which overviewed the challenge's functionality, and the general path needed to take in order to solve it; here's a TL;DR of the key points with some visuals.
|
||||
|
||||
@@ -113,7 +113,7 @@ Here's a verbose `curl` which demonstrates the request and response (of course,
|
||||
|
||||
This was all of the planning and designing that went into the actual challenge structure itself. It's purposefully simple to make the reverse-engineering process as straightforward as possible, and to make the challenge more beginner-friendly. We can now go over how I created the backend server for the challenge.
|
||||
|
||||
### Implementing the Challenge: Backend
|
||||
## Implementing the Challenge: Backend
|
||||
|
||||
Since I'm really comfortable with vanilla TypeScript, I decided that it'd be best to use it to write the backend server (contrary to the typical Python Flask server in CTFs).
|
||||
|
||||
@@ -165,7 +165,7 @@ Beautiful! We finished off the backend by deploying to [Cloudzy](https://cloudzy
|
||||
|
||||
Let's talk about the Unity graphics next.
|
||||
|
||||
### Replicating UI Elements with Figma
|
||||
## Replicating UI Elements with Figma
|
||||
|
||||
I wanted to make the game look as polished and as similar to the real product as possible, so I began by recreating and grabbing all of the assets that the game used in the gacha page. I found a bunch of YouTube videos which provided a nice overview of what the in-game screen looked like:
|
||||
|
||||
@@ -202,7 +202,7 @@ Of course, the [Internet Archive](https://archive.org/download/Fontworks/) has a
|
||||
|
||||
All elements that I were unable to recreate due to complexity (like the character attribute icons, star rarities, banner logos, etc.) I grabbed from online wikis (e.g. [Fandom](https://projectsekai.fandom.com/wiki/Project_SEKAI_Wiki), [Sekaipedia](https://www.sekaipedia.org/wiki/Category:Game_assets)) of the game. With this undoubtedly important step out of the way, let's talk about the Unity game itself.
|
||||
|
||||
### Implementing the Challenge: Unity
|
||||
## Implementing the Challenge: Unity
|
||||
|
||||
Of course, I'm no professional Unity game developer——I've only ever made a few major 2D projects in the past, and I've only touched the engine's 3D components before once (my Unity challenge from last year, [reverse/Perfect Match X-treme](https://github.com/project-sekai-ctf/sekaictf-2022/tree/main/reverse/perfect-match-xtreme/Challenge)). However, I was confident that I would be able to emulate the appearance and functionality of the gacha system with a bit of graphic design knowledge and some janky code.
|
||||
|
||||
@@ -446,7 +446,7 @@ These two scripts were adopted from the [`wmjoers/CameraScaler`](https://github.
|
||||
|
||||
Since we have a lot of context now on how the game is made and the various scripts that we need to exploit/manipulate, we can now trivially reverse-engineer the game and hack in the 4\* character. Let's get started!
|
||||
|
||||
### Writeup
|
||||
## Writeup
|
||||
|
||||
We're initially provided with a `dist/` folder with the following structure:
|
||||
|
||||
@@ -529,7 +529,7 @@ After fixing some minor errors, we can recompile the DLL and save it over the or
|
||||
|
||||
---
|
||||
|
||||
### Afterword
|
||||
## Afterword
|
||||
|
||||
That's about all there is left to cover in terms of the Unity project! Honestly, for a 1\* (baby difficulty) reverse-engineering challenge which could probably be solved in less than 5 minutes by an experienced reverse player, a lot of sweat and tears were put into this entire process——a total of 29 hours (via [WakaTime](https://wakatime.com/)) over the course of 3 weeks were spent designing, programming, and polishing. I'm really proud of the final product that I've created; you can genuinely see the overall growth from last year's challenge. I hope you enjoyed reading about the process as much as I enjoyed making it!
|
||||
|
||||
|
||||
@@ -14,12 +14,18 @@ images: ['/static/images/shctf-2022/banner.webp']
|
||||
layout: PostSimple
|
||||
---
|
||||
|
||||
### Intro
|
||||
## Intro
|
||||
|
||||
So me and a couple of LFGs (looking-for-groups) played [Space Heroes CTF](https://ctftime.org/event/1567/), organized by Florida Tech's [FITSEC](https://ctftime.org/team/65296) cybersecurity team. As one of the first CTFs I've played in over a year, it was an amazing learning experience for me being thrown into the mystical world of binary exploitation/pwn. I've made a couple of writeups for the cooler challenges I've solved; enjoy!
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Guardians of the Galaxy
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Guardians of the Galaxy"
|
||||
authors="GlitchArchetype"
|
||||
@@ -50,6 +56,12 @@ This script will send a UTF-8 encoded format string, with `str(i)` being the ite
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Vader
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Vader"
|
||||
authors="v10l3nt"
|
||||
@@ -122,6 +134,12 @@ This is considered a "simple" challenge for those experienced with the field of
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Warmup to the Dark Side
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Warmup to the Dark Side"
|
||||
authors="v10l3nt"
|
||||
@@ -148,6 +166,12 @@ Let's run this script on the server to see if we can get the flag:
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Rahool's Challenge
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="Rahool's Challenge"
|
||||
authors="excaligator"
|
||||
|
||||
@@ -10,7 +10,7 @@ authors: ['enscribe']
|
||||
layout: PostSimple
|
||||
---
|
||||
|
||||
### Intro
|
||||
## Intro
|
||||
|
||||
Over the last weekend, I played in WolvSec's second CTF iteration with [Project Sekai](https://sekai.team/) — [WolvCTF 2023](https://ctftime.org/event/1866). We placed first in the open division, and throughout the solving process I became intrigued by a specific series of challenges placed under the OSINT category: **WannaFlag**. Telling a story of a supposed ransomware group which had been terrorizing the CTF community for the past several months, these series of challenges offered an opportunity for players to track down this group's means of operation. Ultimately, the goal was to find WannaFlag's kingpin through all possible methods.
|
||||
|
||||
@@ -18,6 +18,12 @@ Project Sekai was the first to blood the entire series. Here was our thought pro
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Wannaflag I: An Introduction
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="WannaFlag I: An Introduction"
|
||||
authors="dree"
|
||||
@@ -85,6 +91,12 @@ A simple [base64 decode](https://www.base64decode.org/) of the exfiltrated strin
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Wannaflag II: Payments
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="WannaFlag II: Payments"
|
||||
authors="dree"
|
||||
@@ -137,6 +149,12 @@ If you're lost, I made a visualization of the transactions which took place, hig
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Wannaflag III: Infiltration
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="WannaFlag III: Infiltration"
|
||||
solvers="jktrn"
|
||||
@@ -178,6 +196,12 @@ We've managed to recover the flag from the first OSINT challenge, but it's actua
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Wannaflag IV: Exfiltration
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="WannaFlag IV: Exfiltration"
|
||||
solvers="jktrn"
|
||||
@@ -276,6 +300,12 @@ The password is `Great_Falls_city_60506`, which we can now use to open the sprea
|
||||
|
||||
---
|
||||
|
||||
<div className="invisible !h-0">
|
||||
|
||||
## Wannaflag V: The Mastermind
|
||||
|
||||
</div>
|
||||
|
||||
<Challenge
|
||||
title="WannaFlag V: The Mastermind"
|
||||
solvers={['Guesslemonger', 'jktrn', 'sahuang', 'Violin']}
|
||||
@@ -378,7 +408,7 @@ A [base64 decode](https://www.base64decode.org/) reveals our final flag:
|
||||
|
||||
---
|
||||
|
||||
### Afterword
|
||||
## Afterword
|
||||
|
||||
This was an extraordinarily well-designed challenge. A lot of OSINT nowadays isn't creative at all, and doesn't employ any sort of "out-of-the-box" thinking. The WannaFlag series, however, was my breath of fresh air — it brought in some really wacky and unique stuff, like the TF2 map/Steam (the Excel password cracking bit was more forensics, but that's just part of the nature of OSINT in general). I hope to see more of these types of challenges in the future. Here is a compiled list of tools that I used throughout the challenge — I hope you find them useful:
|
||||
|
||||
|
||||
@@ -4,11 +4,13 @@ import Image from '@/components/Image'
|
||||
import Link from '@/components/Link'
|
||||
import PageTitle from '@/components/PageTitle'
|
||||
import ScrollTopAndComment from '@/components/ScrollTopAndComment'
|
||||
import TOCInline from '@/components/TOCInline'
|
||||
import Tag from '@/components/Tag'
|
||||
import { Skeleton } from '@/components/shadcn/skeleton'
|
||||
import siteMetadata from '@/data/siteMetadata'
|
||||
import type { Authors, Blog } from 'contentlayer/generated'
|
||||
import NextImage from 'next/image'
|
||||
import { Toc } from 'pliny/mdx-plugins'
|
||||
import { CoreContent } from 'pliny/utils/contentlayer'
|
||||
import { formatDate } from 'pliny/utils/formatDate'
|
||||
import { ReactNode, useEffect, useState } from 'react'
|
||||
@@ -17,11 +19,19 @@ interface LayoutProps {
|
||||
content: CoreContent<Blog>
|
||||
authorDetails: CoreContent<Authors>[]
|
||||
children: ReactNode
|
||||
toc: Toc
|
||||
next?: { path: string; title: string }
|
||||
prev?: { path: string; title: string }
|
||||
}
|
||||
|
||||
export default function PostLayout({ content, authorDetails, next, prev, children }: LayoutProps) {
|
||||
export default function PostLayout({
|
||||
content,
|
||||
authorDetails,
|
||||
toc,
|
||||
next,
|
||||
prev,
|
||||
children,
|
||||
}: LayoutProps) {
|
||||
const { path, slug, tags, date, title, thumbnail } = content
|
||||
const displayThumbnail = thumbnail ? thumbnail : '/static/images/twitter-card.png'
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
@@ -162,7 +172,10 @@ export default function PostLayout({ content, authorDetails, next, prev, childre
|
||||
<div className="grid-rows-[auto_1fr] divide-y divide-muted-foreground pb-8 dark:divide-muted xl:divide-y-0">
|
||||
<div className="divide-y divide-accent-foreground dark:divide-accent xl:col-span-3 xl:row-span-2 xl:pb-0">
|
||||
<div className="prose prose-sm max-w-none pb-8 pt-10 dark:prose-invert">
|
||||
{children}
|
||||
<div className="toc">
|
||||
<TOCInline toc={toc} />
|
||||
</div>
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* {siteMetadata.comments && (
|
||||
|
||||
9
package-lock.json
generated
9
package-lock.json
generated
@@ -53,6 +53,7 @@
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-html": "^16.0.1",
|
||||
"remark-math": "^5.1.1",
|
||||
"slugify": "^1.6.6",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
@@ -15154,6 +15155,14 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/slugify": {
|
||||
"version": "1.6.6",
|
||||
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz",
|
||||
"integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==",
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/snake-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz",
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-html": "^16.0.1",
|
||||
"remark-math": "^5.1.1",
|
||||
"slugify": "^1.6.6",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
|
||||
Reference in New Issue
Block a user