feat: sticky table of contents

This commit is contained in:
jason
2024-04-15 13:00:40 -07:00
parent 6275a84e89
commit 92e71d17d8
20 changed files with 576 additions and 107 deletions

View File

@@ -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>
</>

View File

@@ -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' })

View File

@@ -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;
}

View File

@@ -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,21 +66,12 @@ const Challenge = ({
fetchUserAvatars()
}, [authors, solvers])
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}
</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) ? (
const renderSolvers = (solverList) => (
<>
<span className="flex items-center space-x-2">
<Users size={14} strokeWidth={3} /> <b>solvers</b>:
</span>
{solvers.map((solver, index) => (
{solverList.map((solver, index) => (
<span key={index} className="flex items-center space-x-2">
-&nbsp;
{userAvatars[solver] ? (
@@ -101,15 +93,43 @@ const Challenge = ({
) : (
<span>{usernameMapping[solver] || solver}</span>
)}
{index !== solvers.length - 1 && <br />}
{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" 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">
{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>
:&nbsp;
<User size={14} strokeWidth={3} /> <b>solver</b>:&nbsp;
</span>
<span className="flex items-center space-x-1">
{userAvatars[solvers] ? (
@@ -135,25 +155,13 @@ 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 && (
{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">

135
components/TOCInline.tsx Normal file
View 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

View File

@@ -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!

View File

@@ -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']}

View File

@@ -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! 🤞

View File

@@ -12,12 +12,18 @@ layout: PostSimple
![Banner](/static/images/idekctf-2023/banner.svg)
### 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 @@ Heres the solution: simply Google “Elektro Weißensteiner” and youll f
---
## Pit Stop
#### Pit Stop
Weve now come to a completely arbitrary stopping point—from here on out, each `.png` will become exponentially harder... so lets just recap what weve 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:

View File

@@ -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
![Day 0 Map 1](/static/images/japan-retrospective/day-0-map-1.png)

View File

@@ -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 -&gt; Student 1 = 86 -
Student 0 -&gt; Student 2 = 60 - Student 0 -&gt; Student 3 = 67
The first line, `86 60 67...`, can be translated into:
<ul>
<li>Student 0 -&gt; Student 1 = 86</li>
<li>Student 0 -&gt; Student 2 = 60</li>
<li>Student 0 -&gt; 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 -&gt; Student 0 = 76
- Student 1 -&gt; Student 2 = 0 - Student 1 -&gt; Student 3 = 74
Let's do the same thing for the second line, `76 0 74...`:
<ul>
<li>Student 1 -&gt; Student 0 = 76</li>
<li>Student 1 -&gt; Student 2 = 0</li>
<li>Student 1 -&gt; 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.

View File

@@ -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"

View File

@@ -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
![memcmp2](/static/images/picoctf-2022/buffer-overflow/memcmp2.svg)
### 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:

View File

@@ -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"

View File

@@ -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!

View File

@@ -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"

View File

@@ -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:

View File

@@ -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
View File

@@ -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",

View File

@@ -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",